@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/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) {
@@ -10962,6 +10991,14 @@ var OpenAIClient = class {
10962
10991
  name = "openai";
10963
10992
  baseUrl;
10964
10993
  fetchImpl;
10994
+ /**
10995
+ * Whether this client recovers leaked Hermes tool-call dialect from assistant
10996
+ * text (theokit#58 follow-up). Observability for the route→client wiring of
10997
+ * `extractToolCallsFromContent`. @internal
10998
+ */
10999
+ get recoversLeakedToolCalls() {
11000
+ return this.options.extractToolCallsFromContent ?? false;
11001
+ }
10965
11002
  // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: HTTP+SSE handshake + accumulator is intentionally one block
10966
11003
  async *stream(request, signal) {
10967
11004
  const headers = {
@@ -11012,7 +11049,10 @@ var OpenAIClient = class {
11012
11049
  endpoint: "/v1/chat/completions"
11013
11050
  });
11014
11051
  }
11015
- const accumulator = new OpenAIStreamAccumulator();
11052
+ const accumulator = new OpenAIStreamAccumulator(
11053
+ this.options.extractToolCallsFromContent ?? false,
11054
+ providerId
11055
+ );
11016
11056
  for await (const record of parseSseStream(response.body, signal)) {
11017
11057
  if (record.data === "[DONE]") break;
11018
11058
  let chunk;
@@ -11038,6 +11078,16 @@ var OpenAIClient = class {
11038
11078
  }
11039
11079
  };
11040
11080
  var OpenAIStreamAccumulator = class {
11081
+ /**
11082
+ * @param extractFromContent opt-in leaked-dialect safe-parse (theokit#58). Default false.
11083
+ * @param providerName provider id, used only to label the recovery log line.
11084
+ */
11085
+ constructor(extractFromContent = false, providerName = "openai") {
11086
+ this.extractFromContent = extractFromContent;
11087
+ this.providerName = providerName;
11088
+ }
11089
+ extractFromContent;
11090
+ providerName;
11041
11091
  text = "";
11042
11092
  stopReason = "end_turn";
11043
11093
  inputTokens;
@@ -11103,9 +11153,26 @@ var OpenAIStreamAccumulator = class {
11103
11153
  const input = parseToolArguments(call.args);
11104
11154
  toolCalls.push({ type: "tool_use", id: call.id, name: call.name, input });
11105
11155
  }
11156
+ let text = this.text;
11157
+ let stopReason = this.stopReason;
11158
+ if (this.extractFromContent && toolCalls.length === 0) {
11159
+ const recovered = extractHermesToolCalls(
11160
+ this.text,
11161
+ () => `hermes-${globalThis.crypto.randomUUID()}`
11162
+ );
11163
+ if (recovered.toolCalls.length > 0) {
11164
+ toolCalls.push(...recovered.toolCalls);
11165
+ text = recovered.residualText;
11166
+ stopReason = "tool_use";
11167
+ process.stderr.write(
11168
+ `[theokit-sdk] recovered ${recovered.toolCalls.length} leaked tool call(s) from assistant content (provider="${this.providerName}", names=${recovered.toolCalls.map((c) => c.name).join(",")})
11169
+ `
11170
+ );
11171
+ }
11172
+ }
11106
11173
  return makeLlmFinish({
11107
- stopReason: this.stopReason,
11108
- text: this.text,
11174
+ stopReason,
11175
+ text,
11109
11176
  toolCalls,
11110
11177
  inputTokens: this.inputTokens,
11111
11178
  outputTokens: this.outputTokens,
@@ -11582,8 +11649,9 @@ function buildChain(options) {
11582
11649
  return clients;
11583
11650
  }
11584
11651
  function buildClient(name, routerOptions) {
11585
- const profile = getProviderProfile(name);
11586
- if (profile === void 0) return void 0;
11652
+ const baseProfile = getProviderProfile(name);
11653
+ if (baseProfile === void 0) return void 0;
11654
+ const profile = routerOptions.extractToolCallsFromContent === true && baseProfile.extractToolCallsFromContent !== true ? { ...baseProfile, extractToolCallsFromContent: true } : baseProfile;
11587
11655
  const ambient = currentCredentialPool(name);
11588
11656
  if (ambient !== void 0) {
11589
11657
  return new PoolAwareLlmClient(ambient, (apiKey) => selectTransport(profile, apiKey));
@@ -11685,6 +11753,9 @@ function selectTransport(profile, apiKey) {
11685
11753
  const opts = { apiKey };
11686
11754
  opts.baseUrl = profile.baseUrl;
11687
11755
  opts.providerName = profile.name;
11756
+ if (profile.extractToolCallsFromContent === true) {
11757
+ opts.extractToolCallsFromContent = true;
11758
+ }
11688
11759
  if (profile.name === "openai" && process.env.OPENAI_ORGANIZATION !== void 0) {
11689
11760
  opts.organization = process.env.OPENAI_ORGANIZATION;
11690
11761
  }
@@ -11981,11 +12052,13 @@ function buildLoopInputs(options, runId, userText) {
11981
12052
  const fallback = options.agentOptions.providers?.fallback;
11982
12053
  const apiKeys = options.agentOptions.providers?.apiKeys;
11983
12054
  const credentialPoolStrategy = options.agentOptions.providers?.credentialPoolStrategy;
12055
+ const extractToolCallsFromContent = options.agentOptions.providers?.routes?.[0]?.extractToolCallsFromContent;
11984
12056
  const chain = resolveProviderChain({
11985
12057
  primary,
11986
12058
  ...fallback !== void 0 ? { fallback } : {},
11987
12059
  ...apiKeys !== void 0 ? { apiKeys } : {},
11988
- ...credentialPoolStrategy !== void 0 ? { credentialPoolStrategy } : {}
12060
+ ...credentialPoolStrategy !== void 0 ? { credentialPoolStrategy } : {},
12061
+ ...extractToolCallsFromContent === true ? { extractToolCallsFromContent: true } : {}
11989
12062
  });
11990
12063
  const llm = chain.length === 1 ? chain[0] : new FallbackLlmClient(chain);
11991
12064
  return {