@chatman-media/kb 1.3.0 → 1.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.
@@ -54,6 +54,15 @@ export interface AnswerInput {
54
54
  * uses a calm FAQ-support block instead — answering questions without selling.
55
55
  */
56
56
  supportPhase?: "docs" | "submitted";
57
+ /**
58
+ * Per-stage override (Phase 2 C-2): the lead's current funnel-stage goal/
59
+ * guidance from stage_definitions. Takes precedence over the Style's
60
+ * per-sales-stage config when composing the prompt.
61
+ */
62
+ stageOverride?: {
63
+ goal: string;
64
+ guidance?: string;
65
+ };
57
66
  /**
58
67
  * Called after every `answerWithRag` or `answerWithRagStream` call with the
59
68
  * final telemetry. Useful for logging, metrics, or A/B experiment recording
@@ -1 +1 @@
1
- {"version":3,"file":"answer-types.d.ts","sourceRoot":"","sources":["../src/answer-types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"answer-types.d.ts","sourceRoot":"","sources":["../src/answer-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,KAAK,EAAE,qBAAqB,EAAE,WAAW,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAC7F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAExD,eAAO,MAAM,iBAAiB,mBAAmB,CAAC;AAElD,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,GAAG,WAAW,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,QAAQ,CAAC;IACb,QAAQ,EAAE,eAAe,CAAC;IAC1B,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACnC;;;;OAIG;IACH,aAAa,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACjD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IACpC;;;;OAIG;IACH,aAAa,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpD;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,eAAe,KAAK,IAAI,CAAC;IACnD;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;IACd;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,YAAY,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC;CAC7B;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,WAAW,GAAG,cAAc,GAAG,YAAY,GAAG,YAAY,GAAG,IAAI,GAAG,WAAW,CAAC;IACtF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvE;;;OAGG;IACH,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;IAC7C;;;;OAIG;IACH,SAAS,CAAC,EAAE,KAAK,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC9B,MAAM,EAAE,OAAO,CAAC;QAChB,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,YAAY,CAAC,OAAO,GAAG,OAAO;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB,SAAS,EAAE,eAAe,CAAC;IAC3B,qFAAqF;IACrF,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB"}
@@ -1 +1 @@
1
- {"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../src/answer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,YAAY,EAGjB,KAAK,OAAO,EACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AA8BzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG9C,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,iBAAiB,EACjB,KAAK,OAAO,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,EACtB,0BAA0B,EAC1B,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EACL,iBAAiB,EACjB,4BAA4B,EAC5B,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAI5B,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,oEAAoE;IACpE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC,CAgG/E;AA2OD;;;;;;;;GAQG;AACH,wBAAuB,mBAAmB,CAAC,KAAK,EAAE,WAAW,GAAG,aAAa,CAAC,MAAM,CAAC,CA0HpF;AAED,wBAAsB,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EACxD,KAAK,EAAE,WAAW,GAAG;IAAE,YAAY,EAAE,CAAC,CAAA;CAAE,GACvC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrC,wBAAsB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;AAiG/E;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CAAC,KAAK,EAAE;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;CACzB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BlB"}
1
+ {"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../src/answer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,YAAY,EAGjB,KAAK,OAAO,EACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AA8BzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAG9C,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,iBAAiB,EACjB,KAAK,OAAO,GACb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,EACtB,0BAA0B,EAC1B,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EACL,iBAAiB,EACjB,4BAA4B,EAC5B,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAI5B,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,oEAAoE;IACpE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC,CAgG/E;AA4OD;;;;;;;;GAQG;AACH,wBAAuB,mBAAmB,CAAC,KAAK,EAAE,WAAW,GAAG,aAAa,CAAC,MAAM,CAAC,CA2HpF;AAED,wBAAsB,aAAa,CAAC,CAAC,SAAS,CAAC,CAAC,UAAU,EACxD,KAAK,EAAE,WAAW,GAAG;IAAE,YAAY,EAAE,CAAC,CAAA;CAAE,GACvC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrC,wBAAsB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;AAiG/E;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CAAC,KAAK,EAAE;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;CACzB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BlB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=extract-user-facts.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract-user-facts.test.d.ts","sourceRoot":"","sources":["../src/extract-user-facts.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fact-checker.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fact-checker.test.d.ts","sourceRoot":"","sources":["../src/fact-checker.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=grade-skills.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grade-skills.test.d.ts","sourceRoot":"","sources":["../src/grade-skills.test.ts"],"names":[],"mappings":""}
package/dist/index.js CHANGED
@@ -44647,7 +44647,7 @@ var NO_CONTEXT_MARKER = "__NO_CONTEXT__";
44647
44647
  var replaceEmDash = {
44648
44648
  name: "replace-em-dash",
44649
44649
  description: "U+2014 \xAB\u2014\xBB \u2192 \xAB-\xBB (\u0441 \u043D\u043E\u0440\u043C\u0430\u043B\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u043F\u0440\u043E\u0431\u0435\u043B\u043E\u0432)",
44650
- apply: (s) => s.replace(/\s*\u2014\s*/g, " - ").replace(/ {2,}/g, " ")
44650
+ apply: (s) => s.replace(/\s{0,200}\u2014\s{0,200}/g, " - ").replace(/ {2,}/g, " ")
44651
44651
  };
