fix(ai): restore detectCompat runtime fallback in openai-completions
refs #6020
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
- Fixed request-scoped `apiKey` and `env` values to participate in provider auth resolution, so providers such as Cloudflare can derive request-specific base URLs from explicit call options ([#6021](https://github.com/earendil-works/pi/issues/6021)).
|
- Fixed request-scoped `apiKey` and `env` values to participate in provider auth resolution, so providers such as Cloudflare can derive request-specific base URLs from explicit call options ([#6021](https://github.com/earendil-works/pi/issues/6021)).
|
||||||
- Restored temporary legacy per-API stream aliases such as `streamSimpleOpenAICompletions` on the compat entrypoint ([#6016](https://github.com/earendil-works/pi/issues/6016), [#6017](https://github.com/earendil-works/pi/issues/6017)).
|
- Restored temporary legacy per-API stream aliases such as `streamSimpleOpenAICompletions` on the compat entrypoint ([#6016](https://github.com/earendil-works/pi/issues/6016), [#6017](https://github.com/earendil-works/pi/issues/6017)).
|
||||||
|
- Restored runtime `detectCompat` fallback in `openai-completions` for models without explicit compat metadata ([#6020](https://github.com/earendil-works/pi/issues/6020)).
|
||||||
|
|
||||||
## [0.80.1] - 2026-06-23
|
## [0.80.1] - 2026-06-23
|
||||||
|
|
||||||
|
|||||||
@@ -1163,55 +1163,123 @@ function mapStopReason(reason: ChatCompletionChunk.Choice["finish_reason"] | str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_COMPAT: ResolvedOpenAICompletionsCompat = {
|
/**
|
||||||
supportsStore: true,
|
* Auto-detect compatibility settings from provider name and baseUrl.
|
||||||
supportsDeveloperRole: true,
|
* Used as the base when model.compat is not set; explicit model.compat
|
||||||
supportsReasoningEffort: true,
|
* entries override these detected values.
|
||||||
supportsUsageInStreaming: true,
|
*/
|
||||||
maxTokensField: "max_completion_tokens",
|
function detectCompat(model: Model<"openai-completions">): ResolvedOpenAICompletionsCompat {
|
||||||
requiresToolResultName: false,
|
const provider = model.provider;
|
||||||
requiresAssistantAfterToolResult: false,
|
const baseUrl = model.baseUrl;
|
||||||
requiresThinkingAsText: false,
|
|
||||||
requiresReasoningContentOnAssistantMessages: false,
|
const isZai =
|
||||||
thinkingFormat: "openai",
|
provider === "zai" ||
|
||||||
openRouterRouting: {},
|
provider === "zai-coding-cn" ||
|
||||||
vercelGatewayRouting: {},
|
baseUrl.includes("api.z.ai") ||
|
||||||
chatTemplateKwargs: {},
|
baseUrl.includes("open.bigmodel.cn");
|
||||||
zaiToolStream: false,
|
const isTogether =
|
||||||
supportsStrictMode: true,
|
provider === "together" || baseUrl.includes("api.together.ai") || baseUrl.includes("api.together.xyz");
|
||||||
cacheControlFormat: undefined,
|
const isMoonshot = provider === "moonshotai" || provider === "moonshotai-cn" || baseUrl.includes("api.moonshot.");
|
||||||
sendSessionAffinityHeaders: false,
|
const isOpenRouter = provider === "openrouter" || baseUrl.includes("openrouter.ai");
|
||||||
supportsLongCacheRetention: true,
|
const isCloudflareWorkersAI = provider === "cloudflare-workers-ai" || baseUrl.includes("api.cloudflare.com");
|
||||||
};
|
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 isNonStandard =
|
||||||
|
isNvidia ||
|
||||||
|
provider === "cerebras" ||
|
||||||
|
baseUrl.includes("cerebras.ai") ||
|
||||||
|
provider === "xai" ||
|
||||||
|
baseUrl.includes("api.x.ai") ||
|
||||||
|
isTogether ||
|
||||||
|
baseUrl.includes("chutes.ai") ||
|
||||||
|
baseUrl.includes("deepseek.com") ||
|
||||||
|
isZai ||
|
||||||
|
isMoonshot ||
|
||||||
|
provider === "opencode" ||
|
||||||
|
baseUrl.includes("opencode.ai") ||
|
||||||
|
isCloudflareWorkersAI ||
|
||||||
|
isCloudflareAiGateway ||
|
||||||
|
isAntLing;
|
||||||
|
|
||||||
|
const useMaxTokens =
|
||||||
|
baseUrl.includes("chutes.ai") || isMoonshot || isCloudflareAiGateway || isTogether || isNvidia || isAntLing;
|
||||||
|
|
||||||
|
const isGrok = provider === "xai" || baseUrl.includes("api.x.ai");
|
||||||
|
const isDeepSeek = provider === "deepseek" || baseUrl.includes("deepseek.com");
|
||||||
|
const isOpenRouterDeveloperRoleModel =
|
||||||
|
isOpenRouter && (model.id.startsWith("anthropic/") || model.id.startsWith("openai/"));
|
||||||
|
const cacheControlFormat = provider === "openrouter" && model.id.startsWith("anthropic/") ? "anthropic" : undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
supportsStore: !isNonStandard,
|
||||||
|
supportsDeveloperRole: isOpenRouterDeveloperRoleModel || (!isNonStandard && !isOpenRouter),
|
||||||
|
supportsReasoningEffort:
|
||||||
|
!isGrok && !isZai && !isMoonshot && !isTogether && !isCloudflareAiGateway && !isNvidia && !isAntLing,
|
||||||
|
supportsUsageInStreaming: true,
|
||||||
|
maxTokensField: useMaxTokens ? "max_tokens" : "max_completion_tokens",
|
||||||
|
requiresToolResultName: false,
|
||||||
|
requiresAssistantAfterToolResult: false,
|
||||||
|
requiresThinkingAsText: false,
|
||||||
|
requiresReasoningContentOnAssistantMessages: isDeepSeek,
|
||||||
|
thinkingFormat: isDeepSeek
|
||||||
|
? "deepseek"
|
||||||
|
: isZai
|
||||||
|
? "zai"
|
||||||
|
: isTogether
|
||||||
|
? "together"
|
||||||
|
: isAntLing
|
||||||
|
? "ant-ling"
|
||||||
|
: isOpenRouter
|
||||||
|
? "openrouter"
|
||||||
|
: "openai",
|
||||||
|
openRouterRouting: {},
|
||||||
|
vercelGatewayRouting: {},
|
||||||
|
chatTemplateKwargs: {},
|
||||||
|
zaiToolStream: false,
|
||||||
|
supportsStrictMode: !isMoonshot && !isTogether && !isCloudflareAiGateway && !isNvidia,
|
||||||
|
cacheControlFormat,
|
||||||
|
sendSessionAffinityHeaders: false,
|
||||||
|
supportsLongCacheRetention: !(
|
||||||
|
isTogether ||
|
||||||
|
isCloudflareWorkersAI ||
|
||||||
|
isCloudflareAiGateway ||
|
||||||
|
isNvidia ||
|
||||||
|
isAntLing
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get resolved compatibility settings for a model.
|
* Get resolved compatibility settings for a model.
|
||||||
* Uses explicit generated/custom model.compat over OpenAI-standard defaults.
|
* Auto-detects from provider/URL then overrides with explicit model.compat.
|
||||||
*/
|
*/
|
||||||
function getCompat(model: Model<"openai-completions">): ResolvedOpenAICompletionsCompat {
|
function getCompat(model: Model<"openai-completions">): ResolvedOpenAICompletionsCompat {
|
||||||
if (!model.compat) return DEFAULT_COMPAT;
|
const detected = detectCompat(model);
|
||||||
|
if (!model.compat) return detected;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
supportsStore: model.compat.supportsStore ?? DEFAULT_COMPAT.supportsStore,
|
supportsStore: model.compat.supportsStore ?? detected.supportsStore,
|
||||||
supportsDeveloperRole: model.compat.supportsDeveloperRole ?? DEFAULT_COMPAT.supportsDeveloperRole,
|
supportsDeveloperRole: model.compat.supportsDeveloperRole ?? detected.supportsDeveloperRole,
|
||||||
supportsReasoningEffort: model.compat.supportsReasoningEffort ?? DEFAULT_COMPAT.supportsReasoningEffort,
|
supportsReasoningEffort: model.compat.supportsReasoningEffort ?? detected.supportsReasoningEffort,
|
||||||
supportsUsageInStreaming: model.compat.supportsUsageInStreaming ?? DEFAULT_COMPAT.supportsUsageInStreaming,
|
supportsUsageInStreaming: model.compat.supportsUsageInStreaming ?? detected.supportsUsageInStreaming,
|
||||||
maxTokensField: model.compat.maxTokensField ?? DEFAULT_COMPAT.maxTokensField,
|
maxTokensField: model.compat.maxTokensField ?? detected.maxTokensField,
|
||||||
requiresToolResultName: model.compat.requiresToolResultName ?? DEFAULT_COMPAT.requiresToolResultName,
|
requiresToolResultName: model.compat.requiresToolResultName ?? detected.requiresToolResultName,
|
||||||
requiresAssistantAfterToolResult:
|
requiresAssistantAfterToolResult:
|
||||||
model.compat.requiresAssistantAfterToolResult ?? DEFAULT_COMPAT.requiresAssistantAfterToolResult,
|
model.compat.requiresAssistantAfterToolResult ?? detected.requiresAssistantAfterToolResult,
|
||||||
requiresThinkingAsText: model.compat.requiresThinkingAsText ?? DEFAULT_COMPAT.requiresThinkingAsText,
|
requiresThinkingAsText: model.compat.requiresThinkingAsText ?? detected.requiresThinkingAsText,
|
||||||
requiresReasoningContentOnAssistantMessages:
|
requiresReasoningContentOnAssistantMessages:
|
||||||
model.compat.requiresReasoningContentOnAssistantMessages ??
|
model.compat.requiresReasoningContentOnAssistantMessages ??
|
||||||
DEFAULT_COMPAT.requiresReasoningContentOnAssistantMessages,
|
detected.requiresReasoningContentOnAssistantMessages,
|
||||||
thinkingFormat: model.compat.thinkingFormat ?? DEFAULT_COMPAT.thinkingFormat,
|
thinkingFormat: model.compat.thinkingFormat ?? detected.thinkingFormat,
|
||||||
openRouterRouting: model.compat.openRouterRouting ?? DEFAULT_COMPAT.openRouterRouting,
|
openRouterRouting: model.compat.openRouterRouting ?? {},
|
||||||
vercelGatewayRouting: model.compat.vercelGatewayRouting ?? DEFAULT_COMPAT.vercelGatewayRouting,
|
vercelGatewayRouting: model.compat.vercelGatewayRouting ?? detected.vercelGatewayRouting,
|
||||||
chatTemplateKwargs: model.compat.chatTemplateKwargs ?? DEFAULT_COMPAT.chatTemplateKwargs,
|
chatTemplateKwargs: model.compat.chatTemplateKwargs ?? detected.chatTemplateKwargs,
|
||||||
zaiToolStream: model.compat.zaiToolStream ?? DEFAULT_COMPAT.zaiToolStream,
|
zaiToolStream: model.compat.zaiToolStream ?? detected.zaiToolStream,
|
||||||
supportsStrictMode: model.compat.supportsStrictMode ?? DEFAULT_COMPAT.supportsStrictMode,
|
supportsStrictMode: model.compat.supportsStrictMode ?? detected.supportsStrictMode,
|
||||||
cacheControlFormat: model.compat.cacheControlFormat ?? DEFAULT_COMPAT.cacheControlFormat,
|
cacheControlFormat: model.compat.cacheControlFormat ?? detected.cacheControlFormat,
|
||||||
sendSessionAffinityHeaders: model.compat.sendSessionAffinityHeaders ?? DEFAULT_COMPAT.sendSessionAffinityHeaders,
|
sendSessionAffinityHeaders: model.compat.sendSessionAffinityHeaders ?? detected.sendSessionAffinityHeaders,
|
||||||
supportsLongCacheRetention: model.compat.supportsLongCacheRetention ?? DEFAULT_COMPAT.supportsLongCacheRetention,
|
supportsLongCacheRetention: model.compat.supportsLongCacheRetention ?? detected.supportsLongCacheRetention,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user