llm-exe 3.0.0 → 3.0.2

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.js CHANGED
@@ -67,10 +67,12 @@ __export(index_exports, {
67
67
  createState: () => createState,
68
68
  createStateItem: () => createStateItem,
69
69
  defineSchema: () => defineSchema,
70
+ formatLlmExeErrorForLog: () => formatLlmExeErrorForLog,
70
71
  guards: () => guards_exports,
71
72
  isLlmExeError: () => isLlmExeError,
72
73
  registerHelpers: () => registerHelpers,
73
74
  registerPartials: () => registerPartials,
75
+ serializeLlmExeError: () => serializeLlmExeError,
74
76
  useExecutors: () => useExecutors,
75
77
  useLlm: () => useLlm,
76
78
  useLlmConfiguration: () => useLlmConfiguration,
@@ -530,6 +532,48 @@ function formatErrorList(values, options) {
530
532
  }
531
533
  return parts.join(", ");
532
534
  }
535
+ function formatLlmExeErrorForLog(error) {
536
+ if (!error || typeof error !== "object") {
537
+ return formatErrorValue(error);
538
+ }
539
+ const e = error;
540
+ const name = typeof e.name === "string" && e.name ? e.name : "Error";
541
+ const message = typeof e.message === "string" ? e.message : "";
542
+ const code = typeof e.code === "string" ? e.code : void 0;
543
+ const category = typeof e.category === "string" ? e.category : void 0;
544
+ let header;
545
+ if (code) {
546
+ header = `${name} [${code}]: ${message}`;
547
+ } else if (category) {
548
+ header = `${name} [${category}]: ${message}`;
549
+ } else {
550
+ header = `${name}: ${message}`;
551
+ }
552
+ const chain = [];
553
+ let current = e.cause;
554
+ let depth = 0;
555
+ const seen = /* @__PURE__ */ new WeakSet();
556
+ while (current && depth < 5) {
557
+ if (typeof current === "object") {
558
+ if (seen.has(current)) {
559
+ chain.push("Caused by: [Circular]");
560
+ break;
561
+ }
562
+ seen.add(current);
563
+ }
564
+ const c = current;
565
+ const cName = typeof c.name === "string" && c.name ? c.name : "Error";
566
+ const cMsg = typeof c.message === "string" ? c.message : String(current);
567
+ const cCode = typeof c.code === "string" ? c.code : void 0;
568
+ chain.push(
569
+ cCode ? `Caused by: ${cName} [${cCode}]: ${cMsg}` : `Caused by: ${cName}: ${cMsg}`
570
+ );
571
+ current = c.cause;
572
+ depth++;
573
+ }
574
+ return chain.length > 0 ? `${header}
575
+ ${chain.join("\n")}` : header;
576
+ }
533
577
 
534
578
  // src/errors/createLlmExeError.ts
535
579
  var DOCS_BASE_URL = "https://llm-exe.com";
@@ -1051,7 +1095,9 @@ var BaseExecutor = class {
1051
1095
  return this;
1052
1096
  }
1053
1097
  on(eventName, fn) {
1054
- return this.setHooks({ [eventName]: fn });
1098
+ return this.setHooks({
1099
+ [eventName]: fn
1100
+ });
1055
1101
  }
1056
1102
  off(eventName, fn) {
1057
1103
  return this.removeHook(eventName, fn);
@@ -1149,8 +1195,8 @@ var CoreExecutor = class extends BaseExecutor {
1149
1195
  __publicField(this, "_handler");
1150
1196
  this._handler = fn.handler.bind(null);
1151
1197
  }
1152
- async handler(_input) {
1153
- return this._handler.call(null, _input);
1198
+ async handler(_input, _options, _context) {
1199
+ return this._handler.call(null, _input, _context);
1154
1200
  }
1155
1201
  };
1156
1202
 
@@ -3302,6 +3348,7 @@ __export(utils_exports, {
3302
3348
  assert: () => assert,
3303
3349
  asyncCallWithTimeout: () => asyncCallWithTimeout,
3304
3350
  defineSchema: () => defineSchema,
3351
+ escapeTemplateString: () => escapeTemplateString,
3305
3352
  filterObjectOnSchema: () => filterObjectOnSchema,
3306
3353
  guessProviderFromModel: () => guessProviderFromModel,
3307
3354
  importHelpers: () => importHelpers,
@@ -3374,6 +3421,12 @@ function importHelpers(_helpers) {
3374
3421
  return helpers;
3375
3422
  }
3376
3423
 
3424
+ // src/utils/modules/escapeTemplateString.ts
3425
+ function escapeTemplateString(input) {
3426
+ if (typeof input !== "string") return input;
3427
+ return input.replace(/\{\{/g, "\\{{");
3428
+ }
3429
+
3377
3430
  // src/utils/modules/replaceTemplateStringAsync.ts
3378
3431
  async function replaceTemplateStringAsync(templateString, substitutions = {}, configuration = {
3379
3432
  helpers: [],
@@ -4039,9 +4092,53 @@ function OutputOpenAIChat(result, _config) {
4039
4092
  };
4040
4093
  }
4041
4094
 
4095
+ // src/llm/config/_utils/imageContent.ts
4096
+ function isImageUrlContentBlock(block) {
4097
+ if (typeof block !== "object" || block === null) {
4098
+ return false;
4099
+ }
4100
+ const imageUrl = block.image_url;
4101
+ return typeof imageUrl === "object" && imageUrl !== null && typeof imageUrl.url === "string";
4102
+ }
4103
+ var DATA_URI_PATTERN = /^data:([^;,]+);base64,(.+)$/;
4104
+ function parseImageUrl(url, context) {
4105
+ if (typeof url !== "string" || url.trim() === "") {
4106
+ throw new LlmExeError("Image content block has an empty url", {
4107
+ code: "prompt.invalid_messages",
4108
+ context: {
4109
+ ...context,
4110
+ received: url,
4111
+ expected: "an https URL or a data: URI",
4112
+ resolution: "Set image_url.url to an https URL or a base64 data: URI (data:image/png;base64,...)."
4113
+ }
4114
+ });
4115
+ }
4116
+ if (url.startsWith("data:")) {
4117
+ const match = url.match(DATA_URI_PATTERN);
4118
+ if (!match) {
4119
+ throw new LlmExeError("Malformed data: URI in image content block", {
4120
+ code: "prompt.invalid_messages",
4121
+ context: {
4122
+ ...context,
4123
+ received: `${url.slice(0, 48)}...`,
4124
+ expected: "data:<media-type>;base64,<data>",
4125
+ resolution: "Images must be base64-encoded with an explicit media type, e.g. data:image/png;base64,iVBOR..."
4126
+ }
4127
+ });
4128
+ }
4129
+ return { kind: "base64", mediaType: match[1], data: match[2] };
4130
+ }
4131
+ return { kind: "url", url };
4132
+ }
4133
+
4042
4134
  // src/llm/config/openai/promptSanitizeMessageCallback.ts
4043
4135
  function openaiPromptMessageCallback(_message) {
4044
4136
  let message = { ..._message };
4137
+ if (Array.isArray(message.content)) {
4138
+ message.content = message.content.map(
4139
+ (block) => isImageUrlContentBlock(block) && block.type !== "image_url" ? { ...block, type: "image_url" } : block
4140
+ );
4141
+ }
4045
4142
  if (message.role === "function") {
4046
4143
  message.role = "tool";
4047
4144
  message.tool_call_id = message.id;
@@ -4247,6 +4344,9 @@ var openai = {
4247
4344
  "openai.chat.v1": openAiChatV1,
4248
4345
  "openai.chat-mock.v1": openAiChatMockV1,
4249
4346
  // GPT-5 family
4347
+ "openai.gpt-5.5": withDefaultModel(openAiChatV1, "gpt-5.5"),
4348
+ "openai.gpt-5.4": withDefaultModel(openAiChatV1, "gpt-5.4"),
4349
+ "openai.gpt-5.4-mini": withDefaultModel(openAiChatV1, "gpt-5.4-mini"),
4250
4350
  "openai.gpt-5.2": withDefaultModel(openAiChatV1, "gpt-5.2"),
4251
4351
  "openai.gpt-5-mini": withDefaultModel(openAiChatV1, "gpt-5-mini"),
4252
4352
  "openai.gpt-5-nano": withDefaultModel(openAiChatV1, "gpt-5-nano"),
@@ -4268,8 +4368,46 @@ var openai = {
4268
4368
  };
4269
4369
 
4270
4370
  // src/llm/config/anthropic/promptSanitizeMessageCallback.ts
4271
- function anthropicPromptMessageCallback(_message) {
4371
+ function anthropicPromptMessageCallback(_message, options = {}) {
4372
+ const { provider = "anthropic.chat", allowImageUrlSources = true } = options;
4272
4373
  let message = { ..._message };
4374
+ if (Array.isArray(message.content)) {
4375
+ message.content = message.content.map((block) => {
4376
+ if (!isImageUrlContentBlock(block)) {
4377
+ return block;
4378
+ }
4379
+ const parsed = parseImageUrl(block.image_url.url, {
4380
+ operation: "anthropicPromptMessageCallback",
4381
+ provider
4382
+ });
4383
+ if (parsed.kind === "base64") {
4384
+ return {
4385
+ type: "image",
4386
+ source: {
4387
+ type: "base64",
4388
+ media_type: parsed.mediaType,
4389
+ data: parsed.data
4390
+ }
4391
+ };
4392
+ }
4393
+ if (!allowImageUrlSources) {
4394
+ throw new LlmExeError("Image URLs are not supported by this provider", {
4395
+ code: "prompt.invalid_messages",
4396
+ context: {
4397
+ operation: "anthropicPromptMessageCallback",
4398
+ provider,
4399
+ received: parsed.url,
4400
+ expected: "a base64 data: URI",
4401
+ resolution: "Bedrock does not fetch remote images. Base64-encode the image and pass it as a data: URI (data:image/png;base64,...)."
4402
+ }
4403
+ });
4404
+ }
4405
+ return {
4406
+ type: "image",
4407
+ source: { type: "url", url: parsed.url }
4408
+ };
4409
+ });
4410
+ }
4273
4411
  if (message.role === "function") {
4274
4412
  message.role = "user";
4275
4413
  message.content = [
@@ -4315,10 +4453,11 @@ function mergeConsecutiveSameRole(messages) {
4315
4453
  }
4316
4454
  return merged;
4317
4455
  }
4318
- function anthropicPromptSanitize(_messages, _inputBodyObj, _outputObj) {
4456
+ function anthropicPromptSanitize(_messages, _inputBodyObj, _outputObj, _options = {}) {
4457
+ const toAnthropicMessage = (m) => anthropicPromptMessageCallback(m, _options);
4319
4458
  if (typeof _messages === "string") {
4320
4459
  return [{ role: "user", content: _messages }].map(
4321
- anthropicPromptMessageCallback
4460
+ toAnthropicMessage
4322
4461
  );
4323
4462
  }
4324
4463
  const [first, ...messages] = [
@@ -4328,7 +4467,7 @@ function anthropicPromptSanitize(_messages, _inputBodyObj, _outputObj) {
4328
4467
  return [
4329
4468
  { role: "user", content: first.content },
4330
4469
  ...messages
4331
- ].map(anthropicPromptMessageCallback);
4470
+ ].map(toAnthropicMessage);
4332
4471
  }
4333
4472
  if (first.role === "system" && messages.length > 0) {
4334
4473
  _outputObj.system = first.content;
@@ -4337,7 +4476,7 @@ function anthropicPromptSanitize(_messages, _inputBodyObj, _outputObj) {
4337
4476
  return { ...m, role: "user" };
4338
4477
  }
4339
4478
  return m;
4340
- }).map(anthropicPromptMessageCallback);
4479
+ }).map(toAnthropicMessage);
4341
4480
  return mergeConsecutiveSameRole(result2);
4342
4481
  }
4343
4482
  const result = [
@@ -4348,7 +4487,7 @@ function anthropicPromptSanitize(_messages, _inputBodyObj, _outputObj) {
4348
4487
  }
4349
4488
  return m;
4350
4489
  })
4351
- ].map(anthropicPromptMessageCallback);
4490
+ ].map(toAnthropicMessage);
4352
4491
  return mergeConsecutiveSameRole(result);
4353
4492
  }
4354
4493
 
@@ -4461,7 +4600,11 @@ var amazonAnthropicChatV1 = {
4461
4600
  mapBody: {
4462
4601
  prompt: {
4463
4602
  key: "messages",
4464
- transform: anthropicPromptSanitize
4603
+ transform: (v, i, o) => anthropicPromptSanitize(v, i, o, {
4604
+ provider: "amazon:anthropic.chat",
4605
+ // Bedrock's invoke API only accepts base64 image sources
4606
+ allowImageUrlSources: false
4607
+ })
4465
4608
  },
4466
4609
  topP: {
4467
4610
  key: "top_p"
@@ -4517,6 +4660,21 @@ var amazonMetaChatV1 = {
4517
4660
  if (typeof messages === "string") {
4518
4661
  return messages;
4519
4662
  } else {
4663
+ const hasImageContent = messages.some(
4664
+ (message) => Array.isArray(message?.content) && message.content.some(isImageUrlContentBlock)
4665
+ );
4666
+ if (hasImageContent) {
4667
+ throw new LlmExeError("Image content is not supported", {
4668
+ code: "prompt.invalid_messages",
4669
+ context: {
4670
+ operation: "amazonMetaChatV1.prompt.transform",
4671
+ provider: "amazon:meta.chat",
4672
+ received: "a message with image content",
4673
+ expected: "text-only messages",
4674
+ resolution: "This model accepts a flattened text prompt and cannot receive images."
4675
+ }
4676
+ });
4677
+ }
4520
4678
  return replaceTemplateString(`{{>DialogueHistory key='messages'}}`, {
4521
4679
  messages
4522
4680
  });
@@ -4544,7 +4702,11 @@ var bedrock = {
4544
4702
 
4545
4703
  // src/llm/config/anthropic/index.ts
4546
4704
  var ANTHROPIC_VERSION = "2023-06-01";
4547
- var MODELS_REJECTING_SAMPLING_PARAMS = ["claude-opus-4-7", "claude-opus-4-8"];
4705
+ var MODELS_REJECTING_SAMPLING_PARAMS = [
4706
+ "claude-opus-4-7",
4707
+ "claude-opus-4-8",
4708
+ "claude-fable-5"
4709
+ ];
4548
4710
  var isClaude4x = (model) => /^claude-(opus|sonnet|haiku)-4-/.test(model);
4549
4711
  var dropIfModelRejectsSamplingParams = (v, body) => MODELS_REJECTING_SAMPLING_PARAMS.includes(body.model) ? void 0 : v;
4550
4712
  var topPTransform = (v, body) => {
@@ -4565,6 +4727,15 @@ var anthropicChatV1 = {
4565
4727
  required: [true, "maxTokens required"],
4566
4728
  default: 4096
4567
4729
  },
4730
+ // Every key mapped in mapBody must also be declared here:
4731
+ // stateFromOptions picks only declared option keys, so an undeclared
4732
+ // key never reaches mapBody (see issue #661).
4733
+ temperature: {},
4734
+ topP: {},
4735
+ topK: {},
4736
+ stopSequences: {},
4737
+ metadata: {},
4738
+ serviceTier: {},
4568
4739
  anthropicApiKey: {
4569
4740
  default: getEnvironmentVariable("ANTHROPIC_API_KEY")
4570
4741
  }
@@ -4598,9 +4769,8 @@ var anthropicChatV1 = {
4598
4769
  stopSequences: {
4599
4770
  key: "stop_sequences"
4600
4771
  },
4601
- stream: {
4602
- key: "stream"
4603
- },
4772
+ // No `stream` mapping: the request pipeline has no SSE support, so
4773
+ // forwarding stream=true would return an unparseable response.
4604
4774
  metadata: {
4605
4775
  key: "metadata"
4606
4776
  },
@@ -4629,6 +4799,11 @@ var anthropicChatV1 = {
4629
4799
  };
4630
4800
  var anthropic = {
4631
4801
  "anthropic.chat.v1": anthropicChatV1,
4802
+ // Claude Fable 5 models
4803
+ "anthropic.claude-fable-5": withDefaultModel(
4804
+ anthropicChatV1,
4805
+ "claude-fable-5"
4806
+ ),
4632
4807
  // Claude 4.8 models
4633
4808
  "anthropic.claude-opus-4-8": withDefaultModel(
4634
4809
  anthropicChatV1,
@@ -4662,10 +4837,9 @@ var anthropic = {
4662
4837
  config: withDefaultModel(anthropicChatV1, "claude-opus-4-6"),
4663
4838
  message: 'Shorthand "anthropic.claude-opus-4-6" is deprecated and may be removed in a future release.'
4664
4839
  }),
4665
- ...deprecateShorthand("anthropic.claude-opus-4-1", {
4666
- config: withDefaultModel(anthropicChatV1, "claude-opus-4-1-20250805"),
4667
- message: 'Shorthand "anthropic.claude-opus-4-1" is deprecated and may be removed in a future release.'
4668
- }),
4840
+ // NOTE: "anthropic.claude-opus-4-1" (claude-opus-4-1-20250805) was removed —
4841
+ // Anthropic retires that model on Aug 5, 2026. Migrate to
4842
+ // anthropic.claude-opus-4-5/-6/-7/-8.
4669
4843
  ...deprecateShorthand("anthropic.claude-sonnet-4", {
4670
4844
  config: withDefaultModel(anthropicChatV1, "claude-sonnet-4-0"),
4671
4845
  message: 'Shorthand "anthropic.claude-sonnet-4" is deprecated and may be removed in a future release.'
@@ -4811,6 +4985,60 @@ function OutputOllamaChat(result, _config) {
4811
4985
  };
4812
4986
  }
4813
4987
 
4988
+ // src/llm/config/ollama/promptSanitize.ts
4989
+ function ollamaPromptSanitize(_messages) {
4990
+ if (typeof _messages === "string") {
4991
+ return [{ role: "user", content: _messages }];
4992
+ }
4993
+ return _messages.map((_message) => {
4994
+ if (!Array.isArray(_message.content)) {
4995
+ return _message;
4996
+ }
4997
+ const message = { ..._message };
4998
+ const textParts = [];
4999
+ const images = [];
5000
+ for (const block of message.content) {
5001
+ if (isImageUrlContentBlock(block)) {
5002
+ const parsed = parseImageUrl(block.image_url.url, {
5003
+ operation: "ollamaPromptSanitize",
5004
+ provider: "ollama.chat"
5005
+ });
5006
+ if (parsed.kind === "url") {
5007
+ throw new LlmExeError("Image URLs are not supported by ollama", {
5008
+ code: "prompt.invalid_messages",
5009
+ context: {
5010
+ operation: "ollamaPromptSanitize",
5011
+ provider: "ollama.chat",
5012
+ received: parsed.url,
5013
+ expected: "a base64 data: URI",
5014
+ resolution: "Ollama does not fetch remote images. Base64-encode the image and pass it as a data: URI (data:image/png;base64,...)."
5015
+ }
5016
+ });
5017
+ }
5018
+ images.push(parsed.data);
5019
+ } else if (typeof block?.text === "string") {
5020
+ textParts.push(block.text);
5021
+ } else {
5022
+ throw new LlmExeError("Unsupported content block for ollama", {
5023
+ code: "prompt.invalid_messages",
5024
+ context: {
5025
+ operation: "ollamaPromptSanitize",
5026
+ provider: "ollama.chat",
5027
+ received: block,
5028
+ expected: "text or image_url content blocks",
5029
+ resolution: "Ollama messages only support text and base64 images."
5030
+ }
5031
+ });
5032
+ }
5033
+ }
5034
+ message.content = textParts.join("\n");
5035
+ if (images.length > 0) {
5036
+ message.images = images;
5037
+ }
5038
+ return message;
5039
+ });
5040
+ }
5041
+
4814
5042
  // src/llm/config/ollama/index.ts
4815
5043
  var ollamaChatV1 = {
4816
5044
  key: "ollama.chat.v1",
@@ -4828,12 +5056,7 @@ var ollamaChatV1 = {
4828
5056
  mapBody: {
4829
5057
  prompt: {
4830
5058
  key: "messages",
4831
- transform: (v) => {
4832
- if (typeof v === "string") {
4833
- return [{ role: "user", content: v }];
4834
- }
4835
- return v;
4836
- }
5059
+ transform: (v) => ollamaPromptSanitize(v)
4837
5060
  },
4838
5061
  model: {
4839
5062
  key: "model"
@@ -4870,6 +5093,37 @@ var ollama = {
4870
5093
  };
4871
5094
 
4872
5095
  // src/llm/config/google/promptSanitizeMessageCallback.ts
5096
+ var GOOGLE_SUPPORTED_FILE_URI = /^(gs:\/\/|https:\/\/generativelanguage\.googleapis\.com\/)/;
5097
+ function googleGeminiContentBlockToPart(block) {
5098
+ if (isImageUrlContentBlock(block)) {
5099
+ const parsed = parseImageUrl(block.image_url.url, {
5100
+ operation: "googleGeminiPromptMessageCallback",
5101
+ provider: "google.chat"
5102
+ });
5103
+ if (parsed.kind === "base64") {
5104
+ return {
5105
+ inlineData: { mimeType: parsed.mediaType, data: parsed.data }
5106
+ };
5107
+ }
5108
+ if (GOOGLE_SUPPORTED_FILE_URI.test(parsed.url)) {
5109
+ return { fileData: { fileUri: parsed.url } };
5110
+ }
5111
+ throw new LlmExeError("Gemini cannot load images from arbitrary URLs", {
5112
+ code: "prompt.invalid_messages",
5113
+ context: {
5114
+ operation: "googleGeminiPromptMessageCallback",
5115
+ provider: "google.chat",
5116
+ received: parsed.url,
5117
+ expected: "a base64 data: URI, a Files API URI, or a gs:// URI",
5118
+ resolution: "Pass the image as a data: URI (data:image/png;base64,...), or upload it with the Gemini Files API and pass the returned file URI."
5119
+ }
5120
+ });
5121
+ }
5122
+ if (typeof block?.text === "string") {
5123
+ return { text: block.text };
5124
+ }
5125
+ return block;
5126
+ }
4873
5127
  function googleGeminiPromptMessageCallback(_message) {
4874
5128
  let message = { ..._message };
4875
5129
  const parts = [];
@@ -4883,6 +5137,9 @@ function googleGeminiPromptMessageCallback(_message) {
4883
5137
  if (typeof payload.content === "string" && message.role !== "function") {
4884
5138
  parts.push({ text: message.content });
4885
5139
  }
5140
+ if (Array.isArray(payload.content) && message.role !== "function") {
5141
+ parts.push(...payload.content.map(googleGeminiContentBlockToPart));
5142
+ }
4886
5143
  if (message.role === "function") {
4887
5144
  role = "user";
4888
5145
  parts.push({
@@ -7700,10 +7957,12 @@ function createStateItem(name, defaultValue) {
7700
7957
  createState,
7701
7958
  createStateItem,
7702
7959
  defineSchema,
7960
+ formatLlmExeErrorForLog,
7703
7961
  guards,
7704
7962
  isLlmExeError,
7705
7963
  registerHelpers,
7706
7964
  registerPartials,
7965
+ serializeLlmExeError,
7707
7966
  useExecutors,
7708
7967
  useLlm,
7709
7968
  useLlmConfiguration,