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