44652
44652
  var replaceEnDash = {
44653
44653
  name: "replace-en-dash",
@@ -45100,9 +45100,11 @@ ${triggerLine}${h.body}`;
45100
45100
  const skillsBlock = skillsForStage.length ? `\u041F\u0420\u0418\u0401\u041C\u042B (\u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u0443\u043C\u0435\u0441\u0442\u043D\u044B\u0435, \u043D\u0435 \u0432\u0441\u0435 \u0441\u0440\u0430\u0437\u0443 \u2014 \u0432\u044B\u0431\u0438\u0440\u0430\u0439 \u043F\u043E \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0443):
45101
45101
  ` + skillsForStage.map((s) => `- ${s.displayName} \u2014 ${s.promptFragment}`).join(`
45102
45102
  `) : "";
45103
- const stageBlock = stageCfg ? `\u0422\u0415\u041A\u0423\u0429\u0418\u0419 \u042D\u0422\u0410\u041F: ${stage.toUpperCase()}.
45104
- ` + `\u0426\u0415\u041B\u042C \u042D\u0422\u0410\u041F\u0410: ${stageCfg.goal}.` + (stageCfg.guidance ? `
45105
- \u041A\u0410\u041A: ${stageCfg.guidance}` : "") + (stageCfg.groundingRequired ? `
45103
+ const effGoal = options.stageOverride?.goal ?? stageCfg?.goal;
45104
+ const effGuidance = options.stageOverride?.guidance ?? stageCfg?.guidance;
45105
+ const stageBlock = effGoal ? `\u0422\u0415\u041A\u0423\u0429\u0418\u0419 \u042D\u0422\u0410\u041F: ${stage.toUpperCase()}.
45106
+ ` + `\u0426\u0415\u041B\u042C \u042D\u0422\u0410\u041F\u0410: ${effGoal}.` + (effGuidance ? `
45107
+ \u041A\u0410\u041A: ${effGuidance}` : "") + (stageCfg?.groundingRequired ? `
45106
45108
  GROUNDING: \u043D\u0430 \u044D\u0442\u043E\u043C \u044D\u0442\u0430\u043F\u0435 \u0432\u0441\u0435 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u044B\u0435 \u0444\u0430\u043A\u0442\u044B (\u0446\u0438\u0444\u0440\u044B, \u0441\u0443\u043C\u043C\u044B, \u0441\u0440\u043E\u043A\u0438) \u0431\u0435\u0440\u0438 \u0422\u041E\u041B\u042C\u041A\u041E \u0438\u0437 \u0441\u0435\u043A\u0446\u0438\u0438 KB CONTEXT \u043D\u0438\u0436\u0435. \u0415\u0441\u043B\u0438 \u0435\u0451 \u043D\u0435\u0442 \u0438\u043B\u0438 \u043D\u0443\u0436\u043D\u043E\u0433\u043E \u0444\u0430\u043A\u0442\u0430 \u0432 \u043D\u0435\u0439 \u043D\u0435\u0442 \u2014 \u043D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439, \u0441\u043A\u0430\u0436\u0438 \u0447\u0442\u043E \u0443\u0442\u043E\u0447\u043D\u0438\u0448\u044C.` : "") : `\u0422\u0415\u041A\u0423\u0429\u0418\u0419 \u042D\u0422\u0410\u041F: ${stage}. (\u0421\u043F\u0435\u0446\u0438\u0444\u0438\u0447\u0435\u0441\u043A\u0438\u0445 \u043F\u0440\u0430\u0432\u0438\u043B \u0434\u043B\u044F \u044D\u0442\u0430\u043F\u0430 \u043D\u0435\u0442 \u2014 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u043E\u0431\u0449\u0438\u0439 \u0441\u0442\u0438\u043B\u044C.)`;
45107
45109
  const minorRule = guardrails.noMinors ? "- \u0415\u0441\u043B\u0438 prospect <18 \u043B\u0435\u0442 \u2014 \u0432\u0435\u0436\u043B\u0438\u0432\u043E \u0437\u0430\u0432\u0435\u0440\u0448\u0438 \u0434\u0438\u0430\u043B\u043E\u0433." : "";
45108
45110
  const topicsRule = guardrails.forbiddenTopics.length ? `- \u0417\u0430\u043F\u0440\u0435\u0449\u0451\u043D\u043D\u044B\u0435 \u0442\u0435\u043C\u044B: ${guardrails.forbiddenTopics.join(", ")}.` : "";
@@ -59649,7 +59651,7 @@ Schema:
59649
59651
  ${schemaStr}`;
59650
59652
  }
59651
59653
  function parseStructuredOutput(raw, schema) {
59652
- const cleaned = raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "").trim();
59654
+ const cleaned = raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s{0,1000}```$/, "").trim();
59653
59655
  let parsed;
59654
59656
  try {
59655
59657
  parsed = JSON.parse(cleaned);
@@ -59905,7 +59907,8 @@ ${kbContextStr}` : vacBlock : kbContextStr;
59905
59907
  ...input.conversationSummary ? { conversationSummary: input.conversationSummary } : {},
59906
59908
  ...input.skills && input.skills.length > 0 ? { skills: input.skills } : {},
59907
59909
  ...input.directorHooks && input.directorHooks.length > 0 ? { directorHooks: input.directorHooks } : {},
59908
- ...input.supportPhase ? { supportPhase: input.supportPhase } : {}
59910
+ ...input.supportPhase ? { supportPhase: input.supportPhase } : {},
59911
+ ...input.stageOverride ? { stageOverride: input.stageOverride } : {}
59909
59912
  });
59910
59913
  temperature = input.style.model.temperature;
59911
59914
  } else {
@@ -60092,7 +60095,8 @@ ${kbContextStr}` : vacBlock : kbContextStr;
60092
60095
  ...input.conversationSummary ? { conversationSummary: input.conversationSummary } : {},
60093
60096
  ...input.skills && input.skills.length > 0 ? { skills: input.skills } : {},
60094
60097
  ...input.directorHooks && input.directorHooks.length > 0 ? { directorHooks: input.directorHooks } : {},
60095
- ...input.supportPhase ? { supportPhase: input.supportPhase } : {}
60098
+ ...input.supportPhase ? { supportPhase: input.supportPhase } : {},
60099
+ ...input.stageOverride ? { stageOverride: input.stageOverride } : {}
60096
60100
  });
60097
60101
  temperature = input.style.model.temperature;
