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