@agentplat/runtime 0.3.0-beta.2 → 0.3.0-beta.4

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 (42) hide show
  1. package/README.md +63 -0
  2. package/dist/adapter-bridge.d.ts +14 -0
  3. package/dist/adapter-bridge.d.ts.map +1 -0
  4. package/dist/adapter-bridge.js +267 -0
  5. package/dist/adapter-bridge.js.map +1 -0
  6. package/dist/adapter-contracts.d.ts +351 -0
  7. package/dist/adapter-contracts.d.ts.map +1 -0
  8. package/dist/adapter-contracts.js +2 -0
  9. package/dist/adapter-contracts.js.map +1 -0
  10. package/dist/adapter-errors.d.ts +6 -0
  11. package/dist/adapter-errors.d.ts.map +1 -0
  12. package/dist/adapter-errors.js +9 -0
  13. package/dist/adapter-errors.js.map +1 -0
  14. package/dist/adapter-registry.d.ts +25 -0
  15. package/dist/adapter-registry.d.ts.map +1 -0
  16. package/dist/adapter-registry.js +116 -0
  17. package/dist/adapter-registry.js.map +1 -0
  18. package/dist/adapter-runtime.d.ts +41 -0
  19. package/dist/adapter-runtime.d.ts.map +1 -0
  20. package/dist/adapter-runtime.js +591 -0
  21. package/dist/adapter-runtime.js.map +1 -0
  22. package/dist/adapter-store.d.ts +9 -0
  23. package/dist/adapter-store.d.ts.map +1 -0
  24. package/dist/adapter-store.js +26 -0
  25. package/dist/adapter-store.js.map +1 -0
  26. package/dist/adapter-validation.d.ts +39 -0
  27. package/dist/adapter-validation.d.ts.map +1 -0
  28. package/dist/adapter-validation.js +853 -0
  29. package/dist/adapter-validation.js.map +1 -0
  30. package/dist/adapter.d.ts +9 -0
  31. package/dist/adapter.d.ts.map +1 -0
  32. package/dist/adapter.js +9 -0
  33. package/dist/adapter.js.map +1 -0
  34. package/dist/cognitive-adapter.d.ts +259 -0
  35. package/dist/cognitive-adapter.d.ts.map +1 -0
  36. package/dist/cognitive-adapter.js +813 -0
  37. package/dist/cognitive-adapter.js.map +1 -0
  38. package/dist/index.d.ts +1 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +1 -0
  41. package/dist/index.js.map +1 -1
  42. package/package.json +13 -5
