@graphorin/agent 0.6.0 → 0.7.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 (110) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +31 -11
  3. package/dist/errors/index.d.ts +17 -4
  4. package/dist/errors/index.d.ts.map +1 -1
  5. package/dist/errors/index.js +19 -3
  6. package/dist/errors/index.js.map +1 -1
  7. package/dist/factory.d.ts.map +1 -1
  8. package/dist/factory.js +153 -1930
  9. package/dist/factory.js.map +1 -1
  10. package/dist/fanout/index.d.ts +13 -1
  11. package/dist/fanout/index.d.ts.map +1 -1
  12. package/dist/fanout/index.js +13 -4
  13. package/dist/fanout/index.js.map +1 -1
  14. package/dist/index.d.ts +7 -6
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +11 -9
  17. package/dist/index.js.map +1 -1
  18. package/dist/lateral-leak/index.js +1 -1
  19. package/dist/package.js +6 -0
  20. package/dist/package.js.map +1 -0
  21. package/dist/run-state/index.d.ts +32 -6
  22. package/dist/run-state/index.d.ts.map +1 -1
  23. package/dist/run-state/index.js +46 -22
  24. package/dist/run-state/index.js.map +1 -1
  25. package/dist/runtime/agent-surface.js +122 -0
  26. package/dist/runtime/agent-surface.js.map +1 -0
  27. package/dist/runtime/agent-to-tool.d.ts +51 -0
  28. package/dist/runtime/agent-to-tool.d.ts.map +1 -0
  29. package/dist/runtime/agent-to-tool.js +145 -0
  30. package/dist/runtime/agent-to-tool.js.map +1 -0
  31. package/dist/runtime/approvals.js +0 -0
  32. package/dist/runtime/approvals.js.map +1 -0
  33. package/dist/runtime/dispatch.js +108 -0
  34. package/dist/runtime/dispatch.js.map +1 -0
  35. package/dist/runtime/executor-wiring.js +128 -0
  36. package/dist/runtime/executor-wiring.js.map +1 -0
  37. package/dist/runtime/fallback-chain.js +139 -0
  38. package/dist/runtime/fallback-chain.js.map +1 -0
  39. package/dist/runtime/handoff.js +307 -0
  40. package/dist/runtime/handoff.js.map +1 -0
  41. package/dist/runtime/messages.d.ts +22 -0
  42. package/dist/runtime/messages.d.ts.map +1 -0
  43. package/dist/runtime/messages.js +204 -0
  44. package/dist/runtime/messages.js.map +1 -0
  45. package/dist/runtime/provider-events.js +117 -0
  46. package/dist/runtime/provider-events.js.map +1 -0
  47. package/dist/runtime/run-compaction.js +210 -0
  48. package/dist/runtime/run-compaction.js.map +1 -0
  49. package/dist/runtime/run-finish.js +48 -0
  50. package/dist/runtime/run-finish.js.map +1 -0
  51. package/dist/runtime/run-gates.js +336 -0
  52. package/dist/runtime/run-gates.js.map +1 -0
  53. package/dist/runtime/run-init.js +81 -0
  54. package/dist/runtime/run-init.js.map +1 -0
  55. package/dist/runtime/run-input.js +46 -0
  56. package/dist/runtime/run-input.js.map +1 -0
  57. package/dist/runtime/step-catalogue.js +173 -0
  58. package/dist/runtime/step-catalogue.js.map +1 -0
  59. package/dist/runtime/tool-call-walk.js +189 -0
  60. package/dist/runtime/tool-call-walk.js.map +1 -0
  61. package/dist/runtime/tool-wiring.js +159 -0
  62. package/dist/runtime/tool-wiring.js.map +1 -0
  63. package/dist/tooling/adapters.js +1 -1
  64. package/dist/tooling/dataflow.js +1 -1
  65. package/dist/tooling/policy.js +2 -0
  66. package/dist/tooling/policy.js.map +1 -1
  67. package/dist/types.d.ts +63 -13
  68. package/dist/types.d.ts.map +1 -1
  69. package/package.json +20 -20
  70. package/src/errors/index.ts +320 -0
  71. package/src/evaluator-optimizer/index.ts +212 -0
  72. package/src/factory.ts +957 -0
  73. package/src/fallback/index.ts +108 -0
  74. package/src/fanout/index.ts +523 -0
  75. package/src/filters/index.ts +347 -0
  76. package/src/index.ts +180 -0
  77. package/src/internal/ids.ts +46 -0
  78. package/src/internal/usage-accumulator.ts +90 -0
  79. package/src/lateral-leak/causality-monitor.ts +221 -0
  80. package/src/lateral-leak/index.ts +35 -0
  81. package/src/lateral-leak/merge-guard.ts +151 -0
  82. package/src/lateral-leak/protocol-guard.ts +222 -0
  83. package/src/preferred-model/index.ts +210 -0
  84. package/src/progress/index.ts +238 -0
  85. package/src/run-state/index.ts +607 -0
  86. package/src/runtime/agent-surface.ts +218 -0
  87. package/src/runtime/agent-to-tool.ts +323 -0
  88. package/src/runtime/approvals.ts +0 -0
  89. package/src/runtime/dispatch.ts +183 -0
  90. package/src/runtime/executor-wiring.ts +331 -0
  91. package/src/runtime/fallback-chain.ts +250 -0
  92. package/src/runtime/handoff.ts +428 -0
  93. package/src/runtime/messages.ts +309 -0
  94. package/src/runtime/provider-events.ts +175 -0
  95. package/src/runtime/run-compaction.ts +288 -0
  96. package/src/runtime/run-finish.ts +93 -0
  97. package/src/runtime/run-gates.ts +419 -0
  98. package/src/runtime/run-init.ts +169 -0
  99. package/src/runtime/run-input.ts +102 -0
  100. package/src/runtime/step-catalogue.ts +338 -0
  101. package/src/runtime/tool-call-walk.ts +301 -0
  102. package/src/runtime/tool-wiring.ts +218 -0
  103. package/src/testing/replay-provider.ts +121 -0
  104. package/src/tooling/adapters.ts +403 -0
  105. package/src/tooling/catalogue.ts +36 -0
  106. package/src/tooling/dataflow.ts +171 -0
  107. package/src/tooling/plan.ts +123 -0
  108. package/src/tooling/policy.ts +67 -0
  109. package/src/tooling/registry-build.ts +191 -0
  110. package/src/types.ts +696 -0
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Handoff filter library - a small set of pure, composable functions
3
+ * that take the parent agent's message history and return a filtered
4
+ * subset suitable for forwarding to a child agent.
5
+ *
6
+ * Every filter pairs a `HandoffFilter` runtime function with a
7
+ * serializable {@link HandoffInputFilterDescriptor} so the JSONL
8
+ * session export (`@graphorin/sessions`) can replay the filter stack
9
+ * even after the runtime implementations evolve.
10
+ *
11
+ * Reasoning content is **always** stripped at the handoff boundary -
12
+ * `filters.compose(...)` guarantees `stripReasoning()` runs last so a
13
+ * caller-supplied filter cannot accidentally forward reasoning to a
14
+ * child agent. This is the confidentiality + token-economy default
15
+ * documented in the agent-loop reference.
16
+ *
17
+ * @packageDocumentation
18
+ */
19
+
20
+ import type {
21
+ HandoffFilter,
22
+ HandoffInputFilterDescriptor,
23
+ Message,
24
+ Sensitivity,
25
+ } from '@graphorin/core';
26
+ import { SENSITIVITY_ORDER } from '@graphorin/core';
27
+
28
+ const SENSITIVITY_RANK: Record<Sensitivity, number> = {
29
+ public: 0,
30
+ internal: 1,
31
+ secret: 2,
32
+ };
33
+
34
+ /**
35
+ * Return `true` iff the message's effective sensitivity is at most
36
+ * `maxTier` (a strictly weaker check than core's
37
+ * `acceptsSensitivity` because the filter library does not have
38
+ * the provider's `acceptsSensitivity[]` array - only a ceiling).
39
+ */
40
+ function withinTier(record: Sensitivity, maxTier: Sensitivity): boolean {
41
+ return SENSITIVITY_RANK[record] <= SENSITIVITY_RANK[maxTier];
42
+ }
43
+
44
+ void SENSITIVITY_ORDER;
45
+
46
+ /**
47
+ * A `HandoffFilter` paired with the serializable descriptor that
48
+ * round-trips through the JSONL session export. Authors of custom
49
+ * filters return one of these via `filters.custom({...})`.
50
+ *
51
+ * @stable
52
+ */
53
+ export interface DescribedFilter extends HandoffFilter {
54
+ readonly descriptor: HandoffInputFilterDescriptor;
55
+ }
56
+
57
+ function makeFilter(fn: HandoffFilter, descriptor: HandoffInputFilterDescriptor): DescribedFilter {
58
+ const wrapped = ((history: readonly Message[]): readonly Message[] =>
59
+ fn(history)) as DescribedFilter;
60
+ Object.defineProperty(wrapped, 'descriptor', { value: descriptor, enumerable: true });
61
+ return wrapped;
62
+ }
63
+
64
+ function isReasoningPart(part: unknown): boolean {
65
+ if (typeof part !== 'object' || part === null) return false;
66
+ const t = (part as { readonly type?: unknown }).type;
67
+ return t === 'reasoning';
68
+ }
69
+
70
+ function stripReasoningFromMessage(msg: Message): Message {
71
+ if (msg.role === 'system' || msg.role === 'tool') return msg;
72
+ const content = msg.content;
73
+ if (typeof content === 'string') return msg;
74
+ const filtered = content.filter((part) => !isReasoningPart(part));
75
+ if (filtered.length === content.length) return msg;
76
+ if (msg.role === 'assistant') {
77
+ return { ...msg, content: filtered };
78
+ }
79
+ return { ...msg, content: filtered };
80
+ }
81
+
82
+ /**
83
+ * Keep the parent's system prompt and the last `n` non-system
84
+ * messages. Default `n = 10` per DEC-146 / RB-40 security-first
85
+ * compose.
86
+ *
87
+ * @stable
88
+ */
89
+ export function lastN(n = 10): DescribedFilter {
90
+ if (!Number.isFinite(n) || n <= 0) {
91
+ throw new RangeError(`filters.lastN: n must be a positive integer (got ${String(n)})`);
92
+ }
93
+ return makeFilter(
94
+ (history) => {
95
+ const out: Message[] = [];
96
+ for (const msg of history) {
97
+ if (msg.role === 'system') {
98
+ out.push(msg);
99
+ }
100
+ }
101
+ const nonSystem: Message[] = history.filter((m) => m.role !== 'system');
102
+ const tail = nonSystem.slice(Math.max(0, nonSystem.length - n));
103
+ return [...out, ...tail];
104
+ },
105
+ { kind: 'last-n', meta: { n } },
106
+ );
107
+ }
108
+
109
+ /**
110
+ * Keep only the parent's system prompt and the most recent user
111
+ * message. Useful for simple sub-agents that only need the question.
112
+ *
113
+ * @stable
114
+ */
115
+ export function lastUser(): DescribedFilter {
116
+ return makeFilter(
117
+ (history) => {
118
+ const out: Message[] = [];
119
+ let lastUserIdx = -1;
120
+ for (let i = history.length - 1; i >= 0; i--) {
121
+ const msg = history[i];
122
+ if (msg && msg.role === 'user') {
123
+ lastUserIdx = i;
124
+ break;
125
+ }
126
+ }
127
+ for (const msg of history) {
128
+ if (msg.role === 'system') out.push(msg);
129
+ }
130
+ if (lastUserIdx >= 0) {
131
+ const m = history[lastUserIdx];
132
+ if (m) out.push(m);
133
+ }
134
+ return out;
135
+ },
136
+ { kind: 'last-user' },
137
+ );
138
+ }
139
+
140
+ /**
141
+ * The full unfiltered history. Discouraged - security-conscious
142
+ * callers should pick {@link lastN} or {@link bySensitivity} instead
143
+ * (a sub-agent rarely needs the parent's entire conversation).
144
+ *
145
+ * @stable
146
+ */
147
+ export function full(): DescribedFilter {
148
+ return makeFilter((history) => history.slice(), { kind: 'full' });
149
+ }
150
+
151
+ /**
152
+ * Replace the parent's history with a single system message carrying
153
+ * the supplied summary. Used by callers that wire in an LLM-based
154
+ * summarizer outside the framework.
155
+ *
156
+ * @stable
157
+ */
158
+ export function summary(text: string): DescribedFilter {
159
+ return makeFilter(
160
+ () => [
161
+ {
162
+ role: 'system',
163
+ content: `[Summary of parent conversation]\n${text}`,
164
+ },
165
+ ],
166
+ { kind: 'summary', meta: { summaryLength: text.length } },
167
+ );
168
+ }
169
+
170
+ /**
171
+ * Drop messages whose effective sensitivity ceiling exceeds
172
+ * `maxTier`. Messages without sensitivity metadata default to
173
+ * `'public'` and are always kept.
174
+ *
175
+ * The framework currently records sensitivity at the
176
+ * `MessageContent` part level via the `inboundTrust` / `secret`
177
+ * annotations. v0.1 ships a coarse-grained heuristic: a message is
178
+ * kept iff every text part's content does not contain the literal
179
+ * `[REDACTED:secret]` token AND every part's annotated sensitivity
180
+ * is acceptable to `maxTier`. Operators that need a stricter
181
+ * filter compose the function with `stripSensitiveOutputs()` or a
182
+ * custom predicate.
183
+ *
184
+ * @stable
185
+ */
186
+ export function bySensitivity(args: { readonly maxTier?: Sensitivity } = {}): DescribedFilter {
187
+ const maxTier: Sensitivity = args.maxTier ?? 'public';
188
+ return makeFilter(
189
+ (history) => {
190
+ const out: Message[] = [];
191
+ for (const msg of history) {
192
+ const content = msg.role === 'system' ? msg.content : msg.content;
193
+ const text = typeof content === 'string' ? content : JSON.stringify(content);
194
+ if (text.includes('[REDACTED:secret]') && !withinTier('secret', maxTier)) {
195
+ continue;
196
+ }
197
+ out.push(msg);
198
+ }
199
+ return out;
200
+ },
201
+ { kind: 'sensitivity-filter', meta: { maxTier } },
202
+ );
203
+ }
204
+
205
+ /**
206
+ * Strip every `ReasoningContent` part from each message. Always
207
+ * applied at the handoff boundary (the `compose(...)` helper appends
208
+ * this filter automatically).
209
+ *
210
+ * @stable
211
+ */
212
+ export function stripReasoning(): DescribedFilter {
213
+ return makeFilter((history) => history.map(stripReasoningFromMessage), {
214
+ kind: 'strip-reasoning',
215
+ });
216
+ }
217
+
218
+ /**
219
+ * Strip tool messages whose `content` carries the literal token
220
+ * `[REDACTED:secret]` or whose `secret` annotation marks the body as
221
+ * sensitive. Conservative-by-design: the agent runtime tags
222
+ * sensitive tool outputs at session-write time so this filter has
223
+ * stable bytes to scan against.
224
+ *
225
+ * @stable
226
+ */
227
+ export function stripSensitiveOutputs(): DescribedFilter {
228
+ return makeFilter(
229
+ (history) => {
230
+ const out: Message[] = [];
231
+ let stripped = 0;
232
+ for (const msg of history) {
233
+ if (msg.role === 'tool') {
234
+ const text = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
235
+ if (text.includes('[REDACTED:')) {
236
+ stripped += 1;
237
+ continue;
238
+ }
239
+ }
240
+ out.push(msg);
241
+ }
242
+ void stripped;
243
+ return out;
244
+ },
245
+ { kind: 'strip-sensitive-outputs' },
246
+ );
247
+ }
248
+
249
+ /**
250
+ * Drop every assistant `toolCalls` array AND every `tool` message.
251
+ * Useful when a sub-agent should only see the textual conversation.
252
+ *
253
+ * @stable
254
+ */
255
+ export function stripToolCalls(): DescribedFilter {
256
+ return makeFilter(
257
+ (history) => {
258
+ const out: Message[] = [];
259
+ for (const msg of history) {
260
+ if (msg.role === 'tool') continue;
261
+ if (msg.role === 'assistant' && msg.toolCalls && msg.toolCalls.length > 0) {
262
+ const { toolCalls: _toolCalls, ...rest } = msg;
263
+ out.push(rest);
264
+ continue;
265
+ }
266
+ out.push(msg);
267
+ }
268
+ return out;
269
+ },
270
+ { kind: 'strip-tool-calls' },
271
+ );
272
+ }
273
+
274
+ /**
275
+ * Compose multiple filters left-to-right. The composer **always**
276
+ * appends `stripReasoning()` at the end so reasoning content never
277
+ * crosses a handoff boundary regardless of caller intent.
278
+ *
279
+ * @stable
280
+ */
281
+ export function compose(...filters: ReadonlyArray<HandoffFilter>): DescribedFilter {
282
+ const stripper = stripReasoning();
283
+ const ordered: ReadonlyArray<HandoffFilter> = [...filters, stripper];
284
+ const descriptors: HandoffInputFilterDescriptor[] = filters.map((f) => {
285
+ const desc = (f as DescribedFilter).descriptor;
286
+ return desc ?? { kind: 'custom' };
287
+ });
288
+ descriptors.push(stripper.descriptor);
289
+ return makeFilter(
290
+ (history) => {
291
+ let current: readonly Message[] = history;
292
+ for (const filter of ordered) {
293
+ current = filter(current);
294
+ }
295
+ return current;
296
+ },
297
+ { kind: 'compose', meta: { steps: descriptors } },
298
+ );
299
+ }
300
+
301
+ /**
302
+ * Wrap a caller-supplied function as a {@link DescribedFilter} with
303
+ * the canonical `'custom'` descriptor.
304
+ *
305
+ * @stable
306
+ */
307
+ export function custom(
308
+ fn: HandoffFilter,
309
+ meta?: Readonly<Record<string, unknown>>,
310
+ ): DescribedFilter {
311
+ return makeFilter(fn, meta !== undefined ? { kind: 'custom', meta } : { kind: 'custom' });
312
+ }
313
+
314
+ /**
315
+ * The canonical default applied by the agent runtime to every
316
+ * `Agent.toTool(...)` and `handoff(...)` invocation when the caller
317
+ * does not supply an explicit filter.
318
+ *
319
+ * @stable
320
+ */
321
+ export function defaultHandoffFilter(): DescribedFilter {
322
+ return compose(lastN(10), stripSensitiveOutputs());
323
+ }
324
+
325
+ /**
326
+ * Pure `HandoffInputFilterDescriptor` for callers that just need the
327
+ * descriptor without instantiating the runtime function (e.g. the
328
+ * sessions package's lenient-forward-parse path).
329
+ *
330
+ * @stable
331
+ */
332
+ export const FILTER_KIND_CUSTOM: HandoffInputFilterDescriptor = { kind: 'custom' };
333
+
334
+ /** Aggregate module export. */
335
+ export const filters = {
336
+ lastN,
337
+ lastUser,
338
+ full,
339
+ summary,
340
+ bySensitivity,
341
+ stripReasoning,
342
+ stripSensitiveOutputs,
343
+ stripToolCalls,
344
+ compose,
345
+ custom,
346
+ defaultHandoffFilter,
347
+ };
package/src/index.ts ADDED
@@ -0,0 +1,180 @@
1
+ /**
2
+ * `@graphorin/agent` - agent runtime for the Graphorin framework.
3
+ *
4
+ * The package owns:
5
+ *
6
+ * - The `createAgent({...})` factory that wires the typed
7
+ * `model -> tool calls -> model` loop, the streaming event
8
+ * surface, the steering / followUp queues, durable HITL
9
+ * approvals, multi-agent handoffs (`Agent.toTool`, the filter
10
+ * library, secrets isolation), the agent-level model fallback
11
+ * chain, the per-step compaction lifecycle, the per-tool
12
+ * preferred-model resolution, the structured progress-artifact
13
+ * APIs, and the lateral-leak defense layer.
14
+ * - `runStateToJSON / runStateFromJSON` helpers for caller-managed
15
+ * durable HITL.
16
+ * - The handoff filter library.
17
+ * - The agent-step-level fan-out helpers (`runFanOut`,
18
+ * `evaluatorOptimizer`, the progress IO surface).
19
+ * - Pure decision functions consulted by the loop
20
+ * (`isAgentFallbackEligible`, `resolvePreferredModel`).
21
+ * - Lateral-leak primitives (`CausalityMonitor`,
22
+ * `MergeAgentSidewaysInjectionGuard` decision helpers,
23
+ * protocol-injection guard).
24
+ *
25
+ * The full documentation lives in the package `README.md`.
26
+ *
27
+ * @packageDocumentation
28
+ */
29
+
30
+ /** Canonical version constant, derived from `package.json` at build time. */
31
+ import pkg from '../package.json' with { type: 'json' };
32
+
33
+ export const VERSION: string = pkg.version;
34
+
35
+ // AG-2 / SDF-4: the canonical guardrail contract lives in
36
+ // `@graphorin/security`; re-exported here for config ergonomics.
37
+ export type {
38
+ GuardrailContext,
39
+ GuardrailDefinition,
40
+ GuardrailResult,
41
+ InputGuardrail,
42
+ OutputGuardrail,
43
+ } from '@graphorin/security/guardrails';
44
+ export {
45
+ AgentResolutionError,
46
+ AgentRuntimeError,
47
+ type AgentRuntimeErrorCode,
48
+ EvaluatorOptimizerConfigError,
49
+ InvalidAgentConfigError,
50
+ InvalidPreferredModelError,
51
+ MergeBlockedError,
52
+ MultipleHandoffsInStepError,
53
+ ProgressWriteError,
54
+ ProtocolInjectionRejectError,
55
+ ProviderMiddlewareOrderError,
56
+ RunStateMalformedError,
57
+ RunStateVersionUnsupportedError,
58
+ SubAgentResumeTargetNotFoundError,
59
+ ToolNotFoundError,
60
+ } from './errors/index.js';
61
+ export {
62
+ type EvaluatorCallable,
63
+ type EvaluatorOptimizerOptions,
64
+ type EvaluatorOptimizerOutcome,
65
+ type EvaluatorOutcome,
66
+ evaluatorOptimizer,
67
+ type GeneratorCallable,
68
+ type Rubric,
69
+ } from './evaluator-optimizer/index.js';
70
+ export { createAgent } from './factory.js';
71
+ export {
72
+ type AgentFallbackEligibility,
73
+ type AgentFallbackPolicy,
74
+ type AgentFallbackReason,
75
+ isAgentFallbackEligible,
76
+ } from './fallback/index.js';
77
+ export {
78
+ type ChildResult,
79
+ type FanOutOptions,
80
+ type FanOutResult,
81
+ type MergeStrategy,
82
+ type PerChildBudget,
83
+ runFanOut,
84
+ } from './fanout/index.js';
85
+ export {
86
+ bySensitivity,
87
+ compose,
88
+ custom,
89
+ type DescribedFilter,
90
+ defaultHandoffFilter,
91
+ FILTER_KIND_CUSTOM,
92
+ filters,
93
+ full,
94
+ lastN,
95
+ lastUser,
96
+ stripReasoning,
97
+ stripSensitiveOutputs,
98
+ stripToolCalls,
99
+ summary,
100
+ } from './filters/index.js';
101
+ export {
102
+ CausalityMonitor,
103
+ type CausalityMonitorCheck,
104
+ type CausalityMonitorConfig,
105
+ type CausalityMonitorStrictness,
106
+ type ChildTrustInput,
107
+ type ContentOriginKind,
108
+ computeSourceTrust,
109
+ DEFAULT_DENIAL_PATTERNS,
110
+ DEFAULT_MAX_CHAIN_DEPTH,
111
+ evaluateMerge,
112
+ type GuardOutcome,
113
+ guardOutboundContent,
114
+ type MergeBiasDecision,
115
+ type MergeGuardConfig,
116
+ type ProtocolBoundary,
117
+ type ProtocolEscapePolicy,
118
+ type ProtocolGuardConfig,
119
+ resolvePolicy,
120
+ type TrustClass,
121
+ } from './lateral-leak/index.js';
122
+ export {
123
+ type PreferredModelResolution,
124
+ pickTopTierAcrossTools,
125
+ type ResolvePreferredModelInput,
126
+ resolvePreferredModel,
127
+ } from './preferred-model/index.js';
128
+ export {
129
+ createProgressIO,
130
+ type ProgressIO,
131
+ type ProgressIOConfig,
132
+ type ProgressReadOptions,
133
+ type ProgressWriteOptions,
134
+ } from './progress/index.js';
135
+ export {
136
+ addModelUsage,
137
+ aggregateUsageFromByModel,
138
+ completedToolCallsFromState,
139
+ createInitialRunState,
140
+ deserializeRunState,
141
+ RUN_STATE_SCHEMA_MAJOR_SUPPORTED,
142
+ RUN_STATE_SCHEMA_VERSION,
143
+ runStateFromJSON,
144
+ runStateToJSON,
145
+ type SerializedPendingSubRun,
146
+ type SerializedRunState,
147
+ type SerializeRunStateOptions,
148
+ serializeRunState,
149
+ } from './run-state/index.js';
150
+ export {
151
+ getSubAgentToolRefs,
152
+ SUBAGENT_TOOL,
153
+ type SubAgentFoldTaint,
154
+ type SubAgentToolRefs,
155
+ } from './runtime/agent-to-tool.js';
156
+ export { foldChildRunUsage } from './runtime/messages.js';
157
+ export {
158
+ createReplayProvider,
159
+ type ReplayProviderOptions,
160
+ } from './testing/replay-provider.js';
161
+ export type {
162
+ AbortOptions,
163
+ Agent,
164
+ AgentCallOptions,
165
+ AgentCapability,
166
+ AgentConfig,
167
+ AgentInput,
168
+ AgentToToolOptions,
169
+ CompactionApiResult,
170
+ CompactOptions,
171
+ HandoffEntry,
172
+ OutputSpec,
173
+ PostCompactionHook,
174
+ PrepareStepHook,
175
+ PrepareStepOverrides,
176
+ ResponseVerifier,
177
+ ResumeDirective,
178
+ SkillsRegistryLike,
179
+ VerifierResult,
180
+ } from './types.js';
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Tiny id helpers used by the agent runtime. URL-safe Base32 alphabet,
3
+ * monotonic timestamp prefix to keep ids loosely sortable.
4
+ *
5
+ * @packageDocumentation
6
+ */
7
+
8
+ import { randomBytes } from 'node:crypto';
9
+
10
+ const ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
11
+
12
+ function pad(value: string, width: number): string {
13
+ return value.padStart(width, '0');
14
+ }
15
+
16
+ /**
17
+ * 7-character Base32 tail backed by 35 bits of CSPRNG entropy. We read
18
+ * 5 random bytes (40 bits) and consume 5 bits per emitted character
19
+ * MSB-first; the leftover bits are discarded. Sourcing from
20
+ * `randomBytes` (uniform over `[0, 256)`) guarantees a uniform
21
+ * distribution over `ALPHABET` - no modulo bias.
22
+ */
23
+ function randomTail(): string {
24
+ const buf = randomBytes(5);
25
+ let bitBuffer = 0;
26
+ let bitCount = 0;
27
+ let cursor = 0;
28
+ let out = '';
29
+ for (let i = 0; i < 7; i++) {
30
+ while (bitCount < 5) {
31
+ bitBuffer = (bitBuffer << 8) | (buf[cursor++] ?? 0);
32
+ bitCount += 8;
33
+ }
34
+ bitCount -= 5;
35
+ const idx = (bitBuffer >>> bitCount) & 0x1f;
36
+ out += ALPHABET[idx];
37
+ }
38
+ return out;
39
+ }
40
+
41
+ /** Generate a fresh, sortable, URL-safe identifier. */
42
+ export function newId(prefix?: string): string {
43
+ const ts = pad(Date.now().toString(32).toUpperCase(), 9);
44
+ const id = `${ts}${randomTail()}`;
45
+ return prefix ? `${prefix}_${id}` : id;
46
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Minimal {@link UsageAccumulator} implementation used by the agent
3
+ * runtime when the consumer does not pass a custom one through
4
+ * `RunContext.usage`. Tracks per-model breakdown and a running
5
+ * aggregate.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ import type { ModelUsage, Usage, UsageAccumulator, UsageSnapshot } from '@graphorin/core';
11
+
12
+ export class InMemoryUsageAccumulator implements UsageAccumulator {
13
+ #aggregate: Usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
14
+ readonly #byModel = new Map<string, ModelUsage>();
15
+
16
+ get total(): Usage {
17
+ return this.#aggregate;
18
+ }
19
+
20
+ get byModel(): ReadonlyMap<string, ModelUsage> {
21
+ return this.#byModel;
22
+ }
23
+
24
+ add(modelId: string, usage: Usage): void {
25
+ const prev = this.#byModel.get(modelId);
26
+ if (prev === undefined) {
27
+ this.#byModel.set(modelId, {
28
+ modelId,
29
+ promptTokens: usage.promptTokens,
30
+ completionTokens: usage.completionTokens,
31
+ totalTokens: usage.totalTokens,
32
+ callCount: 1,
33
+ ...(usage.reasoningTokens !== undefined ? { reasoningTokens: usage.reasoningTokens } : {}),
34
+ ...(usage.cachedReadTokens !== undefined
35
+ ? { cachedReadTokens: usage.cachedReadTokens }
36
+ : {}),
37
+ ...(usage.cacheWriteTokens !== undefined
38
+ ? { cacheWriteTokens: usage.cacheWriteTokens }
39
+ : {}),
40
+ ...(usage.cost !== undefined ? { cost: usage.cost } : {}),
41
+ });
42
+ } else {
43
+ const merged: ModelUsage = {
44
+ modelId,
45
+ promptTokens: prev.promptTokens + usage.promptTokens,
46
+ completionTokens: prev.completionTokens + usage.completionTokens,
47
+ totalTokens: prev.totalTokens + usage.totalTokens,
48
+ callCount: prev.callCount + 1,
49
+ ...sumOptional('reasoningTokens', prev.reasoningTokens, usage.reasoningTokens),
50
+ ...sumOptional('cachedReadTokens', prev.cachedReadTokens, usage.cachedReadTokens),
51
+ ...sumOptional('cacheWriteTokens', prev.cacheWriteTokens, usage.cacheWriteTokens),
52
+ };
53
+ this.#byModel.set(modelId, merged);
54
+ }
55
+ this.#aggregate = {
56
+ promptTokens: this.#aggregate.promptTokens + usage.promptTokens,
57
+ completionTokens: this.#aggregate.completionTokens + usage.completionTokens,
58
+ totalTokens: this.#aggregate.totalTokens + usage.totalTokens,
59
+ ...sumOptional('reasoningTokens', this.#aggregate.reasoningTokens, usage.reasoningTokens),
60
+ ...sumOptional('cachedReadTokens', this.#aggregate.cachedReadTokens, usage.cachedReadTokens),
61
+ ...sumOptional('cacheWriteTokens', this.#aggregate.cacheWriteTokens, usage.cacheWriteTokens),
62
+ };
63
+ }
64
+
65
+ reset(): void {
66
+ this.#aggregate = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
67
+ this.#byModel.clear();
68
+ }
69
+
70
+ snapshot(): UsageSnapshot {
71
+ return {
72
+ total: { ...this.#aggregate },
73
+ byModel: Array.from(this.#byModel.values()).map((m) => ({ ...m })),
74
+ };
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Sum an optional token field: present in the output only when at least
80
+ * one side carries it (so a run that never saw the field keeps the same
81
+ * serialized shape as before the field existed).
82
+ */
83
+ function sumOptional<K extends string>(
84
+ key: K,
85
+ a: number | undefined,
86
+ b: number | undefined,
87
+ ): Partial<Record<K, number>> {
88
+ if (a === undefined && b === undefined) return {};
89
+ return { [key]: (a ?? 0) + (b ?? 0) } as Partial<Record<K, number>>;
90
+ }