@skyold/provider-adapters 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 (116) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +59 -0
  3. package/README.zh-CN.md +15 -0
  4. package/dist/anthropic-messages/adapter.d.ts +3 -0
  5. package/dist/anthropic-messages/adapter.d.ts.map +1 -0
  6. package/dist/anthropic-messages/adapter.js +50 -0
  7. package/dist/anthropic-messages/adapter.js.map +1 -0
  8. package/dist/anthropic-messages/codec.d.ts +7 -0
  9. package/dist/anthropic-messages/codec.d.ts.map +1 -0
  10. package/dist/anthropic-messages/codec.js +581 -0
  11. package/dist/anthropic-messages/codec.js.map +1 -0
  12. package/dist/anthropic-messages/sse.d.ts +7 -0
  13. package/dist/anthropic-messages/sse.d.ts.map +1 -0
  14. package/dist/anthropic-messages/sse.js +77 -0
  15. package/dist/anthropic-messages/sse.js.map +1 -0
  16. package/dist/builtin-registry.d.ts +17 -0
  17. package/dist/builtin-registry.d.ts.map +1 -0
  18. package/dist/builtin-registry.js +110 -0
  19. package/dist/builtin-registry.js.map +1 -0
  20. package/dist/image-response-json.d.ts +36 -0
  21. package/dist/image-response-json.d.ts.map +1 -0
  22. package/dist/image-response-json.js +193 -0
  23. package/dist/image-response-json.js.map +1 -0
  24. package/dist/index.d.ts +24 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +24 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/openai-chat/adapter.d.ts +3 -0
  29. package/dist/openai-chat/adapter.d.ts.map +1 -0
  30. package/dist/openai-chat/adapter.js +83 -0
  31. package/dist/openai-chat/adapter.js.map +1 -0
  32. package/dist/openai-chat/codec.d.ts +7 -0
  33. package/dist/openai-chat/codec.d.ts.map +1 -0
  34. package/dist/openai-chat/codec.js +584 -0
  35. package/dist/openai-chat/codec.js.map +1 -0
  36. package/dist/openai-chat/sse.d.ts +3 -0
  37. package/dist/openai-chat/sse.d.ts.map +1 -0
  38. package/dist/openai-chat/sse.js +64 -0
  39. package/dist/openai-chat/sse.js.map +1 -0
  40. package/dist/openai-embeddings/adapter.d.ts +3 -0
  41. package/dist/openai-embeddings/adapter.d.ts.map +1 -0
  42. package/dist/openai-embeddings/adapter.js +90 -0
  43. package/dist/openai-embeddings/adapter.js.map +1 -0
  44. package/dist/openai-embeddings/codec.d.ts +7 -0
  45. package/dist/openai-embeddings/codec.d.ts.map +1 -0
  46. package/dist/openai-embeddings/codec.js +120 -0
  47. package/dist/openai-embeddings/codec.js.map +1 -0
  48. package/dist/openai-images/adapter.d.ts +4 -0
  49. package/dist/openai-images/adapter.d.ts.map +1 -0
  50. package/dist/openai-images/adapter.js +103 -0
  51. package/dist/openai-images/adapter.js.map +1 -0
  52. package/dist/openai-images/codec.d.ts +24 -0
  53. package/dist/openai-images/codec.d.ts.map +1 -0
  54. package/dist/openai-images/codec.js +979 -0
  55. package/dist/openai-images/codec.js.map +1 -0
  56. package/dist/openai-responses/adapter.d.ts +3 -0
  57. package/dist/openai-responses/adapter.d.ts.map +1 -0
  58. package/dist/openai-responses/adapter.js +102 -0
  59. package/dist/openai-responses/adapter.js.map +1 -0
  60. package/dist/openai-responses/codec.d.ts +7 -0
  61. package/dist/openai-responses/codec.d.ts.map +1 -0
  62. package/dist/openai-responses/codec.js +1086 -0
  63. package/dist/openai-responses/codec.js.map +1 -0
  64. package/dist/openai-responses/sse.d.ts +7 -0
  65. package/dist/openai-responses/sse.d.ts.map +1 -0
  66. package/dist/openai-responses/sse.js +82 -0
  67. package/dist/openai-responses/sse.js.map +1 -0
  68. package/dist/ppio-video/adapter.d.ts +37 -0
  69. package/dist/ppio-video/adapter.d.ts.map +1 -0
  70. package/dist/ppio-video/adapter.js +155 -0
  71. package/dist/ppio-video/adapter.js.map +1 -0
  72. package/dist/ppio-video/codec.d.ts +19 -0
  73. package/dist/ppio-video/codec.d.ts.map +1 -0
  74. package/dist/ppio-video/codec.js +172 -0
  75. package/dist/ppio-video/codec.js.map +1 -0
  76. package/dist/ppio-video/estimator.d.ts +21 -0
  77. package/dist/ppio-video/estimator.d.ts.map +1 -0
  78. package/dist/ppio-video/estimator.js +23 -0
  79. package/dist/ppio-video/estimator.js.map +1 -0
  80. package/dist/registry.d.ts +15 -0
  81. package/dist/registry.d.ts.map +1 -0
  82. package/dist/registry.js +36 -0
  83. package/dist/registry.js.map +1 -0
  84. package/dist/transport-profile.d.ts +17 -0
  85. package/dist/transport-profile.d.ts.map +1 -0
  86. package/dist/transport-profile.js +36 -0
  87. package/dist/transport-profile.js.map +1 -0
  88. package/dist/volc-embeddings/adapter.d.ts +4 -0
  89. package/dist/volc-embeddings/adapter.d.ts.map +1 -0
  90. package/dist/volc-embeddings/adapter.js +92 -0
  91. package/dist/volc-embeddings/adapter.js.map +1 -0
  92. package/dist/volc-embeddings/codec.d.ts +8 -0
  93. package/dist/volc-embeddings/codec.d.ts.map +1 -0
  94. package/dist/volc-embeddings/codec.js +196 -0
  95. package/dist/volc-embeddings/codec.js.map +1 -0
  96. package/dist/volc-images/adapter.d.ts +3 -0
  97. package/dist/volc-images/adapter.d.ts.map +1 -0
  98. package/dist/volc-images/adapter.js +96 -0
  99. package/dist/volc-images/adapter.js.map +1 -0
  100. package/dist/volc-images/codec.d.ts +13 -0
  101. package/dist/volc-images/codec.d.ts.map +1 -0
  102. package/dist/volc-images/codec.js +581 -0
  103. package/dist/volc-images/codec.js.map +1 -0
  104. package/dist/volc-video/adapter.d.ts +40 -0
  105. package/dist/volc-video/adapter.d.ts.map +1 -0
  106. package/dist/volc-video/adapter.js +165 -0
  107. package/dist/volc-video/adapter.js.map +1 -0
  108. package/dist/volc-video/codec.d.ts +26 -0
  109. package/dist/volc-video/codec.d.ts.map +1 -0
  110. package/dist/volc-video/codec.js +203 -0
  111. package/dist/volc-video/codec.js.map +1 -0
  112. package/dist/volc-video/estimator.d.ts +18 -0
  113. package/dist/volc-video/estimator.d.ts.map +1 -0
  114. package/dist/volc-video/estimator.js +21 -0
  115. package/dist/volc-video/estimator.js.map +1 -0
  116. package/package.json +61 -0
