Merge PR #7195: fix(coding-agent): omit unsupported Anthropic search temperature (@will-bogusz)

This commit is contained in:
can1357
2026-08-01 20:13:38 +02:00
4 changed files with 66 additions and 3 deletions
+2 -2
View File
@@ -50,7 +50,7 @@
| `recency` | `"day" \| "week" \| "month" \| "year"` | No | Time filter. Only providers that implement it use it; code maps it for Brave, Perplexity, Tavily, SearXNG, Kagi, TinyFish, Firecrawl, and xAI. |
| `limit` | `number` | No | Max results to return. Usually becomes the provider request's result-count parameter when `num_search_results` is absent. TinyFish uses it for paginated fetches before slicing; xAI sends it as `search_parameters.max_search_results` when `num_search_results` is absent and also caps parsed sources/citations locally, defaulting to `10` and max `30`. |
| `max_tokens` | `number` | No | Passed through as provider token caps (`maxOutputTokens`, `max_tokens`, or xAI `max_output_tokens`) only by Anthropic, Gemini, xAI, and Perplexity API-key mode. Ignored by the other providers. |
| `temperature` | `number` | No | Passed through only by Anthropic, Gemini, xAI, and Perplexity API-key mode. Ignored by the other providers. |
| `temperature` | `number` | No | Passed through only by Anthropic models that support sampling parameters, Gemini, xAI, and Perplexity API-key mode. Ignored or omitted by the other provider/model paths. |
| `num_search_results` | `number` | No | Requested search breadth or local result cap. Most providers send it upstream. TinyFish clamps to `1..20` with default `10`, sends it as `num_results` per page, and uses paginated fetches before slicing. xAI sends it as `search_parameters.max_search_results` and caps parsed sources/citations locally with default `10` and max `30`. |
## Outputs
@@ -126,7 +126,7 @@ Streaming: none. `WebSearchTool.execute()` forwards its `AbortSignal` into `exec
- `ANTHROPIC_SEARCH_BASE_URL` — search-only base URL for either `ANTHROPIC_SEARCH_API_KEY` or fallback Anthropic credentials; overrides `ANTHROPIC_BASE_URL` (and `FOUNDRY_BASE_URL` in Foundry mode); defaults to `https://api.anthropic.com`.
- `ANTHROPIC_SEARCH_MODEL` — search model; defaults to `claude-haiku-4-5`.
- Querying: Claude Messages API with web-search tool enabled.
- `max_tokens` and `temperature` pass through.
- `max_tokens` passes through. `temperature` passes through only for models that support sampling parameters; it is omitted for Opus 4.7+, Sonnet 5+, and Fable/Mythos 5+ because those APIs reject sampling parameters.
- `limit` and `num_search_results` are collapsed together before dispatch: `num_results = params.numSearchResults ?? params.limit`.
- Output may include `answer`, `sources`, `citations`, `searchQueries`, `usage.searchRequests`, `model`, `requestId`.
- **Codex** — `packages/coding-agent/src/web/search/providers/codex.ts`
+1
View File
@@ -25,6 +25,7 @@
### Added
- Added `requestIdFormat` (`"string"` | `"number"`, default `"string"`) to MCP server config, honored by the stdio, HTTP, and SSE transports. JSON-RPC 2.0 permits both id shapes, but Apple's `xcrun mcpbridge` decodes `id` as an integer only and silently drops OMP's snowflake strings (`mcpbridge.DecodeError Code=1`), hanging every request until it times out. Opting a server into `"number"` allocates per-connection sequential integer ids instead. The option is OMP-specific, so set it in an OMP-owned config (`.omp/mcp.json`, `~/.omp/agent/mcp.json`, a project `mcp.json`/`.mcp.json`, or an OMP plugin); servers imported from another tool's config ignore it ([#7053](https://github.com/can1357/oh-my-pi/issues/7053)).
- Fixed Anthropic web search sending unsupported temperature parameters to sampling-restricted Claude models ([#7195](https://github.com/can1357/oh-my-pi/pull/7195) by [@will-bogusz](https://github.com/will-bogusz)).
## [17.2.2] - 2026-07-31
@@ -19,6 +19,7 @@ import {
withAuth,
wrapFetchForCch,
} from "@oh-my-pi/pi-ai";
import { hasOpus47ApiRestrictions } from "@oh-my-pi/pi-catalog/identity/family";
import { $env } from "@oh-my-pi/pi-utils";
import type {
AnthropicApiResponse,
@@ -176,7 +177,8 @@ async function callSearch(
body.metadata = { user_id: metadataUserId };
}
if (temperature !== undefined) {
// Opus 4.7+, Sonnet 5+, and Fable/Mythos 5 reject sampling parameters with a 400.
if (temperature !== undefined && !hasOpus47ApiRestrictions(model)) {
body.temperature = temperature;
}
@@ -25,6 +25,20 @@ function makeCaptureFetch(): { fetch: FetchImpl; body: () => Record<string, unkn
return { fetch, body: () => captured };
}
function withSearchModel(model: string) {
const original = Bun.env.ANTHROPIC_SEARCH_MODEL;
Bun.env.ANTHROPIC_SEARCH_MODEL = model;
return {
[Symbol.dispose]() {
if (original === undefined) {
delete Bun.env.ANTHROPIC_SEARCH_MODEL;
} else {
Bun.env.ANTHROPIC_SEARCH_MODEL = original;
}
},
};
}
describe("Anthropic search request body", () => {
it("forwards the raw session id as metadata.user_id for API-key auth", async () => {
using tempDir = TempDir.createSync("@pi-anthropic-search-apikey-");
@@ -127,4 +141,50 @@ describe("Anthropic search request body", () => {
authStorage.close();
}
});
it("omits temperature for sampling-restricted models", async () => {
using _model = withSearchModel("claude-opus-5");
using tempDir = TempDir.createSync("@pi-anthropic-search-opus-");
const authStorage = await CodingAuthStorage.create(path.join(tempDir.path(), "auth.db"));
try {
authStorage.setRuntimeApiKey("anthropic", "test-key");
const cap = makeCaptureFetch();
await searchAnthropic({
query: "sampling compatibility",
systemPrompt: "Use web search.",
temperature: 0.1,
authStorage,
fetch: cap.fetch,
});
expect(cap.body()?.model).toBe("claude-opus-5");
expect(cap.body()).not.toHaveProperty("temperature");
} finally {
authStorage.close();
}
});
it("preserves temperature for compatible models", async () => {
using _model = withSearchModel("claude-haiku-4-5");
using tempDir = TempDir.createSync("@pi-anthropic-search-haiku-");
const authStorage = await CodingAuthStorage.create(path.join(tempDir.path(), "auth.db"));
try {
authStorage.setRuntimeApiKey("anthropic", "test-key");
const cap = makeCaptureFetch();
await searchAnthropic({
query: "sampling compatibility",
systemPrompt: "Use web search.",
temperature: 0.1,
authStorage,
fetch: cap.fetch,
});
expect(cap.body()?.model).toBe("claude-haiku-4-5");
expect(cap.body()?.temperature).toBe(0.1);
} finally {
authStorage.close();
}
});
});