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