@directive-run/knowledge 1.23.0 → 1.24.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/ai/ai-adapters.md +28 -2
- package/ai/ai-agents-streaming.md +5 -1
- package/ai/ai-security.md +2 -2
- package/examples/cloudflare-directive-ai.ts +178 -0
- package/package.json +4 -4
- package/sitemap.md +9 -0
package/ai/ai-adapters.md
CHANGED
|
@@ -74,6 +74,27 @@ const proxiedRunner = createAnthropicRunner({
|
|
|
74
74
|
});
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
### Prompt Caching (Anthropic)
|
|
78
|
+
|
|
79
|
+
Opt in with `promptCaching: "automatic"` to add a `cache_control` breakpoint on the agent's instructions. Anthropic caches that stable system prefix so repeat calls that share it read from cache (~0.1x input cost) instead of reprocessing it; the variable message suffix stays uncached. Off by default (bare-string system, unchanged prior behavior), non-breaking, and no beta header is required on `anthropic-version: 2023-06-01`. Supported on the non-streaming `createAnthropicRunner` (streaming is a follow-up).
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
const runner = createAnthropicRunner({
|
|
83
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
84
|
+
promptCaching: "automatic",
|
|
85
|
+
});
|
|
86
|
+
const result = await runner(agent, prompt);
|
|
87
|
+
|
|
88
|
+
// Cache usage is surfaced on tokenUsage – present only when caching is active:
|
|
89
|
+
const { cacheReadTokens = 0, cacheCreationTokens = 0 } = result.tokenUsage;
|
|
90
|
+
// cacheCreationTokens – tokens written to cache on the first call (~1.25x cost)
|
|
91
|
+
// cacheReadTokens – tokens served from cache on repeat calls (~0.1x cost)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`inputTokens` remains the uncached remainder; the cache fields are separate and additive, and `totalTokens` includes all four (input + output + cache-read + cache-creation).
|
|
95
|
+
|
|
96
|
+
**Minimum cacheable prefix (the #1 support gotcha).** Anthropic silently ignores `cache_control` when the cached prefix is below a per-model minimum – ~1024 tokens on Sonnet-tier models, 2048 on Sonnet-4.6 & Haiku-3.5, 4096 on Opus & Haiku-4.5. No error is raised: caching simply doesn't happen and `cacheReadTokens` stays `0` on repeat calls (that `0` is the diagnostic). Since Directive caches `agent.instructions`, short instructions commonly fall below the threshold. The `ephemeral` breakpoint also carries a 5-minute default TTL – a prefix not re-read within that window is evicted. (Cost note: `withBudget`/`estimateCost` weight all tokens equally today, so cached runs are not yet repriced for the ~0.1x read / ~1.25x write rates – cache-aware pricing is a planned follow-up.)
|
|
97
|
+
|
|
77
98
|
## OpenAI Adapter
|
|
78
99
|
|
|
79
100
|
```typescript
|
|
@@ -147,7 +168,7 @@ console.log(result.tokenUsage.input_tokens); // undefined!
|
|
|
147
168
|
const result = await runner.run(agent, prompt);
|
|
148
169
|
console.log(result.tokenUsage.inputTokens); // number
|
|
149
170
|
console.log(result.tokenUsage.outputTokens); // number
|
|
150
|
-
console.log(result.totalTokens); // inputTokens + outputTokens
|
|
171
|
+
console.log(result.totalTokens); // inputTokens + outputTokens (+ cache tokens when caching is active)
|
|
151
172
|
```
|
|
152
173
|
|
|
153
174
|
All adapters normalize token usage to the same shape regardless of provider:
|
|
@@ -156,9 +177,14 @@ All adapters normalize token usage to the same shape regardless of provider:
|
|
|
156
177
|
interface NormalizedTokenUsage {
|
|
157
178
|
inputTokens: number;
|
|
158
179
|
outputTokens: number;
|
|
180
|
+
// Populated by adapters with prompt caching active (e.g. Anthropic
|
|
181
|
+
// `promptCaching: "automatic"`); omitted otherwise.
|
|
182
|
+
cacheReadTokens?: number;
|
|
183
|
+
cacheCreationTokens?: number;
|
|
159
184
|
}
|
|
160
185
|
|
|
161
186
|
// result.totalTokens = inputTokens + outputTokens
|
|
187
|
+
// + cacheReadTokens + cacheCreationTokens (cache fields are 0 when absent)
|
|
162
188
|
```
|
|
163
189
|
|
|
164
190
|
## Adapter Hooks
|
|
@@ -244,7 +270,7 @@ const orchestrator = createAgentOrchestrator({ runner });
|
|
|
244
270
|
|
|
245
271
|
| Adapter | Import Path | Key Options |
|
|
246
272
|
|---|---|---|
|
|
247
|
-
| Anthropic | `@directive-run/ai/anthropic` | `apiKey`, `defaultModel`, `maxTokens` |
|
|
273
|
+
| Anthropic | `@directive-run/ai/anthropic` | `apiKey`, `defaultModel`, `maxTokens`, `promptCaching` |
|
|
248
274
|
| OpenAI | `@directive-run/ai/openai` | `apiKey`, `defaultModel`, `organization` |
|
|
249
275
|
| Ollama | `@directive-run/ai/ollama` | `baseURL`, `defaultModel` |
|
|
250
276
|
| Gemini | `@directive-run/ai/gemini` | `apiKey`, `defaultModel` |
|
|
@@ -55,7 +55,11 @@ interface RunResult<T = unknown> {
|
|
|
55
55
|
interface TokenUsage {
|
|
56
56
|
inputTokens: number;
|
|
57
57
|
outputTokens: number;
|
|
58
|
-
//
|
|
58
|
+
// Populated by adapters with prompt caching active (e.g. Anthropic
|
|
59
|
+
// `promptCaching: "automatic"`); omitted otherwise.
|
|
60
|
+
cacheReadTokens?: number;
|
|
61
|
+
cacheCreationTokens?: number;
|
|
62
|
+
// Note: NO `total` field — sum `inputTokens + outputTokens` (+ cache tokens when present) when needed, or use `result.totalTokens`.
|
|
59
63
|
}
|
|
60
64
|
```
|
|
61
65
|
|
package/ai/ai-security.md
CHANGED
|
@@ -388,8 +388,8 @@ source publishes PII into a fact and the agent's prompt template embeds
|
|
|
388
388
|
that fact (`"Hello ${facts.email}..."`), the PII reaches the LLM call
|
|
389
389
|
without hitting the input guardrail chain.
|
|
390
390
|
|
|
391
|
-
This is
|
|
392
|
-
|
|
391
|
+
This is a documented gap in the input-only guardrail chain — the fix
|
|
392
|
+
is a fact-store-boundary guardrail that runs on every fact write:
|
|
393
393
|
|
|
394
394
|
```ts
|
|
395
395
|
import { createSystem, createModule, t } from "@directive-run/core";
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// Example: cloudflare-directive-ai
|
|
2
|
+
// Source: examples/cloudflare-directive-ai/src/module.ts
|
|
3
|
+
// Pure module file — no DOM wiring
|
|
4
|
+
|
|
5
|
+
import { createModule, createSystem } from "@directive-run/core";
|
|
6
|
+
import { sourceFromDOAlarm } from "@directive-run/sources/cloudflare";
|
|
7
|
+
import { schema, type PublishingStatus } from "./schema.js";
|
|
8
|
+
import type { AuditLog, Broadcaster } from "./deps.js";
|
|
9
|
+
import { createPublishingChain, type PublishingChain } from "./agents.js";
|
|
10
|
+
|
|
11
|
+
export interface PublishingDeps {
|
|
12
|
+
auditLog: AuditLog;
|
|
13
|
+
broadcaster: Broadcaster;
|
|
14
|
+
anthropicApiKey: string;
|
|
15
|
+
storage: DurableObjectStorage;
|
|
16
|
+
intervalMs?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PublishingModuleHandle {
|
|
20
|
+
alarmTick: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function createPublishingSystem(deps: PublishingDeps) {
|
|
24
|
+
const chain = createPublishingChain(deps.anthropicApiKey);
|
|
25
|
+
const handle: PublishingModuleHandle = {
|
|
26
|
+
alarmTick: () => {},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const alarmSource = sourceFromDOAlarm({
|
|
30
|
+
storage: deps.storage,
|
|
31
|
+
intervalMs: deps.intervalMs ?? 5 * 60 * 1000,
|
|
32
|
+
eventName: "tick",
|
|
33
|
+
payload: () => ({}),
|
|
34
|
+
onTickRegistered: (tick) => {
|
|
35
|
+
handle.alarmTick = tick;
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const publishingModule = createModule("publishing", {
|
|
40
|
+
schema,
|
|
41
|
+
|
|
42
|
+
init: (facts) => {
|
|
43
|
+
facts.status = "idle" satisfies PublishingStatus;
|
|
44
|
+
facts.currentArticle = {};
|
|
45
|
+
facts.ingestQueue = [];
|
|
46
|
+
facts.reviewQueue = [];
|
|
47
|
+
facts.errorLog = [];
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
derive: {
|
|
51
|
+
queueDepth: (facts) => facts.ingestQueue.length,
|
|
52
|
+
reviewDepth: (facts) => facts.reviewQueue.length,
|
|
53
|
+
isReadyToAggregate: (facts) =>
|
|
54
|
+
facts.ingestQueue.length >= 3 && facts.status === "idle",
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
events: {
|
|
58
|
+
ingest: (facts, payload) => {
|
|
59
|
+
facts.ingestQueue = [
|
|
60
|
+
...facts.ingestQueue,
|
|
61
|
+
{ url: payload.url, capturedAt: new Date().toISOString() },
|
|
62
|
+
];
|
|
63
|
+
},
|
|
64
|
+
tick: (facts) => {
|
|
65
|
+
if (facts.status === "failed" || facts.status === "published") {
|
|
66
|
+
facts.status = "idle";
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
sources: {
|
|
72
|
+
alarm: alarmSource,
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
constraints: {
|
|
76
|
+
startAggregation: {
|
|
77
|
+
priority: 100,
|
|
78
|
+
when: (facts) =>
|
|
79
|
+
facts.ingestQueue.length >= 3 && facts.status === "idle",
|
|
80
|
+
require: (facts) => ({
|
|
81
|
+
type: "RUN_PUBLISHING_CHAIN",
|
|
82
|
+
sources: facts.ingestQueue.slice(0, 8),
|
|
83
|
+
}),
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
routeArticle: {
|
|
87
|
+
priority: 90,
|
|
88
|
+
when: (facts) => facts.status === "reviewing",
|
|
89
|
+
require: (facts) => ({
|
|
90
|
+
type: "ROUTE_ARTICLE",
|
|
91
|
+
article: facts.currentArticle,
|
|
92
|
+
}),
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
resolvers: {
|
|
97
|
+
runChain: {
|
|
98
|
+
requirement: "RUN_PUBLISHING_CHAIN",
|
|
99
|
+
key: (req) => `chain-${req.sources.map((s) => s.url).join("|")}`,
|
|
100
|
+
resolve: async (req, context) => {
|
|
101
|
+
context.facts.status = "aggregating";
|
|
102
|
+
context.facts.ingestQueue = context.facts.ingestQueue.slice(
|
|
103
|
+
req.sources.length,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const result = await chain.run(req.sources, "demo");
|
|
108
|
+
|
|
109
|
+
context.facts.currentArticle = result;
|
|
110
|
+
context.facts.status = "reviewing";
|
|
111
|
+
await deps.auditLog.append({
|
|
112
|
+
event: "chain-completed",
|
|
113
|
+
at: new Date().toISOString(),
|
|
114
|
+
sources: req.sources.length,
|
|
115
|
+
confidence: result.confidence,
|
|
116
|
+
slug: result.slug,
|
|
117
|
+
});
|
|
118
|
+
} catch (err) {
|
|
119
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
120
|
+
context.facts.errorLog = [
|
|
121
|
+
...context.facts.errorLog,
|
|
122
|
+
{
|
|
123
|
+
at: new Date().toISOString(),
|
|
124
|
+
source: "publishing-chain",
|
|
125
|
+
message,
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
context.facts.status = "failed";
|
|
129
|
+
await deps.auditLog.append({
|
|
130
|
+
event: "chain-failed",
|
|
131
|
+
at: new Date().toISOString(),
|
|
132
|
+
error: message,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
routeArticle: {
|
|
139
|
+
requirement: "ROUTE_ARTICLE",
|
|
140
|
+
key: (req) => `route-${req.article.slug ?? "unknown"}`,
|
|
141
|
+
resolve: async (req, context) => {
|
|
142
|
+
const confidence = req.article.confidence ?? 0;
|
|
143
|
+
const slug = req.article.slug ?? "unknown";
|
|
144
|
+
|
|
145
|
+
if (confidence >= 0.85) {
|
|
146
|
+
await deps.broadcaster.send(req.article);
|
|
147
|
+
context.facts.status = "published";
|
|
148
|
+
await deps.auditLog.append({
|
|
149
|
+
event: "auto-published",
|
|
150
|
+
at: new Date().toISOString(),
|
|
151
|
+
slug,
|
|
152
|
+
confidence,
|
|
153
|
+
});
|
|
154
|
+
} else {
|
|
155
|
+
context.facts.reviewQueue = [
|
|
156
|
+
...context.facts.reviewQueue,
|
|
157
|
+
{ articleId: slug, confidence },
|
|
158
|
+
];
|
|
159
|
+
context.facts.status = "idle";
|
|
160
|
+
await deps.auditLog.append({
|
|
161
|
+
event: "routed-to-review",
|
|
162
|
+
at: new Date().toISOString(),
|
|
163
|
+
slug,
|
|
164
|
+
confidence,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const system = createSystem({ module: publishingModule });
|
|
173
|
+
|
|
174
|
+
return { system, handle };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export type PublishingSystem = ReturnType<typeof createPublishingSystem>;
|
|
178
|
+
export type { PublishingChain };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@directive-run/knowledge",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.0",
|
|
4
4
|
"description": "Knowledge files, examples, and validation for Directive — the constraint-driven TypeScript runtime.",
|
|
5
5
|
"license": "(MIT OR Apache-2.0)",
|
|
6
6
|
"author": "Jason Comes",
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"tsup": "^8.3.5",
|
|
53
53
|
"tsx": "^4.19.2",
|
|
54
54
|
"typescript": "^5.7.2",
|
|
55
|
-
"vitest": "^3.
|
|
56
|
-
"@directive-run/core": "1.
|
|
57
|
-
"@directive-run/ai": "1.
|
|
55
|
+
"vitest": "^3.2.6",
|
|
56
|
+
"@directive-run/core": "1.24.0",
|
|
57
|
+
"@directive-run/ai": "1.24.0"
|
|
58
58
|
},
|
|
59
59
|
"scripts": {
|
|
60
60
|
"build": "tsx scripts/generate-api-skeleton.ts && tsx scripts/generate-sitemap.ts && tsx scripts/extract-examples.ts && tsup",
|
package/sitemap.md
CHANGED
|
@@ -86,6 +86,7 @@ Website: https://directive.run
|
|
|
86
86
|
- [Persistence](https://directive.run/docs/plugins/persistence)
|
|
87
87
|
- [Performance](https://directive.run/docs/plugins/performance)
|
|
88
88
|
- [Circuit Breaker](https://directive.run/docs/plugins/circuit-breaker)
|
|
89
|
+
- [Clobber Loop Detector](https://directive.run/docs/plugins/clobber-loop)
|
|
89
90
|
- [Observability](https://directive.run/docs/plugins/observability)
|
|
90
91
|
- [Custom Plugins](https://directive.run/docs/plugins/custom)
|
|
91
92
|
|
|
@@ -108,6 +109,14 @@ Website: https://directive.run
|
|
|
108
109
|
- [Resolver Binding](https://directive.run/docs/resolver-binding)
|
|
109
110
|
- [Audit Ledger](https://directive.run/docs/audit-ledger)
|
|
110
111
|
|
|
112
|
+
### Broadcast
|
|
113
|
+
- [Overview](https://directive.run/docs/broadcast)
|
|
114
|
+
- [GitHub Action](https://directive.run/docs/broadcast/action)
|
|
115
|
+
- [CLI](https://directive.run/docs/broadcast/cli)
|
|
116
|
+
- [Cloudflare Worker](https://directive.run/docs/broadcast/worker)
|
|
117
|
+
- [MCP server](https://directive.run/docs/broadcast/mcp)
|
|
118
|
+
- [README badge](https://directive.run/docs/broadcast/badge)
|
|
119
|
+
|
|
111
120
|
### Examples
|
|
112
121
|
- [Number Match](https://directive.run/docs/examples/counter)
|
|
113
122
|
- [Auth Flow](https://directive.run/docs/examples/auth-flow)
|