@arnilo/prism 0.0.1 → 0.0.2

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 (120) hide show
  1. package/CHANGELOG.md +4 -2
  2. package/README.md +17 -7
  3. package/dist/agent-definitions.d.ts +12 -0
  4. package/dist/agent-definitions.js +131 -0
  5. package/dist/agent-loops.d.ts +14 -0
  6. package/dist/agent-loops.js +161 -0
  7. package/dist/agents.js +263 -76
  8. package/dist/cache-helpers.d.ts +28 -0
  9. package/dist/cache-helpers.js +73 -0
  10. package/dist/cli-runner.d.ts +38 -2
  11. package/dist/cli-runner.js +167 -5
  12. package/dist/compaction.js +2 -0
  13. package/dist/config.js +47 -12
  14. package/dist/contracts.d.ts +581 -6
  15. package/dist/contracts.js +41 -1
  16. package/dist/contribution-parsing.d.ts +19 -0
  17. package/dist/contribution-parsing.js +124 -0
  18. package/dist/contributions.d.ts +13 -3
  19. package/dist/contributions.js +96 -20
  20. package/dist/extensions.js +3 -0
  21. package/dist/index.d.ts +19 -9
  22. package/dist/index.js +10 -4
  23. package/dist/input.d.ts +7 -1
  24. package/dist/input.js +52 -11
  25. package/dist/instruction-injection.d.ts +28 -0
  26. package/dist/instruction-injection.js +55 -0
  27. package/dist/manifests.d.ts +1 -1
  28. package/dist/manifests.js +3 -3
  29. package/dist/models.d.ts +4 -1
  30. package/dist/models.js +5 -2
  31. package/dist/node/agent-definitions.d.ts +98 -0
  32. package/dist/node/agent-definitions.js +389 -0
  33. package/dist/node/contribution-discovery.d.ts +17 -0
  34. package/dist/node/contribution-discovery.js +163 -0
  35. package/dist/node/instruction-injectors.d.ts +32 -0
  36. package/dist/node/instruction-injectors.js +72 -0
  37. package/dist/node/session-store-jsonl.d.ts +1 -1
  38. package/dist/node/session-store-jsonl.js +42 -4
  39. package/dist/node/system-project-prompts.d.ts +30 -0
  40. package/dist/node/system-project-prompts.js +53 -0
  41. package/dist/provider-events.d.ts +3 -1
  42. package/dist/provider-events.js +34 -0
  43. package/dist/provider-request-policy.js +15 -1
  44. package/dist/providers/openai-compatible.js +1 -1
  45. package/dist/providers.d.ts +6 -2
  46. package/dist/providers.js +15 -1
  47. package/dist/redaction.d.ts +2 -1
  48. package/dist/redaction.js +3 -0
  49. package/dist/registry-options.d.ts +5 -0
  50. package/dist/registry-options.js +5 -0
  51. package/dist/rpc.d.ts +6 -2
  52. package/dist/rpc.js +71 -13
  53. package/dist/session-stores.d.ts +3 -1
  54. package/dist/session-stores.js +67 -6
  55. package/dist/skills.d.ts +4 -1
  56. package/dist/skills.js +3 -1
  57. package/dist/system-prompts.js +6 -2
  58. package/dist/testing/compaction-conformance.d.ts +17 -0
  59. package/dist/testing/compaction-conformance.js +61 -0
  60. package/dist/testing/extension-conformance.d.ts +26 -0
  61. package/dist/testing/extension-conformance.js +55 -0
  62. package/dist/testing/provider-conformance.d.ts +7 -0
  63. package/dist/testing/provider-conformance.js +18 -31
  64. package/dist/testing/session-store-conformance.d.ts +20 -0
  65. package/dist/testing/session-store-conformance.js +92 -0
  66. package/dist/testing/tool-conformance.d.ts +39 -0
  67. package/dist/testing/tool-conformance.js +79 -0
  68. package/dist/tools.d.ts +7 -2
  69. package/dist/tools.js +50 -13
  70. package/docs/agent-definitions.md +251 -0
  71. package/docs/agent-events.md +199 -0
  72. package/docs/agent-loops.md +217 -0
  73. package/docs/agent-session-runtime.md +20 -8
  74. package/docs/cli-rpc.md +39 -4
  75. package/docs/compaction-and-retry.md +2 -2
  76. package/docs/compaction-conformance.md +76 -0
  77. package/docs/compaction-llm.md +6 -3
  78. package/docs/compaction-observational-memory.md +4 -4
  79. package/docs/configuration-and-manifests.md +6 -1
  80. package/docs/context-and-skills.md +79 -6
  81. package/docs/contribution-discovery.md +149 -0
  82. package/docs/contribution-registries.md +9 -6
  83. package/docs/credentials-and-redaction.md +2 -0
  84. package/docs/customization.md +191 -0
  85. package/docs/database-persistence.md +407 -0
  86. package/docs/extension-authoring.md +193 -0
  87. package/docs/extension-conformance.md +80 -0
  88. package/docs/extensions.md +6 -0
  89. package/docs/host-security.md +141 -0
  90. package/docs/index.md +40 -19
  91. package/docs/input-and-prompt-assembly.md +19 -3
  92. package/docs/instruction-injection.md +183 -0
  93. package/docs/migration.md +201 -0
  94. package/docs/model-registry.md +122 -0
  95. package/docs/node-jsonl-session-store.md +5 -4
  96. package/docs/performance.md +127 -0
  97. package/docs/provider-caching.md +206 -0
  98. package/docs/provider-conformance.md +32 -5
  99. package/docs/provider-layer.md +51 -11
  100. package/docs/provider-packages.md +65 -5
  101. package/docs/provider-request-policies.md +113 -0
  102. package/docs/providers/kimi.md +22 -0
  103. package/docs/providers/neuralwatt.md +388 -0
  104. package/docs/providers/openai-compatible.md +1 -0
  105. package/docs/providers/openai.md +21 -0
  106. package/docs/providers/opencode-go.md +31 -3
  107. package/docs/providers/openrouter.md +29 -0
  108. package/docs/providers/zai.md +17 -0
  109. package/docs/public-contracts.md +87 -12
  110. package/docs/release-and-install.md +76 -26
  111. package/docs/runs-and-usage.md +236 -0
  112. package/docs/session-store-conformance.md +78 -0
  113. package/docs/session-stores-and-branching.md +10 -6
  114. package/docs/session-stores.md +126 -0
  115. package/docs/settings-auth-trust-security.md +18 -4
  116. package/docs/structured-output.md +247 -0
  117. package/docs/system-prompts.md +104 -2
  118. package/docs/tool-conformance.md +87 -0
  119. package/docs/tools.md +64 -8
  120. package/package.json +35 -2
