@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/index.cjs
CHANGED
|
@@ -1201,6 +1201,16 @@ var init_credential_pool_types = __esm({
|
|
|
1201
1201
|
});
|
|
1202
1202
|
|
|
1203
1203
|
// src/internal/llm/retry.ts
|
|
1204
|
+
function computeBackoffMs(opts) {
|
|
1205
|
+
const base = opts.baseMs ?? DEFAULT_BASE_MS;
|
|
1206
|
+
const cap2 = opts.capMs ?? DEFAULT_CAP_MS;
|
|
1207
|
+
const rng = opts.rng ?? Math.random;
|
|
1208
|
+
if (opts.retryAfterMs !== void 0 && opts.retryAfterMs >= 0) {
|
|
1209
|
+
return Math.max(base, Math.min(cap2, opts.retryAfterMs));
|
|
1210
|
+
}
|
|
1211
|
+
const ceiling = Math.min(cap2, base * 2 ** opts.attempt);
|
|
1212
|
+
return Math.floor(rng() * ceiling);
|
|
1213
|
+
}
|
|
1204
1214
|
function sleepWithAbort(ms, signal) {
|
|
1205
1215
|
if (ms <= 0 || signal.aborted) return Promise.resolve();
|
|
1206
1216
|
return new Promise((resolve3) => {
|
|
@@ -1215,8 +1225,11 @@ function sleepWithAbort(ms, signal) {
|
|
|
1215
1225
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
1216
1226
|
});
|
|
1217
1227
|
}
|
|
1228
|
+
var DEFAULT_BASE_MS, DEFAULT_CAP_MS;
|
|
1218
1229
|
var init_retry = __esm({
|
|
1219
1230
|
"src/internal/llm/retry.ts"() {
|
|
1231
|
+
DEFAULT_BASE_MS = 500;
|
|
1232
|
+
DEFAULT_CAP_MS = 32e3;
|
|
1220
1233
|
}
|
|
1221
1234
|
});
|
|
1222
1235
|
|
|
@@ -5283,7 +5296,7 @@ function abortRun(name, runId, startedAt, stepResults, signal) {
|
|
|
5283
5296
|
}
|
|
5284
5297
|
async function runStepsLoop(params) {
|
|
5285
5298
|
const { options, steps, ctx, runId, startedAt, signal } = params;
|
|
5286
|
-
const stepResults = [];
|
|
5299
|
+
const stepResults = [...params.initialStepResults ?? []];
|
|
5287
5300
|
let acc = params.input;
|
|
5288
5301
|
for (const step of steps) {
|
|
5289
5302
|
if (signal.aborted) return abortRun(options.name, runId, startedAt, stepResults, signal);
|
|
@@ -5342,7 +5355,8 @@ async function executeWorkflow(options, steps, input, runOpts) {
|
|
|
5342
5355
|
ctx,
|
|
5343
5356
|
runId,
|
|
5344
5357
|
startedAt,
|
|
5345
|
-
signal
|
|
5358
|
+
signal,
|
|
5359
|
+
...runOpts?.initialStepResults !== void 0 ? { initialStepResults: runOpts.initialStepResults } : {}
|
|
5346
5360
|
});
|
|
5347
5361
|
} finally {
|
|
5348
5362
|
runSpan.end();
|
|
@@ -5425,7 +5439,9 @@ async function resumeWorkflow(opts) {
|
|
|
5425
5439
|
await store.delete(opts.runId);
|
|
5426
5440
|
return executeWorkflow(options, remainingSteps, resumeInput, {
|
|
5427
5441
|
signal: opts.signal,
|
|
5428
|
-
runId: opts.runId
|
|
5442
|
+
runId: opts.runId,
|
|
5443
|
+
// M3 #62 — restore prior step outputs so the resumed run is not lossy (internal seam).
|
|
5444
|
+
initialStepResults: snapshot.stepResults
|
|
5429
5445
|
});
|
|
5430
5446
|
}
|
|
5431
5447
|
var init_executor = __esm({
|
|
@@ -7056,13 +7072,15 @@ function buildGitInfo() {
|
|
|
7056
7072
|
init_errors();
|
|
7057
7073
|
|
|
7058
7074
|
// src/internal/llm/sse.ts
|
|
7059
|
-
|
|
7075
|
+
init_errors();
|
|
7076
|
+
var DEFAULT_SSE_IDLE_MS = 6e4;
|
|
7077
|
+
async function* parseSseStream(body, signal, idleTimeoutMs = DEFAULT_SSE_IDLE_MS) {
|
|
7060
7078
|
if (body === null) return;
|
|
7061
7079
|
const reader = body.getReader();
|
|
7062
7080
|
const decoder = new TextDecoder("utf-8");
|
|
7063
7081
|
const state4 = { buffer: "", event: "message", data: "" };
|
|
7064
7082
|
try {
|
|
7065
|
-
for await (const chunk of readChunks(reader, signal)) {
|
|
7083
|
+
for await (const chunk of readChunks(reader, signal, idleTimeoutMs)) {
|
|
7066
7084
|
state4.buffer += decoder.decode(chunk, { stream: true });
|
|
7067
7085
|
for (const record of drainCompleteRecords(state4)) yield record;
|
|
7068
7086
|
}
|
|
@@ -7072,14 +7090,38 @@ async function* parseSseStream(body, signal) {
|
|
|
7072
7090
|
releaseReader(reader);
|
|
7073
7091
|
}
|
|
7074
7092
|
}
|
|
7075
|
-
async function* readChunks(reader, signal) {
|
|
7093
|
+
async function* readChunks(reader, signal, idleTimeoutMs) {
|
|
7076
7094
|
while (true) {
|
|
7077
7095
|
if (signal.aborted) return;
|
|
7078
|
-
const { value, done } = await reader
|
|
7096
|
+
const { value, done } = await readWithIdleTimeout(reader, idleTimeoutMs);
|
|
7079
7097
|
if (done) return;
|
|
7080
7098
|
if (value !== void 0) yield value;
|
|
7081
7099
|
}
|
|
7082
7100
|
}
|
|
7101
|
+
function readWithIdleTimeout(reader, idleTimeoutMs) {
|
|
7102
|
+
if (idleTimeoutMs <= 0) return reader.read();
|
|
7103
|
+
return new Promise(
|
|
7104
|
+
(resolve3, reject) => {
|
|
7105
|
+
const timer = setTimeout(() => {
|
|
7106
|
+
reject(
|
|
7107
|
+
new exports.NetworkError(`SSE stream idle for ${idleTimeoutMs}ms \u2014 upstream stalled`, {
|
|
7108
|
+
code: "stream_idle_timeout"
|
|
7109
|
+
})
|
|
7110
|
+
);
|
|
7111
|
+
}, idleTimeoutMs);
|
|
7112
|
+
reader.read().then(
|
|
7113
|
+
(result) => {
|
|
7114
|
+
clearTimeout(timer);
|
|
7115
|
+
resolve3(result);
|
|
7116
|
+
},
|
|
7117
|
+
(err) => {
|
|
7118
|
+
clearTimeout(timer);
|
|
7119
|
+
reject(err);
|
|
7120
|
+
}
|
|
7121
|
+
);
|
|
7122
|
+
}
|
|
7123
|
+
);
|
|
7124
|
+
}
|
|
7083
7125
|
async function cancelReaderQuietly(reader) {
|
|
7084
7126
|
try {
|
|
7085
7127
|
await reader.cancel();
|
|
@@ -7773,6 +7815,9 @@ var PersonalityStore = class {
|
|
|
7773
7815
|
}
|
|
7774
7816
|
};
|
|
7775
7817
|
|
|
7818
|
+
// src/internal/plugins/manager.ts
|
|
7819
|
+
init_errors();
|
|
7820
|
+
|
|
7776
7821
|
// src/internal/plugins/context.ts
|
|
7777
7822
|
function createPluginContext() {
|
|
7778
7823
|
const registrations = {
|
|
@@ -7834,6 +7879,9 @@ var PluginManager = class {
|
|
|
7834
7879
|
memoryProviders: []
|
|
7835
7880
|
};
|
|
7836
7881
|
#initialized = false;
|
|
7882
|
+
// #68 — registrations of plugins added post-init via `register()`, keyed by
|
|
7883
|
+
// plugin name so a re-register REPLACES (not appends) the prior hooks.
|
|
7884
|
+
#byName = /* @__PURE__ */ new Map();
|
|
7837
7885
|
async initialize(plugins) {
|
|
7838
7886
|
if (this.#initialized) {
|
|
7839
7887
|
throw new Error("PluginManager.initialize called twice \u2014 register only once per process");
|
|
@@ -7851,6 +7899,36 @@ var PluginManager = class {
|
|
|
7851
7899
|
await this.#dispatchPlugin(plugin);
|
|
7852
7900
|
}
|
|
7853
7901
|
}
|
|
7902
|
+
/**
|
|
7903
|
+
* #68 — register a single `general` plugin AFTER `initialize()` has run.
|
|
7904
|
+
*
|
|
7905
|
+
* The bulk `initialize()` is single-shot (one call per process); late
|
|
7906
|
+
* registration is a distinct, named operation used by adapters that install
|
|
7907
|
+
* a plugin per-session/per-request (e.g. the ACP permission veto, which is
|
|
7908
|
+
* installed once the permission mode + connection are known — after the
|
|
7909
|
+
* agent's own plugins were already initialized).
|
|
7910
|
+
*
|
|
7911
|
+
* Idempotent by plugin NAME: re-registering a plugin with the same name
|
|
7912
|
+
* REPLACES its prior hooks/tools instead of appending duplicates (the ACP
|
|
7913
|
+
* permission plugin is re-installed on every prompt).
|
|
7914
|
+
*
|
|
7915
|
+
* Only `general` plugins may be registered late — model-provider / memory
|
|
7916
|
+
* plugins are resolved during the bulk init and cannot be added afterwards.
|
|
7917
|
+
*/
|
|
7918
|
+
async register(plugin) {
|
|
7919
|
+
if (plugin.kind !== "general") {
|
|
7920
|
+
throw new exports.ConfigurationError(
|
|
7921
|
+
`late register supports general plugins only (got "${plugin.kind}" for "${plugin.name}")`,
|
|
7922
|
+
{ code: "plugin_late_register_kind" }
|
|
7923
|
+
);
|
|
7924
|
+
}
|
|
7925
|
+
const prior = this.#byName.get(plugin.name);
|
|
7926
|
+
if (prior !== void 0) this.#unmerge(prior);
|
|
7927
|
+
const { ctx, registrations } = createPluginContext();
|
|
7928
|
+
await plugin.register(ctx);
|
|
7929
|
+
this.#byName.set(plugin.name, registrations);
|
|
7930
|
+
this.#merge(registrations);
|
|
7931
|
+
}
|
|
7854
7932
|
get aggregated() {
|
|
7855
7933
|
return this.#aggregated;
|
|
7856
7934
|
}
|
|
@@ -7931,6 +8009,64 @@ var PluginManager = class {
|
|
|
7931
8009
|
}
|
|
7932
8010
|
}
|
|
7933
8011
|
}
|
|
8012
|
+
// #65 — the previously-dead hooks, now wired. Fire-and-forget hooks run
|
|
8013
|
+
// in order (per-handler errors logged, never thrown); transform hooks fold
|
|
8014
|
+
// over the payload (a handler returning a value replaces it).
|
|
8015
|
+
/** @internal */
|
|
8016
|
+
async #runFireAndForget(name, ctx) {
|
|
8017
|
+
for (const h of this.#aggregated.hooks.get(name) ?? []) {
|
|
8018
|
+
try {
|
|
8019
|
+
await h(ctx);
|
|
8020
|
+
} catch (err) {
|
|
8021
|
+
process.stderr.write(
|
|
8022
|
+
`[theokit-sdk] ${name} hook failed: ${err instanceof Error ? err.message : String(err)}
|
|
8023
|
+
`
|
|
8024
|
+
);
|
|
8025
|
+
}
|
|
8026
|
+
}
|
|
8027
|
+
}
|
|
8028
|
+
/** @internal — fold: each handler may return a replacement payload; a throw keeps the prior value. */
|
|
8029
|
+
async #runTransform(name, payload, ctx) {
|
|
8030
|
+
let current = payload;
|
|
8031
|
+
for (const h of this.#aggregated.hooks.get(name) ?? []) {
|
|
8032
|
+
try {
|
|
8033
|
+
const out = await h(current, ctx);
|
|
8034
|
+
if (out !== void 0) current = out;
|
|
8035
|
+
} catch (err) {
|
|
8036
|
+
process.stderr.write(
|
|
8037
|
+
`[theokit-sdk] ${name} hook failed: ${err instanceof Error ? err.message : String(err)}
|
|
8038
|
+
`
|
|
8039
|
+
);
|
|
8040
|
+
}
|
|
8041
|
+
}
|
|
8042
|
+
return current;
|
|
8043
|
+
}
|
|
8044
|
+
/** #65 — fired after a tool call completes. @internal */
|
|
8045
|
+
runPostToolCallHooks(ctx) {
|
|
8046
|
+
return this.#runFireAndForget("post_tool_call", ctx);
|
|
8047
|
+
}
|
|
8048
|
+
/** #65 — fired before / after each LLM turn. @internal */
|
|
8049
|
+
runPreLlmCallHooks(ctx) {
|
|
8050
|
+
return this.#runFireAndForget("pre_llm_call", ctx);
|
|
8051
|
+
}
|
|
8052
|
+
runPostLlmCallHooks(ctx) {
|
|
8053
|
+
return this.#runFireAndForget("post_llm_call", ctx);
|
|
8054
|
+
}
|
|
8055
|
+
/** #65 — fired at run start / end. @internal */
|
|
8056
|
+
runOnSessionStartHooks(ctx) {
|
|
8057
|
+
return this.#runFireAndForget("on_session_start", ctx);
|
|
8058
|
+
}
|
|
8059
|
+
runOnSessionEndHooks(ctx) {
|
|
8060
|
+
return this.#runFireAndForget("on_session_end", ctx);
|
|
8061
|
+
}
|
|
8062
|
+
/** #65/#57 — transform tool results before they reach the LLM (the #57 seam). @internal */
|
|
8063
|
+
runTransformToolResultHooks(results, ctx) {
|
|
8064
|
+
return this.#runTransform("transform_tool_result", results, ctx);
|
|
8065
|
+
}
|
|
8066
|
+
/** #65 — transform the LLM output text before it is consumed. @internal */
|
|
8067
|
+
runTransformLlmOutputHooks(output, ctx) {
|
|
8068
|
+
return this.#runTransform("transform_llm_output", output, ctx);
|
|
8069
|
+
}
|
|
7934
8070
|
async #dispatchPlugin(plugin) {
|
|
7935
8071
|
if (plugin.kind === "general") {
|
|
7936
8072
|
const { ctx, registrations } = createPluginContext();
|
|
@@ -7958,7 +8094,29 @@ var PluginManager = class {
|
|
|
7958
8094
|
}
|
|
7959
8095
|
this.#aggregated.injected.push(...r.injected);
|
|
7960
8096
|
}
|
|
8097
|
+
/**
|
|
8098
|
+
* #68 — inverse of #merge: remove a prior registration's contributions from
|
|
8099
|
+
* the aggregated view by object identity. Used by `register()` to replace a
|
|
8100
|
+
* same-named plugin's hooks/tools instead of accumulating duplicates.
|
|
8101
|
+
*/
|
|
8102
|
+
#unmerge(r) {
|
|
8103
|
+
removeAll(this.#aggregated.tools, r.tools);
|
|
8104
|
+
removeAll(this.#aggregated.commands, r.commands);
|
|
8105
|
+
removeAll(this.#aggregated.injected, r.injected);
|
|
8106
|
+
for (const [hook, handlers] of r.hooks.entries()) {
|
|
8107
|
+
const existing = this.#aggregated.hooks.get(hook);
|
|
8108
|
+
if (existing === void 0) continue;
|
|
8109
|
+
removeAll(existing, handlers);
|
|
8110
|
+
if (existing.length === 0) this.#aggregated.hooks.delete(hook);
|
|
8111
|
+
}
|
|
8112
|
+
}
|
|
7961
8113
|
};
|
|
8114
|
+
function removeAll(arr, toRemove) {
|
|
8115
|
+
for (const item of toRemove) {
|
|
8116
|
+
const idx = arr.indexOf(item);
|
|
8117
|
+
if (idx !== -1) arr.splice(idx, 1);
|
|
8118
|
+
}
|
|
8119
|
+
}
|
|
7962
8120
|
|
|
7963
8121
|
// src/internal/telemetry/span-names.ts
|
|
7964
8122
|
var SPAN_NAMES = {
|
|
@@ -7966,7 +8124,12 @@ var SPAN_NAMES = {
|
|
|
7966
8124
|
AGENT_SEND: "agent.send",
|
|
7967
8125
|
MEMORY_RECALL: "memory.recall"};
|
|
7968
8126
|
var HISTOGRAM_NAMES = {
|
|
7969
|
-
MEMORY_RECALL_DURATION_MS: "theokit_memory_recall_duration_ms"
|
|
8127
|
+
MEMORY_RECALL_DURATION_MS: "theokit_memory_recall_duration_ms",
|
|
8128
|
+
TOOL_CALL_DURATION_MS: "theokit_tool_call_duration_ms",
|
|
8129
|
+
LLM_CALL_DURATION_MS: "theokit_llm_call_duration_ms",
|
|
8130
|
+
LLM_TOKENS: "theokit_llm_tokens",
|
|
8131
|
+
/** M3 #66 — count of finishes where the provider omitted usage (silent undercount). */
|
|
8132
|
+
LLM_USAGE_MISSING: "theokit_llm_usage_missing"
|
|
7970
8133
|
};
|
|
7971
8134
|
|
|
7972
8135
|
// src/internal/telemetry/tracer.ts
|
|
@@ -8304,7 +8467,25 @@ function createTelemetry(settings) {
|
|
|
8304
8467
|
enabled: true,
|
|
8305
8468
|
includeContent: settings.includeContent === true,
|
|
8306
8469
|
startSpan: startNewSpan,
|
|
8307
|
-
|
|
8470
|
+
// M3 #64 — actually nest the child under its parent instead of discarding it.
|
|
8471
|
+
// The parent's SpanContext is set on a fresh OTel context so the child links
|
|
8472
|
+
// to it (traceId + parentSpanId), reconstructing the causal trace tree. Falls
|
|
8473
|
+
// back to a root span when the parent has no valid span id (telemetry off /
|
|
8474
|
+
// NOOP), preserving the pre-M3 behavior for parentless callers.
|
|
8475
|
+
startChildSpan: (parent, name, attrs) => {
|
|
8476
|
+
const redactedAttrs = attrs === void 0 ? void 0 : redactAttrs(attrs);
|
|
8477
|
+
const opts = redactedAttrs ? { attributes: redactedAttrs } : void 0;
|
|
8478
|
+
const pctx = safe(() => parent?.spanContext(), void 0);
|
|
8479
|
+
const span = safe(() => {
|
|
8480
|
+
if (pctx !== void 0 && pctx.spanId !== "0".repeat(16)) {
|
|
8481
|
+
const childCtx = otel.trace.setSpanContext(otel.context.active(), pctx);
|
|
8482
|
+
return tracer.startSpan(name, opts, childCtx);
|
|
8483
|
+
}
|
|
8484
|
+
return tracer.startSpan(name, opts);
|
|
8485
|
+
}, NOOP_SPAN);
|
|
8486
|
+
if (span !== NOOP_SPAN) openSpans.add(span);
|
|
8487
|
+
return wrapSpan(span, openSpans);
|
|
8488
|
+
},
|
|
8308
8489
|
recordHistogram,
|
|
8309
8490
|
endAll: () => {
|
|
8310
8491
|
for (const span of openSpans) safe(() => span.end(), void 0);
|
|
@@ -8340,12 +8521,62 @@ function redactAttrs(attrs) {
|
|
|
8340
8521
|
}
|
|
8341
8522
|
return out;
|
|
8342
8523
|
}
|
|
8524
|
+
|
|
8525
|
+
// src/internal/runtime/lifecycle/env-policy.ts
|
|
8526
|
+
var SECRET_PATTERNS = [
|
|
8527
|
+
/KEY/i,
|
|
8528
|
+
/SECRET/i,
|
|
8529
|
+
/TOKEN/i,
|
|
8530
|
+
/PASSWORD/i,
|
|
8531
|
+
/PASSWD/i,
|
|
8532
|
+
/PASSPHRASE/i,
|
|
8533
|
+
/[_-]PWD/i,
|
|
8534
|
+
/CREDENTIAL/i,
|
|
8535
|
+
/PRIVATE/i,
|
|
8536
|
+
/_AUTH/i
|
|
8537
|
+
];
|
|
8538
|
+
var CORE_VARS = [
|
|
8539
|
+
"PATH",
|
|
8540
|
+
"HOME",
|
|
8541
|
+
"SHELL",
|
|
8542
|
+
"LANG",
|
|
8543
|
+
"LC_ALL",
|
|
8544
|
+
"LC_CTYPE",
|
|
8545
|
+
"TMPDIR",
|
|
8546
|
+
"TMP",
|
|
8547
|
+
"TEMP",
|
|
8548
|
+
"USER",
|
|
8549
|
+
"LOGNAME"
|
|
8550
|
+
];
|
|
8551
|
+
function isSecretName(name) {
|
|
8552
|
+
return SECRET_PATTERNS.some((re) => re.test(name));
|
|
8553
|
+
}
|
|
8554
|
+
function inheritsUnderPolicy(name, policy) {
|
|
8555
|
+
if (policy === "all") return true;
|
|
8556
|
+
if (policy === "core") return CORE_VARS.includes(name);
|
|
8557
|
+
return !isSecretName(name);
|
|
8558
|
+
}
|
|
8559
|
+
function resolveChildEnv(options = {}) {
|
|
8560
|
+
const parent = options.parent ?? process.env;
|
|
8561
|
+
const policy = options.policy ?? "inherit-scrubbed";
|
|
8562
|
+
const base = {};
|
|
8563
|
+
for (const [name, value] of Object.entries(parent)) {
|
|
8564
|
+
if (value !== void 0 && inheritsUnderPolicy(name, policy)) base[name] = value;
|
|
8565
|
+
}
|
|
8566
|
+
for (const [name, value] of Object.entries(options.overrides ?? {})) {
|
|
8567
|
+
base[name] = value;
|
|
8568
|
+
}
|
|
8569
|
+
return base;
|
|
8570
|
+
}
|
|
8571
|
+
|
|
8572
|
+
// src/internal/runtime/lifecycle/spawn-collect.ts
|
|
8343
8573
|
function spawnAndCollect(options) {
|
|
8344
8574
|
return new Promise((resolve3) => {
|
|
8345
8575
|
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
8346
8576
|
const spawnOptions = {
|
|
8347
8577
|
cwd: options.cwd,
|
|
8348
|
-
|
|
8578
|
+
// #54 — scrub secret-like parent env by default; `options.env` still wins.
|
|
8579
|
+
env: resolveChildEnv({ policy: options.envPolicy, overrides: options.env })
|
|
8349
8580
|
};
|
|
8350
8581
|
const child = child_process.spawn(options.command, options.args ?? [], spawnOptions);
|
|
8351
8582
|
let stdout = "";
|
|
@@ -8527,15 +8758,24 @@ function warnMalformed(agentId, line) {
|
|
|
8527
8758
|
`
|
|
8528
8759
|
);
|
|
8529
8760
|
}
|
|
8761
|
+
function hydrateSessionLine(parsed) {
|
|
8762
|
+
if (typeof parsed.text !== "string" || parsed.role === void 0) return void 0;
|
|
8763
|
+
if (parsed.role === "user" || parsed.role === "assistant") {
|
|
8764
|
+
return { role: parsed.role, text: parsed.text };
|
|
8765
|
+
}
|
|
8766
|
+
if (parsed.role === "tool_call" || parsed.role === "tool_result") {
|
|
8767
|
+
const label = parsed.role === "tool_call" ? "tool call" : "tool result";
|
|
8768
|
+
return { role: "assistant", text: `[${label}] ${parsed.text}` };
|
|
8769
|
+
}
|
|
8770
|
+
return void 0;
|
|
8771
|
+
}
|
|
8530
8772
|
async function readSessionFile(cwd, agentId) {
|
|
8531
8773
|
const lines = await readJsonlLines(cwd, agentId);
|
|
8532
8774
|
const messages = [];
|
|
8533
8775
|
for (const line of lines) {
|
|
8534
8776
|
try {
|
|
8535
|
-
const
|
|
8536
|
-
if (
|
|
8537
|
-
messages.push({ role: parsed.role, text: parsed.text });
|
|
8538
|
-
}
|
|
8777
|
+
const msg = hydrateSessionLine(JSON.parse(line));
|
|
8778
|
+
if (msg !== void 0) messages.push(msg);
|
|
8539
8779
|
} catch {
|
|
8540
8780
|
warnMalformed(agentId, line);
|
|
8541
8781
|
}
|
|
@@ -8562,28 +8802,78 @@ async function readAllPersistedMessages(cwd, agentId) {
|
|
|
8562
8802
|
return messages;
|
|
8563
8803
|
}
|
|
8564
8804
|
async function appendAnyPersistedMessage(cwd, agentId, record) {
|
|
8805
|
+
await appendPersistedMessages(cwd, agentId, [record]);
|
|
8806
|
+
}
|
|
8807
|
+
async function appendPersistedMessages(cwd, agentId, records) {
|
|
8808
|
+
if (records.length === 0) return;
|
|
8565
8809
|
const path$1 = sessionFilePath(cwd, agentId);
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8810
|
+
const payload = records.map((r) => `${redactSecrets(JSON.stringify(r))}
|
|
8811
|
+
`).join("");
|
|
8812
|
+
const dir = path.dirname(path$1);
|
|
8813
|
+
let written = false;
|
|
8814
|
+
const attempt = async () => {
|
|
8815
|
+
await promises.mkdir(dir, { recursive: true });
|
|
8816
|
+
await withFileLock(path$1, async () => {
|
|
8817
|
+
await promises.appendFile(path$1, payload, "utf8");
|
|
8818
|
+
written = true;
|
|
8819
|
+
});
|
|
8820
|
+
};
|
|
8821
|
+
try {
|
|
8822
|
+
await attempt();
|
|
8823
|
+
} catch (cause) {
|
|
8824
|
+
if (written || cause.code !== "ENOENT") throw cause;
|
|
8825
|
+
await attempt();
|
|
8826
|
+
}
|
|
8827
|
+
}
|
|
8828
|
+
async function rewriteLockedSession(path, transform) {
|
|
8829
|
+
await withFileLock(path, async () => {
|
|
8830
|
+
let raw;
|
|
8831
|
+
try {
|
|
8832
|
+
raw = await promises.readFile(path, "utf8");
|
|
8833
|
+
} catch {
|
|
8834
|
+
return;
|
|
8835
|
+
}
|
|
8836
|
+
const lines = raw.split("\n").filter((line) => line.length > 0);
|
|
8837
|
+
const next = transform(lines);
|
|
8838
|
+
if (next === void 0) return;
|
|
8839
|
+
await replaceFileAtomic(path, next);
|
|
8840
|
+
});
|
|
8569
8841
|
}
|
|
8570
8842
|
async function compactSessionFile(cwd, agentId, maxTurns) {
|
|
8571
8843
|
const path = sessionFilePath(cwd, agentId);
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
const
|
|
8844
|
+
if (!fs.existsSync(path)) return;
|
|
8845
|
+
await rewriteLockedSession(
|
|
8846
|
+
path,
|
|
8847
|
+
(lines) => lines.length <= maxTurns * 2 ? void 0 : `${lines.slice(-maxTurns).join("\n")}
|
|
8848
|
+
`
|
|
8849
|
+
);
|
|
8850
|
+
}
|
|
8851
|
+
async function truncateSessionTo(cwd, agentId, keepCount) {
|
|
8852
|
+
const path = sessionFilePath(cwd, agentId);
|
|
8853
|
+
if (!fs.existsSync(path)) return 0;
|
|
8854
|
+
let kept = 0;
|
|
8855
|
+
await rewriteLockedSession(path, (lines) => {
|
|
8856
|
+
const keep = Math.max(0, Math.min(keepCount, lines.length));
|
|
8857
|
+
kept = keep;
|
|
8858
|
+
if (keep === lines.length) return void 0;
|
|
8859
|
+
return keep === 0 ? "" : `${lines.slice(0, keep).join("\n")}
|
|
8581
8860
|
`;
|
|
8582
|
-
|
|
8861
|
+
});
|
|
8862
|
+
return kept;
|
|
8583
8863
|
}
|
|
8584
8864
|
|
|
8585
8865
|
// src/internal/persistence/conversation-storage-fs.ts
|
|
8586
8866
|
init_security();
|
|
8867
|
+
|
|
8868
|
+
// src/internal/persistence/pagination.ts
|
|
8869
|
+
function paginate(items, opts) {
|
|
8870
|
+
if (opts === void 0 || opts.offset === void 0 && opts.limit === void 0) return items;
|
|
8871
|
+
const start = Math.max(0, opts.offset ?? 0);
|
|
8872
|
+
const end = opts.limit === void 0 ? items.length : start + Math.max(0, opts.limit);
|
|
8873
|
+
return items.slice(start, end);
|
|
8874
|
+
}
|
|
8875
|
+
|
|
8876
|
+
// src/internal/persistence/conversation-storage-fs.ts
|
|
8587
8877
|
var FileSystemConversationStorage = class {
|
|
8588
8878
|
#root;
|
|
8589
8879
|
constructor(opts = {}) {
|
|
@@ -8593,23 +8883,31 @@ var FileSystemConversationStorage = class {
|
|
|
8593
8883
|
get root() {
|
|
8594
8884
|
return this.#root;
|
|
8595
8885
|
}
|
|
8596
|
-
async getMessages(conversationId) {
|
|
8886
|
+
async getMessages(conversationId, opts) {
|
|
8597
8887
|
const records = await readAllPersistedMessages(this.#root, conversationId);
|
|
8598
|
-
|
|
8888
|
+
const all = records.map(toStoredMessage);
|
|
8889
|
+
return paginate(all, opts);
|
|
8599
8890
|
}
|
|
8600
8891
|
async appendMessage(conversationId, message) {
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
8892
|
+
await appendAnyPersistedMessage(this.#root, conversationId, toRecord(message));
|
|
8893
|
+
}
|
|
8894
|
+
async appendMessages(conversationId, messages) {
|
|
8895
|
+
await appendPersistedMessages(this.#root, conversationId, messages.map(toRecord));
|
|
8896
|
+
}
|
|
8897
|
+
async truncateConversation(conversationId, keepCount) {
|
|
8898
|
+
return truncateSessionTo(this.#root, conversationId, keepCount);
|
|
8607
8899
|
}
|
|
8608
8900
|
async deleteConversation(conversationId) {
|
|
8609
8901
|
const safe2 = sanitizeIdentifier(conversationId, { maxLen: 128 });
|
|
8610
8902
|
const dirPath = safePathJoin(this.#root, ".theokit", "agents", safe2);
|
|
8611
8903
|
await promises.rm(dirPath, { recursive: true, force: true });
|
|
8612
8904
|
}
|
|
8905
|
+
async deleteScope(prefix) {
|
|
8906
|
+
const ids = await this.listConversationIds();
|
|
8907
|
+
const matching = ids.filter((id) => id.startsWith(prefix));
|
|
8908
|
+
for (const id of matching) await this.deleteConversation(id);
|
|
8909
|
+
return matching.length;
|
|
8910
|
+
}
|
|
8613
8911
|
async listConversationIds(opts = {}) {
|
|
8614
8912
|
const agentsRoot = safePathJoin(this.#root, ".theokit", "agents");
|
|
8615
8913
|
let entries;
|
|
@@ -8635,6 +8933,9 @@ function toStoredMessage(record) {
|
|
|
8635
8933
|
at: record.at
|
|
8636
8934
|
};
|
|
8637
8935
|
}
|
|
8936
|
+
function toRecord(message) {
|
|
8937
|
+
return { role: message.role, text: message.content, at: message.at ?? Date.now() };
|
|
8938
|
+
}
|
|
8638
8939
|
|
|
8639
8940
|
// src/internal/runtime/session/agent-session.ts
|
|
8640
8941
|
var DEFAULT_MAX_TURNS = 200;
|
|
@@ -8720,12 +9021,19 @@ async function readPersistedForCache(adapter, agentId) {
|
|
|
8720
9021
|
const records = await adapter.getMessages(agentId);
|
|
8721
9022
|
const out = [];
|
|
8722
9023
|
for (const r of records) {
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
}
|
|
9024
|
+
const folded = foldStoredToSession(r);
|
|
9025
|
+
if (folded !== void 0) out.push(folded);
|
|
8726
9026
|
}
|
|
8727
9027
|
return out;
|
|
8728
9028
|
}
|
|
9029
|
+
function foldStoredToSession(r) {
|
|
9030
|
+
if (r.role === "user" || r.role === "assistant") return { role: r.role, text: r.content };
|
|
9031
|
+
if (r.role === "tool_call" || r.role === "tool_result") {
|
|
9032
|
+
const label = r.role === "tool_call" ? "tool call" : "tool result";
|
|
9033
|
+
return { role: "assistant", text: `[${label}] ${r.content}` };
|
|
9034
|
+
}
|
|
9035
|
+
return void 0;
|
|
9036
|
+
}
|
|
8729
9037
|
async function flushSessionWrites() {
|
|
8730
9038
|
while (pendingAppends.size > 0) {
|
|
8731
9039
|
const all = Array.from(pendingAppends.values());
|
|
@@ -11077,11 +11385,32 @@ function reasoningEffortFromParams(params) {
|
|
|
11077
11385
|
const thinking = params?.find((p) => p.id === "thinking");
|
|
11078
11386
|
return thinking !== void 0 && thinking.value.length > 0 ? thinking.value : void 0;
|
|
11079
11387
|
}
|
|
11388
|
+
function emitLlmMetrics(inputs, result, startAt) {
|
|
11389
|
+
inputs.telemetry?.recordHistogram(HISTOGRAM_NAMES.LLM_CALL_DURATION_MS, Date.now() - startAt, {
|
|
11390
|
+
provider: inputs.llm.name
|
|
11391
|
+
});
|
|
11392
|
+
if (result.inputTokens === void 0 && result.outputTokens === void 0) {
|
|
11393
|
+
inputs.telemetry?.recordHistogram(HISTOGRAM_NAMES.LLM_USAGE_MISSING, 1, {
|
|
11394
|
+
provider: inputs.llm.name
|
|
11395
|
+
});
|
|
11396
|
+
process.stderr.write(
|
|
11397
|
+
`[theokit-sdk] llm usage missing from ${inputs.llm.name} finish \u2014 budget may undercount
|
|
11398
|
+
`
|
|
11399
|
+
);
|
|
11400
|
+
return;
|
|
11401
|
+
}
|
|
11402
|
+
inputs.telemetry?.recordHistogram(
|
|
11403
|
+
HISTOGRAM_NAMES.LLM_TOKENS,
|
|
11404
|
+
(result.inputTokens ?? 0) + (result.outputTokens ?? 0),
|
|
11405
|
+
{ provider: inputs.llm.name }
|
|
11406
|
+
);
|
|
11407
|
+
}
|
|
11080
11408
|
async function streamLlmTurn(inputs, ctx) {
|
|
11081
|
-
const llmSpan = inputs.telemetry?.
|
|
11409
|
+
const llmSpan = inputs.telemetry?.startChildSpan(ctx.sendSpan, "llm.call", {
|
|
11082
11410
|
"model.id": inputs.model.id ?? "auto",
|
|
11083
11411
|
provider: inputs.llm.name
|
|
11084
11412
|
});
|
|
11413
|
+
const startAt = Date.now();
|
|
11085
11414
|
const signal = inputs.signal ?? new AbortController().signal;
|
|
11086
11415
|
const generator = inputs.llm.stream(
|
|
11087
11416
|
{
|
|
@@ -11124,6 +11453,7 @@ async function streamLlmTurn(inputs, ctx) {
|
|
|
11124
11453
|
inputTokens: result.inputTokens ?? 0,
|
|
11125
11454
|
outputTokens: result.outputTokens ?? 0
|
|
11126
11455
|
});
|
|
11456
|
+
emitLlmMetrics(inputs, result, startAt);
|
|
11127
11457
|
llmSpan?.end();
|
|
11128
11458
|
const stripped = stripThinkBlocks(collected.accumulatedText);
|
|
11129
11459
|
return {
|
|
@@ -11353,21 +11683,21 @@ async function executeTool(inputs, resolved, call) {
|
|
|
11353
11683
|
}
|
|
11354
11684
|
if (resolved.origin === "shell") return runShellTool(inputs, call);
|
|
11355
11685
|
if (resolved.origin === "memory") return runMemoryTool(resolved, call);
|
|
11356
|
-
if (resolved.origin === "custom") return runCustomTool(resolved, call);
|
|
11686
|
+
if (resolved.origin === "custom") return runCustomTool(resolved, call, inputs.signal);
|
|
11357
11687
|
return runMcpTool(inputs, resolved, call);
|
|
11358
11688
|
}
|
|
11359
11689
|
async function runMemoryTool(resolved, call) {
|
|
11360
11690
|
return runHandlerTool("memory", resolved.memoryHandler, call);
|
|
11361
11691
|
}
|
|
11362
|
-
async function runCustomTool(resolved, call) {
|
|
11363
|
-
return runHandlerTool("custom", resolved.customHandler, call);
|
|
11692
|
+
async function runCustomTool(resolved, call, signal) {
|
|
11693
|
+
return runHandlerTool("custom", resolved.customHandler, call, signal);
|
|
11364
11694
|
}
|
|
11365
|
-
async function runHandlerTool(kind, handler, call) {
|
|
11695
|
+
async function runHandlerTool(kind, handler, call, signal) {
|
|
11366
11696
|
if (handler === void 0) {
|
|
11367
11697
|
return { stdout: "", stderr: `${kind} tool ${call.name} has no handler`, exitCode: 127 };
|
|
11368
11698
|
}
|
|
11369
11699
|
try {
|
|
11370
|
-
const stdout = await handler(call.input);
|
|
11700
|
+
const stdout = await handler(call.input, { signal });
|
|
11371
11701
|
return { stdout, stderr: "", exitCode: 0 };
|
|
11372
11702
|
} catch (cause) {
|
|
11373
11703
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
@@ -11413,22 +11743,66 @@ ${result.stderr}`.trim();
|
|
|
11413
11743
|
return result.stdout.trim();
|
|
11414
11744
|
}
|
|
11415
11745
|
|
|
11746
|
+
// src/internal/agent-loop/tool-timeout.ts
|
|
11747
|
+
var TOOL_ABORTED_EXIT = 124;
|
|
11748
|
+
function abortedResult(signal) {
|
|
11749
|
+
const reason = signal.reason;
|
|
11750
|
+
const timedOut = reason?.name === "TimeoutError";
|
|
11751
|
+
return {
|
|
11752
|
+
stdout: "",
|
|
11753
|
+
stderr: timedOut ? "tool execution timed out" : "tool execution aborted",
|
|
11754
|
+
exitCode: TOOL_ABORTED_EXIT
|
|
11755
|
+
};
|
|
11756
|
+
}
|
|
11757
|
+
function raceToolExecution(exec, opts) {
|
|
11758
|
+
const { signal, timeoutMs } = opts;
|
|
11759
|
+
if (signal === void 0 && timeoutMs === void 0) return exec;
|
|
11760
|
+
const signals = [];
|
|
11761
|
+
if (signal !== void 0) signals.push(signal);
|
|
11762
|
+
if (timeoutMs !== void 0) signals.push(AbortSignal.timeout(timeoutMs));
|
|
11763
|
+
const merged = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
11764
|
+
if (merged.aborted) return Promise.resolve(abortedResult(merged));
|
|
11765
|
+
return new Promise((resolve3, reject) => {
|
|
11766
|
+
let settled = false;
|
|
11767
|
+
const onAbort = () => {
|
|
11768
|
+
if (settled) return;
|
|
11769
|
+
settled = true;
|
|
11770
|
+
resolve3(abortedResult(merged));
|
|
11771
|
+
};
|
|
11772
|
+
merged.addEventListener("abort", onAbort, { once: true });
|
|
11773
|
+
exec.then(
|
|
11774
|
+
(r) => {
|
|
11775
|
+
if (settled) return;
|
|
11776
|
+
settled = true;
|
|
11777
|
+
merged.removeEventListener("abort", onAbort);
|
|
11778
|
+
resolve3(r);
|
|
11779
|
+
},
|
|
11780
|
+
(e) => {
|
|
11781
|
+
if (settled) return;
|
|
11782
|
+
settled = true;
|
|
11783
|
+
merged.removeEventListener("abort", onAbort);
|
|
11784
|
+
reject(e);
|
|
11785
|
+
}
|
|
11786
|
+
);
|
|
11787
|
+
});
|
|
11788
|
+
}
|
|
11789
|
+
|
|
11416
11790
|
// src/internal/agent-loop/tool-dispatch.ts
|
|
11417
|
-
async function dispatchTools(inputs, tools, toolCalls, events) {
|
|
11791
|
+
async function dispatchTools(inputs, tools, toolCalls, events, parentSpan) {
|
|
11418
11792
|
const maxConcurrent = inputs.maxConcurrentTools ?? 4;
|
|
11419
11793
|
return mapWithConcurrency(
|
|
11420
11794
|
toolCalls,
|
|
11421
11795
|
maxConcurrent,
|
|
11422
|
-
(call) => dispatchSingleCall(inputs, tools, call, events)
|
|
11796
|
+
(call) => dispatchSingleCall(inputs, tools, call, events, parentSpan)
|
|
11423
11797
|
);
|
|
11424
11798
|
}
|
|
11425
|
-
async function dispatchSingleCall(inputs, tools, call, events) {
|
|
11799
|
+
async function dispatchSingleCall(inputs, tools, call, events, parentSpan) {
|
|
11426
11800
|
const { call: workingCall, repairs } = applyRepairAndExtractCall(tools, call);
|
|
11427
11801
|
const callId = generateCallId();
|
|
11428
11802
|
const forkVeto = vetoFromForkWhitelist(inputs, workingCall, callId, events);
|
|
11429
11803
|
if (forkVeto !== void 0) return forkVeto;
|
|
11430
11804
|
const resolved = tools.find((tool) => tool.name === workingCall.name);
|
|
11431
|
-
const toolSpan = startToolCallSpan(inputs, workingCall, resolved, callId, repairs);
|
|
11805
|
+
const toolSpan = startToolCallSpan(inputs, workingCall, resolved, callId, repairs, parentSpan);
|
|
11432
11806
|
events.push(buildToolUseRunning(inputs, callId, workingCall));
|
|
11433
11807
|
const pluginVeto = await vetoFromPluginPreHook(inputs, workingCall, callId, events);
|
|
11434
11808
|
if (pluginVeto !== void 0) {
|
|
@@ -11445,6 +11819,13 @@ async function dispatchSingleCall(inputs, tools, call, events) {
|
|
|
11445
11819
|
return fileVeto;
|
|
11446
11820
|
}
|
|
11447
11821
|
const result = await runToolWithLifecycle(inputs, resolved, workingCall, callId);
|
|
11822
|
+
await inputs.pluginManager?.runPostToolCallHooks({
|
|
11823
|
+
name: workingCall.name,
|
|
11824
|
+
args: workingCall.input,
|
|
11825
|
+
result: { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode },
|
|
11826
|
+
agentId: inputs.agentId,
|
|
11827
|
+
runId: inputs.runId
|
|
11828
|
+
});
|
|
11448
11829
|
return finalizeSpanAndPostHook(inputs, workingCall, callId, result, events, toolSpan);
|
|
11449
11830
|
}
|
|
11450
11831
|
function applyRepairAndExtractCall(tools, call) {
|
|
@@ -11477,8 +11858,8 @@ function vetoFromForkWhitelist(inputs, call, callId, events) {
|
|
|
11477
11858
|
content: `Tool blocked by fork whitelist: ${whitelistDecision.reason}`
|
|
11478
11859
|
};
|
|
11479
11860
|
}
|
|
11480
|
-
function startToolCallSpan(inputs, call, resolved, callId, repairs) {
|
|
11481
|
-
const toolSpan = inputs.telemetry?.
|
|
11861
|
+
function startToolCallSpan(inputs, call, resolved, callId, repairs, parentSpan) {
|
|
11862
|
+
const toolSpan = inputs.telemetry?.startChildSpan(parentSpan, "tool.call", {
|
|
11482
11863
|
"tool.name": call.name,
|
|
11483
11864
|
"tool.origin": resolved?.origin ?? "unknown",
|
|
11484
11865
|
callId
|
|
@@ -11542,8 +11923,14 @@ async function runToolWithLifecycle(inputs, resolved, call, callId) {
|
|
|
11542
11923
|
conversationId: inputs.agentId,
|
|
11543
11924
|
callId
|
|
11544
11925
|
});
|
|
11545
|
-
const result = await executeTool(inputs, resolved, call)
|
|
11926
|
+
const result = await raceToolExecution(executeTool(inputs, resolved, call), {
|
|
11927
|
+
signal: inputs.signal,
|
|
11928
|
+
timeoutMs: inputs.perToolTimeoutMs
|
|
11929
|
+
});
|
|
11546
11930
|
const durationMs = Date.now() - startAt;
|
|
11931
|
+
inputs.telemetry?.recordHistogram(HISTOGRAM_NAMES.TOOL_CALL_DURATION_MS, durationMs, {
|
|
11932
|
+
"tool.name": call.name
|
|
11933
|
+
});
|
|
11547
11934
|
if (result.exitCode !== void 0 && result.exitCode !== 0 && result.exitCode !== null) {
|
|
11548
11935
|
await safeEmitToolHook(inputs.onToolError, {
|
|
11549
11936
|
toolName: call.name,
|
|
@@ -11642,6 +12029,35 @@ function buildToolUseCompleted(inputs, callId, call, result) {
|
|
|
11642
12029
|
};
|
|
11643
12030
|
}
|
|
11644
12031
|
|
|
12032
|
+
// src/internal/agent-loop/tool-result-guard.ts
|
|
12033
|
+
var OPEN = "<untrusted-tool-output>";
|
|
12034
|
+
var CLOSE = "</untrusted-tool-output>";
|
|
12035
|
+
var PII_PATTERNS = [
|
|
12036
|
+
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
|
|
12037
|
+
// email
|
|
12038
|
+
/\b(?:\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/g
|
|
12039
|
+
// phone
|
|
12040
|
+
];
|
|
12041
|
+
function guardText(content, opts) {
|
|
12042
|
+
let out = content;
|
|
12043
|
+
if (opts.redactPii === true) {
|
|
12044
|
+
for (const re of PII_PATTERNS) out = out.replace(re, "[REDACTED]");
|
|
12045
|
+
}
|
|
12046
|
+
if (opts.delimit === true) {
|
|
12047
|
+
const safe2 = out.split(CLOSE).join("</ untrusted-tool-output>");
|
|
12048
|
+
out = `${OPEN}
|
|
12049
|
+
${safe2}
|
|
12050
|
+
${CLOSE}`;
|
|
12051
|
+
}
|
|
12052
|
+
return out;
|
|
12053
|
+
}
|
|
12054
|
+
function applyToolResultGuard(parts, opts) {
|
|
12055
|
+
if (opts.delimit !== true && opts.redactPii !== true) return parts;
|
|
12056
|
+
return parts.map(
|
|
12057
|
+
(p) => p.type === "tool_result" ? { ...p, content: guardText(p.content, opts) } : p
|
|
12058
|
+
);
|
|
12059
|
+
}
|
|
12060
|
+
|
|
11645
12061
|
// src/internal/budget/pricing-data.json
|
|
11646
12062
|
var pricing_data_default = {
|
|
11647
12063
|
_meta: {
|
|
@@ -11992,6 +12408,11 @@ async function runAgentLoop(inputs) {
|
|
|
11992
12408
|
try {
|
|
11993
12409
|
const ctx = await initLoopContext(inputs);
|
|
11994
12410
|
ctxRef = ctx;
|
|
12411
|
+
ctx.sendSpan = sendSpan;
|
|
12412
|
+
await inputs.pluginManager?.runOnSessionStartHooks({
|
|
12413
|
+
agentId: inputs.agentId,
|
|
12414
|
+
runId: inputs.runId
|
|
12415
|
+
});
|
|
11995
12416
|
const budget = inputs.budget ?? new IterationBudget({ maxIterations: inputs.maxIterations ?? 8 });
|
|
11996
12417
|
let lastTurnDecision;
|
|
11997
12418
|
while (budget.shouldContinue()) {
|
|
@@ -12019,6 +12440,7 @@ async function runAgentLoop(inputs) {
|
|
|
12019
12440
|
}
|
|
12020
12441
|
budget.consume();
|
|
12021
12442
|
inputs.budgetTracker?.nextIteration?.();
|
|
12443
|
+
if (inputs.signal?.aborted === true) break;
|
|
12022
12444
|
}
|
|
12023
12445
|
if (lastTurnDecision === "continue" && budget.shouldContinue() === false) {
|
|
12024
12446
|
ctx.stoppedAtIterationLimit = true;
|
|
@@ -12058,6 +12480,10 @@ async function runAgentLoop(inputs) {
|
|
|
12058
12480
|
...ctx.stoppedByDoomLoop === true ? { stoppedByDoomLoop: true } : {}
|
|
12059
12481
|
};
|
|
12060
12482
|
} finally {
|
|
12483
|
+
await inputs.pluginManager?.runOnSessionEndHooks({
|
|
12484
|
+
agentId: inputs.agentId,
|
|
12485
|
+
runId: inputs.runId
|
|
12486
|
+
});
|
|
12061
12487
|
if (ctxRef !== void 0 && ctxRef.memoryProviderHandle !== void 0 && inputs.memoryProvider !== void 0) {
|
|
12062
12488
|
try {
|
|
12063
12489
|
await inputs.memoryProvider.dispose(ctxRef.memoryProviderHandle);
|
|
@@ -12161,7 +12587,10 @@ async function finishOrReflect(inputs, ctx, llmOutput) {
|
|
|
12161
12587
|
return "done";
|
|
12162
12588
|
}
|
|
12163
12589
|
async function runIteration(inputs, ctx) {
|
|
12590
|
+
const hookCtx = { agentId: inputs.agentId, runId: inputs.runId };
|
|
12591
|
+
await inputs.pluginManager?.runPreLlmCallHooks(hookCtx);
|
|
12164
12592
|
const llmOutput = await streamLlmTurn(inputs, ctx);
|
|
12593
|
+
await inputs.pluginManager?.runPostLlmCallHooks(hookCtx);
|
|
12165
12594
|
accumulateUsage(ctx.usage, llmOutput);
|
|
12166
12595
|
if (inputs.budgetTracker !== void 0) {
|
|
12167
12596
|
const modelId = inputs.model.id ?? "auto";
|
|
@@ -12187,6 +12616,13 @@ async function runIteration(inputs, ctx) {
|
|
|
12187
12616
|
}
|
|
12188
12617
|
return continueOrTerminate(inputs, ctx, llmOutput);
|
|
12189
12618
|
}
|
|
12619
|
+
async function transformLlmOutputText(inputs, text, ctx) {
|
|
12620
|
+
return inputs.pluginManager !== void 0 ? inputs.pluginManager.runTransformLlmOutputHooks(text, ctx) : text;
|
|
12621
|
+
}
|
|
12622
|
+
async function guardAndTransformToolResults(inputs, raw, ctx) {
|
|
12623
|
+
const guarded = inputs.toolResultGuard !== void 0 ? applyToolResultGuard(raw, inputs.toolResultGuard) : raw;
|
|
12624
|
+
return inputs.pluginManager !== void 0 ? inputs.pluginManager.runTransformToolResultHooks(guarded, ctx) : guarded;
|
|
12625
|
+
}
|
|
12190
12626
|
async function continueOrTerminate(inputs, ctx, llmOutput) {
|
|
12191
12627
|
if (llmOutput.errored) return "error";
|
|
12192
12628
|
if (llmOutput.text.length > 0) {
|
|
@@ -12195,8 +12631,18 @@ async function continueOrTerminate(inputs, ctx, llmOutput) {
|
|
|
12195
12631
|
if (llmOutput.stopReason !== "tool_use" || llmOutput.toolCalls.length === 0) {
|
|
12196
12632
|
return finishOrReflect(inputs, ctx, llmOutput);
|
|
12197
12633
|
}
|
|
12198
|
-
|
|
12199
|
-
const
|
|
12634
|
+
const tCtx = { agentId: inputs.agentId, runId: inputs.runId };
|
|
12635
|
+
const outText = await transformLlmOutputText(inputs, llmOutput.text, tCtx);
|
|
12636
|
+
ctx.messages.push(buildAssistantTurn(outText, llmOutput.toolCalls));
|
|
12637
|
+
const rawResults = await dispatchTools(
|
|
12638
|
+
inputs,
|
|
12639
|
+
ctx.tools,
|
|
12640
|
+
llmOutput.toolCalls,
|
|
12641
|
+
ctx.events,
|
|
12642
|
+
ctx.sendSpan
|
|
12643
|
+
// M3 #64 — nest tool.call spans under agent.send
|
|
12644
|
+
);
|
|
12645
|
+
const toolResults = await guardAndTransformToolResults(inputs, rawResults, tCtx);
|
|
12200
12646
|
ctx.messages.push({ role: "user", content: toolResults });
|
|
12201
12647
|
if (inputs.onStep !== void 0) {
|
|
12202
12648
|
const cb = inputs.onStep;
|
|
@@ -12641,6 +13087,56 @@ function mapAnthropicStatusToCode(status, body) {
|
|
|
12641
13087
|
function formatMessage(status, code) {
|
|
12642
13088
|
return `Anthropic API error: ${code} (HTTP ${status})`;
|
|
12643
13089
|
}
|
|
13090
|
+
var cachedJsonrepair;
|
|
13091
|
+
function loadJsonrepair() {
|
|
13092
|
+
if (cachedJsonrepair === void 0) {
|
|
13093
|
+
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)));
|
|
13094
|
+
cachedJsonrepair = req("jsonrepair").jsonrepair;
|
|
13095
|
+
}
|
|
13096
|
+
return cachedJsonrepair;
|
|
13097
|
+
}
|
|
13098
|
+
function isPlainObject(v) {
|
|
13099
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
13100
|
+
}
|
|
13101
|
+
function toFiniteNumber(raw) {
|
|
13102
|
+
if (raw === "") return void 0;
|
|
13103
|
+
const n = Number(raw);
|
|
13104
|
+
return Number.isFinite(n) && String(n) === raw ? n : void 0;
|
|
13105
|
+
}
|
|
13106
|
+
function tryJson(raw, repair) {
|
|
13107
|
+
const t = raw.trimStart();
|
|
13108
|
+
if (!(t.startsWith("{") || t.startsWith("["))) return void 0;
|
|
13109
|
+
try {
|
|
13110
|
+
return JSON.parse(repair ? loadJsonrepair()(t) : t);
|
|
13111
|
+
} catch {
|
|
13112
|
+
return void 0;
|
|
13113
|
+
}
|
|
13114
|
+
}
|
|
13115
|
+
function heuristicCoerce(raw, repairJson) {
|
|
13116
|
+
if (raw === "true") return true;
|
|
13117
|
+
if (raw === "false") return false;
|
|
13118
|
+
if (raw === "null") return null;
|
|
13119
|
+
const n = toFiniteNumber(raw);
|
|
13120
|
+
if (n !== void 0) return n;
|
|
13121
|
+
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
13122
|
+
return json === void 0 ? raw : json;
|
|
13123
|
+
}
|
|
13124
|
+
function coerceCandidates(raw, repairJson) {
|
|
13125
|
+
const out = [];
|
|
13126
|
+
if (raw === "true") out.push(true);
|
|
13127
|
+
else if (raw === "false") out.push(false);
|
|
13128
|
+
else if (raw === "null") out.push(null);
|
|
13129
|
+
const n = toFiniteNumber(raw);
|
|
13130
|
+
if (n !== void 0) out.push(n);
|
|
13131
|
+
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
13132
|
+
if (json !== void 0) out.push(json);
|
|
13133
|
+
out.push(raw);
|
|
13134
|
+
return out;
|
|
13135
|
+
}
|
|
13136
|
+
function objectShape(schema) {
|
|
13137
|
+
const shape = schema?.shape;
|
|
13138
|
+
return shape !== null && typeof shape === "object" ? shape : void 0;
|
|
13139
|
+
}
|
|
12644
13140
|
|
|
12645
13141
|
// src/internal/llm/finish.ts
|
|
12646
13142
|
function collapseSystemText(system) {
|
|
@@ -12653,9 +13149,21 @@ function parseToolArguments(buffered) {
|
|
|
12653
13149
|
try {
|
|
12654
13150
|
return JSON.parse(buffered);
|
|
12655
13151
|
} catch {
|
|
13152
|
+
const repaired = tryJson(buffered, true);
|
|
13153
|
+
if (isPlainObject(repaired)) return repaired;
|
|
12656
13154
|
return { raw: buffered };
|
|
12657
13155
|
}
|
|
12658
13156
|
}
|
|
13157
|
+
function mapOpenAIFinish(reason) {
|
|
13158
|
+
switch (reason) {
|
|
13159
|
+
case "tool_calls":
|
|
13160
|
+
return "tool_use";
|
|
13161
|
+
case "length":
|
|
13162
|
+
return "max_tokens";
|
|
13163
|
+
default:
|
|
13164
|
+
return "end_turn";
|
|
13165
|
+
}
|
|
13166
|
+
}
|
|
12659
13167
|
function makeLlmFinish(state4) {
|
|
12660
13168
|
const finish = {
|
|
12661
13169
|
stopReason: state4.stopReason,
|
|
@@ -13537,56 +14045,9 @@ function toOllamaTools(tools) {
|
|
|
13537
14045
|
}
|
|
13538
14046
|
}));
|
|
13539
14047
|
}
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
|
|
13543
|
-
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)));
|
|
13544
|
-
cachedJsonrepair = req("jsonrepair").jsonrepair;
|
|
13545
|
-
}
|
|
13546
|
-
return cachedJsonrepair;
|
|
13547
|
-
}
|
|
13548
|
-
function isPlainObject(v) {
|
|
13549
|
-
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
13550
|
-
}
|
|
13551
|
-
function toFiniteNumber(raw) {
|
|
13552
|
-
if (raw === "") return void 0;
|
|
13553
|
-
const n = Number(raw);
|
|
13554
|
-
return Number.isFinite(n) && String(n) === raw ? n : void 0;
|
|
13555
|
-
}
|
|
13556
|
-
function tryJson(raw, repair) {
|
|
13557
|
-
const t = raw.trimStart();
|
|
13558
|
-
if (!(t.startsWith("{") || t.startsWith("["))) return void 0;
|
|
13559
|
-
try {
|
|
13560
|
-
return JSON.parse(repair ? loadJsonrepair()(t) : t);
|
|
13561
|
-
} catch {
|
|
13562
|
-
return void 0;
|
|
13563
|
-
}
|
|
13564
|
-
}
|
|
13565
|
-
function heuristicCoerce(raw, repairJson) {
|
|
13566
|
-
if (raw === "true") return true;
|
|
13567
|
-
if (raw === "false") return false;
|
|
13568
|
-
if (raw === "null") return null;
|
|
13569
|
-
const n = toFiniteNumber(raw);
|
|
13570
|
-
if (n !== void 0) return n;
|
|
13571
|
-
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
13572
|
-
return json === void 0 ? raw : json;
|
|
13573
|
-
}
|
|
13574
|
-
function coerceCandidates(raw, repairJson) {
|
|
13575
|
-
const out = [];
|
|
13576
|
-
if (raw === "true") out.push(true);
|
|
13577
|
-
else if (raw === "false") out.push(false);
|
|
13578
|
-
else if (raw === "null") out.push(null);
|
|
13579
|
-
const n = toFiniteNumber(raw);
|
|
13580
|
-
if (n !== void 0) out.push(n);
|
|
13581
|
-
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
13582
|
-
if (json !== void 0) out.push(json);
|
|
13583
|
-
out.push(raw);
|
|
13584
|
-
return out;
|
|
13585
|
-
}
|
|
13586
|
-
function objectShape(schema) {
|
|
13587
|
-
const shape = schema?.shape;
|
|
13588
|
-
return shape !== null && typeof shape === "object" ? shape : void 0;
|
|
13589
|
-
}
|
|
14048
|
+
|
|
14049
|
+
// src/internal/llm/openai.ts
|
|
14050
|
+
init_errors();
|
|
13590
14051
|
|
|
13591
14052
|
// src/sanitize/sanitize-tool-input.ts
|
|
13592
14053
|
function applyTrim(key2, value, ctx) {
|
|
@@ -13683,6 +14144,77 @@ function parseHermesParams(inner) {
|
|
|
13683
14144
|
}
|
|
13684
14145
|
return sanitizeToolInput(input, { trim: true }).value;
|
|
13685
14146
|
}
|
|
14147
|
+
var STREAM_MARKER = "<function=";
|
|
14148
|
+
var DEFAULT_STREAM_BUFFER_CAP = 8192;
|
|
14149
|
+
var isStreamWs = (c) => c === " " || c === " " || c === "\n" || c === "\r";
|
|
14150
|
+
function streamToolCallBufferState(held, allowedToolNames, cap2 = DEFAULT_STREAM_BUFFER_CAP) {
|
|
14151
|
+
if (allowedToolNames.size === 0) return "impossible";
|
|
14152
|
+
const t = held.trimStart();
|
|
14153
|
+
if (t.length < STREAM_MARKER.length) {
|
|
14154
|
+
return STREAM_MARKER.startsWith(t) ? "possible" : "impossible";
|
|
14155
|
+
}
|
|
14156
|
+
if (!t.startsWith(STREAM_MARKER)) return "impossible";
|
|
14157
|
+
const parsed = parseStreamMarkerName(t);
|
|
14158
|
+
if (parsed === "building") return "possible";
|
|
14159
|
+
if (parsed === "invalid") return "impossible";
|
|
14160
|
+
const nameOk = parsed.complete ? allowedToolNames.has(parsed.name) : someToolNameStartsWith(allowedToolNames, parsed.name);
|
|
14161
|
+
if (!nameOk) return "impossible";
|
|
14162
|
+
return held.length > cap2 ? "impossible" : "possible";
|
|
14163
|
+
}
|
|
14164
|
+
function parseStreamMarkerName(t) {
|
|
14165
|
+
let cursor = STREAM_MARKER.length;
|
|
14166
|
+
while (cursor < t.length && isStreamWs(t[cursor])) cursor += 1;
|
|
14167
|
+
const nameStart = cursor;
|
|
14168
|
+
while (cursor < t.length && t[cursor] !== ">" && !isStreamWs(t[cursor])) cursor += 1;
|
|
14169
|
+
const name = t.slice(nameStart, cursor);
|
|
14170
|
+
if (name.length === 0) return cursor >= t.length ? "building" : "invalid";
|
|
14171
|
+
return { name, complete: cursor < t.length && t[cursor] === ">" };
|
|
14172
|
+
}
|
|
14173
|
+
function someToolNameStartsWith(allowedToolNames, prefix) {
|
|
14174
|
+
for (const name of allowedToolNames) {
|
|
14175
|
+
if (name.startsWith(prefix)) return true;
|
|
14176
|
+
}
|
|
14177
|
+
return false;
|
|
14178
|
+
}
|
|
14179
|
+
function firstPossibleMarkerStart(held, allowedToolNames) {
|
|
14180
|
+
for (let i = held.indexOf("<"); i !== -1; i = held.indexOf("<", i + 1)) {
|
|
14181
|
+
if (streamToolCallBufferState(held.slice(i), allowedToolNames) === "possible") return i;
|
|
14182
|
+
}
|
|
14183
|
+
return -1;
|
|
14184
|
+
}
|
|
14185
|
+
var StreamSuppressionBuffer = class {
|
|
14186
|
+
constructor(allowedToolNames) {
|
|
14187
|
+
this.allowedToolNames = allowedToolNames;
|
|
14188
|
+
}
|
|
14189
|
+
allowedToolNames;
|
|
14190
|
+
#held = "";
|
|
14191
|
+
/** Feed a content delta; returns the text to emit as a `text_delta` now, or `undefined` to hold. */
|
|
14192
|
+
push(content) {
|
|
14193
|
+
this.#held += content;
|
|
14194
|
+
if (streamToolCallBufferState(this.#held, this.allowedToolNames) === "possible")
|
|
14195
|
+
return void 0;
|
|
14196
|
+
const holdStart = firstPossibleMarkerStart(this.#held, this.allowedToolNames);
|
|
14197
|
+
if (holdStart > 0) {
|
|
14198
|
+
const flush2 = this.#held.slice(0, holdStart);
|
|
14199
|
+
this.#held = this.#held.slice(holdStart);
|
|
14200
|
+
return flush2;
|
|
14201
|
+
}
|
|
14202
|
+
const flush = this.#held;
|
|
14203
|
+
this.#held = "";
|
|
14204
|
+
return flush;
|
|
14205
|
+
}
|
|
14206
|
+
/** Drain the held buffer at stream end. `hasNativeCalls` mirrors `finish()`'s size-guard: when
|
|
14207
|
+
* native `tool_calls` exist, `finish()` won't strip the leaked block, so stream the held text WHOLE
|
|
14208
|
+
* (keeping `accumulatedText == finish.text`); otherwise strip the recoverable blocks. Idempotent. */
|
|
14209
|
+
drain(hasNativeCalls) {
|
|
14210
|
+
if (this.#held.length === 0) return void 0;
|
|
14211
|
+
const held = this.#held;
|
|
14212
|
+
this.#held = "";
|
|
14213
|
+
if (hasNativeCalls) return held;
|
|
14214
|
+
const residual = extractHermesToolCalls(held, () => "held", this.allowedToolNames).residualText;
|
|
14215
|
+
return residual.length > 0 ? residual : void 0;
|
|
14216
|
+
}
|
|
14217
|
+
};
|
|
13686
14218
|
|
|
13687
14219
|
// src/internal/llm/openai.ts
|
|
13688
14220
|
var OpenAIClient = class {
|
|
@@ -13760,8 +14292,12 @@ var OpenAIClient = class {
|
|
|
13760
14292
|
// model was actually given. Empty set (no tools) recovers nothing.
|
|
13761
14293
|
new Set(request.tools?.map((tool) => tool.name) ?? [])
|
|
13762
14294
|
);
|
|
14295
|
+
let sawDone = false;
|
|
13763
14296
|
for await (const record of parseSseStream(response.body, signal)) {
|
|
13764
|
-
if (record.data === "[DONE]")
|
|
14297
|
+
if (record.data === "[DONE]") {
|
|
14298
|
+
sawDone = true;
|
|
14299
|
+
break;
|
|
14300
|
+
}
|
|
13765
14301
|
let chunk;
|
|
13766
14302
|
try {
|
|
13767
14303
|
chunk = JSON.parse(record.data);
|
|
@@ -13781,6 +14317,13 @@ var OpenAIClient = class {
|
|
|
13781
14317
|
const events = accumulator.consume(chunk);
|
|
13782
14318
|
for (const event of events) yield event;
|
|
13783
14319
|
}
|
|
14320
|
+
if (!sawDone && !accumulator.finishReasonSeen) {
|
|
14321
|
+
throw new exports.NetworkError("SSE stream truncated (no finish_reason / [DONE])", {
|
|
14322
|
+
code: "stream_truncated"
|
|
14323
|
+
});
|
|
14324
|
+
}
|
|
14325
|
+
const drainEvent = accumulator.finalizeHeldText();
|
|
14326
|
+
if (drainEvent !== void 0) yield drainEvent;
|
|
13784
14327
|
return accumulator.finish();
|
|
13785
14328
|
}
|
|
13786
14329
|
};
|
|
@@ -13796,6 +14339,7 @@ var OpenAIStreamAccumulator = class {
|
|
|
13796
14339
|
this.extractFromContent = extractFromContent;
|
|
13797
14340
|
this.providerName = providerName;
|
|
13798
14341
|
this.allowedToolNames = allowedToolNames;
|
|
14342
|
+
this.suppress = extractFromContent && allowedToolNames !== void 0 && allowedToolNames.size > 0 ? new StreamSuppressionBuffer(allowedToolNames) : void 0;
|
|
13799
14343
|
}
|
|
13800
14344
|
extractFromContent;
|
|
13801
14345
|
providerName;
|
|
@@ -13808,18 +14352,30 @@ var OpenAIStreamAccumulator = class {
|
|
|
13808
14352
|
cacheWriteTokens;
|
|
13809
14353
|
reasoningTokens;
|
|
13810
14354
|
toolCalls = /* @__PURE__ */ new Map();
|
|
14355
|
+
/** R7: present only when recovery is enabled AND the request declares tools — holds suspected
|
|
14356
|
+
* leaked-dialect content back from the `text_delta` stream. `undefined` ⇒ stream immediately. */
|
|
14357
|
+
suppress;
|
|
13811
14358
|
consume(chunk) {
|
|
13812
14359
|
const events = [];
|
|
13813
14360
|
this.applyUsage(chunk.usage);
|
|
13814
14361
|
for (const choice of chunk.choices ?? []) {
|
|
13815
|
-
|
|
13816
|
-
|
|
13817
|
-
|
|
13818
|
-
|
|
13819
|
-
|
|
13820
|
-
|
|
13821
|
-
|
|
13822
|
-
|
|
14362
|
+
events.push(...this.applyChoice(choice));
|
|
14363
|
+
}
|
|
14364
|
+
return events;
|
|
14365
|
+
}
|
|
14366
|
+
applyChoice(choice) {
|
|
14367
|
+
const events = [];
|
|
14368
|
+
const reasoningEvent = this.applyReasoningDelta(
|
|
14369
|
+
choice.delta?.reasoning ?? choice.delta?.reasoning_content
|
|
14370
|
+
);
|
|
14371
|
+
if (reasoningEvent !== void 0) events.push(reasoningEvent);
|
|
14372
|
+
const textEvent = this.applyContentDelta(choice.delta?.content);
|
|
14373
|
+
if (textEvent !== void 0) events.push(textEvent);
|
|
14374
|
+
this.mergeToolCallDeltas(choice.delta?.tool_calls);
|
|
14375
|
+
this.applyFinishReason(choice.finish_reason);
|
|
14376
|
+
if (choice.finish_reason !== void 0 && choice.finish_reason !== null) {
|
|
14377
|
+
const flushEvent = this.finalizeHeldText();
|
|
14378
|
+
if (flushEvent !== void 0) events.push(flushEvent);
|
|
13823
14379
|
}
|
|
13824
14380
|
return events;
|
|
13825
14381
|
}
|
|
@@ -13844,7 +14400,17 @@ var OpenAIStreamAccumulator = class {
|
|
|
13844
14400
|
applyContentDelta(content) {
|
|
13845
14401
|
if (typeof content !== "string" || content.length === 0) return void 0;
|
|
13846
14402
|
this.text += content;
|
|
13847
|
-
return { type: "text_delta", text: content };
|
|
14403
|
+
if (this.suppress === void 0) return { type: "text_delta", text: content };
|
|
14404
|
+
const emit2 = this.suppress.push(content);
|
|
14405
|
+
return emit2 !== void 0 ? { type: "text_delta", text: emit2 } : void 0;
|
|
14406
|
+
}
|
|
14407
|
+
/** R7 held-buffer finalizer, called at the `finish_reason` chunk (in `applyChoice`) AND after the
|
|
14408
|
+
* SSE loop in `stream()` — so a stream that omits a `finish_reason` terminal never silently drops
|
|
14409
|
+
* held text. `toolCalls.size > 0` (native calls present) makes `finish()` skip recovery, so the
|
|
14410
|
+
* buffer streams the held text whole. Idempotent once drained. */
|
|
14411
|
+
finalizeHeldText() {
|
|
14412
|
+
const emit2 = this.suppress?.drain(this.toolCalls.size > 0);
|
|
14413
|
+
return emit2 !== void 0 ? { type: "text_delta", text: emit2 } : void 0;
|
|
13848
14414
|
}
|
|
13849
14415
|
mergeToolCallDeltas(deltas) {
|
|
13850
14416
|
for (const call of deltas ?? []) {
|
|
@@ -13855,8 +14421,15 @@ var OpenAIStreamAccumulator = class {
|
|
|
13855
14421
|
this.toolCalls.set(call.index, existing);
|
|
13856
14422
|
}
|
|
13857
14423
|
}
|
|
14424
|
+
/** M2 #61 — true once any chunk carried a non-null `finish_reason` (else a
|
|
14425
|
+
* stream ending without `[DONE]` is a truncation, not a clean end). */
|
|
14426
|
+
sawFinishReason = false;
|
|
14427
|
+
get finishReasonSeen() {
|
|
14428
|
+
return this.sawFinishReason;
|
|
14429
|
+
}
|
|
13858
14430
|
applyFinishReason(reason) {
|
|
13859
14431
|
if (reason === void 0 || reason === null) return;
|
|
14432
|
+
this.sawFinishReason = true;
|
|
13860
14433
|
this.stopReason = mapOpenAIFinish(reason);
|
|
13861
14434
|
}
|
|
13862
14435
|
finish() {
|
|
@@ -13901,18 +14474,6 @@ var OpenAIStreamAccumulator = class {
|
|
|
13901
14474
|
});
|
|
13902
14475
|
}
|
|
13903
14476
|
};
|
|
13904
|
-
function mapOpenAIFinish(reason) {
|
|
13905
|
-
switch (reason) {
|
|
13906
|
-
case "tool_calls":
|
|
13907
|
-
return "tool_use";
|
|
13908
|
-
case "length":
|
|
13909
|
-
return "max_tokens";
|
|
13910
|
-
case "stop":
|
|
13911
|
-
return "end_turn";
|
|
13912
|
-
default:
|
|
13913
|
-
return "end_turn";
|
|
13914
|
-
}
|
|
13915
|
-
}
|
|
13916
14477
|
function applyReasoningRequest(body, effort, providerName) {
|
|
13917
14478
|
if (providerName === "openai") {
|
|
13918
14479
|
body.reasoning_effort = effort;
|
|
@@ -14010,19 +14571,76 @@ function assistantMessage(message) {
|
|
|
14010
14571
|
|
|
14011
14572
|
// src/internal/llm/pool-aware-client.ts
|
|
14012
14573
|
init_errors();
|
|
14574
|
+
|
|
14575
|
+
// src/internal/resilience/circuit-breaker.ts
|
|
14576
|
+
var DEFAULT_MAX_TIMEOUTS = 3;
|
|
14577
|
+
var DEFAULT_COOLDOWN_MS2 = 6e4;
|
|
14578
|
+
var CircuitBreaker = class {
|
|
14579
|
+
constructor(opts = {}) {
|
|
14580
|
+
this.opts = opts;
|
|
14581
|
+
}
|
|
14582
|
+
opts;
|
|
14583
|
+
states = /* @__PURE__ */ new Map();
|
|
14584
|
+
/** @returns true when the breaker is open and the call should be skipped. */
|
|
14585
|
+
shouldSkip(key2) {
|
|
14586
|
+
const state4 = this.states.get(key2);
|
|
14587
|
+
if (state4 === void 0) return false;
|
|
14588
|
+
if (state4.cooldownUntilMs === 0) return false;
|
|
14589
|
+
if (this.now() < state4.cooldownUntilMs) return true;
|
|
14590
|
+
state4.cooldownUntilMs = 0;
|
|
14591
|
+
state4.consecutiveTimeouts = 0;
|
|
14592
|
+
return false;
|
|
14593
|
+
}
|
|
14594
|
+
recordSuccess(key2) {
|
|
14595
|
+
const state4 = this.states.get(key2);
|
|
14596
|
+
if (state4 === void 0) return;
|
|
14597
|
+
state4.consecutiveTimeouts = 0;
|
|
14598
|
+
state4.cooldownUntilMs = 0;
|
|
14599
|
+
}
|
|
14600
|
+
recordTimeout(key2) {
|
|
14601
|
+
const state4 = this.states.get(key2) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
14602
|
+
state4.consecutiveTimeouts += 1;
|
|
14603
|
+
if (state4.consecutiveTimeouts >= (this.opts.maxTimeouts ?? DEFAULT_MAX_TIMEOUTS)) {
|
|
14604
|
+
state4.cooldownUntilMs = this.now() + (this.opts.cooldownMs ?? DEFAULT_COOLDOWN_MS2);
|
|
14605
|
+
}
|
|
14606
|
+
this.states.set(key2, state4);
|
|
14607
|
+
}
|
|
14608
|
+
/** @internal — tests inspect counter state. */
|
|
14609
|
+
inspect(key2) {
|
|
14610
|
+
return this.states.get(key2) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
14611
|
+
}
|
|
14612
|
+
now() {
|
|
14613
|
+
return this.opts.now?.() ?? Date.now();
|
|
14614
|
+
}
|
|
14615
|
+
};
|
|
14616
|
+
|
|
14617
|
+
// src/internal/llm/pool-aware-client.ts
|
|
14618
|
+
init_retry();
|
|
14013
14619
|
var PoolAwareLlmClient = class {
|
|
14014
|
-
constructor(pool, buildClient2, waitForAvailableMs = 3e4) {
|
|
14620
|
+
constructor(pool, buildClient2, waitForAvailableMs = 3e4, resilience = {}) {
|
|
14015
14621
|
this.pool = pool;
|
|
14016
14622
|
this.buildClient = buildClient2;
|
|
14017
14623
|
this.waitForAvailableMs = waitForAvailableMs;
|
|
14018
14624
|
this.name = `pool-aware:${pool.provider}`;
|
|
14625
|
+
this.breaker = resilience.breaker ?? new CircuitBreaker();
|
|
14626
|
+
this.backoffBaseMs = resilience.backoffBaseMs;
|
|
14627
|
+
this.rng = resilience.rng;
|
|
14019
14628
|
}
|
|
14020
14629
|
pool;
|
|
14021
14630
|
buildClient;
|
|
14022
14631
|
waitForAvailableMs;
|
|
14023
14632
|
name;
|
|
14633
|
+
/** M2 #60 — provider-level circuit breaker (consecutive-failure). */
|
|
14634
|
+
breaker;
|
|
14635
|
+
backoffBaseMs;
|
|
14636
|
+
rng;
|
|
14024
14637
|
// 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.
|
|
14025
14638
|
async *stream(request, signal) {
|
|
14639
|
+
if (this.breaker.shouldSkip(this.pool.provider)) {
|
|
14640
|
+
throw new exports.NetworkError(`${this.pool.provider} circuit open \u2014 failing fast`, {
|
|
14641
|
+
code: "circuit_open"
|
|
14642
|
+
});
|
|
14643
|
+
}
|
|
14026
14644
|
let hasRetried429 = false;
|
|
14027
14645
|
while (true) {
|
|
14028
14646
|
if (signal.aborted) throw abortError2(signal);
|
|
@@ -14037,6 +14655,7 @@ var PoolAwareLlmClient = class {
|
|
|
14037
14655
|
}
|
|
14038
14656
|
}
|
|
14039
14657
|
if (entry === null) {
|
|
14658
|
+
this.breaker.recordTimeout(this.pool.provider);
|
|
14040
14659
|
throw new CredentialPoolExhaustedError(
|
|
14041
14660
|
`All ${this.pool.provider} credentials exhausted; next retry available at ${this.nextRetryHint() ?? "unknown"}`,
|
|
14042
14661
|
{ provider: this.pool.provider, nextRetryAt: this.nextRetryHint() }
|
|
@@ -14046,10 +14665,19 @@ var PoolAwareLlmClient = class {
|
|
|
14046
14665
|
const realClient = this.buildClient(entry.accessToken);
|
|
14047
14666
|
const attempt = await tryFirstEvent(realClient, request, signal);
|
|
14048
14667
|
if (attempt.kind === "ok") {
|
|
14668
|
+
this.breaker.recordSuccess(this.pool.provider);
|
|
14049
14669
|
return yield* relayStream(attempt.generator, attempt.firstResult);
|
|
14050
14670
|
}
|
|
14051
14671
|
const decision = classifyAndDecide(attempt.error, hasRetried429);
|
|
14052
14672
|
if (decision === "retry") {
|
|
14673
|
+
await sleepWithAbort(
|
|
14674
|
+
computeBackoffMs({
|
|
14675
|
+
attempt: 0,
|
|
14676
|
+
...this.backoffBaseMs !== void 0 ? { baseMs: this.backoffBaseMs } : {},
|
|
14677
|
+
...this.rng !== void 0 ? { rng: this.rng } : {}
|
|
14678
|
+
}),
|
|
14679
|
+
signal
|
|
14680
|
+
);
|
|
14053
14681
|
hasRetried429 = true;
|
|
14054
14682
|
continue;
|
|
14055
14683
|
}
|
|
@@ -14069,6 +14697,7 @@ var PoolAwareLlmClient = class {
|
|
|
14069
14697
|
hasRetried429 = false;
|
|
14070
14698
|
continue;
|
|
14071
14699
|
}
|
|
14700
|
+
this.breaker.recordTimeout(this.pool.provider);
|
|
14072
14701
|
throw attempt.error;
|
|
14073
14702
|
}
|
|
14074
14703
|
}
|
|
@@ -14504,9 +15133,28 @@ function selectTransport(profile, apiKey) {
|
|
|
14504
15133
|
// src/internal/mcp/client.ts
|
|
14505
15134
|
init_errors();
|
|
14506
15135
|
init_path_guard();
|
|
14507
|
-
function createMcpClient(name, config) {
|
|
15136
|
+
function createMcpClient(name, config, fetchImpl = fetch) {
|
|
14508
15137
|
if (isStdio(config)) return new StdioMcpClient(name, config);
|
|
14509
|
-
return new HttpMcpClient(name, config);
|
|
15138
|
+
return new HttpMcpClient(name, config, fetchImpl);
|
|
15139
|
+
}
|
|
15140
|
+
var DEFAULT_MCP_TIMEOUT_MS = 3e4;
|
|
15141
|
+
var MAX_STDIO_BUFFER_BYTES = 8 * 1024 * 1024;
|
|
15142
|
+
var RECONNECT_BASE_MS = 250;
|
|
15143
|
+
var MAX_RECONNECT_ATTEMPTS = 2;
|
|
15144
|
+
function reconnectDelay(attempt) {
|
|
15145
|
+
const ceiling = RECONNECT_BASE_MS * 2 ** attempt;
|
|
15146
|
+
const ms = Math.floor(Math.random() * ceiling);
|
|
15147
|
+
return ms <= 0 ? Promise.resolve() : new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
15148
|
+
}
|
|
15149
|
+
function mcpTimeoutError(name, timeoutMs) {
|
|
15150
|
+
return new exports.NetworkError(`MCP ${name} request timed out after ${timeoutMs}ms`, {
|
|
15151
|
+
code: "mcp_timeout"
|
|
15152
|
+
});
|
|
15153
|
+
}
|
|
15154
|
+
function isAbortLike(cause) {
|
|
15155
|
+
if (typeof cause !== "object" || cause === null || !("name" in cause)) return false;
|
|
15156
|
+
const name = cause.name;
|
|
15157
|
+
return name === "TimeoutError" || name === "AbortError";
|
|
14510
15158
|
}
|
|
14511
15159
|
async function rpcInitialize(request) {
|
|
14512
15160
|
await request("initialize", {
|
|
@@ -14552,32 +15200,107 @@ var StdioMcpClient = class extends BaseMcpClient {
|
|
|
14552
15200
|
name;
|
|
14553
15201
|
child;
|
|
14554
15202
|
nextId = 1;
|
|
15203
|
+
// #59 — pending requests carry a reject + timer so a silent server times out
|
|
15204
|
+
// (typed error), a late reply after timeout is a no-op, and close() settles them.
|
|
14555
15205
|
pending = /* @__PURE__ */ new Map();
|
|
14556
15206
|
buffer = "";
|
|
14557
|
-
|
|
15207
|
+
// M2 #59 — reconnect-after-drop state. `dropped` is set when the child exits
|
|
15208
|
+
// unexpectedly OR times out (not via close()); the next request re-spawns with
|
|
15209
|
+
// backoff. `reconnectPromise` is a SINGLE in-flight reconnect shared by every
|
|
15210
|
+
// concurrent request so parallel tool dispatch after a drop awaits one handshake
|
|
15211
|
+
// instead of racing (or spuriously failing with mcp_not_init).
|
|
15212
|
+
dropped = false;
|
|
15213
|
+
reconnectAttempts = 0;
|
|
15214
|
+
reconnectPromise;
|
|
15215
|
+
get timeoutMs() {
|
|
15216
|
+
return this.config.requestTimeoutMs ?? DEFAULT_MCP_TIMEOUT_MS;
|
|
15217
|
+
}
|
|
15218
|
+
/** Spawn the server child and wire stdout/stderr/error/exit handlers.
|
|
15219
|
+
* Shared by `initialize()` and the M2 #59 reconnect path. */
|
|
15220
|
+
spawnChild() {
|
|
14558
15221
|
const resolvedCwd = resolveMcpCwd(this.config.cwd);
|
|
14559
15222
|
const child = child_process.spawn(this.config.command, this.config.args ?? [], {
|
|
14560
15223
|
cwd: resolvedCwd,
|
|
14561
|
-
|
|
15224
|
+
// #54 (F-H1) — a third-party MCP server binary must not inherit host
|
|
15225
|
+
// secrets. Scrub secret-like vars by default; `config.env` still wins.
|
|
15226
|
+
env: resolveChildEnv({ policy: this.config.envPolicy, overrides: this.config.env })
|
|
14562
15227
|
});
|
|
14563
15228
|
this.child = child;
|
|
14564
15229
|
child.stdout.on("data", (chunk) => this.consume(chunk));
|
|
14565
15230
|
child.stderr.on("data", () => void 0);
|
|
15231
|
+
child.stdin.on("error", () => void 0);
|
|
14566
15232
|
child.on("error", () => {
|
|
14567
|
-
|
|
14568
|
-
|
|
14569
|
-
|
|
14570
|
-
|
|
15233
|
+
this.rejectAllPending(
|
|
15234
|
+
new exports.NetworkError(`MCP ${this.name} process crashed`, { code: "mcp_crashed" })
|
|
15235
|
+
);
|
|
15236
|
+
});
|
|
15237
|
+
child.on("exit", () => {
|
|
15238
|
+
if (this.child !== child) return;
|
|
15239
|
+
this.child = void 0;
|
|
15240
|
+
this.dropped = true;
|
|
15241
|
+
this.rejectAllPending(
|
|
15242
|
+
new exports.NetworkError(`MCP ${this.name} disconnected`, { code: "mcp_disconnected" })
|
|
15243
|
+
);
|
|
14571
15244
|
});
|
|
15245
|
+
}
|
|
15246
|
+
async initialize() {
|
|
15247
|
+
this.spawnChild();
|
|
14572
15248
|
await super.initialize();
|
|
14573
15249
|
}
|
|
15250
|
+
/** M2 #59 — ensure a live child before a request. Reconnect (bounded, with
|
|
15251
|
+
* full-jitter backoff) when the client was dropped; fail fast when never
|
|
15252
|
+
* initialized. Concurrent callers share ONE reconnect handshake. */
|
|
15253
|
+
ensureConnected() {
|
|
15254
|
+
if (this.child !== void 0) return Promise.resolve();
|
|
15255
|
+
if (!this.dropped) {
|
|
15256
|
+
return Promise.reject(
|
|
15257
|
+
new exports.ConfigurationError(`MCP ${this.name} is not initialized`, { code: "mcp_not_init" })
|
|
15258
|
+
);
|
|
15259
|
+
}
|
|
15260
|
+
this.reconnectPromise ??= this.reconnect().finally(() => {
|
|
15261
|
+
this.reconnectPromise = void 0;
|
|
15262
|
+
});
|
|
15263
|
+
return this.reconnectPromise;
|
|
15264
|
+
}
|
|
15265
|
+
async reconnect() {
|
|
15266
|
+
if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
15267
|
+
throw new exports.NetworkError(`MCP ${this.name} reconnect exhausted`, { code: "mcp_disconnected" });
|
|
15268
|
+
}
|
|
15269
|
+
await reconnectDelay(this.reconnectAttempts);
|
|
15270
|
+
this.reconnectAttempts += 1;
|
|
15271
|
+
this.spawnChild();
|
|
15272
|
+
await super.initialize();
|
|
15273
|
+
this.dropped = false;
|
|
15274
|
+
this.reconnectAttempts = 0;
|
|
15275
|
+
}
|
|
14574
15276
|
async close() {
|
|
14575
|
-
|
|
14576
|
-
this.child
|
|
15277
|
+
this.rejectAllPending(new exports.NetworkError(`MCP ${this.name} closed`, { code: "mcp_closed" }));
|
|
15278
|
+
const child = this.child;
|
|
14577
15279
|
this.child = void 0;
|
|
15280
|
+
this.dropped = false;
|
|
15281
|
+
child?.kill("SIGTERM");
|
|
15282
|
+
}
|
|
15283
|
+
/** Reject + clear every pending request (crash / close). @internal */
|
|
15284
|
+
rejectAllPending(error) {
|
|
15285
|
+
for (const entry of this.pending.values()) {
|
|
15286
|
+
clearTimeout(entry.timer);
|
|
15287
|
+
entry.reject(error);
|
|
15288
|
+
}
|
|
15289
|
+
this.pending.clear();
|
|
14578
15290
|
}
|
|
14579
15291
|
consume(chunk) {
|
|
14580
15292
|
this.buffer += chunk.toString("utf8");
|
|
15293
|
+
if (this.buffer.length > MAX_STDIO_BUFFER_BYTES) {
|
|
15294
|
+
this.buffer = "";
|
|
15295
|
+
this.rejectAllPending(
|
|
15296
|
+
new exports.NetworkError(`MCP ${this.name} exceeded stdout buffer limit`, {
|
|
15297
|
+
code: "mcp_buffer_overflow"
|
|
15298
|
+
})
|
|
15299
|
+
);
|
|
15300
|
+
this.child?.kill("SIGKILL");
|
|
15301
|
+
this.child = void 0;
|
|
15302
|
+
return;
|
|
15303
|
+
}
|
|
14581
15304
|
let newlineIndex = this.buffer.indexOf("\n");
|
|
14582
15305
|
while (newlineIndex !== -1) {
|
|
14583
15306
|
const line = this.buffer.slice(0, newlineIndex).trim();
|
|
@@ -14594,23 +15317,47 @@ var StdioMcpClient = class extends BaseMcpClient {
|
|
|
14594
15317
|
return;
|
|
14595
15318
|
}
|
|
14596
15319
|
if (typeof message.id !== "number") return;
|
|
14597
|
-
const
|
|
14598
|
-
if (
|
|
15320
|
+
const entry = this.pending.get(message.id);
|
|
15321
|
+
if (entry === void 0) return;
|
|
14599
15322
|
this.pending.delete(message.id);
|
|
14600
|
-
|
|
15323
|
+
clearTimeout(entry.timer);
|
|
15324
|
+
entry.resolve(message);
|
|
14601
15325
|
}
|
|
14602
15326
|
request(method, params) {
|
|
14603
|
-
|
|
14604
|
-
|
|
14605
|
-
|
|
14606
|
-
|
|
15327
|
+
const child = this.child;
|
|
15328
|
+
if (child !== void 0) return this.send(child, method, params);
|
|
15329
|
+
if (this.dropped) return this.reconnectAndRequest(method, params);
|
|
15330
|
+
return Promise.reject(
|
|
15331
|
+
new exports.ConfigurationError(`MCP ${this.name} is not initialized`, { code: "mcp_not_init" })
|
|
15332
|
+
);
|
|
15333
|
+
}
|
|
15334
|
+
/** M2 #59 — reconnect a dropped client, then send. Separate async path so the
|
|
15335
|
+
* happy path above never pays an extra microtask tick. */
|
|
15336
|
+
async reconnectAndRequest(method, params) {
|
|
15337
|
+
await this.ensureConnected();
|
|
15338
|
+
const child = this.child;
|
|
15339
|
+
if (child === void 0) {
|
|
15340
|
+
throw new exports.ConfigurationError(`MCP ${this.name} is not initialized`, { code: "mcp_not_init" });
|
|
14607
15341
|
}
|
|
15342
|
+
return this.send(child, method, params);
|
|
15343
|
+
}
|
|
15344
|
+
send(child, method, params) {
|
|
14608
15345
|
const id = this.nextId++;
|
|
14609
15346
|
const payload = { jsonrpc: "2.0", id, method, params };
|
|
14610
|
-
|
|
15347
|
+
child.stdin.write(`${JSON.stringify(payload)}
|
|
14611
15348
|
`);
|
|
14612
|
-
return new Promise((resolve3) => {
|
|
14613
|
-
|
|
15349
|
+
return new Promise((resolve3, reject) => {
|
|
15350
|
+
const timer = setTimeout(() => {
|
|
15351
|
+
this.pending.delete(id);
|
|
15352
|
+
reject(mcpTimeoutError(this.name, this.timeoutMs));
|
|
15353
|
+
this.child?.kill("SIGKILL");
|
|
15354
|
+
this.child = void 0;
|
|
15355
|
+
this.dropped = true;
|
|
15356
|
+
this.rejectAllPending(
|
|
15357
|
+
new exports.NetworkError(`MCP ${this.name} disconnected`, { code: "mcp_disconnected" })
|
|
15358
|
+
);
|
|
15359
|
+
}, this.timeoutMs);
|
|
15360
|
+
this.pending.set(id, { resolve: resolve3, reject, timer });
|
|
14614
15361
|
});
|
|
14615
15362
|
}
|
|
14616
15363
|
};
|
|
@@ -14636,11 +15383,20 @@ var HttpMcpClient = class extends BaseMcpClient {
|
|
|
14636
15383
|
accept: "application/json",
|
|
14637
15384
|
...this.config.headers ?? {}
|
|
14638
15385
|
};
|
|
14639
|
-
const
|
|
14640
|
-
|
|
14641
|
-
|
|
14642
|
-
|
|
14643
|
-
|
|
15386
|
+
const timeoutMs = this.config.requestTimeoutMs ?? DEFAULT_MCP_TIMEOUT_MS;
|
|
15387
|
+
let response;
|
|
15388
|
+
try {
|
|
15389
|
+
response = await this.fetchImpl(this.config.url, {
|
|
15390
|
+
method: "POST",
|
|
15391
|
+
headers,
|
|
15392
|
+
body: JSON.stringify(payload),
|
|
15393
|
+
// #59 — bound the request; a non-responding endpoint aborts here.
|
|
15394
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
15395
|
+
});
|
|
15396
|
+
} catch (cause) {
|
|
15397
|
+
if (isAbortLike(cause)) throw mcpTimeoutError(this.name, timeoutMs);
|
|
15398
|
+
throw cause;
|
|
15399
|
+
}
|
|
14644
15400
|
if (!response.ok) {
|
|
14645
15401
|
throw new exports.NetworkError(`MCP ${this.name} returned ${response.status}`, {
|
|
14646
15402
|
code: "mcp_http_error"
|
|
@@ -14755,11 +15511,33 @@ function resolveRunProvider(options) {
|
|
|
14755
15511
|
);
|
|
14756
15512
|
}
|
|
14757
15513
|
const parsedModel = parseModelId(options.model?.id);
|
|
14758
|
-
const
|
|
14759
|
-
const
|
|
14760
|
-
const
|
|
15514
|
+
const modelInferredProvider = parsedModel.provider !== void 0 && getProviderProfile(parsedModel.provider) !== void 0 ? parsedModel.provider : void 0;
|
|
15515
|
+
const keyInferredProvider = inferProviderFromApiKey(options.agentOptions.apiKey);
|
|
15516
|
+
const primary = options.agentOptions.providers?.routes?.[0]?.provider ?? keyInferredProvider ?? modelInferredProvider ?? detectPrimaryProvider();
|
|
15517
|
+
const effectiveModelId = modelInferredProvider !== void 0 && modelInferredProvider === primary ? parsedModel.name : options.model?.id ?? "claude-sonnet-4-6";
|
|
14761
15518
|
return { primary, effectiveModelId };
|
|
14762
15519
|
}
|
|
15520
|
+
function inferProviderFromApiKey(apiKey) {
|
|
15521
|
+
if (apiKey === void 0 || apiKey.length === 0) return void 0;
|
|
15522
|
+
const byPrefix = [
|
|
15523
|
+
{ provider: "openrouter", prefix: "sk-or-" },
|
|
15524
|
+
{ provider: "anthropic", prefix: "sk-ant-" },
|
|
15525
|
+
{ provider: "openai", prefix: "sk-" }
|
|
15526
|
+
];
|
|
15527
|
+
for (const { provider, prefix } of byPrefix) {
|
|
15528
|
+
if (apiKey.startsWith(prefix) && getProviderProfile(provider) !== void 0) {
|
|
15529
|
+
return provider;
|
|
15530
|
+
}
|
|
15531
|
+
}
|
|
15532
|
+
return void 0;
|
|
15533
|
+
}
|
|
15534
|
+
function mergeExplicitApiKey(pools, primary, apiKey) {
|
|
15535
|
+
if (apiKey === void 0 || apiKey.length === 0) return pools;
|
|
15536
|
+
if (isFixtureApiKey(apiKey) || apiKey === LOCAL_RUNTIME_MOCK_KEY) return pools;
|
|
15537
|
+
const existing = pools?.[primary];
|
|
15538
|
+
if (existing !== void 0 && existing.length > 0) return pools;
|
|
15539
|
+
return { ...pools ?? {}, [primary]: [apiKey] };
|
|
15540
|
+
}
|
|
14763
15541
|
function buildLoopInputs(options, runId, userText) {
|
|
14764
15542
|
const maxIterations = options.sendOptions.maxIterations;
|
|
14765
15543
|
if (maxIterations !== void 0 && (!Number.isInteger(maxIterations) || maxIterations < 1)) {
|
|
@@ -14770,7 +15548,11 @@ function buildLoopInputs(options, runId, userText) {
|
|
|
14770
15548
|
}
|
|
14771
15549
|
const { primary, effectiveModelId } = resolveRunProvider(options);
|
|
14772
15550
|
const fallback = options.agentOptions.providers?.fallback;
|
|
14773
|
-
const apiKeys =
|
|
15551
|
+
const apiKeys = mergeExplicitApiKey(
|
|
15552
|
+
options.agentOptions.providers?.apiKeys,
|
|
15553
|
+
primary,
|
|
15554
|
+
options.agentOptions.apiKey
|
|
15555
|
+
);
|
|
14774
15556
|
const credentialPoolStrategy = options.agentOptions.providers?.credentialPoolStrategy;
|
|
14775
15557
|
const extractToolCallsFromContent = options.agentOptions.providers?.routes?.[0]?.extractToolCallsFromContent;
|
|
14776
15558
|
const chain = resolveProviderChain({
|
|
@@ -14814,6 +15596,10 @@ function buildLoopInputs(options, runId, userText) {
|
|
|
14814
15596
|
// D318 — forward SendOptions.signal to the agent loop so streamLlmTurn
|
|
14815
15597
|
// can attach it to the LLM `fetch({ signal })` call.
|
|
14816
15598
|
...options.sendOptions.signal !== void 0 ? { signal: options.sendOptions.signal } : {},
|
|
15599
|
+
// #58 / #57 — forward the per-tool timeout + tool-result guard so a consumer
|
|
15600
|
+
// can enable them via SendOptions (not only internal AgentLoopInputs).
|
|
15601
|
+
...options.sendOptions.perToolTimeoutMs !== void 0 ? { perToolTimeoutMs: options.sendOptions.perToolTimeoutMs } : {},
|
|
15602
|
+
...options.sendOptions.toolResultGuard !== void 0 ? { toolResultGuard: options.sendOptions.toolResultGuard } : {},
|
|
14817
15603
|
// M1-2: per-send iteration ceiling (validated above). The loop reads
|
|
14818
15604
|
// inputs.maxIterations (default 8 when unset).
|
|
14819
15605
|
...maxIterations !== void 0 ? { maxIterations } : {},
|
|
@@ -15131,7 +15917,12 @@ async function runActiveMemory(args) {
|
|
|
15131
15917
|
hits: []
|
|
15132
15918
|
});
|
|
15133
15919
|
}
|
|
15134
|
-
const
|
|
15920
|
+
const tenantCtx = {
|
|
15921
|
+
namespace: args.namespace,
|
|
15922
|
+
userId: args.userId,
|
|
15923
|
+
scope: args.scope
|
|
15924
|
+
};
|
|
15925
|
+
const cached2 = args.cache?.get(args.userText, cfg.queryMode, tenantCtx);
|
|
15135
15926
|
if (cached2 !== void 0) return endRecallSpan(span, args, cached2);
|
|
15136
15927
|
const query = buildQuery(args.userText, args.priorMessages, cfg.queryMode, cfg.recentUserTurns);
|
|
15137
15928
|
if (query.trim().length === 0) {
|
|
@@ -15216,7 +16007,12 @@ function notifyBreaker(breaker, key2, status) {
|
|
|
15216
16007
|
else if (status === "ok" || status === "no-recall") breaker.recordSuccess(key2);
|
|
15217
16008
|
}
|
|
15218
16009
|
async function finalize(args, queryMode, result) {
|
|
15219
|
-
|
|
16010
|
+
const tenantCtx = {
|
|
16011
|
+
namespace: args.namespace,
|
|
16012
|
+
userId: args.userId,
|
|
16013
|
+
scope: args.scope
|
|
16014
|
+
};
|
|
16015
|
+
args.cache?.set(args.userText, queryMode, result, tenantCtx);
|
|
15220
16016
|
if (args.persistTranscripts === true && args.cwd !== void 0) {
|
|
15221
16017
|
await persistActiveMemoryTranscript(args.cwd, {
|
|
15222
16018
|
runId: args.runId ?? `run-${Date.now()}`,
|
|
@@ -15789,48 +16585,6 @@ var MEMORY_EMBEDDING_ADAPTERS = {
|
|
|
15789
16585
|
gemini: geminiMemoryEmbeddingProviderAdapter
|
|
15790
16586
|
};
|
|
15791
16587
|
|
|
15792
|
-
// src/internal/memory/circuit-breaker.ts
|
|
15793
|
-
var DEFAULT_MAX_TIMEOUTS = 3;
|
|
15794
|
-
var DEFAULT_COOLDOWN_MS2 = 6e4;
|
|
15795
|
-
var CircuitBreaker = class {
|
|
15796
|
-
constructor(opts = {}) {
|
|
15797
|
-
this.opts = opts;
|
|
15798
|
-
}
|
|
15799
|
-
opts;
|
|
15800
|
-
states = /* @__PURE__ */ new Map();
|
|
15801
|
-
/** @returns true when the breaker is open and the call should be skipped. */
|
|
15802
|
-
shouldSkip(key2) {
|
|
15803
|
-
const state4 = this.states.get(key2);
|
|
15804
|
-
if (state4 === void 0) return false;
|
|
15805
|
-
if (state4.cooldownUntilMs === 0) return false;
|
|
15806
|
-
if (this.now() < state4.cooldownUntilMs) return true;
|
|
15807
|
-
state4.cooldownUntilMs = 0;
|
|
15808
|
-
state4.consecutiveTimeouts = 0;
|
|
15809
|
-
return false;
|
|
15810
|
-
}
|
|
15811
|
-
recordSuccess(key2) {
|
|
15812
|
-
const state4 = this.states.get(key2);
|
|
15813
|
-
if (state4 === void 0) return;
|
|
15814
|
-
state4.consecutiveTimeouts = 0;
|
|
15815
|
-
state4.cooldownUntilMs = 0;
|
|
15816
|
-
}
|
|
15817
|
-
recordTimeout(key2) {
|
|
15818
|
-
const state4 = this.states.get(key2) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
15819
|
-
state4.consecutiveTimeouts += 1;
|
|
15820
|
-
if (state4.consecutiveTimeouts >= (this.opts.maxTimeouts ?? DEFAULT_MAX_TIMEOUTS)) {
|
|
15821
|
-
state4.cooldownUntilMs = this.now() + (this.opts.cooldownMs ?? DEFAULT_COOLDOWN_MS2);
|
|
15822
|
-
}
|
|
15823
|
-
this.states.set(key2, state4);
|
|
15824
|
-
}
|
|
15825
|
-
/** @internal — tests inspect counter state. */
|
|
15826
|
-
inspect(key2) {
|
|
15827
|
-
return this.states.get(key2) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
15828
|
-
}
|
|
15829
|
-
now() {
|
|
15830
|
-
return this.opts.now?.() ?? Date.now();
|
|
15831
|
-
}
|
|
15832
|
-
};
|
|
15833
|
-
|
|
15834
16588
|
// src/internal/runtime/local-agent/local-agent-memory.ts
|
|
15835
16589
|
init_index_manager();
|
|
15836
16590
|
|
|
@@ -18419,13 +19173,13 @@ function defineTool(spec) {
|
|
|
18419
19173
|
name: spec.name,
|
|
18420
19174
|
description: spec.description,
|
|
18421
19175
|
inputSchema,
|
|
18422
|
-
handler: async (input) => {
|
|
19176
|
+
handler: async (input, ctx) => {
|
|
18423
19177
|
const raw = spec.sanitize ? sanitizeToolInput(input, {
|
|
18424
19178
|
...spec.sanitize === true ? {} : spec.sanitize,
|
|
18425
19179
|
schema: spec.inputSchema
|
|
18426
19180
|
}).value : input;
|
|
18427
19181
|
const parsed = spec.inputSchema.parse(raw);
|
|
18428
|
-
return await spec.handler(parsed);
|
|
19182
|
+
return await spec.handler(parsed, ctx);
|
|
18429
19183
|
}
|
|
18430
19184
|
};
|
|
18431
19185
|
}
|
|
@@ -18436,6 +19190,14 @@ init_errors();
|
|
|
18436
19190
|
// src/event-bus.ts
|
|
18437
19191
|
var EventBus = class {
|
|
18438
19192
|
handlers = /* @__PURE__ */ new Map();
|
|
19193
|
+
// M3 #64 — a swallowed handler error used to vanish without a trace (fail-loud
|
|
19194
|
+
// violation). We now log it AND expose an observable count so ops/tests can see
|
|
19195
|
+
// that a subscriber is silently failing, without breaking the EC-2 contract.
|
|
19196
|
+
#handlerErrorCount = 0;
|
|
19197
|
+
/** M3 #64 — number of handler invocations that threw (and were logged). */
|
|
19198
|
+
get handlerErrorCount() {
|
|
19199
|
+
return this.#handlerErrorCount;
|
|
19200
|
+
}
|
|
18439
19201
|
/**
|
|
18440
19202
|
* Subscribe to an event. Returns an unsubscribe function.
|
|
18441
19203
|
*/
|
|
@@ -18458,7 +19220,13 @@ var EventBus = class {
|
|
|
18458
19220
|
for (const handler of set) {
|
|
18459
19221
|
try {
|
|
18460
19222
|
handler(payload);
|
|
18461
|
-
} catch {
|
|
19223
|
+
} catch (cause) {
|
|
19224
|
+
this.#handlerErrorCount += 1;
|
|
19225
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
19226
|
+
process.stderr.write(
|
|
19227
|
+
`[theokit-sdk] event-bus: handler for "${String(event)}" threw: ${message}
|
|
19228
|
+
`
|
|
19229
|
+
);
|
|
18462
19230
|
}
|
|
18463
19231
|
}
|
|
18464
19232
|
}
|
|
@@ -18481,11 +19249,17 @@ init_generate_object();
|
|
|
18481
19249
|
// src/internal/persistence/conversation-storage-memory.ts
|
|
18482
19250
|
var InMemoryConversationStorage = class {
|
|
18483
19251
|
#store = /* @__PURE__ */ new Map();
|
|
18484
|
-
async getMessages(conversationId) {
|
|
19252
|
+
async getMessages(conversationId, opts) {
|
|
18485
19253
|
const existing = this.#store.get(conversationId);
|
|
18486
|
-
return existing === void 0 ? [] : existing.slice();
|
|
19254
|
+
return existing === void 0 ? [] : paginate(existing.slice(), opts);
|
|
18487
19255
|
}
|
|
18488
19256
|
async appendMessage(conversationId, message) {
|
|
19257
|
+
this.#appendOne(conversationId, message);
|
|
19258
|
+
}
|
|
19259
|
+
async appendMessages(conversationId, messages) {
|
|
19260
|
+
for (const message of messages) this.#appendOne(conversationId, message);
|
|
19261
|
+
}
|
|
19262
|
+
#appendOne(conversationId, message) {
|
|
18489
19263
|
const existing = this.#store.get(conversationId);
|
|
18490
19264
|
const stamped = message.at === void 0 ? { ...message, at: Date.now() } : message;
|
|
18491
19265
|
if (existing === void 0) {
|
|
@@ -18494,9 +19268,26 @@ var InMemoryConversationStorage = class {
|
|
|
18494
19268
|
}
|
|
18495
19269
|
existing.push(stamped);
|
|
18496
19270
|
}
|
|
19271
|
+
async truncateConversation(conversationId, keepCount) {
|
|
19272
|
+
const existing = this.#store.get(conversationId);
|
|
19273
|
+
if (existing === void 0) return 0;
|
|
19274
|
+
const keep = Math.max(0, Math.min(keepCount, existing.length));
|
|
19275
|
+
this.#store.set(conversationId, existing.slice(0, keep));
|
|
19276
|
+
return keep;
|
|
19277
|
+
}
|
|
18497
19278
|
async deleteConversation(conversationId) {
|
|
18498
19279
|
this.#store.delete(conversationId);
|
|
18499
19280
|
}
|
|
19281
|
+
async deleteScope(prefix) {
|
|
19282
|
+
let n = 0;
|
|
19283
|
+
for (const id of [...this.#store.keys()]) {
|
|
19284
|
+
if (id.startsWith(prefix)) {
|
|
19285
|
+
this.#store.delete(id);
|
|
19286
|
+
n += 1;
|
|
19287
|
+
}
|
|
19288
|
+
}
|
|
19289
|
+
return n;
|
|
19290
|
+
}
|
|
18500
19291
|
async listConversationIds(opts) {
|
|
18501
19292
|
const all = Array.from(this.#store.keys());
|
|
18502
19293
|
if (opts?.limit !== void 0) return all.slice(0, opts.limit);
|
|
@@ -18674,23 +19465,40 @@ function createNoopMemoryProvider() {
|
|
|
18674
19465
|
}
|
|
18675
19466
|
var JobQueue = class {
|
|
18676
19467
|
jobs = /* @__PURE__ */ new Map();
|
|
19468
|
+
controllers = /* @__PURE__ */ new Map();
|
|
19469
|
+
maxConcurrency;
|
|
19470
|
+
running = 0;
|
|
19471
|
+
waiting = [];
|
|
19472
|
+
constructor(options = {}) {
|
|
19473
|
+
this.maxConcurrency = options.maxConcurrency === void 0 ? Number.POSITIVE_INFINITY : Math.max(1, options.maxConcurrency);
|
|
19474
|
+
}
|
|
18677
19475
|
/**
|
|
18678
|
-
* Enqueue a background function. Returns the job ID immediately.
|
|
18679
|
-
*
|
|
19476
|
+
* Enqueue a background function. Returns the job ID immediately. The function
|
|
19477
|
+
* receives an `AbortSignal` that fires when the job is cancelled (#58) — a
|
|
19478
|
+
* cooperative job should observe it to stop early. Existing `() => Promise<T>`
|
|
19479
|
+
* callers are unaffected (the signal argument is simply ignored).
|
|
18680
19480
|
*/
|
|
18681
19481
|
enqueue(fn) {
|
|
18682
19482
|
const id = crypto.randomUUID();
|
|
18683
19483
|
const job = { id, status: "pending" };
|
|
18684
19484
|
this.jobs.set(id, job);
|
|
18685
|
-
|
|
18686
|
-
|
|
18687
|
-
|
|
18688
|
-
job.
|
|
18689
|
-
|
|
18690
|
-
|
|
18691
|
-
|
|
18692
|
-
job.
|
|
18693
|
-
|
|
19485
|
+
const controller = new AbortController();
|
|
19486
|
+
this.controllers.set(id, controller);
|
|
19487
|
+
void this.#acquire().then(() => {
|
|
19488
|
+
if (job.status === "cancelled") {
|
|
19489
|
+
this.#release(id);
|
|
19490
|
+
return;
|
|
19491
|
+
}
|
|
19492
|
+
job.status = "running";
|
|
19493
|
+
Promise.resolve().then(() => fn(controller.signal)).then((result) => {
|
|
19494
|
+
if (job.status === "cancelled") return;
|
|
19495
|
+
job.result = result;
|
|
19496
|
+
job.status = "completed";
|
|
19497
|
+
}).catch((err) => {
|
|
19498
|
+
if (job.status === "cancelled") return;
|
|
19499
|
+
job.error = err instanceof Error ? err.message : String(err);
|
|
19500
|
+
job.status = "failed";
|
|
19501
|
+
}).finally(() => this.#release(id));
|
|
18694
19502
|
});
|
|
18695
19503
|
return id;
|
|
18696
19504
|
}
|
|
@@ -18701,17 +19509,39 @@ var JobQueue = class {
|
|
|
18701
19509
|
return [...this.jobs.values()];
|
|
18702
19510
|
}
|
|
18703
19511
|
/**
|
|
18704
|
-
* Cancel a pending or running job. Returns true if cancelled.
|
|
19512
|
+
* Cancel a pending or running job. Returns true if cancelled. #58 — aborts the
|
|
19513
|
+
* job's `AbortSignal` so a cooperative running job is actually interrupted.
|
|
18705
19514
|
*/
|
|
18706
19515
|
cancel(id) {
|
|
18707
19516
|
const job = this.jobs.get(id);
|
|
18708
19517
|
if (!job) return false;
|
|
18709
19518
|
if (job.status === "pending" || job.status === "running") {
|
|
18710
19519
|
job.status = "cancelled";
|
|
19520
|
+
this.controllers.get(id)?.abort();
|
|
18711
19521
|
return true;
|
|
18712
19522
|
}
|
|
18713
19523
|
return false;
|
|
18714
19524
|
}
|
|
19525
|
+
/** Acquire a concurrency slot (resolves immediately when unbounded/free). */
|
|
19526
|
+
#acquire() {
|
|
19527
|
+
if (this.running < this.maxConcurrency) {
|
|
19528
|
+
this.running += 1;
|
|
19529
|
+
return Promise.resolve();
|
|
19530
|
+
}
|
|
19531
|
+
return new Promise((resolve3) => {
|
|
19532
|
+
this.waiting.push(() => {
|
|
19533
|
+
this.running += 1;
|
|
19534
|
+
resolve3();
|
|
19535
|
+
});
|
|
19536
|
+
});
|
|
19537
|
+
}
|
|
19538
|
+
/** Release a slot + clean up the controller; start the next waiting job. */
|
|
19539
|
+
#release(id) {
|
|
19540
|
+
this.controllers.delete(id);
|
|
19541
|
+
this.running -= 1;
|
|
19542
|
+
const next = this.waiting.shift();
|
|
19543
|
+
if (next !== void 0) next();
|
|
19544
|
+
}
|
|
18715
19545
|
};
|
|
18716
19546
|
|
|
18717
19547
|
// src/internal/memory/dreaming/run.ts
|
|
@@ -19204,24 +20034,42 @@ async function migrateSqliteToLance2(options) {
|
|
|
19204
20034
|
}
|
|
19205
20035
|
|
|
19206
20036
|
// src/permission-engine.ts
|
|
20037
|
+
function argMatches(matcher, value) {
|
|
20038
|
+
if (typeof matcher === "function") return matcher(value);
|
|
20039
|
+
if (value === void 0) return false;
|
|
20040
|
+
if (matcher instanceof RegExp) return matcher.test(String(value));
|
|
20041
|
+
return matcher === value;
|
|
20042
|
+
}
|
|
19207
20043
|
var PermissionEngine = class {
|
|
19208
20044
|
constructor(rules, options = {}) {
|
|
19209
20045
|
this.rules = rules;
|
|
19210
|
-
this.defaultAction = options.defaultAction ?? "
|
|
20046
|
+
this.defaultAction = options.defaultAction ?? "ask";
|
|
19211
20047
|
}
|
|
19212
20048
|
rules;
|
|
19213
20049
|
defaultAction;
|
|
19214
20050
|
/**
|
|
19215
|
-
* Evaluate a tool name against the rules. First
|
|
19216
|
-
* configured `defaultAction` (default `"
|
|
20051
|
+
* Evaluate a tool name (and optional arguments) against the rules. First
|
|
20052
|
+
* match wins; falls back to the configured `defaultAction` (default `"ask"`,
|
|
20053
|
+
* fail-closed) when no rule matches. #55 — a rule with `args` gates on the
|
|
20054
|
+
* argument values, so the same tool name can resolve to different actions
|
|
20055
|
+
* depending on what it is asked to do.
|
|
19217
20056
|
*/
|
|
19218
|
-
evaluate(toolName) {
|
|
20057
|
+
evaluate(toolName, args) {
|
|
19219
20058
|
for (const rule of this.rules) {
|
|
19220
|
-
const
|
|
19221
|
-
if (
|
|
20059
|
+
const nameMatches = typeof rule.tool === "string" ? rule.tool === toolName : rule.tool.test(toolName);
|
|
20060
|
+
if (!nameMatches) continue;
|
|
20061
|
+
if (rule.args !== void 0 && !this.#argsMatch(rule.args, args)) continue;
|
|
20062
|
+
return rule.action;
|
|
19222
20063
|
}
|
|
19223
20064
|
return this.defaultAction;
|
|
19224
20065
|
}
|
|
20066
|
+
#argsMatch(matchers, args) {
|
|
20067
|
+
const call = args ?? {};
|
|
20068
|
+
for (const [key2, matcher] of Object.entries(matchers)) {
|
|
20069
|
+
if (!argMatches(matcher, call[key2])) return false;
|
|
20070
|
+
}
|
|
20071
|
+
return true;
|
|
20072
|
+
}
|
|
19225
20073
|
};
|
|
19226
20074
|
|
|
19227
20075
|
// src/permission-plugin.ts
|
|
@@ -19232,8 +20080,8 @@ function createPermissionPlugin(engine, opts = {}) {
|
|
|
19232
20080
|
kind: "general",
|
|
19233
20081
|
register(ctx) {
|
|
19234
20082
|
ctx.on("pre_tool_call", (rawCtx) => {
|
|
19235
|
-
const { name } = rawCtx;
|
|
19236
|
-
const action = engine.evaluate(name);
|
|
20083
|
+
const { name, args } = rawCtx;
|
|
20084
|
+
const action = engine.evaluate(name, args);
|
|
19237
20085
|
if (action === "deny") {
|
|
19238
20086
|
return { block: true, message: `denied by permission engine: ${name}` };
|
|
19239
20087
|
}
|
|
@@ -19298,6 +20146,14 @@ var Security = class {
|
|
|
19298
20146
|
}
|
|
19299
20147
|
};
|
|
19300
20148
|
|
|
20149
|
+
// src/session-scope.ts
|
|
20150
|
+
function scopedConversationId(scope, id) {
|
|
20151
|
+
return `${scope}__${id}`;
|
|
20152
|
+
}
|
|
20153
|
+
function sessionScopePrefix(scope) {
|
|
20154
|
+
return `${scope}__`;
|
|
20155
|
+
}
|
|
20156
|
+
|
|
19301
20157
|
// src/squad.ts
|
|
19302
20158
|
init_errors();
|
|
19303
20159
|
var PersistenceSchema = zod.z.object({
|
|
@@ -20048,6 +20904,8 @@ exports.migrateSqliteToLance = migrateSqliteToLance2;
|
|
|
20048
20904
|
exports.mkMemoryId = mkMemoryId;
|
|
20049
20905
|
exports.normalizeUsage = normalizeUsage;
|
|
20050
20906
|
exports.preflightCheck = preflightCheck;
|
|
20907
|
+
exports.scopedConversationId = scopedConversationId;
|
|
20908
|
+
exports.sessionScopePrefix = sessionScopePrefix;
|
|
20051
20909
|
exports.toShareGptTrajectory = toShareGptTrajectory;
|
|
20052
20910
|
exports.withCwdMutex = withCwdMutex;
|
|
20053
20911
|
//# sourceMappingURL=index.cjs.map
|