@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,474 @@
1
+ import { createHash, createHmac } from 'node:crypto';
2
+ import { BaseConnector } from '../../base/base.connector.js';
3
+ import { ConnectorError } from '../../types/index.js';
4
+ const DEFAULT_MAX_TOKENS = 4096;
5
+ const SERVICE = 'bedrock';
6
+ /**
7
+ * Native adapter for AWS Bedrock's unified **Converse API**
8
+ * (`POST /model/{modelId}/converse`). Requests are signed with hand-rolled AWS
9
+ * SigV4 on `node:crypto` (zero third-party deps — same approach as the
10
+ * notifications SES/SNS connectors). Converse is structurally different from
11
+ * OpenAI (top-level `system[]`, typed content blocks, `inferenceConfig`,
12
+ * `toolConfig`, tool results in a user turn), so this is a full translation
13
+ * layer emitting the identical normalized `ChatResult` / `ChatStreamDelta` /
14
+ * `ConnectorError`.
15
+ *
16
+ * v1 streaming NOTE: Bedrock's `converse-stream` uses AWS's binary
17
+ * event-stream framing (`application/vnd.amazon.eventstream`), not SSE. v1 does
18
+ * NOT parse that binary protocol — `stream()` falls back to a single
19
+ * non-incremental Converse call and yields the full result as deltas. True
20
+ * incremental streaming (a binary event-stream parser) is a documented
21
+ * follow-up.
22
+ *
23
+ * Not normalized here (passthrough/raw, never emulated): reasoning control
24
+ * (model-dependent on Bedrock — pass `additionalModelRequestFields` via
25
+ * `_passthrough.body`), `responseFormat`, prompt caching (`cachePoint` blocks
26
+ * via `_passthrough`), and `top_k`.
27
+ */
28
+ export class BedrockConnector extends BaseConnector {
29
+ constructor(config) {
30
+ super(config.fetch);
31
+ this.id = 'bedrock';
32
+ this.config = config;
33
+ this.origin = (config.baseUrl ?? `https://bedrock-runtime.${config.region}.amazonaws.com`).replace(/\/+$/, '');
34
+ try {
35
+ this.host = new URL(this.origin).host;
36
+ }
37
+ catch {
38
+ throw new ConnectorError({
39
+ message: `Bedrock: invalid \`baseUrl\`/\`region\` — could not derive a host from '${this.origin}'`,
40
+ statusCode: null,
41
+ providerCode: 'invalid_request',
42
+ });
43
+ }
44
+ }
45
+ async complete(input) {
46
+ const serialized = JSON.stringify(this.buildRequest(input));
47
+ // The wire path is single-encoded, but the SigV4 canonical URI must be
48
+ // DOUBLE-encoded: every non-S3 AWS service URI-encodes the received path a
49
+ // second time before recomputing the signature, so a model id containing `:`
50
+ // (all on-demand IDs like `…-v2:0`, cross-region profiles `us.…-v2:0`, ARNs)
51
+ // must be signed as `%253A` while `%3A` travels on the wire — otherwise the
52
+ // server's signature never matches ours → 403 SignatureDoesNotMatch. For a
53
+ // colon-free id the two are identical, so nothing regresses.
54
+ const wirePath = `/model/${encodeURIComponent(input.model)}/converse`;
55
+ const canonicalPath = `/model/${encodeURIComponent(encodeURIComponent(input.model))}/converse`;
56
+ // `_passthrough.query` must be folded into the SigV4 canonical query BEFORE
57
+ // signing (the query string is part of the signed canonical request); the
58
+ // same canonical form then travels on the wire, so the server's recomputed
59
+ // signature matches. Appending it post-signing would 403 SignatureDoesNotMatch.
60
+ const canonicalQuery = buildCanonicalQuery(input._passthrough?.query);
61
+ const headers = this.buildSignedHeaders('POST', canonicalPath, serialized, input, canonicalQuery);
62
+ const wireUrl = `${this.origin}${wirePath}${canonicalQuery ? `?${canonicalQuery}` : ''}`;
63
+ const response = await this.invokeFetch(wireUrl, {
64
+ method: 'POST',
65
+ headers,
66
+ body: serialized,
67
+ signal: input.signal,
68
+ });
69
+ if (!response.ok) {
70
+ const errBody = await response.json().catch(() => null);
71
+ throw this.mapVendorError(response.status, errBody, response.headers);
72
+ }
73
+ const json = this.requireDecodedBody((await response.json().catch(() => null)), response.status);
74
+ return this.parseResult(json, input.model);
75
+ }
76
+ /**
77
+ * v1 fallback: non-incremental. See the class NOTE — Bedrock streaming is a
78
+ * binary event-stream, not SSE; v1 issues one Converse call and yields the
79
+ * result as a content delta + tool-call deltas + a terminal finish/usage delta.
80
+ */
81
+ async *stream(input) {
82
+ const result = await this.complete(input);
83
+ if (result.message.content)
84
+ yield { contentDelta: result.message.content };
85
+ if (result.message.toolCalls) {
86
+ let index = 0;
87
+ for (const tc of result.message.toolCalls) {
88
+ yield {
89
+ toolCallDelta: {
90
+ index,
91
+ id: tc.id,
92
+ functionName: tc.function.name,
93
+ argumentsDelta: tc.function.arguments,
94
+ },
95
+ };
96
+ index++;
97
+ }
98
+ }
99
+ yield { finishReason: result.finishReason, usage: result.usage, raw: result.raw };
100
+ }
101
+ buildSignedHeaders(method, canonicalPath, serializedBody, input, canonicalQuery) {
102
+ const extra = {
103
+ 'Content-Type': 'application/json',
104
+ ...this.config.headers,
105
+ ...input._passthrough?.headers,
106
+ };
107
+ const signed = signAwsRequest({
108
+ method,
109
+ path: canonicalPath,
110
+ service: SERVICE,
111
+ region: this.config.region,
112
+ host: this.host,
113
+ accessKeyId: this.config.accessKeyId,
114
+ secretAccessKey: this.config.secretAccessKey,
115
+ sessionToken: this.config.sessionToken,
116
+ serializedBody,
117
+ additionalSignedHeaders: extra,
118
+ isoTimestamp: isoBasicTimestamp(),
119
+ canonicalQuery,
120
+ });
121
+ return { ...extra, ...signed };
122
+ }
123
+ buildRequest(input) {
124
+ const { system, messages } = splitMessages(input.messages);
125
+ const body = { messages };
126
+ if (system.length > 0)
127
+ body.system = system;
128
+ const inferenceConfig = {
129
+ maxTokens: input.maxOutputTokens ?? this.config.defaultMaxTokens ?? DEFAULT_MAX_TOKENS,
130
+ };
131
+ if (input.temperature !== undefined)
132
+ inferenceConfig.temperature = input.temperature;
133
+ if (input.topP !== undefined)
134
+ inferenceConfig.topP = input.topP;
135
+ if (input.stop !== undefined) {
136
+ inferenceConfig.stopSequences = Array.isArray(input.stop) ? input.stop : [input.stop];
137
+ }
138
+ body.inferenceConfig = inferenceConfig;
139
+ if (input.tools && input.tools.length > 0) {
140
+ const toolConfig = {
141
+ tools: input.tools.map((t) => ({
142
+ toolSpec: {
143
+ name: t.function.name,
144
+ ...(t.function.description ? { description: t.function.description } : {}),
145
+ inputSchema: { json: t.function.parameters ?? { type: 'object', properties: {} } },
146
+ },
147
+ })),
148
+ };
149
+ const tc = mapToolChoice(input.toolChoice);
150
+ if (tc)
151
+ toolConfig.toolChoice = tc;
152
+ body.toolConfig = toolConfig;
153
+ }
154
+ // reasoning control is model-dependent on Bedrock → not auto-mapped in v1;
155
+ // pass `additionalModelRequestFields` via `_passthrough.body`.
156
+ if (input._passthrough?.body)
157
+ Object.assign(body, input._passthrough.body);
158
+ return body;
159
+ }
160
+ parseResult(json, requestedModel) {
161
+ const blocks = json?.output?.message?.content ?? [];
162
+ let text = '';
163
+ const toolCalls = [];
164
+ for (const b of blocks) {
165
+ if (typeof b.text === 'string') {
166
+ text += b.text;
167
+ }
168
+ else if (b.toolUse) {
169
+ toolCalls.push({
170
+ id: b.toolUse.toolUseId ?? '',
171
+ type: 'function',
172
+ function: { name: b.toolUse.name ?? '', arguments: JSON.stringify(b.toolUse.input ?? {}) },
173
+ });
174
+ }
175
+ }
176
+ const inputTokens = json?.usage?.inputTokens ?? 0;
177
+ const outputTokens = json?.usage?.outputTokens ?? 0;
178
+ return {
179
+ message: {
180
+ role: 'assistant',
181
+ content: text.length > 0 ? text : null,
182
+ ...(toolCalls.length > 0 ? { toolCalls } : {}),
183
+ },
184
+ finishReason: mapStopReason(json?.stopReason),
185
+ usage: {
186
+ inputTokens,
187
+ outputTokens,
188
+ totalTokens: json?.usage?.totalTokens ?? inputTokens + outputTokens,
189
+ },
190
+ model: requestedModel,
191
+ raw: json,
192
+ };
193
+ }
194
+ mapVendorError(status, body, headers) {
195
+ const message = extractBedrockError(body) ?? `HTTP ${status}`;
196
+ const providerCode = mapStatusToProviderCode(status, message);
197
+ const retryAfter = headers.get('retry-after');
198
+ const cause = { raw: body ?? null };
199
+ if (retryAfter != null) {
200
+ cause.retryAfter = retryAfter;
201
+ const secs = parseRetryAfterSeconds(retryAfter);
202
+ if (secs != null)
203
+ cause.retryAfterSeconds = secs;
204
+ }
205
+ return new ConnectorError({
206
+ message,
207
+ statusCode: status,
208
+ providerCode,
209
+ providerMessage: message,
210
+ cause,
211
+ });
212
+ }
213
+ }
214
+ // ---------------------------------------------------------------------------
215
+ // Hand-rolled AWS SigV4 (node:crypto; adapted from the notifications SES signer)
216
+ // ---------------------------------------------------------------------------
217
+ const UNSIGNABLE_HEADERS = new Set([
218
+ 'authorization',
219
+ 'cache-control',
220
+ 'connection',
221
+ 'content-length',
222
+ 'expect',
223
+ 'from',
224
+ 'keep-alive',
225
+ 'max-forwards',
226
+ 'pragma',
227
+ 'proxy-authenticate',
228
+ 'proxy-authorization',
229
+ 'referer',
230
+ 'te',
231
+ 'trailer',
232
+ 'transfer-encoding',
233
+ 'upgrade',
234
+ 'user-agent',
235
+ 'x-amzn-trace-id',
236
+ ]);
237
+ /**
238
+ * Hand-rolled AWS SigV4 signer (exported for white-box testing of the
239
+ * canonical-path encoding; NOT re-exported from the package barrel).
240
+ */
241
+ export function signAwsRequest(opts) {
242
+ const canonicalQuery = opts.canonicalQuery ?? '';
243
+ const dateStamp = opts.isoTimestamp.slice(0, 8);
244
+ const hashedPayload = createHash('sha256').update(opts.serializedBody).digest('hex');
245
+ const canonicalHeaders = {
246
+ ...opts.additionalSignedHeaders,
247
+ Host: opts.host,
248
+ 'X-Amz-Date': opts.isoTimestamp,
249
+ 'X-Amz-Content-Sha256': hashedPayload,
250
+ };
251
+ if (opts.sessionToken)
252
+ canonicalHeaders['X-Amz-Security-Token'] = opts.sessionToken;
253
+ const normalized = {};
254
+ for (const [name, value] of Object.entries(canonicalHeaders)) {
255
+ const lowerName = name.toLowerCase();
256
+ if (UNSIGNABLE_HEADERS.has(lowerName))
257
+ continue;
258
+ normalized[lowerName] = String(value).trim().replace(/\s+/g, ' ');
259
+ }
260
+ const sortedNames = Object.keys(normalized).sort();
261
+ const canonicalHeadersString = sortedNames.map((name) => `${name}:${normalized[name]}\n`).join('');
262
+ const signedHeadersList = sortedNames.join(';');
263
+ const canonicalRequest = `${opts.method}\n${opts.path}\n${canonicalQuery}\n${canonicalHeadersString}\n${signedHeadersList}\n${hashedPayload}`;
264
+ const credentialScope = `${dateStamp}/${opts.region}/${opts.service}/aws4_request`;
265
+ const stringToSign = `AWS4-HMAC-SHA256\n${opts.isoTimestamp}\n${credentialScope}\n` +
266
+ createHash('sha256').update(canonicalRequest).digest('hex');
267
+ const hmac = (key, data) => createHmac('sha256', key).update(data).digest();
268
+ const kDate = hmac('AWS4' + opts.secretAccessKey, dateStamp);
269
+ const kRegion = hmac(kDate, opts.region);
270
+ const kService = hmac(kRegion, opts.service);
271
+ const kSigning = hmac(kService, 'aws4_request');
272
+ const signature = createHmac('sha256', kSigning).update(stringToSign).digest('hex');
273
+ const authorization = `AWS4-HMAC-SHA256 Credential=${opts.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeadersList}, Signature=${signature}`;
274
+ const out = {
275
+ Host: opts.host,
276
+ 'X-Amz-Date': opts.isoTimestamp,
277
+ 'X-Amz-Content-Sha256': hashedPayload,
278
+ Authorization: authorization,
279
+ };
280
+ if (opts.sessionToken)
281
+ out['X-Amz-Security-Token'] = opts.sessionToken;
282
+ return out;
283
+ }
284
+ function isoBasicTimestamp() {
285
+ return new Date().toISOString().replace(/[-:]/g, '').replace(/\.\d{3}/, '');
286
+ }
287
+ /**
288
+ * RFC-3986 percent-encode a query key/value for the SigV4 canonical query
289
+ * string. `encodeURIComponent` leaves `!'()*` unescaped, but SigV4's unreserved
290
+ * set is exactly `A-Za-z0-9-_.~`, so those must also be escaped or the
291
+ * signature won't match.
292
+ */
293
+ function awsUriEncodeComponent(s) {
294
+ return encodeURIComponent(s).replace(/[!'()*]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase());
295
+ }
296
+ /**
297
+ * Build the SigV4 canonical query string from `_passthrough.query`: encode each
298
+ * key/value, then sort by encoded key (then value). The same string is both
299
+ * signed and sent on the wire. Returns '' when there are no params.
300
+ */
301
+ function buildCanonicalQuery(query) {
302
+ if (!query)
303
+ return '';
304
+ const pairs = Object.entries(query).map(([k, v]) => [awsUriEncodeComponent(k), awsUriEncodeComponent(v)]);
305
+ if (pairs.length === 0)
306
+ return '';
307
+ pairs.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0));
308
+ return pairs.map(([k, v]) => `${k}=${v}`).join('&');
309
+ }
310
+ // ---------------------------------------------------------------------------
311
+ // Converse mappers
312
+ // ---------------------------------------------------------------------------
313
+ function splitMessages(messages) {
314
+ const system = [];
315
+ const out = [];
316
+ for (const m of messages) {
317
+ if (m.role === 'system') {
318
+ const t = contentToText(m.content);
319
+ if (t)
320
+ system.push({ text: t });
321
+ continue;
322
+ }
323
+ if (m.role === 'tool') {
324
+ const block = {
325
+ toolResult: { toolUseId: m.toolCallId ?? '', content: [{ text: contentToText(m.content) }] },
326
+ };
327
+ const last = out[out.length - 1];
328
+ if (last && last.role === 'user' && isToolResultContent(last.content)) {
329
+ last.content.push(block);
330
+ }
331
+ else {
332
+ out.push({ role: 'user', content: [block] });
333
+ }
334
+ continue;
335
+ }
336
+ out.push({ role: m.role, content: mapContentBlocks(m) });
337
+ }
338
+ return { system, messages: out };
339
+ }
340
+ function contentToText(content) {
341
+ if (content === null)
342
+ return '';
343
+ if (typeof content === 'string')
344
+ return content;
345
+ return content
346
+ .filter((p) => p.type === 'text')
347
+ .map((p) => p.text)
348
+ .join('');
349
+ }
350
+ function mapContentBlocks(m) {
351
+ const blocks = [];
352
+ if (typeof m.content === 'string') {
353
+ if (m.content.length > 0)
354
+ blocks.push({ text: m.content });
355
+ }
356
+ else if (Array.isArray(m.content)) {
357
+ for (const p of m.content) {
358
+ if (p.type === 'text') {
359
+ blocks.push({ text: p.text });
360
+ }
361
+ else {
362
+ blocks.push({ image: { format: imageFormat(p.mediaType), source: { bytes: p.base64 } } });
363
+ }
364
+ }
365
+ }
366
+ if (m.role === 'assistant' && m.toolCalls) {
367
+ for (const tc of m.toolCalls) {
368
+ blocks.push({
369
+ toolUse: { toolUseId: tc.id, name: tc.function.name, input: safeJsonParse(tc.function.arguments) },
370
+ });
371
+ }
372
+ }
373
+ if (blocks.length === 0)
374
+ blocks.push({ text: '' });
375
+ return blocks;
376
+ }
377
+ function isToolResultContent(content) {
378
+ return (content.length > 0 &&
379
+ typeof content[0] === 'object' &&
380
+ content[0] !== null &&
381
+ 'toolResult' in content[0]);
382
+ }
383
+ function mapToolChoice(tc) {
384
+ if (tc === undefined)
385
+ return null;
386
+ if (tc === 'auto')
387
+ return { auto: {} };
388
+ if (tc === 'required')
389
+ return { any: {} };
390
+ if (tc === 'none')
391
+ return null; // Bedrock has no "none"; omit toolChoice
392
+ if (typeof tc === 'object' && tc.type === 'function')
393
+ return { tool: { name: tc.function.name } };
394
+ return null;
395
+ }
396
+ function mapStopReason(sr) {
397
+ switch (sr) {
398
+ case 'end_turn':
399
+ case 'stop_sequence':
400
+ return 'stop';
401
+ case 'max_tokens':
402
+ return 'length';
403
+ case 'tool_use':
404
+ return 'tool_calls';
405
+ case 'content_filtered':
406
+ case 'guardrail_intervened':
407
+ return 'content_filter';
408
+ default:
409
+ return 'unknown';
410
+ }
411
+ }
412
+ function imageFormat(mediaType) {
413
+ const m = mediaType.toLowerCase();
414
+ if (m.includes('png'))
415
+ return 'png';
416
+ if (m.includes('jpeg') || m.includes('jpg'))
417
+ return 'jpeg';
418
+ if (m.includes('gif'))
419
+ return 'gif';
420
+ if (m.includes('webp'))
421
+ return 'webp';
422
+ return 'png';
423
+ }
424
+ function safeJsonParse(s) {
425
+ try {
426
+ return JSON.parse(s);
427
+ }
428
+ catch {
429
+ return {};
430
+ }
431
+ }
432
+ function extractBedrockError(body) {
433
+ if (typeof body === 'string')
434
+ return body;
435
+ if (!body || typeof body !== 'object')
436
+ return null;
437
+ const b = body;
438
+ if (typeof b.message === 'string')
439
+ return b.message;
440
+ if (typeof b.Message === 'string')
441
+ return b.Message;
442
+ if (typeof b.__type === 'string')
443
+ return b.__type;
444
+ return null;
445
+ }
446
+ function mapStatusToProviderCode(status, message) {
447
+ if (status === 401 || status === 403)
448
+ return 'auth_failed';
449
+ if (status === 429)
450
+ return 'rate_limited';
451
+ if (status >= 500)
452
+ return 'provider_unavailable';
453
+ if (status === 400 || status === 404 || status === 422) {
454
+ const msg = message.toLowerCase();
455
+ if (msg.includes('throttl'))
456
+ return 'rate_limited';
457
+ if (msg.includes('too long') || msg.includes('context') || msg.includes('token') || msg.includes('max')) {
458
+ return 'context_length_exceeded';
459
+ }
460
+ return 'invalid_request';
461
+ }
462
+ return 'unknown';
463
+ }
464
+ function parseRetryAfterSeconds(header) {
465
+ const asNum = Number(header);
466
+ if (Number.isFinite(asNum))
467
+ return asNum;
468
+ const asDate = Date.parse(header);
469
+ if (!Number.isNaN(asDate)) {
470
+ const secs = Math.round((asDate - Date.now()) / 1000);
471
+ return secs >= 0 ? secs : 0;
472
+ }
473
+ return null;
474
+ }
@@ -0,0 +1,2 @@
1
+ export { BedrockConnector } from './bedrock.connector.js';
2
+ export type { BedrockConfig } from './bedrock.config.js';
@@ -0,0 +1 @@
1
+ export { BedrockConnector } from './bedrock.connector.js';
@@ -0,0 +1,12 @@
1
+ export interface GeminiConfig {
2
+ /** Google AI Studio API key — sent as the `x-goog-api-key` header. */
3
+ apiKey: string;
4
+ /** Override the base URL. Default `https://generativelanguage.googleapis.com/v1beta`. */
5
+ baseUrl?: string;
6
+ /** `generationConfig.maxOutputTokens` used when `ChatInput.maxOutputTokens` is omitted. */
7
+ defaultMaxTokens?: number;
8
+ /** Bring-your-own fetch. Defaults to `globalThis.fetch`. */
9
+ fetch?: typeof fetch;
10
+ /** Extra headers merged onto every request. */
11
+ headers?: Record<string, string>;
12
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ import { BaseConnector } from '../../base/base.connector.js';
2
+ import type { ChatInput, ChatResult, ChatStreamDelta, IChatConnector } from '../../types/index.js';
3
+ import type { GeminiConfig } from './gemini.config.js';
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 declare class GeminiConnector extends BaseConnector implements IChatConnector {
18
+ readonly id = "gemini";
19
+ private readonly config;
20
+ private readonly baseUrl;
21
+ constructor(config: GeminiConfig);
22
+ complete(input: ChatInput): Promise<ChatResult>;
23
+ stream(input: ChatInput): AsyncGenerator<ChatStreamDelta>;
24
+ private authHeaders;
25
+ private buildRequest;
26
+ private parseResult;
27
+ private mapVendorError;
28
+ }