@llm-ports/adapter-google 0.1.0-alpha.5

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Babak Abbaschian and contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # @llm-ports/adapter-google
2
+
3
+ Native Google Gemini adapter for [`llm-ports`](https://github.com/baabakk/llm-ports), built on the unified [`@google/genai`](https://www.npmjs.com/package/@google/genai) SDK. Implements `LLMPort` with full multimodal support — image content blocks pass through as `inlineData` (base64) or `fileData` (URL), not degraded to placeholder text.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @llm-ports/core @llm-ports/adapter-google @google/genai zod
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { createRegistryFromEnv } from "@llm-ports/core";
15
+ import { createGoogleAdapter } from "@llm-ports/adapter-google";
16
+
17
+ const registry = createRegistryFromEnv({
18
+ adapters: {
19
+ google: createGoogleAdapter({
20
+ apiKey: process.env.GOOGLE_API_KEY!, // get one at https://aistudio.google.com/apikey
21
+ }),
22
+ },
23
+ });
24
+
25
+ const llm = registry.getPort();
26
+ ```
27
+
28
+ Env config:
29
+
30
+ ```bash
31
+ LLM_PROVIDER_FAST=google|gemini-2.5-flash|cost:5/day
32
+ LLM_PROVIDER_SMART=google|gemini-2.5-pro|cost:50/day
33
+ LLM_TASK_ROUTE_TRIAGE=fast,smart
34
+ ```
35
+
36
+ ## Why use this over the OpenAI-compat baseURL
37
+
38
+ Gemini exposes an OpenAI-compatible surface at `https://generativelanguage.googleapis.com/v1beta/openai/`. It works for most cases. Reasons to prefer this native adapter:
39
+
40
+ | Concern | OpenAI-compat baseURL | `adapter-google` |
41
+ |---|---|---|
42
+ | `ImageSource.detail` field | Silently ignored | Ignored explicitly (consistent w/ other non-OpenAI providers) |
43
+ | `systemInstruction` | Prepended to user message — changes Gemini's behavior | Native top-level field |
44
+ | Multimodal image richness | image_url with base64 data URI (lossy) | inlineData with explicit mediaType |
45
+ | Bundled pricing | None — supply via `pricingOverrides` | Gemini 2.5 + 2.0 family bundled |
46
+ | `responseSchema` constrained-decoding | Not exposed | v0.2 (currently prompted-JSON + Zod) |
47
+
48
+ ## Supported features (v0.1)
49
+
50
+ | Feature | Status |
51
+ |---------|--------|
52
+ | `generateText` | ✓ |
53
+ | `generateStructured` (Zod schemas) | ✓ (prompted JSON + alpha.5 repair pass; native `responseSchema` in v0.2) |
54
+ | `streamText` | ✓ |
55
+ | `streamStructured` | ✓ (best-effort partial parse) |
56
+ | `runAgent` (single-turn) | ✓ (multi-turn native function-calling in v0.2) |
57
+ | Vision input — base64 images | ✓ (inlineData) |
58
+ | Vision input — URL images | ✓ (fileData) |
59
+ | Audio input — base64 | ✓ (inlineData) |
60
+ | Image size + URL validation at boundary | ✓ (alpha.5) |
61
+ | Embeddings (`gemini-embedding-001`) | ✗ — v0.2 |
62
+ | Explicit context caching | ✗ — v0.2 |
63
+ | Code execution tool | ✗ — v0.2 |
64
+
65
+ ## Bundled pricing
66
+
67
+ | Model | Input/1M | Output/1M | Cached input/1M |
68
+ |-------|---------:|----------:|----------------:|
69
+ | `gemini-2.5-pro` | $1.25 | $5.00 | $0.3125 |
70
+ | `gemini-2.5-flash` | $0.075 | $0.30 | $0.01875 |
71
+ | `gemini-2.5-flash-lite` | $0.0375 | $0.15 | $0.009375 |
72
+ | `gemini-2.0-flash` | $0.10 | $0.40 | $0.025 |
73
+ | `gemini-2.0-flash-lite` | $0.075 | $0.30 | — |
74
+
75
+ Pricing source: <https://ai.google.dev/gemini-api/docs/pricing> (verified 2026-05). Bundled values are the under-200k-token tier. For long-context workloads, supply `pricingOverrides` with the over-200k rates.
76
+
77
+ ## Adapter options
78
+
79
+ ```ts
80
+ interface GoogleAdapterOptions {
81
+ apiKey: string;
82
+ pricingOverrides?: Record<string, ModelPricing>;
83
+ validationStrategy?: ValidationStrategy;
84
+ imageSizeLimitBytes?: number; // default 20 MB
85
+ }
86
+ ```
87
+
88
+ ## License
89
+
90
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,386 @@
1
+ 'use strict';
2
+
3
+ var genai = require('@google/genai');
4
+ var core = require('@llm-ports/core');
5
+
6
+ // src/adapter.ts
7
+ var ADAPTER_NAME = "google";
8
+ function toGeminiParts(block) {
9
+ switch (block.type) {
10
+ case "text":
11
+ return [{ text: block.text }];
12
+ case "image": {
13
+ if (block.source.kind === "base64") {
14
+ return [
15
+ {
16
+ inlineData: {
17
+ mimeType: block.source.mediaType,
18
+ data: block.source.data
19
+ }
20
+ }
21
+ ];
22
+ }
23
+ return [
24
+ {
25
+ fileData: {
26
+ // Gemini infers mimeType from URL extension when not provided.
27
+ // We pass image/jpeg as a sane default; users wanting tighter
28
+ // control should pass base64 with explicit mediaType.
29
+ mimeType: "image/jpeg",
30
+ fileUri: block.source.url
31
+ }
32
+ }
33
+ ];
34
+ }
35
+ case "audio": {
36
+ if (block.source.kind === "base64") {
37
+ return [
38
+ {
39
+ inlineData: {
40
+ mimeType: block.source.mediaType,
41
+ data: block.source.data
42
+ }
43
+ }
44
+ ];
45
+ }
46
+ throw new core.ContentBlockUnsupportedError(ADAPTER_NAME, "audio (url; Gemini accepts base64 or fileData with fileUri)");
47
+ }
48
+ case "tool_use": {
49
+ const args = block.input !== null && typeof block.input === "object" ? block.input : { value: block.input };
50
+ return [{ functionCall: { name: block.name, args } }];
51
+ }
52
+ case "tool_result": {
53
+ const response = typeof block.content === "string" ? { result: block.content } : { result: extractTextOnly(block.content) };
54
+ return [
55
+ {
56
+ functionResponse: {
57
+ // Gemini's API requires the tool name; we use toolUseId since
58
+ // llm-ports' ToolResultBlock doesn't carry the name. Adapters
59
+ // that need the name can plumb it through a separate channel.
60
+ name: block.toolUseId,
61
+ response
62
+ }
63
+ }
64
+ ];
65
+ }
66
+ }
67
+ }
68
+ function extractTextOnly(blocks) {
69
+ return blocks.filter((b) => b.type === "text").map((b) => b.text).join("\n");
70
+ }
71
+ function toGeminiParts2(content) {
72
+ if (typeof content === "string") {
73
+ return [{ text: content }];
74
+ }
75
+ return content.flatMap(toGeminiParts);
76
+ }
77
+ function toGeminiRequest(messages) {
78
+ let systemInstruction;
79
+ const contents = [];
80
+ for (const msg of messages) {
81
+ if (msg.role === "system") {
82
+ const text = typeof msg.content === "string" ? msg.content : extractTextOnly(msg.content);
83
+ systemInstruction = systemInstruction === void 0 ? text : `${systemInstruction}
84
+
85
+ ${text}`;
86
+ continue;
87
+ }
88
+ const role = msg.role === "tool" ? "function" : msg.role === "assistant" ? "model" : "user";
89
+ contents.push({
90
+ role,
91
+ parts: toGeminiParts2(msg.content)
92
+ });
93
+ }
94
+ return systemInstruction !== void 0 ? { systemInstruction, contents } : { contents };
95
+ }
96
+ function extractGeminiText(parts) {
97
+ if (!parts) return "";
98
+ return parts.filter((p) => "text" in p).map((p) => p.text).join("");
99
+ }
100
+
101
+ // src/pricing.ts
102
+ var GEMINI_PRICING = {
103
+ // Gemini 2.5 family (2026-05 GA pricing)
104
+ "gemini-2.5-pro": {
105
+ inputPer1M: 1.25,
106
+ outputPer1M: 5,
107
+ cacheReadPer1M: 0.3125
108
+ },
109
+ "gemini-2.5-flash": {
110
+ inputPer1M: 0.075,
111
+ outputPer1M: 0.3,
112
+ cacheReadPer1M: 0.01875
113
+ },
114
+ "gemini-2.5-flash-lite": {
115
+ inputPer1M: 0.0375,
116
+ outputPer1M: 0.15,
117
+ cacheReadPer1M: 9375e-6
118
+ },
119
+ // Gemini 2.0 family (still available)
120
+ "gemini-2.0-flash": {
121
+ inputPer1M: 0.1,
122
+ outputPer1M: 0.4,
123
+ cacheReadPer1M: 0.025
124
+ },
125
+ "gemini-2.0-flash-lite": {
126
+ inputPer1M: 0.075,
127
+ outputPer1M: 0.3
128
+ }
129
+ };
130
+ function lookupGeminiPricing(modelId) {
131
+ return GEMINI_PRICING[modelId];
132
+ }
133
+
134
+ // src/adapter.ts
135
+ function pricingFor(ctx, modelId) {
136
+ const pricing = ctx.pricingOverrides[modelId] ?? GEMINI_PRICING[modelId];
137
+ if (!pricing) {
138
+ throw new Error(
139
+ `No pricing entry for Google Gemini model "${modelId}". Provide pricingOverrides or update src/pricing.ts.`
140
+ );
141
+ }
142
+ return pricing;
143
+ }
144
+ function createGoogleAdapter(opts) {
145
+ const mergedPricing = {
146
+ ...GEMINI_PRICING,
147
+ ...opts.pricingOverrides ?? {}
148
+ };
149
+ const ctx = {
150
+ client: new genai.GoogleGenAI({ apiKey: opts.apiKey }),
151
+ validationStrategy: opts.validationStrategy ?? {
152
+ kind: "retry-with-feedback",
153
+ maxAttempts: 2,
154
+ includeOriginalError: true
155
+ },
156
+ pricingOverrides: opts.pricingOverrides ?? {},
157
+ imageSizeLimitBytes: opts.imageSizeLimitBytes ?? 20 * 1024 * 1024
158
+ };
159
+ return {
160
+ name: "google",
161
+ pricing: mergedPricing,
162
+ createLLMPort: (modelId, alias) => createPort(ctx, modelId, alias)
163
+ };
164
+ }
165
+ function createPort(ctx, modelId, alias) {
166
+ const pricing = pricingFor(ctx, modelId);
167
+ const validateContent = (content) => {
168
+ if (Array.isArray(content)) {
169
+ core.validateImageBlocks(content, {
170
+ alias,
171
+ ...ctx.imageSizeLimitBytes > 0 ? { limitBytes: ctx.imageSizeLimitBytes } : {}
172
+ });
173
+ }
174
+ };
175
+ const validateMessages = (messages) => {
176
+ for (const msg of messages) validateContent(msg.content);
177
+ };
178
+ return {
179
+ async generateText(options) {
180
+ validateContent(options.prompt);
181
+ const start = Date.now();
182
+ try {
183
+ const parts = toGeminiParts2(options.prompt);
184
+ const response = await ctx.client.models.generateContent({
185
+ model: modelId,
186
+ contents: [{ role: "user", parts }],
187
+ config: {
188
+ ...options.instructions !== void 0 ? { systemInstruction: options.instructions } : {},
189
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {},
190
+ ...options.maxOutputTokens !== void 0 ? { maxOutputTokens: options.maxOutputTokens } : {}
191
+ }
192
+ });
193
+ const candidate = response.candidates?.[0];
194
+ const text = extractGeminiText(candidate?.content?.parts);
195
+ const usage = parseUsage(response);
196
+ return {
197
+ text,
198
+ usage,
199
+ cost: core.computeChatCost(usage, pricing),
200
+ modelId: response.modelVersion ?? modelId,
201
+ providerAlias: alias,
202
+ latencyMs: Date.now() - start
203
+ };
204
+ } catch (err) {
205
+ throw core.wrapProviderError(alias, err);
206
+ }
207
+ },
208
+ async generateStructured(options) {
209
+ validateContent(options.prompt);
210
+ const start = Date.now();
211
+ let attempts = 0;
212
+ const maxAttempts = ctx.validationStrategy.kind === "retry-with-feedback" ? ctx.validationStrategy.maxAttempts : 1;
213
+ let correctionPrompt = null;
214
+ let lastUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
215
+ let lastModelId = modelId;
216
+ while (attempts < maxAttempts) {
217
+ attempts++;
218
+ const userText = correctionPrompt ? `${core.stringifyContentBlocks(options.prompt)}
219
+
220
+ ${correctionPrompt}` : `${core.stringifyContentBlocks(options.prompt)}
221
+
222
+ Reply with a single JSON object only. No prose, no code fences.`;
223
+ try {
224
+ const response = await ctx.client.models.generateContent({
225
+ model: modelId,
226
+ contents: [{ role: "user", parts: [{ text: userText }] }],
227
+ config: {
228
+ ...options.instructions !== void 0 ? { systemInstruction: options.instructions } : {},
229
+ temperature: options.temperature ?? 0,
230
+ ...options.maxOutputTokens !== void 0 ? { maxOutputTokens: options.maxOutputTokens } : {},
231
+ responseMimeType: "application/json"
232
+ }
233
+ });
234
+ const candidate = response.candidates?.[0];
235
+ const raw = extractGeminiText(candidate?.content?.parts);
236
+ lastUsage = parseUsage(response);
237
+ lastModelId = response.modelVersion ?? modelId;
238
+ const decoded = core.extractJSON(raw);
239
+ let parsed = options.schema.safeParse(decoded);
240
+ if (!parsed.success) {
241
+ const repaired = core.attemptValidationRepair(decoded, parsed.error);
242
+ const reparsed = options.schema.safeParse(repaired);
243
+ if (reparsed.success) parsed = reparsed;
244
+ }
245
+ if (parsed.success) {
246
+ return {
247
+ data: parsed.data,
248
+ usage: lastUsage,
249
+ cost: core.computeChatCost(lastUsage, pricing),
250
+ modelId: lastModelId,
251
+ providerAlias: alias,
252
+ latencyMs: Date.now() - start,
253
+ validationAttempts: attempts
254
+ };
255
+ }
256
+ if (ctx.validationStrategy.kind === "retry-with-feedback" && attempts < maxAttempts) {
257
+ const issues = parsed.error.issues.map((i) => `- ${i.path.join(".") || "<root>"}: ${i.message}`).join("\n");
258
+ correctionPrompt = `Your previous response failed validation:
259
+ ${issues}
260
+
261
+ Reply with a single corrected JSON object only.`;
262
+ continue;
263
+ }
264
+ core.failValidation(parsed.error.issues, attempts);
265
+ } catch (err) {
266
+ throw core.wrapProviderError(alias, err);
267
+ }
268
+ }
269
+ throw new Error("generateStructured exhausted attempts");
270
+ },
271
+ async *streamText(options) {
272
+ validateContent(options.prompt);
273
+ try {
274
+ const parts = toGeminiParts2(options.prompt);
275
+ const stream = await ctx.client.models.generateContentStream({
276
+ model: modelId,
277
+ contents: [{ role: "user", parts }],
278
+ config: {
279
+ ...options.instructions !== void 0 ? { systemInstruction: options.instructions } : {},
280
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {},
281
+ ...options.maxOutputTokens !== void 0 ? { maxOutputTokens: options.maxOutputTokens } : {}
282
+ }
283
+ });
284
+ for await (const chunk of stream) {
285
+ const text = extractGeminiText(
286
+ chunk.candidates?.[0]?.content?.parts
287
+ );
288
+ if (text.length > 0) yield text;
289
+ }
290
+ } catch (err) {
291
+ throw core.wrapProviderError(alias, err);
292
+ }
293
+ },
294
+ async *streamStructured(options) {
295
+ validateContent(options.prompt);
296
+ try {
297
+ const stream = await ctx.client.models.generateContentStream({
298
+ model: modelId,
299
+ contents: [
300
+ {
301
+ role: "user",
302
+ parts: [
303
+ {
304
+ text: `${core.stringifyContentBlocks(options.prompt)}
305
+
306
+ Reply with a single JSON object only. Stream the JSON progressively.`
307
+ }
308
+ ]
309
+ }
310
+ ],
311
+ config: {
312
+ ...options.instructions !== void 0 ? { systemInstruction: options.instructions } : {},
313
+ temperature: options.temperature ?? 0,
314
+ ...options.maxOutputTokens !== void 0 ? { maxOutputTokens: options.maxOutputTokens } : {},
315
+ responseMimeType: "application/json"
316
+ }
317
+ });
318
+ let buffer = "";
319
+ for await (const chunk of stream) {
320
+ const text = extractGeminiText(
321
+ chunk.candidates?.[0]?.content?.parts
322
+ );
323
+ if (text.length === 0) continue;
324
+ buffer += text;
325
+ const partial = core.tryParsePartialJSON(buffer);
326
+ if (partial !== null) yield partial;
327
+ }
328
+ } catch (err) {
329
+ throw core.wrapProviderError(alias, err);
330
+ }
331
+ },
332
+ async runAgent(options) {
333
+ validateMessages(options.messages);
334
+ const start = Date.now();
335
+ try {
336
+ const { systemInstruction, contents } = toGeminiRequest(options.messages);
337
+ const response = await ctx.client.models.generateContent({
338
+ model: modelId,
339
+ contents,
340
+ config: {
341
+ ...systemInstruction !== void 0 ? { systemInstruction } : {},
342
+ ...options.instructions !== void 0 ? { systemInstruction: options.instructions } : {}
343
+ }
344
+ });
345
+ const candidate = response.candidates?.[0];
346
+ const text = extractGeminiText(candidate?.content?.parts);
347
+ const usage = parseUsage(response);
348
+ const toolCalls = [];
349
+ return {
350
+ text,
351
+ messages: [
352
+ ...options.messages,
353
+ { role: "assistant", content: text }
354
+ ],
355
+ usage,
356
+ cost: core.computeChatCost(usage, pricing),
357
+ modelId: response.modelVersion ?? modelId,
358
+ providerAlias: alias,
359
+ latencyMs: Date.now() - start,
360
+ toolCalls,
361
+ stepsTaken: 1,
362
+ terminationReason: "completed"
363
+ };
364
+ } catch (err) {
365
+ throw core.wrapProviderError(alias, err);
366
+ }
367
+ }
368
+ };
369
+ }
370
+ function parseUsage(response) {
371
+ const m = response.usageMetadata ?? {};
372
+ const inputTokens = m.promptTokenCount ?? 0;
373
+ const outputTokens = m.candidatesTokenCount ?? 0;
374
+ const totalTokens = m.totalTokenCount ?? inputTokens + outputTokens;
375
+ const usage = { inputTokens, outputTokens, totalTokens };
376
+ if (m.cachedContentTokenCount !== void 0 && m.cachedContentTokenCount > 0) {
377
+ usage.cacheReadTokens = m.cachedContentTokenCount;
378
+ }
379
+ return usage;
380
+ }
381
+
382
+ exports.GEMINI_PRICING = GEMINI_PRICING;
383
+ exports.createGoogleAdapter = createGoogleAdapter;
384
+ exports.lookupGeminiPricing = lookupGeminiPricing;
385
+ //# sourceMappingURL=index.cjs.map
386
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/content.ts","../src/pricing.ts","../src/adapter.ts"],"names":["ContentBlockUnsupportedError","GoogleGenAI","validateImageBlocks","computeChatCost","wrapProviderError","stringifyContentBlocks","extractJSON","attemptValidationRepair","failValidation","tryParsePartialJSON"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\r\n * Content translation between llm-ports ContentBlock[] and Gemini's\r\n * Content + Part shapes.\r\n *\r\n * Gemini's content model:\r\n * Content = { role: \"user\" | \"model\" | \"function\", parts: Part[] }\r\n * Part shapes (the ones we care about):\r\n * - { text: string }\r\n * - { inlineData: { mimeType: string, data: string (base64) } }\r\n * - { fileData: { mimeType: string, fileUri: string } }\r\n * - { functionCall: { name: string, args: object } }\r\n * - { functionResponse: { name: string, response: object } }\r\n *\r\n * Notes:\r\n * - System messages map to a top-level `systemInstruction` field on the\r\n * request, NOT a Content with role: \"system\". `toGeminiRequest` handles\r\n * this split.\r\n * - The assistant role is `\"model\"` in Gemini's vocabulary.\r\n * - Tool results are mapped to `functionResponse` parts.\r\n */\r\n\r\nimport {\r\n ContentBlockUnsupportedError,\r\n type ContentBlock,\r\n type LLMMessage,\r\n type MessageContent,\r\n} from \"@llm-ports/core\";\r\n\r\nconst ADAPTER_NAME = \"google\";\r\n\r\n// ─── Outgoing: ContentBlock[] → Gemini Part[] ────────────────────────\r\n\r\ninterface GeminiTextPart {\r\n text: string;\r\n}\r\ninterface GeminiInlineDataPart {\r\n inlineData: { mimeType: string; data: string };\r\n}\r\ninterface GeminiFileDataPart {\r\n fileData: { mimeType: string; fileUri: string };\r\n}\r\ninterface GeminiFunctionCallPart {\r\n functionCall: { name: string; args: Record<string, unknown> };\r\n}\r\ninterface GeminiFunctionResponsePart {\r\n functionResponse: { name: string; response: Record<string, unknown> };\r\n}\r\nexport type GeminiPart =\r\n | GeminiTextPart\r\n | GeminiInlineDataPart\r\n | GeminiFileDataPart\r\n | GeminiFunctionCallPart\r\n | GeminiFunctionResponsePart;\r\n\r\nexport interface GeminiContent {\r\n role: \"user\" | \"model\" | \"function\";\r\n parts: GeminiPart[];\r\n}\r\n\r\n/** Translate a single ContentBlock to one or more GeminiParts. */\r\nfunction toGeminiParts(block: ContentBlock): GeminiPart[] {\r\n switch (block.type) {\r\n case \"text\":\r\n return [{ text: block.text }];\r\n case \"image\": {\r\n if (block.source.kind === \"base64\") {\r\n return [\r\n {\r\n inlineData: {\r\n mimeType: block.source.mediaType,\r\n data: block.source.data,\r\n },\r\n },\r\n ];\r\n }\r\n // URL form\r\n return [\r\n {\r\n fileData: {\r\n // Gemini infers mimeType from URL extension when not provided.\r\n // We pass image/jpeg as a sane default; users wanting tighter\r\n // control should pass base64 with explicit mediaType.\r\n mimeType: \"image/jpeg\",\r\n fileUri: block.source.url,\r\n },\r\n },\r\n ];\r\n }\r\n case \"audio\": {\r\n if (block.source.kind === \"base64\") {\r\n return [\r\n {\r\n inlineData: {\r\n mimeType: block.source.mediaType,\r\n data: block.source.data,\r\n },\r\n },\r\n ];\r\n }\r\n throw new ContentBlockUnsupportedError(ADAPTER_NAME, \"audio (url; Gemini accepts base64 or fileData with fileUri)\");\r\n }\r\n case \"tool_use\": {\r\n // Gemini's tool-call shape has args as a plain object; we pass the input\r\n // through if it's already an object, else wrap.\r\n const args =\r\n block.input !== null && typeof block.input === \"object\"\r\n ? (block.input as Record<string, unknown>)\r\n : { value: block.input };\r\n return [{ functionCall: { name: block.name, args } }];\r\n }\r\n case \"tool_result\": {\r\n // Gemini's functionResponse expects a `response` object. If the\r\n // ContentBlock.tool_result.content is a string, wrap it.\r\n const response: Record<string, unknown> =\r\n typeof block.content === \"string\"\r\n ? { result: block.content }\r\n : { result: extractTextOnly(block.content) };\r\n return [\r\n {\r\n functionResponse: {\r\n // Gemini's API requires the tool name; we use toolUseId since\r\n // llm-ports' ToolResultBlock doesn't carry the name. Adapters\r\n // that need the name can plumb it through a separate channel.\r\n name: block.toolUseId,\r\n response,\r\n },\r\n },\r\n ];\r\n }\r\n }\r\n}\r\n\r\nfunction extractTextOnly(blocks: ContentBlock[]): string {\r\n return blocks\r\n .filter((b): b is Extract<ContentBlock, { type: \"text\" }> => b.type === \"text\")\r\n .map((b) => b.text)\r\n .join(\"\\n\");\r\n}\r\n\r\n/** Translate a MessageContent to Gemini Parts. */\r\nexport function toGeminiParts2(content: MessageContent): GeminiPart[] {\r\n if (typeof content === \"string\") {\r\n return [{ text: content }];\r\n }\r\n return content.flatMap(toGeminiParts);\r\n}\r\n\r\n/**\r\n * Translate an array of LLMMessages into:\r\n * - `systemInstruction`: the concatenated system messages (Gemini puts\r\n * these at the top level of the request, not in `contents`).\r\n * - `contents`: the user + assistant + tool messages, with roles mapped.\r\n */\r\nexport function toGeminiRequest(messages: LLMMessage[]): {\r\n systemInstruction?: string;\r\n contents: GeminiContent[];\r\n} {\r\n let systemInstruction: string | undefined;\r\n const contents: GeminiContent[] = [];\r\n for (const msg of messages) {\r\n if (msg.role === \"system\") {\r\n const text =\r\n typeof msg.content === \"string\" ? msg.content : extractTextOnly(msg.content);\r\n systemInstruction = systemInstruction === undefined ? text : `${systemInstruction}\\n\\n${text}`;\r\n continue;\r\n }\r\n const role: GeminiContent[\"role\"] =\r\n msg.role === \"tool\" ? \"function\" : msg.role === \"assistant\" ? \"model\" : \"user\";\r\n contents.push({\r\n role,\r\n parts: toGeminiParts2(msg.content),\r\n });\r\n }\r\n return systemInstruction !== undefined\r\n ? { systemInstruction, contents }\r\n : { contents };\r\n}\r\n\r\n// ─── Incoming: Gemini response → ContentBlock[] ──────────────────────\r\n\r\ninterface GeminiResponseCandidate {\r\n content?: {\r\n role?: string;\r\n parts?: GeminiPart[];\r\n };\r\n finishReason?: string;\r\n}\r\n\r\n/**\r\n * Extract the assistant text from a Gemini response. Used by generateText\r\n * and by the structured-output path before JSON parsing.\r\n */\r\nexport function extractGeminiText(parts: GeminiPart[] | undefined): string {\r\n if (!parts) return \"\";\r\n return parts\r\n .filter((p): p is GeminiTextPart => \"text\" in p)\r\n .map((p) => p.text)\r\n .join(\"\");\r\n}\r\n\r\n/**\r\n * Translate a Gemini response candidate's parts back into ContentBlock[].\r\n * Used by runAgent to reconstruct the model's tool_use blocks.\r\n */\r\nexport function fromGeminiCandidate(candidate: GeminiResponseCandidate): ContentBlock[] {\r\n const out: ContentBlock[] = [];\r\n const parts = candidate.content?.parts ?? [];\r\n for (const part of parts) {\r\n if (\"text\" in part && part.text.length > 0) {\r\n out.push({ type: \"text\", text: part.text });\r\n } else if (\"functionCall\" in part) {\r\n out.push({\r\n type: \"tool_use\",\r\n id: `gemini-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\r\n name: part.functionCall.name,\r\n input: part.functionCall.args,\r\n });\r\n } else if (\"inlineData\" in part) {\r\n // Inline (base64) image in assistant response. Decode if media type\r\n // is one we support, else drop (consistent with adapter-openai's\r\n // unknown-media-type behavior).\r\n const mt = part.inlineData.mimeType;\r\n if (\r\n mt === \"image/jpeg\" ||\r\n mt === \"image/png\" ||\r\n mt === \"image/gif\" ||\r\n mt === \"image/webp\"\r\n ) {\r\n out.push({\r\n type: \"image\",\r\n source: { kind: \"base64\", mediaType: mt, data: part.inlineData.data },\r\n });\r\n }\r\n }\r\n // fileData / functionResponse in assistant responses: not currently observed.\r\n }\r\n return out;\r\n}\r\n","/**\r\n * Bundled pricing for Google Gemini models.\r\n *\r\n * Source: https://ai.google.dev/gemini-api/docs/pricing (verified 2026-05).\r\n * Override per model via `pricingOverrides` on the adapter options.\r\n *\r\n * Gemini pricing has separate tiers for prompts under 200k tokens vs over\r\n * 200k tokens (the \"long-context premium\"). The bundled values are the\r\n * UNDER-200k-token rates, which dominate typical usage. For long-context\r\n * workloads, supply `pricingOverrides` with the over-200k rates.\r\n */\r\n\r\nimport type { ModelPricing } from \"@llm-ports/core\";\r\n\r\nexport const GEMINI_PRICING: Record<string, ModelPricing> = {\r\n // Gemini 2.5 family (2026-05 GA pricing)\r\n \"gemini-2.5-pro\": {\r\n inputPer1M: 1.25,\r\n outputPer1M: 5.0,\r\n cacheReadPer1M: 0.3125,\r\n },\r\n \"gemini-2.5-flash\": {\r\n inputPer1M: 0.075,\r\n outputPer1M: 0.3,\r\n cacheReadPer1M: 0.01875,\r\n },\r\n \"gemini-2.5-flash-lite\": {\r\n inputPer1M: 0.0375,\r\n outputPer1M: 0.15,\r\n cacheReadPer1M: 0.009375,\r\n },\r\n // Gemini 2.0 family (still available)\r\n \"gemini-2.0-flash\": {\r\n inputPer1M: 0.1,\r\n outputPer1M: 0.4,\r\n cacheReadPer1M: 0.025,\r\n },\r\n \"gemini-2.0-flash-lite\": {\r\n inputPer1M: 0.075,\r\n outputPer1M: 0.3,\r\n },\r\n};\r\n\r\nexport function lookupGeminiPricing(modelId: string): ModelPricing | undefined {\r\n return GEMINI_PRICING[modelId];\r\n}\r\n","/**\r\n * Google Gemini adapter for llm-ports.\r\n *\r\n * Wraps @google/genai (the unified Gemini + Vertex SDK as of 2026) to\r\n * implement LLMPort. Provides:\r\n *\r\n * - Native multimodal: image content blocks pass through as inlineData\r\n * (base64) or fileData (URL). NO degradation, unlike OpenAI-compat\r\n * baseURL where image_url.detail is silently ignored.\r\n * - Native streaming via generateContentStream\r\n * - Structured output via prompted-JSON + Zod retry-with-feedback\r\n * + alpha.5 programmatic repair. Native Gemini responseSchema lands\r\n * in v0.2.\r\n * - Image-block boundary validation (size + URL scheme) — same shape\r\n * as adapter-anthropic and adapter-openai (alpha.5).\r\n *\r\n * Out of scope for v0.1 alpha:\r\n * - Embeddings (Gemini's embedding API is separate; lands in v0.2)\r\n * - Multi-turn runAgent through Gemini's native automatic tool calling\r\n * (v0.1 ships a single-turn shim consistent with adapter-vercel)\r\n * - Caching API (Gemini supports explicit context caching; lands in v0.2)\r\n * - Code execution tool (Gemini's built-in code interpreter; lands in v0.2)\r\n */\r\n\r\nimport { GoogleGenAI } from \"@google/genai\";\r\nimport {\r\n attemptValidationRepair,\r\n computeChatCost,\r\n extractJSON,\r\n failValidation,\r\n stringifyContentBlocks,\r\n tryParsePartialJSON,\r\n validateImageBlocks,\r\n wrapProviderError,\r\n type AgentResult,\r\n type ContentBlock,\r\n type GenerateStructuredOptions,\r\n type GenerateStructuredResult,\r\n type GenerateTextOptions,\r\n type GenerateTextResult,\r\n type LLMPort,\r\n type MessageContent,\r\n type ModelPricing,\r\n type RunAgentOptions,\r\n type StreamStructuredOptions,\r\n type StreamTextOptions,\r\n type TokenUsage,\r\n type ValidationStrategy,\r\n} from \"@llm-ports/core\";\r\nimport {\r\n extractGeminiText,\r\n toGeminiParts2,\r\n toGeminiRequest,\r\n type GeminiPart,\r\n} from \"./content.js\";\r\nimport { GEMINI_PRICING } from \"./pricing.js\";\r\n\r\n// ─── Adapter options ─────────────────────────────────────────────────\r\n\r\nexport interface GoogleAdapterOptions {\r\n /** Google AI API key (https://aistudio.google.com/apikey). */\r\n apiKey: string;\r\n /** Override Gemini pricing for any model id. Falls back to the bundled table. */\r\n pricingOverrides?: Record<string, ModelPricing>;\r\n /** Default validation strategy if the registry doesn't override per-call. */\r\n validationStrategy?: ValidationStrategy;\r\n /**\r\n * Maximum bytes per base64 image. Defaults to 20MB (Gemini accepts up to\r\n * 20MB inlined; fileData URLs are unconstrained but provider-fetched).\r\n * Set to 0 or a negative number to disable size validation.\r\n */\r\n imageSizeLimitBytes?: number;\r\n}\r\n\r\n// ─── Internal context ────────────────────────────────────────────────\r\n\r\ninterface AdapterContext {\r\n client: GoogleGenAI;\r\n validationStrategy: ValidationStrategy;\r\n pricingOverrides: Record<string, ModelPricing>;\r\n imageSizeLimitBytes: number;\r\n}\r\n\r\nfunction pricingFor(ctx: AdapterContext, modelId: string): ModelPricing {\r\n const pricing = ctx.pricingOverrides[modelId] ?? GEMINI_PRICING[modelId];\r\n if (!pricing) {\r\n throw new Error(\r\n `No pricing entry for Google Gemini model \"${modelId}\". Provide pricingOverrides or update src/pricing.ts.`,\r\n );\r\n }\r\n return pricing;\r\n}\r\n\r\n// ─── Public factory ──────────────────────────────────────────────────\r\n\r\nexport interface GoogleAdapter {\r\n name: \"google\";\r\n pricing: Record<string, ModelPricing>;\r\n createLLMPort: (modelId: string, alias: string) => LLMPort;\r\n}\r\n\r\nexport function createGoogleAdapter(opts: GoogleAdapterOptions): GoogleAdapter {\r\n const mergedPricing: Record<string, ModelPricing> = {\r\n ...GEMINI_PRICING,\r\n ...(opts.pricingOverrides ?? {}),\r\n };\r\n const ctx: AdapterContext = {\r\n client: new GoogleGenAI({ apiKey: opts.apiKey }),\r\n validationStrategy: opts.validationStrategy ?? {\r\n kind: \"retry-with-feedback\",\r\n maxAttempts: 2,\r\n includeOriginalError: true,\r\n },\r\n pricingOverrides: opts.pricingOverrides ?? {},\r\n imageSizeLimitBytes: opts.imageSizeLimitBytes ?? 20 * 1024 * 1024,\r\n };\r\n return {\r\n name: \"google\",\r\n pricing: mergedPricing,\r\n createLLMPort: (modelId, alias) => createPort(ctx, modelId, alias),\r\n };\r\n}\r\n\r\n// ─── Port implementation ─────────────────────────────────────────────\r\n\r\nfunction createPort(ctx: AdapterContext, modelId: string, alias: string): LLMPort {\r\n const pricing = pricingFor(ctx, modelId);\r\n\r\n // Image-block validation closure: throws ImageTooLargeError or\r\n // InvalidImageUrlError before the SDK call.\r\n const validateContent = (content: MessageContent): void => {\r\n if (Array.isArray(content)) {\r\n validateImageBlocks(content, {\r\n alias,\r\n ...(ctx.imageSizeLimitBytes > 0 ? { limitBytes: ctx.imageSizeLimitBytes } : {}),\r\n });\r\n }\r\n };\r\n const validateMessages = (messages: ReadonlyArray<{ content: MessageContent }>): void => {\r\n for (const msg of messages) validateContent(msg.content);\r\n };\r\n\r\n return {\r\n async generateText(options: GenerateTextOptions): Promise<GenerateTextResult> {\r\n validateContent(options.prompt);\r\n const start = Date.now();\r\n try {\r\n const parts = toGeminiParts2(options.prompt);\r\n const response = await ctx.client.models.generateContent({\r\n model: modelId,\r\n contents: [{ role: \"user\", parts }],\r\n config: {\r\n ...(options.instructions !== undefined\r\n ? { systemInstruction: options.instructions }\r\n : {}),\r\n ...(options.temperature !== undefined ? { temperature: options.temperature } : {}),\r\n ...(options.maxOutputTokens !== undefined\r\n ? { maxOutputTokens: options.maxOutputTokens }\r\n : {}),\r\n },\r\n });\r\n const candidate = response.candidates?.[0];\r\n const text = extractGeminiText(candidate?.content?.parts as GeminiPart[] | undefined);\r\n const usage = parseUsage(response);\r\n return {\r\n text,\r\n usage,\r\n cost: computeChatCost(usage, pricing),\r\n modelId: response.modelVersion ?? modelId,\r\n providerAlias: alias,\r\n latencyMs: Date.now() - start,\r\n };\r\n } catch (err) {\r\n throw wrapProviderError(alias, err);\r\n }\r\n },\r\n\r\n async generateStructured<T>(\r\n options: GenerateStructuredOptions<T>,\r\n ): Promise<GenerateStructuredResult<T>> {\r\n validateContent(options.prompt);\r\n const start = Date.now();\r\n let attempts = 0;\r\n const maxAttempts =\r\n ctx.validationStrategy.kind === \"retry-with-feedback\"\r\n ? ctx.validationStrategy.maxAttempts\r\n : 1;\r\n\r\n let correctionPrompt: string | null = null;\r\n let lastUsage: TokenUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };\r\n let lastModelId = modelId;\r\n\r\n while (attempts < maxAttempts) {\r\n attempts++;\r\n const userText = correctionPrompt\r\n ? `${stringifyContentBlocks(options.prompt)}\\n\\n${correctionPrompt}`\r\n : `${stringifyContentBlocks(options.prompt)}\\n\\nReply with a single JSON object only. No prose, no code fences.`;\r\n\r\n try {\r\n const response = await ctx.client.models.generateContent({\r\n model: modelId,\r\n contents: [{ role: \"user\", parts: [{ text: userText }] }],\r\n config: {\r\n ...(options.instructions !== undefined\r\n ? { systemInstruction: options.instructions }\r\n : {}),\r\n temperature: options.temperature ?? 0,\r\n ...(options.maxOutputTokens !== undefined\r\n ? { maxOutputTokens: options.maxOutputTokens }\r\n : {}),\r\n responseMimeType: \"application/json\",\r\n },\r\n });\r\n const candidate = response.candidates?.[0];\r\n const raw = extractGeminiText(candidate?.content?.parts as GeminiPart[] | undefined);\r\n lastUsage = parseUsage(response);\r\n lastModelId = response.modelVersion ?? modelId;\r\n\r\n const decoded = extractJSON(raw);\r\n let parsed = options.schema.safeParse(decoded);\r\n if (!parsed.success) {\r\n const repaired = attemptValidationRepair(decoded, parsed.error);\r\n const reparsed = options.schema.safeParse(repaired);\r\n if (reparsed.success) parsed = reparsed;\r\n }\r\n if (parsed.success) {\r\n return {\r\n data: parsed.data as T,\r\n usage: lastUsage,\r\n cost: computeChatCost(lastUsage, pricing),\r\n modelId: lastModelId,\r\n providerAlias: alias,\r\n latencyMs: Date.now() - start,\r\n validationAttempts: attempts,\r\n };\r\n }\r\n if (\r\n ctx.validationStrategy.kind === \"retry-with-feedback\" &&\r\n attempts < maxAttempts\r\n ) {\r\n const issues = parsed.error.issues\r\n .map((i) => `- ${i.path.join(\".\") || \"<root>\"}: ${i.message}`)\r\n .join(\"\\n\");\r\n correctionPrompt = `Your previous response failed validation:\\n${issues}\\n\\nReply with a single corrected JSON object only.`;\r\n continue;\r\n }\r\n failValidation(parsed.error.issues, attempts);\r\n } catch (err) {\r\n throw wrapProviderError(alias, err);\r\n }\r\n }\r\n throw new Error(\"generateStructured exhausted attempts\");\r\n },\r\n\r\n async *streamText(options: StreamTextOptions): AsyncIterable<string> {\r\n validateContent(options.prompt);\r\n try {\r\n const parts = toGeminiParts2(options.prompt);\r\n const stream = await ctx.client.models.generateContentStream({\r\n model: modelId,\r\n contents: [{ role: \"user\", parts }],\r\n config: {\r\n ...(options.instructions !== undefined\r\n ? { systemInstruction: options.instructions }\r\n : {}),\r\n ...(options.temperature !== undefined ? { temperature: options.temperature } : {}),\r\n ...(options.maxOutputTokens !== undefined\r\n ? { maxOutputTokens: options.maxOutputTokens }\r\n : {}),\r\n },\r\n });\r\n for await (const chunk of stream) {\r\n const text = extractGeminiText(\r\n chunk.candidates?.[0]?.content?.parts as GeminiPart[] | undefined,\r\n );\r\n if (text.length > 0) yield text;\r\n }\r\n } catch (err) {\r\n throw wrapProviderError(alias, err);\r\n }\r\n },\r\n\r\n async *streamStructured<T>(options: StreamStructuredOptions<T>): AsyncIterable<Partial<T>> {\r\n validateContent(options.prompt);\r\n try {\r\n const stream = await ctx.client.models.generateContentStream({\r\n model: modelId,\r\n contents: [\r\n {\r\n role: \"user\",\r\n parts: [\r\n {\r\n text: `${stringifyContentBlocks(options.prompt)}\\n\\nReply with a single JSON object only. Stream the JSON progressively.`,\r\n },\r\n ],\r\n },\r\n ],\r\n config: {\r\n ...(options.instructions !== undefined\r\n ? { systemInstruction: options.instructions }\r\n : {}),\r\n temperature: options.temperature ?? 0,\r\n ...(options.maxOutputTokens !== undefined\r\n ? { maxOutputTokens: options.maxOutputTokens }\r\n : {}),\r\n responseMimeType: \"application/json\",\r\n },\r\n });\r\n let buffer = \"\";\r\n for await (const chunk of stream) {\r\n const text = extractGeminiText(\r\n chunk.candidates?.[0]?.content?.parts as GeminiPart[] | undefined,\r\n );\r\n if (text.length === 0) continue;\r\n buffer += text;\r\n const partial = tryParsePartialJSON(buffer);\r\n if (partial !== null) yield partial as Partial<T>;\r\n }\r\n } catch (err) {\r\n throw wrapProviderError(alias, err);\r\n }\r\n },\r\n\r\n async runAgent(options: RunAgentOptions): Promise<AgentResult> {\r\n validateMessages(options.messages);\r\n // v0.1: single-turn agent loop. Gemini's native automatic-function-calling\r\n // multi-turn runAgent ships in v0.2 (matches adapter-vercel's v0.1 shape).\r\n const start = Date.now();\r\n try {\r\n const { systemInstruction, contents } = toGeminiRequest(options.messages);\r\n const response = await ctx.client.models.generateContent({\r\n model: modelId,\r\n contents,\r\n config: {\r\n ...(systemInstruction !== undefined ? { systemInstruction } : {}),\r\n ...(options.instructions !== undefined\r\n ? { systemInstruction: options.instructions }\r\n : {}),\r\n },\r\n });\r\n const candidate = response.candidates?.[0];\r\n const text = extractGeminiText(candidate?.content?.parts as GeminiPart[] | undefined);\r\n const usage = parseUsage(response);\r\n const toolCalls: AgentResult[\"toolCalls\"] = [];\r\n // v0.1 stub: we surface no tool calls. Real tool-use is v0.2 scope.\r\n return {\r\n text,\r\n messages: [\r\n ...options.messages,\r\n { role: \"assistant\" as const, content: text },\r\n ],\r\n usage,\r\n cost: computeChatCost(usage, pricing),\r\n modelId: response.modelVersion ?? modelId,\r\n providerAlias: alias,\r\n latencyMs: Date.now() - start,\r\n toolCalls,\r\n stepsTaken: 1,\r\n terminationReason: \"completed\",\r\n };\r\n } catch (err) {\r\n throw wrapProviderError(alias, err);\r\n }\r\n },\r\n };\r\n}\r\n\r\n// ─── Helpers ─────────────────────────────────────────────────────────\r\n\r\ninterface GeminiUsageMetadata {\r\n promptTokenCount?: number;\r\n candidatesTokenCount?: number;\r\n totalTokenCount?: number;\r\n cachedContentTokenCount?: number;\r\n}\r\n\r\ninterface GeminiResponseShape {\r\n usageMetadata?: GeminiUsageMetadata;\r\n}\r\n\r\nfunction parseUsage(response: GeminiResponseShape): TokenUsage {\r\n const m = response.usageMetadata ?? {};\r\n const inputTokens = m.promptTokenCount ?? 0;\r\n const outputTokens = m.candidatesTokenCount ?? 0;\r\n const totalTokens = m.totalTokenCount ?? inputTokens + outputTokens;\r\n const usage: TokenUsage = { inputTokens, outputTokens, totalTokens };\r\n if (m.cachedContentTokenCount !== undefined && m.cachedContentTokenCount > 0) {\r\n usage.cacheReadTokens = m.cachedContentTokenCount;\r\n }\r\n return usage;\r\n}\r\n\r\n// Re-export ContentBlock for the rare adapter user that wants to type-check\r\n// outside of @llm-ports/core. Keeps the import surface symmetric with the\r\n// other adapters.\r\nexport type { ContentBlock };\r\n"]}
@@ -0,0 +1,63 @@
1
+ import { ModelPricing, LLMPort, ValidationStrategy } from '@llm-ports/core';
2
+
3
+ /**
4
+ * Google Gemini adapter for llm-ports.
5
+ *
6
+ * Wraps @google/genai (the unified Gemini + Vertex SDK as of 2026) to
7
+ * implement LLMPort. Provides:
8
+ *
9
+ * - Native multimodal: image content blocks pass through as inlineData
10
+ * (base64) or fileData (URL). NO degradation, unlike OpenAI-compat
11
+ * baseURL where image_url.detail is silently ignored.
12
+ * - Native streaming via generateContentStream
13
+ * - Structured output via prompted-JSON + Zod retry-with-feedback
14
+ * + alpha.5 programmatic repair. Native Gemini responseSchema lands
15
+ * in v0.2.
16
+ * - Image-block boundary validation (size + URL scheme) — same shape
17
+ * as adapter-anthropic and adapter-openai (alpha.5).
18
+ *
19
+ * Out of scope for v0.1 alpha:
20
+ * - Embeddings (Gemini's embedding API is separate; lands in v0.2)
21
+ * - Multi-turn runAgent through Gemini's native automatic tool calling
22
+ * (v0.1 ships a single-turn shim consistent with adapter-vercel)
23
+ * - Caching API (Gemini supports explicit context caching; lands in v0.2)
24
+ * - Code execution tool (Gemini's built-in code interpreter; lands in v0.2)
25
+ */
26
+
27
+ interface GoogleAdapterOptions {
28
+ /** Google AI API key (https://aistudio.google.com/apikey). */
29
+ apiKey: string;
30
+ /** Override Gemini pricing for any model id. Falls back to the bundled table. */
31
+ pricingOverrides?: Record<string, ModelPricing>;
32
+ /** Default validation strategy if the registry doesn't override per-call. */
33
+ validationStrategy?: ValidationStrategy;
34
+ /**
35
+ * Maximum bytes per base64 image. Defaults to 20MB (Gemini accepts up to
36
+ * 20MB inlined; fileData URLs are unconstrained but provider-fetched).
37
+ * Set to 0 or a negative number to disable size validation.
38
+ */
39
+ imageSizeLimitBytes?: number;
40
+ }
41
+ interface GoogleAdapter {
42
+ name: "google";
43
+ pricing: Record<string, ModelPricing>;
44
+ createLLMPort: (modelId: string, alias: string) => LLMPort;
45
+ }
46
+ declare function createGoogleAdapter(opts: GoogleAdapterOptions): GoogleAdapter;
47
+
48
+ /**
49
+ * Bundled pricing for Google Gemini models.
50
+ *
51
+ * Source: https://ai.google.dev/gemini-api/docs/pricing (verified 2026-05).
52
+ * Override per model via `pricingOverrides` on the adapter options.
53
+ *
54
+ * Gemini pricing has separate tiers for prompts under 200k tokens vs over
55
+ * 200k tokens (the "long-context premium"). The bundled values are the
56
+ * UNDER-200k-token rates, which dominate typical usage. For long-context
57
+ * workloads, supply `pricingOverrides` with the over-200k rates.
58
+ */
59
+
60
+ declare const GEMINI_PRICING: Record<string, ModelPricing>;
61
+ declare function lookupGeminiPricing(modelId: string): ModelPricing | undefined;
62
+
63
+ export { GEMINI_PRICING, type GoogleAdapter, type GoogleAdapterOptions, createGoogleAdapter, lookupGeminiPricing };
@@ -0,0 +1,63 @@
1
+ import { ModelPricing, LLMPort, ValidationStrategy } from '@llm-ports/core';
2
+
3
+ /**
4
+ * Google Gemini adapter for llm-ports.
5
+ *
6
+ * Wraps @google/genai (the unified Gemini + Vertex SDK as of 2026) to
7
+ * implement LLMPort. Provides:
8
+ *
9
+ * - Native multimodal: image content blocks pass through as inlineData
10
+ * (base64) or fileData (URL). NO degradation, unlike OpenAI-compat
11
+ * baseURL where image_url.detail is silently ignored.
12
+ * - Native streaming via generateContentStream
13
+ * - Structured output via prompted-JSON + Zod retry-with-feedback
14
+ * + alpha.5 programmatic repair. Native Gemini responseSchema lands
15
+ * in v0.2.
16
+ * - Image-block boundary validation (size + URL scheme) — same shape
17
+ * as adapter-anthropic and adapter-openai (alpha.5).
18
+ *
19
+ * Out of scope for v0.1 alpha:
20
+ * - Embeddings (Gemini's embedding API is separate; lands in v0.2)
21
+ * - Multi-turn runAgent through Gemini's native automatic tool calling
22
+ * (v0.1 ships a single-turn shim consistent with adapter-vercel)
23
+ * - Caching API (Gemini supports explicit context caching; lands in v0.2)
24
+ * - Code execution tool (Gemini's built-in code interpreter; lands in v0.2)
25
+ */
26
+
27
+ interface GoogleAdapterOptions {
28
+ /** Google AI API key (https://aistudio.google.com/apikey). */
29
+ apiKey: string;
30
+ /** Override Gemini pricing for any model id. Falls back to the bundled table. */
31
+ pricingOverrides?: Record<string, ModelPricing>;
32
+ /** Default validation strategy if the registry doesn't override per-call. */
33
+ validationStrategy?: ValidationStrategy;
34
+ /**
35
+ * Maximum bytes per base64 image. Defaults to 20MB (Gemini accepts up to
36
+ * 20MB inlined; fileData URLs are unconstrained but provider-fetched).
37
+ * Set to 0 or a negative number to disable size validation.
38
+ */
39
+ imageSizeLimitBytes?: number;
40
+ }
41
+ interface GoogleAdapter {
42
+ name: "google";
43
+ pricing: Record<string, ModelPricing>;
44
+ createLLMPort: (modelId: string, alias: string) => LLMPort;
45
+ }
46
+ declare function createGoogleAdapter(opts: GoogleAdapterOptions): GoogleAdapter;
47
+
48
+ /**
49
+ * Bundled pricing for Google Gemini models.
50
+ *
51
+ * Source: https://ai.google.dev/gemini-api/docs/pricing (verified 2026-05).
52
+ * Override per model via `pricingOverrides` on the adapter options.
53
+ *
54
+ * Gemini pricing has separate tiers for prompts under 200k tokens vs over
55
+ * 200k tokens (the "long-context premium"). The bundled values are the
56
+ * UNDER-200k-token rates, which dominate typical usage. For long-context
57
+ * workloads, supply `pricingOverrides` with the over-200k rates.
58
+ */
59
+
60
+ declare const GEMINI_PRICING: Record<string, ModelPricing>;
61
+ declare function lookupGeminiPricing(modelId: string): ModelPricing | undefined;
62
+
63
+ export { GEMINI_PRICING, type GoogleAdapter, type GoogleAdapterOptions, createGoogleAdapter, lookupGeminiPricing };
package/dist/index.mjs ADDED
@@ -0,0 +1,382 @@
1
+ import { GoogleGenAI } from '@google/genai';
2
+ import { computeChatCost, wrapProviderError, stringifyContentBlocks, tryParsePartialJSON, extractJSON, attemptValidationRepair, failValidation, validateImageBlocks, ContentBlockUnsupportedError } from '@llm-ports/core';
3
+
4
+ // src/adapter.ts
5
+ var ADAPTER_NAME = "google";
6
+ function toGeminiParts(block) {
7
+ switch (block.type) {
8
+ case "text":
9
+ return [{ text: block.text }];
10
+ case "image": {
11
+ if (block.source.kind === "base64") {
12
+ return [
13
+ {
14
+ inlineData: {
15
+ mimeType: block.source.mediaType,
16
+ data: block.source.data
17
+ }
18
+ }
19
+ ];
20
+ }
21
+ return [
22
+ {
23
+ fileData: {
24
+ // Gemini infers mimeType from URL extension when not provided.
25
+ // We pass image/jpeg as a sane default; users wanting tighter
26
+ // control should pass base64 with explicit mediaType.
27
+ mimeType: "image/jpeg",
28
+ fileUri: block.source.url
29
+ }
30
+ }
31
+ ];
32
+ }
33
+ case "audio": {
34
+ if (block.source.kind === "base64") {
35
+ return [
36
+ {
37
+ inlineData: {
38
+ mimeType: block.source.mediaType,
39
+ data: block.source.data
40
+ }
41
+ }
42
+ ];
43
+ }
44
+ throw new ContentBlockUnsupportedError(ADAPTER_NAME, "audio (url; Gemini accepts base64 or fileData with fileUri)");
45
+ }
46
+ case "tool_use": {
47
+ const args = block.input !== null && typeof block.input === "object" ? block.input : { value: block.input };
48
+ return [{ functionCall: { name: block.name, args } }];
49
+ }
50
+ case "tool_result": {
51
+ const response = typeof block.content === "string" ? { result: block.content } : { result: extractTextOnly(block.content) };
52
+ return [
53
+ {
54
+ functionResponse: {
55
+ // Gemini's API requires the tool name; we use toolUseId since
56
+ // llm-ports' ToolResultBlock doesn't carry the name. Adapters
57
+ // that need the name can plumb it through a separate channel.
58
+ name: block.toolUseId,
59
+ response
60
+ }
61
+ }
62
+ ];
63
+ }
64
+ }
65
+ }
66
+ function extractTextOnly(blocks) {
67
+ return blocks.filter((b) => b.type === "text").map((b) => b.text).join("\n");
68
+ }
69
+ function toGeminiParts2(content) {
70
+ if (typeof content === "string") {
71
+ return [{ text: content }];
72
+ }
73
+ return content.flatMap(toGeminiParts);
74
+ }
75
+ function toGeminiRequest(messages) {
76
+ let systemInstruction;
77
+ const contents = [];
78
+ for (const msg of messages) {
79
+ if (msg.role === "system") {
80
+ const text = typeof msg.content === "string" ? msg.content : extractTextOnly(msg.content);
81
+ systemInstruction = systemInstruction === void 0 ? text : `${systemInstruction}
82
+
83
+ ${text}`;
84
+ continue;
85
+ }
86
+ const role = msg.role === "tool" ? "function" : msg.role === "assistant" ? "model" : "user";
87
+ contents.push({
88
+ role,
89
+ parts: toGeminiParts2(msg.content)
90
+ });
91
+ }
92
+ return systemInstruction !== void 0 ? { systemInstruction, contents } : { contents };
93
+ }
94
+ function extractGeminiText(parts) {
95
+ if (!parts) return "";
96
+ return parts.filter((p) => "text" in p).map((p) => p.text).join("");
97
+ }
98
+
99
+ // src/pricing.ts
100
+ var GEMINI_PRICING = {
101
+ // Gemini 2.5 family (2026-05 GA pricing)
102
+ "gemini-2.5-pro": {
103
+ inputPer1M: 1.25,
104
+ outputPer1M: 5,
105
+ cacheReadPer1M: 0.3125
106
+ },
107
+ "gemini-2.5-flash": {
108
+ inputPer1M: 0.075,
109
+ outputPer1M: 0.3,
110
+ cacheReadPer1M: 0.01875
111
+ },
112
+ "gemini-2.5-flash-lite": {
113
+ inputPer1M: 0.0375,
114
+ outputPer1M: 0.15,
115
+ cacheReadPer1M: 9375e-6
116
+ },
117
+ // Gemini 2.0 family (still available)
118
+ "gemini-2.0-flash": {
119
+ inputPer1M: 0.1,
120
+ outputPer1M: 0.4,
121
+ cacheReadPer1M: 0.025
122
+ },
123
+ "gemini-2.0-flash-lite": {
124
+ inputPer1M: 0.075,
125
+ outputPer1M: 0.3
126
+ }
127
+ };
128
+ function lookupGeminiPricing(modelId) {
129
+ return GEMINI_PRICING[modelId];
130
+ }
131
+
132
+ // src/adapter.ts
133
+ function pricingFor(ctx, modelId) {
134
+ const pricing = ctx.pricingOverrides[modelId] ?? GEMINI_PRICING[modelId];
135
+ if (!pricing) {
136
+ throw new Error(
137
+ `No pricing entry for Google Gemini model "${modelId}". Provide pricingOverrides or update src/pricing.ts.`
138
+ );
139
+ }
140
+ return pricing;
141
+ }
142
+ function createGoogleAdapter(opts) {
143
+ const mergedPricing = {
144
+ ...GEMINI_PRICING,
145
+ ...opts.pricingOverrides ?? {}
146
+ };
147
+ const ctx = {
148
+ client: new GoogleGenAI({ apiKey: opts.apiKey }),
149
+ validationStrategy: opts.validationStrategy ?? {
150
+ kind: "retry-with-feedback",
151
+ maxAttempts: 2,
152
+ includeOriginalError: true
153
+ },
154
+ pricingOverrides: opts.pricingOverrides ?? {},
155
+ imageSizeLimitBytes: opts.imageSizeLimitBytes ?? 20 * 1024 * 1024
156
+ };
157
+ return {
158
+ name: "google",
159
+ pricing: mergedPricing,
160
+ createLLMPort: (modelId, alias) => createPort(ctx, modelId, alias)
161
+ };
162
+ }
163
+ function createPort(ctx, modelId, alias) {
164
+ const pricing = pricingFor(ctx, modelId);
165
+ const validateContent = (content) => {
166
+ if (Array.isArray(content)) {
167
+ validateImageBlocks(content, {
168
+ alias,
169
+ ...ctx.imageSizeLimitBytes > 0 ? { limitBytes: ctx.imageSizeLimitBytes } : {}
170
+ });
171
+ }
172
+ };
173
+ const validateMessages = (messages) => {
174
+ for (const msg of messages) validateContent(msg.content);
175
+ };
176
+ return {
177
+ async generateText(options) {
178
+ validateContent(options.prompt);
179
+ const start = Date.now();
180
+ try {
181
+ const parts = toGeminiParts2(options.prompt);
182
+ const response = await ctx.client.models.generateContent({
183
+ model: modelId,
184
+ contents: [{ role: "user", parts }],
185
+ config: {
186
+ ...options.instructions !== void 0 ? { systemInstruction: options.instructions } : {},
187
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {},
188
+ ...options.maxOutputTokens !== void 0 ? { maxOutputTokens: options.maxOutputTokens } : {}
189
+ }
190
+ });
191
+ const candidate = response.candidates?.[0];
192
+ const text = extractGeminiText(candidate?.content?.parts);
193
+ const usage = parseUsage(response);
194
+ return {
195
+ text,
196
+ usage,
197
+ cost: computeChatCost(usage, pricing),
198
+ modelId: response.modelVersion ?? modelId,
199
+ providerAlias: alias,
200
+ latencyMs: Date.now() - start
201
+ };
202
+ } catch (err) {
203
+ throw wrapProviderError(alias, err);
204
+ }
205
+ },
206
+ async generateStructured(options) {
207
+ validateContent(options.prompt);
208
+ const start = Date.now();
209
+ let attempts = 0;
210
+ const maxAttempts = ctx.validationStrategy.kind === "retry-with-feedback" ? ctx.validationStrategy.maxAttempts : 1;
211
+ let correctionPrompt = null;
212
+ let lastUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
213
+ let lastModelId = modelId;
214
+ while (attempts < maxAttempts) {
215
+ attempts++;
216
+ const userText = correctionPrompt ? `${stringifyContentBlocks(options.prompt)}
217
+
218
+ ${correctionPrompt}` : `${stringifyContentBlocks(options.prompt)}
219
+
220
+ Reply with a single JSON object only. No prose, no code fences.`;
221
+ try {
222
+ const response = await ctx.client.models.generateContent({
223
+ model: modelId,
224
+ contents: [{ role: "user", parts: [{ text: userText }] }],
225
+ config: {
226
+ ...options.instructions !== void 0 ? { systemInstruction: options.instructions } : {},
227
+ temperature: options.temperature ?? 0,
228
+ ...options.maxOutputTokens !== void 0 ? { maxOutputTokens: options.maxOutputTokens } : {},
229
+ responseMimeType: "application/json"
230
+ }
231
+ });
232
+ const candidate = response.candidates?.[0];
233
+ const raw = extractGeminiText(candidate?.content?.parts);
234
+ lastUsage = parseUsage(response);
235
+ lastModelId = response.modelVersion ?? modelId;
236
+ const decoded = extractJSON(raw);
237
+ let parsed = options.schema.safeParse(decoded);
238
+ if (!parsed.success) {
239
+ const repaired = attemptValidationRepair(decoded, parsed.error);
240
+ const reparsed = options.schema.safeParse(repaired);
241
+ if (reparsed.success) parsed = reparsed;
242
+ }
243
+ if (parsed.success) {
244
+ return {
245
+ data: parsed.data,
246
+ usage: lastUsage,
247
+ cost: computeChatCost(lastUsage, pricing),
248
+ modelId: lastModelId,
249
+ providerAlias: alias,
250
+ latencyMs: Date.now() - start,
251
+ validationAttempts: attempts
252
+ };
253
+ }
254
+ if (ctx.validationStrategy.kind === "retry-with-feedback" && attempts < maxAttempts) {
255
+ const issues = parsed.error.issues.map((i) => `- ${i.path.join(".") || "<root>"}: ${i.message}`).join("\n");
256
+ correctionPrompt = `Your previous response failed validation:
257
+ ${issues}
258
+
259
+ Reply with a single corrected JSON object only.`;
260
+ continue;
261
+ }
262
+ failValidation(parsed.error.issues, attempts);
263
+ } catch (err) {
264
+ throw wrapProviderError(alias, err);
265
+ }
266
+ }
267
+ throw new Error("generateStructured exhausted attempts");
268
+ },
269
+ async *streamText(options) {
270
+ validateContent(options.prompt);
271
+ try {
272
+ const parts = toGeminiParts2(options.prompt);
273
+ const stream = await ctx.client.models.generateContentStream({
274
+ model: modelId,
275
+ contents: [{ role: "user", parts }],
276
+ config: {
277
+ ...options.instructions !== void 0 ? { systemInstruction: options.instructions } : {},
278
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {},
279
+ ...options.maxOutputTokens !== void 0 ? { maxOutputTokens: options.maxOutputTokens } : {}
280
+ }
281
+ });
282
+ for await (const chunk of stream) {
283
+ const text = extractGeminiText(
284
+ chunk.candidates?.[0]?.content?.parts
285
+ );
286
+ if (text.length > 0) yield text;
287
+ }
288
+ } catch (err) {
289
+ throw wrapProviderError(alias, err);
290
+ }
291
+ },
292
+ async *streamStructured(options) {
293
+ validateContent(options.prompt);
294
+ try {
295
+ const stream = await ctx.client.models.generateContentStream({
296
+ model: modelId,
297
+ contents: [
298
+ {
299
+ role: "user",
300
+ parts: [
301
+ {
302
+ text: `${stringifyContentBlocks(options.prompt)}
303
+
304
+ Reply with a single JSON object only. Stream the JSON progressively.`
305
+ }
306
+ ]
307
+ }
308
+ ],
309
+ config: {
310
+ ...options.instructions !== void 0 ? { systemInstruction: options.instructions } : {},
311
+ temperature: options.temperature ?? 0,
312
+ ...options.maxOutputTokens !== void 0 ? { maxOutputTokens: options.maxOutputTokens } : {},
313
+ responseMimeType: "application/json"
314
+ }
315
+ });
316
+ let buffer = "";
317
+ for await (const chunk of stream) {
318
+ const text = extractGeminiText(
319
+ chunk.candidates?.[0]?.content?.parts
320
+ );
321
+ if (text.length === 0) continue;
322
+ buffer += text;
323
+ const partial = tryParsePartialJSON(buffer);
324
+ if (partial !== null) yield partial;
325
+ }
326
+ } catch (err) {
327
+ throw wrapProviderError(alias, err);
328
+ }
329
+ },
330
+ async runAgent(options) {
331
+ validateMessages(options.messages);
332
+ const start = Date.now();
333
+ try {
334
+ const { systemInstruction, contents } = toGeminiRequest(options.messages);
335
+ const response = await ctx.client.models.generateContent({
336
+ model: modelId,
337
+ contents,
338
+ config: {
339
+ ...systemInstruction !== void 0 ? { systemInstruction } : {},
340
+ ...options.instructions !== void 0 ? { systemInstruction: options.instructions } : {}
341
+ }
342
+ });
343
+ const candidate = response.candidates?.[0];
344
+ const text = extractGeminiText(candidate?.content?.parts);
345
+ const usage = parseUsage(response);
346
+ const toolCalls = [];
347
+ return {
348
+ text,
349
+ messages: [
350
+ ...options.messages,
351
+ { role: "assistant", content: text }
352
+ ],
353
+ usage,
354
+ cost: computeChatCost(usage, pricing),
355
+ modelId: response.modelVersion ?? modelId,
356
+ providerAlias: alias,
357
+ latencyMs: Date.now() - start,
358
+ toolCalls,
359
+ stepsTaken: 1,
360
+ terminationReason: "completed"
361
+ };
362
+ } catch (err) {
363
+ throw wrapProviderError(alias, err);
364
+ }
365
+ }
366
+ };
367
+ }
368
+ function parseUsage(response) {
369
+ const m = response.usageMetadata ?? {};
370
+ const inputTokens = m.promptTokenCount ?? 0;
371
+ const outputTokens = m.candidatesTokenCount ?? 0;
372
+ const totalTokens = m.totalTokenCount ?? inputTokens + outputTokens;
373
+ const usage = { inputTokens, outputTokens, totalTokens };
374
+ if (m.cachedContentTokenCount !== void 0 && m.cachedContentTokenCount > 0) {
375
+ usage.cacheReadTokens = m.cachedContentTokenCount;
376
+ }
377
+ return usage;
378
+ }
379
+
380
+ export { GEMINI_PRICING, createGoogleAdapter, lookupGeminiPricing };
381
+ //# sourceMappingURL=index.mjs.map
382
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/content.ts","../src/pricing.ts","../src/adapter.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\r\n * Content translation between llm-ports ContentBlock[] and Gemini's\r\n * Content + Part shapes.\r\n *\r\n * Gemini's content model:\r\n * Content = { role: \"user\" | \"model\" | \"function\", parts: Part[] }\r\n * Part shapes (the ones we care about):\r\n * - { text: string }\r\n * - { inlineData: { mimeType: string, data: string (base64) } }\r\n * - { fileData: { mimeType: string, fileUri: string } }\r\n * - { functionCall: { name: string, args: object } }\r\n * - { functionResponse: { name: string, response: object } }\r\n *\r\n * Notes:\r\n * - System messages map to a top-level `systemInstruction` field on the\r\n * request, NOT a Content with role: \"system\". `toGeminiRequest` handles\r\n * this split.\r\n * - The assistant role is `\"model\"` in Gemini's vocabulary.\r\n * - Tool results are mapped to `functionResponse` parts.\r\n */\r\n\r\nimport {\r\n ContentBlockUnsupportedError,\r\n type ContentBlock,\r\n type LLMMessage,\r\n type MessageContent,\r\n} from \"@llm-ports/core\";\r\n\r\nconst ADAPTER_NAME = \"google\";\r\n\r\n// ─── Outgoing: ContentBlock[] → Gemini Part[] ────────────────────────\r\n\r\ninterface GeminiTextPart {\r\n text: string;\r\n}\r\ninterface GeminiInlineDataPart {\r\n inlineData: { mimeType: string; data: string };\r\n}\r\ninterface GeminiFileDataPart {\r\n fileData: { mimeType: string; fileUri: string };\r\n}\r\ninterface GeminiFunctionCallPart {\r\n functionCall: { name: string; args: Record<string, unknown> };\r\n}\r\ninterface GeminiFunctionResponsePart {\r\n functionResponse: { name: string; response: Record<string, unknown> };\r\n}\r\nexport type GeminiPart =\r\n | GeminiTextPart\r\n | GeminiInlineDataPart\r\n | GeminiFileDataPart\r\n | GeminiFunctionCallPart\r\n | GeminiFunctionResponsePart;\r\n\r\nexport interface GeminiContent {\r\n role: \"user\" | \"model\" | \"function\";\r\n parts: GeminiPart[];\r\n}\r\n\r\n/** Translate a single ContentBlock to one or more GeminiParts. */\r\nfunction toGeminiParts(block: ContentBlock): GeminiPart[] {\r\n switch (block.type) {\r\n case \"text\":\r\n return [{ text: block.text }];\r\n case \"image\": {\r\n if (block.source.kind === \"base64\") {\r\n return [\r\n {\r\n inlineData: {\r\n mimeType: block.source.mediaType,\r\n data: block.source.data,\r\n },\r\n },\r\n ];\r\n }\r\n // URL form\r\n return [\r\n {\r\n fileData: {\r\n // Gemini infers mimeType from URL extension when not provided.\r\n // We pass image/jpeg as a sane default; users wanting tighter\r\n // control should pass base64 with explicit mediaType.\r\n mimeType: \"image/jpeg\",\r\n fileUri: block.source.url,\r\n },\r\n },\r\n ];\r\n }\r\n case \"audio\": {\r\n if (block.source.kind === \"base64\") {\r\n return [\r\n {\r\n inlineData: {\r\n mimeType: block.source.mediaType,\r\n data: block.source.data,\r\n },\r\n },\r\n ];\r\n }\r\n throw new ContentBlockUnsupportedError(ADAPTER_NAME, \"audio (url; Gemini accepts base64 or fileData with fileUri)\");\r\n }\r\n case \"tool_use\": {\r\n // Gemini's tool-call shape has args as a plain object; we pass the input\r\n // through if it's already an object, else wrap.\r\n const args =\r\n block.input !== null && typeof block.input === \"object\"\r\n ? (block.input as Record<string, unknown>)\r\n : { value: block.input };\r\n return [{ functionCall: { name: block.name, args } }];\r\n }\r\n case \"tool_result\": {\r\n // Gemini's functionResponse expects a `response` object. If the\r\n // ContentBlock.tool_result.content is a string, wrap it.\r\n const response: Record<string, unknown> =\r\n typeof block.content === \"string\"\r\n ? { result: block.content }\r\n : { result: extractTextOnly(block.content) };\r\n return [\r\n {\r\n functionResponse: {\r\n // Gemini's API requires the tool name; we use toolUseId since\r\n // llm-ports' ToolResultBlock doesn't carry the name. Adapters\r\n // that need the name can plumb it through a separate channel.\r\n name: block.toolUseId,\r\n response,\r\n },\r\n },\r\n ];\r\n }\r\n }\r\n}\r\n\r\nfunction extractTextOnly(blocks: ContentBlock[]): string {\r\n return blocks\r\n .filter((b): b is Extract<ContentBlock, { type: \"text\" }> => b.type === \"text\")\r\n .map((b) => b.text)\r\n .join(\"\\n\");\r\n}\r\n\r\n/** Translate a MessageContent to Gemini Parts. */\r\nexport function toGeminiParts2(content: MessageContent): GeminiPart[] {\r\n if (typeof content === \"string\") {\r\n return [{ text: content }];\r\n }\r\n return content.flatMap(toGeminiParts);\r\n}\r\n\r\n/**\r\n * Translate an array of LLMMessages into:\r\n * - `systemInstruction`: the concatenated system messages (Gemini puts\r\n * these at the top level of the request, not in `contents`).\r\n * - `contents`: the user + assistant + tool messages, with roles mapped.\r\n */\r\nexport function toGeminiRequest(messages: LLMMessage[]): {\r\n systemInstruction?: string;\r\n contents: GeminiContent[];\r\n} {\r\n let systemInstruction: string | undefined;\r\n const contents: GeminiContent[] = [];\r\n for (const msg of messages) {\r\n if (msg.role === \"system\") {\r\n const text =\r\n typeof msg.content === \"string\" ? msg.content : extractTextOnly(msg.content);\r\n systemInstruction = systemInstruction === undefined ? text : `${systemInstruction}\\n\\n${text}`;\r\n continue;\r\n }\r\n const role: GeminiContent[\"role\"] =\r\n msg.role === \"tool\" ? \"function\" : msg.role === \"assistant\" ? \"model\" : \"user\";\r\n contents.push({\r\n role,\r\n parts: toGeminiParts2(msg.content),\r\n });\r\n }\r\n return systemInstruction !== undefined\r\n ? { systemInstruction, contents }\r\n : { contents };\r\n}\r\n\r\n// ─── Incoming: Gemini response → ContentBlock[] ──────────────────────\r\n\r\ninterface GeminiResponseCandidate {\r\n content?: {\r\n role?: string;\r\n parts?: GeminiPart[];\r\n };\r\n finishReason?: string;\r\n}\r\n\r\n/**\r\n * Extract the assistant text from a Gemini response. Used by generateText\r\n * and by the structured-output path before JSON parsing.\r\n */\r\nexport function extractGeminiText(parts: GeminiPart[] | undefined): string {\r\n if (!parts) return \"\";\r\n return parts\r\n .filter((p): p is GeminiTextPart => \"text\" in p)\r\n .map((p) => p.text)\r\n .join(\"\");\r\n}\r\n\r\n/**\r\n * Translate a Gemini response candidate's parts back into ContentBlock[].\r\n * Used by runAgent to reconstruct the model's tool_use blocks.\r\n */\r\nexport function fromGeminiCandidate(candidate: GeminiResponseCandidate): ContentBlock[] {\r\n const out: ContentBlock[] = [];\r\n const parts = candidate.content?.parts ?? [];\r\n for (const part of parts) {\r\n if (\"text\" in part && part.text.length > 0) {\r\n out.push({ type: \"text\", text: part.text });\r\n } else if (\"functionCall\" in part) {\r\n out.push({\r\n type: \"tool_use\",\r\n id: `gemini-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\r\n name: part.functionCall.name,\r\n input: part.functionCall.args,\r\n });\r\n } else if (\"inlineData\" in part) {\r\n // Inline (base64) image in assistant response. Decode if media type\r\n // is one we support, else drop (consistent with adapter-openai's\r\n // unknown-media-type behavior).\r\n const mt = part.inlineData.mimeType;\r\n if (\r\n mt === \"image/jpeg\" ||\r\n mt === \"image/png\" ||\r\n mt === \"image/gif\" ||\r\n mt === \"image/webp\"\r\n ) {\r\n out.push({\r\n type: \"image\",\r\n source: { kind: \"base64\", mediaType: mt, data: part.inlineData.data },\r\n });\r\n }\r\n }\r\n // fileData / functionResponse in assistant responses: not currently observed.\r\n }\r\n return out;\r\n}\r\n","/**\r\n * Bundled pricing for Google Gemini models.\r\n *\r\n * Source: https://ai.google.dev/gemini-api/docs/pricing (verified 2026-05).\r\n * Override per model via `pricingOverrides` on the adapter options.\r\n *\r\n * Gemini pricing has separate tiers for prompts under 200k tokens vs over\r\n * 200k tokens (the \"long-context premium\"). The bundled values are the\r\n * UNDER-200k-token rates, which dominate typical usage. For long-context\r\n * workloads, supply `pricingOverrides` with the over-200k rates.\r\n */\r\n\r\nimport type { ModelPricing } from \"@llm-ports/core\";\r\n\r\nexport const GEMINI_PRICING: Record<string, ModelPricing> = {\r\n // Gemini 2.5 family (2026-05 GA pricing)\r\n \"gemini-2.5-pro\": {\r\n inputPer1M: 1.25,\r\n outputPer1M: 5.0,\r\n cacheReadPer1M: 0.3125,\r\n },\r\n \"gemini-2.5-flash\": {\r\n inputPer1M: 0.075,\r\n outputPer1M: 0.3,\r\n cacheReadPer1M: 0.01875,\r\n },\r\n \"gemini-2.5-flash-lite\": {\r\n inputPer1M: 0.0375,\r\n outputPer1M: 0.15,\r\n cacheReadPer1M: 0.009375,\r\n },\r\n // Gemini 2.0 family (still available)\r\n \"gemini-2.0-flash\": {\r\n inputPer1M: 0.1,\r\n outputPer1M: 0.4,\r\n cacheReadPer1M: 0.025,\r\n },\r\n \"gemini-2.0-flash-lite\": {\r\n inputPer1M: 0.075,\r\n outputPer1M: 0.3,\r\n },\r\n};\r\n\r\nexport function lookupGeminiPricing(modelId: string): ModelPricing | undefined {\r\n return GEMINI_PRICING[modelId];\r\n}\r\n","/**\r\n * Google Gemini adapter for llm-ports.\r\n *\r\n * Wraps @google/genai (the unified Gemini + Vertex SDK as of 2026) to\r\n * implement LLMPort. Provides:\r\n *\r\n * - Native multimodal: image content blocks pass through as inlineData\r\n * (base64) or fileData (URL). NO degradation, unlike OpenAI-compat\r\n * baseURL where image_url.detail is silently ignored.\r\n * - Native streaming via generateContentStream\r\n * - Structured output via prompted-JSON + Zod retry-with-feedback\r\n * + alpha.5 programmatic repair. Native Gemini responseSchema lands\r\n * in v0.2.\r\n * - Image-block boundary validation (size + URL scheme) — same shape\r\n * as adapter-anthropic and adapter-openai (alpha.5).\r\n *\r\n * Out of scope for v0.1 alpha:\r\n * - Embeddings (Gemini's embedding API is separate; lands in v0.2)\r\n * - Multi-turn runAgent through Gemini's native automatic tool calling\r\n * (v0.1 ships a single-turn shim consistent with adapter-vercel)\r\n * - Caching API (Gemini supports explicit context caching; lands in v0.2)\r\n * - Code execution tool (Gemini's built-in code interpreter; lands in v0.2)\r\n */\r\n\r\nimport { GoogleGenAI } from \"@google/genai\";\r\nimport {\r\n attemptValidationRepair,\r\n computeChatCost,\r\n extractJSON,\r\n failValidation,\r\n stringifyContentBlocks,\r\n tryParsePartialJSON,\r\n validateImageBlocks,\r\n wrapProviderError,\r\n type AgentResult,\r\n type ContentBlock,\r\n type GenerateStructuredOptions,\r\n type GenerateStructuredResult,\r\n type GenerateTextOptions,\r\n type GenerateTextResult,\r\n type LLMPort,\r\n type MessageContent,\r\n type ModelPricing,\r\n type RunAgentOptions,\r\n type StreamStructuredOptions,\r\n type StreamTextOptions,\r\n type TokenUsage,\r\n type ValidationStrategy,\r\n} from \"@llm-ports/core\";\r\nimport {\r\n extractGeminiText,\r\n toGeminiParts2,\r\n toGeminiRequest,\r\n type GeminiPart,\r\n} from \"./content.js\";\r\nimport { GEMINI_PRICING } from \"./pricing.js\";\r\n\r\n// ─── Adapter options ─────────────────────────────────────────────────\r\n\r\nexport interface GoogleAdapterOptions {\r\n /** Google AI API key (https://aistudio.google.com/apikey). */\r\n apiKey: string;\r\n /** Override Gemini pricing for any model id. Falls back to the bundled table. */\r\n pricingOverrides?: Record<string, ModelPricing>;\r\n /** Default validation strategy if the registry doesn't override per-call. */\r\n validationStrategy?: ValidationStrategy;\r\n /**\r\n * Maximum bytes per base64 image. Defaults to 20MB (Gemini accepts up to\r\n * 20MB inlined; fileData URLs are unconstrained but provider-fetched).\r\n * Set to 0 or a negative number to disable size validation.\r\n */\r\n imageSizeLimitBytes?: number;\r\n}\r\n\r\n// ─── Internal context ────────────────────────────────────────────────\r\n\r\ninterface AdapterContext {\r\n client: GoogleGenAI;\r\n validationStrategy: ValidationStrategy;\r\n pricingOverrides: Record<string, ModelPricing>;\r\n imageSizeLimitBytes: number;\r\n}\r\n\r\nfunction pricingFor(ctx: AdapterContext, modelId: string): ModelPricing {\r\n const pricing = ctx.pricingOverrides[modelId] ?? GEMINI_PRICING[modelId];\r\n if (!pricing) {\r\n throw new Error(\r\n `No pricing entry for Google Gemini model \"${modelId}\". Provide pricingOverrides or update src/pricing.ts.`,\r\n );\r\n }\r\n return pricing;\r\n}\r\n\r\n// ─── Public factory ──────────────────────────────────────────────────\r\n\r\nexport interface GoogleAdapter {\r\n name: \"google\";\r\n pricing: Record<string, ModelPricing>;\r\n createLLMPort: (modelId: string, alias: string) => LLMPort;\r\n}\r\n\r\nexport function createGoogleAdapter(opts: GoogleAdapterOptions): GoogleAdapter {\r\n const mergedPricing: Record<string, ModelPricing> = {\r\n ...GEMINI_PRICING,\r\n ...(opts.pricingOverrides ?? {}),\r\n };\r\n const ctx: AdapterContext = {\r\n client: new GoogleGenAI({ apiKey: opts.apiKey }),\r\n validationStrategy: opts.validationStrategy ?? {\r\n kind: \"retry-with-feedback\",\r\n maxAttempts: 2,\r\n includeOriginalError: true,\r\n },\r\n pricingOverrides: opts.pricingOverrides ?? {},\r\n imageSizeLimitBytes: opts.imageSizeLimitBytes ?? 20 * 1024 * 1024,\r\n };\r\n return {\r\n name: \"google\",\r\n pricing: mergedPricing,\r\n createLLMPort: (modelId, alias) => createPort(ctx, modelId, alias),\r\n };\r\n}\r\n\r\n// ─── Port implementation ─────────────────────────────────────────────\r\n\r\nfunction createPort(ctx: AdapterContext, modelId: string, alias: string): LLMPort {\r\n const pricing = pricingFor(ctx, modelId);\r\n\r\n // Image-block validation closure: throws ImageTooLargeError or\r\n // InvalidImageUrlError before the SDK call.\r\n const validateContent = (content: MessageContent): void => {\r\n if (Array.isArray(content)) {\r\n validateImageBlocks(content, {\r\n alias,\r\n ...(ctx.imageSizeLimitBytes > 0 ? { limitBytes: ctx.imageSizeLimitBytes } : {}),\r\n });\r\n }\r\n };\r\n const validateMessages = (messages: ReadonlyArray<{ content: MessageContent }>): void => {\r\n for (const msg of messages) validateContent(msg.content);\r\n };\r\n\r\n return {\r\n async generateText(options: GenerateTextOptions): Promise<GenerateTextResult> {\r\n validateContent(options.prompt);\r\n const start = Date.now();\r\n try {\r\n const parts = toGeminiParts2(options.prompt);\r\n const response = await ctx.client.models.generateContent({\r\n model: modelId,\r\n contents: [{ role: \"user\", parts }],\r\n config: {\r\n ...(options.instructions !== undefined\r\n ? { systemInstruction: options.instructions }\r\n : {}),\r\n ...(options.temperature !== undefined ? { temperature: options.temperature } : {}),\r\n ...(options.maxOutputTokens !== undefined\r\n ? { maxOutputTokens: options.maxOutputTokens }\r\n : {}),\r\n },\r\n });\r\n const candidate = response.candidates?.[0];\r\n const text = extractGeminiText(candidate?.content?.parts as GeminiPart[] | undefined);\r\n const usage = parseUsage(response);\r\n return {\r\n text,\r\n usage,\r\n cost: computeChatCost(usage, pricing),\r\n modelId: response.modelVersion ?? modelId,\r\n providerAlias: alias,\r\n latencyMs: Date.now() - start,\r\n };\r\n } catch (err) {\r\n throw wrapProviderError(alias, err);\r\n }\r\n },\r\n\r\n async generateStructured<T>(\r\n options: GenerateStructuredOptions<T>,\r\n ): Promise<GenerateStructuredResult<T>> {\r\n validateContent(options.prompt);\r\n const start = Date.now();\r\n let attempts = 0;\r\n const maxAttempts =\r\n ctx.validationStrategy.kind === \"retry-with-feedback\"\r\n ? ctx.validationStrategy.maxAttempts\r\n : 1;\r\n\r\n let correctionPrompt: string | null = null;\r\n let lastUsage: TokenUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };\r\n let lastModelId = modelId;\r\n\r\n while (attempts < maxAttempts) {\r\n attempts++;\r\n const userText = correctionPrompt\r\n ? `${stringifyContentBlocks(options.prompt)}\\n\\n${correctionPrompt}`\r\n : `${stringifyContentBlocks(options.prompt)}\\n\\nReply with a single JSON object only. No prose, no code fences.`;\r\n\r\n try {\r\n const response = await ctx.client.models.generateContent({\r\n model: modelId,\r\n contents: [{ role: \"user\", parts: [{ text: userText }] }],\r\n config: {\r\n ...(options.instructions !== undefined\r\n ? { systemInstruction: options.instructions }\r\n : {}),\r\n temperature: options.temperature ?? 0,\r\n ...(options.maxOutputTokens !== undefined\r\n ? { maxOutputTokens: options.maxOutputTokens }\r\n : {}),\r\n responseMimeType: \"application/json\",\r\n },\r\n });\r\n const candidate = response.candidates?.[0];\r\n const raw = extractGeminiText(candidate?.content?.parts as GeminiPart[] | undefined);\r\n lastUsage = parseUsage(response);\r\n lastModelId = response.modelVersion ?? modelId;\r\n\r\n const decoded = extractJSON(raw);\r\n let parsed = options.schema.safeParse(decoded);\r\n if (!parsed.success) {\r\n const repaired = attemptValidationRepair(decoded, parsed.error);\r\n const reparsed = options.schema.safeParse(repaired);\r\n if (reparsed.success) parsed = reparsed;\r\n }\r\n if (parsed.success) {\r\n return {\r\n data: parsed.data as T,\r\n usage: lastUsage,\r\n cost: computeChatCost(lastUsage, pricing),\r\n modelId: lastModelId,\r\n providerAlias: alias,\r\n latencyMs: Date.now() - start,\r\n validationAttempts: attempts,\r\n };\r\n }\r\n if (\r\n ctx.validationStrategy.kind === \"retry-with-feedback\" &&\r\n attempts < maxAttempts\r\n ) {\r\n const issues = parsed.error.issues\r\n .map((i) => `- ${i.path.join(\".\") || \"<root>\"}: ${i.message}`)\r\n .join(\"\\n\");\r\n correctionPrompt = `Your previous response failed validation:\\n${issues}\\n\\nReply with a single corrected JSON object only.`;\r\n continue;\r\n }\r\n failValidation(parsed.error.issues, attempts);\r\n } catch (err) {\r\n throw wrapProviderError(alias, err);\r\n }\r\n }\r\n throw new Error(\"generateStructured exhausted attempts\");\r\n },\r\n\r\n async *streamText(options: StreamTextOptions): AsyncIterable<string> {\r\n validateContent(options.prompt);\r\n try {\r\n const parts = toGeminiParts2(options.prompt);\r\n const stream = await ctx.client.models.generateContentStream({\r\n model: modelId,\r\n contents: [{ role: \"user\", parts }],\r\n config: {\r\n ...(options.instructions !== undefined\r\n ? { systemInstruction: options.instructions }\r\n : {}),\r\n ...(options.temperature !== undefined ? { temperature: options.temperature } : {}),\r\n ...(options.maxOutputTokens !== undefined\r\n ? { maxOutputTokens: options.maxOutputTokens }\r\n : {}),\r\n },\r\n });\r\n for await (const chunk of stream) {\r\n const text = extractGeminiText(\r\n chunk.candidates?.[0]?.content?.parts as GeminiPart[] | undefined,\r\n );\r\n if (text.length > 0) yield text;\r\n }\r\n } catch (err) {\r\n throw wrapProviderError(alias, err);\r\n }\r\n },\r\n\r\n async *streamStructured<T>(options: StreamStructuredOptions<T>): AsyncIterable<Partial<T>> {\r\n validateContent(options.prompt);\r\n try {\r\n const stream = await ctx.client.models.generateContentStream({\r\n model: modelId,\r\n contents: [\r\n {\r\n role: \"user\",\r\n parts: [\r\n {\r\n text: `${stringifyContentBlocks(options.prompt)}\\n\\nReply with a single JSON object only. Stream the JSON progressively.`,\r\n },\r\n ],\r\n },\r\n ],\r\n config: {\r\n ...(options.instructions !== undefined\r\n ? { systemInstruction: options.instructions }\r\n : {}),\r\n temperature: options.temperature ?? 0,\r\n ...(options.maxOutputTokens !== undefined\r\n ? { maxOutputTokens: options.maxOutputTokens }\r\n : {}),\r\n responseMimeType: \"application/json\",\r\n },\r\n });\r\n let buffer = \"\";\r\n for await (const chunk of stream) {\r\n const text = extractGeminiText(\r\n chunk.candidates?.[0]?.content?.parts as GeminiPart[] | undefined,\r\n );\r\n if (text.length === 0) continue;\r\n buffer += text;\r\n const partial = tryParsePartialJSON(buffer);\r\n if (partial !== null) yield partial as Partial<T>;\r\n }\r\n } catch (err) {\r\n throw wrapProviderError(alias, err);\r\n }\r\n },\r\n\r\n async runAgent(options: RunAgentOptions): Promise<AgentResult> {\r\n validateMessages(options.messages);\r\n // v0.1: single-turn agent loop. Gemini's native automatic-function-calling\r\n // multi-turn runAgent ships in v0.2 (matches adapter-vercel's v0.1 shape).\r\n const start = Date.now();\r\n try {\r\n const { systemInstruction, contents } = toGeminiRequest(options.messages);\r\n const response = await ctx.client.models.generateContent({\r\n model: modelId,\r\n contents,\r\n config: {\r\n ...(systemInstruction !== undefined ? { systemInstruction } : {}),\r\n ...(options.instructions !== undefined\r\n ? { systemInstruction: options.instructions }\r\n : {}),\r\n },\r\n });\r\n const candidate = response.candidates?.[0];\r\n const text = extractGeminiText(candidate?.content?.parts as GeminiPart[] | undefined);\r\n const usage = parseUsage(response);\r\n const toolCalls: AgentResult[\"toolCalls\"] = [];\r\n // v0.1 stub: we surface no tool calls. Real tool-use is v0.2 scope.\r\n return {\r\n text,\r\n messages: [\r\n ...options.messages,\r\n { role: \"assistant\" as const, content: text },\r\n ],\r\n usage,\r\n cost: computeChatCost(usage, pricing),\r\n modelId: response.modelVersion ?? modelId,\r\n providerAlias: alias,\r\n latencyMs: Date.now() - start,\r\n toolCalls,\r\n stepsTaken: 1,\r\n terminationReason: \"completed\",\r\n };\r\n } catch (err) {\r\n throw wrapProviderError(alias, err);\r\n }\r\n },\r\n };\r\n}\r\n\r\n// ─── Helpers ─────────────────────────────────────────────────────────\r\n\r\ninterface GeminiUsageMetadata {\r\n promptTokenCount?: number;\r\n candidatesTokenCount?: number;\r\n totalTokenCount?: number;\r\n cachedContentTokenCount?: number;\r\n}\r\n\r\ninterface GeminiResponseShape {\r\n usageMetadata?: GeminiUsageMetadata;\r\n}\r\n\r\nfunction parseUsage(response: GeminiResponseShape): TokenUsage {\r\n const m = response.usageMetadata ?? {};\r\n const inputTokens = m.promptTokenCount ?? 0;\r\n const outputTokens = m.candidatesTokenCount ?? 0;\r\n const totalTokens = m.totalTokenCount ?? inputTokens + outputTokens;\r\n const usage: TokenUsage = { inputTokens, outputTokens, totalTokens };\r\n if (m.cachedContentTokenCount !== undefined && m.cachedContentTokenCount > 0) {\r\n usage.cacheReadTokens = m.cachedContentTokenCount;\r\n }\r\n return usage;\r\n}\r\n\r\n// Re-export ContentBlock for the rare adapter user that wants to type-check\r\n// outside of @llm-ports/core. Keeps the import surface symmetric with the\r\n// other adapters.\r\nexport type { ContentBlock };\r\n"]}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@llm-ports/adapter-google",
3
+ "version": "0.1.0-alpha.5",
4
+ "description": "Google Gemini adapter for llm-ports — native @google/genai SDK integration with bundled pricing, content-block translation, validation repair, and image-size + URL validation.",
5
+ "license": "MIT",
6
+ "author": "Babak Abbaschian",
7
+ "type": "module",
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.mjs",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.cjs"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
22
+ "dependencies": {
23
+ "@llm-ports/core": "0.1.0-alpha.5"
24
+ },
25
+ "peerDependencies": {
26
+ "@google/genai": "^2.0.0",
27
+ "zod": ">=3.24.0 <5"
28
+ },
29
+ "devDependencies": {
30
+ "@google/genai": "^2.5.0",
31
+ "tsup": "^8.3.0",
32
+ "typescript": "^5.6.3",
33
+ "vitest": "^2.1.4",
34
+ "zod": "^3.25.76",
35
+ "@llm-ports/adapter-contract-tests": "0.0.0"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/baabakk/llm-ports.git",
43
+ "directory": "packages/adapter-google"
44
+ },
45
+ "keywords": [
46
+ "llm",
47
+ "ai",
48
+ "typescript",
49
+ "google",
50
+ "gemini",
51
+ "google-genai",
52
+ "vision",
53
+ "multimodal",
54
+ "ports-and-adapters",
55
+ "llm-ports",
56
+ "production-llm"
57
+ ],
58
+ "homepage": "https://github.com/baabakk/llm-ports#readme",
59
+ "bugs": {
60
+ "url": "https://github.com/baabakk/llm-ports/issues"
61
+ },
62
+ "engines": {
63
+ "node": ">=18.0.0"
64
+ },
65
+ "scripts": {
66
+ "build": "tsup",
67
+ "dev": "tsup --watch",
68
+ "test": "vitest run",
69
+ "test:watch": "vitest",
70
+ "typecheck": "tsc --noEmit",
71
+ "clean": "rm -rf dist"
72
+ }
73
+ }