@theokit/sdk 2.15.2 → 2.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -0
- package/dist/a2a/index.cjs +875 -198
- package/dist/a2a/index.cjs.map +1 -1
- package/dist/a2a/index.js +876 -199
- package/dist/a2a/index.js.map +1 -1
- package/dist/{cron-BxLSz1UH.d.cts → cron-Bbg0mBOv.d.ts} +33 -3
- package/dist/{cron-DcaoP7aW.d.ts → cron-ZLSKbDbB.d.cts} +33 -3
- package/dist/cron.cjs +840 -187
- package/dist/cron.cjs.map +1 -1
- package/dist/cron.d.cts +2 -2
- package/dist/cron.d.ts +2 -2
- package/dist/cron.js +840 -187
- package/dist/cron.js.map +1 -1
- package/dist/define-tool.d.ts +9 -2
- package/dist/{errors-Bart0ptP.d.cts → errors-1tVcX3Fq.d.cts} +1 -1
- package/dist/{errors-DJuuubJK.d.ts → errors-qyVYfk9H.d.ts} +1 -1
- package/dist/errors.d.cts +2 -2
- package/dist/eval.cjs +846 -189
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +846 -189
- package/dist/eval.js.map +1 -1
- package/dist/event-bus.d.ts +3 -0
- package/dist/index.cjs +977 -215
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -27
- package/dist/index.d.ts +122 -27
- package/dist/index.js +977 -217
- package/dist/index.js.map +1 -1
- package/dist/internal/agent-loop/tool-dispatch.d.ts +3 -1
- package/dist/internal/agent-loop/tool-result-guard.d.ts +24 -0
- package/dist/internal/agent-loop/tool-timeout.d.ts +23 -0
- package/dist/internal/llm/openai.d.ts +4 -0
- package/dist/internal/llm/sse.d.ts +13 -1
- package/dist/internal/mcp/client.d.ts +1 -1
- package/dist/internal/memory/active-memory.d.ts +1 -1
- package/dist/internal/persistence/conversation-storage-fs.d.cts +7 -1
- package/dist/internal/persistence/conversation-storage-fs.d.ts +7 -1
- package/dist/internal/persistence/conversation-storage-memory.d.cts +7 -1
- package/dist/internal/persistence/conversation-storage-memory.d.ts +7 -1
- package/dist/internal/persistence/pagination.d.cts +8 -0
- package/dist/internal/persistence/pagination.d.ts +8 -0
- package/dist/internal/plugins/index.cjs +135 -0
- package/dist/internal/plugins/index.cjs.map +1 -1
- package/dist/internal/plugins/index.js +135 -0
- package/dist/internal/plugins/index.js.map +1 -1
- package/dist/internal/plugins/manager.d.cts +21 -1
- package/dist/internal/plugins/manager.d.ts +21 -1
- package/dist/internal/plugins/types.d.cts +40 -0
- package/dist/internal/plugins/types.d.ts +40 -0
- package/dist/internal/{memory → resilience}/circuit-breaker.d.ts +5 -1
- package/dist/internal/runtime/hooks/hooks-frontmatter.d.ts +1 -1
- package/dist/internal/runtime/lifecycle/env-policy.d.ts +30 -0
- package/dist/internal/runtime/session/agent-session-store.d.ts +1 -0
- package/dist/internal/telemetry/span-names.d.ts +7 -1
- package/dist/job-queue.d.ts +29 -7
- package/dist/permission-engine.d.ts +32 -7
- package/dist/{run-DXy_MVwz.d.cts → run-pE-34AAo.d.cts} +64 -3
- package/dist/{run-DXy_MVwz.d.ts → run-pE-34AAo.d.ts} +64 -3
- package/dist/sandbox/index.cjs +53 -2
- package/dist/sandbox/index.cjs.map +1 -1
- package/dist/sandbox/index.js +53 -2
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/local-sandbox.d.cts +11 -3
- package/dist/sandbox/local-sandbox.d.ts +11 -3
- package/dist/sandbox/types.d.cts +7 -0
- package/dist/sandbox/types.d.ts +7 -0
- package/dist/types/agent-prims.d.ts +6 -2
- package/dist/types/conversation-storage.d.ts +32 -2
- package/dist/types/mcp.d.ts +20 -0
- package/dist/types/run.d.ts +17 -0
- package/dist/workflow.cjs +6 -3
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.js +6 -3
- package/dist/workflow.js.map +1 -1
- package/package.json +14 -14
package/dist/a2a/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, realpathSync, mkdirSync, lstatSync, readlinkSync, readFileSync, readdirSync } from 'fs';
|
|
2
2
|
import { randomUUID, createHash, randomBytes } from 'crypto';
|
|
3
3
|
import { join, dirname, resolve, sep, relative, isAbsolute } from 'path';
|
|
4
|
-
import { stat, mkdir, readFile, rename, rm, readdir,
|
|
4
|
+
import { stat, mkdir, readFile, rename, rm, readdir, open, unlink, appendFile, statfs, access } from 'fs/promises';
|
|
5
5
|
import { homedir } from 'os';
|
|
6
6
|
import { z, toJSONSchema } from 'zod';
|
|
7
7
|
import { createRequire } from 'module';
|
|
@@ -2545,13 +2545,13 @@ var init_cloud_run = __esm({
|
|
|
2545
2545
|
});
|
|
2546
2546
|
|
|
2547
2547
|
// src/internal/llm/sse.ts
|
|
2548
|
-
async function* parseSseStream(body, signal) {
|
|
2548
|
+
async function* parseSseStream(body, signal, idleTimeoutMs = DEFAULT_SSE_IDLE_MS) {
|
|
2549
2549
|
if (body === null) return;
|
|
2550
2550
|
const reader = body.getReader();
|
|
2551
2551
|
const decoder = new TextDecoder("utf-8");
|
|
2552
2552
|
const state2 = { buffer: "", event: "message", data: "" };
|
|
2553
2553
|
try {
|
|
2554
|
-
for await (const chunk of readChunks(reader, signal)) {
|
|
2554
|
+
for await (const chunk of readChunks(reader, signal, idleTimeoutMs)) {
|
|
2555
2555
|
state2.buffer += decoder.decode(chunk, { stream: true });
|
|
2556
2556
|
for (const record of drainCompleteRecords(state2)) yield record;
|
|
2557
2557
|
}
|
|
@@ -2561,14 +2561,38 @@ async function* parseSseStream(body, signal) {
|
|
|
2561
2561
|
releaseReader(reader);
|
|
2562
2562
|
}
|
|
2563
2563
|
}
|
|
2564
|
-
async function* readChunks(reader, signal) {
|
|
2564
|
+
async function* readChunks(reader, signal, idleTimeoutMs) {
|
|
2565
2565
|
while (true) {
|
|
2566
2566
|
if (signal.aborted) return;
|
|
2567
|
-
const { value, done } = await reader
|
|
2567
|
+
const { value, done } = await readWithIdleTimeout(reader, idleTimeoutMs);
|
|
2568
2568
|
if (done) return;
|
|
2569
2569
|
if (value !== void 0) yield value;
|
|
2570
2570
|
}
|
|
2571
2571
|
}
|
|
2572
|
+
function readWithIdleTimeout(reader, idleTimeoutMs) {
|
|
2573
|
+
if (idleTimeoutMs <= 0) return reader.read();
|
|
2574
|
+
return new Promise(
|
|
2575
|
+
(resolve3, reject) => {
|
|
2576
|
+
const timer = setTimeout(() => {
|
|
2577
|
+
reject(
|
|
2578
|
+
new NetworkError(`SSE stream idle for ${idleTimeoutMs}ms \u2014 upstream stalled`, {
|
|
2579
|
+
code: "stream_idle_timeout"
|
|
2580
|
+
})
|
|
2581
|
+
);
|
|
2582
|
+
}, idleTimeoutMs);
|
|
2583
|
+
reader.read().then(
|
|
2584
|
+
(result) => {
|
|
2585
|
+
clearTimeout(timer);
|
|
2586
|
+
resolve3(result);
|
|
2587
|
+
},
|
|
2588
|
+
(err) => {
|
|
2589
|
+
clearTimeout(timer);
|
|
2590
|
+
reject(err);
|
|
2591
|
+
}
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2594
|
+
);
|
|
2595
|
+
}
|
|
2572
2596
|
async function cancelReaderQuietly(reader) {
|
|
2573
2597
|
try {
|
|
2574
2598
|
await reader.cancel();
|
|
@@ -2619,8 +2643,11 @@ function releaseReader(reader) {
|
|
|
2619
2643
|
} catch {
|
|
2620
2644
|
}
|
|
2621
2645
|
}
|
|
2646
|
+
var DEFAULT_SSE_IDLE_MS;
|
|
2622
2647
|
var init_sse = __esm({
|
|
2623
2648
|
"src/internal/llm/sse.ts"() {
|
|
2649
|
+
init_errors();
|
|
2650
|
+
DEFAULT_SSE_IDLE_MS = 6e4;
|
|
2624
2651
|
}
|
|
2625
2652
|
});
|
|
2626
2653
|
|
|
@@ -3569,9 +3596,16 @@ var init_context = __esm({
|
|
|
3569
3596
|
});
|
|
3570
3597
|
|
|
3571
3598
|
// src/internal/plugins/manager.ts
|
|
3599
|
+
function removeAll(arr, toRemove) {
|
|
3600
|
+
for (const item of toRemove) {
|
|
3601
|
+
const idx = arr.indexOf(item);
|
|
3602
|
+
if (idx !== -1) arr.splice(idx, 1);
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3572
3605
|
var PluginManager;
|
|
3573
3606
|
var init_manager = __esm({
|
|
3574
3607
|
"src/internal/plugins/manager.ts"() {
|
|
3608
|
+
init_errors();
|
|
3575
3609
|
init_context();
|
|
3576
3610
|
PluginManager = class {
|
|
3577
3611
|
#aggregated = {
|
|
@@ -3583,6 +3617,9 @@ var init_manager = __esm({
|
|
|
3583
3617
|
memoryProviders: []
|
|
3584
3618
|
};
|
|
3585
3619
|
#initialized = false;
|
|
3620
|
+
// #68 — registrations of plugins added post-init via `register()`, keyed by
|
|
3621
|
+
// plugin name so a re-register REPLACES (not appends) the prior hooks.
|
|
3622
|
+
#byName = /* @__PURE__ */ new Map();
|
|
3586
3623
|
async initialize(plugins) {
|
|
3587
3624
|
if (this.#initialized) {
|
|
3588
3625
|
throw new Error("PluginManager.initialize called twice \u2014 register only once per process");
|
|
@@ -3600,6 +3637,36 @@ var init_manager = __esm({
|
|
|
3600
3637
|
await this.#dispatchPlugin(plugin);
|
|
3601
3638
|
}
|
|
3602
3639
|
}
|
|
3640
|
+
/**
|
|
3641
|
+
* #68 — register a single `general` plugin AFTER `initialize()` has run.
|
|
3642
|
+
*
|
|
3643
|
+
* The bulk `initialize()` is single-shot (one call per process); late
|
|
3644
|
+
* registration is a distinct, named operation used by adapters that install
|
|
3645
|
+
* a plugin per-session/per-request (e.g. the ACP permission veto, which is
|
|
3646
|
+
* installed once the permission mode + connection are known — after the
|
|
3647
|
+
* agent's own plugins were already initialized).
|
|
3648
|
+
*
|
|
3649
|
+
* Idempotent by plugin NAME: re-registering a plugin with the same name
|
|
3650
|
+
* REPLACES its prior hooks/tools instead of appending duplicates (the ACP
|
|
3651
|
+
* permission plugin is re-installed on every prompt).
|
|
3652
|
+
*
|
|
3653
|
+
* Only `general` plugins may be registered late — model-provider / memory
|
|
3654
|
+
* plugins are resolved during the bulk init and cannot be added afterwards.
|
|
3655
|
+
*/
|
|
3656
|
+
async register(plugin) {
|
|
3657
|
+
if (plugin.kind !== "general") {
|
|
3658
|
+
throw new ConfigurationError(
|
|
3659
|
+
`late register supports general plugins only (got "${plugin.kind}" for "${plugin.name}")`,
|
|
3660
|
+
{ code: "plugin_late_register_kind" }
|
|
3661
|
+
);
|
|
3662
|
+
}
|
|
3663
|
+
const prior = this.#byName.get(plugin.name);
|
|
3664
|
+
if (prior !== void 0) this.#unmerge(prior);
|
|
3665
|
+
const { ctx, registrations } = createPluginContext();
|
|
3666
|
+
await plugin.register(ctx);
|
|
3667
|
+
this.#byName.set(plugin.name, registrations);
|
|
3668
|
+
this.#merge(registrations);
|
|
3669
|
+
}
|
|
3603
3670
|
get aggregated() {
|
|
3604
3671
|
return this.#aggregated;
|
|
3605
3672
|
}
|
|
@@ -3680,6 +3747,64 @@ var init_manager = __esm({
|
|
|
3680
3747
|
}
|
|
3681
3748
|
}
|
|
3682
3749
|
}
|
|
3750
|
+
// #65 — the previously-dead hooks, now wired. Fire-and-forget hooks run
|
|
3751
|
+
// in order (per-handler errors logged, never thrown); transform hooks fold
|
|
3752
|
+
// over the payload (a handler returning a value replaces it).
|
|
3753
|
+
/** @internal */
|
|
3754
|
+
async #runFireAndForget(name, ctx) {
|
|
3755
|
+
for (const h of this.#aggregated.hooks.get(name) ?? []) {
|
|
3756
|
+
try {
|
|
3757
|
+
await h(ctx);
|
|
3758
|
+
} catch (err) {
|
|
3759
|
+
process.stderr.write(
|
|
3760
|
+
`[theokit-sdk] ${name} hook failed: ${err instanceof Error ? err.message : String(err)}
|
|
3761
|
+
`
|
|
3762
|
+
);
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
/** @internal — fold: each handler may return a replacement payload; a throw keeps the prior value. */
|
|
3767
|
+
async #runTransform(name, payload, ctx) {
|
|
3768
|
+
let current = payload;
|
|
3769
|
+
for (const h of this.#aggregated.hooks.get(name) ?? []) {
|
|
3770
|
+
try {
|
|
3771
|
+
const out = await h(current, ctx);
|
|
3772
|
+
if (out !== void 0) current = out;
|
|
3773
|
+
} catch (err) {
|
|
3774
|
+
process.stderr.write(
|
|
3775
|
+
`[theokit-sdk] ${name} hook failed: ${err instanceof Error ? err.message : String(err)}
|
|
3776
|
+
`
|
|
3777
|
+
);
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
return current;
|
|
3781
|
+
}
|
|
3782
|
+
/** #65 — fired after a tool call completes. @internal */
|
|
3783
|
+
runPostToolCallHooks(ctx) {
|
|
3784
|
+
return this.#runFireAndForget("post_tool_call", ctx);
|
|
3785
|
+
}
|
|
3786
|
+
/** #65 — fired before / after each LLM turn. @internal */
|
|
3787
|
+
runPreLlmCallHooks(ctx) {
|
|
3788
|
+
return this.#runFireAndForget("pre_llm_call", ctx);
|
|
3789
|
+
}
|
|
3790
|
+
runPostLlmCallHooks(ctx) {
|
|
3791
|
+
return this.#runFireAndForget("post_llm_call", ctx);
|
|
3792
|
+
}
|
|
3793
|
+
/** #65 — fired at run start / end. @internal */
|
|
3794
|
+
runOnSessionStartHooks(ctx) {
|
|
3795
|
+
return this.#runFireAndForget("on_session_start", ctx);
|
|
3796
|
+
}
|
|
3797
|
+
runOnSessionEndHooks(ctx) {
|
|
3798
|
+
return this.#runFireAndForget("on_session_end", ctx);
|
|
3799
|
+
}
|
|
3800
|
+
/** #65/#57 — transform tool results before they reach the LLM (the #57 seam). @internal */
|
|
3801
|
+
runTransformToolResultHooks(results, ctx) {
|
|
3802
|
+
return this.#runTransform("transform_tool_result", results, ctx);
|
|
3803
|
+
}
|
|
3804
|
+
/** #65 — transform the LLM output text before it is consumed. @internal */
|
|
3805
|
+
runTransformLlmOutputHooks(output, ctx) {
|
|
3806
|
+
return this.#runTransform("transform_llm_output", output, ctx);
|
|
3807
|
+
}
|
|
3683
3808
|
async #dispatchPlugin(plugin) {
|
|
3684
3809
|
if (plugin.kind === "general") {
|
|
3685
3810
|
const { ctx, registrations } = createPluginContext();
|
|
@@ -3707,6 +3832,22 @@ var init_manager = __esm({
|
|
|
3707
3832
|
}
|
|
3708
3833
|
this.#aggregated.injected.push(...r.injected);
|
|
3709
3834
|
}
|
|
3835
|
+
/**
|
|
3836
|
+
* #68 — inverse of #merge: remove a prior registration's contributions from
|
|
3837
|
+
* the aggregated view by object identity. Used by `register()` to replace a
|
|
3838
|
+
* same-named plugin's hooks/tools instead of accumulating duplicates.
|
|
3839
|
+
*/
|
|
3840
|
+
#unmerge(r) {
|
|
3841
|
+
removeAll(this.#aggregated.tools, r.tools);
|
|
3842
|
+
removeAll(this.#aggregated.commands, r.commands);
|
|
3843
|
+
removeAll(this.#aggregated.injected, r.injected);
|
|
3844
|
+
for (const [hook, handlers] of r.hooks.entries()) {
|
|
3845
|
+
const existing = this.#aggregated.hooks.get(hook);
|
|
3846
|
+
if (existing === void 0) continue;
|
|
3847
|
+
removeAll(existing, handlers);
|
|
3848
|
+
if (existing.length === 0) this.#aggregated.hooks.delete(hook);
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3710
3851
|
};
|
|
3711
3852
|
}
|
|
3712
3853
|
});
|
|
@@ -3731,7 +3872,12 @@ var init_span_names = __esm({
|
|
|
3731
3872
|
LLM_CALL: "llm.call"
|
|
3732
3873
|
};
|
|
3733
3874
|
HISTOGRAM_NAMES = {
|
|
3734
|
-
MEMORY_RECALL_DURATION_MS: "theokit_memory_recall_duration_ms"
|
|
3875
|
+
MEMORY_RECALL_DURATION_MS: "theokit_memory_recall_duration_ms",
|
|
3876
|
+
TOOL_CALL_DURATION_MS: "theokit_tool_call_duration_ms",
|
|
3877
|
+
LLM_CALL_DURATION_MS: "theokit_llm_call_duration_ms",
|
|
3878
|
+
LLM_TOKENS: "theokit_llm_tokens",
|
|
3879
|
+
/** M3 #66 — count of finishes where the provider omitted usage (silent undercount). */
|
|
3880
|
+
LLM_USAGE_MISSING: "theokit_llm_usage_missing"
|
|
3735
3881
|
};
|
|
3736
3882
|
}
|
|
3737
3883
|
});
|
|
@@ -4096,7 +4242,25 @@ function createTelemetry(settings) {
|
|
|
4096
4242
|
enabled: true,
|
|
4097
4243
|
includeContent: settings.includeContent === true,
|
|
4098
4244
|
startSpan: startNewSpan,
|
|
4099
|
-
|
|
4245
|
+
// M3 #64 — actually nest the child under its parent instead of discarding it.
|
|
4246
|
+
// The parent's SpanContext is set on a fresh OTel context so the child links
|
|
4247
|
+
// to it (traceId + parentSpanId), reconstructing the causal trace tree. Falls
|
|
4248
|
+
// back to a root span when the parent has no valid span id (telemetry off /
|
|
4249
|
+
// NOOP), preserving the pre-M3 behavior for parentless callers.
|
|
4250
|
+
startChildSpan: (parent, name, attrs) => {
|
|
4251
|
+
const redactedAttrs = attrs === void 0 ? void 0 : redactAttrs(attrs);
|
|
4252
|
+
const opts = redactedAttrs ? { attributes: redactedAttrs } : void 0;
|
|
4253
|
+
const pctx = safe(() => parent?.spanContext(), void 0);
|
|
4254
|
+
const span = safe(() => {
|
|
4255
|
+
if (pctx !== void 0 && pctx.spanId !== "0".repeat(16)) {
|
|
4256
|
+
const childCtx = otel.trace.setSpanContext(otel.context.active(), pctx);
|
|
4257
|
+
return tracer.startSpan(name, opts, childCtx);
|
|
4258
|
+
}
|
|
4259
|
+
return tracer.startSpan(name, opts);
|
|
4260
|
+
}, NOOP_SPAN);
|
|
4261
|
+
if (span !== NOOP_SPAN) openSpans.add(span);
|
|
4262
|
+
return wrapSpan(span, openSpans);
|
|
4263
|
+
},
|
|
4100
4264
|
recordHistogram,
|
|
4101
4265
|
endAll: () => {
|
|
4102
4266
|
for (const span of openSpans) safe(() => span.end(), void 0);
|
|
@@ -4166,12 +4330,65 @@ var init_tracer = __esm({
|
|
|
4166
4330
|
warnedOnce = false;
|
|
4167
4331
|
}
|
|
4168
4332
|
});
|
|
4333
|
+
|
|
4334
|
+
// src/internal/runtime/lifecycle/env-policy.ts
|
|
4335
|
+
function isSecretName(name) {
|
|
4336
|
+
return SECRET_PATTERNS.some((re) => re.test(name));
|
|
4337
|
+
}
|
|
4338
|
+
function inheritsUnderPolicy(name, policy) {
|
|
4339
|
+
if (policy === "all") return true;
|
|
4340
|
+
if (policy === "core") return CORE_VARS.includes(name);
|
|
4341
|
+
return !isSecretName(name);
|
|
4342
|
+
}
|
|
4343
|
+
function resolveChildEnv(options = {}) {
|
|
4344
|
+
const parent = options.parent ?? process.env;
|
|
4345
|
+
const policy = options.policy ?? "inherit-scrubbed";
|
|
4346
|
+
const base = {};
|
|
4347
|
+
for (const [name, value] of Object.entries(parent)) {
|
|
4348
|
+
if (value !== void 0 && inheritsUnderPolicy(name, policy)) base[name] = value;
|
|
4349
|
+
}
|
|
4350
|
+
for (const [name, value] of Object.entries(options.overrides ?? {})) {
|
|
4351
|
+
base[name] = value;
|
|
4352
|
+
}
|
|
4353
|
+
return base;
|
|
4354
|
+
}
|
|
4355
|
+
var SECRET_PATTERNS, CORE_VARS;
|
|
4356
|
+
var init_env_policy = __esm({
|
|
4357
|
+
"src/internal/runtime/lifecycle/env-policy.ts"() {
|
|
4358
|
+
SECRET_PATTERNS = [
|
|
4359
|
+
/KEY/i,
|
|
4360
|
+
/SECRET/i,
|
|
4361
|
+
/TOKEN/i,
|
|
4362
|
+
/PASSWORD/i,
|
|
4363
|
+
/PASSWD/i,
|
|
4364
|
+
/PASSPHRASE/i,
|
|
4365
|
+
/[_-]PWD/i,
|
|
4366
|
+
/CREDENTIAL/i,
|
|
4367
|
+
/PRIVATE/i,
|
|
4368
|
+
/_AUTH/i
|
|
4369
|
+
];
|
|
4370
|
+
CORE_VARS = [
|
|
4371
|
+
"PATH",
|
|
4372
|
+
"HOME",
|
|
4373
|
+
"SHELL",
|
|
4374
|
+
"LANG",
|
|
4375
|
+
"LC_ALL",
|
|
4376
|
+
"LC_CTYPE",
|
|
4377
|
+
"TMPDIR",
|
|
4378
|
+
"TMP",
|
|
4379
|
+
"TEMP",
|
|
4380
|
+
"USER",
|
|
4381
|
+
"LOGNAME"
|
|
4382
|
+
];
|
|
4383
|
+
}
|
|
4384
|
+
});
|
|
4169
4385
|
function spawnAndCollect(options) {
|
|
4170
4386
|
return new Promise((resolve3) => {
|
|
4171
4387
|
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
4172
4388
|
const spawnOptions = {
|
|
4173
4389
|
cwd: options.cwd,
|
|
4174
|
-
|
|
4390
|
+
// #54 — scrub secret-like parent env by default; `options.env` still wins.
|
|
4391
|
+
env: resolveChildEnv({ policy: options.envPolicy, overrides: options.env })
|
|
4175
4392
|
};
|
|
4176
4393
|
const child = spawn(options.command, options.args ?? [], spawnOptions);
|
|
4177
4394
|
let stdout = "";
|
|
@@ -4212,6 +4429,7 @@ function spawnAndCollect(options) {
|
|
|
4212
4429
|
}
|
|
4213
4430
|
var init_spawn_collect = __esm({
|
|
4214
4431
|
"src/internal/runtime/lifecycle/spawn-collect.ts"() {
|
|
4432
|
+
init_env_policy();
|
|
4215
4433
|
}
|
|
4216
4434
|
});
|
|
4217
4435
|
|
|
@@ -4400,15 +4618,24 @@ function warnMalformed(agentId, line) {
|
|
|
4400
4618
|
`
|
|
4401
4619
|
);
|
|
4402
4620
|
}
|
|
4621
|
+
function hydrateSessionLine(parsed) {
|
|
4622
|
+
if (typeof parsed.text !== "string" || parsed.role === void 0) return void 0;
|
|
4623
|
+
if (parsed.role === "user" || parsed.role === "assistant") {
|
|
4624
|
+
return { role: parsed.role, text: parsed.text };
|
|
4625
|
+
}
|
|
4626
|
+
if (parsed.role === "tool_call" || parsed.role === "tool_result") {
|
|
4627
|
+
const label = parsed.role === "tool_call" ? "tool call" : "tool result";
|
|
4628
|
+
return { role: "assistant", text: `[${label}] ${parsed.text}` };
|
|
4629
|
+
}
|
|
4630
|
+
return void 0;
|
|
4631
|
+
}
|
|
4403
4632
|
async function readSessionFile(cwd, agentId) {
|
|
4404
4633
|
const lines = await readJsonlLines(cwd, agentId);
|
|
4405
4634
|
const messages = [];
|
|
4406
4635
|
for (const line of lines) {
|
|
4407
4636
|
try {
|
|
4408
|
-
const
|
|
4409
|
-
if (
|
|
4410
|
-
messages.push({ role: parsed.role, text: parsed.text });
|
|
4411
|
-
}
|
|
4637
|
+
const msg = hydrateSessionLine(JSON.parse(line));
|
|
4638
|
+
if (msg !== void 0) messages.push(msg);
|
|
4412
4639
|
} catch {
|
|
4413
4640
|
warnMalformed(agentId, line);
|
|
4414
4641
|
}
|
|
@@ -4435,29 +4662,70 @@ async function readAllPersistedMessages(cwd, agentId) {
|
|
|
4435
4662
|
return messages;
|
|
4436
4663
|
}
|
|
4437
4664
|
async function appendAnyPersistedMessage(cwd, agentId, record) {
|
|
4438
|
-
|
|
4439
|
-
await mkdir(dirname(path), { recursive: true });
|
|
4440
|
-
await appendFile(path, `${redactSecrets(JSON.stringify(record))}
|
|
4441
|
-
`, "utf8");
|
|
4665
|
+
await appendPersistedMessages(cwd, agentId, [record]);
|
|
4442
4666
|
}
|
|
4443
|
-
async function
|
|
4667
|
+
async function appendPersistedMessages(cwd, agentId, records) {
|
|
4668
|
+
if (records.length === 0) return;
|
|
4444
4669
|
const path = sessionFilePath(cwd, agentId);
|
|
4445
|
-
|
|
4670
|
+
const payload = records.map((r) => `${redactSecrets(JSON.stringify(r))}
|
|
4671
|
+
`).join("");
|
|
4672
|
+
const dir = dirname(path);
|
|
4673
|
+
let written = false;
|
|
4674
|
+
const attempt = async () => {
|
|
4675
|
+
await mkdir(dir, { recursive: true });
|
|
4676
|
+
await withFileLock(path, async () => {
|
|
4677
|
+
await appendFile(path, payload, "utf8");
|
|
4678
|
+
written = true;
|
|
4679
|
+
});
|
|
4680
|
+
};
|
|
4446
4681
|
try {
|
|
4447
|
-
|
|
4448
|
-
} catch {
|
|
4449
|
-
|
|
4682
|
+
await attempt();
|
|
4683
|
+
} catch (cause) {
|
|
4684
|
+
if (written || cause.code !== "ENOENT") throw cause;
|
|
4685
|
+
await attempt();
|
|
4450
4686
|
}
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4687
|
+
}
|
|
4688
|
+
async function rewriteLockedSession(path, transform) {
|
|
4689
|
+
await withFileLock(path, async () => {
|
|
4690
|
+
let raw;
|
|
4691
|
+
try {
|
|
4692
|
+
raw = await readFile(path, "utf8");
|
|
4693
|
+
} catch {
|
|
4694
|
+
return;
|
|
4695
|
+
}
|
|
4696
|
+
const lines = raw.split("\n").filter((line) => line.length > 0);
|
|
4697
|
+
const next = transform(lines);
|
|
4698
|
+
if (next === void 0) return;
|
|
4699
|
+
await replaceFileAtomic(path, next);
|
|
4700
|
+
});
|
|
4701
|
+
}
|
|
4702
|
+
async function compactSessionFile(cwd, agentId, maxTurns) {
|
|
4703
|
+
const path = sessionFilePath(cwd, agentId);
|
|
4704
|
+
if (!existsSync(path)) return;
|
|
4705
|
+
await rewriteLockedSession(
|
|
4706
|
+
path,
|
|
4707
|
+
(lines) => lines.length <= maxTurns * 2 ? void 0 : `${lines.slice(-maxTurns).join("\n")}
|
|
4708
|
+
`
|
|
4709
|
+
);
|
|
4710
|
+
}
|
|
4711
|
+
async function truncateSessionTo(cwd, agentId, keepCount) {
|
|
4712
|
+
const path = sessionFilePath(cwd, agentId);
|
|
4713
|
+
if (!existsSync(path)) return 0;
|
|
4714
|
+
let kept = 0;
|
|
4715
|
+
await rewriteLockedSession(path, (lines) => {
|
|
4716
|
+
const keep = Math.max(0, Math.min(keepCount, lines.length));
|
|
4717
|
+
kept = keep;
|
|
4718
|
+
if (keep === lines.length) return void 0;
|
|
4719
|
+
return keep === 0 ? "" : `${lines.slice(0, keep).join("\n")}
|
|
4454
4720
|
`;
|
|
4455
|
-
|
|
4721
|
+
});
|
|
4722
|
+
return kept;
|
|
4456
4723
|
}
|
|
4457
4724
|
var VALID_ROLES;
|
|
4458
4725
|
var init_agent_session_store = __esm({
|
|
4459
4726
|
"src/internal/runtime/session/agent-session-store.ts"() {
|
|
4460
4727
|
init_atomic_write();
|
|
4728
|
+
init_file_lock();
|
|
4461
4729
|
init_security();
|
|
4462
4730
|
VALID_ROLES = /* @__PURE__ */ new Set([
|
|
4463
4731
|
"user",
|
|
@@ -4468,6 +4736,18 @@ var init_agent_session_store = __esm({
|
|
|
4468
4736
|
]);
|
|
4469
4737
|
}
|
|
4470
4738
|
});
|
|
4739
|
+
|
|
4740
|
+
// src/internal/persistence/pagination.ts
|
|
4741
|
+
function paginate(items, opts) {
|
|
4742
|
+
if (opts === void 0 || opts.offset === void 0 && opts.limit === void 0) return items;
|
|
4743
|
+
const start = Math.max(0, opts.offset ?? 0);
|
|
4744
|
+
const end = opts.limit === void 0 ? items.length : start + Math.max(0, opts.limit);
|
|
4745
|
+
return items.slice(start, end);
|
|
4746
|
+
}
|
|
4747
|
+
var init_pagination = __esm({
|
|
4748
|
+
"src/internal/persistence/pagination.ts"() {
|
|
4749
|
+
}
|
|
4750
|
+
});
|
|
4471
4751
|
function toStoredMessage(record) {
|
|
4472
4752
|
return {
|
|
4473
4753
|
role: record.role,
|
|
@@ -4475,11 +4755,15 @@ function toStoredMessage(record) {
|
|
|
4475
4755
|
at: record.at
|
|
4476
4756
|
};
|
|
4477
4757
|
}
|
|
4758
|
+
function toRecord(message) {
|
|
4759
|
+
return { role: message.role, text: message.content, at: message.at ?? Date.now() };
|
|
4760
|
+
}
|
|
4478
4761
|
var FileSystemConversationStorage;
|
|
4479
4762
|
var init_conversation_storage_fs = __esm({
|
|
4480
4763
|
"src/internal/persistence/conversation-storage-fs.ts"() {
|
|
4481
4764
|
init_agent_session_store();
|
|
4482
4765
|
init_security();
|
|
4766
|
+
init_pagination();
|
|
4483
4767
|
FileSystemConversationStorage = class {
|
|
4484
4768
|
#root;
|
|
4485
4769
|
constructor(opts = {}) {
|
|
@@ -4489,23 +4773,31 @@ var init_conversation_storage_fs = __esm({
|
|
|
4489
4773
|
get root() {
|
|
4490
4774
|
return this.#root;
|
|
4491
4775
|
}
|
|
4492
|
-
async getMessages(conversationId) {
|
|
4776
|
+
async getMessages(conversationId, opts) {
|
|
4493
4777
|
const records = await readAllPersistedMessages(this.#root, conversationId);
|
|
4494
|
-
|
|
4778
|
+
const all = records.map(toStoredMessage);
|
|
4779
|
+
return paginate(all, opts);
|
|
4495
4780
|
}
|
|
4496
4781
|
async appendMessage(conversationId, message) {
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4782
|
+
await appendAnyPersistedMessage(this.#root, conversationId, toRecord(message));
|
|
4783
|
+
}
|
|
4784
|
+
async appendMessages(conversationId, messages) {
|
|
4785
|
+
await appendPersistedMessages(this.#root, conversationId, messages.map(toRecord));
|
|
4786
|
+
}
|
|
4787
|
+
async truncateConversation(conversationId, keepCount) {
|
|
4788
|
+
return truncateSessionTo(this.#root, conversationId, keepCount);
|
|
4503
4789
|
}
|
|
4504
4790
|
async deleteConversation(conversationId) {
|
|
4505
4791
|
const safe2 = sanitizeIdentifier(conversationId, { maxLen: 128 });
|
|
4506
4792
|
const dirPath = safePathJoin(this.#root, ".theokit", "agents", safe2);
|
|
4507
4793
|
await rm(dirPath, { recursive: true, force: true });
|
|
4508
4794
|
}
|
|
4795
|
+
async deleteScope(prefix) {
|
|
4796
|
+
const ids = await this.listConversationIds();
|
|
4797
|
+
const matching = ids.filter((id) => id.startsWith(prefix));
|
|
4798
|
+
for (const id of matching) await this.deleteConversation(id);
|
|
4799
|
+
return matching.length;
|
|
4800
|
+
}
|
|
4509
4801
|
async listConversationIds(opts = {}) {
|
|
4510
4802
|
const agentsRoot = safePathJoin(this.#root, ".theokit", "agents");
|
|
4511
4803
|
let entries;
|
|
@@ -4602,12 +4894,19 @@ async function readPersistedForCache(adapter, agentId) {
|
|
|
4602
4894
|
const records = await adapter.getMessages(agentId);
|
|
4603
4895
|
const out = [];
|
|
4604
4896
|
for (const r of records) {
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
}
|
|
4897
|
+
const folded = foldStoredToSession(r);
|
|
4898
|
+
if (folded !== void 0) out.push(folded);
|
|
4608
4899
|
}
|
|
4609
4900
|
return out;
|
|
4610
4901
|
}
|
|
4902
|
+
function foldStoredToSession(r) {
|
|
4903
|
+
if (r.role === "user" || r.role === "assistant") return { role: r.role, text: r.content };
|
|
4904
|
+
if (r.role === "tool_call" || r.role === "tool_result") {
|
|
4905
|
+
const label = r.role === "tool_call" ? "tool call" : "tool result";
|
|
4906
|
+
return { role: "assistant", text: `[${label}] ${r.content}` };
|
|
4907
|
+
}
|
|
4908
|
+
return void 0;
|
|
4909
|
+
}
|
|
4611
4910
|
async function flushSessionWrites() {
|
|
4612
4911
|
while (pendingAppends.size > 0) {
|
|
4613
4912
|
const all = Array.from(pendingAppends.values());
|
|
@@ -7178,11 +7477,32 @@ function reasoningEffortFromParams(params) {
|
|
|
7178
7477
|
const thinking = params?.find((p) => p.id === "thinking");
|
|
7179
7478
|
return thinking !== void 0 && thinking.value.length > 0 ? thinking.value : void 0;
|
|
7180
7479
|
}
|
|
7480
|
+
function emitLlmMetrics(inputs, result, startAt) {
|
|
7481
|
+
inputs.telemetry?.recordHistogram(HISTOGRAM_NAMES.LLM_CALL_DURATION_MS, Date.now() - startAt, {
|
|
7482
|
+
provider: inputs.llm.name
|
|
7483
|
+
});
|
|
7484
|
+
if (result.inputTokens === void 0 && result.outputTokens === void 0) {
|
|
7485
|
+
inputs.telemetry?.recordHistogram(HISTOGRAM_NAMES.LLM_USAGE_MISSING, 1, {
|
|
7486
|
+
provider: inputs.llm.name
|
|
7487
|
+
});
|
|
7488
|
+
process.stderr.write(
|
|
7489
|
+
`[theokit-sdk] llm usage missing from ${inputs.llm.name} finish \u2014 budget may undercount
|
|
7490
|
+
`
|
|
7491
|
+
);
|
|
7492
|
+
return;
|
|
7493
|
+
}
|
|
7494
|
+
inputs.telemetry?.recordHistogram(
|
|
7495
|
+
HISTOGRAM_NAMES.LLM_TOKENS,
|
|
7496
|
+
(result.inputTokens ?? 0) + (result.outputTokens ?? 0),
|
|
7497
|
+
{ provider: inputs.llm.name }
|
|
7498
|
+
);
|
|
7499
|
+
}
|
|
7181
7500
|
async function streamLlmTurn(inputs, ctx) {
|
|
7182
|
-
const llmSpan = inputs.telemetry?.
|
|
7501
|
+
const llmSpan = inputs.telemetry?.startChildSpan(ctx.sendSpan, "llm.call", {
|
|
7183
7502
|
"model.id": inputs.model.id ?? "auto",
|
|
7184
7503
|
provider: inputs.llm.name
|
|
7185
7504
|
});
|
|
7505
|
+
const startAt = Date.now();
|
|
7186
7506
|
const signal = inputs.signal ?? new AbortController().signal;
|
|
7187
7507
|
const generator = inputs.llm.stream(
|
|
7188
7508
|
{
|
|
@@ -7225,6 +7545,7 @@ async function streamLlmTurn(inputs, ctx) {
|
|
|
7225
7545
|
inputTokens: result.inputTokens ?? 0,
|
|
7226
7546
|
outputTokens: result.outputTokens ?? 0
|
|
7227
7547
|
});
|
|
7548
|
+
emitLlmMetrics(inputs, result, startAt);
|
|
7228
7549
|
llmSpan?.end();
|
|
7229
7550
|
const stripped = stripThinkBlocks(collected.accumulatedText);
|
|
7230
7551
|
return {
|
|
@@ -7321,6 +7642,7 @@ async function emitReasoningDeltaCallback(inputs, text) {
|
|
|
7321
7642
|
var init_loop_llm_stream = __esm({
|
|
7322
7643
|
"src/internal/agent-loop/loop-llm-stream.ts"() {
|
|
7323
7644
|
init_safe_call();
|
|
7645
|
+
init_span_names();
|
|
7324
7646
|
init_strip_think();
|
|
7325
7647
|
init_message_builders();
|
|
7326
7648
|
}
|
|
@@ -7539,21 +7861,21 @@ async function executeTool(inputs, resolved, call) {
|
|
|
7539
7861
|
}
|
|
7540
7862
|
if (resolved.origin === "shell") return runShellTool(inputs, call);
|
|
7541
7863
|
if (resolved.origin === "memory") return runMemoryTool(resolved, call);
|
|
7542
|
-
if (resolved.origin === "custom") return runCustomTool(resolved, call);
|
|
7864
|
+
if (resolved.origin === "custom") return runCustomTool(resolved, call, inputs.signal);
|
|
7543
7865
|
return runMcpTool(inputs, resolved, call);
|
|
7544
7866
|
}
|
|
7545
7867
|
async function runMemoryTool(resolved, call) {
|
|
7546
7868
|
return runHandlerTool("memory", resolved.memoryHandler, call);
|
|
7547
7869
|
}
|
|
7548
|
-
async function runCustomTool(resolved, call) {
|
|
7549
|
-
return runHandlerTool("custom", resolved.customHandler, call);
|
|
7870
|
+
async function runCustomTool(resolved, call, signal) {
|
|
7871
|
+
return runHandlerTool("custom", resolved.customHandler, call, signal);
|
|
7550
7872
|
}
|
|
7551
|
-
async function runHandlerTool(kind, handler, call) {
|
|
7873
|
+
async function runHandlerTool(kind, handler, call, signal) {
|
|
7552
7874
|
if (handler === void 0) {
|
|
7553
7875
|
return { stdout: "", stderr: `${kind} tool ${call.name} has no handler`, exitCode: 127 };
|
|
7554
7876
|
}
|
|
7555
7877
|
try {
|
|
7556
|
-
const stdout = await handler(call.input);
|
|
7878
|
+
const stdout = await handler(call.input, { signal });
|
|
7557
7879
|
return { stdout, stderr: "", exitCode: 0 };
|
|
7558
7880
|
} catch (cause) {
|
|
7559
7881
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
@@ -7604,22 +7926,71 @@ var init_tool_executors = __esm({
|
|
|
7604
7926
|
}
|
|
7605
7927
|
});
|
|
7606
7928
|
|
|
7929
|
+
// src/internal/agent-loop/tool-timeout.ts
|
|
7930
|
+
function abortedResult(signal) {
|
|
7931
|
+
const reason = signal.reason;
|
|
7932
|
+
const timedOut = reason?.name === "TimeoutError";
|
|
7933
|
+
return {
|
|
7934
|
+
stdout: "",
|
|
7935
|
+
stderr: timedOut ? "tool execution timed out" : "tool execution aborted",
|
|
7936
|
+
exitCode: TOOL_ABORTED_EXIT
|
|
7937
|
+
};
|
|
7938
|
+
}
|
|
7939
|
+
function raceToolExecution(exec, opts) {
|
|
7940
|
+
const { signal, timeoutMs } = opts;
|
|
7941
|
+
if (signal === void 0 && timeoutMs === void 0) return exec;
|
|
7942
|
+
const signals = [];
|
|
7943
|
+
if (signal !== void 0) signals.push(signal);
|
|
7944
|
+
if (timeoutMs !== void 0) signals.push(AbortSignal.timeout(timeoutMs));
|
|
7945
|
+
const merged = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
7946
|
+
if (merged.aborted) return Promise.resolve(abortedResult(merged));
|
|
7947
|
+
return new Promise((resolve3, reject) => {
|
|
7948
|
+
let settled = false;
|
|
7949
|
+
const onAbort = () => {
|
|
7950
|
+
if (settled) return;
|
|
7951
|
+
settled = true;
|
|
7952
|
+
resolve3(abortedResult(merged));
|
|
7953
|
+
};
|
|
7954
|
+
merged.addEventListener("abort", onAbort, { once: true });
|
|
7955
|
+
exec.then(
|
|
7956
|
+
(r) => {
|
|
7957
|
+
if (settled) return;
|
|
7958
|
+
settled = true;
|
|
7959
|
+
merged.removeEventListener("abort", onAbort);
|
|
7960
|
+
resolve3(r);
|
|
7961
|
+
},
|
|
7962
|
+
(e) => {
|
|
7963
|
+
if (settled) return;
|
|
7964
|
+
settled = true;
|
|
7965
|
+
merged.removeEventListener("abort", onAbort);
|
|
7966
|
+
reject(e);
|
|
7967
|
+
}
|
|
7968
|
+
);
|
|
7969
|
+
});
|
|
7970
|
+
}
|
|
7971
|
+
var TOOL_ABORTED_EXIT;
|
|
7972
|
+
var init_tool_timeout = __esm({
|
|
7973
|
+
"src/internal/agent-loop/tool-timeout.ts"() {
|
|
7974
|
+
TOOL_ABORTED_EXIT = 124;
|
|
7975
|
+
}
|
|
7976
|
+
});
|
|
7977
|
+
|
|
7607
7978
|
// src/internal/agent-loop/tool-dispatch.ts
|
|
7608
|
-
async function dispatchTools(inputs, tools, toolCalls, events) {
|
|
7979
|
+
async function dispatchTools(inputs, tools, toolCalls, events, parentSpan) {
|
|
7609
7980
|
const maxConcurrent = inputs.maxConcurrentTools ?? 4;
|
|
7610
7981
|
return mapWithConcurrency(
|
|
7611
7982
|
toolCalls,
|
|
7612
7983
|
maxConcurrent,
|
|
7613
|
-
(call) => dispatchSingleCall(inputs, tools, call, events)
|
|
7984
|
+
(call) => dispatchSingleCall(inputs, tools, call, events, parentSpan)
|
|
7614
7985
|
);
|
|
7615
7986
|
}
|
|
7616
|
-
async function dispatchSingleCall(inputs, tools, call, events) {
|
|
7987
|
+
async function dispatchSingleCall(inputs, tools, call, events, parentSpan) {
|
|
7617
7988
|
const { call: workingCall, repairs } = applyRepairAndExtractCall(tools, call);
|
|
7618
7989
|
const callId = generateCallId();
|
|
7619
7990
|
const forkVeto = vetoFromForkWhitelist(inputs, workingCall, callId, events);
|
|
7620
7991
|
if (forkVeto !== void 0) return forkVeto;
|
|
7621
7992
|
const resolved = tools.find((tool) => tool.name === workingCall.name);
|
|
7622
|
-
const toolSpan = startToolCallSpan(inputs, workingCall, resolved, callId, repairs);
|
|
7993
|
+
const toolSpan = startToolCallSpan(inputs, workingCall, resolved, callId, repairs, parentSpan);
|
|
7623
7994
|
events.push(buildToolUseRunning(inputs, callId, workingCall));
|
|
7624
7995
|
const pluginVeto = await vetoFromPluginPreHook(inputs, workingCall, callId, events);
|
|
7625
7996
|
if (pluginVeto !== void 0) {
|
|
@@ -7636,6 +8007,13 @@ async function dispatchSingleCall(inputs, tools, call, events) {
|
|
|
7636
8007
|
return fileVeto;
|
|
7637
8008
|
}
|
|
7638
8009
|
const result = await runToolWithLifecycle(inputs, resolved, workingCall, callId);
|
|
8010
|
+
await inputs.pluginManager?.runPostToolCallHooks({
|
|
8011
|
+
name: workingCall.name,
|
|
8012
|
+
args: workingCall.input,
|
|
8013
|
+
result: { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode },
|
|
8014
|
+
agentId: inputs.agentId,
|
|
8015
|
+
runId: inputs.runId
|
|
8016
|
+
});
|
|
7639
8017
|
return finalizeSpanAndPostHook(inputs, workingCall, callId, result, events, toolSpan);
|
|
7640
8018
|
}
|
|
7641
8019
|
function applyRepairAndExtractCall(tools, call) {
|
|
@@ -7668,8 +8046,8 @@ function vetoFromForkWhitelist(inputs, call, callId, events) {
|
|
|
7668
8046
|
content: `Tool blocked by fork whitelist: ${whitelistDecision.reason}`
|
|
7669
8047
|
};
|
|
7670
8048
|
}
|
|
7671
|
-
function startToolCallSpan(inputs, call, resolved, callId, repairs) {
|
|
7672
|
-
const toolSpan = inputs.telemetry?.
|
|
8049
|
+
function startToolCallSpan(inputs, call, resolved, callId, repairs, parentSpan) {
|
|
8050
|
+
const toolSpan = inputs.telemetry?.startChildSpan(parentSpan, "tool.call", {
|
|
7673
8051
|
"tool.name": call.name,
|
|
7674
8052
|
"tool.origin": resolved?.origin ?? "unknown",
|
|
7675
8053
|
callId
|
|
@@ -7733,8 +8111,14 @@ async function runToolWithLifecycle(inputs, resolved, call, callId) {
|
|
|
7733
8111
|
conversationId: inputs.agentId,
|
|
7734
8112
|
callId
|
|
7735
8113
|
});
|
|
7736
|
-
const result = await executeTool(inputs, resolved, call)
|
|
8114
|
+
const result = await raceToolExecution(executeTool(inputs, resolved, call), {
|
|
8115
|
+
signal: inputs.signal,
|
|
8116
|
+
timeoutMs: inputs.perToolTimeoutMs
|
|
8117
|
+
});
|
|
7737
8118
|
const durationMs = Date.now() - startAt;
|
|
8119
|
+
inputs.telemetry?.recordHistogram(HISTOGRAM_NAMES.TOOL_CALL_DURATION_MS, durationMs, {
|
|
8120
|
+
"tool.name": call.name
|
|
8121
|
+
});
|
|
7738
8122
|
if (result.exitCode !== void 0 && result.exitCode !== 0 && result.exitCode !== null) {
|
|
7739
8123
|
await safeEmitToolHook(inputs.onToolError, {
|
|
7740
8124
|
toolName: call.name,
|
|
@@ -7837,8 +8221,44 @@ var init_tool_dispatch = __esm({
|
|
|
7837
8221
|
init_ids();
|
|
7838
8222
|
init_async_local_storage();
|
|
7839
8223
|
init_map_with_concurrency();
|
|
8224
|
+
init_span_names();
|
|
7840
8225
|
init_repair_middleware();
|
|
7841
8226
|
init_tool_executors();
|
|
8227
|
+
init_tool_timeout();
|
|
8228
|
+
}
|
|
8229
|
+
});
|
|
8230
|
+
|
|
8231
|
+
// src/internal/agent-loop/tool-result-guard.ts
|
|
8232
|
+
function guardText(content, opts) {
|
|
8233
|
+
let out = content;
|
|
8234
|
+
if (opts.redactPii === true) {
|
|
8235
|
+
for (const re of PII_PATTERNS) out = out.replace(re, "[REDACTED]");
|
|
8236
|
+
}
|
|
8237
|
+
if (opts.delimit === true) {
|
|
8238
|
+
const safe2 = out.split(CLOSE).join("</ untrusted-tool-output>");
|
|
8239
|
+
out = `${OPEN}
|
|
8240
|
+
${safe2}
|
|
8241
|
+
${CLOSE}`;
|
|
8242
|
+
}
|
|
8243
|
+
return out;
|
|
8244
|
+
}
|
|
8245
|
+
function applyToolResultGuard(parts, opts) {
|
|
8246
|
+
if (opts.delimit !== true && opts.redactPii !== true) return parts;
|
|
8247
|
+
return parts.map(
|
|
8248
|
+
(p) => p.type === "tool_result" ? { ...p, content: guardText(p.content, opts) } : p
|
|
8249
|
+
);
|
|
8250
|
+
}
|
|
8251
|
+
var OPEN, CLOSE, PII_PATTERNS;
|
|
8252
|
+
var init_tool_result_guard = __esm({
|
|
8253
|
+
"src/internal/agent-loop/tool-result-guard.ts"() {
|
|
8254
|
+
OPEN = "<untrusted-tool-output>";
|
|
8255
|
+
CLOSE = "</untrusted-tool-output>";
|
|
8256
|
+
PII_PATTERNS = [
|
|
8257
|
+
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
|
|
8258
|
+
// email
|
|
8259
|
+
/\b(?:\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/g
|
|
8260
|
+
// phone
|
|
8261
|
+
];
|
|
7842
8262
|
}
|
|
7843
8263
|
});
|
|
7844
8264
|
|
|
@@ -8222,6 +8642,11 @@ async function runAgentLoop(inputs) {
|
|
|
8222
8642
|
try {
|
|
8223
8643
|
const ctx = await initLoopContext(inputs);
|
|
8224
8644
|
ctxRef = ctx;
|
|
8645
|
+
ctx.sendSpan = sendSpan;
|
|
8646
|
+
await inputs.pluginManager?.runOnSessionStartHooks({
|
|
8647
|
+
agentId: inputs.agentId,
|
|
8648
|
+
runId: inputs.runId
|
|
8649
|
+
});
|
|
8225
8650
|
const budget = inputs.budget ?? new IterationBudget({ maxIterations: inputs.maxIterations ?? 8 });
|
|
8226
8651
|
let lastTurnDecision;
|
|
8227
8652
|
while (budget.shouldContinue()) {
|
|
@@ -8249,6 +8674,7 @@ async function runAgentLoop(inputs) {
|
|
|
8249
8674
|
}
|
|
8250
8675
|
budget.consume();
|
|
8251
8676
|
inputs.budgetTracker?.nextIteration?.();
|
|
8677
|
+
if (inputs.signal?.aborted === true) break;
|
|
8252
8678
|
}
|
|
8253
8679
|
if (lastTurnDecision === "continue" && budget.shouldContinue() === false) {
|
|
8254
8680
|
ctx.stoppedAtIterationLimit = true;
|
|
@@ -8288,6 +8714,10 @@ async function runAgentLoop(inputs) {
|
|
|
8288
8714
|
...ctx.stoppedByDoomLoop === true ? { stoppedByDoomLoop: true } : {}
|
|
8289
8715
|
};
|
|
8290
8716
|
} finally {
|
|
8717
|
+
await inputs.pluginManager?.runOnSessionEndHooks({
|
|
8718
|
+
agentId: inputs.agentId,
|
|
8719
|
+
runId: inputs.runId
|
|
8720
|
+
});
|
|
8291
8721
|
if (ctxRef !== void 0 && ctxRef.memoryProviderHandle !== void 0 && inputs.memoryProvider !== void 0) {
|
|
8292
8722
|
try {
|
|
8293
8723
|
await inputs.memoryProvider.dispose(ctxRef.memoryProviderHandle);
|
|
@@ -8391,7 +8821,10 @@ async function finishOrReflect(inputs, ctx, llmOutput) {
|
|
|
8391
8821
|
return "done";
|
|
8392
8822
|
}
|
|
8393
8823
|
async function runIteration(inputs, ctx) {
|
|
8824
|
+
const hookCtx = { agentId: inputs.agentId, runId: inputs.runId };
|
|
8825
|
+
await inputs.pluginManager?.runPreLlmCallHooks(hookCtx);
|
|
8394
8826
|
const llmOutput = await streamLlmTurn(inputs, ctx);
|
|
8827
|
+
await inputs.pluginManager?.runPostLlmCallHooks(hookCtx);
|
|
8395
8828
|
accumulateUsage(ctx.usage, llmOutput);
|
|
8396
8829
|
if (inputs.budgetTracker !== void 0) {
|
|
8397
8830
|
const modelId = inputs.model.id ?? "auto";
|
|
@@ -8417,6 +8850,13 @@ async function runIteration(inputs, ctx) {
|
|
|
8417
8850
|
}
|
|
8418
8851
|
return continueOrTerminate(inputs, ctx, llmOutput);
|
|
8419
8852
|
}
|
|
8853
|
+
async function transformLlmOutputText(inputs, text, ctx) {
|
|
8854
|
+
return inputs.pluginManager !== void 0 ? inputs.pluginManager.runTransformLlmOutputHooks(text, ctx) : text;
|
|
8855
|
+
}
|
|
8856
|
+
async function guardAndTransformToolResults(inputs, raw, ctx) {
|
|
8857
|
+
const guarded = inputs.toolResultGuard !== void 0 ? applyToolResultGuard(raw, inputs.toolResultGuard) : raw;
|
|
8858
|
+
return inputs.pluginManager !== void 0 ? inputs.pluginManager.runTransformToolResultHooks(guarded, ctx) : guarded;
|
|
8859
|
+
}
|
|
8420
8860
|
async function continueOrTerminate(inputs, ctx, llmOutput) {
|
|
8421
8861
|
if (llmOutput.errored) return "error";
|
|
8422
8862
|
if (llmOutput.text.length > 0) {
|
|
@@ -8425,8 +8865,18 @@ async function continueOrTerminate(inputs, ctx, llmOutput) {
|
|
|
8425
8865
|
if (llmOutput.stopReason !== "tool_use" || llmOutput.toolCalls.length === 0) {
|
|
8426
8866
|
return finishOrReflect(inputs, ctx, llmOutput);
|
|
8427
8867
|
}
|
|
8428
|
-
|
|
8429
|
-
const
|
|
8868
|
+
const tCtx = { agentId: inputs.agentId, runId: inputs.runId };
|
|
8869
|
+
const outText = await transformLlmOutputText(inputs, llmOutput.text, tCtx);
|
|
8870
|
+
ctx.messages.push(buildAssistantTurn(outText, llmOutput.toolCalls));
|
|
8871
|
+
const rawResults = await dispatchTools(
|
|
8872
|
+
inputs,
|
|
8873
|
+
ctx.tools,
|
|
8874
|
+
llmOutput.toolCalls,
|
|
8875
|
+
ctx.events,
|
|
8876
|
+
ctx.sendSpan
|
|
8877
|
+
// M3 #64 — nest tool.call spans under agent.send
|
|
8878
|
+
);
|
|
8879
|
+
const toolResults = await guardAndTransformToolResults(inputs, rawResults, tCtx);
|
|
8430
8880
|
ctx.messages.push({ role: "user", content: toolResults });
|
|
8431
8881
|
if (inputs.onStep !== void 0) {
|
|
8432
8882
|
const cb = inputs.onStep;
|
|
@@ -8476,6 +8926,7 @@ var init_loop = __esm({
|
|
|
8476
8926
|
init_loop_llm_stream();
|
|
8477
8927
|
init_message_builders();
|
|
8478
8928
|
init_tool_dispatch();
|
|
8929
|
+
init_tool_result_guard();
|
|
8479
8930
|
init_usage_and_cost();
|
|
8480
8931
|
MAX_NUDGE_ATTEMPTS = 2;
|
|
8481
8932
|
MAX_STOP_FEEDBACK_ATTEMPTS = 2;
|
|
@@ -8965,6 +9416,60 @@ var init_anthropic2 = __esm({
|
|
|
8965
9416
|
init_shared();
|
|
8966
9417
|
}
|
|
8967
9418
|
});
|
|
9419
|
+
function loadJsonrepair() {
|
|
9420
|
+
if (cachedJsonrepair === void 0) {
|
|
9421
|
+
const req = createRequire(import.meta.url);
|
|
9422
|
+
cachedJsonrepair = req("jsonrepair").jsonrepair;
|
|
9423
|
+
}
|
|
9424
|
+
return cachedJsonrepair;
|
|
9425
|
+
}
|
|
9426
|
+
function isPlainObject(v) {
|
|
9427
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
9428
|
+
}
|
|
9429
|
+
function toFiniteNumber(raw) {
|
|
9430
|
+
if (raw === "") return void 0;
|
|
9431
|
+
const n = Number(raw);
|
|
9432
|
+
return Number.isFinite(n) && String(n) === raw ? n : void 0;
|
|
9433
|
+
}
|
|
9434
|
+
function tryJson(raw, repair) {
|
|
9435
|
+
const t = raw.trimStart();
|
|
9436
|
+
if (!(t.startsWith("{") || t.startsWith("["))) return void 0;
|
|
9437
|
+
try {
|
|
9438
|
+
return JSON.parse(repair ? loadJsonrepair()(t) : t);
|
|
9439
|
+
} catch {
|
|
9440
|
+
return void 0;
|
|
9441
|
+
}
|
|
9442
|
+
}
|
|
9443
|
+
function heuristicCoerce(raw, repairJson) {
|
|
9444
|
+
if (raw === "true") return true;
|
|
9445
|
+
if (raw === "false") return false;
|
|
9446
|
+
if (raw === "null") return null;
|
|
9447
|
+
const n = toFiniteNumber(raw);
|
|
9448
|
+
if (n !== void 0) return n;
|
|
9449
|
+
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
9450
|
+
return json === void 0 ? raw : json;
|
|
9451
|
+
}
|
|
9452
|
+
function coerceCandidates(raw, repairJson) {
|
|
9453
|
+
const out = [];
|
|
9454
|
+
if (raw === "true") out.push(true);
|
|
9455
|
+
else if (raw === "false") out.push(false);
|
|
9456
|
+
else if (raw === "null") out.push(null);
|
|
9457
|
+
const n = toFiniteNumber(raw);
|
|
9458
|
+
if (n !== void 0) out.push(n);
|
|
9459
|
+
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
9460
|
+
if (json !== void 0) out.push(json);
|
|
9461
|
+
out.push(raw);
|
|
9462
|
+
return out;
|
|
9463
|
+
}
|
|
9464
|
+
function objectShape(schema) {
|
|
9465
|
+
const shape = schema?.shape;
|
|
9466
|
+
return shape !== null && typeof shape === "object" ? shape : void 0;
|
|
9467
|
+
}
|
|
9468
|
+
var cachedJsonrepair;
|
|
9469
|
+
var init_coerce = __esm({
|
|
9470
|
+
"src/sanitize/coerce.ts"() {
|
|
9471
|
+
}
|
|
9472
|
+
});
|
|
8968
9473
|
|
|
8969
9474
|
// src/internal/llm/finish.ts
|
|
8970
9475
|
function collapseSystemText(system) {
|
|
@@ -8977,9 +9482,21 @@ function parseToolArguments(buffered) {
|
|
|
8977
9482
|
try {
|
|
8978
9483
|
return JSON.parse(buffered);
|
|
8979
9484
|
} catch {
|
|
9485
|
+
const repaired = tryJson(buffered, true);
|
|
9486
|
+
if (isPlainObject(repaired)) return repaired;
|
|
8980
9487
|
return { raw: buffered };
|
|
8981
9488
|
}
|
|
8982
9489
|
}
|
|
9490
|
+
function mapOpenAIFinish(reason) {
|
|
9491
|
+
switch (reason) {
|
|
9492
|
+
case "tool_calls":
|
|
9493
|
+
return "tool_use";
|
|
9494
|
+
case "length":
|
|
9495
|
+
return "max_tokens";
|
|
9496
|
+
default:
|
|
9497
|
+
return "end_turn";
|
|
9498
|
+
}
|
|
9499
|
+
}
|
|
8983
9500
|
function makeLlmFinish(state2) {
|
|
8984
9501
|
const finish = {
|
|
8985
9502
|
stopReason: state2.stopReason,
|
|
@@ -8995,6 +9512,7 @@ function makeLlmFinish(state2) {
|
|
|
8995
9512
|
}
|
|
8996
9513
|
var init_finish = __esm({
|
|
8997
9514
|
"src/internal/llm/finish.ts"() {
|
|
9515
|
+
init_coerce();
|
|
8998
9516
|
}
|
|
8999
9517
|
});
|
|
9000
9518
|
|
|
@@ -9438,6 +9956,16 @@ var init_credential_pool_types = __esm({
|
|
|
9438
9956
|
});
|
|
9439
9957
|
|
|
9440
9958
|
// src/internal/llm/retry.ts
|
|
9959
|
+
function computeBackoffMs(opts) {
|
|
9960
|
+
const base = opts.baseMs ?? DEFAULT_BASE_MS;
|
|
9961
|
+
const cap = opts.capMs ?? DEFAULT_CAP_MS;
|
|
9962
|
+
const rng = opts.rng ?? Math.random;
|
|
9963
|
+
if (opts.retryAfterMs !== void 0 && opts.retryAfterMs >= 0) {
|
|
9964
|
+
return Math.max(base, Math.min(cap, opts.retryAfterMs));
|
|
9965
|
+
}
|
|
9966
|
+
const ceiling = Math.min(cap, base * 2 ** opts.attempt);
|
|
9967
|
+
return Math.floor(rng() * ceiling);
|
|
9968
|
+
}
|
|
9441
9969
|
function sleepWithAbort(ms, signal) {
|
|
9442
9970
|
if (ms <= 0 || signal.aborted) return Promise.resolve();
|
|
9443
9971
|
return new Promise((resolve3) => {
|
|
@@ -9452,8 +9980,11 @@ function sleepWithAbort(ms, signal) {
|
|
|
9452
9980
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
9453
9981
|
});
|
|
9454
9982
|
}
|
|
9983
|
+
var DEFAULT_BASE_MS, DEFAULT_CAP_MS;
|
|
9455
9984
|
var init_retry = __esm({
|
|
9456
9985
|
"src/internal/llm/retry.ts"() {
|
|
9986
|
+
DEFAULT_BASE_MS = 500;
|
|
9987
|
+
DEFAULT_CAP_MS = 32e3;
|
|
9457
9988
|
}
|
|
9458
9989
|
});
|
|
9459
9990
|
|
|
@@ -10209,60 +10740,6 @@ var init_ollama_native = __esm({
|
|
|
10209
10740
|
ollamaSystemText = collapseSystemText;
|
|
10210
10741
|
}
|
|
10211
10742
|
});
|
|
10212
|
-
function loadJsonrepair() {
|
|
10213
|
-
if (cachedJsonrepair === void 0) {
|
|
10214
|
-
const req = createRequire(import.meta.url);
|
|
10215
|
-
cachedJsonrepair = req("jsonrepair").jsonrepair;
|
|
10216
|
-
}
|
|
10217
|
-
return cachedJsonrepair;
|
|
10218
|
-
}
|
|
10219
|
-
function isPlainObject(v) {
|
|
10220
|
-
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
10221
|
-
}
|
|
10222
|
-
function toFiniteNumber(raw) {
|
|
10223
|
-
if (raw === "") return void 0;
|
|
10224
|
-
const n = Number(raw);
|
|
10225
|
-
return Number.isFinite(n) && String(n) === raw ? n : void 0;
|
|
10226
|
-
}
|
|
10227
|
-
function tryJson(raw, repair) {
|
|
10228
|
-
const t = raw.trimStart();
|
|
10229
|
-
if (!(t.startsWith("{") || t.startsWith("["))) return void 0;
|
|
10230
|
-
try {
|
|
10231
|
-
return JSON.parse(repair ? loadJsonrepair()(t) : t);
|
|
10232
|
-
} catch {
|
|
10233
|
-
return void 0;
|
|
10234
|
-
}
|
|
10235
|
-
}
|
|
10236
|
-
function heuristicCoerce(raw, repairJson) {
|
|
10237
|
-
if (raw === "true") return true;
|
|
10238
|
-
if (raw === "false") return false;
|
|
10239
|
-
if (raw === "null") return null;
|
|
10240
|
-
const n = toFiniteNumber(raw);
|
|
10241
|
-
if (n !== void 0) return n;
|
|
10242
|
-
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
10243
|
-
return json === void 0 ? raw : json;
|
|
10244
|
-
}
|
|
10245
|
-
function coerceCandidates(raw, repairJson) {
|
|
10246
|
-
const out = [];
|
|
10247
|
-
if (raw === "true") out.push(true);
|
|
10248
|
-
else if (raw === "false") out.push(false);
|
|
10249
|
-
else if (raw === "null") out.push(null);
|
|
10250
|
-
const n = toFiniteNumber(raw);
|
|
10251
|
-
if (n !== void 0) out.push(n);
|
|
10252
|
-
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
10253
|
-
if (json !== void 0) out.push(json);
|
|
10254
|
-
out.push(raw);
|
|
10255
|
-
return out;
|
|
10256
|
-
}
|
|
10257
|
-
function objectShape(schema) {
|
|
10258
|
-
const shape = schema?.shape;
|
|
10259
|
-
return shape !== null && typeof shape === "object" ? shape : void 0;
|
|
10260
|
-
}
|
|
10261
|
-
var cachedJsonrepair;
|
|
10262
|
-
var init_coerce = __esm({
|
|
10263
|
-
"src/sanitize/coerce.ts"() {
|
|
10264
|
-
}
|
|
10265
|
-
});
|
|
10266
10743
|
|
|
10267
10744
|
// src/sanitize/sanitize-tool-input.ts
|
|
10268
10745
|
function applyTrim(key, value, ctx) {
|
|
@@ -10443,18 +10920,6 @@ var init_hermes_tool_extract = __esm({
|
|
|
10443
10920
|
});
|
|
10444
10921
|
|
|
10445
10922
|
// src/internal/llm/openai.ts
|
|
10446
|
-
function mapOpenAIFinish(reason) {
|
|
10447
|
-
switch (reason) {
|
|
10448
|
-
case "tool_calls":
|
|
10449
|
-
return "tool_use";
|
|
10450
|
-
case "length":
|
|
10451
|
-
return "max_tokens";
|
|
10452
|
-
case "stop":
|
|
10453
|
-
return "end_turn";
|
|
10454
|
-
default:
|
|
10455
|
-
return "end_turn";
|
|
10456
|
-
}
|
|
10457
|
-
}
|
|
10458
10923
|
function applyReasoningRequest(body, effort, providerName) {
|
|
10459
10924
|
if (providerName === "openai") {
|
|
10460
10925
|
body.reasoning_effort = effort;
|
|
@@ -10551,6 +11016,7 @@ function assistantMessage(message) {
|
|
|
10551
11016
|
var OpenAIClient, OpenAIStreamAccumulator, openAISystemText;
|
|
10552
11017
|
var init_openai2 = __esm({
|
|
10553
11018
|
"src/internal/llm/openai.ts"() {
|
|
11019
|
+
init_errors();
|
|
10554
11020
|
init_ollama2();
|
|
10555
11021
|
init_openai_compatible();
|
|
10556
11022
|
init_finish();
|
|
@@ -10631,8 +11097,12 @@ var init_openai2 = __esm({
|
|
|
10631
11097
|
// model was actually given. Empty set (no tools) recovers nothing.
|
|
10632
11098
|
new Set(request.tools?.map((tool) => tool.name) ?? [])
|
|
10633
11099
|
);
|
|
11100
|
+
let sawDone = false;
|
|
10634
11101
|
for await (const record of parseSseStream(response.body, signal)) {
|
|
10635
|
-
if (record.data === "[DONE]")
|
|
11102
|
+
if (record.data === "[DONE]") {
|
|
11103
|
+
sawDone = true;
|
|
11104
|
+
break;
|
|
11105
|
+
}
|
|
10636
11106
|
let chunk;
|
|
10637
11107
|
try {
|
|
10638
11108
|
chunk = JSON.parse(record.data);
|
|
@@ -10652,6 +11122,11 @@ var init_openai2 = __esm({
|
|
|
10652
11122
|
const events = accumulator.consume(chunk);
|
|
10653
11123
|
for (const event of events) yield event;
|
|
10654
11124
|
}
|
|
11125
|
+
if (!sawDone && !accumulator.finishReasonSeen) {
|
|
11126
|
+
throw new NetworkError("SSE stream truncated (no finish_reason / [DONE])", {
|
|
11127
|
+
code: "stream_truncated"
|
|
11128
|
+
});
|
|
11129
|
+
}
|
|
10655
11130
|
const drainEvent = accumulator.finalizeHeldText();
|
|
10656
11131
|
if (drainEvent !== void 0) yield drainEvent;
|
|
10657
11132
|
return accumulator.finish();
|
|
@@ -10751,8 +11226,15 @@ var init_openai2 = __esm({
|
|
|
10751
11226
|
this.toolCalls.set(call.index, existing);
|
|
10752
11227
|
}
|
|
10753
11228
|
}
|
|
11229
|
+
/** M2 #61 — true once any chunk carried a non-null `finish_reason` (else a
|
|
11230
|
+
* stream ending without `[DONE]` is a truncation, not a clean end). */
|
|
11231
|
+
sawFinishReason = false;
|
|
11232
|
+
get finishReasonSeen() {
|
|
11233
|
+
return this.sawFinishReason;
|
|
11234
|
+
}
|
|
10754
11235
|
applyFinishReason(reason) {
|
|
10755
11236
|
if (reason === void 0 || reason === null) return;
|
|
11237
|
+
this.sawFinishReason = true;
|
|
10756
11238
|
this.stopReason = mapOpenAIFinish(reason);
|
|
10757
11239
|
}
|
|
10758
11240
|
finish() {
|
|
@@ -10801,6 +11283,53 @@ var init_openai2 = __esm({
|
|
|
10801
11283
|
}
|
|
10802
11284
|
});
|
|
10803
11285
|
|
|
11286
|
+
// src/internal/resilience/circuit-breaker.ts
|
|
11287
|
+
var DEFAULT_MAX_TIMEOUTS, DEFAULT_COOLDOWN_MS2, CircuitBreaker;
|
|
11288
|
+
var init_circuit_breaker = __esm({
|
|
11289
|
+
"src/internal/resilience/circuit-breaker.ts"() {
|
|
11290
|
+
DEFAULT_MAX_TIMEOUTS = 3;
|
|
11291
|
+
DEFAULT_COOLDOWN_MS2 = 6e4;
|
|
11292
|
+
CircuitBreaker = class {
|
|
11293
|
+
constructor(opts = {}) {
|
|
11294
|
+
this.opts = opts;
|
|
11295
|
+
}
|
|
11296
|
+
opts;
|
|
11297
|
+
states = /* @__PURE__ */ new Map();
|
|
11298
|
+
/** @returns true when the breaker is open and the call should be skipped. */
|
|
11299
|
+
shouldSkip(key) {
|
|
11300
|
+
const state2 = this.states.get(key);
|
|
11301
|
+
if (state2 === void 0) return false;
|
|
11302
|
+
if (state2.cooldownUntilMs === 0) return false;
|
|
11303
|
+
if (this.now() < state2.cooldownUntilMs) return true;
|
|
11304
|
+
state2.cooldownUntilMs = 0;
|
|
11305
|
+
state2.consecutiveTimeouts = 0;
|
|
11306
|
+
return false;
|
|
11307
|
+
}
|
|
11308
|
+
recordSuccess(key) {
|
|
11309
|
+
const state2 = this.states.get(key);
|
|
11310
|
+
if (state2 === void 0) return;
|
|
11311
|
+
state2.consecutiveTimeouts = 0;
|
|
11312
|
+
state2.cooldownUntilMs = 0;
|
|
11313
|
+
}
|
|
11314
|
+
recordTimeout(key) {
|
|
11315
|
+
const state2 = this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
11316
|
+
state2.consecutiveTimeouts += 1;
|
|
11317
|
+
if (state2.consecutiveTimeouts >= (this.opts.maxTimeouts ?? DEFAULT_MAX_TIMEOUTS)) {
|
|
11318
|
+
state2.cooldownUntilMs = this.now() + (this.opts.cooldownMs ?? DEFAULT_COOLDOWN_MS2);
|
|
11319
|
+
}
|
|
11320
|
+
this.states.set(key, state2);
|
|
11321
|
+
}
|
|
11322
|
+
/** @internal — tests inspect counter state. */
|
|
11323
|
+
inspect(key) {
|
|
11324
|
+
return this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
11325
|
+
}
|
|
11326
|
+
now() {
|
|
11327
|
+
return this.opts.now?.() ?? Date.now();
|
|
11328
|
+
}
|
|
11329
|
+
};
|
|
11330
|
+
}
|
|
11331
|
+
});
|
|
11332
|
+
|
|
10804
11333
|
// src/internal/llm/pool-aware-client.ts
|
|
10805
11334
|
function classifyAndDecide(error, hasRetried429) {
|
|
10806
11335
|
if (error instanceof NetworkError) return "propagate";
|
|
@@ -10829,20 +11358,34 @@ var PoolAwareLlmClient;
|
|
|
10829
11358
|
var init_pool_aware_client = __esm({
|
|
10830
11359
|
"src/internal/llm/pool-aware-client.ts"() {
|
|
10831
11360
|
init_errors();
|
|
11361
|
+
init_circuit_breaker();
|
|
11362
|
+
init_retry();
|
|
10832
11363
|
init_stream_relay();
|
|
10833
11364
|
PoolAwareLlmClient = class {
|
|
10834
|
-
constructor(pool, buildClient2, waitForAvailableMs = 3e4) {
|
|
11365
|
+
constructor(pool, buildClient2, waitForAvailableMs = 3e4, resilience = {}) {
|
|
10835
11366
|
this.pool = pool;
|
|
10836
11367
|
this.buildClient = buildClient2;
|
|
10837
11368
|
this.waitForAvailableMs = waitForAvailableMs;
|
|
10838
11369
|
this.name = `pool-aware:${pool.provider}`;
|
|
11370
|
+
this.breaker = resilience.breaker ?? new CircuitBreaker();
|
|
11371
|
+
this.backoffBaseMs = resilience.backoffBaseMs;
|
|
11372
|
+
this.rng = resilience.rng;
|
|
10839
11373
|
}
|
|
10840
11374
|
pool;
|
|
10841
11375
|
buildClient;
|
|
10842
11376
|
waitForAvailableMs;
|
|
10843
11377
|
name;
|
|
11378
|
+
/** M2 #60 — provider-level circuit breaker (consecutive-failure). */
|
|
11379
|
+
breaker;
|
|
11380
|
+
backoffBaseMs;
|
|
11381
|
+
rng;
|
|
10844
11382
|
// 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.
|
|
10845
11383
|
async *stream(request, signal) {
|
|
11384
|
+
if (this.breaker.shouldSkip(this.pool.provider)) {
|
|
11385
|
+
throw new NetworkError(`${this.pool.provider} circuit open \u2014 failing fast`, {
|
|
11386
|
+
code: "circuit_open"
|
|
11387
|
+
});
|
|
11388
|
+
}
|
|
10846
11389
|
let hasRetried429 = false;
|
|
10847
11390
|
while (true) {
|
|
10848
11391
|
if (signal.aborted) throw abortError2(signal);
|
|
@@ -10857,6 +11400,7 @@ var init_pool_aware_client = __esm({
|
|
|
10857
11400
|
}
|
|
10858
11401
|
}
|
|
10859
11402
|
if (entry === null) {
|
|
11403
|
+
this.breaker.recordTimeout(this.pool.provider);
|
|
10860
11404
|
throw new CredentialPoolExhaustedError(
|
|
10861
11405
|
`All ${this.pool.provider} credentials exhausted; next retry available at ${this.nextRetryHint() ?? "unknown"}`,
|
|
10862
11406
|
{ provider: this.pool.provider, nextRetryAt: this.nextRetryHint() }
|
|
@@ -10866,10 +11410,19 @@ var init_pool_aware_client = __esm({
|
|
|
10866
11410
|
const realClient = this.buildClient(entry.accessToken);
|
|
10867
11411
|
const attempt = await tryFirstEvent(realClient, request, signal);
|
|
10868
11412
|
if (attempt.kind === "ok") {
|
|
11413
|
+
this.breaker.recordSuccess(this.pool.provider);
|
|
10869
11414
|
return yield* relayStream(attempt.generator, attempt.firstResult);
|
|
10870
11415
|
}
|
|
10871
11416
|
const decision = classifyAndDecide(attempt.error, hasRetried429);
|
|
10872
11417
|
if (decision === "retry") {
|
|
11418
|
+
await sleepWithAbort(
|
|
11419
|
+
computeBackoffMs({
|
|
11420
|
+
attempt: 0,
|
|
11421
|
+
...this.backoffBaseMs !== void 0 ? { baseMs: this.backoffBaseMs } : {},
|
|
11422
|
+
...this.rng !== void 0 ? { rng: this.rng } : {}
|
|
11423
|
+
}),
|
|
11424
|
+
signal
|
|
11425
|
+
);
|
|
10873
11426
|
hasRetried429 = true;
|
|
10874
11427
|
continue;
|
|
10875
11428
|
}
|
|
@@ -10889,6 +11442,7 @@ var init_pool_aware_client = __esm({
|
|
|
10889
11442
|
hasRetried429 = false;
|
|
10890
11443
|
continue;
|
|
10891
11444
|
}
|
|
11445
|
+
this.breaker.recordTimeout(this.pool.provider);
|
|
10892
11446
|
throw attempt.error;
|
|
10893
11447
|
}
|
|
10894
11448
|
}
|
|
@@ -11347,9 +11901,24 @@ var init_router = __esm({
|
|
|
11347
11901
|
warnedProviders = /* @__PURE__ */ new Set();
|
|
11348
11902
|
}
|
|
11349
11903
|
});
|
|
11350
|
-
function createMcpClient(name, config) {
|
|
11904
|
+
function createMcpClient(name, config, fetchImpl = fetch) {
|
|
11351
11905
|
if (isStdio(config)) return new StdioMcpClient(name, config);
|
|
11352
|
-
return new HttpMcpClient(name, config);
|
|
11906
|
+
return new HttpMcpClient(name, config, fetchImpl);
|
|
11907
|
+
}
|
|
11908
|
+
function reconnectDelay(attempt) {
|
|
11909
|
+
const ceiling = RECONNECT_BASE_MS * 2 ** attempt;
|
|
11910
|
+
const ms = Math.floor(Math.random() * ceiling);
|
|
11911
|
+
return ms <= 0 ? Promise.resolve() : new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
11912
|
+
}
|
|
11913
|
+
function mcpTimeoutError(name, timeoutMs) {
|
|
11914
|
+
return new NetworkError(`MCP ${name} request timed out after ${timeoutMs}ms`, {
|
|
11915
|
+
code: "mcp_timeout"
|
|
11916
|
+
});
|
|
11917
|
+
}
|
|
11918
|
+
function isAbortLike(cause) {
|
|
11919
|
+
if (typeof cause !== "object" || cause === null || !("name" in cause)) return false;
|
|
11920
|
+
const name = cause.name;
|
|
11921
|
+
return name === "TimeoutError" || name === "AbortError";
|
|
11353
11922
|
}
|
|
11354
11923
|
async function rpcInitialize(request) {
|
|
11355
11924
|
await request("initialize", {
|
|
@@ -11379,11 +11948,16 @@ function resolveMcpCwd(configCwd) {
|
|
|
11379
11948
|
if (isAbsolute(configCwd)) return configCwd;
|
|
11380
11949
|
return safePathJoin(process.cwd(), configCwd);
|
|
11381
11950
|
}
|
|
11382
|
-
var BaseMcpClient, StdioMcpClient, HttpMcpClient;
|
|
11951
|
+
var DEFAULT_MCP_TIMEOUT_MS, MAX_STDIO_BUFFER_BYTES, RECONNECT_BASE_MS, MAX_RECONNECT_ATTEMPTS, BaseMcpClient, StdioMcpClient, HttpMcpClient;
|
|
11383
11952
|
var init_client = __esm({
|
|
11384
11953
|
"src/internal/mcp/client.ts"() {
|
|
11385
11954
|
init_errors();
|
|
11955
|
+
init_env_policy();
|
|
11386
11956
|
init_path_guard();
|
|
11957
|
+
DEFAULT_MCP_TIMEOUT_MS = 3e4;
|
|
11958
|
+
MAX_STDIO_BUFFER_BYTES = 8 * 1024 * 1024;
|
|
11959
|
+
RECONNECT_BASE_MS = 250;
|
|
11960
|
+
MAX_RECONNECT_ATTEMPTS = 2;
|
|
11387
11961
|
BaseMcpClient = class {
|
|
11388
11962
|
initialize() {
|
|
11389
11963
|
return rpcInitialize((method, params) => this.request(method, params));
|
|
@@ -11405,32 +11979,107 @@ var init_client = __esm({
|
|
|
11405
11979
|
name;
|
|
11406
11980
|
child;
|
|
11407
11981
|
nextId = 1;
|
|
11982
|
+
// #59 — pending requests carry a reject + timer so a silent server times out
|
|
11983
|
+
// (typed error), a late reply after timeout is a no-op, and close() settles them.
|
|
11408
11984
|
pending = /* @__PURE__ */ new Map();
|
|
11409
11985
|
buffer = "";
|
|
11410
|
-
|
|
11986
|
+
// M2 #59 — reconnect-after-drop state. `dropped` is set when the child exits
|
|
11987
|
+
// unexpectedly OR times out (not via close()); the next request re-spawns with
|
|
11988
|
+
// backoff. `reconnectPromise` is a SINGLE in-flight reconnect shared by every
|
|
11989
|
+
// concurrent request so parallel tool dispatch after a drop awaits one handshake
|
|
11990
|
+
// instead of racing (or spuriously failing with mcp_not_init).
|
|
11991
|
+
dropped = false;
|
|
11992
|
+
reconnectAttempts = 0;
|
|
11993
|
+
reconnectPromise;
|
|
11994
|
+
get timeoutMs() {
|
|
11995
|
+
return this.config.requestTimeoutMs ?? DEFAULT_MCP_TIMEOUT_MS;
|
|
11996
|
+
}
|
|
11997
|
+
/** Spawn the server child and wire stdout/stderr/error/exit handlers.
|
|
11998
|
+
* Shared by `initialize()` and the M2 #59 reconnect path. */
|
|
11999
|
+
spawnChild() {
|
|
11411
12000
|
const resolvedCwd = resolveMcpCwd(this.config.cwd);
|
|
11412
12001
|
const child = spawn(this.config.command, this.config.args ?? [], {
|
|
11413
12002
|
cwd: resolvedCwd,
|
|
11414
|
-
|
|
12003
|
+
// #54 (F-H1) — a third-party MCP server binary must not inherit host
|
|
12004
|
+
// secrets. Scrub secret-like vars by default; `config.env` still wins.
|
|
12005
|
+
env: resolveChildEnv({ policy: this.config.envPolicy, overrides: this.config.env })
|
|
11415
12006
|
});
|
|
11416
12007
|
this.child = child;
|
|
11417
12008
|
child.stdout.on("data", (chunk) => this.consume(chunk));
|
|
11418
12009
|
child.stderr.on("data", () => void 0);
|
|
12010
|
+
child.stdin.on("error", () => void 0);
|
|
11419
12011
|
child.on("error", () => {
|
|
11420
|
-
|
|
11421
|
-
|
|
11422
|
-
|
|
11423
|
-
|
|
12012
|
+
this.rejectAllPending(
|
|
12013
|
+
new NetworkError(`MCP ${this.name} process crashed`, { code: "mcp_crashed" })
|
|
12014
|
+
);
|
|
12015
|
+
});
|
|
12016
|
+
child.on("exit", () => {
|
|
12017
|
+
if (this.child !== child) return;
|
|
12018
|
+
this.child = void 0;
|
|
12019
|
+
this.dropped = true;
|
|
12020
|
+
this.rejectAllPending(
|
|
12021
|
+
new NetworkError(`MCP ${this.name} disconnected`, { code: "mcp_disconnected" })
|
|
12022
|
+
);
|
|
12023
|
+
});
|
|
12024
|
+
}
|
|
12025
|
+
async initialize() {
|
|
12026
|
+
this.spawnChild();
|
|
12027
|
+
await super.initialize();
|
|
12028
|
+
}
|
|
12029
|
+
/** M2 #59 — ensure a live child before a request. Reconnect (bounded, with
|
|
12030
|
+
* full-jitter backoff) when the client was dropped; fail fast when never
|
|
12031
|
+
* initialized. Concurrent callers share ONE reconnect handshake. */
|
|
12032
|
+
ensureConnected() {
|
|
12033
|
+
if (this.child !== void 0) return Promise.resolve();
|
|
12034
|
+
if (!this.dropped) {
|
|
12035
|
+
return Promise.reject(
|
|
12036
|
+
new ConfigurationError(`MCP ${this.name} is not initialized`, { code: "mcp_not_init" })
|
|
12037
|
+
);
|
|
12038
|
+
}
|
|
12039
|
+
this.reconnectPromise ??= this.reconnect().finally(() => {
|
|
12040
|
+
this.reconnectPromise = void 0;
|
|
11424
12041
|
});
|
|
12042
|
+
return this.reconnectPromise;
|
|
12043
|
+
}
|
|
12044
|
+
async reconnect() {
|
|
12045
|
+
if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
12046
|
+
throw new NetworkError(`MCP ${this.name} reconnect exhausted`, { code: "mcp_disconnected" });
|
|
12047
|
+
}
|
|
12048
|
+
await reconnectDelay(this.reconnectAttempts);
|
|
12049
|
+
this.reconnectAttempts += 1;
|
|
12050
|
+
this.spawnChild();
|
|
11425
12051
|
await super.initialize();
|
|
12052
|
+
this.dropped = false;
|
|
12053
|
+
this.reconnectAttempts = 0;
|
|
11426
12054
|
}
|
|
11427
12055
|
async close() {
|
|
11428
|
-
|
|
11429
|
-
this.child
|
|
12056
|
+
this.rejectAllPending(new NetworkError(`MCP ${this.name} closed`, { code: "mcp_closed" }));
|
|
12057
|
+
const child = this.child;
|
|
11430
12058
|
this.child = void 0;
|
|
12059
|
+
this.dropped = false;
|
|
12060
|
+
child?.kill("SIGTERM");
|
|
12061
|
+
}
|
|
12062
|
+
/** Reject + clear every pending request (crash / close). @internal */
|
|
12063
|
+
rejectAllPending(error) {
|
|
12064
|
+
for (const entry of this.pending.values()) {
|
|
12065
|
+
clearTimeout(entry.timer);
|
|
12066
|
+
entry.reject(error);
|
|
12067
|
+
}
|
|
12068
|
+
this.pending.clear();
|
|
11431
12069
|
}
|
|
11432
12070
|
consume(chunk) {
|
|
11433
12071
|
this.buffer += chunk.toString("utf8");
|
|
12072
|
+
if (this.buffer.length > MAX_STDIO_BUFFER_BYTES) {
|
|
12073
|
+
this.buffer = "";
|
|
12074
|
+
this.rejectAllPending(
|
|
12075
|
+
new NetworkError(`MCP ${this.name} exceeded stdout buffer limit`, {
|
|
12076
|
+
code: "mcp_buffer_overflow"
|
|
12077
|
+
})
|
|
12078
|
+
);
|
|
12079
|
+
this.child?.kill("SIGKILL");
|
|
12080
|
+
this.child = void 0;
|
|
12081
|
+
return;
|
|
12082
|
+
}
|
|
11434
12083
|
let newlineIndex = this.buffer.indexOf("\n");
|
|
11435
12084
|
while (newlineIndex !== -1) {
|
|
11436
12085
|
const line = this.buffer.slice(0, newlineIndex).trim();
|
|
@@ -11447,23 +12096,47 @@ var init_client = __esm({
|
|
|
11447
12096
|
return;
|
|
11448
12097
|
}
|
|
11449
12098
|
if (typeof message.id !== "number") return;
|
|
11450
|
-
const
|
|
11451
|
-
if (
|
|
12099
|
+
const entry = this.pending.get(message.id);
|
|
12100
|
+
if (entry === void 0) return;
|
|
11452
12101
|
this.pending.delete(message.id);
|
|
11453
|
-
|
|
12102
|
+
clearTimeout(entry.timer);
|
|
12103
|
+
entry.resolve(message);
|
|
11454
12104
|
}
|
|
11455
12105
|
request(method, params) {
|
|
11456
|
-
|
|
11457
|
-
|
|
11458
|
-
|
|
11459
|
-
|
|
12106
|
+
const child = this.child;
|
|
12107
|
+
if (child !== void 0) return this.send(child, method, params);
|
|
12108
|
+
if (this.dropped) return this.reconnectAndRequest(method, params);
|
|
12109
|
+
return Promise.reject(
|
|
12110
|
+
new ConfigurationError(`MCP ${this.name} is not initialized`, { code: "mcp_not_init" })
|
|
12111
|
+
);
|
|
12112
|
+
}
|
|
12113
|
+
/** M2 #59 — reconnect a dropped client, then send. Separate async path so the
|
|
12114
|
+
* happy path above never pays an extra microtask tick. */
|
|
12115
|
+
async reconnectAndRequest(method, params) {
|
|
12116
|
+
await this.ensureConnected();
|
|
12117
|
+
const child = this.child;
|
|
12118
|
+
if (child === void 0) {
|
|
12119
|
+
throw new ConfigurationError(`MCP ${this.name} is not initialized`, { code: "mcp_not_init" });
|
|
11460
12120
|
}
|
|
12121
|
+
return this.send(child, method, params);
|
|
12122
|
+
}
|
|
12123
|
+
send(child, method, params) {
|
|
11461
12124
|
const id = this.nextId++;
|
|
11462
12125
|
const payload = { jsonrpc: "2.0", id, method, params };
|
|
11463
|
-
|
|
12126
|
+
child.stdin.write(`${JSON.stringify(payload)}
|
|
11464
12127
|
`);
|
|
11465
|
-
return new Promise((resolve3) => {
|
|
11466
|
-
|
|
12128
|
+
return new Promise((resolve3, reject) => {
|
|
12129
|
+
const timer = setTimeout(() => {
|
|
12130
|
+
this.pending.delete(id);
|
|
12131
|
+
reject(mcpTimeoutError(this.name, this.timeoutMs));
|
|
12132
|
+
this.child?.kill("SIGKILL");
|
|
12133
|
+
this.child = void 0;
|
|
12134
|
+
this.dropped = true;
|
|
12135
|
+
this.rejectAllPending(
|
|
12136
|
+
new NetworkError(`MCP ${this.name} disconnected`, { code: "mcp_disconnected" })
|
|
12137
|
+
);
|
|
12138
|
+
}, this.timeoutMs);
|
|
12139
|
+
this.pending.set(id, { resolve: resolve3, reject, timer });
|
|
11467
12140
|
});
|
|
11468
12141
|
}
|
|
11469
12142
|
};
|
|
@@ -11489,11 +12162,20 @@ var init_client = __esm({
|
|
|
11489
12162
|
accept: "application/json",
|
|
11490
12163
|
...this.config.headers ?? {}
|
|
11491
12164
|
};
|
|
11492
|
-
const
|
|
11493
|
-
|
|
11494
|
-
|
|
11495
|
-
|
|
11496
|
-
|
|
12165
|
+
const timeoutMs = this.config.requestTimeoutMs ?? DEFAULT_MCP_TIMEOUT_MS;
|
|
12166
|
+
let response;
|
|
12167
|
+
try {
|
|
12168
|
+
response = await this.fetchImpl(this.config.url, {
|
|
12169
|
+
method: "POST",
|
|
12170
|
+
headers,
|
|
12171
|
+
body: JSON.stringify(payload),
|
|
12172
|
+
// #59 — bound the request; a non-responding endpoint aborts here.
|
|
12173
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
12174
|
+
});
|
|
12175
|
+
} catch (cause) {
|
|
12176
|
+
if (isAbortLike(cause)) throw mcpTimeoutError(this.name, timeoutMs);
|
|
12177
|
+
throw cause;
|
|
12178
|
+
}
|
|
11497
12179
|
if (!response.ok) {
|
|
11498
12180
|
throw new NetworkError(`MCP ${this.name} returned ${response.status}`, {
|
|
11499
12181
|
code: "mcp_http_error"
|
|
@@ -11613,11 +12295,33 @@ function resolveRunProvider(options) {
|
|
|
11613
12295
|
);
|
|
11614
12296
|
}
|
|
11615
12297
|
const parsedModel = parseModelId(options.model?.id);
|
|
11616
|
-
const
|
|
11617
|
-
const
|
|
11618
|
-
const
|
|
12298
|
+
const modelInferredProvider = parsedModel.provider !== void 0 && getProviderProfile(parsedModel.provider) !== void 0 ? parsedModel.provider : void 0;
|
|
12299
|
+
const keyInferredProvider = inferProviderFromApiKey(options.agentOptions.apiKey);
|
|
12300
|
+
const primary = options.agentOptions.providers?.routes?.[0]?.provider ?? keyInferredProvider ?? modelInferredProvider ?? detectPrimaryProvider();
|
|
12301
|
+
const effectiveModelId = modelInferredProvider !== void 0 && modelInferredProvider === primary ? parsedModel.name : options.model?.id ?? "claude-sonnet-4-6";
|
|
11619
12302
|
return { primary, effectiveModelId };
|
|
11620
12303
|
}
|
|
12304
|
+
function inferProviderFromApiKey(apiKey) {
|
|
12305
|
+
if (apiKey === void 0 || apiKey.length === 0) return void 0;
|
|
12306
|
+
const byPrefix = [
|
|
12307
|
+
{ provider: "openrouter", prefix: "sk-or-" },
|
|
12308
|
+
{ provider: "anthropic", prefix: "sk-ant-" },
|
|
12309
|
+
{ provider: "openai", prefix: "sk-" }
|
|
12310
|
+
];
|
|
12311
|
+
for (const { provider, prefix } of byPrefix) {
|
|
12312
|
+
if (apiKey.startsWith(prefix) && getProviderProfile(provider) !== void 0) {
|
|
12313
|
+
return provider;
|
|
12314
|
+
}
|
|
12315
|
+
}
|
|
12316
|
+
return void 0;
|
|
12317
|
+
}
|
|
12318
|
+
function mergeExplicitApiKey(pools, primary, apiKey) {
|
|
12319
|
+
if (apiKey === void 0 || apiKey.length === 0) return pools;
|
|
12320
|
+
if (isFixtureApiKey(apiKey) || apiKey === LOCAL_RUNTIME_MOCK_KEY) return pools;
|
|
12321
|
+
const existing = pools?.[primary];
|
|
12322
|
+
if (existing !== void 0 && existing.length > 0) return pools;
|
|
12323
|
+
return { ...pools ?? {}, [primary]: [apiKey] };
|
|
12324
|
+
}
|
|
11621
12325
|
function buildLoopInputs(options, runId, userText) {
|
|
11622
12326
|
const maxIterations = options.sendOptions.maxIterations;
|
|
11623
12327
|
if (maxIterations !== void 0 && (!Number.isInteger(maxIterations) || maxIterations < 1)) {
|
|
@@ -11628,7 +12332,11 @@ function buildLoopInputs(options, runId, userText) {
|
|
|
11628
12332
|
}
|
|
11629
12333
|
const { primary, effectiveModelId } = resolveRunProvider(options);
|
|
11630
12334
|
const fallback = options.agentOptions.providers?.fallback;
|
|
11631
|
-
const apiKeys =
|
|
12335
|
+
const apiKeys = mergeExplicitApiKey(
|
|
12336
|
+
options.agentOptions.providers?.apiKeys,
|
|
12337
|
+
primary,
|
|
12338
|
+
options.agentOptions.apiKey
|
|
12339
|
+
);
|
|
11632
12340
|
const credentialPoolStrategy = options.agentOptions.providers?.credentialPoolStrategy;
|
|
11633
12341
|
const extractToolCallsFromContent = options.agentOptions.providers?.routes?.[0]?.extractToolCallsFromContent;
|
|
11634
12342
|
const chain = resolveProviderChain({
|
|
@@ -11672,6 +12380,10 @@ function buildLoopInputs(options, runId, userText) {
|
|
|
11672
12380
|
// D318 — forward SendOptions.signal to the agent loop so streamLlmTurn
|
|
11673
12381
|
// can attach it to the LLM `fetch({ signal })` call.
|
|
11674
12382
|
...options.sendOptions.signal !== void 0 ? { signal: options.sendOptions.signal } : {},
|
|
12383
|
+
// #58 / #57 — forward the per-tool timeout + tool-result guard so a consumer
|
|
12384
|
+
// can enable them via SendOptions (not only internal AgentLoopInputs).
|
|
12385
|
+
...options.sendOptions.perToolTimeoutMs !== void 0 ? { perToolTimeoutMs: options.sendOptions.perToolTimeoutMs } : {},
|
|
12386
|
+
...options.sendOptions.toolResultGuard !== void 0 ? { toolResultGuard: options.sendOptions.toolResultGuard } : {},
|
|
11675
12387
|
// M1-2: per-send iteration ceiling (validated above). The loop reads
|
|
11676
12388
|
// inputs.maxIterations (default 8 when unset).
|
|
11677
12389
|
...maxIterations !== void 0 ? { maxIterations } : {},
|
|
@@ -11738,6 +12450,8 @@ var init_real_local_run = __esm({
|
|
|
11738
12450
|
"src/internal/runtime/local-agent/real-local-run.ts"() {
|
|
11739
12451
|
init_errors();
|
|
11740
12452
|
init_loop();
|
|
12453
|
+
init_api_key_validator();
|
|
12454
|
+
init_fixture_mode();
|
|
11741
12455
|
init_fallback_client();
|
|
11742
12456
|
init_model_identifier();
|
|
11743
12457
|
init_router();
|
|
@@ -12014,7 +12728,12 @@ async function runActiveMemory(args) {
|
|
|
12014
12728
|
hits: []
|
|
12015
12729
|
});
|
|
12016
12730
|
}
|
|
12017
|
-
const
|
|
12731
|
+
const tenantCtx = {
|
|
12732
|
+
namespace: args.namespace,
|
|
12733
|
+
userId: args.userId,
|
|
12734
|
+
scope: args.scope
|
|
12735
|
+
};
|
|
12736
|
+
const cached2 = args.cache?.get(args.userText, cfg.queryMode, tenantCtx);
|
|
12018
12737
|
if (cached2 !== void 0) return endRecallSpan(span, args, cached2);
|
|
12019
12738
|
const query = buildQuery(args.userText, args.priorMessages, cfg.queryMode, cfg.recentUserTurns);
|
|
12020
12739
|
if (query.trim().length === 0) {
|
|
@@ -12099,7 +12818,12 @@ function notifyBreaker(breaker, key, status) {
|
|
|
12099
12818
|
else if (status === "ok" || status === "no-recall") breaker.recordSuccess(key);
|
|
12100
12819
|
}
|
|
12101
12820
|
async function finalize(args, queryMode, result) {
|
|
12102
|
-
|
|
12821
|
+
const tenantCtx = {
|
|
12822
|
+
namespace: args.namespace,
|
|
12823
|
+
userId: args.userId,
|
|
12824
|
+
scope: args.scope
|
|
12825
|
+
};
|
|
12826
|
+
args.cache?.set(args.userText, queryMode, result, tenantCtx);
|
|
12103
12827
|
if (args.persistTranscripts === true && args.cwd !== void 0) {
|
|
12104
12828
|
await persistActiveMemoryTranscript(args.cwd, {
|
|
12105
12829
|
runId: args.runId ?? `run-${Date.now()}`,
|
|
@@ -12804,53 +13528,6 @@ var init_catalog = __esm({
|
|
|
12804
13528
|
}
|
|
12805
13529
|
});
|
|
12806
13530
|
|
|
12807
|
-
// src/internal/memory/circuit-breaker.ts
|
|
12808
|
-
var DEFAULT_MAX_TIMEOUTS, DEFAULT_COOLDOWN_MS2, CircuitBreaker;
|
|
12809
|
-
var init_circuit_breaker = __esm({
|
|
12810
|
-
"src/internal/memory/circuit-breaker.ts"() {
|
|
12811
|
-
DEFAULT_MAX_TIMEOUTS = 3;
|
|
12812
|
-
DEFAULT_COOLDOWN_MS2 = 6e4;
|
|
12813
|
-
CircuitBreaker = class {
|
|
12814
|
-
constructor(opts = {}) {
|
|
12815
|
-
this.opts = opts;
|
|
12816
|
-
}
|
|
12817
|
-
opts;
|
|
12818
|
-
states = /* @__PURE__ */ new Map();
|
|
12819
|
-
/** @returns true when the breaker is open and the call should be skipped. */
|
|
12820
|
-
shouldSkip(key) {
|
|
12821
|
-
const state2 = this.states.get(key);
|
|
12822
|
-
if (state2 === void 0) return false;
|
|
12823
|
-
if (state2.cooldownUntilMs === 0) return false;
|
|
12824
|
-
if (this.now() < state2.cooldownUntilMs) return true;
|
|
12825
|
-
state2.cooldownUntilMs = 0;
|
|
12826
|
-
state2.consecutiveTimeouts = 0;
|
|
12827
|
-
return false;
|
|
12828
|
-
}
|
|
12829
|
-
recordSuccess(key) {
|
|
12830
|
-
const state2 = this.states.get(key);
|
|
12831
|
-
if (state2 === void 0) return;
|
|
12832
|
-
state2.consecutiveTimeouts = 0;
|
|
12833
|
-
state2.cooldownUntilMs = 0;
|
|
12834
|
-
}
|
|
12835
|
-
recordTimeout(key) {
|
|
12836
|
-
const state2 = this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
12837
|
-
state2.consecutiveTimeouts += 1;
|
|
12838
|
-
if (state2.consecutiveTimeouts >= (this.opts.maxTimeouts ?? DEFAULT_MAX_TIMEOUTS)) {
|
|
12839
|
-
state2.cooldownUntilMs = this.now() + (this.opts.cooldownMs ?? DEFAULT_COOLDOWN_MS2);
|
|
12840
|
-
}
|
|
12841
|
-
this.states.set(key, state2);
|
|
12842
|
-
}
|
|
12843
|
-
/** @internal — tests inspect counter state. */
|
|
12844
|
-
inspect(key) {
|
|
12845
|
-
return this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
12846
|
-
}
|
|
12847
|
-
now() {
|
|
12848
|
-
return this.opts.now?.() ?? Date.now();
|
|
12849
|
-
}
|
|
12850
|
-
};
|
|
12851
|
-
}
|
|
12852
|
-
});
|
|
12853
|
-
|
|
12854
13531
|
// src/internal/persistence/fts5-sanitize.ts
|
|
12855
13532
|
function sanitizeFts5Query(query) {
|
|
12856
13533
|
if (query.length === 0) return query;
|
|
@@ -14016,9 +14693,9 @@ var init_local_agent_memory = __esm({
|
|
|
14016
14693
|
init_active_memory();
|
|
14017
14694
|
init_active_memory_cache();
|
|
14018
14695
|
init_catalog();
|
|
14019
|
-
init_circuit_breaker();
|
|
14020
14696
|
init_index_manager();
|
|
14021
14697
|
init_tools();
|
|
14698
|
+
init_circuit_breaker();
|
|
14022
14699
|
LocalAgentMemory = class {
|
|
14023
14700
|
constructor(options, workspaceCwd, agentId) {
|
|
14024
14701
|
this.options = options;
|