UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

496 lines (495 loc) • 17.5 kB
import { d as sleepWithAbort } from "./src-D1BHGNTj.mjs"; import { Et as _enum, Fn as object, Jn as string, Lt as boolean, Nt as array, Pn as number, Sn as looseObject, Yt as discriminatedUnion, or as union, sr as unknown, xn as literal } from "./schemas-BOYIvvln.mjs"; import { i as fetchWithSsrFGuard, u as responseWithRelease } from "./fetch-guard-C19WJ54-.mjs"; import { d as resolveProviderRequestHeaders } from "./provider-request-config-zrhxha-3.mjs"; import { t as retryAsync } from "./retry-Bgt50HOo.mjs"; import "./fetch-runtime-CTtEWKS4.mjs"; import "./runtime-env-CySdLHPX.mjs"; import "./retry-runtime-Dz_IZ2a5.mjs"; import "./ssrf-runtime-CR-jTHeC.mjs"; import "./provider-http-JgjW-z-k.mjs"; import { t as DEFAULT_NATIVE_TOOLS } from "./config-9TiZiL1V.mjs"; import { randomUUID } from "node:crypto"; import { setTimeout } from "node:timers/promises"; import OpenAI from "openai"; //#region extensions/agentsapi/agentsapi-client.ts const usageSchema = looseObject({ input_tokens: number(), output_tokens: number(), total_tokens: number().optional(), input_tokens_details: looseObject({ cached_tokens: number() }).optional(), output_tokens_details: looseObject({ reasoning_tokens: number() }).optional() }); const errorSchema = looseObject({ message: string(), code: string().nullable().optional(), type: string().optional(), param: string().nullable().optional() }); const functionCallSchema = looseObject({ type: literal("function_call"), turn_id: string().min(1), call_id: string().min(1), name: string().min(1), arguments: unknown() }); const sessionSchema = looseObject({ id: string(), status: _enum([ "idle", "in_progress", "requires_action", "failed" ]), error: string().nullable(), usage: usageSchema.nullable().optional(), environment: discriminatedUnion("type", [ looseObject({ type: literal("openai_hosted"), id: string().min(1) }), looseObject({ type: literal("self_hosted"), id: string().min(1), workspace_directory: string(), remote_url: string().min(1) }), looseObject({ type: literal("none") }) ]), required_actions: array(union([functionCallSchema, looseObject({ type: literal("environment_connection"), environment_id: string() })])) }); const artifactSchema = looseObject({ id: string().min(1), object: literal("agent.session.artifact"), session_id: string(), environment_id: string(), turn_id: string(), path: string(), size_bytes: number().int().nonnegative().safe() }); const textPartSchema = looseObject({ type: string(), text: string().optional() }); const itemSchema = looseObject({ id: string(), type: string(), role: string().optional(), phase: string().nullable().optional(), status: string().nullable().optional(), turn_id: string().optional(), content: array(textPartSchema).optional(), summary: array(textPartSchema).optional(), command: string().optional(), cwd: string().nullable().optional(), duration_ms: number().nullable().optional(), exit_code: number().nullable().optional(), name: string().optional(), call_id: string().optional(), server_label: string().optional(), arguments: unknown().optional(), output: unknown().optional(), error: unknown().optional(), action: looseObject({ type: string(), query: string().nullable().optional(), queries: array(string()).nullable().optional(), url: string().nullable().optional(), pattern: string().nullable().optional() }).nullable().optional() }); const eventSchema = looseObject({ type: string(), event_id: string().optional(), session_id: string().optional(), turn_id: string().nullable().optional(), item_id: string().optional(), output_index: number().nullable().optional(), content_index: number().optional(), summary_index: number().optional(), status: string().nullable().optional(), delta: string().optional(), text: string().optional(), part: textPartSchema.optional(), item: itemSchema.optional(), usage: usageSchema.nullable().optional(), session: sessionSchema.optional(), environment: looseObject({ id: string(), type: string(), status: string(), error: errorSchema.nullable() }).optional(), turn: looseObject({ id: string(), subagent_id: string().nullable(), session_id: string().optional(), status: string().optional(), agent_id: string().optional(), created_at: number().optional(), started_at: number().nullable().optional(), completed_at: number().nullable().optional(), error: errorSchema.nullable().optional(), usage: usageSchema.nullable().optional() }).optional(), error: errorSchema.optional() }); /** The SDK owns the wire protocol; OpenClaw retains native session authority. */ var AgentsApiClient = class { constructor(apiKey, assertCurrent, assertRequestCurrent = assertCurrent) { this.assertCurrent = assertCurrent; const agents = new OpenAI({ apiKey, baseURL: null, maxRetries: 0, defaultHeaders: { Authorization: `Bearer ${apiKey}`, "OpenAI-Organization": null, "OpenAI-Project": null }, fetch: async (input, init) => { this.assertCurrent(); const url = input instanceof Request ? input.url : String(input); const headers = resolveProviderRequestHeaders({ provider: "openai", baseUrl: url, transport: "http", callerHeaders: Object.fromEntries(new Headers(init?.headers)) }); const guarded = await fetchWithSsrFGuard({ url, init: { ...init, headers }, signal: init?.signal ?? void 0, beforeRequest: assertRequestCurrent }); const response = responseWithRelease(guarded.response, guarded.release); try { this.assertCurrent(); } catch (error) { await response.body?.cancel().catch(() => void 0); throw error; } return response; } }).beta.agents; this.sessions = agents.sessions; this.environments = agents.environments; } async create(signal, instructions, model, options) { const environment = options?.environment ?? { type: "openai_hosted" }; const tools = [ ...options?.nativeTools ?? DEFAULT_NATIVE_TOOLS, ...options?.mcpTools ?? [], ...options?.functions ?? [] ]; const body = { agent: { model, instructions, reasoning: options?.reasoning, multi_agent: { enabled: false } }, environment: environment.type === "openai_hosted" ? { ...environment, files: options?.files ?? [] } : environment }; const session = await this.sessions.create(body, { signal, headers: { "Idempotency-Key": randomUUID() }, body: { ...body, agent: { ...body.agent, tools } } }); this.assertCurrent(); return session.id; } async createIsolated(signal, instructions, input, model, reasoning) { const session = await this.sessions.create({ agent: { model, instructions, reasoning, tools: [], multi_agent: { enabled: false } }, environment: { type: "none" }, input, vault_ids: [] }, { signal, headers: { "Idempotency-Key": randomUUID() } }); this.assertCurrent(); return session; } async deleteSession(sessionId, signal) { const deleted = await this.sessions.delete(sessionId, { signal }); this.assertCurrent(); if (deleted.id !== sessionId || !deleted.deleted) throw new Error("Agents API did not delete the requested isolated session"); } async setReasoningEffort(sessionId, effort, signal) { const session = await this.sessions.update(sessionId, {}, { signal, headers: { "Idempotency-Key": randomUUID() }, body: { agent: { reasoning: { effort: effort ?? null } } } }); this.assertCurrent(); if (session.id !== sessionId) throw new Error("Agents API returned a different session"); } async subscribe(sessionId, signal) { const stream = await this.sessions.events.stream(sessionId, { signal }); try { this.assertCurrent(); signal.throwIfAborted(); } catch (error) { stream.controller.abort(); throw error; } return observeEvents(stream, signal, sessionId, this.assertCurrent); } async session(sessionId, signal) { const session = await this.sessions.retrieve(sessionId, { signal }); this.assertCurrent(); if (session.id !== sessionId) throw new Error("Agents API returned a different session"); return session; } async pendingFunctionCalls(sessionId, signal, connectEnvironment) { const session = sessionSchema.parse(await this.session(sessionId, signal)); if (session.status === "failed") throw new Error(session.error ?? "Agents API session failed"); if (session.status !== "requires_action") return []; const calls = []; for (const action of session.required_actions) { if (action.type === "environment_connection" && session.environment.type === "self_hosted" && action.environment_id === session.environment.id) { if (connectEnvironment) { await connectEnvironment(action.environment_id); this.assertCurrent(); } continue; } if (action.type !== "function_call") throw new Error("Agents API hosted prototype cannot reconnect an environment_connection"); calls.push(action); } return calls; } async environment(environmentId, signal) { const environment = await this.environments.retrieve(environmentId, { signal }); this.assertCurrent(); if (environment.id !== environmentId) throw new Error("Agents API returned a different environment"); return environment; } async toolResult(sessionId, call, result, signal) { await this.submitEvents(sessionId, [{ type: "agent.session.input.tool_result", turn_id: call.turn_id, call_id: call.call_id, ...result.success ? { success: true, output: result.output } : { success: false, error: result.error } }], signal); } async turn(sessionId, turnId, signal) { const turn = await this.sessions.turns.retrieve(turnId, { session_id: sessionId }, { signal }); this.assertCurrent(); if (turn.id !== turnId || turn.session_id !== sessionId || turn.subagent_id !== null) throw new Error("Agents API returned a turn outside the requested root session"); return turn; } async uploadFile(sessionId, file, signal) { const session = await this.session(sessionId, signal); signal.throwIfAborted(); if (session.environment.type !== "openai_hosted") throw new Error("Agents API file upload requires the session's connected hosted environment"); const retrieved = await this.environments.retrieve(session.environment.id, { signal }); const environment = object({ id: string(), type: literal("openai_hosted"), status: string() }).parse(retrieved); this.assertCurrent(); signal.throwIfAborted(); if (environment.id !== session.environment.id) throw new Error("Agents API file upload requires the session's connected hosted environment"); if (environment.status === "disconnected") return { status: "unavailable" }; if (environment.status !== "connected") throw new Error("Agents API file upload requires the session's connected hosted environment"); let uploaded; try { uploaded = await this.environments.files.create(session.environment.id, file, { signal, headers: { "Idempotency-Key": randomUUID() } }); } catch (error) { if (error instanceof OpenAI.ConflictError && error.status === 409) { const failure = errorSchema.safeParse(error.error); if (failure.success && failure.data.type === "conflict_error" && failure.data.message === "the hosted environment is dormant; submit new input to start a fresh sandbox") { this.assertCurrent(); signal.throwIfAborted(); return { status: "unavailable" }; } } throw error; } const saved = object({ environment_id: string(), path: string(), size_bytes: number() }).parse(uploaded); this.assertCurrent(); signal.throwIfAborted(); if (saved.environment_id !== session.environment.id || saved.path !== file.path || saved.size_bytes !== Buffer.from(file.data, "base64").byteLength) throw new Error("Agents API uploaded file did not match the requested environment, path, or size"); return { status: "uploaded" }; } async artifacts(sessionId, turnId, signal) { const artifacts = []; const seen = /* @__PURE__ */ new Set(); let after; for (let pages = 0; pages < 100; pages++) { const listed = await this.sessions.artifacts.list(sessionId, { order: "asc", limit: 100, after }, { signal }); const page = object({ data: array(artifactSchema), has_more: boolean() }).parse(listed); this.assertCurrent(); if (page.data.some((artifact) => artifact.session_id !== sessionId)) throw new Error("Agents API returned an artifact outside the requested session"); artifacts.push(...page.data.filter((artifact) => artifact.turn_id === turnId)); if (!page.has_more) return artifacts; const lastId = page.data.at(-1)?.id; if (!lastId || seen.has(lastId)) throw new Error("Agents API artifacts page has no valid continuation cursor"); after = lastId; seen.add(after); } throw new Error("Agents API artifact listing exceeded the pagination limit"); } async artifactContent(sessionId, artifact, maxBytes, signal) { if (artifact.session_id !== sessionId || !Number.isSafeInteger(maxBytes) || maxBytes < 0 || artifact.size_bytes > maxBytes) throw new Error("Agents API artifact download exceeds its session or byte bounds"); const response = await this.sessions.artifacts.content(artifact.id, { session_id: sessionId }, { signal }); if (!response.body) throw new Error("Agents API artifact returned no content body"); const reader = response.body.getReader(); const chunks = []; let bytes = 0; try { while (true) { signal.throwIfAborted(); this.assertCurrent(); const chunk = await reader.read(); this.assertCurrent(); if (chunk.done) break; bytes += chunk.value.byteLength; if (bytes > maxBytes || bytes > artifact.size_bytes) throw new Error("Agents API artifact content exceeded its immutable size or byte limit"); chunks.push(chunk.value); } if (bytes !== artifact.size_bytes) throw new Error("Agents API artifact content did not match its immutable size"); return Buffer.concat(chunks, bytes); } finally { await closeResponseReader(reader, signal); } } async turns(sessionId, signal, after, latestOnly = false) { const turns = []; const pages = this.sessions.turns.list(sessionId, { order: latestOnly ? "desc" : "asc", limit: latestOnly ? 1 : 100, after }, { signal }); for await (const page of (await pages).iterPages()) { this.assertCurrent(); if (page.data.some((turn) => turn.session_id !== sessionId || turn.subagent_id !== null)) throw new Error("Agents API returned a turn outside the single-agent session"); turns.push(...page.data); if (latestOnly) break; if (page.has_more && !page.hasNextPage()) throw new Error("Agents API turns page has no continuation cursor"); } return turns; } async message(sessionId, text, signal) { await this.submitEvents(sessionId, [{ type: "agent.session.input.message", input: [{ role: "user", content: [{ type: "input_text", text }] }] }], signal); } async cancel(sessionId, signal) { await this.submitEvents(sessionId, [{ type: "agent.session.input.cancel" }], signal); while (true) { const session = await this.session(sessionId, signal); if (session.status === "idle" || session.status === "failed") return; await setTimeout(500, void 0, { signal }); } } async items(sessionId, turnId, signal) { const items = []; const pages = this.sessions.items.list(sessionId, { order: "asc", limit: 100 }, { signal }); for await (const page of (await pages).iterPages()) { this.assertCurrent(); items.push(...page.data.filter((item) => turnId === void 0 || item.turn_id === turnId).map((item) => itemSchema.parse(item))); if (page.has_more && !page.hasNextPage()) throw new Error("Agents API items page has no continuation cursor"); } return items; } async submitEvents(sessionId, events, signal) { const params = { events, "Idempotency-Key": randomUUID() }; await retryAsync(async () => { signal.throwIfAborted(); this.assertCurrent(); await this.sessions.events.create(sessionId, params, { signal }); }, { attempts: 3, minDelayMs: 500, maxDelayMs: 5e3, jitter: .25, shouldRetry: (error) => !signal.aborted && error instanceof OpenAI.APIError && error.status !== void 0 && error.status >= 500 && error.status < 600 && error.headers?.get("x-should-retry") !== "false", sleep: (ms) => sleepWithAbort(ms, signal) }); this.assertCurrent(); } }; function isAgentsApiTerminalTurn(status) { return status === "completed" || status === "failed" || status === "cancelled"; } async function* observeEvents(stream, signal, sessionId, assertCurrent) { for await (const rawEvent of stream) { signal.throwIfAborted(); assertCurrent(); const event = eventSchema.parse(rawEvent); if (event.session_id && event.session_id !== sessionId || event.session && event.session.id !== sessionId || event.turn?.session_id && event.turn.session_id !== sessionId) throw new Error("Agents API returned an event outside the requested session"); yield event; } signal.throwIfAborted(); } async function closeResponseReader(reader, signal) { try { await reader.cancel(); } catch (error) { if (!signal.aborted) throw error; } finally { reader.releaseLock(); } signal.throwIfAborted(); } //#endregion export { isAgentsApiTerminalTurn as n, AgentsApiClient as t };