diff --git a/packages/client/src/ai-model.ts b/packages/client/src/ai-model.ts index cf23e9d4..f2be6ada 100644 --- a/packages/client/src/ai-model.ts +++ b/packages/client/src/ai-model.ts @@ -23,7 +23,7 @@ export const createClientAiModel = (config: { }) => { switch (config.provider) { case "anthropic": - return createAnthropic({ baseURL: config.baseUrl, apiKey: config.apiKey, fetch: config.fetch })(config.modelId); + return createAnthropic({ baseURL: config.baseUrl, apiKey: config.apiKey, fetch: config.fetch, headers: { "anthropic-dangerous-direct-browser-access": "true" } })(config.modelId); case "google": return createGoogle({ baseURL: config.baseUrl, apiKey: config.apiKey, fetch: config.fetch })(config.modelId); default: diff --git a/packages/client/src/ai-provider-direct.ts b/packages/client/src/ai-provider-direct.ts index 9f04ed89..85fe693f 100644 --- a/packages/client/src/ai-provider-direct.ts +++ b/packages/client/src/ai-provider-direct.ts @@ -14,6 +14,7 @@ export const discoverAiModels = async ( if (config.provider === "anthropic") { headers.set("x-api-key", config.apiKey); headers.set("anthropic-version", "2023-06-01"); + headers.set("anthropic-dangerous-direct-browser-access", "true"); } else if (config.provider === "google") { headers.set("x-goog-api-key", config.apiKey); } else { diff --git a/packages/client/src/client-only-ai.test.mjs b/packages/client/src/client-only-ai.test.mjs index 7e7e53ea..ae90f416 100644 --- a/packages/client/src/client-only-ai.test.mjs +++ b/packages/client/src/client-only-ai.test.mjs @@ -54,3 +54,23 @@ test("infographic tools and model loop run on the client after credential prepar expect(paths).toEqual(["/api/v1/ai/infographic-agent/prepare"]);expect(calls).toBe(2); expect(events).toContainEqual({type:"proposal",...proposal});expect(events.at(-1)).toEqual({type:"finish"}); }); + +test("Anthropic client generation and model discovery opt in to browser-direct access", async () => { + const { buildAiDirectProviderRequest } = await import("@edgeever/shared/ai-generation"); + const prepared = { ...credential, provider: "anthropic", baseUrl: "https://api.anthropic.com/v1", system: "Reply", prompt: "OK", maxOutputTokens: 16 }; + const request = buildAiDirectProviderRequest(prepared); + expect(request.headers["anthropic-dangerous-direct-browser-access"]).toBe("true"); + const { createClientAiModel } = await import("./ai-model.ts"); + const { generateText } = await import("ai"); + const paths = []; + const fetch = async (url, init) => { + paths.push(String(url));expect(new Headers(init.headers).get("anthropic-dangerous-direct-browser-access")).toBe("true"); + if (String(url).endsWith("/models")) return Response.json({ data: [{ id: "claude-test" }] }); + return Response.json({ id: "msg-1", type: "message", role: "assistant", model: "claude-test", content: [{ type: "text", text: "OK" }], stop_reason: "end_turn", usage: { input_tokens: 1, output_tokens: 1 } }); + }; + const result = await generateText({ model: createClientAiModel({ ...prepared, fetch }), prompt: "OK", maxRetries: 0 }); + expect(result.text).toBe("OK"); + const { discoverAiModels } = await import("./ai-provider-direct.ts"); + expect(await discoverAiModels(prepared, fetch)).toEqual([{ modelId: "claude-test", displayName: "claude-test" }]); + expect(paths).toHaveLength(2); +}); diff --git a/packages/shared/src/ai-generation.ts b/packages/shared/src/ai-generation.ts index dabae9d8..969662a0 100644 --- a/packages/shared/src/ai-generation.ts +++ b/packages/shared/src/ai-generation.ts @@ -209,6 +209,7 @@ export const buildAiDirectProviderRequest = ( accept: "text/event-stream", "x-api-key": prepared.apiKey, "anthropic-version": "2023-06-01", + "anthropic-dangerous-direct-browser-access": "true", }, body: JSON.stringify({ model: prepared.modelId,