From dca243242677628063a4c716f893a2c71f28d6ff Mon Sep 17 00:00:00 2001 From: Simon Couch Date: Wed, 26 Aug 2026 10:06:06 -0700 Subject: [PATCH 1/5] add DeepSeek V4 Flash model profile --- packages/ai-config/README.md | 2 +- packages/ai-config/src/index.ts | 1 + .../__tests__/infer.test.ts | 4 +-- .../src/model-capabilities/gemma-helpers.ts | 9 +++-- .../ai-config/src/model-capabilities/infer.ts | 16 ++++----- .../model-capabilities/model-apis-helpers.ts | 25 ++++++++++---- packages/ai-config/src/types.ts | 8 +++-- .../src/__tests__/utils.test.ts | 31 +++++++++++------ .../src/model-clients/ModelClient.ts | 12 +++++-- .../src/model-clients/PositAiClient.ts | 2 +- packages/ai-provider-bridge/src/types.ts | 7 ++-- packages/ai-provider-bridge/src/utils.ts | 33 +++++++++++-------- 12 files changed, 93 insertions(+), 57 deletions(-) diff --git a/packages/ai-config/README.md b/packages/ai-config/README.md index 2ed4969..a455e7d 100644 --- a/packages/ai-config/README.md +++ b/packages/ai-config/README.md @@ -174,7 +174,7 @@ import type { InferredModelCapabilities } from "ai-config"; The per-provider `get*ModelCapabilities(modelId)` helpers are pure, regex-driven lookups that map a provider-specific model id to a `Partial` (or `undefined`/a default object when the id doesn't match that provider's family). `openaiMaxInputTokens(caps)` derives the OpenAI input-token ceiling from a capability object's context window and output-token reservation. -`inferModelCapabilities(providerId, modelId)` is the single entry point that ties the tables together: it merges a conservative generic baseline (128k context, tools on, no images, no web search) under whichever provider-family table applies, with the table's values winning per field. It also derives `supportsImages` from a table's `supportedInputMediaTypes` when the table sets media types but leaves the flag itself unset, and resolves `protocol` for `snowflake-cortex` ids (Claude ids → `"anthropic-messages"`, everything else → `"openai-chat"` after stripping a leading `openai-` prefix). The result is shaped to spread straight into a `models.custom` entry, so it omits `requiresChatTemplateKwargs` (a runtime-only flag the strict custom-model schema rejects). It's the intended delegation target for any consumer that needs model capabilities without the bridge's dependency tree. +`inferModelCapabilities(providerId, modelId)` is the single entry point that ties the tables together: it merges a conservative generic baseline (128k context, tools on, no images, no web search) under whichever provider-family table applies, with the table's values winning per field. It also derives `supportsImages` from a table's `supportedInputMediaTypes` when the table sets media types but leaves the flag itself unset, and resolves `protocol` for `snowflake-cortex` ids (Claude ids → `"anthropic-messages"`, everything else → `"openai-chat"` after stripping a leading `openai-` prefix). The result is shaped to spread straight into a `models.custom` entry, so it omits `openAiChatThinkingProfile` (runtime-only request-shaping metadata the strict custom-model schema rejects). It's the intended delegation target for any consumer that needs model capabilities without the bridge's dependency tree. `ai-provider-bridge` re-exports the per-provider `get*ModelCapabilities` helpers and `openaiMaxInputTokens` from its own root for existing consumers, but not `inferModelCapabilities` — import that from `ai-config` directly. diff --git a/packages/ai-config/src/index.ts b/packages/ai-config/src/index.ts index be39d6b..075c262 100644 --- a/packages/ai-config/src/index.ts +++ b/packages/ai-config/src/index.ts @@ -62,6 +62,7 @@ export type { ModelInfoLike, ModelOverride, ModelsBlock, + OpenAiChatThinkingProfile, ProvidersConfig, ProvidersMap, ResolvedConnection, diff --git a/packages/ai-config/src/model-capabilities/__tests__/infer.test.ts b/packages/ai-config/src/model-capabilities/__tests__/infer.test.ts index a6d0680..6ebb9db 100644 --- a/packages/ai-config/src/model-capabilities/__tests__/infer.test.ts +++ b/packages/ai-config/src/model-capabilities/__tests__/infer.test.ts @@ -120,11 +120,11 @@ describe("inferModelCapabilities", () => { expect(kimiK3.maxOutputTokens).toBe(131_072); }); - it("omits requiresChatTemplateKwargs so the result fits a models.custom entry", () => { + it("omits openAiChatThinkingProfile so the result fits a models.custom entry", () => { // The Gemma table sets this runtime-only flag, but the strict custom-model // schema rejects it; inferModelCapabilities must not surface it. const caps = inferModelCapabilities("positai", "google/gemma-4-27b-it"); - expect(caps).not.toHaveProperty("requiresChatTemplateKwargs"); + expect(caps).not.toHaveProperty("openAiChatThinkingProfile"); }); it("produces a spread that validates against the strict customModelSchema", () => { diff --git a/packages/ai-config/src/model-capabilities/gemma-helpers.ts b/packages/ai-config/src/model-capabilities/gemma-helpers.ts index f167f25..aa0c477 100644 --- a/packages/ai-config/src/model-capabilities/gemma-helpers.ts +++ b/packages/ai-config/src/model-capabilities/gemma-helpers.ts @@ -42,9 +42,8 @@ function normalizeGemmaModelId(modelId: string): string | undefined { /** * Infer Gemma model capabilities from a model ID. * - * @returns A partial `ModelInfo` with family, thinking effort levels, and - * `requiresChatTemplateKwargs` for vLLM thinking support, - * or `undefined` for non-Gemma models. + * @returns A partial `ModelInfo` with family, thinking effort levels, and the + * vLLM thinking request profile, or `undefined` for non-Gemma models. */ export function getGemmaModelCapabilities(modelId: string): Partial | undefined { const normalized = normalizeGemmaModelId(modelId); @@ -57,7 +56,7 @@ export function getGemmaModelCapabilities(modelId: string): Partial | return { family: rule?.family ?? "gemma", thinkingEffortLevels: rule?.thinkingEffortLevels, - // Gemma 4 models served by vLLM require chat_template_kwargs to enable thinking - requiresChatTemplateKwargs: rule?.thinkingEffortLevels !== undefined, + openAiChatThinkingProfile: + rule?.thinkingEffortLevels !== undefined ? "chat-template-enable-thinking" : undefined, }; } diff --git a/packages/ai-config/src/model-capabilities/infer.ts b/packages/ai-config/src/model-capabilities/infer.ts index 2f7f5c6..1416e34 100644 --- a/packages/ai-config/src/model-capabilities/infer.ts +++ b/packages/ai-config/src/model-capabilities/infer.ts @@ -100,7 +100,7 @@ function familyDefaults(providerId: string, modelId: string): Partial; /** @@ -119,7 +119,7 @@ export function completeCapabilities( caps.supportedInputMediaTypes?.some((mediaType) => mediaType.startsWith("image/")) ? { ...caps, supportsImages: true } : caps; - const { requiresChatTemplateKwargs: _drop, ...inferred } = withDerivedImageSupport; + const { openAiChatThinkingProfile: _drop, ...inferred } = withDerivedImageSupport; return { ...inferred, maxContextLength: inferred.maxContextLength ?? GENERIC_BASELINE.maxContextLength, @@ -224,15 +224,15 @@ export function inferLitellmModelProfile(input: LitellmModelProfileInput): Litel * inference determined them. * * The result is shaped to spread directly into a `models.custom` entry, so it - * excludes `requiresChatTemplateKwargs`: that flag is a runtime request-shaping - * detail (it tells the vLLM client to send `chat_template_kwargs`), re-derived - * from the model id at request time by the bridge's positai path, and it is not - * a field the strict `customModelSchema` accepts. The capability tables still - * carry it for that runtime use; it is dropped only here, at the migration seam. + * excludes `openAiChatThinkingProfile`: that value is a runtime request-shaping + * detail re-derived from the model id at request time by the bridge's positai + * path, and it is not a field the strict `customModelSchema` accepts. The + * capability tables still carry it for that runtime use; it is dropped only + * here, at the migration seam. */ export function inferModelCapabilities( providerId: string, modelId: string, -): Omit { +): Omit { return completeCapabilities(familyDefaults(providerId, modelId)); } diff --git a/packages/ai-config/src/model-capabilities/model-apis-helpers.ts b/packages/ai-config/src/model-capabilities/model-apis-helpers.ts index 70eb52d..e143c8a 100644 --- a/packages/ai-config/src/model-capabilities/model-apis-helpers.ts +++ b/packages/ai-config/src/model-capabilities/model-apis-helpers.ts @@ -9,17 +9,16 @@ import type { InferredModelCapabilities as ModelInfo } from "../types.js"; * endpoint, keyed by exact model ID as returned by the Posit AI /models * endpoint. Adding a Model APIs model is one entry here. * - * Thinking is off by default for these models and streams back as - * `reasoning_content`. Models with `requiresChatTemplateKwargs` expose a binary - * toggle via the vLLM-style `chat_template_kwargs: { enable_thinking: true }` - * request field; models with named effort levels take a top-level OpenAI-style - * `reasoning_effort` instead. + * Thinking streams back as `reasoning_content`, but its request shape varies: + * binary vLLM toggles, top-level OpenAI-style effort, or DeepSeek's nested + * thinking and effort fields. The request profile keeps that wire knowledge + * with the capability entry rather than requiring model-id checks in clients. */ const MODEL_APIS_CAPABILITIES: Record> = { "zai-org/GLM-5.2": { family: "glm", thinkingEffortLevels: ["off", "on"], - requiresChatTemplateKwargs: true, + openAiChatThinkingProfile: "chat-template-enable-thinking", supportsImages: false, supportsToolResultImages: false, supportedInputMediaTypes: [], @@ -29,7 +28,7 @@ const MODEL_APIS_CAPABILITIES: Record> = { "moonshotai/Kimi-K2.7-Code": { family: "kimi", thinkingEffortLevels: ["off", "on"], - requiresChatTemplateKwargs: true, + openAiChatThinkingProfile: "chat-template-enable-thinking", supportedInputMediaTypes: ["image/png", "image/jpeg", "image/gif", "image/webp"], maxContextLength: 262_000, maxInputTokens: 262_000, @@ -37,6 +36,7 @@ const MODEL_APIS_CAPABILITIES: Record> = { "moonshotai/Kimi-K3": { family: "kimi", thinkingEffortLevels: ["off", "low", "high", "max"], + openAiChatThinkingProfile: "top-level-reasoning-effort", supportedInputMediaTypes: ["image/png", "image/jpeg", "image/gif", "image/webp"], // Kimi K3's documented max output (Moonshot API's default // max_completion_tokens). @@ -45,6 +45,17 @@ const MODEL_APIS_CAPABILITIES: Record> = { maxContextLength: 250_000, maxInputTokens: 250_000, }, + "deepseek-ai/DeepSeek-V4-Flash-0731": { + family: "deepseek-v4", + thinkingEffortLevels: ["off", "low", "high", "max"], + openAiChatThinkingProfile: "chat-template-deepseek", + supportsImages: false, + supportsToolResultImages: false, + supportedInputMediaTypes: [], + maxOutputTokens: 384_000, + maxContextLength: 200_000, + maxInputTokens: 200_000, + }, }; /** diff --git a/packages/ai-config/src/types.ts b/packages/ai-config/src/types.ts index 4ba9766..dfc7edc 100644 --- a/packages/ai-config/src/types.ts +++ b/packages/ai-config/src/types.ts @@ -232,6 +232,11 @@ export interface ResolvedProvider { // ModelInfoLike — local mirror of overridable ModelInfo fields // --------------------------------------------------------------------------- +export type OpenAiChatThinkingProfile = + | "top-level-reasoning-effort" + | "chat-template-enable-thinking" + | "chat-template-deepseek"; + /** * Subset of bridge ModelInfo fields that `resolveModels` operates on. * Defined locally so the pure entry has no bridge dependency. At the @@ -253,8 +258,7 @@ export interface ModelInfoLike { supportedInputMediaTypes?: string[]; supportsWebSearch: boolean; thinkingEffortLevels?: string[]; - /** Whether the model requires vLLM-style `chat_template_kwargs` to enable thinking. */ - requiresChatTemplateKwargs?: boolean; + openAiChatThinkingProfile?: OpenAiChatThinkingProfile; } /** diff --git a/packages/ai-provider-bridge/src/__tests__/utils.test.ts b/packages/ai-provider-bridge/src/__tests__/utils.test.ts index 49a1c5f..bf3ec52 100644 --- a/packages/ai-provider-bridge/src/__tests__/utils.test.ts +++ b/packages/ai-provider-bridge/src/__tests__/utils.test.ts @@ -12,24 +12,35 @@ import { } from "../utils"; describe("thinkingRequestFields", () => { - it("returns undefined when thinking is off or unset", () => { - expect(thinkingRequestFields(undefined, true)).toBeUndefined(); - expect(thinkingRequestFields("off", true)).toBeUndefined(); + it("omits fields when thinking or the request profile is unset", () => { + expect(thinkingRequestFields(undefined, "chat-template-deepseek")).toBeUndefined(); + expect(thinkingRequestFields("high", undefined)).toBeUndefined(); }); - it("maps a named effort level to a top-level reasoning_effort", () => { - expect(thinkingRequestFields("low", false)).toEqual({ reasoning_effort: "low" }); - expect(thinkingRequestFields("max", false)).toEqual({ reasoning_effort: "max" }); + it("maps named effort to a top-level reasoning_effort profile", () => { + expect(thinkingRequestFields("off", "top-level-reasoning-effort")).toBeUndefined(); + expect(thinkingRequestFields("low", "top-level-reasoning-effort")).toEqual({ + reasoning_effort: "low", + }); + expect(thinkingRequestFields("max", "top-level-reasoning-effort")).toEqual({ + reasoning_effort: "max", + }); }); - it("maps binary 'on' to chat_template_kwargs when the model requires it", () => { - expect(thinkingRequestFields("on", true)).toEqual({ + it("maps binary on to the enable_thinking chat-template profile", () => { + expect(thinkingRequestFields("off", "chat-template-enable-thinking")).toBeUndefined(); + expect(thinkingRequestFields("on", "chat-template-enable-thinking")).toEqual({ chat_template_kwargs: { enable_thinking: true }, }); }); - it("returns undefined for binary 'on' without the chat_template_kwargs flag", () => { - expect(thinkingRequestFields("on", false)).toBeUndefined(); + it("maps DeepSeek off and named efforts to its chat-template profile", () => { + expect(thinkingRequestFields("off", "chat-template-deepseek")).toEqual({ + chat_template_kwargs: { thinking: false }, + }); + expect(thinkingRequestFields("high", "chat-template-deepseek")).toEqual({ + chat_template_kwargs: { thinking: true, reasoning_effort: "high" }, + }); }); }); diff --git a/packages/ai-provider-bridge/src/model-clients/ModelClient.ts b/packages/ai-provider-bridge/src/model-clients/ModelClient.ts index f996a75..8728fc1 100644 --- a/packages/ai-provider-bridge/src/model-clients/ModelClient.ts +++ b/packages/ai-provider-bridge/src/model-clients/ModelClient.ts @@ -9,7 +9,13 @@ import type { ModelMessage } from "ai"; import type { StepLogger } from "../StepLogger"; -import type { AiToolWithJsonSchema, CancellationToken, LMStreamPart, Protocol } from "../types"; +import type { + AiToolWithJsonSchema, + CancellationToken, + LMStreamPart, + OpenAiChatThinkingProfile, + Protocol, +} from "../types"; /** * Parameters for a chat request. Shared across all ModelClient implementations @@ -34,8 +40,8 @@ export interface ModelClientChatParams { contextLength?: number; /** Whether provider-side web search should be enabled for this request. */ webSearchEnabled?: boolean; - /** Whether the model requires vLLM-style `chat_template_kwargs` to enable thinking. */ - requiresChatTemplateKwargs?: boolean; + /** Provider wire profile copied from ModelInfo; the receiving client owns serialization. */ + openAiChatThinkingProfile?: OpenAiChatThinkingProfile; /** * Whether the target model accepts image input at all. Used to decide how * tool-result images are transformed for APIs that can't embed images in diff --git a/packages/ai-provider-bridge/src/model-clients/PositAiClient.ts b/packages/ai-provider-bridge/src/model-clients/PositAiClient.ts index 07cf2ea..c94a1a7 100644 --- a/packages/ai-provider-bridge/src/model-clients/PositAiClient.ts +++ b/packages/ai-provider-bridge/src/model-clients/PositAiClient.ts @@ -240,7 +240,7 @@ export class PositAiClient implements ModelClient { } else if (normalizedProtocol === "openai-chat") { const thinkingFields = thinkingRequestFields( params.thinkingEffort, - params.requiresChatTemplateKwargs ?? false, + params.openAiChatThinkingProfile, ); // Use OpenAI-compatible provider with OAuth authentication diff --git a/packages/ai-provider-bridge/src/types.ts b/packages/ai-provider-bridge/src/types.ts index e6d1eff..681eb64 100644 --- a/packages/ai-provider-bridge/src/types.ts +++ b/packages/ai-provider-bridge/src/types.ts @@ -18,8 +18,8 @@ */ import type * as ai from "ai"; -import type { ResolvedProviderId } from "ai-config"; -export type { ResolvedProviderId }; +import type { OpenAiChatThinkingProfile, ResolvedProviderId } from "ai-config"; +export type { OpenAiChatThinkingProfile, ResolvedProviderId }; // Credential types re-exported from ai-credentials/types (single source of truth) export type { @@ -207,8 +207,7 @@ export interface ModelInfo { * for this model. */ supportsWebSearch: boolean; thinkingEffortLevels?: string[]; - /** Whether the model requires vLLM-style `chat_template_kwargs` to enable thinking. */ - requiresChatTemplateKwargs?: boolean; + openAiChatThinkingProfile?: OpenAiChatThinkingProfile; maxContextLength: number; } diff --git a/packages/ai-provider-bridge/src/utils.ts b/packages/ai-provider-bridge/src/utils.ts index 448aef2..054b7de 100644 --- a/packages/ai-provider-bridge/src/utils.ts +++ b/packages/ai-provider-bridge/src/utils.ts @@ -7,6 +7,8 @@ * Kept here to avoid depending on any consumer package. */ +import type { OpenAiChatThinkingProfile } from "./types"; + // --------------------------------------------------------------------------- // Thinking effort // --------------------------------------------------------------------------- @@ -17,28 +19,31 @@ export function isThinkingEnabled(effort: string | undefined): boolean { } /** - * Request-body fields that enable thinking on an OpenAI-chat-protocol model. - * - * Named effort levels (anything but the binary `"on"`) go out as the OpenAI-style - * top-level `reasoning_effort`. Binary-toggle models (`requiresChatTemplateKwargs`) - * take the vLLM-style `chat_template_kwargs` instead. + * Request-body fields for thinking on an OpenAI-chat-protocol model. The + * capability-owned profile selects the provider's wire shape without model-id + * inference in the client. * - * @returns Fields to merge into the request body, or `undefined` when thinking - * is off or the model has no way to enable it. + * @returns Fields to merge into the request body, or `undefined` when the + * provider default should apply. */ export function thinkingRequestFields( effort: string | undefined, - requiresChatTemplateKwargs: boolean, + profile: OpenAiChatThinkingProfile | undefined, ): Record | undefined { - if (!isThinkingEnabled(effort)) { + if (effort === undefined || profile === undefined) { return undefined; } - if (effort !== "on") { - return { reasoning_effort: effort }; + if (profile === "top-level-reasoning-effort") { + return isThinkingEnabled(effort) ? { reasoning_effort: effort } : undefined; + } + if (profile === "chat-template-enable-thinking") { + return effort === "on" ? { chat_template_kwargs: { enable_thinking: true } } : undefined; } - return requiresChatTemplateKwargs - ? { chat_template_kwargs: { enable_thinking: true } } - : undefined; + return { + chat_template_kwargs: isThinkingEnabled(effort) + ? { thinking: true, reasoning_effort: effort } + : { thinking: false }, + }; } // --------------------------------------------------------------------------- From 8226708f3ac7cc532e6cefcc754b4dad614f4a7a Mon Sep 17 00:00:00 2001 From: Simon Couch Date: Wed, 26 Aug 2026 13:57:52 -0700 Subject: [PATCH 2/5] simplify DeepSeek thinking support --- packages/ai-config/README.md | 2 +- packages/ai-config/src/index.ts | 1 - .../__tests__/infer.test.ts | 12 +++++- .../src/model-capabilities/gemma-helpers.ts | 9 +++-- .../ai-config/src/model-capabilities/infer.ts | 16 ++++---- .../model-capabilities/model-apis-helpers.ts | 15 ++++---- packages/ai-config/src/types.ts | 8 +--- .../src/__tests__/utils.test.ts | 32 ++++++++-------- .../src/model-clients/ModelClient.ts | 12 ++---- .../src/model-clients/PositAiClient.ts | 3 +- packages/ai-provider-bridge/src/types.ts | 7 ++-- packages/ai-provider-bridge/src/utils.ts | 37 +++++++++---------- 12 files changed, 77 insertions(+), 77 deletions(-) diff --git a/packages/ai-config/README.md b/packages/ai-config/README.md index a455e7d..2ed4969 100644 --- a/packages/ai-config/README.md +++ b/packages/ai-config/README.md @@ -174,7 +174,7 @@ import type { InferredModelCapabilities } from "ai-config"; The per-provider `get*ModelCapabilities(modelId)` helpers are pure, regex-driven lookups that map a provider-specific model id to a `Partial` (or `undefined`/a default object when the id doesn't match that provider's family). `openaiMaxInputTokens(caps)` derives the OpenAI input-token ceiling from a capability object's context window and output-token reservation. -`inferModelCapabilities(providerId, modelId)` is the single entry point that ties the tables together: it merges a conservative generic baseline (128k context, tools on, no images, no web search) under whichever provider-family table applies, with the table's values winning per field. It also derives `supportsImages` from a table's `supportedInputMediaTypes` when the table sets media types but leaves the flag itself unset, and resolves `protocol` for `snowflake-cortex` ids (Claude ids → `"anthropic-messages"`, everything else → `"openai-chat"` after stripping a leading `openai-` prefix). The result is shaped to spread straight into a `models.custom` entry, so it omits `openAiChatThinkingProfile` (runtime-only request-shaping metadata the strict custom-model schema rejects). It's the intended delegation target for any consumer that needs model capabilities without the bridge's dependency tree. +`inferModelCapabilities(providerId, modelId)` is the single entry point that ties the tables together: it merges a conservative generic baseline (128k context, tools on, no images, no web search) under whichever provider-family table applies, with the table's values winning per field. It also derives `supportsImages` from a table's `supportedInputMediaTypes` when the table sets media types but leaves the flag itself unset, and resolves `protocol` for `snowflake-cortex` ids (Claude ids → `"anthropic-messages"`, everything else → `"openai-chat"` after stripping a leading `openai-` prefix). The result is shaped to spread straight into a `models.custom` entry, so it omits `requiresChatTemplateKwargs` (a runtime-only flag the strict custom-model schema rejects). It's the intended delegation target for any consumer that needs model capabilities without the bridge's dependency tree. `ai-provider-bridge` re-exports the per-provider `get*ModelCapabilities` helpers and `openaiMaxInputTokens` from its own root for existing consumers, but not `inferModelCapabilities` — import that from `ai-config` directly. diff --git a/packages/ai-config/src/index.ts b/packages/ai-config/src/index.ts index 075c262..be39d6b 100644 --- a/packages/ai-config/src/index.ts +++ b/packages/ai-config/src/index.ts @@ -62,7 +62,6 @@ export type { ModelInfoLike, ModelOverride, ModelsBlock, - OpenAiChatThinkingProfile, ProvidersConfig, ProvidersMap, ResolvedConnection, diff --git a/packages/ai-config/src/model-capabilities/__tests__/infer.test.ts b/packages/ai-config/src/model-capabilities/__tests__/infer.test.ts index 6ebb9db..a6492e7 100644 --- a/packages/ai-config/src/model-capabilities/__tests__/infer.test.ts +++ b/packages/ai-config/src/model-capabilities/__tests__/infer.test.ts @@ -118,13 +118,21 @@ describe("inferModelCapabilities", () => { expect(kimiK3.maxContextLength).toBe(250_000); expect(kimiK3.maxInputTokens).toBe(250_000); expect(kimiK3.maxOutputTokens).toBe(131_072); + + const deepSeek = inferModelCapabilities("positai", "deepseek-ai/DeepSeek-V4-Flash-0731"); + expect(deepSeek.family).toBe("deepseek-v4"); + expect(deepSeek.thinkingEffortLevels).toEqual(["off", "low", "high", "max"]); + expect(deepSeek.supportsImages).toBe(false); + expect(deepSeek.maxContextLength).toBe(200_000); + expect(deepSeek.maxInputTokens).toBe(200_000); + expect(deepSeek.maxOutputTokens).toBe(384_000); }); - it("omits openAiChatThinkingProfile so the result fits a models.custom entry", () => { + it("omits requiresChatTemplateKwargs so the result fits a models.custom entry", () => { // The Gemma table sets this runtime-only flag, but the strict custom-model // schema rejects it; inferModelCapabilities must not surface it. const caps = inferModelCapabilities("positai", "google/gemma-4-27b-it"); - expect(caps).not.toHaveProperty("openAiChatThinkingProfile"); + expect(caps).not.toHaveProperty("requiresChatTemplateKwargs"); }); it("produces a spread that validates against the strict customModelSchema", () => { diff --git a/packages/ai-config/src/model-capabilities/gemma-helpers.ts b/packages/ai-config/src/model-capabilities/gemma-helpers.ts index aa0c477..f167f25 100644 --- a/packages/ai-config/src/model-capabilities/gemma-helpers.ts +++ b/packages/ai-config/src/model-capabilities/gemma-helpers.ts @@ -42,8 +42,9 @@ function normalizeGemmaModelId(modelId: string): string | undefined { /** * Infer Gemma model capabilities from a model ID. * - * @returns A partial `ModelInfo` with family, thinking effort levels, and the - * vLLM thinking request profile, or `undefined` for non-Gemma models. + * @returns A partial `ModelInfo` with family, thinking effort levels, and + * `requiresChatTemplateKwargs` for vLLM thinking support, + * or `undefined` for non-Gemma models. */ export function getGemmaModelCapabilities(modelId: string): Partial | undefined { const normalized = normalizeGemmaModelId(modelId); @@ -56,7 +57,7 @@ export function getGemmaModelCapabilities(modelId: string): Partial | return { family: rule?.family ?? "gemma", thinkingEffortLevels: rule?.thinkingEffortLevels, - openAiChatThinkingProfile: - rule?.thinkingEffortLevels !== undefined ? "chat-template-enable-thinking" : undefined, + // Gemma 4 models served by vLLM require chat_template_kwargs to enable thinking + requiresChatTemplateKwargs: rule?.thinkingEffortLevels !== undefined, }; } diff --git a/packages/ai-config/src/model-capabilities/infer.ts b/packages/ai-config/src/model-capabilities/infer.ts index 1416e34..2f7f5c6 100644 --- a/packages/ai-config/src/model-capabilities/infer.ts +++ b/packages/ai-config/src/model-capabilities/infer.ts @@ -100,7 +100,7 @@ function familyDefaults(providerId: string, modelId: string): Partial; /** @@ -119,7 +119,7 @@ export function completeCapabilities( caps.supportedInputMediaTypes?.some((mediaType) => mediaType.startsWith("image/")) ? { ...caps, supportsImages: true } : caps; - const { openAiChatThinkingProfile: _drop, ...inferred } = withDerivedImageSupport; + const { requiresChatTemplateKwargs: _drop, ...inferred } = withDerivedImageSupport; return { ...inferred, maxContextLength: inferred.maxContextLength ?? GENERIC_BASELINE.maxContextLength, @@ -224,15 +224,15 @@ export function inferLitellmModelProfile(input: LitellmModelProfileInput): Litel * inference determined them. * * The result is shaped to spread directly into a `models.custom` entry, so it - * excludes `openAiChatThinkingProfile`: that value is a runtime request-shaping - * detail re-derived from the model id at request time by the bridge's positai - * path, and it is not a field the strict `customModelSchema` accepts. The - * capability tables still carry it for that runtime use; it is dropped only - * here, at the migration seam. + * excludes `requiresChatTemplateKwargs`: that flag is a runtime request-shaping + * detail (it tells the vLLM client to send `chat_template_kwargs`), re-derived + * from the model id at request time by the bridge's positai path, and it is not + * a field the strict `customModelSchema` accepts. The capability tables still + * carry it for that runtime use; it is dropped only here, at the migration seam. */ export function inferModelCapabilities( providerId: string, modelId: string, -): Omit { +): Omit { return completeCapabilities(familyDefaults(providerId, modelId)); } diff --git a/packages/ai-config/src/model-capabilities/model-apis-helpers.ts b/packages/ai-config/src/model-capabilities/model-apis-helpers.ts index e143c8a..99cf751 100644 --- a/packages/ai-config/src/model-capabilities/model-apis-helpers.ts +++ b/packages/ai-config/src/model-capabilities/model-apis-helpers.ts @@ -9,16 +9,17 @@ import type { InferredModelCapabilities as ModelInfo } from "../types.js"; * endpoint, keyed by exact model ID as returned by the Posit AI /models * endpoint. Adding a Model APIs model is one entry here. * - * Thinking streams back as `reasoning_content`, but its request shape varies: - * binary vLLM toggles, top-level OpenAI-style effort, or DeepSeek's nested - * thinking and effort fields. The request profile keeps that wire knowledge - * with the capability entry rather than requiring model-id checks in clients. + * Thinking is off by default for these models and streams back as + * `reasoning_content`. Models with `requiresChatTemplateKwargs` expose a binary + * toggle via the vLLM-style `chat_template_kwargs: { enable_thinking: true }` + * request field; models with named effort levels take a top-level OpenAI-style + * `reasoning_effort` instead. */ const MODEL_APIS_CAPABILITIES: Record> = { "zai-org/GLM-5.2": { family: "glm", thinkingEffortLevels: ["off", "on"], - openAiChatThinkingProfile: "chat-template-enable-thinking", + requiresChatTemplateKwargs: true, supportsImages: false, supportsToolResultImages: false, supportedInputMediaTypes: [], @@ -28,7 +29,7 @@ const MODEL_APIS_CAPABILITIES: Record> = { "moonshotai/Kimi-K2.7-Code": { family: "kimi", thinkingEffortLevels: ["off", "on"], - openAiChatThinkingProfile: "chat-template-enable-thinking", + requiresChatTemplateKwargs: true, supportedInputMediaTypes: ["image/png", "image/jpeg", "image/gif", "image/webp"], maxContextLength: 262_000, maxInputTokens: 262_000, @@ -36,7 +37,6 @@ const MODEL_APIS_CAPABILITIES: Record> = { "moonshotai/Kimi-K3": { family: "kimi", thinkingEffortLevels: ["off", "low", "high", "max"], - openAiChatThinkingProfile: "top-level-reasoning-effort", supportedInputMediaTypes: ["image/png", "image/jpeg", "image/gif", "image/webp"], // Kimi K3's documented max output (Moonshot API's default // max_completion_tokens). @@ -48,7 +48,6 @@ const MODEL_APIS_CAPABILITIES: Record> = { "deepseek-ai/DeepSeek-V4-Flash-0731": { family: "deepseek-v4", thinkingEffortLevels: ["off", "low", "high", "max"], - openAiChatThinkingProfile: "chat-template-deepseek", supportsImages: false, supportsToolResultImages: false, supportedInputMediaTypes: [], diff --git a/packages/ai-config/src/types.ts b/packages/ai-config/src/types.ts index dfc7edc..4ba9766 100644 --- a/packages/ai-config/src/types.ts +++ b/packages/ai-config/src/types.ts @@ -232,11 +232,6 @@ export interface ResolvedProvider { // ModelInfoLike — local mirror of overridable ModelInfo fields // --------------------------------------------------------------------------- -export type OpenAiChatThinkingProfile = - | "top-level-reasoning-effort" - | "chat-template-enable-thinking" - | "chat-template-deepseek"; - /** * Subset of bridge ModelInfo fields that `resolveModels` operates on. * Defined locally so the pure entry has no bridge dependency. At the @@ -258,7 +253,8 @@ export interface ModelInfoLike { supportedInputMediaTypes?: string[]; supportsWebSearch: boolean; thinkingEffortLevels?: string[]; - openAiChatThinkingProfile?: OpenAiChatThinkingProfile; + /** Whether the model requires vLLM-style `chat_template_kwargs` to enable thinking. */ + requiresChatTemplateKwargs?: boolean; } /** diff --git a/packages/ai-provider-bridge/src/__tests__/utils.test.ts b/packages/ai-provider-bridge/src/__tests__/utils.test.ts index bf3ec52..87972d8 100644 --- a/packages/ai-provider-bridge/src/__tests__/utils.test.ts +++ b/packages/ai-provider-bridge/src/__tests__/utils.test.ts @@ -12,34 +12,36 @@ import { } from "../utils"; describe("thinkingRequestFields", () => { - it("omits fields when thinking or the request profile is unset", () => { - expect(thinkingRequestFields(undefined, "chat-template-deepseek")).toBeUndefined(); - expect(thinkingRequestFields("high", undefined)).toBeUndefined(); + it("returns undefined when thinking is off or unset", () => { + expect(thinkingRequestFields(undefined, true, "zai-org/GLM-5.2")).toBeUndefined(); + expect(thinkingRequestFields("off", false, "moonshotai/Kimi-K3")).toBeUndefined(); }); - it("maps named effort to a top-level reasoning_effort profile", () => { - expect(thinkingRequestFields("off", "top-level-reasoning-effort")).toBeUndefined(); - expect(thinkingRequestFields("low", "top-level-reasoning-effort")).toEqual({ + it("maps a named effort level to a top-level reasoning_effort", () => { + expect(thinkingRequestFields("low", false, "moonshotai/Kimi-K3")).toEqual({ reasoning_effort: "low", }); - expect(thinkingRequestFields("max", "top-level-reasoning-effort")).toEqual({ + expect(thinkingRequestFields("max", false, "moonshotai/Kimi-K3")).toEqual({ reasoning_effort: "max", }); }); - it("maps binary on to the enable_thinking chat-template profile", () => { - expect(thinkingRequestFields("off", "chat-template-enable-thinking")).toBeUndefined(); - expect(thinkingRequestFields("on", "chat-template-enable-thinking")).toEqual({ + it("maps binary 'on' to chat_template_kwargs when the model requires it", () => { + expect(thinkingRequestFields("on", true, "zai-org/GLM-5.2")).toEqual({ chat_template_kwargs: { enable_thinking: true }, }); }); - it("maps DeepSeek off and named efforts to its chat-template profile", () => { - expect(thinkingRequestFields("off", "chat-template-deepseek")).toEqual({ - chat_template_kwargs: { thinking: false }, + it("returns undefined for binary 'on' without the chat_template_kwargs flag", () => { + expect(thinkingRequestFields("on", false, "moonshotai/Kimi-K3")).toBeUndefined(); + }); + + it("maps DeepSeek named and off efforts to top-level values", () => { + expect(thinkingRequestFields("high", false, "deepseek-ai/DeepSeek-V4-Flash-0731")).toEqual({ + reasoning_effort: "high", }); - expect(thinkingRequestFields("high", "chat-template-deepseek")).toEqual({ - chat_template_kwargs: { thinking: true, reasoning_effort: "high" }, + expect(thinkingRequestFields("off", false, "deepseek-ai/DeepSeek-V4-Flash-0731")).toEqual({ + reasoning_effort: "none", }); }); }); diff --git a/packages/ai-provider-bridge/src/model-clients/ModelClient.ts b/packages/ai-provider-bridge/src/model-clients/ModelClient.ts index 8728fc1..f996a75 100644 --- a/packages/ai-provider-bridge/src/model-clients/ModelClient.ts +++ b/packages/ai-provider-bridge/src/model-clients/ModelClient.ts @@ -9,13 +9,7 @@ import type { ModelMessage } from "ai"; import type { StepLogger } from "../StepLogger"; -import type { - AiToolWithJsonSchema, - CancellationToken, - LMStreamPart, - OpenAiChatThinkingProfile, - Protocol, -} from "../types"; +import type { AiToolWithJsonSchema, CancellationToken, LMStreamPart, Protocol } from "../types"; /** * Parameters for a chat request. Shared across all ModelClient implementations @@ -40,8 +34,8 @@ export interface ModelClientChatParams { contextLength?: number; /** Whether provider-side web search should be enabled for this request. */ webSearchEnabled?: boolean; - /** Provider wire profile copied from ModelInfo; the receiving client owns serialization. */ - openAiChatThinkingProfile?: OpenAiChatThinkingProfile; + /** Whether the model requires vLLM-style `chat_template_kwargs` to enable thinking. */ + requiresChatTemplateKwargs?: boolean; /** * Whether the target model accepts image input at all. Used to decide how * tool-result images are transformed for APIs that can't embed images in diff --git a/packages/ai-provider-bridge/src/model-clients/PositAiClient.ts b/packages/ai-provider-bridge/src/model-clients/PositAiClient.ts index c94a1a7..92bb33e 100644 --- a/packages/ai-provider-bridge/src/model-clients/PositAiClient.ts +++ b/packages/ai-provider-bridge/src/model-clients/PositAiClient.ts @@ -240,7 +240,8 @@ export class PositAiClient implements ModelClient { } else if (normalizedProtocol === "openai-chat") { const thinkingFields = thinkingRequestFields( params.thinkingEffort, - params.openAiChatThinkingProfile, + params.requiresChatTemplateKwargs ?? false, + params.model, ); // Use OpenAI-compatible provider with OAuth authentication diff --git a/packages/ai-provider-bridge/src/types.ts b/packages/ai-provider-bridge/src/types.ts index 681eb64..e6d1eff 100644 --- a/packages/ai-provider-bridge/src/types.ts +++ b/packages/ai-provider-bridge/src/types.ts @@ -18,8 +18,8 @@ */ import type * as ai from "ai"; -import type { OpenAiChatThinkingProfile, ResolvedProviderId } from "ai-config"; -export type { OpenAiChatThinkingProfile, ResolvedProviderId }; +import type { ResolvedProviderId } from "ai-config"; +export type { ResolvedProviderId }; // Credential types re-exported from ai-credentials/types (single source of truth) export type { @@ -207,7 +207,8 @@ export interface ModelInfo { * for this model. */ supportsWebSearch: boolean; thinkingEffortLevels?: string[]; - openAiChatThinkingProfile?: OpenAiChatThinkingProfile; + /** Whether the model requires vLLM-style `chat_template_kwargs` to enable thinking. */ + requiresChatTemplateKwargs?: boolean; maxContextLength: number; } diff --git a/packages/ai-provider-bridge/src/utils.ts b/packages/ai-provider-bridge/src/utils.ts index 054b7de..8e45d05 100644 --- a/packages/ai-provider-bridge/src/utils.ts +++ b/packages/ai-provider-bridge/src/utils.ts @@ -7,8 +7,6 @@ * Kept here to avoid depending on any consumer package. */ -import type { OpenAiChatThinkingProfile } from "./types"; - // --------------------------------------------------------------------------- // Thinking effort // --------------------------------------------------------------------------- @@ -19,31 +17,32 @@ export function isThinkingEnabled(effort: string | undefined): boolean { } /** - * Request-body fields for thinking on an OpenAI-chat-protocol model. The - * capability-owned profile selects the provider's wire shape without model-id - * inference in the client. + * Request-body fields that control thinking on an OpenAI-chat-protocol model. + * + * Named effort levels (anything but the binary `"on"`) go out as the OpenAI-style + * top-level `reasoning_effort`. Binary-toggle models (`requiresChatTemplateKwargs`) + * take the vLLM-style `chat_template_kwargs` instead. * - * @returns Fields to merge into the request body, or `undefined` when the - * provider default should apply. + * @returns Fields to merge into the request body, or `undefined` when no + * explicit thinking control is needed. */ export function thinkingRequestFields( effort: string | undefined, - profile: OpenAiChatThinkingProfile | undefined, + requiresChatTemplateKwargs: boolean, + modelId: string, ): Record | undefined { - if (effort === undefined || profile === undefined) { - return undefined; + if (effort === "off" && modelId === "deepseek-ai/DeepSeek-V4-Flash-0731") { + return { reasoning_effort: "none" }; } - if (profile === "top-level-reasoning-effort") { - return isThinkingEnabled(effort) ? { reasoning_effort: effort } : undefined; + if (!isThinkingEnabled(effort)) { + return undefined; } - if (profile === "chat-template-enable-thinking") { - return effort === "on" ? { chat_template_kwargs: { enable_thinking: true } } : undefined; + if (effort !== "on") { + return { reasoning_effort: effort }; } - return { - chat_template_kwargs: isThinkingEnabled(effort) - ? { thinking: true, reasoning_effort: effort } - : { thinking: false }, - }; + return requiresChatTemplateKwargs + ? { chat_template_kwargs: { enable_thinking: true } } + : undefined; } // --------------------------------------------------------------------------- From 78cb7901e7a4c6e13e00a8c908565a3ff9517825 Mon Sep 17 00:00:00 2001 From: Simon Couch Date: Wed, 26 Aug 2026 14:02:52 -0700 Subject: [PATCH 3/5] isolate DeepSeek thinking behavior --- .../src/__tests__/utils.test.ts | 31 ++++++++++--------- .../src/model-clients/PositAiClient.ts | 6 ++-- packages/ai-provider-bridge/src/utils.ts | 21 ++++++++----- 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/packages/ai-provider-bridge/src/__tests__/utils.test.ts b/packages/ai-provider-bridge/src/__tests__/utils.test.ts index 87972d8..178f7f4 100644 --- a/packages/ai-provider-bridge/src/__tests__/utils.test.ts +++ b/packages/ai-provider-bridge/src/__tests__/utils.test.ts @@ -8,41 +8,42 @@ import { buildSnowflakeCortexUrl, buildSnowflakeCortexUrlFromHost, normalizeProviderBaseUrl, + positAiThinkingRequestFields, thinkingRequestFields, } from "../utils"; describe("thinkingRequestFields", () => { it("returns undefined when thinking is off or unset", () => { - expect(thinkingRequestFields(undefined, true, "zai-org/GLM-5.2")).toBeUndefined(); - expect(thinkingRequestFields("off", false, "moonshotai/Kimi-K3")).toBeUndefined(); + expect(thinkingRequestFields(undefined, true)).toBeUndefined(); + expect(thinkingRequestFields("off", true)).toBeUndefined(); }); it("maps a named effort level to a top-level reasoning_effort", () => { - expect(thinkingRequestFields("low", false, "moonshotai/Kimi-K3")).toEqual({ - reasoning_effort: "low", - }); - expect(thinkingRequestFields("max", false, "moonshotai/Kimi-K3")).toEqual({ - reasoning_effort: "max", - }); + expect(thinkingRequestFields("low", false)).toEqual({ reasoning_effort: "low" }); + expect(thinkingRequestFields("max", false)).toEqual({ reasoning_effort: "max" }); }); it("maps binary 'on' to chat_template_kwargs when the model requires it", () => { - expect(thinkingRequestFields("on", true, "zai-org/GLM-5.2")).toEqual({ + expect(thinkingRequestFields("on", true)).toEqual({ chat_template_kwargs: { enable_thinking: true }, }); }); it("returns undefined for binary 'on' without the chat_template_kwargs flag", () => { - expect(thinkingRequestFields("on", false, "moonshotai/Kimi-K3")).toBeUndefined(); + expect(thinkingRequestFields("on", false)).toBeUndefined(); }); +}); - it("maps DeepSeek named and off efforts to top-level values", () => { - expect(thinkingRequestFields("high", false, "deepseek-ai/DeepSeek-V4-Flash-0731")).toEqual({ - reasoning_effort: "high", - }); - expect(thinkingRequestFields("off", false, "deepseek-ai/DeepSeek-V4-Flash-0731")).toEqual({ +describe("positAiThinkingRequestFields", () => { + it("maps DeepSeek off to none and reuses named efforts", () => { + expect( + positAiThinkingRequestFields("deepseek-ai/DeepSeek-V4-Flash-0731", "off", false), + ).toEqual({ reasoning_effort: "none", }); + expect( + positAiThinkingRequestFields("deepseek-ai/DeepSeek-V4-Flash-0731", "high", false), + ).toEqual({ reasoning_effort: "high" }); }); }); diff --git a/packages/ai-provider-bridge/src/model-clients/PositAiClient.ts b/packages/ai-provider-bridge/src/model-clients/PositAiClient.ts index 92bb33e..45a5400 100644 --- a/packages/ai-provider-bridge/src/model-clients/PositAiClient.ts +++ b/packages/ai-provider-bridge/src/model-clients/PositAiClient.ts @@ -29,7 +29,7 @@ import { isClaudeModel, isThinkingEnabled, joinPath, - thinkingRequestFields, + positAiThinkingRequestFields, } from "../utils"; import { convertAiSdkStreamToPlatform, @@ -238,10 +238,10 @@ export class PositAiClient implements ModelClient { return convertAiSdkStreamToPlatform(result.fullStream, cleanup); } else if (normalizedProtocol === "openai-chat") { - const thinkingFields = thinkingRequestFields( + const thinkingFields = positAiThinkingRequestFields( + params.model, params.thinkingEffort, params.requiresChatTemplateKwargs ?? false, - params.model, ); // Use OpenAI-compatible provider with OAuth authentication diff --git a/packages/ai-provider-bridge/src/utils.ts b/packages/ai-provider-bridge/src/utils.ts index 8e45d05..5514677 100644 --- a/packages/ai-provider-bridge/src/utils.ts +++ b/packages/ai-provider-bridge/src/utils.ts @@ -17,23 +17,19 @@ export function isThinkingEnabled(effort: string | undefined): boolean { } /** - * Request-body fields that control thinking on an OpenAI-chat-protocol model. + * Request-body fields that enable thinking on an OpenAI-chat-protocol model. * * Named effort levels (anything but the binary `"on"`) go out as the OpenAI-style * top-level `reasoning_effort`. Binary-toggle models (`requiresChatTemplateKwargs`) * take the vLLM-style `chat_template_kwargs` instead. * - * @returns Fields to merge into the request body, or `undefined` when no - * explicit thinking control is needed. + * @returns Fields to merge into the request body, or `undefined` when thinking + * is off or the model has no way to enable it. */ export function thinkingRequestFields( effort: string | undefined, requiresChatTemplateKwargs: boolean, - modelId: string, ): Record | undefined { - if (effort === "off" && modelId === "deepseek-ai/DeepSeek-V4-Flash-0731") { - return { reasoning_effort: "none" }; - } if (!isThinkingEnabled(effort)) { return undefined; } @@ -45,6 +41,17 @@ export function thinkingRequestFields( : undefined; } +export function positAiThinkingRequestFields( + modelId: string, + effort: string | undefined, + requiresChatTemplateKwargs: boolean, +): Record | undefined { + if (modelId === "deepseek-ai/DeepSeek-V4-Flash-0731" && effort === "off") { + return { reasoning_effort: "none" }; + } + return thinkingRequestFields(effort, requiresChatTemplateKwargs); +} + // --------------------------------------------------------------------------- // Model ID helpers // --------------------------------------------------------------------------- From a4ae376c1c78cb49c2c345f9dd43cfbd77fb970e Mon Sep 17 00:00:00 2001 From: Simon Couch Date: Wed, 26 Aug 2026 14:06:36 -0700 Subject: [PATCH 4/5] raise DeepSeek context limit --- .../ai-config/src/model-capabilities/__tests__/infer.test.ts | 4 ++-- .../ai-config/src/model-capabilities/model-apis-helpers.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ai-config/src/model-capabilities/__tests__/infer.test.ts b/packages/ai-config/src/model-capabilities/__tests__/infer.test.ts index a6492e7..7bba09c 100644 --- a/packages/ai-config/src/model-capabilities/__tests__/infer.test.ts +++ b/packages/ai-config/src/model-capabilities/__tests__/infer.test.ts @@ -123,8 +123,8 @@ describe("inferModelCapabilities", () => { expect(deepSeek.family).toBe("deepseek-v4"); expect(deepSeek.thinkingEffortLevels).toEqual(["off", "low", "high", "max"]); expect(deepSeek.supportsImages).toBe(false); - expect(deepSeek.maxContextLength).toBe(200_000); - expect(deepSeek.maxInputTokens).toBe(200_000); + expect(deepSeek.maxContextLength).toBe(250_000); + expect(deepSeek.maxInputTokens).toBe(250_000); expect(deepSeek.maxOutputTokens).toBe(384_000); }); diff --git a/packages/ai-config/src/model-capabilities/model-apis-helpers.ts b/packages/ai-config/src/model-capabilities/model-apis-helpers.ts index 99cf751..269a120 100644 --- a/packages/ai-config/src/model-capabilities/model-apis-helpers.ts +++ b/packages/ai-config/src/model-capabilities/model-apis-helpers.ts @@ -52,8 +52,8 @@ const MODEL_APIS_CAPABILITIES: Record> = { supportsToolResultImages: false, supportedInputMediaTypes: [], maxOutputTokens: 384_000, - maxContextLength: 200_000, - maxInputTokens: 200_000, + maxContextLength: 250_000, + maxInputTokens: 250_000, }, }; From a04bbd242404dfc0c9694b2dc565ab97fdf69382 Mon Sep 17 00:00:00 2001 From: Simon Couch Date: Wed, 26 Aug 2026 14:29:06 -0700 Subject: [PATCH 5/5] remove redundant capability test --- .../src/model-capabilities/__tests__/infer.test.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/ai-config/src/model-capabilities/__tests__/infer.test.ts b/packages/ai-config/src/model-capabilities/__tests__/infer.test.ts index 7bba09c..a6d0680 100644 --- a/packages/ai-config/src/model-capabilities/__tests__/infer.test.ts +++ b/packages/ai-config/src/model-capabilities/__tests__/infer.test.ts @@ -118,14 +118,6 @@ describe("inferModelCapabilities", () => { expect(kimiK3.maxContextLength).toBe(250_000); expect(kimiK3.maxInputTokens).toBe(250_000); expect(kimiK3.maxOutputTokens).toBe(131_072); - - const deepSeek = inferModelCapabilities("positai", "deepseek-ai/DeepSeek-V4-Flash-0731"); - expect(deepSeek.family).toBe("deepseek-v4"); - expect(deepSeek.thinkingEffortLevels).toEqual(["off", "low", "high", "max"]); - expect(deepSeek.supportsImages).toBe(false); - expect(deepSeek.maxContextLength).toBe(250_000); - expect(deepSeek.maxInputTokens).toBe(250_000); - expect(deepSeek.maxOutputTokens).toBe(384_000); }); it("omits requiresChatTemplateKwargs so the result fits a models.custom entry", () => {