@@ -0,0 +1,813 @@
1
+ import { PortableAgentErrorV1 } from "./adapter-errors.js";
2
+ import { normalizeAdapterManifestV1 } from "./adapter-validation.js";
3
+ export const COGNITIVE_AGENT_ADAPTER_SCHEMA_VERSION_V2 = 2;
4
+ export const COGNITIVE_OPERATION_KINDS_V2 = Object.freeze([
5
+ "observe",
6
+ "plan",
7
+ "memory_query",
8
+ "memory_mutation",
9
+ "tool",
10
+ "alignment",
11
+ "intervention",
12
+ ]);
13
+ export const COGNITIVE_EFFECTFUL_OPERATION_KINDS_V2 = Object.freeze([
14
+ "memory_mutation",
15
+ "tool",
16
+ ]);
17
+ export class InMemoryCognitiveSessionStateStoreV2 {
18
+ #states = new Map();
19
+ #operations = new Map();
20
+ #revisionClaims = new Map();
21
+ async load(sessionId) {
22
+ const state = this.#states.get(sessionId);
23
+ return state ? immutable(state) : null;
24
+ }
25
+ async save(state, expectedRevision) {
26
+ const current = this.#states.get(state.sessionId);
27
+ if (expectedRevision !== null &&
28
+ this.#revisionClaims.has(durableRevisionKey(state.tenantId, state.sessionId, expectedRevision)))
29
+ return false;
30
+ if ((expectedRevision === null &&
31
+ (current !== undefined || state.revision !== 0)) ||
32
+ (expectedRevision !== null &&
33
+ (!current ||
34
+ current.revision !== expectedRevision ||
35
+ state.revision !== expectedRevision + 1)))
36
+ return false;
37
+ this.#states.set(state.sessionId, immutable(state));
38
+ return true;
39
+ }
40
+ async loadOperation(input) {
41
+ const operation = this.#operations.get(durableOperationKey(input.tenantId, input.sessionId, input.operationId));
42
+ return operation ? immutable(operation) : null;
43
+ }
44
+ async prepareOperation(input) {
45
+ const operation = input.operation;
46
+ const operationKey = durableOperationKey(operation.tenantId, operation.sessionId, operation.operationId);
47
+ const revisionKey = durableRevisionKey(operation.tenantId, operation.sessionId, operation.expectedSessionRevision);
48
+ const current = this.#states.get(operation.sessionId);
49
+ if (this.#operations.has(operationKey) ||
50
+ this.#revisionClaims.has(revisionKey) ||
51
+ !current ||
52
+ current.tenantId !== operation.tenantId ||
53
+ current.agentId !== operation.agentId ||
54
+ current.revision !== operation.expectedSessionRevision ||
55
+ current.stateDigest !== operation.previousStateDigest)
56
+ return false;
57
+ this.#operations.set(operationKey, immutable(operation));
58
+ this.#revisionClaims.set(revisionKey, operationKey);
59
+ return true;
60
+ }
61
+ async commitOperation(input) {
62
+ const operation = input.operation;
63
+ const operationKey = durableOperationKey(operation.tenantId, operation.sessionId, operation.operationId);
64
+ const revisionKey = durableRevisionKey(operation.tenantId, operation.sessionId, operation.expectedSessionRevision);
65
+ const prepared = this.#operations.get(operationKey);
66
+ const current = this.#states.get(operation.sessionId);
67
+ const outcome = operation.outcome;
68
+ if (input.expectedOperationRevision !== 0 ||
69
+ !prepared ||
70
+ prepared.status !== "prepared" ||
71
+ prepared.journalRevision !== input.expectedOperationRevision ||
72
+ !sameDurableOperationIdentity(prepared, operation) ||
73
+ this.#revisionClaims.get(revisionKey) !== operationKey ||
74
+ !current ||
75
+ current.tenantId !== operation.tenantId ||
76
+ current.agentId !== operation.agentId ||
77
+ current.revision !== operation.expectedSessionRevision ||
78
+ current.stateDigest !== operation.previousStateDigest ||
79
+ outcome.state.tenantId !== operation.tenantId ||
80
+ outcome.state.sessionId !== operation.sessionId ||
81
+ outcome.state.agentId !== operation.agentId ||
82
+ outcome.state.revision !== operation.expectedSessionRevision + 1 ||
83
+ outcome.receipt.operationId !== operation.operationId ||
84
+ outcome.receipt.previousStateDigest !== current.stateDigest ||
85
+ outcome.result.operationId !== operation.operationId ||
86
+ outcome.state.receipts.at(-1)?.receiptDigest !==
87
+ outcome.receipt.receiptDigest)
88
+ return false;
89
+ this.#states.set(operation.sessionId, immutable(outcome.state));
90
+ this.#operations.set(operationKey, immutable(operation));
91
+ return true;
92
+ }
93
+ }
94
+ /**
95
+ * Revision-checked cognitive operation host. It serializes no hidden reasoning,
96
+ * bounds session receipts, and uses a separate durable outcome journal only for
97
+ * adapters that advertise effectful operations.
98
+ */
99
+ export class CognitiveAgentRuntimeV2 {
100
+ #adapter;
101
+ #guard;
102
+ #integrity;
103
+ #store;
104
+ #durableStore;
105
+ #effectSink;
106
+ #maximumCommitAttempts;
107
+ #activeSessions = new Set();
108
+ constructor(options) {
109
+ validateManifest(options.adapter?.manifest);
110
+ if (!options.adapter.portable ||
111
+ typeof options.adapter.execute !== "function")
112
+ invalid("cognitive adapter implementation is required");
113
+ if (!options.guard || typeof options.guard.authorize !== "function")
114
+ invalid("cognitive operation guard is required");
115
+ this.#adapter = options.adapter;
116
+ this.#guard = options.guard;
117
+ this.#integrity =
118
+ options.integrity ?? createWebCryptoCognitiveIntegrityV2();
119
+ this.#store = options.store ?? new InMemoryCognitiveSessionStateStoreV2();
120
+ const effectCapable = options.adapter.manifest.operations.some((operation) => COGNITIVE_EFFECTFUL_OPERATION_KINDS_V2.includes(operation));
121
+ if (effectCapable) {
122
+ if (!options.store || !durableOperationStore(options.store))
123
+ throw new PortableAgentErrorV1("ADAPTER_INCOMPATIBLE", "effectful cognitive operations require an explicit durable operation store");
124
+ if (options.adapter.effectSink?.protocol !== "idempotent_effect_sink_v2" ||
125
+ typeof options.adapter.effectSink.lookup !== "function" ||
126
+ typeof options.adapter.effectSink.apply !== "function")
127
+ throw new PortableAgentErrorV1("ADAPTER_INCOMPATIBLE", "effectful cognitive operations require an idempotent effect sink with lookup");
128
+ this.#durableStore = options.store;
129
+ this.#effectSink = options.adapter.effectSink;
130
+ }
131
+ else {
132
+ this.#durableStore = null;
133
+ this.#effectSink = null;
134
+ }
135
+ this.#maximumCommitAttempts = boundedInteger(options.maximumCommitAttempts ?? 4, "maximumCommitAttempts", 1, 32);
136
+ }
137
+ async createSession(input) {
138
+ identifier(input.tenantId, "tenantId");
139
+ identifier(input.sessionId, "sessionId");
140
+ identifier(input.agentId, "agentId");
141
+ const manifest = this.#adapter.manifest;
142
+ const body = {
143
+ schemaVersion: 2,
144
+ tenantId: input.tenantId,
145
+ sessionId: input.sessionId,
146
+ agentId: input.agentId,
147
+ adapterId: manifest.adapterId,
148
+ adapterVersion: manifest.adapterVersion,
149
+ implementationId: manifest.implementationId,
150
+ revision: 0,
151
+ logicalTimeHighWaterMs: 0,
152
+ receipts: [],
153
+ };
154
+ const state = immutable({
155
+ ...body,
156
+ stateDigest: await this.#integrity.digest("cognitive-session-state-v2", body),
157
+ });
158
+ if (!(await this.#store.save(state, null)))
159
+ throw new PortableAgentErrorV1("STATE_CONFLICT", "cognitive session exists");
160
+ return state;
161
+ }
162
+ async getSession(sessionId) {
163
+ identifier(sessionId, "sessionId");
164
+ const state = await this.#store.load(sessionId);
165
+ return state === null
166
+ ? null
167
+ : validateCognitiveSessionStateV2(state, this.#integrity, this.#adapter.manifest.maximumReceiptHistory);
168
+ }
169
+ async execute(requestInput, context) {
170
+ const request = await this.#validateRequest(requestInput);
171
+ if (context.tenant.tenantId !== request.tenantId)
172
+ invalid("tenant context does not match cognitive operation");
173
+ if (context.signal.aborted)
174
+ throw new PortableAgentErrorV1("CONFLICT", "operation aborted");
175
+ if (this.#activeSessions.has(request.sessionId))
176
+ throw new PortableAgentErrorV1("STATE_CONFLICT", "session operation is already active");
177
+ this.#activeSessions.add(request.sessionId);
178
+ try {
179
+ return this.#durableStore
180
+ ? await this.#executeDurably(request, context, this.#durableStore)
181
+ : await this.#executeDirectly(request, context);
182
+ }
183
+ finally {
184
+ this.#activeSessions.delete(request.sessionId);
185
+ }
186
+ }
187
+ async #executeDirectly(request, context) {
188
+ const current = await this.#currentSession(request);
189
+ if (current.receipts.some((item) => item.operationId === request.operationId))
190
+ throw new PortableAgentErrorV1("STATE_CONFLICT", "operation identifier was already committed");
191
+ this.#assertRequestPosition(current, request);
192
+ await this.#authorize(request);
193
+ const result = await this.#adapter.execute(request, context);
194
+ await this.#validateResult(request, result);
195
+ const outcome = await this.#createOutcome(current, request, result);
196
+ for (let attempt = 0; attempt < this.#maximumCommitAttempts; attempt += 1)
197
+ if (await this.#store.save(outcome.state, current.revision))
198
+ return outcome;
199
+ throw new PortableAgentErrorV1("STATE_CONFLICT", "commit attempts exhausted");
200
+ }
201
+ async #executeDurably(request, context, store) {
202
+ const requestDigest = await this.#requestDigest(request);
203
+ const idempotencyKey = await this.#integrity.digest("cognitive-effect-idempotency-v2", {
204
+ tenantId: request.tenantId,
205
+ sessionId: request.sessionId,
206
+ operationId: request.operationId,
207
+ });
208
+ for (let attempt = 0; attempt < this.#maximumCommitAttempts; attempt += 1) {
209
+ const existing = await store.loadOperation(request);
210
+ if (existing) {
211
+ const operation = await this.#validatedDurableOperation(existing, request, requestDigest, idempotencyKey);
212
+ if (operation.status === "applied")
213
+ return this.#replayedOutcome(request, operation.outcome);
214
+ return this.#resumePreparedOperation(request, context, store, operation);
215
+ }
216
+ const current = await this.#currentSession(request);
217
+ if (current.receipts.some((item) => item.operationId === request.operationId))
218
+ throw new PortableAgentErrorV1("STATE_INVALID", "committed operation is missing its durable journal record");
219
+ this.#assertRequestPosition(current, request);
220
+ await this.#authorize(request);
221
+ const preparedBody = {
222
+ schemaVersion: 2,
223
+ tenantId: request.tenantId,
224
+ sessionId: request.sessionId,
225
+ agentId: request.agentId,
226
+ operationId: request.operationId,
227
+ operation: request.operation,
228
+ adapterId: this.#adapter.manifest.adapterId,
229
+ adapterVersion: this.#adapter.manifest.adapterVersion,
230
+ implementationId: this.#adapter.manifest.implementationId,
231
+ requestDigest,
232
+ idempotencyKey,
233
+ expectedSessionRevision: request.expectedRevision,
234
+ previousStateDigest: current.stateDigest,
235
+ preparedAtLogicalMs: request.logicalTimeMs,
236
+ status: "prepared",
237
+ journalRevision: 0,
238
+ outcome: null,
239
+ };
240
+ const prepared = immutable({
241
+ ...preparedBody,
242
+ recordDigest: await this.#integrity.digest("cognitive-durable-operation-v2", preparedBody),
243
+ });
244
+ if (await store.prepareOperation({ operation: prepared }))
245
+ return this.#resumePreparedOperation(request, context, store, prepared);
246
+ }
247
+ throw new PortableAgentErrorV1("STATE_CONFLICT", "durable operation reservation attempts exhausted");
248
+ }
249
+ async #resumePreparedOperation(request, context, store, prepared) {
250
+ const current = await this.#currentSession(request);
251
+ if (current.revision !== prepared.expectedSessionRevision ||
252
+ current.stateDigest !== prepared.previousStateDigest)
253
+ throw new PortableAgentErrorV1("STATE_CONFLICT", "prepared cognitive operation lost its session revision");
254
+ const effectful = COGNITIVE_EFFECTFUL_OPERATION_KINDS_V2.includes(request.operation);
255
+ let result;
256
+ if (effectful) {
257
+ const sink = this.#effectSink;
258
+ if (!sink)
259
+ throw new PortableAgentErrorV1("ADAPTER_INCOMPATIBLE", "effect sink is unavailable");
260
+ const invocation = immutable({
261
+ schemaVersion: 2,
262
+ idempotencyKey: prepared.idempotencyKey,
263
+ requestDigest: prepared.requestDigest,
264
+ });
265
+ const recovered = await sink.lookup({ request, invocation }, context);
266
+ if (recovered) {
267
+ result = recovered;
268
+ }
269
+ else {
270
+ if (context.signal.aborted)
271
+ throw new PortableAgentErrorV1("CONFLICT", "operation aborted");
272
+ // A prepared claim can outlive the authority that created it. Recheck
273
+ // control immediately before starting an effect; reconciliation of an
274
+ // already-applied sink receipt remains allowed without a second effect.
275
+ await this.#authorize(request);
276
+ result = await sink.apply({ request, invocation }, context);
277
+ }
278
+ }
279
+ else {
280
+ await this.#authorize(request);
281
+ result = await this.#adapter.execute(request, context);
282
+ }
283
+ await this.#validateResult(request, result);
284
+ const outcome = await this.#createOutcome(current, request, result);
285
+ const { recordDigest: _preparedDigest, ...preparedBody } = prepared;
286
+ const appliedBody = {
287
+ ...preparedBody,
288
+ status: "applied",
289
+ journalRevision: 1,
290
+ outcome,
291
+ };
292
+ const applied = immutable({
293
+ ...appliedBody,
294
+ recordDigest: await this.#integrity.digest("cognitive-durable-operation-v2", appliedBody),
295
+ });
296
+ for (let attempt = 0; attempt < this.#maximumCommitAttempts; attempt += 1) {
297
+ if (await store.commitOperation({
298
+ operation: applied,
299
+ expectedOperationRevision: 0,
300
+ }))
301
+ return outcome;
302
+ const raced = await store.loadOperation(request);
303
+ if (raced) {
304
+ const validated = await this.#validatedDurableOperation(raced, request, prepared.requestDigest, prepared.idempotencyKey);
305
+ if (validated.status === "applied")
306
+ return this.#replayedOutcome(request, validated.outcome);
307
+ }
308
+ }
309
+ throw new PortableAgentErrorV1("STATE_CONFLICT", "durable operation commit attempts exhausted");
310
+ }
311
+ async #createOutcome(current, request, result) {
312
+ const receiptBody = {
313
+ schemaVersion: 2,
314
+ operationId: request.operationId,
315
+ operation: request.operation,
316
+ sessionId: request.sessionId,
317
+ agentId: request.agentId,
318
+ revision: current.revision + 1,
319
+ logicalTimeMs: request.logicalTimeMs,
320
+ payloadDigest: request.payloadDigest,
321
+ metadataDigest: request.metadataDigest,
322
+ outputDigest: result.outputDigest,
323
+ authorityDigest: request.authorityDigest,
324
+ roleBindingDigest: request.roleBindingDigest,
325
+ ...(request.controlPlaneDigest === undefined
326
+ ? {}
327
+ : { controlPlaneDigest: request.controlPlaneDigest }),
328
+ implementationId: this.#adapter.manifest.implementationId,
329
+ status: result.status,
330
+ reasonCode: result.reasonCode,
331
+ controlSurface: result.controlSurface,
332
+ previousStateDigest: current.stateDigest,
333
+ };
334
+ const receipt = immutable({
335
+ ...receiptBody,
336
+ receiptDigest: await this.#integrity.digest("cognitive-operation-receipt-v2", receiptBody),
337
+ });
338
+ const retained = [...current.receipts, receipt].slice(-this.#adapter.manifest.maximumReceiptHistory);
339
+ const stateBody = {
340
+ ...current,
341
+ revision: current.revision + 1,
342
+ logicalTimeHighWaterMs: request.logicalTimeMs,
343
+ receipts: retained,
344
+ };
345
+ const { stateDigest: _priorDigest, ...digestable } = stateBody;
346
+ const state = immutable({
347
+ ...stateBody,
348
+ stateDigest: await this.#integrity.digest("cognitive-session-state-v2", digestable),
349
+ });
350
+ return immutable({ result, receipt, state });
351
+ }
352
+ async #currentSession(request) {
353
+ const current = await this.getSession(request.sessionId);
354
+ if (!current)
355
+ throw new PortableAgentErrorV1("NOT_FOUND", "session not found");
356
+ this.#assertBinding(current, request);
357
+ return current;
358
+ }
359
+ async #replayedOutcome(request, outcome) {
360
+ const current = await this.#currentSession(request);
361
+ if (current.revision < outcome.state.revision ||
362
+ (current.revision === outcome.state.revision &&
363
+ current.stateDigest !== outcome.state.stateDigest))
364
+ throw new PortableAgentErrorV1("STATE_INVALID", "durable cognitive operation and session state diverged");
365
+ return outcome;
366
+ }
367
+ #assertRequestPosition(current, request) {
368
+ if (current.revision !== request.expectedRevision)
369
+ throw new PortableAgentErrorV1("STATE_CONFLICT", "session revision conflict");
370
+ if (request.logicalTimeMs < current.logicalTimeHighWaterMs)
371
+ invalid("operation logical time is below the session high-water mark");
372
+ }
373
+ async #authorize(request) {
374
+ const authorization = await this.#guard.authorize({
375
+ manifest: this.#adapter.manifest,
376
+ request,
377
+ });
378
+ if (!authorization.allowed)
379
+ throw new PortableAgentErrorV1("CONTROL_DENIED", `cognitive operation denied: ${authorization.reasonCode}`);
380
+ }
381
+ async #requestDigest(request) {
382
+ const body = {
383
+ schemaVersion: request.schemaVersion,
384
+ operationId: request.operationId,
385
+ operation: request.operation,
386
+ tenantId: request.tenantId,
387
+ sessionId: request.sessionId,
388
+ agentId: request.agentId,
389
+ expectedRevision: request.expectedRevision,
390
+ logicalTimeMs: request.logicalTimeMs,
391
+ payloadDigest: request.payloadDigest,
392
+ metadataDigest: request.metadataDigest,
393
+ authorityDigest: request.authorityDigest,
394
+ roleBindingDigest: request.roleBindingDigest,
395
+ ...(request.controlPlaneDigest === undefined
396
+ ? {}
397
+ : { controlPlaneDigest: request.controlPlaneDigest }),
398
+ };
399
+ return this.#integrity.digest("cognitive-operation-request-v2", body);
400
+ }
401
+ async #validatedDurableOperation(input, request, requestDigest, idempotencyKey) {
402
+ if (!input || input.schemaVersion !== 2)
403
+ invalid("durable cognitive operation schema is invalid");
404
+ exactKeys(input, [
405
+ "adapterId",
406
+ "adapterVersion",
407
+ "agentId",
408
+ "expectedSessionRevision",
409
+ "idempotencyKey",
410
+ "implementationId",
411
+ "journalRevision",
412
+ "operation",
413
+ "operationId",
414
+ "outcome",
415
+ "preparedAtLogicalMs",
416
+ "previousStateDigest",
417
+ "recordDigest",
418
+ "requestDigest",
419
+ "schemaVersion",
420
+ "sessionId",
421
+ "status",
422
+ "tenantId",
423
+ ], "durable cognitive operation");
424
+ identifier(input.tenantId, "operation.tenantId");
425
+ identifier(input.sessionId, "operation.sessionId");
426
+ identifier(input.agentId, "operation.agentId");
427
+ identifier(input.operationId, "operation.operationId");
428
+ identifier(input.adapterId, "operation.adapterId");
429
+ token(input.adapterVersion, "operation.adapterVersion", 128);
430
+ identifier(input.implementationId, "operation.implementationId");
431
+ if (!COGNITIVE_OPERATION_KINDS_V2.includes(input.operation))
432
+ invalid("durable cognitive operation kind is invalid");
433
+ digest(input.requestDigest, "operation.requestDigest");
434
+ digest(input.idempotencyKey, "operation.idempotencyKey");
435
+ digest(input.previousStateDigest, "operation.previousStateDigest");
436
+ digest(input.recordDigest, "operation.recordDigest");
437
+ boundedInteger(input.expectedSessionRevision, "operation.expectedSessionRevision", 0, 1_000_000_000);
438
+ boundedInteger(input.preparedAtLogicalMs, "operation.preparedAtLogicalMs", 0, Number.MAX_SAFE_INTEGER);
439
+ const { recordDigest, ...body } = input;
440
+ if ((await this.#integrity.digest("cognitive-durable-operation-v2", body)) !== recordDigest)
441
+ invalid("durable cognitive operation digest is invalid");
442
+ if (input.tenantId !== request.tenantId ||
443
+ input.sessionId !== request.sessionId ||
444
+ input.operationId !== request.operationId ||
445
+ input.adapterId !== this.#adapter.manifest.adapterId ||
446
+ input.adapterVersion !== this.#adapter.manifest.adapterVersion ||
447
+ input.implementationId !== this.#adapter.manifest.implementationId ||
448
+ input.idempotencyKey !== idempotencyKey)
449
+ invalid("durable cognitive operation binding is invalid");
450
+ if (input.requestDigest !== requestDigest)
451
+ throw new PortableAgentErrorV1("STATE_CONFLICT", "operation identifier request digest conflicts with its reservation");
452
+ if (input.agentId !== request.agentId ||
453
+ input.operation !== request.operation ||
454
+ input.expectedSessionRevision !== request.expectedRevision ||
455
+ input.preparedAtLogicalMs !== request.logicalTimeMs)
456
+ invalid("durable cognitive operation request binding is invalid");
457
+ if (input.status === "prepared") {
458
+ if (input.journalRevision !== 0 || input.outcome !== null)
459
+ invalid("prepared cognitive operation state is invalid");
460
+ return immutable(input);
461
+ }
462
+ if (input.status !== "applied" ||
463
+ input.journalRevision !== 1 ||
464
+ !input.outcome)
465
+ invalid("applied cognitive operation state is invalid");
466
+ exactKeys(input.outcome, ["receipt", "result", "state"], "durable cognitive operation outcome");
467
+ await this.#validateResult(request, input.outcome.result);
468
+ const state = await validateCognitiveSessionStateV2(input.outcome.state, this.#integrity, this.#adapter.manifest.maximumReceiptHistory);
469
+ this.#assertBinding(state, request);
470
+ const receipt = input.outcome.receipt;
471
+ if (!receipt || receipt.schemaVersion !== 2)
472
+ invalid("durable cognitive operation receipt is invalid");
473
+ const { receiptDigest, ...receiptBody } = receipt;
474
+ if ((await this.#integrity.digest("cognitive-operation-receipt-v2", receiptBody)) !== receiptDigest)
475
+ invalid("durable cognitive operation receipt digest is invalid");
476
+ const retained = state.receipts.at(-1);
477
+ if (receipt.operationId !== request.operationId ||
478
+ receipt.operation !== request.operation ||
479
+ receipt.sessionId !== request.sessionId ||
480
+ receipt.agentId !== request.agentId ||
481
+ receipt.revision !== request.expectedRevision + 1 ||
482
+ receipt.logicalTimeMs !== request.logicalTimeMs ||
483
+ receipt.payloadDigest !== request.payloadDigest ||
484
+ receipt.metadataDigest !== request.metadataDigest ||
485
+ receipt.authorityDigest !== request.authorityDigest ||
486
+ receipt.roleBindingDigest !== request.roleBindingDigest ||
487
+ receipt.controlPlaneDigest !== request.controlPlaneDigest ||
488
+ receipt.implementationId !== this.#adapter.manifest.implementationId ||
489
+ receipt.outputDigest !== input.outcome.result.outputDigest ||
490
+ receipt.status !== input.outcome.result.status ||
491
+ receipt.reasonCode !== input.outcome.result.reasonCode ||
492
+ receipt.controlSurface !== input.outcome.result.controlSurface ||
493
+ receipt.previousStateDigest !== input.previousStateDigest ||
494
+ state.revision !== request.expectedRevision + 1 ||
495
+ retained?.receiptDigest !== receipt.receiptDigest)
496
+ invalid("applied cognitive operation outcome binding is invalid");
497
+ return immutable(input);
498
+ }
499
+ async #validateRequest(request) {
500
+ if (!request || request.schemaVersion !== 2)
501
+ invalid("request schema is invalid");
502
+ exactKeys(request, [
503
+ "agentId",
504
+ "authorityDigest",
505
+ ...(request.controlPlaneDigest === undefined
506
+ ? []
507
+ : ["controlPlaneDigest"]),
508
+ "expectedRevision",
509
+ "logicalTimeMs",
510
+ "metadata",
511
+ "metadataDigest",
512
+ "operation",
513
+ "operationId",
514
+ "payload",
515
+ "payloadDigest",
516
+ "roleBindingDigest",
517
+ "schemaVersion",
518
+ "sessionId",
519
+ "tenantId",
520
+ ], "cognitive operation request");
521
+ identifier(request.operationId, "operationId");
522
+ identifier(request.tenantId, "tenantId");
523
+ identifier(request.sessionId, "sessionId");
524
+ identifier(request.agentId, "agentId");
525
+ digest(request.payloadDigest, "payloadDigest");
526
+ digest(request.metadataDigest, "metadataDigest");
527
+ digest(request.authorityDigest, "authorityDigest");
528
+ digest(request.roleBindingDigest, "roleBindingDigest");
529
+ if (request.controlPlaneDigest !== undefined)
530
+ digest(request.controlPlaneDigest, "controlPlaneDigest");
531
+ boundedInteger(request.expectedRevision, "expectedRevision", 0, 1_000_000_000);
532
+ boundedInteger(request.logicalTimeMs, "logicalTimeMs", 0, Number.MAX_SAFE_INTEGER);
533
+ if (!COGNITIVE_OPERATION_KINDS_V2.includes(request.operation))
534
+ invalid("operation kind is unsupported");
535
+ if (!this.#adapter.manifest.operations.includes(request.operation))
536
+ throw new PortableAgentErrorV1("ADAPTER_INCOMPATIBLE", `adapter does not implement ${request.operation}`);
537
+ const encoded = `${canonicalJson(request.payload)}${canonicalJson(request.metadata)}`;
538
+ if (new TextEncoder().encode(encoded).byteLength >
539
+ this.#adapter.manifest.maximumOperationBytes)
540
+ invalid("operation payload exceeds adapter limit");
541
+ const actual = await this.#integrity.digest("cognitive-operation-payload-v2", request.payload);
542
+ if (actual !== request.payloadDigest)
543
+ invalid("operation payload digest mismatch");
544
+ const actualMetadata = await this.#integrity.digest("cognitive-operation-metadata-v2", request.metadata);
545
+ if (actualMetadata !== request.metadataDigest)
546
+ invalid("operation metadata digest mismatch");
547
+ return immutable(request);
548
+ }
549
+ async #validateResult(request, result) {
550
+ if (!result ||
551
+ result.schemaVersion !== 2 ||
552
+ result.operationId !== request.operationId)
553
+ invalid("adapter returned an invalid cognitive result");
554
+ exactKeys(result, [
555
+ "controlSurface",
556
+ "operationId",
557
+ "output",
558
+ "outputDigest",
559
+ "reasonCode",
560
+ "schemaVersion",
561
+ "status",
562
+ ], "cognitive operation result");
563
+ if (!["completed", "refused", "abstained", "failed"].includes(result.status))
564
+ invalid("cognitive result status is invalid");
565
+ token(result.reasonCode, "reasonCode", 160);
566
+ digest(result.outputDigest, "outputDigest");
567
+ if (result.controlSurface !== null &&
568
+ !this.#adapter.manifest.controlSurfaces.includes(result.controlSurface))
569
+ invalid("adapter returned an undeclared control surface");
570
+ if (new TextEncoder().encode(canonicalJson(result.output)).byteLength >
571
+ this.#adapter.manifest.maximumResultBytes)
572
+ invalid("cognitive result exceeds adapter limit");
573
+ const actual = await this.#integrity.digest("cognitive-operation-output-v2", result.output);
574
+ if (actual !== result.outputDigest)
575
+ invalid("cognitive result digest mismatch");
576
+ }
577
+ #assertBinding(state, request) {
578
+ const manifest = this.#adapter.manifest;
579
+ if (state.tenantId !== request.tenantId ||
580
+ state.agentId !== request.agentId ||
581
+ state.adapterId !== manifest.adapterId ||
582
+ state.adapterVersion !== manifest.adapterVersion ||
583
+ state.implementationId !== manifest.implementationId)
584
+ invalid("cognitive session binding mismatch");
585
+ }
586
+ }
587
+ export async function validateCognitiveSessionStateV2(input, integrity, maximumReceiptHistory) {
588
+ if (!input || input.schemaVersion !== 2)
589
+ invalid("cognitive session state schema is invalid");
590
+ if (!integrity || typeof integrity.digest !== "function")
591
+ invalid("cognitive integrity implementation is required");
592
+ identifier(input.tenantId, "state.tenantId");
593
+ identifier(input.sessionId, "state.sessionId");
594
+ identifier(input.agentId, "state.agentId");
595
+ identifier(input.adapterId, "state.adapterId");
596
+ token(input.adapterVersion, "state.adapterVersion", 128);
597
+ identifier(input.implementationId, "state.implementationId");
598
+ boundedInteger(input.revision, "state.revision", 0, 1_000_000_000);
599
+ boundedInteger(input.logicalTimeHighWaterMs, "state.logicalTimeHighWaterMs", 0, Number.MAX_SAFE_INTEGER);
600
+ boundedInteger(maximumReceiptHistory, "maximumReceiptHistory", 1, 100_000);
601
+ if (input.receipts.length > maximumReceiptHistory)
602
+ invalid("cognitive receipt retention exceeded");
603
+ const operationIds = new Set();
604
+ let prior = null;
605
+ for (const receipt of input.receipts) {
606
+ if (!receipt || receipt.schemaVersion !== 2)
607
+ invalid("cognitive receipt schema is invalid");
608
+ identifier(receipt.operationId, "receipt.operationId");
609
+ if (operationIds.has(receipt.operationId))
610
+ invalid("cognitive receipt operation is duplicated");
611
+ operationIds.add(receipt.operationId);
612
+ if (!COGNITIVE_OPERATION_KINDS_V2.includes(receipt.operation))
613
+ invalid("cognitive receipt operation is invalid");
614
+ identifier(receipt.sessionId, "receipt.sessionId");
615
+ identifier(receipt.agentId, "receipt.agentId");
616
+ if (receipt.sessionId !== input.sessionId ||
617
+ receipt.agentId !== input.agentId)
618
+ invalid("cognitive receipt session binding is invalid");
619
+ boundedInteger(receipt.revision, "receipt.revision", 1, input.revision);
620
+ boundedInteger(receipt.logicalTimeMs, "receipt.logicalTimeMs", 0, input.logicalTimeHighWaterMs);
621
+ for (const [label, value] of Object.entries({
622
+ payloadDigest: receipt.payloadDigest,
623
+ metadataDigest: receipt.metadataDigest,
624
+ outputDigest: receipt.outputDigest,
625
+ authorityDigest: receipt.authorityDigest,
626
+ roleBindingDigest: receipt.roleBindingDigest,
627
+ previousStateDigest: receipt.previousStateDigest,
628
+ receiptDigest: receipt.receiptDigest,
629
+ }))
630
+ digest(value, label);
631
+ if (receipt.controlPlaneDigest !== undefined)
632
+ digest(receipt.controlPlaneDigest, "controlPlaneDigest");
633
+ identifier(receipt.implementationId, "receipt.implementationId");
634
+ if (receipt.implementationId !== input.implementationId)
635
+ invalid("cognitive receipt implementation binding is invalid");
636
+ if (!["completed", "refused", "abstained", "failed"].includes(receipt.status))
637
+ invalid("cognitive receipt status is invalid");
638
+ token(receipt.reasonCode, "receipt.reasonCode", 160);
639
+ if (receipt.controlSurface !== null &&
640
+ ![
641
+ "context",
642
+ "memory",
643
+ "tool",
644
+ "output",
645
+ "action",
646
+ "representation",
647
+ ].includes(receipt.controlSurface))
648
+ invalid("cognitive receipt control surface is invalid");
649
+ if (prior &&
650
+ (receipt.revision !== prior.revision + 1 ||
651
+ receipt.logicalTimeMs < prior.logicalTimeMs))
652
+ invalid("cognitive receipt sequence is invalid");
653
+ const { receiptDigest, ...body } = receipt;
654
+ if ((await integrity.digest("cognitive-operation-receipt-v2", body)) !== receiptDigest)
655
+ invalid("cognitive receipt digest is invalid");
656
+ prior = receipt;
657
+ }
658
+ if ((input.revision === 0) !== (input.receipts.length === 0) ||
659
+ (prior !== null && prior.revision !== input.revision))
660
+ invalid("cognitive session revision and receipts differ");
661
+ const { stateDigest, ...body } = input;
662
+ digest(stateDigest, "stateDigest");
663
+ if ((await integrity.digest("cognitive-session-state-v2", body)) !== stateDigest)
664
+ invalid("cognitive session state digest is invalid");
665
+ return immutable(input);
666
+ }
667
+ export async function createCognitiveOperationRequestV2(input, integrity = createWebCryptoCognitiveIntegrityV2()) {
668
+ if (!integrity || typeof integrity.digest !== "function")
669
+ invalid("cognitive integrity implementation is required");
670
+ return immutable({
671
+ ...input,
672
+ payloadDigest: await integrity.digest("cognitive-operation-payload-v2", input.payload),
673
+ metadataDigest: await integrity.digest("cognitive-operation-metadata-v2", input.metadata),
674
+ });
675
+ }
676
+ export function createWebCryptoCognitiveIntegrityV2() {
677
+ return Object.freeze({
678
+ async digest(domain, value) {
679
+ token(domain, "digest domain", 160);
680
+ if (!globalThis.crypto?.subtle)
681
+ throw new PortableAgentErrorV1("ADAPTER_INCOMPATIBLE", "Web Crypto SHA-256 is unavailable");
682
+ const material = new TextEncoder().encode(`${domain}\u0000${canonicalJson(value)}`);
683
+ const hashed = await globalThis.crypto.subtle.digest("SHA-256", material);
684
+ return `sha256:${[...new Uint8Array(hashed)]
685
+ .map((byte) => byte.toString(16).padStart(2, "0"))
686
+ .join("")}`;
687
+ },
688
+ });
689
+ }
690
+ function validateManifest(manifest) {
691
+ if (!manifest || manifest.schemaVersion !== 2)
692
+ invalid("cognitive manifest is invalid");
693
+ identifier(manifest.adapterId, "adapterId");
694
+ token(manifest.adapterVersion, "adapterVersion", 128);
695
+ identifier(manifest.implementationId, "implementationId");
696
+ normalizeAdapterManifestV1(manifest.portable);
697
+ if (manifest.portable.adapterId !== manifest.adapterId ||
698
+ manifest.portable.adapterVersion !== manifest.adapterVersion ||
699
+ manifest.portable.implementationId !== manifest.implementationId)
700
+ invalid("portable and cognitive manifests are not bound to one implementation");
701
+ canonicalUnique(manifest.operations, COGNITIVE_OPERATION_KINDS_V2, "operations");
702
+ canonicalUnique(manifest.controlSurfaces, [
703
+ "context",
704
+ "memory",
705
+ "tool",
706
+ "output",
707
+ "action",
708
+ "representation",
709
+ ], "controlSurfaces");
710
+ boundedInteger(manifest.maximumOperationBytes, "maximumOperationBytes", 1_024, 67_108_864);
711
+ boundedInteger(manifest.maximumResultBytes, "maximumResultBytes", 1_024, 67_108_864);
712
+ boundedInteger(manifest.maximumReceiptHistory, "maximumReceiptHistory", 1, 100_000);
713
+ if (typeof manifest.supportsBlackBoxControl !== "boolean" ||
714
+ typeof manifest.supportsRepresentationControl !== "boolean" ||
715
+ typeof manifest.supportsMultimodalState !== "boolean")
716
+ invalid("cognitive manifest support flags are invalid");
717
+ if (manifest.supportsBlackBoxControl &&
718
+ !manifest.controlSurfaces.some((item) => ["context", "memory", "tool"].includes(item)))
719
+ invalid("black-box control requires a declared black-box surface");
720
+ if (manifest.supportsRepresentationControl &&
721
+ !manifest.controlSurfaces.includes("representation"))
722
+ invalid("representation control requires the representation surface");
723
+ }
724
+ function canonicalUnique(values, allowed, label) {
725
+ if (!Array.isArray(values) ||
726
+ values.length === 0 ||
727
+ values.length > allowed.length)
728
+ invalid(`${label} are invalid`);
729
+ const normalized = [...new Set(values)].sort();
730
+ if (normalized.length !== values.length ||
731
+ normalized.some((value, index) => value !== values[index]) ||
732
+ normalized.some((value) => !allowed.includes(value)))
733
+ invalid(`${label} must be canonical, unique and supported`);
734
+ }
735
+ function identifier(value, label) {
736
+ if (typeof value !== "string" ||
737
+ !/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,239}$/.test(value))
738
+ invalid(`${label} is invalid`);
739
+ }
740
+ function token(value, label, maximum) {
741
+ if (typeof value !== "string" ||
742
+ value.length < 1 ||
743
+ value.length > maximum ||
744
+ /[\u0000-\u001f]/u.test(value))
745
+ invalid(`${label} is invalid`);
746
+ }
747
+ function digest(value, label) {
748
+ if (typeof value !== "string" || !/^sha256:[a-f0-9]{64}$/.test(value))
749
+ invalid(`${label} must be a SHA-256 digest`);
750
+ }
751
+ function boundedInteger(value, label, minimum, maximum) {
752
+ if (!Number.isSafeInteger(value) ||
753
+ value < minimum ||
754
+ value > maximum)
755
+ invalid(`${label} is outside its supported range`);
756
+ return value;
757
+ }
758
+ function exactKeys(value, keys, label) {
759
+ if (Object.keys(value).sort().join("\u0000") !== [...keys].sort().join("\u0000"))
760
+ invalid(`${label} has unsupported fields`);
761
+ }
762
+ function canonicalJson(value) {
763
+ if (value === null || typeof value !== "object")
764
+ return JSON.stringify(value);
765
+ if (Array.isArray(value))
766
+ return `[${value.map(canonicalJson).join(",")}]`;
767
+ return `{${Object.keys(value)
768
+ .sort()
769
+ .map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
770
+ .join(",")}}`;
771
+ }
772
+ function durableOperationStore(value) {
773
+ const candidate = value;
774
+ return (typeof candidate.loadOperation === "function" &&
775
+ typeof candidate.prepareOperation === "function" &&
776
+ typeof candidate.commitOperation === "function");
777
+ }
778
+ function durableOperationKey(tenantId, sessionId, operationId) {
779
+ return `${tenantId}\u0000${sessionId}\u0000${operationId}`;
780
+ }
781
+ function durableRevisionKey(tenantId, sessionId, revision) {
782
+ return `${tenantId}\u0000${sessionId}\u0000${revision}`;
783
+ }
784
+ function sameDurableOperationIdentity(prepared, applied) {
785
+ return (prepared.tenantId === applied.tenantId &&
786
+ prepared.sessionId === applied.sessionId &&
787
+ prepared.agentId === applied.agentId &&
788
+ prepared.operationId === applied.operationId &&
789
+ prepared.operation === applied.operation &&
790
+ prepared.adapterId === applied.adapterId &&
791
+ prepared.adapterVersion === applied.adapterVersion &&
792
+ prepared.implementationId === applied.implementationId &&
793
+ prepared.requestDigest === applied.requestDigest &&
794
+ prepared.idempotencyKey === applied.idempotencyKey &&
795
+ prepared.expectedSessionRevision === applied.expectedSessionRevision &&
796
+ prepared.previousStateDigest === applied.previousStateDigest &&
797
+ prepared.preparedAtLogicalMs === applied.preparedAtLogicalMs);
798
+ }
799
+ function immutable(value) {
800
+ return deepFreeze(structuredClone(value));
801
+ }
802
+ function deepFreeze(value) {
803
+ if (value && typeof value === "object" && !Object.isFrozen(value)) {
804
+ Object.freeze(value);
805
+ for (const item of Object.values(value))
806
+ deepFreeze(item);
807
+ }
808
+ return value;
809
+ }
810
+ function invalid(message) {
811
+ throw new PortableAgentErrorV1("STATE_INVALID", message);
812
+ }
813
+ //# sourceMappingURL=cognitive-adapter.js.map