Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 59 additions & 15 deletions packages/runtime/src/__tests__/model-factory-thinking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ describe('buildProviderOptions: thinking level', () => {
});
// Copilot defaults to its OpenAI-compatible chat wire without a protocol hint.
assert.deepEqual(buildProviderOptions(conn('github-copilot'), 'gpt-5.4', 'high'), {
'github-copilot': { reasoningEffort: 'high' },
githubCopilot: { reasoningEffort: 'high' },
});
});

Expand Down Expand Up @@ -295,20 +295,20 @@ describe('buildProviderOptions: thinking level', () => {
);
assert.deepEqual(buildProviderOptions(conn('cloudflare-workers-ai'), modelId), {});
assert.deepEqual(buildProviderOptions(conn('cloudflare-workers-ai'), modelId, 'high'), {
'cloudflare-workers-ai': { reasoningEffort: 'high' },
cloudflareWorkersAi: { reasoningEffort: 'high' },
});
assert.deepEqual(buildProviderOptions(conn('cloudflare-workers-ai'), modelId, 'off'), {
'cloudflare-workers-ai': { chat_template_kwargs: { thinking: false } },
cloudflareWorkersAi: { chat_template_kwargs: { thinking: false } },
});
});

test('StepFun Step Plan sends only officially supported reasoning effort levels', () => {
assert.deepEqual(buildProviderOptions(conn('stepfun-step-plan'), 'step-3.7-flash', 'medium'), {
'stepfun-step-plan': { reasoningEffort: 'medium' },
stepfunStepPlan: { reasoningEffort: 'medium' },
});
assert.deepEqual(
buildProviderOptions(conn('stepfun-step-plan'), 'step-3.5-flash-2603', 'high'),
{ 'stepfun-step-plan': { reasoningEffort: 'high' } },
{ stepfunStepPlan: { reasoningEffort: 'high' } },
);
assert.deepEqual(
buildProviderOptions(conn('stepfun-step-plan'), 'step-3.5-flash-2603', 'medium'),
Expand All @@ -329,13 +329,13 @@ describe('buildProviderOptions: thinking level', () => {
['off', 'minimal', 'low', 'medium', 'high'],
);
assert.deepEqual(buildProviderOptions(conn('volcengine-ark'), modelId), {
'volcengine-ark': { thinking: { type: 'enabled' } },
volcengineArk: { thinking: { type: 'enabled' } },
});
assert.deepEqual(buildProviderOptions(conn('volcengine-ark'), modelId, 'high'), {
'volcengine-ark': { thinking: { type: 'enabled' }, reasoningEffort: 'high' },
volcengineArk: { thinking: { type: 'enabled' }, reasoningEffort: 'high' },
});
assert.deepEqual(buildProviderOptions(conn('volcengine-ark'), modelId, 'off'), {
'volcengine-ark': { thinking: { type: 'disabled' } },
volcengineArk: { thinking: { type: 'disabled' } },
});
});

Expand Down Expand Up @@ -369,7 +369,7 @@ describe('buildProviderOptions: thinking level', () => {
['low', 'medium', 'high'],
);
assert.deepEqual(buildProviderOptions(conn('tencent-token-plan'), 'hy3', 'high'), {
'tencent-token-plan': { reasoningEffort: 'high' },
tencentTokenPlan: { reasoningEffort: 'high' },
});
assert.deepEqual(buildProviderOptions(conn('tencent-token-plan'), 'hy3', 'off'), {});
});
Expand All @@ -394,17 +394,17 @@ describe('buildProviderOptions: thinking level', () => {
['off', 'low', 'medium', 'high', 'max'],
);
assert.deepEqual(buildProviderOptions(conn('ollama-cloud'), 'glm-5.2', 'high'), {
'ollama-cloud': { reasoningEffort: 'high' },
ollamaCloud: { reasoningEffort: 'high' },
});
assert.deepEqual(buildProviderOptions(conn('ollama-cloud'), 'glm-5.2', 'off'), {
'ollama-cloud': { reasoningEffort: 'none' },
ollamaCloud: { reasoningEffort: 'none' },
});
assert.deepEqual(
[...thinkingVariantsForModel('ollama-cloud', 'gpt-oss:120b')],
['low', 'medium', 'high'],
);
assert.deepEqual(buildProviderOptions(conn('ollama-cloud'), 'gpt-oss:120b', 'high'), {
'ollama-cloud': { reasoningEffort: 'high' },
ollamaCloud: { reasoningEffort: 'high' },
});
assert.deepEqual(buildProviderOptions(conn('ollama-cloud'), 'gpt-oss:120b', 'off'), {});
});
Expand Down Expand Up @@ -490,14 +490,14 @@ describe('getAIModel: models.dev registry providers', () => {
});

