diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index c1b019efa..9c6c24593 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -45,6 +45,7 @@ ### Fixed +- Fixed Anthropic web search requests to include `metadata.user_id` from the active session id, matching the main Messages path for gateway attribution ([#2295](https://github.com/can1357/oh-my-pi/issues/2295)). - Fixed `irc` live message delivery so successfully handed-off messages are no longer enqueued as mailbox mail, so they do not inflate unread `irc` counts - Fixed `irc send` with `await: true` to wait for a fresh reply to the current call instead of consuming previously buffered messages - Fixed main-session chat output to stop duplicating outbound `irc` sends from the main agent as relay cards diff --git a/packages/coding-agent/src/web/search/providers/anthropic.ts b/packages/coding-agent/src/web/search/providers/anthropic.ts index a99a494f1..3e354373c 100644 --- a/packages/coding-agent/src/web/search/providers/anthropic.ts +++ b/packages/coding-agent/src/web/search/providers/anthropic.ts @@ -82,6 +82,7 @@ function buildSystemBlocks( * @param auth - Authentication configuration (API key or OAuth) * @param model - Model identifier to use * @param query - Search query from the user + * @param sessionId - Optional session id forwarded as Anthropic metadata.user_id * @param systemPrompt - Optional system prompt for guiding response style * @returns Raw API response from Anthropic * @throws {SearchProviderError} If the API request fails @@ -90,6 +91,7 @@ async function callSearch( auth: AnthropicAuthConfig, model: string, query: string, + sessionId?: string, systemPrompt?: string, maxTokens?: number, temperature?: number, @@ -113,6 +115,10 @@ async function callSearch( ], }; + if (sessionId) { + body.metadata = { user_id: sessionId }; + } + if (temperature !== undefined) { body.temperature = temperature; } @@ -280,6 +286,7 @@ export async function searchAnthropic( buildAnthropicAuthConfig(key, searchBaseUrl), model, params.query, + "authStorage" in params ? params.sessionId : undefined, systemPrompt, maxTokens, params.temperature, diff --git a/packages/coding-agent/test/web/search/anthropic.test.ts b/packages/coding-agent/test/web/search/anthropic.test.ts new file mode 100644 index 000000000..bbdd5a062 --- /dev/null +++ b/packages/coding-agent/test/web/search/anthropic.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from "bun:test"; +import * as path from "node:path"; +import type { FetchImpl } from "@oh-my-pi/pi-ai"; +import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; +import { searchAnthropic } from "@oh-my-pi/pi-coding-agent/web/search/providers/anthropic"; +import { TempDir } from "@oh-my-pi/pi-utils"; + +describe("Anthropic search request body", () => { + it("passes the session id as Messages metadata.user_id", async () => { + using tempDir = TempDir.createSync("@pi-anthropic-search-"); + const authStorage = await AuthStorage.create(path.join(tempDir.path(), "auth.db")); + try { + authStorage.setRuntimeApiKey("anthropic", "test-key"); + + let capturedBody: Record | undefined; + const fetchMock: FetchImpl = async (_input, init) => { + capturedBody = JSON.parse(String(init?.body)); + return new Response( + JSON.stringify({ + id: "msg_test", + model: "claude-haiku-4-5", + content: [], + usage: { input_tokens: 1, output_tokens: 2 }, + }), + { status: 200, headers: { "Content-Type": "application/json" } }, + ); + }; + + await searchAnthropic({ + query: "gateway attribution requirements", + systemPrompt: "Use web search.", + sessionId: "session-2295", + authStorage, + fetch: fetchMock, + }); + + expect(capturedBody?.metadata).toEqual({ user_id: "session-2295" }); + } finally { + authStorage.close(); + } + }); +});