@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/CHANGELOG.md +12 -0
- package/dist/a2a/index.cjs +85 -6
- package/dist/a2a/index.cjs.map +1 -1
- package/dist/a2a/index.js +85 -6
- package/dist/a2a/index.js.map +1 -1
- package/dist/{cron-BRfFPEKY.d.ts → cron-CL_9nfhQ.d.ts} +18 -0
- package/dist/{cron-DOw-Hqol.d.cts → cron-CpxLdAXc.d.cts} +18 -0
- package/dist/cron.cjs +79 -6
- package/dist/cron.cjs.map +1 -1
- package/dist/cron.d.cts +1 -1
- package/dist/cron.d.ts +1 -1
- package/dist/cron.js +79 -6
- package/dist/cron.js.map +1 -1
- package/dist/eval.cjs +79 -6
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +79 -6
- package/dist/eval.js.map +1 -1
- package/dist/index.cjs +79 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +79 -6
- package/dist/index.js.map +1 -1
- package/dist/internal/llm/hermes-tool-extract.d.ts +6 -0
- package/dist/internal/llm/openai.d.ts +7 -0
- package/dist/internal/llm/router.d.ts +8 -0
- package/dist/internal/providers/types.d.ts +7 -0
- package/dist/types/providers.d.ts +11 -0
- package/package.json +14 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.13.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 958a81f: Add per-route opt-in for leaked-dialect safe-parse (`ProviderRoute.extractToolCallsFromContent`, default off). The 2.12.0 release exposed the recovery flag only on a static `ProviderProfile`; enabling it required redeclaring a provider profile. This adds the same flag at the routing layer, so a consumer can opt a single chat route into recovery without cloning the built-in provider: `providers: { routes: [{ capability: "chat", provider: "openrouter", extractToolCallsFromContent: true }] }`. The router clones the resolved profile with the flag for that run (built-in profiles still ship the flag off), so the OpenAI-compatible transport recovers the Hermes `<function=…></tool_call>` dialect leaked as text by models like qwen3-coder. Derived from `routes[0]` (mirrors how the primary provider is derived) and applied to the resolved chat chain; fail-open and default-off, so a non-leaking route is unaffected. This is the enablement path consumed by `@theokit/agents`' `recoverLeakedToolCalls` knob.
|
|
8
|
+
|
|
9
|
+
## 2.12.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 95e9cba: Add opt-in leaked-dialect safe-parse for OpenAI-compatible providers (`ProviderProfile.extractToolCallsFromContent`, default off). Some models — notably qwen3-coder via OpenRouter — intermittently emit their Hermes tool-call dialect (`<function=NAME><parameter=KEY>VALUE</parameter></function></tool_call>`) as assistant TEXT instead of native `tool_calls`. When that happens the provider sends ZERO native `tool_calls`, so the agent loop sees a plain `end_turn` and the intended call is silently lost (theokit#58 follow-up). With the flag enabled, a `chat_completions` finish that has no native `tool_calls` has its assistant content scanned for the leaked dialect; any recovered calls are surfaced as real `tool_calls` and the stop reason flips to `tool_use` so the loop dispatches them. Fail-open like `stripThinkBlocks` — a partial/unclosed block is left as text and never fabricates a call. Default off, dedup-guarded (native `tool_calls` always win, no double-count), and scoped per-provider so a code assistant printing a literal `<function=` in a fenced block on a non-leaking route is unaffected.
|
|
14
|
+
|
|
3
15
|
## 2.11.3
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/a2a/index.cjs
CHANGED
|
@@ -10097,6 +10097,40 @@ var init_ollama_native = __esm({
|
|
|
10097
10097
|
}
|
|
10098
10098
|
});
|
|
10099
10099
|
|
|
10100
|
+
// src/internal/llm/hermes-tool-extract.ts
|
|
10101
|
+
function extractHermesToolCalls(content, makeId) {
|
|
10102
|
+
const toolCalls = [];
|
|
10103
|
+
for (const block of content.matchAll(HERMES_BLOCK)) {
|
|
10104
|
+
const name = (block[1] ?? "").trim();
|
|
10105
|
+
if (name.length === 0) continue;
|
|
10106
|
+
toolCalls.push({
|
|
10107
|
+
type: "tool_use",
|
|
10108
|
+
id: makeId(),
|
|
10109
|
+
name,
|
|
10110
|
+
input: parseHermesParams(block[2] ?? "")
|
|
10111
|
+
});
|
|
10112
|
+
}
|
|
10113
|
+
const residualText = toolCalls.length === 0 ? content : content.replace(HERMES_BLOCK, "").trim();
|
|
10114
|
+
return { toolCalls, residualText };
|
|
10115
|
+
}
|
|
10116
|
+
function parseHermesParams(inner) {
|
|
10117
|
+
const input = {};
|
|
10118
|
+
for (const param of inner.matchAll(HERMES_PARAM)) {
|
|
10119
|
+
const key = param[1];
|
|
10120
|
+
const value = param[2];
|
|
10121
|
+
if (key === void 0 || value === void 0) continue;
|
|
10122
|
+
input[key.trim()] = value;
|
|
10123
|
+
}
|
|
10124
|
+
return input;
|
|
10125
|
+
}
|
|
10126
|
+
var HERMES_BLOCK, HERMES_PARAM;
|
|
10127
|
+
var init_hermes_tool_extract = __esm({
|
|
10128
|
+
"src/internal/llm/hermes-tool-extract.ts"() {
|
|
10129
|
+
HERMES_BLOCK = /<function=\s*([^>\s]+)\s*>([\s\S]*?)<\/tool_call>/g;
|
|
10130
|
+
HERMES_PARAM = /<parameter=\s*([^>\s]+)\s*>([\s\S]*?)<\/parameter>/g;
|
|
10131
|
+
}
|
|
10132
|
+
});
|
|
10133
|
+
|
|
10100
10134
|
// src/internal/llm/openai.ts
|
|
10101
10135
|
function mapOpenAIFinish(reason) {
|
|
10102
10136
|
switch (reason) {
|
|
@@ -10209,6 +10243,7 @@ var init_openai2 = __esm({
|
|
|
10209
10243
|
init_ollama2();
|
|
10210
10244
|
init_openai_compatible();
|
|
10211
10245
|
init_finish();
|
|
10246
|
+
init_hermes_tool_extract();
|
|
10212
10247
|
init_sse();
|
|
10213
10248
|
OpenAIClient = class {
|
|
10214
10249
|
constructor(options) {
|
|
@@ -10220,6 +10255,14 @@ var init_openai2 = __esm({
|
|
|
10220
10255
|
name = "openai";
|
|
10221
10256
|
baseUrl;
|
|
10222
10257
|
fetchImpl;
|
|
10258
|
+
/**
|
|
10259
|
+
* Whether this client recovers leaked Hermes tool-call dialect from assistant
|
|
10260
|
+
* text (theokit#58 follow-up). Observability for the route→client wiring of
|
|
10261
|
+
* `extractToolCallsFromContent`. @internal
|
|
10262
|
+
*/
|
|
10263
|
+
get recoversLeakedToolCalls() {
|
|
10264
|
+
return this.options.extractToolCallsFromContent ?? false;
|
|
10265
|
+
}
|
|
10223
10266
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: HTTP+SSE handshake + accumulator is intentionally one block
|
|
10224
10267
|
async *stream(request, signal) {
|
|
10225
10268
|
const headers = {
|
|
@@ -10270,7 +10313,10 @@ var init_openai2 = __esm({
|
|
|
10270
10313
|
endpoint: "/v1/chat/completions"
|
|
10271
10314
|
});
|
|
10272
10315
|
}
|
|
10273
|
-
const accumulator = new OpenAIStreamAccumulator(
|
|
10316
|
+
const accumulator = new OpenAIStreamAccumulator(
|
|
10317
|
+
this.options.extractToolCallsFromContent ?? false,
|
|
10318
|
+
providerId
|
|
10319
|
+
);
|
|
10274
10320
|
for await (const record of parseSseStream(response.body, signal)) {
|
|
10275
10321
|
if (record.data === "[DONE]") break;
|
|
10276
10322
|
let chunk;
|
|
@@ -10296,6 +10342,16 @@ var init_openai2 = __esm({
|
|
|
10296
10342
|
}
|
|
10297
10343
|
};
|
|
10298
10344
|
OpenAIStreamAccumulator = class {
|
|
10345
|
+
/**
|
|
10346
|
+
* @param extractFromContent opt-in leaked-dialect safe-parse (theokit#58). Default false.
|
|
10347
|
+
* @param providerName provider id, used only to label the recovery log line.
|
|
10348
|
+
*/
|
|
10349
|
+
constructor(extractFromContent = false, providerName = "openai") {
|
|
10350
|
+
this.extractFromContent = extractFromContent;
|
|
10351
|
+
this.providerName = providerName;
|
|
10352
|
+
}
|
|
10353
|
+
extractFromContent;
|
|
10354
|
+
providerName;
|
|
10299
10355
|
text = "";
|
|
10300
10356
|
stopReason = "end_turn";
|
|
10301
10357
|
inputTokens;
|
|
@@ -10361,9 +10417,26 @@ var init_openai2 = __esm({
|
|
|
10361
10417
|
const input = parseToolArguments(call.args);
|
|
10362
10418
|
toolCalls.push({ type: "tool_use", id: call.id, name: call.name, input });
|
|
10363
10419
|
}
|
|
10420
|
+
let text = this.text;
|
|
10421
|
+
let stopReason = this.stopReason;
|
|
10422
|
+
if (this.extractFromContent && toolCalls.length === 0) {
|
|
10423
|
+
const recovered = extractHermesToolCalls(
|
|
10424
|
+
this.text,
|
|
10425
|
+
() => `hermes-${globalThis.crypto.randomUUID()}`
|
|
10426
|
+
);
|
|
10427
|
+
if (recovered.toolCalls.length > 0) {
|
|
10428
|
+
toolCalls.push(...recovered.toolCalls);
|
|
10429
|
+
text = recovered.residualText;
|
|
10430
|
+
stopReason = "tool_use";
|
|
10431
|
+
process.stderr.write(
|
|
10432
|
+
`[theokit-sdk] recovered ${recovered.toolCalls.length} leaked tool call(s) from assistant content (provider="${this.providerName}", names=${recovered.toolCalls.map((c) => c.name).join(",")})
|
|
10433
|
+
`
|
|
10434
|
+
);
|
|
10435
|
+
}
|
|
10436
|
+
}
|
|
10364
10437
|
return makeLlmFinish({
|
|
10365
|
-
stopReason
|
|
10366
|
-
text
|
|
10438
|
+
stopReason,
|
|
10439
|
+
text,
|
|
10367
10440
|
toolCalls,
|
|
10368
10441
|
inputTokens: this.inputTokens,
|
|
10369
10442
|
outputTokens: this.outputTokens,
|
|
@@ -10775,8 +10848,9 @@ function buildChain(options) {
|
|
|
10775
10848
|
return clients;
|
|
10776
10849
|
}
|
|
10777
10850
|
function buildClient(name, routerOptions) {
|
|
10778
|
-
const
|
|
10779
|
-
if (
|
|
10851
|
+
const baseProfile = getProviderProfile(name);
|
|
10852
|
+
if (baseProfile === void 0) return void 0;
|
|
10853
|
+
const profile = routerOptions.extractToolCallsFromContent === true && baseProfile.extractToolCallsFromContent !== true ? { ...baseProfile, extractToolCallsFromContent: true } : baseProfile;
|
|
10780
10854
|
const ambient = currentCredentialPool(name);
|
|
10781
10855
|
if (ambient !== void 0) {
|
|
10782
10856
|
return new PoolAwareLlmClient(ambient, (apiKey) => selectTransport(profile, apiKey));
|
|
@@ -10876,6 +10950,9 @@ function selectTransport(profile, apiKey) {
|
|
|
10876
10950
|
const opts = { apiKey };
|
|
10877
10951
|
opts.baseUrl = profile.baseUrl;
|
|
10878
10952
|
opts.providerName = profile.name;
|
|
10953
|
+
if (profile.extractToolCallsFromContent === true) {
|
|
10954
|
+
opts.extractToolCallsFromContent = true;
|
|
10955
|
+
}
|
|
10879
10956
|
if (profile.name === "openai" && process.env.OPENAI_ORGANIZATION !== void 0) {
|
|
10880
10957
|
opts.organization = process.env.OPENAI_ORGANIZATION;
|
|
10881
10958
|
}
|
|
@@ -11202,11 +11279,13 @@ function buildLoopInputs(options, runId, userText) {
|
|
|
11202
11279
|
const fallback = options.agentOptions.providers?.fallback;
|
|
11203
11280
|
const apiKeys = options.agentOptions.providers?.apiKeys;
|
|
11204
11281
|
const credentialPoolStrategy = options.agentOptions.providers?.credentialPoolStrategy;
|
|
11282
|
+
const extractToolCallsFromContent = options.agentOptions.providers?.routes?.[0]?.extractToolCallsFromContent;
|
|
11205
11283
|
const chain = resolveProviderChain({
|
|
11206
11284
|
primary,
|
|
11207
11285
|
...fallback !== void 0 ? { fallback } : {},
|
|
11208
11286
|
...apiKeys !== void 0 ? { apiKeys } : {},
|
|
11209
|
-
...credentialPoolStrategy !== void 0 ? { credentialPoolStrategy } : {}
|
|
11287
|
+
...credentialPoolStrategy !== void 0 ? { credentialPoolStrategy } : {},
|
|
11288
|
+
...extractToolCallsFromContent === true ? { extractToolCallsFromContent: true } : {}
|
|
11210
11289
|
});
|
|
11211
11290
|
const llm = chain.length === 1 ? chain[0] : new FallbackLlmClient(chain);
|
|
11212
11291
|
return {
|