import { buildCloudHeaders, type AssistantCloudAPI } from "./AssistantCloudAPI"; import type { AssistantCloudRunReportToolCall } from "./runTelemetry"; import { AssistantStream, PlainTextDecoder } from "assistant-stream"; import { CloudResponseError, readCloudRecord, readCloudString, } from "./cloudResponse"; type AssistantCloudRunsStreamBody = { thread_id: string; assistant_id: "system/thread_title"; messages: readonly unknown[]; // TODO type }; // NOTE: Keep this payload shape aligned with the runtime validator in // assistant-cloud: apps/api/src/endpoints/runs/create.ts // (createRunSchema). New telemetry fields must be accepted there first. export type AssistantCloudRunReport = { thread_id: string; status: "completed" | "incomplete" | "error"; outcome_type?: | "rate_limited" | "validation_failed" | "provider_error" | "server_error" | "budget_denied" | "persistence_error" | "aborted" | "timeout" | "disconnected" | "length" | "content_filter"; message_id?: string; user_message_id?: string; first_token_ms?: number; release?: string; environment?: string; tags?: string[]; provider?: string; trace_id?: string; root_span_id?: string; error_code?: string; error?: string; total_steps?: number; tool_calls?: AssistantCloudRunReportToolCall[]; steps?: { input_tokens?: number; output_tokens?: number; reasoning_tokens?: number; cached_input_tokens?: number; tool_calls?: AssistantCloudRunReportToolCall[]; start_ms?: number; end_ms?: number; finish_reason?: string; input?: string; }[]; input_tokens?: number; output_tokens?: number; reasoning_tokens?: number; cached_input_tokens?: number; cost_usd?: number; cost_details?: { input?: number; input_cached_tokens?: number; output?: number; total?: number; }; model_id?: string; provider_type?: string; duration_ms?: number; output_text?: string; attributes?: Record; metadata?: Record; }; export class AssistantCloudRuns { private cloud: AssistantCloudAPI; constructor(cloud: AssistantCloudAPI) { this.cloud = cloud; } public __internal_getAssistantOptions(assistantId: string) { return { api: `${this.cloud._baseUrl}/v1/runs/stream`, protocol: "ui-message-stream" as const, headers: (): Promise<{ Accept: string; "Aui-Sdk": string }> => buildCloudHeaders(this.cloud, { Accept: "text/plain" }), body: async (options?: { threadId?: string }) => { const threadId = options?.threadId; if (threadId === undefined) { throw new Error( "Assistant Cloud runs need a thread; the thread list adapter has not assigned a remote id to this thread yet.", ); } return { assistant_id: assistantId, response_format: "vercel-ai-data-stream/v1", thread_id: threadId, }; }, }; } public async stream( body: AssistantCloudRunsStreamBody, ): Promise { const response = await this.cloud.makeRawRequest("/runs/stream", { method: "POST", headers: { Accept: "text/plain", }, body, }); if (!response.body) { throw new CloudResponseError( 'Invalid Assistant Cloud response for "run stream": expected a response body', ); } const receivedContentType = response.headers.get("content-type"); const contentType = receivedContentType ?.split(";", 1)[0] ?.trim() .toLowerCase(); if (contentType !== "text/plain") { await response.body.cancel().catch(() => undefined); throw new CloudResponseError( `Invalid Assistant Cloud response for "run stream": expected a "text/plain" content type, received ${ receivedContentType ? `"${receivedContentType}"` : "no Content-Type header" }`, ); } return AssistantStream.fromResponse(response, new PlainTextDecoder()); } public async report( body: AssistantCloudRunReport, ): Promise<{ run_id: string }> { const response = readCloudRecord( await this.cloud.makeRequest("/runs", { method: "POST", body }), "run report response", ); return { run_id: readCloudString(response.run_id, "run_id") }; } }