@fayegram-ai/decision-bridge 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/.env.example +10 -0
  2. package/CHANGELOG.md +75 -0
  3. package/CONTRIBUTING.md +57 -0
  4. package/LICENSE +21 -0
  5. package/README.md +307 -2
  6. package/SECURITY.md +88 -0
  7. package/bin/decision.mjs +15 -0
  8. package/docs/README.md +58 -0
  9. package/docs/development/ARCHITECTURE.md +118 -0
  10. package/docs/development/DESIGN.md +66 -0
  11. package/docs/development/README.md +12 -0
  12. package/docs/development/RELEASING.md +100 -0
  13. package/docs/development/REPOSITORY_LAYOUT.md +33 -0
  14. package/docs/development/TESTING.md +131 -0
  15. package/docs/guides/BROWSER.md +43 -0
  16. package/docs/guides/CLOUDFLARE_DECISIONS.md +374 -0
  17. package/docs/guides/CUSTOMIZATION.md +141 -0
  18. package/docs/guides/CUSTOM_DECISION.md +66 -0
  19. package/docs/guides/CUSTOM_GENERATIVE.md +164 -0
  20. package/docs/guides/CUSTOM_PROVIDERS.md +81 -0
  21. package/docs/guides/GETTING_STARTED.md +252 -0
  22. package/docs/guides/OPENAI_DECISIONS.md +210 -0
  23. package/docs/guides/PLATFORMS.md +107 -0
  24. package/docs/guides/SWITCHING_MODELS.md +202 -0
  25. package/docs/guides/TROUBLESHOOTING.md +82 -0
  26. package/docs/reference/CLI.md +202 -0
  27. package/docs/reference/CONFIGURATION.md +168 -0
  28. package/docs/reference/ERRORS.md +82 -0
  29. package/docs/reference/PROVIDERS.md +113 -0
  30. package/docs/reference/SDK.md +388 -0
  31. package/docs/reference/SOURCES.md +51 -0
  32. package/examples/basic.mjs +17 -0
  33. package/examples/batch.mjs +13 -0
  34. package/examples/cloudflare-decisions.mjs +22 -0
  35. package/examples/cloudflare-omni.mjs +32 -0
  36. package/examples/cloudflare-worker.mjs +21 -0
  37. package/examples/cloudflare.native.json +28 -0
  38. package/examples/configs/anthropic.json +9 -0
  39. package/examples/configs/cloudflare-decisions.json +8 -0
  40. package/examples/configs/custom-decision.json +9 -0
  41. package/examples/configs/custom-generative.json +14 -0
  42. package/examples/configs/jev.json +7 -0
  43. package/examples/configs/local.json +12 -0
  44. package/examples/configs/openai-decisions.json +7 -0
  45. package/examples/configs/openai.json +10 -0
  46. package/examples/configs/openrouter.json +10 -0
  47. package/examples/custom/decision-provider.mjs +15 -0
  48. package/examples/custom/generative-provider.mjs +13 -0
  49. package/examples/custom/openrouter.mjs +13 -0
  50. package/examples/customize.mjs +23 -0
  51. package/examples/decisions.native.json +19 -0
  52. package/examples/extended.mjs +43 -0
  53. package/examples/openai-decisions.mjs +20 -0
  54. package/examples/request.json +31 -0
  55. package/examples/requests.jsonl +2 -0
  56. package/package.json +87 -4
  57. package/schemas/decision.config.schema.json +553 -0
  58. package/src/async.mjs +63 -0
  59. package/src/batch.mjs +38 -0
  60. package/src/bridge.mjs +184 -0
  61. package/src/capabilities.mjs +95 -0
  62. package/src/cli/config-validation.mjs +89 -0
  63. package/src/cli/config.mjs +84 -0
  64. package/src/cli/decisions.mjs +19 -0
  65. package/src/cli/initialize.mjs +79 -0
  66. package/src/cli/io.mjs +171 -0
  67. package/src/cli/main.mjs +280 -0
  68. package/src/cli/ordered.mjs +43 -0
  69. package/src/cli/provider-loader.mjs +58 -0
  70. package/src/cloudflare-decisions.d.mts +78 -0
  71. package/src/custom/decision.d.mts +5 -0
  72. package/src/custom/decision.mjs +6 -0
  73. package/src/custom/definition.mjs +19 -0
  74. package/src/custom/generative/anthropic.mjs +75 -0
  75. package/src/custom/generative/index.d.mts +42 -0
  76. package/src/custom/generative/index.mjs +29 -0
  77. package/src/custom/generative/openai-compatible.mjs +101 -0
  78. package/src/custom/generative/schema.mjs +95 -0
  79. package/src/custom/generative/shared.mjs +39 -0
  80. package/src/decisions.d.mts +45 -0
  81. package/src/embedded.mjs +24 -0
  82. package/src/errors.mjs +49 -0
  83. package/src/index.d.mts +229 -0
  84. package/src/index.mjs +7 -0
  85. package/src/input.mjs +37 -0
  86. package/src/options.mjs +15 -0
  87. package/src/policy.mjs +44 -0
  88. package/src/probabilities.mjs +27 -0
  89. package/src/provider-contract.mjs +42 -0
  90. package/src/providers/cloudflare-audio.mjs +83 -0
  91. package/src/providers/cloudflare-codec.mjs +39 -0
  92. package/src/providers/cloudflare-decisions.mjs +81 -0
  93. package/src/providers/cloudflare-images.mjs +95 -0
  94. package/src/providers/cloudflare-media.mjs +36 -0
  95. package/src/providers/cloudflare-preflight.mjs +92 -0
  96. package/src/providers/cloudflare-profile.mjs +102 -0
  97. package/src/providers/cloudflare-transport.mjs +63 -0
  98. package/src/providers/cloudflare-validation.mjs +48 -0
  99. package/src/providers/cloudflare-video.mjs +84 -0
  100. package/src/providers/decisions-codec.mjs +61 -0
  101. package/src/providers/decisions-preflight.mjs +20 -0
  102. package/src/providers/decisions-validation.mjs +150 -0
  103. package/src/providers/index.mjs +26 -0
  104. package/src/providers/jev.mjs +51 -0
  105. package/src/providers/media-binary.mjs +51 -0
  106. package/src/providers/openai-decisions.mjs +51 -0
  107. package/src/questions.mjs +15 -0
  108. package/src/response-transport.mjs +176 -0
  109. package/src/runtime-env.mjs +4 -0
  110. package/src/transport.mjs +51 -0
  111. package/src/validation.mjs +245 -0
  112. package/src/version.mjs +2 -0
