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