@thinwrap/llm 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -0
  3. package/dist/cjs/base/base.connector.d.ts +44 -0
  4. package/dist/cjs/base/base.connector.js +216 -0
  5. package/dist/cjs/facades/chat.facade.d.ts +17 -0
  6. package/dist/cjs/facades/chat.facade.js +63 -0
  7. package/dist/cjs/facades/embeddings.facade.d.ts +23 -0
  8. package/dist/cjs/facades/embeddings.facade.js +63 -0
  9. package/dist/cjs/index.d.ts +15 -0
  10. package/dist/cjs/index.js +35 -0
  11. package/dist/cjs/package.json +1 -0
  12. package/dist/cjs/providers/_shared/openai-compat.connector.d.ts +23 -0
  13. package/dist/cjs/providers/_shared/openai-compat.connector.js +320 -0
  14. package/dist/cjs/providers/_shared/openai-compat.embeddings.connector.d.ts +18 -0
  15. package/dist/cjs/providers/_shared/openai-compat.embeddings.connector.js +105 -0
  16. package/dist/cjs/providers/_shared/spec.d.ts +99 -0
  17. package/dist/cjs/providers/_shared/spec.js +84 -0
  18. package/dist/cjs/providers/anthropic/anthropic.config.d.ts +17 -0
  19. package/dist/cjs/providers/anthropic/anthropic.config.js +2 -0
  20. package/dist/cjs/providers/anthropic/anthropic.connector.d.ts +30 -0
  21. package/dist/cjs/providers/anthropic/anthropic.connector.js +427 -0
  22. package/dist/cjs/providers/anthropic/index.d.ts +2 -0
  23. package/dist/cjs/providers/anthropic/index.js +5 -0
  24. package/dist/cjs/providers/bedrock/bedrock.config.d.ts +18 -0
  25. package/dist/cjs/providers/bedrock/bedrock.config.js +2 -0
  26. package/dist/cjs/providers/bedrock/bedrock.connector.d.ts +61 -0
  27. package/dist/cjs/providers/bedrock/bedrock.connector.js +479 -0
  28. package/dist/cjs/providers/bedrock/index.d.ts +2 -0
  29. package/dist/cjs/providers/bedrock/index.js +5 -0
  30. package/dist/cjs/providers/gemini/gemini.config.d.ts +12 -0
  31. package/dist/cjs/providers/gemini/gemini.config.js +2 -0
  32. package/dist/cjs/providers/gemini/gemini.connector.d.ts +28 -0
  33. package/dist/cjs/providers/gemini/gemini.connector.js +434 -0
  34. package/dist/cjs/providers/gemini/index.d.ts +2 -0
  35. package/dist/cjs/providers/gemini/index.js +5 -0
  36. package/dist/cjs/types/chat.types.d.ts +115 -0
  37. package/dist/cjs/types/chat.types.js +2 -0
  38. package/dist/cjs/types/config-map.type.d.ts +29 -0
  39. package/dist/cjs/types/config-map.type.js +2 -0
  40. package/dist/cjs/types/embeddings.types.d.ts +29 -0
  41. package/dist/cjs/types/embeddings.types.js +2 -0
  42. package/dist/cjs/types/error.types.d.ts +20 -0
  43. package/dist/cjs/types/error.types.js +22 -0
  44. package/dist/cjs/types/index.d.ts +7 -0
  45. package/dist/cjs/types/index.js +23 -0
  46. package/dist/cjs/types/passthrough.type.d.ts +15 -0
  47. package/dist/cjs/types/passthrough.type.js +2 -0
  48. package/dist/cjs/types/provider-id.enum.d.ts +8 -0
  49. package/dist/cjs/types/provider-id.enum.js +29 -0
  50. package/dist/cjs/types/provider.interface.d.ts +7 -0
  51. package/dist/cjs/types/provider.interface.js +2 -0
  52. package/dist/esm/base/base.connector.d.ts +44 -0
  53. package/dist/esm/base/base.connector.js +209 -0
  54. package/dist/esm/facades/chat.facade.d.ts +17 -0
  55. package/dist/esm/facades/chat.facade.js +59 -0
  56. package/dist/esm/facades/embeddings.facade.d.ts +23 -0
  57. package/dist/esm/facades/embeddings.facade.js +59 -0
  58. package/dist/esm/index.d.ts +15 -0
  59. package/dist/esm/index.js +9 -0
  60. package/dist/esm/package.json +1 -0
  61. package/dist/esm/providers/_shared/openai-compat.connector.d.ts +23 -0
  62. package/dist/esm/providers/_shared/openai-compat.connector.js +316 -0
  63. package/dist/esm/providers/_shared/openai-compat.embeddings.connector.d.ts +18 -0
  64. package/dist/esm/providers/_shared/openai-compat.embeddings.connector.js +101 -0
  65. package/dist/esm/providers/_shared/spec.d.ts +99 -0
  66. package/dist/esm/providers/_shared/spec.js +81 -0
  67. package/dist/esm/providers/anthropic/anthropic.config.d.ts +17 -0
  68. package/dist/esm/providers/anthropic/anthropic.config.js +1 -0
  69. package/dist/esm/providers/anthropic/anthropic.connector.d.ts +30 -0
  70. package/dist/esm/providers/anthropic/anthropic.connector.js +423 -0
  71. package/dist/esm/providers/anthropic/index.d.ts +2 -0
  72. package/dist/esm/providers/anthropic/index.js +1 -0
  73. package/dist/esm/providers/bedrock/bedrock.config.d.ts +18 -0
  74. package/dist/esm/providers/bedrock/bedrock.config.js +1 -0
  75. package/dist/esm/providers/bedrock/bedrock.connector.d.ts +61 -0
  76. package/dist/esm/providers/bedrock/bedrock.connector.js +474 -0
  77. package/dist/esm/providers/bedrock/index.d.ts +2 -0
  78. package/dist/esm/providers/bedrock/index.js +1 -0
  79. package/dist/esm/providers/gemini/gemini.config.d.ts +12 -0
  80. package/dist/esm/providers/gemini/gemini.config.js +1 -0
  81. package/dist/esm/providers/gemini/gemini.connector.d.ts +28 -0
  82. package/dist/esm/providers/gemini/gemini.connector.js +430 -0
  83. package/dist/esm/providers/gemini/index.d.ts +2 -0
  84. package/dist/esm/providers/gemini/index.js +1 -0
  85. package/dist/esm/types/chat.types.d.ts +115 -0
  86. package/dist/esm/types/chat.types.js +1 -0
  87. package/dist/esm/types/config-map.type.d.ts +29 -0
  88. package/dist/esm/types/config-map.type.js +1 -0
  89. package/dist/esm/types/embeddings.types.d.ts +29 -0
  90. package/dist/esm/types/embeddings.types.js +1 -0
  91. package/dist/esm/types/error.types.d.ts +20 -0
  92. package/dist/esm/types/error.types.js +18 -0
  93. package/dist/esm/types/index.d.ts +7 -0
  94. package/dist/esm/types/index.js +7 -0
  95. package/dist/esm/types/passthrough.type.d.ts +15 -0
  96. package/dist/esm/types/passthrough.type.js +1 -0
  97. package/dist/esm/types/provider-id.enum.d.ts +8 -0
  98. package/dist/esm/types/provider-id.enum.js +26 -0
  99. package/dist/esm/types/provider.interface.d.ts +7 -0
  100. package/dist/esm/types/provider.interface.js +1 -0
  101. package/package.json +73 -0
  102. package/src/providers/anthropic/README.md +64 -0
  103. package/src/providers/azure-openai/README.md +62 -0
  104. package/src/providers/bedrock/README.md +71 -0
  105. package/src/providers/cloudflare/README.md +62 -0
  106. package/src/providers/deepinfra/README.md +61 -0
  107. package/src/providers/deepseek/README.md +50 -0
  108. package/src/providers/fireworks/README.md +62 -0
  109. package/src/providers/gemini/README.md +67 -0
  110. package/src/providers/groq/README.md +50 -0
  111. package/src/providers/lmstudio/README.md +62 -0
  112. package/src/providers/mistral/README.md +61 -0
  113. package/src/providers/ollama/README.md +62 -0
  114. package/src/providers/openai/README.md +62 -0
  115. package/src/providers/openrouter/README.md +62 -0
  116. package/src/providers/perplexity/README.md +50 -0
  117. package/src/providers/together/README.md +61 -0
  118. package/src/providers/vllm/README.md +62 -0
  119. package/src/providers/xai/README.md +50 -0
