@animalabs/membrane 0.5.13 → 0.5.15

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.
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Google Gemini provider adapter
3
+ *
4
+ * Direct adapter for Google's Generative AI REST API.
5
+ * Supports Gemini 2.x, 2.5, 3.x models with:
6
+ * - Text and image input
7
+ * - Tool/function calling
8
+ * - Streaming via SSE
9
+ *
10
+ * Auth: API key passed as query parameter (?key=...)
11
+ * Endpoint: generativelanguage.googleapis.com/v1beta
12
+ */
13
+ import type { ProviderAdapter, ProviderRequest, ProviderRequestOptions, ProviderResponse, StreamCallbacks, ContentBlock } from '../types/index.js';
14
+ interface GeminiPart {
15
+ text?: string;
16
+ inlineData?: {
17
+ mimeType: string;
18
+ data: string;
19
+ };
20
+ functionCall?: {
21
+ name: string;
22
+ args: Record<string, unknown>;
23
+ };
24
+ functionResponse?: {
25
+ name: string;
26
+ response: Record<string, unknown>;
27
+ };
28
+ }
29
+ export interface GeminiAdapterConfig {
30
+ /** Google AI API key */
31
+ apiKey?: string;
32
+ /** Base URL (default: https://generativelanguage.googleapis.com/v1beta) */
33
+ baseURL?: string;
34
+ /** Default max output tokens */
35
+ defaultMaxTokens?: number;
36
+ }
37
+ export declare class GeminiAdapter implements ProviderAdapter {
38
+ readonly name = "gemini";
39
+ private apiKey;
40
+ private baseURL;
41
+ private defaultMaxTokens;
42
+ constructor(config?: GeminiAdapterConfig);
43
+ supportsModel(modelId: string): boolean;
44
+ complete(request: ProviderRequest, options?: ProviderRequestOptions): Promise<ProviderResponse>;
45
+ stream(request: ProviderRequest, callbacks: StreamCallbacks, options?: ProviderRequestOptions): Promise<ProviderResponse>;
46
+ private buildRequest;
47
+ private convertMessages;
48
+ /**
49
+ * Gemini requires strictly alternating user/model messages.
50
+ * Merge consecutive messages with the same role into one.
51
+ */
52
+ private mergeConsecutiveRoles;
53
+ private convertTools;
54
+ private parseResponse;
55
+ private buildContentBlocks;
56
+ private mapFinishReason;
57
+ private handleError;
58
+ }
59
+ /**
60
+ * Convert normalized content blocks to Gemini parts
61
+ */
62
+ export declare function toGeminiParts(blocks: ContentBlock[]): GeminiPart[];
63
+ /**
64
+ * Convert Gemini parts to normalized content blocks
65
+ */
66
+ export declare function fromGeminiParts(parts: GeminiPart[]): ContentBlock[];
67
+ export {};
68
+ //# sourceMappingURL=gemini.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../src/providers/gemini.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,gBAAgB,EAChB,eAAe,EACf,YAAY,EACb,MAAM,mBAAmB,CAAC;AAe3B,UAAU,UAAU;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,YAAY,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;IAC/D,gBAAgB,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;CACxE;AA6CD,MAAM,WAAW,mBAAmB;IAClC,wBAAwB;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,gCAAgC;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAMD,qBAAa,aAAc,YAAW,eAAe;IACnD,QAAQ,CAAC,IAAI,YAAY;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,gBAAgB,CAAS;gBAErB,MAAM,GAAE,mBAAwB;IAU5C,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAIjC,QAAQ,CACZ,OAAO,EAAE,eAAe,EACxB,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,gBAAgB,CAAC;IA8BtB,MAAM,CACV,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,eAAe,EAC1B,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,gBAAgB,CAAC;IAoG5B,OAAO,CAAC,YAAY;IAoDpB,OAAO,CAAC,eAAe;IA2EvB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAyB7B,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,aAAa;IAsCrB,OAAO,CAAC,kBAAkB;IAsB1B,OAAO,CAAC,eAAe;IAsBvB,OAAO,CAAC,WAAW;CAyCpB;AAMD;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,UAAU,EAAE,CAelE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,YAAY,EAAE,CAkBnE"}
@@ -0,0 +1,441 @@
1
+ /**
2
+ * Google Gemini provider adapter
3
+ *
4
+ * Direct adapter for Google's Generative AI REST API.
5
+ * Supports Gemini 2.x, 2.5, 3.x models with:
6
+ * - Text and image input
7
+ * - Tool/function calling
8
+ * - Streaming via SSE
9
+ *
10
+ * Auth: API key passed as query parameter (?key=...)
11
+ * Endpoint: generativelanguage.googleapis.com/v1beta
12
+ */
13
+ import { MembraneError, rateLimitError, contextLengthError, authError, serverError, abortError, networkError, } from '../types/index.js';
14
+ // ============================================================================
15
+ // Gemini Adapter
16
+ // ============================================================================
17
+ export class GeminiAdapter {
18
+ name = 'gemini';
19
+ apiKey;
20
+ baseURL;
21
+ defaultMaxTokens;
22
+ constructor(config = {}) {
23
+ this.apiKey = config.apiKey ?? process.env.GOOGLE_API_KEY ?? '';
24
+ this.baseURL = (config.baseURL ?? 'https://generativelanguage.googleapis.com/v1beta').replace(/\/$/, '');
25
+ this.defaultMaxTokens = config.defaultMaxTokens ?? 4096;
26
+ if (!this.apiKey) {
27
+ throw new Error('Google AI API key not provided');
28
+ }
29
+ }
30
+ supportsModel(modelId) {
31
+ return modelId.startsWith('gemini-');
32
+ }
33
+ async complete(request, options) {
34
+ const geminiRequest = this.buildRequest(request);
35
+ options?.onRequest?.(geminiRequest);
36
+ try {
37
+ const url = `${this.baseURL}/models/${request.model}:generateContent?key=${this.apiKey}`;
38
+ const response = await fetch(url, {
39
+ method: 'POST',
40
+ headers: { 'Content-Type': 'application/json' },
41
+ body: JSON.stringify(geminiRequest),
42
+ signal: options?.signal,
43
+ });
44
+ if (!response.ok) {
45
+ const errorText = await response.text();
46
+ throw new Error(`Gemini API error: ${response.status} ${errorText}`);
47
+ }
48
+ const data = await response.json();
49
+ if (data.error) {
50
+ throw new Error(`Gemini API error: ${data.error.code} ${data.error.message}`);
51
+ }
52
+ return this.parseResponse(data, request.model, geminiRequest);
53
+ }
54
+ catch (error) {
55
+ throw this.handleError(error, geminiRequest);
56
+ }
57
+ }
58
+ async stream(request, callbacks, options) {
59
+ const geminiRequest = this.buildRequest(request);
60
+ options?.onRequest?.(geminiRequest);
61
+ try {
62
+ const url = `${this.baseURL}/models/${request.model}:streamGenerateContent?alt=sse&key=${this.apiKey}`;
63
+ const response = await fetch(url, {
64
+ method: 'POST',
65
+ headers: { 'Content-Type': 'application/json' },
66
+ body: JSON.stringify(geminiRequest),
67
+ signal: options?.signal,
68
+ });
69
+ if (!response.ok) {
70
+ const errorText = await response.text();
71
+ throw new Error(`Gemini API error: ${response.status} ${errorText}`);
72
+ }
73
+ const reader = response.body?.getReader();
74
+ if (!reader) {
75
+ throw new Error('No response body');
76
+ }
77
+ const decoder = new TextDecoder();
78
+ let accumulated = '';
79
+ let finishReason = 'STOP';
80
+ let toolCalls = [];
81
+ let lastUsage;
82
+ let buffer = '';
83
+ while (true) {
84
+ const { done, value } = await reader.read();
85
+ if (done)
86
+ break;
87
+ buffer += decoder.decode(value, { stream: true });
88
+ const lines = buffer.split('\n');
89
+ // Keep the last potentially incomplete line in buffer
90
+ buffer = lines.pop() ?? '';
91
+ for (const line of lines) {
92
+ if (!line.startsWith('data: '))
93
+ continue;
94
+ const data = line.slice(6).trim();
95
+ if (!data || data === '[DONE]')
96
+ continue;
97
+ try {
98
+ const parsed = JSON.parse(data);
99
+ const candidate = parsed.candidates?.[0];
100
+ if (candidate?.content?.parts) {
101
+ for (const part of candidate.content.parts) {
102
+ if (part.text) {
103
+ accumulated += part.text;
104
+ callbacks.onChunk(part.text);
105
+ }
106
+ if (part.functionCall) {
107
+ toolCalls.push({
108
+ name: part.functionCall.name,
109
+ args: part.functionCall.args,
110
+ });
111
+ }
112
+ }
113
+ }
114
+ if (candidate?.finishReason) {
115
+ finishReason = candidate.finishReason;
116
+ }
117
+ if (parsed.usageMetadata) {
118
+ lastUsage = parsed.usageMetadata;
119
+ }
120
+ }
121
+ catch {
122
+ // Ignore parse errors in stream chunks
123
+ }
124
+ }
125
+ }
126
+ return {
127
+ content: this.buildContentBlocks(accumulated, toolCalls),
128
+ stopReason: this.mapFinishReason(finishReason),
129
+ stopSequence: undefined,
130
+ usage: {
131
+ inputTokens: lastUsage?.promptTokenCount ?? 0,
132
+ outputTokens: lastUsage?.candidatesTokenCount ?? 0,
133
+ cacheReadTokens: lastUsage?.cachedContentTokenCount
134
+ ? lastUsage.cachedContentTokenCount
135
+ : undefined,
136
+ },
137
+ model: request.model,
138
+ rawRequest: geminiRequest,
139
+ raw: { finishReason, usage: lastUsage },
140
+ };
141
+ }
142
+ catch (error) {
143
+ throw this.handleError(error, geminiRequest);
144
+ }
145
+ }
146
+ // --------------------------------------------------------------------------
147
+ // Request Building
148
+ // --------------------------------------------------------------------------
149
+ buildRequest(request) {
150
+ const contents = this.convertMessages(request.messages);
151
+ const maxTokens = request.maxTokens || this.defaultMaxTokens;
152
+ const geminiRequest = { contents };
153
+ // System instruction
154
+ if (request.system) {
155
+ const systemText = typeof request.system === 'string'
156
+ ? request.system
157
+ : request.system
158
+ .filter((b) => b.type === 'text')
159
+ .map((b) => b.text)
160
+ .join('\n');
161
+ if (systemText) {
162
+ geminiRequest.systemInstruction = {
163
+ parts: [{ text: systemText }],
164
+ };
165
+ }
166
+ }
167
+ // Generation config
168
+ geminiRequest.generationConfig = {
169
+ maxOutputTokens: maxTokens,
170
+ };
171
+ if (request.temperature !== undefined) {
172
+ geminiRequest.generationConfig.temperature = request.temperature;
173
+ }
174
+ if (request.stopSequences && request.stopSequences.length > 0) {
175
+ // Gemini API limits stop sequences to 5
176
+ geminiRequest.generationConfig.stopSequences = request.stopSequences.slice(0, 5);
177
+ }
178
+ // Tools
179
+ if (request.tools && request.tools.length > 0) {
180
+ geminiRequest.tools = [{
181
+ functionDeclarations: this.convertTools(request.tools),
182
+ }];
183
+ }
184
+ // Extra params
185
+ if (request.extra) {
186
+ const { normalizedMessages, prompt, ...rest } = request.extra;
187
+ Object.assign(geminiRequest, rest);
188
+ }
189
+ return geminiRequest;
190
+ }
191
+ convertMessages(messages) {
192
+ const contents = [];
193
+ for (const msg of messages) {
194
+ const role = msg.role === 'assistant' ? 'model' : 'user';
195
+ // Simple string content
196
+ if (typeof msg.content === 'string') {
197
+ contents.push({ role, parts: [{ text: msg.content }] });
198
+ continue;
199
+ }
200
+ // Array content blocks (Anthropic-style)
201
+ if (Array.isArray(msg.content)) {
202
+ const parts = [];
203
+ const toolResultParts = [];
204
+ for (const block of msg.content) {
205
+ if (block.type === 'text') {
206
+ if (block.text)
207
+ parts.push({ text: block.text });
208
+ }
209
+ else if (block.type === 'image') {
210
+ // Anthropic image format → Gemini inlineData
211
+ const source = block.source;
212
+ if (source?.type === 'base64' && source.data) {
213
+ parts.push({
214
+ inlineData: {
215
+ mimeType: source.media_type ?? 'image/jpeg',
216
+ data: source.data,
217
+ },
218
+ });
219
+ }
220
+ }
221
+ else if (block.type === 'tool_use') {
222
+ parts.push({
223
+ functionCall: {
224
+ name: block.name,
225
+ args: block.input ?? {},
226
+ },
227
+ });
228
+ }
229
+ else if (block.type === 'tool_result') {
230
+ const resultContent = typeof block.content === 'string'
231
+ ? block.content
232
+ : JSON.stringify(block.content);
233
+ toolResultParts.push({
234
+ functionResponse: {
235
+ name: block.name ?? block.tool_use_id ?? 'unknown',
236
+ response: { result: resultContent },
237
+ },
238
+ });
239
+ }
240
+ }
241
+ // Tool results go in a user message
242
+ if (toolResultParts.length > 0) {
243
+ contents.push({ role: 'user', parts: toolResultParts });
244
+ }
245
+ if (parts.length > 0) {
246
+ contents.push({ role, parts });
247
+ }
248
+ continue;
249
+ }
250
+ // Null/empty content — skip
251
+ if (msg.content === null || msg.content === undefined)
252
+ continue;
253
+ // Fallback
254
+ contents.push({ role, parts: [{ text: String(msg.content) }] });
255
+ }
256
+ // Gemini requires alternating user/model roles.
257
+ // Merge consecutive same-role messages.
258
+ return this.mergeConsecutiveRoles(contents);
259
+ }
260
+ /**
261
+ * Gemini requires strictly alternating user/model messages.
262
+ * Merge consecutive messages with the same role into one.
263
+ */
264
+ mergeConsecutiveRoles(contents) {
265
+ if (contents.length === 0)
266
+ return contents;
267
+ const merged = [contents[0]];
268
+ for (let i = 1; i < contents.length; i++) {
269
+ const current = contents[i];
270
+ const last = merged[merged.length - 1];
271
+ if (current.role === last.role) {
272
+ // Merge parts into the previous message
273
+ last.parts.push(...current.parts);
274
+ }
275
+ else {
276
+ merged.push(current);
277
+ }
278
+ }
279
+ // Gemini also requires the first message to be "user"
280
+ if (merged.length > 0 && merged[0].role !== 'user') {
281
+ merged.unshift({ role: 'user', parts: [{ text: '[Start]' }] });
282
+ }
283
+ return merged;
284
+ }
285
+ convertTools(tools) {
286
+ return tools.map(tool => {
287
+ const schema = tool.inputSchema || tool.input_schema || { type: 'object', properties: {} };
288
+ return {
289
+ name: tool.name,
290
+ description: tool.description ?? '',
291
+ parameters: schema,
292
+ };
293
+ });
294
+ }
295
+ // --------------------------------------------------------------------------
296
+ // Response Parsing
297
+ // --------------------------------------------------------------------------
298
+ parseResponse(response, requestedModel, rawRequest) {
299
+ const candidate = response.candidates?.[0];
300
+ const parts = candidate?.content?.parts ?? [];
301
+ let text = '';
302
+ const toolCalls = [];
303
+ for (const part of parts) {
304
+ if (part.text)
305
+ text += part.text;
306
+ if (part.functionCall) {
307
+ toolCalls.push({
308
+ name: part.functionCall.name,
309
+ args: part.functionCall.args,
310
+ });
311
+ }
312
+ }
313
+ return {
314
+ content: this.buildContentBlocks(text, toolCalls),
315
+ stopReason: this.mapFinishReason(candidate?.finishReason),
316
+ stopSequence: undefined,
317
+ usage: {
318
+ inputTokens: response.usageMetadata?.promptTokenCount ?? 0,
319
+ outputTokens: response.usageMetadata?.candidatesTokenCount ?? 0,
320
+ cacheReadTokens: response.usageMetadata?.cachedContentTokenCount
321
+ ? response.usageMetadata.cachedContentTokenCount
322
+ : undefined,
323
+ },
324
+ model: response.modelVersion ?? requestedModel,
325
+ rawRequest,
326
+ raw: response,
327
+ };
328
+ }
329
+ buildContentBlocks(text, toolCalls) {
330
+ const content = [];
331
+ if (text) {
332
+ content.push({ type: 'text', text });
333
+ }
334
+ for (const tc of toolCalls) {
335
+ content.push({
336
+ type: 'tool_use',
337
+ id: `gemini-tc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
338
+ name: tc.name,
339
+ input: tc.args,
340
+ });
341
+ }
342
+ return content;
343
+ }
344
+ mapFinishReason(reason) {
345
+ switch (reason) {
346
+ case 'STOP':
347
+ return 'end_turn';
348
+ case 'MAX_TOKENS':
349
+ return 'max_tokens';
350
+ case 'SAFETY':
351
+ return 'refusal';
352
+ case 'RECITATION':
353
+ return 'refusal';
354
+ case 'TOOL_CALLS':
355
+ case 'FUNCTION_CALL':
356
+ return 'tool_use';
357
+ default:
358
+ return 'end_turn';
359
+ }
360
+ }
361
+ // --------------------------------------------------------------------------
362
+ // Error Handling
363
+ // --------------------------------------------------------------------------
364
+ handleError(error, rawRequest) {
365
+ if (error instanceof MembraneError)
366
+ return error;
367
+ if (error instanceof Error) {
368
+ const message = error.message;
369
+ if (message.includes('401') || message.includes('403') || message.includes('API_KEY_INVALID') || message.includes('PERMISSION_DENIED')) {
370
+ return authError(message, error, rawRequest);
371
+ }
372
+ if (message.includes('429') || message.includes('RESOURCE_EXHAUSTED')) {
373
+ const retryMatch = message.match(/retry.after[:\s]*(\d+)/i);
374
+ const retryAfter = retryMatch?.[1] ? parseInt(retryMatch[1], 10) * 1000 : undefined;
375
+ return rateLimitError(message, retryAfter, error, rawRequest);
376
+ }
377
+ if (message.includes('context') || message.includes('too long') || message.includes('token limit')) {
378
+ return contextLengthError(message, error, rawRequest);
379
+ }
380
+ if (message.includes('500') || message.includes('502') || message.includes('503') || message.includes('INTERNAL')) {
381
+ return serverError(message, undefined, error, rawRequest);
382
+ }
383
+ if (error.name === 'AbortError') {
384
+ return abortError(undefined, rawRequest);
385
+ }
386
+ if (message.includes('network') || message.includes('fetch') || message.includes('ECONNREFUSED')) {
387
+ return networkError(message, error, rawRequest);
388
+ }
389
+ }
390
+ return new MembraneError({
391
+ type: 'unknown',
392
+ message: error instanceof Error ? error.message : String(error),
393
+ retryable: false,
394
+ rawError: error,
395
+ rawRequest,
396
+ });
397
+ }
398
+ }
399
+ // ============================================================================
400
+ // Content Conversion Utilities
401
+ // ============================================================================
402
+ /**
403
+ * Convert normalized content blocks to Gemini parts
404
+ */
405
+ export function toGeminiParts(blocks) {
406
+ return blocks.map(block => {
407
+ if (block.type === 'text') {
408
+ return { text: block.text };
409
+ }
410
+ if (block.type === 'tool_use') {
411
+ return {
412
+ functionCall: {
413
+ name: block.name,
414
+ args: block.input ?? {},
415
+ },
416
+ };
417
+ }
418
+ return { text: String(block) };
419
+ });
420
+ }
421
+ /**
422
+ * Convert Gemini parts to normalized content blocks
423
+ */
424
+ export function fromGeminiParts(parts) {
425
+ const result = [];
426
+ for (const part of parts) {
427
+ if (part.text) {
428
+ result.push({ type: 'text', text: part.text });
429
+ }
430
+ if (part.functionCall) {
431
+ result.push({
432
+ type: 'tool_use',
433
+ id: `gemini-tc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
434
+ name: part.functionCall.name,
435
+ input: part.functionCall.args,
436
+ });
437
+ }
438
+ }
439
+ return result;
440
+ }
441
+ //# sourceMappingURL=gemini.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../src/providers/gemini.ts"],"names":[],"mappings":"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"}
@@ -8,4 +8,5 @@ export { OpenAICompatibleAdapter, toOpenAIMessages, fromOpenAIMessage, type Open
8
8
  export { OpenAICompletionsAdapter, type OpenAICompletionsAdapterConfig, } from './openai-completions.js';
9
9
  export { MockAdapter, createEchoAdapter, createCannedAdapter, type MockAdapterConfig, } from './mock.js';
10
10
  export { BedrockAdapter, type BedrockAdapterConfig, } from './bedrock.js';
11
+ export { GeminiAdapter, toGeminiParts, fromGeminiParts, type GeminiAdapterConfig, } from './gemini.js';
11
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,KAAK,sBAAsB,GAC5B,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,qBAAqB,EACrB,KAAK,uBAAuB,GAC7B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,KAAK,mBAAmB,GACzB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,6BAA6B,GACnC,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,wBAAwB,EACxB,KAAK,8BAA8B,GACpC,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,iBAAiB,GACvB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,cAAc,EACd,KAAK,oBAAoB,GAC1B,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,KAAK,sBAAsB,GAC5B,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,qBAAqB,EACrB,KAAK,uBAAuB,GAC7B,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,KAAK,mBAAmB,GACzB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,6BAA6B,GACnC,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,wBAAwB,EACxB,KAAK,8BAA8B,GACpC,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,iBAAiB,GACvB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,cAAc,EACd,KAAK,oBAAoB,GAC1B,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,aAAa,EACb,aAAa,EACb,eAAe,EACf,KAAK,mBAAmB,GACzB,MAAM,aAAa,CAAC"}
@@ -8,4 +8,5 @@ export { OpenAICompatibleAdapter, toOpenAIMessages, fromOpenAIMessage, } from '.
8
8
  export { OpenAICompletionsAdapter, } from './openai-completions.js';
9
9
  export { MockAdapter, createEchoAdapter, createCannedAdapter, } from './mock.js';
10
10
  export { BedrockAdapter, } from './bedrock.js';
11
+ export { GeminiAdapter, toGeminiParts, fromGeminiParts, } from './gemini.js';
11
12
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,GAErB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,qBAAqB,GAEtB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,aAAa,EACb,eAAe,EACf,iBAAiB,GAElB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,GAElB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,wBAAwB,GAEzB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,mBAAmB,GAEpB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,cAAc,GAEf,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,GAErB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,qBAAqB,GAEtB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,aAAa,EACb,eAAe,EACf,iBAAiB,GAElB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,GAElB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,wBAAwB,GAEzB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,mBAAmB,GAEpB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,cAAc,GAEf,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,aAAa,EACb,aAAa,EACb,eAAe,GAEhB,MAAM,aAAa,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@animalabs/membrane",
3
- "version": "0.5.13",
3
+ "version": "0.5.15",
4
4
  "description": "LLM middleware - a selective boundary that transforms what passes through",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,599 @@
1
+ /**
2
+ * Google Gemini provider adapter
3
+ *
4
+ * Direct adapter for Google's Generative AI REST API.
5
+ * Supports Gemini 2.x, 2.5, 3.x models with:
6
+ * - Text and image input
7
+ * - Tool/function calling
8
+ * - Streaming via SSE
9
+ *
10
+ * Auth: API key passed as query parameter (?key=...)
11
+ * Endpoint: generativelanguage.googleapis.com/v1beta
12
+ */
13
+
14
+ import type {
15
+ ProviderAdapter,
16
+ ProviderRequest,
17
+ ProviderRequestOptions,
18
+ ProviderResponse,
19
+ StreamCallbacks,
20
+ ContentBlock,
21
+ } from '../types/index.js';
22
+ import {
23
+ MembraneError,
24
+ rateLimitError,
25
+ contextLengthError,
26
+ authError,
27
+ serverError,
28
+ abortError,
29
+ networkError,
30
+ } from '../types/index.js';
31
+
32
+ // ============================================================================
33
+ // Gemini API Types
34
+ // ============================================================================
35
+
36
+ interface GeminiPart {
37
+ text?: string;
38
+ inlineData?: { mimeType: string; data: string };
39
+ functionCall?: { name: string; args: Record<string, unknown> };
40
+ functionResponse?: { name: string; response: Record<string, unknown> };
41
+ }
42
+
43
+ interface GeminiContent {
44
+ role: 'user' | 'model';
45
+ parts: GeminiPart[];
46
+ }
47
+
48
+ interface GeminiRequest {
49
+ contents: GeminiContent[];
50
+ systemInstruction?: { parts: GeminiPart[] };
51
+ generationConfig?: {
52
+ maxOutputTokens?: number;
53
+ temperature?: number;
54
+ topP?: number;
55
+ stopSequences?: string[];
56
+ };
57
+ tools?: { functionDeclarations: GeminiFunctionDeclaration[] }[];
58
+ }
59
+
60
+ interface GeminiFunctionDeclaration {
61
+ name: string;
62
+ description: string;
63
+ parameters?: Record<string, unknown>;
64
+ }
65
+
66
+ interface GeminiResponse {
67
+ candidates?: {
68
+ content?: GeminiContent;
69
+ finishReason?: string;
70
+ safetyRatings?: unknown[];
71
+ }[];
72
+ usageMetadata?: {
73
+ promptTokenCount?: number;
74
+ candidatesTokenCount?: number;
75
+ totalTokenCount?: number;
76
+ cachedContentTokenCount?: number;
77
+ };
78
+ modelVersion?: string;
79
+ error?: { code: number; message: string; status: string };
80
+ }
81
+
82
+ // ============================================================================
83
+ // Adapter Configuration
84
+ // ============================================================================
85
+
86
+ export interface GeminiAdapterConfig {
87
+ /** Google AI API key */
88
+ apiKey?: string;
89
+
90
+ /** Base URL (default: https://generativelanguage.googleapis.com/v1beta) */
91
+ baseURL?: string;
92
+
93
+ /** Default max output tokens */
94
+ defaultMaxTokens?: number;
95
+ }
96
+
97
+ // ============================================================================
98
+ // Gemini Adapter
99
+ // ============================================================================
100
+
101
+ export class GeminiAdapter implements ProviderAdapter {
102
+ readonly name = 'gemini';
103
+ private apiKey: string;
104
+ private baseURL: string;
105
+ private defaultMaxTokens: number;
106
+
107
+ constructor(config: GeminiAdapterConfig = {}) {
108
+ this.apiKey = config.apiKey ?? process.env.GOOGLE_API_KEY ?? '';
109
+ this.baseURL = (config.baseURL ?? 'https://generativelanguage.googleapis.com/v1beta').replace(/\/$/, '');
110
+ this.defaultMaxTokens = config.defaultMaxTokens ?? 4096;
111
+
112
+ if (!this.apiKey) {
113
+ throw new Error('Google AI API key not provided');
114
+ }
115
+ }
116
+
117
+ supportsModel(modelId: string): boolean {
118
+ return modelId.startsWith('gemini-');
119
+ }
120
+
121
+ async complete(
122
+ request: ProviderRequest,
123
+ options?: ProviderRequestOptions
124
+ ): Promise<ProviderResponse> {
125
+ const geminiRequest = this.buildRequest(request);
126
+ options?.onRequest?.(geminiRequest);
127
+
128
+ try {
129
+ const url = `${this.baseURL}/models/${request.model}:generateContent?key=${this.apiKey}`;
130
+ const response = await fetch(url, {
131
+ method: 'POST',
132
+ headers: { 'Content-Type': 'application/json' },
133
+ body: JSON.stringify(geminiRequest),
134
+ signal: options?.signal,
135
+ });
136
+
137
+ if (!response.ok) {
138
+ const errorText = await response.text();
139
+ throw new Error(`Gemini API error: ${response.status} ${errorText}`);
140
+ }
141
+
142
+ const data = await response.json() as GeminiResponse;
143
+
144
+ if (data.error) {
145
+ throw new Error(`Gemini API error: ${data.error.code} ${data.error.message}`);
146
+ }
147
+
148
+ return this.parseResponse(data, request.model, geminiRequest);
149
+ } catch (error) {
150
+ throw this.handleError(error, geminiRequest);
151
+ }
152
+ }
153
+
154
+ async stream(
155
+ request: ProviderRequest,
156
+ callbacks: StreamCallbacks,
157
+ options?: ProviderRequestOptions
158
+ ): Promise<ProviderResponse> {
159
+ const geminiRequest = this.buildRequest(request);
160
+ options?.onRequest?.(geminiRequest);
161
+
162
+ try {
163
+ const url = `${this.baseURL}/models/${request.model}:streamGenerateContent?alt=sse&key=${this.apiKey}`;
164
+ const response = await fetch(url, {
165
+ method: 'POST',
166
+ headers: { 'Content-Type': 'application/json' },
167
+ body: JSON.stringify(geminiRequest),
168
+ signal: options?.signal,
169
+ });
170
+
171
+ if (!response.ok) {
172
+ const errorText = await response.text();
173
+ throw new Error(`Gemini API error: ${response.status} ${errorText}`);
174
+ }
175
+
176
+ const reader = response.body?.getReader();
177
+ if (!reader) {
178
+ throw new Error('No response body');
179
+ }
180
+
181
+ const decoder = new TextDecoder();
182
+ let accumulated = '';
183
+ let finishReason = 'STOP';
184
+ let toolCalls: { name: string; args: Record<string, unknown> }[] = [];
185
+ let lastUsage: GeminiResponse['usageMetadata'] | undefined;
186
+ let buffer = '';
187
+
188
+ while (true) {
189
+ const { done, value } = await reader.read();
190
+ if (done) break;
191
+
192
+ buffer += decoder.decode(value, { stream: true });
193
+ const lines = buffer.split('\n');
194
+ // Keep the last potentially incomplete line in buffer
195
+ buffer = lines.pop() ?? '';
196
+
197
+ for (const line of lines) {
198
+ if (!line.startsWith('data: ')) continue;
199
+ const data = line.slice(6).trim();
200
+ if (!data || data === '[DONE]') continue;
201
+
202
+ try {
203
+ const parsed = JSON.parse(data) as GeminiResponse;
204
+ const candidate = parsed.candidates?.[0];
205
+
206
+ if (candidate?.content?.parts) {
207
+ for (const part of candidate.content.parts) {
208
+ if (part.text) {
209
+ accumulated += part.text;
210
+ callbacks.onChunk(part.text);
211
+ }
212
+ if (part.functionCall) {
213
+ toolCalls.push({
214
+ name: part.functionCall.name,
215
+ args: part.functionCall.args,
216
+ });
217
+ }
218
+ }
219
+ }
220
+
221
+ if (candidate?.finishReason) {
222
+ finishReason = candidate.finishReason;
223
+ }
224
+
225
+ if (parsed.usageMetadata) {
226
+ lastUsage = parsed.usageMetadata;
227
+ }
228
+ } catch {
229
+ // Ignore parse errors in stream chunks
230
+ }
231
+ }
232
+ }
233
+
234
+ return {
235
+ content: this.buildContentBlocks(accumulated, toolCalls),
236
+ stopReason: this.mapFinishReason(finishReason),
237
+ stopSequence: undefined,
238
+ usage: {
239
+ inputTokens: lastUsage?.promptTokenCount ?? 0,
240
+ outputTokens: lastUsage?.candidatesTokenCount ?? 0,
241
+ cacheReadTokens: lastUsage?.cachedContentTokenCount
242
+ ? lastUsage.cachedContentTokenCount
243
+ : undefined,
244
+ },
245
+ model: request.model,
246
+ rawRequest: geminiRequest,
247
+ raw: { finishReason, usage: lastUsage },
248
+ };
249
+ } catch (error) {
250
+ throw this.handleError(error, geminiRequest);
251
+ }
252
+ }
253
+
254
+ // --------------------------------------------------------------------------
255
+ // Request Building
256
+ // --------------------------------------------------------------------------
257
+
258
+ private buildRequest(request: ProviderRequest): GeminiRequest {
259
+ const contents = this.convertMessages(request.messages as any[]);
260
+ const maxTokens = request.maxTokens || this.defaultMaxTokens;
261
+
262
+ const geminiRequest: GeminiRequest = { contents };
263
+
264
+ // System instruction
265
+ if (request.system) {
266
+ const systemText = typeof request.system === 'string'
267
+ ? request.system
268
+ : (request.system as any[])
269
+ .filter((b: any) => b.type === 'text')
270
+ .map((b: any) => b.text)
271
+ .join('\n');
272
+
273
+ if (systemText) {
274
+ geminiRequest.systemInstruction = {
275
+ parts: [{ text: systemText }],
276
+ };
277
+ }
278
+ }
279
+
280
+ // Generation config
281
+ geminiRequest.generationConfig = {
282
+ maxOutputTokens: maxTokens,
283
+ };
284
+
285
+ if (request.temperature !== undefined) {
286
+ geminiRequest.generationConfig.temperature = request.temperature;
287
+ }
288
+
289
+ if (request.stopSequences && request.stopSequences.length > 0) {
290
+ // Gemini API limits stop sequences to 5
291
+ geminiRequest.generationConfig.stopSequences = request.stopSequences.slice(0, 5);
292
+ }
293
+
294
+ // Tools
295
+ if (request.tools && request.tools.length > 0) {
296
+ geminiRequest.tools = [{
297
+ functionDeclarations: this.convertTools(request.tools as any[]),
298
+ }];
299
+ }
300
+
301
+ // Extra params
302
+ if (request.extra) {
303
+ const { normalizedMessages, prompt, ...rest } = request.extra as Record<string, unknown>;
304
+ Object.assign(geminiRequest, rest);
305
+ }
306
+
307
+ return geminiRequest;
308
+ }
309
+
310
+ private convertMessages(messages: any[]): GeminiContent[] {
311
+ const contents: GeminiContent[] = [];
312
+
313
+ for (const msg of messages) {
314
+ const role: 'user' | 'model' = msg.role === 'assistant' ? 'model' : 'user';
315
+
316
+ // Simple string content
317
+ if (typeof msg.content === 'string') {
318
+ contents.push({ role, parts: [{ text: msg.content }] });
319
+ continue;
320
+ }
321
+
322
+ // Array content blocks (Anthropic-style)
323
+ if (Array.isArray(msg.content)) {
324
+ const parts: GeminiPart[] = [];
325
+ const toolResultParts: GeminiPart[] = [];
326
+
327
+ for (const block of msg.content) {
328
+ if (block.type === 'text') {
329
+ if (block.text) parts.push({ text: block.text });
330
+ } else if (block.type === 'image') {
331
+ // Anthropic image format → Gemini inlineData
332
+ const source = block.source;
333
+ if (source?.type === 'base64' && source.data) {
334
+ parts.push({
335
+ inlineData: {
336
+ mimeType: source.media_type ?? 'image/jpeg',
337
+ data: source.data,
338
+ },
339
+ });
340
+ }
341
+ } else if (block.type === 'tool_use') {
342
+ parts.push({
343
+ functionCall: {
344
+ name: block.name,
345
+ args: block.input ?? {},
346
+ },
347
+ });
348
+ } else if (block.type === 'tool_result') {
349
+ const resultContent = typeof block.content === 'string'
350
+ ? block.content
351
+ : JSON.stringify(block.content);
352
+ toolResultParts.push({
353
+ functionResponse: {
354
+ name: block.name ?? block.tool_use_id ?? 'unknown',
355
+ response: { result: resultContent },
356
+ },
357
+ });
358
+ }
359
+ }
360
+
361
+ // Tool results go in a user message
362
+ if (toolResultParts.length > 0) {
363
+ contents.push({ role: 'user', parts: toolResultParts });
364
+ }
365
+
366
+ if (parts.length > 0) {
367
+ contents.push({ role, parts });
368
+ }
369
+
370
+ continue;
371
+ }
372
+
373
+ // Null/empty content — skip
374
+ if (msg.content === null || msg.content === undefined) continue;
375
+
376
+ // Fallback
377
+ contents.push({ role, parts: [{ text: String(msg.content) }] });
378
+ }
379
+
380
+ // Gemini requires alternating user/model roles.
381
+ // Merge consecutive same-role messages.
382
+ return this.mergeConsecutiveRoles(contents);
383
+ }
384
+
385
+ /**
386
+ * Gemini requires strictly alternating user/model messages.
387
+ * Merge consecutive messages with the same role into one.
388
+ */
389
+ private mergeConsecutiveRoles(contents: GeminiContent[]): GeminiContent[] {
390
+ if (contents.length === 0) return contents;
391
+
392
+ const merged: GeminiContent[] = [contents[0]!];
393
+
394
+ for (let i = 1; i < contents.length; i++) {
395
+ const current = contents[i]!;
396
+ const last = merged[merged.length - 1]!;
397
+
398
+ if (current.role === last.role) {
399
+ // Merge parts into the previous message
400
+ last.parts.push(...current.parts);
401
+ } else {
402
+ merged.push(current);
403
+ }
404
+ }
405
+
406
+ // Gemini also requires the first message to be "user"
407
+ if (merged.length > 0 && merged[0]!.role !== 'user') {
408
+ merged.unshift({ role: 'user', parts: [{ text: '[Start]' }] });
409
+ }
410
+
411
+ return merged;
412
+ }
413
+
414
+ private convertTools(tools: any[]): GeminiFunctionDeclaration[] {
415
+ return tools.map(tool => {
416
+ const schema = tool.inputSchema || tool.input_schema || { type: 'object', properties: {} };
417
+ return {
418
+ name: tool.name,
419
+ description: tool.description ?? '',
420
+ parameters: schema,
421
+ };
422
+ });
423
+ }
424
+
425
+ // --------------------------------------------------------------------------
426
+ // Response Parsing
427
+ // --------------------------------------------------------------------------
428
+
429
+ private parseResponse(
430
+ response: GeminiResponse,
431
+ requestedModel: string,
432
+ rawRequest: unknown
433
+ ): ProviderResponse {
434
+ const candidate = response.candidates?.[0];
435
+ const parts = candidate?.content?.parts ?? [];
436
+
437
+ let text = '';
438
+ const toolCalls: { name: string; args: Record<string, unknown> }[] = [];
439
+
440
+ for (const part of parts) {
441
+ if (part.text) text += part.text;
442
+ if (part.functionCall) {
443
+ toolCalls.push({
444
+ name: part.functionCall.name,
445
+ args: part.functionCall.args,
446
+ });
447
+ }
448
+ }
449
+
450
+ return {
451
+ content: this.buildContentBlocks(text, toolCalls),
452
+ stopReason: this.mapFinishReason(candidate?.finishReason),
453
+ stopSequence: undefined,
454
+ usage: {
455
+ inputTokens: response.usageMetadata?.promptTokenCount ?? 0,
456
+ outputTokens: response.usageMetadata?.candidatesTokenCount ?? 0,
457
+ cacheReadTokens: response.usageMetadata?.cachedContentTokenCount
458
+ ? response.usageMetadata.cachedContentTokenCount
459
+ : undefined,
460
+ },
461
+ model: response.modelVersion ?? requestedModel,
462
+ rawRequest,
463
+ raw: response,
464
+ };
465
+ }
466
+
467
+ private buildContentBlocks(
468
+ text: string,
469
+ toolCalls: { name: string; args: Record<string, unknown> }[]
470
+ ): ContentBlock[] {
471
+ const content: ContentBlock[] = [];
472
+
473
+ if (text) {
474
+ content.push({ type: 'text', text });
475
+ }
476
+
477
+ for (const tc of toolCalls) {
478
+ content.push({
479
+ type: 'tool_use',
480
+ id: `gemini-tc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
481
+ name: tc.name,
482
+ input: tc.args,
483
+ });
484
+ }
485
+
486
+ return content;
487
+ }
488
+
489
+ private mapFinishReason(reason: string | undefined): string {
490
+ switch (reason) {
491
+ case 'STOP':
492
+ return 'end_turn';
493
+ case 'MAX_TOKENS':
494
+ return 'max_tokens';
495
+ case 'SAFETY':
496
+ return 'refusal';
497
+ case 'RECITATION':
498
+ return 'refusal';
499
+ case 'TOOL_CALLS':
500
+ case 'FUNCTION_CALL':
501
+ return 'tool_use';
502
+ default:
503
+ return 'end_turn';
504
+ }
505
+ }
506
+
507
+ // --------------------------------------------------------------------------
508
+ // Error Handling
509
+ // --------------------------------------------------------------------------
510
+
511
+ private handleError(error: unknown, rawRequest?: unknown): MembraneError {
512
+ if (error instanceof MembraneError) return error;
513
+
514
+ if (error instanceof Error) {
515
+ const message = error.message;
516
+
517
+ if (message.includes('401') || message.includes('403') || message.includes('API_KEY_INVALID') || message.includes('PERMISSION_DENIED')) {
518
+ return authError(message, error, rawRequest);
519
+ }
520
+
521
+ if (message.includes('429') || message.includes('RESOURCE_EXHAUSTED')) {
522
+ const retryMatch = message.match(/retry.after[:\s]*(\d+)/i);
523
+ const retryAfter = retryMatch?.[1] ? parseInt(retryMatch[1], 10) * 1000 : undefined;
524
+ return rateLimitError(message, retryAfter, error, rawRequest);
525
+ }
526
+
527
+ if (message.includes('context') || message.includes('too long') || message.includes('token limit')) {
528
+ return contextLengthError(message, error, rawRequest);
529
+ }
530
+
531
+ if (message.includes('500') || message.includes('502') || message.includes('503') || message.includes('INTERNAL')) {
532
+ return serverError(message, undefined, error, rawRequest);
533
+ }
534
+
535
+ if (error.name === 'AbortError') {
536
+ return abortError(undefined, rawRequest);
537
+ }
538
+
539
+ if (message.includes('network') || message.includes('fetch') || message.includes('ECONNREFUSED')) {
540
+ return networkError(message, error, rawRequest);
541
+ }
542
+ }
543
+
544
+ return new MembraneError({
545
+ type: 'unknown',
546
+ message: error instanceof Error ? error.message : String(error),
547
+ retryable: false,
548
+ rawError: error,
549
+ rawRequest,
550
+ });
551
+ }
552
+ }
553
+
554
+ // ============================================================================
555
+ // Content Conversion Utilities
556
+ // ============================================================================
557
+
558
+ /**
559
+ * Convert normalized content blocks to Gemini parts
560
+ */
561
+ export function toGeminiParts(blocks: ContentBlock[]): GeminiPart[] {
562
+ return blocks.map(block => {
563
+ if (block.type === 'text') {
564
+ return { text: (block as any).text };
565
+ }
566
+ if (block.type === 'tool_use') {
567
+ return {
568
+ functionCall: {
569
+ name: (block as any).name,
570
+ args: (block as any).input ?? {},
571
+ },
572
+ };
573
+ }
574
+ return { text: String(block) };
575
+ });
576
+ }
577
+
578
+ /**
579
+ * Convert Gemini parts to normalized content blocks
580
+ */
581
+ export function fromGeminiParts(parts: GeminiPart[]): ContentBlock[] {
582
+ const result: ContentBlock[] = [];
583
+
584
+ for (const part of parts) {
585
+ if (part.text) {
586
+ result.push({ type: 'text', text: part.text });
587
+ }
588
+ if (part.functionCall) {
589
+ result.push({
590
+ type: 'tool_use',
591
+ id: `gemini-tc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
592
+ name: part.functionCall.name,
593
+ input: part.functionCall.args,
594
+ });
595
+ }
596
+ }
597
+
598
+ return result;
599
+ }
@@ -46,3 +46,10 @@ export {
46
46
  BedrockAdapter,
47
47
  type BedrockAdapterConfig,
48
48
  } from './bedrock.js';
49
+
50
+ export {
51
+ GeminiAdapter,
52
+ toGeminiParts,
53
+ fromGeminiParts,
54
+ type GeminiAdapterConfig,
55
+ } from './gemini.js';