@directive-run/claude-plugin 1.16.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 (83) hide show
  1. package/.claude-plugin/plugin.json +6 -0
  2. package/LICENSE +26 -0
  3. package/README.md +113 -0
  4. package/dist/index.cjs +2 -0
  5. package/dist/index.cjs.map +1 -0
  6. package/dist/index.d.cts +66 -0
  7. package/dist/index.d.ts +66 -0
  8. package/dist/index.js +2 -0
  9. package/dist/index.js.map +1 -0
  10. package/package.json +64 -0
  11. package/skills/README.md +9 -0
  12. package/skills/building-ai-agents/SKILL.md +414 -0
  13. package/skills/building-ai-agents/ai-adapters.md +257 -0
  14. package/skills/building-ai-agents/ai-agents-streaming.md +307 -0
  15. package/skills/building-ai-agents/ai-communication.md +304 -0
  16. package/skills/building-ai-agents/api-skeleton.md +5 -0
  17. package/skills/building-ai-agents/examples.md +849 -0
  18. package/skills/building-ai-orchestrators/SKILL.md +401 -0
  19. package/skills/building-ai-orchestrators/ai-multi-agent.md +375 -0
  20. package/skills/building-ai-orchestrators/ai-orchestrator.md +466 -0
  21. package/skills/building-ai-orchestrators/ai-tasks.md +268 -0
  22. package/skills/building-ai-orchestrators/api-skeleton.md +5 -0
  23. package/skills/building-ai-orchestrators/examples.md +1609 -0
  24. package/skills/building-directive-systems/SKILL.md +499 -0
  25. package/skills/building-directive-systems/api-skeleton.md +5 -0
  26. package/skills/building-directive-systems/examples.md +4149 -0
  27. package/skills/building-directive-systems/multi-module.md +327 -0
  28. package/skills/building-directive-systems/plugins.md +361 -0
  29. package/skills/building-directive-systems/react-adapter.md +376 -0
  30. package/skills/building-directive-systems/system-api.md +463 -0
  31. package/skills/getting-started-with-directive/SKILL.md +198 -0
  32. package/skills/getting-started-with-directive/api-skeleton.md +5 -0
  33. package/skills/getting-started-with-directive/core-patterns.md +240 -0
  34. package/skills/getting-started-with-directive/examples.md +87 -0
  35. package/skills/getting-started-with-directive/sitemap.md +202 -0
  36. package/skills/hardening-ai-systems/SKILL.md +377 -0
  37. package/skills/hardening-ai-systems/ai-budget-resilience.md +408 -0
  38. package/skills/hardening-ai-systems/ai-guardrails-memory.md +370 -0
  39. package/skills/hardening-ai-systems/ai-security.md +400 -0
  40. package/skills/hardening-ai-systems/api-skeleton.md +5 -0
  41. package/skills/hardening-ai-systems/examples.md +628 -0
  42. package/skills/migrating-to-directive/SKILL.md +249 -0
  43. package/skills/migrating-to-directive/anti-patterns.md +382 -0
  44. package/skills/migrating-to-directive/api-skeleton.md +5 -0
  45. package/skills/migrating-to-directive/core-patterns.md +240 -0
  46. package/skills/migrating-to-directive/examples.md +511 -0
  47. package/skills/migrating-to-directive/schema-types.md +273 -0
  48. package/skills/reviewing-directive-code/SKILL.md +232 -0
  49. package/skills/reviewing-directive-code/anti-patterns.md +382 -0
  50. package/skills/reviewing-directive-code/api-skeleton.md +5 -0
  51. package/skills/reviewing-directive-code/core-patterns.md +240 -0
  52. package/skills/reviewing-directive-code/examples.md +463 -0
  53. package/skills/reviewing-directive-code/naming.md +321 -0
  54. package/skills/scaffolding-directive-modules/SKILL.md +218 -0
  55. package/skills/scaffolding-directive-modules/api-skeleton.md +5 -0
  56. package/skills/scaffolding-directive-modules/core-patterns.md +240 -0
  57. package/skills/scaffolding-directive-modules/examples.md +1386 -0
  58. package/skills/scaffolding-directive-modules/naming.md +321 -0
  59. package/skills/scaffolding-directive-modules/schema-types.md +273 -0
  60. package/skills/testing-ai-systems/SKILL.md +398 -0
  61. package/skills/testing-ai-systems/ai-debug-observability.md +329 -0
  62. package/skills/testing-ai-systems/ai-mcp-rag.md +293 -0
  63. package/skills/testing-ai-systems/ai-testing-evals.md +485 -0
  64. package/skills/testing-ai-systems/api-skeleton.md +5 -0
  65. package/skills/testing-ai-systems/examples.md +1261 -0
  66. package/skills/testing-directive-code/SKILL.md +479 -0
  67. package/skills/testing-directive-code/api-skeleton.md +5 -0
  68. package/skills/testing-directive-code/examples.md +156 -0
  69. package/skills/testing-directive-code/history.md +351 -0
  70. package/skills/testing-directive-code/testing.md +412 -0
  71. package/skills/writing-directive-constraints/SKILL.md +486 -0
  72. package/skills/writing-directive-constraints/api-skeleton.md +5 -0
  73. package/skills/writing-directive-constraints/constraints.md +274 -0
  74. package/skills/writing-directive-constraints/error-boundaries.md +330 -0
  75. package/skills/writing-directive-constraints/examples.md +1361 -0
  76. package/skills/writing-directive-constraints/resolvers.md +367 -0
  77. package/skills/writing-directive-modules/SKILL.md +342 -0
  78. package/skills/writing-directive-modules/anti-patterns.md +382 -0
  79. package/skills/writing-directive-modules/api-skeleton.md +5 -0
  80. package/skills/writing-directive-modules/core-patterns.md +240 -0
  81. package/skills/writing-directive-modules/examples.md +1639 -0
  82. package/skills/writing-directive-modules/naming.md +321 -0
  83. package/skills/writing-directive-modules/schema-types.md +273 -0
