@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,298 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import { AbstractAIProvider, SilentLogger } from '@robota-sdk/agent-core';
|
|
3
|
+
import type {
|
|
4
|
+
IChatOptions,
|
|
5
|
+
IProviderCapabilities,
|
|
6
|
+
TTextDeltaCallback,
|
|
7
|
+
TUniversalMessage,
|
|
8
|
+
} from '@robota-sdk/agent-core';
|
|
9
|
+
import {
|
|
10
|
+
assembleOpenAICompatibleStream,
|
|
11
|
+
convertToOpenAICompatibleMessages,
|
|
12
|
+
convertToOpenAICompatibleTools,
|
|
13
|
+
observeProviderNativeRawPayloadStream,
|
|
14
|
+
OpenAICompatibleResponseParser,
|
|
15
|
+
} from '../shared/openai-compatible/index.js';
|
|
16
|
+
import type { IOpenAICompatibleError } from '../shared/openai-compatible/index.js';
|
|
17
|
+
import { DEFAULT_DEEPSEEK_PROVIDER_BASE_URL } from './defaults';
|
|
18
|
+
import type {
|
|
19
|
+
IDeepSeekProviderOptions,
|
|
20
|
+
IDeepSeekThinkingConfig,
|
|
21
|
+
TDeepSeekReasoningEffort,
|
|
22
|
+
} from './types';
|
|
23
|
+
|
|
24
|
+
type TDeepSeekChatCompletionCreateParamsNonStreaming = Omit<
|
|
25
|
+
OpenAI.Chat.ChatCompletionCreateParamsNonStreaming,
|
|
26
|
+
'reasoning_effort'
|
|
27
|
+
> & {
|
|
28
|
+
thinking?: IDeepSeekThinkingConfig;
|
|
29
|
+
reasoning_effort?: TDeepSeekReasoningEffort;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type TDeepSeekChatCompletionCreateParamsStreaming = Omit<
|
|
33
|
+
OpenAI.Chat.ChatCompletionCreateParamsStreaming,
|
|
34
|
+
'reasoning_effort'
|
|
35
|
+
> & {
|
|
36
|
+
thinking?: IDeepSeekThinkingConfig;
|
|
37
|
+
reasoning_effort?: TDeepSeekReasoningEffort;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export class DeepSeekProvider extends AbstractAIProvider {
|
|
41
|
+
override readonly name = 'deepseek';
|
|
42
|
+
override readonly version = '1.0.0';
|
|
43
|
+
|
|
44
|
+
private readonly client?: OpenAI;
|
|
45
|
+
private readonly options: IDeepSeekProviderOptions;
|
|
46
|
+
private readonly responseParser: OpenAICompatibleResponseParser;
|
|
47
|
+
|
|
48
|
+
onTextDelta?: TTextDeltaCallback;
|
|
49
|
+
|
|
50
|
+
constructor(options: IDeepSeekProviderOptions) {
|
|
51
|
+
super(options.logger || SilentLogger);
|
|
52
|
+
this.options = options;
|
|
53
|
+
|
|
54
|
+
if (options.executor) {
|
|
55
|
+
this.executor = options.executor;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!this.executor) {
|
|
59
|
+
if (options.client) {
|
|
60
|
+
this.client = options.client;
|
|
61
|
+
} else if (options.apiKey) {
|
|
62
|
+
this.client = new OpenAI({
|
|
63
|
+
apiKey: options.apiKey,
|
|
64
|
+
baseURL: options.baseURL ?? DEFAULT_DEEPSEEK_PROVIDER_BASE_URL,
|
|
65
|
+
...(options.timeout !== undefined && { timeout: options.timeout }),
|
|
66
|
+
});
|
|
67
|
+
} else {
|
|
68
|
+
throw new Error('Either DeepSeek client, apiKey, or executor is required');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.responseParser = new OpenAICompatibleResponseParser({ logger: this.logger });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
override async chat(
|
|
76
|
+
messages: TUniversalMessage[],
|
|
77
|
+
options?: IChatOptions,
|
|
78
|
+
): Promise<TUniversalMessage> {
|
|
79
|
+
this.validateMessages(messages);
|
|
80
|
+
this.validateNativeWebTools(options?.nativeWebTools);
|
|
81
|
+
|
|
82
|
+
if (this.executor) {
|
|
83
|
+
try {
|
|
84
|
+
return await this.executeViaExecutorOrDirect(messages, options);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
this.logger.error(
|
|
87
|
+
'DeepSeek Provider executor chat error:',
|
|
88
|
+
error instanceof Error ? error.message : String(error),
|
|
89
|
+
);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const client = this.getClient();
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const requestParams = this.buildRequestParams(messages, options);
|
|
98
|
+
const textDeltaCb = options?.onTextDelta ?? this.onTextDelta;
|
|
99
|
+
if (textDeltaCb) {
|
|
100
|
+
return await this.chatWithStreamingAssembly(
|
|
101
|
+
{ ...requestParams, stream: true },
|
|
102
|
+
{ ...options, onTextDelta: textDeltaCb },
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
options?.onProviderNativeRawPayload?.({
|
|
107
|
+
provider: 'deepseek',
|
|
108
|
+
apiSurface: 'chat-completions',
|
|
109
|
+
payloadKind: 'request',
|
|
110
|
+
payload: requestParams,
|
|
111
|
+
});
|
|
112
|
+
const response = await client.chat.completions.create(
|
|
113
|
+
requestParams as OpenAI.Chat.ChatCompletionCreateParamsNonStreaming,
|
|
114
|
+
);
|
|
115
|
+
options?.onProviderNativeRawPayload?.({
|
|
116
|
+
provider: 'deepseek',
|
|
117
|
+
apiSurface: 'chat-completions',
|
|
118
|
+
payloadKind: 'response',
|
|
119
|
+
payload: response,
|
|
120
|
+
});
|
|
121
|
+
return this.responseParser.parseResponse(response);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
const deepSeekError = error as IOpenAICompatibleError;
|
|
124
|
+
const errorMessage = deepSeekError.message || 'DeepSeek API request failed';
|
|
125
|
+
throw new Error(`DeepSeek chat failed: ${errorMessage}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
override async *chatStream(
|
|
130
|
+
messages: TUniversalMessage[],
|
|
131
|
+
options?: IChatOptions,
|
|
132
|
+
): AsyncIterable<TUniversalMessage> {
|
|
133
|
+
this.validateMessages(messages);
|
|
134
|
+
this.validateNativeWebTools(options?.nativeWebTools);
|
|
135
|
+
|
|
136
|
+
if (this.executor) {
|
|
137
|
+
try {
|
|
138
|
+
yield* this.executeStreamViaExecutorOrDirect(messages, options);
|
|
139
|
+
return;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
this.logger.error(
|
|
142
|
+
'DeepSeek Provider executor stream error:',
|
|
143
|
+
error instanceof Error ? error.message : String(error),
|
|
144
|
+
);
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const client = this.getClient();
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const requestParams = this.buildStreamingRequestParams(messages, options);
|
|
153
|
+
options?.onProviderNativeRawPayload?.({
|
|
154
|
+
provider: 'deepseek',
|
|
155
|
+
apiSurface: 'chat-completions',
|
|
156
|
+
payloadKind: 'request',
|
|
157
|
+
payload: requestParams,
|
|
158
|
+
});
|
|
159
|
+
const stream = await client.chat.completions.create(
|
|
160
|
+
requestParams as OpenAI.Chat.ChatCompletionCreateParamsStreaming,
|
|
161
|
+
);
|
|
162
|
+
const observedStream = observeProviderNativeRawPayloadStream(stream, {
|
|
163
|
+
provider: 'deepseek',
|
|
164
|
+
apiSurface: 'chat-completions',
|
|
165
|
+
onProviderNativeRawPayload: options?.onProviderNativeRawPayload,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
for await (const chunk of this.streamWithAbort(observedStream, options?.signal)) {
|
|
169
|
+
const universalMessage = this.responseParser.parseStreamingChunk(chunk);
|
|
170
|
+
if (universalMessage) {
|
|
171
|
+
yield universalMessage;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
} catch (error) {
|
|
175
|
+
const deepSeekError = error as IOpenAICompatibleError;
|
|
176
|
+
const errorMessage = deepSeekError.message || 'DeepSeek API request failed';
|
|
177
|
+
throw new Error(`DeepSeek stream failed: ${errorMessage}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
override supportsTools(): boolean {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
override getCapabilities(): IProviderCapabilities {
|
|
186
|
+
return {
|
|
187
|
+
functionCalling: { supported: true },
|
|
188
|
+
nativeWebTools: {
|
|
189
|
+
webSearch: {
|
|
190
|
+
supported: false,
|
|
191
|
+
enabled: false,
|
|
192
|
+
source: 'openai-compatible-chat-completions',
|
|
193
|
+
reason:
|
|
194
|
+
'DeepSeek OpenAI-compatible Chat Completions supports declared function tools, not provider-native web search.',
|
|
195
|
+
},
|
|
196
|
+
webFetch: {
|
|
197
|
+
supported: false,
|
|
198
|
+
enabled: false,
|
|
199
|
+
source: 'openai-compatible-chat-completions',
|
|
200
|
+
reason:
|
|
201
|
+
'DeepSeek OpenAI-compatible Chat Completions supports declared function tools, not provider-native web fetch.',
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
override validateConfig(): boolean {
|
|
208
|
+
return !!this.client && !!this.options;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
override async dispose(): Promise<void> {
|
|
212
|
+
// OpenAI-compatible DeepSeek clients do not need explicit cleanup.
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private buildRequestParams(
|
|
216
|
+
messages: TUniversalMessage[],
|
|
217
|
+
options: IChatOptions | undefined,
|
|
218
|
+
): TDeepSeekChatCompletionCreateParamsNonStreaming {
|
|
219
|
+
this.validateTools(options?.tools);
|
|
220
|
+
const model = options?.model ?? this.options.defaultModel;
|
|
221
|
+
if (!model) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
'Model is required in chat options. Please specify a model in defaultModel configuration.',
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
model,
|
|
229
|
+
messages: convertToOpenAICompatibleMessages(messages),
|
|
230
|
+
...(options?.temperature !== undefined && { temperature: options.temperature }),
|
|
231
|
+
...(options?.maxTokens !== undefined && { max_tokens: options.maxTokens }),
|
|
232
|
+
...(options?.tools && {
|
|
233
|
+
tools: convertToOpenAICompatibleTools(options.tools),
|
|
234
|
+
tool_choice: 'auto' as const,
|
|
235
|
+
}),
|
|
236
|
+
...(this.options.thinking !== undefined && {
|
|
237
|
+
thinking: { type: this.options.thinking },
|
|
238
|
+
}),
|
|
239
|
+
...(this.options.reasoningEffort !== undefined && {
|
|
240
|
+
reasoning_effort: this.options.reasoningEffort,
|
|
241
|
+
}),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private buildStreamingRequestParams(
|
|
246
|
+
messages: TUniversalMessage[],
|
|
247
|
+
options: IChatOptions | undefined,
|
|
248
|
+
): TDeepSeekChatCompletionCreateParamsStreaming {
|
|
249
|
+
return {
|
|
250
|
+
...this.buildRequestParams(messages, options),
|
|
251
|
+
stream: true,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private getClient(): OpenAI {
|
|
256
|
+
if (!this.client) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
'DeepSeek client not available. Either provide a client/apiKey or use an executor.',
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return this.client;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private async chatWithStreamingAssembly(
|
|
266
|
+
requestParams: TDeepSeekChatCompletionCreateParamsStreaming,
|
|
267
|
+
options: IChatOptions,
|
|
268
|
+
): Promise<TUniversalMessage> {
|
|
269
|
+
const client = this.getClient();
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
options.onProviderNativeRawPayload?.({
|
|
273
|
+
provider: 'deepseek',
|
|
274
|
+
apiSurface: 'chat-completions',
|
|
275
|
+
payloadKind: 'request',
|
|
276
|
+
payload: requestParams,
|
|
277
|
+
});
|
|
278
|
+
const stream = await client.chat.completions.create(
|
|
279
|
+
requestParams as OpenAI.Chat.ChatCompletionCreateParamsStreaming,
|
|
280
|
+
options.signal ? { signal: options.signal } : undefined,
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
return assembleOpenAICompatibleStream({
|
|
284
|
+
stream: observeProviderNativeRawPayloadStream(stream, {
|
|
285
|
+
provider: 'deepseek',
|
|
286
|
+
apiSurface: 'chat-completions',
|
|
287
|
+
onProviderNativeRawPayload: options.onProviderNativeRawPayload,
|
|
288
|
+
}),
|
|
289
|
+
onTextDelta: options.onTextDelta,
|
|
290
|
+
signal: options.signal,
|
|
291
|
+
});
|
|
292
|
+
} catch (error) {
|
|
293
|
+
const deepSeekError = error as IOpenAICompatibleError;
|
|
294
|
+
const errorMessage = deepSeekError.message || 'DeepSeek streaming request failed';
|
|
295
|
+
throw new Error(`DeepSeek stream failed: ${errorMessage}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type OpenAI from 'openai';
|
|
2
|
+
import type { IExecutor, ILogger, TProviderOptionValueBase } from '@robota-sdk/agent-core';
|
|
3
|
+
|
|
4
|
+
export type TDeepSeekThinkingMode = 'enabled' | 'disabled';
|
|
5
|
+
export type TDeepSeekReasoningEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
6
|
+
|
|
7
|
+
export interface IDeepSeekThinkingConfig {
|
|
8
|
+
type: TDeepSeekThinkingMode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type TDeepSeekProviderOptionValue =
|
|
12
|
+
| string
|
|
13
|
+
| number
|
|
14
|
+
| boolean
|
|
15
|
+
| undefined
|
|
16
|
+
| null
|
|
17
|
+
| IDeepSeekThinkingConfig
|
|
18
|
+
| OpenAI
|
|
19
|
+
| ILogger
|
|
20
|
+
| IExecutor
|
|
21
|
+
| TProviderOptionValueBase
|
|
22
|
+
| TDeepSeekProviderOptionValue[]
|
|
23
|
+
| { [key: string]: TDeepSeekProviderOptionValue };
|
|
24
|
+
|
|
25
|
+
export interface IDeepSeekProviderOptions {
|
|
26
|
+
[key: string]: TDeepSeekProviderOptionValue;
|
|
27
|
+
|
|
28
|
+
apiKey?: string;
|
|
29
|
+
baseURL?: string;
|
|
30
|
+
timeout?: number;
|
|
31
|
+
defaultModel?: string;
|
|
32
|
+
thinking?: TDeepSeekThinkingMode;
|
|
33
|
+
reasoningEffort?: TDeepSeekReasoningEffort;
|
|
34
|
+
client?: OpenAI;
|
|
35
|
+
executor?: IExecutor;
|
|
36
|
+
logger?: ILogger;
|
|
37
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import type { GoogleGenAI } from '@google/genai';
|
|
3
|
+
import type { Content, GenerateContentParameters, GenerateContentResponse } from '@google/genai';
|
|
4
|
+
import type { IGeminiProviderOptions } from './types';
|
|
5
|
+
import type {
|
|
6
|
+
TUniversalMessage,
|
|
7
|
+
IChatOptions,
|
|
8
|
+
IImageGenerationResult,
|
|
9
|
+
TProviderMediaResult,
|
|
10
|
+
} from '@robota-sdk/agent-core';
|
|
11
|
+
import {
|
|
12
|
+
convertToGeminiRequestFormat,
|
|
13
|
+
convertFromGeminiResponse,
|
|
14
|
+
convertToolsToGeminiFormat,
|
|
15
|
+
} from './message-converter';
|
|
16
|
+
import {
|
|
17
|
+
hasImagePart,
|
|
18
|
+
mapInlineImagePartsToMediaOutputs,
|
|
19
|
+
buildResponseModalities,
|
|
20
|
+
buildGenerationConfig,
|
|
21
|
+
} from './image-operations';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Execute a direct (non-streaming) chat request against the Gemini API.
|
|
25
|
+
*/
|
|
26
|
+
export async function executeDirect(
|
|
27
|
+
client: GoogleGenAI,
|
|
28
|
+
providerOptions: IGeminiProviderOptions,
|
|
29
|
+
messages: TUniversalMessage[],
|
|
30
|
+
options?: IChatOptions,
|
|
31
|
+
providerName = 'gemini',
|
|
32
|
+
): Promise<TUniversalMessage> {
|
|
33
|
+
const model = resolveGeminiModel(providerOptions, options);
|
|
34
|
+
const responseModalities = buildResponseModalities(
|
|
35
|
+
messages,
|
|
36
|
+
providerOptions.defaultResponseModalities,
|
|
37
|
+
options?.google?.responseModalities,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (options?.onTextDelta && !responseModalities.includes('IMAGE')) {
|
|
41
|
+
return assembleStreamingChatResponse(client, providerOptions, messages, options, providerName);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const requestFormat = convertToGeminiRequestFormat(messages);
|
|
45
|
+
const genConfig = buildGenerationConfig(messages, providerOptions, { ...options, model });
|
|
46
|
+
const request = buildGenerateContentRequest(
|
|
47
|
+
model,
|
|
48
|
+
requestFormat.contents,
|
|
49
|
+
genConfig,
|
|
50
|
+
options,
|
|
51
|
+
requestFormat.systemInstruction,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
emitGeminiNativeRawPayload(options, providerName, 'request', request);
|
|
55
|
+
const result = await client.models.generateContent(request);
|
|
56
|
+
emitGeminiNativeRawPayload(options, providerName, 'response', result);
|
|
57
|
+
|
|
58
|
+
const convertedResponse = convertFromGeminiResponse(result);
|
|
59
|
+
if (responseModalities.includes('IMAGE') && !hasImagePart(convertedResponse.parts)) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
'Gemini response did not include an image part while IMAGE modality was requested.',
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return convertedResponse;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Execute a streaming chat request against the Gemini API.
|
|
69
|
+
*/
|
|
70
|
+
export async function* executeDirectStream(
|
|
71
|
+
client: GoogleGenAI,
|
|
72
|
+
providerOptions: IGeminiProviderOptions,
|
|
73
|
+
messages: TUniversalMessage[],
|
|
74
|
+
options?: IChatOptions,
|
|
75
|
+
providerName = 'gemini',
|
|
76
|
+
): AsyncIterable<TUniversalMessage> {
|
|
77
|
+
const model = resolveGeminiModel(providerOptions, options);
|
|
78
|
+
const responseModalities = buildResponseModalities(
|
|
79
|
+
messages,
|
|
80
|
+
providerOptions.defaultResponseModalities,
|
|
81
|
+
options?.google?.responseModalities,
|
|
82
|
+
);
|
|
83
|
+
if (responseModalities.includes('IMAGE')) {
|
|
84
|
+
throw new Error('Google provider does not support streaming image modality responses.');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const requestFormat = convertToGeminiRequestFormat(messages);
|
|
88
|
+
const genConfig = buildGenerationConfig(messages, providerOptions, { ...options, model });
|
|
89
|
+
const request = buildGenerateContentRequest(
|
|
90
|
+
model,
|
|
91
|
+
requestFormat.contents,
|
|
92
|
+
genConfig,
|
|
93
|
+
options,
|
|
94
|
+
requestFormat.systemInstruction,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
emitGeminiNativeRawPayload(options, providerName, 'request', request);
|
|
98
|
+
const stream = await client.models.generateContentStream(request);
|
|
99
|
+
|
|
100
|
+
let sequence = 0;
|
|
101
|
+
for await (const chunk of stream) {
|
|
102
|
+
emitGeminiNativeRawPayload(options, providerName, 'stream_event', chunk, sequence);
|
|
103
|
+
sequence++;
|
|
104
|
+
const text = extractStreamText(chunk);
|
|
105
|
+
if (text) {
|
|
106
|
+
options?.onTextDelta?.(text);
|
|
107
|
+
yield {
|
|
108
|
+
id: randomUUID(),
|
|
109
|
+
role: 'assistant',
|
|
110
|
+
content: text,
|
|
111
|
+
state: 'complete' as const,
|
|
112
|
+
timestamp: new Date(),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function buildGenerateContentRequest(
|
|
119
|
+
model: string,
|
|
120
|
+
contents: Content[],
|
|
121
|
+
generationOptions: GenerateContentParameters['config'],
|
|
122
|
+
options?: IChatOptions,
|
|
123
|
+
systemInstruction?: string,
|
|
124
|
+
): GenerateContentParameters {
|
|
125
|
+
const config: GenerateContentParameters['config'] = { ...generationOptions };
|
|
126
|
+
if (options?.tools && options.tools.length > 0) {
|
|
127
|
+
config.tools = [{ functionDeclarations: convertToolsToGeminiFormat(options.tools) }];
|
|
128
|
+
}
|
|
129
|
+
if (systemInstruction) {
|
|
130
|
+
config.systemInstruction = systemInstruction;
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
model,
|
|
134
|
+
contents,
|
|
135
|
+
config,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function resolveGeminiModel(
|
|
140
|
+
providerOptions: IGeminiProviderOptions,
|
|
141
|
+
options?: IChatOptions,
|
|
142
|
+
): string {
|
|
143
|
+
const model = options?.model ?? providerOptions.defaultModel;
|
|
144
|
+
if (!model) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
'Model is required in chat options. Please specify a model in defaultModel configuration.',
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
return model;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function assembleStreamingChatResponse(
|
|
153
|
+
client: GoogleGenAI,
|
|
154
|
+
providerOptions: IGeminiProviderOptions,
|
|
155
|
+
messages: TUniversalMessage[],
|
|
156
|
+
options: IChatOptions,
|
|
157
|
+
providerName = 'gemini',
|
|
158
|
+
): Promise<TUniversalMessage> {
|
|
159
|
+
const textParts: string[] = [];
|
|
160
|
+
for await (const chunk of executeDirectStream(
|
|
161
|
+
client,
|
|
162
|
+
providerOptions,
|
|
163
|
+
messages,
|
|
164
|
+
options,
|
|
165
|
+
providerName,
|
|
166
|
+
)) {
|
|
167
|
+
if (typeof chunk.content === 'string') {
|
|
168
|
+
textParts.push(chunk.content);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const content = textParts.join('');
|
|
172
|
+
return {
|
|
173
|
+
id: randomUUID(),
|
|
174
|
+
role: 'assistant',
|
|
175
|
+
content,
|
|
176
|
+
parts: content.length > 0 ? [{ type: 'text', text: content }] : [],
|
|
177
|
+
state: 'complete',
|
|
178
|
+
timestamp: new Date(),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function emitGeminiNativeRawPayload(
|
|
183
|
+
options: IChatOptions | undefined,
|
|
184
|
+
providerName: string,
|
|
185
|
+
payloadKind: 'request' | 'response' | 'stream_event',
|
|
186
|
+
payload: object,
|
|
187
|
+
sequence?: number,
|
|
188
|
+
): void {
|
|
189
|
+
options?.onProviderNativeRawPayload?.({
|
|
190
|
+
provider: providerName,
|
|
191
|
+
apiSurface: 'gemini-generate-content',
|
|
192
|
+
payloadKind,
|
|
193
|
+
...(sequence !== undefined && { sequence }),
|
|
194
|
+
payload,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function extractStreamText(
|
|
199
|
+
chunk: GenerateContentResponse | { readonly text?: () => string },
|
|
200
|
+
): string | undefined {
|
|
201
|
+
const textValue = chunk.text;
|
|
202
|
+
return typeof textValue === 'function' ? textValue() : textValue;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Run an image generation request through the chat API.
|
|
207
|
+
*/
|
|
208
|
+
export async function runImageRequest(
|
|
209
|
+
chatFn: (messages: TUniversalMessage[], options?: IChatOptions) => Promise<TUniversalMessage>,
|
|
210
|
+
messages: TUniversalMessage[],
|
|
211
|
+
model: string,
|
|
212
|
+
): Promise<TProviderMediaResult<IImageGenerationResult>> {
|
|
213
|
+
try {
|
|
214
|
+
const response = await chatFn(messages, {
|
|
215
|
+
model,
|
|
216
|
+
google: { responseModalities: ['TEXT', 'IMAGE'] },
|
|
217
|
+
});
|
|
218
|
+
const outputs = mapInlineImagePartsToMediaOutputs(response.parts);
|
|
219
|
+
if (outputs.length === 0) {
|
|
220
|
+
return {
|
|
221
|
+
ok: false,
|
|
222
|
+
error: {
|
|
223
|
+
code: 'PROVIDER_UPSTREAM_ERROR',
|
|
224
|
+
message: 'Google image response did not include image output parts.',
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return { ok: true, value: { outputs, model } };
|
|
229
|
+
} catch (error) {
|
|
230
|
+
const errorMessage = error instanceof Error ? error.message : 'Google image request failed.';
|
|
231
|
+
return { ok: false, error: { code: 'PROVIDER_UPSTREAM_ERROR', message: errorMessage } };
|
|
232
|
+
}
|
|
233
|
+
}
|