@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/a2a/index.js CHANGED
@@ -10094,6 +10094,40 @@ var init_ollama_native = __esm({
10094
10094
  }
10095
10095
  });
10096
10096
 
10097
+ // src/internal/llm/hermes-tool-extract.ts
10098
+ function extractHermesToolCalls(content, makeId) {
10099
+ const toolCalls = [];
10100
+ for (const block of content.matchAll(HERMES_BLOCK)) {
10101
+ const name = (block[1] ?? "").trim();
10102
+ if (name.length === 0) continue;
10103
+ toolCalls.push({
10104
+ type: "tool_use",
10105
+ id: makeId(),
10106
+ name,
10107
+ input: parseHermesParams(block[2] ?? "")
10108
+ });
10109
+ }
10110
+ const residualText = toolCalls.length === 0 ? content : content.replace(HERMES_BLOCK, "").trim();
10111
+ return { toolCalls, residualText };
10112
+ }
10113
+ function parseHermesParams(inner) {
10114
+ const input = {};
10115
+ for (const param of inner.matchAll(HERMES_PARAM)) {
10116
+ const key = param[1];
10117
+ const value = param[2];
10118
+ if (key === void 0 || value === void 0) continue;
10119
+ input[key.trim()] = value;
10120
+ }
10121
+ return input;
10122
+ }
10123
+ var HERMES_BLOCK, HERMES_PARAM;
10124
+ var init_hermes_tool_extract = __esm({
10125
+ "src/internal/llm/hermes-tool-extract.ts"() {
10126
+ HERMES_BLOCK = /<function=\s*([^>\s]+)\s*>([\s\S]*?)<\/tool_call>/g;
10127
+ HERMES_PARAM = /<parameter=\s*([^>\s]+)\s*>([\s\S]*?)<\/parameter>/g;
10128
+ }
10129
+ });
10130
+
10097
10131
  // src/internal/llm/openai.ts
