@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/index.cjs CHANGED
@@ -13428,6 +13428,35 @@ function toOllamaTools(tools) {
13428
13428
  }));
13429
13429
  }
13430
13430
 
13431
+ // src/internal/llm/hermes-tool-extract.ts
13432
+ var HERMES_BLOCK = /<function=\s*([^>\s]+)\s*>([\s\S]*?)<\/tool_call>/g;
13433
+ var HERMES_PARAM = /<parameter=\s*([^>\s]+)\s*>([\s\S]*?)<\/parameter>/g;
13434
+ function extractHermesToolCalls(content, makeId) {
13435
+ const toolCalls = [];
13436
+ for (const block of content.matchAll(HERMES_BLOCK)) {
13437
+ const name = (block[1] ?? "").trim();
13438
+ if (name.length === 0) continue;
13439
+ toolCalls.push({
13440
+ type: "tool_use",
13441
+ id: makeId(),
13442
+ name,
13443
+ input: parseHermesParams(block[2] ?? "")
13444
+ });
13445
+ }
13446
+ const residualText = toolCalls.length === 0 ? content : content.replace(HERMES_BLOCK, "").trim();
13447
+ return { toolCalls, residualText };
13448
+ }
13449
+ function parseHermesParams(inner) {
13450
+ const input = {};
13451
+ for (const param of inner.matchAll(HERMES_PARAM)) {
13452
+ const key2 = param[1];
13453
+ const value = param[2];
13454
+ if (key2 === void 0 || value === void 0) continue;
13455
+ input[key2.trim()] = value;
13456
+ }
13457
+ return input;
13458
+ }
13459
+
13431
13460
  // src/internal/llm/openai.ts
13432
13461
  var OpenAIClient = class {
13433
13462
  constructor(options) {
@@ -13489,7 +13518,10 @@ var OpenAIClient = class {
13489
13518
  endpoint: "/v1/chat/completions"
13490
13519
  });
13491
13520
  }
13492
- const accumulator = new OpenAIStreamAccumulator();
13521
+ const accumulator = new OpenAIStreamAccumulator(
13522
+ this.options.extractToolCallsFromContent ?? false,
13523
+ providerId
13524
+ );
13493
13525
  for await (const record of parseSseStream(response.body, signal)) {
13494
13526
  if (record.data === "[DONE]") break;
13495
13527
  let chunk;
@@ -13515,6 +13547,16 @@ var OpenAIClient = class {
13515
13547
  }
13516
13548
  };
13517
13549
  var OpenAIStreamAccumulator = class {
13550
+ /**
13551
+ * @param extractFromContent opt-in leaked-dialect safe-parse (theokit#58). Default false.
13552
+ * @param providerName provider id, used only to label the recovery log line.
13553
+ */
13554
+ constructor(extractFromContent = false, providerName = "openai") {
13555
+ this.extractFromContent = extractFromContent;
13556
+ this.providerName = providerName;
13557
+ }
13558
+ extractFromContent;
13559
+ providerName;
13518
13560
  text = "";
13519
13561
  stopReason = "end_turn";
13520
13562
  inputTokens;
@@ -13580,9 +13622,26 @@ var OpenAIStreamAccumulator = class {
13580
13622
  const input = parseToolArguments(call.args);
13581
13623
  toolCalls.push({ type: "tool_use", id: call.id, name: call.name, input });
13582
13624
  }
13625
+ let text = this.text;
13626
+ let stopReason = this.stopReason;
13627
+ if (this.extractFromContent && toolCalls.length === 0) {
13628
+ const recovered = extractHermesToolCalls(
13629
+ this.text,
13630
+ () => `hermes-${globalThis.crypto.randomUUID()}`
13631
+ );
13632
+ if (recovered.toolCalls.length > 0) {
13633
+ toolCalls.push(...recovered.toolCalls);
13634
+ text = recovered.residualText;
13635
+ stopReason = "tool_use";
13636
+ process.stderr.write(
13637
+ `[theokit-sdk] recovered ${recovered.toolCalls.length} leaked tool call(s) from assistant content (provider="${this.providerName}", names=${recovered.toolCalls.map((c) => c.name).join(",")})
13638
+ `
13639
+ );
13640
+ }
13641
+ }
13583
13642
  return makeLlmFinish({
13584
- stopReason: this.stopReason,
13585
- text: this.text,
13643
+ stopReason,
13644
+ text,
13586
13645
  toolCalls,
13587
13646
  inputTokens: this.inputTokens,
13588
13647
  outputTokens: this.outputTokens,
@@ -14162,6 +14221,9 @@ function selectTransport(profile, apiKey) {
14162
14221
  const opts = { apiKey };
14163
14222
  opts.baseUrl = profile.baseUrl;
14164
14223
  opts.providerName = profile.name;
14224
+ if (profile.extractToolCallsFromContent === true) {
14225
+ opts.extractToolCallsFromContent = true;
14226
+ }
14165
14227
  if (profile.name === "openai" && process.env.OPENAI_ORGANIZATION !== void 0) {
14166
14228
  opts.organization = process.env.OPENAI_ORGANIZATION;
14167
14229
  }