llm-exe 3.0.0 → 3.1.0-beta.1

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,17 @@ __export(index_exports, {
67
67
  createState: () => createState,
68
68
  createStateItem: () => createStateItem,
69
69
  defineSchema: () => defineSchema,
70
+ executorFromConfig: () => executorFromConfig,
71
+ formatLlmExeErrorForLog: () => formatLlmExeErrorForLog,
70
72
  guards: () => guards_exports,
71
73
  isLlmExeError: () => isLlmExeError,
74
+ loadConfigFromUrl: () => loadConfigFromUrl,
75
+ loadExecutorConfig: () => loadExecutorConfig,
76
+ parseExecutorConfig: () => parseExecutorConfig,
72
77
  registerHelpers: () => registerHelpers,
73
78
  registerPartials: () => registerPartials,
79
+ runConfig: () => runConfig,
80
+ serializeLlmExeError: () => serializeLlmExeError,
74
81
  useExecutors: () => useExecutors,
75
82
  useLlm: () => useLlm,
76
83
  useLlmConfiguration: () => useLlmConfiguration,
@@ -179,6 +186,11 @@ var ALL_CODES = [
179
186
  "configuration.missing_env",
180
187
  "configuration.missing_option",
181
188
  "configuration.invalid_headers",
189
+ "configuration.parse_failed",
190
+ "configuration.invalid_config",
191
+ "configuration.file_not_found",
192
+ "configuration.file_read_failed",
193
+ "configuration.unsupported_format",
182
194
  "parser.invalid_type",
183
195
  "parser.invalid_input",
184
196
  "parser.parse_failed",
@@ -530,6 +542,48 @@ function formatErrorList(values, options) {
530
542
  }
531
543
  return parts.join(", ");
532
544
  }
545
+ function formatLlmExeErrorForLog(error) {
546
+ if (!error || typeof error !== "object") {
547
+ return formatErrorValue(error);
548
+ }
549
+ const e = error;
550
+ const name = typeof e.name === "string" && e.name ? e.name : "Error";
551
+ const message = typeof e.message === "string" ? e.message : "";
552
+ const code = typeof e.code === "string" ? e.code : void 0;
553
+ const category = typeof e.category === "string" ? e.category : void 0;
554
+ let header;
555
+ if (code) {
556
+ header = `${name} [${code}]: ${message}`;
557
+ } else if (category) {
558
+ header = `${name} [${category}]: ${message}`;
559
+ } else {
560
+ header = `${name}: ${message}`;
561
+ }
562
+ const chain = [];
563
+ let current = e.cause;
564
+ let depth = 0;
565
+ const seen = /* @__PURE__ */ new WeakSet();
566
+ while (current && depth < 5) {
567
+ if (typeof current === "object") {
568
+ if (seen.has(current)) {
569
+ chain.push("Caused by: [Circular]");
570
+ break;
571
+ }
572
+ seen.add(current);
573
+ }
574
+ const c = current;
575
+ const cName = typeof c.name === "string" && c.name ? c.name : "Error";
576
+ const cMsg = typeof c.message === "string" ? c.message : String(current);
577
+ const cCode = typeof c.code === "string" ? c.code : void 0;
578
+ chain.push(
579
+ cCode ? `Caused by: ${cName} [${cCode}]: ${cMsg}` : `Caused by: ${cName}: ${cMsg}`
580
+ );
581
+ current = c.cause;
582
+ depth++;
583
+ }
584
+ return chain.length > 0 ? `${header}
585
+ ${chain.join("\n")}` : header;
586
+ }
533
587
 
534
588
  // src/errors/createLlmExeError.ts
535
589
  var DOCS_BASE_URL = "https://llm-exe.com";
@@ -1051,7 +1105,9 @@ var BaseExecutor = class {
1051
1105
  return this;
1052
1106
  }
1053
1107
  on(eventName, fn) {
1054
- return this.setHooks({ [eventName]: fn });
1108
+ return this.setHooks({
1109
+ [eventName]: fn
1110
+ });
1055
1111
  }
1056
1112
  off(eventName, fn) {
1057
1113
  return this.removeHook(eventName, fn);
@@ -1149,8 +1205,8 @@ var CoreExecutor = class extends BaseExecutor {
1149
1205
  __publicField(this, "_handler");
1150
1206
  this._handler = fn.handler.bind(null);
1151
1207
  }
1152
- async handler(_input) {
1153
- return this._handler.call(null, _input);
1208
+ async handler(_input, _options, _context) {
1209
+ return this._handler.call(null, _input, _context);
1154
1210
  }
1155
1211
  };
1156
1212
 
@@ -1607,9 +1663,9 @@ function validateParserSchema(schema, parsed) {
1607
1663
  if (!schema || !parsed || typeof parsed !== "object") {
1608
1664
  return null;
1609
1665
  }
1610
- const validate = (0, import_jsonschema.validate)(parsed, schema);
1611
- if (validate.errors.length) {
1612
- return validate.errors;
1666
+ const validate2 = (0, import_jsonschema.validate)(parsed, schema);
1667
+ if (validate2.errors.length) {
1668
+ return validate2.errors;
1613
1669
  }
1614
1670
  return null;
1615
1671
  }
@@ -3302,6 +3358,7 @@ __export(utils_exports, {
3302
3358
  assert: () => assert,
3303
3359
  asyncCallWithTimeout: () => asyncCallWithTimeout,
3304
3360
  defineSchema: () => defineSchema,
3361
+ escapeTemplateString: () => escapeTemplateString,
3305
3362
  filterObjectOnSchema: () => filterObjectOnSchema,
3306
3363
  guessProviderFromModel: () => guessProviderFromModel,
3307
3364
  importHelpers: () => importHelpers,
@@ -3374,6 +3431,12 @@ function importHelpers(_helpers) {
3374
3431
  return helpers;
3375
3432
  }
3376
3433
 
3434
+ // src/utils/modules/escapeTemplateString.ts
3435
+ function escapeTemplateString(input) {
3436
+ if (typeof input !== "string") return input;
3437
+ return input.replace(/\{\{/g, "\\{{");
3438
+ }
3439
+
3377
3440
  // src/utils/modules/replaceTemplateStringAsync.ts
3378
3441
  async function replaceTemplateStringAsync(templateString, substitutions = {}, configuration = {
3379
3442
  helpers: [],
@@ -7669,6 +7732,361 @@ function createDialogue(name) {
7669
7732
  function createStateItem(name, defaultValue) {
7670
7733
  return new DefaultStateItem(name, defaultValue);
7671
7734
  }
7735
+
7736
+ // src/config/schema.ts
7737
+ var import_jsonschema2 = require("jsonschema");
7738
+
7739
+ // src/config/errors.ts
7740
+ var DOCS_PATH = "/config";
7741
+ function configParseFailedError(context, options) {
7742
+ return createLlmExeError(
7743
+ {
7744
+ code: "configuration.parse_failed",
7745
+ message: (ctx) => `Failed to parse ${ctx.format ?? "config"} configuration${typeof ctx.position === "number" ? ` at position ${ctx.position}` : ""}.`,
7746
+ resolution: "Check the file is valid for its format (JSON/YAML/markdown frontmatter). Pass an explicit `format` if auto-detection is guessing wrong.",
7747
+ docsPath: DOCS_PATH
7748
+ },
7749
+ context,
7750
+ options
7751
+ );
7752
+ }
7753
+ function configInvalidError(context, options) {
7754
+ return createLlmExeError(
7755
+ {
7756
+ code: "configuration.invalid_config",
7757
+ message: (ctx) => {
7758
+ if (ctx.schemaErrors?.length) {
7759
+ return `Invalid executor config: ${ctx.schemaErrors.join("; ")}.`;
7760
+ }
7761
+ if (ctx.field) {
7762
+ return `Invalid executor config at \`${ctx.field}\`: expected ${String(
7763
+ ctx.expected
7764
+ )}.`;
7765
+ }
7766
+ return "Invalid executor config.";
7767
+ },
7768
+ resolution: "Fix the listed fields. `provider` and `message` are required; `provider` must be a known useLlm key.",
7769
+ docsPath: DOCS_PATH
7770
+ },
7771
+ context,
7772
+ options
7773
+ );
7774
+ }
7775
+
7776
+ // src/config/schema.ts
7777
+ var executorConfigSchema = {
7778
+ $schema: "http://json-schema.org/draft-07/schema#",
7779
+ type: "object",
7780
+ required: ["provider", "message"],
7781
+ additionalProperties: false,
7782
+ properties: {
7783
+ provider: { type: "string", minLength: 1 },
7784
+ model: { type: "string" },
7785
+ system: { type: "string" },
7786
+ message: { type: "string", minLength: 1 },
7787
+ parser: { type: "string", minLength: 1 },
7788
+ parserOptions: { type: "object" },
7789
+ llmOptions: { type: "object" },
7790
+ executorOptions: { type: "object" },
7791
+ data: { type: "object" }
7792
+ }
7793
+ };
7794
+ function validate(input) {
7795
+ const result = (0, import_jsonschema2.validate)(input, executorConfigSchema);
7796
+ if (!result.valid) {
7797
+ const schemaErrors = result.errors.map((error) => {
7798
+ const field = error.property.replace(/^instance\.?/, "") || "(root)";
7799
+ return `${field} ${error.message}`;
7800
+ });
7801
+ throw configInvalidError({
7802
+ schemaErrors,
7803
+ field: result.errors[0]?.property.replace(/^instance\.?/, ""),
7804
+ expected: result.errors[0]?.argument,
7805
+ received: input
7806
+ });
7807
+ }
7808
+ return input;
7809
+ }
7810
+
7811
+ // src/config/normalize.ts
7812
+ var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
7813
+ var PATCH_REPLACE_FIELDS = [
7814
+ "model",
7815
+ "provider",
7816
+ "parser",
7817
+ "parserOptions",
7818
+ "llmOptions",
7819
+ "executorOptions",
7820
+ "system",
7821
+ "message"
7822
+ ];
7823
+ function isPlainObject3(value) {
7824
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
7825
+ return false;
7826
+ }
7827
+ const proto = Object.getPrototypeOf(value);
7828
+ return proto === Object.prototype || proto === null;
7829
+ }
7830
+ function deepMerge(base, override) {
7831
+ const out = {};
7832
+ for (const key of Object.keys(base)) {
7833
+ if (FORBIDDEN_KEYS.has(key)) continue;
7834
+ out[key] = base[key];
7835
+ }
7836
+ for (const key of Object.keys(override)) {
7837
+ if (FORBIDDEN_KEYS.has(key)) continue;
7838
+ const next = override[key];
7839
+ if (next === void 0) continue;
7840
+ const prev = out[key];
7841
+ if (isPlainObject3(next)) {
7842
+ out[key] = deepMerge(isPlainObject3(prev) ? prev : {}, next);
7843
+ } else {
7844
+ out[key] = next;
7845
+ }
7846
+ }
7847
+ return out;
7848
+ }
7849
+ function mergeData(base, override) {
7850
+ if (override === void 0) return base;
7851
+ if (base === void 0) return deepMerge({}, override);
7852
+ return deepMerge(base, override);
7853
+ }
7854
+ function normalizeConfig(raw, patch) {
7855
+ if (!isPlainObject3(raw)) {
7856
+ throw configInvalidError({
7857
+ field: "(root)",
7858
+ expected: "object",
7859
+ received: raw
7860
+ });
7861
+ }
7862
+ const merged = { ...raw };
7863
+ if ("output" in merged) {
7864
+ if (merged.parser === void 0 && merged.output !== void 0) {
7865
+ merged.parser = merged.output;
7866
+ }
7867
+ delete merged.output;
7868
+ }
7869
+ if (patch) {
7870
+ for (const field of PATCH_REPLACE_FIELDS) {
7871
+ if (patch[field] !== void 0) {
7872
+ merged[field] = patch[field];
7873
+ }
7874
+ }
7875
+ if (patch.data !== void 0) {
7876
+ merged.data = mergeData(
7877
+ isPlainObject3(merged.data) ? merged.data : void 0,
7878
+ patch.data
7879
+ );
7880
+ }
7881
+ }
7882
+ if (merged.parser === void 0) {
7883
+ merged.parser = "string";
7884
+ }
7885
+ const config = validate(merged);
7886
+ if (!(config.provider in configs)) {
7887
+ throw configInvalidError({
7888
+ field: "provider",
7889
+ received: config.provider,
7890
+ availableProviders: Object.keys(configs)
7891
+ });
7892
+ }
7893
+ if (config.message.trim() === "") {
7894
+ throw configInvalidError({
7895
+ field: "message",
7896
+ expected: "non-empty string",
7897
+ received: config.message
7898
+ });
7899
+ }
7900
+ return config;
7901
+ }
7902
+
7903
+ // src/config/assemble.ts
7904
+ function loadExecutorConfig(object, patch) {
7905
+ return normalizeConfig(object, patch);
7906
+ }
7907
+ function executorFromConfig(config, createOptions) {
7908
+ const llmOptions = {
7909
+ ...config.llmOptions,
7910
+ ...config.model ? { model: config.model } : {}
7911
+ };
7912
+ const llm = useLlm(config.provider, llmOptions);
7913
+ const prompt = createChatPrompt(config.system || "", {
7914
+ allowUnsafeUserTemplate: true
7915
+ });
7916
+ prompt.addUserMessage(config.message);
7917
+ const parser = createParser(
7918
+ config.parser,
7919
+ config.parserOptions
7920
+ );
7921
+ const llmConfiguration = { llm, prompt, parser };
7922
+ const hasFunctions = Array.isArray(
7923
+ config.executorOptions?.functions
7924
+ );
7925
+ return hasFunctions ? createLlmFunctionExecutor(llmConfiguration, createOptions) : createLlmExecutor(llmConfiguration, createOptions);
7926
+ }
7927
+ function runConfig(config, overrides, createOptions) {
7928
+ const executor = executorFromConfig(config, createOptions);
7929
+ const data = mergeData(config.data, overrides?.data) ?? {};
7930
+ const executorOptions = {
7931
+ ...config.executorOptions,
7932
+ ...overrides?.executorOptions
7933
+ };
7934
+ return executor.execute(data, executorOptions);
7935
+ }
7936
+
7937
+ // src/config/parse.ts
7938
+ var import_js_yaml = require("js-yaml");
7939
+ var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
7940
+ function isRecord(value) {
7941
+ return !!value && typeof value === "object" && !Array.isArray(value);
7942
+ }
7943
+ function snippetOf(source) {
7944
+ return source.length > 120 ? `${source.slice(0, 120)}\u2026` : source;
7945
+ }
7946
+ function formatFromExtension(pathOrUrl) {
7947
+ const clean = pathOrUrl.split(/[?#]/)[0];
7948
+ const dot = clean.lastIndexOf(".");
7949
+ if (dot === -1) return void 0;
7950
+ switch (clean.slice(dot).toLowerCase()) {
7951
+ case ".json":
7952
+ return "json";
7953
+ case ".yml":
7954
+ case ".yaml":
7955
+ return "yaml";
7956
+ case ".md":
7957
+ case ".markdown":
7958
+ return "markdown";
7959
+ default:
7960
+ return void 0;
7961
+ }
7962
+ }
7963
+ function parseJsonSource(source, patch) {
7964
+ let parsed;
7965
+ try {
7966
+ parsed = JSON.parse(source);
7967
+ } catch (cause) {
7968
+ throw configParseFailedError(
7969
+ { format: "json", snippet: snippetOf(source) },
7970
+ { cause }
7971
+ );
7972
+ }
7973
+ return normalizeConfig(parsed, patch);
7974
+ }
7975
+ function parseYamlSource(source, patch) {
7976
+ let parsed;
7977
+ try {
7978
+ parsed = (0, import_js_yaml.load)(source);
7979
+ } catch (cause) {
7980
+ throw configParseFailedError(
7981
+ { format: "yaml", snippet: snippetOf(source) },
7982
+ { cause }
7983
+ );
7984
+ }
7985
+ return normalizeConfig(parsed, patch);
7986
+ }
7987
+ function parseMarkdownSource(source, patch) {
7988
+ const match = FRONTMATTER_RE.exec(source);
7989
+ let frontmatter = {};
7990
+ let body = source;
7991
+ if (match) {
7992
+ const [, fmBlock, bodyBlock] = match;
7993
+ let parsed;
7994
+ try {
7995
+ parsed = (0, import_js_yaml.load)(fmBlock);
7996
+ } catch (cause) {
7997
+ throw configParseFailedError(
7998
+ { format: "markdown", snippet: snippetOf(fmBlock) },
7999
+ { cause }
8000
+ );
8001
+ }
8002
+ frontmatter = isRecord(parsed) ? parsed : {};
8003
+ body = bodyBlock;
8004
+ }
8005
+ const trimmedBody = body.trim();
8006
+ const fmMessage = typeof frontmatter.message === "string" ? frontmatter.message.trim() : "";
8007
+ if (fmMessage !== "" && trimmedBody !== "") {
8008
+ throw configInvalidError({
8009
+ field: "message",
8010
+ expected: "either frontmatter `message` or a markdown body, not both"
8011
+ });
8012
+ }
8013
+ const object = { ...frontmatter };
8014
+ if (trimmedBody !== "") {
8015
+ object.message = trimmedBody;
8016
+ }
8017
+ return normalizeConfig(object, patch);
8018
+ }
8019
+ function parseAutoSource(source, patch) {
8020
+ let jsonParsed;
8021
+ let jsonOk = false;
8022
+ try {
8023
+ jsonParsed = JSON.parse(source);
8024
+ jsonOk = true;
8025
+ } catch {
8026
+ }
8027
+ if (jsonOk) {
8028
+ return normalizeConfig(jsonParsed, patch);
8029
+ }
8030
+ if (FRONTMATTER_RE.test(source)) {
8031
+ return parseMarkdownSource(source, patch);
8032
+ }
8033
+ let yamlParsed;
8034
+ let yamlOk = false;
8035
+ try {
8036
+ yamlParsed = (0, import_js_yaml.load)(source);
8037
+ yamlOk = isRecord(yamlParsed);
8038
+ } catch {
8039
+ }
8040
+ if (yamlOk) {
8041
+ return normalizeConfig(yamlParsed, patch);
8042
+ }
8043
+ throw configParseFailedError({ format: "auto", snippet: snippetOf(source) });
8044
+ }
8045
+ async function parseExecutorConfig(source, opts) {
8046
+ const { format, ...patch } = opts;
8047
+ switch (format) {
8048
+ case "json":
8049
+ return parseJsonSource(source, patch);
8050
+ case "yaml":
8051
+ return parseYamlSource(source, patch);
8052
+ case "markdown":
8053
+ return parseMarkdownSource(source, patch);
8054
+ case "auto":
8055
+ return parseAutoSource(source, patch);
8056
+ default:
8057
+ return parseAutoSource(source, patch);
8058
+ }
8059
+ }
8060
+
8061
+ // src/config/fromUrl.ts
8062
+ async function loadConfigFromUrl(url, opts = {}) {
8063
+ const { fetch: fetchImpl, init, format, ...patch } = opts;
8064
+ const doFetch = fetchImpl ?? globalThis.fetch;
8065
+ if (typeof doFetch !== "function") {
8066
+ throw createLlmExeError(
8067
+ {
8068
+ code: "request.http_error",
8069
+ message: () => `No fetch implementation available to load config from "${url}".`,
8070
+ resolution: "Pass a `fetch` implementation in options, or run on a platform with a global fetch."
8071
+ },
8072
+ { url }
8073
+ );
8074
+ }
8075
+ const response = await doFetch(url, init);
8076
+ if (!response.ok) {
8077
+ throw createLlmExeError(
8078
+ {
8079
+ code: "request.http_error",
8080
+ message: (ctx) => `Failed to fetch config from "${ctx.url}": ${ctx.status ?? ""} ${ctx.statusText ?? ""}`.trim(),
8081
+ resolution: "Check the URL is reachable and returns the config body."
8082
+ },
8083
+ { url, status: response.status, statusText: response.statusText }
8084
+ );
8085
+ }
8086
+ const text = await response.text();
8087
+ const resolvedFormat = format ?? formatFromExtension(url) ?? "auto";
8088
+ return parseExecutorConfig(text, { format: resolvedFormat, ...patch });
8089
+ }
7672
8090
  // Annotate the CommonJS export names for ESM import in node:
7673
8091
  0 && (module.exports = {
7674
8092
  BaseExecutor,
@@ -7700,10 +8118,17 @@ function createStateItem(name, defaultValue) {
7700
8118
  createState,
7701
8119
  createStateItem,
7702
8120
  defineSchema,
8121
+ executorFromConfig,
8122
+ formatLlmExeErrorForLog,
7703
8123
  guards,
7704
8124
  isLlmExeError,
8125
+ loadConfigFromUrl,
8126
+ loadExecutorConfig,
8127
+ parseExecutorConfig,
7705
8128
  registerHelpers,
7706
8129
  registerPartials,
8130
+ runConfig,
8131
+ serializeLlmExeError,
7707
8132
  useExecutors,
7708
8133
  useLlm,
7709
8134
  useLlmConfiguration,