package/dist/agents.js CHANGED
@@ -1,11 +1,14 @@
1
+ import { resolveLoop } from "./agent-loops.js";
1
2
  import { createDefaultCompactionStrategy, isCompactionEntryData } from "./compaction.js";
2
3
  import { assembleProviderInput } from "./input.js";
4
+ import { providerToolCallDeltaContent, reconstructToolCallDeltas } from "./provider-events.js";
3
5
  import { createProviderRequestPolicyChain, mergeProviderRequestOptions, normalizeProviderRequestPolicyResult } from "./provider-request-policy.js";
4
- import { errorToErrorInfo, redactAgentEvent, redactProviderRequest, redactSecrets, redactSessionEntry } from "./redaction.js";
6
+ import { errorToErrorInfo, redactAgentEvent, redactProviderRequest, redactRunLedgerRecord, redactSecrets, redactSessionEntry } from "./redaction.js";
5
7
  import { composeSystemPrompt, mergeSystemPromptConfig } from "./system-prompts.js";
6
8
  import { createDefaultRetryPolicy, waitForRetry } from "./retry.js";
7
9
  import { createMemorySessionStore, createSessionEntry, getSessionBranchEntries, rebuildSessionContext } from "./session-stores.js";
8
10
  import { createToolRegistry, dispatchToolCall } from "./tools.js";
