@graphorin/agent 0.5.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +159 -0
  4. package/dist/errors/index.d.ts +170 -0
  5. package/dist/errors/index.d.ts.map +1 -0
  6. package/dist/errors/index.js +204 -0
  7. package/dist/errors/index.js.map +1 -0
  8. package/dist/evaluator-optimizer/index.d.ts +91 -0
  9. package/dist/evaluator-optimizer/index.d.ts.map +1 -0
  10. package/dist/evaluator-optimizer/index.js +85 -0
  11. package/dist/evaluator-optimizer/index.js.map +1 -0
  12. package/dist/factory.d.ts +13 -0
  13. package/dist/factory.d.ts.map +1 -0
  14. package/dist/factory.js +1853 -0
  15. package/dist/factory.js.map +1 -0
  16. package/dist/fallback/index.d.ts +52 -0
  17. package/dist/fallback/index.d.ts.map +1 -0
  18. package/dist/fallback/index.js +53 -0
  19. package/dist/fallback/index.js.map +1 -0
  20. package/dist/fanout/index.d.ts +142 -0
  21. package/dist/fanout/index.d.ts.map +1 -0
  22. package/dist/fanout/index.js +252 -0
  23. package/dist/fanout/index.js.map +1 -0
  24. package/dist/filters/index.d.ts +137 -0
  25. package/dist/filters/index.d.ts.map +1 -0
  26. package/dist/filters/index.js +273 -0
  27. package/dist/filters/index.js.map +1 -0
  28. package/dist/index.d.ts +51 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +49 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/internal/ids.js +46 -0
  33. package/dist/internal/ids.js.map +1 -0
  34. package/dist/internal/usage-accumulator.js +62 -0
  35. package/dist/internal/usage-accumulator.js.map +1 -0
  36. package/dist/lateral-leak/causality-monitor.d.ts +97 -0
  37. package/dist/lateral-leak/causality-monitor.d.ts.map +1 -0
  38. package/dist/lateral-leak/causality-monitor.js +139 -0
  39. package/dist/lateral-leak/causality-monitor.js.map +1 -0
  40. package/dist/lateral-leak/index.d.ts +4 -0
  41. package/dist/lateral-leak/index.js +5 -0
  42. package/dist/lateral-leak/merge-guard.d.ts +89 -0
  43. package/dist/lateral-leak/merge-guard.d.ts.map +1 -0
  44. package/dist/lateral-leak/merge-guard.js +65 -0
  45. package/dist/lateral-leak/merge-guard.js.map +1 -0
  46. package/dist/lateral-leak/protocol-guard.d.ts +76 -0
  47. package/dist/lateral-leak/protocol-guard.d.ts.map +1 -0
  48. package/dist/lateral-leak/protocol-guard.js +147 -0
  49. package/dist/lateral-leak/protocol-guard.js.map +1 -0
  50. package/dist/preferred-model/index.d.ts +53 -0
  51. package/dist/preferred-model/index.d.ts.map +1 -0
  52. package/dist/preferred-model/index.js +141 -0
  53. package/dist/preferred-model/index.js.map +1 -0
  54. package/dist/progress/index.d.ts +62 -0
  55. package/dist/progress/index.d.ts.map +1 -0
  56. package/dist/progress/index.js +150 -0
  57. package/dist/progress/index.js.map +1 -0
  58. package/dist/run-state/index.d.ts +152 -0
  59. package/dist/run-state/index.d.ts.map +1 -0
  60. package/dist/run-state/index.js +311 -0
  61. package/dist/run-state/index.js.map +1 -0
  62. package/dist/tooling/adapters.js +154 -0
  63. package/dist/tooling/adapters.js.map +1 -0
  64. package/dist/tooling/catalogue.js +37 -0
  65. package/dist/tooling/catalogue.js.map +1 -0
  66. package/dist/tooling/dataflow.js +99 -0
  67. package/dist/tooling/dataflow.js.map +1 -0
  68. package/dist/tooling/registry-build.js +85 -0
  69. package/dist/tooling/registry-build.js.map +1 -0
  70. package/dist/types.d.ts +413 -0
  71. package/dist/types.d.ts.map +1 -0
  72. package/package.json +115 -0
