@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.
- package/dist/providers/gemini.d.ts +68 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +441 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/index.d.ts +1 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +1 -0
- package/dist/providers/index.js.map +1 -1
- package/package.json +1 -1
- package/src/providers/gemini.ts +599 -0
- package/src/providers/index.ts +7 -0
|
@@ -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"}
|
package/dist/providers/index.js
CHANGED
|
@@ -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
|
@@ -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
|
+
}
|