11
+ import { resolveActiveSkills } from "./skills.js";
9
12
  export function createAgent(config) {
10
13
  return {
11
14
  config,
@@ -27,6 +30,11 @@ class RuntimeAgentSession {
27
30
  history = [];
28
31
  activeRun;
29
32
  activeRedactor;
33
+ activeProvider;
34
+ activeLedger;
35
+ activeOwnership;
36
+ activeIdempotencyKey;
37
+ ledgerPromises = [];
30
38
  constructor(config) {
31
39
  this.id = config.id ?? randomId("session");
32
40
  this.agent = config.agent;
@@ -34,8 +42,11 @@ class RuntimeAgentSession {
34
42
  this.store = config.store ?? config.agent.config.store ?? createMemorySessionStore();
35
43
  this.currentLeafId = config.leafId;
36
44
  }
37
- subscribe() {
38
- const subscriber = new EventSubscriber(() => this.subscribers.delete(subscriber));
45
+ get leafId() {
46
+ return this.currentLeafId;
47
+ }
48
+ subscribe(options = {}) {
49
+ const subscriber = new EventSubscriber(this.id, options, () => this.subscribers.delete(subscriber));
39
50
  this.subscribers.add(subscriber);
40
51
  return subscriber;
41
52
  }
@@ -46,16 +57,35 @@ class RuntimeAgentSession {
46
57
  this.emit({ type: "error", sessionId: this.id, runId, error: errorToErrorInfo(error) });
47
58
  throw error;
48
59
  }
49
- let usage;
50
60
  const controller = new AbortController();
51
61
  const cleanupSignal = bridgeAbort(options.signal, controller);
52
62
  this.activeRun = controller;
53
63
  this.activeRedactor = options.redactor ?? this.agent.config.redactor;
64
+ this.activeLedger = options.runLedger ?? this.agent.config.runLedger;
65
+ this.activeOwnership = options.ownership ?? this.agent.config.ownership;
66
+ this.activeIdempotencyKey = options.idempotencyKey ?? this.agent.config.idempotencyKey;
67
+ const model = options.model ?? this.agent.config.model;
68
+ const startedAt = new Date().toISOString();
69
+ let runError;
54
70
  try {
55
- this.requireProvider();
71
+ this.resolveRunProvider(options);
56
72
  throwIfAborted(controller.signal);
57
73
  this.emit({ type: "agent_started", sessionId: this.id, runId });
74
+ const startRecord = {
75
+ id: runId,
76
+ sessionId: this.id,
77
+ branchId: this.currentLeafId,
78
+ model,
79
+ provider: model.provider,
80
+ idempotencyKey: this.activeIdempotencyKey,
81
+ status: "running",
82
+ startedAt,
83
+ ...this.activeOwnership,
84
+ };
85
+ await this.activeLedger?.appendRun(redactRunLedgerRecord(startRecord, this.activeRedactor));
58
86
  await this.rebuildHistory();
87
+ const { registry, tools } = activeTools(this.agent.config.tools);
88
+ const activeSkills = this.resolveRunSkills(options, tools);
59
89
  if (options.model && JSON.stringify(options.model) !== JSON.stringify(this.agent.config.model)) {
60
90
  await this.appendEntry(createSessionEntry({ sessionId: this.id, parentId: this.currentLeafId, runId, kind: "model_change", previousModel: this.agent.config.model, model: options.model }));
61
91
  }
@@ -64,80 +94,127 @@ class RuntimeAgentSession {
64
94
  await this.appendMessage(message, runId);
65
95
  await this.autoCompact(runId, options, controller.signal, inputMessages);
66
96
  const metadata = { ...this.agent.config.metadata, ...this.metadata, ...options.metadata };
67
- const { registry, tools } = activeTools(this.agent.config.tools);
68
97
  const maxToolRounds = options.maxToolRounds ?? 1;
69
- const toolResults = [];
70
- let toolRounds = 0;
71
- let nextInput = input;
72
- for (let turn = 1;; turn += 1) {
73
- throwIfAborted(controller.signal);
74
- this.emit({ type: "turn_started", sessionId: this.id, runId, turn });
75
- const request = await assembleProviderInput({
98
+ const systemInstructions = composeSystemPrompt(mergeSystemPromptConfig(this.agent.config.systemPrompt, options.systemPrompt), { base: this.agent.config.instructions });
99
+ const contextProviders = [
100
+ ...(this.agent.config.context ?? []),
101
+ // ponytail: skill context after host context; no per-skill token budget yet.
102
+ ...activeSkills.flatMap((skill) => skill.context ?? []),
103
+ ];
104
+ const providerOptions = mergeProviderRequestOptions(this.agent.config.providerOptions, options.providerOptions);
105
+ const validate = options.validate ?? this.agent.config.validator;
106
+ // ponytail: RunOptions.instructionInjectors overrides AgentConfig.instructionInjectors (mirrors validate/loop).
107
+ const instructionInjectors = options.instructionInjectors ?? this.agent.config.instructionInjectors ?? [];
108
+ const inputLayout = options.inputLayout ?? this.agent.config.inputLayout;
109
+ const loop = resolveLoop(options, this.agent.config);
110
+ // ponytail: LoopContext binds existing private helpers; loop orchestrates only.
111
+ const ctx = {
112
+ sessionId: this.id,
113
+ runId,
114
+ metadata,
115
+ signal: controller.signal,
116
+ history: this.history,
117
+ input,
118
+ inputMessages,
119
+ maxToolRounds,
120
+ assemble: async (nextInput, toolResults, turn) => assembleProviderInput({
76
121
  model: options.model ?? this.agent.config.model,
77
122
  input: nextInput,
78
123
  history: this.history,
79
124
  summaries: (await this.snapshot()).summaries,
80
- toolResults,
81
- systemInstructions: composeSystemPrompt(mergeSystemPromptConfig(this.agent.config.systemPrompt, options.systemPrompt), { base: this.agent.config.instructions }),
125
+ toolResults: toolResults ?? [],
126
+ turn,
127
+ instructionInjectors,
128
+ inputLayout,
129
+ systemInstructions,
82
130
  inputBuilder: this.agent.config.inputBuilder,
83
131
  promptBuilder: this.agent.config.promptBuilder,
84
- contextProviders: this.agent.config.context,
85
- skills: this.agent.config.skills ? ("list" in this.agent.config.skills ? this.agent.config.skills.list() : this.agent.config.skills) : undefined,
132
+ contextProviders,
133
+ skills: activeSkills,
86
134
  tools,
87
135
  resourceLoader: this.agent.config.resourceLoader,
88
- providerOptions: mergeProviderRequestOptions(this.agent.config.providerOptions, options.providerOptions),
136
+ providerOptions,
137
+ redactor: this.activeRedactor,
89
138
  middleware: this.agent.config.middleware,
90
139
  sessionId: this.id,
91
140
  runId,
92
141
  metadata,
93
142
  signal: controller.signal,
94
- });
95
- throwIfAborted(controller.signal);
96
- const policyResult = await this.applyProviderRequestPolicies(request, runId, options, metadata, controller.signal);
97
- const middlewareRequest = await this.agent.config.middleware?.run("provider_request", policyResult.request) ?? policyResult.request;
98
- const { content, calls, messageId, started, usage: turnUsage } = await this.generateWithRetry(this.redactProviderRequest(middlewareRequest), runId, options, controller.signal, policyResult.secrets);
99
- usage = turnUsage ?? usage;
100
- if (turn === 1)
101
- this.history.push(...inputMessages);
102
- if (started) {
103
- const message = { id: messageId, role: "assistant", content };
104
- this.history.push(message);
105
- await this.appendMessage(message, runId);
106
- this.emit({ type: "message_finished", sessionId: this.id, runId, message });
107
- }
108
- this.emit({ type: "turn_finished", sessionId: this.id, runId, turn });
109
- if (calls.length === 0 || toolRounds >= maxToolRounds)
110
- break;
111
- toolRounds += 1;
112
- for (const call of calls) {
113
- const result = await dispatchToolCall({
114
- call,
115
- registry,
116
- context: { sessionId: this.id, runId, toolCallId: call.id, signal: controller.signal, metadata },
117
- middleware: this.agent.config.middleware,
118
- emit: (event) => this.emit(event),
119
- permission: this.agent.config.permission,
120
- redactor: this.activeRedactor,
121
- });
122
- toolResults.push(result);
123
- await this.appendMessage(toolResultMessage(result), runId);
124
- throwIfAborted(controller.signal);
125
- }
126
- nextInput = [];
143
+ }),
144
+ generate: async (request) => {
145
+ const policyResult = await this.applyProviderRequestPolicies(request, runId, options, metadata, controller.signal);
146
+ const middlewareRequest = await this.agent.config.middleware?.run("provider_request", policyResult.request) ?? policyResult.request;
147
+ return this.generateWithRetry(this.redactProviderRequest(middlewareRequest), runId, options, controller.signal, policyResult.secrets);
148
+ },
149
+ dispatchToolCall: (call) => dispatchToolCall({
150
+ call,
151
+ registry,
152
+ context: { sessionId: this.id, runId, toolCallId: call.id, signal: controller.signal, metadata },
153
+ middleware: this.agent.config.middleware,
154
+ emit: (event) => this.emit(event),
155
+ permission: this.agent.config.permission,
156
+ redactor: this.activeRedactor,
157
+ ledger: this.activeLedger,
158
+ ownership: this.activeOwnership,
159
+ // ponytail: RunOptions wins; array-compose deferred (roadmap: compose-later).
160
+ validate,
161
+ }),
162
+ appendMessage: (message) => this.appendMessage(message, runId),
163
+ emit: (event) => this.emit(event),
164
+ };
165
+ const usage = await loop.run(ctx);
166
+ if (usage && this.activeLedger) {
167
+ const usageRecord = {
168
+ id: randomId("usage"),
169
+ sessionId: this.id,
170
+ runId,
171
+ usage,
172
+ recordedAt: new Date().toISOString(),
173
+ ...this.activeOwnership,
174
+ };
175
+ await this.activeLedger.appendUsage(redactRunLedgerRecord(usageRecord, this.activeRedactor));
127
176
  }
177
+ await this.drainLedger();
128
178
  this.emit({ type: "agent_finished", sessionId: this.id, runId, usage });
129
179
  }
130
180
  catch (error) {
131
- const info = errorToErrorInfo(error);
132
- this.emit({ type: "error", sessionId: this.id, runId, error: info });
181
+ runError = errorToErrorInfo(error);
182
+ this.emit({ type: "error", sessionId: this.id, runId, error: runError });
133
183
  throw error;
134
184
  }
135
185
  finally {
136
186
  if (this.activeRun === controller)
137
187
  this.activeRun = undefined;
138
- this.activeRedactor = undefined;
139
- cleanupSignal();
140
- this.closeSubscribers();
188
+ try {
189
+ await this.drainLedger();
190
+ if (this.activeLedger) {
191
+ const status = controller.signal.aborted ? "aborted" : runError ? "failed" : "succeeded";
192
+ const finishRecord = {
193
+ id: runId,
194
+ sessionId: this.id,
195
+ branchId: this.currentLeafId,
196
+ model,
197
+ provider: model.provider,
198
+ idempotencyKey: this.activeIdempotencyKey,
199
+ status,
200
+ startedAt,
201
+ finishedAt: new Date().toISOString(),
202
+ abortReason: controller.signal.aborted ? String(controller.signal.reason) : undefined,
203
+ error: runError,
204
+ ...this.activeOwnership,
205
+ };
206
+ await this.activeLedger.appendRun(redactRunLedgerRecord(finishRecord, this.activeRedactor));
207
+ }
208
+ }
209
+ finally {
210
+ this.activeLedger = undefined;
211
+ this.activeOwnership = undefined;
212
+ this.activeIdempotencyKey = undefined;
213
+ this.activeRedactor = undefined;
214
+ this.activeProvider = undefined;
215
+ cleanupSignal();
216
+ this.closeSubscribers();
217
+ }
141
218
  }
142
219
  }
143
220
  prompt(input, options) {
@@ -152,7 +229,10 @@ class RuntimeAgentSession {
152
229
  this.activeRun?.abort(reason);
153
230
  }
154
231
  async entries() {
155
- return getSessionBranchEntries(await this.store.list(this.id), { leafId: this.currentLeafId });
232
+ const reader = this.branchReader();
233
+ return reader
234
+ ? getSessionBranchEntries(reader, { sessionId: this.id, leafId: this.currentLeafId })
235
+ : getSessionBranchEntries(await this.store.list(this.id), { leafId: this.currentLeafId });
156
236
  }
157
237
  async checkout(leafId) {
158
238
  this.currentLeafId = leafId;
@@ -163,7 +243,11 @@ class RuntimeAgentSession {
163
243
  }
164
244
  async clone(options = {}) {
165
245
  const id = options.id ?? randomId("session");
166
- const branch = getSessionBranchEntries(await this.store.list(this.id), { leafId: options.leafId ?? this.currentLeafId });
246
+ const leafId = options.leafId ?? this.currentLeafId;
247
+ const reader = this.branchReader();
248
+ const branch = reader
249
+ ? await getSessionBranchEntries(reader, { sessionId: this.id, leafId })
250
+ : getSessionBranchEntries(await this.store.list(this.id), { leafId });
167
251
  const remap = new Map();
168
252
  for (const entry of branch) {
169
253
  const nextId = randomId("entry");
@@ -173,20 +257,65 @@ class RuntimeAgentSession {
173
257
  }
174
258
  return createAgentSession({ agent: this.agent, id, store: this.store, leafId: branch.length ? remap.get(branch[branch.length - 1].id) : undefined, metadata: this.metadata });
175
259
  }
176
- requireProvider() {
177
- if (!this.agent.config.provider)
178
- throw new Error(`Unknown provider: ${this.agent.config.model.provider}`);
260
+ branchReader() {
261
+ // ponytail: prefer the store's readBranchPath (one ancestor-chain query) when present so a
262
+ // DB-backed store never loads the full session; else fall back to list() + in-memory walk.
263
+ const read = this.store.readBranchPath;
264
+ return read ? (query) => read.call(this.store, query) : undefined;
265
+ }
266
+ resolveRunProvider(options) {
267
+ const model = options.model ?? this.agent.config.model;
268
+ // Provider precedence: an explicit `AgentConfig.provider` wins and bypasses
269
+ // the resolver entirely; otherwise `RunOptions.providerSource` overrides
270
+ // `AgentConfig.providerSource` for this run. A miss on every source fails
271
+ // closed with `Unknown provider: ${model.provider}` before any provider turn.
272
+ const provider = this.agent.config.provider ??
273
+ options.providerSource?.(model) ??
274
+ this.agent.config.providerSource?.(model);
275
+ if (!provider)
276
+ throw new Error(`Unknown provider: ${model.provider}`);
277
+ this.activeProvider = provider;
278
+ }
279
+ resolveRunSkills(options, tools) {
280
+ const configured = this.agent.config.skills;
281
+ if (configured && typeof configured === "object" && "list" in configured) {
282
+ if (options.activeSkills)
283
+ return resolveActiveSkills({ registry: configured, names: options.activeSkills, tools });
284
+ return configured.list();
285
+ }
286
+ const arr = options.skills ?? (Array.isArray(configured) ? configured : []);
287
+ return arr;
179
288
  }
180
289
  emit(event) {
181
290
  const redacted = redactAgentEvent(event, this.activeRedactor);
182
291
  for (const subscriber of this.subscribers)
183
292
  subscriber.push(redacted);
293
+ if (this.activeLedger) {
294
+ const record = {
295
+ id: randomId("event"),
296
+ sessionId: event.sessionId ?? this.id,
297
+ runId: event.runId,
298
+ type: event.type,
299
+ timestamp: new Date().toISOString(),
300
+ event: redacted,
301
+ redacted: Boolean(this.activeRedactor),
302
+ ...this.activeOwnership,
303
+ };
304
+ this.ledgerPromises.push(this.activeLedger.appendEvent(redactRunLedgerRecord(record, this.activeRedactor)));
305
+ }
184
306
  }
185
307
  closeSubscribers() {
186
308
  for (const subscriber of this.subscribers)
187
309
  subscriber.close();
188
310
  this.subscribers.clear();
189
311
  }
312
+ async drainLedger() {
313
+ if (this.ledgerPromises.length === 0)
314
+ return;
315
+ const pending = this.ledgerPromises;
316
+ this.ledgerPromises = [];
317
+ await Promise.all(pending);
318
+ }
190
319
  async generateWithRetry(request, runId, options, signal, requestSecrets = []) {
191
320
  const retry = mergeRetry(this.agent.config.retry, options.retry);
192
321
  const secrets = [...requestSecrets, ...(retry?.secrets ?? [])];
@@ -215,16 +344,29 @@ class RuntimeAgentSession {
215
344
  async generateProviderTurn(request, runId, signal, secrets = []) {
216
345
  const content = [];
217
346
  const calls = [];
347
+ const toolDeltas = [];
218
348
  let messageId;
219
349
  let started = false;
220
350
  let usage;
221
351
  try {
222
- for await (const event of this.agent.config.provider.generate(request)) {
352
+ for await (const event of this.activeProvider.generate(request)) {
223
353
  throwIfAborted(signal);
224
354
  if (event.type === "error")
225
355
  throw new ProviderTurnFailure(event.error, started);
226
- if (event.type === "usage")
356
+ if (event.type === "usage") {
227
357
  usage = event.usage;
358
+ if (this.activeLedger) {
359
+ const usageRecord = {
360
+ id: randomId("usage"),
361
+ sessionId: this.id,
362
+ runId,
363
+ usage: event.usage,
364
+ recordedAt: new Date().toISOString(),
365
+ ...this.activeOwnership,
366
+ };
367
+ await this.activeLedger.appendUsage(redactRunLedgerRecord(usageRecord, this.activeRedactor));
368
+ }
369
+ }
228
370
  if (event.type === "done") {
229
371
  usage = event.usage ?? usage;
230
372
  break;
@@ -235,11 +377,16 @@ class RuntimeAgentSession {
235
377
  this.emit({ type: "message_started", sessionId: this.id, runId, message: { id: messageId, role: "assistant", content: [] } });
236
378
  continue;
237
379
  }
238
- if (event.type === "content_delta" || event.type === "tool_call") {
380
+ if (event.type === "content_delta" || event.type === "tool_call" || event.type === "tool_call_delta") {
239
381
  if (!started) {
240
382
  started = true;
241
383
  this.emit({ type: "message_started", sessionId: this.id, runId, message: { role: "assistant", content: [] } });
242
384
  }
385
+ if (event.type === "tool_call_delta") {
386
+ toolDeltas.push(event);
387
+ this.emit({ type: "message_delta", sessionId: this.id, runId, content: providerToolCallDeltaContent(event) });
388
+ continue;
389
+ }
243
390
  const block = providerContent(event);
244
391
  content.push(block);
245
392
  if (block.type === "tool_call")
@@ -247,6 +394,11 @@ class RuntimeAgentSession {
247
394
  this.emit({ type: "message_delta", sessionId: this.id, runId, content: block });
248
395
  }
249
396
  }
397
+ for (const call of reconstructMissingToolCalls(toolDeltas, calls)) {
398
+ content.push(call);
399
+ calls.push(call);
400
+ this.emit({ type: "message_delta", sessionId: this.id, runId, content: call });
401
+ }
250
402
  return { content, calls, messageId, started, usage };
251
403
  }
252
404
  catch (error) {
@@ -298,7 +450,10 @@ class RuntimeAgentSession {
298
450
  }
299
451
  async appendEntry(entry) {
300
452
  const redacted = redactSessionEntry(entry, this.activeRedactor);
301
- await this.store.append(redacted);
453
+ await this.store.append(redacted, {
454
+ expectedParentId: this.currentLeafId,
455
+ idempotencyKey: this.activeIdempotencyKey,
456
+ });
302
457
  this.currentLeafId = redacted.id;
303
458
  }
304
459
  redact(value) {
@@ -311,16 +466,26 @@ class RuntimeAgentSession {
311
466
  this.history = (await this.snapshot()).messages.slice();
312
467
  }
313
468
  async snapshot() {
314
- return rebuildSessionContext(await this.store.list(this.id), { leafId: this.currentLeafId });
469
+ const reader = this.branchReader();
470
+ return reader
471
+ ? rebuildSessionContext(reader, { sessionId: this.id, leafId: this.currentLeafId })
472
+ : rebuildSessionContext(await this.store.list(this.id), { leafId: this.currentLeafId });
315
473
  }
316
474
  }
317
475
  class EventSubscriber {
476
+ sessionId;
318
477
  onClose;
319
478
  queue = [];
320
479
  waiters = [];
480
+ maxQueuedEvents;
481
+ overflow;
321
482
  closed = false;
322
- constructor(onClose) {
483
+ constructor(sessionId, options, onClose) {
484
+ this.sessionId = sessionId;
323
485
  this.onClose = onClose;
486
+ const maxQueuedEvents = options.maxQueuedEvents ?? 1024;
487
+ this.maxQueuedEvents = Number.isFinite(maxQueuedEvents) ? Math.max(1, Math.floor(maxQueuedEvents)) : 1024;
488
+ this.overflow = options.overflow ?? "close";
324
489
  }
325
490
  [Symbol.asyncIterator]() {
326
491
  return this;
@@ -340,10 +505,33 @@ class EventSubscriber {
340
505
  }
341
506
  push(event) {
342
507
  const waiter = this.waiters.shift();
343
- if (waiter)
508
+ if (waiter) {
344
509
  waiter({ value: event, done: false });
345
- else if (!this.closed)
510
+ return;
511
+ }
512
+ if (this.closed)
513
+ return;
514
+ if (this.queue.length < this.maxQueuedEvents) {
346
515
  this.queue.push(event);
516
+ return;
517
+ }
518
+ if (this.overflow === "drop_oldest") {
519
+ this.queue.shift();
520
+ this.queue.push(event);
521
+ return;
522
+ }
523
+ if (this.overflow === "drop_newest")
524
+ return;
525
+ const droppedEvents = this.queue.length + 1;
526
+ this.queue.splice(0, this.queue.length, {
527
+ type: "event_subscriber_overflow",
528
+ sessionId: this.sessionId,
529
+ droppedEvents,
530
+ maxQueuedEvents: this.maxQueuedEvents,
531
+ overflow: this.overflow,
532
+ });
533
+ this.close();
534
+ this.onClose();
347
535
  }
348
536
  close() {
349
537
  this.closed = true;
@@ -354,6 +542,12 @@ class EventSubscriber {
354
542
  function providerContent(event) {
355
543
  return event.type === "content_delta" ? event.content : event.call;
356
544
  }
545
+ function reconstructMissingToolCalls(deltas, calls) {
546
+ if (deltas.length === 0)
547
+ return [];
548
+ const seen = new Set(calls.map((call) => call.id));
549
+ return reconstructToolCallDeltas(deltas).filter((call) => !seen.has(call.id));
550
+ }
357
551
  function inputToMessages(input) {
358
552
  if (typeof input === "string")
359
553
  return [{ role: "user", content: [{ type: "text", text: input }] }];
@@ -361,13 +555,6 @@ function inputToMessages(input) {
361
555
  return [input];
362
556
  return [...input];
363
557
  }
364
- function toolResultMessage(result) {
365
- return {
366
- role: "tool",
367
- content: [{ type: "tool_result", toolCallId: result.toolCallId, name: result.name, result: result.value, error: result.error }, ...(result.content ?? [])],
368
- metadata: result.metadata,
369
- };
370
- }
371
558
  function activeTools(tools) {
372
559
  if (!tools)
373
560
  return { registry: createToolRegistry(), tools: [] };
@@ -0,0 +1,28 @@
1
+ import type { CacheRetention, ContentBlock, Message, ModelConfig, PromptCacheBreakpoint, Usage } from "./contracts.js";
2
+ export interface CacheControlValue {
3
+ readonly type: "ephemeral";
4
+ readonly ttl?: "1h";
5
+ }
6
+ export interface ApplyCacheControlOptions {
7
+ readonly ttl?: "1h";
8
+ readonly maxBreakpoints?: number;
9
+ }
10
+ export type CacheControlledContentBlock = ContentBlock & {
11
+ readonly cache_control?: CacheControlValue;
12
+ };
13
+ export type CacheControlledMessage = Omit<Message, "content"> & {
14
+ readonly content: readonly CacheControlledContentBlock[];
15
+ };
16
+ export interface CacheUsageReport {
17
+ readonly cacheReadTokens: number;
18
+ readonly cacheWriteTokens: number;
19
+ readonly hitRate?: number;
20
+ readonly estimatedSavings?: number;
21
+ readonly currency?: string;
22
+ }
23
+ export declare function sanitizeCacheKey(value: string | undefined, maxLength: number): string | undefined;
24
+ export declare function mapCacheRetention(retention: CacheRetention | undefined, model: ModelConfig): "short" | "long" | undefined;
25
+ export declare function applyCacheControl(messages: readonly Message[], breakpoints: readonly PromptCacheBreakpoint[], options?: ApplyCacheControlOptions): readonly CacheControlledMessage[];
26
+ export declare function cacheHitRate(usage: Usage | undefined): number | undefined;
27
+ export declare function cacheSavings(usage: Usage | undefined, model: ModelConfig): number | undefined;
28
+ export declare function cacheUsageReport(usage: Usage | undefined, model?: ModelConfig): CacheUsageReport | undefined;
@@ -0,0 +1,73 @@
1
+ export function sanitizeCacheKey(value, maxLength) {
2
+ const key = value?.replace(/[^A-Za-z0-9_.:-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, Math.max(0, maxLength));
3
+ return key || undefined;
4
+ }
5
+ export function mapCacheRetention(retention, model) {
6
+ if (!retention || retention === "none" || model.cache?.kind === "none")
7
+ return undefined;
8
+ return retention === "long" && model.cache?.longRetention === false ? "short" : retention;
9
+ }
10
+ export function applyCacheControl(messages, breakpoints, options = {}) {
11
+ const selected = new Set();
12
+ for (const breakpoint of breakpoints) {
13
+ const index = resolveBreakpoint(messages, breakpoint);
14
+ if (index >= 0)
15
+ selected.add(index);
16
+ if (options.maxBreakpoints && selected.size >= options.maxBreakpoints)
17
+ break;
18
+ }
19
+ if (!selected.size)
20
+ return messages;
21
+ const cache_control = options.ttl ? { type: "ephemeral", ttl: options.ttl } : { type: "ephemeral" };
22
+ return messages.map((message, index) => {
23
+ if (!selected.has(index) || !message.content.length)
24
+ return message;
25
+ const content = message.content.map((block, blockIndex) => blockIndex === message.content.length - 1 ? { ...block, cache_control } : block);
26
+ return { ...message, content };
27
+ });
28
+ }
29
+ export function cacheHitRate(usage) {
30
+ const read = usage?.cacheReadTokens;
31
+ const input = usage?.inputTokens;
32
+ return read === undefined || !input ? undefined : read / input;
33
+ }
34
+ export function cacheSavings(usage, model) {
35
+ const read = usage?.cacheReadTokens;
36
+ const input = model.cost?.input;
37
+ const cacheRead = model.cost?.cacheRead;
38
+ if (read === undefined || input === undefined || cacheRead === undefined)
39
+ return undefined;
40
+ return read * Math.max(0, input - cacheRead) / costUnitDivisor(model.cost?.unit);
41
+ }
42
+ export function cacheUsageReport(usage, model) {
43
+ if (!usage)
44
+ return undefined;
45
+ const estimatedSavings = model ? cacheSavings(usage, model) : undefined;
46
+ return {
47
+ cacheReadTokens: usage.cacheReadTokens ?? 0,
48
+ cacheWriteTokens: usage.cacheWriteTokens ?? 0,
49
+ hitRate: cacheHitRate(usage),
50
+ estimatedSavings,
51
+ currency: estimatedSavings === undefined ? usage.currency : model?.cost?.currency ?? usage.currency,
52
+ };
53
+ }
54
+ function resolveBreakpoint(messages, breakpoint) {
55
+ switch (breakpoint.location) {
56
+ case "system_prompt": return messages.findIndex((message) => message.role === "system");
57
+ case "tools": return messages.findIndex((message) => message.role === "tool" || message.content.some((block) => block.type === "tool_call" || block.type === "tool_result"));
58
+ case "stable_context": return messages.findIndex((message) => message.role !== "system");
59
+ case "last_stable_message": return messages.length > 1 ? messages.length - 2 : messages.length - 1;
60
+ case "last_user_message": return findLastIndex(messages, (message) => message.role === "user");
61
+ case "message_id": return messages.findIndex((message) => message.id === breakpoint.messageId);
62
+ }
63
+ }
64
+ function findLastIndex(items, predicate) {
65
+ for (let index = items.length - 1; index >= 0; index--)
66
+ if (predicate(items[index]))
67
+ return index;
68
+ return -1;
69
+ }
70
+ function costUnitDivisor(unit) {
71
+ return unit && /(?:1m|million)/i.test(unit) ? 1_000_000 : 1;
72
+ }
73
+ //# sourceMappingURL=cache-helpers.js.map