10098
10132
  function mapOpenAIFinish(reason) {
10099
10133
  switch (reason) {
@@ -10206,6 +10240,7 @@ var init_openai2 = __esm({
10206
10240
  init_ollama2();
10207
10241
  init_openai_compatible();
10208
10242
  init_finish();
10243
+ init_hermes_tool_extract();
10209
10244
  init_sse();
10210
10245
  OpenAIClient = class {
10211
10246
  constructor(options) {
@@ -10217,6 +10252,14 @@ var init_openai2 = __esm({
10217
10252
  name = "openai";
10218
10253
  baseUrl;
10219
10254
  fetchImpl;
10255
+ /**
10256
+ * Whether this client recovers leaked Hermes tool-call dialect from assistant
10257
+ * text (theokit#58 follow-up). Observability for the route→client wiring of
10258
+ * `extractToolCallsFromContent`. @internal
10259
+ */
10260
+ get recoversLeakedToolCalls() {
10261
+ return this.options.extractToolCallsFromContent ?? false;
10262
+ }
10220
10263
  // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: HTTP+SSE handshake + accumulator is intentionally one block
10221
10264
  async *stream(request, signal) {
10222
10265
  const headers = {
@@ -10267,7 +10310,10 @@ var init_openai2 = __esm({
10267
10310
  endpoint: "/v1/chat/completions"
10268
10311
  });
10269
10312
  }
10270
- const accumulator = new OpenAIStreamAccumulator();
10313
+ const accumulator = new OpenAIStreamAccumulator(
10314
+ this.options.extractToolCallsFromContent ?? false,
10315
+ providerId
10316
+ );
10271
10317
  for await (const record of parseSseStream(response.body, signal)) {
10272
10318
  if (record.data === "[DONE]") break;
10273
10319
  let chunk;
@@ -10293,6 +10339,16 @@ var init_openai2 = __esm({
10293
10339
  }
10294
10340
  };
10295
10341
  OpenAIStreamAccumulator = class {
10342
+ /**
10343
+ * @param extractFromContent opt-in leaked-dialect safe-parse (theokit#58). Default false.
10344
+ * @param providerName provider id, used only to label the recovery log line.
10345
+ */
10346
+ constructor(extractFromContent = false, providerName = "openai") {
10347
+ this.extractFromContent = extractFromContent;
10348
+ this.providerName = providerName;
10349
+ }
10350
+ extractFromContent;
10351
+ providerName;
10296
10352
  text = "";
10297
10353
  stopReason = "end_turn";
10298
10354
  inputTokens;
@@ -10358,9 +10414,26 @@ var init_openai2 = __esm({
10358
10414
  const input = parseToolArguments(call.args);
10359
10415
  toolCalls.push({ type: "tool_use", id: call.id, name: call.name, input });
10360
10416
  }
10417
+ let text = this.text;
10418
+ let stopReason = this.stopReason;
10419
+ if (this.extractFromContent && toolCalls.length === 0) {
10420
+ const recovered = extractHermesToolCalls(
10421
+ this.text,
10422
+ () => `hermes-${globalThis.crypto.randomUUID()}`
10423
+ );
10424
+ if (recovered.toolCalls.length > 0) {
10425
+ toolCalls.push(...recovered.toolCalls);
10426
+ text = recovered.residualText;
10427
+ stopReason = "tool_use";
10428
+ process.stderr.write(
10429
+ `[theokit-sdk] recovered ${recovered.toolCalls.length} leaked tool call(s) from assistant content (provider="${this.providerName}", names=${recovered.toolCalls.map((c) => c.name).join(",")})
10430
+ `
10431
+ );
10432
+ }
10433
+ }
10361
10434
  return makeLlmFinish({
10362
- stopReason: this.stopReason,
10363
- text: this.text,
10435
+ stopReason,
10436
+ text,
10364
10437
  toolCalls,
10365
10438
  inputTokens: this.inputTokens,
10366
10439
  outputTokens: this.outputTokens,
@@ -10772,8 +10845,9 @@ function buildChain(options) {
10772
10845
  return clients;
10773
10846
  }
10774
10847
  function buildClient(name, routerOptions) {
10775
- const profile = getProviderProfile(name);
10776
- if (profile === void 0) return void 0;
10848
+ const baseProfile = getProviderProfile(name);
10849
+ if (baseProfile === void 0) return void 0;
10850
+ const profile = routerOptions.extractToolCallsFromContent === true && baseProfile.extractToolCallsFromContent !== true ? { ...baseProfile, extractToolCallsFromContent: true } : baseProfile;
10777
10851
  const ambient = currentCredentialPool(name);
10778
10852
  if (ambient !== void 0) {
10779
10853
  return new PoolAwareLlmClient(ambient, (apiKey) => selectTransport(profile, apiKey));
@@ -10873,6 +10947,9 @@ function selectTransport(profile, apiKey) {
10873
10947
  const opts = { apiKey };
10874
10948
  opts.baseUrl = profile.baseUrl;
10875
10949
  opts.providerName = profile.name;
10950
+ if (profile.extractToolCallsFromContent === true) {
10951
+ opts.extractToolCallsFromContent = true;
10952
+ }
10876
10953
  if (profile.name === "openai" && process.env.OPENAI_ORGANIZATION !== void 0) {
10877
10954
  opts.organization = process.env.OPENAI_ORGANIZATION;
10878
10955
  }
@@ -11199,11 +11276,13 @@ function buildLoopInputs(options, runId, userText) {
11199
11276
  const fallback = options.agentOptions.providers?.fallback;
11200
11277
  const apiKeys = options.agentOptions.providers?.apiKeys;
11201
11278
  const credentialPoolStrategy = options.agentOptions.providers?.credentialPoolStrategy;
11279
+ const extractToolCallsFromContent = options.agentOptions.providers?.routes?.[0]?.extractToolCallsFromContent;
11202
11280
  const chain = resolveProviderChain({
11203
11281
  primary,
11204
11282
  ...fallback !== void 0 ? { fallback } : {},
11205
11283
  ...apiKeys !== void 0 ? { apiKeys } : {},
11206
- ...credentialPoolStrategy !== void 0 ? { credentialPoolStrategy } : {}
11284
+ ...credentialPoolStrategy !== void 0 ? { credentialPoolStrategy } : {},
11285
+ ...extractToolCallsFromContent === true ? { extractToolCallsFromContent: true } : {}
11207
11286
  });
11208
11287
  const llm = chain.length === 1 ? chain[0] : new FallbackLlmClient(chain);
11209
11288
  return {