@@ -0,0 +1,430 @@
1
+ import { appendPassthroughQuery, BaseConnector, parseSSEStream } from '../../base/base.connector.js';
2
+ import { ConnectorError } from '../../types/index.js';
3
+ const DEFAULT_BASE_URL = 'https://generativelanguage.googleapis.com/v1beta';
4
+ /**
5
+ * Native adapter for the Google Gemini (AI Studio) `generateContent` API.
6
+ * Gemini uses `contents[]`/`parts[]` (roles `user`/`model`), a top-level
7
+ * `systemInstruction`, `functionDeclarations`, and keys tool results by
8
+ * function NAME (not id). This connector translates the normalized surface to
9
+ * that shape and back, emitting the identical `ChatResult`/`ChatStreamDelta`/
10
+ * `ConnectorError`. Unlike Bedrock, Gemini streaming IS SSE
11
+ * (`:streamGenerateContent?alt=sse`), so streaming is truly incremental.
12
+ *
13
+ * Passthrough/raw (not emulated): Gemini-3 `thought_signature` round-trip and
14
+ * reasoning CoT output (`raw`), prompt caching (`cachedContent` via
15
+ * `_passthrough`), safety settings / video params (`_passthrough`).
16
+ */
17
+ export class GeminiConnector extends BaseConnector {
18
+ constructor(config) {
19
+ super(config.fetch);
20
+ this.id = 'gemini';
21
+ this.config = config;
22
+ this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
23
+ }
24
+ async complete(input) {
25
+ const url = appendPassthroughQuery(`${this.baseUrl}/models/${encodeURIComponent(input.model)}:generateContent`, input._passthrough?.query);
26
+ const response = await this.sendPostJson(url, this.buildRequest(input), this.authHeaders(input), input.signal);
27
+ if (!response.ok) {
28
+ const errBody = await response.json().catch(() => null);
29
+ throw this.mapVendorError(response.status, errBody, response.headers);
30
+ }
31
+ const json = this.requireDecodedBody((await response.json().catch(() => null)), response.status);
32
+ return this.parseResult(json, input.model);
33
+ }
34
+ async *stream(input) {
35
+ const url = appendPassthroughQuery(`${this.baseUrl}/models/${encodeURIComponent(input.model)}:streamGenerateContent?alt=sse`, input._passthrough?.query);
36
+ const response = await this.invokeFetch(url, {
37
+ method: 'POST',
38
+ headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream', ...this.authHeaders(input) },
39
+ body: JSON.stringify(this.buildRequest(input)),
40
+ signal: input.signal,
41
+ });
42
+ if (!response.ok) {
43
+ const errBody = await response.json().catch(() => null);
44
+ throw this.mapVendorError(response.status, errBody, response.headers);
45
+ }
46
+ let sawFunctionCall = false;
47
+ // Gemini yields each functionCall part in its own chunk, so assign a
48
+ // monotonically-incrementing tool-call index (mirrors Anthropic's
49
+ // `nextToolIndex++` and the non-stream `idx`) — a hardcoded 0 would make a
50
+ // consumer keyed on `index` merge parallel/multiple calls into one.
51
+ let nextToolIndex = 0;
52
+ // Gemini's `usageMetadata` is cumulative and can appear on more than one
53
+ // chunk. Buffer the latest and emit a SINGLE terminal `usage` delta at
54
+ // stream end (matching the OpenAI-compat `include_usage` trailer and
55
+ // Anthropic's single `message_delta`) so a consumer summing `delta.usage`
56
+ // across the stream never double-counts.
57
+ let latestUsage;
58
+ let latestUsageChunk;
59
+ for await (const raw of parseSSEStream(response)) {
60
+ const chunk = raw;
61
+ // A provider error emitted mid-stream arrives as an `{"error": …}` frame —
62
+ // throw instead of silently ending the stream with no finishReason.
63
+ if (chunk.error != null) {
64
+ throw this.mapVendorError(errorFrameStatus(chunk.error), chunk, new Headers());
65
+ }
66
+ // A prompt-level safety block on a stream arrives as a chunk with no
67
+ // candidate and only promptFeedback — emit a content_filter finish so the
68
+ // consumer sees the block instead of a silent, empty end.
69
+ if (chunk.candidates?.[0] == null && chunk.promptFeedback?.blockReason) {
70
+ yield { finishReason: 'content_filter', raw: chunk };
71
+ continue;
72
+ }
73
+ const cand = chunk.candidates?.[0];
74
+ for (const p of cand?.content?.parts ?? []) {
75
+ if (p.thought === true) {
76
+ continue; // reasoning CoT, not answer content (available via raw)
77
+ }
78
+ else if (typeof p.text === 'string' && p.text.length > 0) {
79
+ yield { contentDelta: p.text, raw: chunk };
80
+ }
81
+ else if (p.functionCall) {
82
+ sawFunctionCall = true;
83
+ yield {
84
+ toolCallDelta: {
85
+ index: nextToolIndex++,
86
+ id: p.functionCall.id ?? `call_${p.functionCall.name ?? ''}`,
87
+ functionName: p.functionCall.name,
88
+ argumentsDelta: JSON.stringify(p.functionCall.args ?? {}),
89
+ },
90
+ raw: chunk,
91
+ };
92
+ }
93
+ }
94
+ if (chunk.usageMetadata) {
95
+ latestUsage = chunk.usageMetadata;
96
+ latestUsageChunk = chunk;
97
+ }
98
+ if (cand?.finishReason) {
99
+ yield {
100
+ finishReason: sawFunctionCall ? 'tool_calls' : mapFinishReason(cand.finishReason),
101
+ raw: chunk,
102
+ };
103
+ }
104
+ }
105
+ if (latestUsage)
106
+ yield { usage: mapUsage(latestUsage), raw: latestUsageChunk };
107
+ }
108
+ authHeaders(input) {
109
+ return {
110
+ 'x-goog-api-key': this.config.apiKey,
111
+ ...this.config.headers,
112
+ ...input._passthrough?.headers,
113
+ };
114
+ }
115
+ buildRequest(input) {
116
+ const { systemInstruction, contents } = splitMessages(input.messages);
117
+ const body = { contents };
118
+ if (systemInstruction)
119
+ body.systemInstruction = systemInstruction;
120
+ if (input.tools && input.tools.length > 0) {
121
+ body.tools = [
122
+ {
123
+ functionDeclarations: input.tools.map((t) => ({
124
+ name: t.function.name,
125
+ ...(t.function.description ? { description: t.function.description } : {}),
126
+ ...(t.function.parameters ? { parameters: t.function.parameters } : {}),
127
+ })),
128
+ },
129
+ ];
130
+ const fc = mapFunctionCallingConfig(input.toolChoice);
131
+ if (fc)
132
+ body.toolConfig = { functionCallingConfig: fc };
133
+ }
134
+ const generationConfig = {};
135
+ const maxTok = input.maxOutputTokens ?? this.config.defaultMaxTokens;
136
+ if (maxTok !== undefined)
137
+ generationConfig.maxOutputTokens = maxTok;
138
+ if (input.temperature !== undefined)
139
+ generationConfig.temperature = input.temperature;
140
+ if (input.topP !== undefined)
141
+ generationConfig.topP = input.topP;
142
+ if (input.stop !== undefined) {
143
+ generationConfig.stopSequences = Array.isArray(input.stop) ? input.stop : [input.stop];
144
+ }
145
+ applyResponseFormat(generationConfig, input.responseFormat);
146
+ if (input.reasoning) {
147
+ generationConfig.thinkingConfig = { thinkingBudget: effortBudget(input.reasoning.effort) };
148
+ }
149
+ if (Object.keys(generationConfig).length > 0)
150
+ body.generationConfig = generationConfig;
151
+ if (input._passthrough?.body)
152
+ Object.assign(body, input._passthrough.body);
153
+ return body;
154
+ }
155
+ parseResult(json, requestedModel) {
156
+ const cand = json?.candidates?.[0];
157
+ const parts = cand?.content?.parts ?? [];
158
+ let text = '';
159
+ const toolCalls = [];
160
+ let idx = 0;
161
+ for (const p of parts) {
162
+ if (p.thought === true) {
163
+ continue; // reasoning CoT, not answer content (available via raw)
164
+ }
165
+ else if (typeof p.text === 'string') {
166
+ text += p.text;
167
+ }
168
+ else if (p.functionCall) {
169
+ toolCalls.push({
170
+ id: p.functionCall.id ?? `call_${idx}`,
171
+ type: 'function',
172
+ function: {
173
+ name: p.functionCall.name ?? '',
174
+ arguments: JSON.stringify(p.functionCall.args ?? {}),
175
+ },
176
+ });
177
+ idx++;
178
+ }
179
+ }
180
+ // No candidate came back: a prompt-level safety block reports only
181
+ // `promptFeedback.blockReason`. Surface it as content_filter rather than a
182
+ // silent empty completion with finishReason unknown.
183
+ const finishReason = toolCalls.length > 0
184
+ ? 'tool_calls'
185
+ : cand?.finishReason == null && json?.promptFeedback?.blockReason
186
+ ? 'content_filter'
187
+ : mapFinishReason(cand?.finishReason);
188
+ return {
189
+ message: {
190
+ role: 'assistant',
191
+ content: text.length > 0 ? text : null,
192
+ ...(toolCalls.length > 0 ? { toolCalls } : {}),
193
+ },
194
+ finishReason,
195
+ usage: mapUsage(json?.usageMetadata),
196
+ model: json?.modelVersion ?? requestedModel,
197
+ raw: json,
198
+ };
199
+ }
200
+ mapVendorError(status, body, headers) {
201
+ const message = extractGeminiError(body) ?? `HTTP ${status}`;
202
+ const providerCode = mapStatusToProviderCode(status, message);
203
+ const cause = { raw: body ?? null };
204
+ const retryAfter = headers.get('retry-after');
205
+ if (retryAfter != null) {
206
+ cause.retryAfter = retryAfter;
207
+ const secs = Number(retryAfter);
208
+ if (Number.isFinite(secs))
209
+ cause.retryAfterSeconds = secs;
210
+ }
211
+ // Gemini has no rate-limit headers — retry timing lives in the 429 body's RetryInfo.
212
+ if (cause.retryAfterSeconds === undefined) {
213
+ const bodySecs = extractRetryDelaySeconds(body);
214
+ if (bodySecs != null)
215
+ cause.retryAfterSeconds = bodySecs;
216
+ }
217
+ return new ConnectorError({
218
+ message,
219
+ statusCode: status,
220
+ providerCode,
221
+ providerMessage: message,
222
+ cause,
223
+ });
224
+ }
225
+ }
226
+ // ---------------------------------------------------------------------------
227
+ // Mappers
228
+ // ---------------------------------------------------------------------------
229
+ function splitMessages(messages) {
230
+ const nameById = new Map();
231
+ for (const m of messages) {
232
+ if (m.role === 'assistant' && m.toolCalls) {
233
+ for (const tc of m.toolCalls)
234
+ nameById.set(tc.id, tc.function.name);
235
+ }
236
+ }
237
+ let systemText = '';
238
+ const contents = [];
239
+ for (const m of messages) {
240
+ if (m.role === 'system') {
241
+ const t = contentToText(m.content);
242
+ systemText = systemText ? `${systemText}\n\n${t}` : t;
243
+ continue;
244
+ }
245
+ if (m.role === 'tool') {
246
+ const name = m.name ?? (m.toolCallId ? nameById.get(m.toolCallId) : undefined) ?? m.toolCallId ?? 'tool';
247
+ const part = {
248
+ functionResponse: { name, response: { result: parseJsonOrString(contentToText(m.content)) } },
249
+ };
250
+ const last = contents[contents.length - 1];
251
+ if (last && last.role === 'user' && isFunctionResponseParts(last.parts)) {
252
+ last.parts.push(part);
253
+ }
254
+ else {
255
+ contents.push({ role: 'user', parts: [part] });
256
+ }
257
+ continue;
258
+ }
259
+ contents.push({ role: m.role === 'assistant' ? 'model' : 'user', parts: mapParts(m) });
260
+ }
261
+ return {
262
+ systemInstruction: systemText ? { parts: [{ text: systemText }] } : undefined,
263
+ contents,
264
+ };
265
+ }
266
+ function contentToText(content) {
267
+ if (content === null)
268
+ return '';
269
+ if (typeof content === 'string')
270
+ return content;
271
+ return content
272
+ .filter((p) => p.type === 'text')
273
+ .map((p) => p.text)
274
+ .join('');
275
+ }
276
+ function mapParts(m) {
277
+ const parts = [];
278
+ if (typeof m.content === 'string') {
279
+ if (m.content.length > 0)
280
+ parts.push({ text: m.content });
281
+ }
282
+ else if (Array.isArray(m.content)) {
283
+ for (const p of m.content) {
284
+ parts.push(p.type === 'text'
285
+ ? { text: p.text }
286
+ : { inlineData: { mimeType: p.mediaType, data: p.base64 } });
287
+ }
288
+ }
289
+ if (m.role === 'assistant' && m.toolCalls) {
290
+ for (const tc of m.toolCalls) {
291
+ parts.push({ functionCall: { name: tc.function.name, args: safeJsonParse(tc.function.arguments) } });
292
+ }
293
+ }
294
+ if (parts.length === 0)
295
+ parts.push({ text: '' });
296
+ return parts;
297
+ }
298
+ function isFunctionResponseParts(parts) {
299
+ return (parts.length > 0 &&
300
+ typeof parts[0] === 'object' &&
301
+ parts[0] !== null &&
302
+ 'functionResponse' in parts[0]);
303
+ }
304
+ function mapFunctionCallingConfig(tc) {
305
+ if (tc === undefined)
306
+ return null;
307
+ if (tc === 'auto')
308
+ return { mode: 'AUTO' };
309
+ if (tc === 'required')
310
+ return { mode: 'ANY' };
311
+ if (tc === 'none')
312
+ return { mode: 'NONE' };
313
+ if (typeof tc === 'object' && tc.type === 'function') {
314
+ return { mode: 'ANY', allowedFunctionNames: [tc.function.name] };
315
+ }
316
+ return null;
317
+ }
318
+ function applyResponseFormat(generationConfig, rf) {
319
+ if (!rf)
320
+ return;
321
+ if (rf.type === 'json_object') {
322
+ generationConfig.responseMimeType = 'application/json';
323
+ }
324
+ else if (rf.type === 'json_schema') {
325
+ generationConfig.responseMimeType = 'application/json';
326
+ generationConfig.responseSchema = rf.jsonSchema.schema;
327
+ }
328
+ }
329
+ function mapFinishReason(fr) {
330
+ switch (fr) {
331
+ case 'STOP':
332
+ return 'stop';
333
+ case 'MAX_TOKENS':
334
+ return 'length';
335
+ case 'SAFETY':
336
+ case 'RECITATION':
337
+ case 'BLOCKLIST':
338
+ case 'PROHIBITED_CONTENT':
339
+ case 'SPII':
340
+ return 'content_filter';
341
+ default:
342
+ return 'unknown';
343
+ }
344
+ }
345
+ function mapUsage(u) {
346
+ const inputTokens = u?.promptTokenCount ?? 0;
347
+ const outputTokens = u?.candidatesTokenCount ?? 0;
348
+ return { inputTokens, outputTokens, totalTokens: u?.totalTokenCount ?? inputTokens + outputTokens };
349
+ }
350
+ function effortBudget(effort) {
351
+ switch (effort) {
352
+ case 'low':
353
+ return 1024;
354
+ case 'medium':
355
+ return 8192;
356
+ case 'high':
357
+ return 24576;
358
+ }
359
+ }
360
+ function safeJsonParse(s) {
361
+ try {
362
+ return JSON.parse(s);
363
+ }
364
+ catch {
365
+ return {};
366
+ }
367
+ }
368
+ function parseJsonOrString(s) {
369
+ try {
370
+ return JSON.parse(s);
371
+ }
372
+ catch {
373
+ return s;
374
+ }
375
+ }
376
+ function extractGeminiError(body) {
377
+ if (typeof body === 'string')
378
+ return body;
379
+ if (!body || typeof body !== 'object')
380
+ return null;
381
+ const b = body;
382
+ if (b.error && typeof b.error === 'object') {
383
+ const em = b.error.message;
384
+ if (typeof em === 'string')
385
+ return em;
386
+ }
387
+ if (typeof b.message === 'string')
388
+ return b.message;
389
+ return null;
390
+ }
391
+ function extractRetryDelaySeconds(body) {
392
+ if (!body || typeof body !== 'object')
393
+ return null;
394
+ const err = body.error;
395
+ if (!err || typeof err !== 'object')
396
+ return null;
397
+ const details = err.details;
398
+ if (!Array.isArray(details))
399
+ return null;
400
+ for (const d of details) {
401
+ const rd = d?.retryDelay;
402
+ if (typeof rd === 'string') {
403
+ const match = rd.match(/^(\d+(?:\.\d+)?)s$/);
404
+ if (match)
405
+ return Math.round(Number(match[1]));
406
+ }
407
+ }
408
+ return null;
409
+ }
410
+ /** HTTP status for a mid-stream Gemini `{"error": …}` frame — Gemini's `error.code` IS the HTTP status. */
411
+ function errorFrameStatus(error) {
412
+ const code = error?.code;
413
+ return typeof code === 'number' && code >= 400 && code <= 599 ? code : 500;
414
+ }
415
+ function mapStatusToProviderCode(status, message) {
416
+ if (status === 401 || status === 403)
417
+ return 'auth_failed';
418
+ if (status === 429)
419
+ return 'rate_limited';
420
+ if (status >= 500)
421
+ return 'provider_unavailable';
422
+ if (status === 400 || status === 404 || status === 422) {
423
+ const msg = message.toLowerCase();
424
+ if (msg.includes('too long') || msg.includes('context') || msg.includes('token count')) {
425
+ return 'context_length_exceeded';
426
+ }
427
+ return 'invalid_request';
428
+ }
429
+ return 'unknown';
430
+ }
@@ -0,0 +1,2 @@
1
+ export { GeminiConnector } from './gemini.connector.js';
2
+ export type { GeminiConfig } from './gemini.config.js';
@@ -0,0 +1 @@
1
+ export { GeminiConnector } from './gemini.connector.js';
@@ -0,0 +1,115 @@
1
+ import type { Passthrough } from './passthrough.type.js';
2
+ export type ChatRole = 'system' | 'user' | 'assistant' | 'tool';
3
+ export interface TextPart {
4
+ type: 'text';
5
+ text: string;
6
+ }
7
+ export interface ImagePart {
8
+ type: 'image';
9
+ /** Raw base64 payload (no `data:` prefix). Canonical image input; the connector encodes for the vendor wire. */
10
+ base64: string;
11
+ /** e.g. `image/png`, `image/jpeg`. */
12
+ mediaType: string;
13
+ }
14
+ export type ContentPart = TextPart | ImagePart;
15
+ export interface ToolCall {
16
+ id: string;
17
+ type: 'function';
18
+ function: {
19
+ name: string;
20
+ /** JSON-encoded arguments string. */
21
+ arguments: string;
22
+ };
23
+ }
24
+ export interface ChatMessage {
25
+ role: ChatRole;
26
+ content: string | ContentPart[] | null;
27
+ /** Present on assistant turns that invoked tools. */
28
+ toolCalls?: ToolCall[];
29
+ /** Required on `role: 'tool'` messages — the id of the tool call being answered. */
30
+ toolCallId?: string;
31
+ /** Optional author / tool name. */
32
+ name?: string;
33
+ }
34
+ export interface ToolDefinition {
35
+ type: 'function';
36
+ function: {
37
+ name: string;
38
+ description?: string;
39
+ /** JSON Schema object describing the arguments. */
40
+ parameters?: Record<string, unknown>;
41
+ };
42
+ }
43
+ export type ToolChoice = 'auto' | 'none' | 'required' | {
44
+ type: 'function';
45
+ function: {
46
+ name: string;
47
+ };
48
+ };
49
+ export type ResponseFormat = {
50
+ type: 'text';
51
+ } | {
52
+ type: 'json_object';
53
+ } | {
54
+ type: 'json_schema';
55
+ jsonSchema: {
56
+ name: string;
57
+ schema: Record<string, unknown>;
58
+ };
59
+ };
60
+ /** Minimal normalized reasoning control (D2). CoT output is NOT normalized — read it from `ChatResult.raw`. */
61
+ export interface ReasoningOptions {
62
+ effort: 'low' | 'medium' | 'high';
63
+ }
64
+ export interface ChatInput {
65
+ model: string;
66
+ messages: ChatMessage[];
67
+ tools?: ToolDefinition[];
68
+ toolChoice?: ToolChoice;
69
+ responseFormat?: ResponseFormat;
70
+ temperature?: number;
71
+ topP?: number;
72
+ maxOutputTokens?: number;
73
+ stop?: string | string[];
74
+ reasoning?: ReasoningOptions;
75
+ /** Optional cancellation signal, forwarded to the underlying `fetch` for both `complete()` and `stream()`. */
76
+ signal?: AbortSignal;
77
+ /** Escape hatch for sub-baseline / provider-specific request fields. Never emulated. */
78
+ _passthrough?: Passthrough;
79
+ }
80
+ export type FinishReason = 'stop' | 'length' | 'tool_calls' | 'content_filter' | 'unknown';
81
+ export interface Usage {
82
+ inputTokens: number;
83
+ outputTokens: number;
84
+ totalTokens: number;
85
+ }
86
+ export interface ChatResult {
87
+ message: {
88
+ role: 'assistant';
89
+ content: string | null;
90
+ toolCalls?: ToolCall[];
91
+ };
92
+ finishReason: FinishReason;
93
+ usage: Usage;
94
+ /** The model id the provider reported serving (may differ from the requested id). */
95
+ model: string;
96
+ /** Verbatim vendor response body — reasoning CoT, cached-token counts, cost, etc. live here. */
97
+ raw: unknown;
98
+ }
99
+ export interface ChatStreamDelta {
100
+ /** Incremental assistant text. */
101
+ contentDelta?: string;
102
+ /** Incremental tool-call fragment (concatenate `argumentsDelta` per `index`). */
103
+ toolCallDelta?: {
104
+ index: number;
105
+ id?: string;
106
+ functionName?: string;
107
+ argumentsDelta?: string;
108
+ };
109
+ /** Present on the terminal chunk. */
110
+ finishReason?: FinishReason;
111
+ /** Present on the final usage chunk, when the provider reports it. */
112
+ usage?: Usage;
113
+ /** Verbatim vendor chunk. */
114
+ raw?: unknown;
115
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ import type { LlmProviderId } from './provider-id.enum.js';
2
+ import type { AnthropicConfig } from '../providers/anthropic/anthropic.config.js';
3
+ import type { BedrockConfig } from '../providers/bedrock/bedrock.config.js';
4
+ import type { GeminiConfig } from '../providers/gemini/gemini.config.js';
5
+ export interface OpenAICompatConfig {
6
+ /** Bearer API key. Optional for self-host providers (vLLM / Ollama / LM Studio) that run without auth. */
7
+ apiKey?: string;
8
+ /**
9
+ * Override the provider's default base URL (e.g. `https://api.openai.com/v1`).
10
+ * Required for providers with no fixed public default: `azure-openai`,
11
+ * `cloudflare`, and self-host providers on a non-default host.
12
+ */
13
+ baseUrl?: string;
14
+ /** Bring-your-own fetch. Defaults to `globalThis.fetch`. */
15
+ fetch?: typeof fetch;
16
+ /** Extra headers merged onto every request (e.g. OpenRouter `HTTP-Referer` / `X-Title`). */
17
+ headers?: Record<string, string>;
18
+ }
19
+ /**
20
+ * Per-provider config type. The 15 first-class providers share
21
+ * `OpenAICompatConfig`; native-adapter providers carry their own config.
22
+ */
23
+ export type ProviderConfigMap = {
24
+ [K in Exclude<LlmProviderId, 'anthropic' | 'bedrock' | 'gemini'>]: OpenAICompatConfig;
25
+ } & {
26
+ anthropic: AnthropicConfig;
27
+ bedrock: BedrockConfig;
28
+ gemini: GeminiConfig;
29
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ import type { Passthrough } from './passthrough.type.js';
2
+ export interface EmbeddingsInput {
3
+ model: string;
4
+ /** A single string or a batch of strings. */
5
+ input: string | string[];
6
+ /** Optional output dimensionality (providers that support `dimensions` / Matryoshka). */
7
+ dimensions?: number;
8
+ /** Optional cancellation signal, forwarded to the underlying `fetch`. */
9
+ signal?: AbortSignal;
10
+ /** Escape hatch for provider-specific request fields (e.g. `input_type`). Never emulated. */
11
+ _passthrough?: Passthrough;
12
+ }
13
+ export interface EmbeddingsUsage {
14
+ inputTokens: number;
15
+ totalTokens: number;
16
+ }
17
+ export interface EmbeddingsResult {
18
+ /** One float vector per input, in input order. */
19
+ embeddings: number[][];
20
+ usage: EmbeddingsUsage;
21
+ /** The model id the provider reported serving. */
22
+ model: string;
23
+ /** Verbatim vendor response body. */
24
+ raw: unknown;
25
+ }
26
+ export interface IEmbeddingsConnector {
27
+ readonly id: string;
28
+ create(input: EmbeddingsInput): Promise<EmbeddingsResult>;
29
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ export type ProviderCode = 'rate_limited' | 'auth_failed' | 'provider_unavailable' | 'invalid_request' | 'context_length_exceeded' | 'content_filtered' | 'unknown';
2
+ /**
3
+ * Canonical error thrown by every connector on vendor HTTP non-2xx (and on
4
+ * network/abort failures). `providerCode` is narrowed to the `ProviderCode`
5
+ * union; the verbatim vendor error payload is preserved on `cause.raw`, and any
6
+ * retry hint on `cause.retryAfter` / `cause.retryAfterSeconds`. There is no
7
+ * top-level `retryAfterSeconds` field (umbrella invariant).
8
+ */
9
+ export declare class ConnectorError extends Error {
10
+ readonly statusCode: number | null;
11
+ readonly providerCode?: ProviderCode;
12
+ readonly providerMessage: string | null;
13
+ constructor(options: {
14
+ message?: string;
15
+ statusCode: number | null;
16
+ providerCode?: ProviderCode;
17
+ providerMessage?: string | null;
18
+ cause?: unknown;
19
+ });
20
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Canonical error thrown by every connector on vendor HTTP non-2xx (and on
3
+ * network/abort failures). `providerCode` is narrowed to the `ProviderCode`
4
+ * union; the verbatim vendor error payload is preserved on `cause.raw`, and any
5
+ * retry hint on `cause.retryAfter` / `cause.retryAfterSeconds`. There is no
6
+ * top-level `retryAfterSeconds` field (umbrella invariant).
7
+ */
8
+ export class ConnectorError extends Error {
9
+ constructor(options) {
10
+ super(options.message ?? options.providerMessage ?? 'Connector error', {
11
+ cause: options.cause,
12
+ });
13
+ this.name = 'ConnectorError';
14
+ this.statusCode = options.statusCode;
15
+ this.providerCode = options.providerCode;
16
+ this.providerMessage = options.providerMessage ?? null;
17
+ }
18
+ }
@@ -0,0 +1,7 @@
1
+ export * from './error.types.js';
2
+ export * from './passthrough.type.js';
3
+ export * from './chat.types.js';
4
+ export * from './embeddings.types.js';
5
+ export * from './provider-id.enum.js';
6
+ export * from './config-map.type.js';
7
+ export * from './provider.interface.js';
@@ -0,0 +1,7 @@
1
+ export * from './error.types.js';
2
+ export * from './passthrough.type.js';
3
+ export * from './chat.types.js';
4
+ export * from './embeddings.types.js';
5
+ export * from './provider-id.enum.js';
6
+ export * from './config-map.type.js';
7
+ export * from './provider.interface.js';