fix(providers): sent anthropic search metadata

Forwarded the active web search session id as Anthropic Messages metadata.user_id so enterprise gateways can attribute and rate-limit search calls consistently with the main streaming path.

Added a focused Anthropic web search request-shape regression test.

Fixes #2295
This commit is contained in:
roboomp
2026-06-11 08:14:46 +00:00
parent e6ee124d7c
commit 22f05cb1a3
3 changed files with 50 additions and 0 deletions
+1
View File
@@ -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
@@ -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,
@@ -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<string, unknown> | 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();
}
});
});