@theokit/sdk 2.15.0 → 2.15.2
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 +136 -17
- package/dist/a2a/index.cjs.map +1 -1
- package/dist/a2a/index.js +136 -17
- package/dist/a2a/index.js.map +1 -1
- package/dist/cron.cjs +135 -16
- package/dist/cron.cjs.map +1 -1
- package/dist/cron.js +135 -16
- package/dist/cron.js.map +1 -1
- package/dist/eval.cjs +135 -16
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +135 -16
- package/dist/eval.js.map +1 -1
- package/dist/index.cjs +135 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +135 -16
- package/dist/index.js.map +1 -1
- package/dist/internal/llm/hermes-tool-extract.d.ts +5 -1
- package/dist/internal/llm/openai.d.ts +14 -1
- package/package.json +1 -1
package/dist/a2a/index.js
CHANGED
|
@@ -10329,11 +10329,16 @@ var init_sanitize_tool_input = __esm({
|
|
|
10329
10329
|
});
|
|
10330
10330
|
|
|
10331
10331
|
// src/internal/llm/hermes-tool-extract.ts
|
|
10332
|
-
function extractHermesToolCalls(content, makeId) {
|
|
10332
|
+
function extractHermesToolCalls(content, makeId, allowedToolNames) {
|
|
10333
|
+
const isPromoted = (name) => name.length > 0 && (allowedToolNames === void 0 || allowedToolNames.has(name));
|
|
10333
10334
|
const toolCalls = [];
|
|
10335
|
+
const droppedNames = [];
|
|
10334
10336
|
for (const block of content.matchAll(HERMES_BLOCK)) {
|
|
10335
10337
|
const name = (block[1] ?? "").trim();
|
|
10336
|
-
if (name
|
|
10338
|
+
if (!isPromoted(name)) {
|
|
10339
|
+
if (name.length > 0 && allowedToolNames !== void 0) droppedNames.push(name);
|
|
10340
|
+
continue;
|
|
10341
|
+
}
|
|
10337
10342
|
toolCalls.push({
|
|
10338
10343
|
type: "tool_use",
|
|
10339
10344
|
id: makeId(),
|
|
@@ -10341,8 +10346,11 @@ function extractHermesToolCalls(content, makeId) {
|
|
|
10341
10346
|
input: parseHermesParams(block[2] ?? "")
|
|
10342
10347
|
});
|
|
10343
10348
|
}
|
|
10344
|
-
const residualText = toolCalls.length === 0 ? content : content.replace(
|
|
10345
|
-
|
|
10349
|
+
const residualText = toolCalls.length === 0 ? content : content.replace(
|
|
10350
|
+
HERMES_BLOCK,
|
|
10351
|
+
(full, rawName) => isPromoted((rawName ?? "").trim()) ? "" : full
|
|
10352
|
+
).trim();
|
|
10353
|
+
return { toolCalls, residualText, droppedNames };
|
|
10346
10354
|
}
|
|
10347
10355
|
function parseHermesParams(inner) {
|
|
10348
10356
|
const input = {};
|
|
@@ -10354,12 +10362,83 @@ function parseHermesParams(inner) {
|
|
|
10354
10362
|
}
|
|
10355
10363
|
return sanitizeToolInput(input, { trim: true }).value;
|
|
10356
10364
|
}
|
|
10357
|
-
|
|
10365
|
+
function streamToolCallBufferState(held, allowedToolNames, cap = DEFAULT_STREAM_BUFFER_CAP) {
|
|
10366
|
+
if (allowedToolNames.size === 0) return "impossible";
|
|
10367
|
+
const t = held.trimStart();
|
|
10368
|
+
if (t.length < STREAM_MARKER.length) {
|
|
10369
|
+
return STREAM_MARKER.startsWith(t) ? "possible" : "impossible";
|
|
10370
|
+
}
|
|
10371
|
+
if (!t.startsWith(STREAM_MARKER)) return "impossible";
|
|
10372
|
+
const parsed = parseStreamMarkerName(t);
|
|
10373
|
+
if (parsed === "building") return "possible";
|
|
10374
|
+
if (parsed === "invalid") return "impossible";
|
|
10375
|
+
const nameOk = parsed.complete ? allowedToolNames.has(parsed.name) : someToolNameStartsWith(allowedToolNames, parsed.name);
|
|
10376
|
+
if (!nameOk) return "impossible";
|
|
10377
|
+
return held.length > cap ? "impossible" : "possible";
|
|
10378
|
+
}
|
|
10379
|
+
function parseStreamMarkerName(t) {
|
|
10380
|
+
let cursor = STREAM_MARKER.length;
|
|
10381
|
+
while (cursor < t.length && isStreamWs(t[cursor])) cursor += 1;
|
|
10382
|
+
const nameStart = cursor;
|
|
10383
|
+
while (cursor < t.length && t[cursor] !== ">" && !isStreamWs(t[cursor])) cursor += 1;
|
|
10384
|
+
const name = t.slice(nameStart, cursor);
|
|
10385
|
+
if (name.length === 0) return cursor >= t.length ? "building" : "invalid";
|
|
10386
|
+
return { name, complete: cursor < t.length && t[cursor] === ">" };
|
|
10387
|
+
}
|
|
10388
|
+
function someToolNameStartsWith(allowedToolNames, prefix) {
|
|
10389
|
+
for (const name of allowedToolNames) {
|
|
10390
|
+
if (name.startsWith(prefix)) return true;
|
|
10391
|
+
}
|
|
10392
|
+
return false;
|
|
10393
|
+
}
|
|
10394
|
+
function firstPossibleMarkerStart(held, allowedToolNames) {
|
|
10395
|
+
for (let i = held.indexOf("<"); i !== -1; i = held.indexOf("<", i + 1)) {
|
|
10396
|
+
if (streamToolCallBufferState(held.slice(i), allowedToolNames) === "possible") return i;
|
|
10397
|
+
}
|
|
10398
|
+
return -1;
|
|
10399
|
+
}
|
|
10400
|
+
var HERMES_BLOCK, HERMES_PARAM, STREAM_MARKER, DEFAULT_STREAM_BUFFER_CAP, isStreamWs, StreamSuppressionBuffer;
|
|
10358
10401
|
var init_hermes_tool_extract = __esm({
|
|
10359
10402
|
"src/internal/llm/hermes-tool-extract.ts"() {
|
|
10360
10403
|
init_sanitize_tool_input();
|
|
10361
10404
|
HERMES_BLOCK = /<function=\s*([^>\s]+)\s*>([\s\S]*?)<\/tool_call>/g;
|
|
10362
10405
|
HERMES_PARAM = /<parameter=\s*([^>\s]+)\s*>([\s\S]*?)<\/parameter>/g;
|
|
10406
|
+
STREAM_MARKER = "<function=";
|
|
10407
|
+
DEFAULT_STREAM_BUFFER_CAP = 8192;
|
|
10408
|
+
isStreamWs = (c) => c === " " || c === " " || c === "\n" || c === "\r";
|
|
10409
|
+
StreamSuppressionBuffer = class {
|
|
10410
|
+
constructor(allowedToolNames) {
|
|
10411
|
+
this.allowedToolNames = allowedToolNames;
|
|
10412
|
+
}
|
|
10413
|
+
allowedToolNames;
|
|
10414
|
+
#held = "";
|
|
10415
|
+
/** Feed a content delta; returns the text to emit as a `text_delta` now, or `undefined` to hold. */
|
|
10416
|
+
push(content) {
|
|
10417
|
+
this.#held += content;
|
|
10418
|
+
if (streamToolCallBufferState(this.#held, this.allowedToolNames) === "possible")
|
|
10419
|
+
return void 0;
|
|
10420
|
+
const holdStart = firstPossibleMarkerStart(this.#held, this.allowedToolNames);
|
|
10421
|
+
if (holdStart > 0) {
|
|
10422
|
+
const flush2 = this.#held.slice(0, holdStart);
|
|
10423
|
+
this.#held = this.#held.slice(holdStart);
|
|
10424
|
+
return flush2;
|
|
10425
|
+
}
|
|
10426
|
+
const flush = this.#held;
|
|
10427
|
+
this.#held = "";
|
|
10428
|
+
return flush;
|
|
10429
|
+
}
|
|
10430
|
+
/** Drain the held buffer at stream end. `hasNativeCalls` mirrors `finish()`'s size-guard: when
|
|
10431
|
+
* native `tool_calls` exist, `finish()` won't strip the leaked block, so stream the held text WHOLE
|
|
10432
|
+
* (keeping `accumulatedText == finish.text`); otherwise strip the recoverable blocks. Idempotent. */
|
|
10433
|
+
drain(hasNativeCalls) {
|
|
10434
|
+
if (this.#held.length === 0) return void 0;
|
|
10435
|
+
const held = this.#held;
|
|
10436
|
+
this.#held = "";
|
|
10437
|
+
if (hasNativeCalls) return held;
|
|
10438
|
+
const residual = extractHermesToolCalls(held, () => "held", this.allowedToolNames).residualText;
|
|
10439
|
+
return residual.length > 0 ? residual : void 0;
|
|
10440
|
+
}
|
|
10441
|
+
};
|
|
10363
10442
|
}
|
|
10364
10443
|
});
|
|
10365
10444
|
|
|
@@ -10547,7 +10626,10 @@ var init_openai2 = __esm({
|
|
|
10547
10626
|
}
|
|
10548
10627
|
const accumulator = new OpenAIStreamAccumulator(
|
|
10549
10628
|
this.options.extractToolCallsFromContent ?? false,
|
|
10550
|
-
providerId
|
|
10629
|
+
providerId,
|
|
10630
|
+
// R5: request-scoped allowlist — leaked recovery only promotes a block whose name is a tool the
|
|
10631
|
+
// model was actually given. Empty set (no tools) recovers nothing.
|
|
10632
|
+
new Set(request.tools?.map((tool) => tool.name) ?? [])
|
|
10551
10633
|
);
|
|
10552
10634
|
for await (const record of parseSseStream(response.body, signal)) {
|
|
10553
10635
|
if (record.data === "[DONE]") break;
|
|
@@ -10570,6 +10652,8 @@ var init_openai2 = __esm({
|
|
|
10570
10652
|
const events = accumulator.consume(chunk);
|
|
10571
10653
|
for (const event of events) yield event;
|
|
10572
10654
|
}
|
|
10655
|
+
const drainEvent = accumulator.finalizeHeldText();
|
|
10656
|
+
if (drainEvent !== void 0) yield drainEvent;
|
|
10573
10657
|
return accumulator.finish();
|
|
10574
10658
|
}
|
|
10575
10659
|
};
|
|
@@ -10577,13 +10661,19 @@ var init_openai2 = __esm({
|
|
|
10577
10661
|
/**
|
|
10578
10662
|
* @param extractFromContent opt-in leaked-dialect safe-parse (theokit#58). Default false.
|
|
10579
10663
|
* @param providerName provider id, used only to label the recovery log line.
|
|
10664
|
+
* @param allowedToolNames R5 request-scoped allowlist — built from `request.tools` at `stream()`;
|
|
10665
|
+
* leaked recovery in `finish()` only promotes a block whose name is in this set. `undefined`
|
|
10666
|
+
* (direct construction) recovers all (back-compat); an empty set recovers nothing.
|
|
10580
10667
|
*/
|
|
10581
|
-
constructor(extractFromContent = false, providerName = "openai") {
|
|
10668
|
+
constructor(extractFromContent = false, providerName = "openai", allowedToolNames) {
|
|
10582
10669
|
this.extractFromContent = extractFromContent;
|
|
10583
10670
|
this.providerName = providerName;
|
|
10671
|
+
this.allowedToolNames = allowedToolNames;
|
|
10672
|
+
this.suppress = extractFromContent && allowedToolNames !== void 0 && allowedToolNames.size > 0 ? new StreamSuppressionBuffer(allowedToolNames) : void 0;
|
|
10584
10673
|
}
|
|
10585
10674
|
extractFromContent;
|
|
10586
10675
|
providerName;
|
|
10676
|
+
allowedToolNames;
|
|
10587
10677
|
text = "";
|
|
10588
10678
|
stopReason = "end_turn";
|
|
10589
10679
|
inputTokens;
|
|
@@ -10592,18 +10682,30 @@ var init_openai2 = __esm({
|
|
|
10592
10682
|
cacheWriteTokens;
|
|
10593
10683
|
reasoningTokens;
|
|
10594
10684
|
toolCalls = /* @__PURE__ */ new Map();
|
|
10685
|
+
/** R7: present only when recovery is enabled AND the request declares tools — holds suspected
|
|
10686
|
+
* leaked-dialect content back from the `text_delta` stream. `undefined` ⇒ stream immediately. */
|
|
10687
|
+
suppress;
|
|
10595
10688
|
consume(chunk) {
|
|
10596
10689
|
const events = [];
|
|
10597
10690
|
this.applyUsage(chunk.usage);
|
|
10598
10691
|
for (const choice of chunk.choices ?? []) {
|
|
10599
|
-
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
|
|
10603
|
-
|
|
10604
|
-
|
|
10605
|
-
|
|
10606
|
-
|
|
10692
|
+
events.push(...this.applyChoice(choice));
|
|
10693
|
+
}
|
|
10694
|
+
return events;
|
|
10695
|
+
}
|
|
10696
|
+
applyChoice(choice) {
|
|
10697
|
+
const events = [];
|
|
10698
|
+
const reasoningEvent = this.applyReasoningDelta(
|
|
10699
|
+
choice.delta?.reasoning ?? choice.delta?.reasoning_content
|
|
10700
|
+
);
|
|
10701
|
+
if (reasoningEvent !== void 0) events.push(reasoningEvent);
|
|
10702
|
+
const textEvent = this.applyContentDelta(choice.delta?.content);
|
|
10703
|
+
if (textEvent !== void 0) events.push(textEvent);
|
|
10704
|
+
this.mergeToolCallDeltas(choice.delta?.tool_calls);
|
|
10705
|
+
this.applyFinishReason(choice.finish_reason);
|
|
10706
|
+
if (choice.finish_reason !== void 0 && choice.finish_reason !== null) {
|
|
10707
|
+
const flushEvent = this.finalizeHeldText();
|
|
10708
|
+
if (flushEvent !== void 0) events.push(flushEvent);
|
|
10607
10709
|
}
|
|
10608
10710
|
return events;
|
|
10609
10711
|
}
|
|
@@ -10628,7 +10730,17 @@ var init_openai2 = __esm({
|
|
|
10628
10730
|
applyContentDelta(content) {
|
|
10629
10731
|
if (typeof content !== "string" || content.length === 0) return void 0;
|
|
10630
10732
|
this.text += content;
|
|
10631
|
-
return { type: "text_delta", text: content };
|
|
10733
|
+
if (this.suppress === void 0) return { type: "text_delta", text: content };
|
|
10734
|
+
const emit = this.suppress.push(content);
|
|
10735
|
+
return emit !== void 0 ? { type: "text_delta", text: emit } : void 0;
|
|
10736
|
+
}
|
|
10737
|
+
/** R7 held-buffer finalizer, called at the `finish_reason` chunk (in `applyChoice`) AND after the
|
|
10738
|
+
* SSE loop in `stream()` — so a stream that omits a `finish_reason` terminal never silently drops
|
|
10739
|
+
* held text. `toolCalls.size > 0` (native calls present) makes `finish()` skip recovery, so the
|
|
10740
|
+
* buffer streams the held text whole. Idempotent once drained. */
|
|
10741
|
+
finalizeHeldText() {
|
|
10742
|
+
const emit = this.suppress?.drain(this.toolCalls.size > 0);
|
|
10743
|
+
return emit !== void 0 ? { type: "text_delta", text: emit } : void 0;
|
|
10632
10744
|
}
|
|
10633
10745
|
mergeToolCallDeltas(deltas) {
|
|
10634
10746
|
for (const call of deltas ?? []) {
|
|
@@ -10654,7 +10766,8 @@ var init_openai2 = __esm({
|
|
|
10654
10766
|
if (this.extractFromContent && toolCalls.length === 0) {
|
|
10655
10767
|
const recovered = extractHermesToolCalls(
|
|
10656
10768
|
this.text,
|
|
10657
|
-
() => `hermes-${globalThis.crypto.randomUUID()}
|
|
10769
|
+
() => `hermes-${globalThis.crypto.randomUUID()}`,
|
|
10770
|
+
this.allowedToolNames
|
|
10658
10771
|
);
|
|
10659
10772
|
if (recovered.toolCalls.length > 0) {
|
|
10660
10773
|
toolCalls.push(...recovered.toolCalls);
|
|
@@ -10662,6 +10775,12 @@ var init_openai2 = __esm({
|
|
|
10662
10775
|
stopReason = "tool_use";
|
|
10663
10776
|
process.stderr.write(
|
|
10664
10777
|
`[theokit-sdk] recovered ${recovered.toolCalls.length} leaked tool call(s) from assistant content (provider="${this.providerName}", names=${recovered.toolCalls.map((c) => c.name).join(",")})
|
|
10778
|
+
`
|
|
10779
|
+
);
|
|
10780
|
+
}
|
|
10781
|
+
if (recovered.droppedNames.length > 0) {
|
|
10782
|
+
process.stderr.write(
|
|
10783
|
+
`[theokit-sdk] dropped ${recovered.droppedNames.length} leaked block(s) whose name is not a tool in the request (provider="${this.providerName}", names=${recovered.droppedNames.join(",")})
|
|
10665
10784
|
`
|
|
10666
10785
|
);
|
|
10667
10786
|
}
|