@graphorin/core 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 (112) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +14 -8
  3. package/dist/channels/channels.d.ts.map +1 -1
  4. package/dist/channels/index.d.ts +2 -2
  5. package/dist/channels/index.js +2 -2
  6. package/dist/channels/pause.d.ts +47 -2
  7. package/dist/channels/pause.d.ts.map +1 -1
  8. package/dist/channels/pause.js +62 -2
  9. package/dist/channels/pause.js.map +1 -1
  10. package/dist/contracts/checkpoint-store.d.ts +97 -1
  11. package/dist/contracts/checkpoint-store.d.ts.map +1 -1
  12. package/dist/contracts/checkpoint-store.js.map +1 -1
  13. package/dist/contracts/index.d.ts +5 -5
  14. package/dist/contracts/index.js +2 -1
  15. package/dist/contracts/memory-store.d.ts +59 -2
  16. package/dist/contracts/memory-store.d.ts.map +1 -1
  17. package/dist/contracts/provider.d.ts +20 -6
  18. package/dist/contracts/provider.d.ts.map +1 -1
  19. package/dist/contracts/session-store.d.ts +10 -2
  20. package/dist/contracts/session-store.d.ts.map +1 -1
  21. package/dist/contracts/tool.d.ts +75 -1
  22. package/dist/contracts/tool.d.ts.map +1 -1
  23. package/dist/contracts/tool.js +57 -0
  24. package/dist/contracts/tool.js.map +1 -0
  25. package/dist/contracts/tracer.d.ts +53 -5
  26. package/dist/contracts/tracer.d.ts.map +1 -1
  27. package/dist/contracts/tracer.js.map +1 -1
  28. package/dist/index.d.ts +14 -9
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +11 -7
  31. package/dist/index.js.map +1 -1
  32. package/dist/package.js +6 -0
  33. package/dist/package.js.map +1 -0
  34. package/dist/types/agent-event-wire.d.ts +74 -0
  35. package/dist/types/agent-event-wire.d.ts.map +1 -0
  36. package/dist/types/agent-event-wire.js +130 -0
  37. package/dist/types/agent-event-wire.js.map +1 -0
  38. package/dist/types/agent-event.d.ts +48 -6
  39. package/dist/types/agent-event.d.ts.map +1 -1
  40. package/dist/types/index.d.ts +3 -2
  41. package/dist/types/index.js +2 -1
  42. package/dist/types/memory.d.ts +11 -0
  43. package/dist/types/memory.d.ts.map +1 -1
  44. package/dist/types/run.d.ts +86 -4
  45. package/dist/types/run.d.ts.map +1 -1
  46. package/dist/types/run.js.map +1 -1
  47. package/dist/types/tool.d.ts +10 -0
  48. package/dist/types/tool.d.ts.map +1 -1
  49. package/dist/types/usage.d.ts +11 -1
  50. package/dist/types/usage.d.ts.map +1 -1
  51. package/dist/types/usage.js.map +1 -1
  52. package/dist/utils/binary-json.d.ts +165 -0
  53. package/dist/utils/binary-json.d.ts.map +1 -0
  54. package/dist/utils/binary-json.js +240 -0
  55. package/dist/utils/binary-json.js.map +1 -0
  56. package/dist/utils/index.d.ts +2 -1
  57. package/dist/utils/index.js +2 -1
  58. package/dist/utils/validation.d.ts +10 -1
  59. package/dist/utils/validation.d.ts.map +1 -1
  60. package/dist/utils/validation.js +1 -1
  61. package/dist/utils/validation.js.map +1 -1
  62. package/package.json +9 -7
  63. package/src/channels/channels.ts +206 -0
  64. package/src/channels/directive.ts +41 -0
  65. package/src/channels/dispatch.ts +37 -0
  66. package/src/channels/durable.ts +151 -0
  67. package/src/channels/index.ts +62 -0
  68. package/src/channels/pause.ts +216 -0
  69. package/src/contracts/auth-token-store.ts +42 -0
  70. package/src/contracts/checkpoint-store.ts +256 -0
  71. package/src/contracts/embedder.ts +42 -0
  72. package/src/contracts/eval-scorer.ts +44 -0
  73. package/src/contracts/index.ts +112 -0
  74. package/src/contracts/local-provider-trust.ts +33 -0
  75. package/src/contracts/logger.ts +61 -0
  76. package/src/contracts/memory-store.ts +187 -0
  77. package/src/contracts/oauth-server-store.ts +78 -0
  78. package/src/contracts/preferred-model.ts +56 -0
  79. package/src/contracts/provider.ts +316 -0
  80. package/src/contracts/reasoning-retention.ts +52 -0
  81. package/src/contracts/redaction-validator.ts +56 -0
  82. package/src/contracts/sandbox.ts +70 -0
  83. package/src/contracts/secret-ref.ts +22 -0
  84. package/src/contracts/secret-value.ts +117 -0
  85. package/src/contracts/secrets-store.ts +90 -0
  86. package/src/contracts/session-store.ts +163 -0
  87. package/src/contracts/token-counter.ts +23 -0
  88. package/src/contracts/tool.ts +397 -0
  89. package/src/contracts/tracer.ts +219 -0
  90. package/src/contracts/trigger-store.ts +40 -0
  91. package/src/index.ts +23 -0
  92. package/src/types/agent-event-wire.ts +193 -0
  93. package/src/types/agent-event.ts +579 -0
  94. package/src/types/handoff.ts +111 -0
  95. package/src/types/index.ts +148 -0
  96. package/src/types/memory.ts +427 -0
  97. package/src/types/message.ts +174 -0
  98. package/src/types/run.ts +312 -0
  99. package/src/types/sensitivity.ts +35 -0
  100. package/src/types/session-scope.ts +18 -0
  101. package/src/types/stop-condition.ts +108 -0
  102. package/src/types/tool-call.ts +24 -0
  103. package/src/types/tool.ts +324 -0
  104. package/src/types/usage.ts +120 -0
  105. package/src/types/workflow-event.ts +132 -0
  106. package/src/utils/assert-never.ts +24 -0
  107. package/src/utils/async-context.ts +55 -0
  108. package/src/utils/binary-json.ts +425 -0
  109. package/src/utils/hash.ts +122 -0
  110. package/src/utils/index.ts +57 -0
  111. package/src/utils/streams.ts +233 -0
  112. package/src/utils/validation.ts +82 -0
