@vinaverse/server 0.0.0-stage → 0.1.0-alpha.10
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/LICENSE +202 -0
- package/README.md +169 -2
- package/dist/api.cjs +546 -0
- package/dist/api.cjs.map +1 -0
- package/dist/api.d.cts +473 -0
- package/dist/api.d.ts +473 -0
- package/dist/api.js +534 -0
- package/dist/api.js.map +1 -0
- package/dist/chunk-FEEJNQXV.js +64 -0
- package/dist/chunk-FEEJNQXV.js.map +1 -0
- package/dist/chunk-GTPMQKGX.js +277 -0
- package/dist/chunk-GTPMQKGX.js.map +1 -0
- package/dist/chunk-NPXO6SMZ.js +84 -0
- package/dist/chunk-NPXO6SMZ.js.map +1 -0
- package/dist/chunk-SDJFZPHJ.js +141 -0
- package/dist/chunk-SDJFZPHJ.js.map +1 -0
- package/dist/express.cjs +397 -0
- package/dist/express.cjs.map +1 -0
- package/dist/express.d.cts +43 -0
- package/dist/express.d.ts +43 -0
- package/dist/express.js +87 -0
- package/dist/express.js.map +1 -0
- package/dist/fastify.cjs +363 -0
- package/dist/fastify.cjs.map +1 -0
- package/dist/fastify.d.cts +62 -0
- package/dist/fastify.d.ts +62 -0
- package/dist/fastify.js +54 -0
- package/dist/fastify.js.map +1 -0
- package/dist/hono.cjs +421 -0
- package/dist/hono.cjs.map +1 -0
- package/dist/hono.d.cts +21 -0
- package/dist/hono.d.ts +21 -0
- package/dist/hono.js +34 -0
- package/dist/hono.js.map +1 -0
- package/dist/index.cjs +1509 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +196 -0
- package/dist/index.d.ts +196 -0
- package/dist/index.js +948 -0
- package/dist/index.js.map +1 -0
- package/dist/lambda.cjs +405 -0
- package/dist/lambda.cjs.map +1 -0
- package/dist/lambda.d.cts +53 -0
- package/dist/lambda.d.ts +53 -0
- package/dist/lambda.js +86 -0
- package/dist/lambda.js.map +1 -0
- package/dist/nonces-scwljZwl.d.cts +92 -0
- package/dist/nonces-scwljZwl.d.ts +92 -0
- package/dist/provider-CbLgJEXQ.d.ts +2193 -0
- package/dist/provider-ytczljc-.d.cts +2193 -0
- package/dist/stores.cjs +315 -0
- package/dist/stores.cjs.map +1 -0
- package/dist/stores.d.cts +52 -0
- package/dist/stores.d.ts +52 -0
- package/dist/stores.js +306 -0
- package/dist/stores.js.map +1 -0
- package/dist/testing.cjs +135 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +29 -0
- package/dist/testing.d.ts +29 -0
- package/dist/testing.js +49 -0
- package/dist/testing.js.map +1 -0
- package/dist/vinaverse-v1.d-DT7a1T80.d.cts +3867 -0
- package/dist/vinaverse-v1.d-DT7a1T80.d.ts +3867 -0
- package/package.json +225 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,948 @@
|
|
|
1
|
+
export { createFetchToolHandler, createFetchWebhookHandler } from './chunk-NPXO6SMZ.js';
|
|
2
|
+
import './chunk-FEEJNQXV.js';
|
|
3
|
+
import { verifyToolCall } from './chunk-SDJFZPHJ.js';
|
|
4
|
+
export { MemoryNonceStore, NONCE_KEEP_SECONDS, verifyToolCall } from './chunk-SDJFZPHJ.js';
|
|
5
|
+
import { sha256Hex, utf8, checkTolerance, toBytes, webCrypto } from './chunk-GTPMQKGX.js';
|
|
6
|
+
export { SIGNATURE_HEADER, TOLERANCE_SECONDS, WEBHOOK_EVENT_TYPES, WebCryptoUnavailableError, isConversationEvent, parseSignatureHeader, verifyWebhook } from './chunk-GTPMQKGX.js';
|
|
7
|
+
|
|
8
|
+
// src/contract.ts
|
|
9
|
+
var CONTRACT = {
|
|
10
|
+
api: "v1-draft.22",
|
|
11
|
+
commit: "4695e3dc042e6e0cf1936aae24fcb093eacd27c8"
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// src/generated/tool-operations.ts
|
|
15
|
+
var TOOL_OPERATIONS = {
|
|
16
|
+
create_handoff: {
|
|
17
|
+
id: "create_handoff",
|
|
18
|
+
method: "POST",
|
|
19
|
+
path: "/handoffs",
|
|
20
|
+
effect: "write",
|
|
21
|
+
maxResultBytes: 16384,
|
|
22
|
+
successStatus: 201,
|
|
23
|
+
idempotencyKey: true,
|
|
24
|
+
body: true,
|
|
25
|
+
pathParams: {},
|
|
26
|
+
queryParams: {}
|
|
27
|
+
},
|
|
28
|
+
create_proposal: {
|
|
29
|
+
id: "create_proposal",
|
|
30
|
+
method: "POST",
|
|
31
|
+
path: "/proposals",
|
|
32
|
+
effect: "propose",
|
|
33
|
+
maxResultBytes: 16384,
|
|
34
|
+
successStatus: 201,
|
|
35
|
+
idempotencyKey: true,
|
|
36
|
+
body: true,
|
|
37
|
+
pathParams: {},
|
|
38
|
+
queryParams: {}
|
|
39
|
+
},
|
|
40
|
+
get_availability: {
|
|
41
|
+
id: "get_availability",
|
|
42
|
+
method: "GET",
|
|
43
|
+
path: "/availability",
|
|
44
|
+
effect: "read",
|
|
45
|
+
maxResultBytes: 16384,
|
|
46
|
+
successStatus: 200,
|
|
47
|
+
idempotencyKey: false,
|
|
48
|
+
body: false,
|
|
49
|
+
pathParams: {},
|
|
50
|
+
queryParams: {
|
|
51
|
+
professional_id: { type: "string", required: true },
|
|
52
|
+
service_id: { type: "string", required: true },
|
|
53
|
+
from: { type: "string", required: true },
|
|
54
|
+
to: { type: "string", required: true },
|
|
55
|
+
limit: { type: "integer", required: false }
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
get_flow_definition: {
|
|
59
|
+
id: "get_flow_definition",
|
|
60
|
+
method: "GET",
|
|
61
|
+
path: "/flows/{flow}/definition",
|
|
62
|
+
effect: "read",
|
|
63
|
+
maxResultBytes: 131072,
|
|
64
|
+
successStatus: 200,
|
|
65
|
+
idempotencyKey: false,
|
|
66
|
+
body: false,
|
|
67
|
+
pathParams: { flow: { type: "string", required: true } },
|
|
68
|
+
queryParams: { version: { type: "integer", required: false } }
|
|
69
|
+
},
|
|
70
|
+
get_my_booking: {
|
|
71
|
+
id: "get_my_booking",
|
|
72
|
+
method: "GET",
|
|
73
|
+
path: "/me/bookings/{id}",
|
|
74
|
+
effect: "read",
|
|
75
|
+
maxResultBytes: 16384,
|
|
76
|
+
successStatus: 200,
|
|
77
|
+
idempotencyKey: false,
|
|
78
|
+
body: false,
|
|
79
|
+
pathParams: { id: { type: "string", required: true } },
|
|
80
|
+
queryParams: {}
|
|
81
|
+
},
|
|
82
|
+
get_my_kit: {
|
|
83
|
+
id: "get_my_kit",
|
|
84
|
+
method: "GET",
|
|
85
|
+
path: "/me/kits/{id}",
|
|
86
|
+
effect: "read",
|
|
87
|
+
maxResultBytes: 16384,
|
|
88
|
+
successStatus: 200,
|
|
89
|
+
idempotencyKey: false,
|
|
90
|
+
body: false,
|
|
91
|
+
pathParams: { id: { type: "string", required: true } },
|
|
92
|
+
queryParams: {}
|
|
93
|
+
},
|
|
94
|
+
get_my_order: {
|
|
95
|
+
id: "get_my_order",
|
|
96
|
+
method: "GET",
|
|
97
|
+
path: "/me/orders/{id}",
|
|
98
|
+
effect: "read",
|
|
99
|
+
maxResultBytes: 16384,
|
|
100
|
+
successStatus: 200,
|
|
101
|
+
idempotencyKey: false,
|
|
102
|
+
body: false,
|
|
103
|
+
pathParams: { id: { type: "string", required: true } },
|
|
104
|
+
queryParams: {}
|
|
105
|
+
},
|
|
106
|
+
get_policy: {
|
|
107
|
+
id: "get_policy",
|
|
108
|
+
method: "GET",
|
|
109
|
+
path: "/policies/{topic}",
|
|
110
|
+
effect: "read",
|
|
111
|
+
maxResultBytes: 16384,
|
|
112
|
+
successStatus: 200,
|
|
113
|
+
idempotencyKey: false,
|
|
114
|
+
body: false,
|
|
115
|
+
pathParams: { topic: { type: "string", required: true } },
|
|
116
|
+
queryParams: {}
|
|
117
|
+
},
|
|
118
|
+
get_professional: {
|
|
119
|
+
id: "get_professional",
|
|
120
|
+
method: "GET",
|
|
121
|
+
path: "/professionals/{id}",
|
|
122
|
+
effect: "read",
|
|
123
|
+
maxResultBytes: 16384,
|
|
124
|
+
successStatus: 200,
|
|
125
|
+
idempotencyKey: false,
|
|
126
|
+
body: false,
|
|
127
|
+
pathParams: { id: { type: "string", required: true } },
|
|
128
|
+
queryParams: {}
|
|
129
|
+
},
|
|
130
|
+
get_quote: {
|
|
131
|
+
id: "get_quote",
|
|
132
|
+
method: "POST",
|
|
133
|
+
path: "/quotes",
|
|
134
|
+
effect: "read",
|
|
135
|
+
maxResultBytes: 16384,
|
|
136
|
+
successStatus: 200,
|
|
137
|
+
idempotencyKey: true,
|
|
138
|
+
body: true,
|
|
139
|
+
pathParams: {},
|
|
140
|
+
queryParams: {}
|
|
141
|
+
},
|
|
142
|
+
get_setup_state: {
|
|
143
|
+
id: "get_setup_state",
|
|
144
|
+
method: "GET",
|
|
145
|
+
path: "/business/setup",
|
|
146
|
+
effect: "read",
|
|
147
|
+
maxResultBytes: 16384,
|
|
148
|
+
successStatus: 200,
|
|
149
|
+
idempotencyKey: false,
|
|
150
|
+
body: false,
|
|
151
|
+
pathParams: {},
|
|
152
|
+
queryParams: {}
|
|
153
|
+
},
|
|
154
|
+
get_workspace: {
|
|
155
|
+
id: "get_workspace",
|
|
156
|
+
method: "GET",
|
|
157
|
+
path: "/workspaces/{id}",
|
|
158
|
+
effect: "read",
|
|
159
|
+
maxResultBytes: 16384,
|
|
160
|
+
successStatus: 200,
|
|
161
|
+
idempotencyKey: false,
|
|
162
|
+
body: false,
|
|
163
|
+
pathParams: { id: { type: "string", required: true } },
|
|
164
|
+
queryParams: {}
|
|
165
|
+
},
|
|
166
|
+
list_my_bookings: {
|
|
167
|
+
id: "list_my_bookings",
|
|
168
|
+
method: "GET",
|
|
169
|
+
path: "/me/bookings",
|
|
170
|
+
effect: "read",
|
|
171
|
+
maxResultBytes: 16384,
|
|
172
|
+
successStatus: 200,
|
|
173
|
+
idempotencyKey: false,
|
|
174
|
+
body: false,
|
|
175
|
+
pathParams: {},
|
|
176
|
+
queryParams: {
|
|
177
|
+
scope: { type: "string", required: false },
|
|
178
|
+
limit: { type: "integer", required: false }
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
list_my_kits: {
|
|
182
|
+
id: "list_my_kits",
|
|
183
|
+
method: "GET",
|
|
184
|
+
path: "/me/kits",
|
|
185
|
+
effect: "read",
|
|
186
|
+
maxResultBytes: 16384,
|
|
187
|
+
successStatus: 200,
|
|
188
|
+
idempotencyKey: false,
|
|
189
|
+
body: false,
|
|
190
|
+
pathParams: {},
|
|
191
|
+
queryParams: {
|
|
192
|
+
scope: { type: "string", required: false },
|
|
193
|
+
limit: { type: "integer", required: false }
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
list_my_orders: {
|
|
197
|
+
id: "list_my_orders",
|
|
198
|
+
method: "GET",
|
|
199
|
+
path: "/me/orders",
|
|
200
|
+
effect: "read",
|
|
201
|
+
maxResultBytes: 16384,
|
|
202
|
+
successStatus: 200,
|
|
203
|
+
idempotencyKey: false,
|
|
204
|
+
body: false,
|
|
205
|
+
pathParams: {},
|
|
206
|
+
queryParams: {
|
|
207
|
+
kit_id: { type: "string", required: false },
|
|
208
|
+
limit: { type: "integer", required: false }
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
list_services: {
|
|
212
|
+
id: "list_services",
|
|
213
|
+
method: "GET",
|
|
214
|
+
path: "/catalog/services",
|
|
215
|
+
effect: "read",
|
|
216
|
+
maxResultBytes: 16384,
|
|
217
|
+
successStatus: 200,
|
|
218
|
+
idempotencyKey: false,
|
|
219
|
+
body: false,
|
|
220
|
+
pathParams: {},
|
|
221
|
+
queryParams: {
|
|
222
|
+
q: { type: "string", required: true },
|
|
223
|
+
limit: { type: "integer", required: false }
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
list_services_in_area: {
|
|
227
|
+
id: "list_services_in_area",
|
|
228
|
+
method: "GET",
|
|
229
|
+
path: "/areas/services",
|
|
230
|
+
effect: "read",
|
|
231
|
+
maxResultBytes: 16384,
|
|
232
|
+
successStatus: 200,
|
|
233
|
+
idempotencyKey: false,
|
|
234
|
+
body: false,
|
|
235
|
+
pathParams: {},
|
|
236
|
+
queryParams: {
|
|
237
|
+
lat: { type: "number", required: false },
|
|
238
|
+
lng: { type: "number", required: false },
|
|
239
|
+
radius_miles: { type: "integer", required: false }
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
resolve_place: {
|
|
243
|
+
id: "resolve_place",
|
|
244
|
+
method: "GET",
|
|
245
|
+
path: "/places/resolve",
|
|
246
|
+
effect: "read",
|
|
247
|
+
maxResultBytes: 16384,
|
|
248
|
+
successStatus: 200,
|
|
249
|
+
idempotencyKey: false,
|
|
250
|
+
body: false,
|
|
251
|
+
pathParams: {},
|
|
252
|
+
queryParams: {
|
|
253
|
+
text: { type: "string", required: true },
|
|
254
|
+
near_lat: { type: "number", required: false },
|
|
255
|
+
near_lng: { type: "number", required: false }
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
search_professionals: {
|
|
259
|
+
id: "search_professionals",
|
|
260
|
+
method: "GET",
|
|
261
|
+
path: "/search/professionals",
|
|
262
|
+
effect: "read",
|
|
263
|
+
maxResultBytes: 16384,
|
|
264
|
+
successStatus: 200,
|
|
265
|
+
idempotencyKey: false,
|
|
266
|
+
body: false,
|
|
267
|
+
pathParams: {},
|
|
268
|
+
queryParams: {
|
|
269
|
+
q: { type: "string", required: false },
|
|
270
|
+
category_id: { type: "string", required: false },
|
|
271
|
+
service_type_id: { type: "string", required: false },
|
|
272
|
+
lat: { type: "number", required: false },
|
|
273
|
+
lng: { type: "number", required: false },
|
|
274
|
+
radius_miles: { type: "integer", required: false },
|
|
275
|
+
date: { type: "string", required: false },
|
|
276
|
+
time_from: { type: "string", required: false },
|
|
277
|
+
time_to: { type: "string", required: false },
|
|
278
|
+
max_price: { type: "integer", required: false },
|
|
279
|
+
min_rating_ppm: { type: "integer", required: false },
|
|
280
|
+
sort: { type: "string", required: false },
|
|
281
|
+
limit: { type: "integer", required: false },
|
|
282
|
+
cursor: { type: "string", required: false }
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
search_workspaces: {
|
|
286
|
+
id: "search_workspaces",
|
|
287
|
+
method: "GET",
|
|
288
|
+
path: "/search/workspaces",
|
|
289
|
+
effect: "read",
|
|
290
|
+
maxResultBytes: 16384,
|
|
291
|
+
successStatus: 200,
|
|
292
|
+
idempotencyKey: false,
|
|
293
|
+
body: false,
|
|
294
|
+
pathParams: {},
|
|
295
|
+
queryParams: {
|
|
296
|
+
q: { type: "string", required: false },
|
|
297
|
+
space_category: { type: "string", required: false },
|
|
298
|
+
lat: { type: "number", required: false },
|
|
299
|
+
lng: { type: "number", required: false },
|
|
300
|
+
radius_miles: { type: "integer", required: false },
|
|
301
|
+
max_price: { type: "integer", required: false },
|
|
302
|
+
min_rating_ppm: { type: "integer", required: false },
|
|
303
|
+
sort: { type: "string", required: false },
|
|
304
|
+
limit: { type: "integer", required: false },
|
|
305
|
+
cursor: { type: "string", required: false }
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
submit_flow: {
|
|
309
|
+
id: "submit_flow",
|
|
310
|
+
method: "POST",
|
|
311
|
+
path: "/flows/{flow}/submissions",
|
|
312
|
+
effect: "write",
|
|
313
|
+
maxResultBytes: 16384,
|
|
314
|
+
successStatus: 200,
|
|
315
|
+
idempotencyKey: false,
|
|
316
|
+
body: true,
|
|
317
|
+
pathParams: { flow: { type: "string", required: true } },
|
|
318
|
+
queryParams: {}
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// src/tools/idempotency.ts
|
|
323
|
+
var MemoryIdempotencyStore = class {
|
|
324
|
+
#held = /* @__PURE__ */ new Map();
|
|
325
|
+
#now;
|
|
326
|
+
#sweepEvery;
|
|
327
|
+
#starts = 0;
|
|
328
|
+
constructor(options = {}) {
|
|
329
|
+
this.#now = options.now ?? (() => Date.now() / 1e3);
|
|
330
|
+
this.#sweepEvery = Math.max(1, options.sweepEvery ?? 256);
|
|
331
|
+
}
|
|
332
|
+
async start(key, fingerprint2, pendingSeconds, owner) {
|
|
333
|
+
const now = this.#now();
|
|
334
|
+
if (++this.#starts % this.#sweepEvery === 0) {
|
|
335
|
+
for (const [k, h] of this.#held) if (h.until < now) this.#held.delete(k);
|
|
336
|
+
}
|
|
337
|
+
const held = this.#live(key);
|
|
338
|
+
if (held) return held.record;
|
|
339
|
+
this.#held.set(key, {
|
|
340
|
+
record: { fingerprint: fingerprint2, state: "pending" },
|
|
341
|
+
owner,
|
|
342
|
+
until: now + pendingSeconds
|
|
343
|
+
});
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
async finish(key, record, keepSeconds, owner) {
|
|
347
|
+
const held = this.#live(key);
|
|
348
|
+
if (held && (held.record.state !== "pending" || held.owner !== owner)) {
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
this.#held.set(key, {
|
|
352
|
+
record: { ...record, state: "done" },
|
|
353
|
+
until: this.#now() + keepSeconds
|
|
354
|
+
});
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
async release(key, owner) {
|
|
358
|
+
const held = this.#live(key);
|
|
359
|
+
if (held?.record.state === "pending" && held.owner === owner) {
|
|
360
|
+
this.#held.delete(key);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/** Records held, expired ones included until a sweep drops them. */
|
|
364
|
+
get size() {
|
|
365
|
+
return this.#held.size;
|
|
366
|
+
}
|
|
367
|
+
#live(key) {
|
|
368
|
+
const held = this.#held.get(key);
|
|
369
|
+
return held && held.until >= this.#now() ? held : void 0;
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
function fingerprint(value) {
|
|
373
|
+
return sha256Hex(utf8(canonicalJson(value)));
|
|
374
|
+
}
|
|
375
|
+
function canonicalJson(value) {
|
|
376
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
377
|
+
if (value !== null && typeof value === "object") {
|
|
378
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
379
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(",")}}`;
|
|
380
|
+
}
|
|
381
|
+
return JSON.stringify(value) ?? "null";
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// src/tools/refusals.ts
|
|
385
|
+
var FLOW_REFUSAL_CODES = {
|
|
386
|
+
REQUIRED_MISSING: true,
|
|
387
|
+
UNKNOWN_VALUE: true,
|
|
388
|
+
TOO_LONG: true,
|
|
389
|
+
VERSION_NOT_ACCEPTED: true,
|
|
390
|
+
TOO_MANY: true,
|
|
391
|
+
TOO_FEW: true,
|
|
392
|
+
BAD_DATE: true,
|
|
393
|
+
UPLOAD_NOT_FOUND: true,
|
|
394
|
+
OUT_OF_RANGE: true
|
|
395
|
+
};
|
|
396
|
+
function isFlowRefusal(problem2) {
|
|
397
|
+
return problem2.status === 422 && Object.hasOwn(FLOW_REFUSAL_CODES, problem2.code) && (problem2.code === "VERSION_NOT_ACCEPTED" || !!problem2.question_id);
|
|
398
|
+
}
|
|
399
|
+
var STATUS = {
|
|
400
|
+
not_found: 404,
|
|
401
|
+
forbidden: 403,
|
|
402
|
+
invalid_argument: 422,
|
|
403
|
+
unavailable: 503,
|
|
404
|
+
token_expired: 401
|
|
405
|
+
};
|
|
406
|
+
var ToolRefusal = class _ToolRefusal extends Error {
|
|
407
|
+
problem;
|
|
408
|
+
constructor(problem2) {
|
|
409
|
+
super(problem2.detail ?? problem2.code);
|
|
410
|
+
this.name = "ToolRefusal";
|
|
411
|
+
this.problem = problem2;
|
|
412
|
+
}
|
|
413
|
+
get status() {
|
|
414
|
+
return this.problem.status;
|
|
415
|
+
}
|
|
416
|
+
/** The record does not exist, or the person may not see it (both are the same 404). */
|
|
417
|
+
static notFound(detail) {
|
|
418
|
+
return _ToolRefusal.#coded("not_found", detail);
|
|
419
|
+
}
|
|
420
|
+
static forbidden(detail) {
|
|
421
|
+
return _ToolRefusal.#coded("forbidden", detail);
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* A value the tenant does not accept. `field` is a JSON Pointer into the
|
|
425
|
+
* body (`/changes/address/postal_code`); `example` is a valid value the
|
|
426
|
+
* agent offers when it asks again.
|
|
427
|
+
*/
|
|
428
|
+
static invalidArgument(options = {}) {
|
|
429
|
+
return new _ToolRefusal({
|
|
430
|
+
status: 422,
|
|
431
|
+
code: "invalid_argument",
|
|
432
|
+
...clip("detail", options.detail, 300),
|
|
433
|
+
...clip("field", options.field, 200),
|
|
434
|
+
...clip("example", options.example, 200)
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
/** A temporary failure; a read is retried. */
|
|
438
|
+
static unavailable(detail) {
|
|
439
|
+
return _ToolRefusal.#coded("unavailable", detail);
|
|
440
|
+
}
|
|
441
|
+
/** The delegated token is not valid (expired, revoked, unknown): a 401, which ends the session's tool access. */
|
|
442
|
+
static tokenExpired(detail) {
|
|
443
|
+
return _ToolRefusal.#coded("token_expired", detail);
|
|
444
|
+
}
|
|
445
|
+
/** `get_flow_definition` with a `version` the tenant no longer accepts: `410 VERSION_GONE`. */
|
|
446
|
+
static versionGone(detail) {
|
|
447
|
+
return new _ToolRefusal({
|
|
448
|
+
status: 410,
|
|
449
|
+
code: "VERSION_GONE",
|
|
450
|
+
...clip("detail", detail, 300)
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* `submit_flow` refused: `422` with a flow code and the question to ask
|
|
455
|
+
* again (none only for `VERSION_NOT_ACCEPTED`). Stored like an acceptance,
|
|
456
|
+
* so the same submission gets the same refusal again.
|
|
457
|
+
*/
|
|
458
|
+
static flowRefused(code, questionId, detail) {
|
|
459
|
+
if (code !== "VERSION_NOT_ACCEPTED" && !questionId) {
|
|
460
|
+
throw new TypeError(`a ${code} refusal names the question to ask again`);
|
|
461
|
+
}
|
|
462
|
+
return new _ToolRefusal({
|
|
463
|
+
status: 422,
|
|
464
|
+
code,
|
|
465
|
+
...questionId ? { question_id: questionId } : {},
|
|
466
|
+
...clip("detail", detail, 300)
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
static #coded(code, detail) {
|
|
470
|
+
return new _ToolRefusal({
|
|
471
|
+
status: STATUS[code],
|
|
472
|
+
code,
|
|
473
|
+
...clip("detail", detail, 300)
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
function clip(key, value, max) {
|
|
478
|
+
if (value === void 0) return {};
|
|
479
|
+
return { [key]: [...value].slice(0, max).join("") };
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// src/tools/provider.ts
|
|
483
|
+
var ACCEPTED = { ok: true };
|
|
484
|
+
var KEEP_SECONDS = {
|
|
485
|
+
submit_flow: 31 * 86400,
|
|
486
|
+
// a conversation lives up to 30 days, and a submission is resent in later sessions
|
|
487
|
+
create_proposal: 8 * 86400,
|
|
488
|
+
// at least until the proposal expires (at most 7 days)
|
|
489
|
+
default: 86400,
|
|
490
|
+
pending: 60
|
|
491
|
+
};
|
|
492
|
+
var MAX_IDEMPOTENCY_KEY = 255;
|
|
493
|
+
var MAX_REQUEST_BYTES = 1024 * 1024;
|
|
494
|
+
var LeaseLostError = class extends Error {
|
|
495
|
+
constructor(operation, leaseSeconds) {
|
|
496
|
+
super(
|
|
497
|
+
`${operation} ran longer than its ${leaseSeconds}-second idempotency lease and another call took the key: the answer was sent but not stored (raise leaseSeconds)`
|
|
498
|
+
);
|
|
499
|
+
this.name = "LeaseLostError";
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
var ResultTooLargeError = class extends Error {
|
|
503
|
+
constructor(operation, bytes, limit) {
|
|
504
|
+
super(
|
|
505
|
+
`${operation} returned ${bytes} bytes; the contract allows ${limit}: the runtime would refuse it`
|
|
506
|
+
);
|
|
507
|
+
this.name = "ResultTooLargeError";
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
function createToolProvider(options) {
|
|
511
|
+
const secrets = typeof options.secret === "string" ? [options.secret] : [...options.secret];
|
|
512
|
+
if (secrets.length === 0 || secrets.some((s) => typeof s !== "string" || !s)) {
|
|
513
|
+
throw new TypeError("createToolProvider needs the tool signing secret");
|
|
514
|
+
}
|
|
515
|
+
checkTolerance(options.toleranceSeconds);
|
|
516
|
+
const maxRequest = options.maxRequestBytes;
|
|
517
|
+
if (maxRequest !== void 0 && (!Number.isSafeInteger(maxRequest) || maxRequest <= 0)) {
|
|
518
|
+
throw new TypeError(
|
|
519
|
+
`maxRequestBytes must be a positive whole number of bytes, not ${maxRequest}`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
const lease = options.leaseSeconds ?? KEEP_SECONDS.pending;
|
|
523
|
+
if (!Number.isFinite(lease) || lease <= 0) {
|
|
524
|
+
throw new TypeError(
|
|
525
|
+
`leaseSeconds must be a positive number of seconds, not ${options.leaseSeconds}`
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
if (typeof options.authenticate !== "function") {
|
|
529
|
+
throw new TypeError(
|
|
530
|
+
"createToolProvider needs authenticate: it resolves the delegated token"
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
const base = (options.basePath ?? "").replace(/\/+$/, "");
|
|
534
|
+
const routes = Object.values(TOOL_OPERATIONS).map((o) => ({
|
|
535
|
+
id: o.id,
|
|
536
|
+
segments: o.path.split("/").filter(Boolean)
|
|
537
|
+
}));
|
|
538
|
+
async function handle(request) {
|
|
539
|
+
const raw = request.body ?? new Uint8Array(0);
|
|
540
|
+
const body = toBytes(raw);
|
|
541
|
+
if (body.length > (options.maxRequestBytes ?? MAX_REQUEST_BYTES)) {
|
|
542
|
+
return problem({
|
|
543
|
+
status: 413,
|
|
544
|
+
code: "invalid_argument",
|
|
545
|
+
detail: "the request body is too large"
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
const token = bearer(request.header("authorization"));
|
|
549
|
+
if (!token) {
|
|
550
|
+
return problem({
|
|
551
|
+
status: 401,
|
|
552
|
+
code: "token_expired",
|
|
553
|
+
detail: "no delegated token (Authorization: Bearer)"
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
const refused = await verifyToolCall(
|
|
557
|
+
{
|
|
558
|
+
method: request.method,
|
|
559
|
+
target: request.target,
|
|
560
|
+
token,
|
|
561
|
+
body,
|
|
562
|
+
header: request.header("vinaverse-signature")
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
secrets,
|
|
566
|
+
nonces: options.nonces,
|
|
567
|
+
now: options.now?.() ?? /* @__PURE__ */ new Date(),
|
|
568
|
+
...options.toleranceSeconds === void 0 ? {} : { toleranceSeconds: options.toleranceSeconds }
|
|
569
|
+
}
|
|
570
|
+
);
|
|
571
|
+
if (refused) {
|
|
572
|
+
options.onRefused?.(refused.code, refused.message);
|
|
573
|
+
return problem({
|
|
574
|
+
status: 403,
|
|
575
|
+
code: "forbidden",
|
|
576
|
+
detail: "the signature was refused"
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
const matched = match(request.method, request.target);
|
|
580
|
+
if (!matched)
|
|
581
|
+
return problem({
|
|
582
|
+
status: 404,
|
|
583
|
+
code: "not_found",
|
|
584
|
+
detail: "no such tool"
|
|
585
|
+
});
|
|
586
|
+
const { id, params, query } = matched;
|
|
587
|
+
const operation = TOOL_OPERATIONS[id];
|
|
588
|
+
const handler = options.handlers[id];
|
|
589
|
+
if (!handler)
|
|
590
|
+
return problem({
|
|
591
|
+
status: 404,
|
|
592
|
+
code: "not_found",
|
|
593
|
+
detail: `${id} is not served here`
|
|
594
|
+
});
|
|
595
|
+
try {
|
|
596
|
+
const principal = await options.authenticate(token, id);
|
|
597
|
+
const typedQuery = readQuery(id, query);
|
|
598
|
+
const parsed = operation.body ? parseBody(body) : void 0;
|
|
599
|
+
const key = request.header("idempotency-key");
|
|
600
|
+
if (operation.idempotencyKey && !key) {
|
|
601
|
+
throw ToolRefusal.invalidArgument({
|
|
602
|
+
detail: "the Idempotency-Key header is required"
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
if (key && key.length > MAX_IDEMPOTENCY_KEY) {
|
|
606
|
+
throw ToolRefusal.invalidArgument({
|
|
607
|
+
detail: `the Idempotency-Key is longer than ${MAX_IDEMPOTENCY_KEY} characters`
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
const call = {
|
|
611
|
+
operation: id,
|
|
612
|
+
principal,
|
|
613
|
+
token,
|
|
614
|
+
params,
|
|
615
|
+
query: typedQuery,
|
|
616
|
+
body: parsed,
|
|
617
|
+
session: session(request),
|
|
618
|
+
...key ? { idempotencyKey: key } : {}
|
|
619
|
+
};
|
|
620
|
+
const run = () => runHandler(id, handler, call);
|
|
621
|
+
const scope = options.scope?.(principal, id);
|
|
622
|
+
const keyOf = (...parts) => (scope === void 0 ? parts : [scope, ...parts]).map(encodeURIComponent).join(":");
|
|
623
|
+
if (id === "submit_flow")
|
|
624
|
+
return await submission(keyOf, params, parsed, run);
|
|
625
|
+
if (id === "create_proposal" && key)
|
|
626
|
+
return await proposal(keyOf(id, key), parsed, principal, run);
|
|
627
|
+
if (operation.idempotencyKey && key) {
|
|
628
|
+
return await keyed(
|
|
629
|
+
id,
|
|
630
|
+
keyOf(id, key),
|
|
631
|
+
await fingerprint(parsed),
|
|
632
|
+
KEEP_SECONDS.default,
|
|
633
|
+
run,
|
|
634
|
+
() => problem({
|
|
635
|
+
status: 409,
|
|
636
|
+
code: "IDEMPOTENCY_KEY_REUSED",
|
|
637
|
+
detail: "this Idempotency-Key was used with another body"
|
|
638
|
+
})
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
return await run();
|
|
642
|
+
} catch (error) {
|
|
643
|
+
return failure(error, id);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
async function runHandler(id, handler, call) {
|
|
647
|
+
const result = await handler(call) ?? (id === "submit_flow" ? ACCEPTED : void 0);
|
|
648
|
+
if (result === void 0) {
|
|
649
|
+
throw new TypeError(`the ${id} handler returned nothing`);
|
|
650
|
+
}
|
|
651
|
+
const text = JSON.stringify(result);
|
|
652
|
+
const limit = TOOL_OPERATIONS[id].maxResultBytes;
|
|
653
|
+
const bytes = toBytes(text).length;
|
|
654
|
+
if (bytes > limit) throw new ResultTooLargeError(id, bytes, limit);
|
|
655
|
+
return json(TOOL_OPERATIONS[id].successStatus, text);
|
|
656
|
+
}
|
|
657
|
+
async function keyed(id, key, print, keepSeconds, run, conflict, replay, keeps = (r) => r.status === 422) {
|
|
658
|
+
const store = options.idempotency;
|
|
659
|
+
const owner = await ownerToken();
|
|
660
|
+
const held = await store.start(key, print, lease, owner);
|
|
661
|
+
if (held) {
|
|
662
|
+
if (held.state === "pending") {
|
|
663
|
+
return problem({
|
|
664
|
+
status: 503,
|
|
665
|
+
code: "unavailable",
|
|
666
|
+
detail: "the same call is still running"
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
if (held.fingerprint !== print) return conflict(held);
|
|
670
|
+
return replay ? replay(held) : stored(held);
|
|
671
|
+
}
|
|
672
|
+
let response;
|
|
673
|
+
try {
|
|
674
|
+
response = await run();
|
|
675
|
+
} catch (error) {
|
|
676
|
+
if (!(error instanceof ToolRefusal && keeps(error))) {
|
|
677
|
+
await store.release(key, owner);
|
|
678
|
+
throw error;
|
|
679
|
+
}
|
|
680
|
+
response = problem(error.problem);
|
|
681
|
+
}
|
|
682
|
+
try {
|
|
683
|
+
const kept = await store.finish(
|
|
684
|
+
key,
|
|
685
|
+
{ fingerprint: print, status: response.status, body: response.body },
|
|
686
|
+
keepSeconds,
|
|
687
|
+
owner
|
|
688
|
+
);
|
|
689
|
+
if (!kept) options.onError?.(new LeaseLostError(id, lease), id);
|
|
690
|
+
} catch (error) {
|
|
691
|
+
options.onError?.(error, id);
|
|
692
|
+
}
|
|
693
|
+
return response;
|
|
694
|
+
}
|
|
695
|
+
async function submission(keyOf, params, body, run) {
|
|
696
|
+
const submissionId = body?.submission_id;
|
|
697
|
+
if (typeof submissionId !== "string" || submissionId === "") {
|
|
698
|
+
throw ToolRefusal.invalidArgument({
|
|
699
|
+
field: "/submission_id",
|
|
700
|
+
detail: "a submission needs its submission_id"
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
const refused = async () => {
|
|
704
|
+
try {
|
|
705
|
+
return await run();
|
|
706
|
+
} catch (error) {
|
|
707
|
+
if (error instanceof ToolRefusal && error.status === 422 && !isFlowRefusal(error.problem)) {
|
|
708
|
+
throw new TypeError(
|
|
709
|
+
`submit_flow refused with ${error.problem.code}: a submission is refused only with ToolRefusal.flowRefused (a flow code and the question to ask again)`
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
throw error;
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
return keyed(
|
|
716
|
+
"submit_flow",
|
|
717
|
+
keyOf("submit_flow", params.flow ?? "", submissionId),
|
|
718
|
+
await fingerprint(body),
|
|
719
|
+
KEEP_SECONDS.submit_flow,
|
|
720
|
+
refused,
|
|
721
|
+
(held) => {
|
|
722
|
+
const was = held.body ? JSON.parse(held.body) : {};
|
|
723
|
+
const storedResult = held.status === 200 ? { result: "accepted" } : {
|
|
724
|
+
result: "refused",
|
|
725
|
+
...was.code ? { code: was.code } : {},
|
|
726
|
+
...was.question_id ? { question_id: was.question_id } : {}
|
|
727
|
+
};
|
|
728
|
+
return json(
|
|
729
|
+
409,
|
|
730
|
+
JSON.stringify({
|
|
731
|
+
status: 409,
|
|
732
|
+
code: "SUBMISSION_ID_REUSED",
|
|
733
|
+
stored: storedResult
|
|
734
|
+
}),
|
|
735
|
+
"application/problem+json"
|
|
736
|
+
);
|
|
737
|
+
},
|
|
738
|
+
void 0,
|
|
739
|
+
(r) => isFlowRefusal(r.problem)
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
async function proposal(key, body, principal, run) {
|
|
743
|
+
const { topic, changes } = body ?? {};
|
|
744
|
+
const current = async (held) => {
|
|
745
|
+
const storedProposal = JSON.parse(
|
|
746
|
+
held.body ?? "null"
|
|
747
|
+
);
|
|
748
|
+
return options.currentProposal ? await options.currentProposal(storedProposal, principal) : storedProposal;
|
|
749
|
+
};
|
|
750
|
+
return keyed(
|
|
751
|
+
"create_proposal",
|
|
752
|
+
key,
|
|
753
|
+
await fingerprint({ topic, changes }),
|
|
754
|
+
KEEP_SECONDS.create_proposal,
|
|
755
|
+
run,
|
|
756
|
+
async (held) => held.status === 201 ? json(
|
|
757
|
+
409,
|
|
758
|
+
JSON.stringify({
|
|
759
|
+
status: 409,
|
|
760
|
+
code: "IDEMPOTENCY_KEY_REUSED",
|
|
761
|
+
proposal: await current(held)
|
|
762
|
+
}),
|
|
763
|
+
"application/problem+json"
|
|
764
|
+
) : stored(held),
|
|
765
|
+
async (held) => held.status === 201 ? json(201, JSON.stringify(await current(held))) : stored(held)
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
function match(method, target) {
|
|
769
|
+
const at = target.indexOf("?");
|
|
770
|
+
const path = at < 0 ? target : target.slice(0, at);
|
|
771
|
+
const query = new URLSearchParams(at < 0 ? "" : target.slice(at + 1));
|
|
772
|
+
if (base && path !== base && !path.startsWith(`${base}/`)) return void 0;
|
|
773
|
+
const segments = path.slice(base.length).split("/").filter(Boolean);
|
|
774
|
+
for (const route of routes) {
|
|
775
|
+
if (TOOL_OPERATIONS[route.id].method !== method.toUpperCase()) continue;
|
|
776
|
+
if (route.segments.length !== segments.length) continue;
|
|
777
|
+
const params = {};
|
|
778
|
+
let ok = true;
|
|
779
|
+
for (const [i, part] of route.segments.entries()) {
|
|
780
|
+
const actual = segments[i];
|
|
781
|
+
if (part.startsWith("{") && part.endsWith("}")) {
|
|
782
|
+
try {
|
|
783
|
+
params[part.slice(1, -1)] = decodeURIComponent(actual);
|
|
784
|
+
} catch {
|
|
785
|
+
ok = false;
|
|
786
|
+
}
|
|
787
|
+
} else if (part !== actual) {
|
|
788
|
+
ok = false;
|
|
789
|
+
}
|
|
790
|
+
if (!ok) break;
|
|
791
|
+
}
|
|
792
|
+
if (ok) return { id: route.id, params, query };
|
|
793
|
+
}
|
|
794
|
+
return void 0;
|
|
795
|
+
}
|
|
796
|
+
function failure(error, id) {
|
|
797
|
+
if (error instanceof ToolRefusal) return problem(error.problem);
|
|
798
|
+
options.onError?.(error, id);
|
|
799
|
+
return problem({
|
|
800
|
+
status: 500,
|
|
801
|
+
code: "unavailable",
|
|
802
|
+
detail: "the tool failed"
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
return { handle };
|
|
806
|
+
}
|
|
807
|
+
function readQuery(id, query) {
|
|
808
|
+
const declared = TOOL_OPERATIONS[id].queryParams;
|
|
809
|
+
const out = {};
|
|
810
|
+
for (const [name, spec] of Object.entries(declared)) {
|
|
811
|
+
const value = query.get(name);
|
|
812
|
+
if (value === null) {
|
|
813
|
+
if (spec.required)
|
|
814
|
+
throw ToolRefusal.invalidArgument({
|
|
815
|
+
detail: `the query parameter ${name} is required`
|
|
816
|
+
});
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
if (spec.type === "integer" || spec.type === "number") {
|
|
820
|
+
const n = Number(value);
|
|
821
|
+
if (value.trim() === "" || !Number.isFinite(n) || spec.type === "integer" && !Number.isInteger(n)) {
|
|
822
|
+
throw ToolRefusal.invalidArgument({
|
|
823
|
+
detail: `the query parameter ${name} must be ${spec.type === "integer" ? "an integer" : "a number"}`
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
out[name] = n;
|
|
827
|
+
} else {
|
|
828
|
+
out[name] = value;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
return out;
|
|
832
|
+
}
|
|
833
|
+
function parseBody(body) {
|
|
834
|
+
if (body.length === 0)
|
|
835
|
+
throw ToolRefusal.invalidArgument({
|
|
836
|
+
detail: "the request needs a JSON body"
|
|
837
|
+
});
|
|
838
|
+
try {
|
|
839
|
+
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(body));
|
|
840
|
+
} catch {
|
|
841
|
+
throw ToolRefusal.invalidArgument({
|
|
842
|
+
detail: "the request body is not JSON"
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
function bearer(value) {
|
|
847
|
+
const m = /^Bearer\s+(\S+)\s*$/i.exec(value ?? "");
|
|
848
|
+
return m?.[1];
|
|
849
|
+
}
|
|
850
|
+
function session(request) {
|
|
851
|
+
const pick = (name) => request.header(name);
|
|
852
|
+
const entries = {
|
|
853
|
+
id: pick("vinaverse-session") ?? pick("vinaverse-session-id"),
|
|
854
|
+
conversationId: pick("vinaverse-conversation"),
|
|
855
|
+
toolCallId: pick("vinaverse-tool-call-id"),
|
|
856
|
+
requestId: pick("vinaverse-request-id")
|
|
857
|
+
};
|
|
858
|
+
return Object.fromEntries(
|
|
859
|
+
Object.entries(entries).filter(([, v]) => v !== void 0)
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
async function ownerToken() {
|
|
863
|
+
const bytes = (await webCrypto()).getRandomValues(new Uint8Array(16));
|
|
864
|
+
return [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
865
|
+
}
|
|
866
|
+
function json(status, body, type = "application/json") {
|
|
867
|
+
return { status, headers: { "content-type": type }, body };
|
|
868
|
+
}
|
|
869
|
+
function problem(body) {
|
|
870
|
+
return json(body.status, JSON.stringify(body), "application/problem+json");
|
|
871
|
+
}
|
|
872
|
+
function stored(held) {
|
|
873
|
+
const status = held.status ?? 200;
|
|
874
|
+
return json(
|
|
875
|
+
status,
|
|
876
|
+
held.body ?? "",
|
|
877
|
+
status >= 400 ? "application/problem+json" : "application/json"
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// src/webhooks/sequence.ts
|
|
882
|
+
function sequenceVerdict(held, incoming) {
|
|
883
|
+
if (held && held.conversation_id !== incoming.conversation_id) {
|
|
884
|
+
throw new TypeError(
|
|
885
|
+
`the states belong to different conversations (${held.conversation_id}, ${incoming.conversation_id})`
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
return held && incoming.sequence <= held.sequence ? "stale" : "apply";
|
|
889
|
+
}
|
|
890
|
+
function applyConversationState(record, incoming) {
|
|
891
|
+
const action = sequenceVerdict(record?.state, incoming);
|
|
892
|
+
const seen = record?.seen.includes(incoming.sequence) ? record.seen : [...record?.seen ?? [], incoming.sequence].sort((a, b) => a - b);
|
|
893
|
+
if (action === "stale" && record)
|
|
894
|
+
return { record: { ...record, seen }, action };
|
|
895
|
+
if (incoming.answers_omitted === true) {
|
|
896
|
+
const { answers_omitted: _omitted, ...rest } = incoming;
|
|
897
|
+
const state = {
|
|
898
|
+
...rest,
|
|
899
|
+
answers: record?.state.answers ?? [],
|
|
900
|
+
remarks: record?.state.remarks ?? {}
|
|
901
|
+
};
|
|
902
|
+
return { record: { state, seen, fullReadNeeded: true }, action };
|
|
903
|
+
}
|
|
904
|
+
return { record: { state: incoming, seen, fullReadNeeded: false }, action };
|
|
905
|
+
}
|
|
906
|
+
function missingSequences(record) {
|
|
907
|
+
const [lowest] = record.seen;
|
|
908
|
+
if (lowest === void 0) return [];
|
|
909
|
+
const missing = [];
|
|
910
|
+
for (let s = lowest + 1; s < record.state.sequence; s++) {
|
|
911
|
+
if (!record.seen.includes(s)) missing.push(s);
|
|
912
|
+
}
|
|
913
|
+
return missing;
|
|
914
|
+
}
|
|
915
|
+
var ConversationReceiver = class {
|
|
916
|
+
#ids = /* @__PURE__ */ new Set();
|
|
917
|
+
#records = /* @__PURE__ */ new Map();
|
|
918
|
+
/**
|
|
919
|
+
* One verified delivery (an event, or a Studio test delivery's envelope):
|
|
920
|
+
* what to do with it. The record for its conversation is then `record(id)`.
|
|
921
|
+
*/
|
|
922
|
+
receive(delivery) {
|
|
923
|
+
if (this.#ids.has(delivery.id)) return "duplicate";
|
|
924
|
+
this.#ids.add(delivery.id);
|
|
925
|
+
const data = delivery.data;
|
|
926
|
+
if (data?.test === true) return "skip_test";
|
|
927
|
+
if (typeof data?.conversation_id !== "string") {
|
|
928
|
+
throw new TypeError(`${delivery.id} carries no conversation state`);
|
|
929
|
+
}
|
|
930
|
+
const state = data;
|
|
931
|
+
const { record, action } = applyConversationState(
|
|
932
|
+
this.#records.get(state.conversation_id),
|
|
933
|
+
state
|
|
934
|
+
);
|
|
935
|
+
this.#records.set(state.conversation_id, record);
|
|
936
|
+
return action;
|
|
937
|
+
}
|
|
938
|
+
record(conversationId) {
|
|
939
|
+
return this.#records.get(conversationId);
|
|
940
|
+
}
|
|
941
|
+
conversations() {
|
|
942
|
+
return [...this.#records.keys()].sort();
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
export { CONTRACT, ConversationReceiver, KEEP_SECONDS, LeaseLostError, MAX_REQUEST_BYTES, MemoryIdempotencyStore, ResultTooLargeError, TOOL_OPERATIONS, ToolRefusal, applyConversationState, canonicalJson, createToolProvider, fingerprint, missingSequences, sequenceVerdict };
|
|
947
|
+
//# sourceMappingURL=index.js.map
|
|
948
|
+
//# sourceMappingURL=index.js.map
|