@deepstrike/sdk 0.2.20 → 0.2.22

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/dist/index.d.ts CHANGED
@@ -60,6 +60,7 @@ export type { RegisteredTool } from "./tools/index.js";
60
60
  export { scanSkillDir, readSkillFile } from "./skills/loader.js";
61
61
  export type { SkillMetadata } from "./skills/loader.js";
62
62
  export { WorkingMemory } from "./memory/working.js";
63
+ export { InMemoryDreamStore } from "./memory/in-memory-store.js";
63
64
  export type { DreamStore, DreamResult, SessionData, SessionMessage, MemoryEntry, CurationResult, CurationStats, MemoryWriteRequest, MemoryQuery, MemoryRetrieval, MemoryMetadata, MemoryKind, } from "./memory/protocols.js";
64
65
  export type { KnowledgeSource } from "./knowledge/source.js";
65
66
  export { ScheduledPrompt } from "./signals/scheduled.js";
@@ -71,7 +72,7 @@ export { Governance, governancePolicyToKernelEvent } from "./governance.js";
71
72
  export type { GovernanceVerdict, GovernancePolicy, GovernanceConstraint } from "./governance.js";
72
73
  export { SinglePassHarness, EvalLoopHarness, HarnessLoop } from "./harness/harness.js";
73
74
  export type { HarnessRequest, HarnessOutcome, HarnessLoopOptions, QualityGate } from "./harness/harness.js";
74
- export type { Message, ToolCall, ToolResult, ToolSchema, ContentPart, TextPart, ImagePart, AudioPart, StreamEvent, TextDelta, ThinkingDelta, ToolCallEvent, ToolChunk, ToolDeltaEvent, ToolSuspendEvent, ToolResultEvent, DoneEvent, ErrorEvent, PermissionRequestEvent, PermissionResolvedEvent, PermissionResponse, LLMProvider, RetryConfig, TokenUsage, ProviderToolSpec, ProviderRunState, ProviderReplay, RenderedContext, ReplayabilityAssessment, } from "./types.js";
75
+ export type { Message, ToolCall, ToolResult, ToolSchema, ContentPart, TextPart, ImagePart, AudioPart, StreamEvent, TextDelta, ThinkingDelta, ToolCallEvent, ToolChunk, ToolDeltaEvent, ToolSuspendEvent, ToolResultEvent, DoneEvent, ErrorEvent, PermissionRequestEvent, PermissionResolvedEvent, PermissionResponse, LLMProvider, RetryConfig, TokenUsage, ProviderToolSpec, ProviderRunState, ProviderReplay, RenderedContext, ReplayabilityAssessment, CacheBreakpointStrategy, } from "./types.js";
75
76
  export type { AgentCapabilityFilter, AgentIdentity, AgentIsolation, AgentRunSpec, AgentProcessChangedObservation, ContextInheritance, KernelAgentRole, LoopResult, MilestoneCheckResult, MilestoneContract, MilestonePhase, MilestonePolicy, SubAgentResult, TerminationReason, WorkflowSpec, WorkflowNodeSpec, WorkflowTaskSpec, WorkflowSpawnInfo, } from "./types/agent.js";
76
77
  export { agentIdentitySub, agentRunSpecToKernel, milestoneCheckFail, milestoneCheckPass, milestoneCheckResultToKernel, subAgentResultToKernel, workflowSpecToKernel, workflowNodeSpecToKernel, submitWorkflowNodesToKernel, submitWorkflowToKernel, submitWorkflowNodesTool, startWorkflowTool, fanoutSynthesize, generateAndFilter, verifyRules, genEval, } from "./types/agent.js";
77
78
  export type { AcceptanceCriterion, VerificationContract, ContractCheckResult, } from "./collaboration/contract.js";
package/dist/index.js CHANGED
@@ -42,6 +42,7 @@ export { tool, streamingTool, executeTools, readFile, validateToolArguments } fr
42
42
  export { scanSkillDir, readSkillFile } from "./skills/loader.js";
43
43
  // ── Memory ─────────────────────────────────────────────────────────────────
44
44
  export { WorkingMemory } from "./memory/working.js";
