@skyold/model-protocol 0.1.0-beta.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 (104) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +22 -0
  3. package/README.zh-CN.md +17 -0
  4. package/dist/chat-egress.d.ts +12 -0
  5. package/dist/chat-egress.d.ts.map +1 -0
  6. package/dist/chat-egress.js +144 -0
  7. package/dist/chat-egress.js.map +1 -0
  8. package/dist/chat-ingress.d.ts +39 -0
  9. package/dist/chat-ingress.d.ts.map +1 -0
  10. package/dist/chat-ingress.js +323 -0
  11. package/dist/chat-ingress.js.map +1 -0
  12. package/dist/chat-stream-egress.d.ts +8 -0
  13. package/dist/chat-stream-egress.d.ts.map +1 -0
  14. package/dist/chat-stream-egress.js +129 -0
  15. package/dist/chat-stream-egress.js.map +1 -0
  16. package/dist/contracts.d.ts +61 -0
  17. package/dist/contracts.d.ts.map +1 -0
  18. package/dist/contracts.js +2 -0
  19. package/dist/contracts.js.map +1 -0
  20. package/dist/egress-codec-registry.d.ts +36 -0
  21. package/dist/egress-codec-registry.d.ts.map +1 -0
  22. package/dist/egress-codec-registry.js +125 -0
  23. package/dist/egress-codec-registry.js.map +1 -0
  24. package/dist/index.d.ts +39 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +25 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/ingress-codec-registry.d.ts +26 -0
  29. package/dist/ingress-codec-registry.d.ts.map +1 -0
  30. package/dist/ingress-codec-registry.js +106 -0
  31. package/dist/ingress-codec-registry.js.map +1 -0
  32. package/dist/media-images.d.ts +154 -0
  33. package/dist/media-images.d.ts.map +1 -0
  34. package/dist/media-images.js +721 -0
  35. package/dist/media-images.js.map +1 -0
  36. package/dist/media-video.d.ts +68 -0
  37. package/dist/media-video.d.ts.map +1 -0
  38. package/dist/media-video.js +196 -0
  39. package/dist/media-video.js.map +1 -0
  40. package/dist/native-images.d.ts +68 -0
  41. package/dist/native-images.d.ts.map +1 -0
  42. package/dist/native-images.js +424 -0
  43. package/dist/native-images.js.map +1 -0
  44. package/dist/openai-embeddings-egress.d.ts +4 -0
  45. package/dist/openai-embeddings-egress.d.ts.map +1 -0
  46. package/dist/openai-embeddings-egress.js +21 -0
  47. package/dist/openai-embeddings-egress.js.map +1 -0
  48. package/dist/openai-embeddings.d.ts +66 -0
  49. package/dist/openai-embeddings.d.ts.map +1 -0
  50. package/dist/openai-embeddings.js +442 -0
  51. package/dist/openai-embeddings.js.map +1 -0
  52. package/dist/openai-responses-egress.d.ts +17 -0
  53. package/dist/openai-responses-egress.d.ts.map +1 -0
  54. package/dist/openai-responses-egress.js +99 -0
  55. package/dist/openai-responses-egress.js.map +1 -0
  56. package/dist/openai-responses-stream-egress.d.ts +4 -0
  57. package/dist/openai-responses-stream-egress.d.ts.map +1 -0
  58. package/dist/openai-responses-stream-egress.js +252 -0
  59. package/dist/openai-responses-stream-egress.js.map +1 -0
  60. package/dist/openai-responses.d.ts +15 -0
  61. package/dist/openai-responses.d.ts.map +1 -0
  62. package/dist/openai-responses.js +363 -0
  63. package/dist/openai-responses.js.map +1 -0
  64. package/dist/provider-source.d.ts +20 -0
  65. package/dist/provider-source.d.ts.map +1 -0
  66. package/dist/provider-source.js +41 -0
  67. package/dist/provider-source.js.map +1 -0
  68. package/dist/provider-transport.d.ts +5 -0
  69. package/dist/provider-transport.d.ts.map +1 -0
  70. package/dist/provider-transport.js +114 -0
  71. package/dist/provider-transport.js.map +1 -0
  72. package/dist/runtime-v2-validation.d.ts +16 -0
  73. package/dist/runtime-v2-validation.d.ts.map +1 -0
  74. package/dist/runtime-v2-validation.js +915 -0
  75. package/dist/runtime-v2-validation.js.map +1 -0
  76. package/dist/runtime-v2.d.ts +462 -0
  77. package/dist/runtime-v2.d.ts.map +1 -0
  78. package/dist/runtime-v2.js +56 -0
  79. package/dist/runtime-v2.js.map +1 -0
  80. package/dist/tokenforge-task-projection.d.ts +8 -0
  81. package/dist/tokenforge-task-projection.d.ts.map +1 -0
  82. package/dist/tokenforge-task-projection.js +247 -0
  83. package/dist/tokenforge-task-projection.js.map +1 -0
  84. package/dist/tokenforge-tasks.d.ts +241 -0
  85. package/dist/tokenforge-tasks.d.ts.map +1 -0
  86. package/dist/tokenforge-tasks.js +1132 -0
  87. package/dist/tokenforge-tasks.js.map +1 -0
  88. package/dist/usage-reservation.d.ts +11 -0
  89. package/dist/usage-reservation.d.ts.map +1 -0
  90. package/dist/usage-reservation.js +40 -0
  91. package/dist/usage-reservation.js.map +1 -0
  92. package/dist/validation.d.ts +22 -0
  93. package/dist/validation.d.ts.map +1 -0
  94. package/dist/validation.js +415 -0
  95. package/dist/validation.js.map +1 -0
  96. package/dist/video-ingress.d.ts +31 -0
  97. package/dist/video-ingress.d.ts.map +1 -0
  98. package/dist/video-ingress.js +246 -0
  99. package/dist/video-ingress.js.map +1 -0
  100. package/dist/volc-video-task-codec.d.ts +59 -0
  101. package/dist/volc-video-task-codec.d.ts.map +1 -0
  102. package/dist/volc-video-task-codec.js +123 -0
  103. package/dist/volc-video-task-codec.js.map +1 -0
  104. package/package.json +54 -0
