diff --git a/src/backend/drivers/ai-chat/providers/openai/OpenAiChatCompletionsProvider.test.ts b/src/backend/drivers/ai-chat/providers/openai/OpenAiChatCompletionsProvider.test.ts index 158becad0..d84368db4 100644 --- a/src/backend/drivers/ai-chat/providers/openai/OpenAiChatCompletionsProvider.test.ts +++ b/src/backend/drivers/ai-chat/providers/openai/OpenAiChatCompletionsProvider.test.ts @@ -154,7 +154,7 @@ describe('OpenAiChatProvider model catalog', () => { expect(provider.getDefaultModel()).toBe('gpt-5-nano'); }); - it('models() filters out responses_api_only entries', () => { + it('models() excludes Responses-only entries and includes dual-API entries', () => { const { provider } = makeProvider(); const ids = provider.models().map((m) => m.id); const responsesOnly = OPEN_AI_MODELS.filter( @@ -165,6 +165,7 @@ describe('OpenAiChatProvider model catalog', () => { } // gpt-5-nano is a Chat-Completions model, must be present. expect(ids).toContain('gpt-5-nano-2025-08-07'); + expect(ids).toContain('gpt-6-astra'); }); it('list() flattens canonical ids and aliases', () => { @@ -269,6 +270,26 @@ describe('OpenAiChatProvider.complete request shape', () => { expect(args.temperature).toBe(0.4); }); + it('resolves the namespaced GPT-6 Astra alias', async () => { + const { provider } = makeProvider(); + createMock.mockResolvedValueOnce(baseCompletion); + + await withTestActor(() => + provider.complete({ + model: 'openai/gpt-6-astra', + messages: [{ role: 'user', content: 'hello' }], + }), + ); + + expect(createMock.mock.calls[0]![0].model).toBe('gpt-6-astra'); + expect(recordSpy).toHaveBeenCalledWith( + expect.any(Object), + expect.anything(), + 'openai:gpt-6-astra', + expect.any(Object), + ); + }); + it('forwards temperature 0 and max_tokens 0 instead of dropping them', async () => { const { provider } = makeProvider(); createMock.mockResolvedValueOnce(baseCompletion); diff --git a/src/backend/drivers/ai-chat/providers/openai/OpenAiChatResponsesProvider.test.ts b/src/backend/drivers/ai-chat/providers/openai/OpenAiChatResponsesProvider.test.ts index 7105833ee..ace3c29e1 100644 --- a/src/backend/drivers/ai-chat/providers/openai/OpenAiChatResponsesProvider.test.ts +++ b/src/backend/drivers/ai-chat/providers/openai/OpenAiChatResponsesProvider.test.ts @@ -161,12 +161,35 @@ describe('OpenAiResponsesChatProvider model catalog', () => { expect(provider.getDefaultModel()).toBe('gpt-5-nano'); }); - it('models() with default args exposes only responses_api_only entries', () => { + it('exposes GPT-6 Astra with its API metadata and pricing', () => { + const { provider } = makeProvider(); + const astra = provider.models().find((m) => m.id === 'gpt-6-astra'); + + expect(astra).toMatchObject({ + puterId: 'openai:openai/gpt-6-astra', + modalities: { input: ['text', 'image'], output: ['text'] }, + knowledge: '2026-04-30', + release_date: '2026-09-04', + aliases: ['openai/gpt-6-astra'], + costs_currency: 'usd-cents', + costs: { + tokens: 1_000_000, + prompt_tokens: 1000, + cached_tokens: 100, + completion_tokens: 5000, + }, + context: 1_050_000, + max_tokens: 128_000, + responses_api: true, + }); + expect(astra).not.toHaveProperty('responses_api_only'); + }); + + it('models() exposes Responses-only and dual-API entries', () => { const { provider } = makeProvider(); const ids = provider.models().map((m: { id: string }) => m.id); - // Sanity: a known responses_api_only model is included. expect(ids).toContain('o3-pro'); - // And a known Chat-Completions-only model is excluded. + expect(ids).toContain('gpt-6-astra'); expect(ids).not.toContain('gpt-5-nano-2025-08-07'); }); @@ -180,11 +203,13 @@ describe('OpenAiResponsesChatProvider model catalog', () => { expect(ids).toContain('gpt-5-nano-2025-08-07'); }); - it('list() flattens canonical ids and aliases for responses-only models', () => { + 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-6-astra'); + expect(ids).toContain('openai/gpt-6-astra'); }); }); @@ -346,6 +371,28 @@ describe('OpenAiResponsesChatProvider model resolution', () => { usage: { input_tokens: 1, output_tokens: 1 }, }; + it('resolves the namespaced GPT-6 Astra alias', async () => { + const { provider } = makeProvider(); + responsesCreateMock.mockResolvedValueOnce(baseResponse); + + await withTestActor(() => + provider.complete({ + model: 'openai/gpt-6-astra', + messages: [{ role: 'user', content: 'hi' }], + }), + ); + + expect(responsesCreateMock.mock.calls[0]![0].model).toBe( + 'gpt-6-astra', + ); + expect(recordSpy).toHaveBeenCalledWith( + expect.any(Object), + expect.anything(), + 'openai:gpt-6-astra', + expect.any(Object), + ); + }); + it('resolves an alias to its canonical id (across the entire model catalog)', async () => { const { provider } = makeProvider(); responsesCreateMock.mockResolvedValueOnce(baseResponse); diff --git a/src/backend/drivers/ai-chat/providers/openai/OpenAiChatResponsesProvider.ts b/src/backend/drivers/ai-chat/providers/openai/OpenAiChatResponsesProvider.ts index 4993cc7a3..96999cc3c 100644 --- a/src/backend/drivers/ai-chat/providers/openai/OpenAiChatResponsesProvider.ts +++ b/src/backend/drivers/ai-chat/providers/openai/OpenAiChatResponsesProvider.ts @@ -74,7 +74,9 @@ export class OpenAiResponsesChatProvider implements IChatProvider { if (extra_params?.no_restrictions) { return OPEN_AI_MODELS; } - return OPEN_AI_MODELS.filter((e) => e.responses_api_only === true); + return OPEN_AI_MODELS.filter( + (e) => e.responses_api_only === true || e.responses_api === true, + ); } list() { diff --git a/src/backend/drivers/ai-chat/providers/openai/models.ts b/src/backend/drivers/ai-chat/providers/openai/models.ts index c6f2672db..f8936942a 100644 --- a/src/backend/drivers/ai-chat/providers/openai/models.ts +++ b/src/backend/drivers/ai-chat/providers/openai/models.ts @@ -23,6 +23,28 @@ import type { IChatModel } from '../../types.js'; // Hardcoded from https://models.dev/api.json export const OPEN_AI_MODELS: IChatModel[] = [ + { + puterId: 'openai:openai/gpt-6-astra', + id: 'gpt-6-astra', + modalities: { input: ['text', 'image'], output: ['text'] }, + open_weights: false, + tool_call: true, + knowledge: '2026-04-30', + release_date: '2026-09-04', + aliases: ['openai/gpt-6-astra'], + costs_currency: 'usd-cents', + input_cost_key: 'prompt_tokens', + output_cost_key: 'completion_tokens', + costs: { + tokens: 1_000_000, + prompt_tokens: 1000, + cached_tokens: 100, + completion_tokens: 5000, + }, + context: 1_050_000, + max_tokens: 128_000, + responses_api: true, + }, { puterId: 'openai:openai/gpt-5.6-sol', id: 'gpt-5.6-sol', diff --git a/src/backend/drivers/ai-chat/types.ts b/src/backend/drivers/ai-chat/types.ts index d9e83d8c2..2950d7a2a 100644 --- a/src/backend/drivers/ai-chat/types.ts +++ b/src/backend/drivers/ai-chat/types.ts @@ -50,6 +50,8 @@ export interface IChatModel extends Record< modalities?: ModelModalities; open_weights?: boolean; tool_call?: boolean; + responses_api?: boolean; + responses_api_only?: boolean; knowledge?: string; release_date?: string; }