@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/a2a/index.js
CHANGED
|
@@ -2333,6 +2333,7 @@ function applyScriptMetrics(base, script) {
|
|
|
2333
2333
|
if (script.usage !== void 0) base.usage = script.usage;
|
|
2334
2334
|
if (script.cost !== void 0) base.cost = script.cost;
|
|
2335
2335
|
if (script.stoppedAtIterationLimit === true) base.stoppedAtIterationLimit = true;
|
|
2336
|
+
if (script.stoppedByDoomLoop === true) base.stoppedByDoomLoop = true;
|
|
2336
2337
|
}
|
|
2337
2338
|
var FixtureRunBase;
|
|
2338
2339
|
var init_fixture_run_base = __esm({
|
|
@@ -6809,6 +6810,98 @@ var init_budget_gate = __esm({
|
|
|
6809
6810
|
}
|
|
6810
6811
|
});
|
|
6811
6812
|
|
|
6813
|
+
// src/internal/agent-loop/doom-loop-tracker.ts
|
|
6814
|
+
function createDoomLoopTracker(option) {
|
|
6815
|
+
if (option === false) return void 0;
|
|
6816
|
+
return new DoomLoopTracker(option);
|
|
6817
|
+
}
|
|
6818
|
+
function assertValidThresholds(soft, hard) {
|
|
6819
|
+
for (const [label, value] of [
|
|
6820
|
+
["softThreshold", soft],
|
|
6821
|
+
["hardThreshold", hard]
|
|
6822
|
+
]) {
|
|
6823
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
6824
|
+
throw new ConfigurationError(
|
|
6825
|
+
`doomLoop.${label} must be a positive integer (received ${value}).`,
|
|
6826
|
+
{ code: "invalid_doom_loop_threshold" }
|
|
6827
|
+
);
|
|
6828
|
+
}
|
|
6829
|
+
}
|
|
6830
|
+
}
|
|
6831
|
+
function sortKeys(value) {
|
|
6832
|
+
if (value === null || typeof value !== "object") return value;
|
|
6833
|
+
if (Array.isArray(value)) return value.map(sortKeys);
|
|
6834
|
+
const out = {};
|
|
6835
|
+
for (const key of Object.keys(value).sort()) {
|
|
6836
|
+
out[key] = sortKeys(value[key]);
|
|
6837
|
+
}
|
|
6838
|
+
return out;
|
|
6839
|
+
}
|
|
6840
|
+
function signatureOf(call) {
|
|
6841
|
+
const { input } = call;
|
|
6842
|
+
let inputSig;
|
|
6843
|
+
if (input === null || input === void 0) inputSig = "null";
|
|
6844
|
+
else if (typeof input !== "object") inputSig = String(input);
|
|
6845
|
+
else {
|
|
6846
|
+
try {
|
|
6847
|
+
inputSig = JSON.stringify(sortKeys(input)) ?? "null";
|
|
6848
|
+
} catch {
|
|
6849
|
+
inputSig = String(input);
|
|
6850
|
+
}
|
|
6851
|
+
}
|
|
6852
|
+
return `${call.name}\0${inputSig}`;
|
|
6853
|
+
}
|
|
6854
|
+
function firstDoomLoopVerdict(tracker, calls) {
|
|
6855
|
+
let escalation = { kind: "ok" };
|
|
6856
|
+
for (const call of calls) {
|
|
6857
|
+
const v = tracker.inspect(call);
|
|
6858
|
+
if (v.kind === "hard") return v;
|
|
6859
|
+
if (v.kind === "soft" && escalation.kind === "ok") escalation = v;
|
|
6860
|
+
}
|
|
6861
|
+
return escalation;
|
|
6862
|
+
}
|
|
6863
|
+
var DEFAULT_CONFIG, DoomLoopTracker;
|
|
6864
|
+
var init_doom_loop_tracker = __esm({
|
|
6865
|
+
"src/internal/agent-loop/doom-loop-tracker.ts"() {
|
|
6866
|
+
init_errors();
|
|
6867
|
+
DEFAULT_CONFIG = { softThreshold: 3, hardThreshold: 5 };
|
|
6868
|
+
DoomLoopTracker = class {
|
|
6869
|
+
#config;
|
|
6870
|
+
#lastSignature = "";
|
|
6871
|
+
#count = 0;
|
|
6872
|
+
constructor(config) {
|
|
6873
|
+
const softThreshold = config?.softThreshold ?? DEFAULT_CONFIG.softThreshold;
|
|
6874
|
+
const hardThreshold = config?.hardThreshold ?? DEFAULT_CONFIG.hardThreshold;
|
|
6875
|
+
assertValidThresholds(softThreshold, hardThreshold);
|
|
6876
|
+
this.#config = { softThreshold, hardThreshold };
|
|
6877
|
+
}
|
|
6878
|
+
inspect(call) {
|
|
6879
|
+
const signature = signatureOf(call);
|
|
6880
|
+
this.#count = signature === this.#lastSignature ? this.#count + 1 : 1;
|
|
6881
|
+
this.#lastSignature = signature;
|
|
6882
|
+
const count = this.#count;
|
|
6883
|
+
if (count >= this.#config.hardThreshold) {
|
|
6884
|
+
return {
|
|
6885
|
+
kind: "hard",
|
|
6886
|
+
message: `Detected ${count} consecutive identical calls to \`${call.name}\`; stopping to avoid a loop.`
|
|
6887
|
+
};
|
|
6888
|
+
}
|
|
6889
|
+
if (count === this.#config.softThreshold) {
|
|
6890
|
+
return {
|
|
6891
|
+
kind: "soft",
|
|
6892
|
+
message: `Detected ${count} consecutive identical calls to \`${call.name}\`; try a different approach.`
|
|
6893
|
+
};
|
|
6894
|
+
}
|
|
6895
|
+
return { kind: "ok" };
|
|
6896
|
+
}
|
|
6897
|
+
reset() {
|
|
6898
|
+
this.#lastSignature = "";
|
|
6899
|
+
this.#count = 0;
|
|
6900
|
+
}
|
|
6901
|
+
};
|
|
6902
|
+
}
|
|
6903
|
+
});
|
|
6904
|
+
|
|
6812
6905
|
// src/internal/budget/usage-accumulator.ts
|
|
6813
6906
|
var UsageAccumulator;
|
|
6814
6907
|
var init_usage_accumulator = __esm({
|
|
@@ -6998,6 +7091,7 @@ async function initLoopContext(inputs) {
|
|
|
6998
7091
|
tools,
|
|
6999
7092
|
finalText: "",
|
|
7000
7093
|
finalStatus: "finished",
|
|
7094
|
+
doomLoop: createDoomLoopTracker(inputs.doomLoop),
|
|
7001
7095
|
usage: new UsageAccumulator(),
|
|
7002
7096
|
nudgeAttempts: 0,
|
|
7003
7097
|
stopFeedbackAttempts: 0,
|
|
@@ -7050,6 +7144,7 @@ function sanitize(name) {
|
|
|
7050
7144
|
var init_loop_context_init = __esm({
|
|
7051
7145
|
"src/internal/agent-loop/loop-context-init.ts"() {
|
|
7052
7146
|
init_usage_accumulator();
|
|
7147
|
+
init_doom_loop_tracker();
|
|
7053
7148
|
init_message_builders();
|
|
7054
7149
|
}
|
|
7055
7150
|
});
|
|
@@ -8162,6 +8257,7 @@ async function runAgentLoop(inputs) {
|
|
|
8162
8257
|
ctx.finalStatus = "error";
|
|
8163
8258
|
}
|
|
8164
8259
|
sendSpan?.setAttribute("status", ctx.finalStatus);
|
|
8260
|
+
if (ctx.stoppedByDoomLoop === true) sendSpan?.setAttribute("stoppedByDoomLoop", true);
|
|
8165
8261
|
if (inputs.telemetry?.includeContent === true && ctx.finalText.length > 0) {
|
|
8166
8262
|
sendSpan?.addEvent("response", { content: ctx.finalText });
|
|
8167
8263
|
}
|
|
@@ -8188,7 +8284,8 @@ async function runAgentLoop(inputs) {
|
|
|
8188
8284
|
...usage !== void 0 ? { usage } : {},
|
|
8189
8285
|
...cost !== void 0 ? { cost } : {},
|
|
8190
8286
|
...ctx.error !== void 0 ? { error: ctx.error } : {},
|
|
8191
|
-
...ctx.stoppedAtIterationLimit === true ? { stoppedAtIterationLimit: true } : {}
|
|
8287
|
+
...ctx.stoppedAtIterationLimit === true ? { stoppedAtIterationLimit: true } : {},
|
|
8288
|
+
...ctx.stoppedByDoomLoop === true ? { stoppedByDoomLoop: true } : {}
|
|
8192
8289
|
};
|
|
8193
8290
|
} finally {
|
|
8194
8291
|
if (ctxRef !== void 0 && ctxRef.memoryProviderHandle !== void 0 && inputs.memoryProvider !== void 0) {
|
|
@@ -8347,8 +8444,26 @@ async function continueOrTerminate(inputs, ctx, llmOutput) {
|
|
|
8347
8444
|
}
|
|
8348
8445
|
}
|
|
8349
8446
|
pushToolConversationSteps(ctx, llmOutput.toolCalls, toolResults);
|
|
8447
|
+
if (await inspectDoomLoop(inputs, ctx, llmOutput.toolCalls) === "stop") return "done";
|
|
8350
8448
|
return handleToolErrorContinuation(inputs, ctx, toolResults);
|
|
8351
8449
|
}
|
|
8450
|
+
async function inspectDoomLoop(inputs, ctx, toolCalls) {
|
|
8451
|
+
if (ctx.doomLoop === void 0) return "continue";
|
|
8452
|
+
const verdict = firstDoomLoopVerdict(ctx.doomLoop, toolCalls);
|
|
8453
|
+
if (verdict.kind === "hard") {
|
|
8454
|
+
ctx.stoppedByDoomLoop = true;
|
|
8455
|
+
await emitAssistantTextStep(
|
|
8456
|
+
inputs,
|
|
8457
|
+
ctx,
|
|
8458
|
+
verdict.message ?? "Stopped: repeated identical tool calls made no progress."
|
|
8459
|
+
);
|
|
8460
|
+
return "stop";
|
|
8461
|
+
}
|
|
8462
|
+
if (verdict.kind === "soft") {
|
|
8463
|
+
ctx.messages.push({ role: "user", content: [{ type: "text", text: verdict.message ?? "" }] });
|
|
8464
|
+
}
|
|
8465
|
+
return "continue";
|
|
8466
|
+
}
|
|
8352
8467
|
var MAX_NUDGE_ATTEMPTS, MAX_STOP_FEEDBACK_ATTEMPTS;
|
|
8353
8468
|
var init_loop = __esm({
|
|
8354
8469
|
"src/internal/agent-loop/loop.ts"() {
|
|
@@ -8356,6 +8471,7 @@ var init_loop = __esm({
|
|
|
8356
8471
|
init_safe_call();
|
|
8357
8472
|
init_validate_response();
|
|
8358
8473
|
init_budget_gate();
|
|
8474
|
+
init_doom_loop_tracker();
|
|
8359
8475
|
init_loop_context_init();
|
|
8360
8476
|
init_loop_llm_stream();
|
|
8361
8477
|
init_message_builders();
|
|
@@ -10213,11 +10329,16 @@ var init_sanitize_tool_input = __esm({
|
|
|
10213
10329
|
});
|
|
10214
10330
|
|
|
10215
10331
|
// src/internal/llm/hermes-tool-extract.ts
|
|
10216
|
-
function extractHermesToolCalls(content, makeId) {
|
|
10332
|
+
function extractHermesToolCalls(content, makeId, allowedToolNames) {
|
|
10333
|
+
const isPromoted = (name) => name.length > 0 && (allowedToolNames === void 0 || allowedToolNames.has(name));
|
|
10217
10334
|
const toolCalls = [];
|
|
10335
|
+
const droppedNames = [];
|
|
10218
10336
|
for (const block of content.matchAll(HERMES_BLOCK)) {
|
|
10219
10337
|
const name = (block[1] ?? "").trim();
|
|
10220
|
-
if (name
|
|
10338
|
+
if (!isPromoted(name)) {
|
|
10339
|
+
if (name.length > 0 && allowedToolNames !== void 0) droppedNames.push(name);
|
|
10340
|
+
continue;
|
|
10341
|
+
}
|
|
10221
10342
|
toolCalls.push({
|
|
10222
10343
|
type: "tool_use",
|
|
10223
10344
|
id: makeId(),
|
|
@@ -10225,8 +10346,11 @@ function extractHermesToolCalls(content, makeId) {
|
|
|
10225
10346
|
input: parseHermesParams(block[2] ?? "")
|
|
10226
10347
|
});
|
|
10227
10348
|
}
|
|
10228
|
-
const residualText = toolCalls.length === 0 ? content : content.replace(
|
|
10229
|
-
|
|
10349
|
+
const residualText = toolCalls.length === 0 ? content : content.replace(
|
|
10350
|
+
HERMES_BLOCK,
|
|
10351
|
+
(full, rawName) => isPromoted((rawName ?? "").trim()) ? "" : full
|
|
10352
|
+
).trim();
|
|
10353
|
+
return { toolCalls, residualText, droppedNames };
|
|
10230
10354
|
}
|
|
10231
10355
|
function parseHermesParams(inner) {
|
|
10232
10356
|
const input = {};
|
|
@@ -10431,7 +10555,10 @@ var init_openai2 = __esm({
|
|
|
10431
10555
|
}
|
|
10432
10556
|
const accumulator = new OpenAIStreamAccumulator(
|
|
10433
10557
|
this.options.extractToolCallsFromContent ?? false,
|
|
10434
|
-
providerId
|
|
10558
|
+
providerId,
|
|
10559
|
+
// R5: request-scoped allowlist — leaked recovery only promotes a block whose name is a tool the
|
|
10560
|
+
// model was actually given. Empty set (no tools) recovers nothing.
|
|
10561
|
+
new Set(request.tools?.map((tool) => tool.name) ?? [])
|
|
10435
10562
|
);
|
|
10436
10563
|
for await (const record of parseSseStream(response.body, signal)) {
|
|
10437
10564
|
if (record.data === "[DONE]") break;
|
|
@@ -10461,13 +10588,18 @@ var init_openai2 = __esm({
|
|
|
10461
10588
|
/**
|
|
10462
10589
|
* @param extractFromContent opt-in leaked-dialect safe-parse (theokit#58). Default false.
|
|
10463
10590
|
* @param providerName provider id, used only to label the recovery log line.
|
|
10591
|
+
* @param allowedToolNames R5 request-scoped allowlist — built from `request.tools` at `stream()`;
|
|
10592
|
+
* leaked recovery in `finish()` only promotes a block whose name is in this set. `undefined`
|
|
10593
|
+
* (direct construction) recovers all (back-compat); an empty set recovers nothing.
|
|
10464
10594
|
*/
|
|
10465
|
-
constructor(extractFromContent = false, providerName = "openai") {
|
|
10595
|
+
constructor(extractFromContent = false, providerName = "openai", allowedToolNames) {
|
|
10466
10596
|
this.extractFromContent = extractFromContent;
|
|
10467
10597
|
this.providerName = providerName;
|
|
10598
|
+
this.allowedToolNames = allowedToolNames;
|
|
10468
10599
|
}
|
|
10469
10600
|
extractFromContent;
|
|
10470
10601
|
providerName;
|
|
10602
|
+
allowedToolNames;
|
|
10471
10603
|
text = "";
|
|
10472
10604
|
stopReason = "end_turn";
|
|
10473
10605
|
inputTokens;
|
|
@@ -10538,7 +10670,8 @@ var init_openai2 = __esm({
|
|
|
10538
10670
|
if (this.extractFromContent && toolCalls.length === 0) {
|
|
10539
10671
|
const recovered = extractHermesToolCalls(
|
|
10540
10672
|
this.text,
|
|
10541
|
-
() => `hermes-${globalThis.crypto.randomUUID()}
|
|
10673
|
+
() => `hermes-${globalThis.crypto.randomUUID()}`,
|
|
10674
|
+
this.allowedToolNames
|
|
10542
10675
|
);
|
|
10543
10676
|
if (recovered.toolCalls.length > 0) {
|
|
10544
10677
|
toolCalls.push(...recovered.toolCalls);
|
|
@@ -10546,6 +10679,12 @@ var init_openai2 = __esm({
|
|
|
10546
10679
|
stopReason = "tool_use";
|
|
10547
10680
|
process.stderr.write(
|
|
10548
10681
|
`[theokit-sdk] recovered ${recovered.toolCalls.length} leaked tool call(s) from assistant content (provider="${this.providerName}", names=${recovered.toolCalls.map((c) => c.name).join(",")})
|
|
10682
|
+
`
|
|
10683
|
+
);
|
|
10684
|
+
}
|
|
10685
|
+
if (recovered.droppedNames.length > 0) {
|
|
10686
|
+
process.stderr.write(
|
|
10687
|
+
`[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(",")})
|
|
10549
10688
|
`
|
|
10550
10689
|
);
|
|
10551
10690
|
}
|
|
@@ -11440,6 +11579,8 @@ function buildLoopInputs(options, runId, userText) {
|
|
|
11440
11579
|
// M1-2: per-send iteration ceiling (validated above). The loop reads
|
|
11441
11580
|
// inputs.maxIterations (default 8 when unset).
|
|
11442
11581
|
...maxIterations !== void 0 ? { maxIterations } : {},
|
|
11582
|
+
// Doom-loop guard config (default on; `false` disables, object tunes thresholds).
|
|
11583
|
+
...options.sendOptions.doomLoop !== void 0 ? { doomLoop: options.sendOptions.doomLoop } : {},
|
|
11443
11584
|
// D315-D317 — tool lifecycle hooks (cost tracking + audit + retry/alert)
|
|
11444
11585
|
...options.agentOptions.onToolStart !== void 0 ? { onToolStart: options.agentOptions.onToolStart } : {},
|
|
11445
11586
|
...options.agentOptions.onToolEnd !== void 0 ? { onToolEnd: options.agentOptions.onToolEnd } : {},
|
|
@@ -11588,6 +11729,7 @@ var init_real_local_run = __esm({
|
|
|
11588
11729
|
if (output.usage !== void 0) this.script.usage = output.usage;
|
|
11589
11730
|
if (output.cost !== void 0) this.script.cost = output.cost;
|
|
11590
11731
|
if (output.stoppedAtIterationLimit === true) this.script.stoppedAtIterationLimit = true;
|
|
11732
|
+
if (output.stoppedByDoomLoop === true) this.script.stoppedByDoomLoop = true;
|
|
11591
11733
|
if (output.error !== void 0 && this.script.errorDetail === void 0) {
|
|
11592
11734
|
this.script.errorDetail = {
|
|
11593
11735
|
message: output.error.message,
|
|
@@ -14579,6 +14721,7 @@ function isEmptyRound(result) {
|
|
|
14579
14721
|
return (result.result ?? "").trim() === "";
|
|
14580
14722
|
}
|
|
14581
14723
|
function classifyRound(result, round, maxRounds, emptyStreak) {
|
|
14724
|
+
if (result.stoppedByDoomLoop === true) return "no_progress";
|
|
14582
14725
|
if (result.stoppedAtIterationLimit !== true) return "done";
|
|
14583
14726
|
if (isEmptyRound(result) && emptyStreak >= 1) return "no_progress";
|
|
14584
14727
|
if (round >= maxRounds) return "step_limit";
|