@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.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;
@@ -11021,6 +11053,16 @@ var OpenAIClient = class {
11021
11053
  } catch {
11022
11054
  continue;
11023
11055
  }
11056
+ if (chunk.error !== void 0 && chunk.error !== null) {
11057
+ const status = typeof chunk.error.code === "number" ? chunk.error.code : 502;
11058
+ throw mapOpenAICompatibleError({
11059
+ providerId,
11060
+ status,
11061
+ body: chunk,
11062
+ headers: response.headers,
11063
+ endpoint: "/v1/chat/completions"
11064
+ });
11065
+ }
11024
11066
  const events = accumulator.consume(chunk);
11025
11067
  for (const event of events) yield event;
11026
11068
  }
@@ -11028,6 +11070,16 @@ var OpenAIClient = class {
11028
11070
  }
11029
11071
  };
11030
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;
11031
11083
  text = "";
11032
11084
  stopReason = "end_turn";
11033
11085
  inputTokens;
@@ -11093,9 +11145,26 @@ var OpenAIStreamAccumulator = class {
11093
11145
  const input = parseToolArguments(call.args);
11094
11146
  toolCalls.push({ type: "tool_use", id: call.id, name: call.name, input });
11095
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
+ }
11096
11165
  return makeLlmFinish({
11097
- stopReason: this.stopReason,
11098
- text: this.text,
11166
+ stopReason,
11167
+ text,
11099
11168
  toolCalls,
11100
11169
  inputTokens: this.inputTokens,
11101
11170
  outputTokens: this.outputTokens,
@@ -11675,6 +11744,9 @@ function selectTransport(profile, apiKey) {
11675
11744
  const opts = { apiKey };
11676
11745
  opts.baseUrl = profile.baseUrl;
11677
11746
  opts.providerName = profile.name;
11747
+ if (profile.extractToolCallsFromContent === true) {
11748
+ opts.extractToolCallsFromContent = true;
11749
+ }
11678
11750
  if (profile.name === "openai" && process.env.OPENAI_ORGANIZATION !== void 0) {
11679
11751
  opts.organization = process.env.OPENAI_ORGANIZATION;
11680
11752
  }