@theelena/sdk 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +3048 -0
- package/dist/agent.js +1786 -0
- package/dist/agent.js.map +1 -0
- package/dist/index.d.ts +901 -0
- package/dist/index.js +1766 -0
- package/dist/index.js.map +1 -0
- package/dist/key-transport.d-Bopgxw9r.d.ts +1487 -0
- package/package.json +1 -1
package/dist/agent.js
ADDED
|
@@ -0,0 +1,1786 @@
|
|
|
1
|
+
// src/agent/index.ts
|
|
2
|
+
import { createHash } from "crypto";
|
|
3
|
+
|
|
4
|
+
// ../shared/src/agent-contracts/common.ts
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
var ELENA_AGENT_CONTRACT_VERSION = "1";
|
|
7
|
+
var elenaAgentVersionSchema = z.literal(ELENA_AGENT_CONTRACT_VERSION);
|
|
8
|
+
var elenaAgentIdSchema = z.uuid();
|
|
9
|
+
var elenaAgentRevisionSchema = z.number().int().min(1).max(Number.MAX_SAFE_INTEGER);
|
|
10
|
+
var elenaAgentTimestampSchema = z.iso.datetime();
|
|
11
|
+
var elenaAgentHashSchema = z.string().regex(/^[a-f0-9]{64}$/);
|
|
12
|
+
var elenaAgentRefSchema = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,99}$/);
|
|
13
|
+
var elenaAgentNameSchema = z.string().trim().min(1).max(160);
|
|
14
|
+
var elenaAgentTextSchema = z.string().trim().min(1).max(2e3);
|
|
15
|
+
var elenaAgentCurrencySchema = z.literal("PEN");
|
|
16
|
+
var elenaAgentNonnegativeMoneySchema = z.string().regex(/^(0|[1-9]\d{0,7})\.\d{2}$/);
|
|
17
|
+
var elenaAgentMoneySchema = elenaAgentNonnegativeMoneySchema.refine(
|
|
18
|
+
(value) => value !== "0.00",
|
|
19
|
+
"Product price must be greater than zero"
|
|
20
|
+
);
|
|
21
|
+
var ELENA_AGENT_LIMITS = Object.freeze({
|
|
22
|
+
categoriesPerImport: 50,
|
|
23
|
+
productsPerImport: 200,
|
|
24
|
+
changesPerOperation: 200,
|
|
25
|
+
publicationProducts: 200,
|
|
26
|
+
galleryImages: 8,
|
|
27
|
+
extraGroups: 10,
|
|
28
|
+
optionsPerGroup: 30,
|
|
29
|
+
jsonBytes: 1024 * 1024,
|
|
30
|
+
mediaBytes: 10 * 1024 * 1024,
|
|
31
|
+
stagedBytesPerRestaurant: 100 * 1024 * 1024,
|
|
32
|
+
stagedFilesPerRestaurant: 20,
|
|
33
|
+
openOperationsPerRestaurant: 20,
|
|
34
|
+
openPreviewsPerRestaurant: 20,
|
|
35
|
+
activeKeysPerFamily: 10,
|
|
36
|
+
keyDefaultTtlSeconds: 30 * 24 * 60 * 60,
|
|
37
|
+
keyMaxTtlSeconds: 90 * 24 * 60 * 60,
|
|
38
|
+
pairingTtlSeconds: 600,
|
|
39
|
+
pairingPollSeconds: 5,
|
|
40
|
+
proposalTtlSeconds: 24 * 60 * 60,
|
|
41
|
+
previewTtlSeconds: 24 * 60 * 60,
|
|
42
|
+
stagingTtlSeconds: 48 * 60 * 60,
|
|
43
|
+
applyWindowSeconds: 60 * 60,
|
|
44
|
+
workerLeaseSeconds: 30,
|
|
45
|
+
workerHeartbeatSeconds: 10,
|
|
46
|
+
maxWorkerAttempts: 5,
|
|
47
|
+
resultRetentionDays: 90,
|
|
48
|
+
pageSize: 100
|
|
49
|
+
});
|
|
50
|
+
var elenaAgentErrorCodeSchema = z.enum([
|
|
51
|
+
"INVALID_REQUEST",
|
|
52
|
+
"UNSUPPORTED_VERSION",
|
|
53
|
+
"CREDENTIAL_REQUIRED",
|
|
54
|
+
"CREDENTIAL_INVALID",
|
|
55
|
+
"CREDENTIAL_EXPIRED",
|
|
56
|
+
"CREDENTIAL_REVOKED",
|
|
57
|
+
"CREDENTIAL_MIXED",
|
|
58
|
+
"CREDENTIAL_WRONG_FAMILY",
|
|
59
|
+
"OWNER_REQUIRED",
|
|
60
|
+
"ORIGIN_REJECTED",
|
|
61
|
+
"APPROVAL_REQUIRED",
|
|
62
|
+
"APPROVAL_REJECTED",
|
|
63
|
+
"APPROVAL_EXPIRED",
|
|
64
|
+
"APPROVAL_MISMATCH",
|
|
65
|
+
"SCOPE_REQUIRED",
|
|
66
|
+
"MODE_READ_ONLY",
|
|
67
|
+
"API_ACCESS_DISABLED",
|
|
68
|
+
"RESTAURANT_NOT_APPROVED",
|
|
69
|
+
"NOT_FOUND",
|
|
70
|
+
"VERSION_CONFLICT",
|
|
71
|
+
"IDEMPOTENCY_CONFLICT",
|
|
72
|
+
"INVALID_PRICE",
|
|
73
|
+
"UNCONFIRMED_DATA",
|
|
74
|
+
"REFERENCE_CONFLICT",
|
|
75
|
+
"MEDIA_INVALID",
|
|
76
|
+
"MEDIA_EXPIRED",
|
|
77
|
+
"QUOTA_EXCEEDED",
|
|
78
|
+
"RATE_LIMITED",
|
|
79
|
+
"PAIRING_PENDING",
|
|
80
|
+
"PAIRING_DENIED",
|
|
81
|
+
"PAIRING_EXPIRED",
|
|
82
|
+
"PAIRING_CONSUMED",
|
|
83
|
+
"OPERATION_TERMINAL",
|
|
84
|
+
"APPLY_WINDOW_EXPIRED",
|
|
85
|
+
"DEPENDENCY_FAILED",
|
|
86
|
+
"TEMPORARY_UNAVAILABLE",
|
|
87
|
+
"INTERNAL_ERROR"
|
|
88
|
+
]);
|
|
89
|
+
var elenaAgentErrorSchema = z.strictObject({
|
|
90
|
+
version: elenaAgentVersionSchema,
|
|
91
|
+
error: z.strictObject({
|
|
92
|
+
code: elenaAgentErrorCodeSchema,
|
|
93
|
+
message: z.string().max(500),
|
|
94
|
+
retryable: z.boolean(),
|
|
95
|
+
requestId: elenaAgentRefSchema,
|
|
96
|
+
retryAfterSeconds: z.number().int().min(1).max(3600).optional(),
|
|
97
|
+
issues: z.array(
|
|
98
|
+
z.strictObject({
|
|
99
|
+
path: z.string().max(200),
|
|
100
|
+
code: elenaAgentErrorCodeSchema,
|
|
101
|
+
message: z.string().max(200)
|
|
102
|
+
})
|
|
103
|
+
).max(250).optional()
|
|
104
|
+
})
|
|
105
|
+
});
|
|
106
|
+
var elenaAgentPageQuerySchema = z.strictObject({
|
|
107
|
+
version: elenaAgentVersionSchema,
|
|
108
|
+
cursor: z.string().regex(/^[a-zA-Z0-9_-]{1,512}$/).optional(),
|
|
109
|
+
limit: z.number().int().min(1).max(ELENA_AGENT_LIMITS.pageSize).default(50)
|
|
110
|
+
});
|
|
111
|
+
var elenaAgentEmptyRequestSchema = z.strictObject({ version: elenaAgentVersionSchema });
|
|
112
|
+
var elenaAgentAckSchema = z.strictObject({
|
|
113
|
+
version: elenaAgentVersionSchema,
|
|
114
|
+
ok: z.literal(true)
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// ../shared/src/agent-contracts/access.ts
|
|
118
|
+
import { z as z2 } from "zod";
|
|
119
|
+
var ELENA_AGENT_TRANSPORT = Object.freeze({
|
|
120
|
+
agentPrefix: "elena_sk_",
|
|
121
|
+
agentHeader: "x-elena-agent-key",
|
|
122
|
+
agentPath: "/api/v1/agent",
|
|
123
|
+
clientPrefix: "elena_pk_",
|
|
124
|
+
clientHeader: "x-elena-client-key",
|
|
125
|
+
clientPath: "/api/v1/public/sdk",
|
|
126
|
+
ownerPath: "/api/v1/agent-access"
|
|
127
|
+
});
|
|
128
|
+
var elenaAgentSecretSchema = z2.string().regex(/^elena_sk_[A-Za-z0-9_-]{43}$/);
|
|
129
|
+
var elenaManualClientKeySchema = z2.string().regex(/^elena_pk_[A-Za-z0-9_-]{43}$/);
|
|
130
|
+
var elenaManagedClientKeySchema = z2.string().max(160).regex(
|
|
131
|
+
/^elena_pk_m_[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.[1-9]\d{0,15}\.[A-Za-z0-9_-]{43}$/
|
|
132
|
+
);
|
|
133
|
+
var elenaClientKeySchema = z2.union([
|
|
134
|
+
elenaManualClientKeySchema,
|
|
135
|
+
elenaManagedClientKeySchema
|
|
136
|
+
]);
|
|
137
|
+
var elenaAgentDeviceCodeSchema = z2.string().regex(/^[A-Za-z0-9_-]{43}$/);
|
|
138
|
+
var elenaAgentUserCodeSchema = z2.string().regex(/^[A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}$/);
|
|
139
|
+
var elenaAgentModeSchema = z2.enum(["read", "mutate"]);
|
|
140
|
+
var elenaAgentScopeSchema = z2.enum([
|
|
141
|
+
"catalog:read",
|
|
142
|
+
"catalog:write",
|
|
143
|
+
"catalog:publish",
|
|
144
|
+
"assets:read",
|
|
145
|
+
"assets:write"
|
|
146
|
+
]);
|
|
147
|
+
var elenaAgentScopesSchema = z2.array(elenaAgentScopeSchema).min(1).max(5).refine((scopes) => new Set(scopes).size === scopes.length, "Duplicate scope").refine((scopes) => scopes.includes("catalog:read"), "catalog:read is mandatory");
|
|
148
|
+
var elenaAgentActionSchema = z2.enum([
|
|
149
|
+
"context.read",
|
|
150
|
+
"catalog.read",
|
|
151
|
+
"assets.read",
|
|
152
|
+
"assets.stage",
|
|
153
|
+
"operations.read",
|
|
154
|
+
"operations.propose",
|
|
155
|
+
"operations.apply",
|
|
156
|
+
"catalog.publish"
|
|
157
|
+
]);
|
|
158
|
+
var elenaAgentRestaurantSchema = z2.strictObject({
|
|
159
|
+
id: elenaAgentIdSchema,
|
|
160
|
+
name: elenaAgentNameSchema,
|
|
161
|
+
slug: z2.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).max(100),
|
|
162
|
+
currency: elenaAgentCurrencySchema
|
|
163
|
+
});
|
|
164
|
+
var keyTtl = z2.number().int().min(3600).max(ELENA_AGENT_LIMITS.keyMaxTtlSeconds).default(ELENA_AGENT_LIMITS.keyDefaultTtlSeconds);
|
|
165
|
+
var elenaAgentKeyCreateRequestSchema = z2.discriminatedUnion("kind", [
|
|
166
|
+
z2.strictObject({
|
|
167
|
+
version: elenaAgentVersionSchema,
|
|
168
|
+
kind: z2.literal("agent"),
|
|
169
|
+
label: elenaAgentNameSchema,
|
|
170
|
+
mode: elenaAgentModeSchema.default("read"),
|
|
171
|
+
scopes: elenaAgentScopesSchema.default(["catalog:read", "assets:read"]),
|
|
172
|
+
ttlSeconds: keyTtl
|
|
173
|
+
}),
|
|
174
|
+
z2.strictObject({
|
|
175
|
+
version: elenaAgentVersionSchema,
|
|
176
|
+
kind: z2.literal("client"),
|
|
177
|
+
label: elenaAgentNameSchema,
|
|
178
|
+
ttlSeconds: keyTtl
|
|
179
|
+
})
|
|
180
|
+
]);
|
|
181
|
+
var elenaAgentKeyPolicyRequestSchema = z2.strictObject({
|
|
182
|
+
version: elenaAgentVersionSchema,
|
|
183
|
+
expectedRevision: elenaAgentRevisionSchema,
|
|
184
|
+
mode: elenaAgentModeSchema,
|
|
185
|
+
scopes: elenaAgentScopesSchema
|
|
186
|
+
});
|
|
187
|
+
var elenaAgentKeyRevokeRequestSchema = z2.strictObject({
|
|
188
|
+
version: elenaAgentVersionSchema,
|
|
189
|
+
expectedRevision: elenaAgentRevisionSchema
|
|
190
|
+
});
|
|
191
|
+
var elenaAgentKeyReplaceRequestSchema = z2.strictObject({
|
|
192
|
+
version: elenaAgentVersionSchema,
|
|
193
|
+
expectedRevision: elenaAgentRevisionSchema,
|
|
194
|
+
ttlSeconds: keyTtl
|
|
195
|
+
});
|
|
196
|
+
var keyMetadata = {
|
|
197
|
+
id: elenaAgentIdSchema,
|
|
198
|
+
label: elenaAgentNameSchema,
|
|
199
|
+
displaySuffix: z2.string().regex(/^[A-Za-z0-9_-]{6}$/),
|
|
200
|
+
revision: elenaAgentRevisionSchema,
|
|
201
|
+
status: z2.enum(["active", "revoked", "expired"]),
|
|
202
|
+
createdAt: elenaAgentTimestampSchema,
|
|
203
|
+
expiresAt: elenaAgentTimestampSchema,
|
|
204
|
+
revokedAt: elenaAgentTimestampSchema.nullable(),
|
|
205
|
+
lastUsedAt: elenaAgentTimestampSchema.nullable()
|
|
206
|
+
};
|
|
207
|
+
var elenaAgentKeyMetadataSchema = z2.discriminatedUnion("kind", [
|
|
208
|
+
z2.strictObject({
|
|
209
|
+
...keyMetadata,
|
|
210
|
+
kind: z2.literal("agent"),
|
|
211
|
+
mode: elenaAgentModeSchema,
|
|
212
|
+
scopes: elenaAgentScopesSchema
|
|
213
|
+
}),
|
|
214
|
+
z2.strictObject({ ...keyMetadata, kind: z2.literal("client") })
|
|
215
|
+
]);
|
|
216
|
+
var elenaAgentKeyListSchema = z2.strictObject({
|
|
217
|
+
version: elenaAgentVersionSchema,
|
|
218
|
+
keys: z2.array(elenaAgentKeyMetadataSchema).max(20)
|
|
219
|
+
});
|
|
220
|
+
var elenaAgentKeyReceiptSchema = z2.discriminatedUnion("kind", [
|
|
221
|
+
z2.strictObject({
|
|
222
|
+
version: elenaAgentVersionSchema,
|
|
223
|
+
kind: z2.literal("agent"),
|
|
224
|
+
key: elenaAgentKeyMetadataSchema.options[0],
|
|
225
|
+
secret: elenaAgentSecretSchema
|
|
226
|
+
}),
|
|
227
|
+
z2.strictObject({
|
|
228
|
+
version: elenaAgentVersionSchema,
|
|
229
|
+
kind: z2.literal("client"),
|
|
230
|
+
key: elenaAgentKeyMetadataSchema.options[1],
|
|
231
|
+
clientKey: elenaManualClientKeySchema
|
|
232
|
+
})
|
|
233
|
+
]);
|
|
234
|
+
var elenaAgentKeyResponseSchema = z2.strictObject({
|
|
235
|
+
version: elenaAgentVersionSchema,
|
|
236
|
+
key: elenaAgentKeyMetadataSchema
|
|
237
|
+
});
|
|
238
|
+
var elenaAgentHttpsUrlSchema = z2.url({ protocol: /^https$/ }).max(2048).refine(
|
|
239
|
+
(value) => /^https:\/\/[^/?#@\\\s]+(?:[/?#]|$)/.test(value),
|
|
240
|
+
"URL must not contain userinfo"
|
|
241
|
+
);
|
|
242
|
+
var elenaAgentConnectionUrlSchema = z2.url({ protocol: /^https?$/ }).max(2048).refine(
|
|
243
|
+
(value) => /^https?:\/\/[^/?#@\\\s]+(?:[/?#]|$)/.test(value),
|
|
244
|
+
"URL must not contain userinfo"
|
|
245
|
+
);
|
|
246
|
+
var elenaAgentBaseUrlSchema = elenaAgentHttpsUrlSchema.refine(
|
|
247
|
+
(value) => /^https:\/\/[^/?#@\\\s]+\/?$/.test(value),
|
|
248
|
+
"Base URL must be an explicit origin"
|
|
249
|
+
);
|
|
250
|
+
var elenaAgentConnectionOriginSchema = elenaAgentConnectionUrlSchema.refine(
|
|
251
|
+
(value) => /^https?:\/\/[^/?#@\\\s]+\/?$/.test(value),
|
|
252
|
+
"Base URL must be an explicit origin"
|
|
253
|
+
);
|
|
254
|
+
var elenaAgentPairingStartRequestSchema = z2.strictObject({
|
|
255
|
+
version: elenaAgentVersionSchema,
|
|
256
|
+
label: elenaAgentNameSchema
|
|
257
|
+
});
|
|
258
|
+
var elenaAgentPairingStartResponseSchema = z2.strictObject({
|
|
259
|
+
version: elenaAgentVersionSchema,
|
|
260
|
+
pairingId: elenaAgentIdSchema,
|
|
261
|
+
deviceCode: elenaAgentDeviceCodeSchema,
|
|
262
|
+
userCode: elenaAgentUserCodeSchema,
|
|
263
|
+
verificationUrl: elenaAgentConnectionUrlSchema,
|
|
264
|
+
expiresAt: elenaAgentTimestampSchema,
|
|
265
|
+
intervalSeconds: z2.literal(ELENA_AGENT_LIMITS.pairingPollSeconds)
|
|
266
|
+
});
|
|
267
|
+
var elenaAgentPairingPollRequestSchema = z2.strictObject({
|
|
268
|
+
version: elenaAgentVersionSchema,
|
|
269
|
+
deviceCode: elenaAgentDeviceCodeSchema
|
|
270
|
+
});
|
|
271
|
+
var elenaAgentPairingReviewSchema = z2.strictObject({
|
|
272
|
+
version: elenaAgentVersionSchema,
|
|
273
|
+
label: elenaAgentNameSchema,
|
|
274
|
+
userCode: elenaAgentUserCodeSchema,
|
|
275
|
+
state: z2.enum(["pending", "approved", "rejected", "expired", "consumed"]),
|
|
276
|
+
expiresAt: elenaAgentTimestampSchema
|
|
277
|
+
});
|
|
278
|
+
var elenaAgentPairingDecisionRequestSchema = z2.discriminatedUnion("decision", [
|
|
279
|
+
z2.strictObject({
|
|
280
|
+
version: elenaAgentVersionSchema,
|
|
281
|
+
userCode: elenaAgentUserCodeSchema,
|
|
282
|
+
decision: z2.literal("approve"),
|
|
283
|
+
mode: elenaAgentModeSchema.default("read"),
|
|
284
|
+
scopes: elenaAgentScopesSchema.default(["catalog:read", "assets:read"]),
|
|
285
|
+
ttlSeconds: keyTtl
|
|
286
|
+
}),
|
|
287
|
+
z2.strictObject({
|
|
288
|
+
version: elenaAgentVersionSchema,
|
|
289
|
+
userCode: elenaAgentUserCodeSchema,
|
|
290
|
+
decision: z2.literal("reject")
|
|
291
|
+
})
|
|
292
|
+
]);
|
|
293
|
+
var elenaAgentPairingPollResponseSchema = z2.discriminatedUnion("status", [
|
|
294
|
+
z2.strictObject({
|
|
295
|
+
version: elenaAgentVersionSchema,
|
|
296
|
+
status: z2.literal("pending"),
|
|
297
|
+
expiresAt: elenaAgentTimestampSchema,
|
|
298
|
+
intervalSeconds: z2.literal(ELENA_AGENT_LIMITS.pairingPollSeconds)
|
|
299
|
+
}),
|
|
300
|
+
z2.strictObject({
|
|
301
|
+
version: elenaAgentVersionSchema,
|
|
302
|
+
status: z2.literal("delivered"),
|
|
303
|
+
restaurant: elenaAgentRestaurantSchema,
|
|
304
|
+
credential: elenaAgentKeyReceiptSchema.options[0]
|
|
305
|
+
})
|
|
306
|
+
]);
|
|
307
|
+
var elenaAgentContextSchema = z2.strictObject({
|
|
308
|
+
version: elenaAgentVersionSchema,
|
|
309
|
+
restaurant: elenaAgentRestaurantSchema,
|
|
310
|
+
principal: z2.strictObject({
|
|
311
|
+
kind: z2.literal("agent"),
|
|
312
|
+
credentialId: elenaAgentIdSchema,
|
|
313
|
+
credentialRevision: elenaAgentRevisionSchema,
|
|
314
|
+
mode: elenaAgentModeSchema,
|
|
315
|
+
scopes: elenaAgentScopesSchema,
|
|
316
|
+
expiresAt: elenaAgentTimestampSchema
|
|
317
|
+
}),
|
|
318
|
+
capabilities: z2.strictObject({
|
|
319
|
+
apiAccess: z2.literal(true),
|
|
320
|
+
menu: z2.literal(true),
|
|
321
|
+
imports: z2.boolean(),
|
|
322
|
+
media: z2.boolean(),
|
|
323
|
+
publication: z2.boolean()
|
|
324
|
+
}),
|
|
325
|
+
actions: z2.array(elenaAgentActionSchema).min(1).max(8).refine((actions) => new Set(actions).size === actions.length, "Duplicate action")
|
|
326
|
+
}).superRefine((value, ctx) => {
|
|
327
|
+
const requiredScopes = {
|
|
328
|
+
"catalog.read": "catalog:read",
|
|
329
|
+
"assets.read": "assets:read",
|
|
330
|
+
"assets.stage": "assets:write",
|
|
331
|
+
"operations.propose": "catalog:write",
|
|
332
|
+
"operations.apply": "catalog:write",
|
|
333
|
+
"catalog.publish": "catalog:publish"
|
|
334
|
+
};
|
|
335
|
+
const writes = {
|
|
336
|
+
"assets.stage": true,
|
|
337
|
+
"operations.propose": true,
|
|
338
|
+
"operations.apply": true,
|
|
339
|
+
"catalog.publish": true
|
|
340
|
+
};
|
|
341
|
+
for (const action of value.actions) {
|
|
342
|
+
if (value.principal.mode === "read" && writes[action]) {
|
|
343
|
+
ctx.addIssue({
|
|
344
|
+
code: "custom",
|
|
345
|
+
path: ["actions"],
|
|
346
|
+
message: "Read mode cannot expose mutation actions"
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
const scope = requiredScopes[action];
|
|
350
|
+
if (scope && !value.principal.scopes.includes(scope)) {
|
|
351
|
+
ctx.addIssue({
|
|
352
|
+
code: "custom",
|
|
353
|
+
path: ["actions"],
|
|
354
|
+
message: "Action exceeds granted scopes"
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (value.actions.includes("catalog.publish") && !value.principal.scopes.includes("catalog:write")) {
|
|
359
|
+
ctx.addIssue({
|
|
360
|
+
code: "custom",
|
|
361
|
+
path: ["actions"],
|
|
362
|
+
message: "Publication also requires catalog:write"
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
const expectedCapabilities = {
|
|
366
|
+
imports: value.actions.includes("operations.propose") && value.actions.includes("operations.apply"),
|
|
367
|
+
media: value.actions.includes("assets.stage"),
|
|
368
|
+
publication: value.actions.includes("catalog.publish")
|
|
369
|
+
};
|
|
370
|
+
for (const capability of ["imports", "media", "publication"]) {
|
|
371
|
+
if (value.capabilities[capability] !== expectedCapabilities[capability]) {
|
|
372
|
+
ctx.addIssue({
|
|
373
|
+
code: "custom",
|
|
374
|
+
path: ["capabilities", capability],
|
|
375
|
+
message: "Capability must match effective actions"
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
var elenaClientContextSchema = z2.strictObject({
|
|
381
|
+
version: elenaAgentVersionSchema,
|
|
382
|
+
restaurant: elenaAgentRestaurantSchema,
|
|
383
|
+
capabilities: z2.strictObject({ menu: z2.literal(true), assets: z2.literal(true) })
|
|
384
|
+
});
|
|
385
|
+
var elenaAgentCliAuthStatusSchema = z2.strictObject({
|
|
386
|
+
version: elenaAgentVersionSchema,
|
|
387
|
+
authenticated: z2.boolean(),
|
|
388
|
+
source: z2.enum(["store", "environment", "none"]),
|
|
389
|
+
baseUrl: elenaAgentBaseUrlSchema.nullable(),
|
|
390
|
+
credentialId: elenaAgentIdSchema.nullable(),
|
|
391
|
+
restaurantId: elenaAgentIdSchema.nullable(),
|
|
392
|
+
expiresAt: elenaAgentTimestampSchema.nullable()
|
|
393
|
+
});
|
|
394
|
+
var elenaAgentCliLogoutSchema = z2.strictObject({
|
|
395
|
+
version: elenaAgentVersionSchema,
|
|
396
|
+
loggedOut: z2.literal(true),
|
|
397
|
+
remoteRevoked: z2.literal(false),
|
|
398
|
+
environmentCredentialPresent: z2.boolean()
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// ../shared/src/agent-contracts/catalog.ts
|
|
402
|
+
import { z as z3 } from "zod";
|
|
403
|
+
var elenaAgentExistingTargetSchema = z3.strictObject({
|
|
404
|
+
kind: z3.literal("id"),
|
|
405
|
+
id: elenaAgentIdSchema,
|
|
406
|
+
expectedVersion: elenaAgentRevisionSchema
|
|
407
|
+
});
|
|
408
|
+
var elenaAgentExternalTargetSchema = z3.strictObject({
|
|
409
|
+
kind: z3.literal("external"),
|
|
410
|
+
namespace: elenaAgentRefSchema,
|
|
411
|
+
key: elenaAgentRefSchema,
|
|
412
|
+
expectedVersion: elenaAgentRevisionSchema.nullable()
|
|
413
|
+
});
|
|
414
|
+
var elenaAgentTargetSchema = z3.discriminatedUnion("kind", [
|
|
415
|
+
elenaAgentExistingTargetSchema,
|
|
416
|
+
elenaAgentExternalTargetSchema
|
|
417
|
+
]);
|
|
418
|
+
var elenaAgentCategoryReferenceSchema = z3.discriminatedUnion("kind", [
|
|
419
|
+
elenaAgentExistingTargetSchema,
|
|
420
|
+
z3.strictObject({ kind: z3.literal("ref"), ref: elenaAgentRefSchema })
|
|
421
|
+
]);
|
|
422
|
+
var elenaAgentDescriptionSchema = z3.discriminatedUnion("status", [
|
|
423
|
+
z3.strictObject({ status: z3.literal("transcribed"), text: elenaAgentTextSchema }),
|
|
424
|
+
z3.strictObject({ status: z3.literal("proposed"), text: elenaAgentTextSchema })
|
|
425
|
+
]);
|
|
426
|
+
var elenaAgentConfirmedPriceSchema = z3.strictObject({
|
|
427
|
+
status: z3.literal("confirmed"),
|
|
428
|
+
amount: elenaAgentMoneySchema,
|
|
429
|
+
basis: z3.literal("operator")
|
|
430
|
+
});
|
|
431
|
+
var elenaAgentDraftPriceSchema = z3.discriminatedUnion("status", [
|
|
432
|
+
elenaAgentConfirmedPriceSchema,
|
|
433
|
+
z3.strictObject({
|
|
434
|
+
status: z3.literal("unconfirmed"),
|
|
435
|
+
reason: z3.enum(["missing", "illegible"]),
|
|
436
|
+
rawText: z3.string().max(100).nullable()
|
|
437
|
+
})
|
|
438
|
+
]);
|
|
439
|
+
var elenaAgentMediaReferenceSchema = z3.discriminatedUnion("kind", [
|
|
440
|
+
z3.strictObject({
|
|
441
|
+
kind: z3.literal("existing"),
|
|
442
|
+
assetId: elenaAgentIdSchema,
|
|
443
|
+
sha256: elenaAgentHashSchema
|
|
444
|
+
}),
|
|
445
|
+
z3.strictObject({
|
|
446
|
+
kind: z3.literal("staged"),
|
|
447
|
+
stagingId: elenaAgentIdSchema,
|
|
448
|
+
sha256: elenaAgentHashSchema
|
|
449
|
+
})
|
|
450
|
+
]);
|
|
451
|
+
var elenaAgentGallerySchema = z3.array(elenaAgentMediaReferenceSchema).max(ELENA_AGENT_LIMITS.galleryImages).refine(
|
|
452
|
+
(images) => new Set(images.map((image) => image.kind === "existing" ? image.assetId : image.stagingId)).size === images.length,
|
|
453
|
+
"Duplicate media reference"
|
|
454
|
+
);
|
|
455
|
+
var elenaAgentExtraOptionSchema = z3.strictObject({
|
|
456
|
+
name: elenaAgentNameSchema,
|
|
457
|
+
additionalPrice: elenaAgentNonnegativeMoneySchema,
|
|
458
|
+
order: z3.number().int().min(0).max(1e3)
|
|
459
|
+
});
|
|
460
|
+
var elenaAgentExtraGroupSchema = z3.strictObject({
|
|
461
|
+
name: elenaAgentNameSchema,
|
|
462
|
+
required: z3.boolean(),
|
|
463
|
+
minSelections: z3.number().int().min(0).max(ELENA_AGENT_LIMITS.optionsPerGroup),
|
|
464
|
+
maxSelections: z3.number().int().min(1).max(ELENA_AGENT_LIMITS.optionsPerGroup),
|
|
465
|
+
order: z3.number().int().min(0).max(1e3),
|
|
466
|
+
options: z3.array(elenaAgentExtraOptionSchema).min(1).max(ELENA_AGENT_LIMITS.optionsPerGroup)
|
|
467
|
+
}).superRefine((group, ctx) => {
|
|
468
|
+
if (group.minSelections > group.maxSelections || group.maxSelections > group.options.length) {
|
|
469
|
+
ctx.addIssue({
|
|
470
|
+
code: "custom",
|
|
471
|
+
path: ["maxSelections"],
|
|
472
|
+
message: "Invalid selection bounds"
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
if (group.required && group.minSelections < 1 || !group.required && group.minSelections !== 0) {
|
|
476
|
+
ctx.addIssue({
|
|
477
|
+
code: "custom",
|
|
478
|
+
path: ["minSelections"],
|
|
479
|
+
message: "Required must match minimum selection count"
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
if (new Set(
|
|
483
|
+
group.options.map((option) => option.name.normalize("NFKC").trim().toLocaleLowerCase("es"))
|
|
484
|
+
).size !== group.options.length) {
|
|
485
|
+
ctx.addIssue({ code: "custom", path: ["options"], message: "Duplicate option name" });
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
var elenaAgentExtrasSchema = z3.strictObject({
|
|
489
|
+
confirmed: z3.literal(true),
|
|
490
|
+
groups: z3.array(elenaAgentExtraGroupSchema).max(ELENA_AGENT_LIMITS.extraGroups)
|
|
491
|
+
}).refine(
|
|
492
|
+
(extras) => new Set(
|
|
493
|
+
extras.groups.map((group) => group.name.normalize("NFKC").trim().toLocaleLowerCase("es"))
|
|
494
|
+
).size === extras.groups.length,
|
|
495
|
+
"Duplicate group name"
|
|
496
|
+
);
|
|
497
|
+
var elenaAgentCategoryDataSchema = z3.strictObject({
|
|
498
|
+
name: elenaAgentNameSchema,
|
|
499
|
+
description: elenaAgentDescriptionSchema.nullable(),
|
|
500
|
+
order: z3.number().int().min(0).max(1e4)
|
|
501
|
+
});
|
|
502
|
+
var productFields = {
|
|
503
|
+
type: z3.literal("menu"),
|
|
504
|
+
name: elenaAgentNameSchema,
|
|
505
|
+
description: elenaAgentDescriptionSchema.nullable(),
|
|
506
|
+
category: elenaAgentCategoryReferenceSchema,
|
|
507
|
+
available: z3.boolean(),
|
|
508
|
+
images: elenaAgentGallerySchema,
|
|
509
|
+
extras: elenaAgentExtrasSchema
|
|
510
|
+
};
|
|
511
|
+
var elenaAgentProductDataSchema = z3.strictObject({
|
|
512
|
+
...productFields,
|
|
513
|
+
price: elenaAgentConfirmedPriceSchema
|
|
514
|
+
});
|
|
515
|
+
var elenaAgentDraftProductDataSchema = z3.strictObject({
|
|
516
|
+
...productFields,
|
|
517
|
+
price: elenaAgentDraftPriceSchema
|
|
518
|
+
});
|
|
519
|
+
var elenaAgentCategoryChangeSchema = z3.strictObject({
|
|
520
|
+
kind: z3.literal("category.upsert"),
|
|
521
|
+
ref: elenaAgentRefSchema,
|
|
522
|
+
target: elenaAgentTargetSchema,
|
|
523
|
+
data: elenaAgentCategoryDataSchema
|
|
524
|
+
});
|
|
525
|
+
var elenaAgentProductChangeSchema = z3.strictObject({
|
|
526
|
+
kind: z3.literal("product.upsert"),
|
|
527
|
+
ref: elenaAgentRefSchema,
|
|
528
|
+
target: elenaAgentTargetSchema,
|
|
529
|
+
data: elenaAgentProductDataSchema
|
|
530
|
+
});
|
|
531
|
+
var elenaAgentChangeSchema = z3.discriminatedUnion("kind", [
|
|
532
|
+
elenaAgentCategoryChangeSchema,
|
|
533
|
+
elenaAgentProductChangeSchema
|
|
534
|
+
]);
|
|
535
|
+
var elenaAgentCategorySchema = z3.strictObject({
|
|
536
|
+
id: elenaAgentIdSchema,
|
|
537
|
+
revision: elenaAgentRevisionSchema,
|
|
538
|
+
name: elenaAgentNameSchema,
|
|
539
|
+
description: elenaAgentTextSchema.nullable(),
|
|
540
|
+
order: z3.number().int().min(0).max(1e4)
|
|
541
|
+
});
|
|
542
|
+
var elenaAgentProductSchema = z3.strictObject({
|
|
543
|
+
id: elenaAgentIdSchema,
|
|
544
|
+
revision: elenaAgentRevisionSchema,
|
|
545
|
+
type: z3.literal("menu"),
|
|
546
|
+
name: elenaAgentNameSchema,
|
|
547
|
+
basePrice: elenaAgentMoneySchema,
|
|
548
|
+
description: elenaAgentTextSchema.nullable(),
|
|
549
|
+
categoryId: elenaAgentIdSchema,
|
|
550
|
+
available: z3.boolean(),
|
|
551
|
+
publishedToMenu: z3.boolean(),
|
|
552
|
+
isDraft: z3.boolean(),
|
|
553
|
+
images: z3.array(z3.strictObject({ assetId: elenaAgentIdSchema, sha256: elenaAgentHashSchema })).max(ELENA_AGENT_LIMITS.galleryImages),
|
|
554
|
+
/** Present on current native reads; optional only for retained v1 snapshots. */
|
|
555
|
+
primaryImageId: elenaAgentIdSchema.nullable().optional(),
|
|
556
|
+
extraGroups: z3.array(elenaAgentExtraGroupSchema).max(ELENA_AGENT_LIMITS.extraGroups)
|
|
557
|
+
});
|
|
558
|
+
var elenaAgentCategoryListSchema = z3.strictObject({
|
|
559
|
+
version: elenaAgentVersionSchema,
|
|
560
|
+
categories: z3.array(elenaAgentCategorySchema).max(ELENA_AGENT_LIMITS.pageSize),
|
|
561
|
+
nextCursor: z3.string().regex(/^[a-zA-Z0-9_-]{1,512}$/).nullable()
|
|
562
|
+
});
|
|
563
|
+
var elenaAgentProductListSchema = z3.strictObject({
|
|
564
|
+
version: elenaAgentVersionSchema,
|
|
565
|
+
products: z3.array(elenaAgentProductSchema).max(ELENA_AGENT_LIMITS.pageSize),
|
|
566
|
+
nextCursor: z3.string().regex(/^[a-zA-Z0-9_-]{1,512}$/).nullable()
|
|
567
|
+
});
|
|
568
|
+
var elenaAgentProductResponseSchema = z3.strictObject({
|
|
569
|
+
version: elenaAgentVersionSchema,
|
|
570
|
+
product: elenaAgentProductSchema,
|
|
571
|
+
/** Authoritative editing metadata; never infer primary image from gallery order. */
|
|
572
|
+
editable: z3.strictObject({
|
|
573
|
+
restaurantId: elenaAgentIdSchema,
|
|
574
|
+
productId: elenaAgentIdSchema,
|
|
575
|
+
imageId: elenaAgentIdSchema.nullable(),
|
|
576
|
+
categoryRevision: elenaAgentRevisionSchema,
|
|
577
|
+
primaryImage: z3.strictObject({ assetId: elenaAgentIdSchema, sha256: elenaAgentHashSchema }).nullable().optional(),
|
|
578
|
+
imageEditorUrl: elenaAgentHttpsUrlSchema.optional()
|
|
579
|
+
}).optional()
|
|
580
|
+
});
|
|
581
|
+
var elenaAgentCategoryResponseSchema = z3.strictObject({
|
|
582
|
+
version: elenaAgentVersionSchema,
|
|
583
|
+
category: elenaAgentCategorySchema
|
|
584
|
+
});
|
|
585
|
+
var elenaAgentMediaStageRequestSchema = z3.strictObject({
|
|
586
|
+
version: elenaAgentVersionSchema,
|
|
587
|
+
idempotencyKey: elenaAgentRefSchema,
|
|
588
|
+
fileName: z3.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,119}\.(?:jpg|jpeg|png|webp)$/),
|
|
589
|
+
mimeType: z3.enum(["image/jpeg", "image/png", "image/webp"]),
|
|
590
|
+
bytes: z3.number().int().min(1).max(ELENA_AGENT_LIMITS.mediaBytes),
|
|
591
|
+
sha256: elenaAgentHashSchema
|
|
592
|
+
});
|
|
593
|
+
var elenaAgentMediaStagedSchema = z3.strictObject({
|
|
594
|
+
version: elenaAgentVersionSchema,
|
|
595
|
+
stagingId: elenaAgentIdSchema,
|
|
596
|
+
sha256: elenaAgentHashSchema,
|
|
597
|
+
mimeType: z3.enum(["image/jpeg", "image/png", "image/webp"]),
|
|
598
|
+
bytes: z3.number().int().min(1).max(ELENA_AGENT_LIMITS.mediaBytes),
|
|
599
|
+
expiresAt: elenaAgentTimestampSchema
|
|
600
|
+
});
|
|
601
|
+
var elenaAgentAssetSchema = z3.strictObject({
|
|
602
|
+
version: elenaAgentVersionSchema,
|
|
603
|
+
assetId: elenaAgentIdSchema,
|
|
604
|
+
sha256: elenaAgentHashSchema,
|
|
605
|
+
mimeType: z3.enum(["image/jpeg", "image/png", "image/webp"]),
|
|
606
|
+
bytes: z3.number().int().min(1).max(ELENA_AGENT_LIMITS.mediaBytes)
|
|
607
|
+
});
|
|
608
|
+
var elenaClientAssetSchema = z3.strictObject({
|
|
609
|
+
assetId: elenaAgentIdSchema,
|
|
610
|
+
url: elenaAgentHttpsUrlSchema
|
|
611
|
+
});
|
|
612
|
+
var elenaClientCategorySchema = z3.strictObject({
|
|
613
|
+
id: elenaAgentIdSchema,
|
|
614
|
+
name: elenaAgentNameSchema,
|
|
615
|
+
description: elenaAgentTextSchema.nullable(),
|
|
616
|
+
order: z3.number().int().min(0).max(1e4)
|
|
617
|
+
});
|
|
618
|
+
var elenaClientProductSchema = z3.strictObject({
|
|
619
|
+
id: elenaAgentIdSchema,
|
|
620
|
+
type: z3.literal("menu"),
|
|
621
|
+
name: elenaAgentNameSchema,
|
|
622
|
+
basePrice: elenaAgentMoneySchema,
|
|
623
|
+
description: elenaAgentTextSchema.nullable(),
|
|
624
|
+
categoryId: elenaAgentIdSchema,
|
|
625
|
+
available: z3.boolean(),
|
|
626
|
+
images: z3.array(elenaClientAssetSchema).max(ELENA_AGENT_LIMITS.galleryImages),
|
|
627
|
+
extraGroups: z3.array(elenaAgentExtraGroupSchema).max(ELENA_AGENT_LIMITS.extraGroups)
|
|
628
|
+
});
|
|
629
|
+
var elenaClientCatalogSchema = z3.strictObject({
|
|
630
|
+
version: elenaAgentVersionSchema,
|
|
631
|
+
categories: z3.array(elenaClientCategorySchema).max(ELENA_AGENT_LIMITS.pageSize),
|
|
632
|
+
products: z3.array(elenaClientProductSchema).max(ELENA_AGENT_LIMITS.pageSize),
|
|
633
|
+
nextCursor: z3.string().regex(/^[a-zA-Z0-9_-]{1,512}$/).nullable()
|
|
634
|
+
});
|
|
635
|
+
var elenaClientProductResponseSchema = z3.strictObject({
|
|
636
|
+
version: elenaAgentVersionSchema,
|
|
637
|
+
product: elenaClientProductSchema
|
|
638
|
+
});
|
|
639
|
+
var elenaClientAssetResponseSchema = z3.strictObject({
|
|
640
|
+
version: elenaAgentVersionSchema,
|
|
641
|
+
asset: elenaClientAssetSchema
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
// ../shared/src/agent-contracts/operations.ts
|
|
645
|
+
import { z as z4 } from "zod";
|
|
646
|
+
function normalizedValuesEqual(left, right) {
|
|
647
|
+
if (left === right) return true;
|
|
648
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
649
|
+
return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => normalizedValuesEqual(value, right[index]));
|
|
650
|
+
}
|
|
651
|
+
if (left === null || right === null || typeof left !== "object" || typeof right !== "object") {
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
const leftRecord = left;
|
|
655
|
+
const rightRecord = right;
|
|
656
|
+
const keys = Object.keys(leftRecord);
|
|
657
|
+
return keys.length === Object.keys(rightRecord).length && keys.every(
|
|
658
|
+
(key) => Object.prototype.hasOwnProperty.call(rightRecord, key) && normalizedValuesEqual(leftRecord[key], rightRecord[key])
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
var elenaAgentChangesSchema = z4.array(elenaAgentChangeSchema).min(1).max(ELENA_AGENT_LIMITS.changesPerOperation).superRefine((changes, ctx) => {
|
|
662
|
+
const refs = /* @__PURE__ */ new Set();
|
|
663
|
+
const targets = /* @__PURE__ */ new Set();
|
|
664
|
+
const categoryRefs = new Set(
|
|
665
|
+
changes.filter((change) => change.kind === "category.upsert").map((change) => change.ref)
|
|
666
|
+
);
|
|
667
|
+
changes.forEach((change, index) => {
|
|
668
|
+
const target = change.target.kind === "id" ? `${change.kind}:id:${change.target.id}` : JSON.stringify([change.kind, "external", change.target.namespace, change.target.key]);
|
|
669
|
+
if (refs.has(change.ref) || targets.has(target)) {
|
|
670
|
+
ctx.addIssue({ code: "custom", path: [index], message: "Duplicate ref or target" });
|
|
671
|
+
}
|
|
672
|
+
refs.add(change.ref);
|
|
673
|
+
targets.add(target);
|
|
674
|
+
if (change.kind === "product.upsert" && change.data.category.kind === "ref" && !categoryRefs.has(change.data.category.ref)) {
|
|
675
|
+
ctx.addIssue({
|
|
676
|
+
code: "custom",
|
|
677
|
+
path: [index, "data", "category"],
|
|
678
|
+
message: "Unknown category ref"
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
});
|
|
683
|
+
var elenaAgentPublicationPayloadSchema = z4.strictObject({
|
|
684
|
+
kind: z4.literal("publication"),
|
|
685
|
+
published: z4.boolean(),
|
|
686
|
+
products: z4.array(elenaAgentExistingTargetSchema).min(1).max(ELENA_AGENT_LIMITS.publicationProducts).refine(
|
|
687
|
+
(products) => new Set(products.map((product) => product.id)).size === products.length,
|
|
688
|
+
"Duplicate product"
|
|
689
|
+
)
|
|
690
|
+
});
|
|
691
|
+
var elenaAgentOperationPayloadSchema = z4.discriminatedUnion("kind", [
|
|
692
|
+
z4.strictObject({ kind: z4.literal("catalog"), changes: elenaAgentChangesSchema }),
|
|
693
|
+
elenaAgentPublicationPayloadSchema,
|
|
694
|
+
z4.strictObject({
|
|
695
|
+
kind: z4.literal("import"),
|
|
696
|
+
importId: elenaAgentIdSchema,
|
|
697
|
+
planHash: elenaAgentHashSchema,
|
|
698
|
+
selectedRefs: z4.array(elenaAgentRefSchema).min(1).max(ELENA_AGENT_LIMITS.productsPerImport + ELENA_AGENT_LIMITS.categoriesPerImport).refine((refs) => new Set(refs).size === refs.length, "Duplicate selected ref")
|
|
699
|
+
})
|
|
700
|
+
]);
|
|
701
|
+
var elenaAgentProposalRequestSchema = z4.strictObject({
|
|
702
|
+
version: elenaAgentVersionSchema,
|
|
703
|
+
idempotencyKey: elenaAgentRefSchema,
|
|
704
|
+
payload: elenaAgentOperationPayloadSchema
|
|
705
|
+
});
|
|
706
|
+
var elenaAgentApplyRequestSchema = z4.strictObject({
|
|
707
|
+
version: elenaAgentVersionSchema,
|
|
708
|
+
expectedRevision: elenaAgentRevisionSchema,
|
|
709
|
+
payloadHash: elenaAgentHashSchema
|
|
710
|
+
});
|
|
711
|
+
var elenaAgentApprovalRequestSchema = z4.strictObject({
|
|
712
|
+
version: elenaAgentVersionSchema,
|
|
713
|
+
expectedRevision: elenaAgentRevisionSchema,
|
|
714
|
+
payloadHash: elenaAgentHashSchema,
|
|
715
|
+
decision: z4.enum(["approve", "reject"])
|
|
716
|
+
});
|
|
717
|
+
var elenaAgentBaseVersionSchema = z4.strictObject({
|
|
718
|
+
kind: z4.enum(["category", "product", "credential"]),
|
|
719
|
+
id: elenaAgentIdSchema,
|
|
720
|
+
revision: elenaAgentRevisionSchema
|
|
721
|
+
});
|
|
722
|
+
var elenaAgentDiffSchema = z4.discriminatedUnion("kind", [
|
|
723
|
+
z4.strictObject({
|
|
724
|
+
kind: z4.literal("category"),
|
|
725
|
+
ref: elenaAgentRefSchema,
|
|
726
|
+
before: elenaAgentCategorySchema.nullable(),
|
|
727
|
+
after: elenaAgentCategoryChangeSchema,
|
|
728
|
+
publicEffect: z4.enum(["none", "immediate_on_apply"])
|
|
729
|
+
}),
|
|
730
|
+
z4.strictObject({
|
|
731
|
+
kind: z4.literal("product"),
|
|
732
|
+
ref: elenaAgentRefSchema,
|
|
733
|
+
before: elenaAgentProductSchema.nullable(),
|
|
734
|
+
after: elenaAgentChangeSchema.options[1],
|
|
735
|
+
publicEffect: z4.enum(["none", "immediate_on_apply"])
|
|
736
|
+
}),
|
|
737
|
+
z4.strictObject({
|
|
738
|
+
kind: z4.literal("publication"),
|
|
739
|
+
ref: elenaAgentRefSchema,
|
|
740
|
+
productId: elenaAgentIdSchema,
|
|
741
|
+
before: z4.boolean(),
|
|
742
|
+
after: z4.boolean(),
|
|
743
|
+
publicEffect: z4.literal("immediate_on_apply")
|
|
744
|
+
})
|
|
745
|
+
]).superRefine((diff, ctx) => {
|
|
746
|
+
if (diff.kind === "publication") {
|
|
747
|
+
if (diff.ref !== `product:${diff.productId}`) {
|
|
748
|
+
ctx.addIssue({
|
|
749
|
+
code: "custom",
|
|
750
|
+
path: ["ref"],
|
|
751
|
+
message: "Publication ref must identify the exact product"
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
if (diff.ref !== diff.after.ref) {
|
|
757
|
+
ctx.addIssue({ code: "custom", path: ["ref"], message: "Diff must identify its change" });
|
|
758
|
+
}
|
|
759
|
+
if (diff.kind === "product") {
|
|
760
|
+
const effect = diff.before?.publishedToMenu ? "immediate_on_apply" : "none";
|
|
761
|
+
if (diff.publicEffect !== effect) {
|
|
762
|
+
ctx.addIssue({
|
|
763
|
+
code: "custom",
|
|
764
|
+
path: ["publicEffect"],
|
|
765
|
+
message: "Diff must disclose current public visibility"
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
var elenaAgentItemErrorSchema = z4.strictObject({
|
|
771
|
+
code: elenaAgentErrorCodeSchema,
|
|
772
|
+
message: z4.string().max(500),
|
|
773
|
+
retryable: z4.boolean()
|
|
774
|
+
});
|
|
775
|
+
var elenaAgentResultItemSchema = z4.discriminatedUnion("status", [
|
|
776
|
+
z4.strictObject({
|
|
777
|
+
ref: elenaAgentRefSchema,
|
|
778
|
+
status: z4.literal("applied"),
|
|
779
|
+
resource: z4.strictObject({
|
|
780
|
+
kind: z4.enum(["category", "product"]),
|
|
781
|
+
id: elenaAgentIdSchema,
|
|
782
|
+
revision: elenaAgentRevisionSchema
|
|
783
|
+
})
|
|
784
|
+
}),
|
|
785
|
+
z4.strictObject({
|
|
786
|
+
ref: elenaAgentRefSchema,
|
|
787
|
+
status: z4.literal("skipped"),
|
|
788
|
+
resource: z4.strictObject({
|
|
789
|
+
kind: z4.enum(["category", "product"]),
|
|
790
|
+
id: elenaAgentIdSchema,
|
|
791
|
+
revision: elenaAgentRevisionSchema
|
|
792
|
+
})
|
|
793
|
+
}),
|
|
794
|
+
z4.strictObject({
|
|
795
|
+
ref: elenaAgentRefSchema,
|
|
796
|
+
status: z4.literal("failed"),
|
|
797
|
+
error: elenaAgentItemErrorSchema
|
|
798
|
+
}),
|
|
799
|
+
z4.strictObject({
|
|
800
|
+
ref: elenaAgentRefSchema,
|
|
801
|
+
status: z4.literal("blocked"),
|
|
802
|
+
error: elenaAgentItemErrorSchema
|
|
803
|
+
})
|
|
804
|
+
]);
|
|
805
|
+
var elenaAgentOperationResultSchema = z4.strictObject({
|
|
806
|
+
completedAt: elenaAgentTimestampSchema,
|
|
807
|
+
items: z4.array(elenaAgentResultItemSchema).min(1).max(250)
|
|
808
|
+
}).refine(
|
|
809
|
+
(result) => new Set(result.items.map((item) => item.ref)).size === result.items.length,
|
|
810
|
+
"Duplicate result ref"
|
|
811
|
+
);
|
|
812
|
+
var elenaAgentOperationStateSchema = z4.enum([
|
|
813
|
+
"pending",
|
|
814
|
+
"approved",
|
|
815
|
+
"rejected",
|
|
816
|
+
"expired",
|
|
817
|
+
"applying",
|
|
818
|
+
"applied",
|
|
819
|
+
"partial",
|
|
820
|
+
"failed",
|
|
821
|
+
"cancelled"
|
|
822
|
+
]);
|
|
823
|
+
var elenaAgentOperationSchema = z4.strictObject({
|
|
824
|
+
version: elenaAgentVersionSchema,
|
|
825
|
+
operationId: elenaAgentIdSchema,
|
|
826
|
+
revision: elenaAgentRevisionSchema,
|
|
827
|
+
state: elenaAgentOperationStateSchema,
|
|
828
|
+
restaurantId: elenaAgentIdSchema,
|
|
829
|
+
credentialId: elenaAgentIdSchema,
|
|
830
|
+
credentialRevision: elenaAgentRevisionSchema,
|
|
831
|
+
payload: elenaAgentOperationPayloadSchema,
|
|
832
|
+
payloadHash: elenaAgentHashSchema,
|
|
833
|
+
baseVersions: z4.array(elenaAgentBaseVersionSchema).max(501),
|
|
834
|
+
diff: z4.array(elenaAgentDiffSchema).max(250),
|
|
835
|
+
approvalUrl: elenaAgentConnectionUrlSchema,
|
|
836
|
+
createdAt: elenaAgentTimestampSchema,
|
|
837
|
+
expiresAt: elenaAgentTimestampSchema,
|
|
838
|
+
decision: z4.strictObject({
|
|
839
|
+
outcome: z4.enum(["approved", "rejected"]),
|
|
840
|
+
decidedAt: elenaAgentTimestampSchema,
|
|
841
|
+
approverId: elenaAgentIdSchema,
|
|
842
|
+
payloadHash: elenaAgentHashSchema
|
|
843
|
+
}).nullable(),
|
|
844
|
+
applyDeadline: elenaAgentTimestampSchema.nullable(),
|
|
845
|
+
result: elenaAgentOperationResultSchema.nullable()
|
|
846
|
+
}).superRefine((operation, ctx) => {
|
|
847
|
+
const approvedRefs = operation.payload.kind === "catalog" ? operation.payload.changes.map((change) => change.ref) : operation.payload.kind === "import" ? operation.payload.selectedRefs : operation.payload.products.map((product) => `product:${product.id}`);
|
|
848
|
+
if (operation.diff.some((diff) => !approvedRefs.includes(diff.ref)) || new Set(operation.diff.map((diff) => diff.ref)).size !== operation.diff.length) {
|
|
849
|
+
ctx.addIssue({
|
|
850
|
+
code: "custom",
|
|
851
|
+
path: ["diff"],
|
|
852
|
+
message: "Diff must be a unique subset of the exact approved selection"
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
const payload = operation.payload;
|
|
856
|
+
if (payload.kind === "catalog") {
|
|
857
|
+
const changes = new Map(payload.changes.map((change) => [change.ref, change]));
|
|
858
|
+
operation.diff.forEach((diff, index) => {
|
|
859
|
+
const change = changes.get(diff.ref);
|
|
860
|
+
if (!change || `${diff.kind}.upsert` !== change.kind || !normalizedValuesEqual(diff.after, change)) {
|
|
861
|
+
ctx.addIssue({
|
|
862
|
+
code: "custom",
|
|
863
|
+
path: ["diff", index],
|
|
864
|
+
message: "Catalog diff must match the exact normalized payload change"
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
} else if (payload.kind === "publication") {
|
|
869
|
+
const productIds = new Set(payload.products.map((product) => product.id));
|
|
870
|
+
operation.diff.forEach((diff, index) => {
|
|
871
|
+
if (diff.kind !== "publication" || !productIds.has(diff.productId) || diff.after !== payload.published) {
|
|
872
|
+
ctx.addIssue({
|
|
873
|
+
code: "custom",
|
|
874
|
+
path: ["diff", index],
|
|
875
|
+
message: "Publication diff must match the exact selected product and publication value"
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
const finished = ["applied", "partial", "failed", "cancelled"].includes(operation.state);
|
|
881
|
+
if (finished !== (operation.result !== null)) {
|
|
882
|
+
ctx.addIssue({
|
|
883
|
+
code: "custom",
|
|
884
|
+
path: ["result"],
|
|
885
|
+
message: "Only executed terminal states carry a complete result"
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
if (["approved", "applying", "applied", "partial", "failed"].includes(operation.state) && operation.decision?.outcome !== "approved") {
|
|
889
|
+
ctx.addIssue({
|
|
890
|
+
code: "custom",
|
|
891
|
+
path: ["decision"],
|
|
892
|
+
message: "State requires owner approval"
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
if (operation.state === "rejected" && operation.decision?.outcome !== "rejected") {
|
|
896
|
+
ctx.addIssue({
|
|
897
|
+
code: "custom",
|
|
898
|
+
path: ["decision"],
|
|
899
|
+
message: "Rejected state requires owner rejection"
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
if (operation.state === "pending" && operation.decision !== null) {
|
|
903
|
+
ctx.addIssue({
|
|
904
|
+
code: "custom",
|
|
905
|
+
path: ["decision"],
|
|
906
|
+
message: "Pending operation cannot have a decision"
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
if (operation.decision && operation.decision.payloadHash !== operation.payloadHash) {
|
|
910
|
+
ctx.addIssue({
|
|
911
|
+
code: "custom",
|
|
912
|
+
path: ["decision", "payloadHash"],
|
|
913
|
+
message: "Decision does not bind payload"
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
if (operation.state === "applying" && operation.applyDeadline === null) {
|
|
917
|
+
ctx.addIssue({
|
|
918
|
+
code: "custom",
|
|
919
|
+
path: ["applyDeadline"],
|
|
920
|
+
message: "Applying requires a deadline"
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
if (operation.result) {
|
|
924
|
+
if (operation.result.items.length !== approvedRefs.length || operation.result.items.some((item) => !approvedRefs.includes(item.ref))) {
|
|
925
|
+
ctx.addIssue({
|
|
926
|
+
code: "custom",
|
|
927
|
+
path: ["result"],
|
|
928
|
+
message: "Result must account for the exact approved selection"
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
const success = operation.result.items.some(
|
|
932
|
+
(item) => item.status === "applied" || item.status === "skipped"
|
|
933
|
+
);
|
|
934
|
+
const failure = operation.result.items.some(
|
|
935
|
+
(item) => item.status === "failed" || item.status === "blocked"
|
|
936
|
+
);
|
|
937
|
+
if (operation.state === "applied" && failure || operation.state === "partial" && (!success || !failure) || ["failed", "cancelled"].includes(operation.state) && success) {
|
|
938
|
+
ctx.addIssue({
|
|
939
|
+
code: "custom",
|
|
940
|
+
path: ["result"],
|
|
941
|
+
message: "Terminal state must describe actual effects"
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
var elenaAgentOperationListSchema = z4.strictObject({
|
|
947
|
+
version: elenaAgentVersionSchema,
|
|
948
|
+
operations: z4.array(elenaAgentOperationSchema).max(ELENA_AGENT_LIMITS.pageSize),
|
|
949
|
+
nextCursor: z4.string().regex(/^[a-zA-Z0-9_-]{1,512}$/).nullable()
|
|
950
|
+
});
|
|
951
|
+
var elenaAgentImportSourceSchema = z4.strictObject({
|
|
952
|
+
kind: z4.enum(["image", "pdf", "table", "manual"]),
|
|
953
|
+
label: elenaAgentNameSchema,
|
|
954
|
+
sha256: elenaAgentHashSchema.optional()
|
|
955
|
+
});
|
|
956
|
+
var elenaAgentImportItemSchema = z4.strictObject({
|
|
957
|
+
ref: elenaAgentRefSchema,
|
|
958
|
+
target: elenaAgentTargetSchema,
|
|
959
|
+
data: elenaAgentDraftProductDataSchema
|
|
960
|
+
});
|
|
961
|
+
var elenaAgentImportPreviewRequestSchema = z4.strictObject({
|
|
962
|
+
version: elenaAgentVersionSchema,
|
|
963
|
+
idempotencyKey: elenaAgentRefSchema,
|
|
964
|
+
currency: elenaAgentCurrencySchema,
|
|
965
|
+
source: elenaAgentImportSourceSchema,
|
|
966
|
+
categories: z4.array(elenaAgentCategoryChangeSchema).max(ELENA_AGENT_LIMITS.categoriesPerImport),
|
|
967
|
+
items: z4.array(elenaAgentImportItemSchema).min(1).max(ELENA_AGENT_LIMITS.productsPerImport)
|
|
968
|
+
}).superRefine((request, ctx) => {
|
|
969
|
+
const refs = /* @__PURE__ */ new Set();
|
|
970
|
+
const targets = /* @__PURE__ */ new Set();
|
|
971
|
+
const categoryRefs = new Set(request.categories.map((category) => category.ref));
|
|
972
|
+
const entries = [
|
|
973
|
+
...request.categories.map((entry) => ({ entry, kind: "category" })),
|
|
974
|
+
...request.items.map((entry) => ({ entry, kind: "product" }))
|
|
975
|
+
];
|
|
976
|
+
for (const { entry, kind } of entries) {
|
|
977
|
+
const identity = entry.target.kind === "id" ? `${kind}:id:${entry.target.id}` : JSON.stringify([kind, "external", entry.target.namespace, entry.target.key]);
|
|
978
|
+
if (refs.has(entry.ref) || targets.has(identity)) {
|
|
979
|
+
ctx.addIssue({ code: "custom", message: "Duplicate import ref or target" });
|
|
980
|
+
}
|
|
981
|
+
refs.add(entry.ref);
|
|
982
|
+
targets.add(identity);
|
|
983
|
+
}
|
|
984
|
+
request.items.forEach((item, index) => {
|
|
985
|
+
if (item.data.category.kind === "ref" && !categoryRefs.has(item.data.category.ref)) {
|
|
986
|
+
ctx.addIssue({
|
|
987
|
+
code: "custom",
|
|
988
|
+
path: ["items", index, "data", "category"],
|
|
989
|
+
message: "Unknown category ref"
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
});
|
|
993
|
+
});
|
|
994
|
+
var elenaAgentPreviewItemSchema = z4.discriminatedUnion("classification", [
|
|
995
|
+
z4.strictObject({
|
|
996
|
+
ref: elenaAgentRefSchema,
|
|
997
|
+
classification: z4.literal("create"),
|
|
998
|
+
change: elenaAgentChangeSchema,
|
|
999
|
+
diff: elenaAgentDiffSchema
|
|
1000
|
+
}),
|
|
1001
|
+
z4.strictObject({
|
|
1002
|
+
ref: elenaAgentRefSchema,
|
|
1003
|
+
classification: z4.literal("update"),
|
|
1004
|
+
change: elenaAgentChangeSchema,
|
|
1005
|
+
diff: elenaAgentDiffSchema
|
|
1006
|
+
}),
|
|
1007
|
+
z4.strictObject({
|
|
1008
|
+
ref: elenaAgentRefSchema,
|
|
1009
|
+
classification: z4.literal("skip"),
|
|
1010
|
+
resource: z4.strictObject({
|
|
1011
|
+
kind: z4.enum(["category", "product"]),
|
|
1012
|
+
id: elenaAgentIdSchema,
|
|
1013
|
+
revision: elenaAgentRevisionSchema
|
|
1014
|
+
})
|
|
1015
|
+
}),
|
|
1016
|
+
z4.strictObject({
|
|
1017
|
+
ref: elenaAgentRefSchema,
|
|
1018
|
+
classification: z4.literal("conflict"),
|
|
1019
|
+
issues: z4.array(elenaAgentItemErrorSchema).min(1).max(20)
|
|
1020
|
+
}),
|
|
1021
|
+
z4.strictObject({
|
|
1022
|
+
ref: elenaAgentRefSchema,
|
|
1023
|
+
classification: z4.literal("invalid"),
|
|
1024
|
+
issues: z4.array(elenaAgentItemErrorSchema).min(1).max(20)
|
|
1025
|
+
})
|
|
1026
|
+
]).superRefine((item, ctx) => {
|
|
1027
|
+
if (item.classification !== "create" && item.classification !== "update") return;
|
|
1028
|
+
const { change, diff } = item;
|
|
1029
|
+
if (item.ref !== change.ref || item.ref !== diff.ref) {
|
|
1030
|
+
ctx.addIssue({
|
|
1031
|
+
code: "custom",
|
|
1032
|
+
path: ["ref"],
|
|
1033
|
+
message: "Preview ref does not match change/diff"
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
if (diff.kind === "publication" || `${diff.kind}.upsert` !== change.kind || !normalizedValuesEqual(change, diff.after)) {
|
|
1037
|
+
ctx.addIssue({
|
|
1038
|
+
code: "custom",
|
|
1039
|
+
path: ["diff"],
|
|
1040
|
+
message: "Preview diff must match the exact normalized change and resource kind"
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
if (diff.kind === "publication") return;
|
|
1044
|
+
if (item.classification === "create") {
|
|
1045
|
+
if (change.target.kind !== "external" || change.target.expectedVersion !== null || diff.before !== null) {
|
|
1046
|
+
ctx.addIssue({
|
|
1047
|
+
code: "custom",
|
|
1048
|
+
path: ["diff"],
|
|
1049
|
+
message: "Create requires an absent external target and no previous resource"
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
} else if (change.target.expectedVersion === null || diff.before === null || diff.before.revision !== change.target.expectedVersion || change.target.kind === "id" && diff.before.id !== change.target.id) {
|
|
1053
|
+
ctx.addIssue({
|
|
1054
|
+
code: "custom",
|
|
1055
|
+
path: ["diff"],
|
|
1056
|
+
message: "Update requires the existing target resource with its exact expected revision"
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
var elenaAgentImportPreviewSchema = z4.strictObject({
|
|
1061
|
+
version: elenaAgentVersionSchema,
|
|
1062
|
+
importId: elenaAgentIdSchema,
|
|
1063
|
+
planHash: elenaAgentHashSchema,
|
|
1064
|
+
createdAt: elenaAgentTimestampSchema,
|
|
1065
|
+
expiresAt: elenaAgentTimestampSchema,
|
|
1066
|
+
state: z4.enum(["ready", "expired"]),
|
|
1067
|
+
currency: elenaAgentCurrencySchema,
|
|
1068
|
+
items: z4.array(elenaAgentPreviewItemSchema).min(1).max(250),
|
|
1069
|
+
summary: z4.strictObject({
|
|
1070
|
+
create: z4.number().int().min(0).max(250),
|
|
1071
|
+
update: z4.number().int().min(0).max(250),
|
|
1072
|
+
skip: z4.number().int().min(0).max(250),
|
|
1073
|
+
conflict: z4.number().int().min(0).max(250),
|
|
1074
|
+
invalid: z4.number().int().min(0).max(250)
|
|
1075
|
+
})
|
|
1076
|
+
}).superRefine((preview, ctx) => {
|
|
1077
|
+
if (new Set(preview.items.map((item) => item.ref)).size !== preview.items.length) {
|
|
1078
|
+
ctx.addIssue({ code: "custom", path: ["items"], message: "Duplicate preview ref" });
|
|
1079
|
+
}
|
|
1080
|
+
for (const classification of ["create", "update", "skip", "conflict", "invalid"]) {
|
|
1081
|
+
if (preview.summary[classification] !== preview.items.filter((item) => item.classification === classification).length) {
|
|
1082
|
+
ctx.addIssue({
|
|
1083
|
+
code: "custom",
|
|
1084
|
+
path: ["summary", classification],
|
|
1085
|
+
message: "Summary does not match items"
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
});
|
|
1090
|
+
var elenaAgentCatalogSampleSchema = z4.strictObject({
|
|
1091
|
+
version: elenaAgentVersionSchema,
|
|
1092
|
+
sample: z4.literal(true),
|
|
1093
|
+
disclaimer: z4.string().min(1).max(2e3),
|
|
1094
|
+
source: z4.strictObject({
|
|
1095
|
+
kind: z4.literal("image"),
|
|
1096
|
+
label: z4.string().min(1).max(2e3),
|
|
1097
|
+
originalAvailable: z4.literal(false),
|
|
1098
|
+
ocrAccuracyVerified: z4.literal(false)
|
|
1099
|
+
}),
|
|
1100
|
+
confirmation: z4.strictObject({
|
|
1101
|
+
status: z4.literal("sample_only"),
|
|
1102
|
+
requiresOperatorReview: z4.literal(true),
|
|
1103
|
+
currency: elenaAgentCurrencySchema
|
|
1104
|
+
}),
|
|
1105
|
+
categories: z4.array(z4.strictObject({ ref: elenaAgentRefSchema, name: elenaAgentNameSchema })).min(1).max(50),
|
|
1106
|
+
items: z4.array(
|
|
1107
|
+
z4.strictObject({
|
|
1108
|
+
ref: elenaAgentRefSchema,
|
|
1109
|
+
categoryRef: elenaAgentRefSchema,
|
|
1110
|
+
name: elenaAgentNameSchema,
|
|
1111
|
+
price: z4.discriminatedUnion("status", [
|
|
1112
|
+
elenaAgentDraftPriceSchema.options[0].extend({ basis: z4.literal("sample") }),
|
|
1113
|
+
elenaAgentDraftPriceSchema.options[1]
|
|
1114
|
+
]),
|
|
1115
|
+
description: elenaAgentDescriptionSchema.nullable()
|
|
1116
|
+
})
|
|
1117
|
+
).min(1).max(200),
|
|
1118
|
+
notes: z4.array(z4.string().min(1).max(2e3)).min(1).max(30)
|
|
1119
|
+
}).superRefine((sample, ctx) => {
|
|
1120
|
+
const categoryRefs = new Set(sample.categories.map((category) => category.ref));
|
|
1121
|
+
const refs = [
|
|
1122
|
+
...sample.categories.map((category) => category.ref),
|
|
1123
|
+
...sample.items.map((item) => item.ref)
|
|
1124
|
+
];
|
|
1125
|
+
if (new Set(refs).size !== refs.length)
|
|
1126
|
+
ctx.addIssue({ code: "custom", message: "Duplicate sample ref" });
|
|
1127
|
+
if (sample.items.some((item) => !categoryRefs.has(item.categoryRef))) {
|
|
1128
|
+
ctx.addIssue({ code: "custom", path: ["items"], message: "Unknown sample category" });
|
|
1129
|
+
}
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
// ../shared/src/agent-contracts/public-connection.ts
|
|
1133
|
+
import { z as z5 } from "zod";
|
|
1134
|
+
var ELENA_PUBLIC_CONNECTION_DISCOVERY_ROOT = "/api/v1/public/catalog-connections";
|
|
1135
|
+
var elenaPublicConnectionSlugSchema = z5.string().min(1).max(120).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/);
|
|
1136
|
+
var ttl = z5.number().int().min(3600).max(ELENA_AGENT_LIMITS.keyMaxTtlSeconds).default(ELENA_AGENT_LIMITS.keyDefaultTtlSeconds);
|
|
1137
|
+
var mutation = {
|
|
1138
|
+
version: elenaAgentVersionSchema,
|
|
1139
|
+
// Zero asserts that no connection has ever been created for this restaurant.
|
|
1140
|
+
expectedRevision: z5.number().int().min(0).max(Number.MAX_SAFE_INTEGER - 1)
|
|
1141
|
+
};
|
|
1142
|
+
var elenaPublicConnectionMutationSchema = z5.discriminatedUnion("action", [
|
|
1143
|
+
z5.strictObject({ ...mutation, action: z5.literal("enable"), ttlSeconds: ttl }),
|
|
1144
|
+
z5.strictObject({ ...mutation, action: z5.literal("renew"), ttlSeconds: ttl }),
|
|
1145
|
+
z5.strictObject({ ...mutation, action: z5.literal("disable") }),
|
|
1146
|
+
z5.strictObject({ ...mutation, action: z5.literal("revoke") })
|
|
1147
|
+
]);
|
|
1148
|
+
var elenaPublicConnectionMetadataSchema = z5.strictObject({
|
|
1149
|
+
revision: elenaAgentRevisionSchema,
|
|
1150
|
+
status: z5.enum(["published", "disabled", "revoked"]),
|
|
1151
|
+
expiresAt: elenaAgentTimestampSchema
|
|
1152
|
+
});
|
|
1153
|
+
var elenaPublicConnectionManagementSchema = z5.strictObject({
|
|
1154
|
+
version: elenaAgentVersionSchema,
|
|
1155
|
+
publication: elenaPublicConnectionMetadataSchema.nullable()
|
|
1156
|
+
});
|
|
1157
|
+
var elenaPublicConnectionSchema = z5.strictObject({
|
|
1158
|
+
version: elenaAgentVersionSchema,
|
|
1159
|
+
apiOrigin: elenaAgentBaseUrlSchema,
|
|
1160
|
+
clientKey: elenaManagedClientKeySchema,
|
|
1161
|
+
publication: z5.strictObject({
|
|
1162
|
+
revision: elenaAgentRevisionSchema,
|
|
1163
|
+
expiresAt: elenaAgentTimestampSchema
|
|
1164
|
+
}),
|
|
1165
|
+
context: elenaClientContextSchema
|
|
1166
|
+
});
|
|
1167
|
+
|
|
1168
|
+
// src/key-transport.ts
|
|
1169
|
+
var ElenaAgentTransportError = class extends Error {
|
|
1170
|
+
constructor(code, status = 0, retryable = false, requestId = null, retryAfterSeconds) {
|
|
1171
|
+
super(`Elena request failed (${code}).`);
|
|
1172
|
+
this.code = code;
|
|
1173
|
+
this.status = status;
|
|
1174
|
+
this.retryable = retryable;
|
|
1175
|
+
this.requestId = requestId;
|
|
1176
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
1177
|
+
}
|
|
1178
|
+
code;
|
|
1179
|
+
status;
|
|
1180
|
+
retryable;
|
|
1181
|
+
requestId;
|
|
1182
|
+
retryAfterSeconds;
|
|
1183
|
+
name = "ElenaAgentTransportError";
|
|
1184
|
+
};
|
|
1185
|
+
function parseWire(schema, value, response = false) {
|
|
1186
|
+
let unsupportedVersion = false;
|
|
1187
|
+
try {
|
|
1188
|
+
if (value !== null && typeof value === "object" && "version" in value) {
|
|
1189
|
+
unsupportedVersion = value.version !== void 0 && value.version !== "1";
|
|
1190
|
+
}
|
|
1191
|
+
const parsed = schema.safeParse(value);
|
|
1192
|
+
if (parsed.success) return parsed.data;
|
|
1193
|
+
} catch {
|
|
1194
|
+
}
|
|
1195
|
+
throw new ElenaAgentTransportError(
|
|
1196
|
+
unsupportedVersion ? "UNSUPPORTED_VERSION" : response ? "INVALID_RESPONSE" : "INVALID_REQUEST"
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
function assertOptionKeys(value, keys) {
|
|
1200
|
+
if (!value || typeof value !== "object" || Object.keys(value).some((key) => !keys.includes(key))) {
|
|
1201
|
+
throw new ElenaAgentTransportError("INVALID_REQUEST");
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
function explicitOrigin(value, allowTestHttp = false) {
|
|
1205
|
+
try {
|
|
1206
|
+
if (typeof value !== "string" || !/^https?:\/\/[^/?#@\\\s]+\/?$/.test(value)) throw 0;
|
|
1207
|
+
if (/elena_(?:sk|pk)_/i.test(value)) throw 0;
|
|
1208
|
+
const url = new URL(value);
|
|
1209
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
1210
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && (typeof process !== "undefined" && process.env.NODE_ENV === "development" || allowTestHttp && loopback)))
|
|
1211
|
+
throw 0;
|
|
1212
|
+
if (url.username || url.password || url.search || url.hash || url.pathname !== "/") throw 0;
|
|
1213
|
+
return url.origin;
|
|
1214
|
+
} catch {
|
|
1215
|
+
throw new ElenaAgentTransportError("ORIGIN_REJECTED");
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
function resourceId(value) {
|
|
1219
|
+
return parseWire(elenaAgentIdSchema, value);
|
|
1220
|
+
}
|
|
1221
|
+
function pageQuery(value = { version: "1" }) {
|
|
1222
|
+
const query = parseWire(elenaAgentPageQuerySchema, value);
|
|
1223
|
+
const params = new URLSearchParams({ version: query.version, limit: String(query.limit) });
|
|
1224
|
+
if (query.cursor !== void 0) params.set("cursor", query.cursor);
|
|
1225
|
+
return `?${params}`;
|
|
1226
|
+
}
|
|
1227
|
+
function jsonBody(schema, value) {
|
|
1228
|
+
const text = JSON.stringify(parseWire(schema, value));
|
|
1229
|
+
if (new TextEncoder().encode(text).byteLength > ELENA_AGENT_LIMITS.jsonBytes) {
|
|
1230
|
+
throw new ElenaAgentTransportError("INVALID_REQUEST");
|
|
1231
|
+
}
|
|
1232
|
+
return text;
|
|
1233
|
+
}
|
|
1234
|
+
function timeoutValue(value) {
|
|
1235
|
+
if (!Number.isInteger(value) || value < 1 || value > 3e5) {
|
|
1236
|
+
throw new ElenaAgentTransportError("INVALID_REQUEST");
|
|
1237
|
+
}
|
|
1238
|
+
return value;
|
|
1239
|
+
}
|
|
1240
|
+
var privateCredential = /elena_sk_[A-Za-z0-9_-]{43}/;
|
|
1241
|
+
var retryCodes = {
|
|
1242
|
+
RATE_LIMITED: true,
|
|
1243
|
+
TEMPORARY_UNAVAILABLE: true
|
|
1244
|
+
};
|
|
1245
|
+
function createKeyTransport(options, header, credential) {
|
|
1246
|
+
if (options.testing !== void 0) {
|
|
1247
|
+
assertOptionKeys(options.testing, ["allowHttp"]);
|
|
1248
|
+
if (options.testing.allowHttp !== true || !options.fetch) {
|
|
1249
|
+
throw new ElenaAgentTransportError("INVALID_REQUEST");
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
const baseUrl = explicitOrigin(options.baseUrl, options.testing?.allowHttp === true);
|
|
1253
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1254
|
+
if (typeof fetchImpl !== "function") throw new ElenaAgentTransportError("INVALID_REQUEST");
|
|
1255
|
+
const defaultTimeout = timeoutValue(options.timeoutMs ?? 3e4);
|
|
1256
|
+
const retries = options.maxRetries ?? 2;
|
|
1257
|
+
if (!Number.isInteger(retries) || retries < 0 || retries > 2) {
|
|
1258
|
+
throw new ElenaAgentTransportError("INVALID_REQUEST");
|
|
1259
|
+
}
|
|
1260
|
+
async function request(path, schema, requestOptions = {}, body, stage = false, retryAmbiguous = true) {
|
|
1261
|
+
assertOptionKeys(requestOptions, ["signal", "timeoutMs"]);
|
|
1262
|
+
if (privateCredential.test(path) || credential && path.includes(credential) || typeof body === "string" && (privateCredential.test(body) || credential && body.includes(credential))) {
|
|
1263
|
+
throw new ElenaAgentTransportError("INVALID_REQUEST");
|
|
1264
|
+
}
|
|
1265
|
+
if (requestOptions.signal !== void 0 && !(requestOptions.signal instanceof AbortSignal)) {
|
|
1266
|
+
throw new ElenaAgentTransportError("INVALID_REQUEST");
|
|
1267
|
+
}
|
|
1268
|
+
const timeoutMs = timeoutValue(requestOptions.timeoutMs ?? (stage ? 12e4 : defaultTimeout));
|
|
1269
|
+
if (requestOptions.signal?.aborted) throw new ElenaAgentTransportError("ABORTED");
|
|
1270
|
+
const controller = new AbortController();
|
|
1271
|
+
let timedOut = false;
|
|
1272
|
+
const timer = setTimeout(() => {
|
|
1273
|
+
timedOut = true;
|
|
1274
|
+
controller.abort();
|
|
1275
|
+
}, timeoutMs);
|
|
1276
|
+
const onAbort = () => controller.abort();
|
|
1277
|
+
requestOptions.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1278
|
+
const abortError = () => new ElenaAgentTransportError(timedOut ? "TIMEOUT" : "ABORTED");
|
|
1279
|
+
const deadline = Date.now() + timeoutMs;
|
|
1280
|
+
const bounded = (promise) => new Promise((resolve, reject) => {
|
|
1281
|
+
const abort = () => {
|
|
1282
|
+
cleanup();
|
|
1283
|
+
reject(abortError());
|
|
1284
|
+
};
|
|
1285
|
+
const cleanup = () => controller.signal.removeEventListener("abort", abort);
|
|
1286
|
+
if (controller.signal.aborted) {
|
|
1287
|
+
reject(abortError());
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
controller.signal.addEventListener("abort", abort, { once: true });
|
|
1291
|
+
promise.then(
|
|
1292
|
+
(value) => {
|
|
1293
|
+
cleanup();
|
|
1294
|
+
resolve(value);
|
|
1295
|
+
},
|
|
1296
|
+
(error) => {
|
|
1297
|
+
cleanup();
|
|
1298
|
+
reject(error);
|
|
1299
|
+
}
|
|
1300
|
+
);
|
|
1301
|
+
});
|
|
1302
|
+
const delay = (ms) => new Promise((resolve, reject) => {
|
|
1303
|
+
const abort = () => {
|
|
1304
|
+
clearTimeout(wait);
|
|
1305
|
+
controller.signal.removeEventListener("abort", abort);
|
|
1306
|
+
reject(abortError());
|
|
1307
|
+
};
|
|
1308
|
+
const wait = setTimeout(() => {
|
|
1309
|
+
controller.signal.removeEventListener("abort", abort);
|
|
1310
|
+
resolve();
|
|
1311
|
+
}, ms);
|
|
1312
|
+
controller.signal.addEventListener("abort", abort, { once: true });
|
|
1313
|
+
if (controller.signal.aborted) abort();
|
|
1314
|
+
});
|
|
1315
|
+
try {
|
|
1316
|
+
for (let attempt = 0; ; attempt++) {
|
|
1317
|
+
let failure;
|
|
1318
|
+
let retryDelay = 100 * 2 ** attempt;
|
|
1319
|
+
try {
|
|
1320
|
+
const headers = new Headers({ Accept: "application/json" });
|
|
1321
|
+
if (header) headers.set(header, credential);
|
|
1322
|
+
if (typeof body === "string") headers.set("Content-Type", "application/json");
|
|
1323
|
+
const response = await bounded(
|
|
1324
|
+
Promise.resolve().then(() => {
|
|
1325
|
+
if (controller.signal.aborted) throw abortError();
|
|
1326
|
+
return fetchImpl(`${baseUrl}${path}`, {
|
|
1327
|
+
method: body === void 0 ? "GET" : "POST",
|
|
1328
|
+
headers,
|
|
1329
|
+
body: typeof body === "function" ? body() : body,
|
|
1330
|
+
credentials: "omit",
|
|
1331
|
+
redirect: "error",
|
|
1332
|
+
referrerPolicy: "no-referrer",
|
|
1333
|
+
cache: "no-store",
|
|
1334
|
+
signal: controller.signal
|
|
1335
|
+
});
|
|
1336
|
+
}).catch(() => {
|
|
1337
|
+
throw new ElenaAgentTransportError("NETWORK_ERROR", 0, retryAmbiguous);
|
|
1338
|
+
})
|
|
1339
|
+
);
|
|
1340
|
+
if (response.redirected || response.status >= 300 && response.status < 400) {
|
|
1341
|
+
throw new ElenaAgentTransportError("REDIRECT_REJECTED", response.status);
|
|
1342
|
+
}
|
|
1343
|
+
if (response.url && new URL(response.url).origin !== baseUrl) {
|
|
1344
|
+
throw new ElenaAgentTransportError("ORIGIN_REJECTED");
|
|
1345
|
+
}
|
|
1346
|
+
const text = await bounded(
|
|
1347
|
+
Promise.resolve().then(() => response.text()).catch(() => {
|
|
1348
|
+
throw new ElenaAgentTransportError("NETWORK_ERROR", 0, retryAmbiguous);
|
|
1349
|
+
})
|
|
1350
|
+
);
|
|
1351
|
+
if (privateCredential.test(text) || credential && text.includes(credential)) {
|
|
1352
|
+
throw new ElenaAgentTransportError("INVALID_RESPONSE", response.status);
|
|
1353
|
+
}
|
|
1354
|
+
let payload;
|
|
1355
|
+
try {
|
|
1356
|
+
payload = JSON.parse(text);
|
|
1357
|
+
} catch {
|
|
1358
|
+
throw new ElenaAgentTransportError("INVALID_RESPONSE", response.status);
|
|
1359
|
+
}
|
|
1360
|
+
const decodedText = JSON.stringify(payload);
|
|
1361
|
+
if (privateCredential.test(decodedText) || credential && decodedText.includes(credential)) {
|
|
1362
|
+
throw new ElenaAgentTransportError("INVALID_RESPONSE", response.status);
|
|
1363
|
+
}
|
|
1364
|
+
if (response.ok) return parseWire(schema, payload, true);
|
|
1365
|
+
const wire = parseWire(elenaAgentErrorSchema, payload, true).error;
|
|
1366
|
+
const retryable = retryCodes[wire.code] === true && wire.retryable && (wire.code === "RATE_LIMITED" && response.status === 429 || wire.code === "TEMPORARY_UNAVAILABLE" && response.status === 503);
|
|
1367
|
+
failure = new ElenaAgentTransportError(
|
|
1368
|
+
wire.code,
|
|
1369
|
+
response.status,
|
|
1370
|
+
retryable,
|
|
1371
|
+
wire.requestId,
|
|
1372
|
+
wire.retryAfterSeconds
|
|
1373
|
+
);
|
|
1374
|
+
const retryAfter = response.headers.get("retry-after");
|
|
1375
|
+
let headerDelay = 0;
|
|
1376
|
+
if (retryAfter !== null) {
|
|
1377
|
+
const seconds = /^\d+$/.test(retryAfter) ? Number(retryAfter) : (Date.parse(retryAfter) - Date.now()) / 1e3;
|
|
1378
|
+
if (!Number.isFinite(seconds) || seconds < 0 || seconds > 3600) {
|
|
1379
|
+
throw new ElenaAgentTransportError(
|
|
1380
|
+
wire.code,
|
|
1381
|
+
response.status,
|
|
1382
|
+
false,
|
|
1383
|
+
wire.requestId,
|
|
1384
|
+
wire.retryAfterSeconds
|
|
1385
|
+
);
|
|
1386
|
+
}
|
|
1387
|
+
headerDelay = Math.ceil(seconds * 1e3);
|
|
1388
|
+
}
|
|
1389
|
+
retryDelay = Math.max(retryDelay, headerDelay, (wire.retryAfterSeconds ?? 0) * 1e3);
|
|
1390
|
+
} catch (error) {
|
|
1391
|
+
if (controller.signal.aborted) throw abortError();
|
|
1392
|
+
failure = error instanceof ElenaAgentTransportError ? error : new ElenaAgentTransportError("NETWORK_ERROR", 0, retryAmbiguous);
|
|
1393
|
+
}
|
|
1394
|
+
if (!failure.retryable || attempt >= retries || Date.now() + retryDelay >= deadline)
|
|
1395
|
+
throw failure;
|
|
1396
|
+
await delay(retryDelay);
|
|
1397
|
+
}
|
|
1398
|
+
} finally {
|
|
1399
|
+
clearTimeout(timer);
|
|
1400
|
+
requestOptions.signal?.removeEventListener("abort", onAbort);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
return { request };
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// src/agent/index.ts
|
|
1407
|
+
function assertServerRuntime() {
|
|
1408
|
+
if (typeof window !== "undefined" || typeof document !== "undefined" || typeof process === "undefined" || typeof process.versions?.node !== "string") {
|
|
1409
|
+
throw new Error("Elena agent client is server-only.");
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
assertServerRuntime();
|
|
1413
|
+
function createElenaAgentClient(options) {
|
|
1414
|
+
assertServerRuntime();
|
|
1415
|
+
assertOptionKeys(options, [
|
|
1416
|
+
"baseUrl",
|
|
1417
|
+
"agentKey",
|
|
1418
|
+
"staffOrigin",
|
|
1419
|
+
"fetch",
|
|
1420
|
+
"timeoutMs",
|
|
1421
|
+
"maxRetries",
|
|
1422
|
+
"testing"
|
|
1423
|
+
]);
|
|
1424
|
+
if (!elenaAgentSecretSchema.safeParse(options.agentKey).success) {
|
|
1425
|
+
throw new ElenaAgentTransportError("CREDENTIAL_WRONG_FAMILY");
|
|
1426
|
+
}
|
|
1427
|
+
const { request } = createKeyTransport(options, "x-elena-agent-key", options.agentKey);
|
|
1428
|
+
const staffOrigin = options.staffOrigin === void 0 ? void 0 : explicitOrigin(options.staffOrigin);
|
|
1429
|
+
const root = "/api/v1/agent";
|
|
1430
|
+
function requireStaffOrigin() {
|
|
1431
|
+
if (!staffOrigin) throw new ElenaAgentTransportError("ORIGIN_REJECTED");
|
|
1432
|
+
}
|
|
1433
|
+
function operationResponse(operation, id, hash) {
|
|
1434
|
+
requireStaffOrigin();
|
|
1435
|
+
const approval = new URL(operation.approvalUrl);
|
|
1436
|
+
if (approval.origin !== staffOrigin || approval.pathname !== `/agent-operations/${operation.operationId}` || approval.search || approval.hash || id !== void 0 && operation.operationId !== id || hash !== void 0 && operation.payloadHash !== hash) {
|
|
1437
|
+
throw new ElenaAgentTransportError("INVALID_RESPONSE");
|
|
1438
|
+
}
|
|
1439
|
+
return operation;
|
|
1440
|
+
}
|
|
1441
|
+
async function getOperation(id, requestOptions) {
|
|
1442
|
+
requireStaffOrigin();
|
|
1443
|
+
const expectedId = resourceId(id);
|
|
1444
|
+
return operationResponse(
|
|
1445
|
+
await request(
|
|
1446
|
+
`${root}/operations/${expectedId}`,
|
|
1447
|
+
elenaAgentOperationSchema,
|
|
1448
|
+
requestOptions
|
|
1449
|
+
),
|
|
1450
|
+
expectedId
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
return Object.freeze({
|
|
1454
|
+
context: Object.freeze({
|
|
1455
|
+
get: (options2) => request(`${root}/context`, elenaAgentContextSchema, options2)
|
|
1456
|
+
}),
|
|
1457
|
+
catalog: Object.freeze({
|
|
1458
|
+
categories: Object.freeze({
|
|
1459
|
+
list: (query, options2) => request(
|
|
1460
|
+
`${root}/catalog/categories${pageQuery(query)}`,
|
|
1461
|
+
elenaAgentCategoryListSchema,
|
|
1462
|
+
options2
|
|
1463
|
+
),
|
|
1464
|
+
get: async (id, options2) => {
|
|
1465
|
+
const expectedId = resourceId(id);
|
|
1466
|
+
const result = await request(
|
|
1467
|
+
`${root}/catalog/categories/${expectedId}`,
|
|
1468
|
+
elenaAgentCategoryResponseSchema,
|
|
1469
|
+
options2
|
|
1470
|
+
);
|
|
1471
|
+
if (result.category.id !== expectedId)
|
|
1472
|
+
throw new ElenaAgentTransportError("INVALID_RESPONSE");
|
|
1473
|
+
return result;
|
|
1474
|
+
}
|
|
1475
|
+
}),
|
|
1476
|
+
products: Object.freeze({
|
|
1477
|
+
list: (query, options2) => request(
|
|
1478
|
+
`${root}/catalog/products${pageQuery(query)}`,
|
|
1479
|
+
elenaAgentProductListSchema,
|
|
1480
|
+
options2
|
|
1481
|
+
),
|
|
1482
|
+
get: async (id, options2) => {
|
|
1483
|
+
const expectedId = resourceId(id);
|
|
1484
|
+
const result = await request(
|
|
1485
|
+
`${root}/catalog/products/${expectedId}`,
|
|
1486
|
+
elenaAgentProductResponseSchema,
|
|
1487
|
+
options2
|
|
1488
|
+
);
|
|
1489
|
+
if (result.product.id !== expectedId)
|
|
1490
|
+
throw new ElenaAgentTransportError("INVALID_RESPONSE");
|
|
1491
|
+
return result;
|
|
1492
|
+
}
|
|
1493
|
+
})
|
|
1494
|
+
}),
|
|
1495
|
+
assets: Object.freeze({
|
|
1496
|
+
get: async (id, options2) => {
|
|
1497
|
+
const expectedId = resourceId(id);
|
|
1498
|
+
const result = await request(
|
|
1499
|
+
`${root}/assets/${expectedId}`,
|
|
1500
|
+
elenaAgentAssetSchema,
|
|
1501
|
+
options2
|
|
1502
|
+
);
|
|
1503
|
+
if (result.assetId !== expectedId) throw new ElenaAgentTransportError("INVALID_RESPONSE");
|
|
1504
|
+
return result;
|
|
1505
|
+
}
|
|
1506
|
+
}),
|
|
1507
|
+
media: Object.freeze({
|
|
1508
|
+
stage: async (input, file, options2) => {
|
|
1509
|
+
const metadata = parseWire(elenaAgentMediaStageRequestSchema, input);
|
|
1510
|
+
const requestOptions = options2 ?? {};
|
|
1511
|
+
assertOptionKeys(requestOptions, ["signal", "timeoutMs"]);
|
|
1512
|
+
const timeoutMs = requestOptions.timeoutMs ?? 12e4;
|
|
1513
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 3e5 || requestOptions.signal !== void 0 && !(requestOptions.signal instanceof AbortSignal)) {
|
|
1514
|
+
throw new ElenaAgentTransportError("INVALID_REQUEST");
|
|
1515
|
+
}
|
|
1516
|
+
if (requestOptions.signal?.aborted) throw new ElenaAgentTransportError("ABORTED");
|
|
1517
|
+
const deadline = Date.now() + timeoutMs;
|
|
1518
|
+
let readTimer;
|
|
1519
|
+
let readTimedOut = false;
|
|
1520
|
+
let onAbort;
|
|
1521
|
+
let snapshot;
|
|
1522
|
+
let hash;
|
|
1523
|
+
try {
|
|
1524
|
+
if (!(file instanceof Blob) || file.size !== metadata.bytes || file.type !== metadata.mimeType) {
|
|
1525
|
+
throw new Error("Invalid media");
|
|
1526
|
+
}
|
|
1527
|
+
const boundedFile = Blob.prototype.slice.call(file, 0, metadata.bytes, metadata.mimeType);
|
|
1528
|
+
const reading = Blob.prototype.arrayBuffer.call(boundedFile);
|
|
1529
|
+
const interrupted = new Promise((_, reject) => {
|
|
1530
|
+
readTimer = setTimeout(() => {
|
|
1531
|
+
readTimedOut = true;
|
|
1532
|
+
reject(new Error("Read timeout"));
|
|
1533
|
+
}, timeoutMs);
|
|
1534
|
+
onAbort = () => reject(new Error("Read aborted"));
|
|
1535
|
+
requestOptions.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1536
|
+
if (requestOptions.signal?.aborted) onAbort();
|
|
1537
|
+
});
|
|
1538
|
+
const bytes = await Promise.race([reading, interrupted]);
|
|
1539
|
+
if (bytes.byteLength !== metadata.bytes) throw new Error("Invalid media");
|
|
1540
|
+
hash = createHash("sha256").update(new Uint8Array(bytes)).digest("hex");
|
|
1541
|
+
if (hash !== metadata.sha256) throw new Error("Invalid media");
|
|
1542
|
+
snapshot = new Blob([bytes], { type: metadata.mimeType });
|
|
1543
|
+
} catch {
|
|
1544
|
+
throw new ElenaAgentTransportError(
|
|
1545
|
+
requestOptions.signal?.aborted ? "ABORTED" : readTimedOut || Date.now() >= deadline ? "TIMEOUT" : "MEDIA_INVALID"
|
|
1546
|
+
);
|
|
1547
|
+
} finally {
|
|
1548
|
+
clearTimeout(readTimer);
|
|
1549
|
+
if (onAbort) requestOptions.signal?.removeEventListener("abort", onAbort);
|
|
1550
|
+
}
|
|
1551
|
+
const remainingMs = deadline - Date.now();
|
|
1552
|
+
if (remainingMs <= 0) throw new ElenaAgentTransportError("TIMEOUT");
|
|
1553
|
+
const result = await request(
|
|
1554
|
+
`${root}/media/stage`,
|
|
1555
|
+
elenaAgentMediaStagedSchema,
|
|
1556
|
+
{ ...requestOptions, timeoutMs: remainingMs },
|
|
1557
|
+
() => {
|
|
1558
|
+
const form = new FormData();
|
|
1559
|
+
form.set("metadata", JSON.stringify(metadata));
|
|
1560
|
+
form.set("file", snapshot, metadata.fileName);
|
|
1561
|
+
return form;
|
|
1562
|
+
},
|
|
1563
|
+
true
|
|
1564
|
+
);
|
|
1565
|
+
if (result.sha256 !== hash || result.bytes !== metadata.bytes || result.mimeType !== metadata.mimeType) {
|
|
1566
|
+
throw new ElenaAgentTransportError("INVALID_RESPONSE");
|
|
1567
|
+
}
|
|
1568
|
+
return result;
|
|
1569
|
+
},
|
|
1570
|
+
get: async (id, options2) => {
|
|
1571
|
+
const expectedId = resourceId(id);
|
|
1572
|
+
const result = await request(
|
|
1573
|
+
`${root}/media/staged/${expectedId}`,
|
|
1574
|
+
elenaAgentMediaStagedSchema,
|
|
1575
|
+
options2
|
|
1576
|
+
);
|
|
1577
|
+
if (result.stagingId !== expectedId) throw new ElenaAgentTransportError("INVALID_RESPONSE");
|
|
1578
|
+
return result;
|
|
1579
|
+
}
|
|
1580
|
+
}),
|
|
1581
|
+
imports: Object.freeze({
|
|
1582
|
+
preview: async (input, options2) => {
|
|
1583
|
+
const normalized = parseWire(elenaAgentImportPreviewRequestSchema, input);
|
|
1584
|
+
const refs = new Set(
|
|
1585
|
+
[...normalized.categories, ...normalized.items].map((item) => item.ref)
|
|
1586
|
+
);
|
|
1587
|
+
const result = await request(
|
|
1588
|
+
`${root}/imports/preview`,
|
|
1589
|
+
elenaAgentImportPreviewSchema,
|
|
1590
|
+
options2,
|
|
1591
|
+
jsonBody(elenaAgentImportPreviewRequestSchema, normalized)
|
|
1592
|
+
);
|
|
1593
|
+
if (result.items.length !== refs.size || result.items.some((item) => !refs.has(item.ref))) {
|
|
1594
|
+
throw new ElenaAgentTransportError("INVALID_RESPONSE");
|
|
1595
|
+
}
|
|
1596
|
+
return result;
|
|
1597
|
+
},
|
|
1598
|
+
get: async (id, options2) => {
|
|
1599
|
+
const expectedId = resourceId(id);
|
|
1600
|
+
const result = await request(
|
|
1601
|
+
`${root}/imports/${expectedId}`,
|
|
1602
|
+
elenaAgentImportPreviewSchema,
|
|
1603
|
+
options2
|
|
1604
|
+
);
|
|
1605
|
+
if (result.importId !== expectedId) throw new ElenaAgentTransportError("INVALID_RESPONSE");
|
|
1606
|
+
return result;
|
|
1607
|
+
}
|
|
1608
|
+
}),
|
|
1609
|
+
operations: Object.freeze({
|
|
1610
|
+
propose: async (input, options2) => {
|
|
1611
|
+
requireStaffOrigin();
|
|
1612
|
+
return operationResponse(
|
|
1613
|
+
await request(
|
|
1614
|
+
`${root}/operations`,
|
|
1615
|
+
elenaAgentOperationSchema,
|
|
1616
|
+
options2,
|
|
1617
|
+
jsonBody(elenaAgentProposalRequestSchema, input)
|
|
1618
|
+
)
|
|
1619
|
+
);
|
|
1620
|
+
},
|
|
1621
|
+
list: async (query, options2) => {
|
|
1622
|
+
requireStaffOrigin();
|
|
1623
|
+
const result = await request(
|
|
1624
|
+
`${root}/operations${pageQuery(query)}`,
|
|
1625
|
+
elenaAgentOperationListSchema,
|
|
1626
|
+
options2
|
|
1627
|
+
);
|
|
1628
|
+
result.operations.forEach((operation) => operationResponse(operation));
|
|
1629
|
+
return result;
|
|
1630
|
+
},
|
|
1631
|
+
get: getOperation,
|
|
1632
|
+
apply: async (id, input, options2) => {
|
|
1633
|
+
requireStaffOrigin();
|
|
1634
|
+
const expectedId = resourceId(id);
|
|
1635
|
+
const normalized = parseWire(elenaAgentApplyRequestSchema, input);
|
|
1636
|
+
const body = jsonBody(elenaAgentApplyRequestSchema, normalized);
|
|
1637
|
+
const { payloadHash } = normalized;
|
|
1638
|
+
if (options2?.signal?.aborted) throw new ElenaAgentTransportError("ABORTED");
|
|
1639
|
+
try {
|
|
1640
|
+
return operationResponse(
|
|
1641
|
+
await request(
|
|
1642
|
+
`${root}/operations/${expectedId}/apply`,
|
|
1643
|
+
elenaAgentOperationSchema,
|
|
1644
|
+
options2,
|
|
1645
|
+
body,
|
|
1646
|
+
false,
|
|
1647
|
+
false
|
|
1648
|
+
),
|
|
1649
|
+
expectedId,
|
|
1650
|
+
payloadHash
|
|
1651
|
+
);
|
|
1652
|
+
} catch (error) {
|
|
1653
|
+
if (!(error instanceof ElenaAgentTransportError) || !["NETWORK_ERROR", "TIMEOUT", "ABORTED"].includes(error.code))
|
|
1654
|
+
throw error;
|
|
1655
|
+
try {
|
|
1656
|
+
const current = operationResponse(
|
|
1657
|
+
await getOperation(expectedId, { timeoutMs: 3e4 }),
|
|
1658
|
+
expectedId,
|
|
1659
|
+
payloadHash
|
|
1660
|
+
);
|
|
1661
|
+
if (["applying", "applied", "partial", "failed", "cancelled"].includes(current.state))
|
|
1662
|
+
return current;
|
|
1663
|
+
} catch {
|
|
1664
|
+
}
|
|
1665
|
+
throw error;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
})
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
export {
|
|
1672
|
+
ELENA_AGENT_CONTRACT_VERSION,
|
|
1673
|
+
ELENA_AGENT_LIMITS,
|
|
1674
|
+
ELENA_AGENT_TRANSPORT,
|
|
1675
|
+
ELENA_PUBLIC_CONNECTION_DISCOVERY_ROOT,
|
|
1676
|
+
ElenaAgentTransportError,
|
|
1677
|
+
createElenaAgentClient,
|
|
1678
|
+
elenaAgentAckSchema,
|
|
1679
|
+
elenaAgentActionSchema,
|
|
1680
|
+
elenaAgentApplyRequestSchema,
|
|
1681
|
+
elenaAgentApprovalRequestSchema,
|
|
1682
|
+
elenaAgentAssetSchema,
|
|
1683
|
+
elenaAgentBaseUrlSchema,
|
|
1684
|
+
elenaAgentBaseVersionSchema,
|
|
1685
|
+
elenaAgentCatalogSampleSchema,
|
|
1686
|
+
elenaAgentCategoryChangeSchema,
|
|
1687
|
+
elenaAgentCategoryDataSchema,
|
|
1688
|
+
elenaAgentCategoryListSchema,
|
|
1689
|
+
elenaAgentCategoryReferenceSchema,
|
|
1690
|
+
elenaAgentCategoryResponseSchema,
|
|
1691
|
+
elenaAgentCategorySchema,
|
|
1692
|
+
elenaAgentChangeSchema,
|
|
1693
|
+
elenaAgentChangesSchema,
|
|
1694
|
+
elenaAgentCliAuthStatusSchema,
|
|
1695
|
+
elenaAgentCliLogoutSchema,
|
|
1696
|
+
elenaAgentConfirmedPriceSchema,
|
|
1697
|
+
elenaAgentConnectionOriginSchema,
|
|
1698
|
+
elenaAgentConnectionUrlSchema,
|
|
1699
|
+
elenaAgentContextSchema,
|
|
1700
|
+
elenaAgentCurrencySchema,
|
|
1701
|
+
elenaAgentDescriptionSchema,
|
|
1702
|
+
elenaAgentDeviceCodeSchema,
|
|
1703
|
+
elenaAgentDiffSchema,
|
|
1704
|
+
elenaAgentDraftPriceSchema,
|
|
1705
|
+
elenaAgentDraftProductDataSchema,
|
|
1706
|
+
elenaAgentEmptyRequestSchema,
|
|
1707
|
+
elenaAgentErrorCodeSchema,
|
|
1708
|
+
elenaAgentErrorSchema,
|
|
1709
|
+
elenaAgentExistingTargetSchema,
|
|
1710
|
+
elenaAgentExternalTargetSchema,
|
|
1711
|
+
elenaAgentExtraGroupSchema,
|
|
1712
|
+
elenaAgentExtraOptionSchema,
|
|
1713
|
+
elenaAgentExtrasSchema,
|
|
1714
|
+
elenaAgentGallerySchema,
|
|
1715
|
+
elenaAgentHashSchema,
|
|
1716
|
+
elenaAgentHttpsUrlSchema,
|
|
1717
|
+
elenaAgentIdSchema,
|
|
1718
|
+
elenaAgentImportItemSchema,
|
|
1719
|
+
elenaAgentImportPreviewRequestSchema,
|
|
1720
|
+
elenaAgentImportPreviewSchema,
|
|
1721
|
+
elenaAgentImportSourceSchema,
|
|
1722
|
+
elenaAgentItemErrorSchema,
|
|
1723
|
+
elenaAgentKeyCreateRequestSchema,
|
|
1724
|
+
elenaAgentKeyListSchema,
|
|
1725
|
+
elenaAgentKeyMetadataSchema,
|
|
1726
|
+
elenaAgentKeyPolicyRequestSchema,
|
|
1727
|
+
elenaAgentKeyReceiptSchema,
|
|
1728
|
+
elenaAgentKeyReplaceRequestSchema,
|
|
1729
|
+
elenaAgentKeyResponseSchema,
|
|
1730
|
+
elenaAgentKeyRevokeRequestSchema,
|
|
1731
|
+
elenaAgentMediaReferenceSchema,
|
|
1732
|
+
elenaAgentMediaStageRequestSchema,
|
|
1733
|
+
elenaAgentMediaStagedSchema,
|
|
1734
|
+
elenaAgentModeSchema,
|
|
1735
|
+
elenaAgentMoneySchema,
|
|
1736
|
+
elenaAgentNameSchema,
|
|
1737
|
+
elenaAgentNonnegativeMoneySchema,
|
|
1738
|
+
elenaAgentOperationListSchema,
|
|
1739
|
+
elenaAgentOperationPayloadSchema,
|
|
1740
|
+
elenaAgentOperationResultSchema,
|
|
1741
|
+
elenaAgentOperationSchema,
|
|
1742
|
+
elenaAgentOperationStateSchema,
|
|
1743
|
+
elenaAgentPageQuerySchema,
|
|
1744
|
+
elenaAgentPairingDecisionRequestSchema,
|
|
1745
|
+
elenaAgentPairingPollRequestSchema,
|
|
1746
|
+
elenaAgentPairingPollResponseSchema,
|
|
1747
|
+
elenaAgentPairingReviewSchema,
|
|
1748
|
+
elenaAgentPairingStartRequestSchema,
|
|
1749
|
+
elenaAgentPairingStartResponseSchema,
|
|
1750
|
+
elenaAgentPreviewItemSchema,
|
|
1751
|
+
elenaAgentProductChangeSchema,
|
|
1752
|
+
elenaAgentProductDataSchema,
|
|
1753
|
+
elenaAgentProductListSchema,
|
|
1754
|
+
elenaAgentProductResponseSchema,
|
|
1755
|
+
elenaAgentProductSchema,
|
|
1756
|
+
elenaAgentProposalRequestSchema,
|
|
1757
|
+
elenaAgentPublicationPayloadSchema,
|
|
1758
|
+
elenaAgentRefSchema,
|
|
1759
|
+
elenaAgentRestaurantSchema,
|
|
1760
|
+
elenaAgentResultItemSchema,
|
|
1761
|
+
elenaAgentRevisionSchema,
|
|
1762
|
+
elenaAgentScopeSchema,
|
|
1763
|
+
elenaAgentScopesSchema,
|
|
1764
|
+
elenaAgentSecretSchema,
|
|
1765
|
+
elenaAgentTargetSchema,
|
|
1766
|
+
elenaAgentTextSchema,
|
|
1767
|
+
elenaAgentTimestampSchema,
|
|
1768
|
+
elenaAgentUserCodeSchema,
|
|
1769
|
+
elenaAgentVersionSchema,
|
|
1770
|
+
elenaClientAssetResponseSchema,
|
|
1771
|
+
elenaClientAssetSchema,
|
|
1772
|
+
elenaClientCatalogSchema,
|
|
1773
|
+
elenaClientCategorySchema,
|
|
1774
|
+
elenaClientContextSchema,
|
|
1775
|
+
elenaClientKeySchema,
|
|
1776
|
+
elenaClientProductResponseSchema,
|
|
1777
|
+
elenaClientProductSchema,
|
|
1778
|
+
elenaManagedClientKeySchema,
|
|
1779
|
+
elenaManualClientKeySchema,
|
|
1780
|
+
elenaPublicConnectionManagementSchema,
|
|
1781
|
+
elenaPublicConnectionMetadataSchema,
|
|
1782
|
+
elenaPublicConnectionMutationSchema,
|
|
1783
|
+
elenaPublicConnectionSchema,
|
|
1784
|
+
elenaPublicConnectionSlugSchema
|
|
1785
|
+
};
|
|
1786
|
+
//# sourceMappingURL=agent.js.map
|