@theokit/sdk 2.11.3 → 2.13.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) {
@@ -13439,6 +13468,14 @@ var OpenAIClient = class {
13439
13468
  name = "openai";
13440
13469
  baseUrl;
13441
13470
  fetchImpl;
13471
+ /**
13472
+ * Whether this client recovers leaked Hermes tool-call dialect from assistant
13473
+ * text (theokit#58 follow-up). Observability for the route→client wiring of
13474
+ * `extractToolCallsFromContent`. @internal
13475
+ */
13476
+ get recoversLeakedToolCalls() {
13477
+ return this.options.extractToolCallsFromContent ?? false;
13478
+ }
13442
13479
  // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: HTTP+SSE handshake + accumulator is intentionally one block
13443
13480
  async *stream(request, signal) {
13444
13481
  const headers = {
@@ -13489,7 +13526,10 @@ var OpenAIClient = class {
13489
13526
  endpoint: "/v1/chat/completions"
13490
13527
  });
13491
13528
  }
13492
- const accumulator = new OpenAIStreamAccumulator();
13529
+ const accumulator = new OpenAIStreamAccumulator(
13530
+ this.options.extractToolCallsFromContent ?? false,
13531
+ providerId
13532
+ );
13493
13533
  for await (const record of parseSseStream(response.body, signal)) {
13494
13534
  if (record.data === "[DONE]") break;
13495
13535
  let chunk;
@@ -13515,6 +13555,16 @@ var OpenAIClient = class {
13515
13555
  }
13516
13556
  };
13517
13557
  var OpenAIStreamAccumulator = class {
13558
+ /**
13559
+ * @param extractFromContent opt-in leaked-dialect safe-parse (theokit#58). Default false.
13560
+ * @param providerName provider id, used only to label the recovery log line.
13561
+ */
13562
+ constructor(extractFromContent = false, providerName = "openai") {
13563
+ this.extractFromContent = extractFromContent;
13564
+ this.providerName = providerName;
13565
+ }
13566
+ extractFromContent;
13567
+ providerName;
13518
13568
  text = "";
13519
13569
  stopReason = "end_turn";
13520
13570
  inputTokens;
@@ -13580,9 +13630,26 @@ var OpenAIStreamAccumulator = class {
13580
13630
  const input = parseToolArguments(call.args);
13581
13631
  toolCalls.push({ type: "tool_use", id: call.id, name: call.name, input });
13582
13632
  }
13633
+ let text = this.text;
13634
+ let stopReason = this.stopReason;
13635
+ if (this.extractFromContent && toolCalls.length === 0) {
13636
+ const recovered = extractHermesToolCalls(
13637
+ this.text,
13638
+ () => `hermes-${globalThis.crypto.randomUUID()}`
13639
+ );
13640
+ if (recovered.toolCalls.length > 0) {
13641
+ toolCalls.push(...recovered.toolCalls);
13642
+ text = recovered.residualText;
13643
+ stopReason = "tool_use";
13644
+ process.stderr.write(
13645
+ `[theokit-sdk] recovered ${recovered.toolCalls.length} leaked tool call(s) from assistant content (provider="${this.providerName}", names=${recovered.toolCalls.map((c) => c.name).join(",")})
13646
+ `
13647
+ );
13648
+ }
13649
+ }
13583
13650
  return makeLlmFinish({
13584
- stopReason: this.stopReason,
13585
- text: this.text,
13651
+ stopReason,
13652
+ text,
13586
13653
  toolCalls,
13587
13654
  inputTokens: this.inputTokens,
13588
13655
  outputTokens: this.outputTokens,
@@ -14059,8 +14126,9 @@ function buildChain(options) {
14059
14126
  return clients;
14060
14127
  }
14061
14128
  function buildClient(name, routerOptions) {
14062
- const profile = getProviderProfile(name);
14063
- if (profile === void 0) return void 0;
14129
+ const baseProfile = getProviderProfile(name);
14130
+ if (baseProfile === void 0) return void 0;
14131
+ const profile = routerOptions.extractToolCallsFromContent === true && baseProfile.extractToolCallsFromContent !== true ? { ...baseProfile, extractToolCallsFromContent: true } : baseProfile;
14064
14132
  const ambient = currentCredentialPool(name);
14065
14133
  if (ambient !== void 0) {
14066
14134
  return new PoolAwareLlmClient(ambient, (apiKey) => selectTransport(profile, apiKey));
@@ -14162,6 +14230,9 @@ function selectTransport(profile, apiKey) {
14162
14230
  const opts = { apiKey };
14163
14231
  opts.baseUrl = profile.baseUrl;
14164
14232
  opts.providerName = profile.name;
14233
+ if (profile.extractToolCallsFromContent === true) {
14234
+ opts.extractToolCallsFromContent = true;
14235
+ }
14165
14236
  if (profile.name === "openai" && process.env.OPENAI_ORGANIZATION !== void 0) {
14166
14237
  opts.organization = process.env.OPENAI_ORGANIZATION;
14167
14238
  }
@@ -14459,11 +14530,13 @@ function buildLoopInputs(options, runId, userText) {
14459
14530
  const fallback = options.agentOptions.providers?.fallback;
14460
14531
  const apiKeys = options.agentOptions.providers?.apiKeys;
14461
14532
  const credentialPoolStrategy = options.agentOptions.providers?.credentialPoolStrategy;
14533
+ const extractToolCallsFromContent = options.agentOptions.providers?.routes?.[0]?.extractToolCallsFromContent;
14462
14534
  const chain = resolveProviderChain({
14463
14535
  primary,
14464
14536
  ...fallback !== void 0 ? { fallback } : {},
14465
14537
  ...apiKeys !== void 0 ? { apiKeys } : {},
14466
- ...credentialPoolStrategy !== void 0 ? { credentialPoolStrategy } : {}
14538
+ ...credentialPoolStrategy !== void 0 ? { credentialPoolStrategy } : {},
14539
+ ...extractToolCallsFromContent === true ? { extractToolCallsFromContent: true } : {}
14467
14540
  });
14468
14541
  const llm = chain.length === 1 ? chain[0] : new FallbackLlmClient(chain);
14469
14542
  return {