@theokit/sdk 2.15.0 → 2.15.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.cjs CHANGED
@@ -13650,11 +13650,16 @@ function sanitizeToolInput(input, options) {
13650
13650
  // src/internal/llm/hermes-tool-extract.ts
13651
13651
  var HERMES_BLOCK = /<function=\s*([^>\s]+)\s*>([\s\S]*?)<\/tool_call>/g;
13652
13652
  var HERMES_PARAM = /<parameter=\s*([^>\s]+)\s*>([\s\S]*?)<\/parameter>/g;
13653
- function extractHermesToolCalls(content, makeId) {
13653
+ function extractHermesToolCalls(content, makeId, allowedToolNames) {
13654
+ const isPromoted = (name) => name.length > 0 && (allowedToolNames === void 0 || allowedToolNames.has(name));
13654
13655
  const toolCalls = [];
13656
+ const droppedNames = [];
13655
13657
  for (const block of content.matchAll(HERMES_BLOCK)) {
13656
13658
  const name = (block[1] ?? "").trim();
13657
- if (name.length === 0) continue;
13659
+ if (!isPromoted(name)) {
13660
+ if (name.length > 0 && allowedToolNames !== void 0) droppedNames.push(name);
13661
+ continue;
13662
+ }
13658
13663
  toolCalls.push({
13659
13664
  type: "tool_use",
13660
13665
  id: makeId(),
@@ -13662,8 +13667,11 @@ function extractHermesToolCalls(content, makeId) {
13662
13667
  input: parseHermesParams(block[2] ?? "")
13663
13668
  });
13664
13669
  }
13665
- const residualText = toolCalls.length === 0 ? content : content.replace(HERMES_BLOCK, "").trim();
13666
- return { toolCalls, residualText };
13670
+ const residualText = toolCalls.length === 0 ? content : content.replace(
13671
+ HERMES_BLOCK,
13672
+ (full, rawName) => isPromoted((rawName ?? "").trim()) ? "" : full
13673
+ ).trim();
13674
+ return { toolCalls, residualText, droppedNames };
13667
13675
  }
13668
13676
  function parseHermesParams(inner) {
13669
13677
  const input = {};
@@ -13747,7 +13755,10 @@ var OpenAIClient = class {
13747
13755
  }
13748
13756
  const accumulator = new OpenAIStreamAccumulator(
13749
13757
  this.options.extractToolCallsFromContent ?? false,
13750
- providerId
13758
+ providerId,
13759
+ // R5: request-scoped allowlist — leaked recovery only promotes a block whose name is a tool the
13760
+ // model was actually given. Empty set (no tools) recovers nothing.
13761
+ new Set(request.tools?.map((tool) => tool.name) ?? [])
13751
13762
  );
13752
13763
  for await (const record of parseSseStream(response.body, signal)) {
13753
13764
  if (record.data === "[DONE]") break;
@@ -13777,13 +13788,18 @@ var OpenAIStreamAccumulator = class {
13777
13788
  /**
13778
13789
  * @param extractFromContent opt-in leaked-dialect safe-parse (theokit#58). Default false.
13779
13790
  * @param providerName provider id, used only to label the recovery log line.
13791
+ * @param allowedToolNames R5 request-scoped allowlist — built from `request.tools` at `stream()`;
13792
+ * leaked recovery in `finish()` only promotes a block whose name is in this set. `undefined`
13793
+ * (direct construction) recovers all (back-compat); an empty set recovers nothing.
13780
13794
  */
13781
- constructor(extractFromContent = false, providerName = "openai") {
13795
+ constructor(extractFromContent = false, providerName = "openai", allowedToolNames) {
13782
13796
  this.extractFromContent = extractFromContent;
13783
13797
  this.providerName = providerName;
13798
+ this.allowedToolNames = allowedToolNames;
13784
13799
  }
13785
13800
  extractFromContent;
13786
13801
  providerName;
13802
+ allowedToolNames;
13787
13803
  text = "";
13788
13804
  stopReason = "end_turn";
13789
13805
  inputTokens;
@@ -13854,7 +13870,8 @@ var OpenAIStreamAccumulator = class {
13854
13870
  if (this.extractFromContent && toolCalls.length === 0) {
13855
13871
  const recovered = extractHermesToolCalls(
13856
13872
  this.text,
13857
- () => `hermes-${globalThis.crypto.randomUUID()}`
13873
+ () => `hermes-${globalThis.crypto.randomUUID()}`,
13874
+ this.allowedToolNames
13858
13875
  );
13859
13876
  if (recovered.toolCalls.length > 0) {
13860
13877
  toolCalls.push(...recovered.toolCalls);
@@ -13862,6 +13879,12 @@ var OpenAIStreamAccumulator = class {
13862
13879
  stopReason = "tool_use";
13863
13880
  process.stderr.write(
13864
13881
  `[theokit-sdk] recovered ${recovered.toolCalls.length} leaked tool call(s) from assistant content (provider="${this.providerName}", names=${recovered.toolCalls.map((c) => c.name).join(",")})
13882
+ `
13883
+ );
13884
+ }
13885
+ if (recovered.droppedNames.length > 0) {
13886
+ process.stderr.write(
13887
+ `[theokit-sdk] dropped ${recovered.droppedNames.length} leaked block(s) whose name is not a tool in the request (provider="${this.providerName}", names=${recovered.droppedNames.join(",")})
13865
13888
  `
13866
13889
  );
13867
13890
  }