@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/a2a/index.cjs
CHANGED
|
@@ -2548,13 +2548,13 @@ var init_cloud_run = __esm({
|
|
|
2548
2548
|
});
|
|
2549
2549
|
|
|
2550
2550
|
// src/internal/llm/sse.ts
|
|
2551
|
-
async function* parseSseStream(body, signal) {
|
|
2551
|
+
async function* parseSseStream(body, signal, idleTimeoutMs = DEFAULT_SSE_IDLE_MS) {
|
|
2552
2552
|
if (body === null) return;
|
|
2553
2553
|
const reader = body.getReader();
|
|
2554
2554
|
const decoder = new TextDecoder("utf-8");
|
|
2555
2555
|
const state2 = { buffer: "", event: "message", data: "" };
|
|
2556
2556
|
try {
|
|
2557
|
-
for await (const chunk of readChunks(reader, signal)) {
|
|
2557
|
+
for await (const chunk of readChunks(reader, signal, idleTimeoutMs)) {
|
|
2558
2558
|
state2.buffer += decoder.decode(chunk, { stream: true });
|
|
2559
2559
|
for (const record of drainCompleteRecords(state2)) yield record;
|
|
2560
2560
|
}
|
|
@@ -2564,14 +2564,38 @@ async function* parseSseStream(body, signal) {
|
|
|
2564
2564
|
releaseReader(reader);
|
|
2565
2565
|
}
|
|
2566
2566
|
}
|
|
2567
|
-
async function* readChunks(reader, signal) {
|
|
2567
|
+
async function* readChunks(reader, signal, idleTimeoutMs) {
|
|
2568
2568
|
while (true) {
|
|
2569
2569
|
if (signal.aborted) return;
|
|
2570
|
-
const { value, done } = await reader
|
|
2570
|
+
const { value, done } = await readWithIdleTimeout(reader, idleTimeoutMs);
|
|
2571
2571
|
if (done) return;
|
|
2572
2572
|
if (value !== void 0) yield value;
|
|
2573
2573
|
}
|
|
2574
2574
|
}
|
|
2575
|
+
function readWithIdleTimeout(reader, idleTimeoutMs) {
|
|
2576
|
+
if (idleTimeoutMs <= 0) return reader.read();
|
|
2577
|
+
return new Promise(
|
|
2578
|
+
(resolve3, reject) => {
|
|
2579
|
+
const timer = setTimeout(() => {
|
|
2580
|
+
reject(
|
|
2581
|
+
new NetworkError(`SSE stream idle for ${idleTimeoutMs}ms \u2014 upstream stalled`, {
|
|
2582
|
+
code: "stream_idle_timeout"
|
|
2583
|
+
})
|
|
2584
|
+
);
|
|
2585
|
+
}, idleTimeoutMs);
|
|
2586
|
+
reader.read().then(
|
|
2587
|
+
(result) => {
|
|
2588
|
+
clearTimeout(timer);
|
|
2589
|
+
resolve3(result);
|
|
2590
|
+
},
|
|
2591
|
+
(err) => {
|
|
2592
|
+
clearTimeout(timer);
|
|
2593
|
+
reject(err);
|
|
2594
|
+
}
|
|
2595
|
+
);
|
|
2596
|
+
}
|
|
2597
|
+
);
|
|
2598
|
+
}
|
|
2575
2599
|
async function cancelReaderQuietly(reader) {
|
|
2576
2600
|
try {
|
|
2577
2601
|
await reader.cancel();
|
|
@@ -2622,8 +2646,11 @@ function releaseReader(reader) {
|
|
|
2622
2646
|
} catch {
|
|
2623
2647
|
}
|
|
2624
2648
|
}
|
|
2649
|
+
var DEFAULT_SSE_IDLE_MS;
|
|
2625
2650
|
var init_sse = __esm({
|
|
2626
2651
|
"src/internal/llm/sse.ts"() {
|
|
2652
|
+
init_errors();
|
|
2653
|
+
DEFAULT_SSE_IDLE_MS = 6e4;
|
|
2627
2654
|
}
|
|
2628
2655
|
});
|
|
2629
2656
|
|
|
@@ -3572,9 +3599,16 @@ var init_context = __esm({
|
|
|
3572
3599
|
});
|
|
3573
3600
|
|
|
3574
3601
|
// src/internal/plugins/manager.ts
|
|
3602
|
+
function removeAll(arr, toRemove) {
|
|
3603
|
+
for (const item of toRemove) {
|
|
3604
|
+
const idx = arr.indexOf(item);
|
|
3605
|
+
if (idx !== -1) arr.splice(idx, 1);
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3575
3608
|
var PluginManager;
|
|
3576
3609
|
var init_manager = __esm({
|
|
3577
3610
|
"src/internal/plugins/manager.ts"() {
|
|
3611
|
+
init_errors();
|
|
3578
3612
|
init_context();
|
|
3579
3613
|
PluginManager = class {
|
|
3580
3614
|
#aggregated = {
|
|
@@ -3586,6 +3620,9 @@ var init_manager = __esm({
|
|
|
3586
3620
|
memoryProviders: []
|
|
3587
3621
|
};
|
|
3588
3622
|
#initialized = false;
|
|
3623
|
+
// #68 — registrations of plugins added post-init via `register()`, keyed by
|
|
3624
|
+
// plugin name so a re-register REPLACES (not appends) the prior hooks.
|
|
3625
|
+
#byName = /* @__PURE__ */ new Map();
|
|
3589
3626
|
async initialize(plugins) {
|
|
3590
3627
|
if (this.#initialized) {
|
|
3591
3628
|
throw new Error("PluginManager.initialize called twice \u2014 register only once per process");
|
|
@@ -3603,6 +3640,36 @@ var init_manager = __esm({
|
|
|
3603
3640
|
await this.#dispatchPlugin(plugin);
|
|
3604
3641
|
}
|
|
3605
3642
|
}
|
|
3643
|
+
/**
|
|
3644
|
+
* #68 — register a single `general` plugin AFTER `initialize()` has run.
|
|
3645
|
+
*
|
|
3646
|
+
* The bulk `initialize()` is single-shot (one call per process); late
|
|
3647
|
+
* registration is a distinct, named operation used by adapters that install
|
|
3648
|
+
* a plugin per-session/per-request (e.g. the ACP permission veto, which is
|
|
3649
|
+
* installed once the permission mode + connection are known — after the
|
|
3650
|
+
* agent's own plugins were already initialized).
|
|
3651
|
+
*
|
|
3652
|
+
* Idempotent by plugin NAME: re-registering a plugin with the same name
|
|
3653
|
+
* REPLACES its prior hooks/tools instead of appending duplicates (the ACP
|
|
3654
|
+
* permission plugin is re-installed on every prompt).
|
|
3655
|
+
*
|
|
3656
|
+
* Only `general` plugins may be registered late — model-provider / memory
|
|
3657
|
+
* plugins are resolved during the bulk init and cannot be added afterwards.
|
|
3658
|
+
*/
|
|
3659
|
+
async register(plugin) {
|
|
3660
|
+
if (plugin.kind !== "general") {
|
|
3661
|
+
throw new ConfigurationError(
|
|
3662
|
+
`late register supports general plugins only (got "${plugin.kind}" for "${plugin.name}")`,
|
|
3663
|
+
{ code: "plugin_late_register_kind" }
|
|
3664
|
+
);
|
|
3665
|
+
}
|
|
3666
|
+
const prior = this.#byName.get(plugin.name);
|
|
3667
|
+
if (prior !== void 0) this.#unmerge(prior);
|
|
3668
|
+
const { ctx, registrations } = createPluginContext();
|
|
3669
|
+
await plugin.register(ctx);
|
|
3670
|
+
this.#byName.set(plugin.name, registrations);
|
|
3671
|
+
this.#merge(registrations);
|
|
3672
|
+
}
|
|
3606
3673
|
get aggregated() {
|
|
3607
3674
|
return this.#aggregated;
|
|
3608
3675
|
}
|
|
@@ -3683,6 +3750,64 @@ var init_manager = __esm({
|
|
|
3683
3750
|
}
|
|
3684
3751
|
}
|
|
3685
3752
|
}
|
|
3753
|
+
// #65 — the previously-dead hooks, now wired. Fire-and-forget hooks run
|
|
3754
|
+
// in order (per-handler errors logged, never thrown); transform hooks fold
|
|
3755
|
+
// over the payload (a handler returning a value replaces it).
|
|
3756
|
+
/** @internal */
|
|
3757
|
+
async #runFireAndForget(name, ctx) {
|
|
3758
|
+
for (const h of this.#aggregated.hooks.get(name) ?? []) {
|
|
3759
|
+
try {
|
|
3760
|
+
await h(ctx);
|
|
3761
|
+
} catch (err) {
|
|
3762
|
+
process.stderr.write(
|
|
3763
|
+
`[theokit-sdk] ${name} hook failed: ${err instanceof Error ? err.message : String(err)}
|
|
3764
|
+
`
|
|
3765
|
+
);
|
|
3766
|
+
}
|
|
3767
|
+
}
|
|
3768
|
+
}
|
|
3769
|
+
/** @internal — fold: each handler may return a replacement payload; a throw keeps the prior value. */
|
|
3770
|
+
async #runTransform(name, payload, ctx) {
|
|
3771
|
+
let current = payload;
|
|
3772
|
+
for (const h of this.#aggregated.hooks.get(name) ?? []) {
|
|
3773
|
+
try {
|
|
3774
|
+
const out = await h(current, ctx);
|
|
3775
|
+
if (out !== void 0) current = out;
|
|
3776
|
+
} catch (err) {
|
|
3777
|
+
process.stderr.write(
|
|
3778
|
+
`[theokit-sdk] ${name} hook failed: ${err instanceof Error ? err.message : String(err)}
|
|
3779
|
+
`
|
|
3780
|
+
);
|
|
3781
|
+
}
|
|
3782
|
+
}
|
|
3783
|
+
return current;
|
|
3784
|
+
}
|
|
3785
|
+
/** #65 — fired after a tool call completes. @internal */
|
|
3786
|
+
runPostToolCallHooks(ctx) {
|
|
3787
|
+
return this.#runFireAndForget("post_tool_call", ctx);
|
|
3788
|
+
}
|
|
3789
|
+
/** #65 — fired before / after each LLM turn. @internal */
|
|
3790
|
+
runPreLlmCallHooks(ctx) {
|
|
3791
|
+
return this.#runFireAndForget("pre_llm_call", ctx);
|
|
3792
|
+
}
|
|
3793
|
+
runPostLlmCallHooks(ctx) {
|
|
3794
|
+
return this.#runFireAndForget("post_llm_call", ctx);
|
|
3795
|
+
}
|
|
3796
|
+
/** #65 — fired at run start / end. @internal */
|
|
3797
|
+
runOnSessionStartHooks(ctx) {
|
|
3798
|
+
return this.#runFireAndForget("on_session_start", ctx);
|
|
3799
|
+
}
|
|
3800
|
+
runOnSessionEndHooks(ctx) {
|
|
3801
|
+
return this.#runFireAndForget("on_session_end", ctx);
|
|
3802
|
+
}
|
|
3803
|
+
/** #65/#57 — transform tool results before they reach the LLM (the #57 seam). @internal */
|
|
3804
|
+
runTransformToolResultHooks(results, ctx) {
|
|
3805
|
+
return this.#runTransform("transform_tool_result", results, ctx);
|
|
3806
|
+
}
|
|
3807
|
+
/** #65 — transform the LLM output text before it is consumed. @internal */
|
|
3808
|
+
runTransformLlmOutputHooks(output, ctx) {
|
|
3809
|
+
return this.#runTransform("transform_llm_output", output, ctx);
|
|
3810
|
+
}
|
|
3686
3811
|
async #dispatchPlugin(plugin) {
|
|
3687
3812
|
if (plugin.kind === "general") {
|
|
3688
3813
|
const { ctx, registrations } = createPluginContext();
|
|
@@ -3710,6 +3835,22 @@ var init_manager = __esm({
|
|
|
3710
3835
|
}
|
|
3711
3836
|
this.#aggregated.injected.push(...r.injected);
|
|
3712
3837
|
}
|
|
3838
|
+
/**
|
|
3839
|
+
* #68 — inverse of #merge: remove a prior registration's contributions from
|
|
3840
|
+
* the aggregated view by object identity. Used by `register()` to replace a
|
|
3841
|
+
* same-named plugin's hooks/tools instead of accumulating duplicates.
|
|
3842
|
+
*/
|
|
3843
|
+
#unmerge(r) {
|
|
3844
|
+
removeAll(this.#aggregated.tools, r.tools);
|
|
3845
|
+
removeAll(this.#aggregated.commands, r.commands);
|
|
3846
|
+
removeAll(this.#aggregated.injected, r.injected);
|
|
3847
|
+
for (const [hook, handlers] of r.hooks.entries()) {
|
|
3848
|
+
const existing = this.#aggregated.hooks.get(hook);
|
|
3849
|
+
if (existing === void 0) continue;
|
|
3850
|
+
removeAll(existing, handlers);
|
|
3851
|
+
if (existing.length === 0) this.#aggregated.hooks.delete(hook);
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3713
3854
|
};
|
|
3714
3855
|
}
|
|
3715
3856
|
});
|
|
@@ -3734,7 +3875,12 @@ var init_span_names = __esm({
|
|
|
3734
3875
|
LLM_CALL: "llm.call"
|
|
3735
3876
|
};
|
|
3736
3877
|
HISTOGRAM_NAMES = {
|
|
3737
|
-
MEMORY_RECALL_DURATION_MS: "theokit_memory_recall_duration_ms"
|
|
3878
|
+
MEMORY_RECALL_DURATION_MS: "theokit_memory_recall_duration_ms",
|
|
3879
|
+
TOOL_CALL_DURATION_MS: "theokit_tool_call_duration_ms",
|
|
3880
|
+
LLM_CALL_DURATION_MS: "theokit_llm_call_duration_ms",
|
|
3881
|
+
LLM_TOKENS: "theokit_llm_tokens",
|
|
3882
|
+
/** M3 #66 — count of finishes where the provider omitted usage (silent undercount). */
|
|
3883
|
+
LLM_USAGE_MISSING: "theokit_llm_usage_missing"
|
|
3738
3884
|
};
|
|
3739
3885
|
}
|
|
3740
3886
|
});
|
|
@@ -4099,7 +4245,25 @@ function createTelemetry(settings) {
|
|
|
4099
4245
|
enabled: true,
|
|
4100
4246
|
includeContent: settings.includeContent === true,
|
|
4101
4247
|
startSpan: startNewSpan,
|
|
4102
|
-
|
|
4248
|
+
// M3 #64 — actually nest the child under its parent instead of discarding it.
|
|
4249
|
+
// The parent's SpanContext is set on a fresh OTel context so the child links
|
|
4250
|
+
// to it (traceId + parentSpanId), reconstructing the causal trace tree. Falls
|
|
4251
|
+
// back to a root span when the parent has no valid span id (telemetry off /
|
|
4252
|
+
// NOOP), preserving the pre-M3 behavior for parentless callers.
|
|
4253
|
+
startChildSpan: (parent, name, attrs) => {
|
|
4254
|
+
const redactedAttrs = attrs === void 0 ? void 0 : redactAttrs(attrs);
|
|
4255
|
+
const opts = redactedAttrs ? { attributes: redactedAttrs } : void 0;
|
|
4256
|
+
const pctx = safe(() => parent?.spanContext(), void 0);
|
|
4257
|
+
const span = safe(() => {
|
|
4258
|
+
if (pctx !== void 0 && pctx.spanId !== "0".repeat(16)) {
|
|
4259
|
+
const childCtx = otel.trace.setSpanContext(otel.context.active(), pctx);
|
|
4260
|
+
return tracer.startSpan(name, opts, childCtx);
|
|
4261
|
+
}
|
|
4262
|
+
return tracer.startSpan(name, opts);
|
|
4263
|
+
}, NOOP_SPAN);
|
|
4264
|
+
if (span !== NOOP_SPAN) openSpans.add(span);
|
|
4265
|
+
return wrapSpan(span, openSpans);
|
|
4266
|
+
},
|
|
4103
4267
|
recordHistogram,
|
|
4104
4268
|
endAll: () => {
|
|
4105
4269
|
for (const span of openSpans) safe(() => span.end(), void 0);
|
|
@@ -4169,12 +4333,65 @@ var init_tracer = __esm({
|
|
|
4169
4333
|
warnedOnce = false;
|
|
4170
4334
|
}
|
|
4171
4335
|
});
|
|
4336
|
+
|
|
4337
|
+
// src/internal/runtime/lifecycle/env-policy.ts
|
|
4338
|
+
function isSecretName(name) {
|
|
4339
|
+
return SECRET_PATTERNS.some((re) => re.test(name));
|
|
4340
|
+
}
|
|
4341
|
+
function inheritsUnderPolicy(name, policy) {
|
|
4342
|
+
if (policy === "all") return true;
|
|
4343
|
+
if (policy === "core") return CORE_VARS.includes(name);
|
|
4344
|
+
return !isSecretName(name);
|
|
4345
|
+
}
|
|
4346
|
+
function resolveChildEnv(options = {}) {
|
|
4347
|
+
const parent = options.parent ?? process.env;
|
|
4348
|
+
const policy = options.policy ?? "inherit-scrubbed";
|
|
4349
|
+
const base = {};
|
|
4350
|
+
for (const [name, value] of Object.entries(parent)) {
|
|
4351
|
+
if (value !== void 0 && inheritsUnderPolicy(name, policy)) base[name] = value;
|
|
4352
|
+
}
|
|
4353
|
+
for (const [name, value] of Object.entries(options.overrides ?? {})) {
|
|
4354
|
+
base[name] = value;
|
|
4355
|
+
}
|
|
4356
|
+
return base;
|
|
4357
|
+
}
|
|
4358
|
+
var SECRET_PATTERNS, CORE_VARS;
|
|
4359
|
+
var init_env_policy = __esm({
|
|
4360
|
+
"src/internal/runtime/lifecycle/env-policy.ts"() {
|
|
4361
|
+
SECRET_PATTERNS = [
|
|
4362
|
+
/KEY/i,
|
|
4363
|
+
/SECRET/i,
|
|
4364
|
+
/TOKEN/i,
|
|
4365
|
+
/PASSWORD/i,
|
|
4366
|
+
/PASSWD/i,
|
|
4367
|
+
/PASSPHRASE/i,
|
|
4368
|
+
/[_-]PWD/i,
|
|
4369
|
+
/CREDENTIAL/i,
|
|
4370
|
+
/PRIVATE/i,
|
|
4371
|
+
/_AUTH/i
|
|
4372
|
+
];
|
|
4373
|
+
CORE_VARS = [
|
|
4374
|
+
"PATH",
|
|
4375
|
+
"HOME",
|
|
4376
|
+
"SHELL",
|
|
4377
|
+
"LANG",
|
|
4378
|
+
"LC_ALL",
|
|
4379
|
+
"LC_CTYPE",
|
|
4380
|
+
"TMPDIR",
|
|
4381
|
+
"TMP",
|
|
4382
|
+
"TEMP",
|
|
4383
|
+
"USER",
|
|
4384
|
+
"LOGNAME"
|
|
4385
|
+
];
|
|
4386
|
+
}
|
|
4387
|
+
});
|
|
4172
4388
|
function spawnAndCollect(options) {
|
|
4173
4389
|
return new Promise((resolve3) => {
|
|
4174
4390
|
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
4175
4391
|
const spawnOptions = {
|
|
4176
4392
|
cwd: options.cwd,
|
|
4177
|
-
|
|
4393
|
+
// #54 — scrub secret-like parent env by default; `options.env` still wins.
|
|
4394
|
+
env: resolveChildEnv({ policy: options.envPolicy, overrides: options.env })
|
|
4178
4395
|
};
|
|
4179
4396
|
const child = child_process.spawn(options.command, options.args ?? [], spawnOptions);
|
|
4180
4397
|
let stdout = "";
|
|
@@ -4215,6 +4432,7 @@ function spawnAndCollect(options) {
|
|
|
4215
4432
|
}
|
|
4216
4433
|
var init_spawn_collect = __esm({
|
|
4217
4434
|
"src/internal/runtime/lifecycle/spawn-collect.ts"() {
|
|
4435
|
+
init_env_policy();
|
|
4218
4436
|
}
|
|
4219
4437
|
});
|
|
4220
4438
|
|
|
@@ -4403,15 +4621,24 @@ function warnMalformed(agentId, line) {
|
|
|
4403
4621
|
`
|
|
4404
4622
|
);
|
|
4405
4623
|
}
|
|
4624
|
+
function hydrateSessionLine(parsed) {
|
|
4625
|
+
if (typeof parsed.text !== "string" || parsed.role === void 0) return void 0;
|
|
4626
|
+
if (parsed.role === "user" || parsed.role === "assistant") {
|
|
4627
|
+
return { role: parsed.role, text: parsed.text };
|
|
4628
|
+
}
|
|
4629
|
+
if (parsed.role === "tool_call" || parsed.role === "tool_result") {
|
|
4630
|
+
const label = parsed.role === "tool_call" ? "tool call" : "tool result";
|
|
4631
|
+
return { role: "assistant", text: `[${label}] ${parsed.text}` };
|
|
4632
|
+
}
|
|
4633
|
+
return void 0;
|
|
4634
|
+
}
|
|
4406
4635
|
async function readSessionFile(cwd, agentId) {
|
|
4407
4636
|
const lines = await readJsonlLines(cwd, agentId);
|
|
4408
4637
|
const messages = [];
|
|
4409
4638
|
for (const line of lines) {
|
|
4410
4639
|
try {
|
|
4411
|
-
const
|
|
4412
|
-
if (
|
|
4413
|
-
messages.push({ role: parsed.role, text: parsed.text });
|
|
4414
|
-
}
|
|
4640
|
+
const msg = hydrateSessionLine(JSON.parse(line));
|
|
4641
|
+
if (msg !== void 0) messages.push(msg);
|
|
4415
4642
|
} catch {
|
|
4416
4643
|
warnMalformed(agentId, line);
|
|
4417
4644
|
}
|
|
@@ -4438,29 +4665,70 @@ async function readAllPersistedMessages(cwd, agentId) {
|
|
|
4438
4665
|
return messages;
|
|
4439
4666
|
}
|
|
4440
4667
|
async function appendAnyPersistedMessage(cwd, agentId, record) {
|
|
4668
|
+
await appendPersistedMessages(cwd, agentId, [record]);
|
|
4669
|
+
}
|
|
4670
|
+
async function appendPersistedMessages(cwd, agentId, records) {
|
|
4671
|
+
if (records.length === 0) return;
|
|
4441
4672
|
const path$1 = sessionFilePath(cwd, agentId);
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4673
|
+
const payload = records.map((r) => `${redactSecrets(JSON.stringify(r))}
|
|
4674
|
+
`).join("");
|
|
4675
|
+
const dir = path.dirname(path$1);
|
|
4676
|
+
let written = false;
|
|
4677
|
+
const attempt = async () => {
|
|
4678
|
+
await promises.mkdir(dir, { recursive: true });
|
|
4679
|
+
await withFileLock(path$1, async () => {
|
|
4680
|
+
await promises.appendFile(path$1, payload, "utf8");
|
|
4681
|
+
written = true;
|
|
4682
|
+
});
|
|
4683
|
+
};
|
|
4684
|
+
try {
|
|
4685
|
+
await attempt();
|
|
4686
|
+
} catch (cause) {
|
|
4687
|
+
if (written || cause.code !== "ENOENT") throw cause;
|
|
4688
|
+
await attempt();
|
|
4689
|
+
}
|
|
4690
|
+
}
|
|
4691
|
+
async function rewriteLockedSession(path, transform) {
|
|
4692
|
+
await withFileLock(path, async () => {
|
|
4693
|
+
let raw;
|
|
4694
|
+
try {
|
|
4695
|
+
raw = await promises.readFile(path, "utf8");
|
|
4696
|
+
} catch {
|
|
4697
|
+
return;
|
|
4698
|
+
}
|
|
4699
|
+
const lines = raw.split("\n").filter((line) => line.length > 0);
|
|
4700
|
+
const next = transform(lines);
|
|
4701
|
+
if (next === void 0) return;
|
|
4702
|
+
await replaceFileAtomic(path, next);
|
|
4703
|
+
});
|
|
4445
4704
|
}
|
|
4446
4705
|
async function compactSessionFile(cwd, agentId, maxTurns) {
|
|
4447
4706
|
const path = sessionFilePath(cwd, agentId);
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
const
|
|
4707
|
+
if (!fs.existsSync(path)) return;
|
|
4708
|
+
await rewriteLockedSession(
|
|
4709
|
+
path,
|
|
4710
|
+
(lines) => lines.length <= maxTurns * 2 ? void 0 : `${lines.slice(-maxTurns).join("\n")}
|
|
4711
|
+
`
|
|
4712
|
+
);
|
|
4713
|
+
}
|
|
4714
|
+
async function truncateSessionTo(cwd, agentId, keepCount) {
|
|
4715
|
+
const path = sessionFilePath(cwd, agentId);
|
|
4716
|
+
if (!fs.existsSync(path)) return 0;
|
|
4717
|
+
let kept = 0;
|
|
4718
|
+
await rewriteLockedSession(path, (lines) => {
|
|
4719
|
+
const keep = Math.max(0, Math.min(keepCount, lines.length));
|
|
4720
|
+
kept = keep;
|
|
4721
|
+
if (keep === lines.length) return void 0;
|
|
4722
|
+
return keep === 0 ? "" : `${lines.slice(0, keep).join("\n")}
|
|
4457
4723
|
`;
|
|
4458
|
-
|
|
4724
|
+
});
|
|
4725
|
+
return kept;
|
|
4459
4726
|
}
|
|
4460
4727
|
var VALID_ROLES;
|
|
4461
4728
|
var init_agent_session_store = __esm({
|
|
4462
4729
|
"src/internal/runtime/session/agent-session-store.ts"() {
|
|
4463
4730
|
init_atomic_write();
|
|
4731
|
+
init_file_lock();
|
|
4464
4732
|
init_security();
|
|
4465
4733
|
VALID_ROLES = /* @__PURE__ */ new Set([
|
|
4466
4734
|
"user",
|
|
@@ -4471,6 +4739,18 @@ var init_agent_session_store = __esm({
|
|
|
4471
4739
|
]);
|
|
4472
4740
|
}
|
|
4473
4741
|
});
|
|
4742
|
+
|
|
4743
|
+
// src/internal/persistence/pagination.ts
|
|
4744
|
+
function paginate(items, opts) {
|
|
4745
|
+
if (opts === void 0 || opts.offset === void 0 && opts.limit === void 0) return items;
|
|
4746
|
+
const start = Math.max(0, opts.offset ?? 0);
|
|
4747
|
+
const end = opts.limit === void 0 ? items.length : start + Math.max(0, opts.limit);
|
|
4748
|
+
return items.slice(start, end);
|
|
4749
|
+
}
|
|
4750
|
+
var init_pagination = __esm({
|
|
4751
|
+
"src/internal/persistence/pagination.ts"() {
|
|
4752
|
+
}
|
|
4753
|
+
});
|
|
4474
4754
|
function toStoredMessage(record) {
|
|
4475
4755
|
return {
|
|
4476
4756
|
role: record.role,
|
|
@@ -4478,11 +4758,15 @@ function toStoredMessage(record) {
|
|
|
4478
4758
|
at: record.at
|
|
4479
4759
|
};
|
|
4480
4760
|
}
|
|
4761
|
+
function toRecord(message) {
|
|
4762
|
+
return { role: message.role, text: message.content, at: message.at ?? Date.now() };
|
|
4763
|
+
}
|
|
4481
4764
|
var FileSystemConversationStorage;
|
|
4482
4765
|
var init_conversation_storage_fs = __esm({
|
|
4483
4766
|
"src/internal/persistence/conversation-storage-fs.ts"() {
|
|
4484
4767
|
init_agent_session_store();
|
|
4485
4768
|
init_security();
|
|
4769
|
+
init_pagination();
|
|
4486
4770
|
FileSystemConversationStorage = class {
|
|
4487
4771
|
#root;
|
|
4488
4772
|
constructor(opts = {}) {
|
|
@@ -4492,23 +4776,31 @@ var init_conversation_storage_fs = __esm({
|
|
|
4492
4776
|
get root() {
|
|
4493
4777
|
return this.#root;
|
|
4494
4778
|
}
|
|
4495
|
-
async getMessages(conversationId) {
|
|
4779
|
+
async getMessages(conversationId, opts) {
|
|
4496
4780
|
const records = await readAllPersistedMessages(this.#root, conversationId);
|
|
4497
|
-
|
|
4781
|
+
const all = records.map(toStoredMessage);
|
|
4782
|
+
return paginate(all, opts);
|
|
4498
4783
|
}
|
|
4499
4784
|
async appendMessage(conversationId, message) {
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4785
|
+
await appendAnyPersistedMessage(this.#root, conversationId, toRecord(message));
|
|
4786
|
+
}
|
|
4787
|
+
async appendMessages(conversationId, messages) {
|
|
4788
|
+
await appendPersistedMessages(this.#root, conversationId, messages.map(toRecord));
|
|
4789
|
+
}
|
|
4790
|
+
async truncateConversation(conversationId, keepCount) {
|
|
4791
|
+
return truncateSessionTo(this.#root, conversationId, keepCount);
|
|
4506
4792
|
}
|
|
4507
4793
|
async deleteConversation(conversationId) {
|
|
4508
4794
|
const safe2 = sanitizeIdentifier(conversationId, { maxLen: 128 });
|
|
4509
4795
|
const dirPath = safePathJoin(this.#root, ".theokit", "agents", safe2);
|
|
4510
4796
|
await promises.rm(dirPath, { recursive: true, force: true });
|
|
4511
4797
|
}
|
|
4798
|
+
async deleteScope(prefix) {
|
|
4799
|
+
const ids = await this.listConversationIds();
|
|
4800
|
+
const matching = ids.filter((id) => id.startsWith(prefix));
|
|
4801
|
+
for (const id of matching) await this.deleteConversation(id);
|
|
4802
|
+
return matching.length;
|
|
4803
|
+
}
|
|
4512
4804
|
async listConversationIds(opts = {}) {
|
|
4513
4805
|
const agentsRoot = safePathJoin(this.#root, ".theokit", "agents");
|
|
4514
4806
|
let entries;
|
|
@@ -4605,12 +4897,19 @@ async function readPersistedForCache(adapter, agentId) {
|
|
|
4605
4897
|
const records = await adapter.getMessages(agentId);
|
|
4606
4898
|
const out = [];
|
|
4607
4899
|
for (const r of records) {
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
}
|
|
4900
|
+
const folded = foldStoredToSession(r);
|
|
4901
|
+
if (folded !== void 0) out.push(folded);
|
|
4611
4902
|
}
|
|
4612
4903
|
return out;
|
|
4613
4904
|
}
|
|
4905
|
+
function foldStoredToSession(r) {
|
|
4906
|
+
if (r.role === "user" || r.role === "assistant") return { role: r.role, text: r.content };
|
|
4907
|
+
if (r.role === "tool_call" || r.role === "tool_result") {
|
|
4908
|
+
const label = r.role === "tool_call" ? "tool call" : "tool result";
|
|
4909
|
+
return { role: "assistant", text: `[${label}] ${r.content}` };
|
|
4910
|
+
}
|
|
4911
|
+
return void 0;
|
|
4912
|
+
}
|
|
4614
4913
|
async function flushSessionWrites() {
|
|
4615
4914
|
while (pendingAppends.size > 0) {
|
|
4616
4915
|
const all = Array.from(pendingAppends.values());
|
|
@@ -7181,11 +7480,32 @@ function reasoningEffortFromParams(params) {
|
|
|
7181
7480
|
const thinking = params?.find((p) => p.id === "thinking");
|
|
7182
7481
|
return thinking !== void 0 && thinking.value.length > 0 ? thinking.value : void 0;
|
|
7183
7482
|
}
|
|
7483
|
+
function emitLlmMetrics(inputs, result, startAt) {
|
|
7484
|
+
inputs.telemetry?.recordHistogram(HISTOGRAM_NAMES.LLM_CALL_DURATION_MS, Date.now() - startAt, {
|
|
7485
|
+
provider: inputs.llm.name
|
|
7486
|
+
});
|
|
7487
|
+
if (result.inputTokens === void 0 && result.outputTokens === void 0) {
|
|
7488
|
+
inputs.telemetry?.recordHistogram(HISTOGRAM_NAMES.LLM_USAGE_MISSING, 1, {
|
|
7489
|
+
provider: inputs.llm.name
|
|
7490
|
+
});
|
|
7491
|
+
process.stderr.write(
|
|
7492
|
+
`[theokit-sdk] llm usage missing from ${inputs.llm.name} finish \u2014 budget may undercount
|
|
7493
|
+
`
|
|
7494
|
+
);
|
|
7495
|
+
return;
|
|
7496
|
+
}
|
|
7497
|
+
inputs.telemetry?.recordHistogram(
|
|
7498
|
+
HISTOGRAM_NAMES.LLM_TOKENS,
|
|
7499
|
+
(result.inputTokens ?? 0) + (result.outputTokens ?? 0),
|
|
7500
|
+
{ provider: inputs.llm.name }
|
|
7501
|
+
);
|
|
7502
|
+
}
|
|
7184
7503
|
async function streamLlmTurn(inputs, ctx) {
|
|
7185
|
-
const llmSpan = inputs.telemetry?.
|
|
7504
|
+
const llmSpan = inputs.telemetry?.startChildSpan(ctx.sendSpan, "llm.call", {
|
|
7186
7505
|
"model.id": inputs.model.id ?? "auto",
|
|
7187
7506
|
provider: inputs.llm.name
|
|
7188
7507
|
});
|
|
7508
|
+
const startAt = Date.now();
|
|
7189
7509
|
const signal = inputs.signal ?? new AbortController().signal;
|
|
7190
7510
|
const generator = inputs.llm.stream(
|
|
7191
7511
|
{
|
|
@@ -7228,6 +7548,7 @@ async function streamLlmTurn(inputs, ctx) {
|
|
|
7228
7548
|
inputTokens: result.inputTokens ?? 0,
|
|
7229
7549
|
outputTokens: result.outputTokens ?? 0
|
|
7230
7550
|
});
|
|
7551
|
+
emitLlmMetrics(inputs, result, startAt);
|
|
7231
7552
|
llmSpan?.end();
|
|
7232
7553
|
const stripped = stripThinkBlocks(collected.accumulatedText);
|
|
7233
7554
|
return {
|
|
@@ -7324,6 +7645,7 @@ async function emitReasoningDeltaCallback(inputs, text) {
|
|
|
7324
7645
|
var init_loop_llm_stream = __esm({
|
|
7325
7646
|
"src/internal/agent-loop/loop-llm-stream.ts"() {
|
|
7326
7647
|
init_safe_call();
|
|
7648
|
+
init_span_names();
|
|
7327
7649
|
init_strip_think();
|
|
7328
7650
|
init_message_builders();
|
|
7329
7651
|
}
|
|
@@ -7542,21 +7864,21 @@ async function executeTool(inputs, resolved, call) {
|
|
|
7542
7864
|
}
|
|
7543
7865
|
if (resolved.origin === "shell") return runShellTool(inputs, call);
|
|
7544
7866
|
if (resolved.origin === "memory") return runMemoryTool(resolved, call);
|
|
7545
|
-
if (resolved.origin === "custom") return runCustomTool(resolved, call);
|
|
7867
|
+
if (resolved.origin === "custom") return runCustomTool(resolved, call, inputs.signal);
|
|
7546
7868
|
return runMcpTool(inputs, resolved, call);
|
|
7547
7869
|
}
|
|
7548
7870
|
async function runMemoryTool(resolved, call) {
|
|
7549
7871
|
return runHandlerTool("memory", resolved.memoryHandler, call);
|
|
7550
7872
|
}
|
|
7551
|
-
async function runCustomTool(resolved, call) {
|
|
7552
|
-
return runHandlerTool("custom", resolved.customHandler, call);
|
|
7873
|
+
async function runCustomTool(resolved, call, signal) {
|
|
7874
|
+
return runHandlerTool("custom", resolved.customHandler, call, signal);
|
|
7553
7875
|
}
|
|
7554
|
-
async function runHandlerTool(kind, handler, call) {
|
|
7876
|
+
async function runHandlerTool(kind, handler, call, signal) {
|
|
7555
7877
|
if (handler === void 0) {
|
|
7556
7878
|
return { stdout: "", stderr: `${kind} tool ${call.name} has no handler`, exitCode: 127 };
|
|
7557
7879
|
}
|
|
7558
7880
|
try {
|
|
7559
|
-
const stdout = await handler(call.input);
|
|
7881
|
+
const stdout = await handler(call.input, { signal });
|
|
7560
7882
|
return { stdout, stderr: "", exitCode: 0 };
|
|
7561
7883
|
} catch (cause) {
|
|
7562
7884
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
@@ -7607,22 +7929,71 @@ var init_tool_executors = __esm({
|
|
|
7607
7929
|
}
|
|
7608
7930
|
});
|
|
7609
7931
|
|
|
7932
|
+
// src/internal/agent-loop/tool-timeout.ts
|
|
7933
|
+
function abortedResult(signal) {
|
|
7934
|
+
const reason = signal.reason;
|
|
7935
|
+
const timedOut = reason?.name === "TimeoutError";
|
|
7936
|
+
return {
|
|
7937
|
+
stdout: "",
|
|
7938
|
+
stderr: timedOut ? "tool execution timed out" : "tool execution aborted",
|
|
7939
|
+
exitCode: TOOL_ABORTED_EXIT
|
|
7940
|
+
};
|
|
7941
|
+
}
|
|
7942
|
+
function raceToolExecution(exec, opts) {
|
|
7943
|
+
const { signal, timeoutMs } = opts;
|
|
7944
|
+
if (signal === void 0 && timeoutMs === void 0) return exec;
|
|
7945
|
+
const signals = [];
|
|
7946
|
+
if (signal !== void 0) signals.push(signal);
|
|
7947
|
+
if (timeoutMs !== void 0) signals.push(AbortSignal.timeout(timeoutMs));
|
|
7948
|
+
const merged = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
7949
|
+
if (merged.aborted) return Promise.resolve(abortedResult(merged));
|
|
7950
|
+
return new Promise((resolve3, reject) => {
|
|
7951
|
+
let settled = false;
|
|
7952
|
+
const onAbort = () => {
|
|
7953
|
+
if (settled) return;
|
|
7954
|
+
settled = true;
|
|
7955
|
+
resolve3(abortedResult(merged));
|
|
7956
|
+
};
|
|
7957
|
+
merged.addEventListener("abort", onAbort, { once: true });
|
|
7958
|
+
exec.then(
|
|
7959
|
+
(r) => {
|
|
7960
|
+
if (settled) return;
|
|
7961
|
+
settled = true;
|
|
7962
|
+
merged.removeEventListener("abort", onAbort);
|
|
7963
|
+
resolve3(r);
|
|
7964
|
+
},
|
|
7965
|
+
(e) => {
|
|
7966
|
+
if (settled) return;
|
|
7967
|
+
settled = true;
|
|
7968
|
+
merged.removeEventListener("abort", onAbort);
|
|
7969
|
+
reject(e);
|
|
7970
|
+
}
|
|
7971
|
+
);
|
|
7972
|
+
});
|
|
7973
|
+
}
|
|
7974
|
+
var TOOL_ABORTED_EXIT;
|
|
7975
|
+
var init_tool_timeout = __esm({
|
|
7976
|
+
"src/internal/agent-loop/tool-timeout.ts"() {
|
|
7977
|
+
TOOL_ABORTED_EXIT = 124;
|
|
7978
|
+
}
|
|
7979
|
+
});
|
|
7980
|
+
|
|
7610
7981
|
// src/internal/agent-loop/tool-dispatch.ts
|
|
7611
|
-
async function dispatchTools(inputs, tools, toolCalls, events) {
|
|
7982
|
+
async function dispatchTools(inputs, tools, toolCalls, events, parentSpan) {
|
|
7612
7983
|
const maxConcurrent = inputs.maxConcurrentTools ?? 4;
|
|
7613
7984
|
return mapWithConcurrency(
|
|
7614
7985
|
toolCalls,
|
|
7615
7986
|
maxConcurrent,
|
|
7616
|
-
(call) => dispatchSingleCall(inputs, tools, call, events)
|
|
7987
|
+
(call) => dispatchSingleCall(inputs, tools, call, events, parentSpan)
|
|
7617
7988
|
);
|
|
7618
7989
|
}
|
|
7619
|
-
async function dispatchSingleCall(inputs, tools, call, events) {
|
|
7990
|
+
async function dispatchSingleCall(inputs, tools, call, events, parentSpan) {
|
|
7620
7991
|
const { call: workingCall, repairs } = applyRepairAndExtractCall(tools, call);
|
|
7621
7992
|
const callId = generateCallId();
|
|
7622
7993
|
const forkVeto = vetoFromForkWhitelist(inputs, workingCall, callId, events);
|
|
7623
7994
|
if (forkVeto !== void 0) return forkVeto;
|
|
7624
7995
|
const resolved = tools.find((tool) => tool.name === workingCall.name);
|
|
7625
|
-
const toolSpan = startToolCallSpan(inputs, workingCall, resolved, callId, repairs);
|
|
7996
|
+
const toolSpan = startToolCallSpan(inputs, workingCall, resolved, callId, repairs, parentSpan);
|
|
7626
7997
|
events.push(buildToolUseRunning(inputs, callId, workingCall));
|
|
7627
7998
|
const pluginVeto = await vetoFromPluginPreHook(inputs, workingCall, callId, events);
|
|
7628
7999
|
if (pluginVeto !== void 0) {
|
|
@@ -7639,6 +8010,13 @@ async function dispatchSingleCall(inputs, tools, call, events) {
|
|
|
7639
8010
|
return fileVeto;
|
|
7640
8011
|
}
|
|
7641
8012
|
const result = await runToolWithLifecycle(inputs, resolved, workingCall, callId);
|
|
8013
|
+
await inputs.pluginManager?.runPostToolCallHooks({
|
|
8014
|
+
name: workingCall.name,
|
|
8015
|
+
args: workingCall.input,
|
|
8016
|
+
result: { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode },
|
|
8017
|
+
agentId: inputs.agentId,
|
|
8018
|
+
runId: inputs.runId
|
|
8019
|
+
});
|
|
7642
8020
|
return finalizeSpanAndPostHook(inputs, workingCall, callId, result, events, toolSpan);
|
|
7643
8021
|
}
|
|
7644
8022
|
function applyRepairAndExtractCall(tools, call) {
|
|
@@ -7671,8 +8049,8 @@ function vetoFromForkWhitelist(inputs, call, callId, events) {
|
|
|
7671
8049
|
content: `Tool blocked by fork whitelist: ${whitelistDecision.reason}`
|
|
7672
8050
|
};
|
|
7673
8051
|
}
|
|
7674
|
-
function startToolCallSpan(inputs, call, resolved, callId, repairs) {
|
|
7675
|
-
const toolSpan = inputs.telemetry?.
|
|
8052
|
+
function startToolCallSpan(inputs, call, resolved, callId, repairs, parentSpan) {
|
|
8053
|
+
const toolSpan = inputs.telemetry?.startChildSpan(parentSpan, "tool.call", {
|
|
7676
8054
|
"tool.name": call.name,
|
|
7677
8055
|
"tool.origin": resolved?.origin ?? "unknown",
|
|
7678
8056
|
callId
|
|
@@ -7736,8 +8114,14 @@ async function runToolWithLifecycle(inputs, resolved, call, callId) {
|
|
|
7736
8114
|
conversationId: inputs.agentId,
|
|
7737
8115
|
callId
|
|
7738
8116
|
});
|
|
7739
|
-
const result = await executeTool(inputs, resolved, call)
|
|
8117
|
+
const result = await raceToolExecution(executeTool(inputs, resolved, call), {
|
|
8118
|
+
signal: inputs.signal,
|
|
8119
|
+
timeoutMs: inputs.perToolTimeoutMs
|
|
8120
|
+
});
|
|
7740
8121
|
const durationMs = Date.now() - startAt;
|
|
8122
|
+
inputs.telemetry?.recordHistogram(HISTOGRAM_NAMES.TOOL_CALL_DURATION_MS, durationMs, {
|
|
8123
|
+
"tool.name": call.name
|
|
8124
|
+
});
|
|
7741
8125
|
if (result.exitCode !== void 0 && result.exitCode !== 0 && result.exitCode !== null) {
|
|
7742
8126
|
await safeEmitToolHook(inputs.onToolError, {
|
|
7743
8127
|
toolName: call.name,
|
|
@@ -7840,8 +8224,44 @@ var init_tool_dispatch = __esm({
|
|
|
7840
8224
|
init_ids();
|
|
7841
8225
|
init_async_local_storage();
|
|
7842
8226
|
init_map_with_concurrency();
|
|
8227
|
+
init_span_names();
|
|
7843
8228
|
init_repair_middleware();
|
|
7844
8229
|
init_tool_executors();
|
|
8230
|
+
init_tool_timeout();
|
|
8231
|
+
}
|
|
8232
|
+
});
|
|
8233
|
+
|
|
8234
|
+
// src/internal/agent-loop/tool-result-guard.ts
|
|
8235
|
+
function guardText(content, opts) {
|
|
8236
|
+
let out = content;
|
|
8237
|
+
if (opts.redactPii === true) {
|
|
8238
|
+
for (const re of PII_PATTERNS) out = out.replace(re, "[REDACTED]");
|
|
8239
|
+
}
|
|
8240
|
+
if (opts.delimit === true) {
|
|
8241
|
+
const safe2 = out.split(CLOSE).join("</ untrusted-tool-output>");
|
|
8242
|
+
out = `${OPEN}
|
|
8243
|
+
${safe2}
|
|
8244
|
+
${CLOSE}`;
|
|
8245
|
+
}
|
|
8246
|
+
return out;
|
|
8247
|
+
}
|
|
8248
|
+
function applyToolResultGuard(parts, opts) {
|
|
8249
|
+
if (opts.delimit !== true && opts.redactPii !== true) return parts;
|
|
8250
|
+
return parts.map(
|
|
8251
|
+
(p) => p.type === "tool_result" ? { ...p, content: guardText(p.content, opts) } : p
|
|
8252
|
+
);
|
|
8253
|
+
}
|
|
8254
|
+
var OPEN, CLOSE, PII_PATTERNS;
|
|
8255
|
+
var init_tool_result_guard = __esm({
|
|
8256
|
+
"src/internal/agent-loop/tool-result-guard.ts"() {
|
|
8257
|
+
OPEN = "<untrusted-tool-output>";
|
|
8258
|
+
CLOSE = "</untrusted-tool-output>";
|
|
8259
|
+
PII_PATTERNS = [
|
|
8260
|
+
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
|
|
8261
|
+
// email
|
|
8262
|
+
/\b(?:\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/g
|
|
8263
|
+
// phone
|
|
8264
|
+
];
|
|
7845
8265
|
}
|
|
7846
8266
|
});
|
|
7847
8267
|
|
|
@@ -8225,6 +8645,11 @@ async function runAgentLoop(inputs) {
|
|
|
8225
8645
|
try {
|
|
8226
8646
|
const ctx = await initLoopContext(inputs);
|
|
8227
8647
|
ctxRef = ctx;
|
|
8648
|
+
ctx.sendSpan = sendSpan;
|
|
8649
|
+
await inputs.pluginManager?.runOnSessionStartHooks({
|
|
8650
|
+
agentId: inputs.agentId,
|
|
8651
|
+
runId: inputs.runId
|
|
8652
|
+
});
|
|
8228
8653
|
const budget = inputs.budget ?? new IterationBudget({ maxIterations: inputs.maxIterations ?? 8 });
|
|
8229
8654
|
let lastTurnDecision;
|
|
8230
8655
|
while (budget.shouldContinue()) {
|
|
@@ -8252,6 +8677,7 @@ async function runAgentLoop(inputs) {
|
|
|
8252
8677
|
}
|
|
8253
8678
|
budget.consume();
|
|
8254
8679
|
inputs.budgetTracker?.nextIteration?.();
|
|
8680
|
+
if (inputs.signal?.aborted === true) break;
|
|
8255
8681
|
}
|
|
8256
8682
|
if (lastTurnDecision === "continue" && budget.shouldContinue() === false) {
|
|
8257
8683
|
ctx.stoppedAtIterationLimit = true;
|
|
@@ -8291,6 +8717,10 @@ async function runAgentLoop(inputs) {
|
|
|
8291
8717
|
...ctx.stoppedByDoomLoop === true ? { stoppedByDoomLoop: true } : {}
|
|
8292
8718
|
};
|
|
8293
8719
|
} finally {
|
|
8720
|
+
await inputs.pluginManager?.runOnSessionEndHooks({
|
|
8721
|
+
agentId: inputs.agentId,
|
|
8722
|
+
runId: inputs.runId
|
|
8723
|
+
});
|
|
8294
8724
|
if (ctxRef !== void 0 && ctxRef.memoryProviderHandle !== void 0 && inputs.memoryProvider !== void 0) {
|
|
8295
8725
|
try {
|
|
8296
8726
|
await inputs.memoryProvider.dispose(ctxRef.memoryProviderHandle);
|
|
@@ -8394,7 +8824,10 @@ async function finishOrReflect(inputs, ctx, llmOutput) {
|
|
|
8394
8824
|
return "done";
|
|
8395
8825
|
}
|
|
8396
8826
|
async function runIteration(inputs, ctx) {
|
|
8827
|
+
const hookCtx = { agentId: inputs.agentId, runId: inputs.runId };
|
|
8828
|
+
await inputs.pluginManager?.runPreLlmCallHooks(hookCtx);
|
|
8397
8829
|
const llmOutput = await streamLlmTurn(inputs, ctx);
|
|
8830
|
+
await inputs.pluginManager?.runPostLlmCallHooks(hookCtx);
|
|
8398
8831
|
accumulateUsage(ctx.usage, llmOutput);
|
|
8399
8832
|
if (inputs.budgetTracker !== void 0) {
|
|
8400
8833
|
const modelId = inputs.model.id ?? "auto";
|
|
@@ -8420,6 +8853,13 @@ async function runIteration(inputs, ctx) {
|
|
|
8420
8853
|
}
|
|
8421
8854
|
return continueOrTerminate(inputs, ctx, llmOutput);
|
|
8422
8855
|
}
|
|
8856
|
+
async function transformLlmOutputText(inputs, text, ctx) {
|
|
8857
|
+
return inputs.pluginManager !== void 0 ? inputs.pluginManager.runTransformLlmOutputHooks(text, ctx) : text;
|
|
8858
|
+
}
|
|
8859
|
+
async function guardAndTransformToolResults(inputs, raw, ctx) {
|
|
8860
|
+
const guarded = inputs.toolResultGuard !== void 0 ? applyToolResultGuard(raw, inputs.toolResultGuard) : raw;
|
|
8861
|
+
return inputs.pluginManager !== void 0 ? inputs.pluginManager.runTransformToolResultHooks(guarded, ctx) : guarded;
|
|
8862
|
+
}
|
|
8423
8863
|
async function continueOrTerminate(inputs, ctx, llmOutput) {
|
|
8424
8864
|
if (llmOutput.errored) return "error";
|
|
8425
8865
|
if (llmOutput.text.length > 0) {
|
|
@@ -8428,8 +8868,18 @@ async function continueOrTerminate(inputs, ctx, llmOutput) {
|
|
|
8428
8868
|
if (llmOutput.stopReason !== "tool_use" || llmOutput.toolCalls.length === 0) {
|
|
8429
8869
|
return finishOrReflect(inputs, ctx, llmOutput);
|
|
8430
8870
|
}
|
|
8431
|
-
|
|
8432
|
-
const
|
|
8871
|
+
const tCtx = { agentId: inputs.agentId, runId: inputs.runId };
|
|
8872
|
+
const outText = await transformLlmOutputText(inputs, llmOutput.text, tCtx);
|
|
8873
|
+
ctx.messages.push(buildAssistantTurn(outText, llmOutput.toolCalls));
|
|
8874
|
+
const rawResults = await dispatchTools(
|
|
8875
|
+
inputs,
|
|
8876
|
+
ctx.tools,
|
|
8877
|
+
llmOutput.toolCalls,
|
|
8878
|
+
ctx.events,
|
|
8879
|
+
ctx.sendSpan
|
|
8880
|
+
// M3 #64 — nest tool.call spans under agent.send
|
|
8881
|
+
);
|
|
8882
|
+
const toolResults = await guardAndTransformToolResults(inputs, rawResults, tCtx);
|
|
8433
8883
|
ctx.messages.push({ role: "user", content: toolResults });
|
|
8434
8884
|
if (inputs.onStep !== void 0) {
|
|
8435
8885
|
const cb = inputs.onStep;
|
|
@@ -8479,6 +8929,7 @@ var init_loop = __esm({
|
|
|
8479
8929
|
init_loop_llm_stream();
|
|
8480
8930
|
init_message_builders();
|
|
8481
8931
|
init_tool_dispatch();
|
|
8932
|
+
init_tool_result_guard();
|
|
8482
8933
|
init_usage_and_cost();
|
|
8483
8934
|
MAX_NUDGE_ATTEMPTS = 2;
|
|
8484
8935
|
MAX_STOP_FEEDBACK_ATTEMPTS = 2;
|
|
@@ -8968,6 +9419,60 @@ var init_anthropic2 = __esm({
|
|
|
8968
9419
|
init_shared();
|
|
8969
9420
|
}
|
|
8970
9421
|
});
|
|
9422
|
+
function loadJsonrepair() {
|
|
9423
|
+
if (cachedJsonrepair === void 0) {
|
|
9424
|
+
const req = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
9425
|
+
cachedJsonrepair = req("jsonrepair").jsonrepair;
|
|
9426
|
+
}
|
|
9427
|
+
return cachedJsonrepair;
|
|
9428
|
+
}
|
|
9429
|
+
function isPlainObject(v) {
|
|
9430
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
9431
|
+
}
|
|
9432
|
+
function toFiniteNumber(raw) {
|
|
9433
|
+
if (raw === "") return void 0;
|
|
9434
|
+
const n = Number(raw);
|
|
9435
|
+
return Number.isFinite(n) && String(n) === raw ? n : void 0;
|
|
9436
|
+
}
|
|
9437
|
+
function tryJson(raw, repair) {
|
|
9438
|
+
const t = raw.trimStart();
|
|
9439
|
+
if (!(t.startsWith("{") || t.startsWith("["))) return void 0;
|
|
9440
|
+
try {
|
|
9441
|
+
return JSON.parse(repair ? loadJsonrepair()(t) : t);
|
|
9442
|
+
} catch {
|
|
9443
|
+
return void 0;
|
|
9444
|
+
}
|
|
9445
|
+
}
|
|
9446
|
+
function heuristicCoerce(raw, repairJson) {
|
|
9447
|
+
if (raw === "true") return true;
|
|
9448
|
+
if (raw === "false") return false;
|
|
9449
|
+
if (raw === "null") return null;
|
|
9450
|
+
const n = toFiniteNumber(raw);
|
|
9451
|
+
if (n !== void 0) return n;
|
|
9452
|
+
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
9453
|
+
return json === void 0 ? raw : json;
|
|
9454
|
+
}
|
|
9455
|
+
function coerceCandidates(raw, repairJson) {
|
|
9456
|
+
const out = [];
|
|
9457
|
+
if (raw === "true") out.push(true);
|
|
9458
|
+
else if (raw === "false") out.push(false);
|
|
9459
|
+
else if (raw === "null") out.push(null);
|
|
9460
|
+
const n = toFiniteNumber(raw);
|
|
9461
|
+
if (n !== void 0) out.push(n);
|
|
9462
|
+
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
9463
|
+
if (json !== void 0) out.push(json);
|
|
9464
|
+
out.push(raw);
|
|
9465
|
+
return out;
|
|
9466
|
+
}
|
|
9467
|
+
function objectShape(schema) {
|
|
9468
|
+
const shape = schema?.shape;
|
|
9469
|
+
return shape !== null && typeof shape === "object" ? shape : void 0;
|
|
9470
|
+
}
|
|
9471
|
+
var cachedJsonrepair;
|
|
9472
|
+
var init_coerce = __esm({
|
|
9473
|
+
"src/sanitize/coerce.ts"() {
|
|
9474
|
+
}
|
|
9475
|
+
});
|
|
8971
9476
|
|
|
8972
9477
|
// src/internal/llm/finish.ts
|
|
8973
9478
|
function collapseSystemText(system) {
|
|
@@ -8980,9 +9485,21 @@ function parseToolArguments(buffered) {
|
|
|
8980
9485
|
try {
|
|
8981
9486
|
return JSON.parse(buffered);
|
|
8982
9487
|
} catch {
|
|
9488
|
+
const repaired = tryJson(buffered, true);
|
|
9489
|
+
if (isPlainObject(repaired)) return repaired;
|
|
8983
9490
|
return { raw: buffered };
|
|
8984
9491
|
}
|
|
8985
9492
|
}
|
|
9493
|
+
function mapOpenAIFinish(reason) {
|
|
9494
|
+
switch (reason) {
|
|
9495
|
+
case "tool_calls":
|
|
9496
|
+
return "tool_use";
|
|
9497
|
+
case "length":
|
|
9498
|
+
return "max_tokens";
|
|
9499
|
+
default:
|
|
9500
|
+
return "end_turn";
|
|
9501
|
+
}
|
|
9502
|
+
}
|
|
8986
9503
|
function makeLlmFinish(state2) {
|
|
8987
9504
|
const finish = {
|
|
8988
9505
|
stopReason: state2.stopReason,
|
|
@@ -8998,6 +9515,7 @@ function makeLlmFinish(state2) {
|
|
|
8998
9515
|
}
|
|
8999
9516
|
var init_finish = __esm({
|
|
9000
9517
|
"src/internal/llm/finish.ts"() {
|
|
9518
|
+
init_coerce();
|
|
9001
9519
|
}
|
|
9002
9520
|
});
|
|
9003
9521
|
|
|
@@ -9441,6 +9959,16 @@ var init_credential_pool_types = __esm({
|
|
|
9441
9959
|
});
|
|
9442
9960
|
|
|
9443
9961
|
// src/internal/llm/retry.ts
|
|
9962
|
+
function computeBackoffMs(opts) {
|
|
9963
|
+
const base = opts.baseMs ?? DEFAULT_BASE_MS;
|
|
9964
|
+
const cap = opts.capMs ?? DEFAULT_CAP_MS;
|
|
9965
|
+
const rng = opts.rng ?? Math.random;
|
|
9966
|
+
if (opts.retryAfterMs !== void 0 && opts.retryAfterMs >= 0) {
|
|
9967
|
+
return Math.max(base, Math.min(cap, opts.retryAfterMs));
|
|
9968
|
+
}
|
|
9969
|
+
const ceiling = Math.min(cap, base * 2 ** opts.attempt);
|
|
9970
|
+
return Math.floor(rng() * ceiling);
|
|
9971
|
+
}
|
|
9444
9972
|
function sleepWithAbort(ms, signal) {
|
|
9445
9973
|
if (ms <= 0 || signal.aborted) return Promise.resolve();
|
|
9446
9974
|
return new Promise((resolve3) => {
|
|
@@ -9455,8 +9983,11 @@ function sleepWithAbort(ms, signal) {
|
|
|
9455
9983
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
9456
9984
|
});
|
|
9457
9985
|
}
|
|
9986
|
+
var DEFAULT_BASE_MS, DEFAULT_CAP_MS;
|
|
9458
9987
|
var init_retry = __esm({
|
|
9459
9988
|
"src/internal/llm/retry.ts"() {
|
|
9989
|
+
DEFAULT_BASE_MS = 500;
|
|
9990
|
+
DEFAULT_CAP_MS = 32e3;
|
|
9460
9991
|
}
|
|
9461
9992
|
});
|
|
9462
9993
|
|
|
@@ -10212,60 +10743,6 @@ var init_ollama_native = __esm({
|
|
|
10212
10743
|
ollamaSystemText = collapseSystemText;
|
|
10213
10744
|
}
|
|
10214
10745
|
});
|
|
10215
|
-
function loadJsonrepair() {
|
|
10216
|
-
if (cachedJsonrepair === void 0) {
|
|
10217
|
-
const req = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
10218
|
-
cachedJsonrepair = req("jsonrepair").jsonrepair;
|
|
10219
|
-
}
|
|
10220
|
-
return cachedJsonrepair;
|
|
10221
|
-
}
|
|
10222
|
-
function isPlainObject(v) {
|
|
10223
|
-
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
10224
|
-
}
|
|
10225
|
-
function toFiniteNumber(raw) {
|
|
10226
|
-
if (raw === "") return void 0;
|
|
10227
|
-
const n = Number(raw);
|
|
10228
|
-
return Number.isFinite(n) && String(n) === raw ? n : void 0;
|
|
10229
|
-
}
|
|
10230
|
-
function tryJson(raw, repair) {
|
|
10231
|
-
const t = raw.trimStart();
|
|
10232
|
-
if (!(t.startsWith("{") || t.startsWith("["))) return void 0;
|
|
10233
|
-
try {
|
|
10234
|
-
return JSON.parse(repair ? loadJsonrepair()(t) : t);
|
|
10235
|
-
} catch {
|
|
10236
|
-
return void 0;
|
|
10237
|
-
}
|
|
10238
|
-
}
|
|
10239
|
-
function heuristicCoerce(raw, repairJson) {
|
|
10240
|
-
if (raw === "true") return true;
|
|
10241
|
-
if (raw === "false") return false;
|
|
10242
|
-
if (raw === "null") return null;
|
|
10243
|
-
const n = toFiniteNumber(raw);
|
|
10244
|
-
if (n !== void 0) return n;
|
|
10245
|
-
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
10246
|
-
return json === void 0 ? raw : json;
|
|
10247
|
-
}
|
|
10248
|
-
function coerceCandidates(raw, repairJson) {
|
|
10249
|
-
const out = [];
|
|
10250
|
-
if (raw === "true") out.push(true);
|
|
10251
|
-
else if (raw === "false") out.push(false);
|
|
10252
|
-
else if (raw === "null") out.push(null);
|
|
10253
|
-
const n = toFiniteNumber(raw);
|
|
10254
|
-
if (n !== void 0) out.push(n);
|
|
10255
|
-
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
10256
|
-
if (json !== void 0) out.push(json);
|
|
10257
|
-
out.push(raw);
|
|
10258
|
-
return out;
|
|
10259
|
-
}
|
|
10260
|
-
function objectShape(schema) {
|
|
10261
|
-
const shape = schema?.shape;
|
|
10262
|
-
return shape !== null && typeof shape === "object" ? shape : void 0;
|
|
10263
|
-
}
|
|
10264
|
-
var cachedJsonrepair;
|
|
10265
|
-
var init_coerce = __esm({
|
|
10266
|
-
"src/sanitize/coerce.ts"() {
|
|
10267
|
-
}
|
|
10268
|
-
});
|
|
10269
10746
|
|
|
10270
10747
|
// src/sanitize/sanitize-tool-input.ts
|
|
10271
10748
|
function applyTrim(key, value, ctx) {
|
|
@@ -10446,18 +10923,6 @@ var init_hermes_tool_extract = __esm({
|
|
|
10446
10923
|
});
|
|
10447
10924
|
|
|
10448
10925
|
// src/internal/llm/openai.ts
|
|
10449
|
-
function mapOpenAIFinish(reason) {
|
|
10450
|
-
switch (reason) {
|
|
10451
|
-
case "tool_calls":
|
|
10452
|
-
return "tool_use";
|
|
10453
|
-
case "length":
|
|
10454
|
-
return "max_tokens";
|
|
10455
|
-
case "stop":
|
|
10456
|
-
return "end_turn";
|
|
10457
|
-
default:
|
|
10458
|
-
return "end_turn";
|
|
10459
|
-
}
|
|
10460
|
-
}
|
|
10461
10926
|
function applyReasoningRequest(body, effort, providerName) {
|
|
10462
10927
|
if (providerName === "openai") {
|
|
10463
10928
|
body.reasoning_effort = effort;
|
|
@@ -10554,6 +11019,7 @@ function assistantMessage(message) {
|
|
|
10554
11019
|
var OpenAIClient, OpenAIStreamAccumulator, openAISystemText;
|
|
10555
11020
|
var init_openai2 = __esm({
|
|
10556
11021
|
"src/internal/llm/openai.ts"() {
|
|
11022
|
+
init_errors();
|
|
10557
11023
|
init_ollama2();
|
|
10558
11024
|
init_openai_compatible();
|
|
10559
11025
|
init_finish();
|
|
@@ -10634,8 +11100,12 @@ var init_openai2 = __esm({
|
|
|
10634
11100
|
// model was actually given. Empty set (no tools) recovers nothing.
|
|
10635
11101
|
new Set(request.tools?.map((tool) => tool.name) ?? [])
|
|
10636
11102
|
);
|
|
11103
|
+
let sawDone = false;
|
|
10637
11104
|
for await (const record of parseSseStream(response.body, signal)) {
|
|
10638
|
-
if (record.data === "[DONE]")
|
|
11105
|
+
if (record.data === "[DONE]") {
|
|
11106
|
+
sawDone = true;
|
|
11107
|
+
break;
|
|
11108
|
+
}
|
|
10639
11109
|
let chunk;
|
|
10640
11110
|
try {
|
|
10641
11111
|
chunk = JSON.parse(record.data);
|
|
@@ -10655,6 +11125,11 @@ var init_openai2 = __esm({
|
|
|
10655
11125
|
const events = accumulator.consume(chunk);
|
|
10656
11126
|
for (const event of events) yield event;
|
|
10657
11127
|
}
|
|
11128
|
+
if (!sawDone && !accumulator.finishReasonSeen) {
|
|
11129
|
+
throw new NetworkError("SSE stream truncated (no finish_reason / [DONE])", {
|
|
11130
|
+
code: "stream_truncated"
|
|
11131
|
+
});
|
|
11132
|
+
}
|
|
10658
11133
|
const drainEvent = accumulator.finalizeHeldText();
|
|
10659
11134
|
if (drainEvent !== void 0) yield drainEvent;
|
|
10660
11135
|
return accumulator.finish();
|
|
@@ -10754,8 +11229,15 @@ var init_openai2 = __esm({
|
|
|
10754
11229
|
this.toolCalls.set(call.index, existing);
|
|
10755
11230
|
}
|
|
10756
11231
|
}
|
|
11232
|
+
/** M2 #61 — true once any chunk carried a non-null `finish_reason` (else a
|
|
11233
|
+
* stream ending without `[DONE]` is a truncation, not a clean end). */
|
|
11234
|
+
sawFinishReason = false;
|
|
11235
|
+
get finishReasonSeen() {
|
|
11236
|
+
return this.sawFinishReason;
|
|
11237
|
+
}
|
|
10757
11238
|
applyFinishReason(reason) {
|
|
10758
11239
|
if (reason === void 0 || reason === null) return;
|
|
11240
|
+
this.sawFinishReason = true;
|
|
10759
11241
|
this.stopReason = mapOpenAIFinish(reason);
|
|
10760
11242
|
}
|
|
10761
11243
|
finish() {
|
|
@@ -10804,6 +11286,53 @@ var init_openai2 = __esm({
|
|
|
10804
11286
|
}
|
|
10805
11287
|
});
|
|
10806
11288
|
|
|
11289
|
+
// src/internal/resilience/circuit-breaker.ts
|
|
11290
|
+
var DEFAULT_MAX_TIMEOUTS, DEFAULT_COOLDOWN_MS2, CircuitBreaker;
|
|
11291
|
+
var init_circuit_breaker = __esm({
|
|
11292
|
+
"src/internal/resilience/circuit-breaker.ts"() {
|
|
11293
|
+
DEFAULT_MAX_TIMEOUTS = 3;
|
|
11294
|
+
DEFAULT_COOLDOWN_MS2 = 6e4;
|
|
11295
|
+
CircuitBreaker = class {
|
|
11296
|
+
constructor(opts = {}) {
|
|
11297
|
+
this.opts = opts;
|
|
11298
|
+
}
|
|
11299
|
+
opts;
|
|
11300
|
+
states = /* @__PURE__ */ new Map();
|
|
11301
|
+
/** @returns true when the breaker is open and the call should be skipped. */
|
|
11302
|
+
shouldSkip(key) {
|
|
11303
|
+
const state2 = this.states.get(key);
|
|
11304
|
+
if (state2 === void 0) return false;
|
|
11305
|
+
if (state2.cooldownUntilMs === 0) return false;
|
|
11306
|
+
if (this.now() < state2.cooldownUntilMs) return true;
|
|
11307
|
+
state2.cooldownUntilMs = 0;
|
|
11308
|
+
state2.consecutiveTimeouts = 0;
|
|
11309
|
+
return false;
|
|
11310
|
+
}
|
|
11311
|
+
recordSuccess(key) {
|
|
11312
|
+
const state2 = this.states.get(key);
|
|
11313
|
+
if (state2 === void 0) return;
|
|
11314
|
+
state2.consecutiveTimeouts = 0;
|
|
11315
|
+
state2.cooldownUntilMs = 0;
|
|
11316
|
+
}
|
|
11317
|
+
recordTimeout(key) {
|
|
11318
|
+
const state2 = this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
11319
|
+
state2.consecutiveTimeouts += 1;
|
|
11320
|
+
if (state2.consecutiveTimeouts >= (this.opts.maxTimeouts ?? DEFAULT_MAX_TIMEOUTS)) {
|
|
11321
|
+
state2.cooldownUntilMs = this.now() + (this.opts.cooldownMs ?? DEFAULT_COOLDOWN_MS2);
|
|
11322
|
+
}
|
|
11323
|
+
this.states.set(key, state2);
|
|
11324
|
+
}
|
|
11325
|
+
/** @internal — tests inspect counter state. */
|
|
11326
|
+
inspect(key) {
|
|
11327
|
+
return this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
11328
|
+
}
|
|
11329
|
+
now() {
|
|
11330
|
+
return this.opts.now?.() ?? Date.now();
|
|
11331
|
+
}
|
|
11332
|
+
};
|
|
11333
|
+
}
|
|
11334
|
+
});
|
|
11335
|
+
|
|
10807
11336
|
// src/internal/llm/pool-aware-client.ts
|
|
10808
11337
|
function classifyAndDecide(error, hasRetried429) {
|
|
10809
11338
|
if (error instanceof NetworkError) return "propagate";
|
|
@@ -10832,20 +11361,34 @@ var PoolAwareLlmClient;
|
|
|
10832
11361
|
var init_pool_aware_client = __esm({
|
|
10833
11362
|
"src/internal/llm/pool-aware-client.ts"() {
|
|
10834
11363
|
init_errors();
|
|
11364
|
+
init_circuit_breaker();
|
|
11365
|
+
init_retry();
|
|
10835
11366
|
init_stream_relay();
|
|
10836
11367
|
PoolAwareLlmClient = class {
|
|
10837
|
-
constructor(pool, buildClient2, waitForAvailableMs = 3e4) {
|
|
11368
|
+
constructor(pool, buildClient2, waitForAvailableMs = 3e4, resilience = {}) {
|
|
10838
11369
|
this.pool = pool;
|
|
10839
11370
|
this.buildClient = buildClient2;
|
|
10840
11371
|
this.waitForAvailableMs = waitForAvailableMs;
|
|
10841
11372
|
this.name = `pool-aware:${pool.provider}`;
|
|
11373
|
+
this.breaker = resilience.breaker ?? new CircuitBreaker();
|
|
11374
|
+
this.backoffBaseMs = resilience.backoffBaseMs;
|
|
11375
|
+
this.rng = resilience.rng;
|
|
10842
11376
|
}
|
|
10843
11377
|
pool;
|
|
10844
11378
|
buildClient;
|
|
10845
11379
|
waitForAvailableMs;
|
|
10846
11380
|
name;
|
|
11381
|
+
/** M2 #60 — provider-level circuit breaker (consecutive-failure). */
|
|
11382
|
+
breaker;
|
|
11383
|
+
backoffBaseMs;
|
|
11384
|
+
rng;
|
|
10847
11385
|
// 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.
|
|
10848
11386
|
async *stream(request, signal) {
|
|
11387
|
+
if (this.breaker.shouldSkip(this.pool.provider)) {
|
|
11388
|
+
throw new NetworkError(`${this.pool.provider} circuit open \u2014 failing fast`, {
|
|
11389
|
+
code: "circuit_open"
|
|
11390
|
+
});
|
|
11391
|
+
}
|
|
10849
11392
|
let hasRetried429 = false;
|
|
10850
11393
|
while (true) {
|
|
10851
11394
|
if (signal.aborted) throw abortError2(signal);
|
|
@@ -10860,6 +11403,7 @@ var init_pool_aware_client = __esm({
|
|
|
10860
11403
|
}
|
|
10861
11404
|
}
|
|
10862
11405
|
if (entry === null) {
|
|
11406
|
+
this.breaker.recordTimeout(this.pool.provider);
|
|
10863
11407
|
throw new CredentialPoolExhaustedError(
|
|
10864
11408
|
`All ${this.pool.provider} credentials exhausted; next retry available at ${this.nextRetryHint() ?? "unknown"}`,
|
|
10865
11409
|
{ provider: this.pool.provider, nextRetryAt: this.nextRetryHint() }
|
|
@@ -10869,10 +11413,19 @@ var init_pool_aware_client = __esm({
|
|
|
10869
11413
|
const realClient = this.buildClient(entry.accessToken);
|
|
10870
11414
|
const attempt = await tryFirstEvent(realClient, request, signal);
|
|
10871
11415
|
if (attempt.kind === "ok") {
|
|
11416
|
+
this.breaker.recordSuccess(this.pool.provider);
|
|
10872
11417
|
return yield* relayStream(attempt.generator, attempt.firstResult);
|
|
10873
11418
|
}
|
|
10874
11419
|
const decision = classifyAndDecide(attempt.error, hasRetried429);
|
|
10875
11420
|
if (decision === "retry") {
|
|
11421
|
+
await sleepWithAbort(
|
|
11422
|
+
computeBackoffMs({
|
|
11423
|
+
attempt: 0,
|
|
11424
|
+
...this.backoffBaseMs !== void 0 ? { baseMs: this.backoffBaseMs } : {},
|
|
11425
|
+
...this.rng !== void 0 ? { rng: this.rng } : {}
|
|
11426
|
+
}),
|
|
11427
|
+
signal
|
|
11428
|
+
);
|
|
10876
11429
|
hasRetried429 = true;
|
|
10877
11430
|
continue;
|
|
10878
11431
|
}
|
|
@@ -10892,6 +11445,7 @@ var init_pool_aware_client = __esm({
|
|
|
10892
11445
|
hasRetried429 = false;
|
|
10893
11446
|
continue;
|
|
10894
11447
|
}
|
|
11448
|
+
this.breaker.recordTimeout(this.pool.provider);
|
|
10895
11449
|
throw attempt.error;
|
|
10896
11450
|
}
|
|
10897
11451
|
}
|
|
@@ -11350,9 +11904,24 @@ var init_router = __esm({
|
|
|
11350
11904
|
warnedProviders = /* @__PURE__ */ new Set();
|
|
11351
11905
|
}
|
|
11352
11906
|
});
|
|
11353
|
-
function createMcpClient(name, config) {
|
|
11907
|
+
function createMcpClient(name, config, fetchImpl = fetch) {
|
|
11354
11908
|
if (isStdio(config)) return new StdioMcpClient(name, config);
|
|
11355
|
-
return new HttpMcpClient(name, config);
|
|
11909
|
+
return new HttpMcpClient(name, config, fetchImpl);
|
|
11910
|
+
}
|
|
11911
|
+
function reconnectDelay(attempt) {
|
|
11912
|
+
const ceiling = RECONNECT_BASE_MS * 2 ** attempt;
|
|
11913
|
+
const ms = Math.floor(Math.random() * ceiling);
|
|
11914
|
+
return ms <= 0 ? Promise.resolve() : new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
11915
|
+
}
|
|
11916
|
+
function mcpTimeoutError(name, timeoutMs) {
|
|
11917
|
+
return new NetworkError(`MCP ${name} request timed out after ${timeoutMs}ms`, {
|
|
11918
|
+
code: "mcp_timeout"
|
|
11919
|
+
});
|
|
11920
|
+
}
|
|
11921
|
+
function isAbortLike(cause) {
|
|
11922
|
+
if (typeof cause !== "object" || cause === null || !("name" in cause)) return false;
|
|
11923
|
+
const name = cause.name;
|
|
11924
|
+
return name === "TimeoutError" || name === "AbortError";
|
|
11356
11925
|
}
|
|
11357
11926
|
async function rpcInitialize(request) {
|
|
11358
11927
|
await request("initialize", {
|
|
@@ -11382,11 +11951,16 @@ function resolveMcpCwd(configCwd) {
|
|
|
11382
11951
|
if (path.isAbsolute(configCwd)) return configCwd;
|
|
11383
11952
|
return safePathJoin(process.cwd(), configCwd);
|
|
11384
11953
|
}
|
|
11385
|
-
var BaseMcpClient, StdioMcpClient, HttpMcpClient;
|
|
11954
|
+
var DEFAULT_MCP_TIMEOUT_MS, MAX_STDIO_BUFFER_BYTES, RECONNECT_BASE_MS, MAX_RECONNECT_ATTEMPTS, BaseMcpClient, StdioMcpClient, HttpMcpClient;
|
|
11386
11955
|
var init_client = __esm({
|
|
11387
11956
|
"src/internal/mcp/client.ts"() {
|
|
11388
11957
|
init_errors();
|
|
11958
|
+
init_env_policy();
|
|
11389
11959
|
init_path_guard();
|
|
11960
|
+
DEFAULT_MCP_TIMEOUT_MS = 3e4;
|
|
11961
|
+
MAX_STDIO_BUFFER_BYTES = 8 * 1024 * 1024;
|
|
11962
|
+
RECONNECT_BASE_MS = 250;
|
|
11963
|
+
MAX_RECONNECT_ATTEMPTS = 2;
|
|
11390
11964
|
BaseMcpClient = class {
|
|
11391
11965
|
initialize() {
|
|
11392
11966
|
return rpcInitialize((method, params) => this.request(method, params));
|
|
@@ -11408,32 +11982,107 @@ var init_client = __esm({
|
|
|
11408
11982
|
name;
|
|
11409
11983
|
child;
|
|
11410
11984
|
nextId = 1;
|
|
11985
|
+
// #59 — pending requests carry a reject + timer so a silent server times out
|
|
11986
|
+
// (typed error), a late reply after timeout is a no-op, and close() settles them.
|
|
11411
11987
|
pending = /* @__PURE__ */ new Map();
|
|
11412
11988
|
buffer = "";
|
|
11413
|
-
|
|
11989
|
+
// M2 #59 — reconnect-after-drop state. `dropped` is set when the child exits
|
|
11990
|
+
// unexpectedly OR times out (not via close()); the next request re-spawns with
|
|
11991
|
+
// backoff. `reconnectPromise` is a SINGLE in-flight reconnect shared by every
|
|
11992
|
+
// concurrent request so parallel tool dispatch after a drop awaits one handshake
|
|
11993
|
+
// instead of racing (or spuriously failing with mcp_not_init).
|
|
11994
|
+
dropped = false;
|
|
11995
|
+
reconnectAttempts = 0;
|
|
11996
|
+
reconnectPromise;
|
|
11997
|
+
get timeoutMs() {
|
|
11998
|
+
return this.config.requestTimeoutMs ?? DEFAULT_MCP_TIMEOUT_MS;
|
|
11999
|
+
}
|
|
12000
|
+
/** Spawn the server child and wire stdout/stderr/error/exit handlers.
|
|
12001
|
+
* Shared by `initialize()` and the M2 #59 reconnect path. */
|
|
12002
|
+
spawnChild() {
|
|
11414
12003
|
const resolvedCwd = resolveMcpCwd(this.config.cwd);
|
|
11415
12004
|
const child = child_process.spawn(this.config.command, this.config.args ?? [], {
|
|
11416
12005
|
cwd: resolvedCwd,
|
|
11417
|
-
|
|
12006
|
+
// #54 (F-H1) — a third-party MCP server binary must not inherit host
|
|
12007
|
+
// secrets. Scrub secret-like vars by default; `config.env` still wins.
|
|
12008
|
+
env: resolveChildEnv({ policy: this.config.envPolicy, overrides: this.config.env })
|
|
11418
12009
|
});
|
|
11419
12010
|
this.child = child;
|
|
11420
12011
|
child.stdout.on("data", (chunk) => this.consume(chunk));
|
|
11421
12012
|
child.stderr.on("data", () => void 0);
|
|
12013
|
+
child.stdin.on("error", () => void 0);
|
|
11422
12014
|
child.on("error", () => {
|
|
11423
|
-
|
|
11424
|
-
|
|
11425
|
-
|
|
11426
|
-
|
|
12015
|
+
this.rejectAllPending(
|
|
12016
|
+
new NetworkError(`MCP ${this.name} process crashed`, { code: "mcp_crashed" })
|
|
12017
|
+
);
|
|
12018
|
+
});
|
|
12019
|
+
child.on("exit", () => {
|
|
12020
|
+
if (this.child !== child) return;
|
|
12021
|
+
this.child = void 0;
|
|
12022
|
+
this.dropped = true;
|
|
12023
|
+
this.rejectAllPending(
|
|
12024
|
+
new NetworkError(`MCP ${this.name} disconnected`, { code: "mcp_disconnected" })
|
|
12025
|
+
);
|
|
12026
|
+
});
|
|
12027
|
+
}
|
|
12028
|
+
async initialize() {
|
|
12029
|
+
this.spawnChild();
|
|
12030
|
+
await super.initialize();
|
|
12031
|
+
}
|
|
12032
|
+
/** M2 #59 — ensure a live child before a request. Reconnect (bounded, with
|
|
12033
|
+
* full-jitter backoff) when the client was dropped; fail fast when never
|
|
12034
|
+
* initialized. Concurrent callers share ONE reconnect handshake. */
|
|
12035
|
+
ensureConnected() {
|
|
12036
|
+
if (this.child !== void 0) return Promise.resolve();
|
|
12037
|
+
if (!this.dropped) {
|
|
12038
|
+
return Promise.reject(
|
|
12039
|
+
new ConfigurationError(`MCP ${this.name} is not initialized`, { code: "mcp_not_init" })
|
|
12040
|
+
);
|
|
12041
|
+
}
|
|
12042
|
+
this.reconnectPromise ??= this.reconnect().finally(() => {
|
|
12043
|
+
this.reconnectPromise = void 0;
|
|
11427
12044
|
});
|
|
12045
|
+
return this.reconnectPromise;
|
|
12046
|
+
}
|
|
12047
|
+
async reconnect() {
|
|
12048
|
+
if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
12049
|
+
throw new NetworkError(`MCP ${this.name} reconnect exhausted`, { code: "mcp_disconnected" });
|
|
12050
|
+
}
|
|
12051
|
+
await reconnectDelay(this.reconnectAttempts);
|
|
12052
|
+
this.reconnectAttempts += 1;
|
|
12053
|
+
this.spawnChild();
|
|
11428
12054
|
await super.initialize();
|
|
12055
|
+
this.dropped = false;
|
|
12056
|
+
this.reconnectAttempts = 0;
|
|
11429
12057
|
}
|
|
11430
12058
|
async close() {
|
|
11431
|
-
|
|
11432
|
-
this.child
|
|
12059
|
+
this.rejectAllPending(new NetworkError(`MCP ${this.name} closed`, { code: "mcp_closed" }));
|
|
12060
|
+
const child = this.child;
|
|
11433
12061
|
this.child = void 0;
|
|
12062
|
+
this.dropped = false;
|
|
12063
|
+
child?.kill("SIGTERM");
|
|
12064
|
+
}
|
|
12065
|
+
/** Reject + clear every pending request (crash / close). @internal */
|
|
12066
|
+
rejectAllPending(error) {
|
|
12067
|
+
for (const entry of this.pending.values()) {
|
|
12068
|
+
clearTimeout(entry.timer);
|
|
12069
|
+
entry.reject(error);
|
|
12070
|
+
}
|
|
12071
|
+
this.pending.clear();
|
|
11434
12072
|
}
|
|
11435
12073
|
consume(chunk) {
|
|
11436
12074
|
this.buffer += chunk.toString("utf8");
|
|
12075
|
+
if (this.buffer.length > MAX_STDIO_BUFFER_BYTES) {
|
|
12076
|
+
this.buffer = "";
|
|
12077
|
+
this.rejectAllPending(
|
|
12078
|
+
new NetworkError(`MCP ${this.name} exceeded stdout buffer limit`, {
|
|
12079
|
+
code: "mcp_buffer_overflow"
|
|
12080
|
+
})
|
|
12081
|
+
);
|
|
12082
|
+
this.child?.kill("SIGKILL");
|
|
12083
|
+
this.child = void 0;
|
|
12084
|
+
return;
|
|
12085
|
+
}
|
|
11437
12086
|
let newlineIndex = this.buffer.indexOf("\n");
|
|
11438
12087
|
while (newlineIndex !== -1) {
|
|
11439
12088
|
const line = this.buffer.slice(0, newlineIndex).trim();
|
|
@@ -11450,23 +12099,47 @@ var init_client = __esm({
|
|
|
11450
12099
|
return;
|
|
11451
12100
|
}
|
|
11452
12101
|
if (typeof message.id !== "number") return;
|
|
11453
|
-
const
|
|
11454
|
-
if (
|
|
12102
|
+
const entry = this.pending.get(message.id);
|
|
12103
|
+
if (entry === void 0) return;
|
|
11455
12104
|
this.pending.delete(message.id);
|
|
11456
|
-
|
|
12105
|
+
clearTimeout(entry.timer);
|
|
12106
|
+
entry.resolve(message);
|
|
11457
12107
|
}
|
|
11458
12108
|
request(method, params) {
|
|
11459
|
-
|
|
11460
|
-
|
|
11461
|
-
|
|
11462
|
-
|
|
12109
|
+
const child = this.child;
|
|
12110
|
+
if (child !== void 0) return this.send(child, method, params);
|
|
12111
|
+
if (this.dropped) return this.reconnectAndRequest(method, params);
|
|
12112
|
+
return Promise.reject(
|
|
12113
|
+
new ConfigurationError(`MCP ${this.name} is not initialized`, { code: "mcp_not_init" })
|
|
12114
|
+
);
|
|
12115
|
+
}
|
|
12116
|
+
/** M2 #59 — reconnect a dropped client, then send. Separate async path so the
|
|
12117
|
+
* happy path above never pays an extra microtask tick. */
|
|
12118
|
+
async reconnectAndRequest(method, params) {
|
|
12119
|
+
await this.ensureConnected();
|
|
12120
|
+
const child = this.child;
|
|
12121
|
+
if (child === void 0) {
|
|
12122
|
+
throw new ConfigurationError(`MCP ${this.name} is not initialized`, { code: "mcp_not_init" });
|
|
11463
12123
|
}
|
|
12124
|
+
return this.send(child, method, params);
|
|
12125
|
+
}
|
|
12126
|
+
send(child, method, params) {
|
|
11464
12127
|
const id = this.nextId++;
|
|
11465
12128
|
const payload = { jsonrpc: "2.0", id, method, params };
|
|
11466
|
-
|
|
12129
|
+
child.stdin.write(`${JSON.stringify(payload)}
|
|
11467
12130
|
`);
|
|
11468
|
-
return new Promise((resolve3) => {
|
|
11469
|
-
|
|
12131
|
+
return new Promise((resolve3, reject) => {
|
|
12132
|
+
const timer = setTimeout(() => {
|
|
12133
|
+
this.pending.delete(id);
|
|
12134
|
+
reject(mcpTimeoutError(this.name, this.timeoutMs));
|
|
12135
|
+
this.child?.kill("SIGKILL");
|
|
12136
|
+
this.child = void 0;
|
|
12137
|
+
this.dropped = true;
|
|
12138
|
+
this.rejectAllPending(
|
|
12139
|
+
new NetworkError(`MCP ${this.name} disconnected`, { code: "mcp_disconnected" })
|
|
12140
|
+
);
|
|
12141
|
+
}, this.timeoutMs);
|
|
12142
|
+
this.pending.set(id, { resolve: resolve3, reject, timer });
|
|
11470
12143
|
});
|
|
11471
12144
|
}
|
|
11472
12145
|
};
|
|
@@ -11492,11 +12165,20 @@ var init_client = __esm({
|
|
|
11492
12165
|
accept: "application/json",
|
|
11493
12166
|
...this.config.headers ?? {}
|
|
11494
12167
|
};
|
|
11495
|
-
const
|
|
11496
|
-
|
|
11497
|
-
|
|
11498
|
-
|
|
11499
|
-
|
|
12168
|
+
const timeoutMs = this.config.requestTimeoutMs ?? DEFAULT_MCP_TIMEOUT_MS;
|
|
12169
|
+
let response;
|
|
12170
|
+
try {
|
|
12171
|
+
response = await this.fetchImpl(this.config.url, {
|
|
12172
|
+
method: "POST",
|
|
12173
|
+
headers,
|
|
12174
|
+
body: JSON.stringify(payload),
|
|
12175
|
+
// #59 — bound the request; a non-responding endpoint aborts here.
|
|
12176
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
12177
|
+
});
|
|
12178
|
+
} catch (cause) {
|
|
12179
|
+
if (isAbortLike(cause)) throw mcpTimeoutError(this.name, timeoutMs);
|
|
12180
|
+
throw cause;
|
|
12181
|
+
}
|
|
11500
12182
|
if (!response.ok) {
|
|
11501
12183
|
throw new NetworkError(`MCP ${this.name} returned ${response.status}`, {
|
|
11502
12184
|
code: "mcp_http_error"
|
|
@@ -11616,11 +12298,33 @@ function resolveRunProvider(options) {
|
|
|
11616
12298
|
);
|
|
11617
12299
|
}
|
|
11618
12300
|
const parsedModel = parseModelId(options.model?.id);
|
|
11619
|
-
const
|
|
11620
|
-
const
|
|
11621
|
-
const
|
|
12301
|
+
const modelInferredProvider = parsedModel.provider !== void 0 && getProviderProfile(parsedModel.provider) !== void 0 ? parsedModel.provider : void 0;
|
|
12302
|
+
const keyInferredProvider = inferProviderFromApiKey(options.agentOptions.apiKey);
|
|
12303
|
+
const primary = options.agentOptions.providers?.routes?.[0]?.provider ?? keyInferredProvider ?? modelInferredProvider ?? detectPrimaryProvider();
|
|
12304
|
+
const effectiveModelId = modelInferredProvider !== void 0 && modelInferredProvider === primary ? parsedModel.name : options.model?.id ?? "claude-sonnet-4-6";
|
|
11622
12305
|
return { primary, effectiveModelId };
|
|
11623
12306
|
}
|
|
12307
|
+
function inferProviderFromApiKey(apiKey) {
|
|
12308
|
+
if (apiKey === void 0 || apiKey.length === 0) return void 0;
|
|
12309
|
+
const byPrefix = [
|
|
12310
|
+
{ provider: "openrouter", prefix: "sk-or-" },
|
|
12311
|
+
{ provider: "anthropic", prefix: "sk-ant-" },
|
|
12312
|
+
{ provider: "openai", prefix: "sk-" }
|
|
12313
|
+
];
|
|
12314
|
+
for (const { provider, prefix } of byPrefix) {
|
|
12315
|
+
if (apiKey.startsWith(prefix) && getProviderProfile(provider) !== void 0) {
|
|
12316
|
+
return provider;
|
|
12317
|
+
}
|
|
12318
|
+
}
|
|
12319
|
+
return void 0;
|
|
12320
|
+
}
|
|
12321
|
+
function mergeExplicitApiKey(pools, primary, apiKey) {
|
|
12322
|
+
if (apiKey === void 0 || apiKey.length === 0) return pools;
|
|
12323
|
+
if (isFixtureApiKey(apiKey) || apiKey === LOCAL_RUNTIME_MOCK_KEY) return pools;
|
|
12324
|
+
const existing = pools?.[primary];
|
|
12325
|
+
if (existing !== void 0 && existing.length > 0) return pools;
|
|
12326
|
+
return { ...pools ?? {}, [primary]: [apiKey] };
|
|
12327
|
+
}
|
|
11624
12328
|
function buildLoopInputs(options, runId, userText) {
|
|
11625
12329
|
const maxIterations = options.sendOptions.maxIterations;
|
|
11626
12330
|
if (maxIterations !== void 0 && (!Number.isInteger(maxIterations) || maxIterations < 1)) {
|
|
@@ -11631,7 +12335,11 @@ function buildLoopInputs(options, runId, userText) {
|
|
|
11631
12335
|
}
|
|
11632
12336
|
const { primary, effectiveModelId } = resolveRunProvider(options);
|
|
11633
12337
|
const fallback = options.agentOptions.providers?.fallback;
|
|
11634
|
-
const apiKeys =
|
|
12338
|
+
const apiKeys = mergeExplicitApiKey(
|
|
12339
|
+
options.agentOptions.providers?.apiKeys,
|
|
12340
|
+
primary,
|
|
12341
|
+
options.agentOptions.apiKey
|
|
12342
|
+
);
|
|
11635
12343
|
const credentialPoolStrategy = options.agentOptions.providers?.credentialPoolStrategy;
|
|
11636
12344
|
const extractToolCallsFromContent = options.agentOptions.providers?.routes?.[0]?.extractToolCallsFromContent;
|
|
11637
12345
|
const chain = resolveProviderChain({
|
|
@@ -11675,6 +12383,10 @@ function buildLoopInputs(options, runId, userText) {
|
|
|
11675
12383
|
// D318 — forward SendOptions.signal to the agent loop so streamLlmTurn
|
|
11676
12384
|
// can attach it to the LLM `fetch({ signal })` call.
|
|
11677
12385
|
...options.sendOptions.signal !== void 0 ? { signal: options.sendOptions.signal } : {},
|
|
12386
|
+
// #58 / #57 — forward the per-tool timeout + tool-result guard so a consumer
|
|
12387
|
+
// can enable them via SendOptions (not only internal AgentLoopInputs).
|
|
12388
|
+
...options.sendOptions.perToolTimeoutMs !== void 0 ? { perToolTimeoutMs: options.sendOptions.perToolTimeoutMs } : {},
|
|
12389
|
+
...options.sendOptions.toolResultGuard !== void 0 ? { toolResultGuard: options.sendOptions.toolResultGuard } : {},
|
|
11678
12390
|
// M1-2: per-send iteration ceiling (validated above). The loop reads
|
|
11679
12391
|
// inputs.maxIterations (default 8 when unset).
|
|
11680
12392
|
...maxIterations !== void 0 ? { maxIterations } : {},
|
|
@@ -11741,6 +12453,8 @@ var init_real_local_run = __esm({
|
|
|
11741
12453
|
"src/internal/runtime/local-agent/real-local-run.ts"() {
|
|
11742
12454
|
init_errors();
|
|
11743
12455
|
init_loop();
|
|
12456
|
+
init_api_key_validator();
|
|
12457
|
+
init_fixture_mode();
|
|
11744
12458
|
init_fallback_client();
|
|
11745
12459
|
init_model_identifier();
|
|
11746
12460
|
init_router();
|
|
@@ -12017,7 +12731,12 @@ async function runActiveMemory(args) {
|
|
|
12017
12731
|
hits: []
|
|
12018
12732
|
});
|
|
12019
12733
|
}
|
|
12020
|
-
const
|
|
12734
|
+
const tenantCtx = {
|
|
12735
|
+
namespace: args.namespace,
|
|
12736
|
+
userId: args.userId,
|
|
12737
|
+
scope: args.scope
|
|
12738
|
+
};
|
|
12739
|
+
const cached2 = args.cache?.get(args.userText, cfg.queryMode, tenantCtx);
|
|
12021
12740
|
if (cached2 !== void 0) return endRecallSpan(span, args, cached2);
|
|
12022
12741
|
const query = buildQuery(args.userText, args.priorMessages, cfg.queryMode, cfg.recentUserTurns);
|
|
12023
12742
|
if (query.trim().length === 0) {
|
|
@@ -12102,7 +12821,12 @@ function notifyBreaker(breaker, key, status) {
|
|
|
12102
12821
|
else if (status === "ok" || status === "no-recall") breaker.recordSuccess(key);
|
|
12103
12822
|
}
|
|
12104
12823
|
async function finalize(args, queryMode, result) {
|
|
12105
|
-
|
|
12824
|
+
const tenantCtx = {
|
|
12825
|
+
namespace: args.namespace,
|
|
12826
|
+
userId: args.userId,
|
|
12827
|
+
scope: args.scope
|
|
12828
|
+
};
|
|
12829
|
+
args.cache?.set(args.userText, queryMode, result, tenantCtx);
|
|
12106
12830
|
if (args.persistTranscripts === true && args.cwd !== void 0) {
|
|
12107
12831
|
await persistActiveMemoryTranscript(args.cwd, {
|
|
12108
12832
|
runId: args.runId ?? `run-${Date.now()}`,
|
|
@@ -12807,53 +13531,6 @@ var init_catalog = __esm({
|
|
|
12807
13531
|
}
|
|
12808
13532
|
});
|
|
12809
13533
|
|
|
12810
|
-
// src/internal/memory/circuit-breaker.ts
|
|
12811
|
-
var DEFAULT_MAX_TIMEOUTS, DEFAULT_COOLDOWN_MS2, CircuitBreaker;
|
|
12812
|
-
var init_circuit_breaker = __esm({
|
|
12813
|
-
"src/internal/memory/circuit-breaker.ts"() {
|
|
12814
|
-
DEFAULT_MAX_TIMEOUTS = 3;
|
|
12815
|
-
DEFAULT_COOLDOWN_MS2 = 6e4;
|
|
12816
|
-
CircuitBreaker = class {
|
|
12817
|
-
constructor(opts = {}) {
|
|
12818
|
-
this.opts = opts;
|
|
12819
|
-
}
|
|
12820
|
-
opts;
|
|
12821
|
-
states = /* @__PURE__ */ new Map();
|
|
12822
|
-
/** @returns true when the breaker is open and the call should be skipped. */
|
|
12823
|
-
shouldSkip(key) {
|
|
12824
|
-
const state2 = this.states.get(key);
|
|
12825
|
-
if (state2 === void 0) return false;
|
|
12826
|
-
if (state2.cooldownUntilMs === 0) return false;
|
|
12827
|
-
if (this.now() < state2.cooldownUntilMs) return true;
|
|
12828
|
-
state2.cooldownUntilMs = 0;
|
|
12829
|
-
state2.consecutiveTimeouts = 0;
|
|
12830
|
-
return false;
|
|
12831
|
-
}
|
|
12832
|
-
recordSuccess(key) {
|
|
12833
|
-
const state2 = this.states.get(key);
|
|
12834
|
-
if (state2 === void 0) return;
|
|
12835
|
-
state2.consecutiveTimeouts = 0;
|
|
12836
|
-
state2.cooldownUntilMs = 0;
|
|
12837
|
-
}
|
|
12838
|
-
recordTimeout(key) {
|
|
12839
|
-
const state2 = this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
12840
|
-
state2.consecutiveTimeouts += 1;
|
|
12841
|
-
if (state2.consecutiveTimeouts >= (this.opts.maxTimeouts ?? DEFAULT_MAX_TIMEOUTS)) {
|
|
12842
|
-
state2.cooldownUntilMs = this.now() + (this.opts.cooldownMs ?? DEFAULT_COOLDOWN_MS2);
|
|
12843
|
-
}
|
|
12844
|
-
this.states.set(key, state2);
|
|
12845
|
-
}
|
|
12846
|
-
/** @internal — tests inspect counter state. */
|
|
12847
|
-
inspect(key) {
|
|
12848
|
-
return this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
12849
|
-
}
|
|
12850
|
-
now() {
|
|
12851
|
-
return this.opts.now?.() ?? Date.now();
|
|
12852
|
-
}
|
|
12853
|
-
};
|
|
12854
|
-
}
|
|
12855
|
-
});
|
|
12856
|
-
|
|
12857
13534
|
// src/internal/persistence/fts5-sanitize.ts
|
|
12858
13535
|
function sanitizeFts5Query(query) {
|
|
12859
13536
|
if (query.length === 0) return query;
|
|
@@ -14019,9 +14696,9 @@ var init_local_agent_memory = __esm({
|
|
|
14019
14696
|
init_active_memory();
|
|
14020
14697
|
init_active_memory_cache();
|
|
14021
14698
|
init_catalog();
|
|
14022
|
-
init_circuit_breaker();
|
|
14023
14699
|
init_index_manager();
|
|
14024
14700
|
init_tools();
|
|
14701
|
+
init_circuit_breaker();
|
|
14025
14702
|
LocalAgentMemory = class {
|
|
14026
14703
|
constructor(options, workspaceCwd, agentId) {
|
|
14027
14704
|
this.options = options;
|