@@ -0,0 +1,979 @@
1
+ import { decodeOpenAiImagesProtocolSnapshotV1, MAX_IMAGE_REVISED_PROMPT_UTF8_BYTES_V1, materializeInlineImageBase64ValueV1, parseImageGenerationCapabilityV1, parseImageTaskCapabilityDeclarationV2, stableMediaInputDigestV2, } from '@skyold/model-protocol';
2
+ import { consumeStrictImageResponseJson, } from '../image-response-json.js';
3
+ const PROTOCOL_FAMILY = 'openai-images/v1';
4
+ const TOKENFORGE_TASKS_PROTOCOL = 'tokenforge-tasks/v1';
5
+ const RESPONSE_INVALID = 'PROVIDER_RESPONSE_INVALID';
6
+ const RESPONSE_TOO_LARGE = 'PROVIDER_RESPONSE_TOO_LARGE';
7
+ const RESPONSE_INCOMPLETE = 'PROVIDER_RESPONSE_INCOMPLETE';
8
+ const MAX_INLINE_TOTAL_BYTES = 100 * 1024 * 1024;
9
+ const MAX_OUTPUT_ITEM_BYTES = 25 * 1024 * 1024;
10
+ const MAX_OUTPUT_ITEM_BASE64_CHARS = 4 * Math.ceil(MAX_OUTPUT_ITEM_BYTES / 3);
11
+ const MAX_BOUNDED_RESPONSE_BYTES = 144 * 1024 * 1024;
12
+ const RESPONSE_ENVELOPE_BYTES = 128 * 1024;
13
+ const RESPONSE_ITEM_METADATA_BYTES = MAX_IMAGE_REVISED_PROMPT_UTF8_BYTES_V1 + 1024;
14
+ const MAX_URL_CHARS = 4_096;
15
+ const MAX_INPUT_IMAGE_BYTES = 16 * 1024 * 1024;
16
+ const MAX_MULTIPART_BYTES = 64 * 1024 * 1024;
17
+ const SHA256 = /^[a-f0-9]{64}$/;
18
+ const encoder = new TextEncoder();
19
+ export const openAiImagesCodec = {
20
+ protocolFamily: PROTOCOL_FAMILY,
21
+ supportsTask(task, capability) {
22
+ try {
23
+ requestFacts(task, capability);
24
+ return true;
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ },
30
+ encodeProviderRequest(task, providerModel, capability) {
31
+ const facts = requestFacts(task, capability);
32
+ return encoder.encode(JSON.stringify({
33
+ model: providerModel,
34
+ prompt: facts.prompt,
35
+ ...(facts.count === undefined ? {} : { n: facts.count }),
36
+ ...(facts.size === undefined ? {} : { size: `${facts.size.width}x${facts.size.height}` }),
37
+ ...(facts.quality === undefined ? {} : { quality: facts.quality }),
38
+ ...(facts.sendOutputFormat === undefined ? {} : { output_format: facts.sendOutputFormat }),
39
+ ...(facts.sendResponseFormat === undefined
40
+ ? {}
41
+ : { response_format: facts.sendResponseFormat }),
42
+ ...facts.providerOptions,
43
+ }));
44
+ },
45
+ async encodeEditProviderRequest(task, providerModel, capability, preparedMedia, signal, diagnostics) {
46
+ const facts = requestFacts(task, capability);
47
+ if (facts.operation !== 'edit')
48
+ throw new Error('OPENAI_IMAGES_REQUEST_INVALID');
49
+ const prepared = snapshotPreparedEditMedia(facts, preparedMedia);
50
+ const fields = [
51
+ textField('model', providerModel),
52
+ textField('prompt', facts.prompt),
53
+ ...prepared.images.map((image, index) => binaryField('image[]', `image-${index + 1}.${extension(image.mimeType)}`, image)),
54
+ ...(prepared.mask === undefined
55
+ ? []
56
+ : [binaryField('mask', `mask.${extension(prepared.mask.mimeType)}`, prepared.mask)]),
57
+ ...(facts.count === undefined ? [] : [textField('n', String(facts.count))]),
58
+ ...(facts.size === undefined
59
+ ? []
60
+ : [textField('size', `${facts.size.width}x${facts.size.height}`)]),
61
+ ...(facts.quality === undefined ? [] : [textField('quality', facts.quality)]),
62
+ ...(facts.background === undefined ? [] : [textField('background', facts.background)]),
63
+ ...(facts.sendOutputFormat === undefined
64
+ ? []
65
+ : [textField('output_format', facts.sendOutputFormat)]),
66
+ ...(facts.outputCompression === undefined
67
+ ? []
68
+ : [textField('output_compression', String(facts.outputCompression))]),
69
+ ];
70
+ const boundary = await deterministicBoundary(fields, signal, diagnostics);
71
+ assertNotAborted(signal);
72
+ const multipart = createMultipartBody(fields, boundary);
73
+ for (const target of multipart.binaryTargets) {
74
+ await writePreparedMedia(target, multipart.body, boundary, facts, signal, diagnostics);
75
+ }
76
+ assertNotAborted(signal);
77
+ return { body: multipart.body, boundary };
78
+ },
79
+ responseBodyPolicy(task, capability) {
80
+ return {
81
+ mode: 'bounded-success-stream',
82
+ maxBytes: responseBodyMaxBytes(requestFacts(task, capability)),
83
+ };
84
+ },
85
+ async decodeProviderStart(response, task, capability, diagnostics) {
86
+ const responseMetadata = metadata(response);
87
+ if (response.status < 200 || response.status >= 300) {
88
+ return failedStart(responseMetadata, response.status);
89
+ }
90
+ try {
91
+ const request = requestFacts(task, capability);
92
+ const decoded = await decodeResponseBody(response.body, request, diagnostics);
93
+ const root = decoded.root;
94
+ const expectedCount = request.count ?? 1;
95
+ if (decoded.items.length !== expectedCount)
96
+ throw new Error(RESPONSE_INVALID);
97
+ const items = decoded.items;
98
+ const decodedUsage = decodeUsage(root.usage, items.length);
99
+ const protocolSnapshot = decodeOpenAiImagesProtocolSnapshotV1({
100
+ protocolFamily: PROTOCOL_FAMILY,
101
+ created: root.created,
102
+ items: decoded.snapshotItems,
103
+ ...(root.background === undefined ? {} : { background: root.background }),
104
+ ...(root.outputFormat === undefined ? {} : { outputFormat: root.outputFormat }),
105
+ ...(root.quality === undefined ? {} : { quality: root.quality }),
106
+ ...(root.size === undefined ? {} : { size: root.size }),
107
+ ...(decodedUsage.snapshot === undefined ? {} : { usage: decodedUsage.snapshot }),
108
+ });
109
+ const output = { type: 'media', items };
110
+ return {
111
+ kind: 'sync',
112
+ acceptance: { state: 'accepted' },
113
+ responseMetadata,
114
+ terminal: {
115
+ status: 'succeeded',
116
+ acceptanceState: 'accepted',
117
+ retryable: false,
118
+ result: {
119
+ taskKind: 'image_generation',
120
+ output,
121
+ usage: decodedUsage.normalized,
122
+ protocolSnapshot,
123
+ },
124
+ usage: decodedUsage.normalized,
125
+ },
126
+ };
127
+ }
128
+ catch (error) {
129
+ return invalidStart(responseMetadata, responseFailureCode(error));
130
+ }
131
+ },
132
+ };
133
+ class ImageResponseError extends Error {
134
+ code;
135
+ constructor(code) {
136
+ super(code);
137
+ this.code = code;
138
+ }
139
+ }
140
+ async function decodeResponseBody(body, request, diagnostics) {
141
+ const rootValues = {};
142
+ const items = [];
143
+ const snapshotItems = [];
144
+ let inlineTotal = 0;
145
+ const tracker = await consumeStrictImageResponseJson({
146
+ body,
147
+ maxBytes: responseBodyMaxBytes(request),
148
+ paths: [
149
+ '$.created',
150
+ '$.background',
151
+ '$.output_format',
152
+ '$.quality',
153
+ '$.size',
154
+ '$.usage',
155
+ '$.data.*',
156
+ ],
157
+ allowedContainer,
158
+ onValue: ({ value, key, parent, stack }) => {
159
+ if (stack.length === 1 && typeof key === 'string') {
160
+ rootValues[key] = value;
161
+ return;
162
+ }
163
+ if (stack.length === 2 && stack[1]?.key === 'data' && typeof key === 'number') {
164
+ diagnostics?.onParserRetainedItems?.(Array.isArray(parent) ? Object.keys(parent).length : 0);
165
+ if (key !== items.length || items.length >= (request.count ?? 1)) {
166
+ throw new ImageResponseError(RESPONSE_INVALID);
167
+ }
168
+ const preflight = preflightItem(value, request);
169
+ const output = materializeItem(preflight, request);
170
+ if (output.kind === 'inline') {
171
+ inlineTotal += output.sizeBytes;
172
+ if (inlineTotal > MAX_INLINE_TOTAL_BYTES) {
173
+ throw new ImageResponseError(RESPONSE_INVALID);
174
+ }
175
+ }
176
+ items.push(output);
177
+ snapshotItems.push(preflight.revisedPrompt === undefined ? {} : { revisedPrompt: preflight.revisedPrompt });
178
+ }
179
+ },
180
+ invalidError: () => new ImageResponseError(RESPONSE_INVALID),
181
+ tooLargeError: () => new ImageResponseError(RESPONSE_TOO_LARGE),
182
+ bodyError: (error) => new ImageResponseError(transportBodyFailureCode(error) ?? RESPONSE_INCOMPLETE),
183
+ isProtocolError: (error) => error instanceof ImageResponseError,
184
+ });
185
+ if (!tracker.sawArray(['data']))
186
+ throw new ImageResponseError(RESPONSE_INVALID);
187
+ const root = decodeResponseRoot({ ...rootValues, data: Array(items.length).fill(null) }, request);
188
+ return { root, items, snapshotItems };
189
+ }
190
+ function responseBodyMaxBytes(request) {
191
+ const count = request.count ?? 1;
192
+ const payloadBytes = request.responseFormat === 'b64_json' ? MAX_OUTPUT_ITEM_BASE64_CHARS : MAX_URL_CHARS;
193
+ return Math.min(MAX_BOUNDED_RESPONSE_BYTES, RESPONSE_ENVELOPE_BYTES + count * (payloadBytes + RESPONSE_ITEM_METADATA_BYTES));
194
+ }
195
+ const ROOT_RESPONSE_KEYS = new Set([
196
+ 'created',
197
+ 'data',
198
+ 'background',
199
+ 'output_format',
200
+ 'quality',
201
+ 'size',
202
+ 'usage',
203
+ ]);
204
+ const DATA_ITEM_KEYS = new Set(['url', 'b64_json', 'revised_prompt']);
205
+ const USAGE_KEYS = new Set([
206
+ 'input_tokens',
207
+ 'input_tokens_details',
208
+ 'output_tokens',
209
+ 'output_tokens_details',
210
+ 'total_tokens',
211
+ ]);
212
+ const TOKEN_DETAIL_KEYS = new Set(['image_tokens', 'text_tokens']);
213
+ function allowedContainer(path, kind) {
214
+ if (path.length === 0)
215
+ return kind === 'object';
216
+ if (typeof path[0] === 'string' && !ROOT_RESPONSE_KEYS.has(path[0]))
217
+ return true;
218
+ if (path.length === 1 && path[0] === 'data')
219
+ return kind === 'array';
220
+ if (path.length === 2 && path[0] === 'data' && typeof path[1] === 'number') {
221
+ return kind === 'object';
222
+ }
223
+ if (path.length >= 3 &&
224
+ path[0] === 'data' &&
225
+ typeof path[1] === 'number' &&
226
+ typeof path[2] === 'string' &&
227
+ !DATA_ITEM_KEYS.has(path[2])) {
228
+ return true;
229
+ }
230
+ if (path.length === 1 && path[0] === 'usage')
231
+ return kind === 'object';
232
+ if (path.length >= 2 &&
233
+ path[0] === 'usage' &&
234
+ typeof path[1] === 'string' &&
235
+ !USAGE_KEYS.has(path[1])) {
236
+ return true;
237
+ }
238
+ if (path.length === 2 &&
239
+ path[0] === 'usage' &&
240
+ (path[1] === 'input_tokens_details' || path[1] === 'output_tokens_details')) {
241
+ return kind === 'object';
242
+ }
243
+ if (path.length >= 3 &&
244
+ path[0] === 'usage' &&
245
+ (path[1] === 'input_tokens_details' || path[1] === 'output_tokens_details') &&
246
+ typeof path[2] === 'string' &&
247
+ !TOKEN_DETAIL_KEYS.has(path[2])) {
248
+ return true;
249
+ }
250
+ return false;
251
+ }
252
+ function transportBodyFailureCode(error) {
253
+ if (error === null || typeof error !== 'object' || !('code' in error))
254
+ return null;
255
+ const code = error.code;
256
+ return code === RESPONSE_TOO_LARGE || code === RESPONSE_INCOMPLETE ? code : null;
257
+ }
258
+ function responseFailureCode(error) {
259
+ if (error instanceof ImageResponseError)
260
+ return error.code;
261
+ return transportBodyFailureCode(error) ?? RESPONSE_INVALID;
262
+ }
263
+ function requestFacts(task, capability) {
264
+ const operation = task.input['operation'];
265
+ if (task.protocolFamily !== PROTOCOL_FAMILY ||
266
+ (task.ingressProtocolFamily !== PROTOCOL_FAMILY &&
267
+ task.ingressProtocolFamily !== TOKENFORGE_TASKS_PROTOCOL) ||
268
+ task.dispatchProtocolFamily !== PROTOCOL_FAMILY ||
269
+ task.taskKind !== 'image_generation' ||
270
+ task.executionMode !== 'sync' ||
271
+ (operation === 'generate'
272
+ ? task.inputModalities.length !== 1 || task.inputModalities[0] !== 'text'
273
+ : operation === 'edit'
274
+ ? task.inputModalities.length !== 2 ||
275
+ task.inputModalities[0] !== 'text' ||
276
+ task.inputModalities[1] !== 'image'
277
+ : true) ||
278
+ task.outputModalities.length !== 1 ||
279
+ task.outputModalities[0] !== 'image') {
280
+ throw new Error('OPENAI_IMAGES_TASK_UNSUPPORTED');
281
+ }
282
+ const declared = parseImageGenerationCapabilityV1(capability.imageGenerationCapability);
283
+ if (declared === null)
284
+ throw new Error('OPENAI_IMAGES_CAPABILITY_INVALID');
285
+ const input = task.input;
286
+ if ((operation !== 'generate' && operation !== 'edit') || typeof input['prompt'] !== 'string') {
287
+ throw new Error('OPENAI_IMAGES_REQUEST_INVALID');
288
+ }
289
+ const declarationV2 = operation === 'edit' || capability.operations !== undefined
290
+ ? parseCapabilityV2(capability)
291
+ : null;
292
+ if (operation === 'edit' &&
293
+ (declarationV2 === null ||
294
+ !declarationV2.operations.includes('edit') ||
295
+ declarationV2.imageEditingCapability === undefined)) {
296
+ throw new Error('OPENAI_IMAGES_CAPABILITY_INVALID');
297
+ }
298
+ if (operation === 'generate' &&
299
+ capability.operations !== undefined &&
300
+ declarationV2?.operations.includes('generate') !== true) {
301
+ throw new Error('OPENAI_IMAGES_CAPABILITY_INVALID');
302
+ }
303
+ if (encoder.encode(input['prompt']).byteLength > declared.maxPromptUtf8Bytes) {
304
+ throw new Error('OPENAI_IMAGES_REQUEST_INVALID');
305
+ }
306
+ const count = optionalInteger(input['count']);
307
+ const effectiveCount = count ?? 1;
308
+ if (effectiveCount < declared.minCount || effectiveCount > declared.maxCount) {
309
+ throw new Error('OPENAI_IMAGES_REQUEST_INVALID');
310
+ }
311
+ const size = optionalSize(input['size']);
312
+ if (size !== undefined &&
313
+ !declared.allowedSizes.some((allowed) => allowed.width === size.width && allowed.height === size.height)) {
314
+ throw new Error('OPENAI_IMAGES_REQUEST_INVALID');
315
+ }
316
+ const quality = optionalQuality(input['quality']);
317
+ if (quality !== undefined && !declared.qualities.includes(quality)) {
318
+ throw new Error('OPENAI_IMAGES_REQUEST_INVALID');
319
+ }
320
+ const output = resolveFormat(optionalOutputFormat(input['outputFormat']), declared.outputFormats, declared.requestMapping.outputFormat);
321
+ const response = resolveFormat(optionalResponseFormat(input['responseFormat']), declared.responseFormats, declared.requestMapping.responseFormat);
322
+ const providerOptions = decodeProviderOptions(task.providerOptions, declared, output.effective, operation);
323
+ if (operation === 'edit' && response.send !== undefined) {
324
+ throw new Error('OPENAI_IMAGES_REQUEST_INVALID');
325
+ }
326
+ let images;
327
+ let mask;
328
+ if (operation === 'edit') {
329
+ images = decodeMediaInputs(input['images']);
330
+ const editing = declarationV2.imageEditingCapability;
331
+ if (images.length === 0 || images.length > editing.maxInputImages) {
332
+ throw new Error('OPENAI_IMAGES_REQUEST_INVALID');
333
+ }
334
+ mask = input['mask'] === undefined ? undefined : decodeMediaInput(input['mask']);
335
+ if (mask !== undefined && !editing.supportsMask) {
336
+ throw new Error('OPENAI_IMAGES_REQUEST_INVALID');
337
+ }
338
+ }
339
+ const background = providerOptions['background'];
340
+ const outputCompression = providerOptions['output_compression'];
341
+ return {
342
+ operation,
343
+ prompt: input['prompt'],
344
+ ...(images === undefined ? {} : { images }),
345
+ ...(mask === undefined ? {} : { mask }),
346
+ ...(operation === 'edit' ? { editingCapability: declarationV2.imageEditingCapability } : {}),
347
+ ...(count === undefined ? {} : { count }),
348
+ ...(size === undefined ? {} : { size }),
349
+ allowedSizes: declared.allowedSizes,
350
+ ...(quality === undefined ? {} : { quality }),
351
+ outputFormat: output.effective,
352
+ responseFormat: response.effective,
353
+ ...(output.send === undefined ? {} : { sendOutputFormat: output.send }),
354
+ ...(response.send === undefined ? {} : { sendResponseFormat: response.send }),
355
+ providerOptions,
356
+ ...(background === 'transparent' || background === 'opaque' || background === 'auto'
357
+ ? { background }
358
+ : {}),
359
+ ...(typeof outputCompression === 'number' ? { outputCompression } : {}),
360
+ };
361
+ }
362
+ function resolveFormat(explicit, supported, mapping) {
363
+ if (mapping.mode === 'fixed') {
364
+ if (explicit !== undefined && explicit !== mapping.value) {
365
+ throw new Error('OPENAI_IMAGES_REQUEST_INVALID');
366
+ }
367
+ return { effective: mapping.value };
368
+ }
369
+ if (explicit === undefined)
370
+ return { effective: mapping.defaultValue };
371
+ if (!supported.includes(explicit))
372
+ throw new Error('OPENAI_IMAGES_REQUEST_INVALID');
373
+ return { effective: explicit, send: explicit };
374
+ }
375
+ function decodeProviderOptions(value, capability, outputFormat, operation) {
376
+ const keys = Object.keys(value);
377
+ if (keys.some((key) => key !== 'openaiImages')) {
378
+ throw new Error('OPENAI_IMAGES_PROVIDER_OPTIONS_INVALID');
379
+ }
380
+ if (value['openaiImages'] === undefined)
381
+ return {};
382
+ const options = record(value['openaiImages']);
383
+ const allowed = new Set(capability.requestMapping.providerOptions.openaiImages);
384
+ if (Object.keys(options).some((key) => (key !== 'background' && key !== 'moderation' && key !== 'outputCompression') ||
385
+ !allowed.has(key))) {
386
+ throw new Error('OPENAI_IMAGES_PROVIDER_OPTIONS_INVALID');
387
+ }
388
+ const background = options['background'];
389
+ const moderation = options['moderation'];
390
+ const outputCompression = options['outputCompression'];
391
+ if ((background !== undefined &&
392
+ background !== 'transparent' &&
393
+ background !== 'opaque' &&
394
+ background !== 'auto') ||
395
+ (moderation !== undefined && moderation !== 'low' && moderation !== 'auto') ||
396
+ (outputCompression !== undefined &&
397
+ (!Number.isSafeInteger(outputCompression) ||
398
+ outputCompression < 0 ||
399
+ outputCompression > 100)) ||
400
+ (background === 'transparent' && outputFormat !== 'png' && outputFormat !== 'webp') ||
401
+ (outputCompression !== undefined && outputFormat !== 'jpeg' && outputFormat !== 'webp') ||
402
+ (operation === 'edit' && moderation !== undefined)) {
403
+ throw new Error('OPENAI_IMAGES_PROVIDER_OPTIONS_INVALID');
404
+ }
405
+ return {
406
+ ...(background === undefined ? {} : { background }),
407
+ ...(moderation === undefined ? {} : { moderation }),
408
+ ...(outputCompression === undefined ? {} : { output_compression: outputCompression }),
409
+ };
410
+ }
411
+ function parseCapabilityV2(capability) {
412
+ return parseImageTaskCapabilityDeclarationV2({
413
+ schemaVersion: 2,
414
+ taskKind: capability.taskKind,
415
+ protocolFamily: capability.protocolFamily,
416
+ operations: capability.operations,
417
+ inputModalities: capability.inputModalities,
418
+ outputModalities: capability.outputModalities,
419
+ executionModes: capability.executionModes,
420
+ supportsCallback: false,
421
+ supportsCancel: false,
422
+ imageGenerationCapability: capability.imageGenerationCapability,
423
+ ...(capability.imageEditingCapability === undefined
424
+ ? {}
425
+ : { imageEditingCapability: capability.imageEditingCapability }),
426
+ });
427
+ }
428
+ function decodeMediaInputs(value) {
429
+ if (!Array.isArray(value))
430
+ throw new Error('OPENAI_IMAGES_REQUEST_INVALID');
431
+ return value.map(decodeMediaInput);
432
+ }
433
+ function decodeMediaInput(value) {
434
+ const input = record(value);
435
+ if (input['kind'] === 'url' &&
436
+ hasExactKeys(input, ['kind', 'url'], ['mimeType']) &&
437
+ typeof input['url'] === 'string' &&
438
+ (input['mimeType'] === undefined || typeof input['mimeType'] === 'string')) {
439
+ return {
440
+ kind: 'url',
441
+ url: input['url'],
442
+ ...(input['mimeType'] === undefined ? {} : { mimeType: input['mimeType'] }),
443
+ };
444
+ }
445
+ if (input['kind'] === 'object' &&
446
+ hasExactKeys(input, ['kind', 'objectKey']) &&
447
+ typeof input['objectKey'] === 'string') {
448
+ return { kind: 'object', objectKey: input['objectKey'] };
449
+ }
450
+ throw new Error('OPENAI_IMAGES_REQUEST_INVALID');
451
+ }
452
+ function snapshotPreparedEditMedia(facts, preparedMedia) {
453
+ const images = facts.images;
454
+ const preparedImages = preparedMedia?.images === undefined ? undefined : [...preparedMedia.images];
455
+ const preparedMask = preparedMedia?.mask;
456
+ if (images === undefined ||
457
+ preparedMedia?.operation !== 'edit' ||
458
+ preparedImages?.length !== images.length ||
459
+ Boolean(preparedMask) !== Boolean(facts.mask)) {
460
+ throw new Error('OPENAI_IMAGES_PREPARED_MEDIA_MISMATCH');
461
+ }
462
+ const imageSnapshots = preparedImages.map(snapshotPreparedMedia);
463
+ const maskSnapshot = preparedMask === undefined ? undefined : snapshotPreparedMedia(preparedMask);
464
+ for (const [index, source] of images.entries()) {
465
+ if (imageSnapshots[index]?.sourceDigest !== stableMediaInputDigestV2(source)) {
466
+ throw new Error('OPENAI_IMAGES_PREPARED_MEDIA_MISMATCH');
467
+ }
468
+ }
469
+ if (facts.mask !== undefined &&
470
+ maskSnapshot?.sourceDigest !== stableMediaInputDigestV2(facts.mask)) {
471
+ throw new Error('OPENAI_IMAGES_PREPARED_MEDIA_MISMATCH');
472
+ }
473
+ const allPrepared = [...imageSnapshots, ...(maskSnapshot === undefined ? [] : [maskSnapshot])];
474
+ let declaredTotal = 0;
475
+ for (const input of allPrepared) {
476
+ validatePreparedMetadata(input, facts);
477
+ declaredTotal += input.sizeBytes;
478
+ if (declaredTotal >= MAX_MULTIPART_BYTES) {
479
+ throw new Error('OPENAI_IMAGES_MULTIPART_TOO_LARGE');
480
+ }
481
+ }
482
+ return {
483
+ images: imageSnapshots,
484
+ ...(maskSnapshot === undefined ? {} : { mask: maskSnapshot }),
485
+ };
486
+ }
487
+ function snapshotPreparedMedia(input) {
488
+ const sourceDigest = input.sourceDigest;
489
+ const mimeType = input.mimeType;
490
+ const sizeBytes = input.sizeBytes;
491
+ const sha256 = input.sha256;
492
+ const stream = Reflect.get(input, 'stream');
493
+ if (typeof stream !== 'function')
494
+ throw new Error('OPENAI_IMAGES_PREPARED_MEDIA_INVALID');
495
+ return {
496
+ sourceDigest,
497
+ mimeType,
498
+ sizeBytes,
499
+ sha256,
500
+ openStream: stream.bind(input),
501
+ };
502
+ }
503
+ async function writePreparedMedia(target, body, boundary, facts, signal, diagnostics) {
504
+ const input = target.field.media;
505
+ validatePreparedMetadata(input, facts);
506
+ assertNotAborted(signal);
507
+ let iterator;
508
+ try {
509
+ iterator = input.openStream()[Symbol.asyncIterator]();
510
+ }
511
+ catch {
512
+ throw new Error('OPENAI_IMAGES_PREPARED_MEDIA_INVALID');
513
+ }
514
+ let size = 0;
515
+ let complete = false;
516
+ try {
517
+ while (true) {
518
+ assertNotAborted(signal);
519
+ let next;
520
+ try {
521
+ next = await nextWithAbort(iterator, signal);
522
+ }
523
+ catch (error) {
524
+ if (signal.aborted || isAbortError(error))
525
+ throw new Error('OPENAI_IMAGES_MEDIA_ABORTED');
526
+ throw new Error('OPENAI_IMAGES_PREPARED_MEDIA_INVALID');
527
+ }
528
+ assertNotAborted(signal);
529
+ if (next.done) {
530
+ diagnostics?.onAfterFinalMediaChunk?.();
531
+ assertNotAborted(signal);
532
+ break;
533
+ }
534
+ if (!(next.value instanceof Uint8Array)) {
535
+ throw new Error('OPENAI_IMAGES_PREPARED_MEDIA_INVALID');
536
+ }
537
+ size += next.value.byteLength;
538
+ if (size > input.sizeBytes || size > MAX_INPUT_IMAGE_BYTES) {
539
+ throw new Error('OPENAI_IMAGES_PREPARED_MEDIA_MISMATCH');
540
+ }
541
+ body.set(next.value, target.offset + size - next.value.byteLength);
542
+ }
543
+ if (size !== input.sizeBytes)
544
+ throw new Error('OPENAI_IMAGES_PREPARED_MEDIA_MISMATCH');
545
+ const bytes = body.subarray(target.offset, target.offset + size);
546
+ const actualSha256 = await sha256Hex(bytes, signal);
547
+ diagnostics?.onAfterMediaDigest?.();
548
+ assertNotAborted(signal);
549
+ if (actualSha256 !== input.sha256 || detectMimeType(bytes) !== input.mimeType) {
550
+ throw new Error('OPENAI_IMAGES_PREPARED_MEDIA_MISMATCH');
551
+ }
552
+ if (containsBytes(bytes, encoder.encode(boundary))) {
553
+ throw new Error('OPENAI_IMAGES_MULTIPART_BOUNDARY_INVALID');
554
+ }
555
+ complete = true;
556
+ }
557
+ finally {
558
+ if (!complete)
559
+ closeIterator(iterator);
560
+ }
561
+ }
562
+ function validatePreparedMetadata(input, facts) {
563
+ if (!Number.isSafeInteger(input.sizeBytes) ||
564
+ input.sizeBytes <= 0 ||
565
+ input.sizeBytes > MAX_INPUT_IMAGE_BYTES ||
566
+ !SHA256.test(input.sha256) ||
567
+ facts.editingCapability?.inputMimeTypes.includes(input.mimeType) !== true) {
568
+ throw new Error('OPENAI_IMAGES_PREPARED_MEDIA_MISMATCH');
569
+ }
570
+ }
571
+ function closeIterator(iterator) {
572
+ if (iterator.return === undefined)
573
+ return;
574
+ try {
575
+ void Promise.resolve(iterator.return()).catch(() => undefined);
576
+ }
577
+ catch {
578
+ // Cleanup is best-effort and must not replace the bounded media failure.
579
+ }
580
+ }
581
+ async function nextWithAbort(iterator, signal) {
582
+ if (signal.aborted)
583
+ throw new Error('OPENAI_IMAGES_MEDIA_ABORTED');
584
+ return await new Promise((resolve, reject) => {
585
+ const aborted = () => reject(new Error('OPENAI_IMAGES_MEDIA_ABORTED'));
586
+ signal.addEventListener('abort', aborted, { once: true });
587
+ iterator
588
+ .next()
589
+ .then(resolve, reject)
590
+ .finally(() => signal.removeEventListener('abort', aborted));
591
+ });
592
+ }
593
+ function assertNotAborted(signal) {
594
+ if (signal.aborted)
595
+ throw new Error('OPENAI_IMAGES_MEDIA_ABORTED');
596
+ }
597
+ function isAbortError(error) {
598
+ return error instanceof Error && error.message === 'OPENAI_IMAGES_MEDIA_ABORTED';
599
+ }
600
+ function detectMimeType(bytes) {
601
+ if (bytes.length >= 8 &&
602
+ [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a].every((value, index) => bytes[index] === value)) {
603
+ return 'image/png';
604
+ }
605
+ if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
606
+ return 'image/jpeg';
607
+ }
608
+ if (bytes.length >= 12 &&
609
+ decoderAscii(bytes, 0, 4) === 'RIFF' &&
610
+ decoderAscii(bytes, 8, 12) === 'WEBP') {
611
+ return 'image/webp';
612
+ }
613
+ return null;
614
+ }
615
+ function decoderAscii(bytes, start, end) {
616
+ return String.fromCharCode(...bytes.subarray(start, end));
617
+ }
618
+ function textField(name, value) {
619
+ return { kind: 'text', name, value: encoder.encode(value) };
620
+ }
621
+ function binaryField(name, filename, media) {
622
+ return {
623
+ kind: 'binary',
624
+ name,
625
+ filename,
626
+ mimeType: media.mimeType,
627
+ media,
628
+ };
629
+ }
630
+ function extension(mimeType) {
631
+ if (mimeType === 'image/jpeg')
632
+ return 'jpg';
633
+ if (mimeType === 'image/webp')
634
+ return 'webp';
635
+ return 'png';
636
+ }
637
+ async function deterministicBoundary(fields, signal, diagnostics) {
638
+ const seed = [];
639
+ for (const field of fields) {
640
+ seed.push(field.kind === 'binary'
641
+ ? [field.name, field.filename, field.mimeType, field.media.sizeBytes, field.media.sha256]
642
+ : [field.name, await sha256Hex(field.value, signal)]);
643
+ }
644
+ for (let attempt = 0; attempt < 100; attempt += 1) {
645
+ const digest = (await sha256Hex(encoder.encode(JSON.stringify(['tokenforge-openai-images-edit-v1', seed, attempt])), signal)).slice(0, 48);
646
+ diagnostics?.onAfterBoundaryDigest?.();
647
+ assertNotAborted(signal);
648
+ const boundary = `tokenforge-${digest}`;
649
+ const boundaryBytes = encoder.encode(boundary);
650
+ if (fields.every((field) => field.kind === 'binary' || !containsBytes(field.value, boundaryBytes))) {
651
+ return boundary;
652
+ }
653
+ }
654
+ throw new Error('OPENAI_IMAGES_MULTIPART_BOUNDARY_INVALID');
655
+ }
656
+ async function sha256Hex(value, signal) {
657
+ const digest = new Uint8Array(await globalThis.crypto.subtle.digest('SHA-256', value));
658
+ assertNotAborted(signal);
659
+ return Array.from(digest, (byte) => byte.toString(16).padStart(2, '0')).join('');
660
+ }
661
+ function createMultipartBody(fields, boundary) {
662
+ const preparedFields = fields.map((field) => ({
663
+ field,
664
+ header: encoder.encode(field.kind === 'text'
665
+ ? `--${boundary}\r\nContent-Disposition: form-data; name="${field.name}"\r\n\r\n`
666
+ : `--${boundary}\r\nContent-Disposition: form-data; name="${field.name}"; filename="${field.filename}"\r\nContent-Type: ${field.mimeType}\r\n\r\n`),
667
+ }));
668
+ const closing = encoder.encode(`--${boundary}--\r\n`);
669
+ let totalBytes = closing.byteLength;
670
+ for (const { field, header } of preparedFields) {
671
+ totalBytes +=
672
+ header.byteLength +
673
+ (field.kind === 'text' ? field.value.byteLength : field.media.sizeBytes) +
674
+ 2;
675
+ if (totalBytes > MAX_MULTIPART_BYTES) {
676
+ throw new Error('OPENAI_IMAGES_MULTIPART_TOO_LARGE');
677
+ }
678
+ }
679
+ const body = new Uint8Array(totalBytes);
680
+ const binaryTargets = [];
681
+ let offset = 0;
682
+ const append = (value) => {
683
+ body.set(value, offset);
684
+ offset += value.byteLength;
685
+ };
686
+ for (const { field, header } of preparedFields) {
687
+ append(header);
688
+ if (field.kind === 'text') {
689
+ append(field.value);
690
+ }
691
+ else {
692
+ binaryTargets.push({ field, offset });
693
+ offset += field.media.sizeBytes;
694
+ }
695
+ append(encoder.encode('\r\n'));
696
+ }
697
+ append(closing);
698
+ return { body, binaryTargets };
699
+ }
700
+ function containsBytes(haystack, needle) {
701
+ outer: for (let offset = 0; offset <= haystack.length - needle.length; offset += 1) {
702
+ for (let index = 0; index < needle.length; index += 1) {
703
+ if (haystack[offset + index] !== needle[index])
704
+ continue outer;
705
+ }
706
+ return true;
707
+ }
708
+ return false;
709
+ }
710
+ function decodeResponseRoot(value, request) {
711
+ const root = record(value);
712
+ if (!hasRequiredKeys(root, ['created', 'data']) ||
713
+ !nonNegativeInteger(root['created']) ||
714
+ !Array.isArray(root['data'])) {
715
+ throw new Error(RESPONSE_INVALID);
716
+ }
717
+ const background = root['background'];
718
+ const outputFormat = root['output_format'];
719
+ const quality = root['quality'];
720
+ const size = root['size'];
721
+ if ((background !== undefined && background !== 'transparent' && background !== 'opaque') ||
722
+ (outputFormat !== undefined &&
723
+ outputFormat !== 'png' &&
724
+ outputFormat !== 'jpeg' &&
725
+ outputFormat !== 'webp') ||
726
+ (outputFormat !== undefined && outputFormat !== request.outputFormat) ||
727
+ (quality !== undefined && quality !== 'low' && quality !== 'medium' && quality !== 'high') ||
728
+ (size !== undefined &&
729
+ (!imagePixelSize(size) ||
730
+ !request.allowedSizes.some((allowed) => `${allowed.width}x${allowed.height}` === size) ||
731
+ (request.size !== undefined && `${request.size.width}x${request.size.height}` !== size)))) {
732
+ throw new Error(RESPONSE_INVALID);
733
+ }
734
+ return {
735
+ created: root['created'],
736
+ data: root['data'],
737
+ ...(background === undefined ? {} : { background }),
738
+ ...(outputFormat === undefined ? {} : { outputFormat }),
739
+ ...(quality === undefined ? {} : { quality }),
740
+ ...(size === undefined ? {} : { size }),
741
+ ...(root['usage'] === undefined ? {} : { usage: root['usage'] }),
742
+ };
743
+ }
744
+ function preflightItem(value, request) {
745
+ const item = record(value);
746
+ const expectedField = request.responseFormat === 'url' ? 'url' : 'b64_json';
747
+ const conflictingField = request.responseFormat === 'url' ? 'b64_json' : 'url';
748
+ if (!Object.prototype.hasOwnProperty.call(item, expectedField) ||
749
+ Object.prototype.hasOwnProperty.call(item, conflictingField) ||
750
+ !hasRequiredKeys(item, [expectedField]) ||
751
+ (item['revised_prompt'] !== undefined &&
752
+ (typeof item['revised_prompt'] !== 'string' ||
753
+ encoder.encode(item['revised_prompt']).byteLength > MAX_IMAGE_REVISED_PROMPT_UTF8_BYTES_V1))) {
754
+ throw new Error(RESPONSE_INVALID);
755
+ }
756
+ const revisedPrompt = item['revised_prompt'];
757
+ if (request.responseFormat === 'url') {
758
+ const url = item['url'];
759
+ if (typeof url !== 'string' ||
760
+ url.length === 0 ||
761
+ url.length > MAX_URL_CHARS ||
762
+ !isHttpsUrl(url)) {
763
+ throw new Error(RESPONSE_INVALID);
764
+ }
765
+ return { kind: 'reference', url, ...(revisedPrompt === undefined ? {} : { revisedPrompt }) };
766
+ }
767
+ return {
768
+ kind: 'inline',
769
+ base64: item['b64_json'],
770
+ ...(revisedPrompt === undefined ? {} : { revisedPrompt }),
771
+ };
772
+ }
773
+ function materializeItem(item, request) {
774
+ if (item.kind === 'reference') {
775
+ return {
776
+ kind: 'reference',
777
+ reference: {
778
+ kind: 'url',
779
+ url: item.url,
780
+ mimeType: mimeType(request.outputFormat),
781
+ },
782
+ };
783
+ }
784
+ return materializeInlineImageBase64ValueV1(item.base64, request.outputFormat);
785
+ }
786
+ function decodeUsage(value, imageCount) {
787
+ if (value === undefined) {
788
+ return { normalized: { requests: 1, imageCount, estimated: false } };
789
+ }
790
+ const usage = record(value);
791
+ if (!hasRequiredKeys(usage, [
792
+ 'input_tokens',
793
+ 'input_tokens_details',
794
+ 'output_tokens',
795
+ 'total_tokens',
796
+ ]) ||
797
+ !nonNegativeInteger(usage['input_tokens']) ||
798
+ !nonNegativeInteger(usage['output_tokens']) ||
799
+ !nonNegativeInteger(usage['total_tokens'])) {
800
+ throw new Error(RESPONSE_INVALID);
801
+ }
802
+ const inputTokenDetails = decodeTokenDetails(usage['input_tokens_details']);
803
+ const outputTokenDetails = usage['output_tokens_details'] === undefined
804
+ ? undefined
805
+ : decodeTokenDetails(usage['output_tokens_details']);
806
+ if (safeIntegerSum(inputTokenDetails.imageTokens, inputTokenDetails.textTokens) !==
807
+ usage['input_tokens'] ||
808
+ (outputTokenDetails !== undefined &&
809
+ safeIntegerSum(outputTokenDetails.imageTokens, outputTokenDetails.textTokens) !==
810
+ usage['output_tokens']) ||
811
+ safeIntegerSum(usage['input_tokens'], usage['output_tokens']) !== usage['total_tokens']) {
812
+ throw new Error(RESPONSE_INVALID);
813
+ }
814
+ const snapshot = {
815
+ inputTokens: usage['input_tokens'],
816
+ inputTokenDetails,
817
+ outputTokens: usage['output_tokens'],
818
+ totalTokens: usage['total_tokens'],
819
+ ...(outputTokenDetails === undefined ? {} : { outputTokenDetails }),
820
+ };
821
+ return {
822
+ normalized: {
823
+ inputTokens: snapshot.inputTokens,
824
+ outputTokens: snapshot.outputTokens,
825
+ totalTokens: snapshot.totalTokens,
826
+ inputTokenDetails,
827
+ ...(outputTokenDetails === undefined ? {} : { outputTokenDetails }),
828
+ requests: 1,
829
+ imageCount,
830
+ estimated: false,
831
+ },
832
+ snapshot,
833
+ };
834
+ }
835
+ function decodeTokenDetails(value) {
836
+ const details = record(value);
837
+ if (!hasRequiredKeys(details, ['image_tokens', 'text_tokens']) ||
838
+ !nonNegativeInteger(details['image_tokens']) ||
839
+ !nonNegativeInteger(details['text_tokens'])) {
840
+ throw new Error(RESPONSE_INVALID);
841
+ }
842
+ return { imageTokens: details['image_tokens'], textTokens: details['text_tokens'] };
843
+ }
844
+ function invalidStart(responseMetadata, failureCode = RESPONSE_INVALID) {
845
+ const failureOrigin = failureCode === RESPONSE_TOO_LARGE || failureCode === RESPONSE_INCOMPLETE
846
+ ? 'transport'
847
+ : 'provider';
848
+ return {
849
+ kind: 'sync',
850
+ acceptance: { state: 'accepted' },
851
+ responseMetadata,
852
+ terminal: {
853
+ status: 'failed',
854
+ acceptanceState: 'accepted',
855
+ failureOrigin,
856
+ failureCode,
857
+ retryable: false,
858
+ },
859
+ };
860
+ }
861
+ function failedStart(responseMetadata, status) {
862
+ const rateLimited = status === 429;
863
+ const unknown = status >= 500;
864
+ const acceptanceState = unknown ? 'unknown' : 'not-accepted';
865
+ return {
866
+ kind: 'sync',
867
+ acceptance: { state: acceptanceState },
868
+ responseMetadata,
869
+ terminal: {
870
+ status: 'failed',
871
+ acceptanceState,
872
+ failureOrigin: 'provider',
873
+ failureCode: rateLimited
874
+ ? 'PROVIDER_RATE_LIMITED'
875
+ : unknown
876
+ ? 'PROVIDER_ACCEPTANCE_UNKNOWN'
877
+ : 'PROVIDER_REQUEST_FAILED',
878
+ retryable: rateLimited,
879
+ },
880
+ };
881
+ }
882
+ function optionalInteger(value) {
883
+ if (value === undefined)
884
+ return undefined;
885
+ if (!Number.isSafeInteger(value) || value <= 0) {
886
+ throw new Error('OPENAI_IMAGES_REQUEST_INVALID');
887
+ }
888
+ return value;
889
+ }
890
+ function optionalSize(value) {
891
+ if (value === undefined)
892
+ return undefined;
893
+ const size = record(value);
894
+ if (!hasExactKeys(size, ['width', 'height']) ||
895
+ !Number.isSafeInteger(size['width']) ||
896
+ !Number.isSafeInteger(size['height'])) {
897
+ throw new Error('OPENAI_IMAGES_REQUEST_INVALID');
898
+ }
899
+ return { width: size['width'], height: size['height'] };
900
+ }
901
+ function optionalQuality(value) {
902
+ if (value === undefined ||
903
+ value === 'auto' ||
904
+ value === 'low' ||
905
+ value === 'medium' ||
906
+ value === 'high') {
907
+ return value;
908
+ }
909
+ throw new Error('OPENAI_IMAGES_REQUEST_INVALID');
910
+ }
911
+ function optionalOutputFormat(value) {
912
+ if (value === undefined || value === 'png' || value === 'jpeg' || value === 'webp')
913
+ return value;
914
+ throw new Error('OPENAI_IMAGES_REQUEST_INVALID');
915
+ }
916
+ function optionalResponseFormat(value) {
917
+ if (value === undefined || value === 'url' || value === 'b64_json')
918
+ return value;
919
+ throw new Error('OPENAI_IMAGES_REQUEST_INVALID');
920
+ }
921
+ function nonNegativeInteger(value) {
922
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
923
+ }
924
+ function imagePixelSize(value) {
925
+ if (typeof value !== 'string')
926
+ return false;
927
+ const match = /^([1-9]\d{1,3})x([1-9]\d{1,3})$/.exec(value);
928
+ if (match === null)
929
+ return false;
930
+ const width = Number(match[1]);
931
+ const height = Number(match[2]);
932
+ return width >= 64 && width <= 8192 && height >= 64 && height <= 8192;
933
+ }
934
+ function safeIntegerSum(left, right) {
935
+ const sum = left + right;
936
+ return Number.isSafeInteger(sum) ? sum : null;
937
+ }
938
+ function mimeType(format) {
939
+ if (format === 'jpeg')
940
+ return 'image/jpeg';
941
+ if (format === 'webp')
942
+ return 'image/webp';
943
+ return 'image/png';
944
+ }
945
+ function isHttpsUrl(value) {
946
+ try {
947
+ const url = new URL(value);
948
+ return url.protocol === 'https:' && Boolean(url.hostname) && !url.username && !url.password;
949
+ }
950
+ catch {
951
+ return false;
952
+ }
953
+ }
954
+ function record(value) {
955
+ if (value === null ||
956
+ typeof value !== 'object' ||
957
+ Array.isArray(value) ||
958
+ (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)) {
959
+ throw new Error(RESPONSE_INVALID);
960
+ }
961
+ return value;
962
+ }
963
+ function hasExactKeys(value, required, optional = []) {
964
+ const allowed = new Set([...required, ...optional]);
965
+ return (Object.keys(value).every((key) => allowed.has(key)) &&
966
+ required.every((key) => Object.prototype.hasOwnProperty.call(value, key)));
967
+ }
968
+ function hasRequiredKeys(value, required) {
969
+ return required.every((key) => Object.prototype.hasOwnProperty.call(value, key));
970
+ }
971
+ function metadata(response) {
972
+ const providerRequestId = response.headers['x-request-id'] ?? response.headers['openai-request-id'];
973
+ return {
974
+ headersReceived: true,
975
+ statusCode: response.status,
976
+ ...(providerRequestId ? { providerRequestId } : {}),
977
+ };
978
+ }
979
+ //# sourceMappingURL=codec.js.map