@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.
Files changed (220) hide show
  1. package/LICENSE +21 -0
  2. package/dist/browser/index.d.ts +1104 -0
  3. package/dist/browser/index.d.ts.map +1 -0
  4. package/dist/browser/index.js +7 -0
  5. package/dist/browser/index.js.map +1 -0
  6. package/dist/loggers/index.cjs +1 -0
  7. package/dist/loggers/index.d.ts +151 -0
  8. package/dist/loggers/index.d.ts.map +1 -0
  9. package/dist/loggers/index.js +2 -0
  10. package/dist/loggers/index.js.map +1 -0
  11. package/dist/node/anthropic/index.cjs +1 -0
  12. package/dist/node/anthropic/index.d.ts +158 -0
  13. package/dist/node/anthropic/index.d.ts.map +1 -0
  14. package/dist/node/anthropic/index.js +1 -0
  15. package/dist/node/anthropic--1vgLC-e.js +5 -0
  16. package/dist/node/anthropic--1vgLC-e.js.map +1 -0
  17. package/dist/node/anthropic-BFQ6DSCP.cjs +4 -0
  18. package/dist/node/bytedance/index.cjs +1 -0
  19. package/dist/node/bytedance/index.d.ts +74 -0
  20. package/dist/node/bytedance/index.d.ts.map +1 -0
  21. package/dist/node/bytedance/index.js +1 -0
  22. package/dist/node/bytedance-C_0sF_pJ.js +2 -0
  23. package/dist/node/bytedance-C_0sF_pJ.js.map +1 -0
  24. package/dist/node/bytedance-DVPxqEiC.cjs +1 -0
  25. package/dist/node/chunk-Bmb41Sf3.cjs +1 -0
  26. package/dist/node/deepseek/index.cjs +1 -0
  27. package/dist/node/deepseek/index.d.ts +2 -0
  28. package/dist/node/deepseek/index.js +1 -0
  29. package/dist/node/deepseek-_8Ixx7rA.js +2 -0
  30. package/dist/node/deepseek-_8Ixx7rA.js.map +1 -0
  31. package/dist/node/deepseek-oA2Y6bD0.cjs +1 -0
  32. package/dist/node/gemini/index.cjs +1 -0
  33. package/dist/node/gemini/index.d.ts +173 -0
  34. package/dist/node/gemini/index.d.ts.map +1 -0
  35. package/dist/node/gemini/index.js +1 -0
  36. package/dist/node/gemini-Bh2U87MY.js +4 -0
  37. package/dist/node/gemini-Bh2U87MY.js.map +1 -0
  38. package/dist/node/gemini-DSaNCxZj.cjs +3 -0
  39. package/dist/node/gemma/index.cjs +1 -0
  40. package/dist/node/gemma/index.d.ts +2 -0
  41. package/dist/node/gemma/index.js +1 -0
  42. package/dist/node/gemma-Dp_AfCUR.js +2 -0
  43. package/dist/node/gemma-Dp_AfCUR.js.map +1 -0
  44. package/dist/node/gemma-G-Pf_PnX.cjs +1 -0
  45. package/dist/node/google/index.cjs +1 -0
  46. package/dist/node/google/index.d.ts +14 -0
  47. package/dist/node/google/index.d.ts.map +1 -0
  48. package/dist/node/google/index.js +2 -0
  49. package/dist/node/google/index.js.map +1 -0
  50. package/dist/node/index-B6PnlDMd.d.ts +82 -0
  51. package/dist/node/index-B6PnlDMd.d.ts.map +1 -0
  52. package/dist/node/index-B7UvPJcI.d.ts +315 -0
  53. package/dist/node/index-B7UvPJcI.d.ts.map +1 -0
  54. package/dist/node/index-BLPOTNb5.d.ts +98 -0
  55. package/dist/node/index-BLPOTNb5.d.ts.map +1 -0
  56. package/dist/node/index-BqixM_XD.d.ts +231 -0
  57. package/dist/node/index-BqixM_XD.d.ts.map +1 -0
  58. package/dist/node/index-C3beaqKO.d.ts +231 -0
  59. package/dist/node/index-C3beaqKO.d.ts.map +1 -0
  60. package/dist/node/index-Cp2XRh9G.d.ts +82 -0
  61. package/dist/node/index-Cp2XRh9G.d.ts.map +1 -0
  62. package/dist/node/index-DSv5xruI.d.ts +98 -0
  63. package/dist/node/index-DSv5xruI.d.ts.map +1 -0
  64. package/dist/node/index-w0bV1uaP.d.ts +315 -0
  65. package/dist/node/index-w0bV1uaP.d.ts.map +1 -0
  66. package/dist/node/index.cjs +1 -0
  67. package/dist/node/index.d.ts +8 -0
  68. package/dist/node/index.js +1 -0
  69. package/dist/node/openai/index.cjs +1 -0
  70. package/dist/node/openai/index.d.ts +2 -0
  71. package/dist/node/openai/index.js +1 -0
  72. package/dist/node/openai-CRQjg4xF.js +2 -0
  73. package/dist/node/openai-CRQjg4xF.js.map +1 -0
  74. package/dist/node/openai-compatible-BYfyY5lb.cjs +1 -0
  75. package/dist/node/openai-compatible-Dm4Sof9e.js +2 -0
  76. package/dist/node/openai-compatible-Dm4Sof9e.js.map +1 -0
  77. package/dist/node/openai-xWC6pY7r.cjs +1 -0
  78. package/dist/node/qwen/index.cjs +1 -0
  79. package/dist/node/qwen/index.d.ts +2 -0
  80. package/dist/node/qwen/index.js +1 -0
  81. package/dist/node/qwen-ChUZobTL.js +2 -0
  82. package/dist/node/qwen-ChUZobTL.js.map +1 -0
  83. package/dist/node/qwen-CjT71vSM.cjs +1 -0
  84. package/package.json +157 -0
  85. package/src/anthropic/__tests__/abort-streaming.test.ts +199 -0
  86. package/src/anthropic/__tests__/model-catalog-refresh.test.ts +92 -0
  87. package/src/anthropic/__tests__/provider-definition.test.ts +55 -0
  88. package/src/anthropic/__tests__/provider.test.ts +1357 -0
  89. package/src/anthropic/__tests__/response-parser.test.ts +326 -0
  90. package/src/anthropic/index.ts +22 -0
  91. package/src/anthropic/message-converter.ts +181 -0
  92. package/src/anthropic/model-catalog-refresh.ts +128 -0
  93. package/src/anthropic/parsers/response-parser.ts +184 -0
  94. package/src/anthropic/provider-definition.ts +93 -0
  95. package/src/anthropic/provider.ts +290 -0
  96. package/src/anthropic/streaming-handler.ts +204 -0
  97. package/src/anthropic/types/api-types.ts +158 -0
  98. package/src/anthropic/types.ts +79 -0
  99. package/src/bytedance/http-client.test.ts +288 -0
  100. package/src/bytedance/http-client.ts +163 -0
  101. package/src/bytedance/index.ts +2 -0
  102. package/src/bytedance/provider.spec.ts +320 -0
  103. package/src/bytedance/provider.ts +171 -0
  104. package/src/bytedance/status-mapper.test.ts +299 -0
  105. package/src/bytedance/status-mapper.ts +141 -0
  106. package/src/bytedance/types.ts +68 -0
  107. package/src/deepseek/defaults.ts +4 -0
  108. package/src/deepseek/index.ts +22 -0
  109. package/src/deepseek/model-catalog-refresh.test.ts +57 -0
  110. package/src/deepseek/model-catalog-refresh.ts +105 -0
  111. package/src/deepseek/model-catalog.ts +55 -0
  112. package/src/deepseek/provider-definition.test.ts +109 -0
  113. package/src/deepseek/provider-definition.ts +132 -0
  114. package/src/deepseek/provider.test.ts +324 -0
  115. package/src/deepseek/provider.ts +298 -0
  116. package/src/deepseek/types.ts +37 -0
  117. package/src/gemini/execution-helpers.ts +233 -0
  118. package/src/gemini/genai-transport.test.ts +208 -0
  119. package/src/gemini/image-operations.test.ts +448 -0
  120. package/src/gemini/image-operations.ts +261 -0
  121. package/src/gemini/index.ts +11 -0
  122. package/src/gemini/message-converter.test.ts +616 -0
  123. package/src/gemini/message-converter.ts +140 -0
  124. package/src/gemini/model-catalog-refresh.test.ts +107 -0
  125. package/src/gemini/model-catalog-refresh.ts +92 -0
  126. package/src/gemini/provider-definition.test.ts +70 -0
  127. package/src/gemini/provider-definition.ts +78 -0
  128. package/src/gemini/provider-extended.test.ts +898 -0
  129. package/src/gemini/provider.spec.ts +216 -0
  130. package/src/gemini/provider.ts +279 -0
  131. package/src/gemini/request-converter.ts +226 -0
  132. package/src/gemini/tool-schema-converter.ts +78 -0
  133. package/src/gemini/types/api-types.ts +235 -0
  134. package/src/gemini/types.ts +121 -0
  135. package/src/gemma/index.ts +5 -0
  136. package/src/gemma/message-factory.ts +38 -0
  137. package/src/gemma/provider-definition.test.ts +43 -0
  138. package/src/gemma/provider-definition.ts +84 -0
  139. package/src/gemma/provider-projection.ts +49 -0
  140. package/src/gemma/provider.test.ts +628 -0
  141. package/src/gemma/provider.ts +308 -0
  142. package/src/gemma/pseudo-command-envelope.ts +58 -0
  143. package/src/gemma/pseudo-tool-call-projector.ts +243 -0
  144. package/src/gemma/pseudo-tool-call-tag-parser.ts +153 -0
  145. package/src/gemma/pseudo-tool-call-types.ts +31 -0
  146. package/src/gemma/reasoning-projector.test.ts +52 -0
  147. package/src/gemma/reasoning-projector.ts +144 -0
  148. package/src/gemma/streaming-projection.ts +79 -0
  149. package/src/gemma/tool-call-argument-parser.ts +126 -0
  150. package/src/gemma/tool-call-projector.test.ts +227 -0
  151. package/src/gemma/tool-call-projector.ts +264 -0
  152. package/src/gemma/types.ts +27 -0
  153. package/src/google/index.ts +11 -0
  154. package/src/google/provider-compat.test.ts +19 -0
  155. package/src/google/provider-definition.ts +6 -0
  156. package/src/google/provider.ts +10 -0
  157. package/src/google/types.ts +5 -0
  158. package/src/index.ts +9 -0
  159. package/src/openai/adapter.test.ts +494 -0
  160. package/src/openai/adapter.ts +145 -0
  161. package/src/openai/chat-completions-chat.ts +189 -0
  162. package/src/openai/executor-integration.test.ts +206 -0
  163. package/src/openai/index.ts +21 -0
  164. package/src/openai/interfaces/payload-logger.ts +48 -0
  165. package/src/openai/loggers/console-payload-logger.test.ts +173 -0
  166. package/src/openai/loggers/console-payload-logger.ts +94 -0
  167. package/src/openai/loggers/console.ts +9 -0
  168. package/src/openai/loggers/file-payload-logger.test.ts +238 -0
  169. package/src/openai/loggers/file-payload-logger.ts +112 -0
  170. package/src/openai/loggers/file.ts +9 -0
  171. package/src/openai/loggers/index.ts +12 -0
  172. package/src/openai/loggers/sanitize-openai-log-data.test.ts +89 -0
  173. package/src/openai/loggers/sanitize-openai-log-data.ts +14 -0
  174. package/src/openai/message-converter.ts +22 -0
  175. package/src/openai/model-catalog-refresh.test.ts +92 -0
  176. package/src/openai/model-catalog-refresh.ts +115 -0
  177. package/src/openai/openai-request-format.ts +92 -0
  178. package/src/openai/parsers/response-parser.test.ts +407 -0
  179. package/src/openai/parsers/response-parser.ts +47 -0
  180. package/src/openai/provider-definition.test.ts +75 -0
  181. package/src/openai/provider-definition.ts +132 -0
  182. package/src/openai/provider.test.ts +1402 -0
  183. package/src/openai/provider.ts +237 -0
  184. package/src/openai/responses-chat.ts +258 -0
  185. package/src/openai/responses-converter.ts +112 -0
  186. package/src/openai/responses-parser.ts +285 -0
  187. package/src/openai/responses-stream-utils.ts +45 -0
  188. package/src/openai/responses-types.ts +195 -0
  189. package/src/openai/streaming/stream-assembler.ts +3 -0
  190. package/src/openai/streaming/stream-handler.test.ts +367 -0
  191. package/src/openai/streaming/stream-handler.ts +119 -0
  192. package/src/openai/types/api-types.ts +112 -0
  193. package/src/openai/types.ts +194 -0
  194. package/src/qwen/defaults.ts +26 -0
  195. package/src/qwen/index.ts +5 -0
  196. package/src/qwen/model-catalog-refresh.test.ts +91 -0
  197. package/src/qwen/model-catalog-refresh.ts +97 -0
  198. package/src/qwen/provider-capabilities.ts +34 -0
  199. package/src/qwen/provider-definition.test.ts +139 -0
  200. package/src/qwen/provider-definition.ts +173 -0
  201. package/src/qwen/provider-streaming-assembly.ts +40 -0
  202. package/src/qwen/provider.test.ts +640 -0
  203. package/src/qwen/provider.ts +293 -0
  204. package/src/qwen/responses-chat.ts +194 -0
  205. package/src/qwen/responses-converter.ts +104 -0
  206. package/src/qwen/responses-parser.ts +299 -0
  207. package/src/qwen/responses-stream-utils.ts +38 -0
  208. package/src/qwen/types.ts +228 -0
  209. package/src/shared/openai-compatible/endpoint-probe.test.ts +52 -0
  210. package/src/shared/openai-compatible/endpoint-probe.ts +43 -0
  211. package/src/shared/openai-compatible/index.ts +6 -0
  212. package/src/shared/openai-compatible/message-converter.test.ts +111 -0
  213. package/src/shared/openai-compatible/message-converter.ts +84 -0
  214. package/src/shared/openai-compatible/native-payload-observer.test.ts +43 -0
  215. package/src/shared/openai-compatible/native-payload-observer.ts +26 -0
  216. package/src/shared/openai-compatible/response-parser.test.ts +172 -0
  217. package/src/shared/openai-compatible/response-parser.ts +180 -0
  218. package/src/shared/openai-compatible/stream-assembler.test.ts +266 -0
  219. package/src/shared/openai-compatible/stream-assembler.ts +248 -0
  220. package/src/shared/openai-compatible/types.ts +59 -0
