@theokit/sdk 2.11.2 → 2.12.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 +81 -3
- package/dist/a2a/index.cjs.map +1 -1
- package/dist/a2a/index.js +81 -3
- package/dist/a2a/index.js.map +1 -1
- package/dist/{cron-BRfFPEKY.d.ts → cron-BchPdvs2.d.ts} +7 -0
- package/dist/{cron-DOw-Hqol.d.cts → cron-Dv94Lgat.d.cts} +7 -0
- package/dist/cron.cjs +75 -3
- 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 +75 -3
- package/dist/cron.js.map +1 -1
- package/dist/eval.cjs +75 -3
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +75 -3
- package/dist/eval.js.map +1 -1
- package/dist/index.cjs +75 -3
- 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 +75 -3
- 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 +19 -0
- package/dist/internal/providers/types.d.ts +7 -0
- package/package.json +14 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.12.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
|
|
9
|
+
## 2.11.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- bb3a7d8: Fail-loud on in-stream provider errors. OpenRouter (and some OpenAI-compatible proxies) report auth / quota / rate-limit failures as an HTTP 200 SSE body carrying `data: {"error":{"message":"...","code":401}}` rather than a non-2xx status. The stream accumulator only reads `choices`, so such an error-only chunk produced zero events and the turn finished empty — the failure was silently swallowed (a dead API key looked like an empty model response). The OpenAI client now detects an in-stream `error` chunk and throws the same typed error a non-2xx HTTP status would (`AuthenticationError` / `RateLimitError` / `ConfigurationError` / …), so callers surface it instead of a blank turn. Fixes usetheodev/theocode#31.
|
|
14
|
+
|
|
3
15
|
## 2.11.2
|
|
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) {
|
|
@@ -10270,7 +10305,10 @@ var init_openai2 = __esm({
|
|
|
10270
10305
|
endpoint: "/v1/chat/completions"
|
|
10271
10306
|
});
|
|
10272
10307
|
}
|
|
10273
|
-
const accumulator = new OpenAIStreamAccumulator(
|
|
10308
|
+
const accumulator = new OpenAIStreamAccumulator(
|
|
10309
|
+
this.options.extractToolCallsFromContent ?? false,
|
|
10310
|
+
providerId
|
|
10311
|
+
);
|
|
10274
10312
|
for await (const record of parseSseStream(response.body, signal)) {
|
|
10275
10313
|
if (record.data === "[DONE]") break;
|
|
10276
10314
|
let chunk;
|
|
@@ -10279,6 +10317,16 @@ var init_openai2 = __esm({
|
|
|
10279
10317
|
} catch {
|
|
10280
10318
|
continue;
|
|
10281
10319
|
}
|
|
10320
|
+
if (chunk.error !== void 0 && chunk.error !== null) {
|
|
10321
|
+
const status = typeof chunk.error.code === "number" ? chunk.error.code : 502;
|
|
10322
|
+
throw mapOpenAICompatibleError({
|
|
10323
|
+
providerId,
|
|
10324
|
+
status,
|
|
10325
|
+
body: chunk,
|
|
10326
|
+
headers: response.headers,
|
|
10327
|
+
endpoint: "/v1/chat/completions"
|
|
10328
|
+
});
|
|
10329
|
+
}
|
|
10282
10330
|
const events = accumulator.consume(chunk);
|
|
10283
10331
|
for (const event of events) yield event;
|
|
10284
10332
|
}
|
|
@@ -10286,6 +10334,16 @@ var init_openai2 = __esm({
|
|
|
10286
10334
|
}
|
|
10287
10335
|
};
|
|
10288
10336
|
OpenAIStreamAccumulator = class {
|
|
10337
|
+
/**
|
|
10338
|
+
* @param extractFromContent opt-in leaked-dialect safe-parse (theokit#58). Default false.
|
|
10339
|
+
* @param providerName provider id, used only to label the recovery log line.
|
|
10340
|
+
*/
|
|
10341
|
+
constructor(extractFromContent = false, providerName = "openai") {
|
|
10342
|
+
this.extractFromContent = extractFromContent;
|
|
10343
|
+
this.providerName = providerName;
|
|
10344
|
+
}
|
|
10345
|
+
extractFromContent;
|
|
10346
|
+
providerName;
|
|
10289
10347
|
text = "";
|
|
10290
10348
|
stopReason = "end_turn";
|
|
10291
10349
|
inputTokens;
|
|
@@ -10351,9 +10409,26 @@ var init_openai2 = __esm({
|
|
|
10351
10409
|
const input = parseToolArguments(call.args);
|
|
10352
10410
|
toolCalls.push({ type: "tool_use", id: call.id, name: call.name, input });
|
|
10353
10411
|
}
|
|
10412
|
+
let text = this.text;
|
|
10413
|
+
let stopReason = this.stopReason;
|
|
10414
|
+
if (this.extractFromContent && toolCalls.length === 0) {
|
|
10415
|
+
const recovered = extractHermesToolCalls(
|
|
10416
|
+
this.text,
|
|
10417
|
+
() => `hermes-${globalThis.crypto.randomUUID()}`
|
|
10418
|
+
);
|
|
10419
|
+
if (recovered.toolCalls.length > 0) {
|
|
10420
|
+
toolCalls.push(...recovered.toolCalls);
|
|
10421
|
+
text = recovered.residualText;
|
|
10422
|
+
stopReason = "tool_use";
|
|
10423
|
+
process.stderr.write(
|
|
10424
|
+
`[theokit-sdk] recovered ${recovered.toolCalls.length} leaked tool call(s) from assistant content (provider="${this.providerName}", names=${recovered.toolCalls.map((c) => c.name).join(",")})
|
|
10425
|
+
`
|
|
10426
|
+
);
|
|
10427
|
+
}
|
|
10428
|
+
}
|
|
10354
10429
|
return makeLlmFinish({
|
|
10355
|
-
stopReason
|
|
10356
|
-
text
|
|
10430
|
+
stopReason,
|
|
10431
|
+
text,
|
|
10357
10432
|
toolCalls,
|
|
10358
10433
|
inputTokens: this.inputTokens,
|
|
10359
10434
|
outputTokens: this.outputTokens,
|
|
@@ -10866,6 +10941,9 @@ function selectTransport(profile, apiKey) {
|
|
|
10866
10941
|
const opts = { apiKey };
|
|
10867
10942
|
opts.baseUrl = profile.baseUrl;
|
|
10868
10943
|
opts.providerName = profile.name;
|
|
10944
|
+
if (profile.extractToolCallsFromContent === true) {
|
|
10945
|
+
opts.extractToolCallsFromContent = true;
|
|
10946
|
+
}
|
|
10869
10947
|
if (profile.name === "openai" && process.env.OPENAI_ORGANIZATION !== void 0) {
|
|
10870
10948
|
opts.organization = process.env.OPENAI_ORGANIZATION;
|
|
10871
10949
|
}
|