Remove deprecated AI chat models flagged by live model check

OpenAI's o3-pro, Mistral's mistral-large-2512 and zai-glm-5-2, and
DeepSeek's deepseek-v4-flash / deepseek-v4-flash-vision-exp are no
longer served by their upstream APIs, so drop them from the hardcoded
provider model lists (closes HeyPuter/puter#3945).

DeepSeek's legacy chat/reasoner aliases and default model previously
pointed at deepseek-v4-flash; they now resolve to deepseek-v4-pro, the
remaining model in that catalog, so existing callers keep working.

Signed-off-by: Anuj Shrivastava <anujshrivastava10e@gmail.com>
Signed-off-by: Anuj Shrivastava <anuj.shrivastava@btr.group>
This commit is contained in:
Anuj Shrivastava authored and Anuj Shrivastava committed 2026-09-25 19:40:02 +05:30
1 parent 5721bdeb1d
commit e041fb6b9b
6 files changed
+81 -203

No files matched your search

@@ -145,9 +145,9 @@ describe('DeepSeekProvider construction', () => {
// ── Model catalog ───────────────────────────────────────────────────
describe('DeepSeekProvider model catalog', () => {
it('returns deepseek-v4-flash as the default', () => {
it('returns deepseek-v4-pro as the default', () => {
const { provider } = makeProvider();
expect(provider.getDefaultModel()).toBe('deepseek-v4-flash');
expect(provider.getDefaultModel()).toBe('deepseek-v4-pro');
});
it('exposes the static DEEPSEEK_MODELS list verbatim from models()', () => {
@@ -164,7 +164,6 @@ describe('DeepSeekProvider model catalog', () => {
expect(ids).toContain(a);
}
}
expect(ids).toContain('deepseek-v4-flash');
expect(ids).toContain('deepseek-v4-pro');
expect(ids).toContain('deepseek-chat');
expect(ids).toContain('deepseek/deepseek-v4-pro');
@@ -190,13 +189,13 @@ describe('DeepSeekProvider.complete request shape', () => {
await withTestActor(() =>
provider.complete({
model: 'deepseek-v4-flash',
model: 'deepseek-v4-pro',
messages: [{ role: 'user', content: 'hello' }],
}),
);
const [args] = createMock.mock.calls[0]!;
expect(args.model).toBe('deepseek-v4-flash');
expect(args.model).toBe('deepseek-v4-pro');
expect(args.messages).toEqual([{ role: 'user', content: 'hello' }]);
expect(args.max_tokens).toBe(1000);
});
@@ -207,7 +206,7 @@ describe('DeepSeekProvider.complete request shape', () => {
await withTestActor(() =>
provider.complete({
model: 'deepseek-v4-flash',
model: 'deepseek-v4-pro',
messages: [{ role: 'user', content: 'hi' }],
max_tokens: 256,
}),
@@ -222,7 +221,7 @@ describe('DeepSeekProvider.complete request shape', () => {
await withTestActor(() =>
provider.complete({
model: 'deepseek-v4-flash',
model: 'deepseek-v4-pro',
messages: [{ role: 'user', content: 'hi' }],
max_tokens: 0,
}),
@@ -237,7 +236,7 @@ describe('DeepSeekProvider.complete request shape', () => {
await withTestActor(() =>
provider.complete({
model: 'deepseek-v4-flash',
model: 'deepseek-v4-pro',
messages: [{ role: 'user', content: 'hi' }],
}),
);
@@ -261,7 +260,7 @@ describe('DeepSeekProvider.complete request shape', () => {
];
await withTestActor(() =>
provider.complete({
model: 'deepseek-v4-flash',
model: 'deepseek-v4-pro',
messages: [{ role: 'user', content: 'hi' }],
tools,
}),
@@ -275,7 +274,7 @@ describe('DeepSeekProvider.complete request shape', () => {
createMock.mockResolvedValueOnce(baseCompletion);
await withTestActor(() =>
provider.complete({
model: 'deepseek-v4-flash',
model: 'deepseek-v4-pro',
messages: [{ role: 'user', content: 'hi' }],
stream: false,
}),
@@ -287,7 +286,7 @@ describe('DeepSeekProvider.complete request shape', () => {
createMock.mockReturnValueOnce(asAsyncIterable([]));
await withTestActor(() =>
provider.complete({
model: 'deepseek-v4-flash',
model: 'deepseek-v4-pro',
messages: [{ role: 'user', content: 'hi' }],
stream: true,
}),
@@ -303,7 +302,7 @@ describe('DeepSeekProvider.complete request shape', () => {
await withTestActor(() =>
provider.complete({
model: 'deepseek-v4-flash',
model: 'deepseek-v4-pro',
messages: [
{
role: 'assistant',
@@ -344,7 +343,7 @@ describe('DeepSeekProvider.complete request shape', () => {
await withTestActor(() =>
provider.complete({
model: 'deepseek-v4-flash',
model: 'deepseek-v4-pro',
messages: [
{
role: 'assistant',
@@ -381,7 +380,7 @@ describe('DeepSeekProvider.complete request shape', () => {
await withTestActor(() =>
provider.complete({
model: 'deepseek-v4-flash',
model: 'deepseek-v4-pro',
messages: [
{ role: 'user', content: 'do tool call' },
{
@@ -429,16 +428,16 @@ describe('DeepSeekProvider model resolution', () => {
await withTestActor(() =>
provider.complete({
model: 'deepseek-v4-flash',
model: 'deepseek-v4-pro',
messages: [{ role: 'user', content: 'hi' }],
}),
);
expect(createMock.mock.calls[0]![0].model).toBe('deepseek-v4-flash');
expect(createMock.mock.calls[0]![0].model).toBe('deepseek-v4-pro');
expect(recordSpy).toHaveBeenCalledWith(
expect.any(Object),
expect.anything(),
'deepseek:deepseek-v4-flash',
'deepseek:deepseek-v4-pro',
expect.any(Object),
);
});
@@ -449,17 +448,17 @@ describe('DeepSeekProvider model resolution', () => {
await withTestActor(() =>
provider.complete({
// `deepseek/deepseek-v4-flash` is an alias of `deepseek-v4-flash`.
model: 'deepseek/deepseek-v4-flash',
// `deepseek/deepseek-v4-pro` is an alias of `deepseek-v4-pro`.
model: 'deepseek/deepseek-v4-pro',
messages: [{ role: 'user', content: 'hi' }],
}),
);
expect(createMock.mock.calls[0]![0].model).toBe('deepseek-v4-flash');
expect(createMock.mock.calls[0]![0].model).toBe('deepseek-v4-pro');
expect(recordSpy).toHaveBeenCalledWith(
expect.any(Object),
expect.anything(),
'deepseek:deepseek-v4-flash',
'deepseek:deepseek-v4-pro',
expect.any(Object),
);
});
@@ -475,26 +474,26 @@ describe('DeepSeekProvider model resolution', () => {
}),
);
expect(createMock.mock.calls[0]![0].model).toBe('deepseek-v4-flash');
expect(createMock.mock.calls[0]![0].model).toBe('deepseek-v4-pro');
expect(recordSpy).toHaveBeenCalledWith(
expect.any(Object),
expect.anything(),
'deepseek:deepseek-v4-flash',
'deepseek:deepseek-v4-pro',
expect.any(Object),
);
});
// The legacy DeepSeek chat/reasoner ids (and their `deepseek/…` and
// `deepseek:deepseek/…` variants) are aliased onto deepseek-v4-flash
// `deepseek:deepseek/…` variants) are aliased onto deepseek-v4-pro
// so callers using the old names get transparently upgraded — and
// metered against the v4-flash canonical prefix.
// metered against the v4-pro canonical prefix.
it.each([
'deepseek-chat',
'deepseek/deepseek-chat',
'deepseek:deepseek/deepseek-chat',
'deepseek/deepseek-reasoner',
'deepseek:deepseek/deepseek-reasoner',
])('maps legacy alias %s onto deepseek-v4-flash', async (alias) => {
])('maps legacy alias %s onto deepseek-v4-pro', async (alias) => {
const { provider } = makeProvider();
createMock.mockResolvedValueOnce(baseCompletion);
@@ -505,11 +504,11 @@ describe('DeepSeekProvider model resolution', () => {
}),
);
expect(createMock.mock.calls[0]![0].model).toBe('deepseek-v4-flash');
expect(createMock.mock.calls[0]![0].model).toBe('deepseek-v4-pro');
expect(recordSpy).toHaveBeenCalledWith(
expect.any(Object),
expect.anything(),
'deepseek:deepseek-v4-flash',
'deepseek:deepseek-v4-pro',
expect.any(Object),
);
});
@@ -536,7 +535,7 @@ describe('DeepSeekProvider.complete non-stream output', () => {
const result = await withTestActor(() =>
provider.complete({
model: 'deepseek-v4-flash',
model: 'deepseek-v4-pro',
messages: [{ role: 'user', content: 'hi' }],
}),
);
@@ -551,8 +550,8 @@ describe('DeepSeekProvider.complete non-stream output', () => {
cached_tokens: 10,
});
// deepseek-v4-flash costs: prompt=14, completion=28, cached=0.28
const chat = DEEPSEEK_MODELS.find((m) => m.id === 'deepseek-v4-flash')!;
// deepseek-v4-pro costs: prompt=174, completion=348, cached=1.45
const chat = DEEPSEEK_MODELS.find((m) => m.id === 'deepseek-v4-pro')!;
expect(recordSpy).toHaveBeenCalledTimes(1);
const [usage, actor, prefix, overrides] = recordSpy.mock.calls[0]!;
expect(usage).toEqual({
@@ -561,7 +560,7 @@ describe('DeepSeekProvider.complete non-stream output', () => {
cached_tokens: 10,
});
expect(actor).toBe(SYSTEM_ACTOR);
expect(prefix).toBe('deepseek:deepseek-v4-flash');
expect(prefix).toBe('deepseek:deepseek-v4-pro');
expect(overrides).toEqual({
prompt_tokens: 100 * Number(chat.costs.prompt_tokens),
completion_tokens: 50 * Number(chat.costs.completion_tokens),
@@ -596,7 +595,7 @@ describe('DeepSeekProvider.complete non-stream output', () => {
const result = (await withTestActor(() =>
provider.complete({
model: 'deepseek-v4-flash',
model: 'deepseek-v4-pro',
messages: [{ role: 'user', content: 'do a tool call' }],
tools: [
{
@@ -631,7 +630,7 @@ describe('DeepSeekProvider.complete non-stream output', () => {
await withTestActor(() =>
provider.complete({
model: 'deepseek-v4-flash',
model: 'deepseek-v4-pro',
messages: [{ role: 'user', content: 'hi' }],
}),
);
@@ -664,7 +663,7 @@ describe('DeepSeekProvider.complete streaming', () => {
const result = await withTestActor(() =>
provider.complete({
model: 'deepseek-v4-flash',
model: 'deepseek-v4-pro',
messages: [{ role: 'user', content: 'say hi' }],
stream: true,
}),
@@ -689,10 +688,10 @@ describe('DeepSeekProvider.complete streaming', () => {
cached_tokens: 1,
});
const chat = DEEPSEEK_MODELS.find((m) => m.id === 'deepseek-v4-flash')!;
const chat = DEEPSEEK_MODELS.find((m) => m.id === 'deepseek-v4-pro')!;
expect(recordSpy).toHaveBeenCalledTimes(1);
const [, , prefix, overrides] = recordSpy.mock.calls[0]!;
expect(prefix).toBe('deepseek:deepseek-v4-flash');
expect(prefix).toBe('deepseek:deepseek-v4-pro');
expect(overrides).toEqual({
prompt_tokens: 4 * Number(chat.costs.prompt_tokens),
completion_tokens: 2 * Number(chat.costs.completion_tokens),
@@ -745,7 +744,7 @@ describe('DeepSeekProvider.complete streaming', () => {
const result = await withTestActor(() =>
provider.complete({
model: 'deepseek-v4-flash',
model: 'deepseek-v4-pro',
messages: [{ role: 'user', content: 'do tool call' }],
tools: [
{
@@ -784,7 +783,7 @@ describe('DeepSeekProvider.complete error mapping', () => {
await expect(
withTestActor(() =>
provider.complete({
model: 'deepseek-v4-flash',
model: 'deepseek-v4-pro',
messages: [{ role: 'user', content: 'boom' }],
}),
),
@@ -41,7 +41,7 @@ export class DeepSeekProvider implements IChatProvider {
}
getDefaultModel() {
return 'deepseek-v4-flash';
return 'deepseek-v4-pro';
}
models() {
@@ -21,60 +21,6 @@ import type { IChatModel } from '../../types.js';
// Hardcoded from https://models.dev/api.json
export const DEEPSEEK_MODELS: IChatModel[] = [
{
puterId: 'deepseek:deepseek/deepseek-v4-flash',
id: 'deepseek-v4-flash',
modalities: { input: ['text'], output: ['text'] },
open_weights: false,
tool_call: true,
knowledge: '2026-04',
release_date: '2026-04-24',
name: 'DeepSeek Chat',
aliases: [
'deepseek/deepseek-v4-flash',
'deepseek-chat',
'deepseek/deepseek-chat',
'deepseek/deepseek-reasoner',
'deepseek:deepseek/deepseek-reasoner',
'deepseek:deepseek/deepseek-chat',
],
context: 1_000_000,
costs_currency: 'usd-cents',
input_cost_key: 'prompt_tokens',
output_cost_key: 'completion_tokens',
costs: {
tokens: 1_000_000,
prompt_tokens: 14,
completion_tokens: 28,
cached_tokens: 0.28,
},
max_tokens: 384_000,
},
{
// Priced identically to deepseek-v4-flash per DeepSeek's launch note
// (https://api-docs.deepseek.com/news/news260821/) and the pricing
// page, which lists the same rates for both models; images are
// tokenized (up to 384 tokens each) and billed as input tokens.
puterId: 'deepseek:deepseek/deepseek-v4-flash-vision-exp',
id: 'deepseek-v4-flash-vision-exp',
modalities: { input: ['text', 'image'], output: ['text'] },
open_weights: false,
tool_call: true,
release_date: '2026-08-21',
name: 'DeepSeek V4 Flash Vision (Experimental)',
aliases: ['deepseek/deepseek-v4-flash-vision-exp'],
context: 1_000_000,
costs_currency: 'usd-cents',
input_cost_key: 'prompt_tokens',
output_cost_key: 'completion_tokens',
costs: {
tokens: 1_000_000,
prompt_tokens: 14,
completion_tokens: 28,
cached_tokens: 0.28,
},
max_tokens: 384_000,
},
{
puterId: 'deepseek:deepseek/deepseek-v4-pro',
id: 'deepseek-v4-pro',
@@ -84,7 +30,14 @@ export const DEEPSEEK_MODELS: IChatModel[] = [
knowledge: '2026-04',
release_date: '2026-04-24',
name: 'DeepSeek Chat',
aliases: ['deepseek/deepseek-v4-pro'],
aliases: [
'deepseek/deepseek-v4-pro',
'deepseek-chat',
'deepseek/deepseek-chat',
'deepseek/deepseek-reasoner',
'deepseek:deepseek/deepseek-reasoner',
'deepseek:deepseek/deepseek-chat',
],
context: 1_000_000,
costs_currency: 'usd-cents',
input_cost_key: 'prompt_tokens',
@@ -53,35 +53,6 @@ export const MISTRAL_MODELS: IChatModel[] = [
completion_tokens: 750,
},
},
{
puterId: 'mistralai:mistralai/mistral-large-2512',
id: 'mistral-large-2512',
modalities: { input: ['text', 'image'], output: ['text'] },
open_weights: true,
tool_call: true,
knowledge: '2025-12',
release_date: '2025-12-02',
name: 'Mistral Large 3',
aliases: [
'mistral-large-latest',
'mistral-large',
'mistralai/mistral-large-latest',
'mistralai/mistral-large-2512',
],
context: 262_144,
max_tokens: 262_144,
description:
'State-of-the-art, open-weight, general-purpose multimodal model.',
provider: 'mistral',
costs_currency: 'usd-cents',
input_cost_key: 'prompt_tokens',
output_cost_key: 'completion_tokens',
costs: {
tokens: 1_000_000,
prompt_tokens: 50,
completion_tokens: 150,
},
},
{
puterId: 'mistralai:mistralai/mistral-small-2603',
id: 'mistral-small-2603',
@@ -111,29 +82,6 @@ export const MISTRAL_MODELS: IChatModel[] = [
completion_tokens: 60,
},
},
{
puterId: 'mistralai:mistralai/zai-glm-5-2',
id: 'zai-glm-5-2',
modalities: { input: ['text'], output: ['text'] },
open_weights: true,
tool_call: true,
release_date: '2026-08-06',
name: 'Z.ai GLM 5.2',
aliases: ['glm-5-2'],
context: 1_000_000,
max_tokens: 128_000,
description:
'Third-party open-source model from Z.ai, hosted by Mistral for long-context coding and agentic workflows.',
provider: 'mistral',
costs_currency: 'usd-cents',
input_cost_key: 'prompt_tokens',
output_cost_key: 'completion_tokens',
costs: {
tokens: 1_000_000,
prompt_tokens: 140,
completion_tokens: 440,
},
},
{
puterId: 'mistralai:mistralai/codestral-2508',
id: 'codestral-2508',
@@ -193,7 +193,7 @@ describe('OpenAiResponsesChatProvider model catalog', () => {
it('models() exposes Responses-only and dual-API entries', () => {
const { provider } = makeProvider();
const ids = provider.models().map((m: { id: string }) => m.id);
expect(ids).toContain('o3-pro');
expect(ids).toContain('gpt-5.6-sol');
expect(ids).toContain('gpt-6-astra');
expect(ids).not.toContain('gpt-5-nano-2025-08-07');
});
@@ -236,15 +236,15 @@ describe('OpenAiResponsesChatProvider model catalog', () => {
.models({ no_restrictions: true })
.map((m: { id: string }) => m.id);
// Both responses-only AND chat-only ids should be present.
expect(ids).toContain('o3-pro');
expect(ids).toContain('gpt-5.6-sol');
expect(ids).toContain('gpt-5-nano-2025-08-07');
});
it('list() flattens canonical ids and aliases for Responses models', () => {
const { provider } = makeProvider();
const ids = provider.list();
expect(ids).toContain('o3-pro');
expect(ids).toContain('openai/o3-pro');
expect(ids).toContain('gpt-5.6-sol');
expect(ids).toContain('openai/gpt-5.6-sol');
expect(ids).toContain('gpt-6-astra');
expect(ids).toContain('openai/gpt-6-astra');
});
@@ -258,7 +258,7 @@ describe('OpenAiResponsesChatProvider.complete argument validation', () => {
await expect(
withTestActor(() =>
provider.complete({
model: 'o3-pro',
model: 'gpt-5.6-sol',
messages: 'hello' as unknown as never,
}),
),
@@ -283,7 +283,7 @@ describe('OpenAiResponsesChatProvider.complete request shape', () => {
await withTestActor(() =>
provider.complete({
model: 'o3-pro',
model: 'gpt-5.6-sol',
messages: [{ role: 'user', content: 'hello' }],
max_tokens: 256,
temperature: 0.4,
@@ -291,7 +291,7 @@ describe('OpenAiResponsesChatProvider.complete request shape', () => {
);
const [args] = responsesCreateMock.mock.calls[0]!;
expect(args.model).toBe('o3-pro');
expect(args.model).toBe('gpt-5.6-sol');
// Responses API takes `input`, not `messages`.
expect(args.input).toEqual([{ role: 'user', content: 'hello' }]);
expect(args.max_output_tokens).toBe(256);
@@ -306,7 +306,7 @@ describe('OpenAiResponsesChatProvider.complete request shape', () => {
await withTestActor(
() =>
provider.complete({
model: 'o3-pro',
model: 'gpt-5.6-sol',
messages: [{ role: 'user', content: 'hello' }],
}),
actor,
@@ -325,7 +325,7 @@ describe('OpenAiResponsesChatProvider.complete request shape', () => {
await withTestActor(() =>
provider.complete({
model: 'o3-pro',
model: 'gpt-5.6-sol',
messages: [{ role: 'user', content: 'hi' }],
tools: [
{
@@ -363,7 +363,7 @@ describe('OpenAiResponsesChatProvider.complete request shape', () => {
await withTestActor(() =>
provider.complete({
model: 'o3-pro',
model: 'gpt-5.6-sol',
messages: [{ role: 'user', content: 'hi' }],
tool_choice: 'auto',
parallel_tool_calls: false,
@@ -404,11 +404,11 @@ describe('OpenAiResponsesChatProvider.complete request shape', () => {
expect('reasoning_effort' in gpt5Args).toBe(false);
expect('verbosity' in gpt5Args).toBe(false);
// o3-pro: not gpt-5 → forwards both.
// o3: not gpt-5 → forwards both.
responsesCreateMock.mockResolvedValueOnce(baseResponse);
await withTestActor(() =>
provider.complete({
model: 'o3-pro',
model: 'o3',
messages: [{ role: 'user', content: 'hi' }],
reasoning_effort: 'medium',
verbosity: 'low',
@@ -509,17 +509,17 @@ describe('OpenAiResponsesChatProvider model resolution', () => {
await withTestActor(() =>
provider.complete({
// openai/o3-pro is an alias of o3-pro.
model: 'openai/o3-pro',
// openai/gpt-5.6-sol is an alias of gpt-5.6-sol.
model: 'openai/gpt-5.6-sol',
messages: [{ role: 'user', content: 'hi' }],
}),
);
expect(responsesCreateMock.mock.calls[0]![0].model).toBe('o3-pro');
expect(responsesCreateMock.mock.calls[0]![0].model).toBe('gpt-5.6-sol');
expect(recordSpy).toHaveBeenCalledWith(
expect.any(Object),
expect.anything(),
'openai:o3-pro',
'openai:gpt-5.6-sol',
expect.any(Object),
);
});
@@ -565,7 +565,7 @@ describe('OpenAiResponsesChatProvider.complete non-stream output', () => {
const result = await withTestActor(() =>
provider.complete({
model: 'o3-pro',
model: 'gpt-5.6-sol',
messages: [{ role: 'user', content: 'hi' }],
}),
);
@@ -584,21 +584,21 @@ describe('OpenAiResponsesChatProvider.complete non-stream output', () => {
cached_tokens: 10,
});
// o3-pro costs: prompt=2000, completion=8000, cached=50.
const o3pro = OPEN_AI_MODELS.find((m) => m.id === 'o3-pro')!;
// gpt-5.6-sol costs: prompt=400, completion=2000, cached=40.
const sol = OPEN_AI_MODELS.find((m) => m.id === 'gpt-5.6-sol')!;
expect(recordSpy).toHaveBeenCalledTimes(1);
const [usage, actor, prefix, overrides] = recordSpy.mock.calls[0]!;
expect(actor).toBe(SYSTEM_ACTOR);
expect(prefix).toBe('openai:o3-pro');
expect(prefix).toBe('openai:gpt-5.6-sol');
expect(usage).toEqual({
prompt_tokens: 90,
completion_tokens: 50,
cached_tokens: 10,
});
expect(overrides).toEqual({
prompt_tokens: 90 * Number(o3pro.costs.prompt_tokens),
completion_tokens: 50 * Number(o3pro.costs.completion_tokens),
cached_tokens: 10 * Number(o3pro.costs.cached_tokens ?? 0),
prompt_tokens: 90 * Number(sol.costs.prompt_tokens),
completion_tokens: 50 * Number(sol.costs.completion_tokens),
cached_tokens: 10 * Number(sol.costs.cached_tokens ?? 0),
});
});
@@ -742,7 +742,7 @@ describe('OpenAiResponsesChatProvider.complete non-stream output', () => {
const result = (await withTestActor(() =>
provider.complete({
model: 'o3-pro',
model: 'gpt-5.6-sol',
messages: [{ role: 'user', content: 'do a tool call' }],
}),
)) as { message: { tool_calls?: unknown[] } };
@@ -768,7 +768,7 @@ describe('OpenAiResponsesChatProvider.complete non-stream output', () => {
await expect(
withTestActor(() =>
provider.complete({
model: 'o3-pro',
model: 'gpt-5.6-sol',
messages: [{ role: 'user', content: 'silence' }],
}),
),
@@ -800,7 +800,7 @@ describe('OpenAiResponsesChatProvider.complete streaming', () => {
const result = await withTestActor(() =>
provider.complete({
model: 'o3-pro',
model: 'gpt-5.6-sol',
messages: [{ role: 'user', content: 'say hi' }],
stream: true,
}),
@@ -826,15 +826,15 @@ describe('OpenAiResponsesChatProvider.complete streaming', () => {
cached_tokens: 1,
});
// o3-pro costs: prompt=2000, completion=8000, cached=50.
const o3pro = OPEN_AI_MODELS.find((m) => m.id === 'o3-pro')!;
// gpt-5.6-sol costs: prompt=400, completion=2000, cached=40.
const sol = OPEN_AI_MODELS.find((m) => m.id === 'gpt-5.6-sol')!;
expect(recordSpy).toHaveBeenCalledTimes(1);
const [, , prefix, overrides] = recordSpy.mock.calls[0]!;
expect(prefix).toBe('openai:o3-pro');
expect(prefix).toBe('openai:gpt-5.6-sol');
expect(overrides).toEqual({
prompt_tokens: 3 * Number(o3pro.costs.prompt_tokens),
completion_tokens: 2 * Number(o3pro.costs.completion_tokens),
cached_tokens: 1 * Number(o3pro.costs.cached_tokens ?? 0),
prompt_tokens: 3 * Number(sol.costs.prompt_tokens),
completion_tokens: 2 * Number(sol.costs.completion_tokens),
cached_tokens: 1 * Number(sol.costs.cached_tokens ?? 0),
});
});
@@ -863,7 +863,7 @@ describe('OpenAiResponsesChatProvider.complete streaming', () => {
const result = await withTestActor(() =>
provider.complete({
model: 'o3-pro',
model: 'gpt-5.6-sol',
messages: [{ role: 'user', content: 'tool call' }],
stream: true,
}),
@@ -928,7 +928,7 @@ describe('OpenAiResponsesChatProvider.complete error mapping', () => {
await expect(
withTestActor(() =>
provider.complete({
model: 'o3-pro',
model: 'gpt-5.6-sol',
messages: [{ role: 'user', content: 'boom' }],
}),
),
@@ -556,28 +556,6 @@ export const OPEN_AI_MODELS: IChatModel[] = [
context: 200_000,
max_tokens: 100000,
},
{
puterId: 'openai:openai/o3-pro',
id: 'o3-pro',
modalities: { input: ['text', 'image'], output: ['text'] },
open_weights: false,
tool_call: true,
knowledge: '2024-05',
release_date: '2025-06-10',
aliases: ['o3-pro-2025-06-10', 'openai/o3-pro'],
costs_currency: 'usd-cents',
input_cost_key: 'prompt_tokens',
output_cost_key: 'completion_tokens',
costs: {
tokens: 1_000_000,
prompt_tokens: 2000,
cached_tokens: 50,
completion_tokens: 8000,
},
context: 200_000,
max_tokens: 100000,
responses_api_only: true,
},
{
puterId: 'openai:openai/o3-mini',
id: 'o3-mini',