describe('buildProviderOptions: openai-compatible namespace', () => {
test('zai-coding-plan emits reasoningEffort under the raw dashed namespace', () => {
test('zai-coding-plan emits reasoningEffort under the camelCase namespace', () => {
assert.deepEqual(
buildProviderOptions(conn('zai-coding-plan', 'zai-coding-plan'), 'glm-5.2', 'high'),
{ 'zai-coding-plan': { reasoningEffort: 'high' } },
{ zaiCodingPlan: { reasoningEffort: 'high' } },
);
assert.deepEqual(
buildProviderOptions(conn('zai-coding-plan', 'zai-coding-plan'), 'glm-5.2', 'max'),
{ 'zai-coding-plan': { reasoningEffort: 'max' } },
{ zaiCodingPlan: { reasoningEffort: 'max' } },
);
});
test('deepseek wires provider-native effort on both chat and Responses dialects', () => {
Expand Down Expand Up @@ -599,4 +599,48 @@ describe('buildProviderOptions: openai-compatible namespace', () => {
JSON.stringify(result.warnings),
);
});

test('built-in dashed provider effort reaches the chat request body without deprecation', async () => {
Comment thread
yihanzhu marked this conversation as resolved.
// Built-in counterpart of the relay capture above: built-in dashed
// providerTypes must emit the SDK's camelCase alias too.
const bodies: Record<string, unknown>[] = [];
const captureFetch: typeof globalThis.fetch = async (_input, init) => {
bodies.push(JSON.parse(String(init?.body)) as Record<string, unknown>);
return new Response(
JSON.stringify({
id: 'chatcmpl-1',
object: 'chat.completion',
created: 1,
model: 'glm-5.2',
choices: [
{
index: 0,
message: { role: 'assistant', content: 'ok' },
finish_reason: 'stop',
},
],
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
}),
{ status: 200, headers: { 'content-type': 'application/json' } },
);
};
const connection = conn('zai-coding-plan', 'zai-coding-plan');
const model = getAIModel({
connection,
apiKey: 'zai-key',
modelId: 'glm-5.2',
fetch: captureFetch,
});
const result = await model.doGenerate({
prompt: [{ role: 'user', content: [{ type: 'text', text: 'hi' }] }],
providerOptions: buildProviderOptions(connection, 'glm-5.2', 'high'),
});
assert.equal(bodies.length, 1);
assert.equal(bodies[0]?.reasoning_effort, 'high');
assert.equal(
(result.warnings ?? []).some((warning) => warning.type === 'deprecated'),
false,
JSON.stringify(result.warnings),
);
});
});
4 changes: 2 additions & 2 deletions packages/runtime/src/__tests__/request-shape.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,8 @@ describe('prepared provider request capture', () => {
const openaiMax = hash({ kimiCodingPlan: { reasoningEffort: 'max' } }, 32_768);
const nativeOpenaiMax = hash({ openai: { reasoningEffort: 'max' } }, 32_768);
const nativeOpenaiHigh = hash({ openai: { reasoningEffort: 'high' } }, 32_768);
const zaiHigh = hash({ 'zai-coding-plan': { reasoningEffort: 'high' } }, 32_768);
const zaiLow = hash({ 'zai-coding-plan': { reasoningEffort: 'low' } }, 32_768);
const zaiHigh = hash({ zaiCodingPlan: { reasoningEffort: 'high' } }, 32_768);
const zaiLow = hash({ zaiCodingPlan: { reasoningEffort: 'low' } }, 32_768);

assert.equal(anthropicMax, openaiMax);
assert.equal(anthropicMax, nativeOpenaiMax);
Expand Down
21 changes: 9 additions & 12 deletions packages/runtime/src/model-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ export function buildProviderOptions(
return buildFamilyWire(connection, modelId, level, thinkingOptions);
case 'volcengine-ark':
return {
[connection.providerType]: {
[toCamelCase(connection.providerType)]: {
thinking: { type: level === 'off' ? 'disabled' : 'enabled' },
...(level && level !== 'off' ? { reasoningEffort: level } : {}),
},
Expand All @@ -495,7 +495,7 @@ export function buildProviderOptions(
case 'cloudflare-workers-ai':
return level
? {
[connection.providerType]:
[toCamelCase(connection.providerType)]:
level === 'off'
? thinkingOptions?.offBehavior === 'cloudflare-chat-template-thinking-false'
? { chat_template_kwargs: { thinking: false } }
Expand Down Expand Up @@ -584,7 +584,7 @@ function buildFamilyWire(
if (copilotProtocol === 'anthropic-messages') {
return level !== 'off' ? { anthropic: { effort: level } } : {};
}
return { 'github-copilot': { reasoningEffort } };
return { githubCopilot: { reasoningEffort } };
}
default:
return {};
Expand Down Expand Up @@ -612,18 +612,15 @@ function toCamelCase(name: string): string {
}

/**
* The providerOptions key for an openai-compatible model. The SDK still
* accepts the raw provider name but flags dashed keys as deprecated (a
* `type: 'deprecated'` warning on every doGenerate result); its canonical
* key is the camelCase alias. Only the custom-relay path keys options by
* the connection slug, so only that path camelCases — built-in adapter
* namespaces stay as they were.
* The providerOptions key for an openai-compatible model: the camelCase
* alias of the identity passed to `createOpenAICompatible`. The SDK
* resolves both spellings — known options and passthrough fields alike —
* but flags dashed keys as deprecated (a `type: 'deprecated'` warning on
* every doGenerate result), so the camelCase alias is the canonical key.
*/
function openAiCompatibleProviderOptionsKey(
adapter: ProviderRuntimeAdapter,
connection: RuntimeExecutionConnection,
): string {
return adapter.kind === 'openai-compatible' && adapter.name === 'connection'
? toCamelCase(connection.slug)
: connection.providerType;
return toCamelCase(openAiCompatibleProviderName(adapter, connection));
Comment thread
yihanzhu marked this conversation as resolved.
}