@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.
- package/CHANGELOG.md +4 -2
- package/README.md +17 -7
- package/dist/agent-definitions.d.ts +12 -0
- package/dist/agent-definitions.js +131 -0
- package/dist/agent-loops.d.ts +14 -0
- package/dist/agent-loops.js +161 -0
- package/dist/agents.js +263 -76
- package/dist/cache-helpers.d.ts +28 -0
- package/dist/cache-helpers.js +73 -0
- package/dist/cli-runner.d.ts +38 -2
- package/dist/cli-runner.js +167 -5
- package/dist/compaction.js +2 -0
- package/dist/config.js +47 -12
- package/dist/contracts.d.ts +581 -6
- package/dist/contracts.js +41 -1
- package/dist/contribution-parsing.d.ts +19 -0
- package/dist/contribution-parsing.js +124 -0
- package/dist/contributions.d.ts +13 -3
- package/dist/contributions.js +96 -20
- package/dist/extensions.js +3 -0
- package/dist/index.d.ts +19 -9
- package/dist/index.js +10 -4
- package/dist/input.d.ts +7 -1
- package/dist/input.js +52 -11
- package/dist/instruction-injection.d.ts +28 -0
- package/dist/instruction-injection.js +55 -0
- package/dist/manifests.d.ts +1 -1
- package/dist/manifests.js +3 -3
- package/dist/models.d.ts +4 -1
- package/dist/models.js +5 -2
- package/dist/node/agent-definitions.d.ts +98 -0
- package/dist/node/agent-definitions.js +389 -0
- package/dist/node/contribution-discovery.d.ts +17 -0
- package/dist/node/contribution-discovery.js +163 -0
- package/dist/node/instruction-injectors.d.ts +32 -0
- package/dist/node/instruction-injectors.js +72 -0
- package/dist/node/session-store-jsonl.d.ts +1 -1
- package/dist/node/session-store-jsonl.js +42 -4
- package/dist/node/system-project-prompts.d.ts +30 -0
- package/dist/node/system-project-prompts.js +53 -0
- package/dist/provider-events.d.ts +3 -1
- package/dist/provider-events.js +34 -0
- package/dist/provider-request-policy.js +15 -1
- package/dist/providers/openai-compatible.js +1 -1
- package/dist/providers.d.ts +6 -2
- package/dist/providers.js +15 -1
- package/dist/redaction.d.ts +2 -1
- package/dist/redaction.js +3 -0
- package/dist/registry-options.d.ts +5 -0
- package/dist/registry-options.js +5 -0
- package/dist/rpc.d.ts +6 -2
- package/dist/rpc.js +71 -13
- package/dist/session-stores.d.ts +3 -1
- package/dist/session-stores.js +67 -6
- package/dist/skills.d.ts +4 -1
- package/dist/skills.js +3 -1
- package/dist/system-prompts.js +6 -2
- package/dist/testing/compaction-conformance.d.ts +17 -0
- package/dist/testing/compaction-conformance.js +61 -0
- package/dist/testing/extension-conformance.d.ts +26 -0
- package/dist/testing/extension-conformance.js +55 -0
- package/dist/testing/provider-conformance.d.ts +7 -0
- package/dist/testing/provider-conformance.js +18 -31
- package/dist/testing/session-store-conformance.d.ts +20 -0
- package/dist/testing/session-store-conformance.js +92 -0
- package/dist/testing/tool-conformance.d.ts +39 -0
- package/dist/testing/tool-conformance.js +79 -0
- package/dist/tools.d.ts +7 -2
- package/dist/tools.js +50 -13
- package/docs/agent-definitions.md +251 -0
- package/docs/agent-events.md +199 -0
- package/docs/agent-loops.md +217 -0
- package/docs/agent-session-runtime.md +20 -8
- package/docs/cli-rpc.md +39 -4
- package/docs/compaction-and-retry.md +2 -2
- package/docs/compaction-conformance.md +76 -0
- package/docs/compaction-llm.md +6 -3
- package/docs/compaction-observational-memory.md +4 -4
- package/docs/configuration-and-manifests.md +6 -1
- package/docs/context-and-skills.md +79 -6
- package/docs/contribution-discovery.md +149 -0
- package/docs/contribution-registries.md +9 -6
- package/docs/credentials-and-redaction.md +2 -0
- package/docs/customization.md +191 -0
- package/docs/database-persistence.md +407 -0
- package/docs/extension-authoring.md +193 -0
- package/docs/extension-conformance.md +80 -0
- package/docs/extensions.md +6 -0
- package/docs/host-security.md +141 -0
- package/docs/index.md +40 -19
- package/docs/input-and-prompt-assembly.md +19 -3
- package/docs/instruction-injection.md +183 -0
- package/docs/migration.md +201 -0
- package/docs/model-registry.md +122 -0
- package/docs/node-jsonl-session-store.md +5 -4
- package/docs/performance.md +127 -0
- package/docs/provider-caching.md +206 -0
- package/docs/provider-conformance.md +32 -5
- package/docs/provider-layer.md +51 -11
- package/docs/provider-packages.md +65 -5
- package/docs/provider-request-policies.md +113 -0
- package/docs/providers/kimi.md +22 -0
- package/docs/providers/neuralwatt.md +388 -0
- package/docs/providers/openai-compatible.md +1 -0
- package/docs/providers/openai.md +21 -0
- package/docs/providers/opencode-go.md +31 -3
- package/docs/providers/openrouter.md +29 -0
- package/docs/providers/zai.md +17 -0
- package/docs/public-contracts.md +87 -12
- package/docs/release-and-install.md +76 -26
- package/docs/runs-and-usage.md +236 -0
- package/docs/session-store-conformance.md +78 -0
- package/docs/session-stores-and-branching.md +10 -6
- package/docs/session-stores.md +126 -0
- package/docs/settings-auth-trust-security.md +18 -4
- package/docs/structured-output.md +247 -0
- package/docs/system-prompts.md +104 -2
- package/docs/tool-conformance.md +87 -0
- package/docs/tools.md +64 -8
- 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
|
-
|
|
38
|
-
|
|
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.
|
|
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
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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
|
|
85
|
-
skills:
|
|
132
|
+
contextProviders,
|
|
133
|
+
skills: activeSkills,
|
|
86
134
|
tools,
|
|
87
135
|
resourceLoader: this.agent.config.resourceLoader,
|
|
88
|
-
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
this.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
|
|
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
|
-
|
|
132
|
-
this.emit({ type: "error", sessionId: this.id, runId, error:
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|