@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.
- package/README.md +50 -15
- package/ai/ai-adapters.md +2 -0
- package/ai/ai-agents-streaming.md +182 -149
- package/ai/ai-budget-resilience.md +299 -132
- package/ai/ai-communication.md +215 -197
- package/ai/ai-debug-observability.md +253 -173
- package/ai/ai-guardrails-memory.md +185 -153
- package/ai/ai-mcp-rag.md +199 -199
- package/ai/ai-multi-agent.md +247 -153
- package/ai/ai-orchestrator.md +344 -114
- package/ai/ai-security.md +282 -180
- package/ai/ai-tasks.md +3 -1
- package/ai/ai-testing-evals.md +357 -256
- package/core/anti-patterns.md +9 -2
- package/core/constraints.md +6 -2
- package/core/core-patterns.md +2 -0
- package/core/error-boundaries.md +2 -0
- package/core/history.md +2 -0
- package/core/multi-module.md +8 -3
- package/core/naming.md +15 -6
- package/core/plugins.md +2 -0
- package/core/react-adapter.md +250 -174
- package/core/resolvers.md +2 -0
- package/core/schema-types.md +2 -0
- package/core/system-api.md +2 -0
- package/core/testing.md +251 -143
- package/examples/checkers.ts +15 -16
- package/examples/contact-form.ts +2 -2
- package/examples/counter-react.ts +1 -1
- package/examples/counter-svelte.ts +1 -1
- package/examples/counter-vue.ts +1 -1
- package/examples/counter.ts +1 -1
- package/examples/data-triggers.ts +4 -4
- package/examples/feature-flags.ts +2 -2
- package/examples/form-wizard.ts +2 -2
- package/examples/newsletter.ts +2 -2
- package/examples/server.ts +2 -2
- package/examples/shopping-cart.ts +1 -1
- package/examples/topic-guard.ts +1 -1
- package/package.json +3 -3
- package/sitemap.md +26 -3
- package/examples/debounce-constraints.ts +0 -95
- package/examples/multi-module.ts +0 -58
package/README.md
CHANGED
|
@@ -1,24 +1,44 @@
|
|
|
1
1
|
# @directive-run/knowledge
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
7
|
+
## Using the knowledge
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1
|
+
# AI agents + streaming
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Covers `@directive-run/ai` — `AgentLike`, `RunResult`, `StreamChunk`, backpressure, `createStreamingRunner`, `createSSETransport`.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
|
11
|
+
├── Yes → orchestrator.run(agent, prompt) → Promise<RunResult>
|
|
10
12
|
└── No, need incremental output
|
|
11
|
-
├──
|
|
12
|
-
├──
|
|
13
|
-
└── Server-Sent Events →
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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 drop → backpressure: "drop"
|
|
19
21
|
```
|
|
20
22
|
|
|
21
|
-
## AgentLike
|
|
23
|
+
## `AgentLike`
|
|
22
24
|
|
|
23
25
|
```typescript
|
|
24
26
|
interface AgentLike {
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
//
|
|
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
|
|
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
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
//
|
|
55
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
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
|
-
//
|
|
84
|
-
| { type: "
|
|
85
|
-
|
|
86
|
-
//
|
|
87
|
-
| { type: "
|
|
88
|
-
|
|
89
|
-
//
|
|
90
|
-
| { type: "
|
|
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
|
|
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(`\
|
|
89
|
+
console.log(`\ntool: ${chunk.tool}`);
|
|
120
90
|
break;
|
|
121
91
|
case "tool_end":
|
|
122
|
-
console.log(`
|
|
92
|
+
console.log(`result: ${chunk.result.slice(0, 100)}`);
|
|
123
93
|
break;
|
|
124
94
|
case "guardrail_triggered":
|
|
125
|
-
console.warn(`
|
|
95
|
+
console.warn(`guardrail ${chunk.guardrailName}: ${chunk.reason}`);
|
|
126
96
|
if (chunk.stopped) {
|
|
127
|
-
console.error("
|
|
97
|
+
console.error("stream stopped by guardrail");
|
|
128
98
|
}
|
|
129
99
|
break;
|
|
130
100
|
case "done":
|
|
131
|
-
console.log(`\
|
|
101
|
+
console.log(`\ntokens: ${chunk.totalTokens}, ${chunk.duration}ms`);
|
|
132
102
|
break;
|
|
133
103
|
case "error":
|
|
134
|
-
console.error("
|
|
104
|
+
console.error("stream error:", chunk.error);
|
|
135
105
|
break;
|
|
136
106
|
}
|
|
137
107
|
}
|
|
138
|
-
```
|
|
139
108
|
|
|
140
|
-
|
|
109
|
+
const final = await result; // RunResult<T>
|
|
141
110
|
|
|
142
|
-
|
|
111
|
+
// Cancel mid-stream
|
|
112
|
+
abort();
|
|
113
|
+
```
|
|
143
114
|
|
|
144
|
-
|
|
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
|
-
|
|
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
|
-
|
|
154
|
-
|
|
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
|
|
131
|
+
| Strategy | Behavior | Use when |
|
|
159
132
|
|---|---|---|
|
|
160
|
-
| `"buffer"` | Buffers all tokens in memory | Consumer is slightly slow
|
|
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
|
|
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
|
-
##
|
|
137
|
+
## `createStreamingRunner(baseRunner, options?)`
|
|
167
138
|
|
|
168
|
-
|
|
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 {
|
|
142
|
+
import { createStreamingRunner, type StreamingCallbackRunner } from "@directive-run/ai";
|
|
172
143
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
182
|
-
|
|
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
|
-
##
|
|
157
|
+
## `createSSETransport(config?)`
|
|
188
158
|
|
|
189
|
-
|
|
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 {
|
|
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
|
-
//
|
|
195
|
-
|
|
196
|
-
const
|
|
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
|
-
|
|
199
|
-
|
|
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
|
|
206
|
-
res.write(
|
|
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.
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
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
|
|
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);
|
|
264
|
+
console.log(chunk.data);
|
|
234
265
|
}
|
|
235
266
|
|
|
236
|
-
// CORRECT
|
|
267
|
+
// CORRECT — switch / narrow on chunk.type
|
|
237
268
|
for await (const chunk of stream) {
|
|
238
269
|
if (chunk.type === "token") {
|
|
239
|
-
|
|
270
|
+
process.stdout.write(chunk.data);
|
|
240
271
|
}
|
|
241
272
|
}
|
|
242
273
|
```
|
|
243
274
|
|
|
244
|
-
### Ignoring
|
|
275
|
+
### Ignoring `guardrail_triggered.stopped`
|
|
245
276
|
|
|
246
277
|
```typescript
|
|
247
|
-
// WRONG
|
|
278
|
+
// WRONG — continuing after a stopping guardrail
|
|
248
279
|
case "guardrail_triggered":
|
|
249
|
-
console.log("
|
|
280
|
+
console.log("guardrail fired, continuing…");
|
|
250
281
|
break;
|
|
251
282
|
|
|
252
|
-
// CORRECT
|
|
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(`
|
|
256
|
-
|
|
286
|
+
console.error(`stopped by ${chunk.guardrailName}: ${chunk.reason}`);
|
|
287
|
+
abort();
|
|
288
|
+
return;
|
|
257
289
|
}
|
|
258
290
|
break;
|
|
259
291
|
```
|
|
260
292
|
|
|
261
|
-
## Quick
|
|
293
|
+
## Quick reference
|
|
262
294
|
|
|
263
|
-
| Type |
|
|
295
|
+
| Type / API | Path | Purpose |
|
|
264
296
|
|---|---|---|
|
|
265
|
-
| `AgentLike` | `{ name, instructions?, model?, tools? }`
|
|
266
|
-
| `RunResult` | `{ output, messages, toolCalls, totalTokens }` |
|
|
267
|
-
| `StreamChunk` |
|
|
268
|
-
| `
|
|
269
|
-
| `
|
|
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 |
|