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