diff --git a/sdks/typescript-agent-sdk/tests/mock-proxy.ts b/sdks/typescript-agent-sdk/tests/mock-proxy.ts index 9efb5ae57b0..bc3674fe168 100644 --- a/sdks/typescript-agent-sdk/tests/mock-proxy.ts +++ b/sdks/typescript-agent-sdk/tests/mock-proxy.ts @@ -23,18 +23,23 @@ interface AgentRecord { interface SessionRecord { id: string; agentId: string; - status: "pending" | "running" | "idle" | "terminated" | "failed"; + status: "provisioning" | "ready" | "busy" | "error" | "terminated"; vmId: string; createdAt: string; runs: Map; followups: string[]; - conversation: { role: string; content: string; runId?: string; createdAt: string }[]; + conversation: { + role: string; + content: string; + runId?: string; + createdAt: string; + }[]; } interface RunRecord { id: string; sessionId: string; - status: "queued" | "running" | "completed" | "failed" | "cancelled"; + status: "queued" | "running" | "finished" | "cancelled" | "error"; result: string | null; events: { seq: number; type: string; data: unknown }[]; startedAt: string | null; @@ -68,7 +73,9 @@ export class MockProxy { } async start(): Promise { - await new Promise((resolve) => this.server.listen(0, "127.0.0.1", resolve)); + await new Promise((resolve) => + this.server.listen(0, "127.0.0.1", resolve), + ); const addr = this.server.address() as AddressInfo; return `http://127.0.0.1:${addr.port}`; } @@ -84,7 +91,7 @@ export class MockProxy { } } await new Promise((resolve, reject) => - this.server.close((err) => (err ? reject(err) : resolve())) + this.server.close((err) => (err ? reject(err) : resolve())), ); } @@ -124,7 +131,7 @@ export class MockProxy { complete(runId: string, result: string): void { const run = this.findRun(runId); if (!run) return; - run.status = "completed"; + run.status = "finished"; run.result = result; run.completedAt = new Date().toISOString(); this.emit(runId, "done", { result }); @@ -137,18 +144,28 @@ export class MockProxy { return `${prefix}_${this.idCounter}`; } - private async handle(req: IncomingMessage, res: ServerResponse): Promise { + private async handle( + req: IncomingMessage, + res: ServerResponse, + ): Promise { if (this.options.failFirstRequest && !this.firstRequestServed) { this.firstRequestServed = true; - res.writeHead(503, { "retry-after": "0", "content-type": "application/json" }); - res.end(JSON.stringify({ error: { code: "transient", message: "boom" } })); + res.writeHead(503, { + "retry-after": "0", + "content-type": "application/json", + }); + res.end( + JSON.stringify({ error: { code: "transient", message: "boom" } }), + ); return; } this.firstRequestServed = true; if (req.headers.authorization !== "Bearer test-key") { res.writeHead(401, { "content-type": "application/json" }); - res.end(JSON.stringify({ error: { code: "unauthorized", message: "bad key" } })); + res.end( + JSON.stringify({ error: { code: "unauthorized", message: "bad key" } }), + ); return; } @@ -174,7 +191,11 @@ export class MockProxy { return ok(res, { ok: true }); } } - if (segments.length === 4 && segments[1] === "agents" && segments[3] === "sessions") { + if ( + segments.length === 4 && + segments[1] === "agents" && + segments[3] === "sessions" + ) { const agent = this.agents.get(segments[2]); if (!agent) return notFound(res); if (method === "POST") return this.createSession(agent, req, res); @@ -183,7 +204,11 @@ export class MockProxy { items: [...agent.sessions.values()].map(serializeSession), }); } - if (segments.length === 5 && segments[1] === "agents" && segments[3] === "sessions") { + if ( + segments.length === 5 && + segments[1] === "agents" && + segments[3] === "sessions" + ) { const agent = this.agents.get(segments[2]); const session = agent?.sessions.get(segments[4]); if (!session) return notFound(res); @@ -200,43 +225,78 @@ export class MockProxy { return ok(res, { ok: true }); } } - if (segments.length === 4 && segments[3] === "runs" && method === "POST") { + if ( + segments.length === 4 && + segments[3] === "runs" && + method === "POST" + ) { return this.createRun(session, req, res); } - if (segments.length === 4 && segments[3] === "runs" && method === "GET") { + if ( + segments.length === 4 && + segments[3] === "runs" && + method === "GET" + ) { return ok(res, { items: [...session.runs.values()].map(serializeRun), }); } - if (segments.length === 4 && segments[3] === "followup" && method === "POST") { + if ( + segments.length === 4 && + segments[3] === "followup" && + method === "POST" + ) { const body = await readJson(req); - session.followups.push(body.message); + // Wire format: {prompt: {text: "..."}} per FollowupCreate. + const text = body?.prompt?.text ?? ""; + session.followups.push(text); session.conversation.push({ role: "user", - content: body.message, + content: text, createdAt: new Date().toISOString(), }); return ok(res, { ok: true }); } - if (segments.length === 4 && segments[3] === "conversation" && method === "GET") { - return ok(res, { turns: session.conversation }); + if ( + segments.length === 4 && + segments[3] === "conversation" && + method === "GET" + ) { + return ok(res, { turns: session.conversation.map(serializeTurn) }); } if (segments.length === 5 && segments[3] === "runs") { const run = session.runs.get(segments[4]); if (!run) return notFound(res); if (method === "GET") return ok(res, serializeRun(run)); } - if (segments.length === 6 && segments[3] === "runs" && segments[5] === "events") { + if ( + segments.length === 6 && + segments[3] === "runs" && + segments[5] === "events" + ) { const run = session.runs.get(segments[4]); if (!run) return notFound(res); return this.streamEvents(run, req, res, url); } - if (segments.length === 6 && segments[3] === "runs" && segments[5] === "conversation") { + if ( + segments.length === 6 && + segments[3] === "runs" && + segments[5] === "conversation" + ) { const run = session.runs.get(segments[4]); if (!run) return notFound(res); - return ok(res, { turns: session.conversation.filter((t) => !t.runId || t.runId === run.id) }); + return ok(res, { + turns: session.conversation + .filter((t) => !t.runId || t.runId === run.id) + .map(serializeTurn), + }); } - if (segments.length === 6 && segments[3] === "runs" && segments[5] === "cancel" && method === "POST") { + if ( + segments.length === 6 && + segments[3] === "runs" && + segments[5] === "cancel" && + method === "POST" + ) { const run = session.runs.get(segments[4]); if (!run) return notFound(res); run.status = "cancelled"; @@ -248,19 +308,26 @@ export class MockProxy { } catch (e) { res.writeHead(500, { "content-type": "application/json" }); res.end( - JSON.stringify({ error: { code: "internal", message: (e as Error).message } }) + JSON.stringify({ + error: { code: "internal", message: (e as Error).message }, + }), ); } } - private async createAgent(req: IncomingMessage, res: ServerResponse): Promise { + private async createAgent( + req: IncomingMessage, + res: ServerResponse, + ): Promise { const body = await readJson(req); const id = this.nextId("agt"); const agent: AgentRecord = { id, name: body.name ?? "unnamed", model: body.model ?? { id: "test-model" }, - systemPrompt: body.systemPrompt, + // Wire format is snake_case (Python idiom); SDK transforms camelCase + // public API to snake_case before sending. + systemPrompt: body.system_prompt, metadata: body.metadata ?? {}, createdAt: new Date().toISOString(), sessions: new Map(), @@ -278,7 +345,7 @@ export class MockProxy { private async createSession( agent: AgentRecord, req: IncomingMessage, - res: ServerResponse + res: ServerResponse, ): Promise { await readJson(req); const id = this.nextId("ses"); @@ -286,7 +353,7 @@ export class MockProxy { const session: SessionRecord = { id, agentId: agent.id, - status: "idle", + status: "ready", vmId, createdAt: new Date().toISOString(), runs: new Map(), @@ -300,7 +367,7 @@ export class MockProxy { private async createRun( session: SessionRecord, req: IncomingMessage, - res: ServerResponse + res: ServerResponse, ): Promise { const body = await readJson(req); // 409 if any non-terminal run exists. @@ -308,7 +375,9 @@ export class MockProxy { if (r.status === "queued" || r.status === "running") { res.writeHead(409, { "content-type": "application/json" }); res.end( - JSON.stringify({ error: { code: "session_busy", message: "run in flight" } }) + JSON.stringify({ + error: { code: "session_busy", message: "run in flight" }, + }), ); return; } @@ -338,7 +407,7 @@ export class MockProxy { run: RunRecord, req: IncomingMessage, res: ServerResponse, - url: URL + url: URL, ): void { const startingSeq = Number(url.searchParams.get("starting_seq") ?? -1); res.writeHead(200, { @@ -348,13 +417,18 @@ export class MockProxy { }); // Replay events from startingSeq forward. - const replayFrom = Number.isFinite(startingSeq) && startingSeq >= 0 ? startingSeq : 0; + const replayFrom = + Number.isFinite(startingSeq) && startingSeq >= 0 ? startingSeq : 0; for (const event of run.events) { if (event.seq < replayFrom) continue; res.write(`id: ${event.seq}\ndata: ${JSON.stringify(event)}\n\n`); } - if (run.status === "completed" || run.status === "failed" || run.status === "cancelled") { + if ( + run.status === "finished" || + run.status === "error" || + run.status === "cancelled" + ) { res.end(); return; } @@ -375,11 +449,12 @@ export class MockProxy { } private serializeAgent(agent: AgentRecord) { + // Wire format is snake_case; SDK transforms back to camelCase on receive. return { id: agent.id, name: agent.name, model: agent.model, - createdAt: agent.createdAt, + created_at: agent.createdAt, }; } } @@ -387,20 +462,34 @@ export class MockProxy { function serializeSession(s: SessionRecord) { return { id: s.id, - agentId: s.agentId, + agent_id: s.agentId, status: s.status, - createdAt: s.createdAt, + created_at: s.createdAt, }; } function serializeRun(r: RunRecord) { return { id: r.id, - sessionId: r.sessionId, + session_id: r.sessionId, status: r.status, result: r.result, - startedAt: r.startedAt, - completedAt: r.completedAt, + started_at: r.startedAt, + completed_at: r.completedAt, + }; +} + +function serializeTurn(t: { + role: string; + content: string; + runId?: string; + createdAt: string; +}) { + return { + role: t.role, + content: t.content, + run_id: t.runId, + created_at: t.createdAt, }; } @@ -411,7 +500,9 @@ function ok(res: ServerResponse, body: unknown): void { function notFound(res: ServerResponse): void { res.writeHead(404, { "content-type": "application/json" }); - res.end(JSON.stringify({ error: { code: "not_found", message: "not found" } })); + res.end( + JSON.stringify({ error: { code: "not_found", message: "not found" } }), + ); } function readJson(req: IncomingMessage): Promise {