@contentgrowth/llm-service 1.3.3 → 1.3.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/dist/index.cjs CHANGED
@@ -38,9 +38,11 @@ __export(index_exports, {
38
38
  FINISH_REASONS: () => FINISH_REASONS,
39
39
  GeminiProvider: () => GoogleProvider,
40
40
  GoogleProvider: () => GoogleProvider,
41
+ LEGACY_MODEL_ALIASES: () => LEGACY_MODEL_ALIASES,
41
42
  LLMService: () => LLMService,
42
43
  LLMServiceException: () => LLMServiceException,
43
44
  MODEL_CONFIGS: () => MODEL_CONFIGS,
45
+ Modality: () => import_genai2.Modality,
44
46
  OpenAIProvider: () => OpenAIProvider,
45
47
  TranscriptionService: () => TranscriptionService,
46
48
  TranscriptionServiceException: () => TranscriptionServiceException,
@@ -49,6 +51,7 @@ __export(index_exports, {
49
51
  extractJsonFromResponse: () => extractJsonFromResponse,
50
52
  extractTextAndJson: () => extractTextAndJson,
51
53
  handleApiError: () => handleApiError,
54
+ normalizeModelId: () => normalizeModelId,
52
55
  sanitizeError: () => sanitizeError
53
56
  });
54
57
  module.exports = __toCommonJS(index_exports);
@@ -119,10 +122,27 @@ var DefaultConfigProvider = class extends BaseConfigProvider {
119
122
  return null;
120
123
  }
121
124
  }
125
+ /**
126
+ * Resolve the model map for a tenant.
127
+ * Precedence (highest first):
128
+ * 1. tenantConfig.model - single model chosen by the tenant (becomes `default`)
129
+ * 2. tenantConfig.models[tier] - per-tier overrides supplied by the tenant
130
+ * 3. MODEL_CONFIGS[provider] - library defaults
131
+ */
132
+ _resolveTenantModels(tenantConfig) {
133
+ const models = {
134
+ ...MODEL_CONFIGS[tenantConfig.provider] || {},
135
+ ...tenantConfig.models || {}
136
+ };
137
+ if (typeof tenantConfig.model === "string" && tenantConfig.model.trim()) {
138
+ models.default = tenantConfig.model;
139
+ }
140
+ return normalizeModelMap(models);
141
+ }
122
142
  _buildTenantConfig(tenantConfig, env) {
123
143
  return {
124
144
  provider: tenantConfig.provider,
125
- models: MODEL_CONFIGS[tenantConfig.provider],
145
+ models: this._resolveTenantModels(tenantConfig),
126
146
  // for backward compatibility, api_key is deprecated and shall never be used!!
127
147
  apiKey: tenantConfig.apiKey || tenantConfig.api_key,
128
148
  baseURL: tenantConfig.baseURL || tenantConfig.base_url || void 0,
@@ -211,39 +231,70 @@ var DefaultConfigProvider = class extends BaseConfigProvider {
211
231
 
212
232
  // src/llm/config-manager.js
213
233
  var MODEL_CONFIGS = {
234
+ // NOTE: these must be OpenAI *API* model IDs. ChatGPT product names such as
235
+ // "GPT-5.5 Instant" / "GPT-5.5 Thinking" are not valid API models and return 404.
214
236
  openai: {
215
- default: "gpt-5.5-instant",
216
- edge: "gpt-5.5-thinking",
217
- fast: "gpt-5.5-instant",
218
- cost: "gpt-5.5-instant",
219
- free: "gpt-5.5-instant"
237
+ default: "gpt-5.6-luna",
238
+ edge: "gpt-5.6-sol",
239
+ fast: "gpt-5.6-luna",
240
+ cost: "gpt-5.6-luna",
241
+ free: "gpt-5.6-luna"
220
242
  },
243
+ // Gemini Developer API (ai.google.dev). There is no "gemini-3.5-pro"; the Pro tier is gemini-3.1-pro-preview.
221
244
  gemini: {
222
- default: "gemini-3.5-flash",
223
- edge: "gemini-3.5-pro",
224
- fast: "gemini-3.5-flash",
225
- cost: "gemini-3.5-flash",
226
- free: "gemini-3.5-flash",
245
+ default: "gemini-3.8-flash",
246
+ edge: "gemini-3.1-pro-preview",
247
+ fast: "gemini-3.8-flash",
248
+ cost: "gemini-3.5-flash-lite",
249
+ free: "gemini-3.5-flash-lite",
227
250
  video: "veo",
228
- image: "gemini-3.5-pro-image-preview"
251
+ image: "gemini-3-pro-image"
229
252
  },
253
+ // Vertex AI / Gemini Enterprise Agent Platform
230
254
  vertex: {
231
- default: "gemini-3.5-flash",
232
- edge: "gemini-3.5-pro",
233
- fast: "gemini-3.5-flash",
234
- cost: "gemini-3.5-flash",
235
- free: "gemini-3.5-flash",
255
+ default: "gemini-3.7-flash",
256
+ edge: "gemini-3.1-pro-preview",
257
+ fast: "gemini-3.7-flash",
258
+ cost: "gemini-3.5-flash-lite",
259
+ free: "gemini-3.5-flash-lite",
236
260
  video: "veo",
237
- image: "gemini-3.5-pro-image-preview"
261
+ image: "gemini-3.1-flash-image"
238
262
  },
263
+ // Anthropic IDs are "claude-<family>-<major>-<minor>", e.g. claude-sonnet-5-5 (not claude-5.5-sonnet).
239
264
  anthropic: {
240
- default: "claude-4.6-sonnet",
241
- edge: "claude-4.8-opus",
242
- fast: "claude-4.5-haiku",
243
- cost: "claude-4.5-haiku",
244
- free: "claude-4.5-haiku"
265
+ default: "claude-sonnet-5-5",
266
+ edge: "claude-opus-5-5",
267
+ fast: "claude-haiku-5-5",
268
+ cost: "claude-haiku-5-5",
269
+ free: "claude-haiku-5-5"
245
270
  }
246
271
  };
272
+ var LEGACY_MODEL_ALIASES = {
273
+ // OpenAI: ChatGPT product names, not API model IDs
274
+ "gpt-5.5-instant": "gpt-5.6-luna",
275
+ "gpt-5.5-thinking": "gpt-5.6-sol",
276
+ // Gemini: never existed
277
+ "gemini-3.5-pro": "gemini-3.1-pro-preview",
278
+ "gemini-3.7-pro": "gemini-3.1-pro-preview",
279
+ "gemini-3.5-pro-image-preview": "gemini-3-pro-image",
280
+ "gemini-3.7-pro-image-preview": "gemini-3-pro-image",
281
+ // Anthropic: wrong segment order
282
+ "claude-4.5-haiku": "claude-haiku-4-5",
283
+ "claude-4.6-sonnet": "claude-sonnet-4-6",
284
+ "claude-4.8-opus": "claude-opus-4-8"
285
+ };
286
+ function normalizeModelId(model) {
287
+ if (typeof model !== "string") return model;
288
+ const trimmed = model.trim();
289
+ return LEGACY_MODEL_ALIASES[trimmed] || trimmed;
290
+ }
291
+ function normalizeModelMap(models = {}) {
292
+ const out = {};
293
+ for (const [tier, id] of Object.entries(models)) {
294
+ out[tier] = normalizeModelId(id);
295
+ }
296
+ return out;
297
+ }
247
298
  var ConfigManager = class {
248
299
  /**
249
300
  * Set a custom configuration provider.
@@ -382,6 +433,13 @@ var BaseLLMProvider = class {
382
433
  async extractWithLLM(fileData, mimeType, prompt, schema, options) {
383
434
  throw new Error("This provider does not support extractWithLLM (Multimodal extraction)");
384
435
  }
436
+ /**
437
+ * Connect to a bidirectional real-time Live session
438
+ * @param {Object} options - { model, config, callbacks }
439
+ */
440
+ async connectLive(options) {
441
+ throw new Error(`This provider (${this.constructor.name}) does not support live real-time sessions`);
442
+ }
385
443
  /**
386
444
  * Helper to get the last 6 digits of the API key for logging.
387
445
  * @returns {string} "..." + last 6 chars, or "not_set"
@@ -471,6 +529,7 @@ function extractTextAndJson(input) {
471
529
  }
472
530
 
473
531
  // src/llm/providers/openai-provider.js
532
+ var REASONING_MODEL_PATTERN = /^(o\d|gpt-5|gpt-6)/i;
474
533
  var OpenAIProvider = class extends BaseLLMProvider {
475
534
  constructor(config) {
476
535
  super(config);
@@ -479,13 +538,12 @@ var OpenAIProvider = class extends BaseLLMProvider {
479
538
  clientOptions.baseURL = config.baseURL;
480
539
  }
481
540
  this.client = new import_openai.default(clientOptions);
482
- this.models = config.models;
483
- this.defaultModel = config.models.default;
541
+ this.models = normalizeModelMap(config.models);
542
+ this.defaultModel = this.models.default;
484
543
  }
485
544
  async chat(userMessage, systemPrompt = "", options = {}) {
486
545
  const messages = [{ role: "user", content: userMessage }];
487
- const tier = options.tier || "default";
488
- const effectiveModel = this._getModelForTier(tier);
546
+ const effectiveModel = this._resolveModel(options);
489
547
  const effectiveMaxTokens = options.maxTokens || this.config.maxTokens;
490
548
  const effectiveTemperature = options.temperature !== void 0 ? options.temperature : this.config.temperature;
491
549
  const response = await this._chatCompletionWithModel(
@@ -503,22 +561,42 @@ var OpenAIProvider = class extends BaseLLMProvider {
503
561
  messages,
504
562
  systemPrompt,
505
563
  tools,
506
- this.defaultModel,
564
+ this._resolveModel(options),
507
565
  this.config.maxTokens,
508
566
  this.config.temperature,
509
567
  options
510
568
  );
511
569
  }
570
+ /**
571
+ * Per-call model resolution: explicit options.model > options.tier > default
572
+ */
573
+ _resolveModel(options = {}) {
574
+ return normalizeModelId(options.model) || this._getModelForTier(options.tier || "default");
575
+ }
576
+ _isReasoningModel(modelName) {
577
+ return REASONING_MODEL_PATTERN.test(modelName || "");
578
+ }
512
579
  async _chatCompletionWithModel(messages, systemPrompt, tools, modelName, maxTokens, temperature, options = {}) {
513
580
  var _a, _b, _c, _d, _e;
581
+ const effectiveMaxTokens = (_a = options.maxTokens) != null ? _a : maxTokens;
514
582
  const requestPayload = {
515
583
  model: modelName,
516
- temperature: (_a = options.temperature) != null ? _a : temperature,
517
- max_tokens: (_b = options.maxTokens) != null ? _b : maxTokens,
518
584
  messages: [{ role: "system", content: systemPrompt }, ...messages],
519
585
  tools,
520
586
  tool_choice: tools ? "auto" : void 0
521
587
  };
588
+ if (this._isReasoningModel(modelName)) {
589
+ requestPayload.max_completion_tokens = effectiveMaxTokens;
590
+ if (options.reasoningEffort) {
591
+ requestPayload.reasoning_effort = options.reasoningEffort;
592
+ }
593
+ if (options.reasoningEffort === "none" && options.temperature !== void 0) {
594
+ requestPayload.temperature = options.temperature;
595
+ }
596
+ } else {
597
+ requestPayload.max_tokens = effectiveMaxTokens;
598
+ requestPayload.temperature = (_b = options.temperature) != null ? _b : temperature;
599
+ }
522
600
  if (options.responseFormat) {
523
601
  requestPayload.response_format = this._buildResponseFormat(options);
524
602
  }
@@ -657,8 +735,8 @@ var import_genai = require("@google/genai");
657
735
  var GoogleProvider = class extends BaseLLMProvider {
658
736
  constructor(config) {
659
737
  super(config);
660
- this.models = config.models;
661
- this.defaultModel = config.models.default;
738
+ this.models = normalizeModelMap(config.models);
739
+ this.defaultModel = this.models.default;
662
740
  this._pendingOperations = /* @__PURE__ */ new Map();
663
741
  const httpOptions = config.baseURL ? { baseUrl: config.baseURL } : void 0;
664
742
  if (config.provider === "vertex") {
@@ -697,7 +775,7 @@ var GoogleProvider = class extends BaseLLMProvider {
697
775
  async chat(userMessage, systemPrompt = "", options = {}) {
698
776
  const messages = [{ role: "user", content: userMessage }];
699
777
  const tier = options.tier || "default";
700
- const effectiveModel = this._getModelForTier(tier);
778
+ const effectiveModel = normalizeModelId(options.model) || this._getModelForTier(tier);
701
779
  const effectiveMaxTokens = options.maxTokens || this.config.maxTokens;
702
780
  const effectiveTemperature = options.temperature !== void 0 ? options.temperature : this.config.temperature;
703
781
  const response = await this._chatCompletionWithModel(
@@ -712,7 +790,7 @@ var GoogleProvider = class extends BaseLLMProvider {
712
790
  }
713
791
  async chatCompletion(messages, systemPrompt, tools = null, options = {}) {
714
792
  const tier = options.tier || "default";
715
- const modelName = this.models[tier] || this.defaultModel;
793
+ const modelName = normalizeModelId(options.model) || this.models[tier] || this.defaultModel;
716
794
  return this._chatCompletionWithModel(
717
795
  messages,
718
796
  systemPrompt,
@@ -1040,7 +1118,7 @@ ${msg.content}`;
1040
1118
  }
1041
1119
  async imageGeneration(prompt, systemPrompt, options = {}) {
1042
1120
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1043
- const modelName = options.model || this.models.image || "gemini-3-pro-image-preview";
1121
+ const modelName = normalizeModelId(options.model) || this.models.image || "gemini-3-pro-image";
1044
1122
  console.log(`[${this.constructor.name}] Generating image with model: ${modelName}`);
1045
1123
  const hasReferenceImages = options.images && options.images.length > 0;
1046
1124
  const generationConfig = {
@@ -1285,15 +1363,38 @@ ${prompt}` : prompt;
1285
1363
  throw error;
1286
1364
  }
1287
1365
  }
1366
+ /**
1367
+ * Connect to Gemini Live bidirectional real-time audio/text session
1368
+ *
1369
+ * @param {Object} options
1370
+ * @param {string} [options.model] - Model name (e.g. models/gemini-2.5-flash-native-audio-preview-09-2025)
1371
+ * @param {Object} options.config - Live session configuration
1372
+ * @param {Object} options.callbacks - Event callbacks (onopen, onmessage, onerror, onclose)
1373
+ * @returns {Promise<any>} The active Live session
1374
+ */
1375
+ async connectLive({ model, config, callbacks }) {
1376
+ var _a, _b, _c;
1377
+ if (!((_b = (_a = this.client) == null ? void 0 : _a.live) == null ? void 0 : _b.connect)) {
1378
+ throw new LLMServiceException("The configured Google provider SDK does not support live sessions.", 500);
1379
+ }
1380
+ const resolvedModel = model || ((_c = this.models) == null ? void 0 : _c.live) || this.defaultModel || "models/gemini-2.5-flash-native-audio-preview-09-2025";
1381
+ return this.client.live.connect({
1382
+ model: resolvedModel,
1383
+ config,
1384
+ callbacks
1385
+ });
1386
+ }
1288
1387
  };
1289
1388
 
1290
1389
  // src/llm/providers/anthropic-provider.js
1291
1390
  var import_sdk = __toESM(require("@anthropic-ai/sdk"), 1);
1391
+ var SAMPLING_PARAMS_MODEL_PATTERN = /^claude-(3[-.]|(opus|sonnet|haiku)-4(-[0-6])?([-@]\d{8})?$)/i;
1392
+ var NON_STREAMING_TIMEOUT_MS = 10 * 60 * 1e3;
1292
1393
  var AnthropicProvider = class extends BaseLLMProvider {
1293
1394
  constructor(config) {
1294
1395
  super(config);
1295
- this.models = config.models || {};
1296
- this.defaultModel = this.models.default || "claude-3-5-sonnet-20241022";
1396
+ this.models = normalizeModelMap(config.models || {});
1397
+ this.defaultModel = this.models.default || "claude-sonnet-5-5";
1297
1398
  const clientOptions = { apiKey: config.apiKey || "" };
1298
1399
  if (config.baseURL) {
1299
1400
  clientOptions.baseURL = config.baseURL;
@@ -1303,8 +1404,7 @@ var AnthropicProvider = class extends BaseLLMProvider {
1303
1404
  async chat(userMessage, systemPrompt = "", options = {}) {
1304
1405
  var _a;
1305
1406
  const messages = [{ role: "user", content: userMessage }];
1306
- const tier = options.tier || "default";
1307
- const effectiveModel = this._getModelForTier(tier);
1407
+ const effectiveModel = this._resolveModel(options);
1308
1408
  const effectiveMaxTokens = options.maxTokens || this.config.maxTokens || 4096;
1309
1409
  const effectiveTemperature = options.temperature !== void 0 ? options.temperature : (_a = this.config.temperature) != null ? _a : 0.7;
1310
1410
  const response = await this._chatCompletionWithModel(
@@ -1320,8 +1420,7 @@ var AnthropicProvider = class extends BaseLLMProvider {
1320
1420
  }
1321
1421
  async chatCompletion(messages, systemPrompt, tools = null, options = {}) {
1322
1422
  var _a;
1323
- const tier = options.tier || "default";
1324
- const effectiveModel = this._getModelForTier(tier);
1423
+ const effectiveModel = this._resolveModel(options);
1325
1424
  const effectiveMaxTokens = options.maxTokens || this.config.maxTokens || 4096;
1326
1425
  const effectiveTemperature = options.temperature !== void 0 ? options.temperature : (_a = this.config.temperature) != null ? _a : 0.7;
1327
1426
  return this._chatCompletionWithModel(
@@ -1335,7 +1434,7 @@ var AnthropicProvider = class extends BaseLLMProvider {
1335
1434
  );
1336
1435
  }
1337
1436
  async _chatCompletionWithModel(messages, systemPrompt, tools, modelName, maxTokens, temperature, options = {}) {
1338
- var _a, _b, _c, _d;
1437
+ var _a, _b, _c, _d, _e;
1339
1438
  let effectiveSystem = systemPrompt || void 0;
1340
1439
  if (options.responseFormat) {
1341
1440
  const formatType = typeof options.responseFormat === "string" ? options.responseFormat : options.responseFormat.type;
@@ -1355,14 +1454,18 @@ ${jsonPrompt}` : jsonPrompt;
1355
1454
  const requestParams = {
1356
1455
  model: modelName,
1357
1456
  max_tokens: maxTokens,
1358
- temperature,
1359
1457
  messages: anthropicMessages,
1360
1458
  ...effectiveSystem ? { system: effectiveSystem } : {},
1361
1459
  ...anthropicTools ? { tools: anthropicTools } : {}
1362
1460
  };
1461
+ if (this._supportsSamplingParams(modelName) && temperature !== void 0 && temperature !== null) {
1462
+ requestParams.temperature = temperature;
1463
+ }
1363
1464
  let response;
1364
1465
  try {
1365
- response = await this.client.messages.create(requestParams);
1466
+ response = await this.client.messages.create(requestParams, {
1467
+ timeout: (_a = this.config.timeout) != null ? _a : NON_STREAMING_TIMEOUT_MS
1468
+ });
1366
1469
  } catch (error) {
1367
1470
  console.error(`[AnthropicProvider] request failed (API Key: ${this._getMaskedApiKey()}):`, error);
1368
1471
  throw error;
@@ -1395,9 +1498,9 @@ ${jsonPrompt}` : jsonPrompt;
1395
1498
  _rawFinishReason: rawFinishReason,
1396
1499
  _responseFormat: options.responseFormat,
1397
1500
  usage: {
1398
- prompt_tokens: ((_a = response.usage) == null ? void 0 : _a.input_tokens) || 0,
1399
- completion_tokens: ((_b = response.usage) == null ? void 0 : _b.output_tokens) || 0,
1400
- total_tokens: (((_c = response.usage) == null ? void 0 : _c.input_tokens) || 0) + (((_d = response.usage) == null ? void 0 : _d.output_tokens) || 0)
1501
+ prompt_tokens: ((_b = response.usage) == null ? void 0 : _b.input_tokens) || 0,
1502
+ completion_tokens: ((_c = response.usage) == null ? void 0 : _c.output_tokens) || 0,
1503
+ total_tokens: (((_d = response.usage) == null ? void 0 : _d.input_tokens) || 0) + (((_e = response.usage) == null ? void 0 : _e.output_tokens) || 0)
1401
1504
  },
1402
1505
  model: modelName
1403
1506
  };
@@ -1511,6 +1614,15 @@ ${jsonPrompt}` : jsonPrompt;
1511
1614
  _getModelForTier(tier) {
1512
1615
  return this.models[tier] || this.defaultModel;
1513
1616
  }
1617
+ /**
1618
+ * Per-call model resolution: explicit options.model > options.tier > default
1619
+ */
1620
+ _resolveModel(options = {}) {
1621
+ return normalizeModelId(options.model) || this._getModelForTier(options.tier || "default");
1622
+ }
1623
+ _supportsSamplingParams(modelName) {
1624
+ return SAMPLING_PARAMS_MODEL_PATTERN.test(modelName || "");
1625
+ }
1514
1626
  _shouldAutoParse(options) {
1515
1627
  if (!options.responseFormat) return false;
1516
1628
  const formatType = typeof options.responseFormat === "string" ? options.responseFormat : options.responseFormat.type;
@@ -1617,7 +1729,7 @@ var LLMService = class {
1617
1729
  return { tools: param1, options: param2 || {} };
1618
1730
  }
1619
1731
  if (typeof param1 === "object") {
1620
- const optionsKeys = ["responseFormat", "responseSchema", "temperature", "maxTokens", "tier", "autoParse", "strictSchema", "schemaName"];
1732
+ const optionsKeys = ["responseFormat", "responseSchema", "temperature", "maxTokens", "tier", "model", "reasoningEffort", "autoParse", "strictSchema", "schemaName"];
1621
1733
  const hasOptionsKeys = optionsKeys.some((key) => key in param1);
1622
1734
  if (hasOptionsKeys) {
1623
1735
  return { tools: null, options: param1 };
@@ -1804,6 +1916,21 @@ var LLMService = class {
1804
1916
  }
1805
1917
  return provider.extractWithLLM(fileData, mimeType, prompt, schema, options);
1806
1918
  }
1919
+ /**
1920
+ * Connect to a bidirectional real-time Live session
1921
+ *
1922
+ * @param {Object} params - { model, config, callbacks }
1923
+ * @param {string|null} [tenantId] - Tenant ID
1924
+ * @returns {Promise<any>} The active Live session
1925
+ */
1926
+ async connectLive(params, tenantId = null) {
1927
+ var _a;
1928
+ const provider = await this._getProvider(tenantId);
1929
+ if (typeof provider.connectLive !== "function") {
1930
+ throw new LLMServiceException(`Provider ${((_a = provider.config) == null ? void 0 : _a.provider) || provider.constructor.name} does not support live real-time sessions`, 400);
1931
+ }
1932
+ return provider.connectLive(params);
1933
+ }
1807
1934
  };
1808
1935
  var LLMServiceException = class extends Error {
1809
1936
  constructor(message, statusCode = 500, details = null) {
@@ -2138,6 +2265,9 @@ function createSpeechHandler(app, getConfig) {
2138
2265
  });
2139
2266
  return app;
2140
2267
  }
2268
+
2269
+ // src/index.js
2270
+ var import_genai2 = require("@google/genai");
2141
2271
  // Annotate the CommonJS export names for ESM import in node:
2142
2272
  0 && (module.exports = {
2143
2273
  AnthropicProvider,
@@ -2147,9 +2277,11 @@ function createSpeechHandler(app, getConfig) {
2147
2277
  FINISH_REASONS,
2148
2278
  GeminiProvider,
2149
2279
  GoogleProvider,
2280
+ LEGACY_MODEL_ALIASES,
2150
2281
  LLMService,
2151
2282
  LLMServiceException,
2152
2283
  MODEL_CONFIGS,
2284
+ Modality,
2153
2285
  OpenAIProvider,
2154
2286
  TranscriptionService,
2155
2287
  TranscriptionServiceException,
@@ -2158,6 +2290,7 @@ function createSpeechHandler(app, getConfig) {
2158
2290
  extractJsonFromResponse,
2159
2291
  extractTextAndJson,
2160
2292
  handleApiError,
2293
+ normalizeModelId,
2161
2294
  sanitizeError
2162
2295
  });
2163
2296
  //# sourceMappingURL=index.cjs.map