@theokit/sdk 2.15.2 → 2.18.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 +90 -0
- package/dist/a2a/index.cjs +875 -198
- package/dist/a2a/index.cjs.map +1 -1
- package/dist/a2a/index.js +876 -199
- package/dist/a2a/index.js.map +1 -1
- package/dist/{cron-BxLSz1UH.d.cts → cron-Bbg0mBOv.d.ts} +33 -3
- package/dist/{cron-DcaoP7aW.d.ts → cron-ZLSKbDbB.d.cts} +33 -3
- package/dist/cron.cjs +840 -187
- 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 +840 -187
- package/dist/cron.js.map +1 -1
- package/dist/define-tool.d.ts +9 -2
- package/dist/{errors-Bart0ptP.d.cts → errors-1tVcX3Fq.d.cts} +1 -1
- package/dist/{errors-DJuuubJK.d.ts → errors-qyVYfk9H.d.ts} +1 -1
- package/dist/errors.d.cts +2 -2
- package/dist/eval.cjs +846 -189
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +846 -189
- package/dist/eval.js.map +1 -1
- package/dist/event-bus.d.ts +3 -0
- package/dist/index.cjs +977 -215
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -27
- package/dist/index.d.ts +122 -27
- package/dist/index.js +977 -217
- package/dist/index.js.map +1 -1
- package/dist/internal/agent-loop/tool-dispatch.d.ts +3 -1
- package/dist/internal/agent-loop/tool-result-guard.d.ts +24 -0
- package/dist/internal/agent-loop/tool-timeout.d.ts +23 -0
- package/dist/internal/llm/openai.d.ts +4 -0
- package/dist/internal/llm/sse.d.ts +13 -1
- package/dist/internal/mcp/client.d.ts +1 -1
- package/dist/internal/memory/active-memory.d.ts +1 -1
- package/dist/internal/persistence/conversation-storage-fs.d.cts +7 -1
- package/dist/internal/persistence/conversation-storage-fs.d.ts +7 -1
- package/dist/internal/persistence/conversation-storage-memory.d.cts +7 -1
- package/dist/internal/persistence/conversation-storage-memory.d.ts +7 -1
- package/dist/internal/persistence/pagination.d.cts +8 -0
- package/dist/internal/persistence/pagination.d.ts +8 -0
- package/dist/internal/plugins/index.cjs +135 -0
- package/dist/internal/plugins/index.cjs.map +1 -1
- package/dist/internal/plugins/index.js +135 -0
- package/dist/internal/plugins/index.js.map +1 -1
- package/dist/internal/plugins/manager.d.cts +21 -1
- package/dist/internal/plugins/manager.d.ts +21 -1
- package/dist/internal/plugins/types.d.cts +40 -0
- package/dist/internal/plugins/types.d.ts +40 -0
- package/dist/internal/{memory → resilience}/circuit-breaker.d.ts +5 -1
- package/dist/internal/runtime/hooks/hooks-frontmatter.d.ts +1 -1
- package/dist/internal/runtime/lifecycle/env-policy.d.ts +30 -0
- package/dist/internal/runtime/session/agent-session-store.d.ts +1 -0
- package/dist/internal/telemetry/span-names.d.ts +7 -1
- package/dist/job-queue.d.ts +29 -7
- package/dist/permission-engine.d.ts +32 -7
- package/dist/{run-DXy_MVwz.d.cts → run-pE-34AAo.d.cts} +64 -3
- package/dist/{run-DXy_MVwz.d.ts → run-pE-34AAo.d.ts} +64 -3
- package/dist/sandbox/index.cjs +53 -2
- package/dist/sandbox/index.cjs.map +1 -1
- package/dist/sandbox/index.js +53 -2
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/local-sandbox.d.cts +11 -3
- package/dist/sandbox/local-sandbox.d.ts +11 -3
- package/dist/sandbox/types.d.cts +7 -0
- package/dist/sandbox/types.d.ts +7 -0
- package/dist/types/agent-prims.d.ts +6 -2
- package/dist/types/conversation-storage.d.ts +32 -2
- package/dist/types/mcp.d.ts +20 -0
- package/dist/types/run.d.ts +17 -0
- package/dist/workflow.cjs +6 -3
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.js +6 -3
- package/dist/workflow.js.map +1 -1
- package/package.json +14 -14
package/dist/cron.cjs
CHANGED
|
@@ -889,6 +889,16 @@ var init_credential_pool_types = __esm({
|
|
|
889
889
|
});
|
|
890
890
|
|
|
891
891
|
// src/internal/llm/retry.ts
|
|
892
|
+
function computeBackoffMs(opts) {
|
|
893
|
+
const base = opts.baseMs ?? DEFAULT_BASE_MS;
|
|
894
|
+
const cap = opts.capMs ?? DEFAULT_CAP_MS;
|
|
895
|
+
const rng = opts.rng ?? Math.random;
|
|
896
|
+
if (opts.retryAfterMs !== void 0 && opts.retryAfterMs >= 0) {
|
|
897
|
+
return Math.max(base, Math.min(cap, opts.retryAfterMs));
|
|
898
|
+
}
|
|
899
|
+
const ceiling = Math.min(cap, base * 2 ** opts.attempt);
|
|
900
|
+
return Math.floor(rng() * ceiling);
|
|
901
|
+
}
|
|
892
902
|
function sleepWithAbort(ms, signal) {
|
|
893
903
|
if (ms <= 0 || signal.aborted) return Promise.resolve();
|
|
894
904
|
return new Promise((resolve3) => {
|
|
@@ -903,8 +913,11 @@ function sleepWithAbort(ms, signal) {
|
|
|
903
913
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
904
914
|
});
|
|
905
915
|
}
|
|
916
|
+
var DEFAULT_BASE_MS, DEFAULT_CAP_MS;
|
|
906
917
|
var init_retry = __esm({
|
|
907
918
|
"src/internal/llm/retry.ts"() {
|
|
919
|
+
DEFAULT_BASE_MS = 500;
|
|
920
|
+
DEFAULT_CAP_MS = 32e3;
|
|
908
921
|
}
|
|
909
922
|
});
|
|
910
923
|
|
|
@@ -4569,13 +4582,15 @@ function buildGitInfo() {
|
|
|
4569
4582
|
init_errors();
|
|
4570
4583
|
|
|
4571
4584
|
// src/internal/llm/sse.ts
|
|
4572
|
-
|
|
4585
|
+
init_errors();
|
|
4586
|
+
var DEFAULT_SSE_IDLE_MS = 6e4;
|
|
4587
|
+
async function* parseSseStream(body, signal, idleTimeoutMs = DEFAULT_SSE_IDLE_MS) {
|
|
4573
4588
|
if (body === null) return;
|
|
4574
4589
|
const reader = body.getReader();
|
|
4575
4590
|
const decoder = new TextDecoder("utf-8");
|
|
4576
4591
|
const state3 = { buffer: "", event: "message", data: "" };
|
|
4577
4592
|
try {
|
|
4578
|
-
for await (const chunk of readChunks(reader, signal)) {
|
|
4593
|
+
for await (const chunk of readChunks(reader, signal, idleTimeoutMs)) {
|
|
4579
4594
|
state3.buffer += decoder.decode(chunk, { stream: true });
|
|
4580
4595
|
for (const record of drainCompleteRecords(state3)) yield record;
|
|
4581
4596
|
}
|
|
@@ -4585,14 +4600,38 @@ async function* parseSseStream(body, signal) {
|
|
|
4585
4600
|
releaseReader(reader);
|
|
4586
4601
|
}
|
|
4587
4602
|
}
|
|
4588
|
-
async function* readChunks(reader, signal) {
|
|
4603
|
+
async function* readChunks(reader, signal, idleTimeoutMs) {
|
|
4589
4604
|
while (true) {
|
|
4590
4605
|
if (signal.aborted) return;
|
|
4591
|
-
const { value, done } = await reader
|
|
4606
|
+
const { value, done } = await readWithIdleTimeout(reader, idleTimeoutMs);
|
|
4592
4607
|
if (done) return;
|
|
4593
4608
|
if (value !== void 0) yield value;
|
|
4594
4609
|
}
|
|
4595
4610
|
}
|
|
4611
|
+
function readWithIdleTimeout(reader, idleTimeoutMs) {
|
|
4612
|
+
if (idleTimeoutMs <= 0) return reader.read();
|
|
4613
|
+
return new Promise(
|
|
4614
|
+
(resolve3, reject) => {
|
|
4615
|
+
const timer = setTimeout(() => {
|
|
4616
|
+
reject(
|
|
4617
|
+
new NetworkError(`SSE stream idle for ${idleTimeoutMs}ms \u2014 upstream stalled`, {
|
|
4618
|
+
code: "stream_idle_timeout"
|
|
4619
|
+
})
|
|
4620
|
+
);
|
|
4621
|
+
}, idleTimeoutMs);
|
|
4622
|
+
reader.read().then(
|
|
4623
|
+
(result) => {
|
|
4624
|
+
clearTimeout(timer);
|
|
4625
|
+
resolve3(result);
|
|
4626
|
+
},
|
|
4627
|
+
(err) => {
|
|
4628
|
+
clearTimeout(timer);
|
|
4629
|
+
reject(err);
|
|
4630
|
+
}
|
|
4631
|
+
);
|
|
4632
|
+
}
|
|
4633
|
+
);
|
|
4634
|
+
}
|
|
4596
4635
|
async function cancelReaderQuietly(reader) {
|
|
4597
4636
|
try {
|
|
4598
4637
|
await reader.cancel();
|
|
@@ -5286,6 +5325,9 @@ var PersonalityStore = class {
|
|
|
5286
5325
|
}
|
|
5287
5326
|
};
|
|
5288
5327
|
|
|
5328
|
+
// src/internal/plugins/manager.ts
|
|
5329
|
+
init_errors();
|
|
5330
|
+
|
|
5289
5331
|
// src/internal/plugins/context.ts
|
|
5290
5332
|
function createPluginContext() {
|
|
5291
5333
|
const registrations = {
|
|
@@ -5347,6 +5389,9 @@ var PluginManager = class {
|
|
|
5347
5389
|
memoryProviders: []
|
|
5348
5390
|
};
|
|
5349
5391
|
#initialized = false;
|
|
5392
|
+
// #68 — registrations of plugins added post-init via `register()`, keyed by
|
|
5393
|
+
// plugin name so a re-register REPLACES (not appends) the prior hooks.
|
|
5394
|
+
#byName = /* @__PURE__ */ new Map();
|
|
5350
5395
|
async initialize(plugins) {
|
|
5351
5396
|
if (this.#initialized) {
|
|
5352
5397
|
throw new Error("PluginManager.initialize called twice \u2014 register only once per process");
|
|
@@ -5364,6 +5409,36 @@ var PluginManager = class {
|
|
|
5364
5409
|
await this.#dispatchPlugin(plugin);
|
|
5365
5410
|
}
|
|
5366
5411
|
}
|
|
5412
|
+
/**
|
|
5413
|
+
* #68 — register a single `general` plugin AFTER `initialize()` has run.
|
|
5414
|
+
*
|
|
5415
|
+
* The bulk `initialize()` is single-shot (one call per process); late
|
|
5416
|
+
* registration is a distinct, named operation used by adapters that install
|
|
5417
|
+
* a plugin per-session/per-request (e.g. the ACP permission veto, which is
|
|
5418
|
+
* installed once the permission mode + connection are known — after the
|
|
5419
|
+
* agent's own plugins were already initialized).
|
|
5420
|
+
*
|
|
5421
|
+
* Idempotent by plugin NAME: re-registering a plugin with the same name
|
|
5422
|
+
* REPLACES its prior hooks/tools instead of appending duplicates (the ACP
|
|
5423
|
+
* permission plugin is re-installed on every prompt).
|
|
5424
|
+
*
|
|
5425
|
+
* Only `general` plugins may be registered late — model-provider / memory
|
|
5426
|
+
* plugins are resolved during the bulk init and cannot be added afterwards.
|
|
5427
|
+
*/
|
|
5428
|
+
async register(plugin) {
|
|
5429
|
+
if (plugin.kind !== "general") {
|
|
5430
|
+
throw new ConfigurationError(
|
|
5431
|
+
`late register supports general plugins only (got "${plugin.kind}" for "${plugin.name}")`,
|
|
5432
|
+
{ code: "plugin_late_register_kind" }
|
|
5433
|
+
);
|
|
5434
|
+
}
|
|
5435
|
+
const prior = this.#byName.get(plugin.name);
|
|
5436
|
+
if (prior !== void 0) this.#unmerge(prior);
|
|
5437
|
+
const { ctx, registrations } = createPluginContext();
|
|
5438
|
+
await plugin.register(ctx);
|
|
5439
|
+
this.#byName.set(plugin.name, registrations);
|
|
5440
|
+
this.#merge(registrations);
|
|
5441
|
+
}
|
|
5367
5442
|
get aggregated() {
|
|
5368
5443
|
return this.#aggregated;
|
|
5369
5444
|
}
|
|
@@ -5444,6 +5519,64 @@ var PluginManager = class {
|
|
|
5444
5519
|
}
|
|
5445
5520
|
}
|
|
5446
5521
|
}
|
|
5522
|
+
// #65 — the previously-dead hooks, now wired. Fire-and-forget hooks run
|
|
5523
|
+
// in order (per-handler errors logged, never thrown); transform hooks fold
|
|
5524
|
+
// over the payload (a handler returning a value replaces it).
|
|
5525
|
+
/** @internal */
|
|
5526
|
+
async #runFireAndForget(name, ctx) {
|
|
5527
|
+
for (const h of this.#aggregated.hooks.get(name) ?? []) {
|
|
5528
|
+
try {
|
|
5529
|
+
await h(ctx);
|
|
5530
|
+
} catch (err) {
|
|
5531
|
+
process.stderr.write(
|
|
5532
|
+
`[theokit-sdk] ${name} hook failed: ${err instanceof Error ? err.message : String(err)}
|
|
5533
|
+
`
|
|
5534
|
+
);
|
|
5535
|
+
}
|
|
5536
|
+
}
|
|
5537
|
+
}
|
|
5538
|
+
/** @internal — fold: each handler may return a replacement payload; a throw keeps the prior value. */
|
|
5539
|
+
async #runTransform(name, payload, ctx) {
|
|
5540
|
+
let current = payload;
|
|
5541
|
+
for (const h of this.#aggregated.hooks.get(name) ?? []) {
|
|
5542
|
+
try {
|
|
5543
|
+
const out = await h(current, ctx);
|
|
5544
|
+
if (out !== void 0) current = out;
|
|
5545
|
+
} catch (err) {
|
|
5546
|
+
process.stderr.write(
|
|
5547
|
+
`[theokit-sdk] ${name} hook failed: ${err instanceof Error ? err.message : String(err)}
|
|
5548
|
+
`
|
|
5549
|
+
);
|
|
5550
|
+
}
|
|
5551
|
+
}
|
|
5552
|
+
return current;
|
|
5553
|
+
}
|
|
5554
|
+
/** #65 — fired after a tool call completes. @internal */
|
|
5555
|
+
runPostToolCallHooks(ctx) {
|
|
5556
|
+
return this.#runFireAndForget("post_tool_call", ctx);
|
|
5557
|
+
}
|
|
5558
|
+
/** #65 — fired before / after each LLM turn. @internal */
|
|
5559
|
+
runPreLlmCallHooks(ctx) {
|
|
5560
|
+
return this.#runFireAndForget("pre_llm_call", ctx);
|
|
5561
|
+
}
|
|
5562
|
+
runPostLlmCallHooks(ctx) {
|
|
5563
|
+
return this.#runFireAndForget("post_llm_call", ctx);
|
|
5564
|
+
}
|
|
5565
|
+
/** #65 — fired at run start / end. @internal */
|
|
5566
|
+
runOnSessionStartHooks(ctx) {
|
|
5567
|
+
return this.#runFireAndForget("on_session_start", ctx);
|
|
5568
|
+
}
|
|
5569
|
+
runOnSessionEndHooks(ctx) {
|
|
5570
|
+
return this.#runFireAndForget("on_session_end", ctx);
|
|
5571
|
+
}
|
|
5572
|
+
/** #65/#57 — transform tool results before they reach the LLM (the #57 seam). @internal */
|
|
5573
|
+
runTransformToolResultHooks(results, ctx) {
|
|
5574
|
+
return this.#runTransform("transform_tool_result", results, ctx);
|
|
5575
|
+
}
|
|
5576
|
+
/** #65 — transform the LLM output text before it is consumed. @internal */
|
|
5577
|
+
runTransformLlmOutputHooks(output, ctx) {
|
|
5578
|
+
return this.#runTransform("transform_llm_output", output, ctx);
|
|
5579
|
+
}
|
|
5447
5580
|
async #dispatchPlugin(plugin) {
|
|
5448
5581
|
if (plugin.kind === "general") {
|
|
5449
5582
|
const { ctx, registrations } = createPluginContext();
|
|
@@ -5471,7 +5604,29 @@ var PluginManager = class {
|
|
|
5471
5604
|
}
|
|
5472
5605
|
this.#aggregated.injected.push(...r.injected);
|
|
5473
5606
|
}
|
|
5607
|
+
/**
|
|
5608
|
+
* #68 — inverse of #merge: remove a prior registration's contributions from
|
|
5609
|
+
* the aggregated view by object identity. Used by `register()` to replace a
|
|
5610
|
+
* same-named plugin's hooks/tools instead of accumulating duplicates.
|
|
5611
|
+
*/
|
|
5612
|
+
#unmerge(r) {
|
|
5613
|
+
removeAll(this.#aggregated.tools, r.tools);
|
|
5614
|
+
removeAll(this.#aggregated.commands, r.commands);
|
|
5615
|
+
removeAll(this.#aggregated.injected, r.injected);
|
|
5616
|
+
for (const [hook, handlers] of r.hooks.entries()) {
|
|
5617
|
+
const existing = this.#aggregated.hooks.get(hook);
|
|
5618
|
+
if (existing === void 0) continue;
|
|
5619
|
+
removeAll(existing, handlers);
|
|
5620
|
+
if (existing.length === 0) this.#aggregated.hooks.delete(hook);
|
|
5621
|
+
}
|
|
5622
|
+
}
|
|
5474
5623
|
};
|
|
5624
|
+
function removeAll(arr, toRemove) {
|
|
5625
|
+
for (const item of toRemove) {
|
|
5626
|
+
const idx = arr.indexOf(item);
|
|
5627
|
+
if (idx !== -1) arr.splice(idx, 1);
|
|
5628
|
+
}
|
|
5629
|
+
}
|
|
5475
5630
|
|
|
5476
5631
|
// src/internal/telemetry/span-names.ts
|
|
5477
5632
|
var SPAN_NAMES = {
|
|
@@ -5479,7 +5634,12 @@ var SPAN_NAMES = {
|
|
|
5479
5634
|
AGENT_SEND: "agent.send",
|
|
5480
5635
|
MEMORY_RECALL: "memory.recall"};
|
|
5481
5636
|
var HISTOGRAM_NAMES = {
|
|
5482
|
-
MEMORY_RECALL_DURATION_MS: "theokit_memory_recall_duration_ms"
|
|
5637
|
+
MEMORY_RECALL_DURATION_MS: "theokit_memory_recall_duration_ms",
|
|
5638
|
+
TOOL_CALL_DURATION_MS: "theokit_tool_call_duration_ms",
|
|
5639
|
+
LLM_CALL_DURATION_MS: "theokit_llm_call_duration_ms",
|
|
5640
|
+
LLM_TOKENS: "theokit_llm_tokens",
|
|
5641
|
+
/** M3 #66 — count of finishes where the provider omitted usage (silent undercount). */
|
|
5642
|
+
LLM_USAGE_MISSING: "theokit_llm_usage_missing"
|
|
5483
5643
|
};
|
|
5484
5644
|
function safeRequire(moduleName) {
|
|
5485
5645
|
try {
|
|
@@ -5814,7 +5974,25 @@ function createTelemetry(settings) {
|
|
|
5814
5974
|
enabled: true,
|
|
5815
5975
|
includeContent: settings.includeContent === true,
|
|
5816
5976
|
startSpan: startNewSpan,
|
|
5817
|
-
|
|
5977
|
+
// M3 #64 — actually nest the child under its parent instead of discarding it.
|
|
5978
|
+
// The parent's SpanContext is set on a fresh OTel context so the child links
|
|
5979
|
+
// to it (traceId + parentSpanId), reconstructing the causal trace tree. Falls
|
|
5980
|
+
// back to a root span when the parent has no valid span id (telemetry off /
|
|
5981
|
+
// NOOP), preserving the pre-M3 behavior for parentless callers.
|
|
5982
|
+
startChildSpan: (parent, name, attrs) => {
|
|
5983
|
+
const redactedAttrs = attrs === void 0 ? void 0 : redactAttrs(attrs);
|
|
5984
|
+
const opts = redactedAttrs ? { attributes: redactedAttrs } : void 0;
|
|
5985
|
+
const pctx = safe(() => parent?.spanContext(), void 0);
|
|
5986
|
+
const span = safe(() => {
|
|
5987
|
+
if (pctx !== void 0 && pctx.spanId !== "0".repeat(16)) {
|
|
5988
|
+
const childCtx = otel.trace.setSpanContext(otel.context.active(), pctx);
|
|
5989
|
+
return tracer.startSpan(name, opts, childCtx);
|
|
5990
|
+
}
|
|
5991
|
+
return tracer.startSpan(name, opts);
|
|
5992
|
+
}, NOOP_SPAN);
|
|
5993
|
+
if (span !== NOOP_SPAN) openSpans.add(span);
|
|
5994
|
+
return wrapSpan(span, openSpans);
|
|
5995
|
+
},
|
|
5818
5996
|
recordHistogram,
|
|
5819
5997
|
endAll: () => {
|
|
5820
5998
|
for (const span of openSpans) safe(() => span.end(), void 0);
|
|
@@ -5850,12 +6028,62 @@ function redactAttrs(attrs) {
|
|
|
5850
6028
|
}
|
|
5851
6029
|
return out;
|
|
5852
6030
|
}
|
|
6031
|
+
|
|
6032
|
+
// src/internal/runtime/lifecycle/env-policy.ts
|
|
6033
|
+
var SECRET_PATTERNS = [
|
|
6034
|
+
/KEY/i,
|
|
6035
|
+
/SECRET/i,
|
|
6036
|
+
/TOKEN/i,
|
|
6037
|
+
/PASSWORD/i,
|
|
6038
|
+
/PASSWD/i,
|
|
6039
|
+
/PASSPHRASE/i,
|
|
6040
|
+
/[_-]PWD/i,
|
|
6041
|
+
/CREDENTIAL/i,
|
|
6042
|
+
/PRIVATE/i,
|
|
6043
|
+
/_AUTH/i
|
|
6044
|
+
];
|
|
6045
|
+
var CORE_VARS = [
|
|
6046
|
+
"PATH",
|
|
6047
|
+
"HOME",
|
|
6048
|
+
"SHELL",
|
|
6049
|
+
"LANG",
|
|
6050
|
+
"LC_ALL",
|
|
6051
|
+
"LC_CTYPE",
|
|
6052
|
+
"TMPDIR",
|
|
6053
|
+
"TMP",
|
|
6054
|
+
"TEMP",
|
|
6055
|
+
"USER",
|
|
6056
|
+
"LOGNAME"
|
|
6057
|
+
];
|
|
6058
|
+
function isSecretName(name) {
|
|
6059
|
+
return SECRET_PATTERNS.some((re) => re.test(name));
|
|
6060
|
+
}
|
|
6061
|
+
function inheritsUnderPolicy(name, policy) {
|
|
6062
|
+
if (policy === "all") return true;
|
|
6063
|
+
if (policy === "core") return CORE_VARS.includes(name);
|
|
6064
|
+
return !isSecretName(name);
|
|
6065
|
+
}
|
|
6066
|
+
function resolveChildEnv(options = {}) {
|
|
6067
|
+
const parent = options.parent ?? process.env;
|
|
6068
|
+
const policy = options.policy ?? "inherit-scrubbed";
|
|
6069
|
+
const base = {};
|
|
6070
|
+
for (const [name, value] of Object.entries(parent)) {
|
|
6071
|
+
if (value !== void 0 && inheritsUnderPolicy(name, policy)) base[name] = value;
|
|
6072
|
+
}
|
|
6073
|
+
for (const [name, value] of Object.entries(options.overrides ?? {})) {
|
|
6074
|
+
base[name] = value;
|
|
6075
|
+
}
|
|
6076
|
+
return base;
|
|
6077
|
+
}
|
|
6078
|
+
|
|
6079
|
+
// src/internal/runtime/lifecycle/spawn-collect.ts
|
|
5853
6080
|
function spawnAndCollect(options) {
|
|
5854
6081
|
return new Promise((resolve3) => {
|
|
5855
6082
|
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
5856
6083
|
const spawnOptions = {
|
|
5857
6084
|
cwd: options.cwd,
|
|
5858
|
-
|
|
6085
|
+
// #54 — scrub secret-like parent env by default; `options.env` still wins.
|
|
6086
|
+
env: resolveChildEnv({ policy: options.envPolicy, overrides: options.env })
|
|
5859
6087
|
};
|
|
5860
6088
|
const child = child_process.spawn(options.command, options.args ?? [], spawnOptions);
|
|
5861
6089
|
let stdout = "";
|
|
@@ -6073,15 +6301,24 @@ function warnMalformed(agentId, line) {
|
|
|
6073
6301
|
`
|
|
6074
6302
|
);
|
|
6075
6303
|
}
|
|
6304
|
+
function hydrateSessionLine(parsed) {
|
|
6305
|
+
if (typeof parsed.text !== "string" || parsed.role === void 0) return void 0;
|
|
6306
|
+
if (parsed.role === "user" || parsed.role === "assistant") {
|
|
6307
|
+
return { role: parsed.role, text: parsed.text };
|
|
6308
|
+
}
|
|
6309
|
+
if (parsed.role === "tool_call" || parsed.role === "tool_result") {
|
|
6310
|
+
const label = parsed.role === "tool_call" ? "tool call" : "tool result";
|
|
6311
|
+
return { role: "assistant", text: `[${label}] ${parsed.text}` };
|
|
6312
|
+
}
|
|
6313
|
+
return void 0;
|
|
6314
|
+
}
|
|
6076
6315
|
async function readSessionFile(cwd, agentId) {
|
|
6077
6316
|
const lines = await readJsonlLines(cwd, agentId);
|
|
6078
6317
|
const messages = [];
|
|
6079
6318
|
for (const line of lines) {
|
|
6080
6319
|
try {
|
|
6081
|
-
const
|
|
6082
|
-
if (
|
|
6083
|
-
messages.push({ role: parsed.role, text: parsed.text });
|
|
6084
|
-
}
|
|
6320
|
+
const msg = hydrateSessionLine(JSON.parse(line));
|
|
6321
|
+
if (msg !== void 0) messages.push(msg);
|
|
6085
6322
|
} catch {
|
|
6086
6323
|
warnMalformed(agentId, line);
|
|
6087
6324
|
}
|
|
@@ -6108,24 +6345,72 @@ async function readAllPersistedMessages(cwd, agentId) {
|
|
|
6108
6345
|
return messages;
|
|
6109
6346
|
}
|
|
6110
6347
|
async function appendAnyPersistedMessage(cwd, agentId, record) {
|
|
6348
|
+
await appendPersistedMessages(cwd, agentId, [record]);
|
|
6349
|
+
}
|
|
6350
|
+
async function appendPersistedMessages(cwd, agentId, records) {
|
|
6351
|
+
if (records.length === 0) return;
|
|
6111
6352
|
const path$1 = sessionFilePath(cwd, agentId);
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6353
|
+
const payload = records.map((r) => `${redactSecrets(JSON.stringify(r))}
|
|
6354
|
+
`).join("");
|
|
6355
|
+
const dir = path.dirname(path$1);
|
|
6356
|
+
let written = false;
|
|
6357
|
+
const attempt = async () => {
|
|
6358
|
+
await promises.mkdir(dir, { recursive: true });
|
|
6359
|
+
await withFileLock(path$1, async () => {
|
|
6360
|
+
await promises.appendFile(path$1, payload, "utf8");
|
|
6361
|
+
written = true;
|
|
6362
|
+
});
|
|
6363
|
+
};
|
|
6364
|
+
try {
|
|
6365
|
+
await attempt();
|
|
6366
|
+
} catch (cause) {
|
|
6367
|
+
if (written || cause.code !== "ENOENT") throw cause;
|
|
6368
|
+
await attempt();
|
|
6369
|
+
}
|
|
6370
|
+
}
|
|
6371
|
+
async function rewriteLockedSession(path, transform) {
|
|
6372
|
+
await withFileLock(path, async () => {
|
|
6373
|
+
let raw;
|
|
6374
|
+
try {
|
|
6375
|
+
raw = await promises.readFile(path, "utf8");
|
|
6376
|
+
} catch {
|
|
6377
|
+
return;
|
|
6378
|
+
}
|
|
6379
|
+
const lines = raw.split("\n").filter((line) => line.length > 0);
|
|
6380
|
+
const next = transform(lines);
|
|
6381
|
+
if (next === void 0) return;
|
|
6382
|
+
await replaceFileAtomic(path, next);
|
|
6383
|
+
});
|
|
6115
6384
|
}
|
|
6116
6385
|
async function compactSessionFile(cwd, agentId, maxTurns) {
|
|
6117
6386
|
const path = sessionFilePath(cwd, agentId);
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
const
|
|
6387
|
+
if (!fs.existsSync(path)) return;
|
|
6388
|
+
await rewriteLockedSession(
|
|
6389
|
+
path,
|
|
6390
|
+
(lines) => lines.length <= maxTurns * 2 ? void 0 : `${lines.slice(-maxTurns).join("\n")}
|
|
6391
|
+
`
|
|
6392
|
+
);
|
|
6393
|
+
}
|
|
6394
|
+
async function truncateSessionTo(cwd, agentId, keepCount) {
|
|
6395
|
+
const path = sessionFilePath(cwd, agentId);
|
|
6396
|
+
if (!fs.existsSync(path)) return 0;
|
|
6397
|
+
let kept = 0;
|
|
6398
|
+
await rewriteLockedSession(path, (lines) => {
|
|
6399
|
+
const keep = Math.max(0, Math.min(keepCount, lines.length));
|
|
6400
|
+
kept = keep;
|
|
6401
|
+
if (keep === lines.length) return void 0;
|
|
6402
|
+
return keep === 0 ? "" : `${lines.slice(0, keep).join("\n")}
|
|
6127
6403
|
`;
|
|
6128
|
-
|
|
6404
|
+
});
|
|
6405
|
+
return kept;
|
|
6406
|
+
}
|
|
6407
|
+
|
|
6408
|
+
// src/internal/persistence/pagination.ts
|
|
6409
|
+
function paginate(items, opts) {
|
|
6410
|
+
if (opts === void 0 || opts.offset === void 0 && opts.limit === void 0) return items;
|
|
6411
|
+
const start = Math.max(0, opts.offset ?? 0);
|
|
6412
|
+
const end = opts.limit === void 0 ? items.length : start + Math.max(0, opts.limit);
|
|
6413
|
+
return items.slice(start, end);
|
|
6129
6414
|
}
|
|
6130
6415
|
|
|
6131
6416
|
// src/internal/persistence/conversation-storage-fs.ts
|
|
@@ -6138,23 +6423,31 @@ var FileSystemConversationStorage = class {
|
|
|
6138
6423
|
get root() {
|
|
6139
6424
|
return this.#root;
|
|
6140
6425
|
}
|
|
6141
|
-
async getMessages(conversationId) {
|
|
6426
|
+
async getMessages(conversationId, opts) {
|
|
6142
6427
|
const records = await readAllPersistedMessages(this.#root, conversationId);
|
|
6143
|
-
|
|
6428
|
+
const all = records.map(toStoredMessage);
|
|
6429
|
+
return paginate(all, opts);
|
|
6144
6430
|
}
|
|
6145
6431
|
async appendMessage(conversationId, message) {
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6432
|
+
await appendAnyPersistedMessage(this.#root, conversationId, toRecord(message));
|
|
6433
|
+
}
|
|
6434
|
+
async appendMessages(conversationId, messages) {
|
|
6435
|
+
await appendPersistedMessages(this.#root, conversationId, messages.map(toRecord));
|
|
6436
|
+
}
|
|
6437
|
+
async truncateConversation(conversationId, keepCount) {
|
|
6438
|
+
return truncateSessionTo(this.#root, conversationId, keepCount);
|
|
6152
6439
|
}
|
|
6153
6440
|
async deleteConversation(conversationId) {
|
|
6154
6441
|
const safe2 = sanitizeIdentifier(conversationId, { maxLen: 128 });
|
|
6155
6442
|
const dirPath = safePathJoin(this.#root, ".theokit", "agents", safe2);
|
|
6156
6443
|
await promises.rm(dirPath, { recursive: true, force: true });
|
|
6157
6444
|
}
|
|
6445
|
+
async deleteScope(prefix) {
|
|
6446
|
+
const ids = await this.listConversationIds();
|
|
6447
|
+
const matching = ids.filter((id) => id.startsWith(prefix));
|
|
6448
|
+
for (const id of matching) await this.deleteConversation(id);
|
|
6449
|
+
return matching.length;
|
|
6450
|
+
}
|
|
6158
6451
|
async listConversationIds(opts = {}) {
|
|
6159
6452
|
const agentsRoot = safePathJoin(this.#root, ".theokit", "agents");
|
|
6160
6453
|
let entries;
|
|
@@ -6180,6 +6473,9 @@ function toStoredMessage(record) {
|
|
|
6180
6473
|
at: record.at
|
|
6181
6474
|
};
|
|
6182
6475
|
}
|
|
6476
|
+
function toRecord(message) {
|
|
6477
|
+
return { role: message.role, text: message.content, at: message.at ?? Date.now() };
|
|
6478
|
+
}
|
|
6183
6479
|
|
|
6184
6480
|
// src/internal/runtime/session/agent-session.ts
|
|
6185
6481
|
var DEFAULT_MAX_TURNS = 200;
|
|
@@ -6265,12 +6561,19 @@ async function readPersistedForCache(adapter, agentId) {
|
|
|
6265
6561
|
const records = await adapter.getMessages(agentId);
|
|
6266
6562
|
const out = [];
|
|
6267
6563
|
for (const r of records) {
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
}
|
|
6564
|
+
const folded = foldStoredToSession(r);
|
|
6565
|
+
if (folded !== void 0) out.push(folded);
|
|
6271
6566
|
}
|
|
6272
6567
|
return out;
|
|
6273
6568
|
}
|
|
6569
|
+
function foldStoredToSession(r) {
|
|
6570
|
+
if (r.role === "user" || r.role === "assistant") return { role: r.role, text: r.content };
|
|
6571
|
+
if (r.role === "tool_call" || r.role === "tool_result") {
|
|
6572
|
+
const label = r.role === "tool_call" ? "tool call" : "tool result";
|
|
6573
|
+
return { role: "assistant", text: `[${label}] ${r.content}` };
|
|
6574
|
+
}
|
|
6575
|
+
return void 0;
|
|
6576
|
+
}
|
|
6274
6577
|
async function flushSessionWrites() {
|
|
6275
6578
|
while (pendingAppends.size > 0) {
|
|
6276
6579
|
const all = Array.from(pendingAppends.values());
|
|
@@ -8620,11 +8923,32 @@ function reasoningEffortFromParams(params) {
|
|
|
8620
8923
|
const thinking = params?.find((p) => p.id === "thinking");
|
|
8621
8924
|
return thinking !== void 0 && thinking.value.length > 0 ? thinking.value : void 0;
|
|
8622
8925
|
}
|
|
8926
|
+
function emitLlmMetrics(inputs, result, startAt) {
|
|
8927
|
+
inputs.telemetry?.recordHistogram(HISTOGRAM_NAMES.LLM_CALL_DURATION_MS, Date.now() - startAt, {
|
|
8928
|
+
provider: inputs.llm.name
|
|
8929
|
+
});
|
|
8930
|
+
if (result.inputTokens === void 0 && result.outputTokens === void 0) {
|
|
8931
|
+
inputs.telemetry?.recordHistogram(HISTOGRAM_NAMES.LLM_USAGE_MISSING, 1, {
|
|
8932
|
+
provider: inputs.llm.name
|
|
8933
|
+
});
|
|
8934
|
+
process.stderr.write(
|
|
8935
|
+
`[theokit-sdk] llm usage missing from ${inputs.llm.name} finish \u2014 budget may undercount
|
|
8936
|
+
`
|
|
8937
|
+
);
|
|
8938
|
+
return;
|
|
8939
|
+
}
|
|
8940
|
+
inputs.telemetry?.recordHistogram(
|
|
8941
|
+
HISTOGRAM_NAMES.LLM_TOKENS,
|
|
8942
|
+
(result.inputTokens ?? 0) + (result.outputTokens ?? 0),
|
|
8943
|
+
{ provider: inputs.llm.name }
|
|
8944
|
+
);
|
|
8945
|
+
}
|
|
8623
8946
|
async function streamLlmTurn(inputs, ctx) {
|
|
8624
|
-
const llmSpan = inputs.telemetry?.
|
|
8947
|
+
const llmSpan = inputs.telemetry?.startChildSpan(ctx.sendSpan, "llm.call", {
|
|
8625
8948
|
"model.id": inputs.model.id ?? "auto",
|
|
8626
8949
|
provider: inputs.llm.name
|
|
8627
8950
|
});
|
|
8951
|
+
const startAt = Date.now();
|
|
8628
8952
|
const signal = inputs.signal ?? new AbortController().signal;
|
|
8629
8953
|
const generator = inputs.llm.stream(
|
|
8630
8954
|
{
|
|
@@ -8667,6 +8991,7 @@ async function streamLlmTurn(inputs, ctx) {
|
|
|
8667
8991
|
inputTokens: result.inputTokens ?? 0,
|
|
8668
8992
|
outputTokens: result.outputTokens ?? 0
|
|
8669
8993
|
});
|
|
8994
|
+
emitLlmMetrics(inputs, result, startAt);
|
|
8670
8995
|
llmSpan?.end();
|
|
8671
8996
|
const stripped = stripThinkBlocks(collected.accumulatedText);
|
|
8672
8997
|
return {
|
|
@@ -8896,21 +9221,21 @@ async function executeTool(inputs, resolved, call) {
|
|
|
8896
9221
|
}
|
|
8897
9222
|
if (resolved.origin === "shell") return runShellTool(inputs, call);
|
|
8898
9223
|
if (resolved.origin === "memory") return runMemoryTool(resolved, call);
|
|
8899
|
-
if (resolved.origin === "custom") return runCustomTool(resolved, call);
|
|
9224
|
+
if (resolved.origin === "custom") return runCustomTool(resolved, call, inputs.signal);
|
|
8900
9225
|
return runMcpTool(inputs, resolved, call);
|
|
8901
9226
|
}
|
|
8902
9227
|
async function runMemoryTool(resolved, call) {
|
|
8903
9228
|
return runHandlerTool("memory", resolved.memoryHandler, call);
|
|
8904
9229
|
}
|
|
8905
|
-
async function runCustomTool(resolved, call) {
|
|
8906
|
-
return runHandlerTool("custom", resolved.customHandler, call);
|
|
9230
|
+
async function runCustomTool(resolved, call, signal) {
|
|
9231
|
+
return runHandlerTool("custom", resolved.customHandler, call, signal);
|
|
8907
9232
|
}
|
|
8908
|
-
async function runHandlerTool(kind, handler, call) {
|
|
9233
|
+
async function runHandlerTool(kind, handler, call, signal) {
|
|
8909
9234
|
if (handler === void 0) {
|
|
8910
9235
|
return { stdout: "", stderr: `${kind} tool ${call.name} has no handler`, exitCode: 127 };
|
|
8911
9236
|
}
|
|
8912
9237
|
try {
|
|
8913
|
-
const stdout = await handler(call.input);
|
|
9238
|
+
const stdout = await handler(call.input, { signal });
|
|
8914
9239
|
return { stdout, stderr: "", exitCode: 0 };
|
|
8915
9240
|
} catch (cause) {
|
|
8916
9241
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
@@ -8956,22 +9281,66 @@ ${result.stderr}`.trim();
|
|
|
8956
9281
|
return result.stdout.trim();
|
|
8957
9282
|
}
|
|
8958
9283
|
|
|
9284
|
+
// src/internal/agent-loop/tool-timeout.ts
|
|
9285
|
+
var TOOL_ABORTED_EXIT = 124;
|
|
9286
|
+
function abortedResult(signal) {
|
|
9287
|
+
const reason = signal.reason;
|
|
9288
|
+
const timedOut = reason?.name === "TimeoutError";
|
|
9289
|
+
return {
|
|
9290
|
+
stdout: "",
|
|
9291
|
+
stderr: timedOut ? "tool execution timed out" : "tool execution aborted",
|
|
9292
|
+
exitCode: TOOL_ABORTED_EXIT
|
|
9293
|
+
};
|
|
9294
|
+
}
|
|
9295
|
+
function raceToolExecution(exec, opts) {
|
|
9296
|
+
const { signal, timeoutMs } = opts;
|
|
9297
|
+
if (signal === void 0 && timeoutMs === void 0) return exec;
|
|
9298
|
+
const signals = [];
|
|
9299
|
+
if (signal !== void 0) signals.push(signal);
|
|
9300
|
+
if (timeoutMs !== void 0) signals.push(AbortSignal.timeout(timeoutMs));
|
|
9301
|
+
const merged = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
9302
|
+
if (merged.aborted) return Promise.resolve(abortedResult(merged));
|
|
9303
|
+
return new Promise((resolve3, reject) => {
|
|
9304
|
+
let settled = false;
|
|
9305
|
+
const onAbort = () => {
|
|
9306
|
+
if (settled) return;
|
|
9307
|
+
settled = true;
|
|
9308
|
+
resolve3(abortedResult(merged));
|
|
9309
|
+
};
|
|
9310
|
+
merged.addEventListener("abort", onAbort, { once: true });
|
|
9311
|
+
exec.then(
|
|
9312
|
+
(r) => {
|
|
9313
|
+
if (settled) return;
|
|
9314
|
+
settled = true;
|
|
9315
|
+
merged.removeEventListener("abort", onAbort);
|
|
9316
|
+
resolve3(r);
|
|
9317
|
+
},
|
|
9318
|
+
(e) => {
|
|
9319
|
+
if (settled) return;
|
|
9320
|
+
settled = true;
|
|
9321
|
+
merged.removeEventListener("abort", onAbort);
|
|
9322
|
+
reject(e);
|
|
9323
|
+
}
|
|
9324
|
+
);
|
|
9325
|
+
});
|
|
9326
|
+
}
|
|
9327
|
+
|
|
8959
9328
|
// src/internal/agent-loop/tool-dispatch.ts
|
|
8960
|
-
async function dispatchTools(inputs, tools, toolCalls, events) {
|
|
9329
|
+
async function dispatchTools(inputs, tools, toolCalls, events, parentSpan) {
|
|
8961
9330
|
const maxConcurrent = inputs.maxConcurrentTools ?? 4;
|
|
8962
9331
|
return mapWithConcurrency(
|
|
8963
9332
|
toolCalls,
|
|
8964
9333
|
maxConcurrent,
|
|
8965
|
-
(call) => dispatchSingleCall(inputs, tools, call, events)
|
|
9334
|
+
(call) => dispatchSingleCall(inputs, tools, call, events, parentSpan)
|
|
8966
9335
|
);
|
|
8967
9336
|
}
|
|
8968
|
-
async function dispatchSingleCall(inputs, tools, call, events) {
|
|
9337
|
+
async function dispatchSingleCall(inputs, tools, call, events, parentSpan) {
|
|
8969
9338
|
const { call: workingCall, repairs } = applyRepairAndExtractCall(tools, call);
|
|
8970
9339
|
const callId = generateCallId();
|
|
8971
9340
|
const forkVeto = vetoFromForkWhitelist(inputs, workingCall, callId, events);
|
|
8972
9341
|
if (forkVeto !== void 0) return forkVeto;
|
|
8973
9342
|
const resolved = tools.find((tool) => tool.name === workingCall.name);
|
|
8974
|
-
const toolSpan = startToolCallSpan(inputs, workingCall, resolved, callId, repairs);
|
|
9343
|
+
const toolSpan = startToolCallSpan(inputs, workingCall, resolved, callId, repairs, parentSpan);
|
|
8975
9344
|
events.push(buildToolUseRunning(inputs, callId, workingCall));
|
|
8976
9345
|
const pluginVeto = await vetoFromPluginPreHook(inputs, workingCall, callId, events);
|
|
8977
9346
|
if (pluginVeto !== void 0) {
|
|
@@ -8988,6 +9357,13 @@ async function dispatchSingleCall(inputs, tools, call, events) {
|
|
|
8988
9357
|
return fileVeto;
|
|
8989
9358
|
}
|
|
8990
9359
|
const result = await runToolWithLifecycle(inputs, resolved, workingCall, callId);
|
|
9360
|
+
await inputs.pluginManager?.runPostToolCallHooks({
|
|
9361
|
+
name: workingCall.name,
|
|
9362
|
+
args: workingCall.input,
|
|
9363
|
+
result: { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode },
|
|
9364
|
+
agentId: inputs.agentId,
|
|
9365
|
+
runId: inputs.runId
|
|
9366
|
+
});
|
|
8991
9367
|
return finalizeSpanAndPostHook(inputs, workingCall, callId, result, events, toolSpan);
|
|
8992
9368
|
}
|
|
8993
9369
|
function applyRepairAndExtractCall(tools, call) {
|
|
@@ -9020,8 +9396,8 @@ function vetoFromForkWhitelist(inputs, call, callId, events) {
|
|
|
9020
9396
|
content: `Tool blocked by fork whitelist: ${whitelistDecision.reason}`
|
|
9021
9397
|
};
|
|
9022
9398
|
}
|
|
9023
|
-
function startToolCallSpan(inputs, call, resolved, callId, repairs) {
|
|
9024
|
-
const toolSpan = inputs.telemetry?.
|
|
9399
|
+
function startToolCallSpan(inputs, call, resolved, callId, repairs, parentSpan) {
|
|
9400
|
+
const toolSpan = inputs.telemetry?.startChildSpan(parentSpan, "tool.call", {
|
|
9025
9401
|
"tool.name": call.name,
|
|
9026
9402
|
"tool.origin": resolved?.origin ?? "unknown",
|
|
9027
9403
|
callId
|
|
@@ -9085,8 +9461,14 @@ async function runToolWithLifecycle(inputs, resolved, call, callId) {
|
|
|
9085
9461
|
conversationId: inputs.agentId,
|
|
9086
9462
|
callId
|
|
9087
9463
|
});
|
|
9088
|
-
const result = await executeTool(inputs, resolved, call)
|
|
9464
|
+
const result = await raceToolExecution(executeTool(inputs, resolved, call), {
|
|
9465
|
+
signal: inputs.signal,
|
|
9466
|
+
timeoutMs: inputs.perToolTimeoutMs
|
|
9467
|
+
});
|
|
9089
9468
|
const durationMs = Date.now() - startAt;
|
|
9469
|
+
inputs.telemetry?.recordHistogram(HISTOGRAM_NAMES.TOOL_CALL_DURATION_MS, durationMs, {
|
|
9470
|
+
"tool.name": call.name
|
|
9471
|
+
});
|
|
9090
9472
|
if (result.exitCode !== void 0 && result.exitCode !== 0 && result.exitCode !== null) {
|
|
9091
9473
|
await safeEmitToolHook(inputs.onToolError, {
|
|
9092
9474
|
toolName: call.name,
|
|
@@ -9185,6 +9567,35 @@ function buildToolUseCompleted(inputs, callId, call, result) {
|
|
|
9185
9567
|
};
|
|
9186
9568
|
}
|
|
9187
9569
|
|
|
9570
|
+
// src/internal/agent-loop/tool-result-guard.ts
|
|
9571
|
+
var OPEN = "<untrusted-tool-output>";
|
|
9572
|
+
var CLOSE = "</untrusted-tool-output>";
|
|
9573
|
+
var PII_PATTERNS = [
|
|
9574
|
+
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
|
|
9575
|
+
// email
|
|
9576
|
+
/\b(?:\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/g
|
|
9577
|
+
// phone
|
|
9578
|
+
];
|
|
9579
|
+
function guardText(content, opts) {
|
|
9580
|
+
let out = content;
|
|
9581
|
+
if (opts.redactPii === true) {
|
|
9582
|
+
for (const re of PII_PATTERNS) out = out.replace(re, "[REDACTED]");
|
|
9583
|
+
}
|
|
9584
|
+
if (opts.delimit === true) {
|
|
9585
|
+
const safe2 = out.split(CLOSE).join("</ untrusted-tool-output>");
|
|
9586
|
+
out = `${OPEN}
|
|
9587
|
+
${safe2}
|
|
9588
|
+
${CLOSE}`;
|
|
9589
|
+
}
|
|
9590
|
+
return out;
|
|
9591
|
+
}
|
|
9592
|
+
function applyToolResultGuard(parts, opts) {
|
|
9593
|
+
if (opts.delimit !== true && opts.redactPii !== true) return parts;
|
|
9594
|
+
return parts.map(
|
|
9595
|
+
(p) => p.type === "tool_result" ? { ...p, content: guardText(p.content, opts) } : p
|
|
9596
|
+
);
|
|
9597
|
+
}
|
|
9598
|
+
|
|
9188
9599
|
// src/internal/budget/pricing-data.json
|
|
9189
9600
|
var pricing_data_default = {
|
|
9190
9601
|
_meta: {
|
|
@@ -9535,6 +9946,11 @@ async function runAgentLoop(inputs) {
|
|
|
9535
9946
|
try {
|
|
9536
9947
|
const ctx = await initLoopContext(inputs);
|
|
9537
9948
|
ctxRef = ctx;
|
|
9949
|
+
ctx.sendSpan = sendSpan;
|
|
9950
|
+
await inputs.pluginManager?.runOnSessionStartHooks({
|
|
9951
|
+
agentId: inputs.agentId,
|
|
9952
|
+
runId: inputs.runId
|
|
9953
|
+
});
|
|
9538
9954
|
const budget = inputs.budget ?? new IterationBudget({ maxIterations: inputs.maxIterations ?? 8 });
|
|
9539
9955
|
let lastTurnDecision;
|
|
9540
9956
|
while (budget.shouldContinue()) {
|
|
@@ -9562,6 +9978,7 @@ async function runAgentLoop(inputs) {
|
|
|
9562
9978
|
}
|
|
9563
9979
|
budget.consume();
|
|
9564
9980
|
inputs.budgetTracker?.nextIteration?.();
|
|
9981
|
+
if (inputs.signal?.aborted === true) break;
|
|
9565
9982
|
}
|
|
9566
9983
|
if (lastTurnDecision === "continue" && budget.shouldContinue() === false) {
|
|
9567
9984
|
ctx.stoppedAtIterationLimit = true;
|
|
@@ -9601,6 +10018,10 @@ async function runAgentLoop(inputs) {
|
|
|
9601
10018
|
...ctx.stoppedByDoomLoop === true ? { stoppedByDoomLoop: true } : {}
|
|
9602
10019
|
};
|
|
9603
10020
|
} finally {
|
|
10021
|
+
await inputs.pluginManager?.runOnSessionEndHooks({
|
|
10022
|
+
agentId: inputs.agentId,
|
|
10023
|
+
runId: inputs.runId
|
|
10024
|
+
});
|
|
9604
10025
|
if (ctxRef !== void 0 && ctxRef.memoryProviderHandle !== void 0 && inputs.memoryProvider !== void 0) {
|
|
9605
10026
|
try {
|
|
9606
10027
|
await inputs.memoryProvider.dispose(ctxRef.memoryProviderHandle);
|
|
@@ -9704,7 +10125,10 @@ async function finishOrReflect(inputs, ctx, llmOutput) {
|
|
|
9704
10125
|
return "done";
|
|
9705
10126
|
}
|
|
9706
10127
|
async function runIteration(inputs, ctx) {
|
|
10128
|
+
const hookCtx = { agentId: inputs.agentId, runId: inputs.runId };
|
|
10129
|
+
await inputs.pluginManager?.runPreLlmCallHooks(hookCtx);
|
|
9707
10130
|
const llmOutput = await streamLlmTurn(inputs, ctx);
|
|
10131
|
+
await inputs.pluginManager?.runPostLlmCallHooks(hookCtx);
|
|
9708
10132
|
accumulateUsage(ctx.usage, llmOutput);
|
|
9709
10133
|
if (inputs.budgetTracker !== void 0) {
|
|
9710
10134
|
const modelId = inputs.model.id ?? "auto";
|
|
@@ -9730,6 +10154,13 @@ async function runIteration(inputs, ctx) {
|
|
|
9730
10154
|
}
|
|
9731
10155
|
return continueOrTerminate(inputs, ctx, llmOutput);
|
|
9732
10156
|
}
|
|
10157
|
+
async function transformLlmOutputText(inputs, text, ctx) {
|
|
10158
|
+
return inputs.pluginManager !== void 0 ? inputs.pluginManager.runTransformLlmOutputHooks(text, ctx) : text;
|
|
10159
|
+
}
|
|
10160
|
+
async function guardAndTransformToolResults(inputs, raw, ctx) {
|
|
10161
|
+
const guarded = inputs.toolResultGuard !== void 0 ? applyToolResultGuard(raw, inputs.toolResultGuard) : raw;
|
|
10162
|
+
return inputs.pluginManager !== void 0 ? inputs.pluginManager.runTransformToolResultHooks(guarded, ctx) : guarded;
|
|
10163
|
+
}
|
|
9733
10164
|
async function continueOrTerminate(inputs, ctx, llmOutput) {
|
|
9734
10165
|
if (llmOutput.errored) return "error";
|
|
9735
10166
|
if (llmOutput.text.length > 0) {
|
|
@@ -9738,8 +10169,18 @@ async function continueOrTerminate(inputs, ctx, llmOutput) {
|
|
|
9738
10169
|
if (llmOutput.stopReason !== "tool_use" || llmOutput.toolCalls.length === 0) {
|
|
9739
10170
|
return finishOrReflect(inputs, ctx, llmOutput);
|
|
9740
10171
|
}
|
|
9741
|
-
|
|
9742
|
-
const
|
|
10172
|
+
const tCtx = { agentId: inputs.agentId, runId: inputs.runId };
|
|
10173
|
+
const outText = await transformLlmOutputText(inputs, llmOutput.text, tCtx);
|
|
10174
|
+
ctx.messages.push(buildAssistantTurn(outText, llmOutput.toolCalls));
|
|
10175
|
+
const rawResults = await dispatchTools(
|
|
10176
|
+
inputs,
|
|
10177
|
+
ctx.tools,
|
|
10178
|
+
llmOutput.toolCalls,
|
|
10179
|
+
ctx.events,
|
|
10180
|
+
ctx.sendSpan
|
|
10181
|
+
// M3 #64 — nest tool.call spans under agent.send
|
|
10182
|
+
);
|
|
10183
|
+
const toolResults = await guardAndTransformToolResults(inputs, rawResults, tCtx);
|
|
9743
10184
|
ctx.messages.push({ role: "user", content: toolResults });
|
|
9744
10185
|
if (inputs.onStep !== void 0) {
|
|
9745
10186
|
const cb = inputs.onStep;
|
|
@@ -10169,6 +10610,56 @@ function mapAnthropicStatusToCode(status, body) {
|
|
|
10169
10610
|
function formatMessage(status, code) {
|
|
10170
10611
|
return `Anthropic API error: ${code} (HTTP ${status})`;
|
|
10171
10612
|
}
|
|
10613
|
+
var cachedJsonrepair;
|
|
10614
|
+
function loadJsonrepair() {
|
|
10615
|
+
if (cachedJsonrepair === void 0) {
|
|
10616
|
+
const req = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cron.cjs', document.baseURI).href)));
|
|
10617
|
+
cachedJsonrepair = req("jsonrepair").jsonrepair;
|
|
10618
|
+
}
|
|
10619
|
+
return cachedJsonrepair;
|
|
10620
|
+
}
|
|
10621
|
+
function isPlainObject(v) {
|
|
10622
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
10623
|
+
}
|
|
10624
|
+
function toFiniteNumber(raw) {
|
|
10625
|
+
if (raw === "") return void 0;
|
|
10626
|
+
const n = Number(raw);
|
|
10627
|
+
return Number.isFinite(n) && String(n) === raw ? n : void 0;
|
|
10628
|
+
}
|
|
10629
|
+
function tryJson(raw, repair) {
|
|
10630
|
+
const t = raw.trimStart();
|
|
10631
|
+
if (!(t.startsWith("{") || t.startsWith("["))) return void 0;
|
|
10632
|
+
try {
|
|
10633
|
+
return JSON.parse(repair ? loadJsonrepair()(t) : t);
|
|
10634
|
+
} catch {
|
|
10635
|
+
return void 0;
|
|
10636
|
+
}
|
|
10637
|
+
}
|
|
10638
|
+
function heuristicCoerce(raw, repairJson) {
|
|
10639
|
+
if (raw === "true") return true;
|
|
10640
|
+
if (raw === "false") return false;
|
|
10641
|
+
if (raw === "null") return null;
|
|
10642
|
+
const n = toFiniteNumber(raw);
|
|
10643
|
+
if (n !== void 0) return n;
|
|
10644
|
+
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
10645
|
+
return json === void 0 ? raw : json;
|
|
10646
|
+
}
|
|
10647
|
+
function coerceCandidates(raw, repairJson) {
|
|
10648
|
+
const out = [];
|
|
10649
|
+
if (raw === "true") out.push(true);
|
|
10650
|
+
else if (raw === "false") out.push(false);
|
|
10651
|
+
else if (raw === "null") out.push(null);
|
|
10652
|
+
const n = toFiniteNumber(raw);
|
|
10653
|
+
if (n !== void 0) out.push(n);
|
|
10654
|
+
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
10655
|
+
if (json !== void 0) out.push(json);
|
|
10656
|
+
out.push(raw);
|
|
10657
|
+
return out;
|
|
10658
|
+
}
|
|
10659
|
+
function objectShape(schema) {
|
|
10660
|
+
const shape = schema?.shape;
|
|
10661
|
+
return shape !== null && typeof shape === "object" ? shape : void 0;
|
|
10662
|
+
}
|
|
10172
10663
|
|
|
10173
10664
|
// src/internal/llm/finish.ts
|
|
10174
10665
|
function collapseSystemText(system) {
|
|
@@ -10181,9 +10672,21 @@ function parseToolArguments(buffered) {
|
|
|
10181
10672
|
try {
|
|
10182
10673
|
return JSON.parse(buffered);
|
|
10183
10674
|
} catch {
|
|
10675
|
+
const repaired = tryJson(buffered, true);
|
|
10676
|
+
if (isPlainObject(repaired)) return repaired;
|
|
10184
10677
|
return { raw: buffered };
|
|
10185
10678
|
}
|
|
10186
10679
|
}
|
|
10680
|
+
function mapOpenAIFinish(reason) {
|
|
10681
|
+
switch (reason) {
|
|
10682
|
+
case "tool_calls":
|
|
10683
|
+
return "tool_use";
|
|
10684
|
+
case "length":
|
|
10685
|
+
return "max_tokens";
|
|
10686
|
+
default:
|
|
10687
|
+
return "end_turn";
|
|
10688
|
+
}
|
|
10689
|
+
}
|
|
10187
10690
|
function makeLlmFinish(state3) {
|
|
10188
10691
|
const finish = {
|
|
10189
10692
|
stopReason: state3.stopReason,
|
|
@@ -11065,56 +11568,9 @@ function toOllamaTools(tools) {
|
|
|
11065
11568
|
}
|
|
11066
11569
|
}));
|
|
11067
11570
|
}
|
|
11068
|
-
|
|
11069
|
-
|
|
11070
|
-
|
|
11071
|
-
const req = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cron.cjs', document.baseURI).href)));
|
|
11072
|
-
cachedJsonrepair = req("jsonrepair").jsonrepair;
|
|
11073
|
-
}
|
|
11074
|
-
return cachedJsonrepair;
|
|
11075
|
-
}
|
|
11076
|
-
function isPlainObject(v) {
|
|
11077
|
-
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
11078
|
-
}
|
|
11079
|
-
function toFiniteNumber(raw) {
|
|
11080
|
-
if (raw === "") return void 0;
|
|
11081
|
-
const n = Number(raw);
|
|
11082
|
-
return Number.isFinite(n) && String(n) === raw ? n : void 0;
|
|
11083
|
-
}
|
|
11084
|
-
function tryJson(raw, repair) {
|
|
11085
|
-
const t = raw.trimStart();
|
|
11086
|
-
if (!(t.startsWith("{") || t.startsWith("["))) return void 0;
|
|
11087
|
-
try {
|
|
11088
|
-
return JSON.parse(repair ? loadJsonrepair()(t) : t);
|
|
11089
|
-
} catch {
|
|
11090
|
-
return void 0;
|
|
11091
|
-
}
|
|
11092
|
-
}
|
|
11093
|
-
function heuristicCoerce(raw, repairJson) {
|
|
11094
|
-
if (raw === "true") return true;
|
|
11095
|
-
if (raw === "false") return false;
|
|
11096
|
-
if (raw === "null") return null;
|
|
11097
|
-
const n = toFiniteNumber(raw);
|
|
11098
|
-
if (n !== void 0) return n;
|
|
11099
|
-
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
11100
|
-
return json === void 0 ? raw : json;
|
|
11101
|
-
}
|
|
11102
|
-
function coerceCandidates(raw, repairJson) {
|
|
11103
|
-
const out = [];
|
|
11104
|
-
if (raw === "true") out.push(true);
|
|
11105
|
-
else if (raw === "false") out.push(false);
|
|
11106
|
-
else if (raw === "null") out.push(null);
|
|
11107
|
-
const n = toFiniteNumber(raw);
|
|
11108
|
-
if (n !== void 0) out.push(n);
|
|
11109
|
-
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
11110
|
-
if (json !== void 0) out.push(json);
|
|
11111
|
-
out.push(raw);
|
|
11112
|
-
return out;
|
|
11113
|
-
}
|
|
11114
|
-
function objectShape(schema) {
|
|
11115
|
-
const shape = schema?.shape;
|
|
11116
|
-
return shape !== null && typeof shape === "object" ? shape : void 0;
|
|
11117
|
-
}
|
|
11571
|
+
|
|
11572
|
+
// src/internal/llm/openai.ts
|
|
11573
|
+
init_errors();
|
|
11118
11574
|
|
|
11119
11575
|
// src/sanitize/sanitize-tool-input.ts
|
|
11120
11576
|
function applyTrim(key, value, ctx) {
|
|
@@ -11359,8 +11815,12 @@ var OpenAIClient = class {
|
|
|
11359
11815
|
// model was actually given. Empty set (no tools) recovers nothing.
|
|
11360
11816
|
new Set(request.tools?.map((tool) => tool.name) ?? [])
|
|
11361
11817
|
);
|
|
11818
|
+
let sawDone = false;
|
|
11362
11819
|
for await (const record of parseSseStream(response.body, signal)) {
|
|
11363
|
-
if (record.data === "[DONE]")
|
|
11820
|
+
if (record.data === "[DONE]") {
|
|
11821
|
+
sawDone = true;
|
|
11822
|
+
break;
|
|
11823
|
+
}
|
|
11364
11824
|
let chunk;
|
|
11365
11825
|
try {
|
|
11366
11826
|
chunk = JSON.parse(record.data);
|
|
@@ -11380,6 +11840,11 @@ var OpenAIClient = class {
|
|
|
11380
11840
|
const events = accumulator.consume(chunk);
|
|
11381
11841
|
for (const event of events) yield event;
|
|
11382
11842
|
}
|
|
11843
|
+
if (!sawDone && !accumulator.finishReasonSeen) {
|
|
11844
|
+
throw new NetworkError("SSE stream truncated (no finish_reason / [DONE])", {
|
|
11845
|
+
code: "stream_truncated"
|
|
11846
|
+
});
|
|
11847
|
+
}
|
|
11383
11848
|
const drainEvent = accumulator.finalizeHeldText();
|
|
11384
11849
|
if (drainEvent !== void 0) yield drainEvent;
|
|
11385
11850
|
return accumulator.finish();
|
|
@@ -11479,8 +11944,15 @@ var OpenAIStreamAccumulator = class {
|
|
|
11479
11944
|
this.toolCalls.set(call.index, existing);
|
|
11480
11945
|
}
|
|
11481
11946
|
}
|
|
11947
|
+
/** M2 #61 — true once any chunk carried a non-null `finish_reason` (else a
|
|
11948
|
+
* stream ending without `[DONE]` is a truncation, not a clean end). */
|
|
11949
|
+
sawFinishReason = false;
|
|
11950
|
+
get finishReasonSeen() {
|
|
11951
|
+
return this.sawFinishReason;
|
|
11952
|
+
}
|
|
11482
11953
|
applyFinishReason(reason) {
|
|
11483
11954
|
if (reason === void 0 || reason === null) return;
|
|
11955
|
+
this.sawFinishReason = true;
|
|
11484
11956
|
this.stopReason = mapOpenAIFinish(reason);
|
|
11485
11957
|
}
|
|
11486
11958
|
finish() {
|
|
@@ -11525,18 +11997,6 @@ var OpenAIStreamAccumulator = class {
|
|
|
11525
11997
|
});
|
|
11526
11998
|
}
|
|
11527
11999
|
};
|
|
11528
|
-
function mapOpenAIFinish(reason) {
|
|
11529
|
-
switch (reason) {
|
|
11530
|
-
case "tool_calls":
|
|
11531
|
-
return "tool_use";
|
|
11532
|
-
case "length":
|
|
11533
|
-
return "max_tokens";
|
|
11534
|
-
case "stop":
|
|
11535
|
-
return "end_turn";
|
|
11536
|
-
default:
|
|
11537
|
-
return "end_turn";
|
|
11538
|
-
}
|
|
11539
|
-
}
|
|
11540
12000
|
function applyReasoningRequest(body, effort, providerName) {
|
|
11541
12001
|
if (providerName === "openai") {
|
|
11542
12002
|
body.reasoning_effort = effort;
|
|
@@ -11634,19 +12094,76 @@ function assistantMessage(message) {
|
|
|
11634
12094
|
|
|
11635
12095
|
// src/internal/llm/pool-aware-client.ts
|
|
11636
12096
|
init_errors();
|
|
12097
|
+
|
|
12098
|
+
// src/internal/resilience/circuit-breaker.ts
|
|
12099
|
+
var DEFAULT_MAX_TIMEOUTS = 3;
|
|
12100
|
+
var DEFAULT_COOLDOWN_MS2 = 6e4;
|
|
12101
|
+
var CircuitBreaker = class {
|
|
12102
|
+
constructor(opts = {}) {
|
|
12103
|
+
this.opts = opts;
|
|
12104
|
+
}
|
|
12105
|
+
opts;
|
|
12106
|
+
states = /* @__PURE__ */ new Map();
|
|
12107
|
+
/** @returns true when the breaker is open and the call should be skipped. */
|
|
12108
|
+
shouldSkip(key) {
|
|
12109
|
+
const state3 = this.states.get(key);
|
|
12110
|
+
if (state3 === void 0) return false;
|
|
12111
|
+
if (state3.cooldownUntilMs === 0) return false;
|
|
12112
|
+
if (this.now() < state3.cooldownUntilMs) return true;
|
|
12113
|
+
state3.cooldownUntilMs = 0;
|
|
12114
|
+
state3.consecutiveTimeouts = 0;
|
|
12115
|
+
return false;
|
|
12116
|
+
}
|
|
12117
|
+
recordSuccess(key) {
|
|
12118
|
+
const state3 = this.states.get(key);
|
|
12119
|
+
if (state3 === void 0) return;
|
|
12120
|
+
state3.consecutiveTimeouts = 0;
|
|
12121
|
+
state3.cooldownUntilMs = 0;
|
|
12122
|
+
}
|
|
12123
|
+
recordTimeout(key) {
|
|
12124
|
+
const state3 = this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
12125
|
+
state3.consecutiveTimeouts += 1;
|
|
12126
|
+
if (state3.consecutiveTimeouts >= (this.opts.maxTimeouts ?? DEFAULT_MAX_TIMEOUTS)) {
|
|
12127
|
+
state3.cooldownUntilMs = this.now() + (this.opts.cooldownMs ?? DEFAULT_COOLDOWN_MS2);
|
|
12128
|
+
}
|
|
12129
|
+
this.states.set(key, state3);
|
|
12130
|
+
}
|
|
12131
|
+
/** @internal — tests inspect counter state. */
|
|
12132
|
+
inspect(key) {
|
|
12133
|
+
return this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
12134
|
+
}
|
|
12135
|
+
now() {
|
|
12136
|
+
return this.opts.now?.() ?? Date.now();
|
|
12137
|
+
}
|
|
12138
|
+
};
|
|
12139
|
+
|
|
12140
|
+
// src/internal/llm/pool-aware-client.ts
|
|
12141
|
+
init_retry();
|
|
11637
12142
|
var PoolAwareLlmClient = class {
|
|
11638
|
-
constructor(pool, buildClient2, waitForAvailableMs = 3e4) {
|
|
12143
|
+
constructor(pool, buildClient2, waitForAvailableMs = 3e4, resilience = {}) {
|
|
11639
12144
|
this.pool = pool;
|
|
11640
12145
|
this.buildClient = buildClient2;
|
|
11641
12146
|
this.waitForAvailableMs = waitForAvailableMs;
|
|
11642
12147
|
this.name = `pool-aware:${pool.provider}`;
|
|
12148
|
+
this.breaker = resilience.breaker ?? new CircuitBreaker();
|
|
12149
|
+
this.backoffBaseMs = resilience.backoffBaseMs;
|
|
12150
|
+
this.rng = resilience.rng;
|
|
11643
12151
|
}
|
|
11644
12152
|
pool;
|
|
11645
12153
|
buildClient;
|
|
11646
12154
|
waitForAvailableMs;
|
|
11647
12155
|
name;
|
|
12156
|
+
/** M2 #60 — provider-level circuit breaker (consecutive-failure). */
|
|
12157
|
+
breaker;
|
|
12158
|
+
backoffBaseMs;
|
|
12159
|
+
rng;
|
|
11648
12160
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: stream() must serialize pool-select → build client → first-event probe → classify → retry/rotate/propagate. Extracting helpers fragments the linear narrative; the comments above each branch keep it readable.
|
|
11649
12161
|
async *stream(request, signal) {
|
|
12162
|
+
if (this.breaker.shouldSkip(this.pool.provider)) {
|
|
12163
|
+
throw new NetworkError(`${this.pool.provider} circuit open \u2014 failing fast`, {
|
|
12164
|
+
code: "circuit_open"
|
|
12165
|
+
});
|
|
12166
|
+
}
|
|
11650
12167
|
let hasRetried429 = false;
|
|
11651
12168
|
while (true) {
|
|
11652
12169
|
if (signal.aborted) throw abortError2(signal);
|
|
@@ -11661,6 +12178,7 @@ var PoolAwareLlmClient = class {
|
|
|
11661
12178
|
}
|
|
11662
12179
|
}
|
|
11663
12180
|
if (entry === null) {
|
|
12181
|
+
this.breaker.recordTimeout(this.pool.provider);
|
|
11664
12182
|
throw new CredentialPoolExhaustedError(
|
|
11665
12183
|
`All ${this.pool.provider} credentials exhausted; next retry available at ${this.nextRetryHint() ?? "unknown"}`,
|
|
11666
12184
|
{ provider: this.pool.provider, nextRetryAt: this.nextRetryHint() }
|
|
@@ -11670,10 +12188,19 @@ var PoolAwareLlmClient = class {
|
|
|
11670
12188
|
const realClient = this.buildClient(entry.accessToken);
|
|
11671
12189
|
const attempt = await tryFirstEvent(realClient, request, signal);
|
|
11672
12190
|
if (attempt.kind === "ok") {
|
|
12191
|
+
this.breaker.recordSuccess(this.pool.provider);
|
|
11673
12192
|
return yield* relayStream(attempt.generator, attempt.firstResult);
|
|
11674
12193
|
}
|
|
11675
12194
|
const decision = classifyAndDecide(attempt.error, hasRetried429);
|
|
11676
12195
|
if (decision === "retry") {
|
|
12196
|
+
await sleepWithAbort(
|
|
12197
|
+
computeBackoffMs({
|
|
12198
|
+
attempt: 0,
|
|
12199
|
+
...this.backoffBaseMs !== void 0 ? { baseMs: this.backoffBaseMs } : {},
|
|
12200
|
+
...this.rng !== void 0 ? { rng: this.rng } : {}
|
|
12201
|
+
}),
|
|
12202
|
+
signal
|
|
12203
|
+
);
|
|
11677
12204
|
hasRetried429 = true;
|
|
11678
12205
|
continue;
|
|
11679
12206
|
}
|
|
@@ -11693,6 +12220,7 @@ var PoolAwareLlmClient = class {
|
|
|
11693
12220
|
hasRetried429 = false;
|
|
11694
12221
|
continue;
|
|
11695
12222
|
}
|
|
12223
|
+
this.breaker.recordTimeout(this.pool.provider);
|
|
11696
12224
|
throw attempt.error;
|
|
11697
12225
|
}
|
|
11698
12226
|
}
|
|
@@ -12127,9 +12655,28 @@ function selectTransport(profile, apiKey) {
|
|
|
12127
12655
|
|
|
12128
12656
|
// src/internal/mcp/client.ts
|
|
12129
12657
|
init_errors();
|
|
12130
|
-
function createMcpClient(name, config) {
|
|
12658
|
+
function createMcpClient(name, config, fetchImpl = fetch) {
|
|
12131
12659
|
if (isStdio(config)) return new StdioMcpClient(name, config);
|
|
12132
|
-
return new HttpMcpClient(name, config);
|
|
12660
|
+
return new HttpMcpClient(name, config, fetchImpl);
|
|
12661
|
+
}
|
|
12662
|
+
var DEFAULT_MCP_TIMEOUT_MS = 3e4;
|
|
12663
|
+
var MAX_STDIO_BUFFER_BYTES = 8 * 1024 * 1024;
|
|
12664
|
+
var RECONNECT_BASE_MS = 250;
|
|
12665
|
+
var MAX_RECONNECT_ATTEMPTS = 2;
|
|
12666
|
+
function reconnectDelay(attempt) {
|
|
12667
|
+
const ceiling = RECONNECT_BASE_MS * 2 ** attempt;
|
|
12668
|
+
const ms = Math.floor(Math.random() * ceiling);
|
|
12669
|
+
return ms <= 0 ? Promise.resolve() : new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
12670
|
+
}
|
|
12671
|
+
function mcpTimeoutError(name, timeoutMs) {
|
|
12672
|
+
return new NetworkError(`MCP ${name} request timed out after ${timeoutMs}ms`, {
|
|
12673
|
+
code: "mcp_timeout"
|
|
12674
|
+
});
|
|
12675
|
+
}
|
|
12676
|
+
function isAbortLike(cause) {
|
|
12677
|
+
if (typeof cause !== "object" || cause === null || !("name" in cause)) return false;
|
|
12678
|
+
const name = cause.name;
|
|
12679
|
+
return name === "TimeoutError" || name === "AbortError";
|
|
12133
12680
|
}
|
|
12134
12681
|
async function rpcInitialize(request) {
|
|
12135
12682
|
await request("initialize", {
|
|
@@ -12175,32 +12722,107 @@ var StdioMcpClient = class extends BaseMcpClient {
|
|
|
12175
12722
|
name;
|
|
12176
12723
|
child;
|
|
12177
12724
|
nextId = 1;
|
|
12725
|
+
// #59 — pending requests carry a reject + timer so a silent server times out
|
|
12726
|
+
// (typed error), a late reply after timeout is a no-op, and close() settles them.
|
|
12178
12727
|
pending = /* @__PURE__ */ new Map();
|
|
12179
12728
|
buffer = "";
|
|
12180
|
-
|
|
12729
|
+
// M2 #59 — reconnect-after-drop state. `dropped` is set when the child exits
|
|
12730
|
+
// unexpectedly OR times out (not via close()); the next request re-spawns with
|
|
12731
|
+
// backoff. `reconnectPromise` is a SINGLE in-flight reconnect shared by every
|
|
12732
|
+
// concurrent request so parallel tool dispatch after a drop awaits one handshake
|
|
12733
|
+
// instead of racing (or spuriously failing with mcp_not_init).
|
|
12734
|
+
dropped = false;
|
|
12735
|
+
reconnectAttempts = 0;
|
|
12736
|
+
reconnectPromise;
|
|
12737
|
+
get timeoutMs() {
|
|
12738
|
+
return this.config.requestTimeoutMs ?? DEFAULT_MCP_TIMEOUT_MS;
|
|
12739
|
+
}
|
|
12740
|
+
/** Spawn the server child and wire stdout/stderr/error/exit handlers.
|
|
12741
|
+
* Shared by `initialize()` and the M2 #59 reconnect path. */
|
|
12742
|
+
spawnChild() {
|
|
12181
12743
|
const resolvedCwd = resolveMcpCwd(this.config.cwd);
|
|
12182
12744
|
const child = child_process.spawn(this.config.command, this.config.args ?? [], {
|
|
12183
12745
|
cwd: resolvedCwd,
|
|
12184
|
-
|
|
12746
|
+
// #54 (F-H1) — a third-party MCP server binary must not inherit host
|
|
12747
|
+
// secrets. Scrub secret-like vars by default; `config.env` still wins.
|
|
12748
|
+
env: resolveChildEnv({ policy: this.config.envPolicy, overrides: this.config.env })
|
|
12185
12749
|
});
|
|
12186
12750
|
this.child = child;
|
|
12187
12751
|
child.stdout.on("data", (chunk) => this.consume(chunk));
|
|
12188
12752
|
child.stderr.on("data", () => void 0);
|
|
12753
|
+
child.stdin.on("error", () => void 0);
|
|
12189
12754
|
child.on("error", () => {
|
|
12190
|
-
|
|
12191
|
-
|
|
12192
|
-
|
|
12193
|
-
this.pending.clear();
|
|
12755
|
+
this.rejectAllPending(
|
|
12756
|
+
new NetworkError(`MCP ${this.name} process crashed`, { code: "mcp_crashed" })
|
|
12757
|
+
);
|
|
12194
12758
|
});
|
|
12759
|
+
child.on("exit", () => {
|
|
12760
|
+
if (this.child !== child) return;
|
|
12761
|
+
this.child = void 0;
|
|
12762
|
+
this.dropped = true;
|
|
12763
|
+
this.rejectAllPending(
|
|
12764
|
+
new NetworkError(`MCP ${this.name} disconnected`, { code: "mcp_disconnected" })
|
|
12765
|
+
);
|
|
12766
|
+
});
|
|
12767
|
+
}
|
|
12768
|
+
async initialize() {
|
|
12769
|
+
this.spawnChild();
|
|
12195
12770
|
await super.initialize();
|
|
12196
12771
|
}
|
|
12772
|
+
/** M2 #59 — ensure a live child before a request. Reconnect (bounded, with
|
|
12773
|
+
* full-jitter backoff) when the client was dropped; fail fast when never
|
|
12774
|
+
* initialized. Concurrent callers share ONE reconnect handshake. */
|
|
12775
|
+
ensureConnected() {
|
|
12776
|
+
if (this.child !== void 0) return Promise.resolve();
|
|
12777
|
+
if (!this.dropped) {
|
|
12778
|
+
return Promise.reject(
|
|
12779
|
+
new ConfigurationError(`MCP ${this.name} is not initialized`, { code: "mcp_not_init" })
|
|
12780
|
+
);
|
|
12781
|
+
}
|
|
12782
|
+
this.reconnectPromise ??= this.reconnect().finally(() => {
|
|
12783
|
+
this.reconnectPromise = void 0;
|
|
12784
|
+
});
|
|
12785
|
+
return this.reconnectPromise;
|
|
12786
|
+
}
|
|
12787
|
+
async reconnect() {
|
|
12788
|
+
if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
12789
|
+
throw new NetworkError(`MCP ${this.name} reconnect exhausted`, { code: "mcp_disconnected" });
|
|
12790
|
+
}
|
|
12791
|
+
await reconnectDelay(this.reconnectAttempts);
|
|
12792
|
+
this.reconnectAttempts += 1;
|
|
12793
|
+
this.spawnChild();
|
|
12794
|
+
await super.initialize();
|
|
12795
|
+
this.dropped = false;
|
|
12796
|
+
this.reconnectAttempts = 0;
|
|
12797
|
+
}
|
|
12197
12798
|
async close() {
|
|
12198
|
-
|
|
12199
|
-
this.child
|
|
12799
|
+
this.rejectAllPending(new NetworkError(`MCP ${this.name} closed`, { code: "mcp_closed" }));
|
|
12800
|
+
const child = this.child;
|
|
12200
12801
|
this.child = void 0;
|
|
12802
|
+
this.dropped = false;
|
|
12803
|
+
child?.kill("SIGTERM");
|
|
12804
|
+
}
|
|
12805
|
+
/** Reject + clear every pending request (crash / close). @internal */
|
|
12806
|
+
rejectAllPending(error) {
|
|
12807
|
+
for (const entry of this.pending.values()) {
|
|
12808
|
+
clearTimeout(entry.timer);
|
|
12809
|
+
entry.reject(error);
|
|
12810
|
+
}
|
|
12811
|
+
this.pending.clear();
|
|
12201
12812
|
}
|
|
12202
12813
|
consume(chunk) {
|
|
12203
12814
|
this.buffer += chunk.toString("utf8");
|
|
12815
|
+
if (this.buffer.length > MAX_STDIO_BUFFER_BYTES) {
|
|
12816
|
+
this.buffer = "";
|
|
12817
|
+
this.rejectAllPending(
|
|
12818
|
+
new NetworkError(`MCP ${this.name} exceeded stdout buffer limit`, {
|
|
12819
|
+
code: "mcp_buffer_overflow"
|
|
12820
|
+
})
|
|
12821
|
+
);
|
|
12822
|
+
this.child?.kill("SIGKILL");
|
|
12823
|
+
this.child = void 0;
|
|
12824
|
+
return;
|
|
12825
|
+
}
|
|
12204
12826
|
let newlineIndex = this.buffer.indexOf("\n");
|
|
12205
12827
|
while (newlineIndex !== -1) {
|
|
12206
12828
|
const line = this.buffer.slice(0, newlineIndex).trim();
|
|
@@ -12217,23 +12839,47 @@ var StdioMcpClient = class extends BaseMcpClient {
|
|
|
12217
12839
|
return;
|
|
12218
12840
|
}
|
|
12219
12841
|
if (typeof message.id !== "number") return;
|
|
12220
|
-
const
|
|
12221
|
-
if (
|
|
12842
|
+
const entry = this.pending.get(message.id);
|
|
12843
|
+
if (entry === void 0) return;
|
|
12222
12844
|
this.pending.delete(message.id);
|
|
12223
|
-
|
|
12845
|
+
clearTimeout(entry.timer);
|
|
12846
|
+
entry.resolve(message);
|
|
12224
12847
|
}
|
|
12225
12848
|
request(method, params) {
|
|
12226
|
-
|
|
12227
|
-
|
|
12228
|
-
|
|
12229
|
-
|
|
12849
|
+
const child = this.child;
|
|
12850
|
+
if (child !== void 0) return this.send(child, method, params);
|
|
12851
|
+
if (this.dropped) return this.reconnectAndRequest(method, params);
|
|
12852
|
+
return Promise.reject(
|
|
12853
|
+
new ConfigurationError(`MCP ${this.name} is not initialized`, { code: "mcp_not_init" })
|
|
12854
|
+
);
|
|
12855
|
+
}
|
|
12856
|
+
/** M2 #59 — reconnect a dropped client, then send. Separate async path so the
|
|
12857
|
+
* happy path above never pays an extra microtask tick. */
|
|
12858
|
+
async reconnectAndRequest(method, params) {
|
|
12859
|
+
await this.ensureConnected();
|
|
12860
|
+
const child = this.child;
|
|
12861
|
+
if (child === void 0) {
|
|
12862
|
+
throw new ConfigurationError(`MCP ${this.name} is not initialized`, { code: "mcp_not_init" });
|
|
12230
12863
|
}
|
|
12864
|
+
return this.send(child, method, params);
|
|
12865
|
+
}
|
|
12866
|
+
send(child, method, params) {
|
|
12231
12867
|
const id = this.nextId++;
|
|
12232
12868
|
const payload = { jsonrpc: "2.0", id, method, params };
|
|
12233
|
-
|
|
12869
|
+
child.stdin.write(`${JSON.stringify(payload)}
|
|
12234
12870
|
`);
|
|
12235
|
-
return new Promise((resolve3) => {
|
|
12236
|
-
|
|
12871
|
+
return new Promise((resolve3, reject) => {
|
|
12872
|
+
const timer = setTimeout(() => {
|
|
12873
|
+
this.pending.delete(id);
|
|
12874
|
+
reject(mcpTimeoutError(this.name, this.timeoutMs));
|
|
12875
|
+
this.child?.kill("SIGKILL");
|
|
12876
|
+
this.child = void 0;
|
|
12877
|
+
this.dropped = true;
|
|
12878
|
+
this.rejectAllPending(
|
|
12879
|
+
new NetworkError(`MCP ${this.name} disconnected`, { code: "mcp_disconnected" })
|
|
12880
|
+
);
|
|
12881
|
+
}, this.timeoutMs);
|
|
12882
|
+
this.pending.set(id, { resolve: resolve3, reject, timer });
|
|
12237
12883
|
});
|
|
12238
12884
|
}
|
|
12239
12885
|
};
|
|
@@ -12259,11 +12905,20 @@ var HttpMcpClient = class extends BaseMcpClient {
|
|
|
12259
12905
|
accept: "application/json",
|
|
12260
12906
|
...this.config.headers ?? {}
|
|
12261
12907
|
};
|
|
12262
|
-
const
|
|
12263
|
-
|
|
12264
|
-
|
|
12265
|
-
|
|
12266
|
-
|
|
12908
|
+
const timeoutMs = this.config.requestTimeoutMs ?? DEFAULT_MCP_TIMEOUT_MS;
|
|
12909
|
+
let response;
|
|
12910
|
+
try {
|
|
12911
|
+
response = await this.fetchImpl(this.config.url, {
|
|
12912
|
+
method: "POST",
|
|
12913
|
+
headers,
|
|
12914
|
+
body: JSON.stringify(payload),
|
|
12915
|
+
// #59 — bound the request; a non-responding endpoint aborts here.
|
|
12916
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
12917
|
+
});
|
|
12918
|
+
} catch (cause) {
|
|
12919
|
+
if (isAbortLike(cause)) throw mcpTimeoutError(this.name, timeoutMs);
|
|
12920
|
+
throw cause;
|
|
12921
|
+
}
|
|
12267
12922
|
if (!response.ok) {
|
|
12268
12923
|
throw new NetworkError(`MCP ${this.name} returned ${response.status}`, {
|
|
12269
12924
|
code: "mcp_http_error"
|
|
@@ -12378,11 +13033,33 @@ function resolveRunProvider(options) {
|
|
|
12378
13033
|
);
|
|
12379
13034
|
}
|
|
12380
13035
|
const parsedModel = parseModelId(options.model?.id);
|
|
12381
|
-
const
|
|
12382
|
-
const
|
|
12383
|
-
const
|
|
13036
|
+
const modelInferredProvider = parsedModel.provider !== void 0 && getProviderProfile(parsedModel.provider) !== void 0 ? parsedModel.provider : void 0;
|
|
13037
|
+
const keyInferredProvider = inferProviderFromApiKey(options.agentOptions.apiKey);
|
|
13038
|
+
const primary = options.agentOptions.providers?.routes?.[0]?.provider ?? keyInferredProvider ?? modelInferredProvider ?? detectPrimaryProvider();
|
|
13039
|
+
const effectiveModelId = modelInferredProvider !== void 0 && modelInferredProvider === primary ? parsedModel.name : options.model?.id ?? "claude-sonnet-4-6";
|
|
12384
13040
|
return { primary, effectiveModelId };
|
|
12385
13041
|
}
|
|
13042
|
+
function inferProviderFromApiKey(apiKey) {
|
|
13043
|
+
if (apiKey === void 0 || apiKey.length === 0) return void 0;
|
|
13044
|
+
const byPrefix = [
|
|
13045
|
+
{ provider: "openrouter", prefix: "sk-or-" },
|
|
13046
|
+
{ provider: "anthropic", prefix: "sk-ant-" },
|
|
13047
|
+
{ provider: "openai", prefix: "sk-" }
|
|
13048
|
+
];
|
|
13049
|
+
for (const { provider, prefix } of byPrefix) {
|
|
13050
|
+
if (apiKey.startsWith(prefix) && getProviderProfile(provider) !== void 0) {
|
|
13051
|
+
return provider;
|
|
13052
|
+
}
|
|
13053
|
+
}
|
|
13054
|
+
return void 0;
|
|
13055
|
+
}
|
|
13056
|
+
function mergeExplicitApiKey(pools, primary, apiKey) {
|
|
13057
|
+
if (apiKey === void 0 || apiKey.length === 0) return pools;
|
|
13058
|
+
if (isFixtureApiKey(apiKey) || apiKey === LOCAL_RUNTIME_MOCK_KEY) return pools;
|
|
13059
|
+
const existing = pools?.[primary];
|
|
13060
|
+
if (existing !== void 0 && existing.length > 0) return pools;
|
|
13061
|
+
return { ...pools ?? {}, [primary]: [apiKey] };
|
|
13062
|
+
}
|
|
12386
13063
|
function buildLoopInputs(options, runId, userText) {
|
|
12387
13064
|
const maxIterations = options.sendOptions.maxIterations;
|
|
12388
13065
|
if (maxIterations !== void 0 && (!Number.isInteger(maxIterations) || maxIterations < 1)) {
|
|
@@ -12393,7 +13070,11 @@ function buildLoopInputs(options, runId, userText) {
|
|
|
12393
13070
|
}
|
|
12394
13071
|
const { primary, effectiveModelId } = resolveRunProvider(options);
|
|
12395
13072
|
const fallback = options.agentOptions.providers?.fallback;
|
|
12396
|
-
const apiKeys =
|
|
13073
|
+
const apiKeys = mergeExplicitApiKey(
|
|
13074
|
+
options.agentOptions.providers?.apiKeys,
|
|
13075
|
+
primary,
|
|
13076
|
+
options.agentOptions.apiKey
|
|
13077
|
+
);
|
|
12397
13078
|
const credentialPoolStrategy = options.agentOptions.providers?.credentialPoolStrategy;
|
|
12398
13079
|
const extractToolCallsFromContent = options.agentOptions.providers?.routes?.[0]?.extractToolCallsFromContent;
|
|
12399
13080
|
const chain = resolveProviderChain({
|
|
@@ -12437,6 +13118,10 @@ function buildLoopInputs(options, runId, userText) {
|
|
|
12437
13118
|
// D318 — forward SendOptions.signal to the agent loop so streamLlmTurn
|
|
12438
13119
|
// can attach it to the LLM `fetch({ signal })` call.
|
|
12439
13120
|
...options.sendOptions.signal !== void 0 ? { signal: options.sendOptions.signal } : {},
|
|
13121
|
+
// #58 / #57 — forward the per-tool timeout + tool-result guard so a consumer
|
|
13122
|
+
// can enable them via SendOptions (not only internal AgentLoopInputs).
|
|
13123
|
+
...options.sendOptions.perToolTimeoutMs !== void 0 ? { perToolTimeoutMs: options.sendOptions.perToolTimeoutMs } : {},
|
|
13124
|
+
...options.sendOptions.toolResultGuard !== void 0 ? { toolResultGuard: options.sendOptions.toolResultGuard } : {},
|
|
12440
13125
|
// M1-2: per-send iteration ceiling (validated above). The loop reads
|
|
12441
13126
|
// inputs.maxIterations (default 8 when unset).
|
|
12442
13127
|
...maxIterations !== void 0 ? { maxIterations } : {},
|
|
@@ -12753,7 +13438,12 @@ async function runActiveMemory(args) {
|
|
|
12753
13438
|
hits: []
|
|
12754
13439
|
});
|
|
12755
13440
|
}
|
|
12756
|
-
const
|
|
13441
|
+
const tenantCtx = {
|
|
13442
|
+
namespace: args.namespace,
|
|
13443
|
+
userId: args.userId,
|
|
13444
|
+
scope: args.scope
|
|
13445
|
+
};
|
|
13446
|
+
const cached2 = args.cache?.get(args.userText, cfg.queryMode, tenantCtx);
|
|
12757
13447
|
if (cached2 !== void 0) return endRecallSpan(span, args, cached2);
|
|
12758
13448
|
const query = buildQuery(args.userText, args.priorMessages, cfg.queryMode, cfg.recentUserTurns);
|
|
12759
13449
|
if (query.trim().length === 0) {
|
|
@@ -12838,7 +13528,12 @@ function notifyBreaker(breaker, key, status) {
|
|
|
12838
13528
|
else if (status === "ok" || status === "no-recall") breaker.recordSuccess(key);
|
|
12839
13529
|
}
|
|
12840
13530
|
async function finalize(args, queryMode, result) {
|
|
12841
|
-
|
|
13531
|
+
const tenantCtx = {
|
|
13532
|
+
namespace: args.namespace,
|
|
13533
|
+
userId: args.userId,
|
|
13534
|
+
scope: args.scope
|
|
13535
|
+
};
|
|
13536
|
+
args.cache?.set(args.userText, queryMode, result, tenantCtx);
|
|
12842
13537
|
if (args.persistTranscripts === true && args.cwd !== void 0) {
|
|
12843
13538
|
await persistActiveMemoryTranscript(args.cwd, {
|
|
12844
13539
|
runId: args.runId ?? `run-${Date.now()}`,
|
|
@@ -13440,48 +14135,6 @@ var MEMORY_EMBEDDING_ADAPTERS = {
|
|
|
13440
14135
|
gemini: geminiMemoryEmbeddingProviderAdapter
|
|
13441
14136
|
};
|
|
13442
14137
|
|
|
13443
|
-
// src/internal/memory/circuit-breaker.ts
|
|
13444
|
-
var DEFAULT_MAX_TIMEOUTS = 3;
|
|
13445
|
-
var DEFAULT_COOLDOWN_MS2 = 6e4;
|
|
13446
|
-
var CircuitBreaker = class {
|
|
13447
|
-
constructor(opts = {}) {
|
|
13448
|
-
this.opts = opts;
|
|
13449
|
-
}
|
|
13450
|
-
opts;
|
|
13451
|
-
states = /* @__PURE__ */ new Map();
|
|
13452
|
-
/** @returns true when the breaker is open and the call should be skipped. */
|
|
13453
|
-
shouldSkip(key) {
|
|
13454
|
-
const state3 = this.states.get(key);
|
|
13455
|
-
if (state3 === void 0) return false;
|
|
13456
|
-
if (state3.cooldownUntilMs === 0) return false;
|
|
13457
|
-
if (this.now() < state3.cooldownUntilMs) return true;
|
|
13458
|
-
state3.cooldownUntilMs = 0;
|
|
13459
|
-
state3.consecutiveTimeouts = 0;
|
|
13460
|
-
return false;
|
|
13461
|
-
}
|
|
13462
|
-
recordSuccess(key) {
|
|
13463
|
-
const state3 = this.states.get(key);
|
|
13464
|
-
if (state3 === void 0) return;
|
|
13465
|
-
state3.consecutiveTimeouts = 0;
|
|
13466
|
-
state3.cooldownUntilMs = 0;
|
|
13467
|
-
}
|
|
13468
|
-
recordTimeout(key) {
|
|
13469
|
-
const state3 = this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
13470
|
-
state3.consecutiveTimeouts += 1;
|
|
13471
|
-
if (state3.consecutiveTimeouts >= (this.opts.maxTimeouts ?? DEFAULT_MAX_TIMEOUTS)) {
|
|
13472
|
-
state3.cooldownUntilMs = this.now() + (this.opts.cooldownMs ?? DEFAULT_COOLDOWN_MS2);
|
|
13473
|
-
}
|
|
13474
|
-
this.states.set(key, state3);
|
|
13475
|
-
}
|
|
13476
|
-
/** @internal — tests inspect counter state. */
|
|
13477
|
-
inspect(key) {
|
|
13478
|
-
return this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
13479
|
-
}
|
|
13480
|
-
now() {
|
|
13481
|
-
return this.opts.now?.() ?? Date.now();
|
|
13482
|
-
}
|
|
13483
|
-
};
|
|
13484
|
-
|
|
13485
14138
|
// src/internal/persistence/fts5-sanitize.ts
|
|
13486
14139
|
var PHRASE_OPEN = "";
|
|
13487
14140
|
var PHRASE_CLOSE = "";
|