@@ -0,0 +1,308 @@
1
+ import OpenAI from 'openai';
2
+ import { AbstractAIProvider, SilentLogger } from '@robota-sdk/agent-core';
3
+ import type {
4
+ IAssistantMessage,
5
+ IChatOptions,
6
+ IProviderCapabilities,
7
+ TTextDeltaCallback,
8
+ TUniversalMessage,
9
+ } from '@robota-sdk/agent-core';
10
+ import {
11
+ assembleOpenAICompatibleStream,
12
+ convertToOpenAICompatibleMessages,
13
+ convertToOpenAICompatibleTools,
14
+ observeProviderNativeRawPayloadStream,
15
+ } from '../shared/openai-compatible/index.js';
16
+ import type { IOpenAICompatibleError } from '../shared/openai-compatible/index.js';
17
+ import type { IGemmaProviderOptions } from './types';
18
+ import { GemmaReasoningProjector } from './reasoning-projector';
19
+ import { createGemmaToolCallProjector } from './tool-call-projector';
20
+ import { parseGemmaChatCompletion, withGemmaProjectionMetadata } from './provider-projection';
21
+ import {
22
+ createGemmaStreamProjectionState,
23
+ flushGemmaStreamProjection,
24
+ projectGemmaStreamChunk,
25
+ } from './streaming-projection';
26
+
27
+ export class GemmaProvider extends AbstractAIProvider {
28
+ override readonly name = 'gemma';
29
+ override readonly version = '1.0.0';
30
+
31
+ private readonly client?: OpenAI;
32
+ private readonly options: IGemmaProviderOptions;
33
+
34
+ onTextDelta?: TTextDeltaCallback;
35
+
36
+ constructor(options: IGemmaProviderOptions) {
37
+ super(options.logger || SilentLogger);
38
+ this.options = options;
39
+
40
+ if (options.executor) {
41
+ this.executor = options.executor;
42
+ }
43
+
44
+ if (!this.executor) {
45
+ if (options.client) {
46
+ this.client = options.client;
47
+ } else if (options.apiKey) {
48
+ this.client = new OpenAI({
49
+ apiKey: options.apiKey,
50
+ ...(options.baseURL !== undefined && { baseURL: options.baseURL }),
51
+ ...(options.timeout !== undefined && { timeout: options.timeout }),
52
+ });
53
+ } else {
54
+ throw new Error('Either Gemma client, apiKey, or executor is required');
55
+ }
56
+ }
57
+ }
58
+
59
+ override async chat(
60
+ messages: TUniversalMessage[],
61
+ options?: IChatOptions,
62
+ ): Promise<TUniversalMessage> {
63
+ this.validateMessages(messages);
64
+ this.validateNativeWebTools(options?.nativeWebTools);
65
+
66
+ if (this.executor) {
67
+ try {
68
+ return await this.executeViaExecutorOrDirect(messages, options);
69
+ } catch (error) {
70
+ this.logger.error(
71
+ 'Gemma Provider executor chat error:',
72
+ error instanceof Error ? error.message : String(error),
73
+ );
74
+ throw error;
75
+ }
76
+ }
77
+
78
+ if (!this.client) {
79
+ throw new Error(
80
+ 'Gemma client not available. Either provide a client/apiKey or use an executor.',
81
+ );
82
+ }
83
+
84
+ try {
85
+ const requestParams = this.buildRequestParams(messages, options);
86
+ const textDeltaCb = options?.onTextDelta ?? this.onTextDelta;
87
+ if (textDeltaCb) {
88
+ return await this.chatWithStreamingAssembly(
89
+ {
90
+ ...requestParams,
91
+ stream: true,
92
+ },
93
+ {
94
+ ...options,
95
+ onTextDelta: textDeltaCb,
96
+ },
97
+ );
98
+ }
99
+
100
+ options?.onProviderNativeRawPayload?.({
101
+ provider: 'gemma',
102
+ apiSurface: 'chat-completions',
103
+ payloadKind: 'request',
104
+ payload: requestParams,
105
+ });
106
+ const response = await this.client.chat.completions.create(requestParams);
107
+ options?.onProviderNativeRawPayload?.({
108
+ provider: 'gemma',
109
+ apiSurface: 'chat-completions',
110
+ payloadKind: 'response',
111
+ payload: response,
112
+ });
113
+ return parseGemmaChatCompletion(response, this.logger, options);
114
+ } catch (error) {
115
+ const gemmaError = error as IOpenAICompatibleError;
116
+ const errorMessage = gemmaError.message || 'Gemma API request failed';
117
+ throw new Error(`Gemma chat failed: ${errorMessage}`);
118
+ }
119
+ }
120
+
121
+ override async *chatStream(
122
+ messages: TUniversalMessage[],
123
+ options?: IChatOptions,
124
+ ): AsyncIterable<TUniversalMessage> {
125
+ this.validateMessages(messages);
126
+ this.validateNativeWebTools(options?.nativeWebTools);
127
+
128
+ if (this.executor) {
129
+ try {
130
+ yield* this.executeStreamViaExecutorOrDirect(messages, options);
131
+ return;
132
+ } catch (error) {
133
+ this.logger.error(
134
+ 'Gemma Provider executor stream error:',
135
+ error instanceof Error ? error.message : String(error),
136
+ );
137
+ throw error;
138
+ }
139
+ }
140
+
141
+ if (!this.client) {
142
+ throw new Error(
143
+ 'Gemma client not available. Either provide a client/apiKey or use an executor.',
144
+ );
145
+ }
146
+
147
+ try {
148
+ const requestParams = this.buildStreamingRequestParams(messages, options);
149
+ options?.onProviderNativeRawPayload?.({
150
+ provider: 'gemma',
151
+ apiSurface: 'chat-completions',
152
+ payloadKind: 'request',
153
+ payload: requestParams,
154
+ });
155
+ const stream = await this.client.chat.completions.create(requestParams);
156
+ const projectionState = createGemmaStreamProjectionState(this.logger, options?.tools);
157
+
158
+ const observedStream = observeProviderNativeRawPayloadStream(stream, {
159
+ provider: 'gemma',
160
+ apiSurface: 'chat-completions',
161
+ onProviderNativeRawPayload: options?.onProviderNativeRawPayload,
162
+ });
163
+
164
+ for await (const chunk of this.streamWithAbort(observedStream, options?.signal)) {
165
+ for (const message of projectGemmaStreamChunk(chunk, projectionState)) {
166
+ yield message;
167
+ }
168
+ }
169
+
170
+ for (const message of flushGemmaStreamProjection(projectionState)) {
171
+ yield message;
172
+ }
173
+ } catch (error) {
174
+ const gemmaError = error as IOpenAICompatibleError;
175
+ const errorMessage = gemmaError.message || 'Gemma API request failed';
176
+ throw new Error(`Gemma stream failed: ${errorMessage}`);
177
+ }
178
+ }
179
+
180
+ override supportsTools(): boolean {
181
+ return true;
182
+ }
183
+
184
+ override getCapabilities(): IProviderCapabilities {
185
+ return {
186
+ functionCalling: { supported: true },
187
+ nativeWebTools: {
188
+ webSearch: {
189
+ supported: false,
190
+ enabled: false,
191
+ source: 'openai-compatible-chat-completions',
192
+ reason:
193
+ 'Gemma OpenAI-compatible endpoints support declared function tools, not provider-native web search.',
194
+ },
195
+ webFetch: {
196
+ supported: false,
197
+ enabled: false,
198
+ source: 'openai-compatible-chat-completions',
199
+ reason:
200
+ 'Gemma OpenAI-compatible endpoints support declared function tools, not provider-native web fetch.',
201
+ },
202
+ },
203
+ };
204
+ }
205
+
206
+ override validateConfig(): boolean {
207
+ return !!this.client && !!this.options;
208
+ }
209
+
210
+ override async dispose(): Promise<void> {
211
+ // OpenAI-compatible local clients do not need explicit cleanup.
212
+ }
213
+
214
+ protected override validateMessages(messages: TUniversalMessage[]): void {
215
+ super.validateMessages(messages);
216
+
217
+ for (const message of messages) {
218
+ if (message.role === 'assistant') {
219
+ const assistantMsg = message as IAssistantMessage;
220
+ if (
221
+ assistantMsg.toolCalls &&
222
+ assistantMsg.toolCalls.length > 0 &&
223
+ assistantMsg.content === ''
224
+ ) {
225
+ continue;
226
+ }
227
+ }
228
+ }
229
+ }
230
+
231
+ private buildRequestParams(
232
+ messages: TUniversalMessage[],
233
+ options: IChatOptions | undefined,
234
+ ): OpenAI.Chat.ChatCompletionCreateParamsNonStreaming {
235
+ const model = options?.model ?? this.options.defaultModel;
236
+ if (!model) {
237
+ throw new Error(
238
+ 'Model is required in chat options. Please specify a model in defaultModel configuration.',
239
+ );
240
+ }
241
+
242
+ const requestParams = {
243
+ model,
244
+ messages: convertToOpenAICompatibleMessages(messages),
245
+ ...(options?.temperature !== undefined && { temperature: options.temperature }),
246
+ ...(options?.maxTokens !== undefined && { max_tokens: options.maxTokens }),
247
+ ...(options?.tools && {
248
+ tools: convertToOpenAICompatibleTools(options.tools),
249
+ tool_choice: 'auto' as const,
250
+ }),
251
+ };
252
+
253
+ return requestParams as OpenAI.Chat.ChatCompletionCreateParamsNonStreaming;
254
+ }
255
+
256
+ private buildStreamingRequestParams(
257
+ messages: TUniversalMessage[],
258
+ options: IChatOptions | undefined,
259
+ ): OpenAI.Chat.ChatCompletionCreateParamsStreaming {
260
+ return {
261
+ ...this.buildRequestParams(messages, options),
262
+ stream: true,
263
+ } as OpenAI.Chat.ChatCompletionCreateParamsStreaming;
264
+ }
265
+
266
+ private async chatWithStreamingAssembly(
267
+ requestParams: OpenAI.Chat.ChatCompletionCreateParamsStreaming,
268
+ options: IChatOptions,
269
+ ): Promise<TUniversalMessage> {
270
+ if (!this.client) {
271
+ throw new Error(
272
+ 'Gemma client not available. Either provide a client/apiKey or use an executor.',
273
+ );
274
+ }
275
+
276
+ try {
277
+ options.onProviderNativeRawPayload?.({
278
+ provider: 'gemma',
279
+ apiSurface: 'chat-completions',
280
+ payloadKind: 'request',
281
+ payload: requestParams,
282
+ });
283
+ const stream = await this.client.chat.completions.create(
284
+ requestParams,
285
+ options.signal ? { signal: options.signal } : undefined,
286
+ );
287
+ const projector = new GemmaReasoningProjector();
288
+ const result = await assembleOpenAICompatibleStream({
289
+ stream: observeProviderNativeRawPayloadStream(stream, {
290
+ provider: 'gemma',
291
+ apiSurface: 'chat-completions',
292
+ onProviderNativeRawPayload: options.onProviderNativeRawPayload,
293
+ }),
294
+ onTextDelta: options.onTextDelta,
295
+ signal: options.signal,
296
+ textProjector: (text) => projector.project(text),
297
+ textProjectorFlush: () => projector.flush(),
298
+ toolCallTextProjector: createGemmaToolCallProjector(options.tools),
299
+ });
300
+
301
+ return withGemmaProjectionMetadata(result, projector.rawText, projector.removedReasoning);
302
+ } catch (error) {
303
+ const gemmaError = error as IOpenAICompatibleError;
304
+ const errorMessage = gemmaError.message || 'Gemma streaming request failed';
305
+ throw new Error(`Gemma stream failed: ${errorMessage}`);
306
+ }
307
+ }
308
+ }
@@ -0,0 +1,58 @@
1
+ import { findGemmaDeclaredToolName } from './pseudo-tool-call-tag-parser';
2
+ import type { TGemmaJsonValue } from './pseudo-tool-call-types';
3
+
4
+ export interface IGemmaPseudoCommandEnvelope {
5
+ toolName: string;
6
+ args: Record<string, TGemmaJsonValue>;
7
+ }
8
+
9
+ export function parseGemmaPseudoCommandEnvelopes(
10
+ rawText: string,
11
+ toolNames: readonly string[],
12
+ ): IGemmaPseudoCommandEnvelope[] {
13
+ const jsonEnvelope = parseGemmaPseudoCommandEnvelope(rawText, toolNames);
14
+ return jsonEnvelope ? [jsonEnvelope] : [];
15
+ }
16
+
17
+ export function parseGemmaPseudoCommandEnvelope(
18
+ rawText: string,
19
+ toolNames: readonly string[],
20
+ ): IGemmaPseudoCommandEnvelope | undefined {
21
+ const openEnd = rawText.indexOf('>');
22
+ const closeStart = rawText.lastIndexOf('</');
23
+ if (openEnd === -1 || closeStart === -1 || closeStart <= openEnd) {
24
+ return undefined;
25
+ }
26
+
27
+ const parsed = parseJsonValue(rawText.slice(openEnd + 1, closeStart).trim());
28
+ if (!isJsonRecord(parsed)) {
29
+ return undefined;
30
+ }
31
+
32
+ const command = parsed['command'];
33
+ const args = parsed['args'];
34
+ if (typeof command !== 'string' || !isJsonRecord(args)) {
35
+ return undefined;
36
+ }
37
+
38
+ const toolName = findGemmaDeclaredToolName(command, toolNames);
39
+ if (!toolName) {
40
+ return undefined;
41
+ }
42
+
43
+ return { toolName, args };
44
+ }
45
+
46
+ function parseJsonValue(text: string): TGemmaJsonValue | undefined {
47
+ try {
48
+ return JSON.parse(text) as TGemmaJsonValue;
49
+ } catch {
50
+ return undefined;
51
+ }
52
+ }
53
+
54
+ function isJsonRecord(
55
+ value: TGemmaJsonValue | undefined,
56
+ ): value is Record<string, TGemmaJsonValue> {
57
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
58
+ }
@@ -0,0 +1,243 @@
1
+ import type { IToolCall } from '@robota-sdk/agent-core';
2
+ import { parseGemmaPseudoCommandEnvelopes } from './pseudo-command-envelope';
3
+ import {
4
+ consumeGemmaPseudoControlBlock,
5
+ consumeGemmaPseudoToolTag,
6
+ createGemmaPseudoStartMarkers,
7
+ findGemmaDeclaredToolName,
8
+ findNextGemmaPseudoStartMarker,
9
+ longestGemmaPseudoStartPrefixSuffixLength,
10
+ parseGemmaPseudoTag,
11
+ } from './pseudo-tool-call-tag-parser';
12
+ import type {
13
+ IGemmaConsumedPseudoBlock,
14
+ IGemmaParsedPseudoTag,
15
+ IGemmaPseudoProjectionOptions,
16
+ TGemmaJsonValue,
17
+ } from './pseudo-tool-call-types';
18
+
19
+ const DEFAULT_CALL_ID_PREFIX = 'gemma_call';
20
+
21
+ export interface IGemmaPseudoToolCallProjection {
22
+ visibleText: string;
23
+ toolCalls: IToolCall[];
24
+ removedToolCallText: boolean;
25
+ rawToolCallTextParts: string[];
26
+ }
27
+
28
+ export interface IGemmaPseudoToolCallProjectorOptions {
29
+ toolNames: readonly string[];
30
+ callIdPrefix?: string;
31
+ startCallIndex?: number;
32
+ }
33
+
34
+ interface IProjectionState {
35
+ visibleParts: string[];
36
+ toolCalls: IToolCall[];
37
+ rawToolCallTextParts: string[];
38
+ removedToolCallText: boolean;
39
+ }
40
+
41
+ interface IProjectTagResult {
42
+ cursor: number;
43
+ completed: boolean;
44
+ }
45
+
46
+ export function projectGemmaPseudoToolCallText(
47
+ rawText: string,
48
+ options: IGemmaPseudoToolCallProjectorOptions,
49
+ projectionOptions: IGemmaPseudoProjectionOptions,
50
+ ): IGemmaPseudoToolCallProjection {
51
+ const state = createProjectionState();
52
+ const markers = createGemmaPseudoStartMarkers(options.toolNames);
53
+ let cursor = 0;
54
+
55
+ while (cursor < rawText.length) {
56
+ const nextMarker = findNextGemmaPseudoStartMarker(rawText, cursor, markers);
57
+ if (nextMarker === -1) {
58
+ appendVisibleTail(state, rawText.slice(cursor), projectionOptions, markers);
59
+ break;
60
+ }
61
+
62
+ appendVisibleTail(state, rawText.slice(cursor, nextMarker), projectionOptions, markers);
63
+ const result = projectTagAt(rawText, nextMarker, state, options, projectionOptions);
64
+ if (!result.completed) {
65
+ break;
66
+ }
67
+ cursor = result.cursor;
68
+ }
69
+
70
+ return {
71
+ visibleText: state.visibleParts.join(''),
72
+ toolCalls: state.toolCalls,
73
+ rawToolCallTextParts: state.rawToolCallTextParts,
74
+ removedToolCallText: state.removedToolCallText,
75
+ };
76
+ }
77
+
78
+ function projectTagAt(
79
+ rawText: string,
80
+ tagStart: number,
81
+ state: IProjectionState,
82
+ options: IGemmaPseudoToolCallProjectorOptions,
83
+ projectionOptions: IGemmaPseudoProjectionOptions,
84
+ ): IProjectTagResult {
85
+ const tag = parseGemmaPseudoTag(rawText, tagStart);
86
+ if (!tag) {
87
+ return projectMalformedTag(rawText, tagStart, state, projectionOptions);
88
+ }
89
+ const toolName = findGemmaDeclaredToolName(tag.tagName, options.toolNames);
90
+ if (toolName) {
91
+ return projectToolTag(rawText, tag, toolName, state, options);
92
+ }
93
+ return projectXmlArtifact(rawText, tagStart, tag, state, options, projectionOptions);
94
+ }
95
+
96
+ function projectMalformedTag(
97
+ rawText: string,
98
+ tagStart: number,
99
+ state: IProjectionState,
100
+ projectionOptions: IGemmaPseudoProjectionOptions,
101
+ ): IProjectTagResult {
102
+ if (!projectionOptions.final) {
103
+ return { cursor: tagStart, completed: false };
104
+ }
105
+ state.visibleParts.push(rawText[tagStart] ?? '');
106
+ return { cursor: tagStart + 1, completed: true };
107
+ }
108
+
109
+ function projectXmlArtifact(
110
+ rawText: string,
111
+ tagStart: number,
112
+ tag: IGemmaParsedPseudoTag,
113
+ state: IProjectionState,
114
+ options: IGemmaPseudoToolCallProjectorOptions,
115
+ projectionOptions: IGemmaPseudoProjectionOptions,
116
+ ): IProjectTagResult {
117
+ const block = consumeGemmaPseudoControlBlock(rawText, tag, projectionOptions);
118
+ if (!block.complete && !projectionOptions.final) {
119
+ return { cursor: tagStart, completed: false };
120
+ }
121
+
122
+ const rawControlText = rawText.slice(tagStart, block.end);
123
+ const rawPartCount = state.rawToolCallTextParts.length;
124
+ state.removedToolCallText = true;
125
+ appendCommandEnvelopeToolCall(state, rawControlText, options);
126
+ mergeProjection(state, projectControlBlockInner(block, state, options));
127
+ if (state.rawToolCallTextParts.length === rawPartCount) {
128
+ state.rawToolCallTextParts.push(rawControlText);
129
+ }
130
+ return { cursor: block.end, completed: true };
131
+ }
132
+
133
+ function projectControlBlockInner(
134
+ block: IGemmaConsumedPseudoBlock,
135
+ state: IProjectionState,
136
+ options: IGemmaPseudoToolCallProjectorOptions,
137
+ ): IGemmaPseudoToolCallProjection {
138
+ return projectGemmaPseudoToolCallText(
139
+ block.innerText,
140
+ {
141
+ ...options,
142
+ startCallIndex: getNextCallIndex(state, options),
143
+ },
144
+ { final: true },
145
+ );
146
+ }
147
+
148
+ function projectToolTag(
149
+ rawText: string,
150
+ tag: IGemmaParsedPseudoTag,
151
+ toolName: string,
152
+ state: IProjectionState,
153
+ options: IGemmaPseudoToolCallProjectorOptions,
154
+ ): IProjectTagResult {
155
+ const rawToolTag = consumeGemmaPseudoToolTag(rawText, tag);
156
+ appendToolCall(state, toolName, tag.attributes, rawToolTag.rawText, options);
157
+ return { cursor: rawToolTag.end, completed: true };
158
+ }
159
+
160
+ function appendVisibleTail(
161
+ state: IProjectionState,
162
+ tail: string,
163
+ options: IGemmaPseudoProjectionOptions,
164
+ markers: readonly string[],
165
+ ): void {
166
+ if (tail.length === 0) {
167
+ return;
168
+ }
169
+ if (options.final) {
170
+ state.visibleParts.push(tail);
171
+ return;
172
+ }
173
+ const heldLength = longestGemmaPseudoStartPrefixSuffixLength(tail, markers);
174
+ state.visibleParts.push(tail.slice(0, tail.length - heldLength));
175
+ }
176
+
177
+ function appendToolCall(
178
+ state: IProjectionState,
179
+ toolName: string,
180
+ args: Record<string, TGemmaJsonValue>,
181
+ rawText: string,
182
+ options: IGemmaPseudoToolCallProjectorOptions,
183
+ ): void {
184
+ if (Object.keys(args).length === 0) {
185
+ return;
186
+ }
187
+ state.toolCalls.push(createToolCall(toolName, args, options, state));
188
+ state.rawToolCallTextParts.push(rawText);
189
+ state.removedToolCallText = true;
190
+ }
191
+
192
+ function appendCommandEnvelopeToolCall(
193
+ state: IProjectionState,
194
+ rawText: string,
195
+ options: IGemmaPseudoToolCallProjectorOptions,
196
+ ): void {
197
+ const commands = parseGemmaPseudoCommandEnvelopes(rawText, options.toolNames);
198
+ for (const command of commands) {
199
+ state.toolCalls.push(createToolCall(command.toolName, command.args, options, state));
200
+ state.rawToolCallTextParts.push(rawText);
201
+ }
202
+ }
203
+
204
+ function createToolCall(
205
+ toolName: string,
206
+ args: Record<string, TGemmaJsonValue>,
207
+ options: IGemmaPseudoToolCallProjectorOptions,
208
+ state: IProjectionState,
209
+ ): IToolCall {
210
+ return {
211
+ id: `${options.callIdPrefix ?? DEFAULT_CALL_ID_PREFIX}_${getNextCallIndex(state, options)}`,
212
+ type: 'function',
213
+ function: {
214
+ name: toolName,
215
+ arguments: JSON.stringify(args),
216
+ },
217
+ };
218
+ }
219
+
220
+ function createProjectionState(): IProjectionState {
221
+ return {
222
+ visibleParts: [],
223
+ toolCalls: [],
224
+ rawToolCallTextParts: [],
225
+ removedToolCallText: false,
226
+ };
227
+ }
228
+
229
+ function mergeProjection(
230
+ state: IProjectionState,
231
+ projection: IGemmaPseudoToolCallProjection,
232
+ ): void {
233
+ state.toolCalls.push(...projection.toolCalls);
234
+ state.rawToolCallTextParts.push(...projection.rawToolCallTextParts);
235
+ state.removedToolCallText = state.removedToolCallText || projection.removedToolCallText;
236
+ }
237
+
238
+ function getNextCallIndex(
239
+ state: IProjectionState,
240
+ options: IGemmaPseudoToolCallProjectorOptions,
241
+ ): number {
242
+ return (options.startCallIndex ?? 0) + state.toolCalls.length;
243
+ }