@theokit/sdk 2.15.2 → 2.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -0
- package/dist/a2a/index.cjs +875 -198
- package/dist/a2a/index.cjs.map +1 -1
- package/dist/a2a/index.js +876 -199
- package/dist/a2a/index.js.map +1 -1
- package/dist/{cron-BxLSz1UH.d.cts → cron-Bbg0mBOv.d.ts} +33 -3
- package/dist/{cron-DcaoP7aW.d.ts → cron-ZLSKbDbB.d.cts} +33 -3
- package/dist/cron.cjs +840 -187
- package/dist/cron.cjs.map +1 -1
- package/dist/cron.d.cts +2 -2
- package/dist/cron.d.ts +2 -2
- package/dist/cron.js +840 -187
- package/dist/cron.js.map +1 -1
- package/dist/define-tool.d.ts +9 -2
- package/dist/{errors-Bart0ptP.d.cts → errors-1tVcX3Fq.d.cts} +1 -1
- package/dist/{errors-DJuuubJK.d.ts → errors-qyVYfk9H.d.ts} +1 -1
- package/dist/errors.d.cts +2 -2
- package/dist/eval.cjs +846 -189
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +846 -189
- package/dist/eval.js.map +1 -1
- package/dist/event-bus.d.ts +3 -0
- package/dist/index.cjs +977 -215
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -27
- package/dist/index.d.ts +122 -27
- package/dist/index.js +977 -217
- package/dist/index.js.map +1 -1
- package/dist/internal/agent-loop/tool-dispatch.d.ts +3 -1
- package/dist/internal/agent-loop/tool-result-guard.d.ts +24 -0
- package/dist/internal/agent-loop/tool-timeout.d.ts +23 -0
- package/dist/internal/llm/openai.d.ts +4 -0
- package/dist/internal/llm/sse.d.ts +13 -1
- package/dist/internal/mcp/client.d.ts +1 -1
- package/dist/internal/memory/active-memory.d.ts +1 -1
- package/dist/internal/persistence/conversation-storage-fs.d.cts +7 -1
- package/dist/internal/persistence/conversation-storage-fs.d.ts +7 -1
- package/dist/internal/persistence/conversation-storage-memory.d.cts +7 -1
- package/dist/internal/persistence/conversation-storage-memory.d.ts +7 -1
- package/dist/internal/persistence/pagination.d.cts +8 -0
- package/dist/internal/persistence/pagination.d.ts +8 -0
- package/dist/internal/plugins/index.cjs +135 -0
- package/dist/internal/plugins/index.cjs.map +1 -1
- package/dist/internal/plugins/index.js +135 -0
- package/dist/internal/plugins/index.js.map +1 -1
- package/dist/internal/plugins/manager.d.cts +21 -1
- package/dist/internal/plugins/manager.d.ts +21 -1
- package/dist/internal/plugins/types.d.cts +40 -0
- package/dist/internal/plugins/types.d.ts +40 -0
- package/dist/internal/{memory → resilience}/circuit-breaker.d.ts +5 -1
- package/dist/internal/runtime/hooks/hooks-frontmatter.d.ts +1 -1
- package/dist/internal/runtime/lifecycle/env-policy.d.ts +30 -0
- package/dist/internal/runtime/session/agent-session-store.d.ts +1 -0
- package/dist/internal/telemetry/span-names.d.ts +7 -1
- package/dist/job-queue.d.ts +29 -7
- package/dist/permission-engine.d.ts +32 -7
- package/dist/{run-DXy_MVwz.d.cts → run-pE-34AAo.d.cts} +64 -3
- package/dist/{run-DXy_MVwz.d.ts → run-pE-34AAo.d.ts} +64 -3
- package/dist/sandbox/index.cjs +53 -2
- package/dist/sandbox/index.cjs.map +1 -1
- package/dist/sandbox/index.js +53 -2
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/local-sandbox.d.cts +11 -3
- package/dist/sandbox/local-sandbox.d.ts +11 -3
- package/dist/sandbox/types.d.cts +7 -0
- package/dist/sandbox/types.d.ts +7 -0
- package/dist/types/agent-prims.d.ts +6 -2
- package/dist/types/conversation-storage.d.ts +32 -2
- package/dist/types/mcp.d.ts +20 -0
- package/dist/types/run.d.ts +17 -0
- package/dist/workflow.cjs +6 -3
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.js +6 -3
- package/dist/workflow.js.map +1 -1
- package/package.json +14 -14
package/dist/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) {
|
|
@@ -13831,8 +14292,12 @@ var OpenAIClient = class {
|
|
|
13831
14292
|
// model was actually given. Empty set (no tools) recovers nothing.
|
|
13832
14293
|
new Set(request.tools?.map((tool) => tool.name) ?? [])
|
|
13833
14294
|
);
|
|
14295
|
+
let sawDone = false;
|
|
13834
14296
|
for await (const record of parseSseStream(response.body, signal)) {
|
|
13835
|
-
if (record.data === "[DONE]")
|
|
14297
|
+
if (record.data === "[DONE]") {
|
|
14298
|
+
sawDone = true;
|
|
14299
|
+
break;
|
|
14300
|
+
}
|
|
13836
14301
|
let chunk;
|
|
13837
14302
|
try {
|
|
13838
14303
|
chunk = JSON.parse(record.data);
|
|
@@ -13852,6 +14317,11 @@ var OpenAIClient = class {
|
|
|
13852
14317
|
const events = accumulator.consume(chunk);
|
|
13853
14318
|
for (const event of events) yield event;
|
|
13854
14319
|
}
|
|
14320
|
+
if (!sawDone && !accumulator.finishReasonSeen) {
|
|
14321
|
+
throw new exports.NetworkError("SSE stream truncated (no finish_reason / [DONE])", {
|
|
14322
|
+
code: "stream_truncated"
|
|
14323
|
+
});
|
|
14324
|
+
}
|
|
13855
14325
|
const drainEvent = accumulator.finalizeHeldText();
|
|
13856
14326
|
if (drainEvent !== void 0) yield drainEvent;
|
|
13857
14327
|
return accumulator.finish();
|
|
@@ -13951,8 +14421,15 @@ var OpenAIStreamAccumulator = class {
|
|
|
13951
14421
|
this.toolCalls.set(call.index, existing);
|
|
13952
14422
|
}
|
|
13953
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
|
+
}
|
|
13954
14430
|
applyFinishReason(reason) {
|
|
13955
14431
|
if (reason === void 0 || reason === null) return;
|
|
14432
|
+
this.sawFinishReason = true;
|
|
13956
14433
|
this.stopReason = mapOpenAIFinish(reason);
|
|
13957
14434
|
}
|
|
13958
14435
|
finish() {
|
|
@@ -13997,18 +14474,6 @@ var OpenAIStreamAccumulator = class {
|
|
|
13997
14474
|
});
|
|
13998
14475
|
}
|
|
13999
14476
|
};
|
|
14000
|
-
function mapOpenAIFinish(reason) {
|
|
14001
|
-
switch (reason) {
|
|
14002
|
-
case "tool_calls":
|
|
14003
|
-
return "tool_use";
|
|
14004
|
-
case "length":
|
|
14005
|
-
return "max_tokens";
|
|
14006
|
-
case "stop":
|
|
14007
|
-
return "end_turn";
|
|
14008
|
-
default:
|
|
14009
|
-
return "end_turn";
|
|
14010
|
-
}
|
|
14011
|
-
}
|
|
14012
14477
|
function applyReasoningRequest(body, effort, providerName) {
|
|
14013
14478
|
if (providerName === "openai") {
|
|
14014
14479
|
body.reasoning_effort = effort;
|
|
@@ -14106,19 +14571,76 @@ function assistantMessage(message) {
|
|
|
14106
14571
|
|
|
14107
14572
|
// src/internal/llm/pool-aware-client.ts
|
|
14108
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();
|
|
14109
14619
|
var PoolAwareLlmClient = class {
|
|
14110
|
-
constructor(pool, buildClient2, waitForAvailableMs = 3e4) {
|
|
14620
|
+
constructor(pool, buildClient2, waitForAvailableMs = 3e4, resilience = {}) {
|
|
14111
14621
|
this.pool = pool;
|
|
14112
14622
|
this.buildClient = buildClient2;
|
|
14113
14623
|
this.waitForAvailableMs = waitForAvailableMs;
|
|
14114
14624
|
this.name = `pool-aware:${pool.provider}`;
|
|
14625
|
+
this.breaker = resilience.breaker ?? new CircuitBreaker();
|
|
14626
|
+
this.backoffBaseMs = resilience.backoffBaseMs;
|
|
14627
|
+
this.rng = resilience.rng;
|
|
14115
14628
|
}
|
|
14116
14629
|
pool;
|
|
14117
14630
|
buildClient;
|
|
14118
14631
|
waitForAvailableMs;
|
|
14119
14632
|
name;
|
|
14633
|
+
/** M2 #60 — provider-level circuit breaker (consecutive-failure). */
|
|
14634
|
+
breaker;
|
|
14635
|
+
backoffBaseMs;
|
|
14636
|
+
rng;
|
|
14120
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.
|
|
14121
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
|
+
}
|
|
14122
14644
|
let hasRetried429 = false;
|
|
14123
14645
|
while (true) {
|
|
14124
14646
|
if (signal.aborted) throw abortError2(signal);
|
|
@@ -14133,6 +14655,7 @@ var PoolAwareLlmClient = class {
|
|
|
14133
14655
|
}
|
|
14134
14656
|
}
|
|
14135
14657
|
if (entry === null) {
|
|
14658
|
+
this.breaker.recordTimeout(this.pool.provider);
|
|
14136
14659
|
throw new CredentialPoolExhaustedError(
|
|
14137
14660
|
`All ${this.pool.provider} credentials exhausted; next retry available at ${this.nextRetryHint() ?? "unknown"}`,
|
|
14138
14661
|
{ provider: this.pool.provider, nextRetryAt: this.nextRetryHint() }
|
|
@@ -14142,10 +14665,19 @@ var PoolAwareLlmClient = class {
|
|
|
14142
14665
|
const realClient = this.buildClient(entry.accessToken);
|
|
14143
14666
|
const attempt = await tryFirstEvent(realClient, request, signal);
|
|
14144
14667
|
if (attempt.kind === "ok") {
|
|
14668
|
+
this.breaker.recordSuccess(this.pool.provider);
|
|
14145
14669
|
return yield* relayStream(attempt.generator, attempt.firstResult);
|
|
14146
14670
|
}
|
|
14147
14671
|
const decision = classifyAndDecide(attempt.error, hasRetried429);
|
|
14148
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
|
+
);
|
|
14149
14681
|
hasRetried429 = true;
|
|
14150
14682
|
continue;
|
|
14151
14683
|
}
|
|
@@ -14165,6 +14697,7 @@ var PoolAwareLlmClient = class {
|
|
|
14165
14697
|
hasRetried429 = false;
|
|
14166
14698
|
continue;
|
|
14167
14699
|
}
|
|
14700
|
+
this.breaker.recordTimeout(this.pool.provider);
|
|
14168
14701
|
throw attempt.error;
|
|
14169
14702
|
}
|
|
14170
14703
|
}
|
|
@@ -14600,9 +15133,28 @@ function selectTransport(profile, apiKey) {
|
|
|
14600
15133
|
// src/internal/mcp/client.ts
|
|
14601
15134
|
init_errors();
|
|
14602
15135
|
init_path_guard();
|
|
14603
|
-
function createMcpClient(name, config) {
|
|
15136
|
+
function createMcpClient(name, config, fetchImpl = fetch) {
|
|
14604
15137
|
if (isStdio(config)) return new StdioMcpClient(name, config);
|
|
14605
|
-
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";
|
|
14606
15158
|
}
|
|
14607
15159
|
async function rpcInitialize(request) {
|
|
14608
15160
|
await request("initialize", {
|
|
@@ -14648,32 +15200,107 @@ var StdioMcpClient = class extends BaseMcpClient {
|
|
|
14648
15200
|
name;
|
|
14649
15201
|
child;
|
|
14650
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.
|
|
14651
15205
|
pending = /* @__PURE__ */ new Map();
|
|
14652
15206
|
buffer = "";
|
|
14653
|
-
|
|
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() {
|
|
14654
15221
|
const resolvedCwd = resolveMcpCwd(this.config.cwd);
|
|
14655
15222
|
const child = child_process.spawn(this.config.command, this.config.args ?? [], {
|
|
14656
15223
|
cwd: resolvedCwd,
|
|
14657
|
-
|
|
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 })
|
|
14658
15227
|
});
|
|
14659
15228
|
this.child = child;
|
|
14660
15229
|
child.stdout.on("data", (chunk) => this.consume(chunk));
|
|
14661
15230
|
child.stderr.on("data", () => void 0);
|
|
15231
|
+
child.stdin.on("error", () => void 0);
|
|
14662
15232
|
child.on("error", () => {
|
|
14663
|
-
|
|
14664
|
-
|
|
14665
|
-
|
|
14666
|
-
|
|
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
|
+
);
|
|
14667
15244
|
});
|
|
15245
|
+
}
|
|
15246
|
+
async initialize() {
|
|
15247
|
+
this.spawnChild();
|
|
14668
15248
|
await super.initialize();
|
|
14669
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
|
+
}
|
|
14670
15276
|
async close() {
|
|
14671
|
-
|
|
14672
|
-
this.child
|
|
15277
|
+
this.rejectAllPending(new exports.NetworkError(`MCP ${this.name} closed`, { code: "mcp_closed" }));
|
|
15278
|
+
const child = this.child;
|
|
14673
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();
|
|
14674
15290
|
}
|
|
14675
15291
|
consume(chunk) {
|
|
14676
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
|
+
}
|
|
14677
15304
|
let newlineIndex = this.buffer.indexOf("\n");
|
|
14678
15305
|
while (newlineIndex !== -1) {
|
|
14679
15306
|
const line = this.buffer.slice(0, newlineIndex).trim();
|
|
@@ -14690,23 +15317,47 @@ var StdioMcpClient = class extends BaseMcpClient {
|
|
|
14690
15317
|
return;
|
|
14691
15318
|
}
|
|
14692
15319
|
if (typeof message.id !== "number") return;
|
|
14693
|
-
const
|
|
14694
|
-
if (
|
|
15320
|
+
const entry = this.pending.get(message.id);
|
|
15321
|
+
if (entry === void 0) return;
|
|
14695
15322
|
this.pending.delete(message.id);
|
|
14696
|
-
|
|
15323
|
+
clearTimeout(entry.timer);
|
|
15324
|
+
entry.resolve(message);
|
|
14697
15325
|
}
|
|
14698
15326
|
request(method, params) {
|
|
14699
|
-
|
|
14700
|
-
|
|
14701
|
-
|
|
14702
|
-
|
|
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" });
|
|
14703
15341
|
}
|
|
15342
|
+
return this.send(child, method, params);
|
|
15343
|
+
}
|
|
15344
|
+
send(child, method, params) {
|
|
14704
15345
|
const id = this.nextId++;
|
|
14705
15346
|
const payload = { jsonrpc: "2.0", id, method, params };
|
|
14706
|
-
|
|
15347
|
+
child.stdin.write(`${JSON.stringify(payload)}
|
|
14707
15348
|
`);
|
|
14708
|
-
return new Promise((resolve3) => {
|
|
14709
|
-
|
|
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 });
|
|
14710
15361
|
});
|
|
14711
15362
|
}
|
|
14712
15363
|
};
|
|
@@ -14732,11 +15383,20 @@ var HttpMcpClient = class extends BaseMcpClient {
|
|
|
14732
15383
|
accept: "application/json",
|
|
14733
15384
|
...this.config.headers ?? {}
|
|
14734
15385
|
};
|
|
14735
|
-
const
|
|
14736
|
-
|
|
14737
|
-
|
|
14738
|
-
|
|
14739
|
-
|
|
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
|
+
}
|
|
14740
15400
|
if (!response.ok) {
|
|
14741
15401
|
throw new exports.NetworkError(`MCP ${this.name} returned ${response.status}`, {
|
|
14742
15402
|
code: "mcp_http_error"
|
|
@@ -14851,11 +15511,33 @@ function resolveRunProvider(options) {
|
|
|
14851
15511
|
);
|
|
14852
15512
|
}
|
|
14853
15513
|
const parsedModel = parseModelId(options.model?.id);
|
|
14854
|
-
const
|
|
14855
|
-
const
|
|
14856
|
-
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";
|
|
14857
15518
|
return { primary, effectiveModelId };
|
|
14858
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
|
+
}
|
|
14859
15541
|
function buildLoopInputs(options, runId, userText) {
|
|
14860
15542
|
const maxIterations = options.sendOptions.maxIterations;
|
|
14861
15543
|
if (maxIterations !== void 0 && (!Number.isInteger(maxIterations) || maxIterations < 1)) {
|
|
@@ -14866,7 +15548,11 @@ function buildLoopInputs(options, runId, userText) {
|
|
|
14866
15548
|
}
|
|
14867
15549
|
const { primary, effectiveModelId } = resolveRunProvider(options);
|
|
14868
15550
|
const fallback = options.agentOptions.providers?.fallback;
|
|
14869
|
-
const apiKeys =
|
|
15551
|
+
const apiKeys = mergeExplicitApiKey(
|
|
15552
|
+
options.agentOptions.providers?.apiKeys,
|
|
15553
|
+
primary,
|
|
15554
|
+
options.agentOptions.apiKey
|
|
15555
|
+
);
|
|
14870
15556
|
const credentialPoolStrategy = options.agentOptions.providers?.credentialPoolStrategy;
|
|
14871
15557
|
const extractToolCallsFromContent = options.agentOptions.providers?.routes?.[0]?.extractToolCallsFromContent;
|
|
14872
15558
|
const chain = resolveProviderChain({
|
|
@@ -14910,6 +15596,10 @@ function buildLoopInputs(options, runId, userText) {
|
|
|
14910
15596
|
// D318 — forward SendOptions.signal to the agent loop so streamLlmTurn
|
|
14911
15597
|
// can attach it to the LLM `fetch({ signal })` call.
|
|
14912
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 } : {},
|
|
14913
15603
|
// M1-2: per-send iteration ceiling (validated above). The loop reads
|
|
14914
15604
|
// inputs.maxIterations (default 8 when unset).
|
|
14915
15605
|
...maxIterations !== void 0 ? { maxIterations } : {},
|
|
@@ -15227,7 +15917,12 @@ async function runActiveMemory(args) {
|
|
|
15227
15917
|
hits: []
|
|
15228
15918
|
});
|
|
15229
15919
|
}
|
|
15230
|
-
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);
|
|
15231
15926
|
if (cached2 !== void 0) return endRecallSpan(span, args, cached2);
|
|
15232
15927
|
const query = buildQuery(args.userText, args.priorMessages, cfg.queryMode, cfg.recentUserTurns);
|
|
15233
15928
|
if (query.trim().length === 0) {
|
|
@@ -15312,7 +16007,12 @@ function notifyBreaker(breaker, key2, status) {
|
|
|
15312
16007
|
else if (status === "ok" || status === "no-recall") breaker.recordSuccess(key2);
|
|
15313
16008
|
}
|
|
15314
16009
|
async function finalize(args, queryMode, result) {
|
|
15315
|
-
|
|
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);
|
|
15316
16016
|
if (args.persistTranscripts === true && args.cwd !== void 0) {
|
|
15317
16017
|
await persistActiveMemoryTranscript(args.cwd, {
|
|
15318
16018
|
runId: args.runId ?? `run-${Date.now()}`,
|
|
@@ -15885,48 +16585,6 @@ var MEMORY_EMBEDDING_ADAPTERS = {
|
|
|
15885
16585
|
gemini: geminiMemoryEmbeddingProviderAdapter
|
|
15886
16586
|
};
|
|
15887
16587
|
|
|
15888
|
-
// src/internal/memory/circuit-breaker.ts
|
|
15889
|
-
var DEFAULT_MAX_TIMEOUTS = 3;
|
|
15890
|
-
var DEFAULT_COOLDOWN_MS2 = 6e4;
|
|
15891
|
-
var CircuitBreaker = class {
|
|
15892
|
-
constructor(opts = {}) {
|
|
15893
|
-
this.opts = opts;
|
|
15894
|
-
}
|
|
15895
|
-
opts;
|
|
15896
|
-
states = /* @__PURE__ */ new Map();
|
|
15897
|
-
/** @returns true when the breaker is open and the call should be skipped. */
|
|
15898
|
-
shouldSkip(key2) {
|
|
15899
|
-
const state4 = this.states.get(key2);
|
|
15900
|
-
if (state4 === void 0) return false;
|
|
15901
|
-
if (state4.cooldownUntilMs === 0) return false;
|
|
15902
|
-
if (this.now() < state4.cooldownUntilMs) return true;
|
|
15903
|
-
state4.cooldownUntilMs = 0;
|
|
15904
|
-
state4.consecutiveTimeouts = 0;
|
|
15905
|
-
return false;
|
|
15906
|
-
}
|
|
15907
|
-
recordSuccess(key2) {
|
|
15908
|
-
const state4 = this.states.get(key2);
|
|
15909
|
-
if (state4 === void 0) return;
|
|
15910
|
-
state4.consecutiveTimeouts = 0;
|
|
15911
|
-
state4.cooldownUntilMs = 0;
|
|
15912
|
-
}
|
|
15913
|
-
recordTimeout(key2) {
|
|
15914
|
-
const state4 = this.states.get(key2) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
15915
|
-
state4.consecutiveTimeouts += 1;
|
|
15916
|
-
if (state4.consecutiveTimeouts >= (this.opts.maxTimeouts ?? DEFAULT_MAX_TIMEOUTS)) {
|
|
15917
|
-
state4.cooldownUntilMs = this.now() + (this.opts.cooldownMs ?? DEFAULT_COOLDOWN_MS2);
|
|
15918
|
-
}
|
|
15919
|
-
this.states.set(key2, state4);
|
|
15920
|
-
}
|
|
15921
|
-
/** @internal — tests inspect counter state. */
|
|
15922
|
-
inspect(key2) {
|
|
15923
|
-
return this.states.get(key2) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
15924
|
-
}
|
|
15925
|
-
now() {
|
|
15926
|
-
return this.opts.now?.() ?? Date.now();
|
|
15927
|
-
}
|
|
15928
|
-
};
|
|
15929
|
-
|
|
15930
16588
|
// src/internal/runtime/local-agent/local-agent-memory.ts
|
|
15931
16589
|
init_index_manager();
|
|
15932
16590
|
|
|
@@ -18515,13 +19173,13 @@ function defineTool(spec) {
|
|
|
18515
19173
|
name: spec.name,
|
|
18516
19174
|
description: spec.description,
|
|
18517
19175
|
inputSchema,
|
|
18518
|
-
handler: async (input) => {
|
|
19176
|
+
handler: async (input, ctx) => {
|
|
18519
19177
|
const raw = spec.sanitize ? sanitizeToolInput(input, {
|
|
18520
19178
|
...spec.sanitize === true ? {} : spec.sanitize,
|
|
18521
19179
|
schema: spec.inputSchema
|
|
18522
19180
|
}).value : input;
|
|
18523
19181
|
const parsed = spec.inputSchema.parse(raw);
|
|
18524
|
-
return await spec.handler(parsed);
|
|
19182
|
+
return await spec.handler(parsed, ctx);
|
|
18525
19183
|
}
|
|
18526
19184
|
};
|
|
18527
19185
|
}
|
|
@@ -18532,6 +19190,14 @@ init_errors();
|
|
|
18532
19190
|
// src/event-bus.ts
|
|
18533
19191
|
var EventBus = class {
|
|
18534
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
|
+
}
|
|
18535
19201
|
/**
|
|
18536
19202
|
* Subscribe to an event. Returns an unsubscribe function.
|
|
18537
19203
|
*/
|
|
@@ -18554,7 +19220,13 @@ var EventBus = class {
|
|
|
18554
19220
|
for (const handler of set) {
|
|
18555
19221
|
try {
|
|
18556
19222
|
handler(payload);
|
|
18557
|
-
} 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
|
+
);
|
|
18558
19230
|
}
|
|
18559
19231
|
}
|
|
18560
19232
|
}
|
|
@@ -18577,11 +19249,17 @@ init_generate_object();
|
|
|
18577
19249
|
// src/internal/persistence/conversation-storage-memory.ts
|
|
18578
19250
|
var InMemoryConversationStorage = class {
|
|
18579
19251
|
#store = /* @__PURE__ */ new Map();
|
|
18580
|
-
async getMessages(conversationId) {
|
|
19252
|
+
async getMessages(conversationId, opts) {
|
|
18581
19253
|
const existing = this.#store.get(conversationId);
|
|
18582
|
-
return existing === void 0 ? [] : existing.slice();
|
|
19254
|
+
return existing === void 0 ? [] : paginate(existing.slice(), opts);
|
|
18583
19255
|
}
|
|
18584
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) {
|
|
18585
19263
|
const existing = this.#store.get(conversationId);
|
|
18586
19264
|
const stamped = message.at === void 0 ? { ...message, at: Date.now() } : message;
|
|
18587
19265
|
if (existing === void 0) {
|
|
@@ -18590,9 +19268,26 @@ var InMemoryConversationStorage = class {
|
|
|
18590
19268
|
}
|
|
18591
19269
|
existing.push(stamped);
|
|
18592
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
|
+
}
|
|
18593
19278
|
async deleteConversation(conversationId) {
|
|
18594
19279
|
this.#store.delete(conversationId);
|
|
18595
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
|
+
}
|
|
18596
19291
|
async listConversationIds(opts) {
|
|
18597
19292
|
const all = Array.from(this.#store.keys());
|
|
18598
19293
|
if (opts?.limit !== void 0) return all.slice(0, opts.limit);
|
|
@@ -18770,23 +19465,40 @@ function createNoopMemoryProvider() {
|
|
|
18770
19465
|
}
|
|
18771
19466
|
var JobQueue = class {
|
|
18772
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
|
+
}
|
|
18773
19475
|
/**
|
|
18774
|
-
* Enqueue a background function. Returns the job ID immediately.
|
|
18775
|
-
*
|
|
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).
|
|
18776
19480
|
*/
|
|
18777
19481
|
enqueue(fn) {
|
|
18778
19482
|
const id = crypto.randomUUID();
|
|
18779
19483
|
const job = { id, status: "pending" };
|
|
18780
19484
|
this.jobs.set(id, job);
|
|
18781
|
-
|
|
18782
|
-
|
|
18783
|
-
|
|
18784
|
-
job.
|
|
18785
|
-
|
|
18786
|
-
|
|
18787
|
-
|
|
18788
|
-
job.
|
|
18789
|
-
|
|
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));
|
|
18790
19502
|
});
|
|
18791
19503
|
return id;
|
|
18792
19504
|
}
|
|
@@ -18797,17 +19509,39 @@ var JobQueue = class {
|
|
|
18797
19509
|
return [...this.jobs.values()];
|
|
18798
19510
|
}
|
|
18799
19511
|
/**
|
|
18800
|
-
* 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.
|
|
18801
19514
|
*/
|
|
18802
19515
|
cancel(id) {
|
|
18803
19516
|
const job = this.jobs.get(id);
|
|
18804
19517
|
if (!job) return false;
|
|
18805
19518
|
if (job.status === "pending" || job.status === "running") {
|
|
18806
19519
|
job.status = "cancelled";
|
|
19520
|
+
this.controllers.get(id)?.abort();
|
|
18807
19521
|
return true;
|
|
18808
19522
|
}
|
|
18809
19523
|
return false;
|
|
18810
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
|
+
}
|
|
18811
19545
|
};
|
|
18812
19546
|
|
|
18813
19547
|
// src/internal/memory/dreaming/run.ts
|
|
@@ -19300,24 +20034,42 @@ async function migrateSqliteToLance2(options) {
|
|
|
19300
20034
|
}
|
|
19301
20035
|
|
|
19302
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
|
+
}
|
|
19303
20043
|
var PermissionEngine = class {
|
|
19304
20044
|
constructor(rules, options = {}) {
|
|
19305
20045
|
this.rules = rules;
|
|
19306
|
-
this.defaultAction = options.defaultAction ?? "
|
|
20046
|
+
this.defaultAction = options.defaultAction ?? "ask";
|
|
19307
20047
|
}
|
|
19308
20048
|
rules;
|
|
19309
20049
|
defaultAction;
|
|
19310
20050
|
/**
|
|
19311
|
-
* Evaluate a tool name against the rules. First
|
|
19312
|
-
* 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.
|
|
19313
20056
|
*/
|
|
19314
|
-
evaluate(toolName) {
|
|
20057
|
+
evaluate(toolName, args) {
|
|
19315
20058
|
for (const rule of this.rules) {
|
|
19316
|
-
const
|
|
19317
|
-
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;
|
|
19318
20063
|
}
|
|
19319
20064
|
return this.defaultAction;
|
|
19320
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
|
+
}
|
|
19321
20073
|
};
|
|
19322
20074
|
|
|
19323
20075
|
// src/permission-plugin.ts
|
|
@@ -19328,8 +20080,8 @@ function createPermissionPlugin(engine, opts = {}) {
|
|
|
19328
20080
|
kind: "general",
|
|
19329
20081
|
register(ctx) {
|
|
19330
20082
|
ctx.on("pre_tool_call", (rawCtx) => {
|
|
19331
|
-
const { name } = rawCtx;
|
|
19332
|
-
const action = engine.evaluate(name);
|
|
20083
|
+
const { name, args } = rawCtx;
|
|
20084
|
+
const action = engine.evaluate(name, args);
|
|
19333
20085
|
if (action === "deny") {
|
|
19334
20086
|
return { block: true, message: `denied by permission engine: ${name}` };
|
|
19335
20087
|
}
|
|
@@ -19394,6 +20146,14 @@ var Security = class {
|
|
|
19394
20146
|
}
|
|
19395
20147
|
};
|
|
19396
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
|
+
|
|
19397
20157
|
// src/squad.ts
|
|
19398
20158
|
init_errors();
|
|
19399
20159
|
var PersistenceSchema = zod.z.object({
|
|
@@ -20144,6 +20904,8 @@ exports.migrateSqliteToLance = migrateSqliteToLance2;
|
|
|
20144
20904
|
exports.mkMemoryId = mkMemoryId;
|
|
20145
20905
|
exports.normalizeUsage = normalizeUsage;
|
|
20146
20906
|
exports.preflightCheck = preflightCheck;
|
|
20907
|
+
exports.scopedConversationId = scopedConversationId;
|
|
20908
|
+
exports.sessionScopePrefix = sessionScopePrefix;
|
|
20147
20909
|
exports.toShareGptTrajectory = toShareGptTrajectory;
|
|
20148
20910
|
exports.withCwdMutex = withCwdMutex;
|
|
20149
20911
|
//# sourceMappingURL=index.cjs.map
|