@@ -0,0 +1,324 @@
1
+ import type { ToolCall } from './tool-call.js';
2
+
3
+ /**
4
+ * Sandbox isolation level requested for a tool's `execute` method.
5
+ *
6
+ * The exact semantics live in `@graphorin/security`; downstream packages
7
+ * type their config field as `SandboxPolicy` so they don't take a security
8
+ * dependency just to type their inputs.
9
+ *
10
+ * @stable
11
+ */
12
+ export type SandboxPolicy = 'none' | 'sandboxed' | 'isolated' | 'docker';
13
+
14
+ /**
15
+ * Memory-modification guard tier requested for a tool's `execute` method.
16
+ *
17
+ * Mirrors the tier classification consumed by `@graphorin/security`'s
18
+ * memory-modification guard so downstream packages can type their tool
19
+ * metadata against this discriminator without a hard dependency on the
20
+ * security package.
21
+ *
22
+ * - `'pure'` - no side effects of any kind.
23
+ * - `'side-effecting-no-memory'` - observable side effects outside of
24
+ * the framework's memory tiers (e.g. external HTTP).
25
+ * - `'memory-aware'` - mutates the framework's memory
26
+ * tiers via the sanctioned `ctx.memory.*` surface only.
27
+ * - `'unknown'` - no declaration; the runtime applies
28
+ * the audit-only baseline.
29
+ * - `'untrusted'` - third-party / untrusted skill code;
30
+ * the runtime forces the strictest snapshot policy.
31
+ *
32
+ * @stable
33
+ */
34
+ export type MemoryGuardTier =
35
+ | 'pure'
36
+ | 'side-effecting-no-memory'
37
+ | 'memory-aware'
38
+ | 'unknown'
39
+ | 'untrusted';
40
+
41
+ /**
42
+ * Side-effect classification declared by a tool author.
43
+ *
44
+ * Surfaced uniformly by the tool dispatcher, downstream session
45
+ * cassette layers, retry middleware, and approval-policy derivations.
46
+ *
47
+ * - `'pure'` - deterministic; same `(input, ctx)` always
48
+ * yields the same output; no I/O of any kind.
49
+ * - `'read-only'` - queries external systems but never
50
+ * mutates them (e.g. database SELECT, HTTP GET).
51
+ * - `'side-effecting'` - mutates state inside the agent's logical
52
+ * boundary (e.g. memory writes, cache writes).
53
+ * - `'external-stateful'` - mutates state outside the agent's
54
+ * boundary that other systems can observe (e.g. issue creation,
55
+ * message dispatch, payment).
56
+ *
57
+ * @stable
58
+ */
59
+ export type SideEffectClass = 'pure' | 'read-only' | 'side-effecting' | 'external-stateful';
60
+
61
+ /**
62
+ * Inbound prompt-injection sanitization policy applied to a tool's
63
+ * result body before it reaches the conversation history.
64
+ *
65
+ * - `'pass-through'` - no scan; bytes-equal forwarding
66
+ * (the trusted-built-in default).
67
+ * - `'detect-and-flag'` - scan; emit a flag span attribute
68
+ * + audit row but do not modify the body.
69
+ * - `'detect-and-strip'` - replace each match with the
70
+ * `[REDACTED:imperative-pattern]` literal token.
71
+ * - `'detect-and-wrap'` - wrap the body in the
72
+ * `<<<untrusted_content>>>` envelope without stripping matches.
73
+ * - `'detect-and-strip-and-wrap'` - both strip matches and wrap the
74
+ * resulting body (the untrusted-source default).
75
+ *
76
+ * @stable
77
+ */
78
+ export type InboundSanitizationPolicy =
79
+ | 'pass-through'
80
+ | 'detect-and-flag'
81
+ | 'detect-and-strip'
82
+ | 'detect-and-wrap'
83
+ | 'detect-and-strip-and-wrap';
84
+
85
+ /**
86
+ * Result-envelope truncation strategy applied to a tool's assembled
87
+ * output before it reaches the conversation history.
88
+ *
89
+ * - `'middle'` - keep head and tail; insert annotation in the
90
+ * middle (the default).
91
+ * - `'tail'` - keep the tail; insert annotation at the
92
+ * head.
93
+ * - `'spill-to-file'` - keep the head; spill the un-truncated body
94
+ * to a per-run artifact file; insert annotation with the artifact
95
+ * path.
96
+ * - `'summarize'` - invoke the agent's configured summarizer
97
+ * and replace the body with the summary.
98
+ *
99
+ * @stable
100
+ */
101
+ export type TruncationStrategy = 'middle' | 'tail' | 'spill-to-file' | 'summarize';
102
+
103
+ /**
104
+ * Trust class assigned to a registered tool. The class is computed at
105
+ * registration time from the registration source and the declared
106
+ * sandbox policy; downstream layers (sanitization, audit) read the
107
+ * class to pick the right default policy.
108
+ *
109
+ * @stable
110
+ */
111
+ export type ToolTrustClass =
112
+ | 'first-party-built-in'
113
+ | 'first-party-user-defined'
114
+ | 'skill-trusted'
115
+ | 'skill-untrusted'
116
+ | 'mcp-derived'
117
+ | 'web-search';
118
+
119
+ /**
120
+ * Source descriptor attached to a `Tool` registration. Mirrors the
121
+ * registration-time provenance the dispatcher uses to derive the
122
+ * trust class and to compute the four collision audit row kinds.
123
+ *
124
+ * @stable
125
+ */
126
+ export type ToolSource =
127
+ | { readonly kind: 'first-party' }
128
+ | { readonly kind: 'built-in'; readonly subsystem: string }
129
+ | {
130
+ readonly kind: 'skill';
131
+ readonly skillName: string;
132
+ readonly trustLevel: 'trusted' | 'untrusted';
133
+ }
134
+ | { readonly kind: 'mcp'; readonly serverIdentity: string }
135
+ | { readonly kind: 'web-search'; readonly providerName: string };
136
+
137
+ /**
138
+ * Single chunk of streamed tool content. Streaming-hint tools emit one
139
+ * chunk per `ctx.streamContent(...)` call; the executor concatenates
140
+ * the chunks into the assembled `output` per the buffer-becomes-output
141
+ * discipline.
142
+ *
143
+ * @stable
144
+ */
145
+ export type ContentChunk =
146
+ | { readonly kind: 'text'; readonly text: string }
147
+ | { readonly kind: 'json-delta'; readonly path: string; readonly value: unknown }
148
+ | { readonly kind: 'image'; readonly data: Uint8Array; readonly mediaType: string };
149
+
150
+ // `ToolCall` is defined in `./tool-call.ts` to break the
151
+ // `message.ts <-> tool.ts` import cycle; re-exported here so existing
152
+ // `@graphorin/core` import paths keep resolving `ToolCall` from this
153
+ // module. The local import lives at the top of the file.
154
+ export type { ToolCall };
155
+
156
+ /**
157
+ * The successful outcome of a tool invocation, returned to the model.
158
+ *
159
+ * @stable
160
+ */
161
+ export interface ToolResult<TOutput = unknown> {
162
+ readonly toolCallId: string;
163
+ readonly toolName: string;
164
+ readonly output: TOutput;
165
+ /**
166
+ * Optional content parts to append to the conversation (images, files,
167
+ * etc.). Tools that emit binary results use this field instead of trying
168
+ * to encode the binary into `output`.
169
+ */
170
+ readonly contentParts?: readonly import('./message.js').MessageContent[];
171
+ readonly durationMs: number;
172
+ /**
173
+ * Set when the tool's output was large enough to be stored behind a
174
+ * handle (the `'spill-to-file'` truncation strategy, or - later - an MCP
175
+ * `resource_link`) instead of being inlined in full. The runtime inlines
176
+ * only the bounded {@link ResultHandle.preview} and lets the model fetch
177
+ * the rest on demand via the built-in `read_result` tool. Absent for
178
+ * results that were inlined directly.
179
+ */
180
+ readonly resultHandle?: ResultHandle;
181
+ }
182
+
183
+ /**
184
+ * An opaque, run-scoped reference to a large tool result that was stored
185
+ * out of the conversation buffer rather than inlined in full. The agent
186
+ * inlines {@link preview} (plus a retrieval hint) and registers the
187
+ * built-in `read_result` tool so the model can page through the full
188
+ * artifact behind {@link uri} on demand - keeping large results out of the
189
+ * context window (P1-4).
190
+ *
191
+ * @stable
192
+ */
193
+ export interface ResultHandle {
194
+ /**
195
+ * Opaque, run-scoped URI - e.g. `graphorin-spill:<runId>/<toolCallId>.json`
196
+ * for a spill artifact. Never a raw filesystem path: the reader resolves
197
+ * it within the configured artifact root, so the model cannot use it to
198
+ * read arbitrary files.
199
+ */
200
+ readonly uri: string;
201
+ /** Backing store kind. `'spill-file'` today; `'resource-link'` is reserved for MCP (WI-13). */
202
+ readonly kind: 'spill-file' | 'resource-link';
203
+ /** A bounded preview of the full body (already inlined alongside the handle). */
204
+ readonly preview: string;
205
+ /** Total byte size of the full stored artifact, when known. */
206
+ readonly bytes?: number;
207
+ /** MIME type of the stored artifact, when known. */
208
+ readonly mediaType?: string;
209
+ /**
210
+ * Trust class of the tool that PRODUCED the stored body (TL-6).
211
+ * `read_result` re-applies inbound sanitization and dataflow
212
+ * provenance by this class, so an untrusted spill cannot launder to
213
+ * trusted through the built-in reader.
214
+ */
215
+ readonly producerTrustClass?: ToolTrustClass;
216
+ }
217
+
218
+ /**
219
+ * Discriminator used by `ToolError.kind`. The list is exhaustive: any new
220
+ * kind must extend the union here and every `assertNever` switch.
221
+ *
222
+ * @stable
223
+ */
224
+ export type ToolErrorKind =
225
+ | 'approval_denied'
226
+ | 'sandbox_violation'
227
+ | 'timeout'
228
+ | 'invalid_input'
229
+ | 'invalid_output'
230
+ | 'execution_failed'
231
+ | 'unknown_tool'
232
+ | 'aborted'
233
+ | 'inbound_sanitization_blocked'
234
+ | 'dataflow_policy_blocked'
235
+ | 'capability_blocked'
236
+ | 'rate_limited';
237
+
238
+ /**
239
+ * Model-facing recovery guidance attached to a {@link ToolError} (C3).
240
+ * Practitioner evidence converges on these two fields being what changes
241
+ * model behaviour after a failure:
242
+ *
243
+ * - `'retry_later'` - transient; the same call is expected to work
244
+ * after a pause (rate limits, timeouts).
245
+ * - `'check_input'` - the arguments are wrong; re-read the schema
246
+ * and fix them before retrying.
247
+ * - `'try_alternative'` - this tool/approach failed non-transiently;
248
+ * try a different tool or strategy.
249
+ * - `'report_to_user'` - a policy/authorization stop; do not retry,
250
+ * surface the situation instead.
251
+ *
252
+ * @stable
253
+ */
254
+ export type RecoveryHint = 'retry_later' | 'check_input' | 'try_alternative' | 'report_to_user';
255
+
256
+ /**
257
+ * The unsuccessful outcome of a tool invocation. The model sees a textual
258
+ * representation of `message`; the runtime sees the typed shape.
259
+ *
260
+ * @stable
261
+ */
262
+ export interface ToolError {
263
+ readonly toolCallId: string;
264
+ readonly toolName: string;
265
+ readonly kind: ToolErrorKind;
266
+ readonly message: string;
267
+ /** Optional underlying cause (chained errors). */
268
+ readonly cause?: unknown;
269
+ /** Optional remediation hint for human readers. */
270
+ readonly hint?: string;
271
+ /**
272
+ * Whether retrying the SAME call can plausibly succeed (C3). Stamped
273
+ * from the error kind by the executor; the harness-side transparent
274
+ * retry consults it together with the tool's side-effect safety.
275
+ */
276
+ readonly recoverable?: boolean;
277
+ /** Model-facing recovery guidance derived from the error kind (C3). */
278
+ readonly recoveryHint?: RecoveryHint;
279
+ }
280
+
281
+ /**
282
+ * Either a `ToolResult` or a `ToolError`. The runtime always returns one
283
+ * of the two - there is no implicit "tool fell through" outcome.
284
+ *
285
+ * @stable
286
+ */
287
+ export type ToolOutcome<TOutput = unknown> = ToolResult<TOutput> | ToolError;
288
+
289
+ /**
290
+ * Pending approval bookkeeping: a tool that needed human confirmation
291
+ * before execution. Stored on `RunState.pendingApprovals` until the
292
+ * caller resumes the run with a granted/denied decision.
293
+ *
294
+ * @stable
295
+ */
296
+ export interface ToolApproval {
297
+ readonly toolCallId: string;
298
+ readonly toolName: string;
299
+ readonly args: unknown;
300
+ readonly reason?: string;
301
+ readonly requestedAt: string;
302
+ /**
303
+ * W-001: set when this approval belongs to a PARKED sub-agent run.
304
+ * It is the PARENT's toolCallId of the parked handoff / sub-agent
305
+ * call (the `RunState.pendingSubRuns` key), never a child-local id.
306
+ * Operators echo it back on the matching `ApprovalDecision` so
307
+ * resume decisions match on the composite (toolCallId,
308
+ * subRunToolCallId) key - child-local toolCallIds of two different
309
+ * children may collide.
310
+ */
311
+ readonly subRunToolCallId?: string;
312
+ }
313
+
314
+ /**
315
+ * A `ToolCall` paired with its outcome and execution metadata. Captured
316
+ * on `RunState.completedToolCalls` after a successful or failed run.
317
+ *
318
+ * @stable
319
+ */
320
+ export interface CompletedToolCall<TOutput = unknown> {
321
+ readonly call: ToolCall;
322
+ readonly outcome: ToolOutcome<TOutput>;
323
+ readonly stepNumber: number;
324
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Token / cost metrics for a single LLM call.
3
+ *
4
+ * `cost` is optional because the framework cannot compute it without a
5
+ * pricing snapshot - providers/middleware fill it in (e.g. through the
6
+ * separate `@graphorin/pricing` package) when the snapshot is available.
7
+ *
8
+ * @stable
9
+ */
10
+ export interface Usage {
11
+ /**
12
+ * Total input tokens for the call, INCLUDING any prompt-cache reads and
13
+ * writes (`cachedReadTokens` / `cacheWriteTokens` are informational
14
+ * subsets, not additions). This matches the context size the model saw.
15
+ */
16
+ promptTokens: number;
17
+ completionTokens: number;
18
+ /**
19
+ * Reasoning tokens billed IN ADDITION to `completionTokens` (exclusive;
20
+ * adapters that receive an inclusive total split it so the sum stays
21
+ * exact). Cost formulas may add this to the output leg without
22
+ * double-counting.
23
+ */
24
+ reasoningTokens?: number;
25
+ /**
26
+ * Prompt tokens served from the provider's prompt cache (a subset of
27
+ * `promptTokens`), billed at the discounted cache-read rate.
28
+ */
29
+ cachedReadTokens?: number;
30
+ /**
31
+ * Prompt tokens written to the provider's prompt cache this call (a
32
+ * subset of `promptTokens`), billed at the cache-write premium where the
33
+ * provider charges one (Anthropic does; OpenAI does not report writes).
34
+ */
35
+ cacheWriteTokens?: number;
36
+ totalTokens: number;
37
+ cost?: Cost;
38
+ }
39
+
40
+ /**
41
+ * Money figure attached to a `Usage`. Always carries a 3-letter currency
42
+ * code so that consumers can perform aggregation safely.
43
+ *
44
+ * @stable
45
+ */
46
+ export interface Cost {
47
+ /**
48
+ * Amount in WHOLE units of `currency` - for USD that is dollars, and
49
+ * fractional values are expected (a typical LLM call costs a fraction
50
+ * of a cent, e.g. `0.0042`). This is deliberately NOT "minor units" /
51
+ * cents (W-045): the canonical producer -
52
+ * `calculateCost` in `@graphorin/pricing` - and every consumer
53
+ * (`CostTracker` in `@graphorin/observability`, the memory
54
+ * consolidator's `costUsd` budget, persisted checkpoints) already
55
+ * operate in whole currency units, and sub-cent per-call figures make
56
+ * minor units impractical. Do not divide by 100.
57
+ */
58
+ amount: number;
59
+ /** ISO-4217 currency code; default `'USD'`. */
60
+ currency: string;
61
+ }
62
+
63
+ /**
64
+ * Per-model breakdown used by aggregators (e.g. `CostTracker` in
65
+ * `@graphorin/observability`).
66
+ *
67
+ * @stable
68
+ */
69
+ export interface ModelUsage {
70
+ modelId: string;
71
+ promptTokens: number;
72
+ completionTokens: number;
73
+ reasoningTokens?: number;
74
+ cachedReadTokens?: number;
75
+ cacheWriteTokens?: number;
76
+ totalTokens: number;
77
+ cost?: Cost;
78
+ callCount: number;
79
+ }
80
+
81
+ /**
82
+ * A live accumulator of token / cost figures. Implementations live in
83
+ * `@graphorin/observability`. The contract sits here so every package
84
+ * (agent, workflow, server, …) can type a parameter as `UsageAccumulator`
85
+ * without taking an observability dependency.
86
+ *
87
+ * @stable
88
+ */
89
+ export interface UsageAccumulator {
90
+ /** Current rolled-up totals across every model that has been added. */
91
+ readonly total: Usage;
92
+ /** Per-model breakdown; preserves call counts for observability. */
93
+ readonly byModel: ReadonlyMap<string, ModelUsage>;
94
+
95
+ /** Add a single LLM-call usage record under the given model id. */
96
+ add(modelId: string, usage: Usage): void;
97
+ /** Reset the accumulator to a zeroed state. */
98
+ reset(): void;
99
+ /** Render an immutable snapshot suitable for serialization / span attrs. */
100
+ snapshot(): UsageSnapshot;
101
+ }
102
+
103
+ /**
104
+ * Immutable, JSON-serializable snapshot of a `UsageAccumulator`.
105
+ *
106
+ * @stable
107
+ */
108
+ export interface UsageSnapshot {
109
+ readonly total: Usage;
110
+ readonly byModel: readonly ModelUsage[];
111
+ }
112
+
113
+ /**
114
+ * Returns a fresh, zeroed `Usage` value.
115
+ *
116
+ * @stable
117
+ */
118
+ export function zeroUsage(): Usage {
119
+ return { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
120
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Discriminated union of every event produced by `Workflow.execute(...)`
3
+ * and `Workflow.resume(...)`.
4
+ *
5
+ * Generic over `TState` so that downstream consumers can discriminate on
6
+ * the workflow's typed state shape.
7
+ *
8
+ * @stable
9
+ */
10
+ export type WorkflowEvent<TState = unknown> =
11
+ | WorkflowStartEvent
12
+ | WorkflowStepStartEvent<TState>
13
+ | WorkflowStepEndEvent<TState>
14
+ | WorkflowTaskStartEvent
15
+ | WorkflowTaskEndEvent
16
+ | WorkflowChannelUpdateEvent<TState>
17
+ | WorkflowCheckpointWrittenEvent
18
+ | WorkflowSuspendedEvent<TState>
19
+ | WorkflowResumedEvent<TState>
20
+ | WorkflowEndEvent<TState>
21
+ | WorkflowErrorEvent
22
+ | WorkflowCustomEvent;
23
+
24
+ /** @stable */
25
+ export interface WorkflowStartEvent {
26
+ readonly type: 'workflow.start';
27
+ readonly threadId: string;
28
+ readonly workflowId: string;
29
+ }
30
+
31
+ /** @stable */
32
+ export interface WorkflowStepStartEvent<TState = unknown> {
33
+ readonly type: 'workflow.step.start';
34
+ readonly stepNumber: number;
35
+ readonly state: TState;
36
+ }
37
+
38
+ /** @stable */
39
+ export interface WorkflowStepEndEvent<TState = unknown> {
40
+ readonly type: 'workflow.step.end';
41
+ readonly stepNumber: number;
42
+ readonly state: TState;
43
+ }
44
+
45
+ /** @stable */
46
+ export interface WorkflowTaskStartEvent {
47
+ readonly type: 'workflow.task.start';
48
+ readonly stepNumber: number;
49
+ readonly taskId: string;
50
+ readonly nodeName: string;
51
+ }
52
+
53
+ /** @stable */
54
+ export interface WorkflowTaskEndEvent {
55
+ readonly type: 'workflow.task.end';
56
+ readonly stepNumber: number;
57
+ readonly taskId: string;
58
+ readonly nodeName: string;
59
+ readonly status: 'completed' | 'failed' | 'paused';
60
+ readonly durationMs: number;
61
+ }
62
+
63
+ /** @stable */
64
+ export interface WorkflowChannelUpdateEvent<TState = unknown> {
65
+ readonly type: 'workflow.channel.update';
66
+ readonly stepNumber: number;
67
+ readonly channel: keyof TState & string;
68
+ readonly version: number;
69
+ /**
70
+ * The merged value, carried ONLY for `ephemeral` channels
71
+ * (workflow-07): their values are wiped from state before the next
72
+ * planning round, so this event is the one place a consumer can
73
+ * observe them. Persistent channels omit it - read the state instead.
74
+ */
75
+ readonly value?: unknown;
76
+ }
77
+
78
+ /** @stable */
79
+ export interface WorkflowCheckpointWrittenEvent {
80
+ readonly type: 'workflow.checkpoint.written';
81
+ readonly checkpointId: string;
82
+ readonly stepNumber: number;
83
+ }
84
+
85
+ /**
86
+ * Workflow paused - for HITL approvals or programmatic `pause(value)`
87
+ * calls. Carries the value passed to `pause(...)` so the caller can
88
+ * choose how to surface the prompt to the user.
89
+ *
90
+ * @stable
91
+ */
92
+ export interface WorkflowSuspendedEvent<TState = unknown> {
93
+ readonly type: 'workflow.suspended';
94
+ readonly threadId: string;
95
+ readonly stepNumber: number;
96
+ readonly state: TState;
97
+ readonly value: unknown;
98
+ }
99
+
100
+ /** @stable */
101
+ export interface WorkflowResumedEvent<TState = unknown> {
102
+ readonly type: 'workflow.resumed';
103
+ readonly threadId: string;
104
+ readonly stepNumber: number;
105
+ readonly state: TState;
106
+ }
107
+
108
+ /** @stable */
109
+ export interface WorkflowEndEvent<TState = unknown> {
110
+ readonly type: 'workflow.end';
111
+ readonly threadId: string;
112
+ readonly state: TState;
113
+ }
114
+
115
+ /** @stable */
116
+ export interface WorkflowErrorEvent {
117
+ readonly type: 'workflow.error';
118
+ readonly threadId: string;
119
+ readonly error: { readonly message: string; readonly code: string };
120
+ }
121
+
122
+ /**
123
+ * Application-defined event emitted from inside a workflow node via
124
+ * `ctx.emit(name, payload)`. The runtime never produces these on its own.
125
+ *
126
+ * @stable
127
+ */
128
+ export interface WorkflowCustomEvent {
129
+ readonly type: 'workflow.custom';
130
+ readonly name: string;
131
+ readonly payload: unknown;
132
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Exhaustiveness helper. Place at the end of a `switch (...)` over a
3
+ * discriminated union: TypeScript narrows the operand to `never` if
4
+ * every variant is handled. Adding a new variant later turns the call
5
+ * site into a compile error - the regression net for our event unions.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * function describe(event: AgentEvent): string {
10
+ * switch (event.type) {
11
+ * case 'agent.start': return 'started';
12
+ * case 'agent.end': return 'ended';
13
+ * // ... every other variant ...
14
+ * default:
15
+ * return assertNever(event, 'Unhandled AgentEvent variant');
16
+ * }
17
+ * }
18
+ * ```
19
+ *
20
+ * @stable
21
+ */
22
+ export function assertNever(value: never, message?: string): never {
23
+ throw new Error(message ?? `graphorin: unhandled discriminated-union variant: ${String(value)}`);
24
+ }
@@ -0,0 +1,55 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+
3
+ /**
4
+ * Thin typed wrapper around Node's `AsyncLocalStorage`. Used to thread a
5
+ * tool-execution / request-scoped context through the async stack
6
+ * without explicit parameter passing.
7
+ *
8
+ * The wrapper exists because:
9
+ * - The Node API requires a fresh `AsyncLocalStorage<T>` per scope; this
10
+ * helper centralizes the construction with consistent typing.
11
+ * - Downstream packages (security, tools) want a single canonical
12
+ * constructor so that their `getStore()` code paths share the same
13
+ * identity (matters for HMR / multi-realm setups).
14
+ *
15
+ * @stable
16
+ */
17
+ export interface AsyncContext<T> {
18
+ /** Run `fn` inside a fresh scope carrying `value`. */
19
+ run<R>(value: T, fn: () => R): R;
20
+ /** Run `fn` inside a fresh scope carrying `value` (async-friendly). */
21
+ runAsync<R>(value: T, fn: () => Promise<R>): Promise<R>;
22
+ /** Get the value of the current scope, or `undefined` outside one. */
23
+ get(): T | undefined;
24
+ /** Replace the value of the current scope (advanced; rarely needed). */
25
+ enterWith(value: T): void;
26
+ /** Exit any in-flight scope (advanced; rarely needed). */
27
+ disable(): void;
28
+ }
29
+
30
+ /**
31
+ * Construct a typed `AsyncContext`. The optional `name` is surfaced in
32
+ * the diagnostics channel of `AsyncLocalStorage` (debugging only).
33
+ *
34
+ * @stable
35
+ */
36
+ export function createAsyncContext<T>(_name?: string): AsyncContext<T> {
37
+ const als = new AsyncLocalStorage<T>();
38
+ return {
39
+ run<R>(value: T, fn: () => R): R {
40
+ return als.run(value, fn);
41
+ },
42
+ runAsync<R>(value: T, fn: () => Promise<R>): Promise<R> {
43
+ return als.run(value, fn);
44
+ },
45
+ get(): T | undefined {
46
+ return als.getStore();
47
+ },
48
+ enterWith(value: T): void {
49
+ als.enterWith(value);
50
+ },
51
+ disable(): void {
52
+ als.disable();
53
+ },
54
+ };
55
+ }