@theokit/sdk 2.11.3 → 2.12.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.
package/dist/eval.cjs CHANGED
@@ -10951,6 +10951,35 @@ function toOllamaTools(tools) {
10951
10951
  }));
10952
10952
  }
10953
10953
 
10954
+ // src/internal/llm/hermes-tool-extract.ts
10955
+ var HERMES_BLOCK = /<function=\s*([^>\s]+)\s*>([\s\S]*?)<\/tool_call>/g;
10956
+ var HERMES_PARAM = /<parameter=\s*([^>\s]+)\s*>([\s\S]*?)<\/parameter>/g;
10957
+ function extractHermesToolCalls(content, makeId) {
10958
+ const toolCalls = [];
10959
+ for (const block of content.matchAll(HERMES_BLOCK)) {
10960
+ const name = (block[1] ?? "").trim();
10961
+ if (name.length === 0) continue;
10962
+ toolCalls.push({
10963
+ type: "tool_use",
10964
+ id: makeId(),
10965
+ name,
10966
+ input: parseHermesParams(block[2] ?? "")
10967
+ });
10968
+ }
10969
+ const residualText = toolCalls.length === 0 ? content : content.replace(HERMES_BLOCK, "").trim();
10970
+ return { toolCalls, residualText };
10971
+ }
10972
+ function parseHermesParams(inner) {
10973
+ const input = {};
10974
+ for (const param of inner.matchAll(HERMES_PARAM)) {
10975
+ const key = param[1];
10976
+ const value = param[2];
10977
+ if (key === void 0 || value === void 0) continue;
10978
+ input[key.trim()] = value;
10979
+ }
10980
+ return input;
10981
+ }
10982
+
10954
10983
  // src/internal/llm/openai.ts
10955
10984
  var OpenAIClient = class {
10956
10985
  constructor(options) {
@@ -11012,7 +11041,10 @@ var OpenAIClient = class {
11012
11041
  endpoint: "/v1/chat/completions"
11013
11042
  });
11014
11043
  }
11015
- const accumulator = new OpenAIStreamAccumulator();
11044
+ const accumulator = new OpenAIStreamAccumulator(
11045
+ this.options.extractToolCallsFromContent ?? false,
11046
+ providerId
11047
+ );
11016
11048
  for await (const record of parseSseStream(response.body, signal)) {
11017
11049
  if (record.data === "[DONE]") break;
11018
11050
  let chunk;
@@ -11038,6 +11070,16 @@ var OpenAIClient = class {
11038
11070
  }
11039
11071
  };
11040
11072
  var OpenAIStreamAccumulator = class {
11073
+ /**
11074
+ * @param extractFromContent opt-in leaked-dialect safe-parse (theokit#58). Default false.
11075
+ * @param providerName provider id, used only to label the recovery log line.
11076
+ */
11077
+ constructor(extractFromContent = false, providerName = "openai") {
11078
+ this.extractFromContent = extractFromContent;
11079
+ this.providerName = providerName;
11080
+ }
11081
+ extractFromContent;
11082
+ providerName;
11041
11083
  text = "";
11042
11084
  stopReason = "end_turn";
11043
11085
  inputTokens;
@@ -11103,9 +11145,26 @@ var OpenAIStreamAccumulator = class {
11103
11145
  const input = parseToolArguments(call.args);
11104
11146
  toolCalls.push({ type: "tool_use", id: call.id, name: call.name, input });
11105
11147
  }
11148
+ let text = this.text;
11149
+ let stopReason = this.stopReason;
11150
+ if (this.extractFromContent && toolCalls.length === 0) {
11151
+ const recovered = extractHermesToolCalls(
11152
+ this.text,
11153
+ () => `hermes-${globalThis.crypto.randomUUID()}`
11154
+ );
11155
+ if (recovered.toolCalls.length > 0) {
11156
+ toolCalls.push(...recovered.toolCalls);
11157
+ text = recovered.residualText;
11158
+ stopReason = "tool_use";
11159
+ process.stderr.write(
11160
+ `[theokit-sdk] recovered ${recovered.toolCalls.length} leaked tool call(s) from assistant content (provider="${this.providerName}", names=${recovered.toolCalls.map((c) => c.name).join(",")})
11161
+ `
11162
+ );
11163
+ }
11164
+ }
11106
11165
  return makeLlmFinish({
11107
- stopReason: this.stopReason,
11108
- text: this.text,
11166
+ stopReason,
11167
+ text,
11109
11168
  toolCalls,
11110
11169
  inputTokens: this.inputTokens,
11111
11170
  outputTokens: this.outputTokens,
@@ -11685,6 +11744,9 @@ function selectTransport(profile, apiKey) {
11685
11744
  const opts = { apiKey };
11686
11745
  opts.baseUrl = profile.baseUrl;
11687
11746
  opts.providerName = profile.name;
11747
+ if (profile.extractToolCallsFromContent === true) {
11748
+ opts.extractToolCallsFromContent = true;
11749
+ }
11688
11750
  if (profile.name === "openai" && process.env.OPENAI_ORGANIZATION !== void 0) {
11689
11751
  opts.organization = process.env.OPENAI_ORGANIZATION;
11690
11752
  }