ai-workflow-logic 0.1.2 → 0.1.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.
@@ -0,0 +1,293 @@
1
+ import type {
2
+ AIAgentEffectConfig,
3
+ AIAgentInstanceReference,
4
+ AIAgentInvocation,
5
+ AIAgentInvocationReceipt,
6
+ AIAgentSelector,
7
+ AIWorkflowProfileDurableState,
8
+ AIWorkflowAgentTaskRef,
9
+ AIWorkflowKind,
10
+ FlowClosedObject,
11
+ FlowClosedValue,
12
+ LegacyAIWorkflowProfileDurableState,
13
+ } from 'ai-workflow-contract';
14
+ import { normalizeFlowClosedObject, normalizeFlowClosedValue } from './run-checkpoint.js';
15
+
16
+ export class AIAgentInvocationError extends Error {
17
+ constructor(public readonly code: string, public readonly location: string, message: string) {
18
+ super(`${location}: ${message}`);
19
+ this.name = 'AIAgentInvocationError';
20
+ }
21
+ }
22
+
23
+ function fail(code: string, location: string, message: string): never {
24
+ throw new AIAgentInvocationError(code, location, message);
25
+ }
26
+
27
+ function record(value: unknown, required: readonly string[], optional: readonly string[], location: string): Record<string, unknown> {
28
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) fail('AI_AGENT_OBJECT_REQUIRED', location, 'Expected a plain data object.');
29
+ const prototype = Object.getPrototypeOf(value);
30
+ if (prototype !== Object.prototype && prototype !== null) fail('AI_AGENT_OBJECT_REQUIRED', location, 'Expected a plain data object.');
31
+ const descriptors = Object.getOwnPropertyDescriptors(value);
32
+ const allowed = new Set([...required, ...optional]);
33
+ for (const key of Reflect.ownKeys(descriptors)) {
34
+ if (typeof key !== 'string') fail('AI_AGENT_SYMBOL_UNSUPPORTED', location, 'Symbol fields are not supported.');
35
+ const descriptor = descriptors[key]!;
36
+ if (!('value' in descriptor)) fail('AI_AGENT_ACCESSOR_UNSUPPORTED', `${location}.${key}`, 'Accessor fields are not supported.');
37
+ if (!allowed.has(key)) fail('AI_AGENT_FIELD_UNSUPPORTED', `${location}.${key}`, 'Unexpected field.');
38
+ }
39
+ for (const key of required) if (!Object.prototype.hasOwnProperty.call(descriptors, key)) fail('AI_AGENT_FIELD_REQUIRED', `${location}.${key}`, 'Required field is missing.');
40
+ return value as Record<string, unknown>;
41
+ }
42
+
43
+ function objectMap(value: unknown, location: string): Record<string, unknown> {
44
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) fail('AI_AGENT_INDEX_REQUIRED', location, 'Expected a plain data index.');
45
+ const prototype = Object.getPrototypeOf(value);
46
+ if (prototype !== Object.prototype && prototype !== null) fail('AI_AGENT_INDEX_REQUIRED', location, 'Expected a plain data index.');
47
+ const descriptors = Object.getOwnPropertyDescriptors(value);
48
+ const output: Record<string, unknown> = {};
49
+ for (const key of Object.keys(descriptors).sort(compareCodeUnits)) {
50
+ const descriptor = descriptors[key]!;
51
+ if (!('value' in descriptor)) fail('AI_AGENT_ACCESSOR_UNSUPPORTED', `${location}.${key}`, 'Accessor fields are not supported.');
52
+ output[key] = descriptor.value;
53
+ }
54
+ if (Reflect.ownKeys(descriptors).some((key) => typeof key !== 'string')) fail('AI_AGENT_SYMBOL_UNSUPPORTED', location, 'Symbol fields are not supported.');
55
+ return output;
56
+ }
57
+
58
+ function compareCodeUnits(left: string, right: string): number {
59
+ const length = Math.min(left.length, right.length);
60
+ for (let index = 0; index < length; index += 1) {
61
+ const difference = left.charCodeAt(index) - right.charCodeAt(index);
62
+ if (difference !== 0) return difference;
63
+ }
64
+ return left.length - right.length;
65
+ }
66
+
67
+ function identity(value: unknown, location: string): string {
68
+ if (typeof value !== 'string' || value.length === 0 || value.trim() !== value || value.normalize('NFC') !== value) {
69
+ fail('AI_AGENT_IDENTITY_INVALID', location, 'Expected one non-empty NFC identity without surrounding whitespace.');
70
+ }
71
+ return value;
72
+ }
73
+
74
+ function resourceRef(value: unknown, location: string): `resource://${string}` {
75
+ const result = identity(value, location);
76
+ if (!result.startsWith('resource://') || result.length === 11) fail('AI_AGENT_DEFINITION_REF_INVALID', location, 'Expected an exact resource:// identity.');
77
+ return result as `resource://${string}`;
78
+ }
79
+
80
+ function optionalClosedObject(value: unknown, location: string): FlowClosedObject {
81
+ return normalizeFlowClosedObject(value, location);
82
+ }
83
+
84
+ const FORBIDDEN_RUNTIME_STATE_KEYS = new Set([
85
+ 'actor', 'actorstate', 'compaction', 'conversation', 'history', 'providertranscript', 'transcript',
86
+ ]);
87
+
88
+ function safeOpaqueObject(value: unknown, location: string): FlowClosedObject {
89
+ const normalized = optionalClosedObject(value, location);
90
+ const visit = (item: FlowClosedValue, itemLocation: string): void => {
91
+ if (Array.isArray(item)) {
92
+ item.forEach((nested, index) => visit(nested, `${itemLocation}[${index}]`));
93
+ return;
94
+ }
95
+ if (item === null || typeof item !== 'object') return;
96
+ for (const [key, nested] of Object.entries(item)) {
97
+ const semanticKey = key.normalize('NFC').toLowerCase().replace(/[^a-z0-9]/g, '');
98
+ if (FORBIDDEN_RUNTIME_STATE_KEYS.has(semanticKey)) {
99
+ fail('AI_AGENT_RUNTIME_STATE_FIELD_FORBIDDEN', `${itemLocation}.${key}`, 'Runtime-owned Agent state cannot enter the Flow checkpoint.');
100
+ }
101
+ visit(nested, `${itemLocation}.${key}`);
102
+ }
103
+ };
104
+ visit(normalized, location);
105
+ return normalized;
106
+ }
107
+
108
+ function optionalStringArray(value: unknown, location: string): readonly string[] {
109
+ if (!Array.isArray(value)) fail('AI_AGENT_ARRAY_REQUIRED', location, 'Expected an array.');
110
+ const descriptors = Object.getOwnPropertyDescriptors(value);
111
+ for (let index = 0; index < value.length; index += 1) {
112
+ if (!Object.prototype.hasOwnProperty.call(descriptors, String(index))) fail('AI_AGENT_ARRAY_SPARSE', `${location}[${index}]`, 'Sparse arrays are not supported.');
113
+ }
114
+ return Object.freeze(value.map((item, index) => identity(item, `${location}[${index}]`)));
115
+ }
116
+
117
+ export function normalizeAIAgentSelector(value: unknown, location = 'selector'): AIAgentSelector {
118
+ const input = record(value, [], ['byInstanceId', 'byInstanceName'], location);
119
+ const hasId = Object.prototype.hasOwnProperty.call(input, 'byInstanceId');
120
+ const hasName = Object.prototype.hasOwnProperty.call(input, 'byInstanceName');
121
+ if (hasId === hasName) fail('AI_AGENT_SELECTOR_EXACTLY_ONE', location, 'Selector must contain exactly one supported key.');
122
+ return Object.freeze(hasId
123
+ ? { byInstanceId: identity(input.byInstanceId, `${location}.byInstanceId`) }
124
+ : { byInstanceName: identity(input.byInstanceName, `${location}.byInstanceName`) });
125
+ }
126
+
127
+ export function normalizeAIAgentEffectConfig(value: unknown, location = 'config'): AIAgentEffectConfig {
128
+ const input = record(value, ['agentDefinitionRef'], ['instanceName', 'materialRefs', 'effectPolicy'], location);
129
+ return Object.freeze({
130
+ agentDefinitionRef: resourceRef(input.agentDefinitionRef, `${location}.agentDefinitionRef`),
131
+ ...(Object.prototype.hasOwnProperty.call(input, 'instanceName') ? { instanceName: identity(input.instanceName, `${location}.instanceName`) } : {}),
132
+ ...(Object.prototype.hasOwnProperty.call(input, 'materialRefs') ? { materialRefs: optionalStringArray(input.materialRefs, `${location}.materialRefs`) } : {}),
133
+ ...(Object.prototype.hasOwnProperty.call(input, 'effectPolicy') ? { effectPolicy: safeOpaqueObject(input.effectPolicy, `${location}.effectPolicy`) } : {}),
134
+ });
135
+ }
136
+
137
+ export interface AIAgentNodeBindingIdentity {
138
+ readonly workflowKind: AIWorkflowKind;
139
+ readonly workflowRef: `resource://${string}`;
140
+ readonly nodeId: string;
141
+ }
142
+
143
+ /** Exact frozen task proof used by Ctrl/Data loaders before constructing a node runtime. */
144
+ export function assertAIAgentNodeTaskBinding(
145
+ task: AIWorkflowAgentTaskRef,
146
+ identityInput: AIAgentNodeBindingIdentity,
147
+ configInput: AIAgentEffectConfig,
148
+ ): AIAgentEffectConfig {
149
+ const taskValue = record(task, ['workflowKind', 'workflowRef', 'nodeId', 'agentDefinitionRef'], [], 'task');
150
+ const identityValue = record(identityInput, ['workflowKind', 'workflowRef', 'nodeId'], [], 'identity');
151
+ if (identityValue.workflowKind !== 'AICtrlWorkflow' && identityValue.workflowKind !== 'AIDataWorkflow') fail('AI_AGENT_TASK_WORKFLOW_KIND_INVALID', 'identity.workflowKind', 'Unsupported AI workflow kind.');
152
+ const identity = {
153
+ workflowKind: identityValue.workflowKind,
154
+ workflowRef: resourceRef(identityValue.workflowRef, 'identity.workflowRef'),
155
+ nodeId: identityValue.nodeId,
156
+ } as const;
157
+ const config = normalizeAIAgentEffectConfig(configInput);
158
+ if (taskValue.workflowKind !== identity.workflowKind) fail('AI_AGENT_TASK_WORKFLOW_KIND_MISMATCH', 'task.workflowKind', 'Frozen task workflow kind differs from the node.');
159
+ if (resourceRef(taskValue.workflowRef, 'task.workflowRef') !== identity.workflowRef) fail('AI_AGENT_TASK_WORKFLOW_REF_MISMATCH', 'task.workflowRef', 'Frozen task workflow identity differs from the node.');
160
+ if (identityValue.nodeId !== taskValue.nodeId) fail('AI_AGENT_TASK_NODE_ID_MISMATCH', 'task.nodeId', 'Frozen task node identity differs from the node.');
161
+ if (resourceRef(taskValue.agentDefinitionRef, 'task.agentDefinitionRef') !== config.agentDefinitionRef) fail('AI_AGENT_TASK_DEFINITION_MISMATCH', 'config.agentDefinitionRef', 'Frozen task Agent definition differs from config.');
162
+ return config;
163
+ }
164
+
165
+ export function normalizeAIAgentInvocation<Input extends FlowClosedValue = FlowClosedValue>(value: unknown, location = 'invocation'): AIAgentInvocation<Input> {
166
+ const input = record(value, ['kind', 'payload'], ['metadata'], location);
167
+ if (input.kind !== 'ai.agent') fail('AI_AGENT_INVOCATION_KIND_INVALID', `${location}.kind`, 'Invocation kind must be ai.agent.');
168
+ return Object.freeze({
169
+ kind: 'ai.agent',
170
+ payload: normalizeFlowClosedValue(input.payload, `${location}.payload`) as Input,
171
+ ...(Object.prototype.hasOwnProperty.call(input, 'metadata') ? { metadata: safeOpaqueObject(input.metadata, `${location}.metadata`) } : {}),
172
+ });
173
+ }
174
+
175
+ function normalizeInstance(value: unknown, id: string, location: string): AIAgentInstanceReference {
176
+ const input = record(value, ['authority', 'instanceId', 'agentDefinitionRef'], ['instanceName', 'sessionId', 'metadata'], location);
177
+ const instanceId = identity(input.instanceId, `${location}.instanceId`);
178
+ if (instanceId !== id) fail('AI_AGENT_INSTANCE_ID_INDEX_MISMATCH', `${location}.instanceId`, 'Instance id must equal its index key.');
179
+ return Object.freeze({
180
+ authority: identity(input.authority, `${location}.authority`),
181
+ instanceId,
182
+ agentDefinitionRef: resourceRef(input.agentDefinitionRef, `${location}.agentDefinitionRef`),
183
+ ...(Object.prototype.hasOwnProperty.call(input, 'instanceName') ? { instanceName: identity(input.instanceName, `${location}.instanceName`) } : {}),
184
+ ...(Object.prototype.hasOwnProperty.call(input, 'sessionId') ? { sessionId: identity(input.sessionId, `${location}.sessionId`) } : {}),
185
+ ...(Object.prototype.hasOwnProperty.call(input, 'metadata') ? { metadata: safeOpaqueObject(input.metadata, `${location}.metadata`) } : {}),
186
+ });
187
+ }
188
+
189
+ function normalizeReceipt(value: unknown, key: string, instances: Readonly<Record<string, AIAgentInstanceReference>>, location: string): AIAgentInvocationReceipt {
190
+ const input = record(value, ['invocationKey', 'nodeId', 'status', 'mode', 'agentDefinitionRef', 'input', 'semanticFacts'], ['instanceName', 'instanceId', 'generation', 'metadata', 'output', 'hostReceipt'], location);
191
+ const invocationKey = identity(input.invocationKey, `${location}.invocationKey`);
192
+ if (invocationKey !== key) fail('AI_AGENT_INVOCATION_KEY_INDEX_MISMATCH', `${location}.invocationKey`, 'Invocation key must equal its index key.');
193
+ if (input.status !== 'pending' && input.status !== 'completed') fail('AI_AGENT_INVOCATION_STATUS_INVALID', `${location}.status`, 'Status must be pending or completed.');
194
+ if (input.mode !== 'new' && input.mode !== 'targeted') fail('AI_AGENT_INVOCATION_MODE_INVALID', `${location}.mode`, 'Mode must be new or targeted.');
195
+ const instanceId = Object.prototype.hasOwnProperty.call(input, 'instanceId') ? identity(input.instanceId, `${location}.instanceId`) : undefined;
196
+ if (input.mode === 'targeted' && instanceId === undefined) fail('AI_AGENT_TARGETED_INSTANCE_REQUIRED', location, 'Targeted invocation requires an instance id.');
197
+ if (input.status === 'completed' && (instanceId === undefined || instances[instanceId] === undefined || !Object.prototype.hasOwnProperty.call(input, 'output'))) {
198
+ fail('AI_AGENT_COMPLETED_INVOCATION_INCOMPLETE', location, 'Completed invocation requires an indexed instance and output.');
199
+ }
200
+ const agentDefinitionRef = resourceRef(input.agentDefinitionRef, `${location}.agentDefinitionRef`);
201
+ if (instanceId !== undefined && instances[instanceId] !== undefined
202
+ && instances[instanceId].agentDefinitionRef !== agentDefinitionRef) {
203
+ fail('AI_AGENT_RECEIPT_DEFINITION_MISMATCH', `${location}.agentDefinitionRef`, 'Invocation receipt definition must equal its indexed instance definition.');
204
+ }
205
+ if (instanceId !== undefined && instances[instanceId] !== undefined) {
206
+ const receiptName = Object.prototype.hasOwnProperty.call(input, 'instanceName')
207
+ ? identity(input.instanceName, `${location}.instanceName`)
208
+ : undefined;
209
+ if (receiptName !== instances[instanceId].instanceName) {
210
+ fail('AI_AGENT_RECEIPT_INSTANCE_NAME_MISMATCH', `${location}.instanceName`, 'Invocation receipt name must equal its indexed instance name.');
211
+ }
212
+ }
213
+ if (Object.prototype.hasOwnProperty.call(input, 'generation') && (typeof input.generation !== 'number' || !Number.isSafeInteger(input.generation) || input.generation < 0)) {
214
+ fail('AI_AGENT_GENERATION_INVALID', `${location}.generation`, 'Generation must be a non-negative safe integer.');
215
+ }
216
+ return Object.freeze({
217
+ invocationKey,
218
+ nodeId: identity(input.nodeId, `${location}.nodeId`),
219
+ status: input.status,
220
+ mode: input.mode,
221
+ agentDefinitionRef,
222
+ input: normalizeFlowClosedValue(input.input, `${location}.input`),
223
+ semanticFacts: safeOpaqueObject(input.semanticFacts, `${location}.semanticFacts`),
224
+ ...(Object.prototype.hasOwnProperty.call(input, 'instanceName') ? { instanceName: identity(input.instanceName, `${location}.instanceName`) } : {}),
225
+ ...(instanceId === undefined ? {} : { instanceId }),
226
+ ...(Object.prototype.hasOwnProperty.call(input, 'generation') ? { generation: input.generation as number } : {}),
227
+ ...(Object.prototype.hasOwnProperty.call(input, 'metadata') ? { metadata: safeOpaqueObject(input.metadata, `${location}.metadata`) } : {}),
228
+ ...(Object.prototype.hasOwnProperty.call(input, 'output') ? { output: normalizeFlowClosedValue(input.output, `${location}.output`) } : {}),
229
+ ...(Object.prototype.hasOwnProperty.call(input, 'hostReceipt') ? { hostReceipt: safeOpaqueObject(input.hostReceipt, `${location}.hostReceipt`) } : {}),
230
+ });
231
+ }
232
+
233
+ export function normalizeCanonicalAIAgentState(value: unknown, location = 'ai'): AIWorkflowProfileDurableState {
234
+ const input = record(value, ['schemaVersion', 'instancesById', 'instanceIdByName', 'invocationsByKey'], [], location);
235
+ if (input.schemaVersion !== 'depa.ai-agent-state/v1') fail('AI_AGENT_STATE_SCHEMA_UNSUPPORTED', `${location}.schemaVersion`, 'Unsupported Agent state schema.');
236
+ const instanceValues = objectMap(input.instancesById, `${location}.instancesById`);
237
+ const instancesById: Record<string, AIAgentInstanceReference> = {};
238
+ for (const id of Object.keys(instanceValues).sort(compareCodeUnits)) instancesById[identity(id, `${location}.instancesById`)] = normalizeInstance(instanceValues[id], id, `${location}.instancesById.${id}`);
239
+ const nameValues = objectMap(input.instanceIdByName, `${location}.instanceIdByName`);
240
+ const instanceIdByName: Record<string, string> = {};
241
+ const seenIds = new Set<string>();
242
+ for (const rawName of Object.keys(nameValues).sort(compareCodeUnits)) {
243
+ const name = identity(rawName, `${location}.instanceIdByName`);
244
+ const id = identity(nameValues[name], `${location}.instanceIdByName.${name}`);
245
+ if (!instancesById[id] || instancesById[id].instanceName !== name) fail('AI_AGENT_INSTANCE_NAME_INDEX_MISMATCH', `${location}.instanceIdByName.${name}`, 'Name index must be reciprocal.');
246
+ if (seenIds.has(id)) fail('AI_AGENT_INSTANCE_NAME_DUPLICATE', `${location}.instanceIdByName.${name}`, 'One instance cannot have multiple names.');
247
+ seenIds.add(id);
248
+ instanceIdByName[name] = id;
249
+ }
250
+ for (const instance of Object.values(instancesById)) if (instance.instanceName !== undefined && instanceIdByName[instance.instanceName] !== instance.instanceId) fail('AI_AGENT_INSTANCE_NAME_INDEX_MISSING', `${location}.instancesById.${instance.instanceId}.instanceName`, 'Named instance requires reciprocal index.');
251
+ const receiptValues = objectMap(input.invocationsByKey, `${location}.invocationsByKey`);
252
+ const invocationsByKey: Record<string, AIAgentInvocationReceipt> = {};
253
+ const reservations = new Set<string>();
254
+ for (const key of Object.keys(receiptValues).sort(compareCodeUnits)) {
255
+ const receipt = normalizeReceipt(receiptValues[key], identity(key, `${location}.invocationsByKey`), instancesById, `${location}.invocationsByKey.${key}`);
256
+ if (receipt.status === 'pending' && receipt.mode === 'new' && receipt.instanceName !== undefined) {
257
+ if (reservations.has(receipt.instanceName) || instanceIdByName[receipt.instanceName] !== undefined) fail('AI_AGENT_INSTANCE_NAME_RESERVED', `${location}.invocationsByKey.${key}.instanceName`, 'Name has another accepted binding or reservation.');
258
+ reservations.add(receipt.instanceName);
259
+ }
260
+ invocationsByKey[key] = receipt;
261
+ }
262
+ return Object.freeze({ schemaVersion: 'depa.ai-agent-state/v1', instancesById: Object.freeze(instancesById), instanceIdByName: Object.freeze(instanceIdByName), invocationsByKey: Object.freeze(invocationsByKey) });
263
+ }
264
+
265
+ export function upgradeLegacyAIWorkflowProfileDurableState(value: LegacyAIWorkflowProfileDurableState, agentDefinitionRefs: Readonly<Record<string, `resource://${string}`>>, location = 'legacyAI'): AIWorkflowProfileDurableState {
266
+ const input = record(value, ['externalRuntimeRefs', 'receipts'], [], location);
267
+ const refs = objectMap(input.externalRuntimeRefs, `${location}.externalRuntimeRefs`);
268
+ const instancesById: Record<string, AIAgentInstanceReference> = {};
269
+ const instanceIdByName: Record<string, string> = {};
270
+ for (const name of Object.keys(refs).sort(compareCodeUnits)) {
271
+ const refLocation = `${location}.externalRuntimeRefs.${name}`;
272
+ const ref = record(refs[name], ['authority', 'instanceId'], ['sessionId', 'metadata'], refLocation);
273
+ const instanceId = identity(ref.instanceId, `${refLocation}.instanceId`);
274
+ if (instancesById[instanceId]) fail('AI_AGENT_LEGACY_INSTANCE_DUPLICATE', refLocation, 'Legacy instance id is not unique.');
275
+ const agentDefinitionRef = agentDefinitionRefs[name];
276
+ if (!agentDefinitionRef) fail('AI_AGENT_LEGACY_DEFINITION_REQUIRED', refLocation, 'Explicit Agent definition is required for legacy upgrade.');
277
+ instanceIdByName[name] = instanceId;
278
+ instancesById[instanceId] = { authority: identity(ref.authority, `${refLocation}.authority`), instanceId, instanceName: identity(name, refLocation), agentDefinitionRef: resourceRef(agentDefinitionRef, `${refLocation}.agentDefinitionRef`), ...(Object.prototype.hasOwnProperty.call(ref, 'sessionId') ? { sessionId: identity(ref.sessionId, `${refLocation}.sessionId`) } : {}), ...(Object.prototype.hasOwnProperty.call(ref, 'metadata') ? { metadata: optionalClosedObject(ref.metadata, `${refLocation}.metadata`) } : {}) };
279
+ }
280
+ if (!Array.isArray(input.receipts)) fail('AI_AGENT_ARRAY_REQUIRED', `${location}.receipts`, 'Expected receipts array.');
281
+ const invocationsByKey: Record<string, AIAgentInvocationReceipt> = {};
282
+ input.receipts.forEach((item, index) => {
283
+ const receiptLocation = `${location}.receipts[${index}]`;
284
+ const receipt = record(item, ['nodeId', 'externalRuntimeRef'], ['metadata'], receiptLocation);
285
+ const nodeId = identity(receipt.nodeId, `${receiptLocation}.nodeId`);
286
+ const name = identity(receipt.externalRuntimeRef, `${receiptLocation}.externalRuntimeRef`);
287
+ const instanceId = instanceIdByName[name];
288
+ if (!instanceId) fail('AI_WORKFLOW_EXTERNAL_RUNTIME_REF_UNKNOWN', `${receiptLocation}.externalRuntimeRef`, 'Legacy receipt reference is unknown.');
289
+ const key = `${nodeId}#legacy-${index + 1}`;
290
+ invocationsByKey[key] = { invocationKey: key, nodeId, status: 'completed', mode: 'targeted', agentDefinitionRef: instancesById[instanceId]!.agentDefinitionRef, instanceId, instanceName: name, input: null, semanticFacts: {}, output: null, ...(Object.prototype.hasOwnProperty.call(receipt, 'metadata') ? { metadata: safeOpaqueObject(receipt.metadata, `${receiptLocation}.metadata`) } : {}) };
291
+ });
292
+ return normalizeCanonicalAIAgentState({ schemaVersion: 'depa.ai-agent-state/v1', instancesById, instanceIdByName, invocationsByKey }, location);
293
+ }