60098
60102
  } else {
@@ -60296,7 +60300,7 @@ class OllamaChatClient {
60296
60300
  disableThinking;
60297
60301
  timeoutMs;
60298
60302
  constructor(opts) {
60299
- this.host = opts.host.replace(/\/+$/, "");
60303
+ this.host = opts.host.replace(/\/{1,512}$/, "");
60300
60304
  this.model = opts.model;
60301
60305
  this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
60302
60306
  this.disableThinking = opts.disableThinking ?? true;
@@ -60427,7 +60431,7 @@ class OllamaEmbeddingClient {
60427
60431
  fetchImpl;
60428
60432
  timeoutMs;
60429
60433
  constructor(opts) {
60430
- this.host = opts.host.replace(/\/+$/, "");
60434
+ this.host = opts.host.replace(/\/{1,512}$/, "");
60431
60435
  this.model = opts.model;
60432
60436
  this.dim = opts.dim;
60433
60437
  this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
@@ -60478,7 +60482,7 @@ class OpenAIChatClient {
60478
60482
  if (!opts.apiKey)
60479
60483
  throw new Error("OpenAIChatClient: apiKey required");
60480
60484
  this.apiKey = opts.apiKey;
60481
- this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
60485
+ this.baseUrl = opts.baseUrl.replace(/\/{1,512}$/, "");
60482
60486
  this.model = opts.model;
60483
60487
  this.timeoutMs = opts.timeoutMs ?? 60000;
60484
60488
  this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
@@ -60624,7 +60628,7 @@ class OpenAIEmbeddingClient {
60624
60628
  if (!opts.apiKey)
60625
60629
  throw new Error("OpenAIEmbeddingClient: apiKey required");
60626
60630
  this.apiKey = opts.apiKey;
60627
- this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
60631
+ this.baseUrl = opts.baseUrl.replace(/\/{1,512}$/, "");
60628
60632
  this.model = opts.model;
60629
60633
  this.dim = opts.dim;
60630
60634
  this.timeoutMs = opts.timeoutMs ?? 60000;
@@ -60701,7 +60705,7 @@ class OpenRouterChatClient {
60701
60705
  throw new Error("OpenRouterChatClient: model required");
60702
60706
  }
60703
60707
  this.apiKey = opts.apiKey;
60704
- this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
60708
+ this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/{1,512}$/, "");
60705
60709
  this.model = opts.model;
60706
60710
  this.siteUrl = opts.siteUrl;
60707
60711
  this.appName = opts.appName;
@@ -61111,7 +61115,7 @@ function chunkBySections(text, opts = {}) {
61111
61115
  bodyLines = [];
61112
61116
  };
61113
61117
  for (const line of lines) {
61114
- const m = line.match(/^(#{1,6})\s+(.+)$/);
61118
+ const m = line.match(/^(#{1,6})\s+(\S.*)$/);
61115
61119
  if (m) {
61116
61120
  flush();
61117
61121
  currentHeading = m[2] ?? null;
@@ -61719,7 +61723,10 @@ function* walk(dir) {
61719
61723
  function stripNonContent(raw) {
61720
61724
  let s = raw;
61721
61725
  s = s.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
61722
- s = s.replace(/<!--[\s\S]*?-->/g, "");
61726
+ for (let prev = "";prev !== s; ) {
61727
+ prev = s;
61728
+ s = s.replace(/<!--[\s\S]{0,10000}?-->/g, "");
61729
+ }
61723
61730
  s = s.replace(/\n{3,}/g, `
61724
61731
 
61725
61732
  `).trim();
@@ -61806,7 +61813,7 @@ class CohereReranker {
61806
61813
  throw new Error("CohereReranker: apiKey required");
61807
61814
  this.apiKey = opts.apiKey;
61808
61815
  this.model = opts.model ?? "rerank-v3.5";
61809
- this.baseUrl = (opts.baseUrl ?? "https://api.cohere.com/v2").replace(/\/+$/, "");
61816
+ this.baseUrl = (opts.baseUrl ?? "https://api.cohere.com/v2").replace(/\/{1,512}$/, "");
61810
61817
  this.timeoutMs = opts.timeoutMs ?? 30000;
61811
61818
  this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
61812
61819
  }
@@ -61852,7 +61859,7 @@ class JinaReranker {
61852
61859
  throw new Error("JinaReranker: apiKey required");
61853
61860
  this.apiKey = opts.apiKey;
61854
61861
  this.model = opts.model ?? "jina-reranker-v2-base-multilingual";
61855
- this.baseUrl = (opts.baseUrl ?? "https://api.jina.ai/v1").replace(/\/+$/, "");
61862
+ this.baseUrl = (opts.baseUrl ?? "https://api.jina.ai/v1").replace(/\/{1,512}$/, "");
61856
61863
  this.timeoutMs = opts.timeoutMs ?? 30000;
61857
61864
  this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
61858
61865
  }
@@ -62424,7 +62431,7 @@ async function classifyPhoto(opts) {
62424
62431
  if (!opts.apiKey || opts.apiKey.trim().length === 0) {
62425
62432
  throw new Error("classifyPhoto: apiKey required");
62426
62433
  }
62427
- const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/, "");
62434
+ const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/{1,512}$/, "");
62428
62435
  const fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
62429
62436
  const mime = opts.mimeType?.trim() ? opts.mimeType : "image/jpeg";
62430
62437
  const base643 = Buffer.from(opts.bytes).toString("base64");
@@ -62519,7 +62526,7 @@ async function extractPassportIdentity(opts) {
62519
62526
  if (!opts.apiKey || opts.apiKey.trim().length === 0) {
62520
62527
  throw new Error("extractPassportIdentity: apiKey required");
62521
62528
  }
62522
- const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/+$/, "");
62529
+ const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/{1,512}$/, "");
62523
62530
  const fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
62524
62531
  const mime = opts.mimeType?.trim() ? opts.mimeType : "image/jpeg";
62525
62532
  const base643 = Buffer.from(opts.bytes).toString("base64");
@@ -1 +1 @@
1
- {"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../src/ingest.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,YAAY,EAAa,MAAM,YAAY,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAEjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3C,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,QAAQ,CAAC;IACb,QAAQ,EAAE,eAAe,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;;sEAEkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;iFAC6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA+C1F;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACtC,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,gBAAgB,CAAC,CA4C3B;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,sBAAsB,CAAC,CAejC;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK7E;AAaD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMnD"}
1
+ {"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../src/ingest.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,YAAY,EAAa,MAAM,YAAY,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAEjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3C,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,QAAQ,CAAC;IACb,QAAQ,EAAE,eAAe,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;;sEAEkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;iFAC6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA+C1F;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACtC,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,gBAAgB,CAAC,CA4C3B;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,sBAAsB,CAAC,CAejC;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK7E;AAaD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAWnD"}
@@ -1 +1 @@
1
- {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAQ,KAAK,EAAE,MAAM,aAAa,CAAC;AA6C5E;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,WAAW,EAClB,mBAAmB,GAAE,MAAM,GAAG,IAAW,EACzC,OAAO,GAAE,cAAmB,GAC3B,MAAM,CAqIR"}
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAQ,KAAK,EAAE,MAAM,aAAa,CAAC;AA6C5E;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,WAAW,EAClB,mBAAmB,GAAE,MAAM,GAAG,IAAW,EACzC,OAAO,GAAE,cAAmB,GAC3B,MAAM,CAyIR"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=prompt.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.test.d.ts","sourceRoot":"","sources":["../src/prompt.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=reflect.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reflect.test.d.ts","sourceRoot":"","sources":["../src/reflect.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rewrite-query.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rewrite-query.test.d.ts","sourceRoot":"","sources":["../src/rewrite-query.test.ts"],"names":[],"mappings":""}
package/dist/styles.d.ts CHANGED
@@ -182,5 +182,14 @@ export interface ComposeOptions {
182
182
  * block instead. `docs` = collecting their documents, `submitted` = filed.
183
183
  */
184
184
  supportPhase?: "docs" | "submitted";
185
+ /**
186
+ * Per-stage override (Phase 2): goal/guidance from the lead's current funnel
187
+ * stage_definition. Takes precedence over `style.stages[stage]` for the stage
188
+ * block. Lets AI-built funnels carry per-stage instructions.
189
+ */
190
+ stageOverride?: {
191
+ goal: string;
192
+ guidance?: string;
193
+ };
185
194
  }
186
195
  //# sourceMappingURL=styles.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,aAAa,+DAAgE,CAAC;AAC3F,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,eAAO,MAAM,gBAAgB,2DAA4D,CAAC;AAC1F,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/D,eAAO,MAAM,UAAU,2FAOb,CAAC;AACX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnD,eAAO,MAAM,UAAU;;;;;;;;;;iBAGrB,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAE9C,eAAO,MAAM,iBAAiB;;;;;iBAK5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,aAAa;;;;;;;;iBAKxB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEzD,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsCtB,CAAC;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,MAAM,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACnC;;;;OAIG;IACH,aAAa,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACjD;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;CACrC"}
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,aAAa,+DAAgE,CAAC;AAC3F,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,eAAO,MAAM,gBAAgB,2DAA4D,CAAC;AAC1F,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/D,eAAO,MAAM,UAAU,2FAOb,CAAC;AACX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnD,eAAO,MAAM,UAAU;;;;;;;;;;iBAGrB,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAE9C,eAAO,MAAM,iBAAiB;;;;;iBAK5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,aAAa;;;;;;;;iBAKxB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEzD,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsCtB,CAAC;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,MAAM,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACnC;;;;OAIG;IACH,aAAa,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACjD;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IACpC;;;;OAIG;IACH,aAAa,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatman-media/kb",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Tenant-scoped Knowledge Base: hybrid retrieval (pgvector + BM25), ingest, answer pipeline, persona/skill composition. LLM I/O живёт в @chatman-media/llm-router.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -56,7 +56,7 @@
56
56
  "url": "https://github.com/chatman-media/lead-engine/issues"
57
57
  },
58
58
  "dependencies": {
59
- "@chatman-media/llm-router": "1.0.0",
59
+ "@chatman-media/llm-router": "1.1.1",
60
60
  "unpdf": "^1.6.2",
61
61
  "zod": "^4.4.1"
62
62
  },
@@ -57,6 +57,12 @@ export interface AnswerInput {
57
57
  * uses a calm FAQ-support block instead — answering questions without selling.
58
58
  */
59
59
  supportPhase?: "docs" | "submitted";
60
+ /**
61
+ * Per-stage override (Phase 2 C-2): the lead's current funnel-stage goal/
62
+ * guidance from stage_definitions. Takes precedence over the Style's
63
+ * per-sales-stage config when composing the prompt.
64
+ */
65
+ stageOverride?: { goal: string; guidance?: string };
60
66
  /**
61
67
  * Called after every `answerWithRag` or `answerWithRagStream` call with the
62
68
  * final telemetry. Useful for logging, metrics, or A/B experiment recording
package/src/answer.ts CHANGED
@@ -234,6 +234,7 @@ async function answerFromHits(opts: {
234
234
  ? { directorHooks: input.directorHooks }
235
235
  : {}),
236
236
  ...(input.supportPhase ? { supportPhase: input.supportPhase } : {}),
237
+ ...(input.stageOverride ? { stageOverride: input.stageOverride } : {}),
237
238
  });
238
239
  temperature = input.style.model.temperature;
239
240
  } else {
@@ -503,6 +504,7 @@ export async function* answerWithRagStream(input: AnswerInput): AsyncIterable<st
503
504
  ? { directorHooks: input.directorHooks }
504
505
  : {}),
505
506
  ...(input.supportPhase ? { supportPhase: input.supportPhase } : {}),
507
+ ...(input.stageOverride ? { stageOverride: input.stageOverride } : {}),
506
508
  });
507
509
  temperature = input.style.model.temperature;
508
510
  } else {
package/src/chunk.ts CHANGED
@@ -137,7 +137,7 @@ export function chunkBySections(text: string, opts: Partial<ChunkOptions> = {}):
137
137
  };
138
138
 
139
139
  for (const line of lines) {
140
- const m = line.match(/^(#{1,6})\s+(.+)$/);
140
+ const m = line.match(/^(#{1,6})\s+(\S.*)$/);
141
141
  if (m) {
142
142
  flush();
143
143
  currentHeading = m[2] ?? null;
@@ -0,0 +1,44 @@
1
+ import type { ChatClient, ChatMessage } from "@chatman-media/llm-router";
2
+ import { describe, expect, it } from "bun:test";
3
+ import { extractUserFacts, parseFactsFromLlmOutput } from "./extract-user-facts.ts";
4
+
5
+ const chat = (text: string): ChatClient => ({ complete: async () => text }) as unknown as ChatClient;
6
+ const chatThrows = (): ChatClient =>
7
+ ({ complete: async () => { throw new Error("x"); } }) as unknown as ChatClient;
8
+
9
+ describe("parseFactsFromLlmOutput", () => {
10
+ it("нет {} → {}", () => {
11
+ expect(parseFactsFromLlmOutput("no json")).toEqual({});
12
+ });
13
+ it("битый JSON → {}", () => {
14
+ expect(parseFactsFromLlmOutput("{broken")).toEqual({});
15
+ });
16
+ it("массив → {}", () => {
17
+ expect(parseFactsFromLlmOutput("[1,2]")).toEqual({});
18
+ });
19
+ it("валидный объект + coercion числовых значений", () => {
20
+ expect(parseFactsFromLlmOutput('{"name":"Аня","age":23}')).toEqual({ name: "Аня", age: "23" });
21
+ });
22
+ it("пустые / слишком длинные значения и длинные ключи отбрасываются", () => {
23
+ const longVal = "x".repeat(300);
24
+ const longKey = "k".repeat(50);
25
+ const out = parseFactsFromLlmOutput(`{"city":" ","name":"Аня","big":"${longVal}","${longKey}":"v"}`);
26
+ expect(out).toEqual({ name: "Аня" });
27
+ });
28
+ it("code-fence/think снимаются", () => {
29
+ expect(parseFactsFromLlmOutput('```json\n{"name":"Ира"}\n```')).toEqual({ name: "Ира" });
30
+ });
31
+ });
32
+
33
+ describe("extractUserFacts", () => {
34
+ const msgs: ChatMessage[] = [{ role: "user", content: "я Аня, мне 23" }];
35
+ it("нет user-сообщений → {} без LLM", async () => {
36
+ expect(await extractUserFacts({ messages: [{ role: "assistant", content: "hi" }], chat: chatThrows() })).toEqual({});
37
+ });
38
+ it("парсит факты из LLM", async () => {
39
+ expect(await extractUserFacts({ messages: msgs, chat: chat('{"name":"Аня","age":"23"}') })).toEqual({ name: "Аня", age: "23" });
40
+ });
41
+ it("LLM упал → {}", async () => {
42
+ expect(await extractUserFacts({ messages: msgs, chat: chatThrows() })).toEqual({});
43
+ });
44
+ });
@@ -0,0 +1,66 @@
1
+ import type { ChatClient } from "@chatman-media/llm-router";
2
+ import { afterEach, describe, expect, it } from "bun:test";
3
+ import { checkFacts, parseFactCheckResult } from "./fact-checker.ts";
4
+
5
+ const chat = (text: string): ChatClient => ({ complete: async () => text }) as unknown as ChatClient;
6
+ const chatThrows = (): ChatClient =>
7
+ ({ complete: async () => { throw new Error("down"); } }) as unknown as ChatClient;
8
+
9
+ const savedEnv = process.env.RAG_FACT_CHECKER_FAIL_OPEN;
10
+ afterEach(() => {
11
+ if (savedEnv === undefined) delete process.env.RAG_FACT_CHECKER_FAIL_OPEN;
12
+ else process.env.RAG_FACT_CHECKER_FAIL_OPEN = savedEnv;
13
+ });
14
+
15
+ describe("parseFactCheckResult", () => {
16
+ it("чистый JSON", () => {
17
+ expect(parseFactCheckResult('{"grounded":false,"vacancyOk":true,"reason":"x"}')).toEqual({
18
+ grounded: false,
19
+ vacancyOk: true,
20
+ reason: "x",
21
+ });
22
+ });
23
+ it("code-fence + think убираются", () => {
24
+ expect(parseFactCheckResult('<think>..</think>```json\n{"grounded":true,"vacancyOk":false}\n```')).toMatchObject({
25
+ grounded: true,
26
+ vacancyOk: false,
27
+ });
28
+ });
29
+ it("нет {} → OK (fail-open парсинга)", () => {
30
+ expect(parseFactCheckResult("no json")).toEqual({ grounded: true, vacancyOk: true });
31
+ });
32
+ it("битый JSON → OK", () => {
33
+ expect(parseFactCheckResult("{broken")).toEqual({ grounded: true, vacancyOk: true });
34
+ });
35
+ it("non-bool поля → дефолт true", () => {
36
+ expect(parseFactCheckResult('{"grounded":"yes"}')).toEqual({ grounded: true, vacancyOk: true });
37
+ });
38
+ });
39
+
40
+ describe("checkFacts", () => {
41
+ const base = { question: "q", answer: "Зарплата 1500$", context: "контекст 1500$" };
42
+ it("пустой ответ → OK без вызова LLM", async () => {
43
+ expect(await checkFacts({ ...base, answer: " ", chat: chatThrows() })).toEqual({ grounded: true, vacancyOk: true });
44
+ });
45
+ it("нет контекста и вакансий → OK", async () => {
46
+ expect(await checkFacts({ ...base, context: "", chat: chatThrows() })).toEqual({ grounded: true, vacancyOk: true });
47
+ });
48
+ it("есть контекст → парсит вердикт LLM", async () => {
49
+ const r = await checkFacts({ ...base, chat: chat('{"grounded":false,"vacancyOk":true,"reason":"выдумал"}') });
50
+ expect(r.grounded).toBe(false);
51
+ });
52
+ it("vacancies-ветка промпта", async () => {
53
+ const r = await checkFacts({ ...base, vacanciesBlock: "Вакансия: 2000$", chat: chat('{"grounded":true,"vacancyOk":false,"reason":"не совпало"}') });
54
+ expect(r.vacancyOk).toBe(false);
55
+ });
56
+ it("LLM упал → fail-closed по умолчанию", async () => {
57
+ delete process.env.RAG_FACT_CHECKER_FAIL_OPEN;
58
+ const r = await checkFacts({ ...base, chat: chatThrows() });
59
+ expect(r.grounded).toBe(false);
60
+ expect(r.reason).toContain("checker_error");
61
+ });
62
+ it("LLM упал + FAIL_OPEN=1 → пропускаем", async () => {
63
+ process.env.RAG_FACT_CHECKER_FAIL_OPEN = "1";
64
+ expect(await checkFacts({ ...base, chat: chatThrows() })).toEqual({ grounded: true, vacancyOk: true });
65
+ });
66
+ });
@@ -0,0 +1,39 @@
1
+ import type { ChatClient } from "@chatman-media/llm-router";
2
+ import { describe, expect, it } from "bun:test";
3
+ import { gradeSkills, parseSlugList } from "./grade-skills.ts";
4
+
5
+ const chat = (text: string): ChatClient => ({ complete: async () => text }) as unknown as ChatClient;
6
+ const chatThrows = (): ChatClient =>
7
+ ({ complete: async () => { throw new Error("x"); } }) as unknown as ChatClient;
8
+ const ALLOWED = ["mirroring", "scarcity-spots-left", "tactical-empathy"] as const;
9
+
10
+ describe("parseSlugList", () => {
11
+ it("пусто → []", () => {
12
+ expect(parseSlugList("", ALLOWED)).toEqual([]);
13
+ });
14
+ it("JSON-массив, фильтр по allowed", () => {
15
+ expect(parseSlugList('["mirroring","unknown-skill","tactical-empathy"]', ALLOWED)).toEqual(["mirroring", "tactical-empathy"]);
16
+ });
17
+ it("code-fenced JSON", () => {
18
+ expect(parseSlugList('```json\n["scarcity-spots-left"]\n```', ALLOWED)).toEqual(["scarcity-spots-left"]);
19
+ });
20
+ it("comma/newline текст → fallback-парсинг", () => {
21
+ expect(parseSlugList("mirroring, tactical-empathy", ALLOWED)).toEqual(["mirroring", "tactical-empathy"]);
22
+ });
23
+ it("всё неразрешённое → []", () => {
24
+ expect(parseSlugList('["foo","bar"]', ALLOWED)).toEqual([]);
25
+ });
26
+ });
27
+
28
+ describe("gradeSkills", () => {
29
+ const base = { question: "q", reply: "r", availableSlugs: ALLOWED };
30
+ it("пустой availableSlugs → [] без LLM", async () => {
31
+ expect(await gradeSkills({ ...base, availableSlugs: [], chat: chatThrows() })).toEqual([]);
32
+ });
33
+ it("парсит и фильтрует ответ LLM", async () => {
34
+ expect(await gradeSkills({ ...base, chat: chat('["mirroring","x"]') })).toEqual(["mirroring"]);
35
+ });
36
+ it("LLM упал → [] (failure-soft)", async () => {
37
+ expect(await gradeSkills({ ...base, chat: chatThrows() })).toEqual([]);
38
+ });
39
+ });
package/src/ingest.ts CHANGED
@@ -187,7 +187,12 @@ function* walk(dir: string): Generator<string> {
187
187
  export function stripNonContent(raw: string): string {
188
188
  let s = raw;
189
189
  s = s.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
190
- s = s.replace(/<!--[\s\S]*?-->/g, "");
190
+ // Strip HTML comments. Bounded quantifier avoids polynomial backtracking;
191
+ // loop until stable so a leftover `<!--` can't survive a single pass.
192
+ for (let prev = ""; prev !== s; ) {
193
+ prev = s;
194
+ s = s.replace(/<!--[\s\S]{0,10000}?-->/g, "");
195
+ }
191
196
  s = s.replace(/\n{3,}/g, "\n\n").trim();
192
197
  return `${s}\n`;
193
198
  }
@@ -0,0 +1,44 @@
1
+ // Unit tests for kb composeSystemPrompt — the prompt builder used by the reply
2
+ // pipeline (answer.ts). Focus: Phase 2 C-2 `stageOverride` precedence (the
3
+ // lead's funnel-stage goal/guidance wins over the Style's per-sales-stage cfg).
4
+
5
+ import { describe, expect, it } from "bun:test";
6
+ import { composeSystemPrompt } from "./prompt.ts";
7
+ import type { Style } from "./styles.ts";
8
+
9
+ const baseStyle: Style = {
10
+ slug: "t",
11
+ displayName: "T",
12
+ persona: { name: "Алекс", role: "human" },
13
+ voice: { tone: "friendly", language: "ru", forbid: [] },
14
+ framework: "SPIN",
15
+ hooks: [],
16
+ stages: { qualify: { goal: "STYLE_GOAL", groundingRequired: false } },
17
+ fewShot: [],
18
+ guardrails: { noMinors: true, botDisclosureOnDirectQuestion: true, forbiddenTopics: [] },
19
+ model: { id: "x", temperature: 0.5, maxTokens: 100 },
20
+ };
21
+
22
+ describe("composeSystemPrompt — stageOverride (Phase 2 C-2)", () => {
23
+ it("без override → goal берётся из Style", () => {
24
+ const p = composeSystemPrompt(baseStyle, "qualify");
25
+ expect(p).toContain("STYLE_GOAL");
26
+ });
27
+
28
+ it("stageOverride имеет приоритет над Style для goal и guidance", () => {
29
+ const p = composeSystemPrompt(baseStyle, "qualify", null, {
30
+ stageOverride: { goal: "OVERRIDE_GOAL", guidance: "OVERRIDE_GUIDE" },
31
+ });
32
+ expect(p).toContain("OVERRIDE_GOAL");
33
+ expect(p).toContain("OVERRIDE_GUIDE");
34
+ expect(p).not.toContain("STYLE_GOAL");
35
+ });
36
+
37
+ it("stageOverride работает для стадии без конфига в Style", () => {
38
+ // "close" нет в baseStyle.stages → раньше был бы generic-блок; теперь override.
39
+ const p = composeSystemPrompt(baseStyle, "close", null, {
40
+ stageOverride: { goal: "CLOSE_GOAL" },
41
+ });
42
+ expect(p).toContain("CLOSE_GOAL");
43
+ });
44
+ });
package/src/prompt.ts CHANGED
@@ -121,11 +121,15 @@ export function composeSystemPrompt(
121
121
  skillsForStage.map((s) => `- ${s.displayName} — ${s.promptFragment}`).join("\n")
122
122
  : "";
123
123
 
124
- const stageBlock = stageCfg
124
+ // Per-stage instructions: the lead's funnel-stage override (Phase 2) takes
125
+ // precedence over the Style's per-sales-stage config; grounding stays from style.
126
+ const effGoal = options.stageOverride?.goal ?? stageCfg?.goal;
127
+ const effGuidance = options.stageOverride?.guidance ?? stageCfg?.guidance;
128
+ const stageBlock = effGoal
125
129
  ? `ТЕКУЩИЙ ЭТАП: ${stage.toUpperCase()}.\n` +
126
- `ЦЕЛЬ ЭТАПА: ${stageCfg.goal}.` +
127
- (stageCfg.guidance ? `\nКАК: ${stageCfg.guidance}` : "") +
128
- (stageCfg.groundingRequired
130
+ `ЦЕЛЬ ЭТАПА: ${effGoal}.` +
131
+ (effGuidance ? `\nКАК: ${effGuidance}` : "") +
132
+ (stageCfg?.groundingRequired
129
133
  ? `\nGROUNDING: на этом этапе все конкретные факты (цифры, суммы, сроки) бери ТОЛЬКО из секции KB CONTEXT ниже. Если её нет или нужного факта в ней нет — не выдумывай, скажи что уточнишь.`
130
134
  : "")
131
135
  : `ТЕКУЩИЙ ЭТАП: ${stage}. (Специфических правил для этапа нет — используй общий стиль.)`;
@@ -0,0 +1,44 @@
1
+ import type { ChatClient } from "@chatman-media/llm-router";
2
+ import { describe, expect, it } from "bun:test";
3
+ import { parseReflection, verifyAnswer } from "./reflect.ts";
4
+
5
+ const chat = (text: string): ChatClient => ({ complete: async () => text }) as unknown as ChatClient;
6
+ const chatThrows = (): ChatClient =>
7
+ ({ complete: async () => { throw new Error("x"); } }) as unknown as ChatClient;
8
+
9
+ describe("parseReflection", () => {
10
+ it("нет {} → grounded:true", () => {
11
+ expect(parseReflection("plain")).toEqual({ grounded: true });
12
+ });
13
+ it("битый JSON → true", () => {
14
+ expect(parseReflection("{nope")).toEqual({ grounded: true });
15
+ });
16
+ it("grounded non-bool → true", () => {
17
+ expect(parseReflection('{"grounded":1}')).toEqual({ grounded: true });
18
+ });
19
+ it("grounded:true → {true}", () => {
20
+ expect(parseReflection('{"grounded":true}')).toEqual({ grounded: true });
21
+ });
22
+ it("grounded:false → {false, reason}", () => {
23
+ expect(parseReflection('{"grounded":false,"reason":"выдумал город"}')).toEqual({ grounded: false, reason: "выдумал город" });
24
+ });
25
+ it("grounded:false без reason → unknown", () => {
26
+ expect(parseReflection('{"grounded":false}')).toEqual({ grounded: false, reason: "unknown" });
27
+ });
28
+ });
29
+
30
+ describe("verifyAnswer", () => {
31
+ const base = { question: "q", answer: "Дубай 1500$", context: "Дубай 1500$" };
32
+ it("пустой ответ → grounded:true", async () => {
33
+ expect(await verifyAnswer({ ...base, answer: " ", chat: chatThrows() })).toEqual({ grounded: true });
34
+ });
35
+ it("нет контекста → grounded:true", async () => {
36
+ expect(await verifyAnswer({ ...base, context: "", chat: chatThrows() })).toEqual({ grounded: true });
37
+ });
38
+ it("LLM-вердикт false → {false,reason}", async () => {
39
+ expect(await verifyAnswer({ ...base, chat: chat('{"grounded":false,"reason":"r"}') })).toMatchObject({ grounded: false });
40
+ });
41
+ it("LLM упал → grounded:true (fail-open)", async () => {
42
+ expect(await verifyAnswer({ ...base, chat: chatThrows() })).toEqual({ grounded: true });
43
+ });
44
+ });
package/src/reranker.ts CHANGED
@@ -49,7 +49,7 @@ export class CohereReranker implements Reranker {
49
49
  if (!opts.apiKey) throw new Error("CohereReranker: apiKey required");
50
50
  this.apiKey = opts.apiKey;
51
51
  this.model = opts.model ?? "rerank-v3.5";
52
- this.baseUrl = (opts.baseUrl ?? "https://api.cohere.com/v2").replace(/\/+$/, "");
52
+ this.baseUrl = (opts.baseUrl ?? "https://api.cohere.com/v2").replace(/\/{1,512}$/, "");
53
53
  this.timeoutMs = opts.timeoutMs ?? 30_000;
54
54
  this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
55
55
  }
@@ -128,7 +128,7 @@ export class JinaReranker implements Reranker {
128
128
  if (!opts.apiKey) throw new Error("JinaReranker: apiKey required");
129
129
  this.apiKey = opts.apiKey;
130
130
  this.model = opts.model ?? "jina-reranker-v2-base-multilingual";
131
- this.baseUrl = (opts.baseUrl ?? "https://api.jina.ai/v1").replace(/\/+$/, "");
131
+ this.baseUrl = (opts.baseUrl ?? "https://api.jina.ai/v1").replace(/\/{1,512}$/, "");
132
132
  this.timeoutMs = opts.timeoutMs ?? 30_000;
133
133
  this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
134
134
  }
@@ -0,0 +1,56 @@
1
+ import type { ChatClient, ChatMessage } from "@chatman-media/llm-router";
2
+ import { describe, expect, it } from "bun:test";
3
+ import { questionNeedsRewrite, rewriteQuery, sanitizeRewritten } from "./rewrite-query.ts";
4
+
5
+ const chat = (text: string): ChatClient => ({ complete: async () => text }) as unknown as ChatClient;
6
+ const chatThrows = (): ChatClient =>
7
+ ({ complete: async () => { throw new Error("x"); } }) as unknown as ChatClient;
8
+ const hist: ChatMessage[] = [{ role: "assistant", content: "в Дубае платят 1500" }];
9
+
10
+ describe("questionNeedsRewrite", () => {
11
+ it("пустой → false", () => {
12
+ expect(questionNeedsRewrite(" ", hist)).toBe(false);
13
+ });
14
+ it("нет истории → false", () => {
15
+ expect(questionNeedsRewrite("а там как?", [])).toBe(false);
16
+ });
17
+ it("короткий (<=4 слова) с историей → true", () => {
18
+ expect(questionNeedsRewrite("а виза как?", hist)).toBe(true);
19
+ });
20
+ it("дейктик-маркер → true", () => {
21
+ expect(questionNeedsRewrite("сколько стоит это оформление документов в итоге", hist)).toBe(true);
22
+ });
23
+ it("follow-up союз в начале → true", () => {
24
+ expect(questionNeedsRewrite("или есть другие варианты работы там сейчас", hist)).toBe(true);
25
+ });
26
+ it("длинный самостоятельный → false", () => {
27
+ expect(questionNeedsRewrite("сколько платят моделям в Дубае за месяц работы по контракту", hist)).toBe(false);
28
+ });
29
+ });
30
+
31
+ describe("sanitizeRewritten", () => {
32
+ it("снимает think/fence/«ответ:» и берёт первую строку", () => {
33
+ expect(sanitizeRewritten('<think>..</think>ответ: какие условия в Дубае\nещё текст', "fb", 200)).toBe("какие условия в Дубае");
34
+ });
35
+ it("пусто/мусор → fallback", () => {
36
+ expect(sanitizeRewritten(" \n ", "ОРИГИНАЛ", 200)).toBe("ОРИГИНАЛ");
37
+ });
38
+ it("обрезка по maxLength", () => {
39
+ expect(sanitizeRewritten("абвгде", "fb", 3)).toBe("абв");
40
+ });
41
+ });
42
+
43
+ describe("rewriteQuery", () => {
44
+ it("пустой вопрос → как есть", async () => {
45
+ expect(await rewriteQuery({ question: " ", chat: chatThrows() })).toBe("");
46
+ });
47
+ it("не нужен рерайт (нет истории) → оригинал без вызова LLM", async () => {
48
+ expect(await rewriteQuery({ question: "а там?", chat: chatThrows() })).toBe("а там?");
49
+ });
50
+ it("нужен рерайт → sanitized из LLM", async () => {
51
+ expect(await rewriteQuery({ question: "а виза?", history: hist, chat: chat("как оформляется виза") })).toBe("как оформляется виза");
52
+ });
53
+ it("LLM упал → оригинал", async () => {
54
+ expect(await rewriteQuery({ question: "а виза?", history: hist, chat: chatThrows() })).toBe("а виза?");
55
+ });
56
+ });
@@ -24,7 +24,7 @@ export function parseStructuredOutput<T extends z.ZodTypeAny>(
24
24
  const cleaned = raw
25
25
  .trim()
26
26
  .replace(/^```(?:json)?\s*/i, "")
27
- .replace(/\s*```$/, "")
27
+ .replace(/\s{0,1000}```$/, "")
28
28
  .trim();
29
29
 
30
30
  let parsed: unknown;
package/src/styles.ts CHANGED
@@ -135,4 +135,10 @@ export interface ComposeOptions {
135
135
  * block instead. `docs` = collecting their documents, `submitted` = filed.
136
136
  */
137
137
  supportPhase?: "docs" | "submitted";
138
+ /**
139
+ * Per-stage override (Phase 2): goal/guidance from the lead's current funnel
140
+ * stage_definition. Takes precedence over `style.stages[stage]` for the stage
141
+ * block. Lets AI-built funnels carry per-stage instructions.
142
+ */
143
+ stageOverride?: { goal: string; guidance?: string };
138
144
  }
@@ -37,7 +37,7 @@ export interface TextStyleRule {
37
37
  export const replaceEmDash: TextStyleRule = {
38
38
  name: "replace-em-dash",
39
39
  description: "U+2014 «—» → «-» (с нормализацией пробелов)",
40
- apply: (s) => s.replace(/\s*—\s*/g, " - ").replace(/ {2,}/g, " "),
40
+ apply: (s) => s.replace(/\s{0,200}—\s{0,200}/g, " - ").replace(/ {2,}/g, " "),
41
41
  };
42
42
 
43
43
  /**
package/src/vision.ts CHANGED
@@ -74,7 +74,7 @@ export async function classifyPhoto(opts: ClassifyPhotoOptions): Promise<PhotoCl
74
74
  if (!opts.apiKey || opts.apiKey.trim().length === 0) {
75
75
  throw new Error("classifyPhoto: apiKey required");
76
76
  }
77
- const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
77
+ const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/{1,512}$/, "");
78
78
  const fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
79
79
  const mime = opts.mimeType?.trim() ? opts.mimeType : "image/jpeg";
80
80
  const base64 = Buffer.from(opts.bytes).toString("base64");
@@ -210,7 +210,7 @@ export async function extractPassportIdentity(
210
210
  if (!opts.apiKey || opts.apiKey.trim().length === 0) {
211
211
  throw new Error("extractPassportIdentity: apiKey required");
212
212
  }
213
- const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
213
+ const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/{1,512}$/, "");
214
214
  const fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
215
215
  const mime = opts.mimeType?.trim() ? opts.mimeType : "image/jpeg";
216
216
  const base64 = Buffer.from(opts.bytes).toString("base64");