import { afterEach, describe, expect, it, vi } from "vitest"; import { AssistantCloudAPI } from "../AssistantCloudAPI"; import { AssistantCloud } from "../AssistantCloud"; import { AssistantCloudRuns } from "../AssistantCloudRuns"; import { CloudResponseError } from "../cloudResponse"; const createCloud = () => new AssistantCloud({ apiKey: "test-key", userId: "user-id", workspaceId: "workspace-id", }); const streamBody = { thread_id: "thread-id", assistant_id: "system/thread_title" as const, messages: [], }; const createAccessToken = (subject: string) => { const header = Buffer.from(JSON.stringify({ alg: "none" })).toString( "base64url", ); const payload = Buffer.from( JSON.stringify({ exp: 4102444800, sub: subject }), ).toString("base64url"); return `${header}.${payload}.sig`; }; const jwt = createAccessToken("user-id"); describe("AssistantCloudRuns", () => { afterEach(() => { vi.unstubAllGlobals(); }); it.each([ { name: "API key", createApi: () => new AssistantCloudAPI({ apiKey: "test-key", userId: "user-id", workspaceId: "workspace-id", }), authHeaders: { Authorization: "Bearer test-key", "Aui-User-Id": "user-id", "Aui-Workspace-Id": "workspace-id", }, }, { name: "JWT", createApi: () => new AssistantCloudAPI({ baseUrl: "https://test.example.com", authToken: async () => jwt, }), authHeaders: { Authorization: `Bearer ${jwt}` }, }, ])( "keeps request and streaming headers for $name auth", async ({ createApi, authHeaders }) => { const fetchMock = vi.fn().mockResolvedValue( new Response("Generated title", { headers: { "Content-Type": "text/plain" }, }), ); vi.stubGlobal("fetch", fetchMock); const api = createApi(); const runs = new AssistantCloudRuns(api); api.registerSdk({ name: "@assistant-ui/core", version: "0.3.18" }); const sdkHeader = api.sdkHeader(); await api.makeRawRequest("/threads", { headers: { "X-Test": "1" } }); await runs.stream(streamBody); const optionHeaders = await runs .__internal_getAssistantOptions("assistant-id") .headers(); expect(fetchMock.mock.calls[0]?.[1]?.headers).toEqual({ ...authHeaders, "X-Test": "1", "Content-Type": "application/json", "Aui-Sdk": sdkHeader, }); expect(fetchMock.mock.calls[1]?.[1]?.headers).toEqual({ ...authHeaders, Accept: "text/plain", "Content-Type": "application/json", "Aui-Sdk": sdkHeader, }); expect(optionHeaders).toEqual({ ...authHeaders, Accept: "text/plain", "Aui-Sdk": sdkHeader, }); }, ); it("decodes text/plain run streams", async () => { vi.stubGlobal( "fetch", vi.fn().mockResolvedValue( new Response("Generated title", { headers: { "Content-Type": "text/plain; charset=utf-8" }, }), ), ); const stream = await createCloud().runs.stream(streamBody); let text = ""; await stream.pipeTo( new WritableStream({ write(chunk) { if (chunk.type === "text-delta") text += chunk.textDelta; }, }), ); expect(text).toBe("Generated title"); }); it("rejects successful run responses without a body", async () => { vi.stubGlobal( "fetch", vi.fn().mockResolvedValue(new Response(null, { status: 204 })), ); await expect(createCloud().runs.stream(streamBody)).rejects.toThrow( new CloudResponseError( 'Invalid Assistant Cloud response for "run stream": expected a response body', ), ); }); it("rejects and cancels run responses with the wrong content type", async () => { const cancel = vi.fn(); const body = new ReadableStream({ cancel }); vi.stubGlobal( "fetch", vi.fn().mockResolvedValue( new Response(body, { headers: { "Content-Type": "text/html; charset=utf-8" }, }), ), ); await expect(createCloud().runs.stream(streamBody)).rejects.toThrow( new CloudResponseError( 'Invalid Assistant Cloud response for "run stream": expected a "text/plain" content type, received "text/html; charset=utf-8"', ), ); expect(cancel).toHaveBeenCalledOnce(); }); it("rejects and cancels run responses without a content type", async () => { const cancel = vi.fn(); const body = new ReadableStream({ cancel }); vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response(body))); await expect(createCloud().runs.stream(streamBody)).rejects.toThrow( new CloudResponseError( 'Invalid Assistant Cloud response for "run stream": expected a "text/plain" content type, received no Content-Type header', ), ); expect(cancel).toHaveBeenCalledOnce(); }); it("pins the UI message stream protocol in assistant options", () => { const { protocol } = createCloud().runs.__internal_getAssistantOptions("assistant-id"); expect(protocol).toBe("ui-message-stream"); }); it("rejects cached auth headers after run auth is invalidated", async () => { const userAToken = createAccessToken("user-a"); const userBToken = createAccessToken("user-b"); let currentToken = userAToken; const api = new AssistantCloudAPI({ baseUrl: "https://test.example.com", authToken: async () => currentToken, }); const options = new AssistantCloudRuns(api).__internal_getAssistantOptions( "assistant-id", ); await api.initializeAuth(); const staleHeaders = options.headers(); currentToken = userBToken; api.invalidateAuth(); await expect(staleHeaders).rejects.toThrow("Authorization failed"); await expect(options.headers()).resolves.toMatchObject({ Authorization: `Bearer ${userBToken}`, }); }); it("uses the requested thread ID in assistant options", async () => { const { body } = createCloud().runs.__internal_getAssistantOptions("assistant-id"); await expect(body({ threadId: "remote-thread" })).resolves.toEqual({ assistant_id: "assistant-id", response_format: "vercel-ai-data-stream/v1", thread_id: "remote-thread", }); }); it("rejects assistant options without a thread ID", async () => { const { body } = createCloud().runs.__internal_getAssistantOptions("assistant-id"); await expect(body({})).rejects.toThrow( "Assistant Cloud runs need a thread", ); await expect(body()).rejects.toThrow("Assistant Cloud runs need a thread"); }); }); const createCloudRuns = () => { const makeRequest = vi.fn(); const api = { makeRequest } as unknown as AssistantCloudAPI; return { runs: new AssistantCloudRuns(api), makeRequest }; }; describe("AssistantCloudRuns responses", () => { it("validates reported run IDs", async () => { const { runs, makeRequest } = createCloudRuns(); const body = { thread_id: "thread-1", status: "completed" } as const; makeRequest.mockResolvedValueOnce({ run_id: "run-1" }); await expect(runs.report(body)).resolves.toEqual({ run_id: "run-1" }); makeRequest.mockResolvedValueOnce({}); await expect(runs.report(body)).rejects.toThrow( 'Invalid Assistant Cloud response for "run_id": expected a string', ); }); });