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