@@ -0,0 +1,414 @@
1
+ ---
2
+ name: building-ai-agents
3
+ description: "Configure AI provider runners (Anthropic, OpenAI, Ollama), stream tokens and structured output, and wire cross-agent communication. Use when setting up LLM provider connections, implementing token streaming, choosing between providers, or building agents that communicate results to each other."
4
+ ---
5
+
6
+ # Building AI Agents
7
+
8
+ ## Prerequisites
9
+
10
+ This skill applies when the project uses `@directive-run/ai`. If not found in `package.json`, suggest installing it: `npm install @directive-run/ai`.
11
+
12
+ ## When Claude Should Use This Skill
13
+
14
+ ## Auto-Invoke Triggers
15
+ - User asks which LLM provider to use or how to set one up
16
+ - User wants to stream tokens or handle streaming responses
17
+ - User mentions `createAnthropicRunner`, `createOpenAIRunner`, `createOllamaRunner`
18
+ - User asks how agents share data or pass results between each other
19
+ - User needs to switch providers or implement a provider abstraction
20
+
21
+ ## Exclusions
22
+ - Do NOT invoke for orchestrator structure (constraints/resolvers) – use `building-ai-orchestrators`
23
+ - Do NOT invoke for rate limits, budgets, or guardrails – use `hardening-ai-systems`
24
+ - Do NOT invoke for testing runners – use `testing-ai-systems`
25
+
26
+ ---
27
+
28
+ ## Quick Reference
29
+
30
+ ## Decision Tree: Which Runner?
31
+
32
+ ```
33
+ Choosing a provider?
34
+ ├── Production, best quality → createAnthropicRunner (claude-opus-4-6)
35
+ ├── Production, cost-efficient → createAnthropicRunner (claude-haiku-4-5)
36
+ ├── OpenAI ecosystem / GPT models → createOpenAIRunner
37
+ ├── Local / private / no API key → createOllamaRunner
38
+ └── Custom provider → implement AgentRunner interface
39
+ ```
40
+
41
+ ## Decision Tree: Streaming vs Non-Streaming?
42
+
43
+ ```
44
+ Need the response immediately as it arrives?
45
+ ├── Yes, show tokens as they generate → runStream() + "token" events
46
+ ├── Yes, but structured JSON → runStream() + "structured" events
47
+ └── No, wait for full response → run()
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Provider Runners
53
+
54
+ ### Anthropic
55
+
56
+ ```typescript
57
+ import { createAnthropicRunner } from "@directive-run/ai/anthropic";
58
+
59
+ const runner = createAnthropicRunner({
60
+ model: "claude-opus-4-6", // or "claude-haiku-4-5", "claude-sonnet-4-6"
61
+ apiKey: process.env.ANTHROPIC_API_KEY,
62
+ maxTokens: 4096, // optional, default: 1024
63
+ temperature: 0.7, // optional, default: 1.0
64
+ systemPrompt: "You are helpful.", // optional default system prompt
65
+ });
66
+ ```
67
+
68
+ ### OpenAI
69
+
70
+ ```typescript
71
+ import { createOpenAIRunner } from "@directive-run/ai/openai";
72
+
73
+ const runner = createOpenAIRunner({
74
+ model: "gpt-4o", // or "gpt-4o-mini", "gpt-3.5-turbo"
75
+ apiKey: process.env.OPENAI_API_KEY,
76
+ maxTokens: 4096,
77
+ temperature: 0.7,
78
+ organization: "org-...", // optional
79
+ });
80
+ ```
81
+
82
+ ### Ollama (Local)
83
+
84
+ ```typescript
85
+ import { createOllamaRunner } from "@directive-run/ai/ollama";
86
+
87
+ const runner = createOllamaRunner({
88
+ model: "llama3.2", // any Ollama-installed model
89
+ baseUrl: "http://localhost:11434", // default Ollama endpoint
90
+ temperature: 0.8,
91
+ });
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Running LLM Calls Inside Resolvers
97
+
98
+ ```typescript
99
+ resolvers: {
100
+ generate: {
101
+ requirement: "GENERATE",
102
+ resolve: async (req, context) => {
103
+ // Basic call – returns full response when complete
104
+ const result = await context.runner.run({
105
+ prompt: "Explain quantum entanglement simply.",
106
+ system: "You are a science communicator.",
107
+ maxTokens: 500,
108
+ });
109
+
110
+ context.facts.output = result.text;
111
+ context.facts.tokenUsage = result.usage.totalTokens;
112
+ },
113
+ },
114
+ },
115
+ ```
116
+
117
+ ## Runner Result Shape
118
+
119
+ ```typescript
120
+ interface RunnerResult {
121
+ text: string; // Generated text
122
+ usage: {
123
+ inputTokens: number;
124
+ outputTokens: number;
125
+ totalTokens: number;
126
+ };
127
+ model: string; // Model used
128
+ stopReason: "end_turn" | "max_tokens" | "stop_sequence";
129
+ raw: unknown; // Raw provider response
130
+ }
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Streaming
136
+
137
+ ### Token Streaming
138
+
139
+ ```typescript
140
+ // orchestrator.runStream() returns AsyncIterable<OrchestratorEvent>
141
+ const stream = orchestrator.runStream({ input: "Write a story about..." });
142
+
143
+ for await (const event of stream) {
144
+ if (event.type === "token") {
145
+ // Arrives as the LLM generates – display immediately
146
+ process.stdout.write(event.text);
147
+ }
148
+
149
+ if (event.type === "done") {
150
+ console.log("\nFinal facts:", event.facts);
151
+ }
152
+
153
+ if (event.type === "error") {
154
+ console.error("Stream error:", event.error.message);
155
+ break;
156
+ }
157
+ }
158
+ ```
159
+
160
+ ### All Stream Event Types
161
+
162
+ ```typescript
163
+ type OrchestratorEvent =
164
+ | { type: "token"; text: string; agentId?: string }
165
+ | { type: "requirement_met"; requirementType: string; agentId?: string }
166
+ | { type: "requirement_queued"; requirementType: string }
167
+ | { type: "agent_started"; agentId: string }
168
+ | { type: "agent_done"; agentId: string }
169
+ | { type: "done"; facts: Record<string, unknown> }
170
+ | { type: "error"; error: Error };
171
+ ```
172
+
173
+ ### Structured Output Streaming
174
+
175
+ ```typescript
176
+ import { z } from "zod";
177
+
178
+ // When you need typed JSON output, not raw text
179
+ const result = await context.runner.runStructured({
180
+ prompt: "Extract product info from this description: ...",
181
+ schema: z.object({
182
+ name: z.string(),
183
+ price: z.number(),
184
+ inStock: z.boolean(),
185
+ }),
186
+ });
187
+
188
+ // result.data is typed to the schema
189
+ context.facts.productName = result.data.name;
190
+ context.facts.price = result.data.price;
191
+ ```
192
+
193
+ ### Emitting Custom Stream Events from Resolvers
194
+
195
+ ```typescript
196
+ resolve: async (req, context) => {
197
+ // Emit custom events consumers can observe
198
+ context.emit({ type: "token", text: "Starting analysis..." });
199
+
200
+ const result = await context.runner.run({ prompt: req.input });
201
+
202
+ context.emit({ type: "token", text: result.text });
203
+ context.facts.output = result.text;
204
+ },
205
+ ```
206
+
207
+ ---
208
+
209
+ ## Cross-Agent Communication
210
+
211
+ ### Via Coordinator Facts (Recommended)
212
+
213
+ In `createMultiAgentOrchestrator`, agents share coordinator facts. The coordinator acts as the message bus.
214
+
215
+ ```typescript
216
+ // researcher agent writes to coordinator facts
217
+ resolve: async (req, context) => {
218
+ const result = await context.runner.run({ prompt: "Research AI trends..." });
219
+ // Writing to shared coordinator facts signals the next agent
220
+ context.facts.researchComplete = true;
221
+ context.facts.researchNotes = result.text;
222
+ },
223
+
224
+ // writer agent reads coordinator facts set by researcher
225
+ constraints: {
226
+ startWriting: {
227
+ // Only activates after researcher signals completion
228
+ when: (facts) => facts.researchComplete && !!facts.researchNotes,
229
+ require: { type: "WRITE_ARTICLE" },
230
+ },
231
+ },
232
+ ```
233
+
234
+ ### Via Requirement Payload
235
+
236
+ ```typescript
237
+ // Pass structured data in the requirement itself
238
+ constraints: {
239
+ routeToSpecialist: {
240
+ when: (facts) => facts.analysisType === "legal",
241
+ require: (facts) => ({
242
+ type: "SPECIALIST_REVIEW",
243
+ category: facts.analysisType,
244
+ priority: facts.urgency,
245
+ content: facts.draftOutput,
246
+ }),
247
+ },
248
+ },
249
+
250
+ resolvers: {
251
+ specialistReview: {
252
+ requirement: "SPECIALIST_REVIEW",
253
+ resolve: async (req, context) => {
254
+ // req contains { category, priority, content } from above
255
+ const result = await context.runner.run({
256
+ prompt: `Review this ${req.category} content: ${req.content}`,
257
+ });
258
+ context.facts.reviewedOutput = result.text;
259
+ },
260
+ },
261
+ },
262
+ ```
263
+
264
+ ---
265
+
266
+ ## Custom Provider – Implement AgentRunner Interface
267
+
268
+ ```typescript
269
+ import type { AgentRunner, RunOptions, RunnerResult } from "@directive-run/ai";
270
+
271
+ class MyCustomRunner implements AgentRunner {
272
+ async run(options: RunOptions): Promise<RunnerResult> {
273
+ // Call your provider
274
+ const response = await myProviderClient.generate({
275
+ prompt: options.prompt,
276
+ system: options.system,
277
+ });
278
+
279
+ return {
280
+ text: response.text,
281
+ usage: {
282
+ inputTokens: response.usage.input,
283
+ outputTokens: response.usage.output,
284
+ totalTokens: response.usage.total,
285
+ },
286
+ model: "my-custom-model",
287
+ stopReason: "end_turn",
288
+ raw: response,
289
+ };
290
+ }
291
+
292
+ // Optional: implement for streaming support
293
+ async *runStream(options: RunOptions): AsyncIterable<string> {
294
+ for await (const chunk of myProviderClient.stream(options)) {
295
+ yield chunk.text;
296
+ }
297
+ }
298
+ }
299
+ ```
300
+
301
+ ---
302
+
303
+ ## Multi-Turn Conversations
304
+
305
+ ```typescript
306
+ // Maintain conversation history in facts
307
+ factsSchema: {
308
+ messages: t.array(t.object<{ role: "user" | "assistant"; content: string }>()),
309
+ latestInput: t.string(),
310
+ latestOutput: t.string().optional(),
311
+ },
312
+
313
+ init: (facts) => {
314
+ facts.messages = [];
315
+ },
316
+
317
+ resolve: async (req, context) => {
318
+ // Add user message to history
319
+ context.facts.messages.push({
320
+ role: "user",
321
+ content: context.facts.latestInput,
322
+ });
323
+
324
+ const result = await context.runner.run({
325
+ messages: context.facts.messages, // Pass full history
326
+ system: "You are a helpful assistant.",
327
+ });
328
+
329
+ // Add assistant reply to history
330
+ context.facts.messages.push({
331
+ role: "assistant",
332
+ content: result.text,
333
+ });
334
+
335
+ context.facts.latestOutput = result.text;
336
+ },
337
+ ```
338
+
339
+ ---
340
+
341
+ ## Critical Anti-Patterns
342
+
343
+ ## Not handling stream errors
344
+
345
+ ```typescript
346
+ // WRONG – if stream throws, it crashes uncaught
347
+ for await (const event of orchestrator.runStream(input)) {
348
+ if (event.type === "token") {
349
+ process.stdout.write(event.text);
350
+ }
351
+ }
352
+
353
+ // CORRECT – always handle error events
354
+ for await (const event of orchestrator.runStream(input)) {
355
+ if (event.type === "token") {
356
+ process.stdout.write(event.text);
357
+ }
358
+
359
+ if (event.type === "error") {
360
+ console.error("Stream failed:", event.error.message);
361
+ break;
362
+ }
363
+ }
364
+ ```
365
+
366
+ ## Blocking on stream consumption inside a resolver
367
+
368
+ ```typescript
369
+ // WRONG – consuming a stream inside a resolver blocks the engine
370
+ resolve: async (req, context) => {
371
+ const stream = orchestrator.runStream(req.input);
372
+ for await (const event of stream) { /* deadlock risk */ }
373
+ },
374
+
375
+ // CORRECT – use context.runner.run() inside resolvers
376
+ resolve: async (req, context) => {
377
+ const result = await context.runner.run({ prompt: req.input });
378
+ context.facts.output = result.text;
379
+ },
380
+ ```
381
+
382
+ ### Resolver parameter naming
383
+ Always use `(req, context)` – never `(req, ctx)` or `(request, context)`.
384
+
385
+ ## Hardcoding API keys
386
+
387
+ ```typescript
388
+ // WRONG
389
+ const runner = createAnthropicRunner({ apiKey: "sk-ant-..." });
390
+
391
+ // CORRECT
392
+ const runner = createAnthropicRunner({ apiKey: process.env.ANTHROPIC_API_KEY });
393
+ ```
394
+
395
+ ## Ignoring token usage
396
+
397
+ ```typescript
398
+ // WRONG – fire and forget, no budget awareness
399
+ const result = await context.runner.run({ prompt });
400
+ context.facts.output = result.text;
401
+
402
+ // CORRECT – track usage for budget management
403
+ const result = await context.runner.run({ prompt });
404
+ context.facts.output = result.text;
405
+ context.facts.tokensUsed = (context.facts.tokensUsed ?? 0) + result.usage.totalTokens;
406
+ ```
407
+
408
+ ---
409
+
410
+ ## Reference Files
411
+
412
+ - `ai-agents-streaming.md` – Streaming events, AsyncIterable patterns, token buffering
413
+ - `ai-adapters.md` – Provider adapter interface, all runner options, custom runner guide
414
+ - `ai-communication.md` – Cross-agent communication, coordinator facts, requirement payloads
@@ -0,0 +1,257 @@
1
+ # AI Adapters
2
+
3
+ > Covers `@directive-run/ai/{openai,anthropic,ollama,gemini}` — provider runners that normalize to the `AgentRunner` interface (zero SDK deps).
4
+
5
+ Adapters connect the orchestrator to LLM providers. Each adapter normalizes provider-specific APIs into Directive's `AgentRunner` interface.
6
+
7
+ ## Decision Tree: "Which adapter do I need?"
8
+
9
+ ```
10
+ Which LLM provider?
11
+ ├── Anthropic (Claude) → createAnthropicRunner from '@directive-run/ai/anthropic'
12
+ ├── OpenAI (GPT) → createOpenAIRunner from '@directive-run/ai/openai'
13
+ ├── Google (Gemini) → createGeminiRunner from '@directive-run/ai/gemini'
14
+ └── Ollama (local) → createOllamaRunner from '@directive-run/ai/ollama'
15
+
16
+ Need streaming?
17
+ ├── Yes → create*StreamingRunner from the same subpath
18
+ └── No → create*Runner is sufficient
19
+
20
+ Need a proxy/self-hosted URL?
21
+ └── Yes → pass baseURL option
22
+ ```
23
+
24
+ ## CRITICAL: Subpath Imports
25
+
26
+ Every adapter MUST be imported from its subpath. The main `@directive-run/ai` entry does NOT export adapters.
27
+
28
+ ```typescript
29
+ // CORRECT – subpath imports
30
+ import { createAnthropicRunner } from "@directive-run/ai/anthropic";
31
+ import { createOpenAIRunner } from "@directive-run/ai/openai";
32
+ import { createOllamaRunner } from "@directive-run/ai/ollama";
33
+ import { createGeminiRunner } from "@directive-run/ai/gemini";
34
+ ```
35
+
36
+ ### Anti-Pattern #26: Importing adapters from the main entry
37
+
38
+ ```typescript
39
+ // WRONG – adapters are NOT exported from the main package
40
+ import { createOpenAIRunner } from "@directive-run/ai";
41
+ import { createAnthropicRunner } from "@directive-run/ai";
42
+
43
+ // CORRECT – use subpath imports
44
+ import { createOpenAIRunner } from "@directive-run/ai/openai";
45
+ import { createAnthropicRunner } from "@directive-run/ai/anthropic";
46
+ ```
47
+
48
+ ## Anthropic Adapter
49
+
50
+ ```typescript
51
+ import {
52
+ createAnthropicRunner,
53
+ createAnthropicStreamingRunner,
54
+ } from "@directive-run/ai/anthropic";
55
+
56
+ // Standard runner
57
+ const runner = createAnthropicRunner({
58
+ apiKey: process.env.ANTHROPIC_API_KEY,
59
+ defaultModel: "claude-sonnet-4-5",
60
+ maxTokens: 4096,
61
+ });
62
+
63
+ // Streaming runner
64
+ const streamingRunner = createAnthropicStreamingRunner({
65
+ apiKey: process.env.ANTHROPIC_API_KEY,
66
+ defaultModel: "claude-sonnet-4-5",
67
+ maxTokens: 4096,
68
+ });
69
+
70
+ // With proxy URL
71
+ const proxiedRunner = createAnthropicRunner({
72
+ apiKey: process.env.ANTHROPIC_API_KEY,
73
+ baseURL: "https://my-proxy.example.com/v1",
74
+ });
75
+ ```
76
+
77
+ ## OpenAI Adapter
78
+
79
+ ```typescript
80
+ import {
81
+ createOpenAIRunner,
82
+ createOpenAIStreamingRunner,
83
+ } from "@directive-run/ai/openai";
84
+
85
+ const runner = createOpenAIRunner({
86
+ apiKey: process.env.OPENAI_API_KEY,
87
+ defaultModel: "gpt-4o",
88
+ organization: "org-xxx", // Optional
89
+ });
90
+
91
+ // Azure OpenAI
92
+ const azureRunner = createOpenAIRunner({
93
+ apiKey: process.env.AZURE_OPENAI_KEY,
94
+ baseURL: "https://my-instance.openai.azure.com/openai/deployments/gpt-4o",
95
+ defaultHeaders: {
96
+ "api-version": "2024-02-01",
97
+ },
98
+ });
99
+ ```
100
+
101
+ ## Ollama Adapter (Local Models)
102
+
103
+ ```typescript
104
+ import {
105
+ createOllamaRunner,
106
+ createOllamaStreamingRunner,
107
+ } from "@directive-run/ai/ollama";
108
+
109
+ const runner = createOllamaRunner({
110
+ // Default: http://localhost:11434
111
+ baseURL: "http://localhost:11434",
112
+ defaultModel: "llama3.1",
113
+ });
114
+
115
+ // Remote Ollama instance
116
+ const remoteRunner = createOllamaRunner({
117
+ baseURL: "https://ollama.my-server.com",
118
+ defaultModel: "mistral",
119
+ });
120
+ ```
121
+
122
+ ## Gemini Adapter
123
+
124
+ ```typescript
125
+ import {
126
+ createGeminiRunner,
127
+ createGeminiStreamingRunner,
128
+ } from "@directive-run/ai/gemini";
129
+
130
+ const runner = createGeminiRunner({
131
+ apiKey: process.env.GOOGLE_API_KEY,
132
+ defaultModel: "gemini-2.0-flash",
133
+ });
134
+ ```
135
+
136
+ ## Token Normalization
137
+
138
+ ### Anti-Pattern #27: Assuming provider-specific token structure
139
+
140
+ ```typescript
141
+ // WRONG – Anthropic returns { input_tokens, output_tokens } natively
142
+ // but adapters normalize this
143
+ const result = await runner.run(agent, prompt);
144
+ console.log(result.tokenUsage.input_tokens); // undefined!
145
+
146
+ // CORRECT – adapters normalize to camelCase
147
+ const result = await runner.run(agent, prompt);
148
+ console.log(result.tokenUsage.inputTokens); // number
149
+ console.log(result.tokenUsage.outputTokens); // number
150
+ console.log(result.totalTokens); // inputTokens + outputTokens
151
+ ```
152
+
153
+ All adapters normalize token usage to the same shape regardless of provider:
154
+
155
+ ```typescript
156
+ interface NormalizedTokenUsage {
157
+ inputTokens: number;
158
+ outputTokens: number;
159
+ }
160
+
161
+ // result.totalTokens = inputTokens + outputTokens
162
+ ```
163
+
164
+ ## Adapter Hooks
165
+
166
+ Every adapter supports lifecycle hooks for logging, metrics, or request modification:
167
+
168
+ ```typescript
169
+ const runner = createAnthropicRunner({
170
+ apiKey: process.env.ANTHROPIC_API_KEY,
171
+ hooks: {
172
+ // Called before every LLM API call
173
+ onBeforeCall: (agent, prompt, options) => {
174
+ console.log(`Calling ${agent.model} for ${agent.name}`);
175
+ metrics.increment("llm.calls");
176
+ },
177
+
178
+ // Called after every successful LLM API call
179
+ onAfterCall: (agent, result) => {
180
+ console.log(`${agent.name}: ${result.totalTokens} tokens`);
181
+ metrics.histogram("llm.tokens", result.totalTokens);
182
+ },
183
+
184
+ // Called on LLM API errors
185
+ onError: (agent, error) => {
186
+ console.error(`${agent.name} failed:`, error.message);
187
+ metrics.increment("llm.errors");
188
+ },
189
+ },
190
+ });
191
+ ```
192
+
193
+ ## Using Adapters with Orchestrators
194
+
195
+ ```typescript
196
+ import { createAgentOrchestrator } from "@directive-run/ai";
197
+ import { createAnthropicRunner } from "@directive-run/ai/anthropic";
198
+
199
+ const runner = createAnthropicRunner({
200
+ apiKey: process.env.ANTHROPIC_API_KEY,
201
+ });
202
+
203
+ // Single-agent
204
+ const orchestrator = createAgentOrchestrator({ runner });
205
+
206
+ // Multi-agent – same runner shared across all agents
207
+ const multiOrchestrator = createMultiAgentOrchestrator({
208
+ agents: { /* ... */ },
209
+ runner,
210
+ });
211
+ ```
212
+
213
+ ## Switching Adapters
214
+
215
+ Adapters are interchangeable. Switch providers by changing the import and config:
216
+
217
+ ```typescript
218
+ // Before: Anthropic
219
+ import { createAnthropicRunner } from "@directive-run/ai/anthropic";
220
+ const runner = createAnthropicRunner({
221
+ apiKey: process.env.ANTHROPIC_API_KEY,
222
+ });
223
+
224
+ // After: OpenAI – same orchestrator, different runner
225
+ import { createOpenAIRunner } from "@directive-run/ai/openai";
226
+ const runner = createOpenAIRunner({
227
+ apiKey: process.env.OPENAI_API_KEY,
228
+ });
229
+
230
+ // Orchestrator code remains unchanged
231
+ const orchestrator = createAgentOrchestrator({ runner });
232
+ ```
233
+
234
+ ## Custom BaseURL Patterns
235
+
236
+ | Provider | Default BaseURL | Custom Use Case |
237
+ |---|---|---|
238
+ | Anthropic | `https://api.anthropic.com` | Corporate proxy, VPN relay |
239
+ | OpenAI | `https://api.openai.com/v1` | Azure OpenAI, LiteLLM proxy |
240
+ | Ollama | `http://localhost:11434` | Remote GPU server |
241
+ | Gemini | `https://generativelanguage.googleapis.com` | Regional endpoint |
242
+
243
+ ## Quick Reference
244
+
245
+ | Adapter | Import Path | Key Options |
246
+ |---|---|---|
247
+ | Anthropic | `@directive-run/ai/anthropic` | `apiKey`, `defaultModel`, `maxTokens` |
248
+ | OpenAI | `@directive-run/ai/openai` | `apiKey`, `defaultModel`, `organization` |
249
+ | Ollama | `@directive-run/ai/ollama` | `baseURL`, `defaultModel` |
250
+ | Gemini | `@directive-run/ai/gemini` | `apiKey`, `defaultModel` |
251
+
252
+ All adapters support: `baseURL`, `defaultHeaders`, `hooks`, streaming variant.
253
+
254
+ ## See also
255
+
256
+ - [`ai-orchestrator.md`](./ai-orchestrator.md) — where the `AgentRunner` an adapter produces gets consumed
257
+ - [`ai-agents-streaming.md`](./ai-agents-streaming.md) — the `StreamingCallbackRunner` shape each adapter's streaming variant satisfies