@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.
- package/.claude-plugin/plugin.json +6 -0
- package/LICENSE +26 -0
- package/README.md +113 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +66 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
- package/skills/README.md +9 -0
- package/skills/building-ai-agents/SKILL.md +414 -0
- package/skills/building-ai-agents/ai-adapters.md +257 -0
- package/skills/building-ai-agents/ai-agents-streaming.md +307 -0
- package/skills/building-ai-agents/ai-communication.md +304 -0
- package/skills/building-ai-agents/api-skeleton.md +5 -0
- package/skills/building-ai-agents/examples.md +849 -0
- package/skills/building-ai-orchestrators/SKILL.md +401 -0
- package/skills/building-ai-orchestrators/ai-multi-agent.md +375 -0
- package/skills/building-ai-orchestrators/ai-orchestrator.md +466 -0
- package/skills/building-ai-orchestrators/ai-tasks.md +268 -0
- package/skills/building-ai-orchestrators/api-skeleton.md +5 -0
- package/skills/building-ai-orchestrators/examples.md +1609 -0
- package/skills/building-directive-systems/SKILL.md +499 -0
- package/skills/building-directive-systems/api-skeleton.md +5 -0
- package/skills/building-directive-systems/examples.md +4149 -0
- package/skills/building-directive-systems/multi-module.md +327 -0
- package/skills/building-directive-systems/plugins.md +361 -0
- package/skills/building-directive-systems/react-adapter.md +376 -0
- package/skills/building-directive-systems/system-api.md +463 -0
- package/skills/getting-started-with-directive/SKILL.md +198 -0
- package/skills/getting-started-with-directive/api-skeleton.md +5 -0
- package/skills/getting-started-with-directive/core-patterns.md +240 -0
- package/skills/getting-started-with-directive/examples.md +87 -0
- package/skills/getting-started-with-directive/sitemap.md +202 -0
- package/skills/hardening-ai-systems/SKILL.md +377 -0
- package/skills/hardening-ai-systems/ai-budget-resilience.md +408 -0
- package/skills/hardening-ai-systems/ai-guardrails-memory.md +370 -0
- package/skills/hardening-ai-systems/ai-security.md +400 -0
- package/skills/hardening-ai-systems/api-skeleton.md +5 -0
- package/skills/hardening-ai-systems/examples.md +628 -0
- package/skills/migrating-to-directive/SKILL.md +249 -0
- package/skills/migrating-to-directive/anti-patterns.md +382 -0
- package/skills/migrating-to-directive/api-skeleton.md +5 -0
- package/skills/migrating-to-directive/core-patterns.md +240 -0
- package/skills/migrating-to-directive/examples.md +511 -0
- package/skills/migrating-to-directive/schema-types.md +273 -0
- package/skills/reviewing-directive-code/SKILL.md +232 -0
- package/skills/reviewing-directive-code/anti-patterns.md +382 -0
- package/skills/reviewing-directive-code/api-skeleton.md +5 -0
- package/skills/reviewing-directive-code/core-patterns.md +240 -0
- package/skills/reviewing-directive-code/examples.md +463 -0
- package/skills/reviewing-directive-code/naming.md +321 -0
- package/skills/scaffolding-directive-modules/SKILL.md +218 -0
- package/skills/scaffolding-directive-modules/api-skeleton.md +5 -0
- package/skills/scaffolding-directive-modules/core-patterns.md +240 -0
- package/skills/scaffolding-directive-modules/examples.md +1386 -0
- package/skills/scaffolding-directive-modules/naming.md +321 -0
- package/skills/scaffolding-directive-modules/schema-types.md +273 -0
- package/skills/testing-ai-systems/SKILL.md +398 -0
- package/skills/testing-ai-systems/ai-debug-observability.md +329 -0
- package/skills/testing-ai-systems/ai-mcp-rag.md +293 -0
- package/skills/testing-ai-systems/ai-testing-evals.md +485 -0
- package/skills/testing-ai-systems/api-skeleton.md +5 -0
- package/skills/testing-ai-systems/examples.md +1261 -0
- package/skills/testing-directive-code/SKILL.md +479 -0
- package/skills/testing-directive-code/api-skeleton.md +5 -0
- package/skills/testing-directive-code/examples.md +156 -0
- package/skills/testing-directive-code/history.md +351 -0
- package/skills/testing-directive-code/testing.md +412 -0
- package/skills/writing-directive-constraints/SKILL.md +486 -0
- package/skills/writing-directive-constraints/api-skeleton.md +5 -0
- package/skills/writing-directive-constraints/constraints.md +274 -0
- package/skills/writing-directive-constraints/error-boundaries.md +330 -0
- package/skills/writing-directive-constraints/examples.md +1361 -0
- package/skills/writing-directive-constraints/resolvers.md +367 -0
- package/skills/writing-directive-modules/SKILL.md +342 -0
- package/skills/writing-directive-modules/anti-patterns.md +382 -0
- package/skills/writing-directive-modules/api-skeleton.md +5 -0
- package/skills/writing-directive-modules/core-patterns.md +240 -0
- package/skills/writing-directive-modules/examples.md +1639 -0
- package/skills/writing-directive-modules/naming.md +321 -0
- package/skills/writing-directive-modules/schema-types.md +273 -0
|
@@ -0,0 +1,849 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
> Auto-generated from extracted examples. Do not edit manually.
|
|
4
|
+
|
|
5
|
+
## ai-checkpoint
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
// Example: ai-checkpoint
|
|
9
|
+
// Source: examples/ai-checkpoint/src/module.ts
|
|
10
|
+
// Pure module file — no DOM wiring
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* AI Pipeline Checkpoint — Module Definition
|
|
14
|
+
*
|
|
15
|
+
* 4-stage pipeline (extract → summarize → classify → archive) with checkpoint
|
|
16
|
+
* at every stage. Save/restore/delete checkpoints. Retry with backoff on failures.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
type Checkpoint,
|
|
21
|
+
InMemoryCheckpointStore,
|
|
22
|
+
createCheckpointId,
|
|
23
|
+
validateCheckpoint,
|
|
24
|
+
} from "@directive-run/ai";
|
|
25
|
+
import {
|
|
26
|
+
type ModuleSchema,
|
|
27
|
+
createModule,
|
|
28
|
+
createSystem,
|
|
29
|
+
t,
|
|
30
|
+
} from "@directive-run/core";
|
|
31
|
+
import { devtoolsPlugin } from "@directive-run/core/plugins";
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// Types
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
export type PipelineStage =
|
|
38
|
+
| "idle"
|
|
39
|
+
| "extract"
|
|
40
|
+
| "summarize"
|
|
41
|
+
| "classify"
|
|
42
|
+
| "archive"
|
|
43
|
+
| "done"
|
|
44
|
+
| "error";
|
|
45
|
+
|
|
46
|
+
export interface StageResult {
|
|
47
|
+
stage: string;
|
|
48
|
+
output: string;
|
|
49
|
+
tokens: number;
|
|
50
|
+
durationMs: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface CheckpointEntry {
|
|
54
|
+
id: string;
|
|
55
|
+
label: string;
|
|
56
|
+
createdAt: string;
|
|
57
|
+
stage: PipelineStage;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface TimelineEntry {
|
|
61
|
+
time: number;
|
|
62
|
+
event: string;
|
|
63
|
+
detail: string;
|
|
64
|
+
type: "stage" | "checkpoint" | "retry" | "error" | "info" | "success";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ============================================================================
|
|
68
|
+
// Constants
|
|
69
|
+
// ============================================================================
|
|
70
|
+
|
|
71
|
+
export const STAGES: PipelineStage[] = [
|
|
72
|
+
"extract",
|
|
73
|
+
"summarize",
|
|
74
|
+
"classify",
|
|
75
|
+
"archive",
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
const STAGE_CONFIG = {
|
|
79
|
+
extract: {
|
|
80
|
+
tokens: 150,
|
|
81
|
+
baseLatency: 300,
|
|
82
|
+
output: "Extracted 3 sections, 2 tables, 5 figures from document.",
|
|
83
|
+
},
|
|
84
|
+
summarize: {
|
|
85
|
+
tokens: 200,
|
|
86
|
+
baseLatency: 400,
|
|
87
|
+
output:
|
|
88
|
+
"Summary: Key findings include efficiency gains of 23% and cost reduction of $1.2M annually.",
|
|
89
|
+
},
|
|
90
|
+
classify: {
|
|
91
|
+
tokens: 80,
|
|
92
|
+
baseLatency: 200,
|
|
93
|
+
output:
|
|
94
|
+
"Classification: category=research, confidence=0.94, tags=[efficiency, cost, annual-review]",
|
|
95
|
+
},
|
|
96
|
+
archive: {
|
|
97
|
+
tokens: 50,
|
|
98
|
+
baseLatency: 150,
|
|
99
|
+
output: "Archived to /documents/2026/research/efficiency-report.json",
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// Timeline
|
|
105
|
+
// ============================================================================
|
|
106
|
+
|
|
107
|
+
export const timeline: TimelineEntry[] = [];
|
|
108
|
+
|
|
109
|
+
export function addTimeline(
|
|
110
|
+
event: string,
|
|
111
|
+
detail: string,
|
|
112
|
+
type: TimelineEntry["type"],
|
|
113
|
+
) {
|
|
114
|
+
timeline.unshift({ time: Date.now(), event, detail, type });
|
|
115
|
+
if (timeline.length > 50) {
|
|
116
|
+
timeline.length = 50;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ============================================================================
|
|
121
|
+
// Checkpoint Store
|
|
122
|
+
// ============================================================================
|
|
123
|
+
|
|
124
|
+
export const checkpointStore = new InMemoryCheckpointStore({
|
|
125
|
+
maxCheckpoints: 20,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// Schema
|
|
130
|
+
// ============================================================================
|
|
131
|
+
|
|
132
|
+
export const schema = {
|
|
133
|
+
facts: {
|
|
134
|
+
currentStage: t.string<PipelineStage>(),
|
|
135
|
+
stageResults: t.array<StageResult>(),
|
|
136
|
+
totalTokens: t.number(),
|
|
137
|
+
retryCount: t.number(),
|
|
138
|
+
maxRetries: t.number(),
|
|
139
|
+
failStage: t.string(),
|
|
140
|
+
isRunning: t.boolean(),
|
|
141
|
+
lastError: t.string(),
|
|
142
|
+
checkpoints: t.array<CheckpointEntry>(),
|
|
143
|
+
selectedCheckpoint: t.string(),
|
|
144
|
+
},
|
|
145
|
+
derivations: {
|
|
146
|
+
completionPercentage: t.number(),
|
|
147
|
+
currentStageIndex: t.number(),
|
|
148
|
+
canAdvance: t.boolean(),
|
|
149
|
+
isPipelineDone: t.boolean(),
|
|
150
|
+
stageCount: t.number(),
|
|
151
|
+
},
|
|
152
|
+
events: {
|
|
153
|
+
setFailStage: { value: t.string() },
|
|
154
|
+
setMaxRetries: { value: t.number() },
|
|
155
|
+
selectCheckpoint: { id: t.string() },
|
|
156
|
+
reset: {},
|
|
157
|
+
},
|
|
158
|
+
requirements: {},
|
|
159
|
+
} satisfies ModuleSchema;
|
|
160
|
+
|
|
161
|
+
// ============================================================================
|
|
162
|
+
// Module
|
|
163
|
+
// ============================================================================
|
|
164
|
+
|
|
165
|
+
const pipelineModule = createModule("pipeline", {
|
|
166
|
+
schema,
|
|
167
|
+
|
|
168
|
+
init: (facts) => {
|
|
169
|
+
facts.currentStage = "idle";
|
|
170
|
+
facts.stageResults = [];
|
|
171
|
+
facts.totalTokens = 0;
|
|
172
|
+
facts.retryCount = 0;
|
|
173
|
+
facts.maxRetries = 2;
|
|
174
|
+
facts.failStage = "";
|
|
175
|
+
facts.isRunning = false;
|
|
176
|
+
facts.lastError = "";
|
|
177
|
+
facts.checkpoints = [];
|
|
178
|
+
facts.selectedCheckpoint = "";
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
derive: {
|
|
182
|
+
completionPercentage: (facts) => {
|
|
183
|
+
if (facts.currentStage === "idle") {
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
if (facts.currentStage === "done") {
|
|
187
|
+
return 100;
|
|
188
|
+
}
|
|
189
|
+
if (facts.currentStage === "error") {
|
|
190
|
+
const idx = facts.stageResults.length;
|
|
191
|
+
|
|
192
|
+
return Math.round((idx / STAGES.length) * 100);
|
|
193
|
+
}
|
|
194
|
+
const idx = STAGES.indexOf(facts.currentStage);
|
|
195
|
+
|
|
196
|
+
return Math.round((idx / STAGES.length) * 100);
|
|
197
|
+
},
|
|
198
|
+
currentStageIndex: (facts) => {
|
|
199
|
+
if (facts.currentStage === "idle") {
|
|
200
|
+
return -1;
|
|
201
|
+
}
|
|
202
|
+
if (facts.currentStage === "done") {
|
|
203
|
+
return STAGES.length;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return STAGES.indexOf(facts.currentStage);
|
|
207
|
+
},
|
|
208
|
+
canAdvance: (facts) => {
|
|
209
|
+
return (
|
|
210
|
+
!facts.isRunning &&
|
|
211
|
+
facts.currentStage !== "done" &&
|
|
212
|
+
facts.currentStage !== "error"
|
|
213
|
+
);
|
|
214
|
+
},
|
|
215
|
+
isPipelineDone: (facts) => facts.currentStage === "done",
|
|
216
|
+
stageCount: () => STAGES.length,
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
events: {
|
|
220
|
+
setFailStage: (facts, { value }) => {
|
|
221
|
+
facts.failStage = value;
|
|
222
|
+
},
|
|
223
|
+
setMaxRetries: (facts, { value }) => {
|
|
224
|
+
facts.maxRetries = value;
|
|
225
|
+
},
|
|
226
|
+
selectCheckpoint: (facts, { id }) => {
|
|
227
|
+
facts.selectedCheckpoint = id;
|
|
228
|
+
},
|
|
229
|
+
reset: (facts) => {
|
|
230
|
+
facts.currentStage = "idle";
|
|
231
|
+
facts.stageResults = [];
|
|
232
|
+
facts.totalTokens = 0;
|
|
233
|
+
facts.retryCount = 0;
|
|
234
|
+
facts.isRunning = false;
|
|
235
|
+
facts.lastError = "";
|
|
236
|
+
facts.selectedCheckpoint = "";
|
|
237
|
+
timeline.length = 0;
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// ============================================================================
|
|
243
|
+
// System
|
|
244
|
+
// ============================================================================
|
|
245
|
+
|
|
246
|
+
export const system = createSystem({
|
|
247
|
+
module: pipelineModule,
|
|
248
|
+
trace: true,
|
|
249
|
+
plugins: [devtoolsPlugin({ name: "ai-checkpoint" })],
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// ============================================================================
|
|
253
|
+
// Pipeline Logic
|
|
254
|
+
// ============================================================================
|
|
255
|
+
|
|
256
|
+
async function runStage(stage: PipelineStage): Promise<StageResult> {
|
|
257
|
+
const config = STAGE_CONFIG[stage as keyof typeof STAGE_CONFIG];
|
|
258
|
+
if (!config) {
|
|
259
|
+
throw new Error(`Unknown stage: ${stage}`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Simulate latency
|
|
263
|
+
const latency = config.baseLatency + Math.random() * 100;
|
|
264
|
+
await new Promise((resolve) => setTimeout(resolve, latency));
|
|
265
|
+
|
|
266
|
+
// Check for injected failure
|
|
267
|
+
if (system.facts.failStage === stage) {
|
|
268
|
+
throw new Error(`${stage}: simulated failure`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
stage,
|
|
273
|
+
output: config.output,
|
|
274
|
+
tokens: config.tokens + Math.floor(Math.random() * 30),
|
|
275
|
+
durationMs: Math.round(latency),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export async function runStageWithRetry(
|
|
280
|
+
stage: PipelineStage,
|
|
281
|
+
renderCallback: () => void,
|
|
282
|
+
): Promise<StageResult> {
|
|
283
|
+
const maxRetries = system.facts.maxRetries;
|
|
284
|
+
let lastError: Error | null = null;
|
|
285
|
+
|
|
286
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
287
|
+
try {
|
|
288
|
+
if (attempt > 0) {
|
|
289
|
+
const delay = Math.min(500 * 2 ** (attempt - 1), 4000);
|
|
290
|
+
const jitter = Math.random() * delay * 0.1;
|
|
291
|
+
system.facts.retryCount = system.facts.retryCount + 1;
|
|
292
|
+
addTimeline(
|
|
293
|
+
"retry",
|
|
294
|
+
`${stage}: attempt ${attempt + 1}/${maxRetries + 1} (delay ${Math.round(delay)}ms)`,
|
|
295
|
+
"retry",
|
|
296
|
+
);
|
|
297
|
+
renderCallback();
|
|
298
|
+
await new Promise((resolve) => setTimeout(resolve, delay + jitter));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return await runStage(stage);
|
|
302
|
+
} catch (err) {
|
|
303
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
304
|
+
addTimeline("error", `${stage}: ${lastError.message}`, "error");
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
throw lastError!;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export async function advancePipeline(
|
|
312
|
+
renderCallback: () => void,
|
|
313
|
+
): Promise<void> {
|
|
314
|
+
if (system.facts.isRunning) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const current = system.facts.currentStage;
|
|
319
|
+
let nextStage: PipelineStage;
|
|
320
|
+
|
|
321
|
+
if (current === "idle") {
|
|
322
|
+
nextStage = STAGES[0]!;
|
|
323
|
+
} else if (current === "done" || current === "error") {
|
|
324
|
+
return;
|
|
325
|
+
} else {
|
|
326
|
+
const idx = STAGES.indexOf(current);
|
|
327
|
+
if (idx < 0 || idx >= STAGES.length - 1) {
|
|
328
|
+
// Current stage should be run first
|
|
329
|
+
nextStage = current;
|
|
330
|
+
} else {
|
|
331
|
+
nextStage = STAGES[idx + 1]!;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
system.facts.isRunning = true;
|
|
336
|
+
system.facts.currentStage = nextStage;
|
|
337
|
+
addTimeline("stage", `${nextStage}: starting`, "stage");
|
|
338
|
+
renderCallback();
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
const result = await runStageWithRetry(nextStage, renderCallback);
|
|
342
|
+
const results = [...system.facts.stageResults, result];
|
|
343
|
+
system.facts.stageResults = results;
|
|
344
|
+
system.facts.totalTokens = system.facts.totalTokens + result.tokens;
|
|
345
|
+
addTimeline(
|
|
346
|
+
"success",
|
|
347
|
+
`${nextStage}: complete (${result.tokens} tokens)`,
|
|
348
|
+
"success",
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
const idx = STAGES.indexOf(nextStage);
|
|
352
|
+
if (idx >= STAGES.length - 1) {
|
|
353
|
+
system.facts.currentStage = "done";
|
|
354
|
+
addTimeline("info", "pipeline complete", "info");
|
|
355
|
+
} else {
|
|
356
|
+
system.facts.currentStage = nextStage;
|
|
357
|
+
}
|
|
358
|
+
} catch (err) {
|
|
359
|
+
system.facts.currentStage = "error";
|
|
360
|
+
system.facts.lastError = err instanceof Error ? err.message : String(err);
|
|
361
|
+
addTimeline("error", `pipeline halted: ${system.facts.lastError}`, "error");
|
|
362
|
+
} finally {
|
|
363
|
+
system.facts.isRunning = false;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export async function autoRun(renderCallback: () => void): Promise<void> {
|
|
368
|
+
if (system.facts.isRunning) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
system.facts.currentStage = "idle";
|
|
373
|
+
system.facts.stageResults = [];
|
|
374
|
+
system.facts.totalTokens = 0;
|
|
375
|
+
system.facts.retryCount = 0;
|
|
376
|
+
system.facts.lastError = "";
|
|
377
|
+
addTimeline("info", "auto-run started", "info");
|
|
378
|
+
|
|
379
|
+
for (const stage of STAGES) {
|
|
380
|
+
system.facts.isRunning = true;
|
|
381
|
+
system.facts.currentStage = stage;
|
|
382
|
+
addTimeline("stage", `${stage}: starting`, "stage");
|
|
383
|
+
renderCallback();
|
|
384
|
+
|
|
385
|
+
try {
|
|
386
|
+
const result = await runStageWithRetry(stage, renderCallback);
|
|
387
|
+
const results = [...system.facts.stageResults, result];
|
|
388
|
+
system.facts.stageResults = results;
|
|
389
|
+
system.facts.totalTokens = system.facts.totalTokens + result.tokens;
|
|
390
|
+
addTimeline(
|
|
391
|
+
"success",
|
|
392
|
+
`${stage}: complete (${result.tokens} tokens)`,
|
|
393
|
+
"success",
|
|
394
|
+
);
|
|
395
|
+
} catch (err) {
|
|
396
|
+
system.facts.currentStage = "error";
|
|
397
|
+
system.facts.lastError = err instanceof Error ? err.message : String(err);
|
|
398
|
+
system.facts.isRunning = false;
|
|
399
|
+
addTimeline(
|
|
400
|
+
"error",
|
|
401
|
+
`pipeline halted at ${stage}: ${system.facts.lastError}`,
|
|
402
|
+
"error",
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
system.facts.isRunning = false;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
system.facts.currentStage = "done";
|
|
412
|
+
addTimeline("info", "pipeline complete (auto-run)", "info");
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// ============================================================================
|
|
416
|
+
// Checkpoint Logic
|
|
417
|
+
// ============================================================================
|
|
418
|
+
|
|
419
|
+
export async function saveCheckpoint(): Promise<void> {
|
|
420
|
+
const stage = system.facts.currentStage;
|
|
421
|
+
const id = createCheckpointId();
|
|
422
|
+
const label = `Stage: ${stage} (${new Date().toLocaleTimeString()})`;
|
|
423
|
+
|
|
424
|
+
const checkpoint: Checkpoint = {
|
|
425
|
+
version: 1,
|
|
426
|
+
id,
|
|
427
|
+
createdAt: new Date().toISOString(),
|
|
428
|
+
label,
|
|
429
|
+
systemExport: JSON.stringify({
|
|
430
|
+
currentStage: system.facts.currentStage,
|
|
431
|
+
stageResults: system.facts.stageResults,
|
|
432
|
+
totalTokens: system.facts.totalTokens,
|
|
433
|
+
retryCount: system.facts.retryCount,
|
|
434
|
+
lastError: system.facts.lastError,
|
|
435
|
+
}),
|
|
436
|
+
timelineExport: JSON.stringify(timeline.slice(0, 20)),
|
|
437
|
+
localState: { type: "single" },
|
|
438
|
+
memoryExport: null,
|
|
439
|
+
orchestratorType: "single",
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
await checkpointStore.save(checkpoint);
|
|
443
|
+
|
|
444
|
+
const entry: CheckpointEntry = {
|
|
445
|
+
id,
|
|
446
|
+
label,
|
|
447
|
+
createdAt: checkpoint.createdAt,
|
|
448
|
+
stage,
|
|
449
|
+
};
|
|
450
|
+
system.facts.checkpoints = [...system.facts.checkpoints, entry];
|
|
451
|
+
|
|
452
|
+
addTimeline("checkpoint", `saved: ${label}`, "checkpoint");
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export async function restoreCheckpoint(checkpointId: string): Promise<void> {
|
|
456
|
+
const checkpoint = await checkpointStore.load(checkpointId);
|
|
457
|
+
if (!checkpoint) {
|
|
458
|
+
addTimeline("error", "checkpoint not found", "error");
|
|
459
|
+
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (!validateCheckpoint(checkpoint)) {
|
|
464
|
+
addTimeline("error", "invalid checkpoint data", "error");
|
|
465
|
+
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const saved = JSON.parse(checkpoint.systemExport);
|
|
470
|
+
system.facts.currentStage = saved.currentStage;
|
|
471
|
+
system.facts.stageResults = saved.stageResults;
|
|
472
|
+
system.facts.totalTokens = saved.totalTokens;
|
|
473
|
+
system.facts.retryCount = saved.retryCount;
|
|
474
|
+
system.facts.lastError = saved.lastError;
|
|
475
|
+
system.facts.isRunning = false;
|
|
476
|
+
|
|
477
|
+
if (checkpoint.timelineExport) {
|
|
478
|
+
const savedTimeline = JSON.parse(checkpoint.timelineExport);
|
|
479
|
+
timeline.length = 0;
|
|
480
|
+
for (const entry of savedTimeline) {
|
|
481
|
+
timeline.push(entry);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
addTimeline("checkpoint", `restored: ${checkpoint.label}`, "checkpoint");
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export async function deleteCheckpoint(checkpointId: string): Promise<void> {
|
|
489
|
+
const deleted = await checkpointStore.delete(checkpointId);
|
|
490
|
+
if (deleted) {
|
|
491
|
+
const checkpoints = system.facts.checkpoints.filter(
|
|
492
|
+
(c) => c.id !== checkpointId,
|
|
493
|
+
);
|
|
494
|
+
system.facts.checkpoints = checkpoints;
|
|
495
|
+
addTimeline("checkpoint", "deleted checkpoint", "checkpoint");
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
## provider-routing
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
// Example: provider-routing
|
|
504
|
+
// Source: examples/provider-routing/src/module.ts
|
|
505
|
+
// Pure module file — no DOM wiring
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Smart Provider Router — Module Definition
|
|
509
|
+
*
|
|
510
|
+
* 3 mock providers (OpenAI, Anthropic, Ollama). Constraint router selects based
|
|
511
|
+
* on cost, error rates, circuit state. Provider fallback chain.
|
|
512
|
+
*/
|
|
513
|
+
|
|
514
|
+
import {
|
|
515
|
+
type ModuleSchema,
|
|
516
|
+
createModule,
|
|
517
|
+
createSystem,
|
|
518
|
+
t,
|
|
519
|
+
} from "@directive-run/core";
|
|
520
|
+
import {
|
|
521
|
+
type CircuitState,
|
|
522
|
+
createCircuitBreaker,
|
|
523
|
+
devtoolsPlugin,
|
|
524
|
+
} from "@directive-run/core/plugins";
|
|
525
|
+
|
|
526
|
+
// ============================================================================
|
|
527
|
+
// Types
|
|
528
|
+
// ============================================================================
|
|
529
|
+
|
|
530
|
+
export interface ProviderStats {
|
|
531
|
+
name: string;
|
|
532
|
+
callCount: number;
|
|
533
|
+
errorCount: number;
|
|
534
|
+
totalCost: number;
|
|
535
|
+
avgLatencyMs: number;
|
|
536
|
+
circuitState: CircuitState;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
export interface TimelineEntry {
|
|
540
|
+
time: number;
|
|
541
|
+
event: string;
|
|
542
|
+
detail: string;
|
|
543
|
+
type: "route" | "error" | "fallback" | "circuit" | "info" | "success";
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// ============================================================================
|
|
547
|
+
// Mock Providers
|
|
548
|
+
// ============================================================================
|
|
549
|
+
|
|
550
|
+
const PROVIDERS = {
|
|
551
|
+
openai: { name: "OpenAI", costPer1k: 0.03, baseLatency: 200 },
|
|
552
|
+
anthropic: { name: "Anthropic", costPer1k: 0.025, baseLatency: 250 },
|
|
553
|
+
ollama: { name: "Ollama", costPer1k: 0.001, baseLatency: 400 },
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
const providerErrors: Record<string, boolean> = {
|
|
557
|
+
openai: false,
|
|
558
|
+
anthropic: false,
|
|
559
|
+
ollama: false,
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
export const circuitBreakers = {
|
|
563
|
+
openai: createCircuitBreaker({
|
|
564
|
+
name: "openai",
|
|
565
|
+
failureThreshold: 3,
|
|
566
|
+
recoveryTimeMs: 5000,
|
|
567
|
+
halfOpenMaxRequests: 2,
|
|
568
|
+
onStateChange: (from, to) =>
|
|
569
|
+
addTimeline("circuit", `openai: ${from} → ${to}`, "circuit"),
|
|
570
|
+
}),
|
|
571
|
+
anthropic: createCircuitBreaker({
|
|
572
|
+
name: "anthropic",
|
|
573
|
+
failureThreshold: 3,
|
|
574
|
+
recoveryTimeMs: 5000,
|
|
575
|
+
halfOpenMaxRequests: 2,
|
|
576
|
+
onStateChange: (from, to) =>
|
|
577
|
+
addTimeline("circuit", `anthropic: ${from} → ${to}`, "circuit"),
|
|
578
|
+
}),
|
|
579
|
+
ollama: createCircuitBreaker({
|
|
580
|
+
name: "ollama",
|
|
581
|
+
failureThreshold: 3,
|
|
582
|
+
recoveryTimeMs: 5000,
|
|
583
|
+
halfOpenMaxRequests: 2,
|
|
584
|
+
onStateChange: (from, to) =>
|
|
585
|
+
addTimeline("circuit", `ollama: ${from} → ${to}`, "circuit"),
|
|
586
|
+
}),
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
// ============================================================================
|
|
590
|
+
// Timeline
|
|
591
|
+
// ============================================================================
|
|
592
|
+
|
|
593
|
+
export const timeline: TimelineEntry[] = [];
|
|
594
|
+
|
|
595
|
+
export function addTimeline(
|
|
596
|
+
event: string,
|
|
597
|
+
detail: string,
|
|
598
|
+
type: TimelineEntry["type"],
|
|
599
|
+
) {
|
|
600
|
+
timeline.unshift({ time: Date.now(), event, detail, type });
|
|
601
|
+
if (timeline.length > 50) {
|
|
602
|
+
timeline.length = 50;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// ============================================================================
|
|
607
|
+
// Schema
|
|
608
|
+
// ============================================================================
|
|
609
|
+
|
|
610
|
+
export const schema = {
|
|
611
|
+
facts: {
|
|
612
|
+
openaiStats: t.object<ProviderStats>(),
|
|
613
|
+
anthropicStats: t.object<ProviderStats>(),
|
|
614
|
+
ollamaStats: t.object<ProviderStats>(),
|
|
615
|
+
budgetRemaining: t.number(),
|
|
616
|
+
budgetTotal: t.number(),
|
|
617
|
+
preferCheapest: t.boolean(),
|
|
618
|
+
lastProvider: t.string(),
|
|
619
|
+
totalRequests: t.number(),
|
|
620
|
+
lastError: t.string(),
|
|
621
|
+
},
|
|
622
|
+
derivations: {
|
|
623
|
+
openaiCircuit: t.string<CircuitState>(),
|
|
624
|
+
anthropicCircuit: t.string<CircuitState>(),
|
|
625
|
+
ollamaCircuit: t.string<CircuitState>(),
|
|
626
|
+
cheapestAvailable: t.string(),
|
|
627
|
+
allDown: t.boolean(),
|
|
628
|
+
},
|
|
629
|
+
events: {
|
|
630
|
+
toggleProviderError: { provider: t.string() },
|
|
631
|
+
setBudget: { value: t.number() },
|
|
632
|
+
togglePreferCheapest: {},
|
|
633
|
+
resetStats: {},
|
|
634
|
+
},
|
|
635
|
+
requirements: {},
|
|
636
|
+
} satisfies ModuleSchema;
|
|
637
|
+
|
|
638
|
+
// ============================================================================
|
|
639
|
+
// Module
|
|
640
|
+
// ============================================================================
|
|
641
|
+
|
|
642
|
+
function defaultStats(name: string): ProviderStats {
|
|
643
|
+
return {
|
|
644
|
+
name,
|
|
645
|
+
callCount: 0,
|
|
646
|
+
errorCount: 0,
|
|
647
|
+
totalCost: 0,
|
|
648
|
+
avgLatencyMs: 0,
|
|
649
|
+
circuitState: "CLOSED",
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const routerModule = createModule("router", {
|
|
654
|
+
schema,
|
|
655
|
+
|
|
656
|
+
init: (facts) => {
|
|
657
|
+
facts.openaiStats = defaultStats("OpenAI");
|
|
658
|
+
facts.anthropicStats = defaultStats("Anthropic");
|
|
659
|
+
facts.ollamaStats = defaultStats("Ollama");
|
|
660
|
+
facts.budgetRemaining = 1.0;
|
|
661
|
+
facts.budgetTotal = 1.0;
|
|
662
|
+
facts.preferCheapest = false;
|
|
663
|
+
facts.lastProvider = "";
|
|
664
|
+
facts.totalRequests = 0;
|
|
665
|
+
facts.lastError = "";
|
|
666
|
+
},
|
|
667
|
+
|
|
668
|
+
derive: {
|
|
669
|
+
openaiCircuit: () => circuitBreakers.openai.getState(),
|
|
670
|
+
anthropicCircuit: () => circuitBreakers.anthropic.getState(),
|
|
671
|
+
ollamaCircuit: () => circuitBreakers.ollama.getState(),
|
|
672
|
+
cheapestAvailable: () => {
|
|
673
|
+
const available: { name: string; cost: number }[] = [];
|
|
674
|
+
for (const [id, config] of Object.entries(PROVIDERS)) {
|
|
675
|
+
const breaker = circuitBreakers[id as keyof typeof circuitBreakers];
|
|
676
|
+
if (breaker.isAllowed()) {
|
|
677
|
+
available.push({ name: id, cost: config.costPer1k });
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
available.sort((a, b) => a.cost - b.cost);
|
|
681
|
+
|
|
682
|
+
return available.length > 0 ? available[0]!.name : "none";
|
|
683
|
+
},
|
|
684
|
+
allDown: () =>
|
|
685
|
+
!circuitBreakers.openai.isAllowed() &&
|
|
686
|
+
!circuitBreakers.anthropic.isAllowed() &&
|
|
687
|
+
!circuitBreakers.ollama.isAllowed(),
|
|
688
|
+
},
|
|
689
|
+
|
|
690
|
+
events: {
|
|
691
|
+
toggleProviderError: (_facts, { provider }) => {
|
|
692
|
+
providerErrors[provider] = !providerErrors[provider];
|
|
693
|
+
},
|
|
694
|
+
setBudget: (facts, { value }) => {
|
|
695
|
+
facts.budgetRemaining = value;
|
|
696
|
+
facts.budgetTotal = value;
|
|
697
|
+
},
|
|
698
|
+
togglePreferCheapest: (facts) => {
|
|
699
|
+
facts.preferCheapest = !facts.preferCheapest;
|
|
700
|
+
},
|
|
701
|
+
resetStats: (facts) => {
|
|
702
|
+
facts.openaiStats = defaultStats("OpenAI");
|
|
703
|
+
facts.anthropicStats = defaultStats("Anthropic");
|
|
704
|
+
facts.ollamaStats = defaultStats("Ollama");
|
|
705
|
+
facts.budgetRemaining = facts.budgetTotal;
|
|
706
|
+
facts.lastProvider = "";
|
|
707
|
+
facts.totalRequests = 0;
|
|
708
|
+
facts.lastError = "";
|
|
709
|
+
providerErrors.openai = false;
|
|
710
|
+
providerErrors.anthropic = false;
|
|
711
|
+
providerErrors.ollama = false;
|
|
712
|
+
circuitBreakers.openai.reset();
|
|
713
|
+
circuitBreakers.anthropic.reset();
|
|
714
|
+
circuitBreakers.ollama.reset();
|
|
715
|
+
timeline.length = 0;
|
|
716
|
+
},
|
|
717
|
+
},
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
// ============================================================================
|
|
721
|
+
// System
|
|
722
|
+
// ============================================================================
|
|
723
|
+
|
|
724
|
+
export const system = createSystem({
|
|
725
|
+
module: routerModule,
|
|
726
|
+
trace: true,
|
|
727
|
+
plugins: [devtoolsPlugin({ name: "provider-routing" })],
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
// ============================================================================
|
|
731
|
+
// Routing Logic
|
|
732
|
+
// ============================================================================
|
|
733
|
+
|
|
734
|
+
function selectProvider(): string | null {
|
|
735
|
+
const budget = system.facts.budgetRemaining;
|
|
736
|
+
const preferCheapest = system.facts.preferCheapest;
|
|
737
|
+
|
|
738
|
+
// Collect available providers (circuit breaker allows + within budget)
|
|
739
|
+
const available: { id: string; cost: number }[] = [];
|
|
740
|
+
for (const [id, config] of Object.entries(PROVIDERS)) {
|
|
741
|
+
const breaker = circuitBreakers[id as keyof typeof circuitBreakers];
|
|
742
|
+
if (breaker.isAllowed() && budget >= config.costPer1k) {
|
|
743
|
+
available.push({ id, cost: config.costPer1k });
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
if (available.length === 0) {
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (preferCheapest) {
|
|
752
|
+
available.sort((a, b) => a.cost - b.cost);
|
|
753
|
+
|
|
754
|
+
return available[0]!.id;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Default: prefer openai > anthropic > ollama
|
|
758
|
+
const priority = ["openai", "anthropic", "ollama"];
|
|
759
|
+
for (const id of priority) {
|
|
760
|
+
if (available.find((a) => a.id === id)) {
|
|
761
|
+
return id;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
return available[0]!.id;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
async function executeProvider(providerId: string): Promise<boolean> {
|
|
769
|
+
const breaker = circuitBreakers[providerId as keyof typeof circuitBreakers];
|
|
770
|
+
const config = PROVIDERS[providerId as keyof typeof PROVIDERS]!;
|
|
771
|
+
const statsKey = `${providerId}Stats` as
|
|
772
|
+
| "openaiStats"
|
|
773
|
+
| "anthropicStats"
|
|
774
|
+
| "ollamaStats";
|
|
775
|
+
|
|
776
|
+
try {
|
|
777
|
+
await breaker.execute(async () => {
|
|
778
|
+
await new Promise((resolve) =>
|
|
779
|
+
setTimeout(resolve, config.baseLatency + Math.random() * 100),
|
|
780
|
+
);
|
|
781
|
+
|
|
782
|
+
if (providerErrors[providerId]) {
|
|
783
|
+
throw new Error(`${config.name}: simulated error`);
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
const stats = system.facts[statsKey];
|
|
788
|
+
const cost = config.costPer1k;
|
|
789
|
+
const latency = config.baseLatency + Math.random() * 100;
|
|
790
|
+
system.facts[statsKey] = {
|
|
791
|
+
...stats,
|
|
792
|
+
callCount: stats.callCount + 1,
|
|
793
|
+
totalCost: Math.round((stats.totalCost + cost) * 1000) / 1000,
|
|
794
|
+
avgLatencyMs: Math.round(
|
|
795
|
+
(stats.avgLatencyMs * stats.callCount + latency) /
|
|
796
|
+
(stats.callCount + 1),
|
|
797
|
+
),
|
|
798
|
+
circuitState: breaker.getState(),
|
|
799
|
+
};
|
|
800
|
+
system.facts.budgetRemaining =
|
|
801
|
+
Math.round((system.facts.budgetRemaining - cost) * 1000) / 1000;
|
|
802
|
+
system.facts.lastError = "";
|
|
803
|
+
addTimeline("success", `${config.name}: ok ($${cost})`, "success");
|
|
804
|
+
|
|
805
|
+
return true;
|
|
806
|
+
} catch (err) {
|
|
807
|
+
const stats = system.facts[statsKey];
|
|
808
|
+
system.facts[statsKey] = {
|
|
809
|
+
...stats,
|
|
810
|
+
errorCount: stats.errorCount + 1,
|
|
811
|
+
circuitState: breaker.getState(),
|
|
812
|
+
};
|
|
813
|
+
system.facts.lastError = err instanceof Error ? err.message : String(err);
|
|
814
|
+
addTimeline("error", `${config.name}: ${system.facts.lastError}`, "error");
|
|
815
|
+
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
export async function sendRequest(): Promise<void> {
|
|
821
|
+
system.facts.totalRequests = system.facts.totalRequests + 1;
|
|
822
|
+
|
|
823
|
+
const providerId = selectProvider();
|
|
824
|
+
if (!providerId) {
|
|
825
|
+
system.facts.lastError = "All providers unavailable or over budget";
|
|
826
|
+
addTimeline("error", "no available providers", "error");
|
|
827
|
+
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
system.facts.lastProvider = providerId;
|
|
832
|
+
addTimeline("route", `→ ${providerId}`, "route");
|
|
833
|
+
|
|
834
|
+
const success = await executeProvider(providerId);
|
|
835
|
+
if (success) {
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Primary failed — try fallback
|
|
840
|
+
const fallbackId = selectProvider();
|
|
841
|
+
if (fallbackId && fallbackId !== providerId) {
|
|
842
|
+
addTimeline("fallback", `falling back to ${fallbackId}`, "fallback");
|
|
843
|
+
system.facts.lastProvider = fallbackId;
|
|
844
|
+
await executeProvider(fallbackId);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
export { providerErrors };
|
|
849
|
+
```
|