@graphorin/core 0.6.1 → 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.
- package/CHANGELOG.md +82 -0
- package/README.md +8 -5
- package/dist/channels/index.d.ts +2 -2
- package/dist/channels/index.js +2 -2
- package/dist/channels/pause.d.ts +47 -2
- package/dist/channels/pause.d.ts.map +1 -1
- package/dist/channels/pause.js +62 -2
- package/dist/channels/pause.js.map +1 -1
- package/dist/contracts/checkpoint-store.d.ts +97 -1
- package/dist/contracts/checkpoint-store.d.ts.map +1 -1
- package/dist/contracts/checkpoint-store.js.map +1 -1
- package/dist/contracts/index.d.ts +5 -5
- package/dist/contracts/index.js +2 -1
- package/dist/contracts/memory-store.d.ts +59 -2
- package/dist/contracts/memory-store.d.ts.map +1 -1
- package/dist/contracts/provider.d.ts +20 -6
- package/dist/contracts/provider.d.ts.map +1 -1
- package/dist/contracts/session-store.d.ts +10 -2
- package/dist/contracts/session-store.d.ts.map +1 -1
- package/dist/contracts/tool.d.ts +75 -1
- package/dist/contracts/tool.d.ts.map +1 -1
- package/dist/contracts/tool.js +57 -0
- package/dist/contracts/tool.js.map +1 -0
- package/dist/contracts/tracer.d.ts +53 -5
- package/dist/contracts/tracer.d.ts.map +1 -1
- package/dist/contracts/tracer.js.map +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/dist/types/agent-event-wire.d.ts +74 -0
- package/dist/types/agent-event-wire.d.ts.map +1 -0
- package/dist/types/agent-event-wire.js +130 -0
- package/dist/types/agent-event-wire.js.map +1 -0
- package/dist/types/agent-event.d.ts +48 -6
- package/dist/types/agent-event.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/types/index.js +2 -1
- package/dist/types/memory.d.ts +11 -0
- package/dist/types/memory.d.ts.map +1 -1
- package/dist/types/run.d.ts +86 -4
- package/dist/types/run.d.ts.map +1 -1
- package/dist/types/run.js.map +1 -1
- package/dist/types/tool.d.ts +10 -0
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/types/usage.d.ts +11 -1
- package/dist/types/usage.d.ts.map +1 -1
- package/dist/types/usage.js.map +1 -1
- package/dist/utils/binary-json.d.ts +165 -0
- package/dist/utils/binary-json.d.ts.map +1 -0
- package/dist/utils/binary-json.js +240 -0
- package/dist/utils/binary-json.js.map +1 -0
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/validation.d.ts +10 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +1 -1
- package/dist/utils/validation.js.map +1 -1
- package/package.json +9 -7
- package/src/channels/channels.ts +206 -0
- package/src/channels/directive.ts +41 -0
- package/src/channels/dispatch.ts +37 -0
- package/src/channels/durable.ts +151 -0
- package/src/channels/index.ts +62 -0
- package/src/channels/pause.ts +216 -0
- package/src/contracts/auth-token-store.ts +42 -0
- package/src/contracts/checkpoint-store.ts +256 -0
- package/src/contracts/embedder.ts +42 -0
- package/src/contracts/eval-scorer.ts +44 -0
- package/src/contracts/index.ts +112 -0
- package/src/contracts/local-provider-trust.ts +33 -0
- package/src/contracts/logger.ts +61 -0
- package/src/contracts/memory-store.ts +187 -0
- package/src/contracts/oauth-server-store.ts +78 -0
- package/src/contracts/preferred-model.ts +56 -0
- package/src/contracts/provider.ts +316 -0
- package/src/contracts/reasoning-retention.ts +52 -0
- package/src/contracts/redaction-validator.ts +56 -0
- package/src/contracts/sandbox.ts +70 -0
- package/src/contracts/secret-ref.ts +22 -0
- package/src/contracts/secret-value.ts +117 -0
- package/src/contracts/secrets-store.ts +90 -0
- package/src/contracts/session-store.ts +163 -0
- package/src/contracts/token-counter.ts +23 -0
- package/src/contracts/tool.ts +397 -0
- package/src/contracts/tracer.ts +219 -0
- package/src/contracts/trigger-store.ts +40 -0
- package/src/index.ts +23 -0
- package/src/types/agent-event-wire.ts +193 -0
- package/src/types/agent-event.ts +579 -0
- package/src/types/handoff.ts +111 -0
- package/src/types/index.ts +148 -0
- package/src/types/memory.ts +427 -0
- package/src/types/message.ts +174 -0
- package/src/types/run.ts +312 -0
- package/src/types/sensitivity.ts +35 -0
- package/src/types/session-scope.ts +18 -0
- package/src/types/stop-condition.ts +108 -0
- package/src/types/tool-call.ts +24 -0
- package/src/types/tool.ts +324 -0
- package/src/types/usage.ts +120 -0
- package/src/types/workflow-event.ts +132 -0
- package/src/utils/assert-never.ts +24 -0
- package/src/utils/async-context.ts +55 -0
- package/src/utils/binary-json.ts +425 -0
- package/src/utils/hash.ts +122 -0
- package/src/utils/index.ts +57 -0
- package/src/utils/streams.ts +233 -0
- 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
|
+
}
|