@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/index.js
CHANGED
|
@@ -13647,11 +13647,16 @@ function sanitizeToolInput(input, options) {
|
|
|
13647
13647
|
// src/internal/llm/hermes-tool-extract.ts
|
|
13648
13648
|
var HERMES_BLOCK = /<function=\s*([^>\s]+)\s*>([\s\S]*?)<\/tool_call>/g;
|
|
13649
13649
|
var HERMES_PARAM = /<parameter=\s*([^>\s]+)\s*>([\s\S]*?)<\/parameter>/g;
|
|
13650
|
-
function extractHermesToolCalls(content, makeId) {
|
|
13650
|
+
function extractHermesToolCalls(content, makeId, allowedToolNames) {
|
|
13651
|
+
const isPromoted = (name) => name.length > 0 && (allowedToolNames === void 0 || allowedToolNames.has(name));
|
|
13651
13652
|
const toolCalls = [];
|
|
13653
|
+
const droppedNames = [];
|
|
13652
13654
|
for (const block of content.matchAll(HERMES_BLOCK)) {
|
|
13653
13655
|
const name = (block[1] ?? "").trim();
|
|
13654
|
-
if (name
|
|
13656
|
+
if (!isPromoted(name)) {
|
|
13657
|
+
if (name.length > 0 && allowedToolNames !== void 0) droppedNames.push(name);
|
|
13658
|
+
continue;
|
|
13659
|
+
}
|
|
13655
13660
|
toolCalls.push({
|
|
13656
13661
|
type: "tool_use",
|
|
13657
13662
|
id: makeId(),
|
|
@@ -13659,8 +13664,11 @@ function extractHermesToolCalls(content, makeId) {
|
|
|
13659
13664
|
input: parseHermesParams(block[2] ?? "")
|
|
13660
13665
|
});
|
|
13661
13666
|
}
|
|
13662
|
-
const residualText = toolCalls.length === 0 ? content : content.replace(
|
|
13663
|
-
|
|
13667
|
+
const residualText = toolCalls.length === 0 ? content : content.replace(
|
|
13668
|
+
HERMES_BLOCK,
|
|
13669
|
+
(full, rawName) => isPromoted((rawName ?? "").trim()) ? "" : full
|
|
13670
|
+
).trim();
|
|
13671
|
+
return { toolCalls, residualText, droppedNames };
|
|
13664
13672
|
}
|
|
13665
13673
|
function parseHermesParams(inner) {
|
|
13666
13674
|
const input = {};
|
|
@@ -13672,6 +13680,77 @@ function parseHermesParams(inner) {
|
|
|
13672
13680
|
}
|
|
13673
13681
|
return sanitizeToolInput(input, { trim: true }).value;
|
|
13674
13682
|
}
|
|
13683
|
+
var STREAM_MARKER = "<function=";
|
|
13684
|
+
var DEFAULT_STREAM_BUFFER_CAP = 8192;
|
|
13685
|
+
var isStreamWs = (c) => c === " " || c === " " || c === "\n" || c === "\r";
|
|
13686
|
+
function streamToolCallBufferState(held, allowedToolNames, cap2 = DEFAULT_STREAM_BUFFER_CAP) {
|
|
13687
|
+
if (allowedToolNames.size === 0) return "impossible";
|
|
13688
|
+
const t = held.trimStart();
|
|
13689
|
+
if (t.length < STREAM_MARKER.length) {
|
|
13690
|
+
return STREAM_MARKER.startsWith(t) ? "possible" : "impossible";
|
|
13691
|
+
}
|
|
13692
|
+
if (!t.startsWith(STREAM_MARKER)) return "impossible";
|
|
13693
|
+
const parsed = parseStreamMarkerName(t);
|
|
13694
|
+
if (parsed === "building") return "possible";
|
|
13695
|
+
if (parsed === "invalid") return "impossible";
|
|
13696
|
+
const nameOk = parsed.complete ? allowedToolNames.has(parsed.name) : someToolNameStartsWith(allowedToolNames, parsed.name);
|
|
13697
|
+
if (!nameOk) return "impossible";
|
|
13698
|
+
return held.length > cap2 ? "impossible" : "possible";
|
|
13699
|
+
}
|
|
13700
|
+
function parseStreamMarkerName(t) {
|
|
13701
|
+
let cursor = STREAM_MARKER.length;
|
|
13702
|
+
while (cursor < t.length && isStreamWs(t[cursor])) cursor += 1;
|
|
13703
|
+
const nameStart = cursor;
|
|
13704
|
+
while (cursor < t.length && t[cursor] !== ">" && !isStreamWs(t[cursor])) cursor += 1;
|
|
13705
|
+
const name = t.slice(nameStart, cursor);
|
|
13706
|
+
if (name.length === 0) return cursor >= t.length ? "building" : "invalid";
|
|
13707
|
+
return { name, complete: cursor < t.length && t[cursor] === ">" };
|
|
13708
|
+
}
|
|
13709
|
+
function someToolNameStartsWith(allowedToolNames, prefix) {
|
|
13710
|
+
for (const name of allowedToolNames) {
|
|
13711
|
+
if (name.startsWith(prefix)) return true;
|
|
13712
|
+
}
|
|
13713
|
+
return false;
|
|
13714
|
+
}
|
|
13715
|
+
function firstPossibleMarkerStart(held, allowedToolNames) {
|
|
13716
|
+
for (let i = held.indexOf("<"); i !== -1; i = held.indexOf("<", i + 1)) {
|
|
13717
|
+
if (streamToolCallBufferState(held.slice(i), allowedToolNames) === "possible") return i;
|
|
13718
|
+
}
|
|
13719
|
+
return -1;
|
|
13720
|
+
}
|
|
13721
|
+
var StreamSuppressionBuffer = class {
|
|
13722
|
+
constructor(allowedToolNames) {
|
|
13723
|
+
this.allowedToolNames = allowedToolNames;
|
|
13724
|
+
}
|
|
13725
|
+
allowedToolNames;
|
|
13726
|
+
#held = "";
|
|
13727
|
+
/** Feed a content delta; returns the text to emit as a `text_delta` now, or `undefined` to hold. */
|
|
13728
|
+
push(content) {
|
|
13729
|
+
this.#held += content;
|
|
13730
|
+
if (streamToolCallBufferState(this.#held, this.allowedToolNames) === "possible")
|
|
13731
|
+
return void 0;
|
|
13732
|
+
const holdStart = firstPossibleMarkerStart(this.#held, this.allowedToolNames);
|
|
13733
|
+
if (holdStart > 0) {
|
|
13734
|
+
const flush2 = this.#held.slice(0, holdStart);
|
|
13735
|
+
this.#held = this.#held.slice(holdStart);
|
|
13736
|
+
return flush2;
|
|
13737
|
+
}
|
|
13738
|
+
const flush = this.#held;
|
|
13739
|
+
this.#held = "";
|
|
13740
|
+
return flush;
|
|
13741
|
+
}
|
|
13742
|
+
/** Drain the held buffer at stream end. `hasNativeCalls` mirrors `finish()`'s size-guard: when
|
|
13743
|
+
* native `tool_calls` exist, `finish()` won't strip the leaked block, so stream the held text WHOLE
|
|
13744
|
+
* (keeping `accumulatedText == finish.text`); otherwise strip the recoverable blocks. Idempotent. */
|
|
13745
|
+
drain(hasNativeCalls) {
|
|
13746
|
+
if (this.#held.length === 0) return void 0;
|
|
13747
|
+
const held = this.#held;
|
|
13748
|
+
this.#held = "";
|
|
13749
|
+
if (hasNativeCalls) return held;
|
|
13750
|
+
const residual = extractHermesToolCalls(held, () => "held", this.allowedToolNames).residualText;
|
|
13751
|
+
return residual.length > 0 ? residual : void 0;
|
|
13752
|
+
}
|
|
13753
|
+
};
|
|
13675
13754
|
|
|
13676
13755
|
// src/internal/llm/openai.ts
|
|
13677
13756
|
var OpenAIClient = class {
|
|
@@ -13744,7 +13823,10 @@ var OpenAIClient = class {
|
|
|
13744
13823
|
}
|
|
13745
13824
|
const accumulator = new OpenAIStreamAccumulator(
|
|
13746
13825
|
this.options.extractToolCallsFromContent ?? false,
|
|
13747
|
-
providerId
|
|
13826
|
+
providerId,
|
|
13827
|
+
// R5: request-scoped allowlist — leaked recovery only promotes a block whose name is a tool the
|
|
13828
|
+
// model was actually given. Empty set (no tools) recovers nothing.
|
|
13829
|
+
new Set(request.tools?.map((tool) => tool.name) ?? [])
|
|
13748
13830
|
);
|
|
13749
13831
|
for await (const record of parseSseStream(response.body, signal)) {
|
|
13750
13832
|
if (record.data === "[DONE]") break;
|
|
@@ -13767,6 +13849,8 @@ var OpenAIClient = class {
|
|
|
13767
13849
|
const events = accumulator.consume(chunk);
|
|
13768
13850
|
for (const event of events) yield event;
|
|
13769
13851
|
}
|
|
13852
|
+
const drainEvent = accumulator.finalizeHeldText();
|
|
13853
|
+
if (drainEvent !== void 0) yield drainEvent;
|
|
13770
13854
|
return accumulator.finish();
|
|
13771
13855
|
}
|
|
13772
13856
|
};
|
|
@@ -13774,13 +13858,19 @@ var OpenAIStreamAccumulator = class {
|
|
|
13774
13858
|
/**
|
|
13775
13859
|
* @param extractFromContent opt-in leaked-dialect safe-parse (theokit#58). Default false.
|
|
13776
13860
|
* @param providerName provider id, used only to label the recovery log line.
|
|
13861
|
+
* @param allowedToolNames R5 request-scoped allowlist — built from `request.tools` at `stream()`;
|
|
13862
|
+
* leaked recovery in `finish()` only promotes a block whose name is in this set. `undefined`
|
|
13863
|
+
* (direct construction) recovers all (back-compat); an empty set recovers nothing.
|
|
13777
13864
|
*/
|
|
13778
|
-
constructor(extractFromContent = false, providerName = "openai") {
|
|
13865
|
+
constructor(extractFromContent = false, providerName = "openai", allowedToolNames) {
|
|
13779
13866
|
this.extractFromContent = extractFromContent;
|
|
13780
13867
|
this.providerName = providerName;
|
|
13868
|
+
this.allowedToolNames = allowedToolNames;
|
|
13869
|
+
this.suppress = extractFromContent && allowedToolNames !== void 0 && allowedToolNames.size > 0 ? new StreamSuppressionBuffer(allowedToolNames) : void 0;
|
|
13781
13870
|
}
|
|
13782
13871
|
extractFromContent;
|
|
13783
13872
|
providerName;
|
|
13873
|
+
allowedToolNames;
|
|
13784
13874
|
text = "";
|
|
13785
13875
|
stopReason = "end_turn";
|
|
13786
13876
|
inputTokens;
|
|
@@ -13789,18 +13879,30 @@ var OpenAIStreamAccumulator = class {
|
|
|
13789
13879
|
cacheWriteTokens;
|
|
13790
13880
|
reasoningTokens;
|
|
13791
13881
|
toolCalls = /* @__PURE__ */ new Map();
|
|
13882
|
+
/** R7: present only when recovery is enabled AND the request declares tools — holds suspected
|
|
13883
|
+
* leaked-dialect content back from the `text_delta` stream. `undefined` ⇒ stream immediately. */
|
|
13884
|
+
suppress;
|
|
13792
13885
|
consume(chunk) {
|
|
13793
13886
|
const events = [];
|
|
13794
13887
|
this.applyUsage(chunk.usage);
|
|
13795
13888
|
for (const choice of chunk.choices ?? []) {
|
|
13796
|
-
|
|
13797
|
-
|
|
13798
|
-
|
|
13799
|
-
|
|
13800
|
-
|
|
13801
|
-
|
|
13802
|
-
|
|
13803
|
-
|
|
13889
|
+
events.push(...this.applyChoice(choice));
|
|
13890
|
+
}
|
|
13891
|
+
return events;
|
|
13892
|
+
}
|
|
13893
|
+
applyChoice(choice) {
|
|
13894
|
+
const events = [];
|
|
13895
|
+
const reasoningEvent = this.applyReasoningDelta(
|
|
13896
|
+
choice.delta?.reasoning ?? choice.delta?.reasoning_content
|
|
13897
|
+
);
|
|
13898
|
+
if (reasoningEvent !== void 0) events.push(reasoningEvent);
|
|
13899
|
+
const textEvent = this.applyContentDelta(choice.delta?.content);
|
|
13900
|
+
if (textEvent !== void 0) events.push(textEvent);
|
|
13901
|
+
this.mergeToolCallDeltas(choice.delta?.tool_calls);
|
|
13902
|
+
this.applyFinishReason(choice.finish_reason);
|
|
13903
|
+
if (choice.finish_reason !== void 0 && choice.finish_reason !== null) {
|
|
13904
|
+
const flushEvent = this.finalizeHeldText();
|
|
13905
|
+
if (flushEvent !== void 0) events.push(flushEvent);
|
|
13804
13906
|
}
|
|
13805
13907
|
return events;
|
|
13806
13908
|
}
|
|
@@ -13825,7 +13927,17 @@ var OpenAIStreamAccumulator = class {
|
|
|
13825
13927
|
applyContentDelta(content) {
|
|
13826
13928
|
if (typeof content !== "string" || content.length === 0) return void 0;
|
|
13827
13929
|
this.text += content;
|
|
13828
|
-
return { type: "text_delta", text: content };
|
|
13930
|
+
if (this.suppress === void 0) return { type: "text_delta", text: content };
|
|
13931
|
+
const emit2 = this.suppress.push(content);
|
|
13932
|
+
return emit2 !== void 0 ? { type: "text_delta", text: emit2 } : void 0;
|
|
13933
|
+
}
|
|
13934
|
+
/** R7 held-buffer finalizer, called at the `finish_reason` chunk (in `applyChoice`) AND after the
|
|
13935
|
+
* SSE loop in `stream()` — so a stream that omits a `finish_reason` terminal never silently drops
|
|
13936
|
+
* held text. `toolCalls.size > 0` (native calls present) makes `finish()` skip recovery, so the
|
|
13937
|
+
* buffer streams the held text whole. Idempotent once drained. */
|
|
13938
|
+
finalizeHeldText() {
|
|
13939
|
+
const emit2 = this.suppress?.drain(this.toolCalls.size > 0);
|
|
13940
|
+
return emit2 !== void 0 ? { type: "text_delta", text: emit2 } : void 0;
|
|
13829
13941
|
}
|
|
13830
13942
|
mergeToolCallDeltas(deltas) {
|
|
13831
13943
|
for (const call of deltas ?? []) {
|
|
@@ -13851,7 +13963,8 @@ var OpenAIStreamAccumulator = class {
|
|
|
13851
13963
|
if (this.extractFromContent && toolCalls.length === 0) {
|
|
13852
13964
|
const recovered = extractHermesToolCalls(
|
|
13853
13965
|
this.text,
|
|
13854
|
-
() => `hermes-${globalThis.crypto.randomUUID()}
|
|
13966
|
+
() => `hermes-${globalThis.crypto.randomUUID()}`,
|
|
13967
|
+
this.allowedToolNames
|
|
13855
13968
|
);
|
|
13856
13969
|
if (recovered.toolCalls.length > 0) {
|
|
13857
13970
|
toolCalls.push(...recovered.toolCalls);
|
|
@@ -13859,6 +13972,12 @@ var OpenAIStreamAccumulator = class {
|
|
|
13859
13972
|
stopReason = "tool_use";
|
|
13860
13973
|
process.stderr.write(
|
|
13861
13974
|
`[theokit-sdk] recovered ${recovered.toolCalls.length} leaked tool call(s) from assistant content (provider="${this.providerName}", names=${recovered.toolCalls.map((c) => c.name).join(",")})
|
|
13975
|
+
`
|
|
13976
|
+
);
|
|
13977
|
+
}
|
|
13978
|
+
if (recovered.droppedNames.length > 0) {
|
|
13979
|
+
process.stderr.write(
|
|
13980
|
+
`[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(",")})
|
|
13862
13981
|
`
|
|
13863
13982
|
);
|
|
13864
13983
|
}
|