45
+ export { InMemoryDreamStore } from "./memory/in-memory-store.js";
45
46
  export { ScheduledPrompt } from "./signals/scheduled.js";
46
47
  export { SignalGateway } from "./signals/gateway.js";
47
48
  // ── Safety & Governance ────────────────────────────────────────────────────
@@ -0,0 +1,37 @@
1
+ /**
2
+ * `InMemoryDreamStore` — a lightweight `DreamStore` implementation backed by per-agent `Map`s.
3
+ *
4
+ * Originally lived as `MockDreamStore` in the SDK's test helpers; promoted here so benchmarks,
5
+ * examples, and downstream consumers can use it without copying the boilerplate.
6
+ *
7
+ * Use cases:
8
+ * - Benchmark A/B variants where memory is on/off (preload via constructor).
9
+ * - Unit tests that exercise `Agent.dream()` or the `memory_query` path without disk I/O.
10
+ * - Local development / CI where a persistent memory store isn't needed.
11
+ *
12
+ * The `search()` impl is intentionally trivial — it returns the first `topK` memories for the
13
+ * agent regardless of `query`. The kernel ranks by score before deciding what to surface, so the
14
+ * order memories were inserted is what callers see. For semantic search, plug in a real store.
15
+ */
16
+ import type { CurationResult, DreamStore, MemoryEntry, SessionData } from "./protocols.js";
17
+ export declare class InMemoryDreamStore implements DreamStore {
18
+ private readonly initialMemories;
19
+ private sessions;
20
+ private memories;
21
+ /** Sessions persisted via `saveSession`; exposed for test assertions. */
22
+ readonly savedSessions: SessionData[];
23
+ /**
24
+ * @param initialMemories Optional seed memories applied to every agent that asks for memories
25
+ * for the first time. Useful for benchmark scenarios that preload a fact.
26
+ */
27
+ constructor(initialMemories?: MemoryEntry[]);
28
+ /** Pre-populate sessions for a specific agent (test/benchmark setup). */
29
+ addSession(agentId: string, session: SessionData): void;
30
+ /** Pre-populate memories for a specific agent (test/benchmark setup). */
31
+ addMemories(agentId: string, entries: MemoryEntry[]): void;
32
+ loadSessions(agentId: string): Promise<SessionData[]>;
33
+ loadMemories(agentId: string): Promise<MemoryEntry[]>;
34
+ commit(agentId: string, result: CurationResult, existing: MemoryEntry[]): Promise<void>;
35
+ search(agentId: string, _query: string, topK?: number): Promise<MemoryEntry[]>;
36
+ saveSession(data: SessionData): Promise<void>;
37
+ }
@@ -0,0 +1,50 @@
1
+ export class InMemoryDreamStore {
2
+ initialMemories;
3
+ sessions = new Map();
4
+ memories = new Map();
5
+ /** Sessions persisted via `saveSession`; exposed for test assertions. */
6
+ savedSessions = [];
7
+ /**
8
+ * @param initialMemories Optional seed memories applied to every agent that asks for memories
9
+ * for the first time. Useful for benchmark scenarios that preload a fact.
10
+ */
11
+ constructor(initialMemories = []) {
12
+ this.initialMemories = initialMemories;
13
+ }
14
+ /** Pre-populate sessions for a specific agent (test/benchmark setup). */
15
+ addSession(agentId, session) {
16
+ const list = this.sessions.get(agentId) ?? [];
17
+ list.push(session);
18
+ this.sessions.set(agentId, list);
19
+ }
20
+ /** Pre-populate memories for a specific agent (test/benchmark setup). */
21
+ addMemories(agentId, entries) {
22
+ this.memories.set(agentId, [...(this.memories.get(agentId) ?? []), ...entries]);
23
+ }
24
+ async loadSessions(agentId) {
25
+ return this.sessions.get(agentId) ?? [];
26
+ }
27
+ async loadMemories(agentId) {
28
+ if (this.memories.has(agentId))
29
+ return this.memories.get(agentId);
30
+ if (this.initialMemories.length > 0) {
31
+ this.memories.set(agentId, [...this.initialMemories]);
32
+ return this.memories.get(agentId);
33
+ }
34
+ return [];
35
+ }
36
+ async commit(agentId, result, existing) {
37
+ const kept = existing.filter((_, i) => !result.toRemoveIndices.includes(i));
38
+ this.memories.set(agentId, [...kept, ...result.toAdd]);
39
+ }
40
+ async search(agentId, _query, topK = 5) {
41
+ const all = await this.loadMemories(agentId);
42
+ return all.slice(0, topK);
43
+ }
44
+ async saveSession(data) {
45
+ this.savedSessions.push(data);
46
+ const list = this.sessions.get(data.agentId) ?? [];
47
+ list.push(data);
48
+ this.sessions.set(data.agentId, list);
49
+ }
50
+ }
@@ -77,19 +77,24 @@ export class AnthropicProvider {
77
77
  * (tools render before system), so a redundant tool breakpoint would only burn
78
78
  * one of Anthropic's 4 cache_control slots — slots the message history needs.
79
79
  */
80
- buildTools(tools, anchorCache) {
80
+ buildTools(tools, anchorCache, strategy) {
81
+ // Tool cache_control is emitted under "default" and "tools-only". "system-only",
82
+ // "frozen-prefix", and "none" all skip it.
83
+ const emitOnLastTool = anchorCache &&
84
+ (strategy === "default" || strategy === "tools-only");
81
85
  return tools.map((t, i) => ({
82
86
  name: t.name,
83
87
  description: t.description,
84
88
  input_schema: JSON.parse(t.parameters),
85
- ...(anchorCache && i === tools.length - 1 ? { cache_control: { type: "ephemeral" } } : {}),
89
+ ...(emitOnLastTool && i === tools.length - 1 ? { cache_control: { type: "ephemeral" } } : {}),
86
90
  }));
87
91
  }
88
92
  async complete(context, tools, extensions) {
89
93
  if (this.circuit.isOpen())
90
94
  throw new Error("Circuit breaker open");
91
- const system = this.buildSystem(context);
92
- const msgs = this.buildMessages(context);
95
+ const strategy = resolveCacheBreakpointStrategy(extensions);
96
+ const system = this.buildSystem(context, strategy);
97
+ const msgs = this.buildMessages(context, strategy);
93
98
  assertCacheBudget(system, tools.length);
94
99
  const requestExtensions = this.requestExtensions(extensions);
95
100
  let lastErr;
@@ -101,7 +106,7 @@ export class AnthropicProvider {
101
106
  max_tokens: typeof extensions?.max_tokens === "number" ? extensions.max_tokens : 8096,
102
107
  ...(system ? { system } : {}),
103
108
  messages: msgs,
104
- ...(tools.length ? { tools: this.buildTools(tools, !Array.isArray(system)) } : {}),
109
+ ...(tools.length ? { tools: this.buildTools(tools, !Array.isArray(system), strategy) } : {}),
105
110
  }, extensions);
106
111
  this.circuit.recordSuccess();
107
112
  let content = "";
@@ -129,8 +134,9 @@ export class AnthropicProvider {
129
134
  throw lastErr;
130
135
  }
131
136
  async *stream(context, tools, extensions, _state, signal) {
132
- const system = this.buildSystem(context);
133
- const msgs = this.buildMessages(context);
137
+ const strategy = resolveCacheBreakpointStrategy(extensions);
138
+ const system = this.buildSystem(context, strategy);
139
+ const msgs = this.buildMessages(context, strategy);
134
140
  assertCacheBudget(system, tools.length);
135
141
  const requestExtensions = this.requestExtensions(extensions);
136
142
  const toolBlocks = {};
@@ -143,7 +149,7 @@ export class AnthropicProvider {
143
149
  max_tokens: typeof extensions?.max_tokens === "number" ? extensions.max_tokens : 8096,
144
150
  ...(system ? { system } : {}),
145
151
  messages: msgs,
146
- ...(tools.length ? { tools: this.buildTools(tools, !Array.isArray(system)) } : {}),
152
+ ...(tools.length ? { tools: this.buildTools(tools, !Array.isArray(system), strategy) } : {}),
147
153
  }, extensions, signal);
148
154
  let uncachedInput = 0;
149
155
  let cacheReadTokens = 0;
@@ -235,7 +241,7 @@ export class AnthropicProvider {
235
241
  ? this.client.beta.messages.stream(params, opts)
236
242
  : this.client.messages.stream(params, opts));
237
243
  }
238
- buildSystem(context) {
244
+ buildSystem(context, strategy) {
239
245
  // B3 note: the system shape is content-driven — 0 blocks (string), 1 block
240
246
  // (stable only), or 2 blocks (stable + knowledge). The first turn `systemKnowledge`
241
247
  // appears, the block count rises 1→2, which is a one-time prompt-cache invalidation
@@ -245,23 +251,27 @@ export class AnthropicProvider {
245
251
  if (!context.systemStable && !context.systemKnowledge) {
246
252
  return context.systemText || undefined;
247
253
  }
254
+ // System cache_control is emitted under "default" and "system-only". Other strategies
255
+ // keep the text-block structure for protocol parity but omit cache_control.
256
+ const emitOnSystemBlocks = strategy === "default" || strategy === "system-only";
257
+ const cc = { type: "ephemeral" };
248
258
  const blocks = [];
249
259
  if (context.systemStable) {
250
- blocks.push({ type: "text", text: context.systemStable, cache_control: { type: "ephemeral" } });
260
+ blocks.push({ type: "text", text: context.systemStable, ...(emitOnSystemBlocks ? { cache_control: cc } : {}) });
251
261
  }
252
262
  if (context.systemKnowledge) {
253
- blocks.push({ type: "text", text: context.systemKnowledge, cache_control: { type: "ephemeral" } });
263
+ blocks.push({ type: "text", text: context.systemKnowledge, ...(emitOnSystemBlocks ? { cache_control: cc } : {}) });
254
264
  }
255
265
  return blocks.length ? blocks : undefined;
256
266
  }
257
- buildMessages(context) {
267
+ buildMessages(context, strategy) {
258
268
  const msgs = toAnthropicMessages(context.turns, message => this.nativeAssistantBlocks.get(assistantReplayKey(message)));
259
269
  // Cache breakpoints anchor on the stable history; the volatile State turn is
260
270
  // appended AFTER them as the uncached tail (so the history prefix re-reads
261
271
  // across turns). On un-rebuilt bindings stateTurn is absent and the state is
262
272
  // already inside `turns` — rendered as-is above. `frozenPrefixLen` (P1-E) pins
263
273
  // the deep breakpoint at the compaction boundary; absent ⇒ rolling-pair fallback.
264
- applyMessageCacheControl(msgs, context.frozenPrefixLen);
274
+ applyMessageCacheControl(msgs, context.frozenPrefixLen, strategy);
265
275
  if (context.stateTurn) {
266
276
  // Render through toAnthropicMessages so assistant tool_use blocks and
267
277
  // tool-role tool_result parts are serialized correctly — toAnthropicContent
@@ -282,6 +292,18 @@ export class AnthropicProvider {
282
292
  this.nativeAssistantBlocks.set(assistantReplayKey(message), blocks);
283
293
  }
284
294
  }
295
+ /** Recognised cache-breakpoint strategy values; any other input (incl. undefined) falls to `"default"`. */
296
+ const CACHE_BREAKPOINT_STRATEGIES = new Set([
297
+ "default", "tools-only", "system-only", "frozen-prefix", "none",
298
+ ]);
299
+ /** Pull `cacheBreakpointStrategy` from per-call extensions; unrecognised values → `"default"`. */
300
+ function resolveCacheBreakpointStrategy(extensions) {
301
+ const raw = extensions?.cacheBreakpointStrategy;
302
+ if (typeof raw === "string" && CACHE_BREAKPOINT_STRATEGIES.has(raw)) {
303
+ return raw;
304
+ }
305
+ return "default";
306
+ }
285
307
  /** Anthropic accepts at most this many cache_control breakpoints per request. */
286
308
  const MAX_CACHE_BREAKPOINTS = 4;
287
309
  /**
@@ -324,15 +346,21 @@ function assertCacheBudget(system, toolCount) {
324
346
  * cache_control attaches to the last content block of each target, promoting a bare
325
347
  * string body to a text block.
326
348
  */
327
- function applyMessageCacheControl(msgs, frozenPrefixLen) {
349
+ function applyMessageCacheControl(msgs, frozenPrefixLen, strategy) {
328
350
  if (!msgs.length)
329
351
  return;
352
+ // Message-level cache_control is emitted under "default" and "frozen-prefix" only.
353
+ // "tools-only", "system-only", and "none" skip the history entirely.
354
+ if (strategy === "tools-only" || strategy === "system-only" || strategy === "none")
355
+ return;
330
356
  const targets = new Set([msgs.length - 1]);
331
357
  if (typeof frozenPrefixLen === "number" && frozenPrefixLen >= 1 && frozenPrefixLen < msgs.length) {
332
358
  // Deep anchor at the frozen-prefix boundary (last frozen turn). Fixed between compactions.
333
359
  targets.add(frozenPrefixLen - 1);
334
360
  }
335
- else {
361
+ else if (strategy === "default") {
362
+ // Rolling fallback is part of the default strategy only — `"frozen-prefix"` deliberately
363
+ // skips it so a verify can isolate the deep-anchor contribution from the rolling pair.
336
364
  for (let i = msgs.length - 2; i >= 0 && targets.size < MESSAGE_CACHE_BREAKPOINTS; i--) {
337
365
  if (msgs[i].role === "user")
338
366
  targets.add(i);
package/dist/types.d.ts CHANGED
@@ -210,6 +210,34 @@ export interface RetryConfig {
210
210
  */
211
211
  export type ProviderRunState = Record<string, unknown>;
212
212
  export type ProviderProtocol = "anthropic-messages" | "openai-chat" | "openai-responses" | "gemini";
213
+ /**
214
+ * Strategy for placing Anthropic-protocol `cache_control` breakpoints across a request's
215
+ * static prefix (tools + system blocks) and rolling history (messages). Pass via the
216
+ * `extensions.cacheBreakpointStrategy` extension on every provider call; the runner already
217
+ * flows `RuntimeOptions.extensions` through, so setting it once on the runner propagates
218
+ * to every Anthropic-protocol call.
219
+ *
220
+ * Values:
221
+ * - `"default"` — current production behavior: a breakpoint on the last tool (when system
222
+ * is rendered as a string), one on each system block (when system blocks are present),
223
+ * and the rolling message pair (last message + frozen-prefix anchor or last preceding
224
+ * user turn).
225
+ * - `"tools-only"` — breakpoint on the last tool only. System blocks and history go
226
+ * uncached. Useful to isolate the tools-prefix cache contribution.
227
+ * - `"system-only"` — breakpoints on system blocks only. No tool, no history caching.
228
+ * Useful to isolate the system-prefix cache contribution.
229
+ * - `"frozen-prefix"` — breakpoints on the message history only, anchored at
230
+ * `frozenPrefixLen` (the compaction boundary, P1-E). Falls back to the last-message
231
+ * breakpoint when no frozen prefix is set. No tools, no system caching. Useful to
232
+ * stress-test the P1-E deep-anchor design.
233
+ * - `"none"` — no `cache_control` anywhere. The baseline for cache-hit attribution.
234
+ *
235
+ * Strategies that disable some breakpoints still keep the structural shape (e.g. system
236
+ * blocks remain text blocks rather than collapsing into a single string), so the only
237
+ * Δ between variants is which blocks carry `cache_control`. Unrecognised strings fall
238
+ * back to `"default"`.
239
+ */
240
+ export type CacheBreakpointStrategy = "default" | "tools-only" | "system-only" | "frozen-prefix" | "none";
213
241
  export interface ProviderDescriptor {
214
242
  provider: string;
215
243
  protocol: ProviderProtocol;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/sdk",
3
- "version": "0.2.20",
3
+ "version": "0.2.22",
4
4
  "description": "DeepStrike Node.js SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@anthropic-ai/sdk": "^0.99.0",
23
- "@deepstrike/core": "0.2.20",
23
+ "@deepstrike/core": "0.2.22",
24
24
  "@google/generative-ai": "^0.24.1",
25
25
  "openai": "^5.23.2"
26
26
  },