From 82c03fd68a93011023cede8290d07201c48d9dc5 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 6 May 2026 14:52:53 -0700 Subject: [PATCH] feat(agent-sdk): add Run class with stream/wait/cancel --- sdks/typescript-agent-sdk/src/run.ts | 102 +++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 sdks/typescript-agent-sdk/src/run.ts diff --git a/sdks/typescript-agent-sdk/src/run.ts b/sdks/typescript-agent-sdk/src/run.ts new file mode 100644 index 00000000000..753e17559db --- /dev/null +++ b/sdks/typescript-agent-sdk/src/run.ts @@ -0,0 +1,102 @@ +/** + * Run — a single execution of an agent inside a session. + * + * Lifecycle: queued → running → completed | failed | cancelled. + */ + +import { resolveClient, requestJson, type ResolvedClient } from "./client/http.js"; +import { streamRunEvents } from "./client/sse.js"; +import { + type ConversationTurn, + type RunEvent, + type RunInfo, + type RunResult, + type RunStatus, +} from "./types.js"; + +const TERMINAL_STATES: RunStatus[] = ["completed", "failed", "cancelled"]; + +export class Run { + readonly id: string; + readonly sessionId: string; + + private _status: RunStatus; + private _result: string | null; + private _git: RunInfo["git"]; + private readonly _client: ResolvedClient; + + constructor(info: RunInfo, client: ResolvedClient) { + this.id = info.id; + this.sessionId = info.sessionId; + this._status = info.status; + this._result = info.result; + this._git = info.git; + this._client = client; + } + + get status(): RunStatus { + return this._status; + } + + get result(): string | null { + return this._result; + } + + get git(): RunInfo["git"] { + return this._git; + } + + /** Open an SSE stream of events for this run. */ + stream(opts: { startingSeq?: number; signal?: AbortSignal } = {}): AsyncIterable { + return streamRunEvents(this._client, this.sessionId, this.id, opts); + } + + /** Block until the run reaches a terminal status. */ + async wait(): Promise { + while (!TERMINAL_STATES.includes(this._status)) { + const info = await requestJson(this._client, { + method: "GET", + path: `/v1/sessions/${encodeURIComponent(this.sessionId)}/runs/${encodeURIComponent(this.id)}`, + }); + this._status = info.status; + this._result = info.result; + this._git = info.git; + if (TERMINAL_STATES.includes(this._status)) break; + await sleep(500); + } + return { + id: this.id, + status: this._status, + result: this._result, + git: this._git, + }; + } + + /** Snapshot of the conversation up through this run. */ + async conversation(): Promise { + const data = await requestJson<{ turns: ConversationTurn[] }>(this._client, { + method: "GET", + path: `/v1/sessions/${encodeURIComponent(this.sessionId)}/runs/${encodeURIComponent(this.id)}/conversation`, + }); + return data.turns ?? []; + } + + /** Cancel a running run; no-op if already terminal. */ + async cancel(): Promise { + if (TERMINAL_STATES.includes(this._status)) return; + await requestJson(this._client, { + method: "POST", + path: `/v1/sessions/${encodeURIComponent(this.sessionId)}/runs/${encodeURIComponent(this.id)}/cancel`, + }); + this._status = "cancelled"; + } +} + +/** Internal helper used by SessionHandle / Agent to build a Run from a wire response. */ +export function runFromInfo(info: RunInfo, options: { apiKey?: string; baseUrl?: string }): Run { + return new Run(info, resolveClient(options)); +} + +function sleep(ms: number): Promise { + return new Promise((r) => setTimeout(r, ms)); +}