@animalabs/membrane 0.5.13 → 0.5.14

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;IAmDpB,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,440 @@
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
+ geminiRequest.generationConfig.stopSequences = request.stopSequences;
176
+ }
177
+ // Tools
178
+ if (request.tools && request.tools.length > 0) {
179
+ geminiRequest.tools = [{
180
+ functionDeclarations: this.convertTools(request.tools),
181
+ }];
182
+ }
183
+ // Extra params
184
+ if (request.extra) {
185
+ const { normalizedMessages, prompt, ...rest } = request.extra;
186
+ Object.assign(geminiRequest, rest);
187
+ }
188
+ return geminiRequest;
189
+ }
190
+ convertMessages(messages) {
191
+ const contents = [];
192
+ for (const msg of messages) {
193
+ const role = msg.role === 'assistant' ? 'model' : 'user';
194
+ // Simple string content
195
+ if (typeof msg.content === 'string') {
196
+ contents.push({ role, parts: [{ text: msg.content }] });
197
+ continue;
198
+ }
199
+ // Array content blocks (Anthropic-style)
200
+ if (Array.isArray(msg.content)) {
201
+ const parts = [];
202
+ const toolResultParts = [];
203
+ for (const block of msg.content) {
204
+ if (block.type === 'text') {
205
+ if (block.text)
206
+ parts.push({ text: block.text });
207
+ }
208
+ else if (block.type === 'image') {
209
+ // Anthropic image format → Gemini inlineData
210
+ const source = block.source;
211
+ if (source?.type === 'base64' && source.data) {
212
+ parts.push({
213
+ inlineData: {
214
+ mimeType: source.media_type ?? 'image/jpeg',
215
+ data: source.data,
216
+ },
217
+ });
218
+ }
219
+ }
220
+ else if (block.type === 'tool_use') {
221
+ parts.push({
222
+ functionCall: {
223
+ name: block.name,
224
+ args: block.input ?? {},
225
+ },
226
+ });
227
+ }
228
+ else if (block.type === 'tool_result') {
229
+ const resultContent = typeof block.content === 'string'
230
+ ? block.content
231
+ : JSON.stringify(block.content);
232
+ toolResultParts.push({
233
+ functionResponse: {
234
+ name: block.name ?? block.tool_use_id ?? 'unknown',
235
+ response: { result: resultContent },
236
+ },
237
+ });
238
+ }
239
+ }
240
+ // Tool results go in a user message
241
+ if (toolResultParts.length > 0) {
242
+ contents.push({ role: 'user', parts: toolResultParts });
243
+ }
244
+ if (parts.length > 0) {
245
+ contents.push({ role, parts });
246
+ }
247
+ continue;
248
+ }
249
+ // Null/empty content — skip
250
+ if (msg.content === null || msg.content === undefined)
251
+ continue;
252
+ // Fallback
253
+ contents.push({ role, parts: [{ text: String(msg.content) }] });
254
+ }
255
+ // Gemini requires alternating user/model roles.
256
+ // Merge consecutive same-role messages.
257
+ return this.mergeConsecutiveRoles(contents);
258
+ }
259
+ /**
260
+ * Gemini requires strictly alternating user/model messages.
261
+ * Merge consecutive messages with the same role into one.
262
+ */
263
+ mergeConsecutiveRoles(contents) {
264
+ if (contents.length === 0)
265
+ return contents;
266
+ const merged = [contents[0]];
267
+ for (let i = 1; i < contents.length; i++) {
268
+ const current = contents[i];
269
+ const last = merged[merged.length - 1];
270
+ if (current.role === last.role) {
271
+ // Merge parts into the previous message
272
+ last.parts.push(...current.parts);
273
+ }
274
+ else {
275
+ merged.push(current);
276
+ }
277
+ }
278
+ // Gemini also requires the first message to be "user"
279
+ if (merged.length > 0 && merged[0].role !== 'user') {
280
+ merged.unshift({ role: 'user', parts: [{ text: '[Start]' }] });
281
+ }
282
+ return merged;
283
+ }
284
+ convertTools(tools) {
285
+ return tools.map(tool => {
286
+ const schema = tool.inputSchema || tool.input_schema || { type: 'object', properties: {} };
287
+ return {
288
+ name: tool.name,
289
+ description: tool.description ?? '',
290
+ parameters: schema,
291
+ };
292
+ });
293
+ }
294
+ // --------------------------------------------------------------------------
295
+ // Response Parsing
296
+ // --------------------------------------------------------------------------
297
+ parseResponse(response, requestedModel, rawRequest) {
298
+ const candidate = response.candidates?.[0];
299
+ const parts = candidate?.content?.parts ?? [];
300
+ let text = '';
301
+ const toolCalls = [];
302
+ for (const part of parts) {
303
+ if (part.text)
304
+ text += part.text;
305
+ if (part.functionCall) {
306
+ toolCalls.push({
307
+ name: part.functionCall.name,
308
+ args: part.functionCall.args,
309
+ });
310
+ }
311
+ }
312
+ return {
313
+ content: this.buildContentBlocks(text, toolCalls),
314
+ stopReason: this.mapFinishReason(candidate?.finishReason),
315
+ stopSequence: undefined,
316
+ usage: {
317
+ inputTokens: response.usageMetadata?.promptTokenCount ?? 0,
318
+ outputTokens: response.usageMetadata?.candidatesTokenCount ?? 0,
319
+ cacheReadTokens: response.usageMetadata?.cachedContentTokenCount
320
+ ? response.usageMetadata.cachedContentTokenCount
321
+ : undefined,
322
+ },
323
+ model: response.modelVersion ?? requestedModel,
324
+ rawRequest,
325
+ raw: response,
326
+ };
327
+ }
328
+ buildContentBlocks(text, toolCalls) {
329
+ const content = [];
330
+ if (text) {
331
+ content.push({ type: 'text', text });
332
+ }
333
+ for (const tc of toolCalls) {
334
+ content.push({
335
+ type: 'tool_use',
336
+ id: `gemini-tc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
337
+ name: tc.name,
338
+ input: tc.args,
339
+ });
340
+ }
341
+ return content;
342
+ }
343
+ mapFinishReason(reason) {
344
+ switch (reason) {
345
+ case 'STOP':
346
+ return 'end_turn';
347
+ case 'MAX_TOKENS':
348
+ return 'max_tokens';
349
+ case 'SAFETY':
350
+ return 'refusal';
351
+ case 'RECITATION':
352
+ return 'refusal';
353
+ case 'TOOL_CALLS':
354
+ case 'FUNCTION_CALL':
355
+ return 'tool_use';
356
+ default:
357
+ return 'end_turn';
358
+ }
359
+ }
360
+ // --------------------------------------------------------------------------
361
+ // Error Handling
362
+ // --------------------------------------------------------------------------
363
+ handleError(error, rawRequest) {
364
+ if (error instanceof MembraneError)
365
+ return error;
366
+ if (error instanceof Error) {
367
+ const message = error.message;
368
+ if (message.includes('429') || message.includes('RESOURCE_EXHAUSTED') || message.includes('rate')) {
369
+ const retryMatch = message.match(/retry.after[:\s]*(\d+)/i);
370
+ const retryAfter = retryMatch?.[1] ? parseInt(retryMatch[1], 10) * 1000 : undefined;
371
+ return rateLimitError(message, retryAfter, error, rawRequest);
372
+ }
373
+ if (message.includes('401') || message.includes('403') || message.includes('API_KEY_INVALID') || message.includes('PERMISSION_DENIED')) {
374
+ return authError(message, error, rawRequest);
375
+ }
376
+ if (message.includes('context') || message.includes('too long') || message.includes('token')) {
377
+ return contextLengthError(message, error, rawRequest);
378
+ }
379
+ if (message.includes('500') || message.includes('502') || message.includes('503') || message.includes('INTERNAL')) {
380
+ return serverError(message, undefined, error, rawRequest);
381
+ }
382
+ if (error.name === 'AbortError') {
383
+ return abortError(undefined, rawRequest);
384
+ }
385
+ if (message.includes('network') || message.includes('fetch') || message.includes('ECONNREFUSED')) {
386
+ return networkError(message, error, rawRequest);
387
+ }
388
+ }
389
+ return new MembraneError({
390
+ type: 'unknown',
391
+ message: error instanceof Error ? error.message : String(error),
392
+ retryable: false,
393
+ rawError: error,
394
+ rawRequest,
395
+ });
396
+ }
397
+ }
398
+ // ============================================================================
399
+ // Content Conversion Utilities
400
+ // ============================================================================
401
+ /**
402
+ * Convert normalized content blocks to Gemini parts
403
+ */
404
+ export function toGeminiParts(blocks) {
405
+ return blocks.map(block => {
406
+ if (block.type === 'text') {
407
+ return { text: block.text };
408
+ }
409
+ if (block.type === 'tool_use') {
410
+ return {
411
+ functionCall: {
412
+ name: block.name,
413
+ args: block.input ?? {},
414
+ },
415
+ };
416
+ }
417
+ return { text: String(block) };
418
+ });
419
+ }
420
+ /**
421
+ * Convert Gemini parts to normalized content blocks
422
+ */
423
+ export function fromGeminiParts(parts) {
424
+ const result = [];
425
+ for (const part of parts) {
426
+ if (part.text) {
427
+ result.push({ type: 'text', text: part.text });
428
+ }
429
+ if (part.functionCall) {
430
+ result.push({
431
+ type: 'tool_use',
432
+ id: `gemini-tc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
433
+ name: part.functionCall.name,
434
+ input: part.functionCall.args,
435
+ });
436
+ }
437
+ }
438
+ return result;
439
+ }
440
+ //# 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.14",
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,598 @@
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
+ geminiRequest.generationConfig.stopSequences = request.stopSequences;
291
+ }
292
+
293
+ // Tools
294
+ if (request.tools && request.tools.length > 0) {
295
+ geminiRequest.tools = [{
296
+ functionDeclarations: this.convertTools(request.tools as any[]),
297
+ }];
298
+ }
299
+
300
+ // Extra params
301
+ if (request.extra) {
302
+ const { normalizedMessages, prompt, ...rest } = request.extra as Record<string, unknown>;
303
+ Object.assign(geminiRequest, rest);
304
+ }
305
+
306
+ return geminiRequest;
307
+ }
308
+
309
+ private convertMessages(messages: any[]): GeminiContent[] {
310
+ const contents: GeminiContent[] = [];
311
+
312
+ for (const msg of messages) {
313
+ const role: 'user' | 'model' = msg.role === 'assistant' ? 'model' : 'user';
314
+
315
+ // Simple string content
316
+ if (typeof msg.content === 'string') {
317
+ contents.push({ role, parts: [{ text: msg.content }] });
318
+ continue;
319
+ }
320
+
321
+ // Array content blocks (Anthropic-style)
322
+ if (Array.isArray(msg.content)) {
323
+ const parts: GeminiPart[] = [];
324
+ const toolResultParts: GeminiPart[] = [];
325
+
326
+ for (const block of msg.content) {
327
+ if (block.type === 'text') {
328
+ if (block.text) parts.push({ text: block.text });
329
+ } else if (block.type === 'image') {
330
+ // Anthropic image format → Gemini inlineData
331
+ const source = block.source;
332
+ if (source?.type === 'base64' && source.data) {
333
+ parts.push({
334
+ inlineData: {
335
+ mimeType: source.media_type ?? 'image/jpeg',
336
+ data: source.data,
337
+ },
338
+ });
339
+ }
340
+ } else if (block.type === 'tool_use') {
341
+ parts.push({
342
+ functionCall: {
343
+ name: block.name,
344
+ args: block.input ?? {},
345
+ },
346
+ });
347
+ } else if (block.type === 'tool_result') {
348
+ const resultContent = typeof block.content === 'string'
349
+ ? block.content
350
+ : JSON.stringify(block.content);
351
+ toolResultParts.push({
352
+ functionResponse: {
353
+ name: block.name ?? block.tool_use_id ?? 'unknown',
354
+ response: { result: resultContent },
355
+ },
356
+ });
357
+ }
358
+ }
359
+
360
+ // Tool results go in a user message
361
+ if (toolResultParts.length > 0) {
362
+ contents.push({ role: 'user', parts: toolResultParts });
363
+ }
364
+
365
+ if (parts.length > 0) {
366
+ contents.push({ role, parts });
367
+ }
368
+
369
+ continue;
370
+ }
371
+
372
+ // Null/empty content — skip
373
+ if (msg.content === null || msg.content === undefined) continue;
374
+
375
+ // Fallback
376
+ contents.push({ role, parts: [{ text: String(msg.content) }] });
377
+ }
378
+
379
+ // Gemini requires alternating user/model roles.
380
+ // Merge consecutive same-role messages.
381
+ return this.mergeConsecutiveRoles(contents);
382
+ }
383
+
384
+ /**
385
+ * Gemini requires strictly alternating user/model messages.
386
+ * Merge consecutive messages with the same role into one.
387
+ */
388
+ private mergeConsecutiveRoles(contents: GeminiContent[]): GeminiContent[] {
389
+ if (contents.length === 0) return contents;
390
+
391
+ const merged: GeminiContent[] = [contents[0]!];
392
+
393
+ for (let i = 1; i < contents.length; i++) {
394
+ const current = contents[i]!;
395
+ const last = merged[merged.length - 1]!;
396
+
397
+ if (current.role === last.role) {
398
+ // Merge parts into the previous message
399
+ last.parts.push(...current.parts);
400
+ } else {
401
+ merged.push(current);
402
+ }
403
+ }
404
+
405
+ // Gemini also requires the first message to be "user"
406
+ if (merged.length > 0 && merged[0]!.role !== 'user') {
407
+ merged.unshift({ role: 'user', parts: [{ text: '[Start]' }] });
408
+ }
409
+
410
+ return merged;
411
+ }
412
+
413
+ private convertTools(tools: any[]): GeminiFunctionDeclaration[] {
414
+ return tools.map(tool => {
415
+ const schema = tool.inputSchema || tool.input_schema || { type: 'object', properties: {} };
416
+ return {
417
+ name: tool.name,
418
+ description: tool.description ?? '',
419
+ parameters: schema,
420
+ };
421
+ });
422
+ }
423
+
424
+ // --------------------------------------------------------------------------
425
+ // Response Parsing
426
+ // --------------------------------------------------------------------------
427
+
428
+ private parseResponse(
429
+ response: GeminiResponse,
430
+ requestedModel: string,
431
+ rawRequest: unknown
432
+ ): ProviderResponse {
433
+ const candidate = response.candidates?.[0];
434
+ const parts = candidate?.content?.parts ?? [];
435
+
436
+ let text = '';
437
+ const toolCalls: { name: string; args: Record<string, unknown> }[] = [];
438
+
439
+ for (const part of parts) {
440
+ if (part.text) text += part.text;
441
+ if (part.functionCall) {
442
+ toolCalls.push({
443
+ name: part.functionCall.name,
444
+ args: part.functionCall.args,
445
+ });
446
+ }
447
+ }
448
+
449
+ return {
450
+ content: this.buildContentBlocks(text, toolCalls),
451
+ stopReason: this.mapFinishReason(candidate?.finishReason),
452
+ stopSequence: undefined,
453
+ usage: {
454
+ inputTokens: response.usageMetadata?.promptTokenCount ?? 0,
455
+ outputTokens: response.usageMetadata?.candidatesTokenCount ?? 0,
456
+ cacheReadTokens: response.usageMetadata?.cachedContentTokenCount
457
+ ? response.usageMetadata.cachedContentTokenCount
458
+ : undefined,
459
+ },
460
+ model: response.modelVersion ?? requestedModel,
461
+ rawRequest,
462
+ raw: response,
463
+ };
464
+ }
465
+
466
+ private buildContentBlocks(
467
+ text: string,
468
+ toolCalls: { name: string; args: Record<string, unknown> }[]
469
+ ): ContentBlock[] {
470
+ const content: ContentBlock[] = [];
471
+
472
+ if (text) {
473
+ content.push({ type: 'text', text });
474
+ }
475
+
476
+ for (const tc of toolCalls) {
477
+ content.push({
478
+ type: 'tool_use',
479
+ id: `gemini-tc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
480
+ name: tc.name,
481
+ input: tc.args,
482
+ });
483
+ }
484
+
485
+ return content;
486
+ }
487
+
488
+ private mapFinishReason(reason: string | undefined): string {
489
+ switch (reason) {
490
+ case 'STOP':
491
+ return 'end_turn';
492
+ case 'MAX_TOKENS':
493
+ return 'max_tokens';
494
+ case 'SAFETY':
495
+ return 'refusal';
496
+ case 'RECITATION':
497
+ return 'refusal';
498
+ case 'TOOL_CALLS':
499
+ case 'FUNCTION_CALL':
500
+ return 'tool_use';
501
+ default:
502
+ return 'end_turn';
503
+ }
504
+ }
505
+
506
+ // --------------------------------------------------------------------------
507
+ // Error Handling
508
+ // --------------------------------------------------------------------------
509
+
510
+ private handleError(error: unknown, rawRequest?: unknown): MembraneError {
511
+ if (error instanceof MembraneError) return error;
512
+
513
+ if (error instanceof Error) {
514
+ const message = error.message;
515
+
516
+ if (message.includes('429') || message.includes('RESOURCE_EXHAUSTED') || message.includes('rate')) {
517
+ const retryMatch = message.match(/retry.after[:\s]*(\d+)/i);
518
+ const retryAfter = retryMatch?.[1] ? parseInt(retryMatch[1], 10) * 1000 : undefined;
519
+ return rateLimitError(message, retryAfter, error, rawRequest);
520
+ }
521
+
522
+ if (message.includes('401') || message.includes('403') || message.includes('API_KEY_INVALID') || message.includes('PERMISSION_DENIED')) {
523
+ return authError(message, error, rawRequest);
524
+ }
525
+
526
+ if (message.includes('context') || message.includes('too long') || message.includes('token')) {
527
+ return contextLengthError(message, error, rawRequest);
528
+ }
529
+
530
+ if (message.includes('500') || message.includes('502') || message.includes('503') || message.includes('INTERNAL')) {
531
+ return serverError(message, undefined, error, rawRequest);
532
+ }
533
+
534
+ if (error.name === 'AbortError') {
535
+ return abortError(undefined, rawRequest);
536
+ }
537
+
538
+ if (message.includes('network') || message.includes('fetch') || message.includes('ECONNREFUSED')) {
539
+ return networkError(message, error, rawRequest);
540
+ }
541
+ }
542
+
543
+ return new MembraneError({
544
+ type: 'unknown',
545
+ message: error instanceof Error ? error.message : String(error),
546
+ retryable: false,
547
+ rawError: error,
548
+ rawRequest,
549
+ });
550
+ }
551
+ }
552
+
553
+ // ============================================================================
554
+ // Content Conversion Utilities
555
+ // ============================================================================
556
+
557
+ /**
558
+ * Convert normalized content blocks to Gemini parts
559
+ */
560
+ export function toGeminiParts(blocks: ContentBlock[]): GeminiPart[] {
561
+ return blocks.map(block => {
562
+ if (block.type === 'text') {
563
+ return { text: (block as any).text };
564
+ }
565
+ if (block.type === 'tool_use') {
566
+ return {
567
+ functionCall: {
568
+ name: (block as any).name,
569
+ args: (block as any).input ?? {},
570
+ },
571
+ };
572
+ }
573
+ return { text: String(block) };
574
+ });
575
+ }
576
+
577
+ /**
578
+ * Convert Gemini parts to normalized content blocks
579
+ */
580
+ export function fromGeminiParts(parts: GeminiPart[]): ContentBlock[] {
581
+ const result: ContentBlock[] = [];
582
+
583
+ for (const part of parts) {
584
+ if (part.text) {
585
+ result.push({ type: 'text', text: part.text });
586
+ }
587
+ if (part.functionCall) {
588
+ result.push({
589
+ type: 'tool_use',
590
+ id: `gemini-tc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
591
+ name: part.functionCall.name,
592
+ input: part.functionCall.args,
593
+ });
594
+ }
595
+ }
596
+
597
+ return result;
598
+ }
@@ -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';