@theokit/sdk 2.15.1 → 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 +71 -0
- package/dist/a2a/index.cjs +981 -208
- package/dist/a2a/index.cjs.map +1 -1
- package/dist/a2a/index.js +982 -209
- 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 +945 -196
- 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 +945 -196
- 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 +951 -198
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +951 -198
- package/dist/eval.js.map +1 -1
- package/dist/event-bus.d.ts +3 -0
- package/dist/index.cjs +1082 -224
- 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 +1082 -226
- 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 +13 -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) {
|
|
@@ -10365,28 +10842,87 @@ function parseHermesParams(inner) {
|
|
|
10365
10842
|
}
|
|
10366
10843
|
return sanitizeToolInput(input, { trim: true }).value;
|
|
10367
10844
|
}
|
|
10368
|
-
|
|
10845
|
+
function streamToolCallBufferState(held, allowedToolNames, cap = DEFAULT_STREAM_BUFFER_CAP) {
|
|
10846
|
+
if (allowedToolNames.size === 0) return "impossible";
|
|
10847
|
+
const t = held.trimStart();
|
|
10848
|
+
if (t.length < STREAM_MARKER.length) {
|
|
10849
|
+
return STREAM_MARKER.startsWith(t) ? "possible" : "impossible";
|
|
10850
|
+
}
|
|
10851
|
+
if (!t.startsWith(STREAM_MARKER)) return "impossible";
|
|
10852
|
+
const parsed = parseStreamMarkerName(t);
|
|
10853
|
+
if (parsed === "building") return "possible";
|
|
10854
|
+
if (parsed === "invalid") return "impossible";
|
|
10855
|
+
const nameOk = parsed.complete ? allowedToolNames.has(parsed.name) : someToolNameStartsWith(allowedToolNames, parsed.name);
|
|
10856
|
+
if (!nameOk) return "impossible";
|
|
10857
|
+
return held.length > cap ? "impossible" : "possible";
|
|
10858
|
+
}
|
|
10859
|
+
function parseStreamMarkerName(t) {
|
|
10860
|
+
let cursor = STREAM_MARKER.length;
|
|
10861
|
+
while (cursor < t.length && isStreamWs(t[cursor])) cursor += 1;
|
|
10862
|
+
const nameStart = cursor;
|
|
10863
|
+
while (cursor < t.length && t[cursor] !== ">" && !isStreamWs(t[cursor])) cursor += 1;
|
|
10864
|
+
const name = t.slice(nameStart, cursor);
|
|
10865
|
+
if (name.length === 0) return cursor >= t.length ? "building" : "invalid";
|
|
10866
|
+
return { name, complete: cursor < t.length && t[cursor] === ">" };
|
|
10867
|
+
}
|
|
10868
|
+
function someToolNameStartsWith(allowedToolNames, prefix) {
|
|
10869
|
+
for (const name of allowedToolNames) {
|
|
10870
|
+
if (name.startsWith(prefix)) return true;
|
|
10871
|
+
}
|
|
10872
|
+
return false;
|
|
10873
|
+
}
|
|
10874
|
+
function firstPossibleMarkerStart(held, allowedToolNames) {
|
|
10875
|
+
for (let i = held.indexOf("<"); i !== -1; i = held.indexOf("<", i + 1)) {
|
|
10876
|
+
if (streamToolCallBufferState(held.slice(i), allowedToolNames) === "possible") return i;
|
|
10877
|
+
}
|
|
10878
|
+
return -1;
|
|
10879
|
+
}
|
|
10880
|
+
var HERMES_BLOCK, HERMES_PARAM, STREAM_MARKER, DEFAULT_STREAM_BUFFER_CAP, isStreamWs, StreamSuppressionBuffer;
|
|
10369
10881
|
var init_hermes_tool_extract = __esm({
|
|
10370
10882
|
"src/internal/llm/hermes-tool-extract.ts"() {
|
|
10371
10883
|
init_sanitize_tool_input();
|
|
10372
10884
|
HERMES_BLOCK = /<function=\s*([^>\s]+)\s*>([\s\S]*?)<\/tool_call>/g;
|
|
10373
10885
|
HERMES_PARAM = /<parameter=\s*([^>\s]+)\s*>([\s\S]*?)<\/parameter>/g;
|
|
10886
|
+
STREAM_MARKER = "<function=";
|
|
10887
|
+
DEFAULT_STREAM_BUFFER_CAP = 8192;
|
|
10888
|
+
isStreamWs = (c) => c === " " || c === " " || c === "\n" || c === "\r";
|
|
10889
|
+
StreamSuppressionBuffer = class {
|
|
10890
|
+
constructor(allowedToolNames) {
|
|
10891
|
+
this.allowedToolNames = allowedToolNames;
|
|
10892
|
+
}
|
|
10893
|
+
allowedToolNames;
|
|
10894
|
+
#held = "";
|
|
10895
|
+
/** Feed a content delta; returns the text to emit as a `text_delta` now, or `undefined` to hold. */
|
|
10896
|
+
push(content) {
|
|
10897
|
+
this.#held += content;
|
|
10898
|
+
if (streamToolCallBufferState(this.#held, this.allowedToolNames) === "possible")
|
|
10899
|
+
return void 0;
|
|
10900
|
+
const holdStart = firstPossibleMarkerStart(this.#held, this.allowedToolNames);
|
|
10901
|
+
if (holdStart > 0) {
|
|
10902
|
+
const flush2 = this.#held.slice(0, holdStart);
|
|
10903
|
+
this.#held = this.#held.slice(holdStart);
|
|
10904
|
+
return flush2;
|
|
10905
|
+
}
|
|
10906
|
+
const flush = this.#held;
|
|
10907
|
+
this.#held = "";
|
|
10908
|
+
return flush;
|
|
10909
|
+
}
|
|
10910
|
+
/** Drain the held buffer at stream end. `hasNativeCalls` mirrors `finish()`'s size-guard: when
|
|
10911
|
+
* native `tool_calls` exist, `finish()` won't strip the leaked block, so stream the held text WHOLE
|
|
10912
|
+
* (keeping `accumulatedText == finish.text`); otherwise strip the recoverable blocks. Idempotent. */
|
|
10913
|
+
drain(hasNativeCalls) {
|
|
10914
|
+
if (this.#held.length === 0) return void 0;
|
|
10915
|
+
const held = this.#held;
|
|
10916
|
+
this.#held = "";
|
|
10917
|
+
if (hasNativeCalls) return held;
|
|
10918
|
+
const residual = extractHermesToolCalls(held, () => "held", this.allowedToolNames).residualText;
|
|
10919
|
+
return residual.length > 0 ? residual : void 0;
|
|
10920
|
+
}
|
|
10921
|
+
};
|
|
10374
10922
|
}
|
|
10375
10923
|
});
|
|
10376
10924
|
|
|
10377
10925
|
// src/internal/llm/openai.ts
|
|
10378
|
-
function mapOpenAIFinish(reason) {
|
|
10379
|
-
switch (reason) {
|
|
10380
|
-
case "tool_calls":
|
|
10381
|
-
return "tool_use";
|
|
10382
|
-
case "length":
|
|
10383
|
-
return "max_tokens";
|
|
10384
|
-
case "stop":
|
|
10385
|
-
return "end_turn";
|
|
10386
|
-
default:
|
|
10387
|
-
return "end_turn";
|
|
10388
|
-
}
|
|
10389
|
-
}
|
|
10390
10926
|
function applyReasoningRequest(body, effort, providerName) {
|
|
10391
10927
|
if (providerName === "openai") {
|
|
10392
10928
|
body.reasoning_effort = effort;
|
|
@@ -10483,6 +11019,7 @@ function assistantMessage(message) {
|
|
|
10483
11019
|
var OpenAIClient, OpenAIStreamAccumulator, openAISystemText;
|
|
10484
11020
|
var init_openai2 = __esm({
|
|
10485
11021
|
"src/internal/llm/openai.ts"() {
|
|
11022
|
+
init_errors();
|
|
10486
11023
|
init_ollama2();
|
|
10487
11024
|
init_openai_compatible();
|
|
10488
11025
|
init_finish();
|
|
@@ -10563,8 +11100,12 @@ var init_openai2 = __esm({
|
|
|
10563
11100
|
// model was actually given. Empty set (no tools) recovers nothing.
|
|
10564
11101
|
new Set(request.tools?.map((tool) => tool.name) ?? [])
|
|
10565
11102
|
);
|
|
11103
|
+
let sawDone = false;
|
|
10566
11104
|
for await (const record of parseSseStream(response.body, signal)) {
|
|
10567
|
-
if (record.data === "[DONE]")
|
|
11105
|
+
if (record.data === "[DONE]") {
|
|
11106
|
+
sawDone = true;
|
|
11107
|
+
break;
|
|
11108
|
+
}
|
|
10568
11109
|
let chunk;
|
|
10569
11110
|
try {
|
|
10570
11111
|
chunk = JSON.parse(record.data);
|
|
@@ -10584,6 +11125,13 @@ var init_openai2 = __esm({
|
|
|
10584
11125
|
const events = accumulator.consume(chunk);
|
|
10585
11126
|
for (const event of events) yield event;
|
|
10586
11127
|
}
|
|
11128
|
+
if (!sawDone && !accumulator.finishReasonSeen) {
|
|
11129
|
+
throw new NetworkError("SSE stream truncated (no finish_reason / [DONE])", {
|
|
11130
|
+
code: "stream_truncated"
|
|
11131
|
+
});
|
|
11132
|
+
}
|
|
11133
|
+
const drainEvent = accumulator.finalizeHeldText();
|
|
11134
|
+
if (drainEvent !== void 0) yield drainEvent;
|
|
10587
11135
|
return accumulator.finish();
|
|
10588
11136
|
}
|
|
10589
11137
|
};
|
|
@@ -10599,6 +11147,7 @@ var init_openai2 = __esm({
|
|
|
10599
11147
|
this.extractFromContent = extractFromContent;
|
|
10600
11148
|
this.providerName = providerName;
|
|
10601
11149
|
this.allowedToolNames = allowedToolNames;
|
|
11150
|
+
this.suppress = extractFromContent && allowedToolNames !== void 0 && allowedToolNames.size > 0 ? new StreamSuppressionBuffer(allowedToolNames) : void 0;
|
|
10602
11151
|
}
|
|
10603
11152
|
extractFromContent;
|
|
10604
11153
|
providerName;
|
|
@@ -10611,18 +11160,30 @@ var init_openai2 = __esm({
|
|
|
10611
11160
|
cacheWriteTokens;
|
|
10612
11161
|
reasoningTokens;
|
|
10613
11162
|
toolCalls = /* @__PURE__ */ new Map();
|
|
11163
|
+
/** R7: present only when recovery is enabled AND the request declares tools — holds suspected
|
|
11164
|
+
* leaked-dialect content back from the `text_delta` stream. `undefined` ⇒ stream immediately. */
|
|
11165
|
+
suppress;
|
|
10614
11166
|
consume(chunk) {
|
|
10615
11167
|
const events = [];
|
|
10616
11168
|
this.applyUsage(chunk.usage);
|
|
10617
11169
|
for (const choice of chunk.choices ?? []) {
|
|
10618
|
-
|
|
10619
|
-
|
|
10620
|
-
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
|
|
10624
|
-
|
|
10625
|
-
|
|
11170
|
+
events.push(...this.applyChoice(choice));
|
|
11171
|
+
}
|
|
11172
|
+
return events;
|
|
11173
|
+
}
|
|
11174
|
+
applyChoice(choice) {
|
|
11175
|
+
const events = [];
|
|
11176
|
+
const reasoningEvent = this.applyReasoningDelta(
|
|
11177
|
+
choice.delta?.reasoning ?? choice.delta?.reasoning_content
|
|
11178
|
+
);
|
|
11179
|
+
if (reasoningEvent !== void 0) events.push(reasoningEvent);
|
|
11180
|
+
const textEvent = this.applyContentDelta(choice.delta?.content);
|
|
11181
|
+
if (textEvent !== void 0) events.push(textEvent);
|
|
11182
|
+
this.mergeToolCallDeltas(choice.delta?.tool_calls);
|
|
11183
|
+
this.applyFinishReason(choice.finish_reason);
|
|
11184
|
+
if (choice.finish_reason !== void 0 && choice.finish_reason !== null) {
|
|
11185
|
+
const flushEvent = this.finalizeHeldText();
|
|
11186
|
+
if (flushEvent !== void 0) events.push(flushEvent);
|
|
10626
11187
|
}
|
|
10627
11188
|
return events;
|
|
10628
11189
|
}
|
|
@@ -10647,7 +11208,17 @@ var init_openai2 = __esm({
|
|
|
10647
11208
|
applyContentDelta(content) {
|
|
10648
11209
|
if (typeof content !== "string" || content.length === 0) return void 0;
|
|
10649
11210
|
this.text += content;
|
|
10650
|
-
return { type: "text_delta", text: content };
|
|
11211
|
+
if (this.suppress === void 0) return { type: "text_delta", text: content };
|
|
11212
|
+
const emit = this.suppress.push(content);
|
|
11213
|
+
return emit !== void 0 ? { type: "text_delta", text: emit } : void 0;
|
|
11214
|
+
}
|
|
11215
|
+
/** R7 held-buffer finalizer, called at the `finish_reason` chunk (in `applyChoice`) AND after the
|
|
11216
|
+
* SSE loop in `stream()` — so a stream that omits a `finish_reason` terminal never silently drops
|
|
11217
|
+
* held text. `toolCalls.size > 0` (native calls present) makes `finish()` skip recovery, so the
|
|
11218
|
+
* buffer streams the held text whole. Idempotent once drained. */
|
|
11219
|
+
finalizeHeldText() {
|
|
11220
|
+
const emit = this.suppress?.drain(this.toolCalls.size > 0);
|
|
11221
|
+
return emit !== void 0 ? { type: "text_delta", text: emit } : void 0;
|
|
10651
11222
|
}
|
|
10652
11223
|
mergeToolCallDeltas(deltas) {
|
|
10653
11224
|
for (const call of deltas ?? []) {
|
|
@@ -10658,8 +11229,15 @@ var init_openai2 = __esm({
|
|
|
10658
11229
|
this.toolCalls.set(call.index, existing);
|
|
10659
11230
|
}
|
|
10660
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
|
+
}
|
|
10661
11238
|
applyFinishReason(reason) {
|
|
10662
11239
|
if (reason === void 0 || reason === null) return;
|
|
11240
|
+
this.sawFinishReason = true;
|
|
10663
11241
|
this.stopReason = mapOpenAIFinish(reason);
|
|
10664
11242
|
}
|
|
10665
11243
|
finish() {
|
|
@@ -10708,6 +11286,53 @@ var init_openai2 = __esm({
|
|
|
10708
11286
|
}
|
|
10709
11287
|
});
|
|
10710
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
|
+
|
|
10711
11336
|
// src/internal/llm/pool-aware-client.ts
|
|
10712
11337
|
function classifyAndDecide(error, hasRetried429) {
|
|
10713
11338
|
if (error instanceof NetworkError) return "propagate";
|
|
@@ -10736,20 +11361,34 @@ var PoolAwareLlmClient;
|
|
|
10736
11361
|
var init_pool_aware_client = __esm({
|
|
10737
11362
|
"src/internal/llm/pool-aware-client.ts"() {
|
|
10738
11363
|
init_errors();
|
|
11364
|
+
init_circuit_breaker();
|
|
11365
|
+
init_retry();
|
|
10739
11366
|
init_stream_relay();
|
|
10740
11367
|
PoolAwareLlmClient = class {
|
|
10741
|
-
constructor(pool, buildClient2, waitForAvailableMs = 3e4) {
|
|
11368
|
+
constructor(pool, buildClient2, waitForAvailableMs = 3e4, resilience = {}) {
|
|
10742
11369
|
this.pool = pool;
|
|
10743
11370
|
this.buildClient = buildClient2;
|
|
10744
11371
|
this.waitForAvailableMs = waitForAvailableMs;
|
|
10745
11372
|
this.name = `pool-aware:${pool.provider}`;
|
|
11373
|
+
this.breaker = resilience.breaker ?? new CircuitBreaker();
|
|
11374
|
+
this.backoffBaseMs = resilience.backoffBaseMs;
|
|
11375
|
+
this.rng = resilience.rng;
|
|
10746
11376
|
}
|
|
10747
11377
|
pool;
|
|
10748
11378
|
buildClient;
|
|
10749
11379
|
waitForAvailableMs;
|
|
10750
11380
|
name;
|
|
11381
|
+
/** M2 #60 — provider-level circuit breaker (consecutive-failure). */
|
|
11382
|
+
breaker;
|
|
11383
|
+
backoffBaseMs;
|
|
11384
|
+
rng;
|
|
10751
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.
|
|
10752
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
|
+
}
|
|
10753
11392
|
let hasRetried429 = false;
|
|
10754
11393
|
while (true) {
|
|
10755
11394
|
if (signal.aborted) throw abortError2(signal);
|
|
@@ -10764,6 +11403,7 @@ var init_pool_aware_client = __esm({
|
|
|
10764
11403
|
}
|
|
10765
11404
|
}
|
|
10766
11405
|
if (entry === null) {
|
|
11406
|
+
this.breaker.recordTimeout(this.pool.provider);
|
|
10767
11407
|
throw new CredentialPoolExhaustedError(
|
|
10768
11408
|
`All ${this.pool.provider} credentials exhausted; next retry available at ${this.nextRetryHint() ?? "unknown"}`,
|
|
10769
11409
|
{ provider: this.pool.provider, nextRetryAt: this.nextRetryHint() }
|
|
@@ -10773,10 +11413,19 @@ var init_pool_aware_client = __esm({
|
|
|
10773
11413
|
const realClient = this.buildClient(entry.accessToken);
|
|
10774
11414
|
const attempt = await tryFirstEvent(realClient, request, signal);
|
|
10775
11415
|
if (attempt.kind === "ok") {
|
|
11416
|
+
this.breaker.recordSuccess(this.pool.provider);
|
|
10776
11417
|
return yield* relayStream(attempt.generator, attempt.firstResult);
|
|
10777
11418
|
}
|
|
10778
11419
|
const decision = classifyAndDecide(attempt.error, hasRetried429);
|
|
10779
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
|
+
);
|
|
10780
11429
|
hasRetried429 = true;
|
|
10781
11430
|
continue;
|
|
10782
11431
|
}
|
|
@@ -10796,6 +11445,7 @@ var init_pool_aware_client = __esm({
|
|
|
10796
11445
|
hasRetried429 = false;
|
|
10797
11446
|
continue;
|
|
10798
11447
|
}
|
|
11448
|
+
this.breaker.recordTimeout(this.pool.provider);
|
|
10799
11449
|
throw attempt.error;
|
|
10800
11450
|
}
|
|
10801
11451
|
}
|
|
@@ -11254,9 +11904,24 @@ var init_router = __esm({
|
|
|
11254
11904
|
warnedProviders = /* @__PURE__ */ new Set();
|
|
11255
11905
|
}
|
|
11256
11906
|
});
|
|
11257
|
-
function createMcpClient(name, config) {
|
|
11907
|
+
function createMcpClient(name, config, fetchImpl = fetch) {
|
|
11258
11908
|
if (isStdio(config)) return new StdioMcpClient(name, config);
|
|
11259
|
-
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";
|
|
11260
11925
|
}
|
|
11261
11926
|
async function rpcInitialize(request) {
|
|
11262
11927
|
await request("initialize", {
|
|
@@ -11286,11 +11951,16 @@ function resolveMcpCwd(configCwd) {
|
|
|
11286
11951
|
if (path.isAbsolute(configCwd)) return configCwd;
|
|
11287
11952
|
return safePathJoin(process.cwd(), configCwd);
|
|
11288
11953
|
}
|
|
11289
|
-
var BaseMcpClient, StdioMcpClient, HttpMcpClient;
|
|
11954
|
+
var DEFAULT_MCP_TIMEOUT_MS, MAX_STDIO_BUFFER_BYTES, RECONNECT_BASE_MS, MAX_RECONNECT_ATTEMPTS, BaseMcpClient, StdioMcpClient, HttpMcpClient;
|
|
11290
11955
|
var init_client = __esm({
|
|
11291
11956
|
"src/internal/mcp/client.ts"() {
|
|
11292
11957
|
init_errors();
|
|
11958
|
+
init_env_policy();
|
|
11293
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;
|
|
11294
11964
|
BaseMcpClient = class {
|
|
11295
11965
|
initialize() {
|
|
11296
11966
|
return rpcInitialize((method, params) => this.request(method, params));
|
|
@@ -11312,32 +11982,107 @@ var init_client = __esm({
|
|
|
11312
11982
|
name;
|
|
11313
11983
|
child;
|
|
11314
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.
|
|
11315
11987
|
pending = /* @__PURE__ */ new Map();
|
|
11316
11988
|
buffer = "";
|
|
11317
|
-
|
|
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() {
|
|
11318
12003
|
const resolvedCwd = resolveMcpCwd(this.config.cwd);
|
|
11319
12004
|
const child = child_process.spawn(this.config.command, this.config.args ?? [], {
|
|
11320
12005
|
cwd: resolvedCwd,
|
|
11321
|
-
|
|
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 })
|
|
11322
12009
|
});
|
|
11323
12010
|
this.child = child;
|
|
11324
12011
|
child.stdout.on("data", (chunk) => this.consume(chunk));
|
|
11325
12012
|
child.stderr.on("data", () => void 0);
|
|
12013
|
+
child.stdin.on("error", () => void 0);
|
|
11326
12014
|
child.on("error", () => {
|
|
11327
|
-
|
|
11328
|
-
|
|
11329
|
-
|
|
11330
|
-
|
|
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;
|
|
11331
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();
|
|
11332
12054
|
await super.initialize();
|
|
12055
|
+
this.dropped = false;
|
|
12056
|
+
this.reconnectAttempts = 0;
|
|
11333
12057
|
}
|
|
11334
12058
|
async close() {
|
|
11335
|
-
|
|
11336
|
-
this.child
|
|
12059
|
+
this.rejectAllPending(new NetworkError(`MCP ${this.name} closed`, { code: "mcp_closed" }));
|
|
12060
|
+
const child = this.child;
|
|
11337
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();
|
|
11338
12072
|
}
|
|
11339
12073
|
consume(chunk) {
|
|
11340
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
|
+
}
|
|
11341
12086
|
let newlineIndex = this.buffer.indexOf("\n");
|
|
11342
12087
|
while (newlineIndex !== -1) {
|
|
11343
12088
|
const line = this.buffer.slice(0, newlineIndex).trim();
|
|
@@ -11354,23 +12099,47 @@ var init_client = __esm({
|
|
|
11354
12099
|
return;
|
|
11355
12100
|
}
|
|
11356
12101
|
if (typeof message.id !== "number") return;
|
|
11357
|
-
const
|
|
11358
|
-
if (
|
|
12102
|
+
const entry = this.pending.get(message.id);
|
|
12103
|
+
if (entry === void 0) return;
|
|
11359
12104
|
this.pending.delete(message.id);
|
|
11360
|
-
|
|
12105
|
+
clearTimeout(entry.timer);
|
|
12106
|
+
entry.resolve(message);
|
|
11361
12107
|
}
|
|
11362
12108
|
request(method, params) {
|
|
11363
|
-
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
|
|
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" });
|
|
11367
12123
|
}
|
|
12124
|
+
return this.send(child, method, params);
|
|
12125
|
+
}
|
|
12126
|
+
send(child, method, params) {
|
|
11368
12127
|
const id = this.nextId++;
|
|
11369
12128
|
const payload = { jsonrpc: "2.0", id, method, params };
|
|
11370
|
-
|
|
12129
|
+
child.stdin.write(`${JSON.stringify(payload)}
|
|
11371
12130
|
`);
|
|
11372
|
-
return new Promise((resolve3) => {
|
|
11373
|
-
|
|
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 });
|
|
11374
12143
|
});
|
|
11375
12144
|
}
|
|
11376
12145
|
};
|
|
@@ -11396,11 +12165,20 @@ var init_client = __esm({
|
|
|
11396
12165
|
accept: "application/json",
|
|
11397
12166
|
...this.config.headers ?? {}
|
|
11398
12167
|
};
|
|
11399
|
-
const
|
|
11400
|
-
|
|
11401
|
-
|
|
11402
|
-
|
|
11403
|
-
|
|
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
|
+
}
|
|
11404
12182
|
if (!response.ok) {
|
|
11405
12183
|
throw new NetworkError(`MCP ${this.name} returned ${response.status}`, {
|
|
11406
12184
|
code: "mcp_http_error"
|
|
@@ -11520,11 +12298,33 @@ function resolveRunProvider(options) {
|
|
|
11520
12298
|
);
|
|
11521
12299
|
}
|
|
11522
12300
|
const parsedModel = parseModelId(options.model?.id);
|
|
11523
|
-
const
|
|
11524
|
-
const
|
|
11525
|
-
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";
|
|
11526
12305
|
return { primary, effectiveModelId };
|
|
11527
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
|
+
}
|
|
11528
12328
|
function buildLoopInputs(options, runId, userText) {
|
|
11529
12329
|
const maxIterations = options.sendOptions.maxIterations;
|
|
11530
12330
|
if (maxIterations !== void 0 && (!Number.isInteger(maxIterations) || maxIterations < 1)) {
|
|
@@ -11535,7 +12335,11 @@ function buildLoopInputs(options, runId, userText) {
|
|
|
11535
12335
|
}
|
|
11536
12336
|
const { primary, effectiveModelId } = resolveRunProvider(options);
|
|
11537
12337
|
const fallback = options.agentOptions.providers?.fallback;
|
|
11538
|
-
const apiKeys =
|
|
12338
|
+
const apiKeys = mergeExplicitApiKey(
|
|
12339
|
+
options.agentOptions.providers?.apiKeys,
|
|
12340
|
+
primary,
|
|
12341
|
+
options.agentOptions.apiKey
|
|
12342
|
+
);
|
|
11539
12343
|
const credentialPoolStrategy = options.agentOptions.providers?.credentialPoolStrategy;
|
|
11540
12344
|
const extractToolCallsFromContent = options.agentOptions.providers?.routes?.[0]?.extractToolCallsFromContent;
|
|
11541
12345
|
const chain = resolveProviderChain({
|
|
@@ -11579,6 +12383,10 @@ function buildLoopInputs(options, runId, userText) {
|
|
|
11579
12383
|
// D318 — forward SendOptions.signal to the agent loop so streamLlmTurn
|
|
11580
12384
|
// can attach it to the LLM `fetch({ signal })` call.
|
|
11581
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 } : {},
|
|
11582
12390
|
// M1-2: per-send iteration ceiling (validated above). The loop reads
|
|
11583
12391
|
// inputs.maxIterations (default 8 when unset).
|
|
11584
12392
|
...maxIterations !== void 0 ? { maxIterations } : {},
|
|
@@ -11645,6 +12453,8 @@ var init_real_local_run = __esm({
|
|
|
11645
12453
|
"src/internal/runtime/local-agent/real-local-run.ts"() {
|
|
11646
12454
|
init_errors();
|
|
11647
12455
|
init_loop();
|
|
12456
|
+
init_api_key_validator();
|
|
12457
|
+
init_fixture_mode();
|
|
11648
12458
|
init_fallback_client();
|
|
11649
12459
|
init_model_identifier();
|
|
11650
12460
|
init_router();
|
|
@@ -11921,7 +12731,12 @@ async function runActiveMemory(args) {
|
|
|
11921
12731
|
hits: []
|
|
11922
12732
|
});
|
|
11923
12733
|
}
|
|
11924
|
-
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);
|
|
11925
12740
|
if (cached2 !== void 0) return endRecallSpan(span, args, cached2);
|
|
11926
12741
|
const query = buildQuery(args.userText, args.priorMessages, cfg.queryMode, cfg.recentUserTurns);
|
|
11927
12742
|
if (query.trim().length === 0) {
|
|
@@ -12006,7 +12821,12 @@ function notifyBreaker(breaker, key, status) {
|
|
|
12006
12821
|
else if (status === "ok" || status === "no-recall") breaker.recordSuccess(key);
|
|
12007
12822
|
}
|
|
12008
12823
|
async function finalize(args, queryMode, result) {
|
|
12009
|
-
|
|
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);
|
|
12010
12830
|
if (args.persistTranscripts === true && args.cwd !== void 0) {
|
|
12011
12831
|
await persistActiveMemoryTranscript(args.cwd, {
|
|
12012
12832
|
runId: args.runId ?? `run-${Date.now()}`,
|
|
@@ -12711,53 +13531,6 @@ var init_catalog = __esm({
|
|
|
12711
13531
|
}
|
|
12712
13532
|
});
|
|
12713
13533
|
|
|
12714
|
-
// src/internal/memory/circuit-breaker.ts
|
|
12715
|
-
var DEFAULT_MAX_TIMEOUTS, DEFAULT_COOLDOWN_MS2, CircuitBreaker;
|
|
12716
|
-
var init_circuit_breaker = __esm({
|
|
12717
|
-
"src/internal/memory/circuit-breaker.ts"() {
|
|
12718
|
-
DEFAULT_MAX_TIMEOUTS = 3;
|
|
12719
|
-
DEFAULT_COOLDOWN_MS2 = 6e4;
|
|
12720
|
-
CircuitBreaker = class {
|
|
12721
|
-
constructor(opts = {}) {
|
|
12722
|
-
this.opts = opts;
|
|
12723
|
-
}
|
|
12724
|
-
opts;
|
|
12725
|
-
states = /* @__PURE__ */ new Map();
|
|
12726
|
-
/** @returns true when the breaker is open and the call should be skipped. */
|
|
12727
|
-
shouldSkip(key) {
|
|
12728
|
-
const state2 = this.states.get(key);
|
|
12729
|
-
if (state2 === void 0) return false;
|
|
12730
|
-
if (state2.cooldownUntilMs === 0) return false;
|
|
12731
|
-
if (this.now() < state2.cooldownUntilMs) return true;
|
|
12732
|
-
state2.cooldownUntilMs = 0;
|
|
12733
|
-
state2.consecutiveTimeouts = 0;
|
|
12734
|
-
return false;
|
|
12735
|
-
}
|
|
12736
|
-
recordSuccess(key) {
|
|
12737
|
-
const state2 = this.states.get(key);
|
|
12738
|
-
if (state2 === void 0) return;
|
|
12739
|
-
state2.consecutiveTimeouts = 0;
|
|
12740
|
-
state2.cooldownUntilMs = 0;
|
|
12741
|
-
}
|
|
12742
|
-
recordTimeout(key) {
|
|
12743
|
-
const state2 = this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
12744
|
-
state2.consecutiveTimeouts += 1;
|
|
12745
|
-
if (state2.consecutiveTimeouts >= (this.opts.maxTimeouts ?? DEFAULT_MAX_TIMEOUTS)) {
|
|
12746
|
-
state2.cooldownUntilMs = this.now() + (this.opts.cooldownMs ?? DEFAULT_COOLDOWN_MS2);
|
|
12747
|
-
}
|
|
12748
|
-
this.states.set(key, state2);
|
|
12749
|
-
}
|
|
12750
|
-
/** @internal — tests inspect counter state. */
|
|
12751
|
-
inspect(key) {
|
|
12752
|
-
return this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
12753
|
-
}
|
|
12754
|
-
now() {
|
|
12755
|
-
return this.opts.now?.() ?? Date.now();
|
|
12756
|
-
}
|
|
12757
|
-
};
|
|
12758
|
-
}
|
|
12759
|
-
});
|
|
12760
|
-
|
|
12761
13534
|
// src/internal/persistence/fts5-sanitize.ts
|
|
12762
13535
|
function sanitizeFts5Query(query) {
|
|
12763
13536
|
if (query.length === 0) return query;
|
|
@@ -13923,9 +14696,9 @@ var init_local_agent_memory = __esm({
|
|
|
13923
14696
|
init_active_memory();
|
|
13924
14697
|
init_active_memory_cache();
|
|
13925
14698
|
init_catalog();
|
|
13926
|
-
init_circuit_breaker();
|
|
13927
14699
|
init_index_manager();
|
|
13928
14700
|
init_tools();
|
|
14701
|
+
init_circuit_breaker();
|
|
13929
14702
|
LocalAgentMemory = class {
|
|
13930
14703
|
constructor(options, workspaceCwd, agentId) {
|
|
13931
14704
|
this.options = options;
|