@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.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) {
@@ -10959,6 +10988,14 @@ var OpenAIClient = class {
10959
10988
  name = "openai";
10960
10989
  baseUrl;
10961
10990
  fetchImpl;
10991
+ /**
10992
+ * Whether this client recovers leaked Hermes tool-call dialect from assistant
10993
+ * text (theokit#58 follow-up). Observability for the route→client wiring of
10994
+ * `extractToolCallsFromContent`. @internal
10995
+ */
10996
+ get recoversLeakedToolCalls() {
10997
+ return this.options.extractToolCallsFromContent ?? false;
10998
+ }
10962
10999
  // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: HTTP+SSE handshake + accumulator is intentionally one block
10963
11000
  async *stream(request, signal) {
10964
11001
  const headers = {
@@ -11009,7 +11046,10 @@ var OpenAIClient = class {
11009
11046
  endpoint: "/v1/chat/completions"
11010
11047
  });
11011
11048
  }
11012
- const accumulator = new OpenAIStreamAccumulator();
11049
+ const accumulator = new OpenAIStreamAccumulator(
11050
+ this.options.extractToolCallsFromContent ?? false,
11051
+ providerId
11052
+ );
11013
11053
  for await (const record of parseSseStream(response.body, signal)) {
11014
11054
  if (record.data === "[DONE]") break;
11015
11055
  let chunk;
@@ -11035,6 +11075,16 @@ var OpenAIClient = class {
11035
11075
  }
11036
11076
  };
11037
11077
  var OpenAIStreamAccumulator = class {
11078
+ /**
11079
+ * @param extractFromContent opt-in leaked-dialect safe-parse (theokit#58). Default false.
11080
+ * @param providerName provider id, used only to label the recovery log line.
11081
+ */
11082
+ constructor(extractFromContent = false, providerName = "openai") {
11083
+ this.extractFromContent = extractFromContent;
11084
+ this.providerName = providerName;
11085
+ }
11086
+ extractFromContent;
11087
+ providerName;
11038
11088
  text = "";
11039
11089
  stopReason = "end_turn";
11040
11090
  inputTokens;
@@ -11100,9 +11150,26 @@ var OpenAIStreamAccumulator = class {
11100
11150
  const input = parseToolArguments(call.args);
11101
11151
  toolCalls.push({ type: "tool_use", id: call.id, name: call.name, input });
11102
11152
  }
11153
+ let text = this.text;
11154
+ let stopReason = this.stopReason;
11155
+ if (this.extractFromContent && toolCalls.length === 0) {
11156
+ const recovered = extractHermesToolCalls(
11157
+ this.text,
11158
+ () => `hermes-${globalThis.crypto.randomUUID()}`
11159
+ );
11160
+ if (recovered.toolCalls.length > 0) {
11161
+ toolCalls.push(...recovered.toolCalls);
11162
+ text = recovered.residualText;
11163
+ stopReason = "tool_use";
11164
+ process.stderr.write(
11165
+ `[theokit-sdk] recovered ${recovered.toolCalls.length} leaked tool call(s) from assistant content (provider="${this.providerName}", names=${recovered.toolCalls.map((c) => c.name).join(",")})
11166
+ `
11167
+ );
11168
+ }
11169
+ }
11103
11170
  return makeLlmFinish({
11104
- stopReason: this.stopReason,
11105
- text: this.text,
11171
+ stopReason,
11172
+ text,
11106
11173
  toolCalls,
11107
11174
  inputTokens: this.inputTokens,
11108
11175
  outputTokens: this.outputTokens,
@@ -11579,8 +11646,9 @@ function buildChain(options) {
11579
11646
  return clients;
11580
11647
  }
11581
11648
  function buildClient(name, routerOptions) {
11582
- const profile = getProviderProfile(name);
11583
- if (profile === void 0) return void 0;
11649
+ const baseProfile = getProviderProfile(name);
11650
+ if (baseProfile === void 0) return void 0;
11651
+ const profile = routerOptions.extractToolCallsFromContent === true && baseProfile.extractToolCallsFromContent !== true ? { ...baseProfile, extractToolCallsFromContent: true } : baseProfile;
11584
11652
  const ambient = currentCredentialPool(name);
11585
11653
  if (ambient !== void 0) {
11586
11654
  return new PoolAwareLlmClient(ambient, (apiKey) => selectTransport(profile, apiKey));
@@ -11682,6 +11750,9 @@ function selectTransport(profile, apiKey) {
11682
11750
  const opts = { apiKey };
11683
11751
  opts.baseUrl = profile.baseUrl;
11684
11752
  opts.providerName = profile.name;
11753
+ if (profile.extractToolCallsFromContent === true) {
11754
+ opts.extractToolCallsFromContent = true;
11755
+ }
11685
11756
  if (profile.name === "openai" && process.env.OPENAI_ORGANIZATION !== void 0) {
11686
11757
  opts.organization = process.env.OPENAI_ORGANIZATION;
11687
11758
  }
@@ -11978,11 +12049,13 @@ function buildLoopInputs(options, runId, userText) {
11978
12049
  const fallback = options.agentOptions.providers?.fallback;
11979
12050
  const apiKeys = options.agentOptions.providers?.apiKeys;
11980
12051
  const credentialPoolStrategy = options.agentOptions.providers?.credentialPoolStrategy;
12052
+ const extractToolCallsFromContent = options.agentOptions.providers?.routes?.[0]?.extractToolCallsFromContent;
11981
12053
  const chain = resolveProviderChain({
11982
12054
  primary,
11983
12055
  ...fallback !== void 0 ? { fallback } : {},
11984
12056
  ...apiKeys !== void 0 ? { apiKeys } : {},
11985
- ...credentialPoolStrategy !== void 0 ? { credentialPoolStrategy } : {}
12057
+ ...credentialPoolStrategy !== void 0 ? { credentialPoolStrategy } : {},
12058
+ ...extractToolCallsFromContent === true ? { extractToolCallsFromContent: true } : {}
11986
12059
  });
11987
12060
  const llm = chain.length === 1 ? chain[0] : new FallbackLlmClient(chain);
11988
12061
  return {