@omnicross/core 0.1.10 → 0.2.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.
- package/LICENSE +21 -21
- package/NOTICE +57 -57
- package/README.md +22 -15
- package/dist/{CompletionService-kNA17cfI.d.cts → CompletionService-BiJqftc-.d.cts} +1 -1
- package/dist/{CompletionService-C3BX0Ovy.d.ts → CompletionService-NXN_Av5W.d.ts} +1 -1
- package/dist/{ProviderProxy-QdwNV6l1.d.cts → ProviderProxy-CgIcCVwk.d.cts} +1 -1
- package/dist/{ProviderProxy--ROec6VI.d.ts → ProviderProxy-DYoY5XoT.d.ts} +1 -1
- package/dist/auth/GeminiCodeAssistProjectResolver.cjs +6 -6
- package/dist/auth/GeminiCodeAssistProjectResolver.js +4 -4
- package/dist/{chunk-QUSEZNYS.cjs → chunk-32SRZFFP.cjs} +4 -1
- package/dist/chunk-4JOU54M5.js +200 -0
- package/dist/{chunk-2UTGX6FM.js → chunk-4VVCXVB7.js} +1 -1
- package/dist/chunk-6DDYNS2B.cjs +384 -0
- package/dist/{chunk-HKB656B4.js → chunk-6OC3ORW6.js} +55 -287
- package/dist/{chunk-GXEZ2R3E.js → chunk-6OKXH2TW.js} +18 -7
- package/dist/{chunk-TIT74RIL.cjs → chunk-7B56AJBB.cjs} +6214 -2423
- package/dist/{chunk-YWL7YZWS.cjs → chunk-7LRTPNFL.cjs} +5 -0
- package/dist/{chunk-CCVRRJOX.js → chunk-7PIUVCKK.js} +9 -2
- package/dist/{chunk-IIRPUVPH.cjs → chunk-7ZHBN75N.cjs} +3 -1
- package/dist/{chunk-RJ5ZGNI3.js → chunk-CFSGYFOZ.js} +1 -1
- package/dist/chunk-CTKM773J.cjs +3919 -0
- package/dist/chunk-DEIUR2TH.js +3919 -0
- package/dist/{chunk-WNKWAEUR.js → chunk-EYGBM2VC.js} +3 -1
- package/dist/{chunk-NGYOO5TO.js → chunk-FJNKXOWE.js} +9 -9
- package/dist/{chunk-UWDW6PN3.cjs → chunk-FX325I5Q.cjs} +20 -9
- package/dist/chunk-G4CZIUIB.cjs +200 -0
- package/dist/chunk-HCOQQAZE.cjs +123 -0
- package/dist/{chunk-55FMGZ3L.js → chunk-HN74IGEO.js} +1 -1
- package/dist/{chunk-ZY6KU6P3.js → chunk-HOMUGKHR.js} +8 -5
- package/dist/{chunk-AOFWNWLT.cjs → chunk-IUL4VWNU.cjs} +22 -1
- package/dist/{chunk-5LNYGCIW.cjs → chunk-KIQ2HA2A.cjs} +5 -5
- package/dist/{chunk-MUHWHQVM.cjs → chunk-KQBLZMP6.cjs} +4 -3
- package/dist/chunk-LFEH7CEO.js +384 -0
- package/dist/chunk-N76KU6F5.js +123 -0
- package/dist/{chunk-CMVXX7ON.cjs → chunk-NOITAVHA.cjs} +114 -58
- package/dist/{chunk-Q27JY5RG.cjs → chunk-OIKREZVQ.cjs} +7 -4
- package/dist/{chunk-RJUONLUQ.cjs → chunk-OST5RJOJ.cjs} +1 -1
- package/dist/{chunk-6D4W22P4.js → chunk-QIIAMBTM.js} +4 -1
- package/dist/{chunk-56QD3YW6.cjs → chunk-QWNUOR7F.cjs} +11 -4
- package/dist/{chunk-S3DYFEBE.js → chunk-R4VCO2CB.js} +10 -1
- package/dist/{chunk-ZQQUD66M.cjs → chunk-SQMAEDNE.cjs} +63 -295
- package/dist/{chunk-4VZX5F4T.cjs → chunk-T6NWPIYZ.cjs} +5 -2
- package/dist/{chunk-P7EM6BND.cjs → chunk-TVYA5JSQ.cjs} +10 -10
- package/dist/{chunk-EBLFCNPY.js → chunk-U47LQXPB.js} +5 -2
- package/dist/{chunk-N7OOJI5V.js → chunk-V2UKUTJ3.js} +5 -0
- package/dist/{chunk-45FHEOKW.js → chunk-V53ZEFXC.js} +92 -36
- package/dist/{chunk-OC6DOOFX.cjs → chunk-VXRTQZF2.cjs} +5 -5
- package/dist/{chunk-FEBAQI5A.js → chunk-WSJE42SG.js} +4964 -1173
- package/dist/{chunk-RICPANBY.cjs → chunk-WX2E2G7H.cjs} +13 -4
- package/dist/{chunk-DE2SRBFZ.js → chunk-X335TJNS.js} +4 -3
- package/dist/{chunk-N2EIM2XJ.js → chunk-ZLNHPUFA.js} +22 -1
- package/dist/completion/CompletionService.cjs +29 -26
- package/dist/completion/CompletionService.d.cts +3 -2
- package/dist/completion/CompletionService.d.ts +3 -2
- package/dist/completion/CompletionService.js +28 -25
- package/dist/completion.cjs +37 -34
- package/dist/completion.d.cts +3 -2
- package/dist/completion.d.ts +3 -2
- package/dist/completion.js +36 -33
- package/dist/image-generation.cjs +52 -0
- package/dist/image-generation.d.cts +93 -0
- package/dist/image-generation.d.ts +93 -0
- package/dist/image-generation.js +52 -0
- package/dist/index.cjs +126 -31
- package/dist/index.d.cts +11 -7
- package/dist/index.d.ts +11 -7
- package/dist/index.js +142 -47
- package/dist/openai-operation.cjs +18 -0
- package/dist/openai-operation.d.cts +19 -0
- package/dist/openai-operation.d.ts +19 -0
- package/dist/openai-operation.js +18 -0
- package/dist/outbound-api/auditCapture.cjs +3 -3
- package/dist/outbound-api/auditCapture.d.cts +13 -2
- package/dist/outbound-api/auditCapture.d.ts +13 -2
- package/dist/outbound-api/auditCapture.js +2 -2
- package/dist/outbound-api/auditRedact.cjs +2 -2
- package/dist/outbound-api/auditRedact.js +1 -1
- package/dist/outbound-api/routeResolver.cjs +2 -2
- package/dist/outbound-api/routeResolver.d.cts +7 -4
- package/dist/outbound-api/routeResolver.d.ts +7 -4
- package/dist/outbound-api/routeResolver.js +1 -1
- package/dist/outbound-api/subscriptionRegistryPort.d.cts +2 -1
- package/dist/outbound-api/subscriptionRegistryPort.d.ts +2 -1
- package/dist/outbound-api/types.d.cts +6 -3
- package/dist/outbound-api/types.d.ts +6 -3
- package/dist/outbound-api.cjs +48 -31
- package/dist/outbound-api.d.cts +95 -9
- package/dist/outbound-api.d.ts +95 -9
- package/dist/outbound-api.js +47 -30
- package/dist/pipeline/AccountAllowanceScheduling.d.cts +18 -15
- package/dist/pipeline/AccountAllowanceScheduling.d.ts +18 -15
- package/dist/pipeline/LlmConfigProviderAuth.cjs +3 -3
- package/dist/pipeline/LlmConfigProviderAuth.js +2 -2
- package/dist/pipeline/SubscriptionAccountHealth.cjs +2 -2
- package/dist/pipeline/SubscriptionAccountHealth.d.cts +7 -0
- package/dist/pipeline/SubscriptionAccountHealth.d.ts +7 -0
- package/dist/pipeline/SubscriptionAccountHealth.js +1 -1
- package/dist/pipeline/executeProviderCall.cjs +2 -2
- package/dist/pipeline/executeProviderCall.d.cts +11 -6
- package/dist/pipeline/executeProviderCall.d.ts +11 -6
- package/dist/pipeline/executeProviderCall.js +1 -1
- package/dist/pipeline/upstreamFetch.cjs +2 -2
- package/dist/pipeline/upstreamFetch.d.cts +5 -0
- package/dist/pipeline/upstreamFetch.d.ts +5 -0
- package/dist/pipeline/upstreamFetch.js +1 -1
- package/dist/ports.d.cts +6 -3
- package/dist/ports.d.ts +6 -3
- package/dist/provider-proxy/ProviderProxy.cjs +29 -26
- package/dist/provider-proxy/ProviderProxy.d.cts +3 -2
- package/dist/provider-proxy/ProviderProxy.d.ts +3 -2
- package/dist/provider-proxy/ProviderProxy.js +28 -25
- package/dist/provider-proxy/identity/codexCliHeaders.cjs +4 -2
- package/dist/provider-proxy/identity/codexCliHeaders.d.cts +26 -6
- package/dist/provider-proxy/identity/codexCliHeaders.d.ts +26 -6
- package/dist/provider-proxy/identity/codexCliHeaders.js +5 -3
- package/dist/provider-proxy/ingress/providerProxyShared.cjs +31 -26
- package/dist/provider-proxy/ingress/providerProxyShared.d.cts +21 -8
- package/dist/provider-proxy/ingress/providerProxyShared.d.ts +21 -8
- package/dist/provider-proxy/ingress/providerProxyShared.js +30 -25
- package/dist/provider-proxy/types.d.cts +14 -483
- package/dist/provider-proxy/types.d.ts +14 -483
- package/dist/provider-proxy.cjs +43 -26
- package/dist/provider-proxy.d.cts +30 -8
- package/dist/provider-proxy.d.ts +30 -8
- package/dist/provider-proxy.js +42 -25
- package/dist/{routeResolver-C0mkkZaM.d.ts → routeResolver-CTCcSHWu.d.ts} +4 -2
- package/dist/{routeResolver-CVFE8KKe.d.cts → routeResolver-rpKQLyDO.d.cts} +4 -2
- package/dist/transformer/TransformerChainExecutor.cjs +2 -2
- package/dist/transformer/TransformerChainExecutor.d.cts +2 -2
- package/dist/transformer/TransformerChainExecutor.d.ts +2 -2
- package/dist/transformer/TransformerChainExecutor.js +1 -1
- package/dist/transformer/transformers/AnthropicOpenAIToAnthropicStream.cjs +16 -0
- package/dist/transformer/transformers/AnthropicOpenAIToAnthropicStream.d.cts +53 -0
- package/dist/transformer/transformers/AnthropicOpenAIToAnthropicStream.d.ts +53 -0
- package/dist/transformer/transformers/AnthropicOpenAIToAnthropicStream.js +16 -0
- package/dist/transformer/transformers/AnthropicTransformer.cjs +4 -3
- package/dist/transformer/transformers/AnthropicTransformer.js +3 -2
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.cjs +4 -4
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.js +3 -3
- package/dist/transformer/transformers/GeminiTransformer.cjs +4 -4
- package/dist/transformer/transformers/GeminiTransformer.js +3 -3
- package/dist/transformer/transformers/OpenAIResponseTransformer.cjs +3 -3
- package/dist/transformer/transformers/OpenAIResponseTransformer.js +2 -2
- package/dist/transformer/transformers/OpenAITransformer.cjs +3 -3
- package/dist/transformer/transformers/OpenAITransformer.js +2 -2
- package/dist/transformer/transformers.cjs +10 -9
- package/dist/transformer/transformers.js +13 -12
- package/dist/transformer/types.d.cts +39 -4
- package/dist/transformer/types.d.ts +39 -4
- package/dist/transformer.cjs +14 -13
- package/dist/transformer.js +14 -13
- package/dist/{types-BB8pxk_3.d.ts → types-5T223WYQ.d.ts} +183 -23
- package/dist/types-Bd72H4SB.d.ts +196 -0
- package/dist/types-CVRge76p.d.ts +1115 -0
- package/dist/types-CbglB73l.d.cts +196 -0
- package/dist/types-DsHBBk3h.d.cts +1115 -0
- package/dist/{types-C2MwkLah.d.cts → types-jjU24eYG.d.cts} +183 -23
- package/dist/usage.cjs +2 -2
- package/dist/usage.d.cts +6 -1
- package/dist/usage.d.ts +6 -1
- package/dist/usage.js +1 -1
- package/package.json +69 -64
- package/dist/chunk-FCR77GYM.cjs +0 -33
- package/dist/chunk-K5NM7VAH.js +0 -33
- /package/dist/{chunk-XXFYLL7R.js → chunk-ECZ3ZE2C.js} +0 -0
- /package/dist/{chunk-3SCOLIPT.cjs → chunk-MXI2PWIC.cjs} +0 -0
|
@@ -0,0 +1,3919 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2;// src/image-generation/capabilities.ts
|
|
2
|
+
var BOOLEAN_KEYS = [
|
|
3
|
+
"available",
|
|
4
|
+
"generate",
|
|
5
|
+
"edit",
|
|
6
|
+
"maskEdit",
|
|
7
|
+
"streaming",
|
|
8
|
+
"transparentBackground",
|
|
9
|
+
"flexibleSizes",
|
|
10
|
+
"responsesTool",
|
|
11
|
+
"multiTurnEdit",
|
|
12
|
+
"supportsFileId",
|
|
13
|
+
"supportsImageUrl"
|
|
14
|
+
];
|
|
15
|
+
var NUMERIC_KEYS = [
|
|
16
|
+
"maxInputImages",
|
|
17
|
+
"maxOutputImages",
|
|
18
|
+
"maxPartialImages"
|
|
19
|
+
];
|
|
20
|
+
var OUTPUT_COMPRESSION_FORMATS = /* @__PURE__ */ new Set(["png", "jpeg", "webp"]);
|
|
21
|
+
function isValidOutputCompressionCapability(value) {
|
|
22
|
+
if (!value || value.supported !== true || !Array.isArray(value.formats) || value.formats.length === 0 || value.formats.some((format) => !OUTPUT_COMPRESSION_FORMATS.has(format)) || new Set(value.formats).size !== value.formats.length || !Number.isInteger(value.min) || value.min < 0 || value.min > 100 || !Number.isInteger(value.max) || value.max < 0 || value.max > 100 || value.min > value.max) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
function intersectStrings(layers, key) {
|
|
28
|
+
const allowed = key === "outputFormats" ? /* @__PURE__ */ new Set(["png", "jpeg", "webp"]) : key === "qualityLevels" ? /* @__PURE__ */ new Set(["auto", "low", "medium", "high"]) : key === "moderationModes" ? /* @__PURE__ */ new Set(["auto", "low"]) : void 0;
|
|
29
|
+
const sets = layers.map((layer) => {
|
|
30
|
+
const value = _optionalChain([layer, 'access', _ => _.values, 'optionalAccess', _2 => _2[key]]);
|
|
31
|
+
return new Set(Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.length > 0 && item.length <= 256 && (!allowed || allowed.has(item))) : []);
|
|
32
|
+
});
|
|
33
|
+
if (sets.length === 0) return [];
|
|
34
|
+
return [...sets[0]].filter((value) => sets.slice(1).every((set) => set.has(value))).sort();
|
|
35
|
+
}
|
|
36
|
+
function intersectOutputCompression(layers) {
|
|
37
|
+
const values = layers.map((layer) => _optionalChain([layer, 'access', _3 => _3.values, 'optionalAccess', _4 => _4.outputCompression]));
|
|
38
|
+
if (!values.every(isValidOutputCompressionCapability)) {
|
|
39
|
+
return { supported: false };
|
|
40
|
+
}
|
|
41
|
+
const formats = values[0].formats.filter((format) => values.slice(1).every((value) => value.formats.includes(format))).sort();
|
|
42
|
+
const min = Math.max(...values.map((value) => value.min));
|
|
43
|
+
const max = Math.min(...values.map((value) => value.max));
|
|
44
|
+
if (formats.length === 0 || min > max) {
|
|
45
|
+
return { supported: false };
|
|
46
|
+
}
|
|
47
|
+
return { supported: true, formats, min, max };
|
|
48
|
+
}
|
|
49
|
+
function unavailableReason(layers, now) {
|
|
50
|
+
for (const layer of layers) {
|
|
51
|
+
if (layer.kind !== "adapter" && layer.kind !== "account" && layer.kind !== "upstream") {
|
|
52
|
+
return "missing_evidence";
|
|
53
|
+
}
|
|
54
|
+
if (!Number.isFinite(layer.verifiedAt) || !layer.values) {
|
|
55
|
+
if (layer.kind === "account") return "account_unverified";
|
|
56
|
+
if (layer.kind === "upstream") return "protocol_unverified";
|
|
57
|
+
return "missing_evidence";
|
|
58
|
+
}
|
|
59
|
+
if (layer.expiresAt != null && (!Number.isFinite(layer.expiresAt) || layer.expiresAt <= now)) {
|
|
60
|
+
return "stale_evidence";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const availability = layers.map((layer) => _optionalChain([layer, 'access', _5 => _5.values, 'optionalAccess', _6 => _6.available]));
|
|
64
|
+
if (availability.some((value) => value === true) && availability.some((value) => value === false)) {
|
|
65
|
+
return "contradictory_evidence";
|
|
66
|
+
}
|
|
67
|
+
if (availability.some((value) => value !== true)) {
|
|
68
|
+
const unavailable = layers.find((layer) => _optionalChain([layer, 'access', _7 => _7.values, 'optionalAccess', _8 => _8.available]) === false);
|
|
69
|
+
if (_optionalChain([unavailable, 'optionalAccess', _9 => _9.kind]) === "adapter") return "adapter_unavailable";
|
|
70
|
+
if (_optionalChain([unavailable, 'optionalAccess', _10 => _10.kind]) === "account") return "account_unverified";
|
|
71
|
+
if (_optionalChain([unavailable, 'optionalAccess', _11 => _11.kind]) === "upstream") return "protocol_unverified";
|
|
72
|
+
return "missing_evidence";
|
|
73
|
+
}
|
|
74
|
+
return void 0;
|
|
75
|
+
}
|
|
76
|
+
function resolveImageCapabilities(evidence, now = Date.now()) {
|
|
77
|
+
const layers = [evidence.adapter, evidence.account, evidence.upstream];
|
|
78
|
+
let reason = unavailableReason(layers, now);
|
|
79
|
+
const booleans = Object.fromEntries(
|
|
80
|
+
BOOLEAN_KEYS.map((key) => [key, layers.every((layer) => _optionalChain([layer, 'access', _12 => _12.values, 'optionalAccess', _13 => _13[key]]) === true)])
|
|
81
|
+
);
|
|
82
|
+
const numbers = Object.fromEntries(
|
|
83
|
+
NUMERIC_KEYS.map((key) => {
|
|
84
|
+
const values = layers.map((layer) => _optionalChain([layer, 'access', _14 => _14.values, 'optionalAccess', _15 => _15[key]]));
|
|
85
|
+
const valid = values.every((value) => Number.isInteger(value) && value >= 0);
|
|
86
|
+
return [key, valid ? Math.min(...values) : 0];
|
|
87
|
+
})
|
|
88
|
+
);
|
|
89
|
+
const models = intersectStrings(layers, "models");
|
|
90
|
+
const outputFormats = intersectStrings(layers, "outputFormats");
|
|
91
|
+
const qualityLevels = intersectStrings(layers, "qualityLevels");
|
|
92
|
+
const moderationModes = intersectStrings(layers, "moderationModes");
|
|
93
|
+
const outputCompression = intersectOutputCompression(layers);
|
|
94
|
+
if (!reason && models.length === 0) reason = "no_common_models";
|
|
95
|
+
if (!reason && outputFormats.length === 0) reason = "no_common_output_formats";
|
|
96
|
+
if (!reason && qualityLevels.length === 0) reason = "no_common_quality_levels";
|
|
97
|
+
if (!reason && moderationModes.length === 0) reason = "no_common_moderation_modes";
|
|
98
|
+
const verifiedAt = layers.map((layer) => layer.verifiedAt).filter((value) => typeof value === "number" && Number.isFinite(value));
|
|
99
|
+
return {
|
|
100
|
+
...booleans,
|
|
101
|
+
...numbers,
|
|
102
|
+
available: !reason && booleans.available && models.length > 0 && outputFormats.length > 0 && qualityLevels.length > 0 && moderationModes.length > 0,
|
|
103
|
+
models,
|
|
104
|
+
outputFormats,
|
|
105
|
+
qualityLevels,
|
|
106
|
+
moderationModes,
|
|
107
|
+
outputCompression,
|
|
108
|
+
reason,
|
|
109
|
+
resolvedAt: now,
|
|
110
|
+
oldestEvidenceAt: verifiedAt.length === layers.length ? Math.min(...verifiedAt) : void 0
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/image-generation/errors.ts
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
var _imagegenerationtypes = require('@omnicross/contracts/image-generation-types');
|
|
118
|
+
var DEFAULT_MESSAGES = {
|
|
119
|
+
invalid_image_request: "The image request is invalid.",
|
|
120
|
+
unsupported_model: "The requested image model is not supported.",
|
|
121
|
+
unsupported_capability: "The requested image capability is not available.",
|
|
122
|
+
invalid_api_key: "The API key is invalid.",
|
|
123
|
+
upstream_auth_required: "The upstream image provider requires authentication.",
|
|
124
|
+
image_reference_not_found: "The image reference was not found.",
|
|
125
|
+
image_reference_expired: "The image reference has expired.",
|
|
126
|
+
image_too_large: "The image exceeds the configured size limit.",
|
|
127
|
+
unsupported_image_type: "The image type is not supported.",
|
|
128
|
+
moderation_blocked: "The image request was blocked by moderation.",
|
|
129
|
+
upstream_rate_limited: "The upstream image provider is rate limited.",
|
|
130
|
+
subscription_usage_limit_reached: "The subscription image usage limit has been reached.",
|
|
131
|
+
image_queue_full: "The image execution queue is full.",
|
|
132
|
+
image_queue_timeout: "The image execution queue timed out.",
|
|
133
|
+
request_cancelled: "The image request was cancelled.",
|
|
134
|
+
upstream_protocol_changed: "The upstream image protocol could not be recognized.",
|
|
135
|
+
image_generation_failed: "The upstream image generation failed.",
|
|
136
|
+
image_generation_timeout: "The upstream image generation timed out."
|
|
137
|
+
};
|
|
138
|
+
var DEFAULT_HTTP_STATUS = {
|
|
139
|
+
invalid_image_request: 400,
|
|
140
|
+
unsupported_model: 400,
|
|
141
|
+
unsupported_capability: 422,
|
|
142
|
+
invalid_api_key: 401,
|
|
143
|
+
upstream_auth_required: 503,
|
|
144
|
+
image_reference_not_found: 404,
|
|
145
|
+
image_reference_expired: 410,
|
|
146
|
+
image_too_large: 413,
|
|
147
|
+
unsupported_image_type: 415,
|
|
148
|
+
moderation_blocked: 422,
|
|
149
|
+
upstream_rate_limited: 429,
|
|
150
|
+
subscription_usage_limit_reached: 429,
|
|
151
|
+
image_queue_full: 429,
|
|
152
|
+
image_queue_timeout: 504,
|
|
153
|
+
request_cancelled: 499,
|
|
154
|
+
upstream_protocol_changed: 502,
|
|
155
|
+
image_generation_failed: 502,
|
|
156
|
+
image_generation_timeout: 504
|
|
157
|
+
};
|
|
158
|
+
function safeParam(value) {
|
|
159
|
+
return typeof value === "string" && /^[a-z][a-z0-9_.-]{0,63}$/i.test(value) ? value : void 0;
|
|
160
|
+
}
|
|
161
|
+
function safeModerationDetails(value) {
|
|
162
|
+
if (!value) return void 0;
|
|
163
|
+
const stage = value.stage === "input" || value.stage === "output" ? value.stage : void 0;
|
|
164
|
+
const categories = _optionalChain([value, 'access', _16 => _16.categories, 'optionalAccess', _17 => _17.filter, 'call', _18 => _18((item) => /^[a-z][a-z0-9_.-]{0,63}$/i.test(item)), 'access', _19 => _19.slice, 'call', _20 => _20(0, 16)]);
|
|
165
|
+
return stage || _optionalChain([categories, 'optionalAccess', _21 => _21.length]) ? { stage, categories: _optionalChain([categories, 'optionalAccess', _22 => _22.length]) ? categories : void 0 } : void 0;
|
|
166
|
+
}
|
|
167
|
+
var ImageGenerationError = class extends Error {
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
constructor(code, options = {}) {
|
|
175
|
+
super(DEFAULT_MESSAGES[code]);
|
|
176
|
+
this.name = "ImageGenerationError";
|
|
177
|
+
this.code = code;
|
|
178
|
+
this.param = safeParam(options.param);
|
|
179
|
+
this.httpStatus = Number.isInteger(options.httpStatus) ? Math.min(599, Math.max(400, options.httpStatus)) : DEFAULT_HTTP_STATUS[code];
|
|
180
|
+
this.retryAfterSeconds = typeof options.retryAfterSeconds === "number" && Number.isFinite(options.retryAfterSeconds) && options.retryAfterSeconds >= 0 ? Math.min(604800, Math.floor(options.retryAfterSeconds)) : void 0;
|
|
181
|
+
this.retrySafety = options.retrySafety;
|
|
182
|
+
this.moderationDetails = safeModerationDetails(options.moderationDetails);
|
|
183
|
+
if (options.cause !== void 0) {
|
|
184
|
+
Object.defineProperty(this, "cause", {
|
|
185
|
+
value: options.cause,
|
|
186
|
+
configurable: true,
|
|
187
|
+
enumerable: false,
|
|
188
|
+
writable: false
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
toJSON() {
|
|
193
|
+
return serializeImageGenerationError(this);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
function isImageGenerationError(error) {
|
|
197
|
+
return error instanceof ImageGenerationError;
|
|
198
|
+
}
|
|
199
|
+
function serializeImageGenerationError(error) {
|
|
200
|
+
return {
|
|
201
|
+
type: error.code === "moderation_blocked" ? "image_generation_user_error" : "image_generation_error",
|
|
202
|
+
code: error.code,
|
|
203
|
+
message: DEFAULT_MESSAGES[error.code],
|
|
204
|
+
httpStatus: error.httpStatus,
|
|
205
|
+
...error.param !== void 0 ? { param: error.param } : {},
|
|
206
|
+
...error.retryAfterSeconds !== void 0 ? { retryAfterSeconds: error.retryAfterSeconds } : {},
|
|
207
|
+
...error.retrySafety !== void 0 ? { retrySafety: error.retrySafety } : {},
|
|
208
|
+
...error.moderationDetails !== void 0 ? { moderationDetails: error.moderationDetails } : {}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function normalizeImageGenerationError(error, fallbackCode = "image_generation_failed", options = {}) {
|
|
212
|
+
if (isImageGenerationError(error)) return error;
|
|
213
|
+
return new ImageGenerationError(fallbackCode, { ...options, cause: error });
|
|
214
|
+
}
|
|
215
|
+
function imageGenerationErrorFromPublic(value) {
|
|
216
|
+
if (!value || typeof value !== "object") return new ImageGenerationError("image_generation_failed");
|
|
217
|
+
const record2 = value;
|
|
218
|
+
const code = _imagegenerationtypes.IMAGE_GENERATION_ERROR_CODES.includes(record2.code) ? record2.code : "image_generation_failed";
|
|
219
|
+
return new ImageGenerationError(code, {
|
|
220
|
+
param: safeParam(record2.param),
|
|
221
|
+
httpStatus: typeof record2.httpStatus === "number" ? record2.httpStatus : void 0,
|
|
222
|
+
retryAfterSeconds: typeof record2.retryAfterSeconds === "number" ? record2.retryAfterSeconds : void 0,
|
|
223
|
+
retrySafety: record2.retrySafety === "before_acceptance" || record2.retrySafety === "after_acceptance" || record2.retrySafety === "unknown" ? record2.retrySafety : void 0,
|
|
224
|
+
moderationDetails: record2.moderationDetails && typeof record2.moderationDetails === "object" ? record2.moderationDetails : void 0
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/image-generation/ports.ts
|
|
229
|
+
var _crypto = require('crypto');
|
|
230
|
+
var InMemoryImageAsset = (_class = class {
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
__init() {this.independentlyDecodable = true}
|
|
239
|
+
#bytes;
|
|
240
|
+
constructor(bytes, metadata) {;_class.prototype.__init.call(this);
|
|
241
|
+
if (bytes.byteLength === 0) throw new TypeError("Image asset bytes must not be empty.");
|
|
242
|
+
this.#bytes = bytes.slice();
|
|
243
|
+
this.artifactId = _nullishCoalesce(metadata.artifactId, () => ( _crypto.randomUUID.call(void 0, )));
|
|
244
|
+
this.mimeType = metadata.mimeType;
|
|
245
|
+
this.byteLength = bytes.byteLength;
|
|
246
|
+
this.width = metadata.width;
|
|
247
|
+
this.height = metadata.height;
|
|
248
|
+
this.hasAlpha = metadata.hasAlpha;
|
|
249
|
+
this.sha256 = metadata.sha256;
|
|
250
|
+
}
|
|
251
|
+
async open(options = {}) {
|
|
252
|
+
if (_optionalChain([options, 'access', _23 => _23.signal, 'optionalAccess', _24 => _24.aborted])) throw options.signal.reason;
|
|
253
|
+
const bytes = this.#bytes.slice();
|
|
254
|
+
return new ReadableStream({
|
|
255
|
+
start(controller) {
|
|
256
|
+
if (_optionalChain([options, 'access', _25 => _25.signal, 'optionalAccess', _26 => _26.aborted])) {
|
|
257
|
+
controller.error(options.signal.reason);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
controller.enqueue(bytes);
|
|
261
|
+
controller.close();
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
/** Best-effort zeroization for short-lived verified/probe artifacts. */
|
|
266
|
+
dispose() {
|
|
267
|
+
this.#bytes.fill(0);
|
|
268
|
+
}
|
|
269
|
+
}, _class);
|
|
270
|
+
async function readImageAssetBytes(asset, maxBytes, signal) {
|
|
271
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || asset.byteLength > maxBytes) {
|
|
272
|
+
throw new RangeError("Image asset exceeds the bounded read limit.");
|
|
273
|
+
}
|
|
274
|
+
if (_optionalChain([signal, 'optionalAccess', _27 => _27.aborted])) throw signal.reason;
|
|
275
|
+
const reader = (await asset.open({ signal })).getReader();
|
|
276
|
+
const chunks = [];
|
|
277
|
+
let total = 0;
|
|
278
|
+
try {
|
|
279
|
+
while (true) {
|
|
280
|
+
if (_optionalChain([signal, 'optionalAccess', _28 => _28.aborted])) throw signal.reason;
|
|
281
|
+
const next = await reader.read();
|
|
282
|
+
if (next.done) break;
|
|
283
|
+
total += next.value.byteLength;
|
|
284
|
+
if (total > maxBytes || total > asset.byteLength) {
|
|
285
|
+
await reader.cancel();
|
|
286
|
+
throw new RangeError("Image asset stream exceeded its declared bound.");
|
|
287
|
+
}
|
|
288
|
+
chunks.push(next.value);
|
|
289
|
+
}
|
|
290
|
+
} finally {
|
|
291
|
+
reader.releaseLock();
|
|
292
|
+
}
|
|
293
|
+
if (total !== asset.byteLength) throw new RangeError("Image asset stream length did not match metadata.");
|
|
294
|
+
const output = new Uint8Array(total);
|
|
295
|
+
let offset = 0;
|
|
296
|
+
for (const chunk of chunks) {
|
|
297
|
+
output.set(chunk, offset);
|
|
298
|
+
offset += chunk.byteLength;
|
|
299
|
+
}
|
|
300
|
+
return output;
|
|
301
|
+
}
|
|
302
|
+
var InMemoryImageReferenceStore = class {
|
|
303
|
+
#entries = /* @__PURE__ */ new Map();
|
|
304
|
+
#now;
|
|
305
|
+
constructor(now = Date.now) {
|
|
306
|
+
this.#now = now;
|
|
307
|
+
}
|
|
308
|
+
async save(input) {
|
|
309
|
+
if (!input.artifact && !input.providerReference) {
|
|
310
|
+
throw new TypeError("A retained image reference requires content or an opaque provider reference.");
|
|
311
|
+
}
|
|
312
|
+
if (!Number.isSafeInteger(input.ttlMs) || input.ttlMs <= 0) {
|
|
313
|
+
throw new RangeError("Image reference TTL must be a positive integer.");
|
|
314
|
+
}
|
|
315
|
+
const now = this.#now();
|
|
316
|
+
const metadata = {
|
|
317
|
+
...input.metadata,
|
|
318
|
+
referenceId: _crypto.randomUUID.call(void 0, ),
|
|
319
|
+
createdAt: now,
|
|
320
|
+
expiresAt: now + input.ttlMs
|
|
321
|
+
};
|
|
322
|
+
this.#entries.set(metadata.referenceId, {
|
|
323
|
+
tenantId: input.tenantId,
|
|
324
|
+
metadata,
|
|
325
|
+
value: { artifact: input.artifact, providerReference: input.providerReference },
|
|
326
|
+
activeLeases: 0,
|
|
327
|
+
deleted: false
|
|
328
|
+
});
|
|
329
|
+
return metadata;
|
|
330
|
+
}
|
|
331
|
+
async resolve(tenantId, referenceId) {
|
|
332
|
+
const entry = this.#entries.get(referenceId);
|
|
333
|
+
if (!entry || entry.tenantId !== tenantId || entry.deleted) return { status: "not_found" };
|
|
334
|
+
if (entry.metadata.expiresAt <= this.#now()) return { status: "expired" };
|
|
335
|
+
entry.activeLeases += 1;
|
|
336
|
+
let released = false;
|
|
337
|
+
return {
|
|
338
|
+
status: "found",
|
|
339
|
+
lease: {
|
|
340
|
+
metadata: entry.metadata,
|
|
341
|
+
value: entry.value,
|
|
342
|
+
async release() {
|
|
343
|
+
if (released) return;
|
|
344
|
+
released = true;
|
|
345
|
+
entry.activeLeases = Math.max(0, entry.activeLeases - 1);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
async delete(tenantId, referenceId) {
|
|
351
|
+
const entry = this.#entries.get(referenceId);
|
|
352
|
+
if (!entry || entry.tenantId !== tenantId || entry.deleted) return false;
|
|
353
|
+
entry.deleted = true;
|
|
354
|
+
if (entry.activeLeases === 0) this.#entries.delete(referenceId);
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
async cleanup(now = this.#now()) {
|
|
358
|
+
let removed = 0;
|
|
359
|
+
for (const [referenceId, entry] of this.#entries) {
|
|
360
|
+
if ((entry.deleted || entry.metadata.expiresAt <= now) && entry.activeLeases === 0) {
|
|
361
|
+
this.#entries.delete(referenceId);
|
|
362
|
+
removed += 1;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return removed;
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
async function emitImageTelemetry(sink, record2) {
|
|
369
|
+
try {
|
|
370
|
+
await _optionalChain([sink, 'optionalAccess', _29 => _29.record, 'call', _30 => _30(record2)]);
|
|
371
|
+
} catch (e) {
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/image-generation/ImageOrchestrator.ts
|
|
376
|
+
function safeJobObservability(job) {
|
|
377
|
+
try {
|
|
378
|
+
const value = _optionalChain([job, 'optionalAccess', _31 => _31.observability, 'optionalAccess', _32 => _32.snapshot, 'call', _33 => _33()]);
|
|
379
|
+
if (!value) return void 0;
|
|
380
|
+
const finite = (input) => typeof input === "number" && Number.isFinite(input) && input >= 0 ? Math.min(Number.MAX_SAFE_INTEGER, input) : void 0;
|
|
381
|
+
return {
|
|
382
|
+
queueWaitMs: finite(value.queueWaitMs),
|
|
383
|
+
generationStartedAt: finite(value.generationStartedAt),
|
|
384
|
+
retryCount: finite(value.retryCount),
|
|
385
|
+
authRefreshCount: finite(value.authRefreshCount)
|
|
386
|
+
};
|
|
387
|
+
} catch (e2) {
|
|
388
|
+
return void 0;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function unsupported(param) {
|
|
392
|
+
throw new ImageGenerationError("unsupported_capability", { param });
|
|
393
|
+
}
|
|
394
|
+
function assertRequestSupported(request2, capabilities) {
|
|
395
|
+
if (typeof request2.prompt !== "string" || !request2.prompt.trim() || request2.prompt.length > 32e3) {
|
|
396
|
+
throw new ImageGenerationError("invalid_image_request", { param: "prompt" });
|
|
397
|
+
}
|
|
398
|
+
if (!capabilities.available) unsupported();
|
|
399
|
+
if (!capabilities.models.includes(request2.model)) {
|
|
400
|
+
throw new ImageGenerationError("unsupported_model", { param: "model" });
|
|
401
|
+
}
|
|
402
|
+
if (!Number.isInteger(request2.n) || request2.n <= 0) {
|
|
403
|
+
throw new ImageGenerationError("invalid_image_request", { param: "n" });
|
|
404
|
+
}
|
|
405
|
+
if (request2.n > capabilities.maxOutputImages) unsupported("n");
|
|
406
|
+
if (!capabilities.outputFormats.includes(request2.outputFormat)) unsupported("output_format");
|
|
407
|
+
if (!Array.isArray(capabilities.qualityLevels) || !capabilities.qualityLevels.includes(request2.quality)) {
|
|
408
|
+
unsupported("quality");
|
|
409
|
+
}
|
|
410
|
+
if (!Array.isArray(capabilities.moderationModes) || !capabilities.moderationModes.includes(request2.moderation)) {
|
|
411
|
+
unsupported("moderation");
|
|
412
|
+
}
|
|
413
|
+
if (request2.outputCompression !== void 0 && (!Number.isInteger(request2.outputCompression) || request2.outputCompression < 0 || request2.outputCompression > 100)) {
|
|
414
|
+
throw new ImageGenerationError("invalid_image_request", { param: "output_compression" });
|
|
415
|
+
}
|
|
416
|
+
if (request2.outputCompression !== void 0) {
|
|
417
|
+
const compression = capabilities.outputCompression;
|
|
418
|
+
if (!compression || compression.supported !== true || !compression.formats.includes(request2.outputFormat) || request2.outputCompression < compression.min || request2.outputCompression > compression.max) {
|
|
419
|
+
unsupported("output_compression");
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (request2.background === "transparent" && !capabilities.transparentBackground) {
|
|
423
|
+
unsupported("background");
|
|
424
|
+
}
|
|
425
|
+
if (request2.size.kind === "pixels" && !capabilities.flexibleSizes) unsupported("size");
|
|
426
|
+
if (request2.stream && !capabilities.streaming) unsupported("stream");
|
|
427
|
+
if (!Number.isInteger(request2.partialImages) || request2.partialImages < 0) {
|
|
428
|
+
throw new ImageGenerationError("invalid_image_request", { param: "partial_images" });
|
|
429
|
+
}
|
|
430
|
+
if (request2.partialImages > 0 && !request2.stream) {
|
|
431
|
+
throw new ImageGenerationError("invalid_image_request", { param: "partial_images" });
|
|
432
|
+
}
|
|
433
|
+
if (request2.partialImages > capabilities.maxPartialImages) unsupported("partial_images");
|
|
434
|
+
if (request2.action === "generate") {
|
|
435
|
+
if (!capabilities.generate) unsupported("action");
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (!capabilities.edit) unsupported("action");
|
|
439
|
+
if (request2.images.length === 0) {
|
|
440
|
+
throw new ImageGenerationError("invalid_image_request", { param: "images" });
|
|
441
|
+
}
|
|
442
|
+
if (request2.images.length > capabilities.maxInputImages) unsupported("images");
|
|
443
|
+
if (request2.mask && !capabilities.maskEdit) unsupported("mask");
|
|
444
|
+
}
|
|
445
|
+
function sanitizeUsage(value) {
|
|
446
|
+
if (value === void 0) return void 0;
|
|
447
|
+
const keys = [
|
|
448
|
+
"totalTokens",
|
|
449
|
+
"inputTokens",
|
|
450
|
+
"outputTokens",
|
|
451
|
+
"inputTextTokens",
|
|
452
|
+
"inputImageTokens",
|
|
453
|
+
"outputImageTokens",
|
|
454
|
+
"generatedImages"
|
|
455
|
+
];
|
|
456
|
+
const output = {};
|
|
457
|
+
for (const key of keys) {
|
|
458
|
+
const item = value[key];
|
|
459
|
+
if (item === void 0) continue;
|
|
460
|
+
if (!Number.isSafeInteger(item) || item < 0) {
|
|
461
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
462
|
+
}
|
|
463
|
+
output[key] = item;
|
|
464
|
+
}
|
|
465
|
+
return Object.keys(output).length ? output : void 0;
|
|
466
|
+
}
|
|
467
|
+
function assertArtifactMetadata(asset) {
|
|
468
|
+
if (asset.independentlyDecodable !== true || !asset.mimeType.startsWith("image/") || !Number.isSafeInteger(asset.byteLength) || asset.byteLength <= 0 || !Number.isSafeInteger(asset.width) || asset.width <= 0 || !Number.isSafeInteger(asset.height) || asset.height <= 0 || typeof asset.open !== "function") {
|
|
469
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
function sanitizeProviderOutput(value) {
|
|
473
|
+
if (!value || typeof value !== "object") {
|
|
474
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
475
|
+
}
|
|
476
|
+
assertArtifactMetadata(value.artifact);
|
|
477
|
+
if (value.providerReference !== void 0 && (typeof value.providerReference !== "string" || value.providerReference.length === 0 || value.providerReference.length > 4096)) {
|
|
478
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
479
|
+
}
|
|
480
|
+
if (value.revisedPrompt !== void 0 && (typeof value.revisedPrompt !== "string" || value.revisedPrompt.length > 32e3)) {
|
|
481
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
482
|
+
}
|
|
483
|
+
return {
|
|
484
|
+
artifact: value.artifact,
|
|
485
|
+
...value.providerReference !== void 0 ? { providerReference: value.providerReference } : {},
|
|
486
|
+
...value.revisedPrompt !== void 0 ? { revisedPrompt: value.revisedPrompt } : {}
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
var OUTPUT_MIME_TYPES = {
|
|
490
|
+
png: "image/png",
|
|
491
|
+
jpeg: "image/jpeg",
|
|
492
|
+
webp: "image/webp"
|
|
493
|
+
};
|
|
494
|
+
function assertFinalOutputMatchesRequest(output, request2) {
|
|
495
|
+
const artifact = output.artifact;
|
|
496
|
+
if (artifact.mimeType !== OUTPUT_MIME_TYPES[request2.outputFormat]) {
|
|
497
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
498
|
+
}
|
|
499
|
+
if (request2.size.kind === "pixels" && (artifact.width !== request2.size.width || artifact.height !== request2.size.height)) {
|
|
500
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
501
|
+
}
|
|
502
|
+
if (request2.background === "transparent" && artifact.hasAlpha !== true) {
|
|
503
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
function cancellationError(cause) {
|
|
507
|
+
return new ImageGenerationError("request_cancelled", { cause });
|
|
508
|
+
}
|
|
509
|
+
var ImageOrchestrator = class {
|
|
510
|
+
#registry;
|
|
511
|
+
#referenceStore;
|
|
512
|
+
#telemetrySink;
|
|
513
|
+
#now;
|
|
514
|
+
constructor(options) {
|
|
515
|
+
this.#registry = options.registry;
|
|
516
|
+
this.#referenceStore = options.referenceStore;
|
|
517
|
+
this.#telemetrySink = options.telemetrySink;
|
|
518
|
+
this.#now = _nullishCoalesce(options.now, () => ( Date.now));
|
|
519
|
+
}
|
|
520
|
+
async getCapabilities(providerId, context) {
|
|
521
|
+
let lease;
|
|
522
|
+
try {
|
|
523
|
+
if (context.signal.aborted) throw cancellationError(context.signal.reason);
|
|
524
|
+
lease = await this.#registry.require(providerId).acquire(context);
|
|
525
|
+
if (context.signal.aborted) throw cancellationError(context.signal.reason);
|
|
526
|
+
return lease.capabilities;
|
|
527
|
+
} catch (error) {
|
|
528
|
+
if (context.signal.aborted) throw cancellationError(context.signal.reason);
|
|
529
|
+
throw normalizeImageGenerationError(error);
|
|
530
|
+
} finally {
|
|
531
|
+
await _optionalChain([lease, 'optionalAccess', _34 => _34.release, 'call', _35 => _35()]);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
async *run(request2, context, options) {
|
|
535
|
+
const startedAt = this.#now();
|
|
536
|
+
let acceptedAt;
|
|
537
|
+
let firstPartialAt;
|
|
538
|
+
let lease;
|
|
539
|
+
let job;
|
|
540
|
+
let iterator;
|
|
541
|
+
let releaseStarted = false;
|
|
542
|
+
let cancelled5 = false;
|
|
543
|
+
let cancelPromise;
|
|
544
|
+
let telemetrySent = false;
|
|
545
|
+
let terminalCode;
|
|
546
|
+
let terminalKind = "failed";
|
|
547
|
+
let finalImages = [];
|
|
548
|
+
let finalUsage;
|
|
549
|
+
let accepted = false;
|
|
550
|
+
let providerTerminal = false;
|
|
551
|
+
let completionExposed = false;
|
|
552
|
+
const retentionAttempt = {
|
|
553
|
+
references: [],
|
|
554
|
+
rollbackFailures: 0
|
|
555
|
+
};
|
|
556
|
+
const releaseOnce = async () => {
|
|
557
|
+
if (!lease || releaseStarted) return;
|
|
558
|
+
releaseStarted = true;
|
|
559
|
+
await lease.release();
|
|
560
|
+
};
|
|
561
|
+
const cancelOnce = (reason = "request_cancelled") => {
|
|
562
|
+
if (!job || cancelled5) return _nullishCoalesce(cancelPromise, () => ( Promise.resolve()));
|
|
563
|
+
cancelled5 = true;
|
|
564
|
+
cancelPromise = Promise.resolve(job.cancel(reason)).catch(() => void 0);
|
|
565
|
+
return cancelPromise;
|
|
566
|
+
};
|
|
567
|
+
const cancelStartedNonterminalOnce = (reason = "request_cancelled") => {
|
|
568
|
+
if (providerTerminal) return _nullishCoalesce(cancelPromise, () => ( Promise.resolve()));
|
|
569
|
+
return cancelOnce(reason);
|
|
570
|
+
};
|
|
571
|
+
const sendTelemetry = async () => {
|
|
572
|
+
if (telemetrySent) return;
|
|
573
|
+
telemetrySent = true;
|
|
574
|
+
const editInputs = request2.action === "edit" ? request2.images : [];
|
|
575
|
+
const jobObservability = safeJobObservability(job);
|
|
576
|
+
await emitImageTelemetry(this.#telemetrySink, {
|
|
577
|
+
requestId: context.requestId,
|
|
578
|
+
providerId: options.providerId,
|
|
579
|
+
model: request2.model,
|
|
580
|
+
action: request2.action,
|
|
581
|
+
quality: request2.quality,
|
|
582
|
+
background: request2.background,
|
|
583
|
+
outputFormat: request2.outputFormat,
|
|
584
|
+
streaming: request2.stream,
|
|
585
|
+
inputCount: editInputs.length,
|
|
586
|
+
inputBytes: editInputs.reduce((sum, asset) => sum + asset.byteLength, 0),
|
|
587
|
+
requestedOutputCount: request2.n,
|
|
588
|
+
outputs: finalImages.map((asset) => ({
|
|
589
|
+
mimeType: asset.mimeType,
|
|
590
|
+
byteLength: asset.byteLength,
|
|
591
|
+
width: asset.width,
|
|
592
|
+
height: asset.height,
|
|
593
|
+
hasAlpha: asset.hasAlpha
|
|
594
|
+
})),
|
|
595
|
+
startedAt,
|
|
596
|
+
acceptedAt,
|
|
597
|
+
firstPartialAt,
|
|
598
|
+
..._optionalChain([jobObservability, 'optionalAccess', _36 => _36.generationStartedAt]) !== void 0 ? { generationStartedAt: jobObservability.generationStartedAt } : {},
|
|
599
|
+
finishedAt: this.#now(),
|
|
600
|
+
terminal: terminalKind,
|
|
601
|
+
errorCode: terminalCode,
|
|
602
|
+
usage: finalUsage,
|
|
603
|
+
usageUnavailable: finalUsage === void 0,
|
|
604
|
+
..._optionalChain([jobObservability, 'optionalAccess', _37 => _37.queueWaitMs]) !== void 0 ? { queueWaitMs: jobObservability.queueWaitMs } : {},
|
|
605
|
+
..._optionalChain([jobObservability, 'optionalAccess', _38 => _38.retryCount]) !== void 0 ? { retryCount: jobObservability.retryCount } : {},
|
|
606
|
+
..._optionalChain([jobObservability, 'optionalAccess', _39 => _39.authRefreshCount]) !== void 0 ? { authRefreshCount: jobObservability.authRefreshCount } : {},
|
|
607
|
+
..._optionalChain([options, 'access', _40 => _40.retention, 'optionalAccess', _41 => _41.enabled]) ? { referenceSaveCount: retentionAttempt.references.length } : {},
|
|
608
|
+
...retentionAttempt.rollbackFailures > 0 ? { retentionRollbackFailures: retentionAttempt.rollbackFailures } : {}
|
|
609
|
+
});
|
|
610
|
+
};
|
|
611
|
+
const rollbackRetainedOnce = () => this.#rollbackRetained(
|
|
612
|
+
context.tenantId,
|
|
613
|
+
retentionAttempt
|
|
614
|
+
);
|
|
615
|
+
const onAbort = () => {
|
|
616
|
+
void cancelStartedNonterminalOnce();
|
|
617
|
+
};
|
|
618
|
+
context.signal.addEventListener("abort", onAbort, { once: true });
|
|
619
|
+
try {
|
|
620
|
+
if (context.signal.aborted) throw cancellationError(context.signal.reason);
|
|
621
|
+
lease = await this.#registry.require(options.providerId).acquire(context);
|
|
622
|
+
if (context.signal.aborted) throw cancellationError(context.signal.reason);
|
|
623
|
+
assertRequestSupported(request2, lease.capabilities);
|
|
624
|
+
if (options.requireResponsesTool === true && lease.capabilities.responsesTool !== true) {
|
|
625
|
+
unsupported("tools");
|
|
626
|
+
}
|
|
627
|
+
job = await lease.start(request2);
|
|
628
|
+
if (context.signal.aborted) throw cancellationError(context.signal.reason);
|
|
629
|
+
iterator = job.events[Symbol.asyncIterator]();
|
|
630
|
+
let pendingTerminal;
|
|
631
|
+
const partialIndexes = /* @__PURE__ */ new Map();
|
|
632
|
+
const partialCounts = /* @__PURE__ */ new Map();
|
|
633
|
+
while (true) {
|
|
634
|
+
let next;
|
|
635
|
+
try {
|
|
636
|
+
const read = iterator.next();
|
|
637
|
+
if (context.signal.aborted) throw cancellationError(context.signal.reason);
|
|
638
|
+
next = await Promise.race([
|
|
639
|
+
read,
|
|
640
|
+
new Promise((_resolve, reject) => {
|
|
641
|
+
const abort = () => reject(cancellationError(context.signal.reason));
|
|
642
|
+
context.signal.addEventListener("abort", abort, { once: true });
|
|
643
|
+
void read.then(
|
|
644
|
+
() => context.signal.removeEventListener("abort", abort),
|
|
645
|
+
() => context.signal.removeEventListener("abort", abort)
|
|
646
|
+
);
|
|
647
|
+
})
|
|
648
|
+
]);
|
|
649
|
+
} catch (error) {
|
|
650
|
+
if (context.signal.aborted) throw cancellationError(context.signal.reason);
|
|
651
|
+
throw normalizeImageGenerationError(error, "image_generation_failed", {
|
|
652
|
+
retrySafety: accepted ? "after_acceptance" : "unknown"
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
if (next.done) {
|
|
656
|
+
if (!pendingTerminal) throw new ImageGenerationError("upstream_protocol_changed");
|
|
657
|
+
if (pendingTerminal.type === "completed") {
|
|
658
|
+
const { usage: providerUsage, ...terminalWithoutUsage } = pendingTerminal;
|
|
659
|
+
const safeUsage = sanitizeUsage(providerUsage);
|
|
660
|
+
const references = await this.#retainCompleted(
|
|
661
|
+
pendingTerminal,
|
|
662
|
+
context,
|
|
663
|
+
options.retention,
|
|
664
|
+
retentionAttempt
|
|
665
|
+
);
|
|
666
|
+
const completed = {
|
|
667
|
+
...terminalWithoutUsage,
|
|
668
|
+
...safeUsage ? { usage: safeUsage } : {},
|
|
669
|
+
...references.length ? { references } : {}
|
|
670
|
+
};
|
|
671
|
+
finalImages = completed.images.map((image) => image.artifact);
|
|
672
|
+
finalUsage = completed.usage;
|
|
673
|
+
terminalKind = "completed";
|
|
674
|
+
if (context.signal.aborted) {
|
|
675
|
+
await rollbackRetainedOnce();
|
|
676
|
+
throw cancellationError(context.signal.reason);
|
|
677
|
+
}
|
|
678
|
+
completionExposed = true;
|
|
679
|
+
yield completed;
|
|
680
|
+
} else {
|
|
681
|
+
const safeError = imageGenerationErrorFromPublic(pendingTerminal.error);
|
|
682
|
+
terminalCode = safeError.code;
|
|
683
|
+
terminalKind = safeError.code === "request_cancelled" ? "cancelled" : "failed";
|
|
684
|
+
await sendTelemetry();
|
|
685
|
+
yield { type: "failed", error: serializeImageGenerationError(safeError) };
|
|
686
|
+
}
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const event = next.value;
|
|
690
|
+
if (pendingTerminal) {
|
|
691
|
+
throw new ImageGenerationError("upstream_protocol_changed", {
|
|
692
|
+
retrySafety: accepted ? "after_acceptance" : "unknown"
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
switch (event.type) {
|
|
696
|
+
case "accepted":
|
|
697
|
+
if (accepted || !Number.isFinite(event.acceptedAt)) {
|
|
698
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
699
|
+
}
|
|
700
|
+
accepted = true;
|
|
701
|
+
acceptedAt = event.acceptedAt;
|
|
702
|
+
yield { type: "accepted", acceptedAt: event.acceptedAt };
|
|
703
|
+
break;
|
|
704
|
+
case "partial_image": {
|
|
705
|
+
if (!accepted) throw new ImageGenerationError("upstream_protocol_changed");
|
|
706
|
+
firstPartialAt ??= this.#now();
|
|
707
|
+
const image = sanitizeProviderOutput(event.image);
|
|
708
|
+
if (!Number.isInteger(event.outputIndex) || event.outputIndex < 0 || event.outputIndex >= request2.n || !Number.isInteger(event.partialImageIndex) || event.partialImageIndex < 0) {
|
|
709
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
710
|
+
}
|
|
711
|
+
const previous = partialIndexes.get(event.outputIndex);
|
|
712
|
+
if (previous != null && event.partialImageIndex <= previous) {
|
|
713
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
714
|
+
}
|
|
715
|
+
const nextCount = (_nullishCoalesce(partialCounts.get(event.outputIndex), () => ( 0))) + 1;
|
|
716
|
+
if (nextCount > request2.partialImages || event.partialImageIndex >= request2.partialImages) {
|
|
717
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
718
|
+
}
|
|
719
|
+
partialIndexes.set(event.outputIndex, event.partialImageIndex);
|
|
720
|
+
partialCounts.set(event.outputIndex, nextCount);
|
|
721
|
+
yield {
|
|
722
|
+
type: "partial_image",
|
|
723
|
+
outputIndex: event.outputIndex,
|
|
724
|
+
partialImageIndex: event.partialImageIndex,
|
|
725
|
+
image
|
|
726
|
+
};
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
case "completed": {
|
|
730
|
+
if (!accepted || !Array.isArray(event.images) || event.images.length !== request2.n) {
|
|
731
|
+
throw new ImageGenerationError("upstream_protocol_changed", {
|
|
732
|
+
retrySafety: accepted ? "after_acceptance" : "unknown"
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
const images = event.images.map((image) => {
|
|
736
|
+
const output = sanitizeProviderOutput(image);
|
|
737
|
+
assertFinalOutputMatchesRequest(output, request2);
|
|
738
|
+
return output;
|
|
739
|
+
});
|
|
740
|
+
pendingTerminal = {
|
|
741
|
+
type: "completed",
|
|
742
|
+
images,
|
|
743
|
+
...event.usage !== void 0 ? { usage: event.usage } : {}
|
|
744
|
+
};
|
|
745
|
+
providerTerminal = true;
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
case "failed":
|
|
749
|
+
pendingTerminal = event;
|
|
750
|
+
providerTerminal = true;
|
|
751
|
+
break;
|
|
752
|
+
default:
|
|
753
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
} catch (error) {
|
|
757
|
+
if (completionExposed) throw error;
|
|
758
|
+
const normalized = context.signal.aborted ? cancellationError(context.signal.reason) : normalizeImageGenerationError(error, "image_generation_failed", {
|
|
759
|
+
retrySafety: acceptedAt == null ? "unknown" : "after_acceptance"
|
|
760
|
+
});
|
|
761
|
+
if (normalized.code === "request_cancelled") await rollbackRetainedOnce();
|
|
762
|
+
terminalCode = normalized.code;
|
|
763
|
+
terminalKind = normalized.code === "request_cancelled" ? "cancelled" : "failed";
|
|
764
|
+
await sendTelemetry();
|
|
765
|
+
throw normalized;
|
|
766
|
+
} finally {
|
|
767
|
+
if (completionExposed) await sendTelemetry();
|
|
768
|
+
context.signal.removeEventListener("abort", onAbort);
|
|
769
|
+
await cancelStartedNonterminalOnce();
|
|
770
|
+
try {
|
|
771
|
+
await _optionalChain([iterator, 'optionalAccess', _42 => _42.return, 'optionalCall', _43 => _43()]);
|
|
772
|
+
} catch (e3) {
|
|
773
|
+
}
|
|
774
|
+
await cancelPromise;
|
|
775
|
+
await releaseOnce();
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
async #retainCompleted(event, context, retention, attempt) {
|
|
779
|
+
if (!_optionalChain([retention, 'optionalAccess', _44 => _44.enabled])) return [];
|
|
780
|
+
if (!this.#referenceStore) {
|
|
781
|
+
throw new ImageGenerationError("image_generation_failed", { retrySafety: "after_acceptance" });
|
|
782
|
+
}
|
|
783
|
+
if (!Number.isSafeInteger(retention.ttlMs) || retention.ttlMs <= 0) {
|
|
784
|
+
throw new ImageGenerationError("invalid_image_request", { param: "reference_ttl" });
|
|
785
|
+
}
|
|
786
|
+
const references = [];
|
|
787
|
+
for (const image of event.images) {
|
|
788
|
+
const asset = image.artifact;
|
|
789
|
+
const reference = await this.#referenceStore.save({
|
|
790
|
+
tenantId: context.tenantId,
|
|
791
|
+
ttlMs: retention.ttlMs,
|
|
792
|
+
artifact: asset,
|
|
793
|
+
providerReference: image.providerReference,
|
|
794
|
+
metadata: {
|
|
795
|
+
mimeType: asset.mimeType,
|
|
796
|
+
byteLength: asset.byteLength,
|
|
797
|
+
width: asset.width,
|
|
798
|
+
height: asset.height
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
references.push(reference);
|
|
802
|
+
attempt.references.push(reference);
|
|
803
|
+
if (context.signal.aborted) {
|
|
804
|
+
await this.#rollbackRetained(context.tenantId, attempt);
|
|
805
|
+
throw cancellationError(context.signal.reason);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return references;
|
|
809
|
+
}
|
|
810
|
+
async #rollbackRetained(tenantId, attempt) {
|
|
811
|
+
attempt.rollbackPromise ??= (async () => {
|
|
812
|
+
const store = this.#referenceStore;
|
|
813
|
+
if (!store) return;
|
|
814
|
+
for (const reference of attempt.references) {
|
|
815
|
+
try {
|
|
816
|
+
if (!await store.delete(tenantId, reference.referenceId)) {
|
|
817
|
+
attempt.rollbackFailures += 1;
|
|
818
|
+
}
|
|
819
|
+
} catch (e4) {
|
|
820
|
+
attempt.rollbackFailures += 1;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
})();
|
|
824
|
+
await attempt.rollbackPromise;
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
// src/image-generation/ImageProviderRegistry.ts
|
|
829
|
+
var ImageProviderRegistry = class {
|
|
830
|
+
#providers = /* @__PURE__ */ new Map();
|
|
831
|
+
constructor(providers = []) {
|
|
832
|
+
for (const provider of providers) this.register(provider);
|
|
833
|
+
}
|
|
834
|
+
register(provider) {
|
|
835
|
+
const id = provider.id.trim();
|
|
836
|
+
if (!id) throw new TypeError("Image provider ID must not be empty.");
|
|
837
|
+
if (this.#providers.has(id)) throw new Error(`Duplicate image provider ID: ${id}`);
|
|
838
|
+
this.#providers.set(id, provider);
|
|
839
|
+
}
|
|
840
|
+
resolve(id) {
|
|
841
|
+
return this.#providers.get(id);
|
|
842
|
+
}
|
|
843
|
+
require(id) {
|
|
844
|
+
const provider = this.resolve(id);
|
|
845
|
+
if (!provider) throw new Error(`Image provider is not registered: ${id}`);
|
|
846
|
+
return provider;
|
|
847
|
+
}
|
|
848
|
+
list() {
|
|
849
|
+
return [...this.#providers.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([, provider]) => provider);
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
// src/image-generation/openai-images/TemporaryImageAsset.ts
|
|
854
|
+
var _fs = require('fs');
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
|
|
864
|
+
var _promises = require('fs/promises');
|
|
865
|
+
var _os = require('os');
|
|
866
|
+
var _path = require('path');
|
|
867
|
+
var _stream = require('stream');
|
|
868
|
+
|
|
869
|
+
var IMAGE_REQUEST_DIRECTORY_MARKER_NAME = ".omnicross-images-owner.json";
|
|
870
|
+
var IMAGE_REQUEST_DIRECTORY_MARKER_CONTENT = '{"schema":"omnicross-images-temporary","version":1}\n';
|
|
871
|
+
function cancelled(signal) {
|
|
872
|
+
throw new ImageGenerationError("request_cancelled", { cause: signal.reason });
|
|
873
|
+
}
|
|
874
|
+
function tooLarge() {
|
|
875
|
+
throw new ImageGenerationError("image_too_large");
|
|
876
|
+
}
|
|
877
|
+
function assertPositiveLimit(value) {
|
|
878
|
+
if (!Number.isSafeInteger(value) || value <= 0) throw new TypeError("Temporary file limits must be positive safe integers.");
|
|
879
|
+
}
|
|
880
|
+
var StagedTemporaryFile = class {
|
|
881
|
+
|
|
882
|
+
#path;
|
|
883
|
+
#scope;
|
|
884
|
+
#kind;
|
|
885
|
+
#disposed = false;
|
|
886
|
+
constructor(scope, path, byteLength2, kind) {
|
|
887
|
+
this.#scope = scope;
|
|
888
|
+
this.#path = path;
|
|
889
|
+
this.byteLength = byteLength2;
|
|
890
|
+
this.#kind = kind;
|
|
891
|
+
}
|
|
892
|
+
async open(signal) {
|
|
893
|
+
if (this.#disposed) throw new ImageGenerationError("request_cancelled");
|
|
894
|
+
this.#scope.assertActive();
|
|
895
|
+
if (_optionalChain([signal, 'optionalAccess', _45 => _45.aborted])) cancelled(signal);
|
|
896
|
+
const source = _fs.createReadStream.call(void 0, this.#path, {
|
|
897
|
+
flags: "r",
|
|
898
|
+
mode: 384,
|
|
899
|
+
start: 0,
|
|
900
|
+
end: this.byteLength - 1,
|
|
901
|
+
autoClose: true,
|
|
902
|
+
...signal ? { signal } : {}
|
|
903
|
+
});
|
|
904
|
+
return _stream.Readable.toWeb(source);
|
|
905
|
+
}
|
|
906
|
+
/** Internal validator seam; no path is returned or retained by the caller. */
|
|
907
|
+
async inspect(callback) {
|
|
908
|
+
if (this.#disposed) throw new ImageGenerationError("request_cancelled");
|
|
909
|
+
this.#scope.assertActive();
|
|
910
|
+
return callback(this.#path);
|
|
911
|
+
}
|
|
912
|
+
async dispose() {
|
|
913
|
+
if (this.#disposed || this.#kind !== "spool") return;
|
|
914
|
+
this.#disposed = true;
|
|
915
|
+
await this.#scope.releaseSpool(this.#path, this.byteLength);
|
|
916
|
+
}
|
|
917
|
+
toImageAsset(metadata) {
|
|
918
|
+
return new TemporaryImageAsset(this, metadata);
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
var TemporaryImageAsset = (_class2 = class {
|
|
922
|
+
|
|
923
|
+
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
__init2() {this.independentlyDecodable = true}
|
|
930
|
+
#file;
|
|
931
|
+
constructor(file, metadata) {;_class2.prototype.__init2.call(this);
|
|
932
|
+
if (file.byteLength <= 0) throw new TypeError("Temporary image assets must not be empty.");
|
|
933
|
+
this.#file = file;
|
|
934
|
+
this.artifactId = _crypto.randomUUID.call(void 0, );
|
|
935
|
+
this.mimeType = metadata.mimeType;
|
|
936
|
+
this.byteLength = file.byteLength;
|
|
937
|
+
this.width = metadata.width;
|
|
938
|
+
this.height = metadata.height;
|
|
939
|
+
this.hasAlpha = metadata.hasAlpha;
|
|
940
|
+
this.sha256 = metadata.sha256;
|
|
941
|
+
}
|
|
942
|
+
open(options = {}) {
|
|
943
|
+
return this.#file.open(options.signal);
|
|
944
|
+
}
|
|
945
|
+
}, _class2);
|
|
946
|
+
var RequestFileWriter = class {
|
|
947
|
+
#scope;
|
|
948
|
+
#handle;
|
|
949
|
+
#path;
|
|
950
|
+
#kind;
|
|
951
|
+
#maxBytes;
|
|
952
|
+
#declaredBytes;
|
|
953
|
+
#byteLength = 0;
|
|
954
|
+
#done = false;
|
|
955
|
+
constructor(options) {
|
|
956
|
+
this.#scope = options.scope;
|
|
957
|
+
this.#handle = options.handle;
|
|
958
|
+
this.#path = options.path;
|
|
959
|
+
this.#kind = options.kind;
|
|
960
|
+
this.#maxBytes = options.maxBytes;
|
|
961
|
+
this.#declaredBytes = options.declaredBytes;
|
|
962
|
+
}
|
|
963
|
+
get byteLength() {
|
|
964
|
+
return this.#byteLength;
|
|
965
|
+
}
|
|
966
|
+
async write(chunk, signal) {
|
|
967
|
+
if (this.#done) throw new TypeError("Temporary writer is already closed.");
|
|
968
|
+
if (_optionalChain([signal, 'optionalAccess', _46 => _46.aborted])) cancelled(signal);
|
|
969
|
+
if (!(chunk instanceof Uint8Array) || chunk.byteLength === 0) return;
|
|
970
|
+
const next = this.#byteLength + chunk.byteLength;
|
|
971
|
+
if (!Number.isSafeInteger(next) || next > this.#maxBytes || this.#declaredBytes !== void 0 && next > this.#declaredBytes) {
|
|
972
|
+
tooLarge();
|
|
973
|
+
}
|
|
974
|
+
this.#scope.reserve(this.#kind, chunk.byteLength);
|
|
975
|
+
try {
|
|
976
|
+
let offset = 0;
|
|
977
|
+
while (offset < chunk.byteLength) {
|
|
978
|
+
if (_optionalChain([signal, 'optionalAccess', _47 => _47.aborted])) cancelled(signal);
|
|
979
|
+
const result = await this.#handle.write(chunk, offset, chunk.byteLength - offset, null);
|
|
980
|
+
if (result.bytesWritten <= 0) throw new ImageGenerationError("image_generation_failed");
|
|
981
|
+
offset += result.bytesWritten;
|
|
982
|
+
}
|
|
983
|
+
this.#byteLength = next;
|
|
984
|
+
} catch (error) {
|
|
985
|
+
this.#scope.unreserve(this.#kind, chunk.byteLength);
|
|
986
|
+
throw error;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
async finish() {
|
|
990
|
+
if (this.#done) throw new TypeError("Temporary writer is already closed.");
|
|
991
|
+
this.#done = true;
|
|
992
|
+
this.#scope.writerFinished(this);
|
|
993
|
+
try {
|
|
994
|
+
await this.#handle.sync();
|
|
995
|
+
} finally {
|
|
996
|
+
await this.#handle.close().catch(() => void 0);
|
|
997
|
+
}
|
|
998
|
+
if (this.#byteLength === 0 || this.#declaredBytes !== void 0 && this.#byteLength !== this.#declaredBytes) {
|
|
999
|
+
throw new ImageGenerationError("invalid_image_request");
|
|
1000
|
+
}
|
|
1001
|
+
const info = await _promises.stat.call(void 0, this.#path);
|
|
1002
|
+
if (!info.isFile() || info.size !== this.#byteLength) throw new ImageGenerationError("image_generation_failed");
|
|
1003
|
+
return new StagedTemporaryFile(this.#scope, this.#path, this.#byteLength, this.#kind);
|
|
1004
|
+
}
|
|
1005
|
+
async abort() {
|
|
1006
|
+
if (this.#done) return;
|
|
1007
|
+
this.#done = true;
|
|
1008
|
+
this.#scope.writerFinished(this);
|
|
1009
|
+
await this.#handle.close().catch(() => void 0);
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
var ImageRequestResourceScope = class _ImageRequestResourceScope {
|
|
1013
|
+
#limits;
|
|
1014
|
+
#signal;
|
|
1015
|
+
#safeRoot;
|
|
1016
|
+
#requestDirectory;
|
|
1017
|
+
#budgetLease;
|
|
1018
|
+
#releaseDirectoryLease;
|
|
1019
|
+
#writers = /* @__PURE__ */ new Set();
|
|
1020
|
+
#leases = /* @__PURE__ */ new Set();
|
|
1021
|
+
#inputBytes = 0;
|
|
1022
|
+
#spoolBytes = 0;
|
|
1023
|
+
#cleaned = false;
|
|
1024
|
+
#cleanupPromise;
|
|
1025
|
+
constructor(options) {
|
|
1026
|
+
this.#limits = options.limits;
|
|
1027
|
+
this.#signal = options.signal;
|
|
1028
|
+
this.#safeRoot = options.safeRoot;
|
|
1029
|
+
this.#requestDirectory = options.requestDirectory;
|
|
1030
|
+
this.#budgetLease = options.budgetLease;
|
|
1031
|
+
this.#releaseDirectoryLease = options.releaseDirectoryLease;
|
|
1032
|
+
}
|
|
1033
|
+
static async create(options) {
|
|
1034
|
+
if (options.signal.aborted) cancelled(options.signal);
|
|
1035
|
+
const tenantId = _optionalChain([options, 'access', _48 => _48.tenantId, 'optionalAccess', _49 => _49.trim, 'call', _50 => _50()]);
|
|
1036
|
+
if (options.sharedBudget && !tenantId) {
|
|
1037
|
+
throw new TypeError("A shared image temporary budget requires a trusted tenant id.");
|
|
1038
|
+
}
|
|
1039
|
+
const budgetLease = _optionalChain([options, 'access', _51 => _51.sharedBudget, 'optionalAccess', _52 => _52.acquireScope, 'call', _53 => _53(tenantId)]);
|
|
1040
|
+
let createdDirectory;
|
|
1041
|
+
let requestDirectory;
|
|
1042
|
+
try {
|
|
1043
|
+
const requestedRoot = _path.resolve.call(void 0, _nullishCoalesce(options.tempRoot, () => ( _os.tmpdir.call(void 0, ))));
|
|
1044
|
+
await _promises.mkdir.call(void 0, requestedRoot, { recursive: true, mode: 448 });
|
|
1045
|
+
const safeRoot = await _promises.realpath.call(void 0, requestedRoot);
|
|
1046
|
+
createdDirectory = await _promises.mkdtemp.call(void 0, _path.join.call(void 0, safeRoot, "omnicross-images-"));
|
|
1047
|
+
await _promises.chmod.call(void 0, createdDirectory, 448);
|
|
1048
|
+
requestDirectory = await _promises.realpath.call(void 0, createdDirectory);
|
|
1049
|
+
if (_path.dirname.call(void 0, requestDirectory) !== safeRoot || !_path.basename.call(void 0, requestDirectory).startsWith("omnicross-images-")) {
|
|
1050
|
+
throw new TypeError("Temporary image request directory escaped the injected safe root.");
|
|
1051
|
+
}
|
|
1052
|
+
if (options.ownedDirectoryMarker) {
|
|
1053
|
+
const marker = await _promises.open.call(void 0,
|
|
1054
|
+
_path.join.call(void 0, requestDirectory, IMAGE_REQUEST_DIRECTORY_MARKER_NAME),
|
|
1055
|
+
"wx",
|
|
1056
|
+
384
|
|
1057
|
+
);
|
|
1058
|
+
try {
|
|
1059
|
+
await marker.writeFile(IMAGE_REQUEST_DIRECTORY_MARKER_CONTENT, { encoding: "utf8" });
|
|
1060
|
+
await marker.sync();
|
|
1061
|
+
} finally {
|
|
1062
|
+
await marker.close().catch(() => void 0);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
const releaseDirectoryLease = _optionalChain([options, 'access', _54 => _54.onDirectoryActive, 'optionalCall', _55 => _55(requestDirectory)]);
|
|
1066
|
+
return new _ImageRequestResourceScope({
|
|
1067
|
+
...options,
|
|
1068
|
+
safeRoot,
|
|
1069
|
+
requestDirectory,
|
|
1070
|
+
...budgetLease ? { budgetLease } : {},
|
|
1071
|
+
...releaseDirectoryLease ? { releaseDirectoryLease } : {}
|
|
1072
|
+
});
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
if (createdDirectory) {
|
|
1075
|
+
await _promises.rm.call(void 0, createdDirectory, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 }).catch(() => void 0);
|
|
1076
|
+
}
|
|
1077
|
+
_optionalChain([budgetLease, 'optionalAccess', _56 => _56.releaseScope, 'call', _57 => _57()]);
|
|
1078
|
+
throw error;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
assertActive() {
|
|
1082
|
+
if (this.#cleaned) throw new ImageGenerationError("request_cancelled");
|
|
1083
|
+
if (this.#signal.aborted) cancelled(this.#signal);
|
|
1084
|
+
}
|
|
1085
|
+
reserve(kind, bytes) {
|
|
1086
|
+
this.assertActive();
|
|
1087
|
+
const current = kind === "input" ? this.#inputBytes : this.#spoolBytes;
|
|
1088
|
+
const max = kind === "input" ? this.#limits.maxTotalInputBytes : this.#limits.maxSpoolBytes;
|
|
1089
|
+
if (!Number.isSafeInteger(current + bytes) || current + bytes > max) tooLarge();
|
|
1090
|
+
_optionalChain([this, 'access', _58 => _58.#budgetLease, 'optionalAccess', _59 => _59.reserve, 'call', _60 => _60(bytes)]);
|
|
1091
|
+
if (kind === "input") this.#inputBytes += bytes;
|
|
1092
|
+
else this.#spoolBytes += bytes;
|
|
1093
|
+
}
|
|
1094
|
+
unreserve(kind, bytes) {
|
|
1095
|
+
const current = kind === "input" ? this.#inputBytes : this.#spoolBytes;
|
|
1096
|
+
const released = Math.min(current, Math.max(0, bytes));
|
|
1097
|
+
if (kind === "input") this.#inputBytes -= released;
|
|
1098
|
+
else this.#spoolBytes -= released;
|
|
1099
|
+
if (released > 0) _optionalChain([this, 'access', _61 => _61.#budgetLease, 'optionalAccess', _62 => _62.release, 'call', _63 => _63(released)]);
|
|
1100
|
+
}
|
|
1101
|
+
writerFinished(writer) {
|
|
1102
|
+
this.#writers.delete(writer);
|
|
1103
|
+
}
|
|
1104
|
+
async releaseSpool(privatePath, bytes) {
|
|
1105
|
+
const target = _path.resolve.call(void 0, privatePath);
|
|
1106
|
+
if (_path.dirname.call(void 0, target) !== this.#requestDirectory || _path.basename.call(void 0, target).length < 16) {
|
|
1107
|
+
throw new TypeError("Refusing to remove an unverified image spool.");
|
|
1108
|
+
}
|
|
1109
|
+
try {
|
|
1110
|
+
await _promises.unlink.call(void 0, target).catch((error) => {
|
|
1111
|
+
if (error.code !== "ENOENT") throw error;
|
|
1112
|
+
});
|
|
1113
|
+
} finally {
|
|
1114
|
+
this.unreserve("spool", bytes);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
async createWriter(options) {
|
|
1118
|
+
this.assertActive();
|
|
1119
|
+
assertPositiveLimit(options.maxBytes);
|
|
1120
|
+
if (options.declaredBytes !== void 0) {
|
|
1121
|
+
if (!Number.isSafeInteger(options.declaredBytes) || options.declaredBytes <= 0) tooLarge();
|
|
1122
|
+
if (options.declaredBytes > options.maxBytes) tooLarge();
|
|
1123
|
+
}
|
|
1124
|
+
const privatePath = _path.join.call(void 0, this.#requestDirectory, _crypto.randomUUID.call(void 0, ));
|
|
1125
|
+
const handle = await _promises.open.call(void 0, privatePath, "wx", 384);
|
|
1126
|
+
const writer = new RequestFileWriter({
|
|
1127
|
+
scope: this,
|
|
1128
|
+
handle,
|
|
1129
|
+
path: privatePath,
|
|
1130
|
+
...options
|
|
1131
|
+
});
|
|
1132
|
+
this.#writers.add(writer);
|
|
1133
|
+
return writer;
|
|
1134
|
+
}
|
|
1135
|
+
async materialize(source, options = {}) {
|
|
1136
|
+
const signal = _nullishCoalesce(options.signal, () => ( this.#signal));
|
|
1137
|
+
const writer = await this.createWriter({
|
|
1138
|
+
kind: _nullishCoalesce(options.kind, () => ( "input")),
|
|
1139
|
+
maxBytes: _nullishCoalesce(options.maxBytes, () => ( this.#limits.maxFileBytes)),
|
|
1140
|
+
...options.declaredBytes !== void 0 ? { declaredBytes: options.declaredBytes } : {}
|
|
1141
|
+
});
|
|
1142
|
+
try {
|
|
1143
|
+
for await (const chunk of source) await writer.write(chunk, signal);
|
|
1144
|
+
return await writer.finish();
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
await writer.abort();
|
|
1147
|
+
throw error;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
addLease(lease) {
|
|
1151
|
+
this.assertActive();
|
|
1152
|
+
this.#leases.add(lease);
|
|
1153
|
+
}
|
|
1154
|
+
async cleanup() {
|
|
1155
|
+
if (this.#cleanupPromise) return this.#cleanupPromise;
|
|
1156
|
+
this.#cleaned = true;
|
|
1157
|
+
this.#cleanupPromise = (async () => {
|
|
1158
|
+
try {
|
|
1159
|
+
await Promise.allSettled([...this.#writers].map((writer) => writer.abort()));
|
|
1160
|
+
this.#writers.clear();
|
|
1161
|
+
await Promise.allSettled([...this.#leases].map((lease) => lease.release()));
|
|
1162
|
+
this.#leases.clear();
|
|
1163
|
+
const target = _path.resolve.call(void 0, this.#requestDirectory);
|
|
1164
|
+
if (_path.dirname.call(void 0, target) !== this.#safeRoot || !_path.basename.call(void 0, target).startsWith("omnicross-images-")) {
|
|
1165
|
+
throw new TypeError("Refusing to clean an unverified temporary image directory.");
|
|
1166
|
+
}
|
|
1167
|
+
await _promises.rm.call(void 0, target, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
|
|
1168
|
+
} finally {
|
|
1169
|
+
this.unreserve("input", this.#inputBytes);
|
|
1170
|
+
this.unreserve("spool", this.#spoolBytes);
|
|
1171
|
+
_optionalChain([this, 'access', _64 => _64.#budgetLease, 'optionalAccess', _65 => _65.releaseScope, 'call', _66 => _66()]);
|
|
1172
|
+
_optionalChain([this, 'access', _67 => _67.#releaseDirectoryLease, 'optionalCall', _68 => _68()]);
|
|
1173
|
+
}
|
|
1174
|
+
})();
|
|
1175
|
+
return this.#cleanupPromise;
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
function createImageRequestResourceScope(limits, signal, tempRoot, ownership) {
|
|
1179
|
+
return ImageRequestResourceScope.create({
|
|
1180
|
+
limits,
|
|
1181
|
+
signal,
|
|
1182
|
+
...tempRoot ? { tempRoot } : {},
|
|
1183
|
+
...ownership ? ownership : {}
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// src/image-generation/openai-images/imageApiErrors.ts
|
|
1188
|
+
|
|
1189
|
+
var SAFE_STATUS = {
|
|
1190
|
+
invalid_image_request: 400,
|
|
1191
|
+
unsupported_model: 400,
|
|
1192
|
+
unsupported_capability: 422,
|
|
1193
|
+
invalid_api_key: 401,
|
|
1194
|
+
upstream_auth_required: 503,
|
|
1195
|
+
image_reference_not_found: 404,
|
|
1196
|
+
image_reference_expired: 410,
|
|
1197
|
+
image_too_large: 413,
|
|
1198
|
+
unsupported_image_type: 415,
|
|
1199
|
+
moderation_blocked: 422,
|
|
1200
|
+
upstream_rate_limited: 429,
|
|
1201
|
+
subscription_usage_limit_reached: 429,
|
|
1202
|
+
image_queue_full: 429,
|
|
1203
|
+
image_queue_timeout: 504,
|
|
1204
|
+
request_cancelled: 499,
|
|
1205
|
+
upstream_protocol_changed: 502,
|
|
1206
|
+
image_generation_failed: 502,
|
|
1207
|
+
image_generation_timeout: 504
|
|
1208
|
+
};
|
|
1209
|
+
function safeImageRequestId(value) {
|
|
1210
|
+
return typeof value === "string" && /^[A-Za-z0-9_.:-]{1,128}$/.test(value) ? value : _crypto.randomUUID.call(void 0, );
|
|
1211
|
+
}
|
|
1212
|
+
function imageApiErrorFromUnknown(error) {
|
|
1213
|
+
return error instanceof ImageGenerationError ? error : normalizeImageGenerationError(error, "image_generation_failed");
|
|
1214
|
+
}
|
|
1215
|
+
function imageApiErrorFromProvider(value) {
|
|
1216
|
+
return imageGenerationErrorFromPublic(value);
|
|
1217
|
+
}
|
|
1218
|
+
function serializeImageApiError(error) {
|
|
1219
|
+
const domainError = imageApiErrorFromUnknown(error);
|
|
1220
|
+
const safe = serializeImageGenerationError(domainError);
|
|
1221
|
+
const headers = {};
|
|
1222
|
+
if (safe.retryAfterSeconds !== void 0) headers["Retry-After"] = String(safe.retryAfterSeconds);
|
|
1223
|
+
return {
|
|
1224
|
+
status: SAFE_STATUS[safe.code],
|
|
1225
|
+
headers,
|
|
1226
|
+
domainError,
|
|
1227
|
+
body: {
|
|
1228
|
+
error: {
|
|
1229
|
+
message: safe.message,
|
|
1230
|
+
type: safe.type,
|
|
1231
|
+
code: safe.code,
|
|
1232
|
+
...safe.param !== void 0 ? { param: safe.param } : {},
|
|
1233
|
+
...safe.retryAfterSeconds !== void 0 ? { retry_after_seconds: safe.retryAfterSeconds } : {},
|
|
1234
|
+
...safe.moderationDetails !== void 0 ? {
|
|
1235
|
+
moderation_details: {
|
|
1236
|
+
...safe.moderationDetails.stage ? { stage: safe.moderationDetails.stage } : {},
|
|
1237
|
+
..._optionalChain([safe, 'access', _69 => _69.moderationDetails, 'access', _70 => _70.categories, 'optionalAccess', _71 => _71.length]) ? { categories: safe.moderationDetails.categories } : {}
|
|
1238
|
+
}
|
|
1239
|
+
} : {}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
function writeImageApiError(response, error, requestId) {
|
|
1245
|
+
const mapped = serializeImageApiError(error);
|
|
1246
|
+
if (!response.headersSent && !response.writableEnded && !response.destroyed) {
|
|
1247
|
+
response.writeHead(mapped.status, {
|
|
1248
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
1249
|
+
"x-request-id": safeImageRequestId(requestId),
|
|
1250
|
+
...mapped.headers
|
|
1251
|
+
});
|
|
1252
|
+
response.end(JSON.stringify(mapped.body));
|
|
1253
|
+
}
|
|
1254
|
+
return mapped.domainError;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// src/image-generation/openai-images/imageApiResponse.ts
|
|
1258
|
+
function cancelled2(signal) {
|
|
1259
|
+
throw new ImageGenerationError("request_cancelled", { cause: signal.reason });
|
|
1260
|
+
}
|
|
1261
|
+
async function writeWithBackpressure(response, chunk, signal) {
|
|
1262
|
+
if (signal.aborted || response.destroyed || response.writableEnded) cancelled2(signal);
|
|
1263
|
+
if (response.write(chunk)) return;
|
|
1264
|
+
await new Promise((resolve2, reject) => {
|
|
1265
|
+
const cleanup = () => {
|
|
1266
|
+
response.removeListener("drain", onDrain);
|
|
1267
|
+
response.removeListener("close", onClose);
|
|
1268
|
+
response.removeListener("error", onError);
|
|
1269
|
+
signal.removeEventListener("abort", onAbort);
|
|
1270
|
+
};
|
|
1271
|
+
const onDrain = () => {
|
|
1272
|
+
cleanup();
|
|
1273
|
+
resolve2();
|
|
1274
|
+
};
|
|
1275
|
+
const onClose = () => {
|
|
1276
|
+
cleanup();
|
|
1277
|
+
reject(new ImageGenerationError("request_cancelled"));
|
|
1278
|
+
};
|
|
1279
|
+
const onError = () => {
|
|
1280
|
+
cleanup();
|
|
1281
|
+
reject(new ImageGenerationError("request_cancelled"));
|
|
1282
|
+
};
|
|
1283
|
+
const onAbort = () => {
|
|
1284
|
+
cleanup();
|
|
1285
|
+
reject(new ImageGenerationError("request_cancelled", { cause: signal.reason }));
|
|
1286
|
+
};
|
|
1287
|
+
response.once("drain", onDrain);
|
|
1288
|
+
response.once("close", onClose);
|
|
1289
|
+
response.once("error", onError);
|
|
1290
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
async function* encodeAssetBase64Chunks(asset, limit, signal) {
|
|
1294
|
+
if (asset.byteLength <= 0 || asset.byteLength > limit) throw new ImageGenerationError("image_too_large");
|
|
1295
|
+
const reader = (await asset.open({ signal })).getReader();
|
|
1296
|
+
let carry = Buffer.alloc(0);
|
|
1297
|
+
let observed = 0;
|
|
1298
|
+
try {
|
|
1299
|
+
while (true) {
|
|
1300
|
+
if (signal.aborted) cancelled2(signal);
|
|
1301
|
+
const next = await reader.read();
|
|
1302
|
+
if (next.done) break;
|
|
1303
|
+
observed += next.value.byteLength;
|
|
1304
|
+
if (observed > limit || observed > asset.byteLength) throw new ImageGenerationError("image_too_large");
|
|
1305
|
+
const source = carry.byteLength ? Buffer.concat([carry, Buffer.from(next.value)]) : Buffer.from(next.value);
|
|
1306
|
+
const complete = source.byteLength - source.byteLength % 3;
|
|
1307
|
+
if (complete) yield Buffer.from(source.subarray(0, complete).toString("base64"), "ascii");
|
|
1308
|
+
carry = Buffer.from(source.subarray(complete));
|
|
1309
|
+
}
|
|
1310
|
+
if (observed !== asset.byteLength) throw new ImageGenerationError("upstream_protocol_changed");
|
|
1311
|
+
if (carry.byteLength) yield Buffer.from(carry.toString("base64"), "ascii");
|
|
1312
|
+
} finally {
|
|
1313
|
+
await reader.cancel().catch(() => void 0);
|
|
1314
|
+
reader.releaseLock();
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
function outputUsage(usage) {
|
|
1318
|
+
if (!usage) return void 0;
|
|
1319
|
+
return {
|
|
1320
|
+
...usage.totalTokens !== void 0 ? { total_tokens: usage.totalTokens } : {},
|
|
1321
|
+
...usage.inputTokens !== void 0 ? { input_tokens: usage.inputTokens } : {},
|
|
1322
|
+
...usage.outputTokens !== void 0 ? { output_tokens: usage.outputTokens } : {},
|
|
1323
|
+
...usage.generatedImages !== void 0 ? { generated_images: usage.generatedImages } : {},
|
|
1324
|
+
...usage.inputTextTokens !== void 0 || usage.inputImageTokens !== void 0 ? {
|
|
1325
|
+
input_tokens_details: {
|
|
1326
|
+
...usage.inputTextTokens !== void 0 ? { text_tokens: usage.inputTextTokens } : {},
|
|
1327
|
+
...usage.inputImageTokens !== void 0 ? { image_tokens: usage.inputImageTokens } : {}
|
|
1328
|
+
}
|
|
1329
|
+
} : {},
|
|
1330
|
+
...usage.outputImageTokens !== void 0 ? { output_tokens_details: { image_tokens: usage.outputImageTokens } } : {}
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
async function stageOutputs(event, count, scope, limits, signal) {
|
|
1334
|
+
if (event.images.length !== count) throw new ImageGenerationError("upstream_protocol_changed");
|
|
1335
|
+
const total = event.images.reduce((sum, image) => sum + image.artifact.byteLength, 0);
|
|
1336
|
+
if (!Number.isSafeInteger(total) || total > limits.maxTotalOutputBytes) {
|
|
1337
|
+
throw new ImageGenerationError("image_too_large");
|
|
1338
|
+
}
|
|
1339
|
+
const outputs = [];
|
|
1340
|
+
for (const image of event.images) {
|
|
1341
|
+
const encodedLength = Math.ceil(image.artifact.byteLength / 3) * 4;
|
|
1342
|
+
const base64 = await scope.materialize(
|
|
1343
|
+
encodeAssetBase64Chunks(image.artifact, limits.maxOutputBytes, signal),
|
|
1344
|
+
{
|
|
1345
|
+
kind: "spool",
|
|
1346
|
+
maxBytes: encodedLength,
|
|
1347
|
+
declaredBytes: encodedLength,
|
|
1348
|
+
signal
|
|
1349
|
+
}
|
|
1350
|
+
);
|
|
1351
|
+
outputs.push({
|
|
1352
|
+
base64,
|
|
1353
|
+
...image.revisedPrompt !== void 0 ? { revisedPrompt: image.revisedPrompt } : {}
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
return outputs;
|
|
1357
|
+
}
|
|
1358
|
+
async function terminalEvent(events, signal) {
|
|
1359
|
+
let accepted = false;
|
|
1360
|
+
let completed;
|
|
1361
|
+
for await (const event of events) {
|
|
1362
|
+
if (signal.aborted) cancelled2(signal);
|
|
1363
|
+
if (event.type === "accepted") {
|
|
1364
|
+
if (accepted || completed) throw new ImageGenerationError("upstream_protocol_changed");
|
|
1365
|
+
accepted = true;
|
|
1366
|
+
} else if (event.type === "partial_image") {
|
|
1367
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
1368
|
+
} else if (event.type === "failed") {
|
|
1369
|
+
throw imageApiErrorFromProvider(event.error);
|
|
1370
|
+
} else {
|
|
1371
|
+
if (!accepted || completed) throw new ImageGenerationError("upstream_protocol_changed");
|
|
1372
|
+
completed = event;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
if (!completed) throw new ImageGenerationError("upstream_protocol_changed");
|
|
1376
|
+
return completed;
|
|
1377
|
+
}
|
|
1378
|
+
async function writeImageApiResponse(options) {
|
|
1379
|
+
const completed = await terminalEvent(options.events, options.signal);
|
|
1380
|
+
const outputs = await stageOutputs(
|
|
1381
|
+
completed,
|
|
1382
|
+
options.requestedCount,
|
|
1383
|
+
options.scope,
|
|
1384
|
+
options.limits,
|
|
1385
|
+
options.signal
|
|
1386
|
+
);
|
|
1387
|
+
const usage = outputUsage(completed.usage);
|
|
1388
|
+
options.response.writeHead(200, {
|
|
1389
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
1390
|
+
"x-request-id": safeImageRequestId(options.requestId)
|
|
1391
|
+
});
|
|
1392
|
+
await writeWithBackpressure(
|
|
1393
|
+
options.response,
|
|
1394
|
+
`{"created":${Math.floor(options.createdAt / 1e3)},"data":[`,
|
|
1395
|
+
options.signal
|
|
1396
|
+
);
|
|
1397
|
+
for (let index = 0; index < outputs.length; index += 1) {
|
|
1398
|
+
const output = outputs[index];
|
|
1399
|
+
await writeWithBackpressure(options.response, `${index ? "," : ""}{"b64_json":"`, options.signal);
|
|
1400
|
+
const reader = (await output.base64.open(options.signal)).getReader();
|
|
1401
|
+
try {
|
|
1402
|
+
while (true) {
|
|
1403
|
+
const next = await reader.read();
|
|
1404
|
+
if (next.done) break;
|
|
1405
|
+
await writeWithBackpressure(options.response, next.value, options.signal);
|
|
1406
|
+
}
|
|
1407
|
+
} finally {
|
|
1408
|
+
await reader.cancel().catch(() => void 0);
|
|
1409
|
+
reader.releaseLock();
|
|
1410
|
+
}
|
|
1411
|
+
await writeWithBackpressure(
|
|
1412
|
+
options.response,
|
|
1413
|
+
`"${output.revisedPrompt !== void 0 ? `,"revised_prompt":${JSON.stringify(output.revisedPrompt)}` : ""}}`,
|
|
1414
|
+
options.signal
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
await writeWithBackpressure(
|
|
1418
|
+
options.response,
|
|
1419
|
+
`]${usage ? `,"usage":${JSON.stringify(usage)}` : ""}}`,
|
|
1420
|
+
options.signal
|
|
1421
|
+
);
|
|
1422
|
+
options.response.end();
|
|
1423
|
+
await Promise.all(outputs.map((output) => output.base64.dispose()));
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// src/image-generation/openai-images/imageApiSse.ts
|
|
1427
|
+
function cancelled3(signal) {
|
|
1428
|
+
throw new ImageGenerationError("request_cancelled", { cause: signal.reason });
|
|
1429
|
+
}
|
|
1430
|
+
async function stageAsset(asset, scope, limits, signal) {
|
|
1431
|
+
const encodedLength = Math.ceil(asset.byteLength / 3) * 4;
|
|
1432
|
+
return scope.materialize(encodeAssetBase64Chunks(asset, limits.maxOutputBytes, signal), {
|
|
1433
|
+
kind: "spool",
|
|
1434
|
+
maxBytes: encodedLength,
|
|
1435
|
+
declaredBytes: encodedLength,
|
|
1436
|
+
signal
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
async function writeAssetFrame(options) {
|
|
1440
|
+
const prefix = JSON.stringify({ type: options.type, ...options.beforeImage, b64_json: "__IMAGE__" });
|
|
1441
|
+
const marker = '"__IMAGE__"';
|
|
1442
|
+
const markerAt = prefix.indexOf(marker);
|
|
1443
|
+
await writeWithBackpressure(
|
|
1444
|
+
options.response,
|
|
1445
|
+
`event: ${options.type}
|
|
1446
|
+
data: ${prefix.slice(0, markerAt)}"`,
|
|
1447
|
+
options.signal
|
|
1448
|
+
);
|
|
1449
|
+
const reader = (await options.spool.open(options.signal)).getReader();
|
|
1450
|
+
try {
|
|
1451
|
+
while (true) {
|
|
1452
|
+
const next = await reader.read();
|
|
1453
|
+
if (next.done) break;
|
|
1454
|
+
await writeWithBackpressure(options.response, next.value, options.signal);
|
|
1455
|
+
}
|
|
1456
|
+
} finally {
|
|
1457
|
+
await reader.cancel().catch(() => void 0);
|
|
1458
|
+
reader.releaseLock();
|
|
1459
|
+
}
|
|
1460
|
+
const suffixObject = _nullishCoalesce(options.afterImage, () => ( {}));
|
|
1461
|
+
const suffix = JSON.stringify(suffixObject);
|
|
1462
|
+
await writeWithBackpressure(
|
|
1463
|
+
options.response,
|
|
1464
|
+
`"${prefix.slice(markerAt + marker.length, -1)}${suffix === "{}" ? "" : `,${suffix.slice(1, -1)}`}}
|
|
1465
|
+
|
|
1466
|
+
`,
|
|
1467
|
+
options.signal
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
async function emitOutput(options) {
|
|
1471
|
+
const spool = await stageAsset(options.output.artifact, options.scope, options.limits, options.signal);
|
|
1472
|
+
try {
|
|
1473
|
+
await writeAssetFrame({
|
|
1474
|
+
response: options.response,
|
|
1475
|
+
spool,
|
|
1476
|
+
type: options.type,
|
|
1477
|
+
beforeImage: {
|
|
1478
|
+
output_index: options.outputIndex,
|
|
1479
|
+
...options.partialImageIndex !== void 0 ? { partial_image_index: options.partialImageIndex } : {}
|
|
1480
|
+
},
|
|
1481
|
+
afterImage: {
|
|
1482
|
+
...options.output.revisedPrompt !== void 0 ? { revised_prompt: options.output.revisedPrompt } : {},
|
|
1483
|
+
..._nullishCoalesce(options.finalFields, () => ( {}))
|
|
1484
|
+
},
|
|
1485
|
+
signal: options.signal
|
|
1486
|
+
});
|
|
1487
|
+
} finally {
|
|
1488
|
+
await spool.dispose();
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
async function writeImageApiSse(options) {
|
|
1492
|
+
const prefix = options.action === "generation" ? "image_generation" : "image_edit";
|
|
1493
|
+
let accepted = false;
|
|
1494
|
+
let completed = false;
|
|
1495
|
+
try {
|
|
1496
|
+
for await (const event of options.events) {
|
|
1497
|
+
if (options.signal.aborted) cancelled3(options.signal);
|
|
1498
|
+
if (event.type === "accepted") {
|
|
1499
|
+
if (accepted) throw new ImageGenerationError("upstream_protocol_changed");
|
|
1500
|
+
accepted = true;
|
|
1501
|
+
options.response.writeHead(200, {
|
|
1502
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
1503
|
+
"Cache-Control": "no-cache, no-transform",
|
|
1504
|
+
Connection: "keep-alive",
|
|
1505
|
+
"x-request-id": safeImageRequestId(options.requestId)
|
|
1506
|
+
});
|
|
1507
|
+
options.response.flushHeaders();
|
|
1508
|
+
continue;
|
|
1509
|
+
}
|
|
1510
|
+
if (event.type === "failed") throw imageApiErrorFromProvider(event.error);
|
|
1511
|
+
if (!accepted) throw new ImageGenerationError("upstream_protocol_changed");
|
|
1512
|
+
if (event.type === "partial_image") {
|
|
1513
|
+
await emitOutput({
|
|
1514
|
+
response: options.response,
|
|
1515
|
+
output: event.image,
|
|
1516
|
+
type: `${prefix}.partial_image`,
|
|
1517
|
+
outputIndex: event.outputIndex,
|
|
1518
|
+
partialImageIndex: event.partialImageIndex,
|
|
1519
|
+
finalFields: {
|
|
1520
|
+
background: options.metadata.background,
|
|
1521
|
+
created_at: Math.floor(options.createdAt / 1e3),
|
|
1522
|
+
output_format: options.metadata.outputFormat,
|
|
1523
|
+
quality: options.metadata.quality,
|
|
1524
|
+
size: options.metadata.size
|
|
1525
|
+
},
|
|
1526
|
+
scope: options.scope,
|
|
1527
|
+
limits: options.limits,
|
|
1528
|
+
signal: options.signal
|
|
1529
|
+
});
|
|
1530
|
+
continue;
|
|
1531
|
+
}
|
|
1532
|
+
if (event.images.length !== options.requestedCount) {
|
|
1533
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
1534
|
+
}
|
|
1535
|
+
const total = event.images.reduce((sum, output) => sum + output.artifact.byteLength, 0);
|
|
1536
|
+
if (!Number.isSafeInteger(total) || total > options.limits.maxTotalOutputBytes) {
|
|
1537
|
+
throw new ImageGenerationError("image_too_large");
|
|
1538
|
+
}
|
|
1539
|
+
const usage = outputUsage(event.usage);
|
|
1540
|
+
for (let index = 0; index < event.images.length; index += 1) {
|
|
1541
|
+
await emitOutput({
|
|
1542
|
+
response: options.response,
|
|
1543
|
+
output: event.images[index],
|
|
1544
|
+
type: `${prefix}.completed`,
|
|
1545
|
+
outputIndex: index,
|
|
1546
|
+
finalFields: {
|
|
1547
|
+
background: options.metadata.background,
|
|
1548
|
+
created_at: Math.floor(options.createdAt / 1e3),
|
|
1549
|
+
output_format: options.metadata.outputFormat,
|
|
1550
|
+
quality: options.metadata.quality,
|
|
1551
|
+
size: options.metadata.size,
|
|
1552
|
+
...index === event.images.length - 1 && usage ? { usage } : {}
|
|
1553
|
+
},
|
|
1554
|
+
scope: options.scope,
|
|
1555
|
+
limits: options.limits,
|
|
1556
|
+
signal: options.signal
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
completed = true;
|
|
1560
|
+
}
|
|
1561
|
+
if (!completed) throw new ImageGenerationError("upstream_protocol_changed");
|
|
1562
|
+
if (!options.response.writableEnded) options.response.end();
|
|
1563
|
+
} catch (error) {
|
|
1564
|
+
const mapped = serializeImageApiError(error);
|
|
1565
|
+
if (!accepted) throw mapped.domainError;
|
|
1566
|
+
if (mapped.domainError.code !== "request_cancelled" && !options.signal.aborted && !options.response.destroyed && !options.response.writableEnded) {
|
|
1567
|
+
const body = JSON.stringify({ type: "error", error: mapped.body.error });
|
|
1568
|
+
await writeWithBackpressure(options.response, `event: error
|
|
1569
|
+
data: ${body}
|
|
1570
|
+
|
|
1571
|
+
`, options.signal).catch(() => void 0);
|
|
1572
|
+
options.response.end();
|
|
1573
|
+
}
|
|
1574
|
+
throw mapped.domainError;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
// src/image-generation/openai-images/normalizeOptions.ts
|
|
1579
|
+
var OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
1580
|
+
"prompt",
|
|
1581
|
+
"model",
|
|
1582
|
+
"n",
|
|
1583
|
+
"quality",
|
|
1584
|
+
"size",
|
|
1585
|
+
"background",
|
|
1586
|
+
"output_format",
|
|
1587
|
+
"output_compression",
|
|
1588
|
+
"moderation",
|
|
1589
|
+
"stream",
|
|
1590
|
+
"partial_images",
|
|
1591
|
+
"user"
|
|
1592
|
+
]);
|
|
1593
|
+
function invalid(param) {
|
|
1594
|
+
throw new ImageGenerationError("invalid_image_request", { param });
|
|
1595
|
+
}
|
|
1596
|
+
function recordOf(value) {
|
|
1597
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) invalid();
|
|
1598
|
+
return value;
|
|
1599
|
+
}
|
|
1600
|
+
function scalar(value, multipart) {
|
|
1601
|
+
if (!multipart || typeof value !== "string") return value;
|
|
1602
|
+
return value.trim();
|
|
1603
|
+
}
|
|
1604
|
+
function integer(value, fallback, param, min, max, multipart) {
|
|
1605
|
+
const item = scalar(value, multipart);
|
|
1606
|
+
const parsed = typeof item === "string" && /^\d+$/.test(item) ? Number(item) : item;
|
|
1607
|
+
if (value === void 0) return fallback;
|
|
1608
|
+
if (!Number.isSafeInteger(parsed) || parsed < min || parsed > max) invalid(param);
|
|
1609
|
+
return parsed;
|
|
1610
|
+
}
|
|
1611
|
+
function booleanValue(value, fallback, param, multipart) {
|
|
1612
|
+
if (value === void 0) return fallback;
|
|
1613
|
+
const item = scalar(value, multipart);
|
|
1614
|
+
if (item === true || item === "true") return true;
|
|
1615
|
+
if (item === false || item === "false") return false;
|
|
1616
|
+
return invalid(param);
|
|
1617
|
+
}
|
|
1618
|
+
function enumValue(value, fallback, allowed, param, multipart) {
|
|
1619
|
+
if (value === void 0) return fallback;
|
|
1620
|
+
const item = scalar(value, multipart);
|
|
1621
|
+
if (typeof item !== "string" || !allowed.includes(item)) invalid(param);
|
|
1622
|
+
return item;
|
|
1623
|
+
}
|
|
1624
|
+
function parseSize(value, multipart) {
|
|
1625
|
+
if (value === void 0 || scalar(value, multipart) === "auto") return { kind: "auto" };
|
|
1626
|
+
const item = scalar(value, multipart);
|
|
1627
|
+
if (typeof item !== "string") invalid("size");
|
|
1628
|
+
const match = /^(\d+)x(\d+)$/.exec(item);
|
|
1629
|
+
if (!match) invalid("size");
|
|
1630
|
+
const width = Number(match[1]);
|
|
1631
|
+
const height = Number(match[2]);
|
|
1632
|
+
const pixels = width * height;
|
|
1633
|
+
const long = Math.max(width, height);
|
|
1634
|
+
const short = Math.min(width, height);
|
|
1635
|
+
if (!Number.isSafeInteger(pixels) || width % 16 !== 0 || height % 16 !== 0 || width <= 0 || height <= 0 || long > 3840 || long / short > 3 || pixels < 655360 || pixels > 8294400) invalid("size");
|
|
1636
|
+
return { kind: "pixels", width, height };
|
|
1637
|
+
}
|
|
1638
|
+
function normalizeImageOptions(value, runtime, input) {
|
|
1639
|
+
const record2 = recordOf(value);
|
|
1640
|
+
const allowed = /* @__PURE__ */ new Set([...OPTION_KEYS, ..._nullishCoalesce(input.extraKeys, () => ( []))]);
|
|
1641
|
+
for (const key of Object.keys(record2)) {
|
|
1642
|
+
if (!allowed.has(key)) invalid(key);
|
|
1643
|
+
}
|
|
1644
|
+
const multipart = input.multipart === true;
|
|
1645
|
+
const promptValue = scalar(record2.prompt, multipart);
|
|
1646
|
+
if (typeof promptValue !== "string" || !promptValue.trim() || promptValue.length > 32e3) {
|
|
1647
|
+
invalid("prompt");
|
|
1648
|
+
}
|
|
1649
|
+
const requestedModel = record2.model === void 0 ? runtime.defaultModel : scalar(record2.model, multipart);
|
|
1650
|
+
if (typeof requestedModel !== "string" || !requestedModel.trim() || requestedModel.length > 128) {
|
|
1651
|
+
invalid("model");
|
|
1652
|
+
}
|
|
1653
|
+
const model = _nullishCoalesce(runtime.modelAliases.get(requestedModel), () => ( requestedModel));
|
|
1654
|
+
const n = integer(record2.n, 1, "n", 1, 10, multipart);
|
|
1655
|
+
const quality = enumValue(record2.quality, "auto", ["auto", "low", "medium", "high"], "quality", multipart);
|
|
1656
|
+
const size = parseSize(record2.size, multipart);
|
|
1657
|
+
const background = enumValue(record2.background, "auto", ["auto", "opaque", "transparent"], "background", multipart);
|
|
1658
|
+
const outputFormat = enumValue(record2.output_format, "png", ["png", "jpeg", "webp"], "output_format", multipart);
|
|
1659
|
+
const outputCompression = record2.output_compression === void 0 ? void 0 : integer(record2.output_compression, 0, "output_compression", 0, 100, multipart);
|
|
1660
|
+
const moderation = enumValue(record2.moderation, "auto", ["auto", "low"], "moderation", multipart);
|
|
1661
|
+
const stream = booleanValue(record2.stream, false, "stream", multipart);
|
|
1662
|
+
const partialImages = integer(record2.partial_images, 0, "partial_images", 0, 3, multipart);
|
|
1663
|
+
if (background === "transparent" && outputFormat === "jpeg") invalid("background");
|
|
1664
|
+
if (outputCompression !== void 0 && outputFormat === "png") invalid("output_compression");
|
|
1665
|
+
if (partialImages > 0 && !stream) invalid("partial_images");
|
|
1666
|
+
let userFingerprint;
|
|
1667
|
+
if (record2.user !== void 0) {
|
|
1668
|
+
const user = scalar(record2.user, multipart);
|
|
1669
|
+
if (typeof user !== "string" || !user || user.length > 256) invalid("user");
|
|
1670
|
+
if (runtime.fingerprintUser) {
|
|
1671
|
+
const fingerprint = runtime.fingerprintUser(user);
|
|
1672
|
+
if (typeof fingerprint !== "string" || !/^[A-Za-z0-9:_-]{1,128}$/.test(fingerprint)) {
|
|
1673
|
+
throw new TypeError("The injected image user fingerprint is not a safe identifier.");
|
|
1674
|
+
}
|
|
1675
|
+
userFingerprint = fingerprint;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
return {
|
|
1679
|
+
model,
|
|
1680
|
+
prompt: promptValue,
|
|
1681
|
+
n,
|
|
1682
|
+
quality,
|
|
1683
|
+
size,
|
|
1684
|
+
background,
|
|
1685
|
+
outputFormat,
|
|
1686
|
+
...outputCompression !== void 0 ? { outputCompression } : {},
|
|
1687
|
+
moderation,
|
|
1688
|
+
stream,
|
|
1689
|
+
partialImages,
|
|
1690
|
+
...userFingerprint !== void 0 ? { userFingerprint } : {}
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
function normalizeGenerateRequest(value, runtime) {
|
|
1694
|
+
return { action: "generate", ...normalizeImageOptions(value, runtime, { action: "generate" }) };
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
// src/image-generation/openai-images/readJsonBody.ts
|
|
1698
|
+
function invalid2(param) {
|
|
1699
|
+
throw new ImageGenerationError("invalid_image_request", { param });
|
|
1700
|
+
}
|
|
1701
|
+
function tooLarge2() {
|
|
1702
|
+
throw new ImageGenerationError("image_too_large");
|
|
1703
|
+
}
|
|
1704
|
+
function mediaType(value) {
|
|
1705
|
+
return (_nullishCoalesce(value, () => ( ""))).split(";", 1)[0].trim().toLowerCase();
|
|
1706
|
+
}
|
|
1707
|
+
function declaredLength(value, limit) {
|
|
1708
|
+
if (value === void 0) return void 0;
|
|
1709
|
+
if (Array.isArray(value) || !/^(0|[1-9]\d*)$/.test(value)) tooLarge2();
|
|
1710
|
+
const parsed = Number(value);
|
|
1711
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0 || parsed > limit) tooLarge2();
|
|
1712
|
+
return parsed;
|
|
1713
|
+
}
|
|
1714
|
+
async function readJsonBody(request2, options) {
|
|
1715
|
+
if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes <= 0) {
|
|
1716
|
+
throw new TypeError("The JSON body limit must be a positive safe integer.");
|
|
1717
|
+
}
|
|
1718
|
+
if (options.signal.aborted) {
|
|
1719
|
+
throw new ImageGenerationError("request_cancelled", { cause: options.signal.reason });
|
|
1720
|
+
}
|
|
1721
|
+
if (mediaType(request2.headers["content-type"]) !== "application/json") invalid2("content_type");
|
|
1722
|
+
const encoding = request2.headers["content-encoding"];
|
|
1723
|
+
if (Array.isArray(encoding) || encoding !== void 0 && encoding.trim().toLowerCase() !== "identity") {
|
|
1724
|
+
invalid2("content_encoding");
|
|
1725
|
+
}
|
|
1726
|
+
const expected = declaredLength(request2.headers["content-length"], options.maxBytes);
|
|
1727
|
+
const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false });
|
|
1728
|
+
const pieces = [];
|
|
1729
|
+
let observed = 0;
|
|
1730
|
+
const abort = () => {
|
|
1731
|
+
request2.destroy(options.signal.reason instanceof Error ? options.signal.reason : void 0);
|
|
1732
|
+
};
|
|
1733
|
+
options.signal.addEventListener("abort", abort, { once: true });
|
|
1734
|
+
try {
|
|
1735
|
+
try {
|
|
1736
|
+
for await (const raw of request2) {
|
|
1737
|
+
if (options.signal.aborted) {
|
|
1738
|
+
throw new ImageGenerationError("request_cancelled", { cause: options.signal.reason });
|
|
1739
|
+
}
|
|
1740
|
+
const chunk = raw instanceof Uint8Array ? raw : Buffer.from(raw);
|
|
1741
|
+
observed += chunk.byteLength;
|
|
1742
|
+
if (!Number.isSafeInteger(observed) || observed > options.maxBytes || expected !== void 0 && observed > expected) {
|
|
1743
|
+
tooLarge2();
|
|
1744
|
+
}
|
|
1745
|
+
pieces.push(decoder.decode(chunk, { stream: true }));
|
|
1746
|
+
}
|
|
1747
|
+
pieces.push(decoder.decode());
|
|
1748
|
+
} catch (error) {
|
|
1749
|
+
if (options.signal.aborted) {
|
|
1750
|
+
throw new ImageGenerationError("request_cancelled", { cause: options.signal.reason });
|
|
1751
|
+
}
|
|
1752
|
+
if (error instanceof ImageGenerationError) throw error;
|
|
1753
|
+
invalid2();
|
|
1754
|
+
}
|
|
1755
|
+
if (observed === 0 || expected !== void 0 && observed !== expected) invalid2();
|
|
1756
|
+
try {
|
|
1757
|
+
return JSON.parse(pieces.join(""));
|
|
1758
|
+
} catch (e5) {
|
|
1759
|
+
invalid2();
|
|
1760
|
+
}
|
|
1761
|
+
} finally {
|
|
1762
|
+
options.signal.removeEventListener("abort", abort);
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
// src/image-generation/openai-images/types.ts
|
|
1767
|
+
var DEFAULT_IMAGE_API_LIMITS = Object.freeze({
|
|
1768
|
+
maxJsonBytes: 1024 * 1024,
|
|
1769
|
+
maxMultipartBytes: 64 * 1024 * 1024,
|
|
1770
|
+
maxFileBytes: 50 * 1024 * 1024,
|
|
1771
|
+
maxTotalInputBytes: 64 * 1024 * 1024,
|
|
1772
|
+
maxFiles: 17,
|
|
1773
|
+
maxFields: 32,
|
|
1774
|
+
maxParts: 49,
|
|
1775
|
+
maxHeaderPairs: 64,
|
|
1776
|
+
maxFieldNameBytes: 128,
|
|
1777
|
+
maxFieldValueBytes: 32 * 1024,
|
|
1778
|
+
maxPixels: 8294400,
|
|
1779
|
+
maxRawBytes: 8294400 * 4,
|
|
1780
|
+
maxOutputBytes: 50 * 1024 * 1024,
|
|
1781
|
+
maxTotalOutputBytes: 100 * 1024 * 1024,
|
|
1782
|
+
maxSpoolBytes: 140 * 1024 * 1024,
|
|
1783
|
+
maxRedirects: 5,
|
|
1784
|
+
maxRemoteUrlBytes: 8 * 1024,
|
|
1785
|
+
maxRemoteHeaderBytes: 32 * 1024,
|
|
1786
|
+
remoteConnectTimeoutMs: 1e4,
|
|
1787
|
+
remoteTotalTimeoutMs: 3e4
|
|
1788
|
+
});
|
|
1789
|
+
function assertFiniteImageApiLimits(limits) {
|
|
1790
|
+
for (const [name, value] of Object.entries(limits)) {
|
|
1791
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
1792
|
+
throw new TypeError(`Image API limit '${name}' must be a positive safe integer.`);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
if (limits.maxFileBytes > 50 * 1024 * 1024) {
|
|
1796
|
+
throw new TypeError("Image API per-file limit exceeds the 50 MiB hard ceiling.");
|
|
1797
|
+
}
|
|
1798
|
+
if (limits.maxFiles > 17) {
|
|
1799
|
+
throw new TypeError("Image API file count exceeds sixteen images plus one mask.");
|
|
1800
|
+
}
|
|
1801
|
+
if (limits.maxPixels > 8294400) {
|
|
1802
|
+
throw new TypeError("Image API pixel limit exceeds the GPT Image hard ceiling.");
|
|
1803
|
+
}
|
|
1804
|
+
if (limits.maxTotalInputBytes < limits.maxFileBytes) {
|
|
1805
|
+
throw new TypeError("Image API aggregate input limit must cover one file.");
|
|
1806
|
+
}
|
|
1807
|
+
if (limits.maxTotalOutputBytes < limits.maxOutputBytes) {
|
|
1808
|
+
throw new TypeError("Image API aggregate output limit must cover one output.");
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
// src/image-generation/openai-images/generateHandler.ts
|
|
1813
|
+
function validateRuntime(runtime) {
|
|
1814
|
+
if (typeof runtime.tenantId !== "string" || !runtime.tenantId || runtime.tenantId.length > 256 || typeof runtime.providerId !== "string" || !runtime.providerId || runtime.providerId.length > 128 || typeof runtime.defaultModel !== "string" || !runtime.defaultModel || runtime.defaultModel.length > 128 || !runtime.modelAliases || typeof runtime.modelAliases.get !== "function") {
|
|
1815
|
+
throw new ImageGenerationError("invalid_api_key");
|
|
1816
|
+
}
|
|
1817
|
+
for (const hint of [runtime.preferredAccountId, runtime.preferredAccountGroup]) {
|
|
1818
|
+
if (hint !== void 0 && (typeof hint !== "string" || !hint.trim() || hint.length > 256)) {
|
|
1819
|
+
throw new ImageGenerationError("invalid_api_key");
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
if (runtime.boundAccountFallbackPolicy !== void 0 && runtime.boundAccountFallbackPolicy !== "strict" && runtime.boundAccountFallbackPolicy !== "pool") {
|
|
1823
|
+
throw new ImageGenerationError("invalid_api_key");
|
|
1824
|
+
}
|
|
1825
|
+
assertFiniteImageApiLimits(runtime.limits);
|
|
1826
|
+
}
|
|
1827
|
+
async function bestEffortAudit(deps, record2) {
|
|
1828
|
+
try {
|
|
1829
|
+
await _optionalChain([deps, 'access', _72 => _72.audit, 'optionalCall', _73 => _73(record2)]);
|
|
1830
|
+
} catch (e6) {
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
function createImageGenerateHandler(deps) {
|
|
1834
|
+
return async (context) => {
|
|
1835
|
+
const requestId = safeImageRequestId(_optionalChain([deps, 'access', _74 => _74.createRequestId, 'optionalCall', _75 => _75()]));
|
|
1836
|
+
const createdAt = (_nullishCoalesce(deps.now, () => ( Date.now)))();
|
|
1837
|
+
let runtime;
|
|
1838
|
+
let request2;
|
|
1839
|
+
let scope;
|
|
1840
|
+
let cleanupOutcome;
|
|
1841
|
+
let terminal = "failed";
|
|
1842
|
+
let terminalCode;
|
|
1843
|
+
try {
|
|
1844
|
+
runtime = await deps.resolveRuntime(context);
|
|
1845
|
+
validateRuntime(runtime);
|
|
1846
|
+
scope = deps.createResourceScope ? await deps.createResourceScope(runtime.limits, context.signal, runtime.tenantId) : await createImageRequestResourceScope(runtime.limits, context.signal);
|
|
1847
|
+
const body = await readJsonBody(context.request, {
|
|
1848
|
+
maxBytes: runtime.limits.maxJsonBytes,
|
|
1849
|
+
signal: context.signal
|
|
1850
|
+
});
|
|
1851
|
+
request2 = normalizeGenerateRequest(body, runtime);
|
|
1852
|
+
const events = deps.orchestrator.run(
|
|
1853
|
+
request2,
|
|
1854
|
+
{
|
|
1855
|
+
requestId,
|
|
1856
|
+
tenantId: runtime.tenantId,
|
|
1857
|
+
signal: context.signal,
|
|
1858
|
+
...context.route.sessionId ? { sessionKey: context.route.sessionId } : {},
|
|
1859
|
+
...runtime.preferredAccountId ? { preferredAccountId: runtime.preferredAccountId } : {},
|
|
1860
|
+
...runtime.preferredAccountGroup ? { preferredAccountGroup: runtime.preferredAccountGroup } : {},
|
|
1861
|
+
...runtime.boundAccountFallbackPolicy ? { boundAccountFallbackPolicy: runtime.boundAccountFallbackPolicy } : {}
|
|
1862
|
+
},
|
|
1863
|
+
{ providerId: runtime.providerId, ...runtime.retention ? { retention: runtime.retention } : {} }
|
|
1864
|
+
);
|
|
1865
|
+
if (request2.stream) {
|
|
1866
|
+
await writeImageApiSse({
|
|
1867
|
+
action: "generation",
|
|
1868
|
+
response: context.response,
|
|
1869
|
+
events,
|
|
1870
|
+
requestedCount: request2.n,
|
|
1871
|
+
requestId,
|
|
1872
|
+
createdAt,
|
|
1873
|
+
metadata: {
|
|
1874
|
+
background: request2.background,
|
|
1875
|
+
outputFormat: request2.outputFormat,
|
|
1876
|
+
quality: request2.quality,
|
|
1877
|
+
size: request2.size.kind === "auto" ? "auto" : `${request2.size.width}x${request2.size.height}`
|
|
1878
|
+
},
|
|
1879
|
+
scope,
|
|
1880
|
+
limits: runtime.limits,
|
|
1881
|
+
signal: context.signal
|
|
1882
|
+
});
|
|
1883
|
+
} else {
|
|
1884
|
+
await writeImageApiResponse({
|
|
1885
|
+
response: context.response,
|
|
1886
|
+
events,
|
|
1887
|
+
requestedCount: request2.n,
|
|
1888
|
+
requestId,
|
|
1889
|
+
createdAt,
|
|
1890
|
+
scope,
|
|
1891
|
+
limits: runtime.limits,
|
|
1892
|
+
signal: context.signal
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
terminal = "completed";
|
|
1896
|
+
} catch (error) {
|
|
1897
|
+
const domainError = imageApiErrorFromUnknown(error);
|
|
1898
|
+
terminal = domainError.code === "request_cancelled" ? "cancelled" : "failed";
|
|
1899
|
+
terminalCode = domainError.code;
|
|
1900
|
+
writeImageApiError(context.response, domainError, requestId);
|
|
1901
|
+
} finally {
|
|
1902
|
+
if (scope) {
|
|
1903
|
+
try {
|
|
1904
|
+
await scope.cleanup();
|
|
1905
|
+
cleanupOutcome = "completed";
|
|
1906
|
+
} catch (e7) {
|
|
1907
|
+
cleanupOutcome = "failed";
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
await bestEffortAudit(deps, {
|
|
1911
|
+
requestId,
|
|
1912
|
+
operationId: "images.generate",
|
|
1913
|
+
...runtime ? { providerId: runtime.providerId } : {},
|
|
1914
|
+
...request2 ? {
|
|
1915
|
+
model: request2.model,
|
|
1916
|
+
options: {
|
|
1917
|
+
n: request2.n,
|
|
1918
|
+
quality: request2.quality,
|
|
1919
|
+
background: request2.background,
|
|
1920
|
+
outputFormat: request2.outputFormat,
|
|
1921
|
+
stream: request2.stream,
|
|
1922
|
+
partialImages: request2.partialImages
|
|
1923
|
+
}
|
|
1924
|
+
} : {},
|
|
1925
|
+
terminal,
|
|
1926
|
+
...cleanupOutcome ? { cleanupOutcome } : {},
|
|
1927
|
+
...terminalCode ? { errorCode: terminalCode } : {}
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
// src/image-generation/openai-images/readMultipartEdit.ts
|
|
1934
|
+
var _busboy = require('busboy'); var _busboy2 = _interopRequireDefault(_busboy);
|
|
1935
|
+
|
|
1936
|
+
// src/image-generation/openai-images/validateRaster.ts
|
|
1937
|
+
|
|
1938
|
+
|
|
1939
|
+
var _sharp = require('sharp'); var _sharp2 = _interopRequireDefault(_sharp);
|
|
1940
|
+
function unsupported2() {
|
|
1941
|
+
throw new ImageGenerationError("unsupported_image_type");
|
|
1942
|
+
}
|
|
1943
|
+
function tooLarge3() {
|
|
1944
|
+
throw new ImageGenerationError("image_too_large");
|
|
1945
|
+
}
|
|
1946
|
+
function hasPrefix(bytes, signature) {
|
|
1947
|
+
return signature.every((value, index) => bytes[index] === value);
|
|
1948
|
+
}
|
|
1949
|
+
function signatureFormat(header) {
|
|
1950
|
+
if (hasPrefix(header, [137, 80, 78, 71, 13, 10, 26, 10])) return "png";
|
|
1951
|
+
if (hasPrefix(header, [255, 216, 255])) return "jpeg";
|
|
1952
|
+
if (hasPrefix(header, [82, 73, 70, 70]) && header[8] === 87 && header[9] === 69 && header[10] === 66 && header[11] === 80) return "webp";
|
|
1953
|
+
unsupported2();
|
|
1954
|
+
}
|
|
1955
|
+
async function inspectContainer(privatePath, byteLength2) {
|
|
1956
|
+
if (byteLength2 < 12) unsupported2();
|
|
1957
|
+
const handle = await _promises.open.call(void 0, privatePath, "r");
|
|
1958
|
+
try {
|
|
1959
|
+
const header = Buffer.allocUnsafe(12);
|
|
1960
|
+
const headerRead = await handle.read(header, 0, header.byteLength, 0);
|
|
1961
|
+
if (headerRead.bytesRead !== header.byteLength) unsupported2();
|
|
1962
|
+
const format = signatureFormat(header);
|
|
1963
|
+
if (format === "png") {
|
|
1964
|
+
if (byteLength2 < 20) unsupported2();
|
|
1965
|
+
const trailer = Buffer.allocUnsafe(12);
|
|
1966
|
+
const result = await handle.read(trailer, 0, trailer.byteLength, byteLength2 - trailer.byteLength);
|
|
1967
|
+
if (result.bytesRead !== trailer.byteLength || trailer.readUInt32BE(0) !== 0 || trailer.toString("ascii", 4, 8) !== "IEND" || !hasPrefix(trailer.subarray(8), [174, 66, 96, 130])) unsupported2();
|
|
1968
|
+
} else if (format === "jpeg") {
|
|
1969
|
+
const trailer = Buffer.allocUnsafe(2);
|
|
1970
|
+
const result = await handle.read(trailer, 0, 2, byteLength2 - 2);
|
|
1971
|
+
if (result.bytesRead !== 2 || trailer[0] !== 255 || trailer[1] !== 217) unsupported2();
|
|
1972
|
+
} else {
|
|
1973
|
+
const declared = header.readUInt32LE(4) + 8;
|
|
1974
|
+
if (declared !== byteLength2) unsupported2();
|
|
1975
|
+
}
|
|
1976
|
+
return format;
|
|
1977
|
+
} finally {
|
|
1978
|
+
await handle.close();
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
function mapSharpFailure(error) {
|
|
1982
|
+
const message = error instanceof Error ? error.message.toLowerCase() : "";
|
|
1983
|
+
if (message.includes("pixel limit") || message.includes("memory") || message.includes("too large")) tooLarge3();
|
|
1984
|
+
unsupported2();
|
|
1985
|
+
}
|
|
1986
|
+
async function validateRaster(file, limits) {
|
|
1987
|
+
if (file.byteLength > limits.maxFileBytes) tooLarge3();
|
|
1988
|
+
const metadata = await file.inspect(async (privatePath) => {
|
|
1989
|
+
const format = await inspectContainer(privatePath, file.byteLength);
|
|
1990
|
+
const encoded = await _promises.readFile.call(void 0, privatePath);
|
|
1991
|
+
if (encoded.byteLength !== file.byteLength) unsupported2();
|
|
1992
|
+
try {
|
|
1993
|
+
const image = _sharp2.default.call(void 0, encoded, {
|
|
1994
|
+
failOn: "warning",
|
|
1995
|
+
sequentialRead: true,
|
|
1996
|
+
limitInputPixels: limits.maxPixels,
|
|
1997
|
+
pages: 1,
|
|
1998
|
+
animated: false
|
|
1999
|
+
});
|
|
2000
|
+
const info = await image.metadata();
|
|
2001
|
+
image.destroy();
|
|
2002
|
+
if (info.format !== format) unsupported2();
|
|
2003
|
+
if ((_nullishCoalesce(info.pages, () => ( 1))) !== 1 || info.pageHeight !== void 0 && info.height !== info.pageHeight) unsupported2();
|
|
2004
|
+
const width = info.width;
|
|
2005
|
+
const height = info.height;
|
|
2006
|
+
const channels = info.channels;
|
|
2007
|
+
if (!width || !height || !channels || !Number.isSafeInteger(width) || !Number.isSafeInteger(height) || !Number.isSafeInteger(channels)) unsupported2();
|
|
2008
|
+
const pixels = width * height;
|
|
2009
|
+
const rawBytes = pixels * channels;
|
|
2010
|
+
if (!Number.isSafeInteger(pixels) || pixels <= 0 || pixels > limits.maxPixels || !Number.isSafeInteger(rawBytes) || rawBytes <= 0 || rawBytes > limits.maxRawBytes) tooLarge3();
|
|
2011
|
+
const decoder = _sharp2.default.call(void 0, encoded, {
|
|
2012
|
+
failOn: "warning",
|
|
2013
|
+
sequentialRead: true,
|
|
2014
|
+
limitInputPixels: limits.maxPixels,
|
|
2015
|
+
pages: 1,
|
|
2016
|
+
animated: false
|
|
2017
|
+
}).raw();
|
|
2018
|
+
const decoded = await decoder.toBuffer({ resolveWithObject: true });
|
|
2019
|
+
decoder.destroy();
|
|
2020
|
+
if (decoded.info.width !== width || decoded.info.height !== height || decoded.info.channels !== channels || decoded.data.byteLength !== rawBytes) unsupported2();
|
|
2021
|
+
return {
|
|
2022
|
+
format,
|
|
2023
|
+
mimeType: `image/${format}`,
|
|
2024
|
+
width,
|
|
2025
|
+
height,
|
|
2026
|
+
hasAlpha: info.hasAlpha === true,
|
|
2027
|
+
sha256: _crypto.createHash.call(void 0, "sha256").update(encoded).digest("hex")
|
|
2028
|
+
};
|
|
2029
|
+
} catch (error) {
|
|
2030
|
+
if (error instanceof ImageGenerationError) throw error;
|
|
2031
|
+
mapSharpFailure(error);
|
|
2032
|
+
}
|
|
2033
|
+
});
|
|
2034
|
+
return file.toImageAsset(metadata);
|
|
2035
|
+
}
|
|
2036
|
+
function assertCompatibleMask(primary, mask) {
|
|
2037
|
+
if (mask.mimeType !== primary.mimeType || mask.width !== primary.width || mask.height !== primary.height || mask.hasAlpha !== true) {
|
|
2038
|
+
throw new ImageGenerationError("invalid_image_request", { param: "mask" });
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
// src/image-generation/openai-images/readMultipartEdit.ts
|
|
2043
|
+
function invalid3(param) {
|
|
2044
|
+
return new ImageGenerationError("invalid_image_request", { param });
|
|
2045
|
+
}
|
|
2046
|
+
function tooLarge4() {
|
|
2047
|
+
return new ImageGenerationError("image_too_large");
|
|
2048
|
+
}
|
|
2049
|
+
function declaredLength2(value, limit) {
|
|
2050
|
+
if (value === void 0) return void 0;
|
|
2051
|
+
if (Array.isArray(value) || !/^(0|[1-9]\d*)$/.test(value)) throw tooLarge4();
|
|
2052
|
+
const parsed = Number(value);
|
|
2053
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0 || parsed > limit) throw tooLarge4();
|
|
2054
|
+
return parsed;
|
|
2055
|
+
}
|
|
2056
|
+
function byteLength(value) {
|
|
2057
|
+
return Buffer.byteLength(value, "utf8");
|
|
2058
|
+
}
|
|
2059
|
+
var MAX_MULTIPART_HEADER_BYTES = 16 * 1024;
|
|
2060
|
+
function multipartBoundary(contentType) {
|
|
2061
|
+
const match = /(?:^|;)\s*boundary=(?:"([^"]*)"|([^;\s]*))/i.exec(contentType);
|
|
2062
|
+
const boundary = _nullishCoalesce(_optionalChain([match, 'optionalAccess', _76 => _76[1]]), () => ( _optionalChain([match, 'optionalAccess', _77 => _77[2]])));
|
|
2063
|
+
if (!boundary || boundary.length > 200 || !/^[\x20-\x7e]+$/.test(boundary) || boundary.endsWith(" ")) {
|
|
2064
|
+
throw invalid3("content_type");
|
|
2065
|
+
}
|
|
2066
|
+
return boundary;
|
|
2067
|
+
}
|
|
2068
|
+
var MultipartHeaderPairGuard = class {
|
|
2069
|
+
#opening;
|
|
2070
|
+
#betweenParts;
|
|
2071
|
+
#maxPairs;
|
|
2072
|
+
#state = "preamble";
|
|
2073
|
+
#pending = Buffer.alloc(0);
|
|
2074
|
+
constructor(boundary, maxPairs) {
|
|
2075
|
+
this.#opening = Buffer.from(`--${boundary}`, "latin1");
|
|
2076
|
+
this.#betweenParts = Buffer.from(`\r
|
|
2077
|
+
--${boundary}`, "latin1");
|
|
2078
|
+
this.#maxPairs = maxPairs;
|
|
2079
|
+
}
|
|
2080
|
+
push(chunk) {
|
|
2081
|
+
if (this.#state === "done") return;
|
|
2082
|
+
let data = this.#pending.length === 0 ? Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength) : Buffer.concat([this.#pending, Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength)]);
|
|
2083
|
+
this.#pending = Buffer.alloc(0);
|
|
2084
|
+
while (data.length > 0) {
|
|
2085
|
+
if (this.#state === "preamble") {
|
|
2086
|
+
const start = data.indexOf(this.#opening);
|
|
2087
|
+
if (start < 0) {
|
|
2088
|
+
if (data.length > MAX_MULTIPART_HEADER_BYTES) throw tooLarge4();
|
|
2089
|
+
this.#pending = Buffer.from(data);
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
2092
|
+
data = data.subarray(start + this.#opening.length);
|
|
2093
|
+
if (data.length < 2) {
|
|
2094
|
+
this.#pending = Buffer.concat([this.#opening, data]);
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
if (data[0] === 45 && data[1] === 45) {
|
|
2098
|
+
this.#state = "done";
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
if (data[0] !== 13 || data[1] !== 10) throw invalid3("content_type");
|
|
2102
|
+
data = data.subarray(2);
|
|
2103
|
+
this.#state = "headers";
|
|
2104
|
+
continue;
|
|
2105
|
+
}
|
|
2106
|
+
if (this.#state === "headers") {
|
|
2107
|
+
const end = data.indexOf("\r\n\r\n");
|
|
2108
|
+
if (end < 0) {
|
|
2109
|
+
if (data.length > MAX_MULTIPART_HEADER_BYTES) throw tooLarge4();
|
|
2110
|
+
this.#pending = Buffer.from(data);
|
|
2111
|
+
return;
|
|
2112
|
+
}
|
|
2113
|
+
const block = data.subarray(0, end);
|
|
2114
|
+
const pairs = block.length === 0 ? 0 : block.toString("latin1").split("\r\n").length;
|
|
2115
|
+
if (pairs > this.#maxPairs) throw tooLarge4();
|
|
2116
|
+
data = data.subarray(end + 4);
|
|
2117
|
+
this.#state = "body";
|
|
2118
|
+
continue;
|
|
2119
|
+
}
|
|
2120
|
+
let searchFrom = 0;
|
|
2121
|
+
let boundaryAt = -1;
|
|
2122
|
+
while ((boundaryAt = data.indexOf(this.#betweenParts, searchFrom)) >= 0) {
|
|
2123
|
+
const suffix2 = boundaryAt + this.#betweenParts.length;
|
|
2124
|
+
if (data.length < suffix2 + 2) {
|
|
2125
|
+
this.#pending = Buffer.from(data.subarray(boundaryAt));
|
|
2126
|
+
return;
|
|
2127
|
+
}
|
|
2128
|
+
if (data[suffix2] === 45 && data[suffix2 + 1] === 45 || data[suffix2] === 13 && data[suffix2 + 1] === 10) break;
|
|
2129
|
+
searchFrom = boundaryAt + 1;
|
|
2130
|
+
}
|
|
2131
|
+
if (boundaryAt < 0) {
|
|
2132
|
+
const keep = Math.min(data.length, this.#betweenParts.length - 1);
|
|
2133
|
+
this.#pending = Buffer.from(data.subarray(data.length - keep));
|
|
2134
|
+
return;
|
|
2135
|
+
}
|
|
2136
|
+
const suffix = boundaryAt + this.#betweenParts.length;
|
|
2137
|
+
if (data[suffix] === 45 && data[suffix + 1] === 45) {
|
|
2138
|
+
this.#state = "done";
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
data = data.subarray(suffix + 2);
|
|
2142
|
+
this.#state = "headers";
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
finish() {
|
|
2146
|
+
if (this.#state !== "done") throw invalid3();
|
|
2147
|
+
}
|
|
2148
|
+
};
|
|
2149
|
+
async function readMultipartEdit(request2, scope, limits, signal) {
|
|
2150
|
+
if (signal.aborted) throw new ImageGenerationError("request_cancelled", { cause: signal.reason });
|
|
2151
|
+
const contentType = request2.headers["content-type"];
|
|
2152
|
+
if (typeof contentType !== "string" || !/^multipart\/form-data(?:\s*;|$)/i.test(contentType)) {
|
|
2153
|
+
throw invalid3("content_type");
|
|
2154
|
+
}
|
|
2155
|
+
const encoding = request2.headers["content-encoding"];
|
|
2156
|
+
if (Array.isArray(encoding) || encoding !== void 0 && encoding.trim().toLowerCase() !== "identity") {
|
|
2157
|
+
throw invalid3("content_encoding");
|
|
2158
|
+
}
|
|
2159
|
+
const expected = declaredLength2(request2.headers["content-length"], limits.maxMultipartBytes);
|
|
2160
|
+
const headerGuard = new MultipartHeaderPairGuard(
|
|
2161
|
+
multipartBoundary(contentType),
|
|
2162
|
+
limits.maxHeaderPairs
|
|
2163
|
+
);
|
|
2164
|
+
const fields = /* @__PURE__ */ Object.create(null);
|
|
2165
|
+
const pending = [];
|
|
2166
|
+
let imageCount = 0;
|
|
2167
|
+
let maskCount = 0;
|
|
2168
|
+
let observed = 0;
|
|
2169
|
+
let settled = false;
|
|
2170
|
+
let parser;
|
|
2171
|
+
try {
|
|
2172
|
+
parser = _busboy2.default.call(void 0, {
|
|
2173
|
+
headers: request2.headers,
|
|
2174
|
+
limits: {
|
|
2175
|
+
fileSize: limits.maxFileBytes,
|
|
2176
|
+
files: limits.maxFiles,
|
|
2177
|
+
fields: limits.maxFields,
|
|
2178
|
+
parts: limits.maxParts,
|
|
2179
|
+
headerPairs: limits.maxHeaderPairs,
|
|
2180
|
+
fieldNameSize: limits.maxFieldNameBytes,
|
|
2181
|
+
fieldSize: limits.maxFieldValueBytes
|
|
2182
|
+
}
|
|
2183
|
+
});
|
|
2184
|
+
} catch (e8) {
|
|
2185
|
+
throw invalid3("content_type");
|
|
2186
|
+
}
|
|
2187
|
+
const parsed = new Promise((resolve2, reject) => {
|
|
2188
|
+
const fail = (error) => {
|
|
2189
|
+
if (settled) return;
|
|
2190
|
+
settled = true;
|
|
2191
|
+
request2.unpipe(parser);
|
|
2192
|
+
request2.pause();
|
|
2193
|
+
parser.destroy(error instanceof Error ? error : invalid3());
|
|
2194
|
+
reject(error);
|
|
2195
|
+
};
|
|
2196
|
+
const onAbort = () => fail(new ImageGenerationError("request_cancelled", { cause: signal.reason }));
|
|
2197
|
+
let requestEnded = false;
|
|
2198
|
+
const onEnd = () => {
|
|
2199
|
+
requestEnded = true;
|
|
2200
|
+
};
|
|
2201
|
+
const onClose = () => {
|
|
2202
|
+
if (!requestEnded) fail(invalid3());
|
|
2203
|
+
};
|
|
2204
|
+
const onRequestError = () => fail(invalid3());
|
|
2205
|
+
const onData = (chunk) => {
|
|
2206
|
+
observed += chunk.byteLength;
|
|
2207
|
+
if (!Number.isSafeInteger(observed) || observed > limits.maxMultipartBytes || expected !== void 0 && observed > expected) {
|
|
2208
|
+
fail(tooLarge4());
|
|
2209
|
+
return;
|
|
2210
|
+
}
|
|
2211
|
+
try {
|
|
2212
|
+
headerGuard.push(chunk);
|
|
2213
|
+
} catch (error) {
|
|
2214
|
+
fail(error);
|
|
2215
|
+
}
|
|
2216
|
+
};
|
|
2217
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2218
|
+
request2.on("data", onData);
|
|
2219
|
+
request2.once("aborted", onAbort);
|
|
2220
|
+
request2.once("end", onEnd);
|
|
2221
|
+
request2.once("close", onClose);
|
|
2222
|
+
request2.once("error", onRequestError);
|
|
2223
|
+
parser.on("file", (name, file) => {
|
|
2224
|
+
if (settled) {
|
|
2225
|
+
file.resume();
|
|
2226
|
+
return;
|
|
2227
|
+
}
|
|
2228
|
+
if (byteLength(name) > limits.maxFieldNameBytes) {
|
|
2229
|
+
file.resume();
|
|
2230
|
+
fail(tooLarge4());
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
let kind;
|
|
2234
|
+
if (name === "image" || name === "image[]") {
|
|
2235
|
+
imageCount += 1;
|
|
2236
|
+
if (imageCount > 16) {
|
|
2237
|
+
file.resume();
|
|
2238
|
+
fail(invalid3("image"));
|
|
2239
|
+
return;
|
|
2240
|
+
}
|
|
2241
|
+
kind = "image";
|
|
2242
|
+
} else if (name === "mask") {
|
|
2243
|
+
maskCount += 1;
|
|
2244
|
+
if (maskCount > 1) {
|
|
2245
|
+
file.resume();
|
|
2246
|
+
fail(invalid3("mask"));
|
|
2247
|
+
return;
|
|
2248
|
+
}
|
|
2249
|
+
kind = "mask";
|
|
2250
|
+
} else {
|
|
2251
|
+
file.resume();
|
|
2252
|
+
fail(invalid3(name.length <= 128 ? name : void 0));
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
let truncated = false;
|
|
2256
|
+
file.once("limit", () => {
|
|
2257
|
+
truncated = true;
|
|
2258
|
+
});
|
|
2259
|
+
const staged2 = scope.materialize(file, {
|
|
2260
|
+
maxBytes: limits.maxFileBytes,
|
|
2261
|
+
signal
|
|
2262
|
+
}).then((value) => {
|
|
2263
|
+
if (truncated || file.truncated) throw tooLarge4();
|
|
2264
|
+
return value;
|
|
2265
|
+
});
|
|
2266
|
+
staged2.catch(fail);
|
|
2267
|
+
pending.push({ kind, staged: staged2 });
|
|
2268
|
+
});
|
|
2269
|
+
parser.on("field", (name, value, info) => {
|
|
2270
|
+
if (settled) return;
|
|
2271
|
+
if (info.nameTruncated || info.valueTruncated || byteLength(name) > limits.maxFieldNameBytes || byteLength(value) > limits.maxFieldValueBytes) {
|
|
2272
|
+
fail(tooLarge4());
|
|
2273
|
+
return;
|
|
2274
|
+
}
|
|
2275
|
+
if (Object.hasOwn(fields, name)) {
|
|
2276
|
+
fail(invalid3(name.length <= 128 ? name : void 0));
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
fields[name] = value;
|
|
2280
|
+
});
|
|
2281
|
+
parser.once("filesLimit", () => fail(tooLarge4()));
|
|
2282
|
+
parser.once("fieldsLimit", () => fail(tooLarge4()));
|
|
2283
|
+
parser.once("partsLimit", () => fail(tooLarge4()));
|
|
2284
|
+
parser.once("error", (error) => fail(error instanceof ImageGenerationError ? error : invalid3()));
|
|
2285
|
+
parser.once("finish", () => {
|
|
2286
|
+
if (settled) return;
|
|
2287
|
+
try {
|
|
2288
|
+
headerGuard.finish();
|
|
2289
|
+
} catch (error) {
|
|
2290
|
+
fail(error);
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2293
|
+
if (expected !== void 0 && observed !== expected) {
|
|
2294
|
+
fail(invalid3());
|
|
2295
|
+
return;
|
|
2296
|
+
}
|
|
2297
|
+
settled = true;
|
|
2298
|
+
resolve2();
|
|
2299
|
+
});
|
|
2300
|
+
request2.pipe(parser);
|
|
2301
|
+
const cleanup = () => {
|
|
2302
|
+
signal.removeEventListener("abort", onAbort);
|
|
2303
|
+
request2.removeListener("data", onData);
|
|
2304
|
+
request2.removeListener("aborted", onAbort);
|
|
2305
|
+
request2.removeListener("end", onEnd);
|
|
2306
|
+
request2.removeListener("close", onClose);
|
|
2307
|
+
request2.removeListener("error", onRequestError);
|
|
2308
|
+
};
|
|
2309
|
+
parser.once("close", cleanup);
|
|
2310
|
+
});
|
|
2311
|
+
await parsed;
|
|
2312
|
+
if (imageCount < 1 || imageCount > 16) throw invalid3("image");
|
|
2313
|
+
const staged = await Promise.all(pending.map((item) => item.staged));
|
|
2314
|
+
const images = [];
|
|
2315
|
+
let mask;
|
|
2316
|
+
for (let index = 0; index < staged.length; index += 1) {
|
|
2317
|
+
const asset = await validateRaster(staged[index], limits);
|
|
2318
|
+
if (pending[index].kind === "mask") mask = asset;
|
|
2319
|
+
else images.push(asset);
|
|
2320
|
+
}
|
|
2321
|
+
if (mask) assertCompatibleMask(images[0], mask);
|
|
2322
|
+
return { fields, images, ...mask ? { mask } : {} };
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
// src/image-generation/openai-images/resolveImageInput.ts
|
|
2326
|
+
function invalid4(param = "image") {
|
|
2327
|
+
throw new ImageGenerationError("invalid_image_request", { param });
|
|
2328
|
+
}
|
|
2329
|
+
function closedInput(value, maxRemoteUrlBytes) {
|
|
2330
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) invalid4();
|
|
2331
|
+
const record2 = value;
|
|
2332
|
+
const keys = Object.keys(record2);
|
|
2333
|
+
if (keys.length !== 1) invalid4();
|
|
2334
|
+
const key = keys[0];
|
|
2335
|
+
if (key !== "file_id" && key !== "image_url") invalid4();
|
|
2336
|
+
const item = record2[key];
|
|
2337
|
+
const maxBytes = key === "file_id" ? 256 : maxRemoteUrlBytes;
|
|
2338
|
+
if (typeof item !== "string" || !item || Buffer.byteLength(item, "utf8") > maxBytes) invalid4();
|
|
2339
|
+
return { [key]: item };
|
|
2340
|
+
}
|
|
2341
|
+
function decodedBase64Length(value) {
|
|
2342
|
+
if (value.length === 0 || value.length % 4 !== 0) invalid4();
|
|
2343
|
+
let padding = 0;
|
|
2344
|
+
if (value.charCodeAt(value.length - 1) === 61) padding += 1;
|
|
2345
|
+
if (value.charCodeAt(value.length - 2) === 61) padding += 1;
|
|
2346
|
+
const decoded = value.length / 4 * 3 - padding;
|
|
2347
|
+
if (!Number.isSafeInteger(decoded) || decoded <= 0) invalid4();
|
|
2348
|
+
return decoded;
|
|
2349
|
+
}
|
|
2350
|
+
function validateBase64(value, padding) {
|
|
2351
|
+
const alphabetEnd = value.length - padding;
|
|
2352
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
2353
|
+
const code = value.charCodeAt(index);
|
|
2354
|
+
const alphabet = code >= 65 && code <= 90 || code >= 97 && code <= 122 || code >= 48 && code <= 57 || code === 43 || code === 47;
|
|
2355
|
+
if (index < alphabetEnd ? !alphabet : code !== 61) invalid4();
|
|
2356
|
+
}
|
|
2357
|
+
const alphabetValue = (code) => {
|
|
2358
|
+
if (code >= 65 && code <= 90) return code - 65;
|
|
2359
|
+
if (code >= 97 && code <= 122) return code - 97 + 26;
|
|
2360
|
+
if (code >= 48 && code <= 57) return code - 48 + 52;
|
|
2361
|
+
return code === 43 ? 62 : 63;
|
|
2362
|
+
};
|
|
2363
|
+
if (padding === 1 && (alphabetValue(value.charCodeAt(value.length - 2)) & 3) !== 0) invalid4();
|
|
2364
|
+
if (padding === 2 && (alphabetValue(value.charCodeAt(value.length - 3)) & 15) !== 0) invalid4();
|
|
2365
|
+
}
|
|
2366
|
+
async function* decodeBase64Chunks(value) {
|
|
2367
|
+
const blockCharacters = 64 * 1024;
|
|
2368
|
+
for (let offset = 0; offset < value.length; offset += blockCharacters) {
|
|
2369
|
+
const end = Math.min(value.length, offset + blockCharacters);
|
|
2370
|
+
yield Buffer.from(value.slice(offset, end), "base64");
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
async function resolveDataUrl(value, runtime, scope, signal) {
|
|
2374
|
+
const comma = value.indexOf(",");
|
|
2375
|
+
if (comma < 0) invalid4();
|
|
2376
|
+
const descriptor = value.slice(0, comma).toLowerCase();
|
|
2377
|
+
if (!["data:image/png;base64", "data:image/jpeg;base64", "data:image/webp;base64"].includes(descriptor)) {
|
|
2378
|
+
invalid4();
|
|
2379
|
+
}
|
|
2380
|
+
const encoded = value.slice(comma + 1);
|
|
2381
|
+
const decoded = decodedBase64Length(encoded);
|
|
2382
|
+
if (decoded > runtime.limits.maxFileBytes) throw new ImageGenerationError("image_too_large");
|
|
2383
|
+
const padding = encoded.endsWith("==") ? 2 : encoded.endsWith("=") ? 1 : 0;
|
|
2384
|
+
validateBase64(encoded, padding);
|
|
2385
|
+
const file = await scope.materialize(decodeBase64Chunks(encoded), {
|
|
2386
|
+
declaredBytes: decoded,
|
|
2387
|
+
maxBytes: runtime.limits.maxFileBytes,
|
|
2388
|
+
signal
|
|
2389
|
+
});
|
|
2390
|
+
return validateRaster(file, runtime.limits);
|
|
2391
|
+
}
|
|
2392
|
+
async function resolveReference(referenceId, runtime, scope) {
|
|
2393
|
+
if (!runtime.referenceStore) throw new ImageGenerationError("unsupported_capability", { param: "file_id" });
|
|
2394
|
+
const result = await runtime.referenceStore.resolve(runtime.tenantId, referenceId);
|
|
2395
|
+
if (result.status === "not_found") throw new ImageGenerationError("image_reference_not_found", { param: "file_id" });
|
|
2396
|
+
if (result.status === "expired") throw new ImageGenerationError("image_reference_expired", { param: "file_id" });
|
|
2397
|
+
const asset = result.lease.value.artifact;
|
|
2398
|
+
if (!asset) {
|
|
2399
|
+
await result.lease.release();
|
|
2400
|
+
throw new ImageGenerationError("unsupported_capability", { param: "file_id" });
|
|
2401
|
+
}
|
|
2402
|
+
if (asset.byteLength !== result.lease.metadata.byteLength || asset.width !== result.lease.metadata.width || asset.height !== result.lease.metadata.height || asset.mimeType !== result.lease.metadata.mimeType) {
|
|
2403
|
+
await result.lease.release();
|
|
2404
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
2405
|
+
}
|
|
2406
|
+
scope.addLease(result.lease);
|
|
2407
|
+
return asset;
|
|
2408
|
+
}
|
|
2409
|
+
async function resolveImageInput(value, runtime, scope, signal) {
|
|
2410
|
+
const input = closedInput(value, runtime.limits.maxRemoteUrlBytes);
|
|
2411
|
+
if ("file_id" in input) return resolveReference(input.file_id, runtime, scope);
|
|
2412
|
+
if (input.image_url.toLowerCase().startsWith("data:")) {
|
|
2413
|
+
return resolveDataUrl(input.image_url, runtime, scope, signal);
|
|
2414
|
+
}
|
|
2415
|
+
if (!runtime.remoteResolver) {
|
|
2416
|
+
throw new ImageGenerationError("unsupported_capability", { param: "image_url" });
|
|
2417
|
+
}
|
|
2418
|
+
const materializer = {
|
|
2419
|
+
async materialize(source, declaredBytes, remoteSignal) {
|
|
2420
|
+
const file = await scope.materialize(source, {
|
|
2421
|
+
maxBytes: runtime.limits.maxFileBytes,
|
|
2422
|
+
...declaredBytes !== void 0 ? { declaredBytes } : {},
|
|
2423
|
+
signal: remoteSignal
|
|
2424
|
+
});
|
|
2425
|
+
return validateRaster(file, runtime.limits);
|
|
2426
|
+
}
|
|
2427
|
+
};
|
|
2428
|
+
return runtime.remoteResolver.resolve({
|
|
2429
|
+
url: input.image_url,
|
|
2430
|
+
signal,
|
|
2431
|
+
limits: runtime.limits,
|
|
2432
|
+
materializer
|
|
2433
|
+
});
|
|
2434
|
+
}
|
|
2435
|
+
function parseImageInputList(value, maxRemoteUrlBytes) {
|
|
2436
|
+
const list = Array.isArray(value) ? value : [value];
|
|
2437
|
+
if (list.length < 1 || list.length > 16) invalid4();
|
|
2438
|
+
for (const item of list) closedInput(item, maxRemoteUrlBytes);
|
|
2439
|
+
return list;
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
// src/image-generation/openai-images/editHandler.ts
|
|
2443
|
+
function mediaType2(value) {
|
|
2444
|
+
return (_nullishCoalesce(value, () => ( ""))).split(";", 1)[0].trim().toLowerCase();
|
|
2445
|
+
}
|
|
2446
|
+
function isReferenceInput(value) {
|
|
2447
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && Object.prototype.hasOwnProperty.call(value, "file_id");
|
|
2448
|
+
}
|
|
2449
|
+
function createImageEditHandler(deps) {
|
|
2450
|
+
return async (context) => {
|
|
2451
|
+
const requestId = safeImageRequestId(_optionalChain([deps, 'access', _78 => _78.createRequestId, 'optionalCall', _79 => _79()]));
|
|
2452
|
+
const createdAt = (_nullishCoalesce(deps.now, () => ( Date.now)))();
|
|
2453
|
+
let runtime;
|
|
2454
|
+
let request2;
|
|
2455
|
+
let scope;
|
|
2456
|
+
let cleanupOutcome;
|
|
2457
|
+
let observedReference = false;
|
|
2458
|
+
const referenceOutcomes = { hits: 0, notFound: 0, expired: 0, failed: 0 };
|
|
2459
|
+
let terminal = "failed";
|
|
2460
|
+
let terminalCode;
|
|
2461
|
+
try {
|
|
2462
|
+
runtime = await deps.resolveRuntime(context);
|
|
2463
|
+
validateRuntime(runtime);
|
|
2464
|
+
scope = deps.createResourceScope ? await deps.createResourceScope(runtime.limits, context.signal, runtime.tenantId) : await createImageRequestResourceScope(runtime.limits, context.signal);
|
|
2465
|
+
const type = mediaType2(context.request.headers["content-type"]);
|
|
2466
|
+
if (type === "application/json") {
|
|
2467
|
+
const value = await readJsonBody(context.request, {
|
|
2468
|
+
maxBytes: runtime.limits.maxJsonBytes,
|
|
2469
|
+
signal: context.signal
|
|
2470
|
+
});
|
|
2471
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2472
|
+
throw new ImageGenerationError("invalid_image_request");
|
|
2473
|
+
}
|
|
2474
|
+
const record2 = value;
|
|
2475
|
+
if (record2.image !== void 0 && record2.images !== void 0) {
|
|
2476
|
+
throw new ImageGenerationError("invalid_image_request", { param: "image" });
|
|
2477
|
+
}
|
|
2478
|
+
const options = normalizeImageOptions(record2, runtime, {
|
|
2479
|
+
action: "edit",
|
|
2480
|
+
extraKeys: ["image", "images", "mask"]
|
|
2481
|
+
});
|
|
2482
|
+
const inputs = parseImageInputList(
|
|
2483
|
+
_nullishCoalesce(record2.images, () => ( record2.image)),
|
|
2484
|
+
runtime.limits.maxRemoteUrlBytes
|
|
2485
|
+
);
|
|
2486
|
+
const resolveObservedInput = async (input) => {
|
|
2487
|
+
const reference = isReferenceInput(input);
|
|
2488
|
+
if (reference) observedReference = true;
|
|
2489
|
+
try {
|
|
2490
|
+
const asset = await resolveImageInput(input, runtime, scope, context.signal);
|
|
2491
|
+
if (reference) referenceOutcomes.hits += 1;
|
|
2492
|
+
return asset;
|
|
2493
|
+
} catch (error) {
|
|
2494
|
+
if (reference) {
|
|
2495
|
+
if (error instanceof ImageGenerationError && error.code === "image_reference_not_found") {
|
|
2496
|
+
referenceOutcomes.notFound += 1;
|
|
2497
|
+
} else if (error instanceof ImageGenerationError && error.code === "image_reference_expired") {
|
|
2498
|
+
referenceOutcomes.expired += 1;
|
|
2499
|
+
} else {
|
|
2500
|
+
referenceOutcomes.failed += 1;
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
throw error;
|
|
2504
|
+
}
|
|
2505
|
+
};
|
|
2506
|
+
const images = [];
|
|
2507
|
+
for (const input of inputs) images.push(await resolveObservedInput(input));
|
|
2508
|
+
const mask = record2.mask === void 0 ? void 0 : await resolveObservedInput(record2.mask);
|
|
2509
|
+
if (mask) assertCompatibleMask(images[0], mask);
|
|
2510
|
+
request2 = { action: "edit", ...options, images, ...mask ? { mask } : {} };
|
|
2511
|
+
} else {
|
|
2512
|
+
const multipart = await readMultipartEdit(context.request, scope, runtime.limits, context.signal);
|
|
2513
|
+
const options = normalizeImageOptions(multipart.fields, runtime, {
|
|
2514
|
+
action: "edit",
|
|
2515
|
+
multipart: true
|
|
2516
|
+
});
|
|
2517
|
+
request2 = {
|
|
2518
|
+
action: "edit",
|
|
2519
|
+
...options,
|
|
2520
|
+
images: multipart.images,
|
|
2521
|
+
...multipart.mask ? { mask: multipart.mask } : {}
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
2524
|
+
const events = deps.orchestrator.run(
|
|
2525
|
+
request2,
|
|
2526
|
+
{
|
|
2527
|
+
requestId,
|
|
2528
|
+
tenantId: runtime.tenantId,
|
|
2529
|
+
signal: context.signal,
|
|
2530
|
+
...context.route.sessionId ? { sessionKey: context.route.sessionId } : {},
|
|
2531
|
+
...runtime.preferredAccountId ? { preferredAccountId: runtime.preferredAccountId } : {},
|
|
2532
|
+
...runtime.preferredAccountGroup ? { preferredAccountGroup: runtime.preferredAccountGroup } : {},
|
|
2533
|
+
...runtime.boundAccountFallbackPolicy ? { boundAccountFallbackPolicy: runtime.boundAccountFallbackPolicy } : {}
|
|
2534
|
+
},
|
|
2535
|
+
{ providerId: runtime.providerId, ...runtime.retention ? { retention: runtime.retention } : {} }
|
|
2536
|
+
);
|
|
2537
|
+
if (request2.stream) {
|
|
2538
|
+
await writeImageApiSse({
|
|
2539
|
+
action: "edit",
|
|
2540
|
+
response: context.response,
|
|
2541
|
+
events,
|
|
2542
|
+
requestedCount: request2.n,
|
|
2543
|
+
requestId,
|
|
2544
|
+
createdAt,
|
|
2545
|
+
metadata: {
|
|
2546
|
+
background: request2.background,
|
|
2547
|
+
outputFormat: request2.outputFormat,
|
|
2548
|
+
quality: request2.quality,
|
|
2549
|
+
size: request2.size.kind === "auto" ? "auto" : `${request2.size.width}x${request2.size.height}`
|
|
2550
|
+
},
|
|
2551
|
+
scope,
|
|
2552
|
+
limits: runtime.limits,
|
|
2553
|
+
signal: context.signal
|
|
2554
|
+
});
|
|
2555
|
+
} else {
|
|
2556
|
+
await writeImageApiResponse({
|
|
2557
|
+
response: context.response,
|
|
2558
|
+
events,
|
|
2559
|
+
requestedCount: request2.n,
|
|
2560
|
+
requestId,
|
|
2561
|
+
createdAt,
|
|
2562
|
+
scope,
|
|
2563
|
+
limits: runtime.limits,
|
|
2564
|
+
signal: context.signal
|
|
2565
|
+
});
|
|
2566
|
+
}
|
|
2567
|
+
terminal = "completed";
|
|
2568
|
+
} catch (error) {
|
|
2569
|
+
const domainError = imageApiErrorFromUnknown(error);
|
|
2570
|
+
terminal = domainError.code === "request_cancelled" ? "cancelled" : "failed";
|
|
2571
|
+
terminalCode = domainError.code;
|
|
2572
|
+
writeImageApiError(context.response, domainError, requestId);
|
|
2573
|
+
} finally {
|
|
2574
|
+
if (scope) {
|
|
2575
|
+
try {
|
|
2576
|
+
await scope.cleanup();
|
|
2577
|
+
cleanupOutcome = "completed";
|
|
2578
|
+
} catch (e9) {
|
|
2579
|
+
cleanupOutcome = "failed";
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
await bestEffortAudit(deps, {
|
|
2583
|
+
requestId,
|
|
2584
|
+
operationId: "images.edit",
|
|
2585
|
+
...runtime ? { providerId: runtime.providerId } : {},
|
|
2586
|
+
...request2 ? {
|
|
2587
|
+
model: request2.model,
|
|
2588
|
+
options: {
|
|
2589
|
+
n: request2.n,
|
|
2590
|
+
quality: request2.quality,
|
|
2591
|
+
background: request2.background,
|
|
2592
|
+
outputFormat: request2.outputFormat,
|
|
2593
|
+
stream: request2.stream,
|
|
2594
|
+
partialImages: request2.partialImages
|
|
2595
|
+
},
|
|
2596
|
+
inputCount: request2.images.length,
|
|
2597
|
+
inputBytes: request2.images.reduce((sum, asset) => sum + asset.byteLength, 0)
|
|
2598
|
+
} : {},
|
|
2599
|
+
terminal,
|
|
2600
|
+
...observedReference ? { referenceOutcomes } : {},
|
|
2601
|
+
...cleanupOutcome ? { cleanupOutcome } : {},
|
|
2602
|
+
...terminalCode ? { errorCode: terminalCode } : {}
|
|
2603
|
+
});
|
|
2604
|
+
}
|
|
2605
|
+
};
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
// src/image-generation/openai-images/contributions.ts
|
|
2609
|
+
function createImageApiContributions(deps) {
|
|
2610
|
+
if (!deps || typeof deps !== "object" || !deps.orchestrator || typeof deps.resolveRuntime !== "function") {
|
|
2611
|
+
throw new TypeError("Image API contributions require an orchestrator and runtime resolver.");
|
|
2612
|
+
}
|
|
2613
|
+
const generate = Object.freeze({
|
|
2614
|
+
operationId: "images.generate",
|
|
2615
|
+
handler: createImageGenerateHandler(deps)
|
|
2616
|
+
});
|
|
2617
|
+
const edit = Object.freeze({
|
|
2618
|
+
operationId: "images.edit",
|
|
2619
|
+
handler: createImageEditHandler(deps)
|
|
2620
|
+
});
|
|
2621
|
+
return Object.freeze({ generate, edit, all: Object.freeze([generate, edit]) });
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
// src/image-generation/openai-images/safeRemoteImageResolver.ts
|
|
2625
|
+
var _promises3 = require('dns/promises');
|
|
2626
|
+
var _net = require('net');
|
|
2627
|
+
var _undici = require('undici');
|
|
2628
|
+
function ipv4Number(value) {
|
|
2629
|
+
const parts = value.split(".");
|
|
2630
|
+
if (parts.length !== 4) return void 0;
|
|
2631
|
+
let output = 0;
|
|
2632
|
+
for (const part of parts) {
|
|
2633
|
+
if (!/^\d{1,3}$/.test(part)) return void 0;
|
|
2634
|
+
const octet = Number(part);
|
|
2635
|
+
if (octet > 255 || String(octet) !== part) return void 0;
|
|
2636
|
+
output = output * 256 + octet;
|
|
2637
|
+
}
|
|
2638
|
+
return output >>> 0;
|
|
2639
|
+
}
|
|
2640
|
+
function blockedIpv4(value) {
|
|
2641
|
+
const address = ipv4Number(value);
|
|
2642
|
+
if (address === void 0) return true;
|
|
2643
|
+
const first = address >>> 24;
|
|
2644
|
+
const second = address >>> 16 & 255;
|
|
2645
|
+
if (first === 0 || first === 10 || first === 127) return true;
|
|
2646
|
+
if (first === 100 && second >= 64 && second <= 127) return true;
|
|
2647
|
+
if (first === 169 && second === 254) return true;
|
|
2648
|
+
if (first === 172 && second >= 16 && second <= 31) return true;
|
|
2649
|
+
if (first === 192 && second === 168) return true;
|
|
2650
|
+
if (first === 192 && second === 0) return true;
|
|
2651
|
+
if (first === 192 && second === 2) return true;
|
|
2652
|
+
if (first === 198 && (second === 18 || second === 19 || second === 51)) return true;
|
|
2653
|
+
if (first === 203 && second === 0) return true;
|
|
2654
|
+
if (first >= 224) return true;
|
|
2655
|
+
return false;
|
|
2656
|
+
}
|
|
2657
|
+
function ipv6Bytes(input) {
|
|
2658
|
+
const zone = input.indexOf("%");
|
|
2659
|
+
const value = (zone >= 0 ? input.slice(0, zone) : input).toLowerCase();
|
|
2660
|
+
const halves = value.split("::");
|
|
2661
|
+
if (halves.length > 2) return void 0;
|
|
2662
|
+
const parseHalf = (half) => {
|
|
2663
|
+
if (!half) return [];
|
|
2664
|
+
const words2 = [];
|
|
2665
|
+
for (const item of half.split(":")) {
|
|
2666
|
+
if (item.includes(".")) {
|
|
2667
|
+
const ipv4 = ipv4Number(item);
|
|
2668
|
+
if (ipv4 === void 0) return void 0;
|
|
2669
|
+
words2.push(ipv4 >>> 16 & 65535, ipv4 & 65535);
|
|
2670
|
+
} else {
|
|
2671
|
+
if (!/^[0-9a-f]{1,4}$/.test(item)) return void 0;
|
|
2672
|
+
words2.push(Number.parseInt(item, 16));
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
return words2;
|
|
2676
|
+
};
|
|
2677
|
+
const left = parseHalf(halves[0]);
|
|
2678
|
+
const right = parseHalf(_nullishCoalesce(halves[1], () => ( "")));
|
|
2679
|
+
if (!left || !right) return void 0;
|
|
2680
|
+
const missing = 8 - left.length - right.length;
|
|
2681
|
+
if (halves.length === 1 && missing !== 0 || halves.length === 2 && missing < 1) return void 0;
|
|
2682
|
+
const words = [...left, ...Array.from({ length: missing }, () => 0), ...right];
|
|
2683
|
+
if (words.length !== 8) return void 0;
|
|
2684
|
+
const bytes = new Uint8Array(16);
|
|
2685
|
+
words.forEach((word, index) => {
|
|
2686
|
+
bytes[index * 2] = word >>> 8;
|
|
2687
|
+
bytes[index * 2 + 1] = word & 255;
|
|
2688
|
+
});
|
|
2689
|
+
return bytes;
|
|
2690
|
+
}
|
|
2691
|
+
function blockedIpv6(value) {
|
|
2692
|
+
const bytes = ipv6Bytes(value);
|
|
2693
|
+
if (!bytes) return true;
|
|
2694
|
+
if (bytes.every((item) => item === 0)) return true;
|
|
2695
|
+
if (bytes.slice(0, 15).every((item) => item === 0) && bytes[15] === 1) return true;
|
|
2696
|
+
if ((bytes[0] & 254) === 252) return true;
|
|
2697
|
+
if (bytes[0] === 254 && (bytes[1] & 192) === 128) return true;
|
|
2698
|
+
if (bytes[0] === 255) return true;
|
|
2699
|
+
const mapped = bytes.slice(0, 10).every((item) => item === 0) && bytes[10] === 255 && bytes[11] === 255;
|
|
2700
|
+
if (mapped) return blockedIpv4(`${bytes[12]}.${bytes[13]}.${bytes[14]}.${bytes[15]}`);
|
|
2701
|
+
const compatible = bytes.slice(0, 12).every((item) => item === 0);
|
|
2702
|
+
if (compatible) return blockedIpv4(`${bytes[12]}.${bytes[13]}.${bytes[14]}.${bytes[15]}`);
|
|
2703
|
+
return false;
|
|
2704
|
+
}
|
|
2705
|
+
function isBlockedRemoteAddress(address) {
|
|
2706
|
+
const family = _net.isIP.call(void 0, address);
|
|
2707
|
+
return family === 4 ? blockedIpv4(address) : family === 6 ? blockedIpv6(address) : true;
|
|
2708
|
+
}
|
|
2709
|
+
function safeUrl(value) {
|
|
2710
|
+
let url;
|
|
2711
|
+
try {
|
|
2712
|
+
url = new URL(value);
|
|
2713
|
+
} catch (e10) {
|
|
2714
|
+
throw new ImageGenerationError("invalid_image_request", { param: "image_url" });
|
|
2715
|
+
}
|
|
2716
|
+
if (url.protocol !== "http:" && url.protocol !== "https:" || url.username || url.password) {
|
|
2717
|
+
throw new ImageGenerationError("invalid_image_request", { param: "image_url" });
|
|
2718
|
+
}
|
|
2719
|
+
return url;
|
|
2720
|
+
}
|
|
2721
|
+
function combinedSignal(parent, timeoutMs) {
|
|
2722
|
+
return AbortSignal.any([parent, AbortSignal.timeout(timeoutMs)]);
|
|
2723
|
+
}
|
|
2724
|
+
function pinnedLookup(address) {
|
|
2725
|
+
return ((_hostname, options, callback) => {
|
|
2726
|
+
if (typeof options === "object" && options.all) {
|
|
2727
|
+
callback(null, [{ address: address.address, family: address.family }]);
|
|
2728
|
+
return;
|
|
2729
|
+
}
|
|
2730
|
+
callback(null, address.address, address.family);
|
|
2731
|
+
});
|
|
2732
|
+
}
|
|
2733
|
+
async function defaultResolveAll(hostname, signal) {
|
|
2734
|
+
const lookup = _promises3.lookup.call(void 0, hostname, { all: true, verbatim: true });
|
|
2735
|
+
let abort;
|
|
2736
|
+
try {
|
|
2737
|
+
const results = await Promise.race([
|
|
2738
|
+
lookup,
|
|
2739
|
+
new Promise((_resolve, reject) => {
|
|
2740
|
+
abort = () => reject(signal.reason);
|
|
2741
|
+
if (signal.aborted) abort();
|
|
2742
|
+
else signal.addEventListener("abort", abort, { once: true });
|
|
2743
|
+
})
|
|
2744
|
+
]);
|
|
2745
|
+
return results.filter((item) => item.family === 4 || item.family === 6);
|
|
2746
|
+
} finally {
|
|
2747
|
+
if (abort) signal.removeEventListener("abort", abort);
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
function headerBytes(headers) {
|
|
2751
|
+
let total = 0;
|
|
2752
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
2753
|
+
total += Buffer.byteLength(name);
|
|
2754
|
+
for (const item of Array.isArray(value) ? value : [_nullishCoalesce(value, () => ( ""))]) total += Buffer.byteLength(item);
|
|
2755
|
+
}
|
|
2756
|
+
return total;
|
|
2757
|
+
}
|
|
2758
|
+
function contentLength(value, maxBytes) {
|
|
2759
|
+
if (value === void 0) return void 0;
|
|
2760
|
+
if (Array.isArray(value) || !/^(0|[1-9]\d*)$/.test(value)) {
|
|
2761
|
+
throw new ImageGenerationError("image_generation_failed");
|
|
2762
|
+
}
|
|
2763
|
+
const parsed = Number(value);
|
|
2764
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) throw new ImageGenerationError("image_generation_failed");
|
|
2765
|
+
if (parsed > maxBytes) throw new ImageGenerationError("image_too_large");
|
|
2766
|
+
return parsed;
|
|
2767
|
+
}
|
|
2768
|
+
async function defaultRequestPinned(input) {
|
|
2769
|
+
const dispatcher = new (0, _undici.Agent)({
|
|
2770
|
+
maxHeaderSize: input.maxHeaderBytes,
|
|
2771
|
+
connect: {
|
|
2772
|
+
lookup: pinnedLookup(input.address),
|
|
2773
|
+
timeout: input.connectTimeoutMs
|
|
2774
|
+
}
|
|
2775
|
+
});
|
|
2776
|
+
try {
|
|
2777
|
+
const response = await _undici.request.call(void 0, input.url, {
|
|
2778
|
+
method: "GET",
|
|
2779
|
+
dispatcher,
|
|
2780
|
+
signal: input.signal,
|
|
2781
|
+
maxRedirections: 0,
|
|
2782
|
+
headersTimeout: input.connectTimeoutMs,
|
|
2783
|
+
bodyTimeout: input.totalTimeoutMs,
|
|
2784
|
+
headers: { accept: "image/png,image/jpeg,image/webp" }
|
|
2785
|
+
});
|
|
2786
|
+
return {
|
|
2787
|
+
statusCode: response.statusCode,
|
|
2788
|
+
headers: response.headers,
|
|
2789
|
+
body: response.body,
|
|
2790
|
+
close: () => dispatcher.close()
|
|
2791
|
+
};
|
|
2792
|
+
} catch (error) {
|
|
2793
|
+
await dispatcher.close().catch(() => void 0);
|
|
2794
|
+
throw error;
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
function createSafeRemoteImageResolver(deps = {}) {
|
|
2798
|
+
const resolveAll = _nullishCoalesce(deps.resolveAll, () => ( defaultResolveAll));
|
|
2799
|
+
const requestPinned = _nullishCoalesce(deps.requestPinned, () => ( defaultRequestPinned));
|
|
2800
|
+
return {
|
|
2801
|
+
async resolve(input) {
|
|
2802
|
+
const totalSignal = combinedSignal(input.signal, input.limits.remoteTotalTimeoutMs);
|
|
2803
|
+
let current = safeUrl(input.url);
|
|
2804
|
+
try {
|
|
2805
|
+
for (let redirects = 0; redirects <= input.limits.maxRedirects; redirects += 1) {
|
|
2806
|
+
if (totalSignal.aborted) throw totalSignal.reason;
|
|
2807
|
+
const literal = current.hostname.startsWith("[") && current.hostname.endsWith("]") ? current.hostname.slice(1, -1) : current.hostname;
|
|
2808
|
+
const family = _net.isIP.call(void 0, literal);
|
|
2809
|
+
const answers = family ? [{ address: literal, family }] : await resolveAll(literal, totalSignal);
|
|
2810
|
+
if (answers.length === 0 || answers.some((answer) => isBlockedRemoteAddress(answer.address))) {
|
|
2811
|
+
throw new ImageGenerationError("invalid_image_request", { param: "image_url" });
|
|
2812
|
+
}
|
|
2813
|
+
const selected = answers[0];
|
|
2814
|
+
const response = await requestPinned({
|
|
2815
|
+
url: current,
|
|
2816
|
+
address: selected,
|
|
2817
|
+
signal: totalSignal,
|
|
2818
|
+
connectTimeoutMs: input.limits.remoteConnectTimeoutMs,
|
|
2819
|
+
totalTimeoutMs: input.limits.remoteTotalTimeoutMs,
|
|
2820
|
+
maxHeaderBytes: input.limits.maxRemoteHeaderBytes
|
|
2821
|
+
});
|
|
2822
|
+
try {
|
|
2823
|
+
if (headerBytes(response.headers) > input.limits.maxRemoteHeaderBytes) {
|
|
2824
|
+
await response.body.dump();
|
|
2825
|
+
throw new ImageGenerationError("image_too_large");
|
|
2826
|
+
}
|
|
2827
|
+
if ([301, 302, 303, 307, 308].includes(response.statusCode)) {
|
|
2828
|
+
const location = response.headers.location;
|
|
2829
|
+
await response.body.dump();
|
|
2830
|
+
if (redirects >= input.limits.maxRedirects || typeof location !== "string") {
|
|
2831
|
+
throw new ImageGenerationError("invalid_image_request", { param: "image_url" });
|
|
2832
|
+
}
|
|
2833
|
+
current = safeUrl(new URL(location, current).toString());
|
|
2834
|
+
continue;
|
|
2835
|
+
}
|
|
2836
|
+
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
2837
|
+
await response.body.dump();
|
|
2838
|
+
throw new ImageGenerationError("image_generation_failed");
|
|
2839
|
+
}
|
|
2840
|
+
const declared = contentLength(response.headers["content-length"], input.limits.maxFileBytes);
|
|
2841
|
+
return await input.materializer.materialize(response.body, declared, totalSignal);
|
|
2842
|
+
} finally {
|
|
2843
|
+
await response.close().catch(() => void 0);
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
throw new ImageGenerationError("invalid_image_request", { param: "image_url" });
|
|
2847
|
+
} catch (error) {
|
|
2848
|
+
if (error instanceof ImageGenerationError) throw error;
|
|
2849
|
+
if (input.signal.aborted) {
|
|
2850
|
+
throw new ImageGenerationError("request_cancelled", { cause: input.signal.reason });
|
|
2851
|
+
}
|
|
2852
|
+
if (totalSignal.aborted) throw new ImageGenerationError("image_generation_timeout");
|
|
2853
|
+
throw new ImageGenerationError("image_generation_failed");
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
};
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
// src/image-generation/responses/ResponsesImageGenerationContribution.ts
|
|
2860
|
+
|
|
2861
|
+
|
|
2862
|
+
// src/image-generation/responses/normalizeResponsesImageTool.ts
|
|
2863
|
+
var IMAGE_DECLARATION_KEYS = /* @__PURE__ */ new Set([
|
|
2864
|
+
"type",
|
|
2865
|
+
"action",
|
|
2866
|
+
"size",
|
|
2867
|
+
"quality",
|
|
2868
|
+
"output_format",
|
|
2869
|
+
"output_compression",
|
|
2870
|
+
"background",
|
|
2871
|
+
"partial_images"
|
|
2872
|
+
]);
|
|
2873
|
+
var CALL_ID_PATTERN = /^ig_[A-Za-z0-9_-]{16,128}$/;
|
|
2874
|
+
var RESPONSE_ID_PATTERN = /^resp_[A-Za-z0-9_-]{1,240}$/;
|
|
2875
|
+
var TOOL_IDENTITY_KEYS = /* @__PURE__ */ new Set(["declarationIndex", "type", "name"]);
|
|
2876
|
+
var MAX_SELECTED_TOOL_CALLS = 1024;
|
|
2877
|
+
var NORMALIZATION_RUNTIME = {
|
|
2878
|
+
tenantId: "responses-image-inspection",
|
|
2879
|
+
providerId: "responses-image-inspection",
|
|
2880
|
+
defaultModel: "responses-image-model",
|
|
2881
|
+
modelAliases: /* @__PURE__ */ new Map(),
|
|
2882
|
+
limits: DEFAULT_IMAGE_API_LIMITS
|
|
2883
|
+
};
|
|
2884
|
+
function invalid5(param) {
|
|
2885
|
+
throw new ImageGenerationError("invalid_image_request", { param });
|
|
2886
|
+
}
|
|
2887
|
+
function protocolFailure() {
|
|
2888
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
2889
|
+
}
|
|
2890
|
+
function record(value, param) {
|
|
2891
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) invalid5(param);
|
|
2892
|
+
return value;
|
|
2893
|
+
}
|
|
2894
|
+
function parseSelectionPolicy(value) {
|
|
2895
|
+
if (value === void 0 || value === "auto") return { kind: "auto" };
|
|
2896
|
+
if (value === "required") return { kind: "required" };
|
|
2897
|
+
if (value === "none") return { kind: "none" };
|
|
2898
|
+
const choice = record(value, "tool_choice");
|
|
2899
|
+
if (typeof choice.type !== "string" || !choice.type.trim() || choice.type.length > 128) {
|
|
2900
|
+
invalid5("tool_choice");
|
|
2901
|
+
}
|
|
2902
|
+
if (choice.type === "image_generation") {
|
|
2903
|
+
if (Object.keys(choice).some((key) => key !== "type")) invalid5("tool_choice");
|
|
2904
|
+
return { kind: "forced_image" };
|
|
2905
|
+
}
|
|
2906
|
+
if (Object.keys(choice).some((key) => key !== "type" && key !== "name")) {
|
|
2907
|
+
invalid5("tool_choice");
|
|
2908
|
+
}
|
|
2909
|
+
if (choice.name !== void 0 && (typeof choice.name !== "string" || !choice.name.trim() || choice.name.length > 128)) {
|
|
2910
|
+
invalid5("tool_choice");
|
|
2911
|
+
}
|
|
2912
|
+
return {
|
|
2913
|
+
kind: "forced_other",
|
|
2914
|
+
toolType: choice.type,
|
|
2915
|
+
...choice.name !== void 0 ? { toolName: choice.name } : {}
|
|
2916
|
+
};
|
|
2917
|
+
}
|
|
2918
|
+
function declaredOtherToolIdentity(value, declarationIndex) {
|
|
2919
|
+
if (typeof value.type !== "string" || !value.type.trim() || value.type.length > 128) {
|
|
2920
|
+
return void 0;
|
|
2921
|
+
}
|
|
2922
|
+
if (value.name !== void 0 && (typeof value.name !== "string" || !value.name.trim() || value.name.length > 128)) {
|
|
2923
|
+
return void 0;
|
|
2924
|
+
}
|
|
2925
|
+
return Object.freeze({
|
|
2926
|
+
declarationIndex,
|
|
2927
|
+
type: value.type,
|
|
2928
|
+
...value.name !== void 0 ? { name: value.name } : {}
|
|
2929
|
+
});
|
|
2930
|
+
}
|
|
2931
|
+
function sameToolIdentity(left, right) {
|
|
2932
|
+
return left.declarationIndex === right.declarationIndex && left.type === right.type && left.name === right.name;
|
|
2933
|
+
}
|
|
2934
|
+
function forcedOtherMatches(policy, identity) {
|
|
2935
|
+
return identity.type === policy.toolType && identity.name === policy.toolName;
|
|
2936
|
+
}
|
|
2937
|
+
function parsePreviousResponseId(value) {
|
|
2938
|
+
if (value === void 0) return void 0;
|
|
2939
|
+
if (typeof value !== "string" || !RESPONSE_ID_PATTERN.test(value)) {
|
|
2940
|
+
invalid5("previous_response_id");
|
|
2941
|
+
}
|
|
2942
|
+
return value;
|
|
2943
|
+
}
|
|
2944
|
+
function parseExplicitCallIds(value) {
|
|
2945
|
+
if (value === void 0 || typeof value === "string") return Object.freeze([]);
|
|
2946
|
+
if (!Array.isArray(value)) invalid5("input");
|
|
2947
|
+
const ids = [];
|
|
2948
|
+
for (const item of value) {
|
|
2949
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
2950
|
+
const candidate = item;
|
|
2951
|
+
if (candidate.type !== "image_generation_call") continue;
|
|
2952
|
+
if (Object.keys(candidate).some((key) => key !== "type" && key !== "id")) invalid5("input");
|
|
2953
|
+
if (typeof candidate.id !== "string" || !CALL_ID_PATTERN.test(candidate.id)) invalid5("input");
|
|
2954
|
+
ids.push(candidate.id);
|
|
2955
|
+
}
|
|
2956
|
+
return Object.freeze(ids);
|
|
2957
|
+
}
|
|
2958
|
+
function parseDeclaration(value, stream) {
|
|
2959
|
+
for (const key of Object.keys(value)) {
|
|
2960
|
+
if (!IMAGE_DECLARATION_KEYS.has(key)) invalid5(`tools.${key}`);
|
|
2961
|
+
}
|
|
2962
|
+
const action = value.action === void 0 ? "auto" : value.action;
|
|
2963
|
+
if (action !== "auto" && action !== "generate" && action !== "edit") invalid5("tools.action");
|
|
2964
|
+
const options = normalizeImageOptions(
|
|
2965
|
+
{
|
|
2966
|
+
prompt: "responses image selection",
|
|
2967
|
+
model: NORMALIZATION_RUNTIME.defaultModel,
|
|
2968
|
+
n: 1,
|
|
2969
|
+
moderation: "auto",
|
|
2970
|
+
stream,
|
|
2971
|
+
...value.size !== void 0 ? { size: value.size } : {},
|
|
2972
|
+
...value.quality !== void 0 ? { quality: value.quality } : {},
|
|
2973
|
+
...value.output_format !== void 0 ? { output_format: value.output_format } : {},
|
|
2974
|
+
...value.output_compression !== void 0 ? { output_compression: value.output_compression } : {},
|
|
2975
|
+
...value.background !== void 0 ? { background: value.background } : {},
|
|
2976
|
+
...value.partial_images !== void 0 ? { partial_images: value.partial_images } : {}
|
|
2977
|
+
},
|
|
2978
|
+
NORMALIZATION_RUNTIME,
|
|
2979
|
+
{ action: "generate" }
|
|
2980
|
+
);
|
|
2981
|
+
return Object.freeze({
|
|
2982
|
+
action,
|
|
2983
|
+
quality: options.quality,
|
|
2984
|
+
size: options.size,
|
|
2985
|
+
background: options.background,
|
|
2986
|
+
outputFormat: options.outputFormat,
|
|
2987
|
+
...options.outputCompression !== void 0 ? { outputCompression: options.outputCompression } : {},
|
|
2988
|
+
partialImages: options.partialImages
|
|
2989
|
+
});
|
|
2990
|
+
}
|
|
2991
|
+
function inspectResponsesImageRequest(input) {
|
|
2992
|
+
if (!input || typeof input !== "object") invalid5();
|
|
2993
|
+
const stream = input.stream === void 0 ? false : input.stream;
|
|
2994
|
+
if (typeof stream !== "boolean") invalid5("stream");
|
|
2995
|
+
const tools = input.tools === void 0 ? [] : input.tools;
|
|
2996
|
+
if (!Array.isArray(tools)) invalid5("tools");
|
|
2997
|
+
let imageToolIndex;
|
|
2998
|
+
let declaration;
|
|
2999
|
+
let otherToolCount = 0;
|
|
3000
|
+
const otherTools = [];
|
|
3001
|
+
for (let index = 0; index < tools.length; index += 1) {
|
|
3002
|
+
const tool = tools[index];
|
|
3003
|
+
if (!tool || typeof tool !== "object" || Array.isArray(tool)) {
|
|
3004
|
+
otherToolCount += 1;
|
|
3005
|
+
continue;
|
|
3006
|
+
}
|
|
3007
|
+
const candidate = tool;
|
|
3008
|
+
if (candidate.type !== "image_generation") {
|
|
3009
|
+
otherToolCount += 1;
|
|
3010
|
+
const identity = declaredOtherToolIdentity(candidate, index);
|
|
3011
|
+
if (identity) otherTools.push(identity);
|
|
3012
|
+
continue;
|
|
3013
|
+
}
|
|
3014
|
+
if (imageToolIndex !== void 0) invalid5("tools");
|
|
3015
|
+
imageToolIndex = index;
|
|
3016
|
+
declaration = parseDeclaration(candidate, stream);
|
|
3017
|
+
}
|
|
3018
|
+
const selectionPolicy = parseSelectionPolicy(input.tool_choice);
|
|
3019
|
+
if (selectionPolicy.kind === "forced_image" && imageToolIndex === void 0) {
|
|
3020
|
+
invalid5("tool_choice");
|
|
3021
|
+
}
|
|
3022
|
+
if (selectionPolicy.kind === "forced_other" && !otherTools.some((identity) => forcedOtherMatches(selectionPolicy, identity))) {
|
|
3023
|
+
invalid5("tool_choice");
|
|
3024
|
+
}
|
|
3025
|
+
const previousResponseId = parsePreviousResponseId(input.previous_response_id);
|
|
3026
|
+
return Object.freeze({
|
|
3027
|
+
declared: imageToolIndex !== void 0,
|
|
3028
|
+
...imageToolIndex !== void 0 ? { imageToolIndex } : {},
|
|
3029
|
+
otherToolCount,
|
|
3030
|
+
otherTools: Object.freeze(otherTools),
|
|
3031
|
+
stream,
|
|
3032
|
+
...previousResponseId !== void 0 ? { previousResponseId } : {},
|
|
3033
|
+
explicitCallIds: parseExplicitCallIds(input.input),
|
|
3034
|
+
selectionPolicy,
|
|
3035
|
+
...declaration ? { options: declaration } : {}
|
|
3036
|
+
});
|
|
3037
|
+
}
|
|
3038
|
+
function validateSelectedCall(call) {
|
|
3039
|
+
if (!call || typeof call !== "object" || typeof call.prompt !== "string") protocolFailure();
|
|
3040
|
+
if (!call.prompt.trim() || call.prompt.length > 32e3) protocolFailure();
|
|
3041
|
+
if (Object.keys(call).some((key) => key !== "prompt")) {
|
|
3042
|
+
protocolFailure();
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
function validateSelectedOtherTool(value) {
|
|
3046
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) protocolFailure();
|
|
3047
|
+
const candidate = value;
|
|
3048
|
+
if (Object.keys(candidate).some((key) => !TOOL_IDENTITY_KEYS.has(key))) protocolFailure();
|
|
3049
|
+
if (!Number.isSafeInteger(value.declarationIndex) || value.declarationIndex < 0 || typeof value.type !== "string" || !value.type.trim() || value.type.length > 128 || value.name !== void 0 && (typeof value.name !== "string" || !value.name.trim() || value.name.length > 128)) {
|
|
3050
|
+
protocolFailure();
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
function validateResponsesImageSelection(admission, selection) {
|
|
3054
|
+
if (!selection || typeof selection !== "object" || Array.isArray(selection) || !Array.isArray(selection.imageCalls) || !Array.isArray(selection.otherTools) || Object.keys(selection).some((key) => key !== "imageCalls" && key !== "otherToolCount" && key !== "otherTools")) {
|
|
3055
|
+
protocolFailure();
|
|
3056
|
+
}
|
|
3057
|
+
if (!Number.isSafeInteger(selection.otherToolCount) || selection.otherToolCount < 0 || selection.otherToolCount !== selection.otherTools.length || selection.imageCalls.length + selection.otherToolCount > MAX_SELECTED_TOOL_CALLS) {
|
|
3058
|
+
protocolFailure();
|
|
3059
|
+
}
|
|
3060
|
+
selection.imageCalls.forEach(validateSelectedCall);
|
|
3061
|
+
selection.otherTools.forEach(validateSelectedOtherTool);
|
|
3062
|
+
if (selection.imageCalls.length > 0 && !admission.declared) protocolFailure();
|
|
3063
|
+
for (const selected of selection.otherTools) {
|
|
3064
|
+
if (!admission.otherTools.some((declared) => sameToolIdentity(declared, selected))) {
|
|
3065
|
+
protocolFailure();
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
const selectedAny = selection.imageCalls.length + selection.otherToolCount > 0;
|
|
3069
|
+
switch (admission.selectionPolicy.kind) {
|
|
3070
|
+
case "auto":
|
|
3071
|
+
return;
|
|
3072
|
+
case "required":
|
|
3073
|
+
if (!selectedAny) protocolFailure();
|
|
3074
|
+
return;
|
|
3075
|
+
case "none":
|
|
3076
|
+
if (selectedAny) protocolFailure();
|
|
3077
|
+
return;
|
|
3078
|
+
case "forced_image":
|
|
3079
|
+
if (selection.imageCalls.length === 0 || selection.otherToolCount !== 0) protocolFailure();
|
|
3080
|
+
return;
|
|
3081
|
+
case "forced_other": {
|
|
3082
|
+
const forcedOtherPolicy = admission.selectionPolicy;
|
|
3083
|
+
if (selection.imageCalls.length > 0 || selection.otherTools.length === 0 || selection.otherTools.some(
|
|
3084
|
+
(selected) => !forcedOtherMatches(forcedOtherPolicy, selected)
|
|
3085
|
+
)) {
|
|
3086
|
+
protocolFailure();
|
|
3087
|
+
}
|
|
3088
|
+
return;
|
|
3089
|
+
}
|
|
3090
|
+
default:
|
|
3091
|
+
protocolFailure();
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
|
|
3095
|
+
// src/image-generation/responses/ResponsesImageGenerationContribution.ts
|
|
3096
|
+
var CALL_ID_PATTERN2 = /^ig_[A-Za-z0-9_-]{16,128}$/;
|
|
3097
|
+
var RESPONSE_ID_PATTERN2 = /^resp_[A-Za-z0-9_-]{1,240}$/;
|
|
3098
|
+
function nonempty(value, max = 512) {
|
|
3099
|
+
return typeof value === "string" && value.trim().length > 0 && value.length <= max;
|
|
3100
|
+
}
|
|
3101
|
+
function cancelled4(signal) {
|
|
3102
|
+
throw new ImageGenerationError("request_cancelled", { cause: signal.reason });
|
|
3103
|
+
}
|
|
3104
|
+
function assertNotAborted(signal) {
|
|
3105
|
+
if (signal.aborted) cancelled4(signal);
|
|
3106
|
+
}
|
|
3107
|
+
function stableError(error, signal) {
|
|
3108
|
+
if (signal.aborted) return new ImageGenerationError("request_cancelled", { cause: signal.reason });
|
|
3109
|
+
return normalizeImageGenerationError(error);
|
|
3110
|
+
}
|
|
3111
|
+
function assertRuntime(runtime) {
|
|
3112
|
+
if (!runtime || typeof runtime !== "object") {
|
|
3113
|
+
throw new ImageGenerationError("invalid_image_request");
|
|
3114
|
+
}
|
|
3115
|
+
if (!nonempty(runtime.tenantId) || !nonempty(runtime.requestId)) {
|
|
3116
|
+
throw new ImageGenerationError("invalid_image_request");
|
|
3117
|
+
}
|
|
3118
|
+
if (!nonempty(runtime.providerId, 128)) {
|
|
3119
|
+
throw new ImageGenerationError("invalid_image_request", { param: "provider" });
|
|
3120
|
+
}
|
|
3121
|
+
if (!nonempty(runtime.imageModel, 128)) {
|
|
3122
|
+
throw new ImageGenerationError("invalid_image_request", { param: "model" });
|
|
3123
|
+
}
|
|
3124
|
+
if (!Number.isSafeInteger(runtime.referenceTtlMs) || runtime.referenceTtlMs <= 0 || !Number.isSafeInteger(runtime.maxOutputBytes) || runtime.maxOutputBytes <= 0 || !Number.isSafeInteger(runtime.maxTotalOutputBytes) || runtime.maxTotalOutputBytes < runtime.maxOutputBytes || !runtime.signal || typeof runtime.signal.aborted !== "boolean" || typeof runtime.signal.addEventListener !== "function" || typeof runtime.signal.removeEventListener !== "function") {
|
|
3125
|
+
throw new ImageGenerationError("invalid_image_request");
|
|
3126
|
+
}
|
|
3127
|
+
for (const hint of [runtime.sessionKey, runtime.preferredAccountId, runtime.preferredAccountGroup]) {
|
|
3128
|
+
if (hint !== void 0 && !nonempty(hint)) {
|
|
3129
|
+
throw new ImageGenerationError("invalid_image_request");
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
if (runtime.boundAccountFallbackPolicy !== void 0 && runtime.boundAccountFallbackPolicy !== "strict" && runtime.boundAccountFallbackPolicy !== "pool") {
|
|
3133
|
+
throw new ImageGenerationError("invalid_image_request");
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
function linkAbortSignal(signal) {
|
|
3137
|
+
const controller = new AbortController();
|
|
3138
|
+
const onAbort = () => {
|
|
3139
|
+
if (!controller.signal.aborted) controller.abort(signal.reason);
|
|
3140
|
+
};
|
|
3141
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3142
|
+
if (signal.aborted) onAbort();
|
|
3143
|
+
return {
|
|
3144
|
+
controller,
|
|
3145
|
+
removeListener: () => signal.removeEventListener("abort", onAbort)
|
|
3146
|
+
};
|
|
3147
|
+
}
|
|
3148
|
+
function referenceFailure(status) {
|
|
3149
|
+
throw new ImageGenerationError(
|
|
3150
|
+
status === "expired" ? "image_reference_expired" : "image_reference_not_found"
|
|
3151
|
+
);
|
|
3152
|
+
}
|
|
3153
|
+
function assertAssetAgreement(lease) {
|
|
3154
|
+
const asset = lease.value.artifact;
|
|
3155
|
+
const metadata = lease.metadata;
|
|
3156
|
+
if (!asset || asset.independentlyDecodable !== true || asset.mimeType !== metadata.mimeType || asset.byteLength !== metadata.byteLength || asset.width !== metadata.width || asset.height !== metadata.height || asset.artifactId.length === 0) {
|
|
3157
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
3158
|
+
}
|
|
3159
|
+
return asset;
|
|
3160
|
+
}
|
|
3161
|
+
async function releaseQuietly(leases) {
|
|
3162
|
+
for (const lease of [...leases].reverse()) {
|
|
3163
|
+
try {
|
|
3164
|
+
await lease.release();
|
|
3165
|
+
} catch (e11) {
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
async function createScope(deps, now, input) {
|
|
3170
|
+
assertRuntime(input.runtime);
|
|
3171
|
+
const { admission, runtime } = input;
|
|
3172
|
+
const linked = linkAbortSignal(runtime.signal);
|
|
3173
|
+
const responseLeases = [];
|
|
3174
|
+
const stateLeases = [];
|
|
3175
|
+
const imageLeases = [];
|
|
3176
|
+
const callIds = [];
|
|
3177
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3178
|
+
const addCallId = (callId) => {
|
|
3179
|
+
if (!seen.has(callId)) {
|
|
3180
|
+
seen.add(callId);
|
|
3181
|
+
callIds.push(callId);
|
|
3182
|
+
}
|
|
3183
|
+
};
|
|
3184
|
+
try {
|
|
3185
|
+
assertNotAborted(linked.controller.signal);
|
|
3186
|
+
if (!admission || typeof admission !== "object") {
|
|
3187
|
+
throw new ImageGenerationError("invalid_image_request");
|
|
3188
|
+
}
|
|
3189
|
+
if (admission.previousResponseId !== void 0) {
|
|
3190
|
+
if (input.authorizedPreviousResponseId !== admission.previousResponseId) {
|
|
3191
|
+
throw new ImageGenerationError("image_reference_not_found");
|
|
3192
|
+
}
|
|
3193
|
+
} else if (input.authorizedPreviousResponseId !== void 0) {
|
|
3194
|
+
throw new ImageGenerationError("invalid_image_request", { param: "previous_response_id" });
|
|
3195
|
+
}
|
|
3196
|
+
if (input.authorizedPreviousResponseKnownEmpty !== void 0 && (typeof input.authorizedPreviousResponseKnownEmpty !== "boolean" || input.authorizedPreviousResponseId === void 0)) {
|
|
3197
|
+
throw new ImageGenerationError("invalid_image_request", { param: "previous_response_id" });
|
|
3198
|
+
}
|
|
3199
|
+
admission.explicitCallIds.forEach(addCallId);
|
|
3200
|
+
if (input.authorizedPreviousResponseId !== void 0) {
|
|
3201
|
+
const response = await deps.stateStore.resolveResponse(
|
|
3202
|
+
runtime.tenantId,
|
|
3203
|
+
input.authorizedPreviousResponseId
|
|
3204
|
+
);
|
|
3205
|
+
if (response.status === "found") responseLeases.push(response.lease);
|
|
3206
|
+
assertNotAborted(linked.controller.signal);
|
|
3207
|
+
if (response.status === "expired") referenceFailure(response.status);
|
|
3208
|
+
if (response.status === "not_found" && input.authorizedPreviousResponseKnownEmpty !== true) {
|
|
3209
|
+
referenceFailure(response.status);
|
|
3210
|
+
}
|
|
3211
|
+
if (response.status === "found") response.lease.callIds.forEach(addCallId);
|
|
3212
|
+
}
|
|
3213
|
+
const resolvedInputs = [];
|
|
3214
|
+
for (const callId of callIds) {
|
|
3215
|
+
assertNotAborted(linked.controller.signal);
|
|
3216
|
+
const state = await deps.stateStore.resolveCall(runtime.tenantId, callId);
|
|
3217
|
+
if (state.status === "found") stateLeases.push(state.lease);
|
|
3218
|
+
assertNotAborted(linked.controller.signal);
|
|
3219
|
+
if (state.status !== "found") referenceFailure(state.status);
|
|
3220
|
+
const image = await deps.referenceStore.resolve(
|
|
3221
|
+
runtime.tenantId,
|
|
3222
|
+
state.lease.binding.referenceId
|
|
3223
|
+
);
|
|
3224
|
+
if (image.status === "found") imageLeases.push(image.lease);
|
|
3225
|
+
assertNotAborted(linked.controller.signal);
|
|
3226
|
+
if (image.status !== "found") referenceFailure(image.status);
|
|
3227
|
+
if (image.lease.metadata.referenceId !== state.lease.binding.referenceId) {
|
|
3228
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
3229
|
+
}
|
|
3230
|
+
resolvedInputs.push({
|
|
3231
|
+
binding: state.lease.binding,
|
|
3232
|
+
asset: assertAssetAgreement(image.lease),
|
|
3233
|
+
stateLease: state.lease,
|
|
3234
|
+
imageLease: image.lease
|
|
3235
|
+
});
|
|
3236
|
+
}
|
|
3237
|
+
assertNotAborted(linked.controller.signal);
|
|
3238
|
+
return new RequestScope(
|
|
3239
|
+
deps,
|
|
3240
|
+
now,
|
|
3241
|
+
input,
|
|
3242
|
+
resolvedInputs,
|
|
3243
|
+
responseLeases,
|
|
3244
|
+
linked.controller,
|
|
3245
|
+
linked.removeListener
|
|
3246
|
+
);
|
|
3247
|
+
} catch (error) {
|
|
3248
|
+
await releaseQuietly([...imageLeases, ...stateLeases, ...responseLeases]);
|
|
3249
|
+
linked.removeListener();
|
|
3250
|
+
throw stableError(error, linked.controller.signal);
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
var RequestScope = class {
|
|
3254
|
+
#deps;
|
|
3255
|
+
#now;
|
|
3256
|
+
#admission;
|
|
3257
|
+
#runtime;
|
|
3258
|
+
#resolvedInputs;
|
|
3259
|
+
#responseLeases;
|
|
3260
|
+
#pendingBindings = [];
|
|
3261
|
+
#newReferenceIds = /* @__PURE__ */ new Set();
|
|
3262
|
+
#newReferenceLeases = [];
|
|
3263
|
+
#allocatedCallIds = /* @__PURE__ */ new Set();
|
|
3264
|
+
#scopeController;
|
|
3265
|
+
#removeRuntimeAbortListener;
|
|
3266
|
+
#usedBytes = 0;
|
|
3267
|
+
#lastOutputIndex = -1;
|
|
3268
|
+
#lastSequenceNumber = -1;
|
|
3269
|
+
#state = "open";
|
|
3270
|
+
#disposeRequested = false;
|
|
3271
|
+
#executionDone;
|
|
3272
|
+
#resolveExecutionDone;
|
|
3273
|
+
#activeExecutionIterator;
|
|
3274
|
+
#activeIteratorClosePromise;
|
|
3275
|
+
#commitResponseId;
|
|
3276
|
+
#commitPromise;
|
|
3277
|
+
#committedResponseId;
|
|
3278
|
+
#disposePromise;
|
|
3279
|
+
constructor(deps, now, input, resolvedInputs, responseLeases, scopeController, removeRuntimeAbortListener) {
|
|
3280
|
+
this.#deps = deps;
|
|
3281
|
+
this.#now = now;
|
|
3282
|
+
this.#admission = input.admission;
|
|
3283
|
+
this.#runtime = input.runtime;
|
|
3284
|
+
this.#resolvedInputs = Object.freeze([...resolvedInputs]);
|
|
3285
|
+
this.#responseLeases = Object.freeze([...responseLeases]);
|
|
3286
|
+
this.#scopeController = scopeController;
|
|
3287
|
+
this.#removeRuntimeAbortListener = removeRuntimeAbortListener;
|
|
3288
|
+
for (const item of resolvedInputs) this.#allocatedCallIds.add(item.binding.callId);
|
|
3289
|
+
}
|
|
3290
|
+
executeSelectedCall(call, allocator) {
|
|
3291
|
+
const generator = this.#executeSelectedCall(call, allocator);
|
|
3292
|
+
let tracked;
|
|
3293
|
+
const clearIfCurrent = () => {
|
|
3294
|
+
if (this.#activeExecutionIterator === tracked) {
|
|
3295
|
+
this.#activeExecutionIterator = void 0;
|
|
3296
|
+
this.#activeIteratorClosePromise = void 0;
|
|
3297
|
+
}
|
|
3298
|
+
};
|
|
3299
|
+
tracked = {
|
|
3300
|
+
next: async () => {
|
|
3301
|
+
if (this.#activeExecutionIterator === void 0 || this.#activeExecutionIterator === tracked) {
|
|
3302
|
+
this.#activeExecutionIterator = tracked;
|
|
3303
|
+
}
|
|
3304
|
+
const result = await generator.next();
|
|
3305
|
+
if (result.done) clearIfCurrent();
|
|
3306
|
+
return result;
|
|
3307
|
+
},
|
|
3308
|
+
return: async () => {
|
|
3309
|
+
try {
|
|
3310
|
+
return await generator.return(void 0);
|
|
3311
|
+
} finally {
|
|
3312
|
+
clearIfCurrent();
|
|
3313
|
+
}
|
|
3314
|
+
},
|
|
3315
|
+
throw: async (error) => {
|
|
3316
|
+
try {
|
|
3317
|
+
return await generator.throw(error);
|
|
3318
|
+
} finally {
|
|
3319
|
+
clearIfCurrent();
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
};
|
|
3323
|
+
return Object.freeze({
|
|
3324
|
+
[Symbol.asyncIterator]: () => tracked
|
|
3325
|
+
});
|
|
3326
|
+
}
|
|
3327
|
+
async *#executeSelectedCall(call, allocator) {
|
|
3328
|
+
let callId;
|
|
3329
|
+
let outputIndex;
|
|
3330
|
+
let started = false;
|
|
3331
|
+
let providerIterator;
|
|
3332
|
+
try {
|
|
3333
|
+
this.#beginExecution();
|
|
3334
|
+
started = true;
|
|
3335
|
+
if (!this.#admission.declared || !this.#admission.options) {
|
|
3336
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
3337
|
+
}
|
|
3338
|
+
if (!call || typeof call !== "object" || !nonempty(call.prompt, 32e3)) {
|
|
3339
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
3340
|
+
}
|
|
3341
|
+
if (!allocator || typeof allocator.reserveOutputIndex !== "function" || typeof allocator.nextSequenceNumber !== "function") {
|
|
3342
|
+
throw new ImageGenerationError("image_generation_failed");
|
|
3343
|
+
}
|
|
3344
|
+
assertNotAborted(this.#scopeController.signal);
|
|
3345
|
+
const candidateCallId = (_nullishCoalesce(this.#deps.createCallId, () => ( (() => `ig_${_crypto.randomUUID.call(void 0, ).replaceAll("-", "")}`))))();
|
|
3346
|
+
if (!CALL_ID_PATTERN2.test(candidateCallId) || this.#allocatedCallIds.has(candidateCallId)) {
|
|
3347
|
+
throw new ImageGenerationError("image_generation_failed");
|
|
3348
|
+
}
|
|
3349
|
+
callId = candidateCallId;
|
|
3350
|
+
this.#allocatedCallIds.add(callId);
|
|
3351
|
+
outputIndex = allocator.reserveOutputIndex();
|
|
3352
|
+
if (!Number.isSafeInteger(outputIndex) || outputIndex < 0 || outputIndex <= this.#lastOutputIndex) {
|
|
3353
|
+
throw new ImageGenerationError("image_generation_failed");
|
|
3354
|
+
}
|
|
3355
|
+
this.#lastOutputIndex = outputIndex;
|
|
3356
|
+
const request2 = this.#buildRequest(call);
|
|
3357
|
+
const context = {
|
|
3358
|
+
requestId: this.#runtime.requestId,
|
|
3359
|
+
tenantId: this.#runtime.tenantId,
|
|
3360
|
+
signal: this.#scopeController.signal,
|
|
3361
|
+
...this.#runtime.sessionKey !== void 0 ? { sessionKey: this.#runtime.sessionKey } : {},
|
|
3362
|
+
...this.#runtime.preferredAccountId !== void 0 ? { preferredAccountId: this.#runtime.preferredAccountId } : {},
|
|
3363
|
+
...this.#runtime.preferredAccountGroup !== void 0 ? { preferredAccountGroup: this.#runtime.preferredAccountGroup } : {},
|
|
3364
|
+
...this.#runtime.boundAccountFallbackPolicy !== void 0 ? { boundAccountFallbackPolicy: this.#runtime.boundAccountFallbackPolicy } : {}
|
|
3365
|
+
};
|
|
3366
|
+
const iterator = this.#deps.orchestrator.run(request2, context, {
|
|
3367
|
+
providerId: this.#runtime.providerId,
|
|
3368
|
+
retention: { enabled: true, ttlMs: this.#runtime.referenceTtlMs },
|
|
3369
|
+
requireResponsesTool: true
|
|
3370
|
+
})[Symbol.asyncIterator]();
|
|
3371
|
+
providerIterator = iterator;
|
|
3372
|
+
while (true) {
|
|
3373
|
+
const next = await iterator.next();
|
|
3374
|
+
if (!next.done && next.value.type === "completed" && next.value.images.length === 1 && _optionalChain([next, 'access', _80 => _80.value, 'access', _81 => _81.references, 'optionalAccess', _82 => _82.length]) === 1) {
|
|
3375
|
+
this.#newReferenceIds.add(next.value.references[0].referenceId);
|
|
3376
|
+
}
|
|
3377
|
+
if (next.done) throw new ImageGenerationError("upstream_protocol_changed");
|
|
3378
|
+
const event = next.value;
|
|
3379
|
+
assertNotAborted(this.#scopeController.signal);
|
|
3380
|
+
if (event.type === "accepted") {
|
|
3381
|
+
yield Object.freeze({
|
|
3382
|
+
kind: "started",
|
|
3383
|
+
outputIndex,
|
|
3384
|
+
item: Object.freeze({
|
|
3385
|
+
id: callId,
|
|
3386
|
+
type: "image_generation_call",
|
|
3387
|
+
status: "in_progress"
|
|
3388
|
+
})
|
|
3389
|
+
});
|
|
3390
|
+
continue;
|
|
3391
|
+
}
|
|
3392
|
+
if (event.type === "partial_image") {
|
|
3393
|
+
const sequence = allocator.nextSequenceNumber();
|
|
3394
|
+
if (!Number.isSafeInteger(sequence) || sequence < 0 || sequence <= this.#lastSequenceNumber) {
|
|
3395
|
+
throw new ImageGenerationError("image_generation_failed");
|
|
3396
|
+
}
|
|
3397
|
+
this.#lastSequenceNumber = sequence;
|
|
3398
|
+
const partial = await this.#readBase64(event.image.artifact);
|
|
3399
|
+
assertNotAborted(this.#scopeController.signal);
|
|
3400
|
+
yield Object.freeze({
|
|
3401
|
+
type: "response.image_generation_call.partial_image",
|
|
3402
|
+
output_index: outputIndex,
|
|
3403
|
+
item_id: callId,
|
|
3404
|
+
sequence_number: sequence,
|
|
3405
|
+
partial_image_index: event.partialImageIndex,
|
|
3406
|
+
partial_image_b64: partial
|
|
3407
|
+
});
|
|
3408
|
+
continue;
|
|
3409
|
+
}
|
|
3410
|
+
if (event.type === "failed") {
|
|
3411
|
+
yield Object.freeze({
|
|
3412
|
+
kind: "failed",
|
|
3413
|
+
outputIndex,
|
|
3414
|
+
callId,
|
|
3415
|
+
error: serializeImageGenerationError(imageGenerationErrorFromPublic(event.error))
|
|
3416
|
+
});
|
|
3417
|
+
return;
|
|
3418
|
+
}
|
|
3419
|
+
if (event.images.length !== 1 || !event.references || event.references.length !== 1) {
|
|
3420
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
3421
|
+
}
|
|
3422
|
+
const image = event.images[0];
|
|
3423
|
+
const reference = event.references[0];
|
|
3424
|
+
const retained = await this.#deps.referenceStore.resolve(
|
|
3425
|
+
this.#runtime.tenantId,
|
|
3426
|
+
reference.referenceId
|
|
3427
|
+
);
|
|
3428
|
+
if (retained.status !== "found") throw new ImageGenerationError("upstream_protocol_changed");
|
|
3429
|
+
this.#newReferenceLeases.push(retained.lease);
|
|
3430
|
+
const retainedAsset = assertAssetAgreement(retained.lease);
|
|
3431
|
+
if (retained.lease.metadata.referenceId !== reference.referenceId || retainedAsset.artifactId !== image.artifact.artifactId || reference.mimeType !== image.artifact.mimeType || reference.byteLength !== image.artifact.byteLength || reference.width !== image.artifact.width || reference.height !== image.artifact.height) {
|
|
3432
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
3433
|
+
}
|
|
3434
|
+
const result = await this.#readBase64(image.artifact);
|
|
3435
|
+
assertNotAborted(this.#scopeController.signal);
|
|
3436
|
+
const binding = Object.freeze({
|
|
3437
|
+
callId,
|
|
3438
|
+
referenceId: reference.referenceId,
|
|
3439
|
+
expiresAt: reference.expiresAt
|
|
3440
|
+
});
|
|
3441
|
+
this.#pendingBindings.push(binding);
|
|
3442
|
+
yield Object.freeze({
|
|
3443
|
+
kind: "completed",
|
|
3444
|
+
outputIndex,
|
|
3445
|
+
item: Object.freeze({
|
|
3446
|
+
id: callId,
|
|
3447
|
+
type: "image_generation_call",
|
|
3448
|
+
status: "completed",
|
|
3449
|
+
result,
|
|
3450
|
+
...image.revisedPrompt !== void 0 ? { revised_prompt: image.revisedPrompt } : {}
|
|
3451
|
+
})
|
|
3452
|
+
});
|
|
3453
|
+
return;
|
|
3454
|
+
}
|
|
3455
|
+
} catch (error) {
|
|
3456
|
+
const safe = stableError(error, this.#scopeController.signal);
|
|
3457
|
+
yield Object.freeze({
|
|
3458
|
+
kind: "failed",
|
|
3459
|
+
...outputIndex !== void 0 ? { outputIndex } : {},
|
|
3460
|
+
...callId !== void 0 ? { callId } : {},
|
|
3461
|
+
error: serializeImageGenerationError(safe)
|
|
3462
|
+
});
|
|
3463
|
+
} finally {
|
|
3464
|
+
try {
|
|
3465
|
+
await _optionalChain([providerIterator, 'optionalAccess', _83 => _83.return, 'optionalCall', _84 => _84()]);
|
|
3466
|
+
} catch (e12) {
|
|
3467
|
+
}
|
|
3468
|
+
if (started) this.#finishExecution();
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
commit(responseId) {
|
|
3472
|
+
if (!RESPONSE_ID_PATTERN2.test(responseId)) return this.#commitFailure();
|
|
3473
|
+
if (this.#state === "committing") {
|
|
3474
|
+
return this.#commitResponseId === responseId ? this.#commitPromise : this.#commitFailure();
|
|
3475
|
+
}
|
|
3476
|
+
if (this.#state === "committed") {
|
|
3477
|
+
return this.#committedResponseId === responseId ? _nullishCoalesce(this.#commitPromise, () => ( Promise.resolve())) : this.#commitFailure();
|
|
3478
|
+
}
|
|
3479
|
+
if (this.#state !== "open" || this.#disposeRequested) return this.#commitFailure();
|
|
3480
|
+
try {
|
|
3481
|
+
assertNotAborted(this.#scopeController.signal);
|
|
3482
|
+
} catch (error) {
|
|
3483
|
+
return Promise.reject(error);
|
|
3484
|
+
}
|
|
3485
|
+
this.#state = "committing";
|
|
3486
|
+
this.#commitResponseId = responseId;
|
|
3487
|
+
this.#commitPromise = Promise.resolve().then(() => this.#performCommit(responseId));
|
|
3488
|
+
return this.#commitPromise;
|
|
3489
|
+
}
|
|
3490
|
+
async #performCommit(responseId) {
|
|
3491
|
+
const bindings = [...this.#resolvedInputs.map((item) => item.binding), ...this.#pendingBindings];
|
|
3492
|
+
try {
|
|
3493
|
+
const evicted = await this.#deps.stateStore.commit({
|
|
3494
|
+
tenantId: this.#runtime.tenantId,
|
|
3495
|
+
responseId,
|
|
3496
|
+
bindings,
|
|
3497
|
+
responseExpiresAt: this.#now() + this.#runtime.referenceTtlMs
|
|
3498
|
+
});
|
|
3499
|
+
this.#committedResponseId = responseId;
|
|
3500
|
+
this.#state = "committed";
|
|
3501
|
+
for (const binding of evicted) {
|
|
3502
|
+
try {
|
|
3503
|
+
await this.#deps.referenceStore.delete(this.#runtime.tenantId, binding.referenceId);
|
|
3504
|
+
} catch (e13) {
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
} catch (error) {
|
|
3508
|
+
if (this.#state === "committing") this.#state = "open";
|
|
3509
|
+
this.#commitResponseId = void 0;
|
|
3510
|
+
throw stableError(error, this.#scopeController.signal);
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
dispose() {
|
|
3514
|
+
if (this.#disposePromise) return this.#disposePromise;
|
|
3515
|
+
this.#disposeRequested = true;
|
|
3516
|
+
this.#disposePromise = this.#performDispose();
|
|
3517
|
+
return this.#disposePromise;
|
|
3518
|
+
}
|
|
3519
|
+
async waitForIdle() {
|
|
3520
|
+
const operations = [this.#executionDone, this.#commitPromise].filter((value) => value !== void 0);
|
|
3521
|
+
await Promise.allSettled(operations);
|
|
3522
|
+
}
|
|
3523
|
+
#buildRequest(call) {
|
|
3524
|
+
const options = this.#admission.options;
|
|
3525
|
+
const common = {
|
|
3526
|
+
model: this.#runtime.imageModel,
|
|
3527
|
+
prompt: call.prompt,
|
|
3528
|
+
n: 1,
|
|
3529
|
+
quality: options.quality,
|
|
3530
|
+
size: options.size,
|
|
3531
|
+
background: options.background,
|
|
3532
|
+
outputFormat: options.outputFormat,
|
|
3533
|
+
...options.outputCompression !== void 0 ? { outputCompression: options.outputCompression } : {},
|
|
3534
|
+
moderation: "auto",
|
|
3535
|
+
stream: options.partialImages > 0,
|
|
3536
|
+
partialImages: options.partialImages
|
|
3537
|
+
};
|
|
3538
|
+
const action = options.action === "auto" ? this.#resolvedInputs.length > 0 ? "edit" : "generate" : options.action;
|
|
3539
|
+
if (action === "generate") return { action, ...common };
|
|
3540
|
+
if (this.#resolvedInputs.length === 0) {
|
|
3541
|
+
throw new ImageGenerationError("invalid_image_request", { param: "input" });
|
|
3542
|
+
}
|
|
3543
|
+
return { action, ...common, images: this.#resolvedInputs.map((item) => item.asset) };
|
|
3544
|
+
}
|
|
3545
|
+
async #readBase64(asset) {
|
|
3546
|
+
assertNotAborted(this.#scopeController.signal);
|
|
3547
|
+
if (!Number.isSafeInteger(asset.byteLength) || asset.byteLength <= 0 || asset.byteLength > this.#runtime.maxOutputBytes || this.#usedBytes > this.#runtime.maxTotalOutputBytes - asset.byteLength) {
|
|
3548
|
+
throw new ImageGenerationError("image_too_large");
|
|
3549
|
+
}
|
|
3550
|
+
this.#usedBytes += asset.byteLength;
|
|
3551
|
+
let bytes;
|
|
3552
|
+
try {
|
|
3553
|
+
bytes = await readImageAssetBytes(
|
|
3554
|
+
asset,
|
|
3555
|
+
this.#runtime.maxOutputBytes,
|
|
3556
|
+
this.#scopeController.signal
|
|
3557
|
+
);
|
|
3558
|
+
} catch (error) {
|
|
3559
|
+
if (this.#scopeController.signal.aborted) cancelled4(this.#scopeController.signal);
|
|
3560
|
+
if (error instanceof RangeError) {
|
|
3561
|
+
throw new ImageGenerationError("upstream_protocol_changed");
|
|
3562
|
+
}
|
|
3563
|
+
throw error;
|
|
3564
|
+
}
|
|
3565
|
+
assertNotAborted(this.#scopeController.signal);
|
|
3566
|
+
return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64");
|
|
3567
|
+
}
|
|
3568
|
+
#beginExecution() {
|
|
3569
|
+
if (this.#state !== "open" || this.#disposeRequested) {
|
|
3570
|
+
throw new ImageGenerationError("invalid_image_request");
|
|
3571
|
+
}
|
|
3572
|
+
this.#state = "executing";
|
|
3573
|
+
this.#executionDone = new Promise((resolve2) => {
|
|
3574
|
+
this.#resolveExecutionDone = resolve2;
|
|
3575
|
+
});
|
|
3576
|
+
}
|
|
3577
|
+
#finishExecution() {
|
|
3578
|
+
if (this.#state === "executing") this.#state = "open";
|
|
3579
|
+
_optionalChain([this, 'access', _85 => _85.#resolveExecutionDone, 'optionalCall', _86 => _86()]);
|
|
3580
|
+
this.#resolveExecutionDone = void 0;
|
|
3581
|
+
}
|
|
3582
|
+
#commitFailure() {
|
|
3583
|
+
return Promise.reject(new ImageGenerationError("image_generation_failed"));
|
|
3584
|
+
}
|
|
3585
|
+
#closeActiveIterator() {
|
|
3586
|
+
if (this.#activeIteratorClosePromise) return this.#activeIteratorClosePromise;
|
|
3587
|
+
const iterator = this.#activeExecutionIterator;
|
|
3588
|
+
if (!_optionalChain([iterator, 'optionalAccess', _87 => _87.return])) return Promise.resolve();
|
|
3589
|
+
this.#activeIteratorClosePromise = Promise.resolve(iterator.return()).then(
|
|
3590
|
+
() => void 0,
|
|
3591
|
+
() => void 0
|
|
3592
|
+
);
|
|
3593
|
+
return this.#activeIteratorClosePromise;
|
|
3594
|
+
}
|
|
3595
|
+
async #performDispose() {
|
|
3596
|
+
if (this.#state === "executing" && !this.#scopeController.signal.aborted) {
|
|
3597
|
+
this.#scopeController.abort(new Error("Responses image request scope disposed."));
|
|
3598
|
+
}
|
|
3599
|
+
const executionDone = this.#executionDone;
|
|
3600
|
+
await Promise.allSettled([
|
|
3601
|
+
this.#closeActiveIterator(),
|
|
3602
|
+
...executionDone ? [executionDone] : []
|
|
3603
|
+
]);
|
|
3604
|
+
if (this.#commitPromise) {
|
|
3605
|
+
await this.#commitPromise.catch(() => void 0);
|
|
3606
|
+
}
|
|
3607
|
+
if (this.#committedResponseId === void 0) {
|
|
3608
|
+
for (const referenceId of this.#newReferenceIds) {
|
|
3609
|
+
try {
|
|
3610
|
+
await this.#deps.referenceStore.delete(this.#runtime.tenantId, referenceId);
|
|
3611
|
+
} catch (e14) {
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
await releaseQuietly([
|
|
3616
|
+
...this.#newReferenceLeases,
|
|
3617
|
+
...this.#resolvedInputs.map((item) => item.imageLease),
|
|
3618
|
+
...this.#resolvedInputs.map((item) => item.stateLease),
|
|
3619
|
+
...this.#responseLeases
|
|
3620
|
+
]);
|
|
3621
|
+
if (!this.#scopeController.signal.aborted) {
|
|
3622
|
+
this.#scopeController.abort(new Error("Responses image request scope disposed."));
|
|
3623
|
+
}
|
|
3624
|
+
this.#removeRuntimeAbortListener();
|
|
3625
|
+
this.#state = "disposed";
|
|
3626
|
+
}
|
|
3627
|
+
};
|
|
3628
|
+
function createResponsesImageGenerationContribution(deps) {
|
|
3629
|
+
if (!deps || typeof deps !== "object" || !deps.orchestrator || !deps.stateStore || !deps.referenceStore || deps.createCallId !== void 0 && typeof deps.createCallId !== "function" || deps.now !== void 0 && typeof deps.now !== "function") {
|
|
3630
|
+
throw new TypeError("Responses image generation requires orchestrator and state/reference stores.");
|
|
3631
|
+
}
|
|
3632
|
+
const now = _nullishCoalesce(deps.now, () => ( Date.now));
|
|
3633
|
+
return Object.freeze({
|
|
3634
|
+
toolType: "image_generation",
|
|
3635
|
+
inspectRequest: inspectResponsesImageRequest,
|
|
3636
|
+
validateSelection: validateResponsesImageSelection,
|
|
3637
|
+
createRequestScope: (input) => createScope(deps, now, input)
|
|
3638
|
+
});
|
|
3639
|
+
}
|
|
3640
|
+
|
|
3641
|
+
// src/image-generation/responses/ResponsesImageStateStore.ts
|
|
3642
|
+
var CALL_ID_PATTERN3 = /^ig_[A-Za-z0-9_-]{16,128}$/;
|
|
3643
|
+
var RESPONSE_ID_PATTERN3 = /^resp_[A-Za-z0-9_-]{1,240}$/;
|
|
3644
|
+
function positiveInteger(value, name) {
|
|
3645
|
+
if (!Number.isSafeInteger(value) || value <= 0) throw new RangeError(`${name} must be positive.`);
|
|
3646
|
+
return value;
|
|
3647
|
+
}
|
|
3648
|
+
function safeTenant(value) {
|
|
3649
|
+
return typeof value === "string" && value.trim().length > 0 && value.length <= 512;
|
|
3650
|
+
}
|
|
3651
|
+
function sameBinding(a, b) {
|
|
3652
|
+
return a.callId === b.callId && a.referenceId === b.referenceId && a.expiresAt === b.expiresAt;
|
|
3653
|
+
}
|
|
3654
|
+
function sameIds(a, b) {
|
|
3655
|
+
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
3656
|
+
}
|
|
3657
|
+
var InMemoryResponsesImageStateStore = class {
|
|
3658
|
+
#calls = /* @__PURE__ */ new Map();
|
|
3659
|
+
#responses = /* @__PURE__ */ new Map();
|
|
3660
|
+
#tombstones = /* @__PURE__ */ new Map();
|
|
3661
|
+
#maxCalls;
|
|
3662
|
+
#maxResponses;
|
|
3663
|
+
#maxTombstones;
|
|
3664
|
+
#tombstoneTtlMs;
|
|
3665
|
+
#now;
|
|
3666
|
+
constructor(options = {}) {
|
|
3667
|
+
this.#maxCalls = positiveInteger(_nullishCoalesce(options.maxCalls, () => ( 1e4)), "maxCalls");
|
|
3668
|
+
this.#maxResponses = positiveInteger(_nullishCoalesce(options.maxResponses, () => ( 1e4)), "maxResponses");
|
|
3669
|
+
this.#maxTombstones = positiveInteger(_nullishCoalesce(options.maxTombstones, () => ( 2e3)), "maxTombstones");
|
|
3670
|
+
this.#tombstoneTtlMs = positiveInteger(
|
|
3671
|
+
_nullishCoalesce(options.tombstoneTtlMs, () => ( 60 * 6e4)),
|
|
3672
|
+
"tombstoneTtlMs"
|
|
3673
|
+
);
|
|
3674
|
+
this.#now = _nullishCoalesce(options.now, () => ( Date.now));
|
|
3675
|
+
}
|
|
3676
|
+
async commit(input) {
|
|
3677
|
+
const now = this.#now();
|
|
3678
|
+
this.#pruneTombstones(now);
|
|
3679
|
+
this.#assertCommit(input, now);
|
|
3680
|
+
const unique = new Set(input.bindings.map((binding) => binding.callId));
|
|
3681
|
+
if (unique.size !== input.bindings.length) {
|
|
3682
|
+
throw new ImageGenerationError("image_generation_failed");
|
|
3683
|
+
}
|
|
3684
|
+
for (const binding of input.bindings) {
|
|
3685
|
+
const current = this.#calls.get(binding.callId);
|
|
3686
|
+
if (current && (current.deleted || current.tenantId !== input.tenantId || !sameBinding(current.binding, binding))) {
|
|
3687
|
+
throw new ImageGenerationError("image_generation_failed");
|
|
3688
|
+
}
|
|
3689
|
+
}
|
|
3690
|
+
const currentResponse = this.#responses.get(input.responseId);
|
|
3691
|
+
const callIds = Object.freeze(input.bindings.map((binding) => binding.callId));
|
|
3692
|
+
if (currentResponse && (currentResponse.deleted || currentResponse.tenantId !== input.tenantId || currentResponse.expiresAt !== input.responseExpiresAt || !sameIds(currentResponse.callIds, callIds))) {
|
|
3693
|
+
throw new ImageGenerationError("image_generation_failed");
|
|
3694
|
+
}
|
|
3695
|
+
const protectedCalls = new Set(callIds);
|
|
3696
|
+
const additionalCalls = input.bindings.filter((binding) => !this.#calls.has(binding.callId)).length;
|
|
3697
|
+
const unleasedCalls = [...this.#calls.entries()].filter(
|
|
3698
|
+
([id, entry]) => entry.activeLeases === 0 && !protectedCalls.has(id)
|
|
3699
|
+
);
|
|
3700
|
+
const evictableCalls = [
|
|
3701
|
+
...unleasedCalls.filter(([, entry]) => entry.binding.expiresAt <= now),
|
|
3702
|
+
...unleasedCalls.filter(([, entry]) => entry.binding.expiresAt > now)
|
|
3703
|
+
];
|
|
3704
|
+
const callsToRemove = Math.max(0, this.#calls.size + additionalCalls - this.#maxCalls);
|
|
3705
|
+
if (evictableCalls.length < callsToRemove) {
|
|
3706
|
+
throw new ImageGenerationError("image_generation_failed");
|
|
3707
|
+
}
|
|
3708
|
+
const additionalResponse = currentResponse ? 0 : 1;
|
|
3709
|
+
const unleasedResponses = [...this.#responses.entries()].filter(
|
|
3710
|
+
([id, entry]) => entry.activeLeases === 0 && id !== input.responseId
|
|
3711
|
+
);
|
|
3712
|
+
const evictableResponses = [
|
|
3713
|
+
...unleasedResponses.filter(([, entry]) => entry.expiresAt <= now),
|
|
3714
|
+
...unleasedResponses.filter(([, entry]) => entry.expiresAt > now)
|
|
3715
|
+
];
|
|
3716
|
+
const responsesToRemove = Math.max(
|
|
3717
|
+
0,
|
|
3718
|
+
this.#responses.size + additionalResponse - this.#maxResponses
|
|
3719
|
+
);
|
|
3720
|
+
if (evictableResponses.length < responsesToRemove) {
|
|
3721
|
+
throw new ImageGenerationError("image_generation_failed");
|
|
3722
|
+
}
|
|
3723
|
+
const evicted = [];
|
|
3724
|
+
for (const [id, entry] of evictableCalls.slice(0, callsToRemove)) {
|
|
3725
|
+
if (entry.binding.expiresAt <= now) {
|
|
3726
|
+
this.#rememberTombstone("call", entry.tenantId, id, now);
|
|
3727
|
+
}
|
|
3728
|
+
this.#calls.delete(id);
|
|
3729
|
+
evicted.push(entry.binding);
|
|
3730
|
+
}
|
|
3731
|
+
for (const [id, entry] of evictableResponses.slice(0, responsesToRemove)) {
|
|
3732
|
+
if (entry.expiresAt <= now) {
|
|
3733
|
+
this.#rememberTombstone("response", entry.tenantId, id, now);
|
|
3734
|
+
}
|
|
3735
|
+
this.#responses.delete(id);
|
|
3736
|
+
}
|
|
3737
|
+
for (const binding of input.bindings) {
|
|
3738
|
+
const current = this.#calls.get(binding.callId);
|
|
3739
|
+
if (current) {
|
|
3740
|
+
this.#touch(this.#calls, binding.callId, current);
|
|
3741
|
+
} else {
|
|
3742
|
+
this.#calls.set(binding.callId, {
|
|
3743
|
+
tenantId: input.tenantId,
|
|
3744
|
+
binding: Object.freeze({ ...binding }),
|
|
3745
|
+
activeLeases: 0,
|
|
3746
|
+
deleted: false
|
|
3747
|
+
});
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
if (currentResponse) {
|
|
3751
|
+
this.#touch(this.#responses, input.responseId, currentResponse);
|
|
3752
|
+
} else {
|
|
3753
|
+
this.#responses.set(input.responseId, {
|
|
3754
|
+
tenantId: input.tenantId,
|
|
3755
|
+
responseId: input.responseId,
|
|
3756
|
+
callIds,
|
|
3757
|
+
expiresAt: input.responseExpiresAt,
|
|
3758
|
+
activeLeases: 0,
|
|
3759
|
+
deleted: false
|
|
3760
|
+
});
|
|
3761
|
+
}
|
|
3762
|
+
return Object.freeze(evicted);
|
|
3763
|
+
}
|
|
3764
|
+
async resolveCall(tenantId, callId) {
|
|
3765
|
+
if (!safeTenant(tenantId) || !CALL_ID_PATTERN3.test(callId)) return { status: "not_found" };
|
|
3766
|
+
const now = this.#now();
|
|
3767
|
+
this.#pruneTombstones(now);
|
|
3768
|
+
const entry = this.#calls.get(callId);
|
|
3769
|
+
if (!entry || entry.tenantId !== tenantId || entry.deleted) {
|
|
3770
|
+
return this.#hasTombstone("call", tenantId, callId) ? { status: "expired" } : { status: "not_found" };
|
|
3771
|
+
}
|
|
3772
|
+
if (entry.binding.expiresAt <= now) {
|
|
3773
|
+
this.#rememberTombstone("call", tenantId, callId, now);
|
|
3774
|
+
return { status: "expired" };
|
|
3775
|
+
}
|
|
3776
|
+
entry.activeLeases += 1;
|
|
3777
|
+
this.#touch(this.#calls, callId, entry);
|
|
3778
|
+
let released = false;
|
|
3779
|
+
return {
|
|
3780
|
+
status: "found",
|
|
3781
|
+
lease: {
|
|
3782
|
+
binding: entry.binding,
|
|
3783
|
+
async release() {
|
|
3784
|
+
if (released) return;
|
|
3785
|
+
released = true;
|
|
3786
|
+
entry.activeLeases = Math.max(0, entry.activeLeases - 1);
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3789
|
+
};
|
|
3790
|
+
}
|
|
3791
|
+
async resolveResponse(tenantId, responseId) {
|
|
3792
|
+
if (!safeTenant(tenantId) || !RESPONSE_ID_PATTERN3.test(responseId)) return { status: "not_found" };
|
|
3793
|
+
const now = this.#now();
|
|
3794
|
+
this.#pruneTombstones(now);
|
|
3795
|
+
const entry = this.#responses.get(responseId);
|
|
3796
|
+
if (!entry || entry.tenantId !== tenantId || entry.deleted) {
|
|
3797
|
+
return this.#hasTombstone("response", tenantId, responseId) ? { status: "expired" } : { status: "not_found" };
|
|
3798
|
+
}
|
|
3799
|
+
if (entry.expiresAt <= now) {
|
|
3800
|
+
this.#rememberTombstone("response", tenantId, responseId, now);
|
|
3801
|
+
return { status: "expired" };
|
|
3802
|
+
}
|
|
3803
|
+
entry.activeLeases += 1;
|
|
3804
|
+
this.#touch(this.#responses, responseId, entry);
|
|
3805
|
+
let released = false;
|
|
3806
|
+
return {
|
|
3807
|
+
status: "found",
|
|
3808
|
+
lease: {
|
|
3809
|
+
responseId,
|
|
3810
|
+
callIds: entry.callIds,
|
|
3811
|
+
expiresAt: entry.expiresAt,
|
|
3812
|
+
async release() {
|
|
3813
|
+
if (released) return;
|
|
3814
|
+
released = true;
|
|
3815
|
+
entry.activeLeases = Math.max(0, entry.activeLeases - 1);
|
|
3816
|
+
}
|
|
3817
|
+
}
|
|
3818
|
+
};
|
|
3819
|
+
}
|
|
3820
|
+
async deleteCall(tenantId, callId) {
|
|
3821
|
+
const entry = this.#calls.get(callId);
|
|
3822
|
+
if (!entry || entry.tenantId !== tenantId || entry.deleted) return void 0;
|
|
3823
|
+
entry.deleted = true;
|
|
3824
|
+
if (entry.activeLeases === 0) this.#calls.delete(callId);
|
|
3825
|
+
return entry.binding;
|
|
3826
|
+
}
|
|
3827
|
+
async deleteResponse(tenantId, responseId) {
|
|
3828
|
+
const entry = this.#responses.get(responseId);
|
|
3829
|
+
if (!entry || entry.tenantId !== tenantId || entry.deleted) return false;
|
|
3830
|
+
entry.deleted = true;
|
|
3831
|
+
if (entry.activeLeases === 0) this.#responses.delete(responseId);
|
|
3832
|
+
return true;
|
|
3833
|
+
}
|
|
3834
|
+
async cleanup(now = this.#now()) {
|
|
3835
|
+
if (!Number.isFinite(now)) throw new RangeError("cleanup time must be finite.");
|
|
3836
|
+
this.#pruneTombstones(now);
|
|
3837
|
+
const removed = [];
|
|
3838
|
+
for (const [callId, entry] of this.#calls) {
|
|
3839
|
+
const expired = entry.binding.expiresAt <= now;
|
|
3840
|
+
if (expired) this.#rememberTombstone("call", entry.tenantId, callId, now);
|
|
3841
|
+
if ((expired || entry.deleted) && entry.activeLeases === 0) {
|
|
3842
|
+
this.#calls.delete(callId);
|
|
3843
|
+
removed.push(entry.binding);
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
for (const [responseId, entry] of this.#responses) {
|
|
3847
|
+
const expired = entry.expiresAt <= now;
|
|
3848
|
+
if (expired) this.#rememberTombstone("response", entry.tenantId, responseId, now);
|
|
3849
|
+
if ((expired || entry.deleted) && entry.activeLeases === 0) {
|
|
3850
|
+
this.#responses.delete(responseId);
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
return Object.freeze(removed);
|
|
3854
|
+
}
|
|
3855
|
+
#assertCommit(input, now) {
|
|
3856
|
+
if (!safeTenant(input.tenantId)) throw new ImageGenerationError("image_generation_failed");
|
|
3857
|
+
if (!RESPONSE_ID_PATTERN3.test(input.responseId)) {
|
|
3858
|
+
throw new ImageGenerationError("image_generation_failed");
|
|
3859
|
+
}
|
|
3860
|
+
if (!Number.isSafeInteger(input.responseExpiresAt) || input.responseExpiresAt <= now) {
|
|
3861
|
+
throw new ImageGenerationError("image_generation_failed");
|
|
3862
|
+
}
|
|
3863
|
+
if (!Array.isArray(input.bindings)) throw new ImageGenerationError("image_generation_failed");
|
|
3864
|
+
for (const binding of input.bindings) {
|
|
3865
|
+
if (!binding || !CALL_ID_PATTERN3.test(binding.callId) || typeof binding.referenceId !== "string" || binding.referenceId.length === 0 || !Number.isSafeInteger(binding.expiresAt) || binding.expiresAt <= now) {
|
|
3866
|
+
throw new ImageGenerationError("image_generation_failed");
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
#rememberTombstone(kind, tenantId, id, now) {
|
|
3871
|
+
const key = `${kind}\0${tenantId}\0${id}`;
|
|
3872
|
+
this.#tombstones.delete(key);
|
|
3873
|
+
this.#tombstones.set(key, now + this.#tombstoneTtlMs);
|
|
3874
|
+
while (this.#tombstones.size > this.#maxTombstones) {
|
|
3875
|
+
const oldest = this.#tombstones.keys().next().value;
|
|
3876
|
+
if (oldest === void 0) break;
|
|
3877
|
+
this.#tombstones.delete(oldest);
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
#hasTombstone(kind, tenantId, id) {
|
|
3881
|
+
return this.#tombstones.has(`${kind}\0${tenantId}\0${id}`);
|
|
3882
|
+
}
|
|
3883
|
+
#pruneTombstones(now) {
|
|
3884
|
+
for (const [key, expiresAt] of this.#tombstones) {
|
|
3885
|
+
if (expiresAt <= now) this.#tombstones.delete(key);
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
#touch(map, key, value) {
|
|
3889
|
+
map.delete(key);
|
|
3890
|
+
map.set(key, value);
|
|
3891
|
+
}
|
|
3892
|
+
};
|
|
3893
|
+
|
|
3894
|
+
|
|
3895
|
+
|
|
3896
|
+
|
|
3897
|
+
|
|
3898
|
+
|
|
3899
|
+
|
|
3900
|
+
|
|
3901
|
+
|
|
3902
|
+
|
|
3903
|
+
|
|
3904
|
+
|
|
3905
|
+
|
|
3906
|
+
|
|
3907
|
+
|
|
3908
|
+
|
|
3909
|
+
|
|
3910
|
+
|
|
3911
|
+
|
|
3912
|
+
|
|
3913
|
+
|
|
3914
|
+
|
|
3915
|
+
|
|
3916
|
+
|
|
3917
|
+
|
|
3918
|
+
|
|
3919
|
+
exports.resolveImageCapabilities = resolveImageCapabilities; exports.ImageGenerationError = ImageGenerationError; exports.isImageGenerationError = isImageGenerationError; exports.serializeImageGenerationError = serializeImageGenerationError; exports.normalizeImageGenerationError = normalizeImageGenerationError; exports.imageGenerationErrorFromPublic = imageGenerationErrorFromPublic; exports.InMemoryImageAsset = InMemoryImageAsset; exports.readImageAssetBytes = readImageAssetBytes; exports.InMemoryImageReferenceStore = InMemoryImageReferenceStore; exports.emitImageTelemetry = emitImageTelemetry; exports.ImageOrchestrator = ImageOrchestrator; exports.ImageProviderRegistry = ImageProviderRegistry; exports.IMAGE_REQUEST_DIRECTORY_MARKER_NAME = IMAGE_REQUEST_DIRECTORY_MARKER_NAME; exports.IMAGE_REQUEST_DIRECTORY_MARKER_CONTENT = IMAGE_REQUEST_DIRECTORY_MARKER_CONTENT; exports.ImageRequestResourceScope = ImageRequestResourceScope; exports.createImageRequestResourceScope = createImageRequestResourceScope; exports.DEFAULT_IMAGE_API_LIMITS = DEFAULT_IMAGE_API_LIMITS; exports.assertFiniteImageApiLimits = assertFiniteImageApiLimits; exports.createImageApiContributions = createImageApiContributions; exports.createSafeRemoteImageResolver = createSafeRemoteImageResolver; exports.inspectResponsesImageRequest = inspectResponsesImageRequest; exports.validateResponsesImageSelection = validateResponsesImageSelection; exports.createResponsesImageGenerationContribution = createResponsesImageGenerationContribution; exports.InMemoryResponsesImageStateStore = InMemoryResponsesImageStateStore;
|