Merge remote-tracking branch 'origin/main' into model-registry
This commit is contained in:
@@ -2,6 +2,30 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- When Amazon Bedrock rejects an unsupported data retention mode, the error now links the AWS data retention documentation ([#5561](https://github.com/earendil-works/pi/pull/5561) by [@unexge](https://github.com/unexge)).
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed Claude Fable 5 thinking-off requests to omit Anthropic's unsupported `thinking.type: "disabled"` payload ([#5567](https://github.com/earendil-works/pi/pull/5567) by [@tmustier](https://github.com/tmustier)).
|
||||
|
||||
## [0.79.1] - 2026-06-09
|
||||
|
||||
### Added
|
||||
|
||||
- Added Claude Fable 5 to Anthropic and Amazon Bedrock model metadata, with adaptive thinking and `xhigh` effort support.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed Amazon Bedrock inference profile ARN region resolution to prefer the ARN's embedded region over `AWS_REGION` ([#5527](https://github.com/earendil-works/pi/pull/5527) by [@AJM10565](https://github.com/AJM10565)).
|
||||
- Fixed z.ai thinking-off requests to send the provider's `thinking: { type: "disabled" }` compatibility parameter ([#5330](https://github.com/earendil-works/pi/issues/5330)).
|
||||
- Fixed OpenCode completions model metadata to send explicit `maxTokens` as `max_tokens` ([#5331](https://github.com/earendil-works/pi/issues/5331)).
|
||||
- Fixed Moonshot Kimi thinking-off requests to send the provider's `thinking: { type: "disabled" }` compatibility parameter ([#5531](https://github.com/earendil-works/pi/issues/5531)).
|
||||
- Fixed Azure OpenAI Responses requests to disable server-side response storage ([#5530](https://github.com/earendil-works/pi/issues/5530)).
|
||||
- Fixed Azure GPT-5.4 and GPT-5.5 context window metadata to 1,050,000 tokens, matching Azure Foundry deployments instead of OpenAI's 272k limit ([#5559](https://github.com/earendil-works/pi/issues/5559)).
|
||||
- Fixed OpenAI and Azure GPT-5 Pro `maxTokens` metadata to 128,000, correcting an upstream value that duplicated the 272,000 input sub-limit as the output limit ([#5559](https://github.com/earendil-works/pi/issues/5559)).
|
||||
|
||||
## [0.79.0] - 2026-06-08
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed OpenAI Responses custom providers to honor `compat.supportsDeveloperRole: false` for reasoning models ([#5456](https://github.com/earendil-works/pi/issues/5456)).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@earendil-works/pi-ai",
|
||||
"version": "0.78.1",
|
||||
"version": "0.79.1",
|
||||
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -92,7 +92,6 @@ const TOGETHER_TOGGLE_REASONING_EFFORT_COMPAT: OpenAICompletionsCompat = {
|
||||
};
|
||||
const TOGETHER_REASONING_ONLY_MODELS = new Set([
|
||||
"deepseek-ai/DeepSeek-R1",
|
||||
"MiniMaxAI/MiniMax-M2.5",
|
||||
"MiniMaxAI/MiniMax-M2.7",
|
||||
]);
|
||||
const TOGETHER_REASONING_EFFORT_MODELS = new Set(["openai/gpt-oss-20b", "openai/gpt-oss-120b"]);
|
||||
@@ -233,7 +232,8 @@ function isAnthropicAdaptiveThinkingModel(modelId: string): boolean {
|
||||
modelId.includes("opus-4-8") ||
|
||||
modelId.includes("opus-4.8") ||
|
||||
modelId.includes("sonnet-4-6") ||
|
||||
modelId.includes("sonnet-4.6")
|
||||
modelId.includes("sonnet-4.6") ||
|
||||
modelId.includes("fable-5")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -295,6 +295,12 @@ function applyThinkingLevelMetadata(model: Model<any>): void {
|
||||
) {
|
||||
mergeThinkingLevelMap(model, { xhigh: "xhigh" });
|
||||
}
|
||||
if (
|
||||
(model.api === "anthropic-messages" || model.api === "bedrock-converse-stream") &&
|
||||
model.id.includes("fable-5")
|
||||
) {
|
||||
mergeThinkingLevelMap(model, { off: null, xhigh: "xhigh" });
|
||||
}
|
||||
if (model.api === "anthropic-messages" && isAnthropicAdaptiveThinkingModel(model.id)) {
|
||||
mergeAnthropicMessagesCompat(model, { forceAdaptiveThinking: true });
|
||||
}
|
||||
@@ -1058,6 +1064,10 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
|
||||
}
|
||||
}
|
||||
|
||||
if (api === "openai-completions") {
|
||||
compat = { ...(compat ?? {}), maxTokensField: "max_tokens" };
|
||||
}
|
||||
|
||||
models.push({
|
||||
id: modelId,
|
||||
name: m.name || modelId,
|
||||
@@ -1216,6 +1226,7 @@ async function loadModelsDevData(): Promise<Model<any>[]> {
|
||||
supportsReasoningEffort: false,
|
||||
maxTokensField: "max_tokens",
|
||||
supportsStrictMode: false,
|
||||
thinkingFormat: "deepseek",
|
||||
};
|
||||
|
||||
for (const { key, provider, baseUrl } of moonshotVariants) {
|
||||
@@ -1355,6 +1366,11 @@ async function generateModels() {
|
||||
candidate.contextWindow = 272000;
|
||||
candidate.maxTokens = 128000;
|
||||
}
|
||||
// models.dev reports gpt-5-pro output as 272000 (a duplicate of the input sub-limit);
|
||||
// the actual max output is 128000. Also propagates to the derived Azure clone.
|
||||
if (candidate.provider === "openai" && candidate.id === "gpt-5-pro") {
|
||||
candidate.maxTokens = 128000;
|
||||
}
|
||||
// Keep selected OpenRouter model metadata stable until upstream settles.
|
||||
if (candidate.provider === "openrouter" && candidate.id === "moonshotai/kimi-k2.5") {
|
||||
candidate.cost.input = 0.41;
|
||||
@@ -2053,6 +2069,12 @@ async function generateModels() {
|
||||
];
|
||||
allModels.push(...vertexModels);
|
||||
|
||||
// Azure Foundry deploys these with larger context windows than OpenAI's own API,
|
||||
// which caps gpt-5.4/gpt-5.5 at 272k. See models-sold-directly-by-azure docs.
|
||||
const AZURE_CONTEXT_WINDOW_OVERRIDES: Record<string, number> = {
|
||||
"gpt-5.4": 1050000,
|
||||
"gpt-5.5": 1050000,
|
||||
};
|
||||
const azureOpenAiModels: Model<Api>[] = allModels
|
||||
.filter((model) => model.provider === "openai" && model.api === "openai-responses")
|
||||
.map((model) => ({
|
||||
@@ -2060,6 +2082,7 @@ async function generateModels() {
|
||||
api: "azure-openai-responses",
|
||||
provider: "azure-openai-responses",
|
||||
baseUrl: "",
|
||||
contextWindow: AZURE_CONTEXT_WINDOW_OVERRIDES[model.id] ?? model.contextWindow,
|
||||
}));
|
||||
allModels.push(...azureOpenAiModels);
|
||||
|
||||
|
||||
@@ -440,6 +440,36 @@ export const IMAGE_MODELS = {
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"sourceful/riverflow-v2.5-fast": {
|
||||
id: "sourceful/riverflow-v2.5-fast",
|
||||
name: "Sourceful: Riverflow V2.5 Fast",
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"sourceful/riverflow-v2.5-pro": {
|
||||
id: "sourceful/riverflow-v2.5-pro",
|
||||
name: "Sourceful: Riverflow V2.5 Pro",
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"x-ai/grok-imagine-image-quality": {
|
||||
id: "x-ai/grok-imagine-image-quality",
|
||||
name: "xAI: Grok Imagine Image Quality",
|
||||
|
||||
+409
-189
File diff suppressed because it is too large
Load Diff
@@ -143,10 +143,13 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream", BedrockOpt
|
||||
|
||||
// in Node.js/Bun environment only
|
||||
if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
|
||||
// Region resolution: explicit option > env vars > SDK default chain.
|
||||
// When AWS_PROFILE is set, we leave region undefined so the SDK can
|
||||
// resovle it from aws profile configs. Otherwise fall back to us-east-1.
|
||||
if (configuredRegion) {
|
||||
// Region resolution: ARN-embedded > explicit option > env vars > SDK default chain.
|
||||
// When the model ID is an inference profile ARN, extract the region from it.
|
||||
// This avoids conflicts with AWS_REGION set for other services.
|
||||
const arnRegionMatch = model.id.match(/^arn:aws(?:-[a-z0-9-]+)?:bedrock:([a-z0-9-]+):/);
|
||||
if (arnRegionMatch) {
|
||||
config.region = arnRegionMatch[1];
|
||||
} else if (configuredRegion) {
|
||||
config.region = configuredRegion;
|
||||
} else if (endpointRegion && useExplicitEndpoint) {
|
||||
config.region = endpointRegion;
|
||||
@@ -287,6 +290,13 @@ const BEDROCK_ERROR_PREFIXES: Record<string, string> = {
|
||||
ServiceUnavailableException: "Service unavailable",
|
||||
};
|
||||
|
||||
/**
|
||||
* Some models reject the account/profile's configured Bedrock data retention mode
|
||||
* (e.g. "data retention mode 'default' is not available for this model"). Point
|
||||
* users at the AWS docs explaining how to configure a supported mode.
|
||||
*/
|
||||
const BEDROCK_DATA_RETENTION_DOCS_URL = "https://docs.aws.amazon.com/bedrock/latest/userguide/data-retention.html";
|
||||
|
||||
/**
|
||||
* Format a Bedrock error with a human-readable prefix.
|
||||
* AWS SDK exceptions (both from `client.send()` and from stream event items)
|
||||
@@ -296,11 +306,14 @@ const BEDROCK_ERROR_PREFIXES: Record<string, string> = {
|
||||
*/
|
||||
function formatBedrockError(error: unknown): string {
|
||||
const message = error instanceof Error ? error.message : JSON.stringify(error);
|
||||
const dataRetentionHint = /data retention mode/i.test(message)
|
||||
? ` See ${BEDROCK_DATA_RETENTION_DOCS_URL} for supported data retention modes.`
|
||||
: "";
|
||||
if (error instanceof BedrockRuntimeServiceException) {
|
||||
const prefix = BEDROCK_ERROR_PREFIXES[error.name] ?? error.name;
|
||||
return `${prefix}: ${message}`;
|
||||
return `${prefix}: ${message}${dataRetentionHint}`;
|
||||
}
|
||||
return message;
|
||||
return `${message}${dataRetentionHint}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -525,13 +538,18 @@ function getModelMatchCandidates(modelId: string, modelName?: string): string[]
|
||||
function supportsAdaptiveThinking(modelId: string, modelName?: string): boolean {
|
||||
const candidates = getModelMatchCandidates(modelId, modelName);
|
||||
return candidates.some(
|
||||
(s) => s.includes("opus-4-6") || s.includes("opus-4-7") || s.includes("opus-4-8") || s.includes("sonnet-4-6"),
|
||||
(s) =>
|
||||
s.includes("opus-4-6") ||
|
||||
s.includes("opus-4-7") ||
|
||||
s.includes("opus-4-8") ||
|
||||
s.includes("sonnet-4-6") ||
|
||||
s.includes("fable-5"),
|
||||
);
|
||||
}
|
||||
|
||||
function supportsNativeXhighEffort(model: Model<"bedrock-converse-stream">): boolean {
|
||||
const candidates = getModelMatchCandidates(model.id, model.name);
|
||||
return candidates.some((s) => s.includes("opus-4-7") || s.includes("opus-4-8"));
|
||||
return candidates.some((s) => s.includes("opus-4-7") || s.includes("opus-4-8") || s.includes("fable-5"));
|
||||
}
|
||||
|
||||
function mapThinkingLevelToEffort(
|
||||
|
||||
@@ -200,7 +200,7 @@ export interface AnthropicOptions extends StreamOptions {
|
||||
* Effort level for adaptive thinking models.
|
||||
* Controls how much thinking Claude allocates:
|
||||
* - "max": Always thinks with no constraints (Opus 4.6 only)
|
||||
* - "xhigh": Highest reasoning level (Opus 4.7)
|
||||
* - "xhigh": Highest reasoning level (Opus 4.7+, Fable 5)
|
||||
* - "high": Always thinks, deep reasoning
|
||||
* - "medium": Moderate thinking, may skip for simple queries
|
||||
* - "low": Minimal thinking, skips for simple tasks
|
||||
@@ -711,7 +711,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOpti
|
||||
|
||||
/**
|
||||
* Map ThinkingLevel to Anthropic effort levels for adaptive thinking.
|
||||
* Note: effort "max" is only valid on Opus 4.6, while Opus 4.7 supports "xhigh".
|
||||
* Note: effort "max" is only valid on Opus 4.6, while Opus 4.7+ and Fable 5 support "xhigh".
|
||||
*/
|
||||
function mapThinkingLevelToEffort(
|
||||
model: Model<"anthropic-messages">,
|
||||
@@ -972,7 +972,7 @@ function buildParams(
|
||||
display,
|
||||
};
|
||||
}
|
||||
} else if (options?.thinkingEnabled === false) {
|
||||
} else if (options?.thinkingEnabled === false && model.thinkingLevelMap?.off !== null) {
|
||||
params.thinking = { type: "disabled" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,6 +256,7 @@ function buildParams(
|
||||
input: messages,
|
||||
stream: true,
|
||||
prompt_cache_key: clampOpenAIPromptCacheKey(options?.sessionId),
|
||||
store: false,
|
||||
};
|
||||
|
||||
if (options?.maxTokens) {
|
||||
|
||||
@@ -554,7 +554,8 @@ function buildParams(
|
||||
}
|
||||
|
||||
if (compat.thinkingFormat === "zai" && model.reasoning) {
|
||||
(params as any).enable_thinking = !!options?.reasoningEffort;
|
||||
const zaiParams = params as typeof params & { thinking?: { type: "enabled" | "disabled" } };
|
||||
zaiParams.thinking = { type: options?.reasoningEffort ? "enabled" : "disabled" };
|
||||
} else if (compat.thinkingFormat === "qwen" && model.reasoning) {
|
||||
(params as any).enable_thinking = !!options?.reasoningEffort;
|
||||
} else if (compat.thinkingFormat === "qwen-chat-template" && model.reasoning) {
|
||||
|
||||
@@ -426,7 +426,7 @@ export interface OpenAICompletionsCompat {
|
||||
requiresThinkingAsText?: boolean;
|
||||
/** Whether all replayed assistant messages must include an empty reasoning_content field when reasoning is enabled. Default: auto-detected from URL. */
|
||||
requiresReasoningContentOnAssistantMessages?: boolean;
|
||||
/** Format for reasoning/thinking parameter. "openai" uses reasoning_effort, "openrouter" uses reasoning: { effort }, "deepseek" uses thinking: { type } plus reasoning_effort when supported, "together" uses reasoning: { enabled } plus reasoning_effort when supported, "zai" uses top-level enable_thinking: boolean, "qwen" uses top-level enable_thinking: boolean, "qwen-chat-template" uses chat_template_kwargs.enable_thinking, "string-thinking" uses top-level thinking: string, and "ant-ling" uses reasoning: { effort } only when the mapped effort is non-null. Default: "openai". */
|
||||
/** Format for reasoning/thinking parameter. "openai" uses reasoning_effort, "openrouter" uses reasoning: { effort }, "deepseek" uses thinking: { type } plus reasoning_effort when supported, "together" uses reasoning: { enabled } plus reasoning_effort when supported, "zai" uses thinking: { type }, "qwen" uses top-level enable_thinking: boolean, "qwen-chat-template" uses chat_template_kwargs.enable_thinking, "string-thinking" uses top-level thinking: string, and "ant-ling" uses reasoning: { effort } only when the mapped effort is non-null. Default: "openai". */
|
||||
thinkingFormat?:
|
||||
| "openai"
|
||||
| "openrouter"
|
||||
|
||||
@@ -3,8 +3,11 @@ import { getModels, getProviders } from "../src/models.ts";
|
||||
import type { Api, Model } from "../src/types.ts";
|
||||
|
||||
const EXPECTED_CURRENT_ADAPTIVE_THINKING_MODELS = [
|
||||
"anthropic/claude-fable-5",
|
||||
"anthropic/claude-opus-4-8",
|
||||
"opencode/claude-fable-5",
|
||||
"opencode/claude-opus-4-8",
|
||||
"vercel-ai-gateway/anthropic/claude-fable-5",
|
||||
"vercel-ai-gateway/anthropic/claude-opus-4.8",
|
||||
];
|
||||
|
||||
@@ -22,7 +25,7 @@ describe("Anthropic adaptive thinking model metadata", () => {
|
||||
|
||||
expect(flaggedModels).toEqual(expect.arrayContaining([...EXPECTED_CURRENT_ADAPTIVE_THINKING_MODELS].sort()));
|
||||
expect(flaggedModels).toEqual(
|
||||
flaggedModels.filter((modelId) => /(opus[-.]4[-.][678]|sonnet[-.]4[-.]6)/.test(modelId)),
|
||||
flaggedModels.filter((modelId) => /(opus[-.]4[-.][678]|sonnet[-.]4[-.]6|fable[-.]5)/.test(modelId)),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -83,6 +83,13 @@ describe("Anthropic forceAdaptiveThinking compat override", () => {
|
||||
expect(payload.output_config).toEqual({ effort: "medium" });
|
||||
});
|
||||
|
||||
it("uses adaptive thinking with native xhigh effort for Claude Fable 5", async () => {
|
||||
const payload = await capturePayload(getModel("anthropic", "claude-fable-5"), { reasoning: "xhigh" });
|
||||
|
||||
expect(payload.thinking).toEqual({ type: "adaptive", display: "summarized" });
|
||||
expect(payload.output_config).toEqual({ effort: "xhigh" });
|
||||
});
|
||||
|
||||
it("allows built-in adaptive models to opt out with compat.forceAdaptiveThinking false", async () => {
|
||||
const model: Model<"anthropic-messages"> = {
|
||||
...getModel("anthropic", "claude-opus-4-8"),
|
||||
|
||||
@@ -132,6 +132,13 @@ describe("Anthropic thinking disable payload", () => {
|
||||
expect(payload.output_config).toBeUndefined();
|
||||
});
|
||||
|
||||
it("omits thinking.type=disabled for Claude Fable 5 when thinking is off", async () => {
|
||||
const payload = await capturePayload(getModel("anthropic", "claude-fable-5"));
|
||||
|
||||
expect(payload.thinking).toBeUndefined();
|
||||
expect(payload.output_config).toBeUndefined();
|
||||
});
|
||||
|
||||
it("uses adaptive thinking for Claude Opus 4.8 when reasoning is enabled", async () => {
|
||||
const payload = await capturePayload(getModel("anthropic", "claude-opus-4-8"), { reasoning: "high" });
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ interface CapturedAzureClientOptions {
|
||||
|
||||
interface CapturedAzureResponsesPayload {
|
||||
prompt_cache_key?: string;
|
||||
store?: boolean;
|
||||
}
|
||||
|
||||
const azureMock = vi.hoisted(() => ({
|
||||
@@ -144,6 +145,16 @@ describe("azure-openai-responses base URL normalization", () => {
|
||||
expect(azureMock.lastParams?.prompt_cache_key).toBe("x".repeat(64));
|
||||
});
|
||||
|
||||
it("disables server-side response storage", async () => {
|
||||
const model = getModel("azure-openai-responses", "gpt-4o-mini");
|
||||
await streamAzureOpenAIResponses(model, context, {
|
||||
apiKey: "test-api-key",
|
||||
azureBaseUrl: "https://my-resource.openai.azure.com",
|
||||
}).result();
|
||||
|
||||
expect(azureMock.lastParams?.store).toBe(false);
|
||||
});
|
||||
|
||||
it("builds correct default URL from AZURE_OPENAI_RESOURCE_NAME", async () => {
|
||||
process.env.AZURE_OPENAI_RESOURCE_NAME = "my-resource";
|
||||
const model = getModel("azure-openai-responses", "gpt-4o-mini");
|
||||
|
||||
@@ -128,4 +128,30 @@ describe("bedrock endpoint resolution", () => {
|
||||
expect(config.endpoint).toBe("https://bedrock-vpc.example.com");
|
||||
expect(config.region).toBe("us-west-2");
|
||||
});
|
||||
|
||||
it("extracts region from inference profile ARN regardless of AWS_REGION", async () => {
|
||||
process.env.AWS_REGION = "us-east-1";
|
||||
const baseModel = getModel("amazon-bedrock", "us.anthropic.claude-opus-4-8");
|
||||
const model: Model<"bedrock-converse-stream"> = {
|
||||
...baseModel,
|
||||
id: "arn:aws:bedrock:us-west-2:123456789012:application-inference-profile/abc123",
|
||||
};
|
||||
|
||||
const config = await captureClientConfig(model);
|
||||
|
||||
expect(config.region).toBe("us-west-2");
|
||||
});
|
||||
|
||||
it("extracts region from GovCloud inference profile ARN", async () => {
|
||||
process.env.AWS_REGION = "us-east-1";
|
||||
const baseModel = getModel("amazon-bedrock", "us.anthropic.claude-opus-4-8");
|
||||
const model: Model<"bedrock-converse-stream"> = {
|
||||
...baseModel,
|
||||
id: "arn:aws-us-gov:bedrock:us-gov-west-1:123456789012:application-inference-profile/abc123",
|
||||
};
|
||||
|
||||
const config = await captureClientConfig(model);
|
||||
|
||||
expect(config.region).toBe("us-gov-west-1");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -83,6 +83,25 @@ describe("Bedrock thinking payload", () => {
|
||||
expect(payload.additionalModelRequestFields?.anthropic_beta).toBeUndefined();
|
||||
});
|
||||
|
||||
it("uses adaptive thinking for Claude Fable 5 when reasoning is enabled", async () => {
|
||||
const model = getModel("amazon-bedrock", "global.anthropic.claude-fable-5");
|
||||
|
||||
const payload = await capturePayload(model);
|
||||
|
||||
expect(payload.additionalModelRequestFields?.thinking).toEqual({ type: "adaptive", display: "summarized" });
|
||||
expect(payload.additionalModelRequestFields?.output_config).toEqual({ effort: "high" });
|
||||
expect(payload.additionalModelRequestFields?.anthropic_beta).toBeUndefined();
|
||||
});
|
||||
|
||||
it("maps xhigh reasoning to effort=xhigh for Claude Fable 5", async () => {
|
||||
const model = getModel("amazon-bedrock", "global.anthropic.claude-fable-5");
|
||||
|
||||
const payload = await capturePayload(model, { reasoning: "xhigh" });
|
||||
|
||||
expect(payload.additionalModelRequestFields?.thinking).toEqual({ type: "adaptive", display: "summarized" });
|
||||
expect(payload.additionalModelRequestFields?.output_config).toEqual({ effort: "xhigh" });
|
||||
});
|
||||
|
||||
it("omits display for GovCloud model ids on non-adaptive Claude thinking", async () => {
|
||||
const baseModel = getModel("amazon-bedrock", "us.anthropic.claude-sonnet-4-5-20250929-v1:0");
|
||||
const model: Model<"bedrock-converse-stream"> = {
|
||||
|
||||
@@ -1120,6 +1120,33 @@ describe("openai-completions tool_choice", () => {
|
||||
expect(params.reasoning_effort).toBeUndefined();
|
||||
});
|
||||
|
||||
it("sends max_tokens for OpenCode completions models", async () => {
|
||||
const cases = [getModel("opencode-go", "kimi-k2.6")!, getModel("opencode", "grok-build-0.1")!] as const;
|
||||
|
||||
for (const model of cases) {
|
||||
let payload: unknown;
|
||||
expect(model.compat?.maxTokensField).toBe("max_tokens");
|
||||
|
||||
await streamSimple(
|
||||
model,
|
||||
{
|
||||
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
|
||||
},
|
||||
{
|
||||
apiKey: "test",
|
||||
maxTokens: 123,
|
||||
onPayload: (params: unknown) => {
|
||||
payload = params;
|
||||
},
|
||||
},
|
||||
).result();
|
||||
|
||||
const params = (payload ?? mockState.lastParams) as { max_tokens?: number; max_completion_tokens?: number };
|
||||
expect(params.max_tokens).toBe(123);
|
||||
expect(params.max_completion_tokens).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it("omits reasoning effort for OpenCode Grok Build", async () => {
|
||||
const model = getModel("opencode", "grok-build-0.1")!;
|
||||
let payload: unknown;
|
||||
|
||||
@@ -20,7 +20,14 @@ describe("getSupportedThinkingLevels", () => {
|
||||
expect(getSupportedThinkingLevels(model!)).toContain("xhigh");
|
||||
});
|
||||
|
||||
it("does not include xhigh for non-Opus Anthropic models", () => {
|
||||
it("includes xhigh but not off for Anthropic Claude Fable 5 on anthropic-messages API", () => {
|
||||
const model = getModel("anthropic", "claude-fable-5");
|
||||
expect(model).toBeDefined();
|
||||
expect(getSupportedThinkingLevels(model!)).toContain("xhigh");
|
||||
expect(getSupportedThinkingLevels(model!)).not.toContain("off");
|
||||
});
|
||||
|
||||
it("does not include xhigh for Claude Sonnet 4.5", () => {
|
||||
const model = getModel("anthropic", "claude-sonnet-4-5");
|
||||
expect(model).toBeDefined();
|
||||
expect(getSupportedThinkingLevels(model!)).not.toContain("xhigh");
|
||||
@@ -79,4 +86,11 @@ describe("getSupportedThinkingLevels", () => {
|
||||
expect(model).toBeDefined();
|
||||
expect(getSupportedThinkingLevels(model!)).toContain("xhigh");
|
||||
});
|
||||
|
||||
it("includes xhigh but not off for Bedrock Claude Fable 5", () => {
|
||||
const model = getModel("amazon-bedrock", "global.anthropic.claude-fable-5");
|
||||
expect(model).toBeDefined();
|
||||
expect(getSupportedThinkingLevels(model!)).toContain("xhigh");
|
||||
expect(getSupportedThinkingLevels(model!)).not.toContain("off");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user