@directive-run/knowledge 1.13.0 → 1.15.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 (43) hide show
  1. package/README.md +50 -15
  2. package/ai/ai-adapters.md +2 -0
  3. package/ai/ai-agents-streaming.md +182 -149
  4. package/ai/ai-budget-resilience.md +299 -132
  5. package/ai/ai-communication.md +215 -197
  6. package/ai/ai-debug-observability.md +253 -173
  7. package/ai/ai-guardrails-memory.md +185 -153
  8. package/ai/ai-mcp-rag.md +199 -199
  9. package/ai/ai-multi-agent.md +247 -153
  10. package/ai/ai-orchestrator.md +344 -114
  11. package/ai/ai-security.md +282 -180
  12. package/ai/ai-tasks.md +3 -1
  13. package/ai/ai-testing-evals.md +357 -256
  14. package/core/anti-patterns.md +9 -2
  15. package/core/constraints.md +6 -2
  16. package/core/core-patterns.md +2 -0
  17. package/core/error-boundaries.md +2 -0
  18. package/core/history.md +2 -0
  19. package/core/multi-module.md +8 -3
  20. package/core/naming.md +15 -6
  21. package/core/plugins.md +2 -0
  22. package/core/react-adapter.md +250 -174
  23. package/core/resolvers.md +2 -0
  24. package/core/schema-types.md +2 -0
  25. package/core/system-api.md +2 -0
  26. package/core/testing.md +251 -143
  27. package/examples/checkers.ts +15 -16
  28. package/examples/contact-form.ts +2 -2
  29. package/examples/counter-react.ts +1 -1
  30. package/examples/counter-svelte.ts +1 -1
  31. package/examples/counter-vue.ts +1 -1
  32. package/examples/counter.ts +1 -1
  33. package/examples/data-triggers.ts +4 -4
  34. package/examples/feature-flags.ts +2 -2
  35. package/examples/form-wizard.ts +2 -2
  36. package/examples/newsletter.ts +2 -2
  37. package/examples/server.ts +2 -2
  38. package/examples/shopping-cart.ts +1 -1
  39. package/examples/topic-guard.ts +1 -1
  40. package/package.json +3 -3
  41. package/sitemap.md +26 -3
  42. package/examples/debounce-constraints.ts +0 -95
  43. package/examples/multi-module.ts +0 -58
package/README.md CHANGED
@@ -1,24 +1,44 @@
1
1
  # @directive-run/knowledge
2
2
 
