@robota-sdk/agent-provider 3.0.0-beta.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/browser/index.d.ts +1104 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +7 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/loggers/index.cjs +1 -0
- package/dist/loggers/index.d.ts +151 -0
- package/dist/loggers/index.d.ts.map +1 -0
- package/dist/loggers/index.js +2 -0
- package/dist/loggers/index.js.map +1 -0
- package/dist/node/anthropic/index.cjs +1 -0
- package/dist/node/anthropic/index.d.ts +158 -0
- package/dist/node/anthropic/index.d.ts.map +1 -0
- package/dist/node/anthropic/index.js +1 -0
- package/dist/node/anthropic--1vgLC-e.js +5 -0
- package/dist/node/anthropic--1vgLC-e.js.map +1 -0
- package/dist/node/anthropic-BFQ6DSCP.cjs +4 -0
- package/dist/node/bytedance/index.cjs +1 -0
- package/dist/node/bytedance/index.d.ts +74 -0
- package/dist/node/bytedance/index.d.ts.map +1 -0
- package/dist/node/bytedance/index.js +1 -0
- package/dist/node/bytedance-C_0sF_pJ.js +2 -0
- package/dist/node/bytedance-C_0sF_pJ.js.map +1 -0
- package/dist/node/bytedance-DVPxqEiC.cjs +1 -0
- package/dist/node/chunk-Bmb41Sf3.cjs +1 -0
- package/dist/node/deepseek/index.cjs +1 -0
- package/dist/node/deepseek/index.d.ts +2 -0
- package/dist/node/deepseek/index.js +1 -0
- package/dist/node/deepseek-_8Ixx7rA.js +2 -0
- package/dist/node/deepseek-_8Ixx7rA.js.map +1 -0
- package/dist/node/deepseek-oA2Y6bD0.cjs +1 -0
- package/dist/node/gemini/index.cjs +1 -0
- package/dist/node/gemini/index.d.ts +173 -0
- package/dist/node/gemini/index.d.ts.map +1 -0
- package/dist/node/gemini/index.js +1 -0
- package/dist/node/gemini-Bh2U87MY.js +4 -0
- package/dist/node/gemini-Bh2U87MY.js.map +1 -0
- package/dist/node/gemini-DSaNCxZj.cjs +3 -0
- package/dist/node/gemma/index.cjs +1 -0
- package/dist/node/gemma/index.d.ts +2 -0
- package/dist/node/gemma/index.js +1 -0
- package/dist/node/gemma-Dp_AfCUR.js +2 -0
- package/dist/node/gemma-Dp_AfCUR.js.map +1 -0
- package/dist/node/gemma-G-Pf_PnX.cjs +1 -0
- package/dist/node/google/index.cjs +1 -0
- package/dist/node/google/index.d.ts +14 -0
- package/dist/node/google/index.d.ts.map +1 -0
- package/dist/node/google/index.js +2 -0
- package/dist/node/google/index.js.map +1 -0
- package/dist/node/index-B6PnlDMd.d.ts +82 -0
- package/dist/node/index-B6PnlDMd.d.ts.map +1 -0
- package/dist/node/index-B7UvPJcI.d.ts +315 -0
- package/dist/node/index-B7UvPJcI.d.ts.map +1 -0
- package/dist/node/index-BLPOTNb5.d.ts +98 -0
- package/dist/node/index-BLPOTNb5.d.ts.map +1 -0
- package/dist/node/index-BqixM_XD.d.ts +231 -0
- package/dist/node/index-BqixM_XD.d.ts.map +1 -0
- package/dist/node/index-C3beaqKO.d.ts +231 -0
- package/dist/node/index-C3beaqKO.d.ts.map +1 -0
- package/dist/node/index-Cp2XRh9G.d.ts +82 -0
- package/dist/node/index-Cp2XRh9G.d.ts.map +1 -0
- package/dist/node/index-DSv5xruI.d.ts +98 -0
- package/dist/node/index-DSv5xruI.d.ts.map +1 -0
- package/dist/node/index-w0bV1uaP.d.ts +315 -0
- package/dist/node/index-w0bV1uaP.d.ts.map +1 -0
- package/dist/node/index.cjs +1 -0
- package/dist/node/index.d.ts +8 -0
- package/dist/node/index.js +1 -0
- package/dist/node/openai/index.cjs +1 -0
- package/dist/node/openai/index.d.ts +2 -0
- package/dist/node/openai/index.js +1 -0
- package/dist/node/openai-CRQjg4xF.js +2 -0
- package/dist/node/openai-CRQjg4xF.js.map +1 -0
- package/dist/node/openai-compatible-BYfyY5lb.cjs +1 -0
- package/dist/node/openai-compatible-Dm4Sof9e.js +2 -0
- package/dist/node/openai-compatible-Dm4Sof9e.js.map +1 -0
- package/dist/node/openai-xWC6pY7r.cjs +1 -0
- package/dist/node/qwen/index.cjs +1 -0
- package/dist/node/qwen/index.d.ts +2 -0
- package/dist/node/qwen/index.js +1 -0
- package/dist/node/qwen-ChUZobTL.js +2 -0
- package/dist/node/qwen-ChUZobTL.js.map +1 -0
- package/dist/node/qwen-CjT71vSM.cjs +1 -0
- package/package.json +157 -0
- package/src/anthropic/__tests__/abort-streaming.test.ts +199 -0
- package/src/anthropic/__tests__/model-catalog-refresh.test.ts +92 -0
- package/src/anthropic/__tests__/provider-definition.test.ts +55 -0
- package/src/anthropic/__tests__/provider.test.ts +1357 -0
- package/src/anthropic/__tests__/response-parser.test.ts +326 -0
- package/src/anthropic/index.ts +22 -0
- package/src/anthropic/message-converter.ts +181 -0
- package/src/anthropic/model-catalog-refresh.ts +128 -0
- package/src/anthropic/parsers/response-parser.ts +184 -0
- package/src/anthropic/provider-definition.ts +93 -0
- package/src/anthropic/provider.ts +290 -0
- package/src/anthropic/streaming-handler.ts +204 -0
- package/src/anthropic/types/api-types.ts +158 -0
- package/src/anthropic/types.ts +79 -0
- package/src/bytedance/http-client.test.ts +288 -0
- package/src/bytedance/http-client.ts +163 -0
- package/src/bytedance/index.ts +2 -0
- package/src/bytedance/provider.spec.ts +320 -0
- package/src/bytedance/provider.ts +171 -0
- package/src/bytedance/status-mapper.test.ts +299 -0
- package/src/bytedance/status-mapper.ts +141 -0
- package/src/bytedance/types.ts +68 -0
- package/src/deepseek/defaults.ts +4 -0
- package/src/deepseek/index.ts +22 -0
- package/src/deepseek/model-catalog-refresh.test.ts +57 -0
- package/src/deepseek/model-catalog-refresh.ts +105 -0
- package/src/deepseek/model-catalog.ts +55 -0
- package/src/deepseek/provider-definition.test.ts +109 -0
- package/src/deepseek/provider-definition.ts +132 -0
- package/src/deepseek/provider.test.ts +324 -0
- package/src/deepseek/provider.ts +298 -0
- package/src/deepseek/types.ts +37 -0
- package/src/gemini/execution-helpers.ts +233 -0
- package/src/gemini/genai-transport.test.ts +208 -0
- package/src/gemini/image-operations.test.ts +448 -0
- package/src/gemini/image-operations.ts +261 -0
- package/src/gemini/index.ts +11 -0
- package/src/gemini/message-converter.test.ts +616 -0
- package/src/gemini/message-converter.ts +140 -0
- package/src/gemini/model-catalog-refresh.test.ts +107 -0
- package/src/gemini/model-catalog-refresh.ts +92 -0
- package/src/gemini/provider-definition.test.ts +70 -0
- package/src/gemini/provider-definition.ts +78 -0
- package/src/gemini/provider-extended.test.ts +898 -0
- package/src/gemini/provider.spec.ts +216 -0
- package/src/gemini/provider.ts +279 -0
- package/src/gemini/request-converter.ts +226 -0
- package/src/gemini/tool-schema-converter.ts +78 -0
- package/src/gemini/types/api-types.ts +235 -0
- package/src/gemini/types.ts +121 -0
- package/src/gemma/index.ts +5 -0
- package/src/gemma/message-factory.ts +38 -0
- package/src/gemma/provider-definition.test.ts +43 -0
- package/src/gemma/provider-definition.ts +84 -0
- package/src/gemma/provider-projection.ts +49 -0
- package/src/gemma/provider.test.ts +628 -0
- package/src/gemma/provider.ts +308 -0
- package/src/gemma/pseudo-command-envelope.ts +58 -0
- package/src/gemma/pseudo-tool-call-projector.ts +243 -0
- package/src/gemma/pseudo-tool-call-tag-parser.ts +153 -0
- package/src/gemma/pseudo-tool-call-types.ts +31 -0
- package/src/gemma/reasoning-projector.test.ts +52 -0
- package/src/gemma/reasoning-projector.ts +144 -0
- package/src/gemma/streaming-projection.ts +79 -0
- package/src/gemma/tool-call-argument-parser.ts +126 -0
- package/src/gemma/tool-call-projector.test.ts +227 -0
- package/src/gemma/tool-call-projector.ts +264 -0
- package/src/gemma/types.ts +27 -0
- package/src/google/index.ts +11 -0
- package/src/google/provider-compat.test.ts +19 -0
- package/src/google/provider-definition.ts +6 -0
- package/src/google/provider.ts +10 -0
- package/src/google/types.ts +5 -0
- package/src/index.ts +9 -0
- package/src/openai/adapter.test.ts +494 -0
- package/src/openai/adapter.ts +145 -0
- package/src/openai/chat-completions-chat.ts +189 -0
- package/src/openai/executor-integration.test.ts +206 -0
- package/src/openai/index.ts +21 -0
- package/src/openai/interfaces/payload-logger.ts +48 -0
- package/src/openai/loggers/console-payload-logger.test.ts +173 -0
- package/src/openai/loggers/console-payload-logger.ts +94 -0
- package/src/openai/loggers/console.ts +9 -0
- package/src/openai/loggers/file-payload-logger.test.ts +238 -0
- package/src/openai/loggers/file-payload-logger.ts +112 -0
- package/src/openai/loggers/file.ts +9 -0
- package/src/openai/loggers/index.ts +12 -0
- package/src/openai/loggers/sanitize-openai-log-data.test.ts +89 -0
- package/src/openai/loggers/sanitize-openai-log-data.ts +14 -0
- package/src/openai/message-converter.ts +22 -0
- package/src/openai/model-catalog-refresh.test.ts +92 -0
- package/src/openai/model-catalog-refresh.ts +115 -0
- package/src/openai/openai-request-format.ts +92 -0
- package/src/openai/parsers/response-parser.test.ts +407 -0
- package/src/openai/parsers/response-parser.ts +47 -0
- package/src/openai/provider-definition.test.ts +75 -0
- package/src/openai/provider-definition.ts +132 -0
- package/src/openai/provider.test.ts +1402 -0
- package/src/openai/provider.ts +237 -0
- package/src/openai/responses-chat.ts +258 -0
- package/src/openai/responses-converter.ts +112 -0
- package/src/openai/responses-parser.ts +285 -0
- package/src/openai/responses-stream-utils.ts +45 -0
- package/src/openai/responses-types.ts +195 -0
- package/src/openai/streaming/stream-assembler.ts +3 -0
- package/src/openai/streaming/stream-handler.test.ts +367 -0
- package/src/openai/streaming/stream-handler.ts +119 -0
- package/src/openai/types/api-types.ts +112 -0
- package/src/openai/types.ts +194 -0
- package/src/qwen/defaults.ts +26 -0
- package/src/qwen/index.ts +5 -0
- package/src/qwen/model-catalog-refresh.test.ts +91 -0
- package/src/qwen/model-catalog-refresh.ts +97 -0
- package/src/qwen/provider-capabilities.ts +34 -0
- package/src/qwen/provider-definition.test.ts +139 -0
- package/src/qwen/provider-definition.ts +173 -0
- package/src/qwen/provider-streaming-assembly.ts +40 -0
- package/src/qwen/provider.test.ts +640 -0
- package/src/qwen/provider.ts +293 -0
- package/src/qwen/responses-chat.ts +194 -0
- package/src/qwen/responses-converter.ts +104 -0
- package/src/qwen/responses-parser.ts +299 -0
- package/src/qwen/responses-stream-utils.ts +38 -0
- package/src/qwen/types.ts +228 -0
- package/src/shared/openai-compatible/endpoint-probe.test.ts +52 -0
- package/src/shared/openai-compatible/endpoint-probe.ts +43 -0
- package/src/shared/openai-compatible/index.ts +6 -0
- package/src/shared/openai-compatible/message-converter.test.ts +111 -0
- package/src/shared/openai-compatible/message-converter.ts +84 -0
- package/src/shared/openai-compatible/native-payload-observer.test.ts +43 -0
- package/src/shared/openai-compatible/native-payload-observer.ts +26 -0
- package/src/shared/openai-compatible/response-parser.test.ts +172 -0
- package/src/shared/openai-compatible/response-parser.ts +180 -0
- package/src/shared/openai-compatible/stream-assembler.test.ts +266 -0
- package/src/shared/openai-compatible/stream-assembler.ts +248 -0
- package/src/shared/openai-compatible/types.ts +59 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { IProviderModelCatalogEntry } from '@robota-sdk/agent-core';
|
|
2
|
+
import { DEFAULT_DEEPSEEK_PROVIDER_MODEL } from './defaults';
|
|
3
|
+
|
|
4
|
+
export const DEEPSEEK_MODEL_CATALOG_SOURCE_URL =
|
|
5
|
+
'https://api-docs.deepseek.com/quick_start/pricing';
|
|
6
|
+
export const DEEPSEEK_MODEL_LIST_SOURCE_URL = 'https://api-docs.deepseek.com/api/list-models';
|
|
7
|
+
export const DEEPSEEK_MODEL_LAST_VERIFIED_AT = '2026-05-07';
|
|
8
|
+
export const DEEPSEEK_DEPRECATED_ALIAS_RETIREMENT_DATE = '2026-07-24';
|
|
9
|
+
|
|
10
|
+
export const DEEPSEEK_MODEL_CATALOG_ENTRIES: readonly IProviderModelCatalogEntry[] = [
|
|
11
|
+
{
|
|
12
|
+
id: DEFAULT_DEEPSEEK_PROVIDER_MODEL,
|
|
13
|
+
displayName: 'DeepSeek V4 Flash',
|
|
14
|
+
contextWindow: 1_000_000,
|
|
15
|
+
capabilities: ['tools', 'reasoning', 'json_schema', 'streaming'],
|
|
16
|
+
lifecycle: 'active',
|
|
17
|
+
sourceUrl: DEEPSEEK_MODEL_CATALOG_SOURCE_URL,
|
|
18
|
+
lastVerifiedAt: DEEPSEEK_MODEL_LAST_VERIFIED_AT,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'deepseek-v4-pro',
|
|
22
|
+
displayName: 'DeepSeek V4 Pro',
|
|
23
|
+
contextWindow: 1_000_000,
|
|
24
|
+
capabilities: ['tools', 'reasoning', 'json_schema', 'streaming'],
|
|
25
|
+
lifecycle: 'active',
|
|
26
|
+
sourceUrl: DEEPSEEK_MODEL_CATALOG_SOURCE_URL,
|
|
27
|
+
lastVerifiedAt: DEEPSEEK_MODEL_LAST_VERIFIED_AT,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'deepseek-chat',
|
|
31
|
+
displayName: `DeepSeek Chat compatibility alias, deprecated ${DEEPSEEK_DEPRECATED_ALIAS_RETIREMENT_DATE}`,
|
|
32
|
+
aliases: [DEFAULT_DEEPSEEK_PROVIDER_MODEL],
|
|
33
|
+
contextWindow: 1_000_000,
|
|
34
|
+
capabilities: ['tools', 'json_schema', 'streaming'],
|
|
35
|
+
lifecycle: 'deprecated',
|
|
36
|
+
sourceUrl: DEEPSEEK_MODEL_CATALOG_SOURCE_URL,
|
|
37
|
+
lastVerifiedAt: DEEPSEEK_MODEL_LAST_VERIFIED_AT,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'deepseek-reasoner',
|
|
41
|
+
displayName: `DeepSeek Reasoner compatibility alias, deprecated ${DEEPSEEK_DEPRECATED_ALIAS_RETIREMENT_DATE}`,
|
|
42
|
+
aliases: [DEFAULT_DEEPSEEK_PROVIDER_MODEL],
|
|
43
|
+
contextWindow: 1_000_000,
|
|
44
|
+
capabilities: ['reasoning', 'json_schema', 'streaming'],
|
|
45
|
+
lifecycle: 'deprecated',
|
|
46
|
+
sourceUrl: DEEPSEEK_MODEL_CATALOG_SOURCE_URL,
|
|
47
|
+
lastVerifiedAt: DEEPSEEK_MODEL_LAST_VERIFIED_AT,
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
export function getDeepSeekFallbackModelCatalogEntry(
|
|
52
|
+
id: string,
|
|
53
|
+
): IProviderModelCatalogEntry | undefined {
|
|
54
|
+
return DEEPSEEK_MODEL_CATALOG_ENTRIES.find((entry) => entry.id === id);
|
|
55
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
createDeepSeekProviderDefinition,
|
|
4
|
+
DeepSeekProvider,
|
|
5
|
+
DEFAULT_DEEPSEEK_PROVIDER_API_KEY_ENV,
|
|
6
|
+
DEFAULT_DEEPSEEK_PROVIDER_API_KEY_REFERENCE,
|
|
7
|
+
DEFAULT_DEEPSEEK_PROVIDER_BASE_URL,
|
|
8
|
+
DEFAULT_DEEPSEEK_PROVIDER_MODEL,
|
|
9
|
+
} from './index';
|
|
10
|
+
|
|
11
|
+
vi.mock('openai', () => {
|
|
12
|
+
const MockOpenAI = vi.fn().mockImplementation(() => ({
|
|
13
|
+
chat: {
|
|
14
|
+
completions: {
|
|
15
|
+
create: vi.fn(),
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
}));
|
|
19
|
+
return { default: MockOpenAI };
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('createDeepSeekProviderDefinition', () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
vi.clearAllMocks();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('exposes DeepSeek defaults, setup steps, and model catalog metadata', () => {
|
|
28
|
+
const definition = createDeepSeekProviderDefinition();
|
|
29
|
+
|
|
30
|
+
expect(definition.type).toBe('deepseek');
|
|
31
|
+
expect(definition.requiresApiKey).toBe(true);
|
|
32
|
+
expect(definition.probeProfile).toBeTypeOf('function');
|
|
33
|
+
expect(definition.refreshModelCatalog).toBeTypeOf('function');
|
|
34
|
+
expect(definition.defaults).toEqual({
|
|
35
|
+
model: DEFAULT_DEEPSEEK_PROVIDER_MODEL,
|
|
36
|
+
apiKey: DEFAULT_DEEPSEEK_PROVIDER_API_KEY_REFERENCE,
|
|
37
|
+
baseURL: DEFAULT_DEEPSEEK_PROVIDER_BASE_URL,
|
|
38
|
+
});
|
|
39
|
+
expect(definition.setupHelpLinks).toEqual([
|
|
40
|
+
{
|
|
41
|
+
kind: 'api-key',
|
|
42
|
+
label: 'DeepSeek API keys',
|
|
43
|
+
url: 'https://platform.deepseek.com/api_keys',
|
|
44
|
+
sourceUrl: 'https://api-docs.deepseek.com/',
|
|
45
|
+
lastVerifiedAt: '2026-05-08',
|
|
46
|
+
},
|
|
47
|
+
]);
|
|
48
|
+
expect(DEFAULT_DEEPSEEK_PROVIDER_API_KEY_ENV).toBe('DEEPSEEK_API_KEY');
|
|
49
|
+
expect(definition.setupSteps).toEqual([
|
|
50
|
+
{
|
|
51
|
+
key: 'baseURL',
|
|
52
|
+
title: 'DeepSeek OpenAI-compatible base URL',
|
|
53
|
+
defaultValue: DEFAULT_DEEPSEEK_PROVIDER_BASE_URL,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
key: 'model',
|
|
57
|
+
title: 'DeepSeek model',
|
|
58
|
+
defaultValue: DEFAULT_DEEPSEEK_PROVIDER_MODEL,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
key: 'apiKey',
|
|
62
|
+
title: 'DeepSeek API key',
|
|
63
|
+
defaultValue: DEFAULT_DEEPSEEK_PROVIDER_API_KEY_REFERENCE,
|
|
64
|
+
masked: true,
|
|
65
|
+
},
|
|
66
|
+
]);
|
|
67
|
+
expect(definition.modelCatalog?.entries?.map((entry) => entry.id)).toEqual([
|
|
68
|
+
'deepseek-v4-flash',
|
|
69
|
+
'deepseek-v4-pro',
|
|
70
|
+
'deepseek-chat',
|
|
71
|
+
'deepseek-reasoner',
|
|
72
|
+
]);
|
|
73
|
+
expect(definition.modelCatalog?.entries?.[2]).toMatchObject({
|
|
74
|
+
id: 'deepseek-chat',
|
|
75
|
+
lifecycle: 'deprecated',
|
|
76
|
+
aliases: ['deepseek-v4-flash'],
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('creates a DeepSeekProvider from a resolved provider profile', () => {
|
|
81
|
+
const definition = createDeepSeekProviderDefinition();
|
|
82
|
+
|
|
83
|
+
const provider = definition.createProvider({
|
|
84
|
+
name: 'deepseek',
|
|
85
|
+
model: 'deepseek-v4-pro',
|
|
86
|
+
apiKey: 'deepseek-key',
|
|
87
|
+
baseURL: 'https://api.deepseek.com',
|
|
88
|
+
timeout: 12_000,
|
|
89
|
+
options: {
|
|
90
|
+
thinking: { type: 'enabled' },
|
|
91
|
+
reasoningEffort: 'max',
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(provider).toBeInstanceOf(DeepSeekProvider);
|
|
96
|
+
expect(provider.name).toBe('deepseek');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('rejects provider creation without an API key', () => {
|
|
100
|
+
const definition = createDeepSeekProviderDefinition();
|
|
101
|
+
|
|
102
|
+
expect(() =>
|
|
103
|
+
definition.createProvider({
|
|
104
|
+
name: 'deepseek',
|
|
105
|
+
model: 'deepseek-v4-flash',
|
|
106
|
+
}),
|
|
107
|
+
).toThrow('Provider deepseek requires apiKey');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { IProviderDefinition, TUniversalValue } from '@robota-sdk/agent-core';
|
|
2
|
+
import { probeOpenAICompatibleProfile } from '../shared/openai-compatible/index.js';
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_DEEPSEEK_PROVIDER_API_KEY_REFERENCE,
|
|
5
|
+
DEFAULT_DEEPSEEK_PROVIDER_BASE_URL,
|
|
6
|
+
DEFAULT_DEEPSEEK_PROVIDER_MODEL,
|
|
7
|
+
} from './defaults';
|
|
8
|
+
import {
|
|
9
|
+
DEEPSEEK_MODEL_CATALOG_ENTRIES,
|
|
10
|
+
DEEPSEEK_MODEL_CATALOG_SOURCE_URL,
|
|
11
|
+
DEEPSEEK_MODEL_LAST_VERIFIED_AT,
|
|
12
|
+
} from './model-catalog';
|
|
13
|
+
import { refreshDeepSeekModelCatalog } from './model-catalog-refresh';
|
|
14
|
+
import { DeepSeekProvider } from './provider';
|
|
15
|
+
import type { TDeepSeekReasoningEffort, TDeepSeekThinkingMode } from './types';
|
|
16
|
+
|
|
17
|
+
const DEEPSEEK_MODEL_CATALOG: NonNullable<IProviderDefinition['modelCatalog']> = {
|
|
18
|
+
status: 'fallback',
|
|
19
|
+
sourceUrl: DEEPSEEK_MODEL_CATALOG_SOURCE_URL,
|
|
20
|
+
lastVerifiedAt: DEEPSEEK_MODEL_LAST_VERIFIED_AT,
|
|
21
|
+
entries: DEEPSEEK_MODEL_CATALOG_ENTRIES,
|
|
22
|
+
};
|
|
23
|
+
const DEEPSEEK_API_KEY_URL = 'https://platform.deepseek.com/api_keys';
|
|
24
|
+
const DEEPSEEK_SETUP_SOURCE_URL = 'https://api-docs.deepseek.com/';
|
|
25
|
+
const DEEPSEEK_SETUP_LAST_VERIFIED_AT = '2026-05-08';
|
|
26
|
+
const DEEPSEEK_SETUP_HELP_LINKS: NonNullable<IProviderDefinition['setupHelpLinks']> = [
|
|
27
|
+
{
|
|
28
|
+
kind: 'api-key',
|
|
29
|
+
label: 'DeepSeek API keys',
|
|
30
|
+
url: DEEPSEEK_API_KEY_URL,
|
|
31
|
+
sourceUrl: DEEPSEEK_SETUP_SOURCE_URL,
|
|
32
|
+
lastVerifiedAt: DEEPSEEK_SETUP_LAST_VERIFIED_AT,
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
export function createDeepSeekProviderDefinition(): IProviderDefinition {
|
|
37
|
+
return {
|
|
38
|
+
type: 'deepseek',
|
|
39
|
+
displayName: 'DeepSeek',
|
|
40
|
+
description: 'DeepSeek OpenAI-compatible endpoint',
|
|
41
|
+
defaults: {
|
|
42
|
+
model: DEFAULT_DEEPSEEK_PROVIDER_MODEL,
|
|
43
|
+
apiKey: DEFAULT_DEEPSEEK_PROVIDER_API_KEY_REFERENCE,
|
|
44
|
+
baseURL: DEFAULT_DEEPSEEK_PROVIDER_BASE_URL,
|
|
45
|
+
},
|
|
46
|
+
modelCatalog: DEEPSEEK_MODEL_CATALOG,
|
|
47
|
+
setupHelpLinks: DEEPSEEK_SETUP_HELP_LINKS,
|
|
48
|
+
setupSteps: [
|
|
49
|
+
{
|
|
50
|
+
key: 'baseURL',
|
|
51
|
+
title: 'DeepSeek OpenAI-compatible base URL',
|
|
52
|
+
defaultValue: DEFAULT_DEEPSEEK_PROVIDER_BASE_URL,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
key: 'model',
|
|
56
|
+
title: 'DeepSeek model',
|
|
57
|
+
defaultValue: DEFAULT_DEEPSEEK_PROVIDER_MODEL,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
key: 'apiKey',
|
|
61
|
+
title: 'DeepSeek API key',
|
|
62
|
+
defaultValue: DEFAULT_DEEPSEEK_PROVIDER_API_KEY_REFERENCE,
|
|
63
|
+
masked: true,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
requiresApiKey: true,
|
|
67
|
+
probeProfile: probeOpenAICompatibleProfile,
|
|
68
|
+
refreshModelCatalog: ({ profile }) => refreshDeepSeekModelCatalog(profile),
|
|
69
|
+
modelCatalogCacheTtlSeconds: 86400,
|
|
70
|
+
createProvider: (config) => {
|
|
71
|
+
const options = parseDeepSeekProviderOptions(config.options);
|
|
72
|
+
return new DeepSeekProvider({
|
|
73
|
+
apiKey: requireApiKey(config.apiKey),
|
|
74
|
+
...(config.baseURL !== undefined && { baseURL: config.baseURL }),
|
|
75
|
+
...(config.timeout !== undefined && { timeout: config.timeout }),
|
|
76
|
+
...(options.thinking !== undefined && { thinking: options.thinking }),
|
|
77
|
+
...(options.reasoningEffort !== undefined && { reasoningEffort: options.reasoningEffort }),
|
|
78
|
+
defaultModel: config.model,
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function requireApiKey(apiKey: string | undefined): string {
|
|
85
|
+
if (!apiKey) {
|
|
86
|
+
throw new Error('Provider deepseek requires apiKey');
|
|
87
|
+
}
|
|
88
|
+
return apiKey;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function parseDeepSeekProviderOptions(options: Record<string, TUniversalValue> | undefined): {
|
|
92
|
+
thinking?: TDeepSeekThinkingMode;
|
|
93
|
+
reasoningEffort?: TDeepSeekReasoningEffort;
|
|
94
|
+
} {
|
|
95
|
+
const thinking = parseThinkingMode(options?.['thinking']);
|
|
96
|
+
const reasoningEffort = parseReasoningEffort(options?.['reasoningEffort']);
|
|
97
|
+
return {
|
|
98
|
+
...(thinking !== undefined && { thinking }),
|
|
99
|
+
...(reasoningEffort !== undefined && { reasoningEffort }),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function parseThinkingMode(value: TUniversalValue | undefined): TDeepSeekThinkingMode | undefined {
|
|
104
|
+
if (value === true) return 'enabled';
|
|
105
|
+
if (value === false) return 'disabled';
|
|
106
|
+
if (value === 'enabled' || value === 'disabled') return value;
|
|
107
|
+
const record = asRecord(value);
|
|
108
|
+
const type = record?.['type'];
|
|
109
|
+
return type === 'enabled' || type === 'disabled' ? type : undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function parseReasoningEffort(
|
|
113
|
+
value: TUniversalValue | undefined,
|
|
114
|
+
): TDeepSeekReasoningEffort | undefined {
|
|
115
|
+
if (
|
|
116
|
+
value === 'low' ||
|
|
117
|
+
value === 'medium' ||
|
|
118
|
+
value === 'high' ||
|
|
119
|
+
value === 'xhigh' ||
|
|
120
|
+
value === 'max'
|
|
121
|
+
) {
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function asRecord(value: TUniversalValue | undefined): Record<string, TUniversalValue> | undefined {
|
|
128
|
+
if (value === null || value === undefined || value instanceof Date || Array.isArray(value)) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
return typeof value === 'object' ? value : undefined;
|
|
132
|
+
}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import type OpenAI from 'openai';
|
|
3
|
+
import type {
|
|
4
|
+
IProviderNativeRawPayloadEvent,
|
|
5
|
+
IToolSchema,
|
|
6
|
+
TUniversalMessage,
|
|
7
|
+
} from '@robota-sdk/agent-core';
|
|
8
|
+
import { DEFAULT_DEEPSEEK_PROVIDER_BASE_URL, DeepSeekProvider } from './index';
|
|
9
|
+
|
|
10
|
+
vi.mock('openai', () => {
|
|
11
|
+
const MockOpenAI = vi.fn().mockImplementation(() => ({
|
|
12
|
+
chat: {
|
|
13
|
+
completions: {
|
|
14
|
+
create: vi.fn(),
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
return { default: MockOpenAI };
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const timestamp = new Date('2026-05-01T00:00:00.000Z');
|
|
22
|
+
|
|
23
|
+
interface IOpenAIClientMock {
|
|
24
|
+
chat: {
|
|
25
|
+
completions: {
|
|
26
|
+
create: ReturnType<typeof vi.fn>;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getClient(provider: DeepSeekProvider): IOpenAIClientMock {
|
|
32
|
+
return (provider as unknown as { client: IOpenAIClientMock }).client;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function createUserMessage(content: string): TUniversalMessage {
|
|
36
|
+
return {
|
|
37
|
+
id: 'user-1',
|
|
38
|
+
role: 'user',
|
|
39
|
+
content,
|
|
40
|
+
state: 'complete',
|
|
41
|
+
timestamp,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function createToolSchema(): IToolSchema {
|
|
46
|
+
return {
|
|
47
|
+
name: 'inspect_file',
|
|
48
|
+
description: 'Inspect a file',
|
|
49
|
+
parameters: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
path: { type: 'string' },
|
|
53
|
+
},
|
|
54
|
+
required: ['path'],
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function* asyncIterableFrom<T>(items: T[]): AsyncIterable<T> {
|
|
60
|
+
for (const item of items) {
|
|
61
|
+
yield item;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function createChunk(
|
|
66
|
+
content: string,
|
|
67
|
+
finishReason: OpenAI.Chat.ChatCompletionChunk.Choice['finish_reason'] = null,
|
|
68
|
+
): OpenAI.Chat.ChatCompletionChunk {
|
|
69
|
+
return {
|
|
70
|
+
id: 'chunk-1',
|
|
71
|
+
object: 'chat.completion.chunk',
|
|
72
|
+
created: 1,
|
|
73
|
+
model: 'deepseek-v4-flash',
|
|
74
|
+
choices: [
|
|
75
|
+
{
|
|
76
|
+
index: 0,
|
|
77
|
+
delta: { content },
|
|
78
|
+
finish_reason: finishReason,
|
|
79
|
+
logprobs: null,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
describe('DeepSeekProvider', () => {
|
|
86
|
+
beforeEach(() => {
|
|
87
|
+
vi.clearAllMocks();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('creates an OpenAI-compatible client with DeepSeek endpoint options', async () => {
|
|
91
|
+
const OpenAIModule = await import('openai');
|
|
92
|
+
const OpenAIConstructor = vi.mocked(OpenAIModule.default);
|
|
93
|
+
|
|
94
|
+
const provider = new DeepSeekProvider({
|
|
95
|
+
apiKey: 'deepseek-key',
|
|
96
|
+
baseURL: 'https://api.deepseek.com',
|
|
97
|
+
timeout: 1000,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(provider.name).toBe('deepseek');
|
|
101
|
+
expect(OpenAIConstructor).toHaveBeenCalledWith({
|
|
102
|
+
apiKey: 'deepseek-key',
|
|
103
|
+
baseURL: 'https://api.deepseek.com',
|
|
104
|
+
timeout: 1000,
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('uses the documented DeepSeek OpenAI-compatible base URL by default', async () => {
|
|
109
|
+
const OpenAIModule = await import('openai');
|
|
110
|
+
const OpenAIConstructor = vi.mocked(OpenAIModule.default);
|
|
111
|
+
|
|
112
|
+
new DeepSeekProvider({
|
|
113
|
+
apiKey: 'deepseek-key',
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(OpenAIConstructor).toHaveBeenCalledWith({
|
|
117
|
+
apiKey: 'deepseek-key',
|
|
118
|
+
baseURL: DEFAULT_DEEPSEEK_PROVIDER_BASE_URL,
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('sends OpenAI-compatible messages, tools, and DeepSeek thinking controls', async () => {
|
|
123
|
+
const provider = new DeepSeekProvider({
|
|
124
|
+
apiKey: 'deepseek-key',
|
|
125
|
+
thinking: 'enabled',
|
|
126
|
+
reasoningEffort: 'high',
|
|
127
|
+
});
|
|
128
|
+
const client = getClient(provider);
|
|
129
|
+
client.chat.completions.create.mockResolvedValue({
|
|
130
|
+
id: 'chatcmpl-test',
|
|
131
|
+
object: 'chat.completion',
|
|
132
|
+
created: 1,
|
|
133
|
+
model: 'deepseek-v4-pro',
|
|
134
|
+
choices: [
|
|
135
|
+
{
|
|
136
|
+
index: 0,
|
|
137
|
+
message: {
|
|
138
|
+
role: 'assistant',
|
|
139
|
+
content: '',
|
|
140
|
+
refusal: null,
|
|
141
|
+
tool_calls: [
|
|
142
|
+
{
|
|
143
|
+
id: 'call_1',
|
|
144
|
+
type: 'function',
|
|
145
|
+
function: {
|
|
146
|
+
name: 'inspect_file',
|
|
147
|
+
arguments: '{"path":"README.md"}',
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
},
|
|
152
|
+
finish_reason: 'tool_calls',
|
|
153
|
+
logprobs: null,
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
usage: {
|
|
157
|
+
prompt_tokens: 10,
|
|
158
|
+
completion_tokens: 4,
|
|
159
|
+
total_tokens: 14,
|
|
160
|
+
},
|
|
161
|
+
} satisfies OpenAI.Chat.ChatCompletion);
|
|
162
|
+
|
|
163
|
+
const result = await provider.chat([createUserMessage('Inspect README')], {
|
|
164
|
+
model: 'deepseek-v4-pro',
|
|
165
|
+
tools: [createToolSchema()],
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(client.chat.completions.create).toHaveBeenCalledWith({
|
|
169
|
+
model: 'deepseek-v4-pro',
|
|
170
|
+
messages: [{ role: 'user', content: 'Inspect README' }],
|
|
171
|
+
tools: [
|
|
172
|
+
{
|
|
173
|
+
type: 'function',
|
|
174
|
+
function: {
|
|
175
|
+
name: 'inspect_file',
|
|
176
|
+
description: 'Inspect a file',
|
|
177
|
+
parameters: createToolSchema().parameters,
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
tool_choice: 'auto',
|
|
182
|
+
thinking: { type: 'enabled' },
|
|
183
|
+
reasoning_effort: 'high',
|
|
184
|
+
});
|
|
185
|
+
if (result.role !== 'assistant') throw new Error('Expected assistant message');
|
|
186
|
+
expect(result.toolCalls).toEqual([
|
|
187
|
+
{
|
|
188
|
+
id: 'call_1',
|
|
189
|
+
type: 'function',
|
|
190
|
+
function: {
|
|
191
|
+
name: 'inspect_file',
|
|
192
|
+
arguments: '{"path":"README.md"}',
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
]);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('emits native Chat Completions request and response payloads', async () => {
|
|
199
|
+
const provider = new DeepSeekProvider({ apiKey: 'deepseek-key' });
|
|
200
|
+
const client = getClient(provider);
|
|
201
|
+
client.chat.completions.create.mockResolvedValue({
|
|
202
|
+
id: 'deepseek-chat-native',
|
|
203
|
+
object: 'chat.completion',
|
|
204
|
+
created: 1,
|
|
205
|
+
model: 'deepseek-v4-flash',
|
|
206
|
+
choices: [
|
|
207
|
+
{
|
|
208
|
+
index: 0,
|
|
209
|
+
message: { role: 'assistant', content: 'native', refusal: null },
|
|
210
|
+
finish_reason: 'stop',
|
|
211
|
+
logprobs: null,
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
} satisfies OpenAI.Chat.ChatCompletion);
|
|
215
|
+
const events: IProviderNativeRawPayloadEvent[] = [];
|
|
216
|
+
|
|
217
|
+
await provider.chat([createUserMessage('Hello')], {
|
|
218
|
+
model: 'deepseek-v4-flash',
|
|
219
|
+
onProviderNativeRawPayload: (event) => events.push(event),
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(events).toEqual([
|
|
223
|
+
expect.objectContaining({
|
|
224
|
+
provider: 'deepseek',
|
|
225
|
+
apiSurface: 'chat-completions',
|
|
226
|
+
payloadKind: 'request',
|
|
227
|
+
}),
|
|
228
|
+
expect.objectContaining({
|
|
229
|
+
provider: 'deepseek',
|
|
230
|
+
apiSurface: 'chat-completions',
|
|
231
|
+
payloadKind: 'response',
|
|
232
|
+
payload: expect.objectContaining({ id: 'deepseek-chat-native' }),
|
|
233
|
+
}),
|
|
234
|
+
]);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('uses streaming assembly when text deltas are requested', async () => {
|
|
238
|
+
const provider = new DeepSeekProvider({ apiKey: 'deepseek-key' });
|
|
239
|
+
const client = getClient(provider);
|
|
240
|
+
client.chat.completions.create.mockResolvedValue(
|
|
241
|
+
asyncIterableFrom([
|
|
242
|
+
createChunk('Hello'),
|
|
243
|
+
createChunk(' from '),
|
|
244
|
+
createChunk('DeepSeek', 'stop'),
|
|
245
|
+
]),
|
|
246
|
+
);
|
|
247
|
+
const onTextDelta = vi.fn();
|
|
248
|
+
|
|
249
|
+
const result = await provider.chat([createUserMessage('Hello')], {
|
|
250
|
+
model: 'deepseek-v4-flash',
|
|
251
|
+
onTextDelta,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
expect(client.chat.completions.create).toHaveBeenCalledWith(
|
|
255
|
+
{
|
|
256
|
+
model: 'deepseek-v4-flash',
|
|
257
|
+
messages: [{ role: 'user', content: 'Hello' }],
|
|
258
|
+
stream: true,
|
|
259
|
+
},
|
|
260
|
+
undefined,
|
|
261
|
+
);
|
|
262
|
+
expect(onTextDelta).toHaveBeenNthCalledWith(1, 'Hello');
|
|
263
|
+
expect(onTextDelta).toHaveBeenNthCalledWith(2, ' from ');
|
|
264
|
+
expect(onTextDelta).toHaveBeenNthCalledWith(3, 'DeepSeek');
|
|
265
|
+
expect(result.content).toBe('Hello from DeepSeek');
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('reports function calling without provider-native web tools', () => {
|
|
269
|
+
const provider = new DeepSeekProvider({ apiKey: 'deepseek-key' });
|
|
270
|
+
|
|
271
|
+
expect(provider.getCapabilities()).toEqual({
|
|
272
|
+
functionCalling: { supported: true },
|
|
273
|
+
nativeWebTools: {
|
|
274
|
+
webSearch: {
|
|
275
|
+
supported: false,
|
|
276
|
+
enabled: false,
|
|
277
|
+
source: 'openai-compatible-chat-completions',
|
|
278
|
+
reason:
|
|
279
|
+
'DeepSeek OpenAI-compatible Chat Completions supports declared function tools, not provider-native web search.',
|
|
280
|
+
},
|
|
281
|
+
webFetch: {
|
|
282
|
+
supported: false,
|
|
283
|
+
enabled: false,
|
|
284
|
+
source: 'openai-compatible-chat-completions',
|
|
285
|
+
reason:
|
|
286
|
+
'DeepSeek OpenAI-compatible Chat Completions supports declared function tools, not provider-native web fetch.',
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('yields universal assistant messages from direct chatStream chunks', async () => {
|
|
293
|
+
const provider = new DeepSeekProvider({ apiKey: 'deepseek-key' });
|
|
294
|
+
const client = getClient(provider);
|
|
295
|
+
client.chat.completions.create.mockResolvedValue(
|
|
296
|
+
asyncIterableFrom([createChunk('Part one'), createChunk(' done', 'stop')]),
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
const chunks: TUniversalMessage[] = [];
|
|
300
|
+
for await (const chunk of provider.chatStream?.([createUserMessage('Stream')], {
|
|
301
|
+
model: 'deepseek-v4-flash',
|
|
302
|
+
}) ?? []) {
|
|
303
|
+
chunks.push(chunk);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
expect(client.chat.completions.create).toHaveBeenCalledWith({
|
|
307
|
+
model: 'deepseek-v4-flash',
|
|
308
|
+
messages: [{ role: 'user', content: 'Stream' }],
|
|
309
|
+
stream: true,
|
|
310
|
+
});
|
|
311
|
+
expect(chunks.map((chunk) => chunk.content)).toEqual(['Part one', ' done']);
|
|
312
|
+
expect(chunks[1]?.metadata?.['isComplete']).toBe(true);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('wraps upstream chat failures with DeepSeek context', async () => {
|
|
316
|
+
const provider = new DeepSeekProvider({ apiKey: 'deepseek-key' });
|
|
317
|
+
const client = getClient(provider);
|
|
318
|
+
client.chat.completions.create.mockRejectedValue(new Error('Invalid API key'));
|
|
319
|
+
|
|
320
|
+
await expect(
|
|
321
|
+
provider.chat([createUserMessage('Hello')], { model: 'deepseek-v4-flash' }),
|
|
322
|
+
).rejects.toThrow('DeepSeek chat failed: Invalid API key');
|
|
323
|
+
});
|
|
324
|
+
});
|