Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
15 changes: 15 additions & 0 deletions packages/core/src/__tests__/llm-connections.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,21 @@ test('the alias table is selected by provider and names only renames', () => {
CLAUDE_SUBSCRIPTION_MODEL_ID_ALIASES,
);
assert.equal(modelIdAliasesForProvider('anthropic'), undefined);
for (const providerType of ['alibaba-token-plan-cn', 'alibaba-token-plan'] as const) {
assert.deepEqual(
reconcileConnectionAfterModelFetch(
{
defaultModel: 'qwen3.8-max-preview',
enabledModelIds: ['qwen3.8-max-preview'],
hasModelInventory: true,
},
[{ id: 'qwen3.8-max' }, { id: 'qwen3.7-max' }],
{ aliases: modelIdAliasesForProvider(providerType) },
),
{ defaultModel: 'qwen3.8-max', enabledModelIds: ['qwen3.8-max'] },
providerType,
);
}
const offered = curatedCatalogFallbackModelsForProvider('claude-subscription') ?? [];
for (const [renamed, target] of Object.entries(CLAUDE_SUBSCRIPTION_MODEL_ID_ALIASES)) {
assert.ok(offered.includes(target), `${target} is not offered by the curated inventory`);
Expand Down
32 changes: 31 additions & 1 deletion packages/core/src/__tests__/model-catalog.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { strict as assert } from 'node:assert';
import { test } from 'node:test';
import { isConnectionReady } from '../connection-readiness.js';
import type { LlmConnection, ProviderType } from '../llm-connections.js';
import { PROVIDER_DEFAULTS, type LlmConnection, type ProviderType } from '../llm-connections.js';
import {
buildConnectionModelCatalogEntries,
buildModelCatalogEntries,
Expand Down Expand Up @@ -144,3 +144,33 @@ test('unknown persisted provider ids return an empty catalog', () => {
[],
);
});

test('Alibaba Token Plan catalogs the formal Qwen3.8 model instead of its retired preview alias', () => {
const modelId = 'qwen3.8-max';
for (const providerType of ['alibaba-token-plan-cn', 'alibaba-token-plan'] as const) {
const defaults = PROVIDER_DEFAULTS[providerType];
assert.equal(defaults.fallbackModels[0], modelId, providerType);
assert.equal(defaults.fallbackModels.includes('qwen3.8-max-preview'), false, providerType);

const entries = buildConnectionModelCatalogEntries({
connection: {
slug: providerType,
providerType,
defaultModel: modelId,
modelSource: 'fallback',
},
});
const model = entries.find((entry) => entry.id === modelId);
assert.equal(model?.displayName, 'Qwen3.8 Max', providerType);
assert.equal(model?.contextWindow, 1_000_000, providerType);
assert.equal(model?.maxOutputTokens, 131_072, providerType);
assert.equal(model?.structuredOutput, true, providerType);
assert.deepEqual(
model?.capabilities,
{ vision: true, reasoning: true, functionCalling: true },
providerType,
);
assert.deepEqual(model?.modalities, { input: ['text', 'image', 'pdf'], output: ['text'] });
assert.equal(model?.canUseAsChatDefault, true, providerType);
}
});
11 changes: 11 additions & 0 deletions packages/core/src/__tests__/model-thinking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,17 @@ test('resolveThinkingLevel discards levels the model does not offer', () => {
assert.equal(resolveThinkingLevel({ providerType: 'openai' }, 'gpt-5.5', 'xhigh'), 'xhigh');
});

test('Alibaba Token Plan exposes the formal Qwen3.8 effort and disable contract', () => {
for (const providerType of ['alibaba-token-plan-cn', 'alibaba-token-plan'] as const) {
assert.deepEqual(
[...thinkingVariantsForModel(providerType, 'qwen3.8-max')],
['off', 'low', 'medium', 'xhigh'],
providerType,
);
assert.equal(resolveThinkingLevel({ providerType }, 'qwen3.8-max', 'off'), 'off', providerType);
}
});

// Reasoning replay has no toggle: DeepSeek-like relays require
// reasoning_content in tool-call history (400 otherwise), and other relays
// ignore it, so the runtime replays unconditionally. That contract is
Expand Down
21 changes: 20 additions & 1 deletion packages/core/src/model-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,16 @@ const ollamaCloudThinkingModels: Record<string, ModelMetadata> = Object.fromEntr
const STATIC_MODEL_METADATA: Partial<Record<ProviderType, Record<string, ModelMetadata>>> = {
anthropic: ANTHROPIC_MODEL_OVERRIDES,
'claude-subscription': CLAUDE_SUBSCRIPTION_MODEL_METADATA,
'alibaba-token-plan-cn': {
'qwen3.8-max': {
thinkingOptions: { efforts: ['none', 'low', 'medium', 'xhigh'], toggle: true },
},
},
'alibaba-token-plan': {
'qwen3.8-max': {
thinkingOptions: { efforts: ['none', 'low', 'medium', 'xhigh'], toggle: true },
},
},
google: GOOGLE_MODEL_OVERRIDES,
cohere: {
'command-a-plus-05-2026': {
Expand Down Expand Up @@ -488,6 +498,11 @@ export const CLAUDE_SUBSCRIPTION_MODEL_ID_ALIASES: Readonly<Record<string, strin
'claude-haiku-4-5-20251001': 'claude-haiku-4-5',
};

/** Token Plan's retired preview id remains a server-side alias of the formal model. */
export const ALIBABA_TOKEN_PLAN_MODEL_ID_ALIASES: Readonly<Record<string, string>> = {
'qwen3.8-max-preview': 'qwen3.8-max',
};

/**
* The rename table that applies to one provider's inventory, or undefined when
* its ids carry no such guarantee.
Expand All @@ -501,7 +516,11 @@ export const CLAUDE_SUBSCRIPTION_MODEL_ID_ALIASES: Readonly<Record<string, strin
export function modelIdAliasesForProvider(
providerType: ProviderType,
): Readonly<Record<string, string>> | undefined {
return providerType === 'claude-subscription' ? CLAUDE_SUBSCRIPTION_MODEL_ID_ALIASES : undefined;
if (providerType === 'claude-subscription') return CLAUDE_SUBSCRIPTION_MODEL_ID_ALIASES;
if (providerType === 'alibaba-token-plan-cn' || providerType === 'alibaba-token-plan') {
return ALIBABA_TOKEN_PLAN_MODEL_ID_ALIASES;
}
return undefined;
}

const CURATED_CATALOG_FALLBACK_MODELS: Partial<Record<ProviderType, readonly string[]>> = {
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/provider-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,9 @@ if (!alibabaTokenPlanGlobal.api) {
// the plan's image models (qwen-image / wan) are not tool-callable, so only the
// tool-calling text models are pinned here. China and global share one model list.
const alibabaTokenPlanModelIds = [
// qwen3.8-max-preview is a retired compatibility alias which the service
// routes to this formal id. New selections must use the billed model id.
'qwen3.8-max',
'qwen3.7-max',
'qwen3.7-plus',
'qwen3.6-plus',
Expand Down
20 changes: 20 additions & 0 deletions packages/runtime/src/__tests__/model-factory-thinking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,26 @@ describe('buildProviderOptions: thinking level', () => {
assert.deepEqual(buildProviderOptions(conn('deepseek'), 'deepseek-chat', 'high'), {});
});

test('Alibaba Token Plan sends the formal Qwen3.8 effort and disable wires', () => {
for (const providerType of ['alibaba-token-plan-cn', 'alibaba-token-plan'] as const) {
assert.deepEqual(
Object.values(buildProviderOptions(conn(providerType), 'qwen3.8-max', 'xhigh')),
[{ reasoningEffort: 'xhigh' }],
providerType,
);
assert.deepEqual(
Object.values(buildProviderOptions(conn(providerType), 'qwen3.8-max', 'medium')),
[{ reasoningEffort: 'medium' }],
providerType,
);
assert.deepEqual(
Object.values(buildProviderOptions(conn(providerType), 'qwen3.8-max', 'off')),
[{ reasoningEffort: 'none' }],
providerType,
);
}
});

test('family fallback wires per-model override adapters under their SDK namespaces', () => {
// opencode serves models across several protocols via models.dev package
// overrides; the family fallback must emit the namespace each SDK consumes.
Expand Down
10 changes: 10 additions & 0 deletions packages/runtime/src/__tests__/responses-wire-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ function openAiNamespace(options: Record<string, unknown>): Record<string, unkno
}

describe('responses wire contract', () => {
test('keeps Qwen3.8 Max on Token Plan Chat until the provider adapter supports Responses', () => {
for (const providerType of ['alibaba-token-plan-cn', 'alibaba-token-plan'] as const) {
assert.equal(
resolveModelRuntime({ providerType }, 'qwen3.8-max').wire,
'openai-chat',
providerType,
);
}
});

test('every Responses model asks for encrypted reasoning', () => {
// `store: false` is not a privacy preference here, it is the switch that
// makes the SDK add `include: ['reasoning.encrypted_content']` and drop
Expand Down
Loading