3
- Knowledge files, examples, and validation scripts for the [Directive](https://directive.run) runtime.
3
+ The **source of truth** for all Directive coding knowledge — used by Claude Code skills, Cursor / Copilot / Windsurf / Cline / Codex rules files, the website's `/llms.txt` route, and the programmatic API for downstream tooling.
4
4
 
5
- This package is the **source of truth** for all Directive coding knowledge used by:
6
- - `@directive-run/cli` – generates AI rules files (`.cursorrules`, `CLAUDE.md`, etc.)
7
- - `@directive-run/claude-plugin` – builds Claude Code plugin skills
8
- - `directive.run/llms.txt` – website LLM reference
5
+ If you want your AI assistant to write idiomatic Directive code, you do not install this package directly — you install one of its consumers below.
9
6
 
10
- ## Contents
7
+ ## Using the knowledge
11
8
 
12
- | Directory | Count | Description |
13
- |-----------|-------|-------------|
14
- | `core/` | 13 | Core Directive knowledge (modules, constraints, resolvers, etc.) |
15
- | `ai/` | 12 | AI orchestrator knowledge (agents, streaming, guardrails, etc.) |
16
- | `examples/` | 37 | Extracted examples (auto-generated, DOM wiring stripped) |
17
- | `api-skeleton.md` | 1 | Auto-generated API reference skeleton |
18
- | `sitemap.md` | 1 | Auto-generated docs site sitemap (125+ pages) |
9
+ ### Claude Code
10
+
11
+ Two commands in a Claude Code session:
12
+
13
+ ```
14
+ /plugin marketplace add directive-run/directive
15
+ /plugin install directive@directive-plugins
16
+ ```
17
+
18
+ Ships 12 skills bundled from this package. Skills are model-invoked — Claude reads each skill's description and auto-loads the relevant one when your task matches.
19
+
20
+ ### Cursor, Copilot, Windsurf, Cline, OpenAI Codex
21
+
22
+ Run [`@directive-run/cli`](../cli) from your project root:
23
+
24
+ ```bash
25
+ npx directive ai-rules init
26
+ ```
27
+
28
+ Generates `.cursorrules` / `.clinerules` / `copilot-instructions.md` / `windsurf.md` / `AGENTS.md` / `CLAUDE.md` tuned to each assistant's ingestion convention.
29
+
30
+ ### LLM agents crawling docs at runtime
31
+
32
+ Point your agent at `https://directive.run/llms.txt`. The route bundles a comparison framing + the full sitemap so an agent doing one-shot retrieval gets a coherent picture.
33
+
34
+ ### Full decision tree
35
+
36
+ See [`/docs/ide-integration`](https://directive.run/docs/ide-integration) for the full path-per-editor decision tree and verification commands.
19
37
 
20
38
  ## Programmatic API
21
39
 
40
+ For tool builders who consume Directive knowledge in their own code (custom AI integrations, doc renderers, etc.):
41
+
22
42
  ```typescript
23
43
  import {
24
44
  getKnowledge,
@@ -43,7 +63,21 @@ const examples = getAllExamples();
43
63
  clearCache();
44
64
  ```
45
65
 
46
- ## Scripts
66
+ If you're just trying to write code with AI help, you don't need this — install the Claude plugin or run `directive ai-rules` above.
67
+
68
+ ## What's in the package
69
+
70
+ | Directory | Count | Description |
71
+ |-----------|-------|-------------|
72
+ | `core/` | 13 | Core Directive knowledge (modules, constraints, resolvers, etc.) |
73
+ | `ai/` | 12 | AI orchestrator knowledge (agents, streaming, guardrails, etc.) |
74
+ | `examples/` | 37 | Extracted examples (auto-generated, DOM wiring stripped) |
75
+ | `api-skeleton.md` | 1 | Auto-generated API reference skeleton |
76
+ | `sitemap.md` | 1 | Auto-generated docs site sitemap (125+ pages) |
77
+
78
+ ## Contributing
79
+
80
+ ### Scripts
47
81
 
48
82
  ```bash
49
83
  pnpm --filter @directive-run/knowledge generate # Regenerate api-skeleton.md
@@ -53,9 +87,10 @@ pnpm --filter @directive-run/knowledge test # Run all tests
53
87
  pnpm --filter @directive-run/knowledge build # Full build (generate + extract + tsup)
54
88
  ```
55
89
 
56
- ## Adding Examples
90
+ ### Adding examples
57
91
 
58
92
  Examples are **auto-discovered** from `examples/*/` in the repo root. The `extract-examples.ts` script:
93
+
59
94
  1. Scans all example directories
60
95
  2. Finds the best source file (prefers `<name>.ts` > `module.ts` > `main.ts`)
61
96
  3. Strips DOM wiring code
package/ai/ai-adapters.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # AI Adapters
2
2
 
3
+ > Covers `@directive-run/ai/{openai,anthropic,ollama,gemini}` — provider runners that normalize to the `AgentRunner` interface (zero SDK deps).
4
+
3
5
  Adapters connect the orchestrator to LLM providers. Each adapter normalizes provider-specific APIs into Directive's `AgentRunner` interface.
4
6
 
5
7
  ## Decision Tree: "Which adapter do I need?"
@@ -1,41 +1,35 @@
1
- # AI Agents and Streaming
1
+ # AI agents + streaming
2
2
 
3
- Defines the AgentLike interface (what runners receive), RunResult (what runners return), StreamChunk union types, backpressure strategies, and SSE transport.
3
+ > Covers `@directive-run/ai` `AgentLike`, `RunResult`, `StreamChunk`, backpressure, `createStreamingRunner`, `createSSETransport`.
4
4
 
5
- ## Decision Tree: "How do I get output from an agent?"
5
+ Defines the `AgentLike` shape, the `RunResult` returned by every runner, the `StreamChunk` discriminated union, backpressure strategies, the streaming runner wrapper, and the SSE transport for piping tokens to a browser.
6
+
7
+ ## Decision tree
6
8
 
7
9
  ```
8
10
  Need the complete result?
9
- ├── Yes → orchestrator.run(agent, prompt) → RunResult
11
+ ├── Yes → orchestrator.run(agent, prompt) → Promise<RunResult>
10
12
  └── No, need incremental output
11
- ├── AsyncIterable → orchestrator.runStream(agent, prompt)
12
- ├── Callback-basedStreamingCallbackRunner
13
- └── Server-Sent Events → createSSEResponse()
14
-
15
- Backpressure concern?
16
- ├── Consumer is slow strategy: "block"
17
- ├── Can drop tokens strategy: "drop"
18
- └── Defaultstrategy: "buffer"
13
+ ├── Async-iterator stream → orchestrator.runStream(agent, prompt) → { stream, result, abort }
14
+ ├── Wrap a base runner createStreamingRunner(baseRunner, opts) → StreamRunner
15
+ └── Server-Sent Events to HTTP createSSETransport(config) → { toResponse, toStream }
16
+
17
+ Backpressure concern?
18
+ ├── Consumer is slow backpressure: "buffer" (default)
19
+ ├── Need every token backpressure: "block"
20
+ └── Real-time, can dropbackpressure: "drop"
19
21
  ```
20
22
 
21
- ## AgentLike – What the Runner Receives
23
+ ## `AgentLike`
22
24
 
23
25
  ```typescript
24
26
  interface AgentLike {
25
- // Required unique identifier
26
- name: string;
27
-
28
- // System prompt / instructions
29
- instructions?: string;
30
-
31
- // Model identifier (adapter-specific)
32
- model?: string;
33
-
34
- // Tools the agent can use
35
- tools?: unknown[];
27
+ name: string; // required unique identifier
28
+ instructions?: string; // system prompt
29
+ model?: string; // adapter-specific model id
30
+ tools?: unknown[]; // tools the agent can call
36
31
  }
37
32
 
38
- // Usage
39
33
  const agent: AgentLike = {
40
34
  name: "analyst",
41
35
  instructions: "You analyze data and provide insights.",
@@ -44,71 +38,47 @@ const agent: AgentLike = {
44
38
  };
45
39
  ```
46
40
 
47
- ## RunResult – What the Runner Returns
41
+ ## `RunResult<T>`
42
+
43
+ Every runner — wrapped, mocked, real — resolves to this shape.
48
44
 
49
45
  ```typescript
50
46
  interface RunResult<T = unknown> {
51
- // The agent's final output
52
- output: T;
53
-
54
- // Full message history from this run
55
- messages: Message[];
56
-
57
- // Tool calls made during this run
58
- toolCalls: ToolCall[];
59
-
60
- // Total tokens consumed (input + output)
61
- totalTokens: number;
62
-
63
- // Detailed token breakdown
64
- tokenUsage?: {
65
- inputTokens: number;
66
- outputTokens: number;
67
- };
47
+ output: T; // the agent's final output
48
+ messages: Message[]; // full message history from this run
49
+ toolCalls: ToolCall[]; // tool calls executed during this run
50
+ totalTokens: number; // cumulative tokens
51
+ tokenUsage?: TokenUsage; // optional input/output breakdown when the provider supplies it
52
+ isCached?: boolean; // true when served from a semantic cache hit
68
53
  }
69
54
 
70
- // Usage
71
- const result = await orchestrator.run(agent, "Analyze sales data");
72
- console.log(result.output);
73
- console.log(`Tokens used: ${result.totalTokens}`);
74
- console.log(`Tool calls: ${result.toolCalls.length}`);
55
+ interface TokenUsage {
56
+ inputTokens: number;
57
+ outputTokens: number;
58
+ // Note: NO `total` field — sum `inputTokens + outputTokens` when needed, or use `result.totalTokens`.
59
+ }
75
60
  ```
76
61
 
77
- ## StreamChunk Union Types
78
-
79
- Each chunk from `runStream` is one of these discriminated union types:
62
+ ## `StreamChunk` discriminated union
80
63
 
81
64
  ```typescript
82
65
  type StreamChunk =
83
- // Text token from the model
84
- | { type: "token"; data: string; tokenCount: number }
85
-
86
- // Tool execution started
87
- | { type: "tool_start"; tool: string; toolCallId: string; arguments: string }
88
-
89
- // Tool execution completed
90
- | { type: "tool_end"; tool: string; toolCallId: string; result: string }
91
-
92
- // Complete message added to history
93
- | { type: "message"; message: Message }
94
-
95
- // Guardrail was triggered during streaming
96
- | { type: "guardrail_triggered"; guardrailName: string; reason: string; stopped: boolean }
97
-
98
- // Progress indicator
99
- | { type: "progress"; phase: "starting" | "generating" | "tool_calling" | "finishing" }
100
-
101
- // Stream completed
102
- | { type: "done"; totalTokens: number; duration: number; droppedTokens: number }
103
-
104
- // Error during streaming
105
- | { type: "error"; error: Error };
66
+ | { type: "token"; data: string; tokenCount: number } // a text token from the model
67
+ | { type: "tool_start"; tool: string; toolCallId: string; arguments: string } // tool started executing
68
+ | { type: "tool_end"; tool: string; toolCallId: string; result: string } // tool finished
69
+ | { type: "message"; message: Message } // a complete message added to history
70
+ | { type: "guardrail_triggered"; guardrailName: string; reason: string; stopped: boolean } // a guardrail fired during streaming
71
+ | { type: "progress"; phase: "starting" | "generating" | "tool_calling" | "finishing" } // coarse progress
72
+ | { type: "done"; totalTokens: number; duration: number; droppedTokens: number } // stream complete
73
+ | { type: "error"; error: Error }; // stream aborted with an error
106
74
  ```
107
75
 
108
- ## Consuming a Stream
76
+ ## Consuming `runStream`
77
+
78
+ `orchestrator.runStream(agent, input, options?)` returns **`OrchestratorStreamResult<T>`** — a `{ stream, result, abort }` triple, NOT an `AsyncIterable` directly. Destructure it before iterating.
109
79
 
110
80
  ```typescript
111
- const stream = orchestrator.runStream(agent, "Write a report");
81
+ const { stream, result, abort } = orchestrator.runStream(agent, "Write a report");
112
82
 
113
83
  for await (const chunk of stream) {
114
84
  switch (chunk.type) {
@@ -116,154 +86,217 @@ for await (const chunk of stream) {
116
86
  process.stdout.write(chunk.data);
117
87
  break;
118
88
  case "tool_start":
119
- console.log(`\nCalling tool: ${chunk.tool}`);
89
+ console.log(`\ntool: ${chunk.tool}`);
120
90
  break;
121
91
  case "tool_end":
122
- console.log(`Tool result: ${chunk.result.slice(0, 100)}`);
92
+ console.log(`result: ${chunk.result.slice(0, 100)}`);
123
93
  break;
124
94
  case "guardrail_triggered":
125
- console.warn(`Guardrail ${chunk.guardrailName}: ${chunk.reason}`);
95
+ console.warn(`guardrail ${chunk.guardrailName}: ${chunk.reason}`);
126
96
  if (chunk.stopped) {
127
- console.error("Stream stopped by guardrail");
97
+ console.error("stream stopped by guardrail");
128
98
  }
129
99
  break;
130
100
  case "done":
131
- console.log(`\nTokens: ${chunk.totalTokens}, Duration: ${chunk.duration}ms`);
101
+ console.log(`\ntokens: ${chunk.totalTokens}, ${chunk.duration}ms`);
132
102
  break;
133
103
  case "error":
134
- console.error("Stream error:", chunk.error);
104
+ console.error("stream error:", chunk.error);
135
105
  break;
136
106
  }
137
107
  }
138
- ```
139
108
 
140
- ## Backpressure Strategies
109
+ const final = await result; // RunResult<T>
141
110
 
142
- Control behavior when the consumer cannot keep up with token production:
111
+ // Cancel mid-stream
112
+ abort();
113
+ ```
143
114
 
144
- ```typescript
145
- const stream = orchestrator.runStream(agent, "Generate long report", {
146
- backpressure: "buffer", // default – buffer all tokens in memory
147
- });
115
+ ## Backpressure strategies
148
116
 
149
- const stream = orchestrator.runStream(agent, "Generate long report", {
150
- backpressure: "block", // pause generation until consumer catches up
151
- });
117
+ Configure how the stream behaves when the consumer can't keep up. Pass via `runStream`'s `options` (orchestrator-side) or via the `StreamRunOptions` if you're calling a `StreamRunner` directly.
152
118
 
153
- const stream = orchestrator.runStream(agent, "Generate long report", {
154
- backpressure: "drop", // drop tokens consumer cannot process in time
119
+ ```typescript
120
+ const { stream, result } = orchestrator.runStream(agent, "Generate a long report", {
121
+ signal: abortController.signal,
122
+ backpressure: "buffer", // default — buffer all tokens
123
+ // backpressure: "block" // pause generation until consumer catches up
124
+ // backpressure: "drop" // drop unprocessed tokens; `done.droppedTokens` reports the count
125
+ bufferSize: 1000,
126
+ stopOnGuardrail: true,
127
+ guardrailCheckInterval: 100,
155
128
  });
156
129
  ```
157
130
 
158
- | Strategy | Behavior | Use When |
131
+ | Strategy | Behavior | Use when |
159
132
  |---|---|---|
160
- | `"buffer"` | Buffers all tokens in memory | Consumer is slightly slow, memory is available |
133
+ | `"buffer"` | Buffers all tokens in memory | Consumer is slightly slow; memory is available |
161
134
  | `"block"` | Pauses model generation | Consumer must process every token |
162
- | `"drop"` | Drops unprocessed tokens | Real-time display, some loss acceptable |
163
-
164
- When using `"drop"`, the `done` chunk reports `droppedTokens` count.
135
+ | `"drop"` | Drops unprocessed tokens | Real-time display; some loss acceptable |
165
136
 
166
- ## StreamingCallbackRunner
137
+ ## `createStreamingRunner(baseRunner, options?)`
167
138
 
168
- For callback-based streaming (instead of AsyncIterable):
139
+ Wrap a base streaming runner (a `StreamingCallbackRunner` — the callback-based adapter interface) into a `StreamRunner` that produces the async-iterator chunks shown above. The factory is `createStreamingRunner`, NOT `createStreamingCallbackRunner` (the "callback" form is the INPUT to this wrapper, not a separate factory).
169
140
 
170
141
  ```typescript
171
- import { createStreamingCallbackRunner } from "@directive-run/ai";
142
+ import { createStreamingRunner, type StreamingCallbackRunner } from "@directive-run/ai";
172
143
 
173
- const callbackRunner = createStreamingCallbackRunner(runner, {
174
- onToken: (token) => process.stdout.write(token),
175
- onToolStart: (tool, id) => console.log(`Tool: ${tool}`),
176
- onToolEnd: (tool, id, result) => console.log(`Result: ${result}`),
177
- onComplete: (result) => console.log("Done:", result.totalTokens),
178
- onError: (error) => console.error(error),
179
- });
144
+ // The base runner is callback-driven. You supply this from your provider adapter.
145
+ const callbackBased: StreamingCallbackRunner = (agent, input, { onToken, onToolStart, onToolEnd, onComplete, signal }) => {
146
+ // call your provider's streaming API; invoke the callbacks as tokens arrive
147
+ };
180
148
 
181
- // Use in orchestrator
182
- const orchestrator = createAgentOrchestrator({
183
- runner: callbackRunner,
149
+ const streamRunner = createStreamingRunner(callbackBased, {
150
+ streamingGuardrails: [],
184
151
  });
152
+
153
+ // Use it directly — returns the same { stream, result, abort } shape
154
+ const { stream, result, abort } = streamRunner(agent, "prompt", { backpressure: "buffer" });
185
155
  ```
186
156
 
187
- ## SSE Transport (Server-Sent Events)
157
+ ## `createSSETransport(config?)`
188
158
 
189
- Pipe agent streaming to HTTP responses for web servers:
159
+ Pipes a token stream to Server-Sent Events for browser consumption. `createSSEResponse` does NOT exist — the real factory is `createSSETransport`, and it returns `{ toResponse, toStream }`.
190
160
 
191
161
  ```typescript
192
- import { createSSEResponse } from "@directive-run/ai";
162
+ import { createSSETransport } from "@directive-run/ai";
163
+
164
+ const sse = createSSETransport({
165
+ maxResponseChars: 50_000,
166
+ truncationMessage: "\n\n*[Response truncated]*",
167
+ heartbeatIntervalMs: 15_000,
168
+ headers: { "X-AI-Service": "directive" },
169
+ errorMessages: { rate_limit: "Service busy — please retry shortly." },
170
+ });
193
171
 
194
- // Express / Node HTTP handler
195
- app.post("/api/chat", async (req, res) => {
196
- const stream = orchestrator.runStream(agent, req.body.prompt);
172
+ // Modern web frameworks (Hono, Next.js, Bun, Deno) — return a Response directly
173
+ export async function POST(req: Request) {
174
+ const { prompt } = await req.json();
175
+ const { stream } = orchestrator.runStream(agent, prompt);
176
+
177
+ return sse.toResponse(stream, agent.name, prompt);
178
+ }
197
179
 
198
- // Creates a ReadableStream of SSE-formatted events
199
- const sseResponse = createSSEResponse(stream);
180
+ // Express / Koa write to res via the ReadableStream
181
+ app.post("/api/chat", async (req, res) => {
182
+ const { stream } = orchestrator.runStream(agent, req.body.prompt);
183
+ const readable = sse.toStream(stream, agent.name, req.body.prompt);
200
184
 
201
185
  res.setHeader("Content-Type", "text/event-stream");
202
186
  res.setHeader("Cache-Control", "no-cache");
203
187
  res.setHeader("Connection", "keep-alive");
204
188
 
205
- for await (const event of sseResponse) {
206
- res.write(event);
189
+ for await (const chunk of readable) {
190
+ res.write(chunk);
207
191
  }
208
-
209
192
  res.end();
210
193
  });
211
194
  ```
212
195
 
213
- Client-side consumption:
196
+ Client-side consumption is plain SSE:
214
197
 
215
198
  ```typescript
216
199
  const eventSource = new EventSource("/api/chat");
217
200
 
218
- eventSource.onmessage = (event) => {
219
- const chunk = JSON.parse(event.data);
220
- if (chunk.type === "token") {
221
- appendToDisplay(chunk.data);
222
- }
223
- };
201
+ eventSource.addEventListener("text", (event) => {
202
+ const data = JSON.parse(event.data) as { type: "text"; text: string };
203
+ appendToDisplay(data.text);
204
+ });
205
+
206
+ eventSource.addEventListener("done", () => eventSource.close());
207
+ ```
208
+
209
+ ## Anti-patterns
210
+
211
+ ### Iterating `runStream` directly
212
+
213
+ ```typescript
214
+ // WRONG — runStream returns { stream, result, abort }, not the iterator itself
215
+ const stream = orchestrator.runStream(agent, prompt);
216
+ for await (const chunk of stream) { /* won't iterate the right thing */ }
217
+
218
+ // CORRECT — destructure
219
+ const { stream, result, abort } = orchestrator.runStream(agent, prompt);
220
+ for await (const chunk of stream) { /* … */ }
221
+ const final = await result;
224
222
  ```
225
223
 
226
- ## Common Mistakes
224
+ ### Importing `createStreamingCallbackRunner`
225
+
226
+ ```typescript
227
+ // WRONG — no factory by that name
228
+ import { createStreamingCallbackRunner } from "@directive-run/ai";
229
+
230
+ // CORRECT — wrap a callback-based runner with createStreamingRunner
231
+ import { createStreamingRunner } from "@directive-run/ai";
232
+ const wrapped = createStreamingRunner(callbackBasedRunner, { streamingGuardrails: [] });
233
+ ```
234
+
235
+ ### Importing `createSSEResponse`
236
+
237
+ ```typescript
238
+ // WRONG — no factory by that name
239
+ import { createSSEResponse } from "@directive-run/ai";
240
+ const sse = createSSEResponse(stream);
241
+
242
+ // CORRECT — createSSETransport returns { toResponse, toStream }
243
+ import { createSSETransport } from "@directive-run/ai";
244
+ const sse = createSSETransport({ heartbeatIntervalMs: 15_000 });
245
+ return sse.toResponse(stream, agentId, prompt);
246
+ ```
247
+
248
+ ### Reading `tokenUsage.total`
249
+
250
+ ```typescript
251
+ // WRONG — TokenUsage has no `total` field
252
+ console.log(result.tokenUsage?.total);
253
+
254
+ // CORRECT — sum, or use the top-level totalTokens
255
+ console.log(result.totalTokens);
256
+ console.log((result.tokenUsage?.inputTokens ?? 0) + (result.tokenUsage?.outputTokens ?? 0));
257
+ ```
227
258
 
228
- ### Not checking chunk.type before accessing fields
259
+ ### Not checking `chunk.type` before accessing fields
229
260
 
230
261
  ```typescript
231
- // WRONG not all chunks have .data
262
+ // WRONG not every chunk has .data; this is undefined for tool_start/done/etc.
232
263
  for await (const chunk of stream) {
233
- console.log(chunk.data); // undefined for non-token chunks
264
+ console.log(chunk.data);
234
265
  }
235
266
 
236
- // CORRECT switch on chunk.type
267
+ // CORRECT switch / narrow on chunk.type
237
268
  for await (const chunk of stream) {
238
269
  if (chunk.type === "token") {
239
- console.log(chunk.data);
270
+ process.stdout.write(chunk.data);
240
271
  }
241
272
  }
242
273
  ```
243
274
 
244
- ### Ignoring the stopped flag on guardrail chunks
275
+ ### Ignoring `guardrail_triggered.stopped`
245
276
 
246
277
  ```typescript
247
- // WRONG continuing after a stopping guardrail
278
+ // WRONG continuing after a stopping guardrail
248
279
  case "guardrail_triggered":
249
- console.log("Guardrail triggered, continuing...");
280
+ console.log("guardrail fired, continuing");
250
281
  break;
251
282
 
252
- // CORRECT check if the stream was stopped
283
+ // CORRECT `stopped: true` means the stream was terminated by the guardrail
253
284
  case "guardrail_triggered":
254
285
  if (chunk.stopped) {
255
- console.error(`Stopped by ${chunk.guardrailName}: ${chunk.reason}`);
256
- // Handle stream termination
286
+ console.error(`stopped by ${chunk.guardrailName}: ${chunk.reason}`);
287
+ abort();
288
+ return;
257
289
  }
258
290
  break;
259
291
  ```
260
292
 
261
- ## Quick Reference
293
+ ## Quick reference
262
294
 
263
- | Type | Interface | Purpose |
295
+ | Type / API | Path | Purpose |
264
296
  |---|---|---|
265
- | `AgentLike` | `{ name, instructions?, model?, tools? }` | Agent definition |
266
- | `RunResult` | `{ output, messages, toolCalls, totalTokens }` | Complete run result |
267
- | `StreamChunk` | Discriminated union (8 types) | Incremental output |
268
- | `StreamingCallbackRunner` | Callback-based adapter | Alternative to AsyncIterable |
269
- | `createSSEResponse` | SSE formatter | Web server streaming |
297
+ | `AgentLike` | `@directive-run/ai` | `{ name, instructions?, model?, tools? }` what runners receive |
298
+ | `RunResult<T>` | `@directive-run/ai` | `{ output, messages, toolCalls, totalTokens, tokenUsage?, isCached? }` |
299
+ | `StreamChunk` | `@directive-run/ai` | 8-way discriminated union for streaming output |
300
+ | `orchestrator.runStream(agent, input, opts?)` | instance method | Returns `{ stream, result, abort }` |
301
+ | `createStreamingRunner(baseRunner, opts?)` | `@directive-run/ai` | Wrap a `StreamingCallbackRunner` into a `StreamRunner` |
302
+ | `createSSETransport(config?)` | `@directive-run/ai` | `{ toResponse, toStream }` for piping a stream to SSE |