diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index 0eff20893..54a2e331b 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -670,13 +670,13 @@ function detectOpenAICompletionsCompat(model: Model<"openai-completions">): Open const isCloudflareAiGateway = provider === "cloudflare-ai-gateway" || baseUrl.includes("gateway.ai.cloudflare.com"); const isNvidia = provider === "nvidia" || baseUrl.includes("integrate.api.nvidia.com"); const isAntLing = provider === "ant-ling" || baseUrl.includes("api.ant-ling.com"); + const isCerebras = provider === "cerebras" || baseUrl.includes("cerebras.ai"); const isTogetherReasoningOnly = isTogether && TOGETHER_REASONING_ONLY_MODELS.has(model.id); const isDeepSeek = provider === "deepseek" || baseUrl.toLowerCase().includes("deepseek.com"); const isNonStandard = isNvidia || - provider === "cerebras" || - baseUrl.includes("cerebras.ai") || + isCerebras || provider === "xai" || baseUrl.includes("api.x.ai") || isTogether || @@ -734,7 +734,7 @@ function detectOpenAICompletionsCompat(model: Model<"openai-completions">): Open chatTemplateKwargs: {}, chatTemplateArgs: {}, zaiToolStream: false, - supportsStrictMode: !isMoonshot && !isTogether && !isCloudflareAiGateway && !isNvidia, + supportsStrictMode: !isMoonshot && !isTogether && !isCloudflareAiGateway && !isNvidia && !isCerebras, supportsOpenAIGrammarTools: false, supportsMidConvoSystemMessages: false, supportsMidConvoToolAdditions: false, diff --git a/packages/ai/src/api/openai-completions.ts b/packages/ai/src/api/openai-completions.ts index cd3ef7bd3..e28e12af5 100644 --- a/packages/ai/src/api/openai-completions.ts +++ b/packages/ai/src/api/openai-completions.ts @@ -1597,12 +1597,12 @@ function detectCompat(model: Model<"openai-completions">): ResolvedOpenAIComplet const isCloudflareAiGateway = provider === "cloudflare-ai-gateway" || baseUrl.includes("gateway.ai.cloudflare.com"); const isNvidia = provider === "nvidia" || baseUrl.includes("integrate.api.nvidia.com"); const isAntLing = provider === "ant-ling" || baseUrl.includes("api.ant-ling.com"); + const isCerebras = provider === "cerebras" || baseUrl.includes("cerebras.ai"); const isDeepSeek = provider === "deepseek" || baseUrl.toLowerCase().includes("deepseek.com"); const isNonStandard = isNvidia || - provider === "cerebras" || - baseUrl.includes("cerebras.ai") || + isCerebras || provider === "xai" || baseUrl.includes("api.x.ai") || isTogether || @@ -1661,7 +1661,7 @@ function detectCompat(model: Model<"openai-completions">): ResolvedOpenAIComplet zaiToolStream: false, supportsThinkingTokenBudget: false, thinkingTokenBudgetField: undefined, - supportsStrictMode: !isMoonshot && !isTogether && !isCloudflareAiGateway && !isNvidia, + supportsStrictMode: !isMoonshot && !isTogether && !isCloudflareAiGateway && !isNvidia && !isCerebras, supportsOpenAIGrammarTools: false, supportsMidConvoSystemMessages: false, supportsMidConvoToolAdditions: false, diff --git a/packages/ai/test/cache-retention.test.ts b/packages/ai/test/cache-retention.test.ts index 980d54938..e03807ee5 100644 --- a/packages/ai/test/cache-retention.test.ts +++ b/packages/ai/test/cache-retention.test.ts @@ -535,5 +535,70 @@ describe("Cache Retention (PI_CACHE_RETENTION)", () => { expect(capturedPayload?.prompt_cache_key).toBeUndefined(); expect(capturedPayload?.prompt_cache_retention).toBeUndefined(); }); + + it.each([MODELS.cerebras["gpt-oss-120b"], MODELS.cerebras["qwen-3.8-27b"]] as const)( + "should omit strict field on tools for cerebras/$id", + async (metadata) => { + const model = metadata as Model<"openai-completions">; + + const contextWithTools = { + messages: [ + { + role: "system" as const, + content: "test", + toolsAdded: [ + { + name: "t1", + description: "strict tool", + parameters: { + type: "object" as const, + properties: { x: { type: "string" } }, + required: ["x"], + }, + constrainedSampling: { type: "json_schema" as const }, + }, + { + name: "t2", + description: "non-strict tool", + parameters: { + type: "object" as const, + properties: { y: { type: "string" } }, + required: ["y"], + }, + }, + ], + timestamp: 0, + }, + { role: "user" as const, content: "hello", timestamp: 1 }, + ], + }; + + let capturedPayload: any; + + try { + const s = streamOpenAICompletions(model, contextWithTools as any, { + apiKey: "fake-key", + sessionId: "test", + onPayload: stopAfterPayload((payload: any) => { + capturedPayload = payload; + }), + }); + + for await (const event of s) { + if (event.type === "error") break; + } + } catch { + // Expected to fail + } + + expect(model.compat?.supportsStrictMode).toBe(false); + expect(capturedPayload).toBeDefined(); + const tools = capturedPayload?.tools as any[] | undefined; + expect(tools).toBeDefined(); + for (const tool of tools!) { + expect(tool.function).not.toHaveProperty("strict"); + } + }, + ); }); });