@@ -0,0 +1,311 @@
1
+ import { RunStateMalformedError, RunStateVersionUnsupportedError } from "../errors/index.js";
2
+ import { zeroUsage } from "@graphorin/core";
3
+
4
+ //#region src/run-state/index.ts
5
+ /**
6
+ * Canonical schema id for serialized {@link RunState} payloads.
7
+ *
8
+ * @stable
9
+ */
10
+ const RUN_STATE_SCHEMA_VERSION = "graphorin-run-state/1.1";
11
+ /**
12
+ * Reader-supported schema id range. Major version 1 only for v0.1.
13
+ *
14
+ * @stable
15
+ */
16
+ const RUN_STATE_SCHEMA_MAJOR_SUPPORTED = 1;
17
+ /**
18
+ * Render a JSON-stable snapshot of the supplied {@link RunState}.
19
+ * The returned value is plain JSON (no `Map`, `Set`, `Date`, ...).
20
+ *
21
+ * @stable
22
+ */
23
+ function serializeRunState(state, options = {}) {
24
+ const out = {
25
+ version: RUN_STATE_SCHEMA_VERSION,
26
+ id: state.id,
27
+ agentId: state.agentId,
28
+ currentAgentId: state.currentAgentId,
29
+ sessionId: state.sessionId,
30
+ ...state.userId !== void 0 ? { userId: state.userId } : {},
31
+ status: state.status,
32
+ steps: state.steps,
33
+ messages: state.messages,
34
+ pendingApprovals: state.pendingApprovals,
35
+ handoffs: state.handoffs,
36
+ usage: state.usage,
37
+ ...state.usageByModel !== void 0 ? { usageByModel: state.usageByModel } : {},
38
+ ...state.taintSummary !== void 0 ? { taintSummary: state.taintSummary } : {},
39
+ ...state.promotedTools !== void 0 ? { promotedTools: state.promotedTools } : {},
40
+ startedAt: state.startedAt,
41
+ ...state.finishedAt !== void 0 ? { finishedAt: state.finishedAt } : {},
42
+ ...state.error !== void 0 ? { error: state.error } : {}
43
+ };
44
+ const detached = JSON.parse(JSON.stringify(out));
45
+ if (options.stripTracingApiKey === true) redactSecretKeysInPlace(detached);
46
+ return detached;
47
+ }
48
+ /**
49
+ * Key names whose values are redacted by
50
+ * `serializeRunState(state, { stripTracingApiKey: true })`.
51
+ */
52
+ const SECRET_KEY_PATTERN = /^(api[-_]?key|tracing[-_]?api[-_]?key|x-api-key|authorization|bearer[-_]?token|access[-_]?token|refresh[-_]?token|secret|password|passphrase)$/i;
53
+ const REDACTED = "[redacted]";
54
+ function redactSecretKeysInPlace(value) {
55
+ if (typeof value !== "object" || value === null) return;
56
+ if (Array.isArray(value)) {
57
+ for (const item of value) redactSecretKeysInPlace(item);
58
+ return;
59
+ }
60
+ const record = value;
61
+ if (record.role === "tool" && typeof record.content === "string") record.content = redactJsonString(record.content);
62
+ for (const [key, inner] of Object.entries(record)) {
63
+ if (SECRET_KEY_PATTERN.test(key)) {
64
+ record[key] = REDACTED;
65
+ continue;
66
+ }
67
+ redactSecretKeysInPlace(inner);
68
+ }
69
+ }
70
+ function redactJsonString(content) {
71
+ try {
72
+ const parsed = JSON.parse(content);
73
+ if (typeof parsed !== "object" || parsed === null) return content;
74
+ redactSecretKeysInPlace(parsed);
75
+ return JSON.stringify(parsed);
76
+ } catch {
77
+ return content;
78
+ }
79
+ }
80
+ /**
81
+ * Render the canonical JSON string representation of the supplied
82
+ * {@link RunState}. `JSON.stringify(serializeRunState(state))` —
83
+ * provided as a convenience.
84
+ *
85
+ * @stable
86
+ */
87
+ function runStateToJSON(state, options) {
88
+ return JSON.stringify(serializeRunState(state, options));
89
+ }
90
+ function isRecord(v) {
91
+ return typeof v === "object" && v !== null && !Array.isArray(v);
92
+ }
93
+ /**
94
+ * Rehydrate a {@link RunState} from the on-disk payload. Throws
95
+ * {@link RunStateVersionUnsupportedError} when the payload version
96
+ * is from a future major; throws
97
+ * {@link RunStateMalformedError} when the payload is structurally
98
+ * invalid.
99
+ *
100
+ * Backwards-compat: a payload that omits `usageByModel` is accepted
101
+ * and the field is synthesized from the aggregate `usage` with
102
+ * `attemptCount: 1` for the primary model.
103
+ *
104
+ * @stable
105
+ */
106
+ function deserializeRunState(payload, options = {}) {
107
+ if (!isRecord(payload)) throw new RunStateMalformedError("payload must be an object");
108
+ const versionRaw = payload.version;
109
+ if (typeof versionRaw !== "string") throw new RunStateMalformedError("missing 'version' field");
110
+ const versionMatch = /^graphorin-run-state\/(\d+)\.(\d+)$/.exec(versionRaw);
111
+ if (versionMatch === null) throw new RunStateMalformedError(`unrecognized version '${versionRaw}'`);
112
+ if (Number(versionMatch[1]) > RUN_STATE_SCHEMA_MAJOR_SUPPORTED) throw new RunStateVersionUnsupportedError(versionRaw, RUN_STATE_SCHEMA_VERSION);
113
+ const requiredString = (key) => {
114
+ const v = payload[key];
115
+ if (typeof v !== "string") throw new RunStateMalformedError(`missing string '${key}' field`);
116
+ return v;
117
+ };
118
+ const requiredArray = (key) => {
119
+ const v = payload[key];
120
+ if (!Array.isArray(v)) throw new RunStateMalformedError(`missing array '${key}' field`);
121
+ return v;
122
+ };
123
+ const id = requiredString("id");
124
+ const agentId = requiredString("agentId");
125
+ const currentAgentId = typeof payload.currentAgentId === "string" ? payload.currentAgentId : agentId;
126
+ const sessionId = requiredString("sessionId");
127
+ const status = requiredString("status");
128
+ const steps = requiredArray("steps");
129
+ const messages = requiredArray("messages");
130
+ const pendingApprovals = requiredArray("pendingApprovals");
131
+ const handoffs = requiredArray("handoffs");
132
+ const usageRaw = payload.usage;
133
+ if (!isRecord(usageRaw)) throw new RunStateMalformedError("missing object 'usage' field");
134
+ const usage = {
135
+ promptTokens: Number(usageRaw.promptTokens ?? 0),
136
+ completionTokens: Number(usageRaw.completionTokens ?? 0),
137
+ totalTokens: Number(usageRaw.totalTokens ?? 0),
138
+ ...usageRaw.reasoningTokens !== void 0 ? { reasoningTokens: Number(usageRaw.reasoningTokens) } : {},
139
+ ...isRecord(usageRaw.cost) ? { cost: {
140
+ amount: Number(usageRaw.cost.amount ?? 0),
141
+ currency: String(usageRaw.cost.currency ?? "USD")
142
+ } } : {}
143
+ };
144
+ const startedAt = requiredString("startedAt");
145
+ const userIdRaw = payload.userId;
146
+ const finishedAtRaw = payload.finishedAt;
147
+ const errorRaw = payload.error;
148
+ let error;
149
+ if (isRecord(errorRaw)) error = {
150
+ message: typeof errorRaw.message === "string" ? errorRaw.message : "unknown",
151
+ code: typeof errorRaw.code === "string" ? errorRaw.code : "unknown",
152
+ ...errorRaw.details !== void 0 ? { details: errorRaw.details } : {}
153
+ };
154
+ let usageByModel;
155
+ if (isRecord(payload.usageByModel)) {
156
+ const m = {};
157
+ for (const [modelId, entryRaw] of Object.entries(payload.usageByModel)) {
158
+ if (!isRecord(entryRaw)) continue;
159
+ m[modelId] = {
160
+ promptTokens: Number(entryRaw.promptTokens ?? 0),
161
+ completionTokens: Number(entryRaw.completionTokens ?? 0),
162
+ totalTokens: Number(entryRaw.totalTokens ?? 0),
163
+ attemptCount: Number(entryRaw.attemptCount ?? 1),
164
+ ...entryRaw.reasoningTokens !== void 0 ? { reasoningTokens: Number(entryRaw.reasoningTokens) } : {},
165
+ ...isRecord(entryRaw.cost) ? { cost: {
166
+ amount: Number(entryRaw.cost.amount ?? 0),
167
+ currency: String(entryRaw.cost.currency ?? "USD")
168
+ } } : {}
169
+ };
170
+ }
171
+ usageByModel = m;
172
+ } else if (options.synthesizeUsageByModel !== false && Number.isFinite(usage.totalTokens)) {
173
+ usageByModel = { [agentId]: {
174
+ ...usage,
175
+ attemptCount: 1
176
+ } };
177
+ options.logger?.(`[graphorin/agent] RunState v0.1-alpha synthesis: per-model breakdown derived from aggregate usage for agent '${agentId}'.`);
178
+ }
179
+ let taintSummary;
180
+ if (isRecord(payload.taintSummary)) {
181
+ const ts = payload.taintSummary;
182
+ taintSummary = {
183
+ untrustedSeen: ts.untrustedSeen === true,
184
+ sensitiveSeen: ts.sensitiveSeen === true,
185
+ untrustedSourceKinds: Array.isArray(ts.untrustedSourceKinds) ? ts.untrustedSourceKinds.filter((k) => typeof k === "string") : []
186
+ };
187
+ }
188
+ const promotedTools = Array.isArray(payload.promotedTools) ? payload.promotedTools.filter((t) => typeof t === "string") : void 0;
189
+ return {
190
+ id,
191
+ agentId,
192
+ currentAgentId,
193
+ sessionId,
194
+ ...typeof userIdRaw === "string" ? { userId: userIdRaw } : {},
195
+ status,
196
+ steps,
197
+ messages,
198
+ pendingApprovals,
199
+ handoffs,
200
+ usage,
201
+ ...usageByModel !== void 0 ? { usageByModel } : {},
202
+ ...taintSummary !== void 0 ? { taintSummary } : {},
203
+ ...promotedTools !== void 0 ? { promotedTools } : {},
204
+ startedAt,
205
+ ...typeof finishedAtRaw === "string" ? { finishedAt: finishedAtRaw } : {},
206
+ ...error !== void 0 ? { error } : {}
207
+ };
208
+ }
209
+ /** Convenience JSON-string parser pairing with {@link runStateToJSON}. */
210
+ function runStateFromJSON(serialized, options) {
211
+ let parsed;
212
+ try {
213
+ parsed = JSON.parse(serialized);
214
+ } catch (cause) {
215
+ throw new RunStateMalformedError(`invalid JSON: ${cause instanceof Error ? cause.message : String(cause)}`);
216
+ }
217
+ return deserializeRunState(parsed, options);
218
+ }
219
+ /**
220
+ * Build a fresh, minimal {@link RunState} for a new run. Helper used
221
+ * by `createAgent({...})` so consumers can construct deterministic
222
+ * run state in tests.
223
+ *
224
+ * @stable
225
+ */
226
+ function createInitialRunState(args) {
227
+ return {
228
+ id: args.id,
229
+ agentId: args.agentId,
230
+ currentAgentId: args.agentId,
231
+ sessionId: args.sessionId,
232
+ ...args.userId !== void 0 ? { userId: args.userId } : {},
233
+ status: "running",
234
+ steps: [],
235
+ messages: [],
236
+ pendingApprovals: [],
237
+ handoffs: [],
238
+ usage: zeroUsage(),
239
+ startedAt: args.startedAt ?? (/* @__PURE__ */ new Date()).toISOString()
240
+ };
241
+ }
242
+ /**
243
+ * Append a per-model usage entry to {@link RunState.usageByModel}.
244
+ * Mutates the supplied state in place — used by the agent runtime's
245
+ * per-step retry loop. Pure callers that need an immutable update
246
+ * should clone the state first.
247
+ *
248
+ * @stable
249
+ */
250
+ function addModelUsage(state, modelId, delta) {
251
+ const current = state.usageByModel ?? {};
252
+ const prev = current[modelId];
253
+ current[modelId] = prev ? {
254
+ promptTokens: prev.promptTokens + delta.promptTokens,
255
+ completionTokens: prev.completionTokens + delta.completionTokens,
256
+ totalTokens: prev.totalTokens + delta.totalTokens,
257
+ ...delta.reasoningTokens !== void 0 || prev.reasoningTokens !== void 0 ? { reasoningTokens: (prev.reasoningTokens ?? 0) + (delta.reasoningTokens ?? 0) } : {},
258
+ attemptCount: prev.attemptCount + 1,
259
+ ...prev.cost !== void 0 ? { cost: prev.cost } : {}
260
+ } : {
261
+ ...delta,
262
+ attemptCount: 1
263
+ };
264
+ state.usageByModel = current;
265
+ }
266
+ /**
267
+ * Recompute the aggregate usage from `usageByModel`. Returns the
268
+ * sum that callers can compare against `state.usage` to verify the
269
+ * per-step retry loop maintained the documented invariant.
270
+ *
271
+ * @stable
272
+ */
273
+ function aggregateUsageFromByModel(byModel) {
274
+ if (byModel === void 0) return zeroUsage();
275
+ let pt = 0;
276
+ let ct = 0;
277
+ let tt = 0;
278
+ let rt = 0;
279
+ let hasReasoning = false;
280
+ for (const entry of Object.values(byModel)) {
281
+ pt += entry.promptTokens;
282
+ ct += entry.completionTokens;
283
+ tt += entry.totalTokens;
284
+ if (entry.reasoningTokens !== void 0) {
285
+ rt += entry.reasoningTokens;
286
+ hasReasoning = true;
287
+ }
288
+ }
289
+ return {
290
+ promptTokens: pt,
291
+ completionTokens: ct,
292
+ totalTokens: tt,
293
+ ...hasReasoning ? { reasoningTokens: rt } : {}
294
+ };
295
+ }
296
+ /**
297
+ * The "tools used" surface of a completed run. Cheap to compute
298
+ * from `RunState.steps`; surfaced as a stand-alone helper for
299
+ * Phase 17 example apps and operator-facing dashboards.
300
+ *
301
+ * @stable
302
+ */
303
+ function completedToolCallsFromState(state) {
304
+ const out = [];
305
+ for (const step of state.steps) for (const tc of step.toolCalls) out.push(tc);
306
+ return out;
307
+ }
308
+
309
+ //#endregion
310
+ export { RUN_STATE_SCHEMA_MAJOR_SUPPORTED, RUN_STATE_SCHEMA_VERSION, addModelUsage, aggregateUsageFromByModel, completedToolCallsFromState, createInitialRunState, deserializeRunState, runStateFromJSON, runStateToJSON, serializeRunState };
311
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["out: SerializedRunState","usage: Usage","error: RunState['error']","usageByModel: RunStateUsageByModel | undefined","m: Record<string, Usage & { attemptCount: number }>","taintSummary: RunTaintSummary | undefined","parsed: unknown","out: CompletedToolCall[]"],"sources":["../../src/run-state/index.ts"],"sourcesContent":["/**\n * `RunState` JSON serialization and rehydration.\n *\n * The on-disk shape carries an explicit `version` field so future\n * schema bumps can detect older payloads. v0.1 ships\n * `'graphorin-run-state/1.0'` — additive fields that older readers\n * do not understand are ignored under the lenient-forward-parse\n * discipline.\n *\n * @packageDocumentation\n */\n\nimport type {\n CompletedToolCall,\n HandoffRecord,\n Message,\n RunState,\n RunStateUsageByModel,\n RunStatus,\n RunStep,\n RunTaintSummary,\n ToolApproval,\n Usage,\n} from '@graphorin/core';\nimport { zeroUsage } from '@graphorin/core';\nimport { RunStateMalformedError, RunStateVersionUnsupportedError } from '../errors/index.js';\n\n/**\n * Canonical schema id for serialized {@link RunState} payloads.\n *\n * @stable\n */\nexport const RUN_STATE_SCHEMA_VERSION = 'graphorin-run-state/1.1' as const;\n\n/**\n * Reader-supported schema id range. Major version 1 only for v0.1.\n *\n * @stable\n */\nexport const RUN_STATE_SCHEMA_MAJOR_SUPPORTED = 1;\n\n/**\n * On-disk payload returned by {@link serializeRunState} and accepted\n * by {@link deserializeRunState}. The shape is JSON-stable.\n *\n * @stable\n */\nexport interface SerializedRunState {\n readonly version: typeof RUN_STATE_SCHEMA_VERSION;\n readonly id: string;\n readonly agentId: string;\n readonly currentAgentId: string;\n readonly sessionId: string;\n readonly userId?: string;\n readonly status: RunStatus;\n readonly steps: ReadonlyArray<RunStep>;\n readonly messages: ReadonlyArray<Message>;\n readonly pendingApprovals: ReadonlyArray<ToolApproval>;\n readonly handoffs: ReadonlyArray<HandoffRecord>;\n readonly usage: Usage;\n readonly usageByModel?: RunStateUsageByModel;\n /** AG-19: coarse data-flow taint summary (no untrusted text). */\n readonly taintSummary?: RunTaintSummary;\n /** AG-19: deferred tools promoted by `tool_search` this run. */\n readonly promotedTools?: ReadonlyArray<string>;\n readonly startedAt: string;\n readonly finishedAt?: string;\n readonly error?: { readonly message: string; readonly code: string; readonly details?: unknown };\n}\n\n/**\n * Options accepted by {@link serializeRunState}.\n *\n * @stable\n */\nexport interface SerializeRunStateOptions {\n /**\n * Deep-redact secret-named keys (`apiKey`, `authorization`,\n * `bearerToken` / `accessToken` / `refreshToken`, `password`,\n * `secret`, …) anywhere in the snapshot — tool results and messages\n * included — replacing their values with `'[redacted]'`. Defaults to\n * `false` for the round-trip canonical helper; the agent runtime\n * passes `true` when persisting through the checkpoint store\n * (AG-23). Redaction is best-effort by key name: secrets stored\n * under unrelated keys are not detected.\n */\n readonly stripTracingApiKey?: boolean;\n}\n\n/**\n * Render a JSON-stable snapshot of the supplied {@link RunState}.\n * The returned value is plain JSON (no `Map`, `Set`, `Date`, ...).\n *\n * @stable\n */\nexport function serializeRunState(\n state: RunState,\n options: SerializeRunStateOptions = {},\n): SerializedRunState {\n const out: SerializedRunState = {\n version: RUN_STATE_SCHEMA_VERSION,\n id: state.id,\n agentId: state.agentId,\n currentAgentId: state.currentAgentId,\n sessionId: state.sessionId,\n ...(state.userId !== undefined ? { userId: state.userId } : {}),\n status: state.status,\n steps: state.steps,\n messages: state.messages,\n pendingApprovals: state.pendingApprovals,\n handoffs: state.handoffs,\n usage: state.usage,\n ...(state.usageByModel !== undefined ? { usageByModel: state.usageByModel } : {}),\n ...(state.taintSummary !== undefined ? { taintSummary: state.taintSummary } : {}),\n ...(state.promotedTools !== undefined ? { promotedTools: state.promotedTools } : {}),\n startedAt: state.startedAt,\n ...(state.finishedAt !== undefined ? { finishedAt: state.finishedAt } : {}),\n ...(state.error !== undefined ? { error: state.error } : {}),\n };\n // AG-23: the snapshot must be DETACHED from the live MutableRunState —\n // a post-suspend mutation must never reach an already-persisted\n // checkpoint. The JSON round-trip doubles as the plain-JSON guarantee\n // this function documents (no Map/Set/Date survive it).\n const detached = JSON.parse(JSON.stringify(out)) as SerializedRunState;\n if (options.stripTracingApiKey === true) {\n redactSecretKeysInPlace(detached);\n }\n return detached;\n}\n\n/**\n * Key names whose values are redacted by\n * `serializeRunState(state, { stripTracingApiKey: true })`.\n */\nconst SECRET_KEY_PATTERN =\n /^(api[-_]?key|tracing[-_]?api[-_]?key|x-api-key|authorization|bearer[-_]?token|access[-_]?token|refresh[-_]?token|secret|password|passphrase)$/i;\n\nconst REDACTED = '[redacted]';\n\nfunction redactSecretKeysInPlace(value: unknown): void {\n if (typeof value !== 'object' || value === null) return;\n if (Array.isArray(value)) {\n for (const item of value) redactSecretKeysInPlace(item);\n return;\n }\n const record = value as Record<string, unknown>;\n // Tool messages embed the tool output as a JSON STRING in `content` —\n // parse, redact, and re-stringify so a secret inside the string form\n // is caught too.\n if (record.role === 'tool' && typeof record.content === 'string') {\n record.content = redactJsonString(record.content);\n }\n for (const [key, inner] of Object.entries(record)) {\n if (SECRET_KEY_PATTERN.test(key)) {\n record[key] = REDACTED;\n continue;\n }\n redactSecretKeysInPlace(inner);\n }\n}\n\nfunction redactJsonString(content: string): string {\n try {\n const parsed = JSON.parse(content) as unknown;\n if (typeof parsed !== 'object' || parsed === null) return content;\n redactSecretKeysInPlace(parsed);\n return JSON.stringify(parsed);\n } catch {\n return content;\n }\n}\n\n/**\n * Render the canonical JSON string representation of the supplied\n * {@link RunState}. `JSON.stringify(serializeRunState(state))` —\n * provided as a convenience.\n *\n * @stable\n */\nexport function runStateToJSON(state: RunState, options?: SerializeRunStateOptions): string {\n return JSON.stringify(serializeRunState(state, options));\n}\n\ninterface DeserializeOptions {\n /**\n * Synthesize `usageByModel` from a v0.1-alpha state that omits\n * the field. Defaults to `true` so callers can rehydrate older\n * states without explicit migration.\n */\n readonly synthesizeUsageByModel?: boolean;\n /**\n * Logger callback for one-time INFO messages emitted on\n * backwards-compat synthesis. Defaults to a no-op.\n */\n readonly logger?: (message: string) => void;\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n/**\n * Rehydrate a {@link RunState} from the on-disk payload. Throws\n * {@link RunStateVersionUnsupportedError} when the payload version\n * is from a future major; throws\n * {@link RunStateMalformedError} when the payload is structurally\n * invalid.\n *\n * Backwards-compat: a payload that omits `usageByModel` is accepted\n * and the field is synthesized from the aggregate `usage` with\n * `attemptCount: 1` for the primary model.\n *\n * @stable\n */\nexport function deserializeRunState(payload: unknown, options: DeserializeOptions = {}): RunState {\n if (!isRecord(payload)) {\n throw new RunStateMalformedError('payload must be an object');\n }\n const versionRaw = payload.version;\n if (typeof versionRaw !== 'string') {\n throw new RunStateMalformedError(\"missing 'version' field\");\n }\n const versionMatch = /^graphorin-run-state\\/(\\d+)\\.(\\d+)$/.exec(versionRaw);\n if (versionMatch === null) {\n throw new RunStateMalformedError(`unrecognized version '${versionRaw}'`);\n }\n const major = Number(versionMatch[1]);\n if (major > RUN_STATE_SCHEMA_MAJOR_SUPPORTED) {\n throw new RunStateVersionUnsupportedError(versionRaw, RUN_STATE_SCHEMA_VERSION);\n }\n const requiredString = (key: string): string => {\n const v = payload[key];\n if (typeof v !== 'string') {\n throw new RunStateMalformedError(`missing string '${key}' field`);\n }\n return v;\n };\n const requiredArray = <T>(key: string): T[] => {\n const v = payload[key];\n if (!Array.isArray(v)) {\n throw new RunStateMalformedError(`missing array '${key}' field`);\n }\n return v as T[];\n };\n const id = requiredString('id');\n const agentId = requiredString('agentId');\n const currentAgentId =\n typeof payload.currentAgentId === 'string' ? payload.currentAgentId : agentId;\n const sessionId = requiredString('sessionId');\n const status = requiredString('status') as RunStatus;\n const steps = requiredArray<RunStep>('steps');\n const messages = requiredArray<Message>('messages');\n const pendingApprovals = requiredArray<ToolApproval>('pendingApprovals');\n const handoffs = requiredArray<HandoffRecord>('handoffs');\n const usageRaw = payload.usage;\n if (!isRecord(usageRaw)) {\n throw new RunStateMalformedError(\"missing object 'usage' field\");\n }\n const usage: Usage = {\n promptTokens: Number(usageRaw.promptTokens ?? 0),\n completionTokens: Number(usageRaw.completionTokens ?? 0),\n totalTokens: Number(usageRaw.totalTokens ?? 0),\n ...(usageRaw.reasoningTokens !== undefined\n ? { reasoningTokens: Number(usageRaw.reasoningTokens) }\n : {}),\n ...(isRecord(usageRaw.cost)\n ? {\n cost: {\n amount: Number(usageRaw.cost.amount ?? 0),\n currency: String(usageRaw.cost.currency ?? 'USD'),\n },\n }\n : {}),\n };\n const startedAt = requiredString('startedAt');\n const userIdRaw = payload.userId;\n const finishedAtRaw = payload.finishedAt;\n const errorRaw = payload.error;\n let error: RunState['error'];\n if (isRecord(errorRaw)) {\n error = {\n message: typeof errorRaw.message === 'string' ? errorRaw.message : 'unknown',\n code: typeof errorRaw.code === 'string' ? errorRaw.code : 'unknown',\n ...(errorRaw.details !== undefined ? { details: errorRaw.details } : {}),\n };\n }\n\n // Backwards-compat synthesis for usageByModel.\n let usageByModel: RunStateUsageByModel | undefined;\n if (isRecord(payload.usageByModel)) {\n const m: Record<string, Usage & { attemptCount: number }> = {};\n for (const [modelId, entryRaw] of Object.entries(payload.usageByModel)) {\n if (!isRecord(entryRaw)) continue;\n m[modelId] = {\n promptTokens: Number(entryRaw.promptTokens ?? 0),\n completionTokens: Number(entryRaw.completionTokens ?? 0),\n totalTokens: Number(entryRaw.totalTokens ?? 0),\n attemptCount: Number(entryRaw.attemptCount ?? 1),\n ...(entryRaw.reasoningTokens !== undefined\n ? { reasoningTokens: Number(entryRaw.reasoningTokens) }\n : {}),\n ...(isRecord(entryRaw.cost)\n ? {\n cost: {\n amount: Number(entryRaw.cost.amount ?? 0),\n currency: String(entryRaw.cost.currency ?? 'USD'),\n },\n }\n : {}),\n };\n }\n usageByModel = m;\n } else if (options.synthesizeUsageByModel !== false && Number.isFinite(usage.totalTokens)) {\n usageByModel = {\n [agentId]: { ...usage, attemptCount: 1 },\n };\n options.logger?.(\n `[graphorin/agent] RunState v0.1-alpha synthesis: per-model breakdown derived from aggregate usage for agent '${agentId}'.`,\n );\n }\n\n // AG-19: rehydrate the coarse taint summary + promoted-tool set (additive;\n // absent on older v1.0 payloads).\n let taintSummary: RunTaintSummary | undefined;\n if (isRecord(payload.taintSummary)) {\n const ts = payload.taintSummary;\n taintSummary = {\n untrustedSeen: ts.untrustedSeen === true,\n sensitiveSeen: ts.sensitiveSeen === true,\n untrustedSourceKinds: Array.isArray(ts.untrustedSourceKinds)\n ? ts.untrustedSourceKinds.filter((k): k is string => typeof k === 'string')\n : [],\n };\n }\n const promotedTools = Array.isArray(payload.promotedTools)\n ? payload.promotedTools.filter((t): t is string => typeof t === 'string')\n : undefined;\n\n const out: RunState = {\n id,\n agentId,\n currentAgentId,\n sessionId,\n ...(typeof userIdRaw === 'string' ? { userId: userIdRaw } : {}),\n status,\n steps,\n messages,\n pendingApprovals,\n handoffs,\n usage,\n ...(usageByModel !== undefined ? { usageByModel } : {}),\n ...(taintSummary !== undefined ? { taintSummary } : {}),\n ...(promotedTools !== undefined ? { promotedTools } : {}),\n startedAt,\n ...(typeof finishedAtRaw === 'string' ? { finishedAt: finishedAtRaw } : {}),\n ...(error !== undefined ? { error } : {}),\n };\n void zeroUsage; // kept available; some callers prefer zeroUsage as a default.\n return out;\n}\n\n/** Convenience JSON-string parser pairing with {@link runStateToJSON}. */\nexport function runStateFromJSON(serialized: string, options?: DeserializeOptions): RunState {\n let parsed: unknown;\n try {\n parsed = JSON.parse(serialized);\n } catch (cause) {\n const message = cause instanceof Error ? cause.message : String(cause);\n throw new RunStateMalformedError(`invalid JSON: ${message}`);\n }\n return deserializeRunState(parsed, options);\n}\n\n/**\n * Build a fresh, minimal {@link RunState} for a new run. Helper used\n * by `createAgent({...})` so consumers can construct deterministic\n * run state in tests.\n *\n * @stable\n */\nexport function createInitialRunState(args: {\n readonly id: string;\n readonly agentId: string;\n readonly sessionId: string;\n readonly userId?: string;\n readonly startedAt?: string;\n}): RunState {\n return {\n id: args.id,\n agentId: args.agentId,\n currentAgentId: args.agentId,\n sessionId: args.sessionId,\n ...(args.userId !== undefined ? { userId: args.userId } : {}),\n status: 'running',\n steps: [],\n messages: [],\n pendingApprovals: [],\n handoffs: [],\n usage: zeroUsage(),\n startedAt: args.startedAt ?? new Date().toISOString(),\n };\n}\n\n/**\n * Append a per-model usage entry to {@link RunState.usageByModel}.\n * Mutates the supplied state in place — used by the agent runtime's\n * per-step retry loop. Pure callers that need an immutable update\n * should clone the state first.\n *\n * @stable\n */\nexport function addModelUsage(state: RunState, modelId: string, delta: Usage): void {\n const current = (state.usageByModel ?? {}) as Record<string, Usage & { attemptCount: number }>;\n const prev = current[modelId];\n const merged: Usage & { attemptCount: number } = prev\n ? {\n promptTokens: prev.promptTokens + delta.promptTokens,\n completionTokens: prev.completionTokens + delta.completionTokens,\n totalTokens: prev.totalTokens + delta.totalTokens,\n ...(delta.reasoningTokens !== undefined || prev.reasoningTokens !== undefined\n ? {\n reasoningTokens: (prev.reasoningTokens ?? 0) + (delta.reasoningTokens ?? 0),\n }\n : {}),\n attemptCount: prev.attemptCount + 1,\n ...(prev.cost !== undefined ? { cost: prev.cost } : {}),\n }\n : { ...delta, attemptCount: 1 };\n current[modelId] = merged;\n // Mutate via the writable typing — `usageByModel` is declared\n // optional; the runtime owns the field's lifecycle.\n (state as { usageByModel?: RunStateUsageByModel }).usageByModel = current;\n}\n\n/**\n * Recompute the aggregate usage from `usageByModel`. Returns the\n * sum that callers can compare against `state.usage` to verify the\n * per-step retry loop maintained the documented invariant.\n *\n * @stable\n */\nexport function aggregateUsageFromByModel(byModel: RunStateUsageByModel | undefined): Usage {\n if (byModel === undefined) return zeroUsage();\n let pt = 0;\n let ct = 0;\n let tt = 0;\n let rt = 0;\n let hasReasoning = false;\n for (const entry of Object.values(byModel)) {\n pt += entry.promptTokens;\n ct += entry.completionTokens;\n tt += entry.totalTokens;\n if (entry.reasoningTokens !== undefined) {\n rt += entry.reasoningTokens;\n hasReasoning = true;\n }\n }\n return {\n promptTokens: pt,\n completionTokens: ct,\n totalTokens: tt,\n ...(hasReasoning ? { reasoningTokens: rt } : {}),\n };\n}\n\n/**\n * The \"tools used\" surface of a completed run. Cheap to compute\n * from `RunState.steps`; surfaced as a stand-alone helper for\n * Phase 17 example apps and operator-facing dashboards.\n *\n * @stable\n */\nexport function completedToolCallsFromState(state: RunState): ReadonlyArray<CompletedToolCall> {\n const out: CompletedToolCall[] = [];\n for (const step of state.steps) {\n for (const tc of step.toolCalls) out.push(tc);\n }\n return out;\n}\n"],"mappings":"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"}
@@ -0,0 +1,154 @@
1
+ import { countTokensHeuristic } from "@graphorin/tools/result";
2
+ import { createGuard } from "@graphorin/security/guard";
3
+ import "@graphorin/security/sandbox";
4
+ import { parseSecretRef, resolveSecret } from "@graphorin/security/secrets";
5
+
6
+ //#region src/tooling/adapters.ts
7
+ /**
8
+ * Adapter A — build the executor's `secretResolver` hook.
9
+ *
10
+ * This is a thin **value resolver**. The executor's secrets accessor
11
+ * (`@graphorin/tools` `tool-context.ts`) already (1) enforces the
12
+ * per-tool `secretsAllowed` ACL via `enforceSecretAcl(key)` inside a
13
+ * `withChildToolSecretsContext(...)` scope and (2) maps a `null` result
14
+ * onto the optional/required-secret error contract. This adapter must
15
+ * therefore **not** re-implement ACL — it only resolves a key to a
16
+ * value.
17
+ */
18
+ function buildSecretResolver(options = {}) {
19
+ const backend = options.backend ?? (async (key) => resolveSecret(parseSecretRef(key)));
20
+ return Object.freeze({ resolve: backend });
21
+ }
22
+ /**
23
+ * Adapter C — build the executor's `memoryGuardFactory` (+ an optional
24
+ * `memoryRegionReader`) from the agent's configured `Memory`.
25
+ *
26
+ * When `memory` is `undefined`, the factory returns `null` for every
27
+ * tier and no reader is supplied — the executor degrades to its
28
+ * audit-only baseline (the guard step is skipped).
29
+ */
30
+ function buildMemoryGuard(memory, options = {}) {
31
+ if (memory === void 0) return Object.freeze({ memoryGuardFactory: () => null });
32
+ const memoryGuardFactory = (tier) => {
33
+ switch (tier) {
34
+ case "pure":
35
+ case "side-effecting-no-memory": return createGuard({ tier });
36
+ case "unknown": return createGuard({
37
+ tier,
38
+ ...options.auditOnly ?? {}
39
+ });
40
+ case "untrusted": return createGuard({
41
+ tier,
42
+ ...options.strictFull ?? {}
43
+ });
44
+ case "memory-aware": return options.apiBoundary === void 0 ? null : createGuard({
45
+ tier,
46
+ ...options.apiBoundary
47
+ });
48
+ default: return null;
49
+ }
50
+ };
51
+ return Object.freeze(options.regionReader === void 0 ? { memoryGuardFactory } : {
52
+ memoryGuardFactory,
53
+ memoryRegionReader: options.regionReader
54
+ });
55
+ }
56
+ /**
57
+ * Construct a `MemoryRegionReader` from an explicit region list and a
58
+ * read function. WI-03 uses this to bind the agent's `Memory` tiers to
59
+ * named regions once a run scope exists; exposed here so the reader
60
+ * contract is unit-testable in isolation.
61
+ */
62
+ function createMemoryRegionReader(regions, read) {
63
+ return Object.freeze({
64
+ regions: Object.freeze([...regions]),
65
+ read
66
+ });
67
+ }
68
+ /**
69
+ * Adapter D — build the **synchronous** token counter the executor's
70
+ * truncation pipeline requires.
71
+ *
72
+ * DESIGN DECISION (impedance mismatch §1.6.1): `@graphorin/tools`'
73
+ * `TokenCounter.count(text): number` is synchronous, while
74
+ * `@graphorin/provider`'s `createDefaultCounter().count(input): Promise<number>`
75
+ * is asynchronous (and also accepts `Message[]`). The truncation path
76
+ * runs inside `executeBatch` and must not `await`, so we never wire the
77
+ * provider's async counter here. We default to the tools heuristic
78
+ * (deterministic, offline) and allow a *synchronous* tokenizer (e.g.
79
+ * js-tiktoken's `encode`) to be injected when a caller wants
80
+ * vendor-accurate counts without blocking. The async provider counter
81
+ * stays reserved for budget accounting elsewhere (WI-09), not for
82
+ * executor truncation.
83
+ */
84
+ function buildToolTokenCounter(options = {}) {
85
+ const { tokenize } = options;
86
+ if (tokenize === void 0) return countTokensHeuristic;
87
+ return Object.freeze({ count: (text) => tokenize(text).length });
88
+ }
89
+ /**
90
+ * Adapter E — bridge the executor's `streamingSink` callback to an
91
+ * async iterator via a bounded queue.
92
+ */
93
+ function createExecutorEventBridge(options = {}) {
94
+ const queueDepth = options.queueDepth ?? 256;
95
+ const buffer = [];
96
+ let pending = null;
97
+ let closed = false;
98
+ let dropped = 0;
99
+ const sink = (event) => {
100
+ if (closed) return;
101
+ if (pending !== null) {
102
+ const resolve = pending;
103
+ pending = null;
104
+ resolve({
105
+ value: event,
106
+ done: false
107
+ });
108
+ return;
109
+ }
110
+ buffer.push(event);
111
+ if (buffer.length > queueDepth) {
112
+ buffer.shift();
113
+ dropped += 1;
114
+ }
115
+ };
116
+ async function* drain() {
117
+ for (;;) {
118
+ if (buffer.length > 0) {
119
+ const event = buffer.shift();
120
+ if (event !== void 0) yield event;
121
+ continue;
122
+ }
123
+ if (closed) return;
124
+ const next = await new Promise((resolve) => {
125
+ pending = resolve;
126
+ });
127
+ if (next.done === true) return;
128
+ yield next.value;
129
+ }
130
+ }
131
+ const close = () => {
132
+ closed = true;
133
+ if (pending !== null) {
134
+ const resolve = pending;
135
+ pending = null;
136
+ resolve({
137
+ value: void 0,
138
+ done: true
139
+ });
140
+ }
141
+ };
142
+ return Object.freeze({
143
+ sink,
144
+ drain,
145
+ close,
146
+ get dropped() {
147
+ return dropped;
148
+ }
149
+ });
150
+ }
151
+
152
+ //#endregion
153
+ export { buildMemoryGuard, buildSecretResolver, buildToolTokenCounter, createExecutorEventBridge, createMemoryRegionReader };
154
+ //# sourceMappingURL=adapters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapters.js","names":["backend: SecretBackend","memoryGuardFactory: MemoryGuardFactory","buffer: T[]","pending: ((result: IteratorResult<T, undefined>) => void) | null"],"sources":["../../src/tooling/adapters.ts"],"sourcesContent":["/**\n * Integration adapters (A–E) that let the `@graphorin/tools` executor\n * consume the agent's `@graphorin/security` / `@graphorin/memory` /\n * `@graphorin/provider` surfaces.\n *\n * These are built and unit-tested in isolation (WI-01). The run loop\n * wires them into `createToolExecutor(...)` in a later work item\n * (WI-03); nothing here is exported from the package entrypoint yet.\n *\n * Every adapter is typed against the executor's `ExecutorOptions` via\n * indexed access, so the values they produce are guaranteed assignable\n * to the hooks `createToolExecutor(...)` expects — without depending on\n * which individual hook types `@graphorin/tools` happens to re-export.\n *\n * @packageDocumentation\n */\n\nimport type { Memory } from '@graphorin/memory';\nimport {\n type ApiBoundaryGuardOptions,\n type AuditOnlyGuardOptions,\n createGuard,\n type StrictFullGuardOptions,\n} from '@graphorin/security/guard';\nimport {\n createDockerSandbox,\n createIsolatedVMSandbox,\n createWorkerThreadsSandbox,\n type SandboxImpl,\n} from '@graphorin/security/sandbox';\nimport { parseSecretRef, resolveSecret } from '@graphorin/security/secrets';\nimport type { ExecutorOptions } from '@graphorin/tools/executor';\nimport { countTokensHeuristic } from '@graphorin/tools/result';\n\n// --- Executor hook shapes (derived from `ExecutorOptions`) ------------------\n// Indexed access keeps these in lock-step with what the executor expects.\n\n/** The executor's secret-resolver hook: `{ resolve(key): Promise<SecretValue | null> }`. */\ntype SecretResolver = NonNullable<ExecutorOptions['secretResolver']>;\n/** A resolved `SecretValue`, or `null` when the backing store has no value. */\ntype SecretValueOrNull = Awaited<ReturnType<SecretResolver['resolve']>>;\n/** The executor's sandbox-dispatch resolver: `(policy) => Sandbox | null`. */\ntype SandboxResolver = NonNullable<ExecutorOptions['sandboxResolver']>;\n/** The executor's memory-guard factory: `(tier) => MemoryModificationGuard | null`. */\ntype MemoryGuardFactory = NonNullable<ExecutorOptions['memoryGuardFactory']>;\n/** The discriminator the guard factory receives (`'pure' | … | 'untrusted'`). */\ntype MemoryGuardTier = Parameters<MemoryGuardFactory>[0];\n/** A guard instance, or `null` when the tier needs no guard / cannot be built. */\ntype MemoryModificationGuardOrNull = ReturnType<MemoryGuardFactory>;\n/** The reader the guard hashes pre/post execution: `{ regions; read(region) }`. */\ntype MemoryRegionReader = NonNullable<ExecutorOptions['memoryRegionReader']>;\n/** The executor's **synchronous** truncation token counter: `{ count(text): number }`. */\ntype ToolTokenCounter = NonNullable<ExecutorOptions['tokenCounter']>;\n/** A value the executor's `streamingSink` receives (a `tool.execute.*` event). */\ntype ExecutorEvent = Parameters<NonNullable<ExecutorOptions['streamingSink']>>[0];\n\n// ---------------------------------------------------------------------------\n// Adapter A — `secretResolver`\n// ---------------------------------------------------------------------------\n\n/**\n * Backend that turns a secret reference into a resolved value (or\n * `null` when the backing store represents \"absent\" as null). Defaults\n * to `@graphorin/security`'s global resolver registry\n * (`resolveSecret(parseSecretRef(key))`), which **rejects** on a\n * malformed ref / unknown scheme / resolution failure and never returns\n * null — those rejections surface as tool errors through the executor's\n * secrets accessor.\n */\nexport type SecretBackend = (key: string) => Promise<SecretValueOrNull>;\n\n/** Options for {@link buildSecretResolver}. */\nexport interface SecretResolverOptions {\n /** Resolution backend. Defaults to `resolveSecret(parseSecretRef(key))`. */\n readonly backend?: SecretBackend;\n}\n\n/**\n * Adapter A — build the executor's `secretResolver` hook.\n *\n * This is a thin **value resolver**. The executor's secrets accessor\n * (`@graphorin/tools` `tool-context.ts`) already (1) enforces the\n * per-tool `secretsAllowed` ACL via `enforceSecretAcl(key)` inside a\n * `withChildToolSecretsContext(...)` scope and (2) maps a `null` result\n * onto the optional/required-secret error contract. This adapter must\n * therefore **not** re-implement ACL — it only resolves a key to a\n * value.\n */\nexport function buildSecretResolver(options: SecretResolverOptions = {}): SecretResolver {\n const backend: SecretBackend =\n options.backend ?? (async (key) => resolveSecret(parseSecretRef(key)));\n return Object.freeze({ resolve: backend });\n}\n\n// ---------------------------------------------------------------------------\n// Adapter B — `sandboxResolver`\n// ---------------------------------------------------------------------------\n\n/** Isolation kinds the default resolver knows how to construct. */\ntype IsolatedKind = 'worker-threads' | 'isolated-vm' | 'docker';\n\n/** Factory producing a concrete sandbox for a given isolation kind. */\nexport type SandboxFactory = () => SandboxImpl;\n\n/** Per-kind sandbox factory overrides (e.g. fakes in tests, custom adapters). */\nexport type SandboxFactoryMap = Partial<Record<IsolatedKind, SandboxFactory>>;\n\n/** Options for {@link buildSandboxResolver}. */\nexport interface SandboxResolverOptions {\n /**\n * Per-kind factory overrides. Defaults wire the three out-of-process\n * `@graphorin/security` adapters. Tests inject fakes so unit runs\n * never spawn real worker threads / containers (offline; R4). The\n * factories are invoked **lazily** — never at build time — so the\n * production default costs nothing until a tool actually needs that\n * isolation kind.\n */\n readonly factories?: SandboxFactoryMap;\n}\n\n/**\n * Adapter B — build the executor's `sandboxResolver`.\n *\n * Maps a `ResolvedSandboxPolicy.kind` to a cached `SandboxImpl`,\n * lazily constructing **one instance per kind**. Returns `null` for\n * `'none'` (run the tool inline) and for any kind without a registered\n * factory (custom kinds need a custom resolver).\n */\nexport function buildSandboxResolver(options: SandboxResolverOptions = {}): SandboxResolver {\n const factories: Record<IsolatedKind, SandboxFactory> = {\n 'worker-threads': options.factories?.['worker-threads'] ?? (() => createWorkerThreadsSandbox()),\n 'isolated-vm': options.factories?.['isolated-vm'] ?? (() => createIsolatedVMSandbox()),\n docker: options.factories?.docker ?? (() => createDockerSandbox()),\n };\n // Look up factories by the (open) policy kind. `SandboxKind` carries a\n // `(string & {})` member that defeats literal narrowing, so we index a\n // string-keyed view: a miss (incl. `'none'` and custom kinds) runs inline.\n const lookup = factories as Record<string, SandboxFactory | undefined>;\n const cache = new Map<string, SandboxImpl>();\n\n return (policy) => {\n const kind = policy.kind;\n const factory = lookup[kind];\n if (factory === undefined) {\n return null;\n }\n let impl = cache.get(kind);\n if (impl === undefined) {\n impl = factory();\n cache.set(kind, impl);\n }\n return impl;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Adapter C — `memoryGuardFactory` + `memoryRegionReader`\n// ---------------------------------------------------------------------------\n\n/** Options for {@link buildMemoryGuard}. */\nexport interface MemoryGuardOptions {\n /**\n * Options for the `'memory-aware'` API-boundary guard. The guard\n * requires an operation allowlist + a recorder of observed\n * `<scope>.<op>` calls, both supplied by the runtime once a run scope\n * exists (WI-03). When omitted, the factory returns `null` for the\n * `'memory-aware'` tier and the executor skips the guard.\n */\n readonly apiBoundary?: ApiBoundaryGuardOptions;\n /** Options for the `'unknown'` (audit-only) guard. */\n readonly auditOnly?: AuditOnlyGuardOptions;\n /** Options for the `'untrusted'` (strict-full) guard. */\n readonly strictFull?: StrictFullGuardOptions;\n /**\n * The region reader the guard hashes pre/post execution. The reader\n * is **scope-bound** (it materialises memory regions for a specific\n * run scope), so the runtime supplies it in WI-03 — the `Memory`\n * facade exposes no scope-free read surface. When omitted, the\n * executor skips the snapshot/verify cycle (it only runs the guard\n * when the reader is present).\n */\n readonly regionReader?: MemoryRegionReader;\n}\n\n/** The executor wiring produced by {@link buildMemoryGuard}. */\nexport interface MemoryGuardWiring {\n readonly memoryGuardFactory: MemoryGuardFactory;\n readonly memoryRegionReader?: MemoryRegionReader;\n}\n\n/**\n * Adapter C — build the executor's `memoryGuardFactory` (+ an optional\n * `memoryRegionReader`) from the agent's configured `Memory`.\n *\n * When `memory` is `undefined`, the factory returns `null` for every\n * tier and no reader is supplied — the executor degrades to its\n * audit-only baseline (the guard step is skipped).\n */\nexport function buildMemoryGuard(\n memory: Memory | undefined,\n options: MemoryGuardOptions = {},\n): MemoryGuardWiring {\n if (memory === undefined) {\n return Object.freeze({ memoryGuardFactory: () => null });\n }\n\n const memoryGuardFactory: MemoryGuardFactory = (\n tier: MemoryGuardTier,\n ): MemoryModificationGuardOrNull => {\n switch (tier) {\n case 'pure':\n case 'side-effecting-no-memory':\n return createGuard({ tier });\n case 'unknown':\n return createGuard({ tier, ...(options.auditOnly ?? {}) });\n case 'untrusted':\n return createGuard({ tier, ...(options.strictFull ?? {}) });\n case 'memory-aware':\n return options.apiBoundary === undefined\n ? null\n : createGuard({ tier, ...options.apiBoundary });\n default:\n return null;\n }\n };\n\n return Object.freeze(\n options.regionReader === undefined\n ? { memoryGuardFactory }\n : { memoryGuardFactory, memoryRegionReader: options.regionReader },\n );\n}\n\n/**\n * Construct a `MemoryRegionReader` from an explicit region list and a\n * read function. WI-03 uses this to bind the agent's `Memory` tiers to\n * named regions once a run scope exists; exposed here so the reader\n * contract is unit-testable in isolation.\n */\nexport function createMemoryRegionReader(\n regions: ReadonlyArray<string>,\n read: (region: string) => Promise<Uint8Array | string>,\n): MemoryRegionReader {\n return Object.freeze({ regions: Object.freeze([...regions]), read });\n}\n\n// ---------------------------------------------------------------------------\n// Adapter D — `tokenCounter` (the sync/async impedance mismatch)\n// ---------------------------------------------------------------------------\n\n/** A synchronous tokenizer (e.g. js-tiktoken's `encode`) returning a token array. */\nexport type SyncTokenize = (text: string) => { readonly length: number };\n\n/** Options for {@link buildToolTokenCounter}. */\nexport interface ToolTokenCounterOptions {\n /**\n * Optional synchronous tokenizer. When provided, the token count is\n * `tokenize(text).length`. Defaults to the `@graphorin/tools`\n * heuristic (4 chars/token).\n */\n readonly tokenize?: SyncTokenize;\n}\n\n/**\n * Adapter D — build the **synchronous** token counter the executor's\n * truncation pipeline requires.\n *\n * DESIGN DECISION (impedance mismatch §1.6.1): `@graphorin/tools`'\n * `TokenCounter.count(text): number` is synchronous, while\n * `@graphorin/provider`'s `createDefaultCounter().count(input): Promise<number>`\n * is asynchronous (and also accepts `Message[]`). The truncation path\n * runs inside `executeBatch` and must not `await`, so we never wire the\n * provider's async counter here. We default to the tools heuristic\n * (deterministic, offline) and allow a *synchronous* tokenizer (e.g.\n * js-tiktoken's `encode`) to be injected when a caller wants\n * vendor-accurate counts without blocking. The async provider counter\n * stays reserved for budget accounting elsewhere (WI-09), not for\n * executor truncation.\n */\nexport function buildToolTokenCounter(options: ToolTokenCounterOptions = {}): ToolTokenCounter {\n const { tokenize } = options;\n if (tokenize === undefined) {\n return countTokensHeuristic;\n }\n return Object.freeze({\n count: (text: string): number => tokenize(text).length,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Adapter E — `streamingSink` → `AgentEvent` stream bridge\n// ---------------------------------------------------------------------------\n\n/** Options for {@link createExecutorEventBridge}. */\nexport interface ExecutorEventBridgeOptions {\n /**\n * Maximum buffered events before backpressure drops the **oldest**\n * event (preserving the most-recent window). Defaults to `256`, matching\n * the executor's `streamingEventQueueDepth` default. Under normal\n * operation the consumer drains faster than the producer and nothing\n * is ever dropped; this bound is a safety valve.\n */\n readonly queueDepth?: number;\n}\n\n/**\n * Bridges the executor's synchronous `streamingSink` callback into an\n * async iterable the run loop can `for await` over.\n *\n * Single-producer / single-consumer: `sink` is the producer (called by\n * the executor); one `drain()` iterator is the consumer (the loop). It\n * is generic over the event type (defaulting to the executor's\n * `ExecutorEvent`) so the queue mechanics can be exercised in isolation.\n */\nexport interface ExecutorEventBridge<T = ExecutorEvent> {\n /**\n * Synchronous sink handed to the executor's `streamingSink`. Never\n * throws and never blocks; events emitted after {@link close} are\n * dropped.\n */\n readonly sink: (event: T) => void;\n /**\n * Async iterable yielding events in arrival order. Completes once\n * {@link close} has been called **and** the buffer is drained.\n */\n drain(): AsyncIterableIterator<T>;\n /** Signal end-of-stream; `drain()` finishes after the buffer drains. */\n close(): void;\n /** Count of events dropped under backpressure (oldest-dropped). */\n readonly dropped: number;\n}\n\n/**\n * Adapter E — bridge the executor's `streamingSink` callback to an\n * async iterator via a bounded queue.\n */\nexport function createExecutorEventBridge<T = ExecutorEvent>(\n options: ExecutorEventBridgeOptions = {},\n): ExecutorEventBridge<T> {\n const queueDepth = options.queueDepth ?? 256;\n const buffer: T[] = [];\n let pending: ((result: IteratorResult<T, undefined>) => void) | null = null;\n let closed = false;\n let dropped = 0;\n\n const sink = (event: T): void => {\n if (closed) {\n return;\n }\n if (pending !== null) {\n // A consumer is parked — hand the event off directly.\n const resolve = pending;\n pending = null;\n resolve({ value: event, done: false });\n return;\n }\n buffer.push(event);\n if (buffer.length > queueDepth) {\n buffer.shift();\n dropped += 1;\n }\n };\n\n async function* drain(): AsyncIterableIterator<T> {\n for (;;) {\n if (buffer.length > 0) {\n const event = buffer.shift();\n if (event !== undefined) {\n yield event;\n }\n continue;\n }\n if (closed) {\n return;\n }\n const next = await new Promise<IteratorResult<T, undefined>>((resolve) => {\n pending = resolve;\n });\n if (next.done === true) {\n return;\n }\n yield next.value;\n }\n }\n\n const close = (): void => {\n closed = true;\n if (pending !== null) {\n const resolve = pending;\n pending = null;\n resolve({ value: undefined, done: true });\n }\n };\n\n return Object.freeze({\n sink,\n drain,\n close,\n get dropped(): number {\n return dropped;\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAwFA,SAAgB,oBAAoB,UAAiC,EAAE,EAAkB;CACvF,MAAMA,UACJ,QAAQ,YAAY,OAAO,QAAQ,cAAc,eAAe,IAAI,CAAC;AACvE,QAAO,OAAO,OAAO,EAAE,SAAS,SAAS,CAAC;;;;;;;;;;AA2G5C,SAAgB,iBACd,QACA,UAA8B,EAAE,EACb;AACnB,KAAI,WAAW,OACb,QAAO,OAAO,OAAO,EAAE,0BAA0B,MAAM,CAAC;CAG1D,MAAMC,sBACJ,SACkC;AAClC,UAAQ,MAAR;GACE,KAAK;GACL,KAAK,2BACH,QAAO,YAAY,EAAE,MAAM,CAAC;GAC9B,KAAK,UACH,QAAO,YAAY;IAAE;IAAM,GAAI,QAAQ,aAAa,EAAE;IAAG,CAAC;GAC5D,KAAK,YACH,QAAO,YAAY;IAAE;IAAM,GAAI,QAAQ,cAAc,EAAE;IAAG,CAAC;GAC7D,KAAK,eACH,QAAO,QAAQ,gBAAgB,SAC3B,OACA,YAAY;IAAE;IAAM,GAAG,QAAQ;IAAa,CAAC;GACnD,QACE,QAAO;;;AAIb,QAAO,OAAO,OACZ,QAAQ,iBAAiB,SACrB,EAAE,oBAAoB,GACtB;EAAE;EAAoB,oBAAoB,QAAQ;EAAc,CACrE;;;;;;;;AASH,SAAgB,yBACd,SACA,MACoB;AACpB,QAAO,OAAO,OAAO;EAAE,SAAS,OAAO,OAAO,CAAC,GAAG,QAAQ,CAAC;EAAE;EAAM,CAAC;;;;;;;;;;;;;;;;;;AAoCtE,SAAgB,sBAAsB,UAAmC,EAAE,EAAoB;CAC7F,MAAM,EAAE,aAAa;AACrB,KAAI,aAAa,OACf,QAAO;AAET,QAAO,OAAO,OAAO,EACnB,QAAQ,SAAyB,SAAS,KAAK,CAAC,QACjD,CAAC;;;;;;AAkDJ,SAAgB,0BACd,UAAsC,EAAE,EAChB;CACxB,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAMC,SAAc,EAAE;CACtB,IAAIC,UAAmE;CACvE,IAAI,SAAS;CACb,IAAI,UAAU;CAEd,MAAM,QAAQ,UAAmB;AAC/B,MAAI,OACF;AAEF,MAAI,YAAY,MAAM;GAEpB,MAAM,UAAU;AAChB,aAAU;AACV,WAAQ;IAAE,OAAO;IAAO,MAAM;IAAO,CAAC;AACtC;;AAEF,SAAO,KAAK,MAAM;AAClB,MAAI,OAAO,SAAS,YAAY;AAC9B,UAAO,OAAO;AACd,cAAW;;;CAIf,gBAAgB,QAAkC;AAChD,WAAS;AACP,OAAI,OAAO,SAAS,GAAG;IACrB,MAAM,QAAQ,OAAO,OAAO;AAC5B,QAAI,UAAU,OACZ,OAAM;AAER;;AAEF,OAAI,OACF;GAEF,MAAM,OAAO,MAAM,IAAI,SAAuC,YAAY;AACxE,cAAU;KACV;AACF,OAAI,KAAK,SAAS,KAChB;AAEF,SAAM,KAAK;;;CAIf,MAAM,cAAoB;AACxB,WAAS;AACT,MAAI,YAAY,MAAM;GACpB,MAAM,UAAU;AAChB,aAAU;AACV,WAAQ;IAAE,OAAO;IAAW,MAAM;IAAM,CAAC;;;AAI7C,QAAO,OAAO,OAAO;EACnB;EACA;EACA;EACA,IAAI,UAAkB;AACpB,UAAO;;EAEV,CAAC"}
@@ -0,0 +1,37 @@
1
+ //#region src/tooling/catalogue.ts
2
+ /**
3
+ * A7 (SOTA): prompt-cache-aware tool-catalogue ordering.
4
+ *
5
+ * The per-step catalogue is `[...eagerTools, ...promotedTools, ...handoffTools]`.
6
+ * The eager prefix is already stable (the registry lists in insertion order), so
7
+ * the only source of churn is the promoted section: building it from the
8
+ * registry's `listDeferred` order means a tool promoted on a later step can sort
9
+ * BEFORE one promoted earlier, shifting the serialized suffix and invalidating
10
+ * the provider's prompt-cache prefix on every subsequent step.
11
+ *
12
+ * `orderPromotedTools` instead emits promoted tools in PROMOTION order (the
13
+ * insertion order of the promoted-name set), so a newly promoted tool always
14
+ * joins the END — the catalogue grows append-only and earlier promotions keep
15
+ * their byte position. A long-running assistant re-pays only for the new tail.
16
+ *
17
+ * @packageDocumentation
18
+ */
19
+ /**
20
+ * Resolve `promotedNames` (insertion-ordered) to the matching `deferred`
21
+ * entries, preserving promotion order. Names absent from the pool (e.g. a tool
22
+ * removed mid-run) are skipped.
23
+ */
24
+ function orderPromotedTools(promotedNames, deferred) {
25
+ const byName = /* @__PURE__ */ new Map();
26
+ for (const tool of deferred) byName.set(tool.name, tool);
27
+ const out = [];
28
+ for (const name of promotedNames) {
29
+ const tool = byName.get(name);
30
+ if (tool !== void 0) out.push(tool);
31
+ }
32
+ return out;
33
+ }
34
+
35
+ //#endregion
36
+ export { orderPromotedTools };
37
+ //# sourceMappingURL=catalogue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalogue.js","names":["out: T[]"],"sources":["../../src/tooling/catalogue.ts"],"sourcesContent":["/**\n * A7 (SOTA): prompt-cache-aware tool-catalogue ordering.\n *\n * The per-step catalogue is `[...eagerTools, ...promotedTools, ...handoffTools]`.\n * The eager prefix is already stable (the registry lists in insertion order), so\n * the only source of churn is the promoted section: building it from the\n * registry's `listDeferred` order means a tool promoted on a later step can sort\n * BEFORE one promoted earlier, shifting the serialized suffix and invalidating\n * the provider's prompt-cache prefix on every subsequent step.\n *\n * `orderPromotedTools` instead emits promoted tools in PROMOTION order (the\n * insertion order of the promoted-name set), so a newly promoted tool always\n * joins the END — the catalogue grows append-only and earlier promotions keep\n * their byte position. A long-running assistant re-pays only for the new tail.\n *\n * @packageDocumentation\n */\n\n/**\n * Resolve `promotedNames` (insertion-ordered) to the matching `deferred`\n * entries, preserving promotion order. Names absent from the pool (e.g. a tool\n * removed mid-run) are skipped.\n */\nexport function orderPromotedTools<T extends { readonly name: string }>(\n promotedNames: Iterable<string>,\n deferred: ReadonlyArray<T>,\n): ReadonlyArray<T> {\n const byName = new Map<string, T>();\n for (const tool of deferred) byName.set(tool.name, tool);\n const out: T[] = [];\n for (const name of promotedNames) {\n const tool = byName.get(name);\n if (tool !== undefined) out.push(tool);\n }\n return out;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,mBACd,eACA,UACkB;CAClB,MAAM,yBAAS,IAAI,KAAgB;AACnC,MAAK,MAAM,QAAQ,SAAU,QAAO,IAAI,KAAK,MAAM,KAAK;CACxD,MAAMA,MAAW,EAAE;AACnB,MAAK,MAAM,QAAQ,eAAe;EAChC,MAAM,OAAO,OAAO,IAAI,KAAK;AAC7B,MAAI,SAAS,OAAW,KAAI,KAAK,KAAK;;AAExC,QAAO"}