libretto 0.3.1 → 0.4.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.
@@ -0,0 +1,438 @@
1
+ const TEST_ATTRS = /* @__PURE__ */ new Set(["data-testid", "data-test", "data-qa", "data-cy"]);
2
+ const TRUSTED_ATTRS = /* @__PURE__ */ new Set([
3
+ "id",
4
+ "name",
5
+ "for",
6
+ "tabindex",
7
+ "contenteditable",
8
+ "role",
9
+ "title",
10
+ "alt",
11
+ "type",
12
+ "value",
13
+ "placeholder",
14
+ "autocomplete",
15
+ "href",
16
+ "action",
17
+ "method",
18
+ "src"
19
+ ]);
20
+ const STATE_ATTRS = /* @__PURE__ */ new Set([
21
+ "disabled",
22
+ "hidden",
23
+ "inert",
24
+ "readonly",
25
+ "required",
26
+ "checked",
27
+ "selected",
28
+ "open",
29
+ "multiple"
30
+ ]);
31
+ const BOOLEAN_ATTRS = /* @__PURE__ */ new Set([
32
+ ...STATE_ATTRS,
33
+ "async",
34
+ "defer",
35
+ "nomodule"
36
+ ]);
37
+ const EMPTY_VALUE_DROP_ATTRS = /* @__PURE__ */ new Set([
38
+ "alt",
39
+ "autocomplete",
40
+ "href",
41
+ "action",
42
+ "method",
43
+ "name",
44
+ "placeholder",
45
+ "src",
46
+ "tabindex",
47
+ "title",
48
+ "type"
49
+ ]);
50
+ const URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "action"]);
51
+ const SCRIPT_ATTRS = /* @__PURE__ */ new Set([
52
+ "src",
53
+ "type",
54
+ "id",
55
+ "defer",
56
+ "async",
57
+ "crossorigin",
58
+ "integrity",
59
+ "nomodule",
60
+ "referrerpolicy"
61
+ ]);
62
+ const STYLE_TAG_ATTRS = /* @__PURE__ */ new Set(["media", "type", "nonce", "title"]);
63
+ const INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
64
+ "a",
65
+ "button",
66
+ "input",
67
+ "select",
68
+ "textarea",
69
+ "form",
70
+ "details",
71
+ "dialog",
72
+ "label"
73
+ ]);
74
+ const INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
75
+ "button",
76
+ "link",
77
+ "tab",
78
+ "menuitem",
79
+ "checkbox",
80
+ "radio",
81
+ "switch",
82
+ "slider",
83
+ "combobox"
84
+ ]);
85
+ const OPEN_TAG_PATTERN = /<([a-zA-Z][\w:-]*)(\s(?:[^"'<>/]|"[^"]*"|'[^']*')*)?\s*(\/?)>/g;
86
+ function condenseDom(html) {
87
+ const originalLength = html.length;
88
+ const reductions = {};
89
+ function track(label, before, after) {
90
+ const diff = before.length - after.length;
91
+ if (diff > 0) {
92
+ reductions[label] = (reductions[label] ?? 0) + diff;
93
+ }
94
+ return after;
95
+ }
96
+ let result = html;
97
+ result = track(
98
+ "noscript",
99
+ result,
100
+ result.replace(/<noscript\b[^>]*>[\s\S]*?<\/noscript>/gi, "")
101
+ );
102
+ result = track(
103
+ "comments",
104
+ result,
105
+ result.replace(/<!--[\s\S]*?(?:-->|$)/g, "")
106
+ );
107
+ result = track(
108
+ "scripts",
109
+ result,
110
+ result.replace(
111
+ /(<script\b[^>]*>)([\s\S]*?)(<\/script(?:\s[^>]*)?>)/gi,
112
+ (_match, open, content, close) => {
113
+ if (!content.trim()) return `${open}${close}`;
114
+ const isDataScript = /type\s*=\s*["']application\/(json|ld\+json)["']/i.test(open);
115
+ if (isDataScript) {
116
+ return `${open}[JSON data, ${content.length} chars]${close}`;
117
+ }
118
+ return `${open}[script, ${content.length} chars]${close}`;
119
+ }
120
+ )
121
+ );
122
+ result = track(
123
+ "styles",
124
+ result,
125
+ result.replace(
126
+ /(<style\b[^>]*>)([\s\S]*?)(<\/style(?:\s[^>]*)?>)/gi,
127
+ (_match, open, content, close) => {
128
+ if (!content.trim()) return `${open}${close}`;
129
+ return `${open}[CSS, ${content.length} chars]${close}`;
130
+ }
131
+ )
132
+ );
133
+ result = track(
134
+ "base64",
135
+ result,
136
+ result.replace(
137
+ /(src|href)\s*=\s*["'](data:[^;]+;base64,)[A-Za-z0-9+/=]{100,}["']/gi,
138
+ (_match, attr, prefix) => {
139
+ const mime = prefix.replace("data:", "").replace(";base64,", "");
140
+ return `${attr}="[base64 ${mime}]"`;
141
+ }
142
+ )
143
+ );
144
+ result = track("attribute-allowlist", result, rewriteTagAttributes(result));
145
+ const svgPattern = /<svg\b([^>]*)>((?:(?!<svg\b)[\s\S])*?)<\/svg>/gi;
146
+ result = track(
147
+ "svg-collapse",
148
+ result,
149
+ (() => {
150
+ let prev;
151
+ let current = result;
152
+ do {
153
+ prev = current;
154
+ current = current.replace(
155
+ svgPattern,
156
+ (_match, attrs, inner) => {
157
+ const keepAttrs = [];
158
+ const attrPatterns = [
159
+ "id",
160
+ "class",
161
+ "role",
162
+ "aria-label",
163
+ "aria-hidden",
164
+ "title",
165
+ "data-testid"
166
+ ];
167
+ for (const name of attrPatterns) {
168
+ const attrToken = findAttributeToken(attrs, name);
169
+ if (attrToken) keepAttrs.push(attrToken);
170
+ }
171
+ const hasAriaLabel = /aria-label\s*=/i.test(attrs);
172
+ if (!hasAriaLabel) {
173
+ const titleMatch = inner.match(
174
+ /<title[^>]*>([^<]+)<\/title>/i
175
+ );
176
+ const descMatch = inner.match(
177
+ /<desc[^>]*>([^<]+)<\/desc>/i
178
+ );
179
+ const labelText = titleMatch?.[1]?.trim() || descMatch?.[1]?.trim();
180
+ if (labelText) {
181
+ keepAttrs.push(
182
+ `aria-label="${escapeHtmlAttribute(labelText)}"`
183
+ );
184
+ }
185
+ }
186
+ const attrStr = keepAttrs.length > 0 ? ` ${keepAttrs.join(" ")}` : "";
187
+ return `<svg${attrStr}><!-- [icon] --></svg>`;
188
+ }
189
+ );
190
+ svgPattern.lastIndex = 0;
191
+ } while (current !== prev);
192
+ return current;
193
+ })()
194
+ );
195
+ const layoutProps = /(?:^|;)\s*(?:display|visibility|opacity|pointer-events|position|z-index|overflow)(?:-[a-z]+)?\s*:[^;"]*/gi;
196
+ result = track(
197
+ "inline-styles",
198
+ result,
199
+ result.replace(
200
+ /\sstyle\s*=\s*["']([^"']*)["']/gi,
201
+ (_match, value) => {
202
+ const kept = [];
203
+ let propMatch;
204
+ layoutProps.lastIndex = 0;
205
+ while ((propMatch = layoutProps.exec(value)) !== null) {
206
+ kept.push(propMatch[0].replace(/^[;\s]+/, "").trim());
207
+ }
208
+ if (kept.length === 0) return "";
209
+ return ` style="${kept.join("; ")}"`;
210
+ }
211
+ )
212
+ );
213
+ result = track(
214
+ "obfuscated-classes",
215
+ result,
216
+ result.replace(
217
+ /\sclass\s*=\s*["']([^"']*)["']/gi,
218
+ (_match, value) => {
219
+ const filtered = filterSemanticClasses(value);
220
+ if (!filtered) return "";
221
+ return ` class="${filtered}"`;
222
+ }
223
+ )
224
+ );
225
+ const removableAttrs = /\s(?:xmlns(?::[a-z]+)?|xml:space|xml:lang|fill|stroke|stroke-width|stroke-linecap|stroke-linejoin|stroke-miterlimit|stroke-dasharray|stroke-dashoffset|stroke-opacity|fill-opacity|clip-rule|fill-rule|focusable)\s*=\s*["'][^"']*["']/gi;
226
+ result = track(
227
+ "framework-svg-attrs",
228
+ result,
229
+ result.replace(removableAttrs, "")
230
+ );
231
+ const preBlocks = [];
232
+ result = result.replace(
233
+ /(<pre\b[^>]*>)([\s\S]*?)(<\/pre>)/gi,
234
+ (_match, open, content, close) => {
235
+ const idx = preBlocks.length;
236
+ preBlocks.push(`${open}${content}${close}`);
237
+ return `__PRE_PLACEHOLDER_${idx}__`;
238
+ }
239
+ );
240
+ result = track(
241
+ "whitespace",
242
+ result,
243
+ result.replace(/[ \t]+/g, " ").replace(/\n\s*\n/g, "\n")
244
+ );
245
+ for (let i = 0; i < preBlocks.length; i++) {
246
+ const placeholder = `__PRE_PLACEHOLDER_${i}__`;
247
+ const preBlock = preBlocks[i];
248
+ result = result.replace(placeholder, () => preBlock);
249
+ }
250
+ return {
251
+ html: result,
252
+ originalLength,
253
+ condensedLength: result.length,
254
+ reductions
255
+ };
256
+ }
257
+ function rewriteTagAttributes(html) {
258
+ return html.replace(
259
+ OPEN_TAG_PATTERN,
260
+ (match, rawTagName, rawAttrs, selfClosing) => {
261
+ const tagName = rawTagName.toLowerCase();
262
+ if (!rawAttrs?.trim()) return match;
263
+ const attrs = parseAttributes(rawAttrs);
264
+ if (attrs.length === 0) return match;
265
+ const interactive = isInteractiveElement(tagName, attrs);
266
+ const kept = attrs.map((attr) => keepAttribute(tagName, attr, interactive)).filter((value) => value !== null);
267
+ const attrStr = kept.length > 0 ? ` ${kept.join(" ")}` : "";
268
+ const closing = selfClosing ? " /" : "";
269
+ return `<${rawTagName}${attrStr}${closing}>`;
270
+ }
271
+ );
272
+ }
273
+ function keepAttribute(tagName, attr, interactive) {
274
+ const name = attr.name.toLowerCase();
275
+ const value = attr.value;
276
+ if (name === "class") {
277
+ if (!value?.trim()) return null;
278
+ const filtered = filterSemanticClasses(value);
279
+ if (!filtered) return null;
280
+ return serializeAttribute(attr.name, filtered);
281
+ }
282
+ if (name === "style") {
283
+ if (!value?.trim()) return null;
284
+ return serializeAttribute(attr.name, value);
285
+ }
286
+ if (name.startsWith("aria-")) {
287
+ if (!value?.trim()) return null;
288
+ return attr.rawToken;
289
+ }
290
+ if (TEST_ATTRS.has(name)) {
291
+ if (!value?.trim()) return null;
292
+ return attr.rawToken;
293
+ }
294
+ if (tagName === "script" && SCRIPT_ATTRS.has(name)) {
295
+ return serializePreservedAttribute(attr);
296
+ }
297
+ if (tagName === "style" && STYLE_TAG_ATTRS.has(name)) {
298
+ if (!value?.trim()) return null;
299
+ return attr.rawToken;
300
+ }
301
+ if (STATE_ATTRS.has(name)) {
302
+ return serializePreservedAttribute(attr);
303
+ }
304
+ if (URL_ATTRS.has(name)) {
305
+ if (!value?.trim()) return null;
306
+ const normalized = normalizeUrlValue(value);
307
+ if (normalized === value) return attr.rawToken;
308
+ return serializeAttribute(attr.name, normalized);
309
+ }
310
+ if (TRUSTED_ATTRS.has(name)) {
311
+ if (shouldDropEmptyValue(name, value)) return null;
312
+ return serializePreservedAttribute(attr);
313
+ }
314
+ if (shouldKeepCustomDataAttribute(tagName, name, value, interactive)) {
315
+ return attr.rawToken;
316
+ }
317
+ return null;
318
+ }
319
+ function serializePreservedAttribute(attr) {
320
+ if (BOOLEAN_ATTRS.has(attr.name.toLowerCase())) {
321
+ return attr.rawToken;
322
+ }
323
+ if (attr.value === null) return attr.rawToken;
324
+ return attr.rawToken;
325
+ }
326
+ function shouldDropEmptyValue(name, value) {
327
+ if (value === null) return false;
328
+ if (value.trim()) return false;
329
+ if (name.startsWith("aria-")) return true;
330
+ return EMPTY_VALUE_DROP_ATTRS.has(name);
331
+ }
332
+ function normalizeUrlValue(value) {
333
+ const loweredValue = value.trim().toLowerCase();
334
+ if (loweredValue.startsWith("blob:")) return "blob:[omitted]";
335
+ if (loweredValue.startsWith("javascript:")) return "javascript:[omitted]";
336
+ if (loweredValue.startsWith("vbscript:")) return "vbscript:[omitted]";
337
+ if (loweredValue.startsWith("data:")) return "data:[omitted]";
338
+ if (value.length <= 160) return value;
339
+ try {
340
+ const isAbsolute = /^[a-z][a-z0-9+.-]*:/i.test(value);
341
+ const parsed = isAbsolute ? new URL(value) : new URL(value, "https://condensed.local");
342
+ const prefix = isAbsolute ? `${parsed.protocol}//${parsed.host}${parsed.pathname}` : `${parsed.pathname}${parsed.hash}`;
343
+ const query = parsed.search ? "?[query omitted]" : "";
344
+ return `${prefix}${query}`;
345
+ } catch {
346
+ return `${value.slice(0, 96)}[omitted]`;
347
+ }
348
+ }
349
+ function filterSemanticClasses(value) {
350
+ const classes = value.split(/\s+/).filter(Boolean);
351
+ const kept = classes.filter((cls) => !isObfuscatedClass(cls));
352
+ return kept.join(" ");
353
+ }
354
+ function isObfuscatedClass(cls) {
355
+ if (cls.length > 80) return true;
356
+ if (/^_?[0-9a-f]{6,}$/i.test(cls)) return true;
357
+ if (/^[a-z]+_[0-9a-f]{4,}$/i.test(cls)) return true;
358
+ if (/^[a-z]{1,2}[0-9]{2,}$/i.test(cls)) return true;
359
+ const digits = (cls.match(/[0-9]/g) || []).length;
360
+ const letters = (cls.match(/[a-zA-Z]/g) || []).length;
361
+ if (cls.length >= 6 && digits >= letters * 0.5 && digits >= 2) return true;
362
+ return false;
363
+ }
364
+ function parseAttributes(rawAttrs) {
365
+ const attrs = [];
366
+ const attrPattern = /([^\s"'<>\/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
367
+ let match;
368
+ while ((match = attrPattern.exec(rawAttrs)) !== null) {
369
+ const name = match[1];
370
+ if (!name) continue;
371
+ attrs.push({
372
+ name,
373
+ rawToken: match[0].trim(),
374
+ value: match[2] ?? match[3] ?? match[4] ?? null
375
+ });
376
+ }
377
+ return attrs;
378
+ }
379
+ function isInteractiveElement(tagName, attrs) {
380
+ if (INTERACTIVE_TAGS.has(tagName)) return true;
381
+ for (const attr of attrs) {
382
+ const name = attr.name.toLowerCase();
383
+ if (name === "tabindex" || name === "contenteditable") return true;
384
+ if (name !== "role") continue;
385
+ const role = attr.value?.trim().toLowerCase();
386
+ if (role && INTERACTIVE_ROLES.has(role)) {
387
+ return true;
388
+ }
389
+ }
390
+ return false;
391
+ }
392
+ function shouldKeepCustomDataAttribute(tagName, attrName, value, interactive) {
393
+ if (!interactive) return false;
394
+ if (!attrName.startsWith("data-")) return false;
395
+ if (TEST_ATTRS.has(attrName)) return false;
396
+ if (!value?.trim()) return false;
397
+ if (value.length > 80) return false;
398
+ if (tagName === "script" || tagName === "style") return false;
399
+ const key = attrName.slice("data-".length);
400
+ if (!looksMeaningfulToken(key)) return false;
401
+ if (!looksMeaningfulDataValue(value)) return false;
402
+ return true;
403
+ }
404
+ function looksMeaningfulToken(value) {
405
+ if (!/^[a-z][a-z0-9-]{1,40}$/i.test(value)) return false;
406
+ if (!/[a-z]{3}/i.test(value)) return false;
407
+ if (/(track|metric|telemetry|analytics|component|display|loaded|token|dps|color|screen|strict|rehydr|fetch)/i.test(value)) {
408
+ return false;
409
+ }
410
+ return true;
411
+ }
412
+ function looksMeaningfulDataValue(value) {
413
+ if (value.length > 80) return false;
414
+ if (/[<>]/.test(value)) return false;
415
+ if (/https?:\/\//i.test(value)) return false;
416
+ return /^[a-z0-9:_./ -]+$/i.test(value);
417
+ }
418
+ function findAttributeToken(attrs, name) {
419
+ const match = attrs.match(
420
+ new RegExp(
421
+ `(?:^|\\s)(${escapeRegExp(name)}(?:\\s*=\\s*(?:"[^"]*"|'[^']*'|[^\\s"'=<>\\x60]+))?)`,
422
+ "i"
423
+ )
424
+ );
425
+ return match?.[1] ?? null;
426
+ }
427
+ function escapeRegExp(value) {
428
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
429
+ }
430
+ function serializeAttribute(name, value) {
431
+ return `${name}="${escapeHtmlAttribute(value)}"`;
432
+ }
433
+ function escapeHtmlAttribute(value) {
434
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
435
+ }
436
+ export {
437
+ condenseDom
438
+ };
@@ -47,7 +47,11 @@ function createLLMClientFromModel(model) {
47
47
  return {
48
48
  role: "user",
49
49
  content: msg.content.map(
50
- (part) => part.type === "text" ? { type: "text", text: part.text } : { type: "image", image: part.image }
50
+ (part) => part.type === "text" ? { type: "text", text: part.text } : {
51
+ type: "image",
52
+ image: part.image,
53
+ ...part.mediaType ? { mediaType: part.mediaType } : {}
54
+ }
51
55
  )
52
56
  };
53
57
  });
@@ -24,7 +24,11 @@ function createLLMClientFromModel(model) {
24
24
  return {
25
25
  role: "user",
26
26
  content: msg.content.map(
27
- (part) => part.type === "text" ? { type: "text", text: part.text } : { type: "image", image: part.image }
27
+ (part) => part.type === "text" ? { type: "text", text: part.text } : {
28
+ type: "image",
29
+ image: part.image,
30
+ ...part.mediaType ? { mediaType: part.mediaType } : {}
31
+ }
28
32
  )
29
33
  };
30
34
  });
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,65 +17,129 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
  var client_exports = {};
20
30
  __export(client_exports, {
21
- createLLMClient: () => createLLMClient
31
+ createLLMClient: () => createLLMClient,
32
+ hasProviderCredentials: () => hasProviderCredentials,
33
+ missingProviderCredentialsMessage: () => missingProviderCredentialsMessage,
34
+ parseModel: () => parseModel
22
35
  });
23
36
  module.exports = __toCommonJS(client_exports);
24
- var import_google_vertex = require("@ai-sdk/google-vertex");
25
- var import_anthropic = require("@ai-sdk/anthropic");
26
- var import_openai = require("@ai-sdk/openai");
27
37
  var import_ai = require("ai");
38
+ const GEMINI_API_KEY_ENV_VARS = [
39
+ "GEMINI_API_KEY",
40
+ "GOOGLE_GENERATIVE_AI_API_KEY"
41
+ ];
42
+ const VERTEX_PROJECT_ENV_VARS = [
43
+ "GOOGLE_CLOUD_PROJECT",
44
+ "GCLOUD_PROJECT"
45
+ ];
46
+ const SUPPORTED_PROVIDER_ALIASES = {
47
+ google: "google",
48
+ gemini: "google",
49
+ vertex: "vertex",
50
+ anthropic: "anthropic",
51
+ codex: "openai",
52
+ openai: "openai"
53
+ };
54
+ function readFirstEnvValue(env, names) {
55
+ for (const name of names) {
56
+ const value = env[name]?.trim();
57
+ if (value) return value;
58
+ }
59
+ return null;
60
+ }
28
61
  function parseModel(model) {
29
62
  const slashIndex = model.indexOf("/");
30
63
  if (slashIndex === -1) {
31
64
  throw new Error(
32
- `Invalid model string "${model}". Expected format: "provider/model-id" (e.g. "google/gemini-3-flash-preview").`
65
+ `Invalid model string "${model}". Expected format: "provider/model-id" (for example "openai/gpt-5.4", "anthropic/claude-sonnet-4-6", "google/gemini-2.5-pro", or "vertex/gemini-2.5-pro").`
33
66
  );
34
67
  }
35
- const provider = model.slice(0, slashIndex);
68
+ const providerInput = model.slice(0, slashIndex).toLowerCase();
69
+ const provider = SUPPORTED_PROVIDER_ALIASES[providerInput];
36
70
  const modelId = model.slice(slashIndex + 1);
37
- if (!["google", "anthropic", "openai"].includes(provider)) {
71
+ if (!provider) {
38
72
  throw new Error(
39
- `Unsupported provider "${provider}". Supported providers: google, anthropic, openai.`
73
+ `Unsupported provider "${providerInput}". Supported providers: openai/codex, anthropic, google (Gemini API), and vertex.`
40
74
  );
41
75
  }
42
76
  return { provider, modelId };
43
77
  }
44
- function getProviderModel(provider, modelId) {
78
+ function hasProviderCredentials(provider, env = process.env) {
79
+ switch (provider) {
80
+ case "google":
81
+ return readFirstEnvValue(env, GEMINI_API_KEY_ENV_VARS) !== null;
82
+ case "vertex":
83
+ return readFirstEnvValue(env, VERTEX_PROJECT_ENV_VARS) !== null;
84
+ case "anthropic":
85
+ return Boolean(env.ANTHROPIC_API_KEY?.trim());
86
+ case "openai":
87
+ return Boolean(env.OPENAI_API_KEY?.trim());
88
+ }
89
+ }
90
+ function missingProviderCredentialsMessage(provider) {
91
+ switch (provider) {
92
+ case "google":
93
+ return "Missing Gemini API key. Set GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY.";
94
+ case "vertex":
95
+ return "Missing Vertex AI project. Set GOOGLE_CLOUD_PROJECT (or GCLOUD_PROJECT) and ensure application default credentials are configured.";
96
+ case "anthropic": {
97
+ return "Missing Anthropic API key. Set ANTHROPIC_API_KEY.";
98
+ }
99
+ case "openai": {
100
+ return "Missing OpenAI API key. Set OPENAI_API_KEY.";
101
+ }
102
+ }
103
+ }
104
+ async function getProviderModel(provider, modelId) {
45
105
  switch (provider) {
46
106
  case "google": {
47
- const project = process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT;
107
+ const apiKey = readFirstEnvValue(process.env, GEMINI_API_KEY_ENV_VARS);
108
+ if (!apiKey) {
109
+ throw new Error(missingProviderCredentialsMessage(provider));
110
+ }
111
+ const { createGoogleGenerativeAI } = await import("@ai-sdk/google");
112
+ const google = createGoogleGenerativeAI({ apiKey });
113
+ return google(modelId);
114
+ }
115
+ case "vertex": {
116
+ const project = readFirstEnvValue(process.env, VERTEX_PROJECT_ENV_VARS);
48
117
  if (!project) {
49
- throw new Error(
50
- "Missing GCP project for Vertex AI. Set GOOGLE_CLOUD_PROJECT environment variable and ensure application default credentials are configured (gcloud auth application-default login)."
51
- );
118
+ throw new Error(missingProviderCredentialsMessage(provider));
52
119
  }
53
- const vertex = (0, import_google_vertex.createVertex)({
120
+ const { createVertex } = await import("@ai-sdk/google-vertex");
121
+ const vertex = createVertex({
54
122
  project,
55
123
  location: process.env.GOOGLE_CLOUD_LOCATION || "global"
56
124
  });
57
125
  return vertex(modelId);
58
126
  }
59
127
  case "anthropic": {
60
- const apiKey = process.env.ANTHROPIC_API_KEY;
128
+ const apiKey = process.env.ANTHROPIC_API_KEY?.trim();
61
129
  if (!apiKey) {
62
- throw new Error(
63
- "Missing API key for Anthropic. Set ANTHROPIC_API_KEY environment variable."
64
- );
130
+ throw new Error(missingProviderCredentialsMessage(provider));
65
131
  }
66
- const anthropic = (0, import_anthropic.createAnthropic)({ apiKey });
132
+ const { createAnthropic } = await import("@ai-sdk/anthropic");
133
+ const anthropic = createAnthropic({ apiKey });
67
134
  return anthropic(modelId);
68
135
  }
69
136
  case "openai": {
70
- const apiKey = process.env.OPENAI_API_KEY;
137
+ const apiKey = process.env.OPENAI_API_KEY?.trim();
71
138
  if (!apiKey) {
72
- throw new Error(
73
- "Missing API key for OpenAI. Set OPENAI_API_KEY environment variable."
74
- );
139
+ throw new Error(missingProviderCredentialsMessage(provider));
75
140
  }
76
- const openai = (0, import_openai.createOpenAI)({ apiKey });
141
+ const { createOpenAI } = await import("@ai-sdk/openai");
142
+ const openai = createOpenAI({ apiKey });
77
143
  return openai(modelId);
78
144
  }
79
145
  }
@@ -83,7 +149,11 @@ function convertUserContentParts(parts) {
83
149
  if (part.type === "text") {
84
150
  return { type: "text", text: part.text };
85
151
  }
86
- return { type: "image", image: part.image };
152
+ return {
153
+ type: "image",
154
+ image: part.image,
155
+ ...part.mediaType ? { mediaType: part.mediaType } : {}
156
+ };
87
157
  });
88
158
  }
89
159
  function convertAssistantContentParts(parts) {
@@ -111,9 +181,14 @@ function convertMessages(messages) {
111
181
  }
112
182
  function createLLMClient(model) {
113
183
  const { provider, modelId } = parseModel(model);
114
- const aiModel = getProviderModel(provider, modelId);
184
+ let modelPromise = null;
185
+ const getModel = () => {
186
+ modelPromise ??= getProviderModel(provider, modelId);
187
+ return modelPromise;
188
+ };
115
189
  return {
116
190
  async generateObject(opts) {
191
+ const aiModel = await getModel();
117
192
  const result = await (0, import_ai.generateObject)({
118
193
  model: aiModel,
119
194
  prompt: opts.prompt,
@@ -123,6 +198,7 @@ function createLLMClient(model) {
123
198
  return result.object;
124
199
  },
125
200
  async generateObjectFromMessages(opts) {
201
+ const aiModel = await getModel();
126
202
  const result = await (0, import_ai.generateObject)({
127
203
  model: aiModel,
128
204
  messages: convertMessages(opts.messages),
@@ -135,5 +211,8 @@ function createLLMClient(model) {
135
211
  }
136
212
  // Annotate the CommonJS export names for ESM import in node:
137
213
  0 && (module.exports = {
138
- createLLMClient
214
+ createLLMClient,
215
+ hasProviderCredentials,
216
+ missingProviderCredentialsMessage,
217
+ parseModel
139
218
  });
@@ -1,6 +1,13 @@
1
1
  import { LLMClient } from './types.cjs';
2
2
  import 'zod';
3
3
 
4
+ type Provider = "google" | "vertex" | "anthropic" | "openai";
5
+ declare function parseModel(model: string): {
6
+ provider: Provider;
7
+ modelId: string;
8
+ };
9
+ declare function hasProviderCredentials(provider: Provider, env?: NodeJS.ProcessEnv): boolean;
10
+ declare function missingProviderCredentialsMessage(provider: Provider): string;
4
11
  declare function createLLMClient(model: string): LLMClient;
5
12
 
6
- export { createLLMClient };
13
+ export { type Provider, createLLMClient, hasProviderCredentials, missingProviderCredentialsMessage, parseModel };