@theokit/sdk 2.14.0 → 2.15.1
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 +151 -8
- package/dist/a2a/index.cjs.map +1 -1
- package/dist/a2a/index.js +151 -8
- package/dist/a2a/index.js.map +1 -1
- package/dist/{cron-CpxLdAXc.d.cts → cron-BxLSz1UH.d.cts} +1 -1
- package/dist/{cron-CL_9nfhQ.d.ts → cron-DcaoP7aW.d.ts} +1 -1
- package/dist/cron.cjs +144 -8
- package/dist/cron.cjs.map +1 -1
- package/dist/cron.d.cts +2 -2
- package/dist/cron.d.ts +2 -2
- package/dist/cron.js +144 -8
- package/dist/cron.js.map +1 -1
- package/dist/{errors-9yw4UQwX.d.cts → errors-Bart0ptP.d.cts} +1 -1
- package/dist/{errors-DFiY-NHK.d.ts → errors-DJuuubJK.d.ts} +1 -1
- package/dist/errors.d.cts +2 -2
- package/dist/eval.cjs +144 -8
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +144 -8
- package/dist/eval.js.map +1 -1
- package/dist/index.cjs +144 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +144 -8
- package/dist/index.js.map +1 -1
- package/dist/internal/agent-loop/doom-loop-tracker.d.ts +22 -0
- package/dist/internal/agent-loop/loop-types.d.ts +6 -0
- package/dist/internal/llm/hermes-tool-extract.d.ts +5 -1
- package/dist/internal/llm/openai.d.ts +5 -1
- package/dist/{run-TMdc7gmo.d.cts → run-DXy_MVwz.d.cts} +29 -1
- package/dist/{run-TMdc7gmo.d.ts → run-DXy_MVwz.d.ts} +29 -1
- package/dist/types/run.d.ts +28 -0
- package/package.json +1 -1
package/dist/eval.js
CHANGED
|
@@ -1454,6 +1454,7 @@ function isEmptyRound(result) {
|
|
|
1454
1454
|
return (result.result ?? "").trim() === "";
|
|
1455
1455
|
}
|
|
1456
1456
|
function classifyRound(result, round, maxRounds, emptyStreak) {
|
|
1457
|
+
if (result.stoppedByDoomLoop === true) return "no_progress";
|
|
1457
1458
|
if (result.stoppedAtIterationLimit !== true) return "done";
|
|
1458
1459
|
if (isEmptyRound(result) && emptyStreak >= 1) return "no_progress";
|
|
1459
1460
|
if (round >= maxRounds) return "step_limit";
|
|
@@ -4487,6 +4488,7 @@ function applyScriptMetrics(base, script) {
|
|
|
4487
4488
|
if (script.usage !== void 0) base.usage = script.usage;
|
|
4488
4489
|
if (script.cost !== void 0) base.cost = script.cost;
|
|
4489
4490
|
if (script.stoppedAtIterationLimit === true) base.stoppedAtIterationLimit = true;
|
|
4491
|
+
if (script.stoppedByDoomLoop === true) base.stoppedByDoomLoop = true;
|
|
4490
4492
|
}
|
|
4491
4493
|
|
|
4492
4494
|
// src/internal/runtime/cloud/cloud-run.ts
|
|
@@ -8268,6 +8270,93 @@ function evaluateBudgetGate(tracker) {
|
|
|
8268
8270
|
}
|
|
8269
8271
|
}
|
|
8270
8272
|
|
|
8273
|
+
// src/internal/agent-loop/doom-loop-tracker.ts
|
|
8274
|
+
init_errors();
|
|
8275
|
+
function createDoomLoopTracker(option) {
|
|
8276
|
+
if (option === false) return void 0;
|
|
8277
|
+
return new DoomLoopTracker(option);
|
|
8278
|
+
}
|
|
8279
|
+
var DEFAULT_CONFIG = { softThreshold: 3, hardThreshold: 5 };
|
|
8280
|
+
function assertValidThresholds(soft, hard) {
|
|
8281
|
+
for (const [label, value] of [
|
|
8282
|
+
["softThreshold", soft],
|
|
8283
|
+
["hardThreshold", hard]
|
|
8284
|
+
]) {
|
|
8285
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
8286
|
+
throw new ConfigurationError(
|
|
8287
|
+
`doomLoop.${label} must be a positive integer (received ${value}).`,
|
|
8288
|
+
{ code: "invalid_doom_loop_threshold" }
|
|
8289
|
+
);
|
|
8290
|
+
}
|
|
8291
|
+
}
|
|
8292
|
+
}
|
|
8293
|
+
function sortKeys(value) {
|
|
8294
|
+
if (value === null || typeof value !== "object") return value;
|
|
8295
|
+
if (Array.isArray(value)) return value.map(sortKeys);
|
|
8296
|
+
const out = {};
|
|
8297
|
+
for (const key of Object.keys(value).sort()) {
|
|
8298
|
+
out[key] = sortKeys(value[key]);
|
|
8299
|
+
}
|
|
8300
|
+
return out;
|
|
8301
|
+
}
|
|
8302
|
+
function signatureOf(call) {
|
|
8303
|
+
const { input } = call;
|
|
8304
|
+
let inputSig;
|
|
8305
|
+
if (input === null || input === void 0) inputSig = "null";
|
|
8306
|
+
else if (typeof input !== "object") inputSig = String(input);
|
|
8307
|
+
else {
|
|
8308
|
+
try {
|
|
8309
|
+
inputSig = JSON.stringify(sortKeys(input)) ?? "null";
|
|
8310
|
+
} catch {
|
|
8311
|
+
inputSig = String(input);
|
|
8312
|
+
}
|
|
8313
|
+
}
|
|
8314
|
+
return `${call.name}\0${inputSig}`;
|
|
8315
|
+
}
|
|
8316
|
+
var DoomLoopTracker = class {
|
|
8317
|
+
#config;
|
|
8318
|
+
#lastSignature = "";
|
|
8319
|
+
#count = 0;
|
|
8320
|
+
constructor(config) {
|
|
8321
|
+
const softThreshold = config?.softThreshold ?? DEFAULT_CONFIG.softThreshold;
|
|
8322
|
+
const hardThreshold = config?.hardThreshold ?? DEFAULT_CONFIG.hardThreshold;
|
|
8323
|
+
assertValidThresholds(softThreshold, hardThreshold);
|
|
8324
|
+
this.#config = { softThreshold, hardThreshold };
|
|
8325
|
+
}
|
|
8326
|
+
inspect(call) {
|
|
8327
|
+
const signature = signatureOf(call);
|
|
8328
|
+
this.#count = signature === this.#lastSignature ? this.#count + 1 : 1;
|
|
8329
|
+
this.#lastSignature = signature;
|
|
8330
|
+
const count = this.#count;
|
|
8331
|
+
if (count >= this.#config.hardThreshold) {
|
|
8332
|
+
return {
|
|
8333
|
+
kind: "hard",
|
|
8334
|
+
message: `Detected ${count} consecutive identical calls to \`${call.name}\`; stopping to avoid a loop.`
|
|
8335
|
+
};
|
|
8336
|
+
}
|
|
8337
|
+
if (count === this.#config.softThreshold) {
|
|
8338
|
+
return {
|
|
8339
|
+
kind: "soft",
|
|
8340
|
+
message: `Detected ${count} consecutive identical calls to \`${call.name}\`; try a different approach.`
|
|
8341
|
+
};
|
|
8342
|
+
}
|
|
8343
|
+
return { kind: "ok" };
|
|
8344
|
+
}
|
|
8345
|
+
reset() {
|
|
8346
|
+
this.#lastSignature = "";
|
|
8347
|
+
this.#count = 0;
|
|
8348
|
+
}
|
|
8349
|
+
};
|
|
8350
|
+
function firstDoomLoopVerdict(tracker, calls) {
|
|
8351
|
+
let escalation = { kind: "ok" };
|
|
8352
|
+
for (const call of calls) {
|
|
8353
|
+
const v = tracker.inspect(call);
|
|
8354
|
+
if (v.kind === "hard") return v;
|
|
8355
|
+
if (v.kind === "soft" && escalation.kind === "ok") escalation = v;
|
|
8356
|
+
}
|
|
8357
|
+
return escalation;
|
|
8358
|
+
}
|
|
8359
|
+
|
|
8271
8360
|
// src/internal/budget/usage-accumulator.ts
|
|
8272
8361
|
var UsageAccumulator = class {
|
|
8273
8362
|
input = 0;
|
|
@@ -8448,6 +8537,7 @@ async function initLoopContext(inputs) {
|
|
|
8448
8537
|
tools,
|
|
8449
8538
|
finalText: "",
|
|
8450
8539
|
finalStatus: "finished",
|
|
8540
|
+
doomLoop: createDoomLoopTracker(inputs.doomLoop),
|
|
8451
8541
|
usage: new UsageAccumulator(),
|
|
8452
8542
|
nudgeAttempts: 0,
|
|
8453
8543
|
stopFeedbackAttempts: 0,
|
|
@@ -9472,6 +9562,7 @@ async function runAgentLoop(inputs) {
|
|
|
9472
9562
|
ctx.finalStatus = "error";
|
|
9473
9563
|
}
|
|
9474
9564
|
sendSpan?.setAttribute("status", ctx.finalStatus);
|
|
9565
|
+
if (ctx.stoppedByDoomLoop === true) sendSpan?.setAttribute("stoppedByDoomLoop", true);
|
|
9475
9566
|
if (inputs.telemetry?.includeContent === true && ctx.finalText.length > 0) {
|
|
9476
9567
|
sendSpan?.addEvent("response", { content: ctx.finalText });
|
|
9477
9568
|
}
|
|
@@ -9498,7 +9589,8 @@ async function runAgentLoop(inputs) {
|
|
|
9498
9589
|
...usage !== void 0 ? { usage } : {},
|
|
9499
9590
|
...cost !== void 0 ? { cost } : {},
|
|
9500
9591
|
...ctx.error !== void 0 ? { error: ctx.error } : {},
|
|
9501
|
-
...ctx.stoppedAtIterationLimit === true ? { stoppedAtIterationLimit: true } : {}
|
|
9592
|
+
...ctx.stoppedAtIterationLimit === true ? { stoppedAtIterationLimit: true } : {},
|
|
9593
|
+
...ctx.stoppedByDoomLoop === true ? { stoppedByDoomLoop: true } : {}
|
|
9502
9594
|
};
|
|
9503
9595
|
} finally {
|
|
9504
9596
|
if (ctxRef !== void 0 && ctxRef.memoryProviderHandle !== void 0 && inputs.memoryProvider !== void 0) {
|
|
@@ -9657,8 +9749,26 @@ async function continueOrTerminate(inputs, ctx, llmOutput) {
|
|
|
9657
9749
|
}
|
|
9658
9750
|
}
|
|
9659
9751
|
pushToolConversationSteps(ctx, llmOutput.toolCalls, toolResults);
|
|
9752
|
+
if (await inspectDoomLoop(inputs, ctx, llmOutput.toolCalls) === "stop") return "done";
|
|
9660
9753
|
return handleToolErrorContinuation(inputs, ctx, toolResults);
|
|
9661
9754
|
}
|
|
9755
|
+
async function inspectDoomLoop(inputs, ctx, toolCalls) {
|
|
9756
|
+
if (ctx.doomLoop === void 0) return "continue";
|
|
9757
|
+
const verdict = firstDoomLoopVerdict(ctx.doomLoop, toolCalls);
|
|
9758
|
+
if (verdict.kind === "hard") {
|
|
9759
|
+
ctx.stoppedByDoomLoop = true;
|
|
9760
|
+
await emitAssistantTextStep(
|
|
9761
|
+
inputs,
|
|
9762
|
+
ctx,
|
|
9763
|
+
verdict.message ?? "Stopped: repeated identical tool calls made no progress."
|
|
9764
|
+
);
|
|
9765
|
+
return "stop";
|
|
9766
|
+
}
|
|
9767
|
+
if (verdict.kind === "soft") {
|
|
9768
|
+
ctx.messages.push({ role: "user", content: [{ type: "text", text: verdict.message ?? "" }] });
|
|
9769
|
+
}
|
|
9770
|
+
return "continue";
|
|
9771
|
+
}
|
|
9662
9772
|
|
|
9663
9773
|
// src/internal/llm/fallback-client.ts
|
|
9664
9774
|
init_errors();
|
|
@@ -11060,11 +11170,16 @@ function sanitizeToolInput(input, options) {
|
|
|
11060
11170
|
// src/internal/llm/hermes-tool-extract.ts
|
|
11061
11171
|
var HERMES_BLOCK = /<function=\s*([^>\s]+)\s*>([\s\S]*?)<\/tool_call>/g;
|
|
11062
11172
|
var HERMES_PARAM = /<parameter=\s*([^>\s]+)\s*>([\s\S]*?)<\/parameter>/g;
|
|
11063
|
-
function extractHermesToolCalls(content, makeId) {
|
|
11173
|
+
function extractHermesToolCalls(content, makeId, allowedToolNames) {
|
|
11174
|
+
const isPromoted = (name) => name.length > 0 && (allowedToolNames === void 0 || allowedToolNames.has(name));
|
|
11064
11175
|
const toolCalls = [];
|
|
11176
|
+
const droppedNames = [];
|
|
11065
11177
|
for (const block of content.matchAll(HERMES_BLOCK)) {
|
|
11066
11178
|
const name = (block[1] ?? "").trim();
|
|
11067
|
-
if (name
|
|
11179
|
+
if (!isPromoted(name)) {
|
|
11180
|
+
if (name.length > 0 && allowedToolNames !== void 0) droppedNames.push(name);
|
|
11181
|
+
continue;
|
|
11182
|
+
}
|
|
11068
11183
|
toolCalls.push({
|
|
11069
11184
|
type: "tool_use",
|
|
11070
11185
|
id: makeId(),
|
|
@@ -11072,8 +11187,11 @@ function extractHermesToolCalls(content, makeId) {
|
|
|
11072
11187
|
input: parseHermesParams(block[2] ?? "")
|
|
11073
11188
|
});
|
|
11074
11189
|
}
|
|
11075
|
-
const residualText = toolCalls.length === 0 ? content : content.replace(
|
|
11076
|
-
|
|
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 };
|
|
11077
11195
|
}
|
|
11078
11196
|
function parseHermesParams(inner) {
|
|
11079
11197
|
const input = {};
|
|
@@ -11157,7 +11275,10 @@ var OpenAIClient = class {
|
|
|
11157
11275
|
}
|
|
11158
11276
|
const accumulator = new OpenAIStreamAccumulator(
|
|
11159
11277
|
this.options.extractToolCallsFromContent ?? false,
|
|
11160
|
-
providerId
|
|
11278
|
+
providerId,
|
|
11279
|
+
// R5: request-scoped allowlist — leaked recovery only promotes a block whose name is a tool the
|
|
11280
|
+
// model was actually given. Empty set (no tools) recovers nothing.
|
|
11281
|
+
new Set(request.tools?.map((tool) => tool.name) ?? [])
|
|
11161
11282
|
);
|
|
11162
11283
|
for await (const record of parseSseStream(response.body, signal)) {
|
|
11163
11284
|
if (record.data === "[DONE]") break;
|
|
@@ -11187,13 +11308,18 @@ var OpenAIStreamAccumulator = class {
|
|
|
11187
11308
|
/**
|
|
11188
11309
|
* @param extractFromContent opt-in leaked-dialect safe-parse (theokit#58). Default false.
|
|
11189
11310
|
* @param providerName provider id, used only to label the recovery log line.
|
|
11311
|
+
* @param allowedToolNames R5 request-scoped allowlist — built from `request.tools` at `stream()`;
|
|
11312
|
+
* leaked recovery in `finish()` only promotes a block whose name is in this set. `undefined`
|
|
11313
|
+
* (direct construction) recovers all (back-compat); an empty set recovers nothing.
|
|
11190
11314
|
*/
|
|
11191
|
-
constructor(extractFromContent = false, providerName = "openai") {
|
|
11315
|
+
constructor(extractFromContent = false, providerName = "openai", allowedToolNames) {
|
|
11192
11316
|
this.extractFromContent = extractFromContent;
|
|
11193
11317
|
this.providerName = providerName;
|
|
11318
|
+
this.allowedToolNames = allowedToolNames;
|
|
11194
11319
|
}
|
|
11195
11320
|
extractFromContent;
|
|
11196
11321
|
providerName;
|
|
11322
|
+
allowedToolNames;
|
|
11197
11323
|
text = "";
|
|
11198
11324
|
stopReason = "end_turn";
|
|
11199
11325
|
inputTokens;
|
|
@@ -11264,7 +11390,8 @@ var OpenAIStreamAccumulator = class {
|
|
|
11264
11390
|
if (this.extractFromContent && toolCalls.length === 0) {
|
|
11265
11391
|
const recovered = extractHermesToolCalls(
|
|
11266
11392
|
this.text,
|
|
11267
|
-
() => `hermes-${globalThis.crypto.randomUUID()}
|
|
11393
|
+
() => `hermes-${globalThis.crypto.randomUUID()}`,
|
|
11394
|
+
this.allowedToolNames
|
|
11268
11395
|
);
|
|
11269
11396
|
if (recovered.toolCalls.length > 0) {
|
|
11270
11397
|
toolCalls.push(...recovered.toolCalls);
|
|
@@ -11272,6 +11399,12 @@ var OpenAIStreamAccumulator = class {
|
|
|
11272
11399
|
stopReason = "tool_use";
|
|
11273
11400
|
process.stderr.write(
|
|
11274
11401
|
`[theokit-sdk] recovered ${recovered.toolCalls.length} leaked tool call(s) from assistant content (provider="${this.providerName}", names=${recovered.toolCalls.map((c) => c.name).join(",")})
|
|
11402
|
+
`
|
|
11403
|
+
);
|
|
11404
|
+
}
|
|
11405
|
+
if (recovered.droppedNames.length > 0) {
|
|
11406
|
+
process.stderr.write(
|
|
11407
|
+
`[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(",")})
|
|
11275
11408
|
`
|
|
11276
11409
|
);
|
|
11277
11410
|
}
|
|
@@ -12203,6 +12336,8 @@ function buildLoopInputs(options, runId, userText) {
|
|
|
12203
12336
|
// M1-2: per-send iteration ceiling (validated above). The loop reads
|
|
12204
12337
|
// inputs.maxIterations (default 8 when unset).
|
|
12205
12338
|
...maxIterations !== void 0 ? { maxIterations } : {},
|
|
12339
|
+
// Doom-loop guard config (default on; `false` disables, object tunes thresholds).
|
|
12340
|
+
...options.sendOptions.doomLoop !== void 0 ? { doomLoop: options.sendOptions.doomLoop } : {},
|
|
12206
12341
|
// D315-D317 — tool lifecycle hooks (cost tracking + audit + retry/alert)
|
|
12207
12342
|
...options.agentOptions.onToolStart !== void 0 ? { onToolStart: options.agentOptions.onToolStart } : {},
|
|
12208
12343
|
...options.agentOptions.onToolEnd !== void 0 ? { onToolEnd: options.agentOptions.onToolEnd } : {},
|
|
@@ -12335,6 +12470,7 @@ var RealLocalRun = class extends FixtureRunBase {
|
|
|
12335
12470
|
if (output.usage !== void 0) this.script.usage = output.usage;
|
|
12336
12471
|
if (output.cost !== void 0) this.script.cost = output.cost;
|
|
12337
12472
|
if (output.stoppedAtIterationLimit === true) this.script.stoppedAtIterationLimit = true;
|
|
12473
|
+
if (output.stoppedByDoomLoop === true) this.script.stoppedByDoomLoop = true;
|
|
12338
12474
|
if (output.error !== void 0 && this.script.errorDetail === void 0) {
|
|
12339
12475
|
this.script.errorDetail = {
|
|
12340
12476
|
message: output.error.message,
|