@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/cron.cjs
CHANGED
|
@@ -11178,11 +11178,16 @@ function sanitizeToolInput(input, options) {
|
|
|
11178
11178
|
// src/internal/llm/hermes-tool-extract.ts
|
|
11179
11179
|
var HERMES_BLOCK = /<function=\s*([^>\s]+)\s*>([\s\S]*?)<\/tool_call>/g;
|
|
11180
11180
|
var HERMES_PARAM = /<parameter=\s*([^>\s]+)\s*>([\s\S]*?)<\/parameter>/g;
|
|
11181
|
-
function extractHermesToolCalls(content, makeId) {
|
|
11181
|
+
function extractHermesToolCalls(content, makeId, allowedToolNames) {
|
|
11182
|
+
const isPromoted = (name) => name.length > 0 && (allowedToolNames === void 0 || allowedToolNames.has(name));
|
|
11182
11183
|
const toolCalls = [];
|
|
11184
|
+
const droppedNames = [];
|
|
11183
11185
|
for (const block of content.matchAll(HERMES_BLOCK)) {
|
|
11184
11186
|
const name = (block[1] ?? "").trim();
|
|
11185
|
-
if (name
|
|
11187
|
+
if (!isPromoted(name)) {
|
|
11188
|
+
if (name.length > 0 && allowedToolNames !== void 0) droppedNames.push(name);
|
|
11189
|
+
continue;
|
|
11190
|
+
}
|
|
11186
11191
|
toolCalls.push({
|
|
11187
11192
|
type: "tool_use",
|
|
11188
11193
|
id: makeId(),
|
|
@@ -11190,8 +11195,11 @@ function extractHermesToolCalls(content, makeId) {
|
|
|
11190
11195
|
input: parseHermesParams(block[2] ?? "")
|
|
11191
11196
|
});
|
|
11192
11197
|
}
|
|
11193
|
-
const residualText = toolCalls.length === 0 ? content : content.replace(
|
|
11194
|
-
|
|
11198
|
+
const residualText = toolCalls.length === 0 ? content : content.replace(
|
|
11199
|
+
HERMES_BLOCK,
|
|
11200
|
+
(full, rawName) => isPromoted((rawName ?? "").trim()) ? "" : full
|
|
11201
|
+
).trim();
|
|
11202
|
+
return { toolCalls, residualText, droppedNames };
|
|
11195
11203
|
}
|
|
11196
11204
|
function parseHermesParams(inner) {
|
|
11197
11205
|
const input = {};
|
|
@@ -11203,6 +11211,77 @@ function parseHermesParams(inner) {
|
|
|
11203
11211
|
}
|
|
11204
11212
|
return sanitizeToolInput(input, { trim: true }).value;
|
|
11205
11213
|
}
|
|
11214
|
+
var STREAM_MARKER = "<function=";
|
|
11215
|
+
var DEFAULT_STREAM_BUFFER_CAP = 8192;
|
|
11216
|
+
var isStreamWs = (c) => c === " " || c === " " || c === "\n" || c === "\r";
|
|
11217
|
+
function streamToolCallBufferState(held, allowedToolNames, cap = DEFAULT_STREAM_BUFFER_CAP) {
|
|
11218
|
+
if (allowedToolNames.size === 0) return "impossible";
|
|
11219
|
+
const t = held.trimStart();
|
|
11220
|
+
if (t.length < STREAM_MARKER.length) {
|
|
11221
|
+
return STREAM_MARKER.startsWith(t) ? "possible" : "impossible";
|
|
11222
|
+
}
|
|
11223
|
+
if (!t.startsWith(STREAM_MARKER)) return "impossible";
|
|
11224
|
+
const parsed = parseStreamMarkerName(t);
|
|
11225
|
+
if (parsed === "building") return "possible";
|
|
11226
|
+
if (parsed === "invalid") return "impossible";
|
|
11227
|
+
const nameOk = parsed.complete ? allowedToolNames.has(parsed.name) : someToolNameStartsWith(allowedToolNames, parsed.name);
|
|
11228
|
+
if (!nameOk) return "impossible";
|
|
11229
|
+
return held.length > cap ? "impossible" : "possible";
|
|
11230
|
+
}
|
|
11231
|
+
function parseStreamMarkerName(t) {
|
|
11232
|
+
let cursor = STREAM_MARKER.length;
|
|
11233
|
+
while (cursor < t.length && isStreamWs(t[cursor])) cursor += 1;
|
|
11234
|
+
const nameStart = cursor;
|
|
11235
|
+
while (cursor < t.length && t[cursor] !== ">" && !isStreamWs(t[cursor])) cursor += 1;
|
|
11236
|
+
const name = t.slice(nameStart, cursor);
|
|
11237
|
+
if (name.length === 0) return cursor >= t.length ? "building" : "invalid";
|
|
11238
|
+
return { name, complete: cursor < t.length && t[cursor] === ">" };
|
|
11239
|
+
}
|
|
11240
|
+
function someToolNameStartsWith(allowedToolNames, prefix) {
|
|
11241
|
+
for (const name of allowedToolNames) {
|
|
11242
|
+
if (name.startsWith(prefix)) return true;
|
|
11243
|
+
}
|
|
11244
|
+
return false;
|
|
11245
|
+
}
|
|
11246
|
+
function firstPossibleMarkerStart(held, allowedToolNames) {
|
|
11247
|
+
for (let i = held.indexOf("<"); i !== -1; i = held.indexOf("<", i + 1)) {
|
|
11248
|
+
if (streamToolCallBufferState(held.slice(i), allowedToolNames) === "possible") return i;
|
|
11249
|
+
}
|
|
11250
|
+
return -1;
|
|
11251
|
+
}
|
|
11252
|
+
var StreamSuppressionBuffer = class {
|
|
11253
|
+
constructor(allowedToolNames) {
|
|
11254
|
+
this.allowedToolNames = allowedToolNames;
|
|
11255
|
+
}
|
|
11256
|
+
allowedToolNames;
|
|
11257
|
+
#held = "";
|
|
11258
|
+
/** Feed a content delta; returns the text to emit as a `text_delta` now, or `undefined` to hold. */
|
|
11259
|
+
push(content) {
|
|
11260
|
+
this.#held += content;
|
|
11261
|
+
if (streamToolCallBufferState(this.#held, this.allowedToolNames) === "possible")
|
|
11262
|
+
return void 0;
|
|
11263
|
+
const holdStart = firstPossibleMarkerStart(this.#held, this.allowedToolNames);
|
|
11264
|
+
if (holdStart > 0) {
|
|
11265
|
+
const flush2 = this.#held.slice(0, holdStart);
|
|
11266
|
+
this.#held = this.#held.slice(holdStart);
|
|
11267
|
+
return flush2;
|
|
11268
|
+
}
|
|
11269
|
+
const flush = this.#held;
|
|
11270
|
+
this.#held = "";
|
|
11271
|
+
return flush;
|
|
11272
|
+
}
|
|
11273
|
+
/** Drain the held buffer at stream end. `hasNativeCalls` mirrors `finish()`'s size-guard: when
|
|
11274
|
+
* native `tool_calls` exist, `finish()` won't strip the leaked block, so stream the held text WHOLE
|
|
11275
|
+
* (keeping `accumulatedText == finish.text`); otherwise strip the recoverable blocks. Idempotent. */
|
|
11276
|
+
drain(hasNativeCalls) {
|
|
11277
|
+
if (this.#held.length === 0) return void 0;
|
|
11278
|
+
const held = this.#held;
|
|
11279
|
+
this.#held = "";
|
|
11280
|
+
if (hasNativeCalls) return held;
|
|
11281
|
+
const residual = extractHermesToolCalls(held, () => "held", this.allowedToolNames).residualText;
|
|
11282
|
+
return residual.length > 0 ? residual : void 0;
|
|
11283
|
+
}
|
|
11284
|
+
};
|
|
11206
11285
|
|
|
11207
11286
|
// src/internal/llm/openai.ts
|
|
11208
11287
|
var OpenAIClient = class {
|
|
@@ -11275,7 +11354,10 @@ var OpenAIClient = class {
|
|
|
11275
11354
|
}
|
|
11276
11355
|
const accumulator = new OpenAIStreamAccumulator(
|
|
11277
11356
|
this.options.extractToolCallsFromContent ?? false,
|
|
11278
|
-
providerId
|
|
11357
|
+
providerId,
|
|
11358
|
+
// R5: request-scoped allowlist — leaked recovery only promotes a block whose name is a tool the
|
|
11359
|
+
// model was actually given. Empty set (no tools) recovers nothing.
|
|
11360
|
+
new Set(request.tools?.map((tool) => tool.name) ?? [])
|
|
11279
11361
|
);
|
|
11280
11362
|
for await (const record of parseSseStream(response.body, signal)) {
|
|
11281
11363
|
if (record.data === "[DONE]") break;
|
|
@@ -11298,6 +11380,8 @@ var OpenAIClient = class {
|
|
|
11298
11380
|
const events = accumulator.consume(chunk);
|
|
11299
11381
|
for (const event of events) yield event;
|
|
11300
11382
|
}
|
|
11383
|
+
const drainEvent = accumulator.finalizeHeldText();
|
|
11384
|
+
if (drainEvent !== void 0) yield drainEvent;
|
|
11301
11385
|
return accumulator.finish();
|
|
11302
11386
|
}
|
|
11303
11387
|
};
|
|
@@ -11305,13 +11389,19 @@ var OpenAIStreamAccumulator = class {
|
|
|
11305
11389
|
/**
|
|
11306
11390
|
* @param extractFromContent opt-in leaked-dialect safe-parse (theokit#58). Default false.
|
|
11307
11391
|
* @param providerName provider id, used only to label the recovery log line.
|
|
11392
|
+
* @param allowedToolNames R5 request-scoped allowlist — built from `request.tools` at `stream()`;
|
|
11393
|
+
* leaked recovery in `finish()` only promotes a block whose name is in this set. `undefined`
|
|
11394
|
+
* (direct construction) recovers all (back-compat); an empty set recovers nothing.
|
|
11308
11395
|
*/
|
|
11309
|
-
constructor(extractFromContent = false, providerName = "openai") {
|
|
11396
|
+
constructor(extractFromContent = false, providerName = "openai", allowedToolNames) {
|
|
11310
11397
|
this.extractFromContent = extractFromContent;
|
|
11311
11398
|
this.providerName = providerName;
|
|
11399
|
+
this.allowedToolNames = allowedToolNames;
|
|
11400
|
+
this.suppress = extractFromContent && allowedToolNames !== void 0 && allowedToolNames.size > 0 ? new StreamSuppressionBuffer(allowedToolNames) : void 0;
|
|
11312
11401
|
}
|
|
11313
11402
|
extractFromContent;
|
|
11314
11403
|
providerName;
|
|
11404
|
+
allowedToolNames;
|
|
11315
11405
|
text = "";
|
|
11316
11406
|
stopReason = "end_turn";
|
|
11317
11407
|
inputTokens;
|
|
@@ -11320,18 +11410,30 @@ var OpenAIStreamAccumulator = class {
|
|
|
11320
11410
|
cacheWriteTokens;
|
|
11321
11411
|
reasoningTokens;
|
|
11322
11412
|
toolCalls = /* @__PURE__ */ new Map();
|
|
11413
|
+
/** R7: present only when recovery is enabled AND the request declares tools — holds suspected
|
|
11414
|
+
* leaked-dialect content back from the `text_delta` stream. `undefined` ⇒ stream immediately. */
|
|
11415
|
+
suppress;
|
|
11323
11416
|
consume(chunk) {
|
|
11324
11417
|
const events = [];
|
|
11325
11418
|
this.applyUsage(chunk.usage);
|
|
11326
11419
|
for (const choice of chunk.choices ?? []) {
|
|
11327
|
-
|
|
11328
|
-
|
|
11329
|
-
|
|
11330
|
-
|
|
11331
|
-
|
|
11332
|
-
|
|
11333
|
-
|
|
11334
|
-
|
|
11420
|
+
events.push(...this.applyChoice(choice));
|
|
11421
|
+
}
|
|
11422
|
+
return events;
|
|
11423
|
+
}
|
|
11424
|
+
applyChoice(choice) {
|
|
11425
|
+
const events = [];
|
|
11426
|
+
const reasoningEvent = this.applyReasoningDelta(
|
|
11427
|
+
choice.delta?.reasoning ?? choice.delta?.reasoning_content
|
|
11428
|
+
);
|
|
11429
|
+
if (reasoningEvent !== void 0) events.push(reasoningEvent);
|
|
11430
|
+
const textEvent = this.applyContentDelta(choice.delta?.content);
|
|
11431
|
+
if (textEvent !== void 0) events.push(textEvent);
|
|
11432
|
+
this.mergeToolCallDeltas(choice.delta?.tool_calls);
|
|
11433
|
+
this.applyFinishReason(choice.finish_reason);
|
|
11434
|
+
if (choice.finish_reason !== void 0 && choice.finish_reason !== null) {
|
|
11435
|
+
const flushEvent = this.finalizeHeldText();
|
|
11436
|
+
if (flushEvent !== void 0) events.push(flushEvent);
|
|
11335
11437
|
}
|
|
11336
11438
|
return events;
|
|
11337
11439
|
}
|
|
@@ -11356,7 +11458,17 @@ var OpenAIStreamAccumulator = class {
|
|
|
11356
11458
|
applyContentDelta(content) {
|
|
11357
11459
|
if (typeof content !== "string" || content.length === 0) return void 0;
|
|
11358
11460
|
this.text += content;
|
|
11359
|
-
return { type: "text_delta", text: content };
|
|
11461
|
+
if (this.suppress === void 0) return { type: "text_delta", text: content };
|
|
11462
|
+
const emit = this.suppress.push(content);
|
|
11463
|
+
return emit !== void 0 ? { type: "text_delta", text: emit } : void 0;
|
|
11464
|
+
}
|
|
11465
|
+
/** R7 held-buffer finalizer, called at the `finish_reason` chunk (in `applyChoice`) AND after the
|
|
11466
|
+
* SSE loop in `stream()` — so a stream that omits a `finish_reason` terminal never silently drops
|
|
11467
|
+
* held text. `toolCalls.size > 0` (native calls present) makes `finish()` skip recovery, so the
|
|
11468
|
+
* buffer streams the held text whole. Idempotent once drained. */
|
|
11469
|
+
finalizeHeldText() {
|
|
11470
|
+
const emit = this.suppress?.drain(this.toolCalls.size > 0);
|
|
11471
|
+
return emit !== void 0 ? { type: "text_delta", text: emit } : void 0;
|
|
11360
11472
|
}
|
|
11361
11473
|
mergeToolCallDeltas(deltas) {
|
|
11362
11474
|
for (const call of deltas ?? []) {
|
|
@@ -11382,7 +11494,8 @@ var OpenAIStreamAccumulator = class {
|
|
|
11382
11494
|
if (this.extractFromContent && toolCalls.length === 0) {
|
|
11383
11495
|
const recovered = extractHermesToolCalls(
|
|
11384
11496
|
this.text,
|
|
11385
|
-
() => `hermes-${globalThis.crypto.randomUUID()}
|
|
11497
|
+
() => `hermes-${globalThis.crypto.randomUUID()}`,
|
|
11498
|
+
this.allowedToolNames
|
|
11386
11499
|
);
|
|
11387
11500
|
if (recovered.toolCalls.length > 0) {
|
|
11388
11501
|
toolCalls.push(...recovered.toolCalls);
|
|
@@ -11390,6 +11503,12 @@ var OpenAIStreamAccumulator = class {
|
|
|
11390
11503
|
stopReason = "tool_use";
|
|
11391
11504
|
process.stderr.write(
|
|
11392
11505
|
`[theokit-sdk] recovered ${recovered.toolCalls.length} leaked tool call(s) from assistant content (provider="${this.providerName}", names=${recovered.toolCalls.map((c) => c.name).join(",")})
|
|
11506
|
+
`
|
|
11507
|
+
);
|
|
11508
|
+
}
|
|
11509
|
+
if (recovered.droppedNames.length > 0) {
|
|
11510
|
+
process.stderr.write(
|
|
11511
|
+
`[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(",")})
|
|
11393
11512
|
`
|
|
11394
11513
|
);
|
|
11395
11514
|
}
|