@theokit/sdk 2.11.2 → 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;
@@ -11018,6 +11050,16 @@ var OpenAIClient = class {
11018
11050
  } catch {
11019
11051
  continue;
11020
11052
  }
11053
+ if (chunk.error !== void 0 && chunk.error !== null) {
11054
+ const status = typeof chunk.error.code === "number" ? chunk.error.code : 502;
11055
+ throw mapOpenAICompatibleError({
11056
+ providerId,
11057
+ status,
11058
+ body: chunk,
11059
+ headers: response.headers,
11060
+ endpoint: "/v1/chat/completions"
11061
+ });
11062
+ }
11021
11063
  const events = accumulator.consume(chunk);
11022
11064
  for (const event of events) yield event;
11023
11065
  }
@@ -11025,6 +11067,16 @@ var OpenAIClient = class {
11025
11067
  }
11026
11068
  };
11027
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;
11028
11080
  text = "";
11029
11081
  stopReason = "end_turn";
11030
11082
  inputTokens;
@@ -11090,9 +11142,26 @@ var OpenAIStreamAccumulator = class {
11090
11142
  const input = parseToolArguments(call.args);
11091
11143
  toolCalls.push({ type: "tool_use", id: call.id, name: call.name, input });
11092
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
+ }
11093
11162
  return makeLlmFinish({
11094
- stopReason: this.stopReason,
11095
- text: this.text,
11163
+ stopReason,
11164
+ text,
11096
11165
  toolCalls,
11097
11166
  inputTokens: this.inputTokens,
11098
11167
  outputTokens: this.outputTokens,
@@ -11672,6 +11741,9 @@ function selectTransport(profile, apiKey) {
11672
11741
  const opts = { apiKey };
11673
11742
  opts.baseUrl = profile.baseUrl;
11674
11743
  opts.providerName = profile.name;
11744
+ if (profile.extractToolCallsFromContent === true) {
11745
+ opts.extractToolCallsFromContent = true;
11746
+ }
11675
11747
  if (profile.name === "openai" && process.env.OPENAI_ORGANIZATION !== void 0) {
11676
11748
  opts.organization = process.env.OPENAI_ORGANIZATION;
11677
11749
  }