@@ -0,0 +1,1132 @@
1
+ import { embeddingCapabilityFailure } from './openai-embeddings.js';
2
+ import { OPENAI_IMAGES_PROTOCOL, VOLC_IMAGES_PROTOCOL, parseImageGenerationCapabilityV1, } from './media-images.js';
3
+ import { parseVideoGenerationCapabilityV1 } from './media-video.js';
4
+ export const TOKENFORGE_TASKS_API_VERSION = '2026-07-13';
5
+ export const TOKENFORGE_TASKS_V2_API_VERSION = '2026-07-19';
6
+ export const TOKENFORGE_TASKS_PROTOCOL = 'tokenforge-tasks/v1';
7
+ /**
8
+ * P1C compatibility contract: the persisted and synchronized `supportsResponses` flag is an
9
+ * exclusive Task chat-family selector, not an additive statement that publishes both families.
10
+ * This keeps Gateway and Server target snapshots deterministic without introducing a new seam.
11
+ */
12
+ export function resolveDeclaredOpenAiChatProtocolFamily(input) {
13
+ return input.supportsResponses ? 'openai-responses/v1' : 'openai-chat/v1';
14
+ }
15
+ const MAX_CLIENT_REQUEST_ID_CHARS = 128;
16
+ const MAX_MESSAGES = 128;
17
+ const MAX_CONTENT_PARTS = 64;
18
+ const MAX_EMBEDDING_BATCH_ITEMS = 2_048;
19
+ const MAX_FUNCTION_IDENTIFIER_CHARS = 128;
20
+ const MAX_FUNCTION_ARGUMENT_BYTES = 65_536;
21
+ const MAX_JSON_SCHEMA_DEPTH = 16;
22
+ const MAX_JSON_SCHEMA_NODES = 1_024;
23
+ const MAX_JSON_SCHEMA_BYTES = 65_536;
24
+ const MAX_V2_BODY_BYTES = 256 * 1024;
25
+ const MAX_PROVIDER_OPTIONS_BYTES = 16 * 1024;
26
+ const MAX_PROMPT_BYTES = 32 * 1024;
27
+ const MAX_MEDIA_URL_CHARS = 4_096;
28
+ const MAX_OBJECT_KEY_CHARS = 1_024;
29
+ const MAX_VIDEO_TEXT_BYTES = 64 * 1024;
30
+ const UTF8_ENCODER = new TextEncoder();
31
+ const TOP_LEVEL_FIELDS = new Set([
32
+ 'apiVersion',
33
+ 'clientRequestId',
34
+ 'model',
35
+ 'task',
36
+ 'providerOptions',
37
+ ]);
38
+ const CHAT_TASK_FIELDS = new Set([
39
+ 'kind',
40
+ 'mode',
41
+ 'input',
42
+ 'maxOutputTokens',
43
+ 'temperature',
44
+ 'tools',
45
+ 'toolChoice',
46
+ ]);
47
+ const EMBEDDING_TASK_FIELDS = new Set(['kind', 'mode', 'input', 'dimensions', 'encodingFormat']);
48
+ const IMAGE_GENERATE_TASK_FIELDS = new Set([
49
+ 'kind',
50
+ 'mode',
51
+ 'operation',
52
+ 'prompt',
53
+ 'count',
54
+ 'size',
55
+ 'quality',
56
+ 'outputFormat',
57
+ 'responseFormat',
58
+ ]);
59
+ const IMAGE_EDIT_TASK_FIELDS = new Set([...IMAGE_GENERATE_TASK_FIELDS, 'images', 'mask']);
60
+ const VIDEO_TASK_FIELDS = new Set([
61
+ 'kind',
62
+ 'mode',
63
+ 'input',
64
+ 'durationSeconds',
65
+ 'aspectRatio',
66
+ 'size',
67
+ 'returnLastFrame',
68
+ 'callbackUrl',
69
+ ]);
70
+ const REASONING_EFFORTS = new Set([
71
+ 'none',
72
+ 'minimal',
73
+ 'low',
74
+ 'medium',
75
+ 'high',
76
+ 'xhigh',
77
+ ]);
78
+ export class TaskStreamEgressError extends Error {
79
+ code;
80
+ name = 'TaskStreamEgressError';
81
+ constructor(code) {
82
+ super(`TokenForge Task stream rejected: ${code}`);
83
+ this.code = code;
84
+ }
85
+ }
86
+ export class TokenforgeTaskRequestError extends Error {
87
+ code;
88
+ status;
89
+ name = 'TokenforgeTaskRequestError';
90
+ constructor(code, status) {
91
+ super(`TokenForge Task request rejected: ${code}`);
92
+ this.code = code;
93
+ this.status = status;
94
+ }
95
+ }
96
+ const TASK_STREAM_TERMINAL_TYPES = new Set([
97
+ 'response.incomplete',
98
+ 'response.completed',
99
+ 'response.failed',
100
+ ]);
101
+ const requiredField = (validate) => ({ validate });
102
+ const optionalField = (validate) => ({
103
+ validate,
104
+ optional: true,
105
+ });
106
+ const isString = (value) => typeof value === 'string';
107
+ const isNonEmptyString = (value) => typeof value === 'string' && value.length > 0;
108
+ const isBoolean = (value) => typeof value === 'boolean';
109
+ const isNonNegativeSafeInteger = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
110
+ function matchesRuntimeSchema(value, schema) {
111
+ if (!isPlainRecord(value) ||
112
+ Object.keys(value).some((key) => !Object.prototype.hasOwnProperty.call(schema, key))) {
113
+ return false;
114
+ }
115
+ return Object.entries(schema).every(([key, rule]) => {
116
+ if (!Object.prototype.hasOwnProperty.call(value, key)) {
117
+ return rule.optional === true;
118
+ }
119
+ return rule.validate(value[key]);
120
+ });
121
+ }
122
+ function isContentPart(value) {
123
+ if (!isPlainRecord(value)) {
124
+ return false;
125
+ }
126
+ if (value['type'] === 'text') {
127
+ return matchesRuntimeSchema(value, {
128
+ type: requiredField((item) => item === 'text'),
129
+ text: requiredField(isString),
130
+ });
131
+ }
132
+ if (value['type'] === 'refusal') {
133
+ return matchesRuntimeSchema(value, {
134
+ type: requiredField((item) => item === 'refusal'),
135
+ text: requiredField(isString),
136
+ });
137
+ }
138
+ if (value['type'] === 'reasoning_summary') {
139
+ return matchesRuntimeSchema(value, {
140
+ type: requiredField((item) => item === 'reasoning_summary'),
141
+ text: requiredField(isString),
142
+ });
143
+ }
144
+ return false;
145
+ }
146
+ function isOutputItem(value) {
147
+ if (!isPlainRecord(value)) {
148
+ return false;
149
+ }
150
+ if (value['type'] === 'message') {
151
+ return matchesRuntimeSchema(value, {
152
+ type: requiredField((item) => item === 'message'),
153
+ content: requiredField((item) => Array.isArray(item) && item.every((part) => isContentPart(part))),
154
+ });
155
+ }
156
+ if (value['type'] === 'function_call') {
157
+ return matchesRuntimeSchema(value, {
158
+ type: requiredField((item) => item === 'function_call'),
159
+ callId: requiredField(isNonEmptyString),
160
+ name: requiredField(isNonEmptyString),
161
+ arguments: requiredField(isString),
162
+ });
163
+ }
164
+ return false;
165
+ }
166
+ function isTokenDetails(value, field) {
167
+ return matchesRuntimeSchema(value, {
168
+ [field]: optionalField(isNonNegativeSafeInteger),
169
+ });
170
+ }
171
+ function isUsage(value) {
172
+ return matchesRuntimeSchema(value, {
173
+ inputTokens: optionalField(isNonNegativeSafeInteger),
174
+ outputTokens: optionalField(isNonNegativeSafeInteger),
175
+ totalTokens: optionalField(isNonNegativeSafeInteger),
176
+ cachedInputTokens: optionalField(isNonNegativeSafeInteger),
177
+ cacheCreationInputTokens: optionalField(isNonNegativeSafeInteger),
178
+ reasoningOutputTokens: optionalField(isNonNegativeSafeInteger),
179
+ inputTokenDetails: optionalField((item) => isTokenDetails(item, 'cachedTokens')),
180
+ outputTokenDetails: optionalField((item) => isTokenDetails(item, 'reasoningTokens')),
181
+ vectorCount: optionalField(isNonNegativeSafeInteger),
182
+ dimensions: optionalField(isNonNegativeSafeInteger),
183
+ encodingFormat: optionalField((item) => item === 'float' || item === 'base64'),
184
+ spaceId: optionalField(isNonEmptyString),
185
+ estimated: requiredField(isBoolean),
186
+ });
187
+ }
188
+ function isIncompleteDetails(value) {
189
+ return matchesRuntimeSchema(value, {
190
+ reason: requiredField(isNonEmptyString),
191
+ });
192
+ }
193
+ function isProviderError(value) {
194
+ return matchesRuntimeSchema(value, {
195
+ code: requiredField(isNonEmptyString),
196
+ message: optionalField(isString),
197
+ });
198
+ }
199
+ function isStreamError(value) {
200
+ return matchesRuntimeSchema(value, {
201
+ code: requiredField(isNonEmptyString),
202
+ category: requiredField(isNonEmptyString),
203
+ retryable: requiredField(isBoolean),
204
+ });
205
+ }
206
+ const BASE_STREAM_EVENT_SCHEMA = {
207
+ type: requiredField(isNonEmptyString),
208
+ eventId: requiredField(isNonEmptyString),
209
+ sequenceNumber: requiredField(isNonNegativeSafeInteger),
210
+ responseId: requiredField(isNonEmptyString),
211
+ };
212
+ const INDEXED_ITEM_SCHEMA = {
213
+ itemId: requiredField(isNonEmptyString),
214
+ outputIndex: requiredField(isNonNegativeSafeInteger),
215
+ };
216
+ const INDEXED_CONTENT_SCHEMA = {
217
+ ...INDEXED_ITEM_SCHEMA,
218
+ contentIndex: requiredField(isNonNegativeSafeInteger),
219
+ };
220
+ const TASK_STREAM_EVENT_SCHEMAS = {
221
+ 'response.started': {},
222
+ 'response.in_progress': {
223
+ status: requiredField((value) => value === 'in_progress'),
224
+ },
225
+ 'output.item.added': {
226
+ ...INDEXED_ITEM_SCHEMA,
227
+ itemKind: requiredField((value) => value === 'message' || value === 'reasoning' || value === 'function_call'),
228
+ item: requiredField(isOutputItem),
229
+ },
230
+ 'output.text.delta': {
231
+ ...INDEXED_CONTENT_SCHEMA,
232
+ delta: requiredField(isString),
233
+ },
234
+ 'output.refusal.delta': {
235
+ ...INDEXED_CONTENT_SCHEMA,
236
+ delta: requiredField(isString),
237
+ },
238
+ 'output.tool_call.delta': {
239
+ ...INDEXED_ITEM_SCHEMA,
240
+ callId: requiredField(isNonEmptyString),
241
+ name: optionalField(isNonEmptyString),
242
+ argumentsDelta: requiredField(isString),
243
+ },
244
+ 'output.reasoning_summary.delta': {
245
+ ...INDEXED_CONTENT_SCHEMA,
246
+ delta: requiredField(isString),
247
+ },
248
+ 'output.content_part.added': {
249
+ ...INDEXED_CONTENT_SCHEMA,
250
+ part: requiredField(isContentPart),
251
+ },
252
+ 'output.content_part.completed': {
253
+ ...INDEXED_CONTENT_SCHEMA,
254
+ part: requiredField(isContentPart),
255
+ },
256
+ 'output.item.completed': {
257
+ ...INDEXED_ITEM_SCHEMA,
258
+ itemKind: requiredField((value) => value === 'message' || value === 'reasoning' || value === 'function_call'),
259
+ status: requiredField((value) => value === 'completed' || value === 'incomplete'),
260
+ item: requiredField(isOutputItem),
261
+ },
262
+ 'output.text.completed': {
263
+ ...INDEXED_CONTENT_SCHEMA,
264
+ text: requiredField(isString),
265
+ },
266
+ 'output.refusal.completed': {
267
+ ...INDEXED_CONTENT_SCHEMA,
268
+ refusal: requiredField(isString),
269
+ },
270
+ 'output.tool_call.completed': {
271
+ ...INDEXED_ITEM_SCHEMA,
272
+ callId: requiredField(isNonEmptyString),
273
+ name: requiredField(isNonEmptyString),
274
+ arguments: requiredField(isString),
275
+ },
276
+ 'output.reasoning_summary.completed': {
277
+ ...INDEXED_CONTENT_SCHEMA,
278
+ text: requiredField(isString),
279
+ },
280
+ 'usage.updated': {
281
+ usage: requiredField(isUsage),
282
+ },
283
+ 'response.incomplete': {
284
+ reason: requiredField(isNonEmptyString),
285
+ status: optionalField((value) => value === 'incomplete'),
286
+ incompleteDetails: optionalField(isIncompleteDetails),
287
+ usage: optionalField(isUsage),
288
+ },
289
+ 'response.completed': {
290
+ status: optionalField((value) => value === 'completed'),
291
+ usage: optionalField(isUsage),
292
+ },
293
+ 'response.failed': {
294
+ status: optionalField((value) => value === 'failed'),
295
+ error: requiredField(isStreamError),
296
+ providerError: optionalField(isProviderError),
297
+ usage: optionalField(isUsage),
298
+ },
299
+ };
300
+ export function createTaskStreamEventBuilderV1(input) {
301
+ let terminalEmitted = false;
302
+ return {
303
+ append(event) {
304
+ if (!isTaskStreamEventPayload(event)) {
305
+ throw new TaskStreamEgressError('invalid_task_stream_event');
306
+ }
307
+ if (terminalEmitted) {
308
+ throw new TaskStreamEgressError('task_stream_already_terminated');
309
+ }
310
+ if (TASK_STREAM_TERMINAL_TYPES.has(event.type)) {
311
+ terminalEmitted = true;
312
+ }
313
+ return {
314
+ event: 'task.event',
315
+ data: {
316
+ apiVersion: TOKENFORGE_TASKS_API_VERSION,
317
+ requestId: input.requestId,
318
+ taskId: input.taskId,
319
+ sequenceNumber: event.sequenceNumber,
320
+ event,
321
+ },
322
+ };
323
+ },
324
+ finish() {
325
+ if (!terminalEmitted) {
326
+ throw new TaskStreamEgressError('task_stream_terminal_required');
327
+ }
328
+ },
329
+ };
330
+ }
331
+ function isTaskStreamEventPayload(event) {
332
+ if (!isPlainRecord(event) || typeof event['type'] !== 'string') {
333
+ return false;
334
+ }
335
+ if (!Object.prototype.hasOwnProperty.call(TASK_STREAM_EVENT_SCHEMAS, event['type'])) {
336
+ return false;
337
+ }
338
+ const schema = TASK_STREAM_EVENT_SCHEMAS[event['type']];
339
+ return matchesRuntimeSchema(event, { ...BASE_STREAM_EVENT_SCHEMA, ...schema });
340
+ }
341
+ export function decodeTokenforgeTaskRequest(body) {
342
+ const request = requireRecord(body, 'body_required');
343
+ if (request['apiVersion'] === TOKENFORGE_TASKS_API_VERSION) {
344
+ return decodeTokenforgeTaskRequestV1(request);
345
+ }
346
+ if (request['apiVersion'] === TOKENFORGE_TASKS_V2_API_VERSION) {
347
+ return decodeTokenforgeTaskRequestV2(request);
348
+ }
349
+ reject('unsupported_api_version', 400);
350
+ }
351
+ function decodeTokenforgeTaskRequestV1(request) {
352
+ assertKnownFields(request, TOP_LEVEL_FIELDS);
353
+ const model = requiredString(request['model'], 'model_required');
354
+ const clientRequestId = decodeClientRequestId(request['clientRequestId']);
355
+ const providerOptions = decodeProviderOptions(request['providerOptions'], false);
356
+ const task = requireRecord(request['task'], 'unsupported_task');
357
+ const kind = task['kind'];
358
+ if (kind === 'chat')
359
+ return decodeChatTask(task, model, clientRequestId, providerOptions);
360
+ if (kind === 'embedding')
361
+ return decodeEmbeddingTask(task, model, clientRequestId, providerOptions);
362
+ reject('unsupported_task', 400);
363
+ }
364
+ function decodeTokenforgeTaskRequestV2(request) {
365
+ assertV2JsonSize(request, MAX_V2_BODY_BYTES);
366
+ assertKnownFields(request, TOP_LEVEL_FIELDS);
367
+ const model = requiredString(request['model'], 'model_required');
368
+ const clientRequestId = decodeClientRequestId(request['clientRequestId']);
369
+ const providerOptions = decodeProviderOptions(request['providerOptions'], true);
370
+ const task = requireRecord(request['task'], 'unsupported_task');
371
+ if (task['kind'] === 'chat')
372
+ return decodeChatTask(task, model, clientRequestId, providerOptions);
373
+ if (task['kind'] === 'embedding')
374
+ return decodeEmbeddingTask(task, model, clientRequestId, providerOptions);
375
+ if (task['kind'] === 'image_generation')
376
+ return decodeImageGenerationTaskV2(task, model, clientRequestId, providerOptions);
377
+ if (task['kind'] === 'video_generation')
378
+ return decodeVideoGenerationTaskV2(task, model, clientRequestId, providerOptions);
379
+ reject('unsupported_task', 400);
380
+ }
381
+ export function validateTokenforgeTaskM1Activation(decoded) {
382
+ if (decoded.taskKind === 'video_generation')
383
+ reject('unsupported_capability', 422);
384
+ if (decoded.taskKind !== 'image_generation')
385
+ return;
386
+ const input = decoded.normalizedInput;
387
+ if (input['operation'] !== 'generate' || decoded.executionMode !== 'sync') {
388
+ reject('unsupported_capability', 422);
389
+ }
390
+ }
391
+ export function validateTokenforgeTaskCapability(decoded, capability) {
392
+ if (decoded.ingressProtocolFamily !== TOKENFORGE_TASKS_PROTOCOL) {
393
+ reject('unsupported_capability', 422);
394
+ }
395
+ const optionNamespaces = Object.keys(decoded.providerOptions);
396
+ const allowedOptionNamespace = optionNamespaceForProtocolFamily(capability.protocolFamily);
397
+ if (optionNamespaces.length > 0 &&
398
+ (allowedOptionNamespace === null ||
399
+ optionNamespaces.some((namespace) => namespace !== allowedOptionNamespace))) {
400
+ reject('unsupported_provider_option', 422);
401
+ }
402
+ if (capability.protocolFamily === 'openai-chat/v1' &&
403
+ Object.keys(decoded.providerOptions['openai'] ?? {}).length > 0) {
404
+ reject('unsupported_provider_option', 422);
405
+ }
406
+ if (decoded.taskKind !== capability.taskKind ||
407
+ !capability.executionModes.includes(decoded.executionMode) ||
408
+ !decoded.inputModalities.every((item) => capability.inputModalities.includes(item)) ||
409
+ !decoded.outputModalities.every((item) => capability.outputModalities.includes(item)) ||
410
+ (decoded.requiresFunctionTools && !capability.features.includes('function_tools')) ||
411
+ (decoded.requiresReasoning && !capability.features.includes('reasoning'))) {
412
+ reject('unsupported_capability', 422);
413
+ }
414
+ if (decoded.taskKind === 'embedding') {
415
+ validateTaskEmbeddingCapability(decoded, capability);
416
+ }
417
+ if (decoded.taskKind === 'image_generation') {
418
+ validateTaskImageGenerationCapability(decoded, capability);
419
+ }
420
+ if (decoded.taskKind === 'video_generation') {
421
+ validateTaskVideoGenerationCapability(decoded, capability);
422
+ }
423
+ }
424
+ function optionNamespaceForProtocolFamily(protocolFamily) {
425
+ if (protocolFamily === 'openai-chat/v1' || protocolFamily === 'openai-responses/v1') {
426
+ return 'openai';
427
+ }
428
+ if (protocolFamily === 'anthropic-messages/v1') {
429
+ return 'anthropic';
430
+ }
431
+ if (protocolFamily === OPENAI_IMAGES_PROTOCOL)
432
+ return 'openaiImages';
433
+ if (protocolFamily === VOLC_IMAGES_PROTOCOL)
434
+ return 'volcImages';
435
+ return null;
436
+ }
437
+ function decodeChatTask(task, model, clientRequestId, providerOptions) {
438
+ assertKnownFields(task, CHAT_TASK_FIELDS);
439
+ if (task['mode'] !== 'sync' && task['mode'] !== 'stream') {
440
+ reject('unsupported_task_mode', 400);
441
+ }
442
+ const messages = decodeMessages(task['input']);
443
+ const maxOutputTokens = optionalPositiveInteger(task['maxOutputTokens'], 'invalid_input');
444
+ const temperature = optionalFiniteNumber(task['temperature']);
445
+ if (temperature !== undefined && (temperature < 0 || temperature > 2)) {
446
+ reject('invalid_input', 400);
447
+ }
448
+ const tools = decodeTools(task['tools']);
449
+ const toolChoice = decodeToolChoice(task['toolChoice']);
450
+ if (typeof toolChoice === 'object' && !tools?.some((tool) => tool.name === toolChoice.call)) {
451
+ reject('invalid_input', 400);
452
+ }
453
+ const modalities = messageModalities(messages);
454
+ const inputUsesFunctions = messages.some((message) => message.role === 'tool' ||
455
+ (message.role === 'assistant' &&
456
+ message.content.some((part) => part.type === 'function_call')));
457
+ const normalizedInput = {
458
+ input: messages,
459
+ ...(clientRequestId === undefined ? {} : { clientRequestId }),
460
+ ...(maxOutputTokens === undefined ? {} : { maxOutputTokens }),
461
+ ...(temperature === undefined ? {} : { temperature }),
462
+ ...(tools === undefined ? {} : { tools }),
463
+ ...(toolChoice === undefined ? {} : { toolChoice }),
464
+ };
465
+ return {
466
+ ingressProtocolFamily: TOKENFORGE_TASKS_PROTOCOL,
467
+ model,
468
+ taskKind: 'chat',
469
+ executionMode: task['mode'],
470
+ inputModalities: modalities,
471
+ outputModalities: ['text'],
472
+ requiresFunctionTools: inputUsesFunctions || (tools !== undefined && tools.length > 0),
473
+ requiresReasoning: providerOptions.openai?.reasoningEffort !== undefined,
474
+ normalizedInput,
475
+ providerOptions: { ...providerOptions },
476
+ };
477
+ }
478
+ function decodeEmbeddingTask(task, model, clientRequestId, providerOptions) {
479
+ assertKnownFields(task, EMBEDDING_TASK_FIELDS);
480
+ if (task['mode'] !== 'sync') {
481
+ reject('unsupported_task_mode', 400);
482
+ }
483
+ if (!Array.isArray(task['input']) || task['input'].length === 0) {
484
+ reject('input_required', 400);
485
+ }
486
+ if (task['input'].length > MAX_EMBEDDING_BATCH_ITEMS) {
487
+ reject('input_limit_exceeded', 400);
488
+ }
489
+ const input = task['input'].map((item) => requiredString(item, 'input_required'));
490
+ const dimensions = optionalPositiveInteger(task['dimensions'], 'invalid_input');
491
+ const encodingFormat = task['encodingFormat'] ?? 'float';
492
+ if (encodingFormat !== 'float' && encodingFormat !== 'base64') {
493
+ reject('invalid_input', 400);
494
+ }
495
+ return {
496
+ ingressProtocolFamily: TOKENFORGE_TASKS_PROTOCOL,
497
+ model,
498
+ taskKind: 'embedding',
499
+ executionMode: 'sync',
500
+ inputModalities: ['text'],
501
+ outputModalities: ['vector'],
502
+ requiresFunctionTools: false,
503
+ requiresReasoning: false,
504
+ normalizedInput: {
505
+ input,
506
+ inputEncoding: 'utf8_text',
507
+ encodingFormat,
508
+ ...(clientRequestId === undefined ? {} : { clientRequestId }),
509
+ ...(dimensions === undefined ? {} : { dimensions }),
510
+ },
511
+ providerOptions: { ...providerOptions },
512
+ };
513
+ }
514
+ function decodeImageGenerationTaskV2(task, model, clientRequestId, providerOptions) {
515
+ const operation = task['operation'];
516
+ if (operation !== 'generate' && operation !== 'edit')
517
+ reject('unsupported_task', 400);
518
+ assertKnownFields(task, operation === 'generate' ? IMAGE_GENERATE_TASK_FIELDS : IMAGE_EDIT_TASK_FIELDS);
519
+ if (task['mode'] !== 'sync' && task['mode'] !== 'stream')
520
+ reject('unsupported_task_mode', 400);
521
+ const prompt = requiredString(task['prompt'], 'input_required', false);
522
+ if (UTF8_ENCODER.encode(prompt).byteLength > MAX_PROMPT_BYTES)
523
+ reject('input_limit_exceeded', 400);
524
+ const count = optionalBoundedInteger(task['count'], 1, 10);
525
+ const size = decodeMediaSize(task['size']);
526
+ const quality = optionalEnum(task['quality'], ['auto', 'low', 'medium', 'high']);
527
+ const outputFormat = optionalEnum(task['outputFormat'], ['png', 'jpeg', 'webp']);
528
+ const responseFormat = optionalEnum(task['responseFormat'], ['url', 'b64_json']);
529
+ const images = operation === 'edit' ? decodeMediaInputs(task['images'], 10, true) : undefined;
530
+ const mask = operation === 'edit' && task['mask'] !== undefined
531
+ ? decodeMediaInputV2(task['mask'])
532
+ : undefined;
533
+ return {
534
+ ingressProtocolFamily: TOKENFORGE_TASKS_PROTOCOL,
535
+ model,
536
+ taskKind: 'image_generation',
537
+ executionMode: task['mode'],
538
+ inputModalities: operation === 'edit' ? ['text', 'image'] : ['text'],
539
+ outputModalities: ['image'],
540
+ requiresFunctionTools: false,
541
+ requiresReasoning: false,
542
+ normalizedInput: {
543
+ operation,
544
+ prompt,
545
+ ...(images === undefined ? {} : { images }),
546
+ ...(mask === undefined ? {} : { mask }),
547
+ ...(count === undefined ? {} : { count }),
548
+ ...(size === undefined ? {} : { size }),
549
+ ...(quality === undefined ? {} : { quality }),
550
+ ...(outputFormat === undefined ? {} : { outputFormat }),
551
+ ...(responseFormat === undefined ? {} : { responseFormat }),
552
+ ...(clientRequestId === undefined ? {} : { clientRequestId }),
553
+ },
554
+ providerOptions: { ...providerOptions },
555
+ };
556
+ }
557
+ function decodeVideoGenerationTaskV2(task, model, clientRequestId, providerOptions) {
558
+ assertKnownFields(task, VIDEO_TASK_FIELDS);
559
+ if (task['mode'] !== 'async')
560
+ reject('unsupported_task_mode', 400);
561
+ const input = decodeVideoInput(task['input']);
562
+ const durationSeconds = optionalBoundedInteger(task['durationSeconds'], 1, 60);
563
+ const aspectRatio = optionalString(task['aspectRatio'], 'invalid_input');
564
+ if (aspectRatio !== undefined && aspectRatio.length > 32)
565
+ reject('input_limit_exceeded', 400);
566
+ const size = decodeMediaSize(task['size']);
567
+ if (task['returnLastFrame'] !== undefined && typeof task['returnLastFrame'] !== 'boolean')
568
+ reject('invalid_input', 400);
569
+ const callbackUrl = optionalString(task['callbackUrl'], 'invalid_input');
570
+ if (callbackUrl !== undefined && (callbackUrl.length > 2_048 || !isHttpsUrl(callbackUrl)))
571
+ reject('invalid_input', 400);
572
+ assertNoEmbeddedVideoCommandConflict(input, durationSeconds, aspectRatio);
573
+ return {
574
+ ingressProtocolFamily: TOKENFORGE_TASKS_PROTOCOL,
575
+ model,
576
+ taskKind: 'video_generation',
577
+ executionMode: 'async',
578
+ inputModalities: videoInputModalities(input),
579
+ outputModalities: ['video'],
580
+ requiresFunctionTools: false,
581
+ requiresReasoning: false,
582
+ normalizedInput: {
583
+ input,
584
+ ...(durationSeconds === undefined ? {} : { durationSeconds }),
585
+ ...(aspectRatio === undefined ? {} : { aspectRatio }),
586
+ ...(size === undefined ? {} : { size }),
587
+ ...(task['returnLastFrame'] === undefined
588
+ ? {}
589
+ : { returnLastFrame: task['returnLastFrame'] }),
590
+ ...(callbackUrl === undefined ? {} : { callbackUrl }),
591
+ ...(clientRequestId === undefined ? {} : { clientRequestId }),
592
+ },
593
+ providerOptions: { ...providerOptions },
594
+ };
595
+ }
596
+ function videoInputModalities(input) {
597
+ const hasText = input.some((item) => item.type === 'input_text');
598
+ const hasImage = input.some((item) => item.type === 'input_image');
599
+ return [...(hasText ? ['text'] : []), ...(hasImage ? ['image'] : [])];
600
+ }
601
+ function decodeMediaInputs(value, max, required) {
602
+ if (!Array.isArray(value) || (required && value.length === 0))
603
+ reject('input_required', 400);
604
+ if (value.length > max)
605
+ reject('input_limit_exceeded', 400);
606
+ return value.map(decodeMediaInputV2);
607
+ }
608
+ export function decodeMediaInputV2(value) {
609
+ const input = requireRecord(value, 'invalid_input');
610
+ if (input['kind'] === 'url') {
611
+ assertKnownFields(input, new Set(['kind', 'url', 'mimeType']));
612
+ const url = requiredString(input['url'], 'invalid_input');
613
+ if (url.length > MAX_MEDIA_URL_CHARS)
614
+ reject('input_limit_exceeded', 400);
615
+ if (!isHttpsUrl(url))
616
+ reject('invalid_input', 400);
617
+ const mimeType = optionalString(input['mimeType'], 'invalid_input');
618
+ return { kind: 'url', url, ...(mimeType === undefined ? {} : { mimeType }) };
619
+ }
620
+ if (input['kind'] === 'object') {
621
+ assertKnownFields(input, new Set(['kind', 'objectKey']));
622
+ const objectKey = requiredString(input['objectKey'], 'invalid_input');
623
+ if (objectKey.length > MAX_OBJECT_KEY_CHARS)
624
+ reject('input_limit_exceeded', 400);
625
+ return { kind: 'object', objectKey };
626
+ }
627
+ reject('invalid_input', 400);
628
+ }
629
+ function decodeVideoInput(value) {
630
+ if (!Array.isArray(value) || value.length === 0)
631
+ reject('input_required', 400);
632
+ if (value.length > 8)
633
+ reject('input_limit_exceeded', 400);
634
+ let textBytes = 0;
635
+ let imageCount = 0;
636
+ return value.map((item) => {
637
+ const part = requireRecord(item, 'invalid_input');
638
+ if (part['type'] === 'input_text') {
639
+ assertKnownFields(part, new Set(['type', 'text']));
640
+ const text = requiredString(part['text'], 'input_required', false);
641
+ textBytes += UTF8_ENCODER.encode(text).byteLength;
642
+ if (textBytes > MAX_VIDEO_TEXT_BYTES)
643
+ reject('input_limit_exceeded', 400);
644
+ return { type: 'input_text', text };
645
+ }
646
+ if (part['type'] === 'input_image') {
647
+ assertKnownFields(part, new Set(['type', 'media']));
648
+ imageCount += 1;
649
+ if (imageCount > 7)
650
+ reject('input_limit_exceeded', 400);
651
+ return { type: 'input_image', media: decodeMediaInputV2(part['media']) };
652
+ }
653
+ reject('invalid_input', 400);
654
+ });
655
+ }
656
+ function assertNoEmbeddedVideoCommandConflict(input, durationSeconds, aspectRatio) {
657
+ for (const part of input) {
658
+ if (part.type !== 'input_text')
659
+ continue;
660
+ const duration = /(?:^|\s)--dur(?:ation)?(?:\s+|=)(\d+)(?=\s|$)/u.exec(part.text)?.[1];
661
+ if (durationSeconds !== undefined &&
662
+ duration !== undefined &&
663
+ Number(duration) !== durationSeconds) {
664
+ reject('invalid_request', 400);
665
+ }
666
+ const ratio = /(?:^|\s)--ratio(?:\s+|=)([^\s]+)(?=\s|$)/u.exec(part.text)?.[1];
667
+ if (aspectRatio !== undefined && ratio !== undefined && ratio !== aspectRatio) {
668
+ reject('invalid_request', 400);
669
+ }
670
+ }
671
+ }
672
+ function decodeMediaSize(value) {
673
+ if (value === undefined)
674
+ return undefined;
675
+ const size = requireRecord(value, 'invalid_input');
676
+ assertKnownFields(size, new Set(['width', 'height']));
677
+ const width = optionalBoundedInteger(size['width'], 256, 4096);
678
+ const height = optionalBoundedInteger(size['height'], 256, 4096);
679
+ if (width === undefined || height === undefined)
680
+ reject('invalid_input', 400);
681
+ return { width, height };
682
+ }
683
+ function decodeMessages(value) {
684
+ if (!Array.isArray(value) || value.length === 0) {
685
+ reject('input_required', 400);
686
+ }
687
+ if (value.length > MAX_MESSAGES) {
688
+ reject('input_limit_exceeded', 400);
689
+ }
690
+ return value.map((item) => decodeMessage(item));
691
+ }
692
+ function decodeMessage(value) {
693
+ const message = requireRecord(value, 'invalid_input');
694
+ assertKnownFields(message, new Set(['role', 'content']));
695
+ const role = message['role'];
696
+ const content = message['content'];
697
+ if (!Array.isArray(content) || content.length === 0) {
698
+ reject('input_required', 400);
699
+ }
700
+ if (content.length > MAX_CONTENT_PARTS) {
701
+ reject('input_limit_exceeded', 400);
702
+ }
703
+ if (role === 'system' || role === 'developer') {
704
+ return { role, content: content.map((part) => decodeInputText(part)) };
705
+ }
706
+ if (role === 'user') {
707
+ return {
708
+ role,
709
+ content: content.map((part) => {
710
+ const type = recordType(part);
711
+ return type === 'input_text' ? decodeInputText(part) : decodeInputImage(part);
712
+ }),
713
+ };
714
+ }
715
+ if (role === 'assistant') {
716
+ return {
717
+ role,
718
+ content: content.map((part) => {
719
+ const type = recordType(part);
720
+ return type === 'input_text' ? decodeInputText(part) : decodeFunctionCall(part);
721
+ }),
722
+ };
723
+ }
724
+ if (role === 'tool') {
725
+ if (content.length !== 1) {
726
+ reject('invalid_input', 400);
727
+ }
728
+ return { role, content: [decodeToolResult(content[0])] };
729
+ }
730
+ reject('invalid_input', 400);
731
+ }
732
+ function decodeInputText(value) {
733
+ const part = requireRecord(value, 'invalid_input');
734
+ if (part['type'] !== 'input_text') {
735
+ reject('invalid_input', 400);
736
+ }
737
+ assertKnownFields(part, new Set(['type', 'text']));
738
+ return { type: 'input_text', text: requiredString(part['text'], 'input_required') };
739
+ }
740
+ function decodeInputImage(value) {
741
+ const part = requireRecord(value, 'invalid_input');
742
+ if (part['type'] !== 'input_image') {
743
+ reject('invalid_input', 400);
744
+ }
745
+ assertKnownFields(part, new Set(['type', 'source', 'detail']));
746
+ const source = requireRecord(part['source'], 'invalid_input');
747
+ assertKnownFields(source, new Set(['kind', 'value']));
748
+ if (source['kind'] !== 'https_url' && source['kind'] !== 'data_url') {
749
+ reject('invalid_input', 400);
750
+ }
751
+ const detail = part['detail'];
752
+ if (detail !== undefined && detail !== 'auto' && detail !== 'low' && detail !== 'high') {
753
+ reject('invalid_input', 400);
754
+ }
755
+ return {
756
+ type: 'input_image',
757
+ source: { kind: source['kind'], value: requiredString(source['value'], 'invalid_input') },
758
+ ...(detail === undefined ? {} : { detail }),
759
+ };
760
+ }
761
+ function decodeFunctionCall(value) {
762
+ const part = requireRecord(value, 'invalid_input');
763
+ if (part['type'] !== 'function_call') {
764
+ reject('invalid_input', 400);
765
+ }
766
+ assertKnownFields(part, new Set(['type', 'callId', 'name', 'arguments']));
767
+ const callId = functionIdentifier(part['callId']);
768
+ const name = functionIdentifier(part['name']);
769
+ const args = requiredString(part['arguments'], 'invalid_input', false);
770
+ if (UTF8_ENCODER.encode(args).byteLength > MAX_FUNCTION_ARGUMENT_BYTES) {
771
+ reject('input_limit_exceeded', 400);
772
+ }
773
+ return { type: 'function_call', callId, name, arguments: args };
774
+ }
775
+ function decodeToolResult(value) {
776
+ const part = requireRecord(value, 'invalid_input');
777
+ if (part['type'] !== 'tool_result') {
778
+ reject('invalid_input', 400);
779
+ }
780
+ assertKnownFields(part, new Set(['type', 'callId', 'output']));
781
+ return {
782
+ type: 'tool_result',
783
+ callId: functionIdentifier(part['callId']),
784
+ output: requiredString(part['output'], 'invalid_input', false),
785
+ };
786
+ }
787
+ function decodeTools(value) {
788
+ if (value === undefined) {
789
+ return undefined;
790
+ }
791
+ if (!Array.isArray(value) || value.length === 0) {
792
+ reject('invalid_input', 400);
793
+ }
794
+ return value.map((rawTool) => {
795
+ const tool = requireRecord(rawTool, 'invalid_input');
796
+ assertKnownFields(tool, new Set(['type', 'name', 'description', 'parameters', 'strict']));
797
+ if (tool['type'] !== 'function') {
798
+ reject('invalid_input', 400);
799
+ }
800
+ const name = functionIdentifier(tool['name']);
801
+ const description = optionalString(tool['description'], 'invalid_input');
802
+ const parameters = validateJsonSchema(tool['parameters']);
803
+ if (tool['strict'] !== undefined && typeof tool['strict'] !== 'boolean') {
804
+ reject('invalid_input', 400);
805
+ }
806
+ return {
807
+ type: 'function',
808
+ name,
809
+ parameters,
810
+ ...(description === undefined ? {} : { description }),
811
+ ...(tool['strict'] === undefined ? {} : { strict: tool['strict'] }),
812
+ };
813
+ });
814
+ }
815
+ function decodeToolChoice(value) {
816
+ if (value === undefined || value === 'auto' || value === 'none') {
817
+ return value;
818
+ }
819
+ const choice = requireRecord(value, 'invalid_input');
820
+ assertKnownFields(choice, new Set(['call']));
821
+ return { call: functionIdentifier(choice['call']) };
822
+ }
823
+ function decodeProviderOptions(value, allowMedia) {
824
+ if (value === undefined) {
825
+ return {};
826
+ }
827
+ if (allowMedia)
828
+ assertV2JsonSize(value, MAX_PROVIDER_OPTIONS_BYTES);
829
+ if (!isPlainRecord(value)) {
830
+ reject('unsupported_provider_option', 400);
831
+ }
832
+ const keys = Object.keys(value);
833
+ if (keys.some((key) => key !== 'openai' &&
834
+ key !== 'anthropic' &&
835
+ (!allowMedia || (key !== 'openaiImages' && key !== 'volcImages')))) {
836
+ reject('unsupported_provider_option', 400);
837
+ }
838
+ const output = {};
839
+ if (value['openai'] !== undefined) {
840
+ const openai = providerOptionRecord(value['openai']);
841
+ if (Object.keys(openai).some((key) => key !== 'reasoningEffort')) {
842
+ reject('unsupported_provider_option', 400);
843
+ }
844
+ const effort = openai['reasoningEffort'];
845
+ if (effort !== undefined && !REASONING_EFFORTS.has(effort)) {
846
+ reject('unsupported_provider_option', 400);
847
+ }
848
+ output.openai = effort === undefined ? {} : { reasoningEffort: effort };
849
+ }
850
+ if (value['anthropic'] !== undefined) {
851
+ const anthropic = providerOptionRecord(value['anthropic']);
852
+ if (Object.keys(anthropic).some((key) => key !== 'topK')) {
853
+ reject('unsupported_provider_option', 400);
854
+ }
855
+ const topK = anthropic['topK'];
856
+ if (topK !== undefined &&
857
+ (typeof topK !== 'number' || !Number.isSafeInteger(topK) || topK <= 0)) {
858
+ reject('unsupported_provider_option', 400);
859
+ }
860
+ output.anthropic = topK === undefined ? {} : { topK };
861
+ }
862
+ if (value['openaiImages'] !== undefined) {
863
+ const openaiImages = providerOptionRecord(value['openaiImages']);
864
+ if (Object.keys(openaiImages).some((key) => key !== 'background' && key !== 'moderation' && key !== 'outputCompression'))
865
+ reject('unsupported_provider_option', 400);
866
+ const background = openaiImages['background'];
867
+ const moderation = openaiImages['moderation'];
868
+ const outputCompression = openaiImages['outputCompression'];
869
+ if ((background !== undefined &&
870
+ background !== 'transparent' &&
871
+ background !== 'opaque' &&
872
+ background !== 'auto') ||
873
+ (moderation !== undefined && moderation !== 'low' && moderation !== 'auto') ||
874
+ (outputCompression !== undefined &&
875
+ (typeof outputCompression !== 'number' ||
876
+ !Number.isSafeInteger(outputCompression) ||
877
+ outputCompression < 0 ||
878
+ outputCompression > 100)))
879
+ reject('unsupported_provider_option', 400);
880
+ output.openaiImages = {
881
+ ...(background === undefined ? {} : { background }),
882
+ ...(moderation === undefined ? {} : { moderation }),
883
+ ...(outputCompression === undefined ? {} : { outputCompression }),
884
+ };
885
+ }
886
+ if (value['volcImages'] !== undefined) {
887
+ const volcImages = providerOptionRecord(value['volcImages']);
888
+ if (Object.keys(volcImages).some((key) => key !== 'watermark') ||
889
+ (volcImages['watermark'] !== undefined && typeof volcImages['watermark'] !== 'boolean'))
890
+ reject('unsupported_provider_option', 400);
891
+ output.volcImages =
892
+ volcImages['watermark'] === undefined ? {} : { watermark: volcImages['watermark'] };
893
+ }
894
+ return output;
895
+ }
896
+ function validateTaskEmbeddingCapability(decoded, capability) {
897
+ const failure = embeddingCapabilityFailure(decoded, capability);
898
+ if (failure !== undefined) {
899
+ reject('unsupported_capability', 422);
900
+ }
901
+ }
902
+ function validateTaskImageGenerationCapability(decoded, capability) {
903
+ if (capability.protocolFamily !== OPENAI_IMAGES_PROTOCOL &&
904
+ capability.protocolFamily !== VOLC_IMAGES_PROTOCOL) {
905
+ reject('unsupported_capability', 422);
906
+ }
907
+ const facts = parseImageGenerationCapabilityV1(capability.imageGenerationCapability);
908
+ if (facts === null)
909
+ reject('unsupported_capability', 422);
910
+ const input = decoded.normalizedInput;
911
+ const prompt = input['prompt'];
912
+ const count = input['count'] ?? 1;
913
+ if (typeof prompt !== 'string' ||
914
+ UTF8_ENCODER.encode(prompt).byteLength > facts.maxPromptUtf8Bytes ||
915
+ typeof count !== 'number' ||
916
+ count < facts.minCount ||
917
+ count > facts.maxCount)
918
+ reject('unsupported_capability', 422);
919
+ const size = input['size'];
920
+ if (size !== undefined &&
921
+ (!isPlainRecord(size) ||
922
+ !facts.allowedSizes.some((allowed) => allowed.width === size['width'] && allowed.height === size['height'])))
923
+ reject('unsupported_capability', 422);
924
+ const quality = input['quality'];
925
+ const outputFormat = input['outputFormat'];
926
+ const responseFormat = input['responseFormat'];
927
+ if ((quality !== undefined && !facts.qualities.includes(quality)) ||
928
+ (outputFormat !== undefined && !facts.outputFormats.includes(outputFormat)) ||
929
+ (responseFormat !== undefined && !facts.responseFormats.includes(responseFormat)))
930
+ reject('unsupported_capability', 422);
931
+ }
932
+ function validateTaskVideoGenerationCapability(decoded, capability) {
933
+ const facts = parseVideoGenerationCapabilityV1(capability.videoGenerationCapability);
934
+ if ((capability.protocolFamily !== 'volc-video/v1' &&
935
+ capability.protocolFamily !== 'ppio-video/v1') ||
936
+ facts === null) {
937
+ reject('unsupported_capability', 422);
938
+ }
939
+ const input = decoded.normalizedInput;
940
+ const items = input['input'];
941
+ if (!Array.isArray(items))
942
+ reject('unsupported_capability', 422);
943
+ const imageCount = items.filter((item) => isPlainRecord(item) && item['type'] === 'input_image').length;
944
+ const hasText = items.some((item) => isPlainRecord(item) && item['type'] === 'input_text');
945
+ if (imageCount > facts.maxInputImages ||
946
+ (imageCount > 0 && !facts.inputKinds.includes('image')) ||
947
+ (hasText && !facts.inputKinds.includes('text'))) {
948
+ reject('unsupported_capability', 422);
949
+ }
950
+ const durationSeconds = input['durationSeconds'];
951
+ const aspectRatio = input['aspectRatio'];
952
+ const size = input['size'];
953
+ if ((durationSeconds !== undefined &&
954
+ (typeof durationSeconds !== 'number' ||
955
+ !facts.allowedDurationsSeconds.includes(durationSeconds))) ||
956
+ (aspectRatio !== undefined &&
957
+ (typeof aspectRatio !== 'string' || !facts.allowedAspectRatios.includes(aspectRatio))) ||
958
+ (size !== undefined &&
959
+ (!isPlainRecord(size) ||
960
+ !facts.allowedSizes.some((allowed) => allowed.width === size['width'] && allowed.height === size['height']))) ||
961
+ (input['returnLastFrame'] === true && !facts.supportsLastFrame) ||
962
+ (input['callbackUrl'] !== undefined && !facts.supportsCallback)) {
963
+ reject('unsupported_capability', 422);
964
+ }
965
+ }
966
+ function validateJsonSchema(value) {
967
+ if (!isPlainRecord(value)) {
968
+ reject('invalid_input', 400);
969
+ }
970
+ let serialized;
971
+ try {
972
+ serialized = JSON.stringify(value);
973
+ }
974
+ catch {
975
+ reject('invalid_input', 400);
976
+ }
977
+ if (UTF8_ENCODER.encode(serialized).byteLength > MAX_JSON_SCHEMA_BYTES) {
978
+ reject('input_limit_exceeded', 400);
979
+ }
980
+ let nodes = 0;
981
+ const seen = new Set();
982
+ const visit = (item, depth) => {
983
+ nodes += 1;
984
+ if (nodes > MAX_JSON_SCHEMA_NODES || depth > MAX_JSON_SCHEMA_DEPTH) {
985
+ reject('input_limit_exceeded', 400);
986
+ }
987
+ if (item === null || typeof item === 'string' || typeof item === 'boolean') {
988
+ return;
989
+ }
990
+ if (typeof item === 'number') {
991
+ if (!Number.isFinite(item)) {
992
+ reject('invalid_input', 400);
993
+ }
994
+ return;
995
+ }
996
+ if (typeof item !== 'object' || seen.has(item)) {
997
+ reject('invalid_input', 400);
998
+ }
999
+ seen.add(item);
1000
+ if (Array.isArray(item)) {
1001
+ item.forEach((child) => visit(child, child !== null && typeof child === 'object' ? depth + 1 : depth));
1002
+ }
1003
+ else if (isPlainRecord(item)) {
1004
+ Object.values(item).forEach((child) => visit(child, child !== null && typeof child === 'object' ? depth + 1 : depth));
1005
+ }
1006
+ else {
1007
+ reject('invalid_input', 400);
1008
+ }
1009
+ seen.delete(item);
1010
+ };
1011
+ visit(value, 1);
1012
+ return value;
1013
+ }
1014
+ function messageModalities(messages) {
1015
+ const hasImage = messages.some((message) => message.role === 'user' && message.content.some((part) => part.type === 'input_image'));
1016
+ return hasImage ? ['text', 'image'] : ['text'];
1017
+ }
1018
+ function recordType(value) {
1019
+ return isPlainRecord(value) ? value['type'] : undefined;
1020
+ }
1021
+ function functionIdentifier(value) {
1022
+ const identifier = requiredString(value, 'invalid_input');
1023
+ if (identifier.length > MAX_FUNCTION_IDENTIFIER_CHARS) {
1024
+ reject('input_limit_exceeded', 400);
1025
+ }
1026
+ return identifier;
1027
+ }
1028
+ function decodeClientRequestId(value) {
1029
+ if (value === undefined) {
1030
+ return undefined;
1031
+ }
1032
+ if (typeof value !== 'string' || !value.trim() || value.length > MAX_CLIENT_REQUEST_ID_CHARS) {
1033
+ reject('invalid_client_request_id', 400);
1034
+ }
1035
+ return value;
1036
+ }
1037
+ function providerOptionRecord(value) {
1038
+ if (!isPlainRecord(value)) {
1039
+ reject('unsupported_provider_option', 400);
1040
+ }
1041
+ return value;
1042
+ }
1043
+ function requireRecord(value, code) {
1044
+ if (!isPlainRecord(value)) {
1045
+ reject(code, 400);
1046
+ }
1047
+ return value;
1048
+ }
1049
+ function isPlainRecord(value) {
1050
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
1051
+ return false;
1052
+ }
1053
+ const prototype = Object.getPrototypeOf(value);
1054
+ return prototype === Object.prototype || prototype === null;
1055
+ }
1056
+ function assertKnownFields(value, allowed, code = 'unknown_field') {
1057
+ if (Object.keys(value).some((key) => !allowed.has(key))) {
1058
+ reject(code, 400);
1059
+ }
1060
+ }
1061
+ function requiredString(value, code, trimRequired = true) {
1062
+ if (typeof value !== 'string' || (trimRequired && !value.trim())) {
1063
+ reject(code, 400);
1064
+ }
1065
+ return value;
1066
+ }
1067
+ function optionalString(value, code) {
1068
+ if (value === undefined) {
1069
+ return undefined;
1070
+ }
1071
+ return requiredString(value, code);
1072
+ }
1073
+ function optionalPositiveInteger(value, code) {
1074
+ if (value === undefined) {
1075
+ return undefined;
1076
+ }
1077
+ if (typeof value !== 'number' || !Number.isSafeInteger(value) || value <= 0) {
1078
+ reject(code, 400);
1079
+ }
1080
+ return value;
1081
+ }
1082
+ function optionalBoundedInteger(value, minimum, maximum) {
1083
+ if (value === undefined)
1084
+ return undefined;
1085
+ if (typeof value !== 'number' ||
1086
+ !Number.isSafeInteger(value) ||
1087
+ value < minimum ||
1088
+ value > maximum) {
1089
+ reject('input_limit_exceeded', 400);
1090
+ }
1091
+ return value;
1092
+ }
1093
+ function optionalEnum(value, allowed) {
1094
+ if (value === undefined)
1095
+ return undefined;
1096
+ if (typeof value !== 'string' || !allowed.includes(value))
1097
+ reject('invalid_input', 400);
1098
+ return value;
1099
+ }
1100
+ function isHttpsUrl(value) {
1101
+ try {
1102
+ const url = new URL(value);
1103
+ return url.protocol === 'https:' && Boolean(url.hostname) && !url.username && !url.password;
1104
+ }
1105
+ catch {
1106
+ return false;
1107
+ }
1108
+ }
1109
+ function assertV2JsonSize(value, maximum) {
1110
+ let serialized;
1111
+ try {
1112
+ serialized = JSON.stringify(value);
1113
+ }
1114
+ catch {
1115
+ reject('invalid_input', 400);
1116
+ }
1117
+ if (UTF8_ENCODER.encode(serialized).byteLength > maximum)
1118
+ reject('input_limit_exceeded', 400);
1119
+ }
1120
+ function optionalFiniteNumber(value) {
1121
+ if (value === undefined) {
1122
+ return undefined;
1123
+ }
1124
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
1125
+ reject('invalid_input', 400);
1126
+ }
1127
+ return value;
1128
+ }
1129
+ function reject(code, status) {
1130
+ throw new TokenforgeTaskRequestError(code, status);
1131
+ }
1132
+ //# sourceMappingURL=tokenforge-tasks.js.map