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