@@ -0,0 +1,553 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "Decision Bridge configuration",
4
+ "description": "Explicit schemaVersion 1 configuration. Runtime preflight also enforces URL, credential and provider limits.",
5
+ "oneOf": [
6
+ {
7
+ "type": "object",
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "$schema": {
11
+ "$ref": "#/$defs/text"
12
+ },
13
+ "schemaVersion": {
14
+ "const": 1
15
+ },
16
+ "model": {
17
+ "$ref": "#/$defs/text"
18
+ },
19
+ "apiKeyEnv": {
20
+ "$ref": "#/$defs/keyEnv"
21
+ },
22
+ "timeoutMs": {
23
+ "$ref": "#/$defs/timeout"
24
+ },
25
+ "baseURL": {
26
+ "$ref": "#/$defs/text"
27
+ },
28
+ "maxRetries": {
29
+ "$ref": "#/$defs/retries"
30
+ },
31
+ "maxRequestBytes": {
32
+ "$ref": "#/$defs/bytes"
33
+ },
34
+ "maxResponseBytes": {
35
+ "$ref": "#/$defs/bytes"
36
+ },
37
+ "provider": {
38
+ "const": "jev"
39
+ }
40
+ }
41
+ },
42
+ {
43
+ "type": "object",
44
+ "additionalProperties": false,
45
+ "properties": {
46
+ "$schema": {
47
+ "$ref": "#/$defs/text"
48
+ },
49
+ "schemaVersion": {
50
+ "const": 1
51
+ },
52
+ "model": {
53
+ "$ref": "#/$defs/text"
54
+ },
55
+ "apiKeyEnv": {
56
+ "$ref": "#/$defs/keyEnv"
57
+ },
58
+ "timeoutMs": {
59
+ "$ref": "#/$defs/timeout"
60
+ },
61
+ "baseURL": {
62
+ "$ref": "#/$defs/text"
63
+ },
64
+ "maxRetries": {
65
+ "$ref": "#/$defs/retries"
66
+ },
67
+ "maxRequestBytes": {
68
+ "$ref": "#/$defs/bytes"
69
+ },
70
+ "maxResponseBytes": {
71
+ "$ref": "#/$defs/bytes"
72
+ },
73
+ "provider": {
74
+ "const": "openai-decisions"
75
+ }
76
+ },
77
+ "required": [
78
+ "provider"
79
+ ]
80
+ },
81
+ {
82
+ "type": "object",
83
+ "additionalProperties": false,
84
+ "properties": {
85
+ "$schema": {
86
+ "$ref": "#/$defs/text"
87
+ },
88
+ "schemaVersion": {
89
+ "const": 1
90
+ },
91
+ "model": {
92
+ "$ref": "#/$defs/text"
93
+ },
94
+ "apiKeyEnv": {
95
+ "$ref": "#/$defs/keyEnv"
96
+ },
97
+ "timeoutMs": {
98
+ "$ref": "#/$defs/timeout"
99
+ },
100
+ "baseURL": {
101
+ "$ref": "#/$defs/text"
102
+ },
103
+ "maxRetries": {
104
+ "$ref": "#/$defs/retries"
105
+ },
106
+ "maxRequestBytes": {
107
+ "$ref": "#/$defs/bytes"
108
+ },
109
+ "maxResponseBytes": {
110
+ "$ref": "#/$defs/bytes"
111
+ },
112
+ "provider": {
113
+ "const": "cloudflare-decisions"
114
+ },
115
+ "accountId": {
116
+ "$ref": "#/$defs/text"
117
+ },
118
+ "rejectIfBusy": {
119
+ "$ref": "#/$defs/boolean"
120
+ },
121
+ "gateway": {
122
+ "$ref": "#/$defs/gateway"
123
+ }
124
+ },
125
+ "required": [
126
+ "provider"
127
+ ],
128
+ "allOf": [
129
+ {
130
+ "if": {
131
+ "properties": {
132
+ "rejectIfBusy": {
133
+ "const": true
134
+ }
135
+ },
136
+ "required": [
137
+ "rejectIfBusy"
138
+ ]
139
+ },
140
+ "then": {
141
+ "properties": {
142
+ "maxRetries": {
143
+ "const": 0
144
+ },
145
+ "gateway": {
146
+ "properties": {
147
+ "retries": {
148
+ "properties": {
149
+ "maxAttempts": {
150
+ "const": 1
151
+ }
152
+ }
153
+ }
154
+ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+ ]
160
+ },
161
+ {
162
+ "type": "object",
163
+ "additionalProperties": false,
164
+ "properties": {
165
+ "$schema": {
166
+ "$ref": "#/$defs/text"
167
+ },
168
+ "schemaVersion": {
169
+ "const": 1
170
+ },
171
+ "model": {
172
+ "$ref": "#/$defs/text"
173
+ },
174
+ "apiKeyEnv": {
175
+ "$ref": "#/$defs/keyEnv"
176
+ },
177
+ "timeoutMs": {
178
+ "$ref": "#/$defs/timeout"
179
+ },
180
+ "baseURL": {
181
+ "$ref": "#/$defs/text"
182
+ },
183
+ "maxRetries": {
184
+ "$ref": "#/$defs/retries"
185
+ },
186
+ "maxRequestBytes": {
187
+ "$ref": "#/$defs/bytes"
188
+ },
189
+ "maxResponseBytes": {
190
+ "$ref": "#/$defs/bytes"
191
+ },
192
+ "maxTokens": {
193
+ "$ref": "#/$defs/tokens"
194
+ },
195
+ "responseFormat": {
196
+ "$ref": "#/$defs/responseFormat"
197
+ },
198
+ "tokenParameter": {
199
+ "$ref": "#/$defs/tokenParameter"
200
+ },
201
+ "parameters": {
202
+ "$ref": "#/$defs/parameters"
203
+ },
204
+ "provider": {
205
+ "const": "custom-generative"
206
+ },
207
+ "preset": {
208
+ "const": "openai-compatible"
209
+ },
210
+ "noAuth": {
211
+ "$ref": "#/$defs/boolean"
212
+ }
213
+ },
214
+ "required": [
215
+ "provider",
216
+ "preset"
217
+ ]
218
+ },
219
+ {
220
+ "type": "object",
221
+ "additionalProperties": false,
222
+ "properties": {
223
+ "$schema": {
224
+ "$ref": "#/$defs/text"
225
+ },
226
+ "schemaVersion": {
227
+ "const": 1
228
+ },
229
+ "model": {
230
+ "$ref": "#/$defs/text"
231
+ },
232
+ "apiKeyEnv": {
233
+ "$ref": "#/$defs/keyEnv"
234
+ },
235
+ "timeoutMs": {
236
+ "$ref": "#/$defs/timeout"
237
+ },
238
+ "maxRetries": {
239
+ "$ref": "#/$defs/retries"
240
+ },
241
+ "maxRequestBytes": {
242
+ "$ref": "#/$defs/bytes"
243
+ },
244
+ "maxResponseBytes": {
245
+ "$ref": "#/$defs/bytes"
246
+ },
247
+ "maxTokens": {
248
+ "$ref": "#/$defs/tokens"
249
+ },
250
+ "responseFormat": {
251
+ "$ref": "#/$defs/responseFormat"
252
+ },
253
+ "tokenParameter": {
254
+ "$ref": "#/$defs/tokenParameter"
255
+ },
256
+ "parameters": {
257
+ "$ref": "#/$defs/parameters"
258
+ },
259
+ "provider": {
260
+ "const": "custom-generative"
261
+ },
262
+ "preset": {
263
+ "const": "openrouter"
264
+ }
265
+ },
266
+ "required": [
267
+ "provider",
268
+ "preset"
269
+ ]
270
+ },
271
+ {
272
+ "type": "object",
273
+ "additionalProperties": false,
274
+ "properties": {
275
+ "$schema": {
276
+ "$ref": "#/$defs/text"
277
+ },
278
+ "schemaVersion": {
279
+ "const": 1
280
+ },
281
+ "model": {
282
+ "$ref": "#/$defs/text"
283
+ },
284
+ "apiKeyEnv": {
285
+ "$ref": "#/$defs/keyEnv"
286
+ },
287
+ "timeoutMs": {
288
+ "$ref": "#/$defs/timeout"
289
+ },
290
+ "baseURL": {
291
+ "$ref": "#/$defs/text"
292
+ },
293
+ "maxRetries": {
294
+ "$ref": "#/$defs/retries"
295
+ },
296
+ "maxRequestBytes": {
297
+ "$ref": "#/$defs/bytes"
298
+ },
299
+ "maxResponseBytes": {
300
+ "$ref": "#/$defs/bytes"
301
+ },
302
+ "maxTokens": {
303
+ "$ref": "#/$defs/tokens"
304
+ },
305
+ "parameters": {
306
+ "allOf": [
307
+ {
308
+ "$ref": "#/$defs/parameters"
309
+ },
310
+ {
311
+ "properties": {
312
+ "thinking": false
313
+ }
314
+ }
315
+ ]
316
+ },
317
+ "provider": {
318
+ "const": "custom-generative"
319
+ },
320
+ "preset": {
321
+ "const": "anthropic"
322
+ }
323
+ },
324
+ "required": [
325
+ "provider",
326
+ "preset"
327
+ ]
328
+ },
329
+ {
330
+ "type": "object",
331
+ "additionalProperties": false,
332
+ "properties": {
333
+ "$schema": {
334
+ "$ref": "#/$defs/text"
335
+ },
336
+ "schemaVersion": {
337
+ "const": 1
338
+ },
339
+ "model": {
340
+ "$ref": "#/$defs/text"
341
+ },
342
+ "apiKeyEnv": {
343
+ "$ref": "#/$defs/keyEnv"
344
+ },
345
+ "timeoutMs": {
346
+ "$ref": "#/$defs/timeout"
347
+ },
348
+ "provider": {
349
+ "const": "custom-decision"
350
+ },
351
+ "module": {
352
+ "$ref": "#/$defs/text"
353
+ },
354
+ "options": {
355
+ "$ref": "#/$defs/options"
356
+ }
357
+ },
358
+ "required": [
359
+ "provider",
360
+ "module"
361
+ ]
362
+ },
363
+ {
364
+ "type": "object",
365
+ "additionalProperties": false,
366
+ "properties": {
367
+ "$schema": {
368
+ "$ref": "#/$defs/text"
369
+ },
370
+ "schemaVersion": {
371
+ "const": 1
372
+ },
373
+ "model": {
374
+ "$ref": "#/$defs/text"
375
+ },
376
+ "apiKeyEnv": {
377
+ "$ref": "#/$defs/keyEnv"
378
+ },
379
+ "timeoutMs": {
380
+ "$ref": "#/$defs/timeout"
381
+ },
382
+ "provider": {
383
+ "const": "custom-generative"
384
+ },
385
+ "module": {
386
+ "$ref": "#/$defs/text"
387
+ },
388
+ "options": {
389
+ "$ref": "#/$defs/options"
390
+ }
391
+ },
392
+ "required": [
393
+ "provider",
394
+ "module"
395
+ ]
396
+ }
397
+ ],
398
+ "$defs": {
399
+ "text": {
400
+ "type": "string",
401
+ "minLength": 1,
402
+ "pattern": "\\S"
403
+ },
404
+ "keyEnv": {
405
+ "type": "string",
406
+ "pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
407
+ },
408
+ "timeout": {
409
+ "type": "integer",
410
+ "minimum": 1,
411
+ "maximum": 2147483647
412
+ },
413
+ "bytes": {
414
+ "type": "integer",
415
+ "minimum": 1,
416
+ "maximum": 67108864
417
+ },
418
+ "retries": {
419
+ "type": "integer",
420
+ "minimum": 0,
421
+ "maximum": 10
422
+ },
423
+ "tokens": {
424
+ "type": "integer",
425
+ "minimum": 1,
426
+ "maximum": 1048576
427
+ },
428
+ "boolean": {
429
+ "type": "boolean"
430
+ },
431
+ "responseFormat": {
432
+ "enum": [
433
+ "json_schema",
434
+ "json_object",
435
+ "text"
436
+ ]
437
+ },
438
+ "tokenParameter": {
439
+ "enum": [
440
+ "max_completion_tokens",
441
+ "max_tokens"
442
+ ]
443
+ },
444
+ "parameters": {
445
+ "type": "object",
446
+ "properties": {
447
+ "model": false,
448
+ "messages": false,
449
+ "system": false,
450
+ "tools": false,
451
+ "tool_choice": false,
452
+ "toolChoice": false,
453
+ "functions": false,
454
+ "function_call": false,
455
+ "response_format": false,
456
+ "stream": false,
457
+ "stream_options": false,
458
+ "n": false,
459
+ "max_tokens": false,
460
+ "max_completion_tokens": false,
461
+ "headers": false,
462
+ "apiKey": false,
463
+ "api_key": false,
464
+ "baseURL": false,
465
+ "authorization": false
466
+ }
467
+ },
468
+ "options": {
469
+ "type": "object",
470
+ "properties": {
471
+ "apiKey": false,
472
+ "api_key": false
473
+ }
474
+ },
475
+ "gateway": {
476
+ "type": "object",
477
+ "additionalProperties": false,
478
+ "required": [
479
+ "id"
480
+ ],
481
+ "properties": {
482
+ "id": {
483
+ "type": "string",
484
+ "minLength": 1,
485
+ "maxLength": 64,
486
+ "pattern": "^[\\x20-\\x7e]*\\S[\\x20-\\x7e]*$"
487
+ },
488
+ "cacheKey": {
489
+ "type": "string",
490
+ "minLength": 1,
491
+ "maxLength": 200,
492
+ "pattern": "^[\\x20-\\x7e]*\\S[\\x20-\\x7e]*$"
493
+ },
494
+ "cacheTtl": {
495
+ "type": "integer",
496
+ "minimum": 60,
497
+ "maximum": 2592000
498
+ },
499
+ "skipCache": {
500
+ "type": "boolean"
501
+ },
502
+ "collectLog": {
503
+ "type": "boolean"
504
+ },
505
+ "requestTimeoutMs": {
506
+ "type": "integer",
507
+ "minimum": 1,
508
+ "maximum": 2147483647
509
+ },
510
+ "metadata": {
511
+ "type": "object",
512
+ "maxProperties": 5,
513
+ "propertyNames": {
514
+ "not": {
515
+ "pattern": "^cf\\."
516
+ }
517
+ },
518
+ "additionalProperties": {
519
+ "type": [
520
+ "string",
521
+ "number",
522
+ "boolean",
523
+ "null"
524
+ ]
525
+ }
526
+ },
527
+ "retries": {
528
+ "type": "object",
529
+ "additionalProperties": false,
530
+ "properties": {
531
+ "maxAttempts": {
532
+ "type": "integer",
533
+ "minimum": 1,
534
+ "maximum": 5
535
+ },
536
+ "retryDelayMs": {
537
+ "type": "integer",
538
+ "minimum": 0,
539
+ "maximum": 60000
540
+ },
541
+ "backoff": {
542
+ "enum": [
543
+ "constant",
544
+ "linear",
545
+ "exponential"
546
+ ]
547
+ }
548
+ }
549
+ }
550
+ }
551
+ }
552
+ }
553
+ }
package/src/async.mjs ADDED
@@ -0,0 +1,63 @@
1
+ import { DecisionError, requireCondition as check } from './errors.mjs';
2
+
3
+ // Carry absolute deadlines through our nested AbortSignals without changing their public shape.
4
+ const signalDeadlines = new WeakMap();
5
+
6
+ export function validateSignal(signal) {
7
+ check(signal === undefined || signal instanceof AbortSignal,
8
+ 'signal must be an AbortSignal.', 'CONFIGURATION_ERROR');
9
+ }
10
+
11
+ export function abortError() {
12
+ return new DecisionError('Request cancelled by caller.', { code: 'ABORTED' });
13
+ }
14
+
15
+ /** Bound caller-visible waiting even when user-supplied code ignores the signal. */
16
+ export function withSignal(promise, signal) {
17
+ if (!signal) return Promise.resolve(promise);
18
+ if (signal.aborted) {
19
+ Promise.resolve(promise).catch(() => {});
20
+ return Promise.reject(abortError());
21
+ }
22
+ return new Promise((resolve, reject) => {
23
+ const aborted = () => { cleanup(); reject(abortError()); };
24
+ const cleanup = () => signal.removeEventListener('abort', aborted);
25
+ signal.addEventListener('abort', aborted, { once: true });
26
+ Promise.resolve(promise).then(
27
+ value => { cleanup(); resolve(value); },
28
+ error => { cleanup(); reject(error); },
29
+ );
30
+ });
31
+ }
32
+
33
+ export function deadline(timeoutMs, parent) {
34
+ validateSignal(parent);
35
+ check(Number.isSafeInteger(timeoutMs) && timeoutMs > 0 && timeoutMs <= 2147483647,
36
+ 'timeoutMs must be a positive 32-bit integer.', 'CONFIGURATION_ERROR');
37
+ const controller = new AbortController();
38
+ const expiresAt = Math.min(performance.now() + timeoutMs, signalDeadlines.get(parent) ?? Infinity);
39
+ signalDeadlines.set(controller.signal, expiresAt);
40
+ let timedOut = false;
41
+ let closed = false;
42
+ const expire = () => { timedOut = true; controller.abort(); };
43
+ // Timers cannot fire during synchronous validation, encoding or user callbacks.
44
+ const refresh = () => {
45
+ if (!closed && !controller.signal.aborted && performance.now() >= expiresAt) expire();
46
+ };
47
+ const cancel = () => controller.abort();
48
+ if (parent?.aborted) cancel();
49
+ parent?.addEventListener('abort', cancel, { once: true });
50
+ const timer = setTimeout(expire, Math.max(0, Math.ceil(expiresAt - performance.now())));
51
+ return {
52
+ signal: controller.signal,
53
+ get timedOut() { refresh(); return timedOut; },
54
+ get remainingMs() { return Math.max(0, expiresAt - performance.now()); },
55
+ throwIfAborted() { refresh(); if (controller.signal.aborted) throw abortError(); },
56
+ close() {
57
+ closed = true;
58
+ clearTimeout(timer);
59
+ signalDeadlines.delete(controller.signal);
60
+ parent?.removeEventListener('abort', cancel);
61
+ },
62
+ };
63
+ }
package/src/batch.mjs ADDED
@@ -0,0 +1,38 @@
1
+ import { optionsObject } from './options.mjs';
2
+ import { requireCondition as check, publicError } from './errors.mjs';
3
+ import { validateRequest } from './validation.mjs';
4
+ import { validateSignal } from './async.mjs';
5
+ import { assertCapabilities, LEGACY_CAPABILITIES } from './capabilities.mjs';
6
+
7
+ /** Ordered, bounded-concurrency batch. Validate every input before spending. */
8
+ export async function evaluateBatch(bridge, inputs, options = {}) {
9
+ const { concurrency = 4, signal, stopOnError = false } = optionsObject(options, ['concurrency', 'signal', 'stopOnError'], 'Batch options');
10
+ check(bridge && typeof bridge.evaluate === 'function', 'A bridge is required.', 'CONFIGURATION_ERROR');
11
+ check(Array.isArray(inputs) && inputs.length <= 10000, 'inputs must be an array of at most 10000 requests.');
12
+ check(Number.isSafeInteger(concurrency) && concurrency >= 1 && concurrency <= 64,
13
+ 'concurrency must be an integer from 1 to 64.', 'CONFIGURATION_ERROR');
14
+ check(typeof stopOnError === 'boolean', 'stopOnError must be boolean.', 'CONFIGURATION_ERROR');
15
+ validateSignal(signal);
16
+ const requests = Array.from(inputs, input => typeof bridge.validate === 'function' ? bridge.validate(input)
17
+ : assertCapabilities(validateRequest(input, bridge.defaultModel ?? ''), LEGACY_CAPABILITIES));
18
+ const results = new Array(requests.length);
19
+ let next = 0;
20
+ let stopped = false;
21
+ async function worker() {
22
+ while (next < requests.length) {
23
+ if (signal?.aborted || stopped) return;
24
+ const index = next++;
25
+ try { results[index] = { index, ok: true, result: await bridge.evaluate(requests[index], { signal }) }; }
26
+ catch (error) {
27
+ results[index] = { index, ok: false, error: publicError(error) };
28
+ if (stopOnError) stopped = true;
29
+ }
30
+ }
31
+ }
32
+ await Promise.all(Array.from({ length: Math.min(concurrency, requests.length) }, worker));
33
+ for (let index = 0; index < results.length; index++) {
34
+ results[index] ??= { index, ok: false, error: { code: signal?.aborted ? 'ABORTED' : 'BATCH_SKIPPED',
35
+ message: signal?.aborted ? 'Request cancelled before dispatch.' : 'Request skipped after a batch failure.' } };
36
+ }
37
+ return results;
38
+ }