mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-16 22:03:52 +00:00
Merge branch 'main' into NathanFlurry/pi-bootstrap-fix
This commit is contained in:
commit
7924e11a23
671 changed files with 52836 additions and 28750 deletions
|
|
@ -79,7 +79,7 @@ pub enum Command {
|
|||
Opencode(OpencodeArgs),
|
||||
/// Manage the sandbox-agent background daemon.
|
||||
Daemon(DaemonArgs),
|
||||
/// Install or reinstall an agent without running the server.
|
||||
/// Install or reinstall one agent, or `all` supported agents, without running the server.
|
||||
InstallAgent(InstallAgentArgs),
|
||||
/// Inspect locally discovered credentials.
|
||||
Credentials(CredentialsArgs),
|
||||
|
|
@ -295,7 +295,10 @@ pub struct AcpCloseArgs {
|
|||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct InstallAgentArgs {
|
||||
agent: String,
|
||||
#[arg(required_unless_present = "all", conflicts_with = "all")]
|
||||
agent: Option<String>,
|
||||
#[arg(long, conflicts_with = "agent")]
|
||||
all: bool,
|
||||
#[arg(long, short = 'r')]
|
||||
reinstall: bool,
|
||||
#[arg(long = "agent-version")]
|
||||
|
|
@ -946,24 +949,73 @@ fn load_json_payload(
|
|||
}
|
||||
|
||||
fn install_agent_local(args: &InstallAgentArgs) -> Result<(), CliError> {
|
||||
let agent_id = AgentId::parse(&args.agent)
|
||||
.ok_or_else(|| CliError::Server(format!("unsupported agent: {}", args.agent)))?;
|
||||
if args.all && (args.agent_version.is_some() || args.agent_process_version.is_some()) {
|
||||
return Err(CliError::Server(
|
||||
"--agent-version and --agent-process-version are only supported for single-agent installs"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let agents = resolve_install_agents(args)?;
|
||||
|
||||
let manager = AgentManager::new(default_install_dir())
|
||||
.map_err(|err| CliError::Server(err.to_string()))?;
|
||||
|
||||
let result = manager
|
||||
.install(
|
||||
agent_id,
|
||||
InstallOptions {
|
||||
reinstall: args.reinstall,
|
||||
version: args.agent_version.clone(),
|
||||
agent_process_version: args.agent_process_version.clone(),
|
||||
},
|
||||
)
|
||||
.map_err(|err| CliError::Server(err.to_string()))?;
|
||||
if agents.len() == 1 {
|
||||
let result = manager
|
||||
.install(
|
||||
agents[0],
|
||||
InstallOptions {
|
||||
reinstall: args.reinstall,
|
||||
version: args.agent_version.clone(),
|
||||
agent_process_version: args.agent_process_version.clone(),
|
||||
},
|
||||
)
|
||||
.map_err(|err| CliError::Server(err.to_string()))?;
|
||||
let output = install_result_json(result);
|
||||
return write_stdout_line(&serde_json::to_string_pretty(&output)?);
|
||||
}
|
||||
|
||||
let output = json!({
|
||||
let mut results = Vec::with_capacity(agents.len());
|
||||
for agent_id in agents {
|
||||
let result = manager
|
||||
.install(
|
||||
agent_id,
|
||||
InstallOptions {
|
||||
reinstall: args.reinstall,
|
||||
version: None,
|
||||
agent_process_version: None,
|
||||
},
|
||||
)
|
||||
.map_err(|err| CliError::Server(err.to_string()))?;
|
||||
results.push(json!({
|
||||
"agent": agent_id.as_str(),
|
||||
"result": install_result_json(result),
|
||||
}));
|
||||
}
|
||||
|
||||
write_stdout_line(&serde_json::to_string_pretty(
|
||||
&json!({ "agents": results }),
|
||||
)?)
|
||||
}
|
||||
|
||||
fn resolve_install_agents(args: &InstallAgentArgs) -> Result<Vec<AgentId>, CliError> {
|
||||
if args.all {
|
||||
return Ok(AgentId::all().to_vec());
|
||||
}
|
||||
|
||||
let agent = args
|
||||
.agent
|
||||
.as_deref()
|
||||
.ok_or_else(|| CliError::Server("missing agent: provide <AGENT> or --all".to_string()))?;
|
||||
|
||||
AgentId::parse(agent)
|
||||
.map(|agent_id| vec![agent_id])
|
||||
.ok_or_else(|| CliError::Server(format!("unsupported agent: {agent}")))
|
||||
}
|
||||
|
||||
fn install_result_json(result: sandbox_agent_agent_management::agents::InstallResult) -> Value {
|
||||
json!({
|
||||
"alreadyInstalled": result.already_installed,
|
||||
"artifacts": result.artifacts.into_iter().map(|artifact| json!({
|
||||
"kind": format!("{:?}", artifact.kind),
|
||||
|
|
@ -971,9 +1023,7 @@ fn install_agent_local(args: &InstallAgentArgs) -> Result<(), CliError> {
|
|||
"source": format!("{:?}", artifact.source),
|
||||
"version": artifact.version,
|
||||
})).collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
write_stdout_line(&serde_json::to_string_pretty(&output)?)
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
|
@ -1416,6 +1466,60 @@ fn write_stderr_line(text: &str) -> Result<(), CliError> {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn resolve_install_agents_expands_all() {
|
||||
assert_eq!(
|
||||
resolve_install_agents(&InstallAgentArgs {
|
||||
agent: None,
|
||||
all: true,
|
||||
reinstall: false,
|
||||
agent_version: None,
|
||||
agent_process_version: None,
|
||||
})
|
||||
.unwrap(),
|
||||
AgentId::all().to_vec()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_install_agents_supports_single_agent() {
|
||||
assert_eq!(
|
||||
resolve_install_agents(&InstallAgentArgs {
|
||||
agent: Some("codex".to_string()),
|
||||
all: false,
|
||||
reinstall: false,
|
||||
agent_version: None,
|
||||
agent_process_version: None,
|
||||
})
|
||||
.unwrap(),
|
||||
vec![AgentId::Codex]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_install_agents_rejects_unknown_agent() {
|
||||
assert!(resolve_install_agents(&InstallAgentArgs {
|
||||
agent: Some("nope".to_string()),
|
||||
all: false,
|
||||
reinstall: false,
|
||||
agent_version: None,
|
||||
agent_process_version: None,
|
||||
})
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_install_agents_rejects_positional_all() {
|
||||
assert!(resolve_install_agents(&InstallAgentArgs {
|
||||
agent: Some("all".to_string()),
|
||||
all: false,
|
||||
reinstall: false,
|
||||
agent_version: None,
|
||||
agent_process_version: None,
|
||||
})
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_last_event_id_header_sets_header_when_provided() {
|
||||
let client = HttpClient::builder().build().expect("build client");
|
||||
|
|
|
|||
|
|
@ -21,10 +21,7 @@ describe("OpenCode-compatible Event Streaming", () => {
|
|||
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
}
|
||||
|
||||
async function initSessionViaHttp(
|
||||
sessionId: string,
|
||||
body: Record<string, unknown>
|
||||
): Promise<void> {
|
||||
async function initSessionViaHttp(sessionId: string, body: Record<string, unknown>): Promise<void> {
|
||||
const response = await fetch(`${handle.baseUrl}/opencode/session/${sessionId}/init`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
|
@ -113,10 +110,7 @@ describe("OpenCode-compatible Event Streaming", () => {
|
|||
for await (const event of (eventStream as any).stream) {
|
||||
events.push(event);
|
||||
// Look for message part updates or completion
|
||||
if (
|
||||
event.type === "message.part.updated" ||
|
||||
event.type === "session.idle"
|
||||
) {
|
||||
if (event.type === "message.part.updated" || event.type === "session.idle") {
|
||||
if (events.length >= 3) {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
|
|
@ -175,10 +169,7 @@ describe("OpenCode-compatible Event Streaming", () => {
|
|||
const statuses: string[] = [];
|
||||
|
||||
const collectIdle = new Promise<void>((resolve, reject) => {
|
||||
const timeout = setTimeout(
|
||||
() => reject(new Error("Timed out waiting for session.idle")),
|
||||
15_000
|
||||
);
|
||||
const timeout = setTimeout(() => reject(new Error("Timed out waiting for session.idle")), 15_000);
|
||||
(async () => {
|
||||
try {
|
||||
for await (const event of (eventStream as any).stream) {
|
||||
|
|
@ -223,10 +214,7 @@ describe("OpenCode-compatible Event Streaming", () => {
|
|||
let busySnapshot: string | undefined;
|
||||
|
||||
const waitForIdle = new Promise<void>((resolve, reject) => {
|
||||
const timeout = setTimeout(
|
||||
() => reject(new Error("Timed out waiting for busy status snapshot + session.idle")),
|
||||
15_000
|
||||
);
|
||||
const timeout = setTimeout(() => reject(new Error("Timed out waiting for busy status snapshot + session.idle")), 15_000);
|
||||
(async () => {
|
||||
try {
|
||||
for await (const event of (eventStream as any).stream) {
|
||||
|
|
@ -276,10 +264,7 @@ describe("OpenCode-compatible Event Streaming", () => {
|
|||
const idles: any[] = [];
|
||||
|
||||
const collectErrorAndIdle = new Promise<void>((resolve, reject) => {
|
||||
const timeout = setTimeout(
|
||||
() => reject(new Error("Timed out waiting for session.error + session.idle")),
|
||||
15_000
|
||||
);
|
||||
const timeout = setTimeout(() => reject(new Error("Timed out waiting for session.error + session.idle")), 15_000);
|
||||
(async () => {
|
||||
try {
|
||||
for await (const event of (eventStream as any).stream) {
|
||||
|
|
@ -428,9 +413,7 @@ describe("OpenCode-compatible Event Streaming", () => {
|
|||
expect(idleEvents.length).toBe(1);
|
||||
|
||||
// All tool parts should have been emitted before idle
|
||||
const toolParts = allEvents.filter(
|
||||
(e) => e.type === "message.part.updated" && e.properties?.part?.type === "tool"
|
||||
);
|
||||
const toolParts = allEvents.filter((e) => e.type === "message.part.updated" && e.properties?.part?.type === "tool");
|
||||
expect(toolParts.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
|
|
@ -459,12 +442,7 @@ describe("OpenCode-compatible Event Streaming", () => {
|
|||
if (!targetMessageId && partType === "tool" && typeof messageId === "string") {
|
||||
targetMessageId = messageId;
|
||||
}
|
||||
if (
|
||||
targetMessageId &&
|
||||
messageId === targetMessageId &&
|
||||
typeof partId === "string" &&
|
||||
!seenPartIds.includes(partId)
|
||||
) {
|
||||
if (targetMessageId && messageId === targetMessageId && typeof partId === "string" && !seenPartIds.includes(partId)) {
|
||||
seenPartIds.push(partId);
|
||||
}
|
||||
}
|
||||
|
|
@ -497,17 +475,12 @@ describe("OpenCode-compatible Event Streaming", () => {
|
|||
expect(targetMessageId).toBeTruthy();
|
||||
expect(seenPartIds.length).toBeGreaterThan(0);
|
||||
|
||||
const response = await fetch(
|
||||
`${handle.baseUrl}/opencode/session/${sessionId}/message/${targetMessageId}`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${handle.token}` },
|
||||
}
|
||||
);
|
||||
const response = await fetch(`${handle.baseUrl}/opencode/session/${sessionId}/message/${targetMessageId}`, {
|
||||
headers: { Authorization: `Bearer ${handle.token}` },
|
||||
});
|
||||
expect(response.ok).toBe(true);
|
||||
const message = (await response.json()) as any;
|
||||
const returnedPartIds = (message?.parts ?? [])
|
||||
.map((part: any) => part?.id)
|
||||
.filter((id: any) => typeof id === "string");
|
||||
const returnedPartIds = (message?.parts ?? []).map((part: any) => part?.id).filter((id: any) => typeof id === "string");
|
||||
|
||||
const expectedSet = new Set(seenPartIds);
|
||||
const returnedFiltered = returnedPartIds.filter((id: string) => expectedSet.has(id));
|
||||
|
|
|
|||
|
|
@ -33,10 +33,7 @@ function findBinary(): string | null {
|
|||
}
|
||||
|
||||
// Check cargo build outputs (relative to tests/opencode-compat/helpers)
|
||||
const cargoPaths = [
|
||||
resolve(__dirname, "../../../../../../target/debug/sandbox-agent"),
|
||||
resolve(__dirname, "../../../../../../target/release/sandbox-agent"),
|
||||
];
|
||||
const cargoPaths = [resolve(__dirname, "../../../../../../target/debug/sandbox-agent"), resolve(__dirname, "../../../../../../target/release/sandbox-agent")];
|
||||
|
||||
for (const p of cargoPaths) {
|
||||
if (existsSync(p)) {
|
||||
|
|
@ -65,12 +62,7 @@ async function getFreePort(host: string): Promise<number> {
|
|||
/**
|
||||
* Wait for the server to become healthy
|
||||
*/
|
||||
async function waitForHealth(
|
||||
baseUrl: string,
|
||||
token: string,
|
||||
timeoutMs: number,
|
||||
child: ChildProcess
|
||||
): Promise<void> {
|
||||
async function waitForHealth(baseUrl: string, token: string, timeoutMs: number, child: ChildProcess): Promise<void> {
|
||||
const start = Date.now();
|
||||
let lastError: string | undefined;
|
||||
|
||||
|
|
@ -130,9 +122,7 @@ export interface SpawnOptions {
|
|||
export async function spawnSandboxAgent(options: SpawnOptions = {}): Promise<SandboxAgentHandle> {
|
||||
const binaryPath = findBinary();
|
||||
if (!binaryPath) {
|
||||
throw new Error(
|
||||
"sandbox-agent binary not found. Run 'cargo build -p sandbox-agent' first or set SANDBOX_AGENT_BIN."
|
||||
);
|
||||
throw new Error("sandbox-agent binary not found. Run 'cargo build -p sandbox-agent' first or set SANDBOX_AGENT_BIN.");
|
||||
}
|
||||
|
||||
const host = options.host ?? "127.0.0.1";
|
||||
|
|
@ -222,7 +212,7 @@ export async function buildSandboxAgent(): Promise<void> {
|
|||
|
||||
console.log("Building sandbox-agent...");
|
||||
const projectRoot = resolve(__dirname, "../../../../../..");
|
||||
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const proc = spawn("cargo", ["build", "-p", "sandbox-agent"], {
|
||||
cwd: projectRoot,
|
||||
|
|
|
|||
|
|
@ -60,8 +60,6 @@ describe("OpenCode-compatible Model API", () => {
|
|||
expect(providerIds.has("claude")).toBe(true);
|
||||
expect(providerIds.has("codex")).toBe(true);
|
||||
expect(providerIds.has("pi")).toBe(true);
|
||||
expect(
|
||||
providerIds.has("opencode") || Array.from(providerIds).some((id) => id.startsWith("opencode:"))
|
||||
).toBe(true);
|
||||
expect(providerIds.has("opencode") || Array.from(providerIds).some((id) => id.startsWith("opencode:"))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll, beforeEach, afterEach } from "vitest";
|
||||
import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk/v1";
|
||||
import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk";
|
||||
import { spawnSandboxAgent, buildSandboxAgent, type SandboxAgentHandle } from "./helpers/spawn";
|
||||
|
||||
describe("OpenCode-compatible Permission API", () => {
|
||||
|
|
@ -53,11 +53,7 @@ describe("OpenCode-compatible Permission API", () => {
|
|||
throw new Error("Timed out waiting for permission request");
|
||||
}
|
||||
|
||||
async function waitForCondition(
|
||||
check: () => boolean | Promise<boolean>,
|
||||
timeoutMs = 10_000,
|
||||
intervalMs = 100,
|
||||
) {
|
||||
async function waitForCondition(check: () => boolean | Promise<boolean>, timeoutMs = 10_000, intervalMs = 100) {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
if (await check()) {
|
||||
|
|
@ -68,11 +64,7 @@ describe("OpenCode-compatible Permission API", () => {
|
|||
throw new Error("Timed out waiting for condition");
|
||||
}
|
||||
|
||||
async function waitForValue<T>(
|
||||
getValue: () => T | undefined | Promise<T | undefined>,
|
||||
timeoutMs = 10_000,
|
||||
intervalMs = 100,
|
||||
): Promise<T> {
|
||||
async function waitForValue<T>(getValue: () => T | undefined | Promise<T | undefined>, timeoutMs = 10_000, intervalMs = 100): Promise<T> {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
const value = await getValue();
|
||||
|
|
@ -175,13 +167,7 @@ describe("OpenCode-compatible Permission API", () => {
|
|||
});
|
||||
expect(firstReply.error).toBeUndefined();
|
||||
|
||||
await waitForCondition(() =>
|
||||
repliedEvents.some(
|
||||
(event) =>
|
||||
event?.properties?.requestID === firstRequestId &&
|
||||
event?.properties?.reply === "always",
|
||||
),
|
||||
);
|
||||
await waitForCondition(() => repliedEvents.some((event) => event?.properties?.requestID === firstRequestId && event?.properties?.reply === "always"));
|
||||
|
||||
await client.session.prompt({
|
||||
sessionID: sessionId,
|
||||
|
|
@ -190,11 +176,7 @@ describe("OpenCode-compatible Permission API", () => {
|
|||
});
|
||||
|
||||
const autoReplyEvent = await waitForValue(() =>
|
||||
repliedEvents.find(
|
||||
(event) =>
|
||||
event?.properties?.requestID !== firstRequestId &&
|
||||
event?.properties?.reply === "always",
|
||||
),
|
||||
repliedEvents.find((event) => event?.properties?.requestID !== firstRequestId && event?.properties?.reply === "always"),
|
||||
);
|
||||
const autoRequestId = autoReplyEvent?.properties?.requestID;
|
||||
expect(autoRequestId).toBeDefined();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll, beforeEach, afterEach } from "vitest";
|
||||
import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk/v1";
|
||||
import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk";
|
||||
import { spawnSandboxAgent, buildSandboxAgent, type SandboxAgentHandle } from "./helpers/spawn";
|
||||
|
||||
describe("OpenCode-compatible Question API", () => {
|
||||
|
|
|
|||
|
|
@ -15,11 +15,7 @@
|
|||
|
||||
import { describe, it, expect, beforeAll, beforeEach, afterEach } from "vitest";
|
||||
import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk";
|
||||
import {
|
||||
spawnSandboxAgent,
|
||||
buildSandboxAgent,
|
||||
type SandboxAgentHandle,
|
||||
} from "./helpers/spawn";
|
||||
import { spawnSandboxAgent, buildSandboxAgent, type SandboxAgentHandle } from "./helpers/spawn";
|
||||
|
||||
const MODEL = process.env.TEST_AGENT_MODEL;
|
||||
|
||||
|
|
@ -62,22 +58,11 @@ describe.skipIf(!MODEL)("Real agent round-trip", () => {
|
|||
* Uses a manual iterator to avoid closing the stream (for-await-of calls
|
||||
* iterator.return() on early exit, which would close the SSE connection).
|
||||
*/
|
||||
function collectUntilIdle(
|
||||
iter: AsyncIterator<any>,
|
||||
timeoutMs = 30_000,
|
||||
): Promise<{ events: any[]; text: string }> {
|
||||
function collectUntilIdle(iter: AsyncIterator<any>, timeoutMs = 30_000): Promise<{ events: any[]; text: string }> {
|
||||
const events: any[] = [];
|
||||
let text = "";
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(
|
||||
() =>
|
||||
reject(
|
||||
new Error(
|
||||
`Timed out after ${timeoutMs}ms. Events: ${JSON.stringify(events.map((e) => e.type))}`,
|
||||
),
|
||||
),
|
||||
timeoutMs,
|
||||
);
|
||||
const timeout = setTimeout(() => reject(new Error(`Timed out after ${timeoutMs}ms. Events: ${JSON.stringify(events.map((e) => e.type))}`)), timeoutMs);
|
||||
(async () => {
|
||||
try {
|
||||
while (true) {
|
||||
|
|
@ -88,10 +73,7 @@ describe.skipIf(!MODEL)("Real agent round-trip", () => {
|
|||
return;
|
||||
}
|
||||
events.push(event);
|
||||
if (
|
||||
event.type === "message.part.updated" &&
|
||||
event.properties?.part?.type === "text"
|
||||
) {
|
||||
if (event.type === "message.part.updated" && event.properties?.part?.type === "text") {
|
||||
// Prefer the delta (chunk) if present; otherwise use the full
|
||||
// accumulated part.text (for non-streaming single-shot events).
|
||||
text += event.properties.delta ?? event.properties.part.text ?? "";
|
||||
|
|
@ -103,11 +85,7 @@ describe.skipIf(!MODEL)("Real agent round-trip", () => {
|
|||
}
|
||||
if (event.type === "session.error") {
|
||||
clearTimeout(timeout);
|
||||
reject(
|
||||
new Error(
|
||||
`session.error: ${JSON.stringify(event.properties?.error)}`,
|
||||
),
|
||||
);
|
||||
reject(new Error(`session.error: ${JSON.stringify(event.properties?.error)}`));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,10 +55,7 @@ describe("OpenCode-compatible Session API", () => {
|
|||
return (sessions ?? []).find((item: any) => item.id === sessionId);
|
||||
}
|
||||
|
||||
async function initSessionViaHttp(
|
||||
sessionId: string,
|
||||
body: Record<string, unknown> = {}
|
||||
): Promise<{ response: Response; data: any }> {
|
||||
async function initSessionViaHttp(sessionId: string, body: Record<string, unknown> = {}): Promise<{ response: Response; data: any }> {
|
||||
const response = await fetch(`${handle.baseUrl}/opencode/session/${sessionId}/init`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
|
@ -300,7 +297,7 @@ describe("OpenCode-compatible Session API", () => {
|
|||
});
|
||||
expect(changed.response.status).toBe(400);
|
||||
expect(changed.data?.errors?.[0]?.message).toBe(
|
||||
"OpenCode compatibility currently does not support changing the model after creating a session. Export with /export and load in to a new session."
|
||||
"OpenCode compatibility currently does not support changing the model after creating a session. Export with /export and load in to a new session.",
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -337,9 +334,7 @@ describe("OpenCode-compatible Session API", () => {
|
|||
|
||||
it("should keep session.get available during first prompt after /new-style creation", async () => {
|
||||
const providers = await getProvidersViaHttp();
|
||||
const providerId = providers.connected.find(
|
||||
(provider) => provider !== "mock" && typeof providers.default?.[provider] === "string"
|
||||
);
|
||||
const providerId = providers.connected.find((provider) => provider !== "mock" && typeof providers.default?.[provider] === "string");
|
||||
if (!providerId) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -478,7 +473,7 @@ describe("OpenCode-compatible Session API", () => {
|
|||
|
||||
expect(response.status).toBe(400);
|
||||
expect(data?.errors?.[0]?.message).toBe(
|
||||
"OpenCode compatibility currently does not support changing the model after creating a session. Export with /export and load in to a new session."
|
||||
"OpenCode compatibility currently does not support changing the model after creating a session. Export with /export and load in to a new session.",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
@ -511,7 +506,7 @@ describe("OpenCode-compatible Session API", () => {
|
|||
|
||||
expect(response.status).toBe(400);
|
||||
expect(data?.errors?.[0]?.message).toBe(
|
||||
"OpenCode compatibility currently does not support changing the model after creating a session. Export with /export and load in to a new session."
|
||||
"OpenCode compatibility currently does not support changing the model after creating a session. Export with /export and load in to a new session.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue