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
23 changes: 23 additions & 0 deletions packages/core/src/__tests__/model-catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,29 @@ test('catalog transport preserves independent limits and their unmodified defaul
assert.equal(decoded.defaultInputLimit, 32000);
});

test('GPT-6 Sol and Luna use GPT labels and expose supported thinking levels', () => {
const models = [{ id: 'gpt-6-sol' }, { id: 'gpt-6-luna' }];
for (const providerType of ['openai', 'openai-codex'] as const) {
const entries = buildModelCatalogEntries({ providerType, models, modelSource: 'fetched' });
assert.deepEqual(
entries.map(({ id, displayName, thinkingLevels }) => ({ id, displayName, thinkingLevels })),
[
{
id: 'gpt-6-sol',
displayName: 'GPT-6 Sol',
thinkingLevels: ['low', 'medium', 'high', 'xhigh', 'max'],
},
{
id: 'gpt-6-luna',
displayName: 'GPT-6 Luna',
thinkingLevels: ['low', 'medium', 'high', 'xhigh', 'max'],
},
],
providerType,
);
}
});

test('a live inventory annotates a model it omits and preserves higher-priority failures', () => {
const input = {
providerType: 'zai-coding-plan' as const,
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/__tests__/model-metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,13 @@ describe('model-metadata vision capability', () => {
});

describe('openAiAdapterApiProtocol', () => {
it('routes a normalized gpt-5 family to the Responses wire', () => {
it('routes normalized GPT-5 and GPT-6 families to the Responses wire', () => {
assert.equal(openAiAdapterApiProtocol(' GPT-5.6-sol '), 'openai-responses');
assert.equal(openAiAdapterApiProtocol('gpt-6-sol'), 'openai-responses');
assert.equal(openAiAdapterApiProtocol('gpt-6-luna'), 'openai-responses');
});

it('keeps a non-gpt-5 OpenAI model on the Chat Completions wire', () => {
it('keeps an older OpenAI model on the Chat Completions wire', () => {
assert.equal(openAiAdapterApiProtocol('gpt-4o'), 'openai-chat');
});

Expand Down
31 changes: 30 additions & 1 deletion packages/core/src/model-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ export function openAiAdapterApiProtocol(
(providerType === 'opencode-go' && id === 'muse-spark-1.2-contributor') ||
((providerType === 'alibaba-token-plan-cn' || providerType === 'alibaba-token-plan') &&
id === 'qwen3.8-max') ||
/^gpt-5/i.test(id) ||
/^gpt-[56]/i.test(id) ||
((providerType === 'xai' || providerType === 'xai-oauth') && id === 'grok-4.5')
? 'openai-responses'
: 'openai-chat';
Expand Down Expand Up @@ -267,6 +267,26 @@ const GOOGLE_MODEL_OVERRIDES: Record<string, ModelMetadata> = {
},
};

// These models are in the live models.dev OpenAI catalog but not yet in the
// bundled snapshot. The OpenAI Responses SDK accepts only these five GPT-6
// efforts on both API and Codex OAuth paths. It discards `none` and the Codex
// model list's `ultra` for Sol, so do not offer them until the request path
// can send and handle them.
const OPENAI_GPT6_THINKING_OPTIONS: ThinkingOptions = {
efforts: ['low', 'medium', 'high', 'xhigh', 'max'],
};

const OPENAI_GPT6_MODEL_OVERRIDES: Record<string, ModelMetadata> = {
'gpt-6-sol': {
displayName: 'GPT-6 Sol',
thinkingOptions: OPENAI_GPT6_THINKING_OPTIONS,
},
'gpt-6-luna': {
displayName: 'GPT-6 Luna',
thinkingOptions: OPENAI_GPT6_THINKING_OPTIONS,
},
};

// The OAuth path pins its own context windows over whatever the public
// catalog says. Base facts come from the active table, falling back to the
// shipped snapshot so a model upstream stops listing keeps a display name.
Expand All @@ -284,6 +304,14 @@ function withoutInputLimit(metadata: ModelMetadata | undefined): ModelMetadata |

function openAiOAuthModelMetadata(active: ModelsDevMetadata): Record<string, ModelMetadata> {
return {
'gpt-6-sol': {
...openAiOAuthBase(active, 'gpt-6-sol'),
...OPENAI_GPT6_MODEL_OVERRIDES['gpt-6-sol'],
},
'gpt-6-luna': {
...openAiOAuthBase(active, 'gpt-6-luna'),
...OPENAI_GPT6_MODEL_OVERRIDES['gpt-6-luna'],
},
'gpt-5.6-sol': {
...openAiOAuthBase(active, 'gpt-5.6-sol'),
contextWindow: 372_000,
Expand Down Expand Up @@ -488,6 +516,7 @@ const COMMAND_CODE_MODEL_METADATA: Record<string, ModelMetadata> = {
function buildStaticModelMetadata(active: ModelsDevMetadata): ModelsDevMetadata {
return {
anthropic: ANTHROPIC_MODEL_OVERRIDES,
openai: OPENAI_GPT6_MODEL_OVERRIDES,
'claude-subscription': claudeSubscriptionModelMetadata(active),
// The Command Code Provider-API plan rides the same effort table.
commandcode: COMMAND_CODE_MODEL_METADATA,
Expand Down
22 changes: 22 additions & 0 deletions packages/runtime/src/__tests__/model-factory-thinking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,28 @@ describe('buildProviderOptions: thinking level', () => {
});
});

test('GPT-6 Sol and Luna send selected reasoning effort on API and Codex paths', () => {
for (const modelId of ['gpt-6-sol', 'gpt-6-luna']) {
assert.deepEqual(buildProviderOptions(conn('openai'), modelId, 'max'), {
openai: {
store: false,
reasoningSummary: 'auto',
reasoningEffort: 'max',
parallelToolCalls: true,
},
});
assert.deepEqual(buildProviderOptions(conn('openai-codex'), modelId, 'max'), {
openai: {
store: false,
textVerbosity: 'medium',
reasoningSummary: 'auto',
reasoningEffort: 'max',
parallelToolCalls: true,
},
});
}
});

test('openai-codex (gpt-5.5) preserves store:false / textVerbosity and merges reasoningEffort', () => {
assert.deepEqual(buildProviderOptions(conn('openai-codex'), 'gpt-5.5'), {
openai: {
Expand Down
42 changes: 42 additions & 0 deletions packages/runtime/src/__tests__/responses-wire-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import type { LlmConnection } from '@maka/core/llm-connections';
import { buildModelCatalogEntries } from '@maka/core/model-catalog';
import type { RuntimeEvent } from '@maka/core/runtime-event';
import { z } from 'zod';
import { modelMetadataIdsForProvider } from '@maka/core/model-metadata';
Expand Down Expand Up @@ -53,6 +54,47 @@ function openAiNamespace(options: Record<string, unknown>): Record<string, unkno
}

describe('responses wire contract', () => {
test('GPT-6 catalog thinking levels reach Responses for API and Codex OAuth', async () => {
for (const providerType of ['openai', 'openai-codex'] as const) {
for (const modelId of ['gpt-6-sol', 'gpt-6-luna']) {
const [entry] = buildModelCatalogEntries({
providerType,
models: [{ id: modelId }],
modelSource: 'fetched',
});
assert.ok(entry);
assert.deepEqual(entry.thinkingLevels, ['low', 'medium', 'high', 'xhigh', 'max']);

const requests: Record<string, unknown>[] = [];
const fetch = (async (_url: string | URL | Request, init?: RequestInit) => {
requests.push(JSON.parse(String(init?.body)) as Record<string, unknown>);
return Response.json({
id: 'response-gpt-6',
object: 'response',
status: 'completed',
output: [],
usage: { input_tokens: 1, output_tokens: 1 },
});
}) as typeof globalThis.fetch;
const connection = conn(providerType);
const model = getAIModel({ connection, apiKey: 'test-token', modelId, fetch });

for (const level of entry.thinkingLevels) {
await model.doGenerate({
prompt: [{ role: 'user', content: [{ type: 'text', text: 'ping' }] }],
providerOptions: buildProviderOptions(connection, modelId, level),
});
}

assert.deepEqual(
requests.map((body) => (body.reasoning as { effort?: string } | undefined)?.effort),
entry.thinkingLevels,
`${providerType}/${modelId}`,
);
}
}
});

test('does not route Maka tool_search history through OpenAI native tool_search validation', async () => {
const connection = conn('openai-codex', 'codex-subscription');
connection.defaultModel = 'gpt-5.6-sol';
Expand Down