@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/cron.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import './run-
|
|
2
|
-
export { K as Cron } from './cron-
|
|
1
|
+
import './run-DXy_MVwz.cjs';
|
|
2
|
+
export { K as Cron } from './cron-BxLSz1UH.cjs';
|
package/dist/cron.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import './run-
|
|
2
|
-
export { K as Cron } from './cron-
|
|
1
|
+
import './run-DXy_MVwz.js';
|
|
2
|
+
export { K as Cron } from './cron-DcaoP7aW.js';
|
package/dist/cron.js
CHANGED
|
@@ -1455,6 +1455,7 @@ function isEmptyRound(result) {
|
|
|
1455
1455
|
return (result.result ?? "").trim() === "";
|
|
1456
1456
|
}
|
|
1457
1457
|
function classifyRound(result, round, maxRounds, emptyStreak) {
|
|
1458
|
+
if (result.stoppedByDoomLoop === true) return "no_progress";
|
|
1458
1459
|
if (result.stoppedAtIterationLimit !== true) return "done";
|
|
1459
1460
|
if (isEmptyRound(result) && emptyStreak >= 1) return "no_progress";
|
|
1460
1461
|
if (round >= maxRounds) return "step_limit";
|
|
@@ -4492,6 +4493,7 @@ function applyScriptMetrics(base, script) {
|
|
|
4492
4493
|
if (script.usage !== void 0) base.usage = script.usage;
|
|
4493
4494
|
if (script.cost !== void 0) base.cost = script.cost;
|
|
4494
4495
|
if (script.stoppedAtIterationLimit === true) base.stoppedAtIterationLimit = true;
|
|
4496
|
+
if (script.stoppedByDoomLoop === true) base.stoppedByDoomLoop = true;
|
|
4495
4497
|
}
|
|
4496
4498
|
|
|
4497
4499
|
// src/internal/runtime/cloud/cloud-run.ts
|
|
@@ -8273,6 +8275,93 @@ function evaluateBudgetGate(tracker) {
|
|
|
8273
8275
|
}
|
|
8274
8276
|
}
|
|
8275
8277
|
|
|
8278
|
+
// src/internal/agent-loop/doom-loop-tracker.ts
|
|
8279
|
+
init_errors();
|
|
8280
|
+
function createDoomLoopTracker(option) {
|
|
8281
|
+
if (option === false) return void 0;
|
|
8282
|
+
return new DoomLoopTracker(option);
|
|
8283
|
+
}
|
|
8284
|
+
var DEFAULT_CONFIG = { softThreshold: 3, hardThreshold: 5 };
|
|
8285
|
+
function assertValidThresholds(soft, hard) {
|
|
8286
|
+
for (const [label, value] of [
|
|
8287
|
+
["softThreshold", soft],
|
|
8288
|
+
["hardThreshold", hard]
|
|
8289
|
+
]) {
|
|
8290
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
8291
|
+
throw new ConfigurationError(
|
|
8292
|
+
`doomLoop.${label} must be a positive integer (received ${value}).`,
|
|
8293
|
+
{ code: "invalid_doom_loop_threshold" }
|
|
8294
|
+
);
|
|
8295
|
+
}
|
|
8296
|
+
}
|
|
8297
|
+
}
|
|
8298
|
+
function sortKeys(value) {
|
|
8299
|
+
if (value === null || typeof value !== "object") return value;
|
|
8300
|
+
if (Array.isArray(value)) return value.map(sortKeys);
|
|
8301
|
+
const out = {};
|
|
8302
|
+
for (const key of Object.keys(value).sort()) {
|
|
8303
|
+
out[key] = sortKeys(value[key]);
|
|
8304
|
+
}
|
|
8305
|
+
return out;
|
|
8306
|
+
}
|
|
8307
|
+
function signatureOf(call) {
|
|
8308
|
+
const { input } = call;
|
|
8309
|
+
let inputSig;
|
|
8310
|
+
if (input === null || input === void 0) inputSig = "null";
|
|
8311
|
+
else if (typeof input !== "object") inputSig = String(input);
|
|
8312
|
+
else {
|
|
8313
|
+
try {
|
|
8314
|
+
inputSig = JSON.stringify(sortKeys(input)) ?? "null";
|
|
8315
|
+
} catch {
|
|
8316
|
+
inputSig = String(input);
|
|
8317
|
+
}
|
|
8318
|
+
}
|
|
8319
|
+
return `${call.name}\0${inputSig}`;
|
|
8320
|
+
}
|
|
8321
|
+
var DoomLoopTracker = class {
|
|
8322
|
+
#config;
|
|
8323
|
+
#lastSignature = "";
|
|
8324
|
+
#count = 0;
|
|
8325
|
+
constructor(config) {
|
|
8326
|
+
const softThreshold = config?.softThreshold ?? DEFAULT_CONFIG.softThreshold;
|
|
8327
|
+
const hardThreshold = config?.hardThreshold ?? DEFAULT_CONFIG.hardThreshold;
|
|
8328
|
+
assertValidThresholds(softThreshold, hardThreshold);
|
|
8329
|
+
this.#config = { softThreshold, hardThreshold };
|
|
8330
|
+
}
|
|
8331
|
+
inspect(call) {
|
|
8332
|
+
const signature = signatureOf(call);
|
|
8333
|
+
this.#count = signature === this.#lastSignature ? this.#count + 1 : 1;
|
|
8334
|
+
this.#lastSignature = signature;
|
|
8335
|
+
const count = this.#count;
|
|
8336
|
+
if (count >= this.#config.hardThreshold) {
|
|
8337
|
+
return {
|
|
8338
|
+
kind: "hard",
|
|
8339
|
+
message: `Detected ${count} consecutive identical calls to \`${call.name}\`; stopping to avoid a loop.`
|
|
8340
|
+
};
|
|
8341
|
+
}
|
|
8342
|
+
if (count === this.#config.softThreshold) {
|
|
8343
|
+
return {
|
|
8344
|
+
kind: "soft",
|
|
8345
|
+
message: `Detected ${count} consecutive identical calls to \`${call.name}\`; try a different approach.`
|
|
8346
|
+
};
|
|
8347
|
+
}
|
|
8348
|
+
return { kind: "ok" };
|
|
8349
|
+
}
|
|
8350
|
+
reset() {
|
|
8351
|
+
this.#lastSignature = "";
|
|
8352
|
+
this.#count = 0;
|
|
8353
|
+
}
|
|
8354
|
+
};
|
|
8355
|
+
function firstDoomLoopVerdict(tracker, calls) {
|
|
8356
|
+
let escalation = { kind: "ok" };
|
|
8357
|
+
for (const call of calls) {
|
|
8358
|
+
const v = tracker.inspect(call);
|
|
8359
|
+
if (v.kind === "hard") return v;
|
|
8360
|
+
if (v.kind === "soft" && escalation.kind === "ok") escalation = v;
|
|
8361
|
+
}
|
|
8362
|
+
return escalation;
|
|
8363
|
+
}
|
|
8364
|
+
|
|
8276
8365
|
// src/internal/budget/usage-accumulator.ts
|
|
8277
8366
|
var UsageAccumulator = class {
|
|
8278
8367
|
input = 0;
|
|
@@ -8453,6 +8542,7 @@ async function initLoopContext(inputs) {
|
|
|
8453
8542
|
tools,
|
|
8454
8543
|
finalText: "",
|
|
8455
8544
|
finalStatus: "finished",
|
|
8545
|
+
doomLoop: createDoomLoopTracker(inputs.doomLoop),
|
|
8456
8546
|
usage: new UsageAccumulator(),
|
|
8457
8547
|
nudgeAttempts: 0,
|
|
8458
8548
|
stopFeedbackAttempts: 0,
|
|
@@ -9477,6 +9567,7 @@ async function runAgentLoop(inputs) {
|
|
|
9477
9567
|
ctx.finalStatus = "error";
|
|
9478
9568
|
}
|
|
9479
9569
|
sendSpan?.setAttribute("status", ctx.finalStatus);
|
|
9570
|
+
if (ctx.stoppedByDoomLoop === true) sendSpan?.setAttribute("stoppedByDoomLoop", true);
|
|
9480
9571
|
if (inputs.telemetry?.includeContent === true && ctx.finalText.length > 0) {
|
|
9481
9572
|
sendSpan?.addEvent("response", { content: ctx.finalText });
|
|
9482
9573
|
}
|
|
@@ -9503,7 +9594,8 @@ async function runAgentLoop(inputs) {
|
|
|
9503
9594
|
...usage !== void 0 ? { usage } : {},
|
|
9504
9595
|
...cost !== void 0 ? { cost } : {},
|
|
9505
9596
|
...ctx.error !== void 0 ? { error: ctx.error } : {},
|
|
9506
|
-
...ctx.stoppedAtIterationLimit === true ? { stoppedAtIterationLimit: true } : {}
|
|
9597
|
+
...ctx.stoppedAtIterationLimit === true ? { stoppedAtIterationLimit: true } : {},
|
|
9598
|
+
...ctx.stoppedByDoomLoop === true ? { stoppedByDoomLoop: true } : {}
|
|
9507
9599
|
};
|
|
9508
9600
|
} finally {
|
|
9509
9601
|
if (ctxRef !== void 0 && ctxRef.memoryProviderHandle !== void 0 && inputs.memoryProvider !== void 0) {
|
|
@@ -9662,8 +9754,26 @@ async function continueOrTerminate(inputs, ctx, llmOutput) {
|
|
|
9662
9754
|
}
|
|
9663
9755
|
}
|
|
9664
9756
|
pushToolConversationSteps(ctx, llmOutput.toolCalls, toolResults);
|
|
9757
|
+
if (await inspectDoomLoop(inputs, ctx, llmOutput.toolCalls) === "stop") return "done";
|
|
9665
9758
|
return handleToolErrorContinuation(inputs, ctx, toolResults);
|
|
9666
9759
|
}
|
|
9760
|
+
async function inspectDoomLoop(inputs, ctx, toolCalls) {
|
|
9761
|
+
if (ctx.doomLoop === void 0) return "continue";
|
|
9762
|
+
const verdict = firstDoomLoopVerdict(ctx.doomLoop, toolCalls);
|
|
9763
|
+
if (verdict.kind === "hard") {
|
|
9764
|
+
ctx.stoppedByDoomLoop = true;
|
|
9765
|
+
await emitAssistantTextStep(
|
|
9766
|
+
inputs,
|
|
9767
|
+
ctx,
|
|
9768
|
+
verdict.message ?? "Stopped: repeated identical tool calls made no progress."
|
|
9769
|
+
);
|
|
9770
|
+
return "stop";
|
|
9771
|
+
}
|
|
9772
|
+
if (verdict.kind === "soft") {
|
|
9773
|
+
ctx.messages.push({ role: "user", content: [{ type: "text", text: verdict.message ?? "" }] });
|
|
9774
|
+
}
|
|
9775
|
+
return "continue";
|
|
9776
|
+
}
|
|
9667
9777
|
|
|
9668
9778
|
// src/internal/llm/fallback-client.ts
|
|
9669
9779
|
init_errors();
|
|
@@ -11065,11 +11175,16 @@ function sanitizeToolInput(input, options) {
|
|
|
11065
11175
|
// src/internal/llm/hermes-tool-extract.ts
|
|
11066
11176
|
var HERMES_BLOCK = /<function=\s*([^>\s]+)\s*>([\s\S]*?)<\/tool_call>/g;
|
|
11067
11177
|
var HERMES_PARAM = /<parameter=\s*([^>\s]+)\s*>([\s\S]*?)<\/parameter>/g;
|
|
11068
|
-
function extractHermesToolCalls(content, makeId) {
|
|
11178
|
+
function extractHermesToolCalls(content, makeId, allowedToolNames) {
|
|
11179
|
+
const isPromoted = (name) => name.length > 0 && (allowedToolNames === void 0 || allowedToolNames.has(name));
|
|
11069
11180
|
const toolCalls = [];
|
|
11181
|
+
const droppedNames = [];
|
|
11070
11182
|
for (const block of content.matchAll(HERMES_BLOCK)) {
|
|
11071
11183
|
const name = (block[1] ?? "").trim();
|
|
11072
|
-
if (name
|
|
11184
|
+
if (!isPromoted(name)) {
|
|
11185
|
+
if (name.length > 0 && allowedToolNames !== void 0) droppedNames.push(name);
|
|
11186
|
+
continue;
|
|
11187
|
+
}
|
|
11073
11188
|
toolCalls.push({
|
|
11074
11189
|
type: "tool_use",
|
|
11075
11190
|
id: makeId(),
|
|
@@ -11077,8 +11192,11 @@ function extractHermesToolCalls(content, makeId) {
|
|
|
11077
11192
|
input: parseHermesParams(block[2] ?? "")
|
|
11078
11193
|
});
|
|
11079
11194
|
}
|
|
11080
|
-
const residualText = toolCalls.length === 0 ? content : content.replace(
|
|
11081
|
-
|
|
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 };
|
|
11082
11200
|
}
|
|
11083
11201
|
function parseHermesParams(inner) {
|
|
11084
11202
|
const input = {};
|
|
@@ -11162,7 +11280,10 @@ var OpenAIClient = class {
|
|
|
11162
11280
|
}
|
|
11163
11281
|
const accumulator = new OpenAIStreamAccumulator(
|
|
11164
11282
|
this.options.extractToolCallsFromContent ?? false,
|
|
11165
|
-
providerId
|
|
11283
|
+
providerId,
|
|
11284
|
+
// R5: request-scoped allowlist — leaked recovery only promotes a block whose name is a tool the
|
|
11285
|
+
// model was actually given. Empty set (no tools) recovers nothing.
|
|
11286
|
+
new Set(request.tools?.map((tool) => tool.name) ?? [])
|
|
11166
11287
|
);
|
|
11167
11288
|
for await (const record of parseSseStream(response.body, signal)) {
|
|
11168
11289
|
if (record.data === "[DONE]") break;
|
|
@@ -11192,13 +11313,18 @@ var OpenAIStreamAccumulator = class {
|
|
|
11192
11313
|
/**
|
|
11193
11314
|
* @param extractFromContent opt-in leaked-dialect safe-parse (theokit#58). Default false.
|
|
11194
11315
|
* @param providerName provider id, used only to label the recovery log line.
|
|
11316
|
+
* @param allowedToolNames R5 request-scoped allowlist — built from `request.tools` at `stream()`;
|
|
11317
|
+
* leaked recovery in `finish()` only promotes a block whose name is in this set. `undefined`
|
|
11318
|
+
* (direct construction) recovers all (back-compat); an empty set recovers nothing.
|
|
11195
11319
|
*/
|
|
11196
|
-
constructor(extractFromContent = false, providerName = "openai") {
|
|
11320
|
+
constructor(extractFromContent = false, providerName = "openai", allowedToolNames) {
|
|
11197
11321
|
this.extractFromContent = extractFromContent;
|
|
11198
11322
|
this.providerName = providerName;
|
|
11323
|
+
this.allowedToolNames = allowedToolNames;
|
|
11199
11324
|
}
|
|
11200
11325
|
extractFromContent;
|
|
11201
11326
|
providerName;
|
|
11327
|
+
allowedToolNames;
|
|
11202
11328
|
text = "";
|
|
11203
11329
|
stopReason = "end_turn";
|
|
11204
11330
|
inputTokens;
|
|
@@ -11269,7 +11395,8 @@ var OpenAIStreamAccumulator = class {
|
|
|
11269
11395
|
if (this.extractFromContent && toolCalls.length === 0) {
|
|
11270
11396
|
const recovered = extractHermesToolCalls(
|
|
11271
11397
|
this.text,
|
|
11272
|
-
() => `hermes-${globalThis.crypto.randomUUID()}
|
|
11398
|
+
() => `hermes-${globalThis.crypto.randomUUID()}`,
|
|
11399
|
+
this.allowedToolNames
|
|
11273
11400
|
);
|
|
11274
11401
|
if (recovered.toolCalls.length > 0) {
|
|
11275
11402
|
toolCalls.push(...recovered.toolCalls);
|
|
@@ -11277,6 +11404,12 @@ var OpenAIStreamAccumulator = class {
|
|
|
11277
11404
|
stopReason = "tool_use";
|
|
11278
11405
|
process.stderr.write(
|
|
11279
11406
|
`[theokit-sdk] recovered ${recovered.toolCalls.length} leaked tool call(s) from assistant content (provider="${this.providerName}", names=${recovered.toolCalls.map((c) => c.name).join(",")})
|
|
11407
|
+
`
|
|
11408
|
+
);
|
|
11409
|
+
}
|
|
11410
|
+
if (recovered.droppedNames.length > 0) {
|
|
11411
|
+
process.stderr.write(
|
|
11412
|
+
`[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(",")})
|
|
11280
11413
|
`
|
|
11281
11414
|
);
|
|
11282
11415
|
}
|
|
@@ -12208,6 +12341,8 @@ function buildLoopInputs(options, runId, userText) {
|
|
|
12208
12341
|
// M1-2: per-send iteration ceiling (validated above). The loop reads
|
|
12209
12342
|
// inputs.maxIterations (default 8 when unset).
|
|
12210
12343
|
...maxIterations !== void 0 ? { maxIterations } : {},
|
|
12344
|
+
// Doom-loop guard config (default on; `false` disables, object tunes thresholds).
|
|
12345
|
+
...options.sendOptions.doomLoop !== void 0 ? { doomLoop: options.sendOptions.doomLoop } : {},
|
|
12211
12346
|
// D315-D317 — tool lifecycle hooks (cost tracking + audit + retry/alert)
|
|
12212
12347
|
...options.agentOptions.onToolStart !== void 0 ? { onToolStart: options.agentOptions.onToolStart } : {},
|
|
12213
12348
|
...options.agentOptions.onToolEnd !== void 0 ? { onToolEnd: options.agentOptions.onToolEnd } : {},
|
|
@@ -12340,6 +12475,7 @@ var RealLocalRun = class extends FixtureRunBase {
|
|
|
12340
12475
|
if (output.usage !== void 0) this.script.usage = output.usage;
|
|
12341
12476
|
if (output.cost !== void 0) this.script.cost = output.cost;
|
|
12342
12477
|
if (output.stoppedAtIterationLimit === true) this.script.stoppedAtIterationLimit = true;
|
|
12478
|
+
if (output.stoppedByDoomLoop === true) this.script.stoppedByDoomLoop = true;
|
|
12343
12479
|
if (output.error !== void 0 && this.script.errorDetail === void 0) {
|
|
12344
12480
|
this.script.errorDetail = {
|
|
12345
12481
|
message: output.error.message,
|