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