@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as CustomTool, M as ModelSelection,
|
|
1
|
+
import { C as CustomTool, M as ModelSelection, G as SDKUserMessage, J as SendOptions, b as Run, r as RunToCompletionOptions, s as RunToCompletionResult, S as SDKMessage, U as StreamToCompletionResult, a as McpServerConfig } from './run-DXy_MVwz.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Fork primitive public type contracts (T1.2, ADRs D110-D114).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as CustomTool, M as ModelSelection,
|
|
1
|
+
import { C as CustomTool, M as ModelSelection, G as SDKUserMessage, J as SendOptions, b as Run, r as RunToCompletionOptions, s as RunToCompletionResult, S as SDKMessage, U as StreamToCompletionResult, a as McpServerConfig } from './run-DXy_MVwz.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Fork primitive public type contracts (T1.2, ADRs D110-D114).
|
package/dist/cron.cjs
CHANGED
|
@@ -1458,6 +1458,7 @@ function isEmptyRound(result) {
|
|
|
1458
1458
|
return (result.result ?? "").trim() === "";
|
|
1459
1459
|
}
|
|
1460
1460
|
function classifyRound(result, round, maxRounds, emptyStreak) {
|
|
1461
|
+
if (result.stoppedByDoomLoop === true) return "no_progress";
|
|
1461
1462
|
if (result.stoppedAtIterationLimit !== true) return "done";
|
|
1462
1463
|
if (isEmptyRound(result) && emptyStreak >= 1) return "no_progress";
|
|
1463
1464
|
if (round >= maxRounds) return "step_limit";
|
|
@@ -4495,6 +4496,7 @@ function applyScriptMetrics(base, script) {
|
|
|
4495
4496
|
if (script.usage !== void 0) base.usage = script.usage;
|
|
4496
4497
|
if (script.cost !== void 0) base.cost = script.cost;
|
|
4497
4498
|
if (script.stoppedAtIterationLimit === true) base.stoppedAtIterationLimit = true;
|
|
4499
|
+
if (script.stoppedByDoomLoop === true) base.stoppedByDoomLoop = true;
|
|
4498
4500
|
}
|
|
4499
4501
|
|
|
4500
4502
|
// src/internal/runtime/cloud/cloud-run.ts
|
|
@@ -8276,6 +8278,93 @@ function evaluateBudgetGate(tracker) {
|
|
|
8276
8278
|
}
|
|
8277
8279
|
}
|
|
8278
8280
|
|
|
8281
|
+
// src/internal/agent-loop/doom-loop-tracker.ts
|
|
8282
|
+
init_errors();
|
|
8283
|
+
function createDoomLoopTracker(option) {
|
|
8284
|
+
if (option === false) return void 0;
|
|
8285
|
+
return new DoomLoopTracker(option);
|
|
8286
|
+
}
|
|
8287
|
+
var DEFAULT_CONFIG = { softThreshold: 3, hardThreshold: 5 };
|
|
8288
|
+
function assertValidThresholds(soft, hard) {
|
|
8289
|
+
for (const [label, value] of [
|
|
8290
|
+
["softThreshold", soft],
|
|
8291
|
+
["hardThreshold", hard]
|
|
8292
|
+
]) {
|
|
8293
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
8294
|
+
throw new ConfigurationError(
|
|
8295
|
+
`doomLoop.${label} must be a positive integer (received ${value}).`,
|
|
8296
|
+
{ code: "invalid_doom_loop_threshold" }
|
|
8297
|
+
);
|
|
8298
|
+
}
|
|
8299
|
+
}
|
|
8300
|
+
}
|
|
8301
|
+
function sortKeys(value) {
|
|
8302
|
+
if (value === null || typeof value !== "object") return value;
|
|
8303
|
+
if (Array.isArray(value)) return value.map(sortKeys);
|
|
8304
|
+
const out = {};
|
|
8305
|
+
for (const key of Object.keys(value).sort()) {
|
|
8306
|
+
out[key] = sortKeys(value[key]);
|
|
8307
|
+
}
|
|
8308
|
+
return out;
|
|
8309
|
+
}
|
|
8310
|
+
function signatureOf(call) {
|
|
8311
|
+
const { input } = call;
|
|
8312
|
+
let inputSig;
|
|
8313
|
+
if (input === null || input === void 0) inputSig = "null";
|
|
8314
|
+
else if (typeof input !== "object") inputSig = String(input);
|
|
8315
|
+
else {
|
|
8316
|
+
try {
|
|
8317
|
+
inputSig = JSON.stringify(sortKeys(input)) ?? "null";
|
|
8318
|
+
} catch {
|
|
8319
|
+
inputSig = String(input);
|
|
8320
|
+
}
|
|
8321
|
+
}
|
|
8322
|
+
return `${call.name}\0${inputSig}`;
|
|
8323
|
+
}
|
|
8324
|
+
var DoomLoopTracker = class {
|
|
8325
|
+
#config;
|
|
8326
|
+
#lastSignature = "";
|
|
8327
|
+
#count = 0;
|
|
8328
|
+
constructor(config) {
|
|
8329
|
+
const softThreshold = config?.softThreshold ?? DEFAULT_CONFIG.softThreshold;
|
|
8330
|
+
const hardThreshold = config?.hardThreshold ?? DEFAULT_CONFIG.hardThreshold;
|
|
8331
|
+
assertValidThresholds(softThreshold, hardThreshold);
|
|
8332
|
+
this.#config = { softThreshold, hardThreshold };
|
|
8333
|
+
}
|
|
8334
|
+
inspect(call) {
|
|
8335
|
+
const signature = signatureOf(call);
|
|
8336
|
+
this.#count = signature === this.#lastSignature ? this.#count + 1 : 1;
|
|
8337
|
+
this.#lastSignature = signature;
|
|
8338
|
+
const count = this.#count;
|
|
8339
|
+
if (count >= this.#config.hardThreshold) {
|
|
8340
|
+
return {
|
|
8341
|
+
kind: "hard",
|
|
8342
|
+
message: `Detected ${count} consecutive identical calls to \`${call.name}\`; stopping to avoid a loop.`
|
|
8343
|
+
};
|
|
8344
|
+
}
|
|
8345
|
+
if (count === this.#config.softThreshold) {
|
|
8346
|
+
return {
|
|
8347
|
+
kind: "soft",
|
|
8348
|
+
message: `Detected ${count} consecutive identical calls to \`${call.name}\`; try a different approach.`
|
|
8349
|
+
};
|
|
8350
|
+
}
|
|
8351
|
+
return { kind: "ok" };
|
|
8352
|
+
}
|
|
8353
|
+
reset() {
|
|
8354
|
+
this.#lastSignature = "";
|
|
8355
|
+
this.#count = 0;
|
|
8356
|
+
}
|
|
8357
|
+
};
|
|
8358
|
+
function firstDoomLoopVerdict(tracker, calls) {
|
|
8359
|
+
let escalation = { kind: "ok" };
|
|
8360
|
+
for (const call of calls) {
|
|
8361
|
+
const v = tracker.inspect(call);
|
|
8362
|
+
if (v.kind === "hard") return v;
|
|
8363
|
+
if (v.kind === "soft" && escalation.kind === "ok") escalation = v;
|
|
8364
|
+
}
|
|
8365
|
+
return escalation;
|
|
8366
|
+
}
|
|
8367
|
+
|
|
8279
8368
|
// src/internal/budget/usage-accumulator.ts
|
|
8280
8369
|
var UsageAccumulator = class {
|
|
8281
8370
|
input = 0;
|
|
@@ -8456,6 +8545,7 @@ async function initLoopContext(inputs) {
|
|
|
8456
8545
|
tools,
|
|
8457
8546
|
finalText: "",
|
|
8458
8547
|
finalStatus: "finished",
|
|
8548
|
+
doomLoop: createDoomLoopTracker(inputs.doomLoop),
|
|
8459
8549
|
usage: new UsageAccumulator(),
|
|
8460
8550
|
nudgeAttempts: 0,
|
|
8461
8551
|
stopFeedbackAttempts: 0,
|
|
@@ -9480,6 +9570,7 @@ async function runAgentLoop(inputs) {
|
|
|
9480
9570
|
ctx.finalStatus = "error";
|
|
9481
9571
|
}
|
|
9482
9572
|
sendSpan?.setAttribute("status", ctx.finalStatus);
|
|
9573
|
+
if (ctx.stoppedByDoomLoop === true) sendSpan?.setAttribute("stoppedByDoomLoop", true);
|
|
9483
9574
|
if (inputs.telemetry?.includeContent === true && ctx.finalText.length > 0) {
|
|
9484
9575
|
sendSpan?.addEvent("response", { content: ctx.finalText });
|
|
9485
9576
|
}
|
|
@@ -9506,7 +9597,8 @@ async function runAgentLoop(inputs) {
|
|
|
9506
9597
|
...usage !== void 0 ? { usage } : {},
|
|
9507
9598
|
...cost !== void 0 ? { cost } : {},
|
|
9508
9599
|
...ctx.error !== void 0 ? { error: ctx.error } : {},
|
|
9509
|
-
...ctx.stoppedAtIterationLimit === true ? { stoppedAtIterationLimit: true } : {}
|
|
9600
|
+
...ctx.stoppedAtIterationLimit === true ? { stoppedAtIterationLimit: true } : {},
|
|
9601
|
+
...ctx.stoppedByDoomLoop === true ? { stoppedByDoomLoop: true } : {}
|
|
9510
9602
|
};
|
|
9511
9603
|
} finally {
|
|
9512
9604
|
if (ctxRef !== void 0 && ctxRef.memoryProviderHandle !== void 0 && inputs.memoryProvider !== void 0) {
|
|
@@ -9665,8 +9757,26 @@ async function continueOrTerminate(inputs, ctx, llmOutput) {
|
|
|
9665
9757
|
}
|
|
9666
9758
|
}
|
|
9667
9759
|
pushToolConversationSteps(ctx, llmOutput.toolCalls, toolResults);
|
|
9760
|
+
if (await inspectDoomLoop(inputs, ctx, llmOutput.toolCalls) === "stop") return "done";
|
|
9668
9761
|
return handleToolErrorContinuation(inputs, ctx, toolResults);
|
|
9669
9762
|
}
|
|
9763
|
+
async function inspectDoomLoop(inputs, ctx, toolCalls) {
|
|
9764
|
+
if (ctx.doomLoop === void 0) return "continue";
|
|
9765
|
+
const verdict = firstDoomLoopVerdict(ctx.doomLoop, toolCalls);
|
|
9766
|
+
if (verdict.kind === "hard") {
|
|
9767
|
+
ctx.stoppedByDoomLoop = true;
|
|
9768
|
+
await emitAssistantTextStep(
|
|
9769
|
+
inputs,
|
|
9770
|
+
ctx,
|
|
9771
|
+
verdict.message ?? "Stopped: repeated identical tool calls made no progress."
|
|
9772
|
+
);
|
|
9773
|
+
return "stop";
|
|
9774
|
+
}
|
|
9775
|
+
if (verdict.kind === "soft") {
|
|
9776
|
+
ctx.messages.push({ role: "user", content: [{ type: "text", text: verdict.message ?? "" }] });
|
|
9777
|
+
}
|
|
9778
|
+
return "continue";
|
|
9779
|
+
}
|
|
9670
9780
|
|
|
9671
9781
|
// src/internal/llm/fallback-client.ts
|
|
9672
9782
|
init_errors();
|
|
@@ -11068,11 +11178,16 @@ function sanitizeToolInput(input, options) {
|
|
|
11068
11178
|
// src/internal/llm/hermes-tool-extract.ts
|
|
11069
11179
|
var HERMES_BLOCK = /<function=\s*([^>\s]+)\s*>([\s\S]*?)<\/tool_call>/g;
|
|
11070
11180
|
var HERMES_PARAM = /<parameter=\s*([^>\s]+)\s*>([\s\S]*?)<\/parameter>/g;
|
|
11071
|
-
function extractHermesToolCalls(content, makeId) {
|
|
11181
|
+
function extractHermesToolCalls(content, makeId, allowedToolNames) {
|
|
11182
|
+
const isPromoted = (name) => name.length > 0 && (allowedToolNames === void 0 || allowedToolNames.has(name));
|
|
11072
11183
|
const toolCalls = [];
|
|
11184
|
+
const droppedNames = [];
|
|
11073
11185
|
for (const block of content.matchAll(HERMES_BLOCK)) {
|
|
11074
11186
|
const name = (block[1] ?? "").trim();
|
|
11075
|
-
if (name
|
|
11187
|
+
if (!isPromoted(name)) {
|
|
11188
|
+
if (name.length > 0 && allowedToolNames !== void 0) droppedNames.push(name);
|
|
11189
|
+
continue;
|
|
11190
|
+
}
|
|
11076
11191
|
toolCalls.push({
|
|
11077
11192
|
type: "tool_use",
|
|
11078
11193
|
id: makeId(),
|
|
@@ -11080,8 +11195,11 @@ function extractHermesToolCalls(content, makeId) {
|
|
|
11080
11195
|
input: parseHermesParams(block[2] ?? "")
|
|
11081
11196
|
});
|
|
11082
11197
|
}
|
|
11083
|
-
const residualText = toolCalls.length === 0 ? content : content.replace(
|
|
11084
|
-
|
|
11198
|
+
const residualText = toolCalls.length === 0 ? content : content.replace(
|
|
11199
|
+
HERMES_BLOCK,
|
|
11200
|
+
(full, rawName) => isPromoted((rawName ?? "").trim()) ? "" : full
|
|
11201
|
+
).trim();
|
|
11202
|
+
return { toolCalls, residualText, droppedNames };
|
|
11085
11203
|
}
|
|
11086
11204
|
function parseHermesParams(inner) {
|
|
11087
11205
|
const input = {};
|
|
@@ -11165,7 +11283,10 @@ var OpenAIClient = class {
|
|
|
11165
11283
|
}
|
|
11166
11284
|
const accumulator = new OpenAIStreamAccumulator(
|
|
11167
11285
|
this.options.extractToolCallsFromContent ?? false,
|
|
11168
|
-
providerId
|
|
11286
|
+
providerId,
|
|
11287
|
+
// R5: request-scoped allowlist — leaked recovery only promotes a block whose name is a tool the
|
|
11288
|
+
// model was actually given. Empty set (no tools) recovers nothing.
|
|
11289
|
+
new Set(request.tools?.map((tool) => tool.name) ?? [])
|
|
11169
11290
|
);
|
|
11170
11291
|
for await (const record of parseSseStream(response.body, signal)) {
|
|
11171
11292
|
if (record.data === "[DONE]") break;
|
|
@@ -11195,13 +11316,18 @@ var OpenAIStreamAccumulator = class {
|
|
|
11195
11316
|
/**
|
|
11196
11317
|
* @param extractFromContent opt-in leaked-dialect safe-parse (theokit#58). Default false.
|
|
11197
11318
|
* @param providerName provider id, used only to label the recovery log line.
|
|
11319
|
+
* @param allowedToolNames R5 request-scoped allowlist — built from `request.tools` at `stream()`;
|
|
11320
|
+
* leaked recovery in `finish()` only promotes a block whose name is in this set. `undefined`
|
|
11321
|
+
* (direct construction) recovers all (back-compat); an empty set recovers nothing.
|
|
11198
11322
|
*/
|
|
11199
|
-
constructor(extractFromContent = false, providerName = "openai") {
|
|
11323
|
+
constructor(extractFromContent = false, providerName = "openai", allowedToolNames) {
|
|
11200
11324
|
this.extractFromContent = extractFromContent;
|
|
11201
11325
|
this.providerName = providerName;
|
|
11326
|
+
this.allowedToolNames = allowedToolNames;
|
|
11202
11327
|
}
|
|
11203
11328
|
extractFromContent;
|
|
11204
11329
|
providerName;
|
|
11330
|
+
allowedToolNames;
|
|
11205
11331
|
text = "";
|
|
11206
11332
|
stopReason = "end_turn";
|
|
11207
11333
|
inputTokens;
|
|
@@ -11272,7 +11398,8 @@ var OpenAIStreamAccumulator = class {
|
|
|
11272
11398
|
if (this.extractFromContent && toolCalls.length === 0) {
|
|
11273
11399
|
const recovered = extractHermesToolCalls(
|
|
11274
11400
|
this.text,
|
|
11275
|
-
() => `hermes-${globalThis.crypto.randomUUID()}
|
|
11401
|
+
() => `hermes-${globalThis.crypto.randomUUID()}`,
|
|
11402
|
+
this.allowedToolNames
|
|
11276
11403
|
);
|
|
11277
11404
|
if (recovered.toolCalls.length > 0) {
|
|
11278
11405
|
toolCalls.push(...recovered.toolCalls);
|
|
@@ -11280,6 +11407,12 @@ var OpenAIStreamAccumulator = class {
|
|
|
11280
11407
|
stopReason = "tool_use";
|
|
11281
11408
|
process.stderr.write(
|
|
11282
11409
|
`[theokit-sdk] recovered ${recovered.toolCalls.length} leaked tool call(s) from assistant content (provider="${this.providerName}", names=${recovered.toolCalls.map((c) => c.name).join(",")})
|
|
11410
|
+
`
|
|
11411
|
+
);
|
|
11412
|
+
}
|
|
11413
|
+
if (recovered.droppedNames.length > 0) {
|
|
11414
|
+
process.stderr.write(
|
|
11415
|
+
`[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(",")})
|
|
11283
11416
|
`
|
|
11284
11417
|
);
|
|
11285
11418
|
}
|
|
@@ -12211,6 +12344,8 @@ function buildLoopInputs(options, runId, userText) {
|
|
|
12211
12344
|
// M1-2: per-send iteration ceiling (validated above). The loop reads
|
|
12212
12345
|
// inputs.maxIterations (default 8 when unset).
|
|
12213
12346
|
...maxIterations !== void 0 ? { maxIterations } : {},
|
|
12347
|
+
// Doom-loop guard config (default on; `false` disables, object tunes thresholds).
|
|
12348
|
+
...options.sendOptions.doomLoop !== void 0 ? { doomLoop: options.sendOptions.doomLoop } : {},
|
|
12214
12349
|
// D315-D317 — tool lifecycle hooks (cost tracking + audit + retry/alert)
|
|
12215
12350
|
...options.agentOptions.onToolStart !== void 0 ? { onToolStart: options.agentOptions.onToolStart } : {},
|
|
12216
12351
|
...options.agentOptions.onToolEnd !== void 0 ? { onToolEnd: options.agentOptions.onToolEnd } : {},
|
|
@@ -12343,6 +12478,7 @@ var RealLocalRun = class extends FixtureRunBase {
|
|
|
12343
12478
|
if (output.usage !== void 0) this.script.usage = output.usage;
|
|
12344
12479
|
if (output.cost !== void 0) this.script.cost = output.cost;
|
|
12345
12480
|
if (output.stoppedAtIterationLimit === true) this.script.stoppedAtIterationLimit = true;
|
|
12481
|
+
if (output.stoppedByDoomLoop === true) this.script.stoppedByDoomLoop = true;
|
|
12346
12482
|
if (output.error !== void 0 && this.script.errorDetail === void 0) {
|
|
12347
12483
|
this.script.errorDetail = {
|
|
12348
12484
|
message: output.error.message,
|