UNPKG

assistant-cloud

Version:

Cloud integration for assistant-ui

171 lines (150 loc) 4.81 kB
import { type AssistantCloudAuthStrategy, AssistantCloudJWTAuthStrategy, AssistantCloudAPIKeyAuthStrategy, AssistantCloudAnonymousAuthStrategy, } from "./AssistantCloudAuthStrategy"; import type { AssistantCloudRunReport } from "./AssistantCloudRuns"; export type AssistantCloudTelemetryConfig = { enabled?: boolean; /** * Called before each telemetry report is sent. * Return a modified report to enrich it (e.g. add `model_id`), * or return `null` to skip the report. */ beforeReport?: ( report: AssistantCloudRunReport, ) => AssistantCloudRunReport | null; }; export type AssistantCloudConfig = ( | { baseUrl: string; authToken: () => Promise<string | null>; } | { baseUrl?: string; apiKey: string; userId: string; workspaceId: string; } | { baseUrl: string; anonymous: true; } ) & { /** * Client-side run telemetry reporting. Default: `true`. * * When enabled, the SDK automatically reports run metadata (status, step * count, tool calls, and token usage) to Assistant Cloud after each * assistant message is saved. No message content is sent. * * - `true` / `undefined` — enabled with defaults * - `false` — disabled * - `{ beforeReport }` — enabled with a hook to enrich or filter reports */ telemetry?: boolean | AssistantCloudTelemetryConfig; }; export class CloudAPIError extends Error { public readonly status: number; constructor(message: string, status: number) { super(message); this.status = status; this.name = "CloudAPIError"; } } type MakeRequestOptions = { method?: "POST" | "PUT" | "DELETE" | undefined; headers?: Record<string, string> | undefined; query?: Record<string, string | number | boolean> | undefined; body?: object | undefined; }; const normalizeBaseUrl = (baseUrl: string) => { if (!baseUrl || !baseUrl.endsWith("/")) return baseUrl; return baseUrl.slice(0, -1); }; export class AssistantCloudAPI { public _auth: AssistantCloudAuthStrategy; public _baseUrl; constructor(config: AssistantCloudConfig) { if ("authToken" in config) { this._baseUrl = normalizeBaseUrl(config.baseUrl); this._auth = new AssistantCloudJWTAuthStrategy(config.authToken); } else if ("apiKey" in config) { this._baseUrl = normalizeBaseUrl( config.baseUrl ?? "https://backend.assistant-api.com", ); this._auth = new AssistantCloudAPIKeyAuthStrategy( config.apiKey, config.userId, config.workspaceId, ); } else if ("anonymous" in config) { this._baseUrl = normalizeBaseUrl(config.baseUrl); this._auth = new AssistantCloudAnonymousAuthStrategy(this._baseUrl); } else { throw new Error( "Invalid configuration: Must provide authToken, apiKey, or anonymous configuration", ); } } public async initializeAuth() { return !!(await this._auth.getAuthHeaders()); } public async makeRawRequest( endpoint: string, options: MakeRequestOptions = {}, ) { const authHeaders = await this._auth.getAuthHeaders(); if (!authHeaders) throw new Error("Authorization failed"); const headers = { ...authHeaders, ...options.headers, "Content-Type": "application/json", }; const queryParams = new URLSearchParams(); if (options.query) { for (const [key, value] of Object.entries(options.query)) { if (value === false) continue; if (value === true) { queryParams.set(key, "true"); } else { queryParams.set(key, value.toString()); } } } const url = new URL(`${this._baseUrl}/v1${endpoint}`); url.search = queryParams.toString(); const response = await fetch(url, { method: options.method ?? "GET", headers, body: options.body ? JSON.stringify(options.body) : null, }); this._auth.readAuthHeaders(response.headers); if (!response.ok) { const text = await response.text(); try { const body = JSON.parse(text); throw new CloudAPIError(body.message, response.status); } catch (error) { if (error instanceof CloudAPIError) throw error; throw new CloudAPIError( `Request failed with status ${response.status}, ${text}`, response.status, ); } } return response; } public async makeRequest(endpoint: string, options: MakeRequestOptions = {}) { const response = await this.makeRawRequest(endpoint, options); if ( response.status === 204 || response.headers.get("content-length") === "0" ) return undefined; const text = await response.text(); if (text.trim() === "") return undefined; return JSON.parse(text); } }