diff --git a/src/backend/drivers/ai-chat/providers/deepseek/DeepSeekProvider.test.ts b/src/backend/drivers/ai-chat/providers/deepseek/DeepSeekProvider.test.ts index b0d59d2ad..5c7903086 100644 --- a/src/backend/drivers/ai-chat/providers/deepseek/DeepSeekProvider.test.ts +++ b/src/backend/drivers/ai-chat/providers/deepseek/DeepSeekProvider.test.ts @@ -145,9 +145,9 @@ describe('DeepSeekProvider construction', () => { // ── Model catalog ─────────────────────────────────────────────────── describe('DeepSeekProvider model catalog', () => { - it('returns deepseek-chat as the default', () => { + it('returns deepseek-v4-flash as the default', () => { const { provider } = makeProvider(); - expect(provider.getDefaultModel()).toBe('deepseek-chat'); + expect(provider.getDefaultModel()).toBe('deepseek-v4-flash'); }); it('exposes the static DEEPSEEK_MODELS list verbatim from models()', () => { @@ -164,8 +164,10 @@ 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-chat'); + expect(ids).toContain('deepseek/deepseek-v4-pro'); }); }); @@ -188,13 +190,13 @@ describe('DeepSeekProvider.complete request shape', () => { await withTestActor(() => provider.complete({ - model: 'deepseek-chat', + model: 'deepseek-v4-flash', messages: [{ role: 'user', content: 'hello' }], }), ); const [args] = createMock.mock.calls[0]!; - expect(args.model).toBe('deepseek-chat'); + expect(args.model).toBe('deepseek-v4-flash'); expect(args.messages).toEqual([{ role: 'user', content: 'hello' }]); expect(args.max_tokens).toBe(1000); }); @@ -205,7 +207,7 @@ describe('DeepSeekProvider.complete request shape', () => { await withTestActor(() => provider.complete({ - model: 'deepseek-chat', + model: 'deepseek-v4-flash', messages: [{ role: 'user', content: 'hi' }], max_tokens: 256, }), @@ -220,7 +222,7 @@ describe('DeepSeekProvider.complete request shape', () => { await withTestActor(() => provider.complete({ - model: 'deepseek-chat', + model: 'deepseek-v4-flash', messages: [{ role: 'user', content: 'hi' }], }), ); @@ -244,7 +246,7 @@ describe('DeepSeekProvider.complete request shape', () => { ]; await withTestActor(() => provider.complete({ - model: 'deepseek-chat', + model: 'deepseek-v4-flash', messages: [{ role: 'user', content: 'hi' }], tools, }), @@ -258,7 +260,7 @@ describe('DeepSeekProvider.complete request shape', () => { createMock.mockResolvedValueOnce(baseCompletion); await withTestActor(() => provider.complete({ - model: 'deepseek-chat', + model: 'deepseek-v4-flash', messages: [{ role: 'user', content: 'hi' }], stream: false, }), @@ -270,7 +272,7 @@ describe('DeepSeekProvider.complete request shape', () => { createMock.mockReturnValueOnce(asAsyncIterable([])); await withTestActor(() => provider.complete({ - model: 'deepseek-chat', + model: 'deepseek-v4-flash', messages: [{ role: 'user', content: 'hi' }], stream: true, }), @@ -286,7 +288,7 @@ describe('DeepSeekProvider.complete request shape', () => { await withTestActor(() => provider.complete({ - model: 'deepseek-chat', + model: 'deepseek-v4-flash', messages: [ { role: 'assistant', @@ -327,7 +329,7 @@ describe('DeepSeekProvider.complete request shape', () => { await withTestActor(() => provider.complete({ - model: 'deepseek-chat', + model: 'deepseek-v4-flash', messages: [ { role: 'assistant', @@ -364,7 +366,7 @@ describe('DeepSeekProvider.complete request shape', () => { await withTestActor(() => provider.complete({ - model: 'deepseek-chat', + model: 'deepseek-v4-flash', messages: [ { role: 'user', content: 'do tool call' }, { @@ -412,16 +414,16 @@ describe('DeepSeekProvider model resolution', () => { await withTestActor(() => provider.complete({ - model: 'deepseek-reasoner', + model: 'deepseek-v4-flash', messages: [{ role: 'user', content: 'hi' }], }), ); - expect(createMock.mock.calls[0]![0].model).toBe('deepseek-reasoner'); + expect(createMock.mock.calls[0]![0].model).toBe('deepseek-v4-flash'); expect(recordSpy).toHaveBeenCalledWith( expect.any(Object), expect.anything(), - 'deepseek:deepseek-reasoner', + 'deepseek:deepseek-v4-flash', expect.any(Object), ); }); @@ -432,17 +434,17 @@ describe('DeepSeekProvider model resolution', () => { await withTestActor(() => provider.complete({ - // `deepseek/deepseek-chat` is an alias of `deepseek-chat`. - model: 'deepseek/deepseek-chat', + // `deepseek/deepseek-v4-flash` is an alias of `deepseek-v4-flash`. + model: 'deepseek/deepseek-v4-flash', messages: [{ role: 'user', content: 'hi' }], }), ); - expect(createMock.mock.calls[0]![0].model).toBe('deepseek-chat'); + expect(createMock.mock.calls[0]![0].model).toBe('deepseek-v4-flash'); expect(recordSpy).toHaveBeenCalledWith( expect.any(Object), expect.anything(), - 'deepseek:deepseek-chat', + 'deepseek:deepseek-v4-flash', expect.any(Object), ); }); @@ -458,11 +460,41 @@ describe('DeepSeekProvider model resolution', () => { }), ); - expect(createMock.mock.calls[0]![0].model).toBe('deepseek-chat'); + expect(createMock.mock.calls[0]![0].model).toBe('deepseek-v4-flash'); expect(recordSpy).toHaveBeenCalledWith( expect.any(Object), expect.anything(), - 'deepseek:deepseek-chat', + 'deepseek:deepseek-v4-flash', + expect.any(Object), + ); + }); + + // The legacy DeepSeek chat/reasoner ids (and their `deepseek/…` and + // `deepseek:deepseek/…` variants) are aliased onto deepseek-v4-flash + // so callers using the old names get transparently upgraded — and + // metered against the v4-flash 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) => { + const { provider } = makeProvider(); + createMock.mockResolvedValueOnce(baseCompletion); + + await withTestActor(() => + provider.complete({ + model: alias, + messages: [{ role: 'user', content: 'hi' }], + }), + ); + + expect(createMock.mock.calls[0]![0].model).toBe('deepseek-v4-flash'); + expect(recordSpy).toHaveBeenCalledWith( + expect.any(Object), + expect.anything(), + 'deepseek:deepseek-v4-flash', expect.any(Object), ); }); @@ -489,7 +521,7 @@ describe('DeepSeekProvider.complete non-stream output', () => { const result = await withTestActor(() => provider.complete({ - model: 'deepseek-chat', + model: 'deepseek-v4-flash', messages: [{ role: 'user', content: 'hi' }], }), ); @@ -504,8 +536,8 @@ describe('DeepSeekProvider.complete non-stream output', () => { cached_tokens: 10, }); - // deepseek-chat costs: prompt=56, completion=168, cached=0. - const chat = DEEPSEEK_MODELS.find((m) => m.id === 'deepseek-chat')!; + // deepseek-v4-flash costs: prompt=14, completion=28, cached=0.28 + const chat = DEEPSEEK_MODELS.find((m) => m.id === 'deepseek-v4-flash')!; expect(recordSpy).toHaveBeenCalledTimes(1); const [usage, actor, prefix, overrides] = recordSpy.mock.calls[0]!; expect(usage).toEqual({ @@ -514,7 +546,7 @@ describe('DeepSeekProvider.complete non-stream output', () => { cached_tokens: 10, }); expect(actor).toBe(SYSTEM_ACTOR); - expect(prefix).toBe('deepseek:deepseek-chat'); + expect(prefix).toBe('deepseek:deepseek-v4-flash'); expect(overrides).toEqual({ prompt_tokens: 100 * Number(chat.costs.prompt_tokens), completion_tokens: 50 * Number(chat.costs.completion_tokens), @@ -549,7 +581,7 @@ describe('DeepSeekProvider.complete non-stream output', () => { const result = (await withTestActor(() => provider.complete({ - model: 'deepseek-chat', + model: 'deepseek-v4-flash', messages: [{ role: 'user', content: 'do a tool call' }], tools: [ { @@ -584,7 +616,7 @@ describe('DeepSeekProvider.complete non-stream output', () => { await withTestActor(() => provider.complete({ - model: 'deepseek-chat', + model: 'deepseek-v4-flash', messages: [{ role: 'user', content: 'hi' }], }), ); @@ -617,7 +649,7 @@ describe('DeepSeekProvider.complete streaming', () => { const result = await withTestActor(() => provider.complete({ - model: 'deepseek-chat', + model: 'deepseek-v4-flash', messages: [{ role: 'user', content: 'say hi' }], stream: true, }), @@ -642,10 +674,10 @@ describe('DeepSeekProvider.complete streaming', () => { cached_tokens: 1, }); - const chat = DEEPSEEK_MODELS.find((m) => m.id === 'deepseek-chat')!; + const chat = DEEPSEEK_MODELS.find((m) => m.id === 'deepseek-v4-flash')!; expect(recordSpy).toHaveBeenCalledTimes(1); const [, , prefix, overrides] = recordSpy.mock.calls[0]!; - expect(prefix).toBe('deepseek:deepseek-chat'); + expect(prefix).toBe('deepseek:deepseek-v4-flash'); expect(overrides).toEqual({ prompt_tokens: 4 * Number(chat.costs.prompt_tokens), completion_tokens: 2 * Number(chat.costs.completion_tokens), @@ -698,7 +730,7 @@ describe('DeepSeekProvider.complete streaming', () => { const result = await withTestActor(() => provider.complete({ - model: 'deepseek-chat', + model: 'deepseek-v4-flash', messages: [{ role: 'user', content: 'do tool call' }], tools: [ { @@ -737,7 +769,7 @@ describe('DeepSeekProvider.complete error mapping', () => { await expect( withTestActor(() => provider.complete({ - model: 'deepseek-chat', + model: 'deepseek-v4-flash', messages: [{ role: 'user', content: 'boom' }], }), ), diff --git a/src/backend/drivers/ai-chat/providers/deepseek/DeepSeekProvider.ts b/src/backend/drivers/ai-chat/providers/deepseek/DeepSeekProvider.ts index 01bbf5356..53edb1db5 100644 --- a/src/backend/drivers/ai-chat/providers/deepseek/DeepSeekProvider.ts +++ b/src/backend/drivers/ai-chat/providers/deepseek/DeepSeekProvider.ts @@ -40,7 +40,7 @@ export class DeepSeekProvider implements IChatProvider { } getDefaultModel() { - return 'deepseek-chat'; + return 'deepseek-v4-flash'; } models() { @@ -137,20 +137,18 @@ export class DeepSeekProvider implements IChatProvider { ); this.#meteringService.utilRecordUsageObject( trackedUsage, - actor, + actor!, `deepseek:${modelUsed.id}`, costsOverrideFromModel, ); return trackedUsage; }, - stream, + stream: stream, completion, }); } - checkModeration( - _text: string, - ): ReturnType { + checkModeration(_text: string) { throw new Error('Method not implemented.'); } } diff --git a/src/backend/drivers/ai-chat/providers/deepseek/models.ts b/src/backend/drivers/ai-chat/providers/deepseek/models.ts index 8bc3a9a25..3705ae20e 100644 --- a/src/backend/drivers/ai-chat/providers/deepseek/models.ts +++ b/src/backend/drivers/ai-chat/providers/deepseek/models.ts @@ -22,47 +22,54 @@ import type { IChatModel } from '../../types.js'; // Hardcoded from https://models.dev/api.json export const DEEPSEEK_MODELS: IChatModel[] = [ { - puterId: 'deepseek:deepseek/deepseek-chat', - id: 'deepseek-chat', + puterId: 'deepseek:deepseek/deepseek-v4-flash', + id: 'deepseek-v4-flash', modalities: { input: ['text'], output: ['text'] }, open_weights: false, tool_call: true, - knowledge: '2024-07', - release_date: '2024-12-26', + knowledge: '2026-04', + release_date: '2026-04-24', name: 'DeepSeek Chat', - aliases: ['deepseek/deepseek-chat'], - context: 128000, + aliases: [ + '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: 56, - completion_tokens: 168, - cached_tokens: 0, + prompt_tokens: 14, + completion_tokens: 28, + cached_tokens: 0.28, }, - max_tokens: 8000, + max_tokens: 384_000, }, { - puterId: 'deepseek:deepseek/deepseek-reasoner', - id: 'deepseek-reasoner', + puterId: 'deepseek:deepseek/deepseek-v4-pro', + id: 'deepseek-v4-pro', modalities: { input: ['text'], output: ['text'] }, open_weights: false, tool_call: true, - knowledge: '2024-07', - release_date: '2025-01-20', - name: 'DeepSeek Reasoner', - aliases: ['deepseek/deepseek-reasoner'], - context: 128000, + knowledge: '2026-04', + release_date: '2026-04-24', + name: 'DeepSeek Chat', + aliases: ['deepseek/deepseek-v4-pro', 'deepseek-v4-pro'], + 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: 56, - completion_tokens: 168, - cached_tokens: 0, + prompt_tokens: 174, + completion_tokens: 348, + cached_tokens: 1.45, }, - max_tokens: 64000, + max_tokens: 384_000, }, ]; diff --git a/src/backend/drivers/ai-chat/utils/OpenAIUtil.js b/src/backend/drivers/ai-chat/utils/OpenAIUtil.js index 05145e8b1..71ddde6cd 100644 --- a/src/backend/drivers/ai-chat/utils/OpenAIUtil.js +++ b/src/backend/drivers/ai-chat/utils/OpenAIUtil.js @@ -384,20 +384,10 @@ export const create_chat_stream_handler_responses_api = chatStream.end(usage); }; -/** - * - * @param {object} params - * @param {(args: {usage: import("openai/resources/completions.mjs").CompletionUsage})=> unknown } params.usage_calculator - * @returns - */ -export const handle_completion_output = async ({ - deviations, - stream, - completion, - moderate, - usage_calculator, - finally_fn, -}) => { +export const handle_completion_output = async ( + /** @type {Record & {usage_calculator:(args: {usage: import("openai/resources/completions.mjs").CompletionUsage})=> unknown }}*/ + { deviations, stream, completion, moderate, usage_calculator, finally_fn }, +) => { deviations = Object.assign( { // affected by: Mistral