@cartanova/qgrid-cli 2.10.5 → 2.10.6
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/bundle/dist/application/monit/monit.frame.js +1 -1
- package/bundle/dist/application/qgrid/expired-token-reminder.js +1 -1
- package/bundle/dist/application/qgrid/oauth.js +1 -1
- package/bundle/dist/application/qgrid/qgrid-image-generation.js +7 -11
- package/bundle/dist/application/qgrid/qgrid-response-format.js +4 -4
- package/bundle/dist/application/qgrid/qgrid-run-lifecycle.js +24 -7
- package/bundle/dist/application/qgrid/qgrid.dispatcher.js +8 -3
- package/bundle/dist/application/qgrid/qgrid.frame.js +24 -10
- package/bundle/dist/application/qgrid/token-death.js +1 -1
- package/bundle/dist/application/qgrid/token-window-keepalive.js +1 -1
- package/bundle/dist/application/qgrid/tool-emulation.js +1 -1
- package/bundle/dist/application/request-log/request-log.model.js +2 -2
- package/bundle/dist/i18n/sd.generated.js +3 -3
- package/bundle/dist/sonamu.config.js +2 -2
- package/bundle/dist/utils/providers/anthropic/anthropic-dispatcher.js +1 -1
- package/bundle/dist/utils/providers/anthropic/anthropic-thinking.js +1 -1
- package/bundle/dist/utils/providers/openai/openai-backend-protocol.js +13 -2
- package/bundle/dist/utils/providers/openai/openai-direct-client.js +5 -4
- package/bundle/dist/utils/providers/openai/openai-dispatcher.js +111 -57
- package/bundle/dist/utils/providers/openai/openai-image-generation.js +25 -17
- package/bundle/dist/utils/providers/openai/openai-refresh.js +1 -1
- package/bundle/src/application/qgrid/qgrid-image-generation.test.ts +19 -8
- package/bundle/src/application/qgrid/qgrid-image-generation.ts +17 -24
- package/bundle/src/application/qgrid/qgrid-response-format.test.ts +4 -2
- package/bundle/src/application/qgrid/qgrid-response-format.ts +7 -10
- package/bundle/src/application/qgrid/qgrid-run-lifecycle.test.ts +87 -9
- package/bundle/src/application/qgrid/qgrid-run-lifecycle.ts +45 -5
- package/bundle/src/application/qgrid/qgrid.dispatcher.test.ts +136 -2
- package/bundle/src/application/qgrid/qgrid.dispatcher.ts +11 -1
- package/bundle/src/application/qgrid/qgrid.frame.test.ts +73 -0
- package/bundle/src/application/qgrid/qgrid.frame.ts +39 -4
- package/bundle/src/application/request-log/request-log.entity.json +2 -2
- package/bundle/src/application/sonamu.generated.http +2 -2
- package/bundle/src/i18n/sd.generated.ts +2 -2
- package/bundle/src/utils/providers/openai/openai-backend-protocol.test.ts +23 -1
- package/bundle/src/utils/providers/openai/openai-backend-protocol.ts +24 -2
- package/bundle/src/utils/providers/openai/openai-direct-client.test.ts +15 -0
- package/bundle/src/utils/providers/openai/openai-direct-client.ts +14 -4
- package/bundle/src/utils/providers/openai/openai-dispatcher.test.ts +223 -32
- package/bundle/src/utils/providers/openai/openai-dispatcher.ts +174 -72
- package/bundle/src/utils/providers/openai/openai-image-generation.test.ts +89 -0
- package/bundle/src/utils/providers/openai/openai-image-generation.ts +26 -15
- package/bundle/web-dist/client/assets/cost-DH2jZbrw.js +1 -0
- package/bundle/web-dist/client/assets/{index-Dxw7DiXF.js → index-D9ryIyL-.js} +4 -4
- package/bundle/web-dist/client/assets/logs-CLf2VNfk.js +1 -0
- package/bundle/web-dist/client/assets/{monit-E8eGW1Sj.js → monit-DojxOLj6.js} +1 -1
- package/bundle/web-dist/client/assets/{routes-7spLfIGZ.js → routes-DXJhNvbI.js} +2 -2
- package/bundle/web-dist/client/assets/{services.generated-jnPHYMER.js → services.generated-DqFYgWsY.js} +1 -1
- package/bundle/web-dist/client/assets/{settings-BdosZFNC.js → settings-bopyz9pa.js} +1 -1
- package/bundle/web-dist/client/assets/{show-b2PFNI3D.js → show-DbLD3MzB.js} +1 -1
- package/bundle/web-dist/client/assets/{tokens-CBdHb2_U.js → tokens-2-AIi9sZ.js} +1 -1
- package/bundle/web-dist/client/assets/{use-oauth-login-flow-Cyn3YPqW.js → use-oauth-login-flow-CnqTWs_H.js} +1 -1
- package/bundle/web-dist/client/assets/{use-update-token-mutation-DQCwCYfR.js → use-update-token-mutation-Cxb9K0fM.js} +1 -1
- package/bundle/web-dist/client/index.html +2 -2
- package/bundle/web-dist/server/assets/{cost-59jBdx-I.js → cost-eEHeTRT5.js} +4 -5
- package/bundle/web-dist/server/assets/{lazyRouteComponent-CUR-kWzI.js → lazyRouteComponent-Bi0fYGLs.js} +2 -2
- package/bundle/web-dist/server/assets/{logs-CaaWO9-Z.js → logs-CIVFxfoi.js} +10 -9
- package/bundle/web-dist/server/assets/{monit-CBil92fJ.js → monit-DBXezg7R.js} +4 -4
- package/bundle/web-dist/server/assets/{plus-Bvdj8Pwx.js → plus-BH-t4cm_.js} +1 -1
- package/bundle/web-dist/server/assets/{react-dom-CyuShs9K.js → react-dom-CZ2eynd8.js} +1 -1
- package/bundle/web-dist/server/assets/{refresh-cw-3QAiSWBd.js → refresh-cw-CosAdcz0.js} +1 -1
- package/bundle/web-dist/server/assets/{routes-DtVf9yqo.js → routes-COWYIvA-.js} +6 -7
- package/bundle/web-dist/server/assets/{services.generated-FPwsG9p3.js → services.generated-607iwhMI.js} +2 -2
- package/bundle/web-dist/server/assets/{settings-DeHNwoMv.js → settings-C_6dpK5p.js} +3 -3
- package/bundle/web-dist/server/assets/{show-8NNdRxhM.js → show-DY396-LD.js} +8 -20
- package/bundle/web-dist/server/assets/{tokens-odgS2-9q.js → tokens-COvV7qUP.js} +4 -4
- package/bundle/web-dist/server/assets/{use-oauth-login-flow-DTQKdYBO.js → use-oauth-login-flow-VYCYf3uv.js} +1 -1
- package/bundle/web-dist/server/assets/{use-update-token-mutation-xvXbPrCI.js → use-update-token-mutation-D8XYV5of.js} +1 -1
- package/bundle/web-dist/server/entry-server.generated.js +10 -10
- package/package.json +1 -1
- package/skills/qgrid/SKILL.md +9 -8
- package/skills/qgrid/references/ai-sdk-provider-contract.md +25 -16
- package/skills/qgrid/references/decision-rationale.md +8 -8
- package/skills/qgrid/references/openai-codex-runtime.md +19 -11
- package/skills/qgrid/references/request-log-run-lifecycle.md +4 -2
- package/skills/qgrid/references/sonamu-api-web-flow.md +2 -2
- package/skills/qgrid/references/verification-and-debugging.md +23 -9
- package/bundle/web-dist/client/assets/cost-CY2BJEPC.js +0 -1
- package/bundle/web-dist/client/assets/logs-3n_U4qnI.js +0 -1
|
@@ -17,10 +17,11 @@ const PNG_SIGNATURE = Buffer.from([
|
|
|
17
17
|
10
|
|
18
18
|
]);
|
|
19
19
|
function buildStandaloneImageRequest(options) {
|
|
20
|
-
if (options.outputSchema || options.tools?.length) throw new OpenAIProtocolError("
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (
|
|
20
|
+
if (options.outputSchema || options.tools?.length) throw new OpenAIProtocolError("Standalone image generation does not support tools or structured output");
|
|
21
|
+
if (!options.imageGeneration) throw new OpenAIProtocolError("Standalone images require image generation to be enabled");
|
|
22
|
+
const controls = typeof options.imageGeneration === "object" ? options.imageGeneration : {};
|
|
23
|
+
if (options.model !== "gpt-image-2" && controls.background !== "transparent") throw new OpenAIProtocolError("Standalone images require an explicit transparent background request");
|
|
24
|
+
if (controls.output_format && controls.output_format !== "png") throw new OpenAIProtocolError("Standalone image generation only supports PNG output");
|
|
24
25
|
const text = [];
|
|
25
26
|
const images = [];
|
|
26
27
|
if (options.instructions) text.push(`System instructions:\n${options.instructions}`);
|
|
@@ -30,14 +31,14 @@ function buildStandaloneImageRequest(options) {
|
|
|
30
31
|
images.push({ image_url: `data:${imageMimeType(item.result, item.output_format)};base64,${item.result}` });
|
|
31
32
|
continue;
|
|
32
33
|
}
|
|
33
|
-
if (item.type !== "message" && typeof item.role !== "string") throw new OpenAIProtocolError("Unsupported conversation item for
|
|
34
|
+
if (item.type !== "message" && typeof item.role !== "string") throw new OpenAIProtocolError("Unsupported conversation item for standalone image generation");
|
|
34
35
|
if (typeof item.content === "string") {
|
|
35
36
|
text.push(`${item.role ?? "user"}:\n${item.content}`);
|
|
36
37
|
continue;
|
|
37
38
|
}
|
|
38
|
-
if (!Array.isArray(item.content)) throw new OpenAIProtocolError("Unsupported message content for
|
|
39
|
+
if (!Array.isArray(item.content)) throw new OpenAIProtocolError("Unsupported message content for standalone image generation");
|
|
39
40
|
for (const part of item.content) {
|
|
40
|
-
if (!part || typeof part !== "object") throw new OpenAIProtocolError("Unsupported message part for
|
|
41
|
+
if (!part || typeof part !== "object") throw new OpenAIProtocolError("Unsupported message part for standalone image generation");
|
|
41
42
|
if ([
|
|
42
43
|
"input_text",
|
|
43
44
|
"output_text",
|
|
@@ -47,24 +48,24 @@ function buildStandaloneImageRequest(options) {
|
|
|
47
48
|
const url = typeof part.image_url === "string" ? part.image_url : part.image_url?.url;
|
|
48
49
|
if (typeof url !== "string" || !/^(https:\/\/|data:image\/)/i.test(url)) throw new OpenAIProtocolError("Image references must be HTTPS URLs or image data URLs");
|
|
49
50
|
images.push({ image_url: url });
|
|
50
|
-
} else throw new OpenAIProtocolError("Unsupported message part for
|
|
51
|
+
} else throw new OpenAIProtocolError("Unsupported message part for standalone image generation");
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
|
-
if (images.length > 5) throw new OpenAIProtocolError("
|
|
54
|
+
if (images.length > 5) throw new OpenAIProtocolError("Standalone image editing supports at most 5 reference images");
|
|
54
55
|
if (!text.some((part) => part.trim())) throw new OpenAIProtocolError("Image prompt is empty");
|
|
55
56
|
return {
|
|
56
57
|
url: CHATGPT_CODEX_RESPONSES_URL.replace(/\/responses$/, `/images/${images.length ? "edits" : "generations"}`),
|
|
57
58
|
body: {
|
|
58
59
|
model: "gpt-image-2",
|
|
59
60
|
prompt: text.join("\n\n"),
|
|
60
|
-
background:
|
|
61
|
+
...controls.background ? { background: controls.background } : {},
|
|
61
62
|
...controls.quality ? { quality: controls.quality } : {},
|
|
62
63
|
...controls.size ? { size: controls.size } : {},
|
|
63
64
|
...images.length ? { images } : {}
|
|
64
65
|
}
|
|
65
66
|
};
|
|
66
67
|
}
|
|
67
|
-
function
|
|
68
|
+
function inspectGeneratedPng(base64, requireTransparency) {
|
|
68
69
|
if (base64.length > Math.ceil(MAX_PNG_BYTES / 3) * 4 || !/^[A-Za-z0-9+/]+={0,2}$/.test(base64)) throw new OpenAIProtocolError("Invalid or oversized generated PNG");
|
|
69
70
|
const bytes = Buffer.from(base64, "base64");
|
|
70
71
|
if (bytes.length < 33 || !bytes.subarray(0, 8).equals(PNG_SIGNATURE) || bytes.toString("ascii", 12, 16) !== "IHDR") throw new OpenAIProtocolError("Image generation did not return a PNG");
|
|
@@ -77,6 +78,10 @@ function inspectTransparentPng(base64) {
|
|
|
77
78
|
} catch {
|
|
78
79
|
throw new OpenAIProtocolError("Image generation returned an invalid PNG");
|
|
79
80
|
}
|
|
81
|
+
if (!requireTransparency) return {
|
|
82
|
+
width: image.width,
|
|
83
|
+
height: image.height
|
|
84
|
+
};
|
|
80
85
|
let transparent = false;
|
|
81
86
|
let visible = false;
|
|
82
87
|
for (let i = 3; i < image.data.length; i += 4) {
|
|
@@ -89,7 +94,7 @@ function inspectTransparentPng(base64) {
|
|
|
89
94
|
}
|
|
90
95
|
throw new OpenAIProtocolError("Image generation did not return visible content on a transparent background");
|
|
91
96
|
}
|
|
92
|
-
async function readStandaloneImageResponse(response) {
|
|
97
|
+
async function readStandaloneImageResponse(response, requireTransparency) {
|
|
93
98
|
if (!response.body) throw new OpenAIProtocolError("Image response had no body");
|
|
94
99
|
const reader = response.body.getReader();
|
|
95
100
|
const chunks = [];
|
|
@@ -114,6 +119,8 @@ async function readStandaloneImageResponse(response) {
|
|
|
114
119
|
revised_prompt: z.string().nullish()
|
|
115
120
|
})).min(1).max(16),
|
|
116
121
|
quality: z.string().optional(),
|
|
122
|
+
model: z.string().optional(),
|
|
123
|
+
background: z.string().optional(),
|
|
117
124
|
usage: ImageGenerationMetadata.shape.usage
|
|
118
125
|
});
|
|
119
126
|
let parsed;
|
|
@@ -122,8 +129,9 @@ async function readStandaloneImageResponse(response) {
|
|
|
122
129
|
} catch {
|
|
123
130
|
throw new OpenAIProtocolError("Invalid standalone image response");
|
|
124
131
|
}
|
|
132
|
+
const background = parsed.background ?? (requireTransparency ? "transparent" : void 0);
|
|
125
133
|
return parsed.data.map((item, index) => {
|
|
126
|
-
const dimensions =
|
|
134
|
+
const dimensions = inspectGeneratedPng(item.b64_json, requireTransparency);
|
|
127
135
|
return {
|
|
128
136
|
type: "image",
|
|
129
137
|
base64: item.b64_json,
|
|
@@ -131,8 +139,8 @@ async function readStandaloneImageResponse(response) {
|
|
|
131
139
|
...item.revised_prompt ? { revisedPrompt: item.revised_prompt } : {},
|
|
132
140
|
generation: {
|
|
133
141
|
route: "codex-images",
|
|
134
|
-
model: "gpt-image-2",
|
|
135
|
-
background:
|
|
142
|
+
model: parsed.model ?? "gpt-image-2",
|
|
143
|
+
...background ? { background } : {},
|
|
136
144
|
size: `${dimensions.width}x${dimensions.height}`,
|
|
137
145
|
...parsed.quality ? { quality: parsed.quality } : {},
|
|
138
146
|
...index === 0 && parsed.usage ? { usage: parsed.usage } : {}
|
|
@@ -141,6 +149,6 @@ async function readStandaloneImageResponse(response) {
|
|
|
141
149
|
});
|
|
142
150
|
}
|
|
143
151
|
//#endregion
|
|
144
|
-
export { buildStandaloneImageRequest,
|
|
152
|
+
export { buildStandaloneImageRequest, readStandaloneImageResponse };
|
|
145
153
|
|
|
146
|
-
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BlbmFpLWltYWdlLWdlbmVyYXRpb24uanMiLCJuYW1lcyI6W10sInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3V0aWxzL3Byb3ZpZGVycy9vcGVuYWkvb3BlbmFpLWltYWdlLWdlbmVyYXRpb24udHMiXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgUE5HIH0gZnJvbSBcInBuZ2pzXCI7XG5pbXBvcnQgeyB6IH0gZnJvbSBcInpvZFwiO1xuXG5pbXBvcnQgeyBJbWFnZUdlbmVyYXRpb25NZXRhZGF0YSB9IGZyb20gXCIuLi8uLi8uLi9hcHBsaWNhdGlvbi9xZ3JpZC9xZ3JpZC50eXBlc1wiO1xuaW1wb3J0IHtcbiAgQ0hBVEdQVF9DT0RFWF9SRVNQT05TRVNfVVJMLFxuICBpbWFnZU1pbWVUeXBlLFxuICBPcGVuQUlQcm90b2NvbEVycm9yLFxuICB0eXBlIE9wZW5BSU5vcm1hbGl6ZWRFdmVudCxcbiAgdHlwZSBPcGVuQUlSZXNwb25zZXNPcHRpb25zLFxufSBmcm9tIFwiLi9vcGVuYWktYmFja2VuZC1wcm90b2NvbFwiO1xuXG5jb25zdCBNQVhfUE5HX0JZVEVTID0gMzIgKiAxMDI0ICogMTAyNDtcbmNvbnN0IE1BWF9JTUFHRV9QSVhFTFMgPSA0MDk2ICogNDA5NjtcbmNvbnN0IE1BWF9SRVNQT05TRV9CWVRFUyA9IDY0ICogMTAyNCAqIDEwMjQ7XG5jb25zdCBQTkdfU0lHTkFUVVJFID0gQnVmZmVyLmZyb20oWzEzNywgODAsIDc4LCA3MSwgMTMsIDEwLCAyNiwgMTBdKTtcblxuZXhwb3J0IGZ1bmN0aW9uIGJ1aWxkU3RhbmRhbG9uZUltYWdlUmVxdWVzdChvcHRpb25zOiBPcGVuQUlSZXNwb25zZXNPcHRpb25zKSB7XG4gIGlmIChvcHRpb25zLm91dHB1dFNjaGVtYSB8fCBvcHRpb25zLnRvb2xzPy5sZW5ndGgpIHtcbiAgICB0aHJvdyBuZXcgT3BlbkFJUHJvdG9jb2xFcnJvcihcbiAgICAgIFwiVHJhbnNwYXJlbnQgaW1hZ2UgZ2VuZXJhdGlvbiBkb2VzIG5vdCBzdXBwb3J0IHRvb2xzIG9yIHN0cnVjdHVyZWQgb3V0cHV0XCIsXG4gICAgKTtcbiAgfVxuICBjb25zdCBjb250cm9scyA9IG9wdGlvbnMuaW1hZ2VHZW5lcmF0aW9uO1xuICBpZiAoIWNvbnRyb2xzIHx8IGNvbnRyb2xzID09PSB0cnVlIHx8IGNvbnRyb2xzLmJhY2tncm91bmQgIT09IFwidHJhbnNwYXJlbnRcIikge1xuICAgIHRocm93IG5ldyBPcGVuQUlQcm90b2NvbEVycm9yKFxuICAgICAgXCJTdGFuZGFsb25lIGltYWdlcyByZXF1aXJlIGFuIGV4cGxpY2l0IHRyYW5zcGFyZW50IGJhY2tncm91bmQgcmVxdWVzdFwiLFxuICAgICk7XG4gIH1cbiAgaWYgKGNvbnRyb2xzLm91dHB1dF9mb3JtYXQgJiYgY29udHJvbHMub3V0cHV0X2Zvcm1hdCAhPT0gXCJwbmdcIikge1xuICAgIHRocm93IG5ldyBPcGVuQUlQcm90b2NvbEVycm9yKFwiVHJhbnNwYXJlbnQgaW1hZ2UgZ2VuZXJhdGlvbiBvbmx5IHN1cHBvcnRzIFBORyBvdXRwdXRcIik7XG4gIH1cbiAgY29uc3QgdGV4dDogc3RyaW5nW10gPSBbXTtcbiAgY29uc3QgaW1hZ2VzOiBBcnJheTx7IGltYWdlX3VybDogc3RyaW5nIH0+ID0gW107XG4gIGlmIChvcHRpb25zLmluc3RydWN0aW9ucykgdGV4dC5wdXNoKGBTeXN0ZW0gaW5zdHJ1Y3Rpb25zOlxcbiR7b3B0aW9ucy5pbnN0cnVjdGlvbnN9YCk7XG4gIGZvciAoY29uc3QgaXRlbSBvZiBvcHRpb25zLmhpc3RvcnkpIHtcbiAgICBpZiAoaXRlbS50eXBlID09PSBcInJlYXNvbmluZ1wiKSBjb250aW51ZTtcbiAgICBpZiAoaXRlbS50eXBlID09PSBcImltYWdlX2dlbmVyYXRpb25fY2FsbFwiICYmIHR5cGVvZiBpdGVtLnJlc3VsdCA9PT0gXCJzdHJpbmdcIikge1xuICAgICAgaW1hZ2VzLnB1c2goe1xuICAgICAgICBpbWFnZV91cmw6IGBkYXRhOiR7aW1hZ2VNaW1lVHlwZShpdGVtLnJlc3VsdCwgaXRlbS5vdXRwdXRfZm9ybWF0KX07YmFzZTY0LCR7aXRlbS5yZXN1bHR9YCxcbiAgICAgIH0pO1xuICAgICAgY29udGludWU7XG4gICAgfVxuICAgIGlmIChpdGVtLnR5cGUgIT09IFwibWVzc2FnZVwiICYmIHR5cGVvZiBpdGVtLnJvbGUgIT09IFwic3RyaW5nXCIpIHtcbiAgICAgIHRocm93IG5ldyBPcGVuQUlQcm90b2NvbEVycm9yKFxuICAgICAgICBcIlVuc3VwcG9ydGVkIGNvbnZlcnNhdGlvbiBpdGVtIGZvciB0cmFuc3BhcmVudCBpbWFnZSBnZW5lcmF0aW9uXCIsXG4gICAgICApO1xuICAgIH1cbiAgICBpZiAodHlwZW9mIGl0ZW0uY29udGVudCA9PT0gXCJzdHJpbmdcIikge1xuICAgICAgdGV4dC5wdXNoKGAke2l0ZW0ucm9sZSA/PyBcInVzZXJcIn06XFxuJHtpdGVtLmNvbnRlbnR9YCk7XG4gICAgICBjb250aW51ZTtcbiAgICB9XG4gICAgaWYgKCFBcnJheS5pc0FycmF5KGl0ZW0uY29udGVudCkpIHtcbiAgICAgIHRocm93IG5ldyBPcGVuQUlQcm90b2NvbEVycm9yKFwiVW5zdXBwb3J0ZWQgbWVzc2FnZSBjb250ZW50IGZvciB0cmFuc3BhcmVudCBpbWFnZSBnZW5lcmF0aW9uXCIpO1xuICAgIH1cbiAgICBmb3IgKGNvbnN0IHBhcnQgb2YgaXRlbS5jb250ZW50KSB7XG4gICAgICBpZiAoIXBhcnQgfHwgdHlwZW9mIHBhcnQgIT09IFwib2JqZWN0XCIpIHtcbiAgICAgICAgdGhyb3cgbmV3IE9wZW5BSVByb3RvY29sRXJyb3IoXCJVbnN1cHBvcnRlZCBtZXNzYWdlIHBhcnQgZm9yIHRyYW5zcGFyZW50IGltYWdlIGdlbmVyYXRpb25cIik7XG4gICAgICB9XG4gICAgICBpZiAoXG4gICAgICAgIFtcImlucHV0X3RleHRcIiwgXCJvdXRwdXRfdGV4dFwiLCBcInRleHRcIl0uaW5jbHVkZXMocGFydC50eXBlKSAmJlxuICAgICAgICB0eXBlb2YgcGFydC50ZXh0ID09PSBcInN0cmluZ1wiXG4gICAgICApIHtcbiAgICAgICAgdGV4dC5wdXNoKGAke2l0ZW0ucm9sZSA/PyBcInVzZXJcIn06XFxuJHtwYXJ0LnRleHR9YCk7XG4gICAgICB9IGVsc2UgaWYgKHBhcnQudHlwZSA9PT0gXCJpbnB1dF9pbWFnZVwiIHx8IHBhcnQudHlwZSA9PT0gXCJpbWFnZV91cmxcIikge1xuICAgICAgICBjb25zdCB1cmwgPSB0eXBlb2YgcGFydC5pbWFnZV91cmwgPT09IFwic3RyaW5nXCIgPyBwYXJ0LmltYWdlX3VybCA6IHBhcnQuaW1hZ2VfdXJsPy51cmw7XG4gICAgICAgIGlmICh0eXBlb2YgdXJsICE9PSBcInN0cmluZ1wiIHx8ICEvXihodHRwczpcXC9cXC98ZGF0YTppbWFnZVxcLykvaS50ZXN0KHVybCkpIHtcbiAgICAgICAgICB0aHJvdyBuZXcgT3BlbkFJUHJvdG9jb2xFcnJvcihcIkltYWdlIHJlZmVyZW5jZXMgbXVzdCBiZSBIVFRQUyBVUkxzIG9yIGltYWdlIGRhdGEgVVJMc1wiKTtcbiAgICAgICAgfVxuICAgICAgICBpbWFnZXMucHVzaCh7IGltYWdlX3VybDogdXJsIH0pO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhyb3cgbmV3IE9wZW5BSVByb3RvY29sRXJyb3IoXCJVbnN1cHBvcnRlZCBtZXNzYWdlIHBhcnQgZm9yIHRyYW5zcGFyZW50IGltYWdlIGdlbmVyYXRpb25cIik7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIGlmIChpbWFnZXMubGVuZ3RoID4gNSlcbiAgICB0aHJvdyBuZXcgT3BlbkFJUHJvdG9jb2xFcnJvcihcIlRyYW5zcGFyZW50IGltYWdlIGVkaXRpbmcgc3VwcG9ydHMgYXQgbW9zdCA1IHJlZmVyZW5jZSBpbWFnZXNcIik7XG4gIGlmICghdGV4dC5zb21lKChwYXJ0KSA9PiBwYXJ0LnRyaW0oKSkpIHRocm93IG5ldyBPcGVuQUlQcm90b2NvbEVycm9yKFwiSW1hZ2UgcHJvbXB0IGlzIGVtcHR5XCIpO1xuICByZXR1cm4ge1xuICAgIHVybDogQ0hBVEdQVF9DT0RFWF9SRVNQT05TRVNfVVJMLnJlcGxhY2UoXG4gICAgICAvXFwvcmVzcG9uc2VzJC8sXG4gICAgICBgL2ltYWdlcy8ke2ltYWdlcy5sZW5ndGggPyBcImVkaXRzXCIgOiBcImdlbmVyYXRpb25zXCJ9YCxcbiAgICApLFxuICAgIGJvZHk6IHtcbiAgICAgIG1vZGVsOiBcImdwdC1pbWFnZS0yXCIsXG4gICAgICBwcm9tcHQ6IHRleHQuam9pbihcIlxcblxcblwiKSxcbiAgICAgIGJhY2tncm91bmQ6IFwidHJhbnNwYXJlbnRcIixcbiAgICAgIC4uLihjb250cm9scy5xdWFsaXR5ID8geyBxdWFsaXR5OiBjb250cm9scy5xdWFsaXR5IH0gOiB7fSksXG4gICAgICAuLi4oY29udHJvbHMuc2l6ZSA/IHsgc2l6ZTogY29udHJvbHMuc2l6ZSB9IDoge30pLFxuICAgICAgLi4uKGltYWdlcy5sZW5ndGggPyB7IGltYWdlcyB9IDoge30pLFxuICAgIH0sXG4gIH07XG59XG5cbi8vIERlY29kZSBiZWZvcmUgYWNjZXB0aW5nIHRoZSByZXN1bHQ6IGFuIGFscGhhIGNoYW5uZWwgYWxvbmUgY2FuIHN0aWxsIGJlIGZ1bGx5IG9wYXF1ZS5cbmV4cG9ydCBmdW5jdGlvbiBpbnNwZWN0VHJhbnNwYXJlbnRQbmcoYmFzZTY0OiBzdHJpbmcpOiB7IHdpZHRoOiBudW1iZXI7IGhlaWdodDogbnVtYmVyIH0ge1xuICBpZiAoYmFzZTY0Lmxlbmd0aCA+IE1hdGguY2VpbChNQVhfUE5HX0JZVEVTIC8gMykgKiA0IHx8ICEvXltBLVphLXowLTkrL10rPXswLDJ9JC8udGVzdChiYXNlNjQpKSB7XG4gICAgdGhyb3cgbmV3IE9wZW5BSVByb3RvY29sRXJyb3IoXCJJbnZhbGlkIG9yIG92ZXJzaXplZCBnZW5lcmF0ZWQgUE5HXCIpO1xuICB9XG4gIGNvbnN0IGJ5dGVzID0gQnVmZmVyLmZyb20oYmFzZTY0LCBcImJhc2U2NFwiKTtcbiAgaWYgKFxuICAgIGJ5dGVzLmxlbmd0aCA8IDMzIHx8XG4gICAgIWJ5dGVzLnN1YmFycmF5KDAsIDgpLmVxdWFscyhQTkdfU0lHTkFUVVJFKSB8fFxuICAgIGJ5dGVzLnRvU3RyaW5nKFwiYXNjaWlcIiwgMTIsIDE2KSAhPT0gXCJJSERSXCJcbiAgKSB7XG4gICAgdGhyb3cgbmV3IE9wZW5BSVByb3RvY29sRXJyb3IoXCJJbWFnZSBnZW5lcmF0aW9uIGRpZCBub3QgcmV0dXJuIGEgUE5HXCIpO1xuICB9XG4gIGNvbnN0IHdpZHRoID0gYnl0ZXMucmVhZFVJbnQzMkJFKDE2KTtcbiAgY29uc3QgaGVpZ2h0ID0gYnl0ZXMucmVhZFVJbnQzMkJFKDIwKTtcbiAgaWYgKCF3aWR0aCB8fCAhaGVpZ2h0IHx8IHdpZHRoICogaGVpZ2h0ID4gTUFYX0lNQUdFX1BJWEVMUykge1xuICAgIHRocm93IG5ldyBPcGVuQUlQcm90b2NvbEVycm9yKFwiR2VuZXJhdGVkIFBORyBkaW1lbnNpb25zIGV4Y2VlZCB0aGUgZGVjb2RpbmcgbGltaXRcIik7XG4gIH1cbiAgbGV0IGltYWdlOiBQTkc7XG4gIHRyeSB7XG4gICAgaW1hZ2UgPSBQTkcuc3luYy5yZWFkKGJ5dGVzKTtcbiAgfSBjYXRjaCB7XG4gICAgdGhyb3cgbmV3IE9wZW5BSVByb3RvY29sRXJyb3IoXCJJbWFnZSBnZW5lcmF0aW9uIHJldHVybmVkIGFuIGludmFsaWQgUE5HXCIpO1xuICB9XG4gIGxldCB0cmFuc3BhcmVudCA9IGZhbHNlO1xuICBsZXQgdmlzaWJsZSA9IGZhbHNlO1xuICBmb3IgKGxldCBpID0gMzsgaSA8IGltYWdlLmRhdGEubGVuZ3RoOyBpICs9IDQpIHtcbiAgICB0cmFuc3BhcmVudCB8fD0gaW1hZ2UuZGF0YVtpXSA9PT0gMDtcbiAgICB2aXNpYmxlIHx8PSBpbWFnZS5kYXRhW2ldISA+IDA7XG4gICAgaWYgKHRyYW5zcGFyZW50ICYmIHZpc2libGUpIHJldHVybiB7IHdpZHRoOiBpbWFnZS53aWR0aCwgaGVpZ2h0OiBpbWFnZS5oZWlnaHQgfTtcbiAgfVxuICB0aHJvdyBuZXcgT3BlbkFJUHJvdG9jb2xFcnJvcihcbiAgICBcIkltYWdlIGdlbmVyYXRpb24gZGlkIG5vdCByZXR1cm4gdmlzaWJsZSBjb250ZW50IG9uIGEgdHJhbnNwYXJlbnQgYmFja2dyb3VuZFwiLFxuICApO1xufVxuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcmVhZFN0YW5kYWxvbmVJbWFnZVJlc3BvbnNlKFxuICByZXNwb25zZTogUmVzcG9uc2UsXG4pOiBQcm9taXNlPE9wZW5BSU5vcm1hbGl6ZWRFdmVudFtdPiB7XG4gIGlmICghcmVzcG9uc2UuYm9keSkgdGhyb3cgbmV3IE9wZW5BSVByb3RvY29sRXJyb3IoXCJJbWFnZSByZXNwb25zZSBoYWQgbm8gYm9keVwiKTtcbiAgY29uc3QgcmVhZGVyID0gcmVzcG9uc2UuYm9keS5nZXRSZWFkZXIoKTtcbiAgY29uc3QgY2h1bmtzOiBVaW50OEFycmF5W10gPSBbXTtcbiAgbGV0IHNpemUgPSAwO1xuICB0cnkge1xuICAgIHdoaWxlICh0cnVlKSB7XG4gICAgICBjb25zdCB7IGRvbmUsIHZhbHVlIH0gPSBhd2FpdCByZWFkZXIucmVhZCgpO1xuICAgICAgaWYgKGRvbmUpIGJyZWFrO1xuICAgICAgc2l6ZSArPSB2YWx1ZS5ieXRlTGVuZ3RoO1xuICAgICAgaWYgKHNpemUgPiBNQVhfUkVTUE9OU0VfQllURVMpIHtcbiAgICAgICAgYXdhaXQgcmVhZGVyLmNhbmNlbCgpO1xuICAgICAgICB0aHJvdyBuZXcgT3BlbkFJUHJvdG9jb2xFcnJvcihcIkltYWdlIHJlc3BvbnNlIGV4Y2VlZGVkIHRoZSBzaXplIGxpbWl0XCIpO1xuICAgICAgfVxuICAgICAgY2h1bmtzLnB1c2godmFsdWUpO1xuICAgIH1cbiAgfSBmaW5hbGx5IHtcbiAgICByZWFkZXIucmVsZWFzZUxvY2soKTtcbiAgfVxuICBjb25zdCBzY2hlbWEgPSB6Lm9iamVjdCh7XG4gICAgZGF0YTogelxuICAgICAgLmFycmF5KHoub2JqZWN0KHsgYjY0X2pzb246IHouc3RyaW5nKCkubWluKDEpLCByZXZpc2VkX3Byb21wdDogei5zdHJpbmcoKS5udWxsaXNoKCkgfSkpXG4gICAgICAubWluKDEpXG4gICAgICAubWF4KDE2KSxcbiAgICBxdWFsaXR5OiB6LnN0cmluZygpLm9wdGlvbmFsKCksXG4gICAgdXNhZ2U6IEltYWdlR2VuZXJhdGlvbk1ldGFkYXRhLnNoYXBlLnVzYWdlLFxuICB9KTtcbiAgbGV0IHBhcnNlZDogei5pbmZlcjx0eXBlb2Ygc2NoZW1hPjtcbiAgdHJ5IHtcbiAgICBwYXJzZWQgPSBzY2hlbWEucGFyc2UoSlNPTi5wYXJzZShCdWZmZXIuY29uY2F0KGNodW5rcykudG9TdHJpbmcoXCJ1dGY4XCIpKSk7XG4gIH0gY2F0Y2gge1xuICAgIHRocm93IG5ldyBPcGVuQUlQcm90b2NvbEVycm9yKFwiSW52YWxpZCBzdGFuZGFsb25lIGltYWdlIHJlc3BvbnNlXCIpO1xuICB9XG4gIHJldHVybiBwYXJzZWQuZGF0YS5tYXAoKGl0ZW0sIGluZGV4KSA9PiB7XG4gICAgY29uc3QgZGltZW5zaW9ucyA9IGluc3BlY3RUcmFuc3BhcmVudFBuZyhpdGVtLmI2NF9qc29uKTtcbiAgICByZXR1cm4ge1xuICAgICAgdHlwZTogXCJpbWFnZVwiLFxuICAgICAgYmFzZTY0OiBpdGVtLmI2NF9qc29uLFxuICAgICAgbWltZVR5cGU6IFwiaW1hZ2UvcG5nXCIsXG4gICAgICAuLi4oaXRlbS5yZXZpc2VkX3Byb21wdCA/IHsgcmV2aXNlZFByb21wdDogaXRlbS5yZXZpc2VkX3Byb21wdCB9IDoge30pLFxuICAgICAgZ2VuZXJhdGlvbjoge1xuICAgICAgICByb3V0ZTogXCJjb2RleC1pbWFnZXNcIixcbiAgICAgICAgbW9kZWw6IFwiZ3B0LWltYWdlLTJcIixcbiAgICAgICAgYmFja2dyb3VuZDogXCJ0cmFuc3BhcmVudFwiLFxuICAgICAgICBzaXplOiBgJHtkaW1lbnNpb25zLndpZHRofXgke2RpbWVuc2lvbnMuaGVpZ2h0fWAsXG4gICAgICAgIC4uLihwYXJzZWQucXVhbGl0eSA/IHsgcXVhbGl0eTogcGFyc2VkLnF1YWxpdHkgfSA6IHt9KSxcbiAgICAgICAgLi4uKGluZGV4ID09PSAwICYmIHBhcnNlZC51c2FnZSA/IHsgdXNhZ2U6IHBhcnNlZC51c2FnZSB9IDoge30pLFxuICAgICAgfSxcbiAgICB9O1xuICB9KTtcbn1cbiJdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFZQSxNQUFNLGdCQUFnQixLQUFLLE9BQU87QUFDbEMsTUFBTSxtQkFBbUIsT0FBTztBQUNoQyxNQUFNLHFCQUFxQixLQUFLLE9BQU87QUFDdkMsTUFBTSxnQkFBZ0IsT0FBTyxLQUFLO0NBQUM7Q0FBSztDQUFJO0NBQUk7Q0FBSTtDQUFJO0NBQUk7Q0FBSTtBQUFFLENBQUM7QUFFbkUsU0FBZ0IsNEJBQTRCLFNBQWlDO0NBQzNFLElBQUksUUFBUSxnQkFBZ0IsUUFBUSxPQUFPLFFBQ3pDLE1BQU0sSUFBSSxvQkFDUiwwRUFDRjtDQUVGLE1BQU0sV0FBVyxRQUFRO0NBQ3pCLElBQUksQ0FBQyxZQUFZLGFBQWEsUUFBUSxTQUFTLGVBQWUsZUFDNUQsTUFBTSxJQUFJLG9CQUNSLHNFQUNGO0NBRUYsSUFBSSxTQUFTLGlCQUFpQixTQUFTLGtCQUFrQixPQUN2RCxNQUFNLElBQUksb0JBQW9CLHVEQUF1RDtDQUV2RixNQUFNLE9BQWlCLENBQUM7Q0FDeEIsTUFBTSxTQUF1QyxDQUFDO0NBQzlDLElBQUksUUFBUSxjQUFjLEtBQUssS0FBSyx5QkFBeUIsUUFBUSxjQUFjO0NBQ25GLEtBQUssTUFBTSxRQUFRLFFBQVEsU0FBUztFQUNsQyxJQUFJLEtBQUssU0FBUyxhQUFhO0VBQy9CLElBQUksS0FBSyxTQUFTLDJCQUEyQixPQUFPLEtBQUssV0FBVyxVQUFVO0dBQzVFLE9BQU8sS0FBSyxFQUNWLFdBQVcsUUFBUSxjQUFjLEtBQUssUUFBUSxLQUFLLGFBQWEsRUFBRSxVQUFVLEtBQUssU0FDbkYsQ0FBQztHQUNEO0VBQ0Y7RUFDQSxJQUFJLEtBQUssU0FBUyxhQUFhLE9BQU8sS0FBSyxTQUFTLFVBQ2xELE1BQU0sSUFBSSxvQkFDUixnRUFDRjtFQUVGLElBQUksT0FB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
|
|
154
|
+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BlbmFpLWltYWdlLWdlbmVyYXRpb24uanMiLCJuYW1lcyI6W10sInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3V0aWxzL3Byb3ZpZGVycy9vcGVuYWkvb3BlbmFpLWltYWdlLWdlbmVyYXRpb24udHMiXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgUE5HIH0gZnJvbSBcInBuZ2pzXCI7XG5pbXBvcnQgeyB6IH0gZnJvbSBcInpvZFwiO1xuXG5pbXBvcnQgeyBJbWFnZUdlbmVyYXRpb25NZXRhZGF0YSB9IGZyb20gXCIuLi8uLi8uLi9hcHBsaWNhdGlvbi9xZ3JpZC9xZ3JpZC50eXBlc1wiO1xuaW1wb3J0IHtcbiAgQ0hBVEdQVF9DT0RFWF9SRVNQT05TRVNfVVJMLFxuICBpbWFnZU1pbWVUeXBlLFxuICBPcGVuQUlQcm90b2NvbEVycm9yLFxuICB0eXBlIE9wZW5BSU5vcm1hbGl6ZWRFdmVudCxcbiAgdHlwZSBPcGVuQUlSZXNwb25zZXNPcHRpb25zLFxufSBmcm9tIFwiLi9vcGVuYWktYmFja2VuZC1wcm90b2NvbFwiO1xuXG5jb25zdCBNQVhfUE5HX0JZVEVTID0gMzIgKiAxMDI0ICogMTAyNDtcbmNvbnN0IE1BWF9JTUFHRV9QSVhFTFMgPSA0MDk2ICogNDA5NjtcbmNvbnN0IE1BWF9SRVNQT05TRV9CWVRFUyA9IDY0ICogMTAyNCAqIDEwMjQ7XG5jb25zdCBQTkdfU0lHTkFUVVJFID0gQnVmZmVyLmZyb20oWzEzNywgODAsIDc4LCA3MSwgMTMsIDEwLCAyNiwgMTBdKTtcblxuZXhwb3J0IGZ1bmN0aW9uIGJ1aWxkU3RhbmRhbG9uZUltYWdlUmVxdWVzdChvcHRpb25zOiBPcGVuQUlSZXNwb25zZXNPcHRpb25zKSB7XG4gIGlmIChvcHRpb25zLm91dHB1dFNjaGVtYSB8fCBvcHRpb25zLnRvb2xzPy5sZW5ndGgpIHtcbiAgICB0aHJvdyBuZXcgT3BlbkFJUHJvdG9jb2xFcnJvcihcbiAgICAgIFwiU3RhbmRhbG9uZSBpbWFnZSBnZW5lcmF0aW9uIGRvZXMgbm90IHN1cHBvcnQgdG9vbHMgb3Igc3RydWN0dXJlZCBvdXRwdXRcIixcbiAgICApO1xuICB9XG4gIGlmICghb3B0aW9ucy5pbWFnZUdlbmVyYXRpb24pIHtcbiAgICB0aHJvdyBuZXcgT3BlbkFJUHJvdG9jb2xFcnJvcihcIlN0YW5kYWxvbmUgaW1hZ2VzIHJlcXVpcmUgaW1hZ2UgZ2VuZXJhdGlvbiB0byBiZSBlbmFibGVkXCIpO1xuICB9XG4gIGNvbnN0IGNvbnRyb2xzID0gdHlwZW9mIG9wdGlvbnMuaW1hZ2VHZW5lcmF0aW9uID09PSBcIm9iamVjdFwiID8gb3B0aW9ucy5pbWFnZUdlbmVyYXRpb24gOiB7fTtcbiAgaWYgKG9wdGlvbnMubW9kZWwgIT09IFwiZ3B0LWltYWdlLTJcIiAmJiBjb250cm9scy5iYWNrZ3JvdW5kICE9PSBcInRyYW5zcGFyZW50XCIpIHtcbiAgICB0aHJvdyBuZXcgT3BlbkFJUHJvdG9jb2xFcnJvcihcbiAgICAgIFwiU3RhbmRhbG9uZSBpbWFnZXMgcmVxdWlyZSBhbiBleHBsaWNpdCB0cmFuc3BhcmVudCBiYWNrZ3JvdW5kIHJlcXVlc3RcIixcbiAgICApO1xuICB9XG4gIGlmIChjb250cm9scy5vdXRwdXRfZm9ybWF0ICYmIGNvbnRyb2xzLm91dHB1dF9mb3JtYXQgIT09IFwicG5nXCIpIHtcbiAgICB0aHJvdyBuZXcgT3BlbkFJUHJvdG9jb2xFcnJvcihcIlN0YW5kYWxvbmUgaW1hZ2UgZ2VuZXJhdGlvbiBvbmx5IHN1cHBvcnRzIFBORyBvdXRwdXRcIik7XG4gIH1cbiAgY29uc3QgdGV4dDogc3RyaW5nW10gPSBbXTtcbiAgY29uc3QgaW1hZ2VzOiBBcnJheTx7IGltYWdlX3VybDogc3RyaW5nIH0+ID0gW107XG4gIGlmIChvcHRpb25zLmluc3RydWN0aW9ucykgdGV4dC5wdXNoKGBTeXN0ZW0gaW5zdHJ1Y3Rpb25zOlxcbiR7b3B0aW9ucy5pbnN0cnVjdGlvbnN9YCk7XG4gIGZvciAoY29uc3QgaXRlbSBvZiBvcHRpb25zLmhpc3RvcnkpIHtcbiAgICBpZiAoaXRlbS50eXBlID09PSBcInJlYXNvbmluZ1wiKSBjb250aW51ZTtcbiAgICBpZiAoaXRlbS50eXBlID09PSBcImltYWdlX2dlbmVyYXRpb25fY2FsbFwiICYmIHR5cGVvZiBpdGVtLnJlc3VsdCA9PT0gXCJzdHJpbmdcIikge1xuICAgICAgaW1hZ2VzLnB1c2goe1xuICAgICAgICBpbWFnZV91cmw6IGBkYXRhOiR7aW1hZ2VNaW1lVHlwZShpdGVtLnJlc3VsdCwgaXRlbS5vdXRwdXRfZm9ybWF0KX07YmFzZTY0LCR7aXRlbS5yZXN1bHR9YCxcbiAgICAgIH0pO1xuICAgICAgY29udGludWU7XG4gICAgfVxuICAgIGlmIChpdGVtLnR5cGUgIT09IFwibWVzc2FnZVwiICYmIHR5cGVvZiBpdGVtLnJvbGUgIT09IFwic3RyaW5nXCIpIHtcbiAgICAgIHRocm93IG5ldyBPcGVuQUlQcm90b2NvbEVycm9yKFxuICAgICAgICBcIlVuc3VwcG9ydGVkIGNvbnZlcnNhdGlvbiBpdGVtIGZvciBzdGFuZGFsb25lIGltYWdlIGdlbmVyYXRpb25cIixcbiAgICAgICk7XG4gICAgfVxuICAgIGlmICh0eXBlb2YgaXRlbS5jb250ZW50ID09PSBcInN0cmluZ1wiKSB7XG4gICAgICB0ZXh0LnB1c2goYCR7aXRlbS5yb2xlID8/IFwidXNlclwifTpcXG4ke2l0ZW0uY29udGVudH1gKTtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cbiAgICBpZiAoIUFycmF5LmlzQXJyYXkoaXRlbS5jb250ZW50KSkge1xuICAgICAgdGhyb3cgbmV3IE9wZW5BSVByb3RvY29sRXJyb3IoXCJVbnN1cHBvcnRlZCBtZXNzYWdlIGNvbnRlbnQgZm9yIHN0YW5kYWxvbmUgaW1hZ2UgZ2VuZXJhdGlvblwiKTtcbiAgICB9XG4gICAgZm9yIChjb25zdCBwYXJ0IG9mIGl0ZW0uY29udGVudCkge1xuICAgICAgaWYgKCFwYXJ0IHx8IHR5cGVvZiBwYXJ0ICE9PSBcIm9iamVjdFwiKSB7XG4gICAgICAgIHRocm93IG5ldyBPcGVuQUlQcm90b2NvbEVycm9yKFwiVW5zdXBwb3J0ZWQgbWVzc2FnZSBwYXJ0IGZvciBzdGFuZGFsb25lIGltYWdlIGdlbmVyYXRpb25cIik7XG4gICAgICB9XG4gICAgICBpZiAoXG4gICAgICAgIFtcImlucHV0X3RleHRcIiwgXCJvdXRwdXRfdGV4dFwiLCBcInRleHRcIl0uaW5jbHVkZXMocGFydC50eXBlKSAmJlxuICAgICAgICB0eXBlb2YgcGFydC50ZXh0ID09PSBcInN0cmluZ1wiXG4gICAgICApIHtcbiAgICAgICAgdGV4dC5wdXNoKGAke2l0ZW0ucm9sZSA/PyBcInVzZXJcIn06XFxuJHtwYXJ0LnRleHR9YCk7XG4gICAgICB9IGVsc2UgaWYgKHBhcnQudHlwZSA9PT0gXCJpbnB1dF9pbWFnZVwiIHx8IHBhcnQudHlwZSA9PT0gXCJpbWFnZV91cmxcIikge1xuICAgICAgICBjb25zdCB1cmwgPSB0eXBlb2YgcGFydC5pbWFnZV91cmwgPT09IFwic3RyaW5nXCIgPyBwYXJ0LmltYWdlX3VybCA6IHBhcnQuaW1hZ2VfdXJsPy51cmw7XG4gICAgICAgIGlmICh0eXBlb2YgdXJsICE9PSBcInN0cmluZ1wiIHx8ICEvXihodHRwczpcXC9cXC98ZGF0YTppbWFnZVxcLykvaS50ZXN0KHVybCkpIHtcbiAgICAgICAgICB0aHJvdyBuZXcgT3BlbkFJUHJvdG9jb2xFcnJvcihcIkltYWdlIHJlZmVyZW5jZXMgbXVzdCBiZSBIVFRQUyBVUkxzIG9yIGltYWdlIGRhdGEgVVJMc1wiKTtcbiAgICAgICAgfVxuICAgICAgICBpbWFnZXMucHVzaCh7IGltYWdlX3VybDogdXJsIH0pO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhyb3cgbmV3IE9wZW5BSVByb3RvY29sRXJyb3IoXCJVbnN1cHBvcnRlZCBtZXNzYWdlIHBhcnQgZm9yIHN0YW5kYWxvbmUgaW1hZ2UgZ2VuZXJhdGlvblwiKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgaWYgKGltYWdlcy5sZW5ndGggPiA1KVxuICAgIHRocm93IG5ldyBPcGVuQUlQcm90b2NvbEVycm9yKFwiU3RhbmRhbG9uZSBpbWFnZSBlZGl0aW5nIHN1cHBvcnRzIGF0IG1vc3QgNSByZWZlcmVuY2UgaW1hZ2VzXCIpO1xuICBpZiAoIXRleHQuc29tZSgocGFydCkgPT4gcGFydC50cmltKCkpKSB0aHJvdyBuZXcgT3BlbkFJUHJvdG9jb2xFcnJvcihcIkltYWdlIHByb21wdCBpcyBlbXB0eVwiKTtcbiAgcmV0dXJuIHtcbiAgICB1cmw6IENIQVRHUFRfQ09ERVhfUkVTUE9OU0VTX1VSTC5yZXBsYWNlKFxuICAgICAgL1xcL3Jlc3BvbnNlcyQvLFxuICAgICAgYC9pbWFnZXMvJHtpbWFnZXMubGVuZ3RoID8gXCJlZGl0c1wiIDogXCJnZW5lcmF0aW9uc1wifWAsXG4gICAgKSxcbiAgICBib2R5OiB7XG4gICAgICBtb2RlbDogXCJncHQtaW1hZ2UtMlwiLFxuICAgICAgcHJvbXB0OiB0ZXh0LmpvaW4oXCJcXG5cXG5cIiksXG4gICAgICAuLi4oY29udHJvbHMuYmFja2dyb3VuZCA/IHsgYmFja2dyb3VuZDogY29udHJvbHMuYmFja2dyb3VuZCB9IDoge30pLFxuICAgICAgLi4uKGNvbnRyb2xzLnF1YWxpdHkgPyB7IHF1YWxpdHk6IGNvbnRyb2xzLnF1YWxpdHkgfSA6IHt9KSxcbiAgICAgIC4uLihjb250cm9scy5zaXplID8geyBzaXplOiBjb250cm9scy5zaXplIH0gOiB7fSksXG4gICAgICAuLi4oaW1hZ2VzLmxlbmd0aCA/IHsgaW1hZ2VzIH0gOiB7fSksXG4gICAgfSxcbiAgfTtcbn1cblxuZnVuY3Rpb24gaW5zcGVjdEdlbmVyYXRlZFBuZyhcbiAgYmFzZTY0OiBzdHJpbmcsXG4gIHJlcXVpcmVUcmFuc3BhcmVuY3k6IGJvb2xlYW4sXG4pOiB7IHdpZHRoOiBudW1iZXI7IGhlaWdodDogbnVtYmVyIH0ge1xuICBpZiAoYmFzZTY0Lmxlbmd0aCA+IE1hdGguY2VpbChNQVhfUE5HX0JZVEVTIC8gMykgKiA0IHx8ICEvXltBLVphLXowLTkrL10rPXswLDJ9JC8udGVzdChiYXNlNjQpKSB7XG4gICAgdGhyb3cgbmV3IE9wZW5BSVByb3RvY29sRXJyb3IoXCJJbnZhbGlkIG9yIG92ZXJzaXplZCBnZW5lcmF0ZWQgUE5HXCIpO1xuICB9XG4gIGNvbnN0IGJ5dGVzID0gQnVmZmVyLmZyb20oYmFzZTY0LCBcImJhc2U2NFwiKTtcbiAgaWYgKFxuICAgIGJ5dGVzLmxlbmd0aCA8IDMzIHx8XG4gICAgIWJ5dGVzLnN1YmFycmF5KDAsIDgpLmVxdWFscyhQTkdfU0lHTkFUVVJFKSB8fFxuICAgIGJ5dGVzLnRvU3RyaW5nKFwiYXNjaWlcIiwgMTIsIDE2KSAhPT0gXCJJSERSXCJcbiAgKSB7XG4gICAgdGhyb3cgbmV3IE9wZW5BSVByb3RvY29sRXJyb3IoXCJJbWFnZSBnZW5lcmF0aW9uIGRpZCBub3QgcmV0dXJuIGEgUE5HXCIpO1xuICB9XG4gIGNvbnN0IHdpZHRoID0gYnl0ZXMucmVhZFVJbnQzMkJFKDE2KTtcbiAgY29uc3QgaGVpZ2h0ID0gYnl0ZXMucmVhZFVJbnQzMkJFKDIwKTtcbiAgaWYgKCF3aWR0aCB8fCAhaGVpZ2h0IHx8IHdpZHRoICogaGVpZ2h0ID4gTUFYX0lNQUdFX1BJWEVMUykge1xuICAgIHRocm93IG5ldyBPcGVuQUlQcm90b2NvbEVycm9yKFwiR2VuZXJhdGVkIFBORyBkaW1lbnNpb25zIGV4Y2VlZCB0aGUgZGVjb2RpbmcgbGltaXRcIik7XG4gIH1cbiAgbGV0IGltYWdlOiBQTkc7XG4gIHRyeSB7XG4gICAgaW1hZ2UgPSBQTkcuc3luYy5yZWFkKGJ5dGVzKTtcbiAgfSBjYXRjaCB7XG4gICAgdGhyb3cgbmV3IE9wZW5BSVByb3RvY29sRXJyb3IoXCJJbWFnZSBnZW5lcmF0aW9uIHJldHVybmVkIGFuIGludmFsaWQgUE5HXCIpO1xuICB9XG4gIGlmICghcmVxdWlyZVRyYW5zcGFyZW5jeSkgcmV0dXJuIHsgd2lkdGg6IGltYWdlLndpZHRoLCBoZWlnaHQ6IGltYWdlLmhlaWdodCB9O1xuICAvLyBBbiBhbHBoYSBjaGFubmVsIGFsb25lIGNhbiBzdGlsbCBiZSBmdWxseSBvcGFxdWUgb3IgaGF2ZSBubyB2aXNpYmxlIGNvbnRlbnQuXG4gIGxldCB0cmFuc3BhcmVudCA9IGZhbHNlO1xuICBsZXQgdmlzaWJsZSA9IGZhbHNlO1xuICBmb3IgKGxldCBpID0gMzsgaSA8IGltYWdlLmRhdGEubGVuZ3RoOyBpICs9IDQpIHtcbiAgICB0cmFuc3BhcmVudCB8fD0gaW1hZ2UuZGF0YVtpXSA9PT0gMDtcbiAgICB2aXNpYmxlIHx8PSBpbWFnZS5kYXRhW2ldISA+IDA7XG4gICAgaWYgKHRyYW5zcGFyZW50ICYmIHZpc2libGUpIHJldHVybiB7IHdpZHRoOiBpbWFnZS53aWR0aCwgaGVpZ2h0OiBpbWFnZS5oZWlnaHQgfTtcbiAgfVxuICB0aHJvdyBuZXcgT3BlbkFJUHJvdG9jb2xFcnJvcihcbiAgICBcIkltYWdlIGdlbmVyYXRpb24gZGlkIG5vdCByZXR1cm4gdmlzaWJsZSBjb250ZW50IG9uIGEgdHJhbnNwYXJlbnQgYmFja2dyb3VuZFwiLFxuICApO1xufVxuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcmVhZFN0YW5kYWxvbmVJbWFnZVJlc3BvbnNlKFxuICByZXNwb25zZTogUmVzcG9uc2UsXG4gIHJlcXVpcmVUcmFuc3BhcmVuY3k6IGJvb2xlYW4sXG4pOiBQcm9taXNlPE9wZW5BSU5vcm1hbGl6ZWRFdmVudFtdPiB7XG4gIGlmICghcmVzcG9uc2UuYm9keSkgdGhyb3cgbmV3IE9wZW5BSVByb3RvY29sRXJyb3IoXCJJbWFnZSByZXNwb25zZSBoYWQgbm8gYm9keVwiKTtcbiAgY29uc3QgcmVhZGVyID0gcmVzcG9uc2UuYm9keS5nZXRSZWFkZXIoKTtcbiAgY29uc3QgY2h1bmtzOiBVaW50OEFycmF5W10gPSBbXTtcbiAgbGV0IHNpemUgPSAwO1xuICB0cnkge1xuICAgIHdoaWxlICh0cnVlKSB7XG4gICAgICBjb25zdCB7IGRvbmUsIHZhbHVlIH0gPSBhd2FpdCByZWFkZXIucmVhZCgpO1xuICAgICAgaWYgKGRvbmUpIGJyZWFrO1xuICAgICAgc2l6ZSArPSB2YWx1ZS5ieXRlTGVuZ3RoO1xuICAgICAgaWYgKHNpemUgPiBNQVhfUkVTUE9OU0VfQllURVMpIHtcbiAgICAgICAgYXdhaXQgcmVhZGVyLmNhbmNlbCgpO1xuICAgICAgICB0aHJvdyBuZXcgT3BlbkFJUHJvdG9jb2xFcnJvcihcIkltYWdlIHJlc3BvbnNlIGV4Y2VlZGVkIHRoZSBzaXplIGxpbWl0XCIpO1xuICAgICAgfVxuICAgICAgY2h1bmtzLnB1c2godmFsdWUpO1xuICAgIH1cbiAgfSBmaW5hbGx5IHtcbiAgICByZWFkZXIucmVsZWFzZUxvY2soKTtcbiAgfVxuICBjb25zdCBzY2hlbWEgPSB6Lm9iamVjdCh7XG4gICAgZGF0YTogelxuICAgICAgLmFycmF5KHoub2JqZWN0KHsgYjY0X2pzb246IHouc3RyaW5nKCkubWluKDEpLCByZXZpc2VkX3Byb21wdDogei5zdHJpbmcoKS5udWxsaXNoKCkgfSkpXG4gICAgICAubWluKDEpXG4gICAgICAubWF4KDE2KSxcbiAgICBxdWFsaXR5OiB6LnN0cmluZygpLm9wdGlvbmFsKCksXG4gICAgbW9kZWw6IHouc3RyaW5nKCkub3B0aW9uYWwoKSxcbiAgICBiYWNrZ3JvdW5kOiB6LnN0cmluZygpLm9wdGlvbmFsKCksXG4gICAgdXNhZ2U6IEltYWdlR2VuZXJhdGlvbk1ldGFkYXRhLnNoYXBlLnVzYWdlLFxuICB9KTtcbiAgbGV0IHBhcnNlZDogei5pbmZlcjx0eXBlb2Ygc2NoZW1hPjtcbiAgdHJ5IHtcbiAgICBwYXJzZWQgPSBzY2hlbWEucGFyc2UoSlNPTi5wYXJzZShCdWZmZXIuY29uY2F0KGNodW5rcykudG9TdHJpbmcoXCJ1dGY4XCIpKSk7XG4gIH0gY2F0Y2gge1xuICAgIHRocm93IG5ldyBPcGVuQUlQcm90b2NvbEVycm9yKFwiSW52YWxpZCBzdGFuZGFsb25lIGltYWdlIHJlc3BvbnNlXCIpO1xuICB9XG4gIGNvbnN0IGJhY2tncm91bmQgPSBwYXJzZWQuYmFja2dyb3VuZCA/PyAocmVxdWlyZVRyYW5zcGFyZW5jeSA/IFwidHJhbnNwYXJlbnRcIiA6IHVuZGVmaW5lZCk7XG4gIHJldHVybiBwYXJzZWQuZGF0YS5tYXAoKGl0ZW0sIGluZGV4KSA9PiB7XG4gICAgY29uc3QgZGltZW5zaW9ucyA9IGluc3BlY3RHZW5lcmF0ZWRQbmcoaXRlbS5iNjRfanNvbiwgcmVxdWlyZVRyYW5zcGFyZW5jeSk7XG4gICAgcmV0dXJuIHtcbiAgICAgIHR5cGU6IFwiaW1hZ2VcIixcbiAgICAgIGJhc2U2NDogaXRlbS5iNjRfanNvbixcbiAgICAgIG1pbWVUeXBlOiBcImltYWdlL3BuZ1wiLFxuICAgICAgLi4uKGl0ZW0ucmV2aXNlZF9wcm9tcHQgPyB7IHJldmlzZWRQcm9tcHQ6IGl0ZW0ucmV2aXNlZF9wcm9tcHQgfSA6IHt9KSxcbiAgICAgIGdlbmVyYXRpb246IHtcbiAgICAgICAgcm91dGU6IFwiY29kZXgtaW1hZ2VzXCIsXG4gICAgICAgIG1vZGVsOiBwYXJzZWQubW9kZWwgPz8gXCJncHQtaW1hZ2UtMlwiLFxuICAgICAgICAuLi4oYmFja2dyb3VuZCA/IHsgYmFja2dyb3VuZCB9IDoge30pLFxuICAgICAgICBzaXplOiBgJHtkaW1lbnNpb25zLndpZHRofXgke2RpbWVuc2lvbnMuaGVpZ2h0fWAsXG4gICAgICAgIC4uLihwYXJzZWQucXVhbGl0eSA/IHsgcXVhbGl0eTogcGFyc2VkLnF1YWxpdHkgfSA6IHt9KSxcbiAgICAgICAgLi4uKGluZGV4ID09PSAwICYmIHBhcnNlZC51c2FnZSA/IHsgdXNhZ2U6IHBhcnNlZC51c2FnZSB9IDoge30pLFxuICAgICAgfSxcbiAgICB9O1xuICB9KTtcbn1cbiJdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFZQSxNQUFNLGdCQUFnQixLQUFLLE9BQU87QUFDbEMsTUFBTSxtQkFBbUIsT0FBTztBQUNoQyxNQUFNLHFCQUFxQixLQUFLLE9BQU87QUFDdkMsTUFBTSxnQkFBZ0IsT0FBTyxLQUFLO0NBQUM7Q0FBSztDQUFJO0NBQUk7Q0FBSTtDQUFJO0NBQUk7Q0FBSTtBQUFFLENBQUM7QUFFbkUsU0FBZ0IsNEJBQTRCLFNBQWlDO0NB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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TokenModel } from "../../../application/token/token.model.js";
|
|
2
|
-
import { OPENAI_CLIENT_ID, OPENAI_TOKEN_URL } from "./openai-constants.js";
|
|
3
2
|
import { deactivateAuthDeadToken } from "../../../application/qgrid/token-death.js";
|
|
3
|
+
import { OPENAI_CLIENT_ID, OPENAI_TOKEN_URL } from "./openai-constants.js";
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
5
5
|
//#region src/utils/providers/openai/openai-refresh.ts
|
|
6
6
|
/**
|
|
@@ -16,7 +16,7 @@ function result(content: QgridContent[]): QueryOutput {
|
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
describe("
|
|
19
|
+
describe("image cost estimates", () => {
|
|
20
20
|
it("accepts size hints without promising public API resolution limits", () => {
|
|
21
21
|
for (const size of ["auto", "1152x2560", "841x1870", "2160x3840"]) {
|
|
22
22
|
expect(QueryInput.parse({ prompt: "draw", imageGenerationOptions: { size } }).imageGenerationOptions?.size).toBe(size);
|
|
@@ -26,14 +26,15 @@ describe("standalone image cost estimates", () => {
|
|
|
26
26
|
}
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
it("
|
|
29
|
+
it("uses the default size price for custom/auto hints and prices hosted output without usage", () => {
|
|
30
30
|
const output = result([{ type: "image", data: "png" }]);
|
|
31
31
|
for (const size of ["auto", "1152x2560"] as const) {
|
|
32
|
-
expect(estimateImageGenerationCostMicroUsd(output, { size })).
|
|
32
|
+
expect(estimateImageGenerationCostMicroUsd(output, { size })).toBe(41_000);
|
|
33
|
+
expect(imageGenerationCostMethod({ size }, output)).toBe("assumed:gpt-image-2:medium:1536x1024:png");
|
|
33
34
|
}
|
|
34
35
|
expect(estimateImageGenerationCostMicroUsd(result([{ type: "image", data: "png", generation: {
|
|
35
36
|
route: "codex-responses", size: "841x1870", quality: "low",
|
|
36
|
-
} }]), options)).
|
|
37
|
+
} }]), options)).toBe(211_000);
|
|
37
38
|
});
|
|
38
39
|
it("accepts automatic image mode while preserving boolean requests", () => {
|
|
39
40
|
for (const imageGeneration of [true, false, "auto"] as const) {
|
|
@@ -41,9 +42,9 @@ describe("standalone image cost estimates", () => {
|
|
|
41
42
|
}
|
|
42
43
|
expect(QueryInput.safeParse({ prompt: "chat", imageGeneration: "required" }).success).toBe(false);
|
|
43
44
|
});
|
|
44
|
-
it("uses
|
|
45
|
+
it.each(["codex-images", "codex-responses"] as const)("uses %s usage once for multiple outputs instead of size or driver tokens", (route) => {
|
|
45
46
|
const output = result([
|
|
46
|
-
{ type: "image", data: "first", generation: { ...generation,
|
|
47
|
+
{ type: "image", data: "first", generation: { ...generation, route,
|
|
47
48
|
usage: { input_tokens: 100, output_tokens: 200, total_tokens: 300,
|
|
48
49
|
input_tokens_details: { text_tokens: 40, image_tokens: 60, cached_tokens: 50 } },
|
|
49
50
|
} },
|
|
@@ -55,8 +56,18 @@ describe("standalone image cost estimates", () => {
|
|
|
55
56
|
);
|
|
56
57
|
});
|
|
57
58
|
|
|
58
|
-
it("
|
|
59
|
-
|
|
59
|
+
it("falls back to per-image pricing when standalone usage is missing", () => {
|
|
60
|
+
const output = result([{ type: "image", data: "png", generation }]);
|
|
61
|
+
expect(estimateImageGenerationCostMicroUsd(output, options)).toBe(211_000);
|
|
62
|
+
expect(imageGenerationCostMethod(options, output)).toBe("assumed:gpt-image-2:high:1024x1024:png");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("respects reported zero usage instead of applying a per-image price", () => {
|
|
66
|
+
const output = result([{ type: "image", data: "png", generation: { ...generation,
|
|
67
|
+
usage: { input_tokens: 0, output_tokens: 0, total_tokens: 0 },
|
|
68
|
+
} }]);
|
|
69
|
+
expect(estimateImageGenerationCostMicroUsd(output, options)).toBe(0);
|
|
70
|
+
expect(imageGenerationCostMethod(options, output)).toBe("estimated:gpt-image-2:reported-usage:public-prices:conservative");
|
|
60
71
|
});
|
|
61
72
|
|
|
62
73
|
it("prices unknown input remainder at the image rate and ignores inconsistent splits", () => {
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type ImageGenerationOptions,
|
|
3
3
|
type ImageGenerationQuality,
|
|
4
|
-
type ImageGenerationSize,
|
|
5
4
|
type QueryOutput,
|
|
6
5
|
} from "./qgrid.types";
|
|
7
6
|
|
|
8
7
|
export const CODEX_IMAGE_GENERATION_MODEL = "gpt-image-2";
|
|
9
8
|
export const DEFAULT_IMAGE_GENERATION_QUALITY: ImageGenerationQuality = "medium";
|
|
10
|
-
export const DEFAULT_IMAGE_GENERATION_SIZE
|
|
9
|
+
export const DEFAULT_IMAGE_GENERATION_SIZE = "1536x1024";
|
|
10
|
+
|
|
11
|
+
type PricedImageSize = "1024x1024" | "1024x1536" | "1536x1024";
|
|
11
12
|
|
|
12
13
|
export type ResolvedImageGenerationOptions = {
|
|
13
14
|
quality: ImageGenerationQuality;
|
|
14
|
-
size:
|
|
15
|
+
size: PricedImageSize;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
// OpenAI gpt-image-2 image output cost estimates by quality/size:
|
|
18
19
|
// https://developers.openai.com/api/docs/guides/image-generation#calculating-costs
|
|
19
20
|
const IMAGE_OUTPUT_COST_MICRO_USD: Record<
|
|
20
21
|
ImageGenerationQuality,
|
|
21
|
-
|
|
22
|
+
Record<PricedImageSize, number>
|
|
22
23
|
> = {
|
|
23
24
|
low: {
|
|
24
25
|
"1024x1024": 6_000,
|
|
@@ -40,31 +41,27 @@ const IMAGE_OUTPUT_COST_MICRO_USD: Record<
|
|
|
40
41
|
export function resolveImageGenerationOptions(
|
|
41
42
|
options: ImageGenerationOptions | undefined,
|
|
42
43
|
): ResolvedImageGenerationOptions {
|
|
44
|
+
const size = options?.size;
|
|
43
45
|
return {
|
|
44
46
|
quality: options?.quality ?? DEFAULT_IMAGE_GENERATION_QUALITY,
|
|
45
|
-
|
|
47
|
+
// Unknown/custom sizes use the default price, without changing output hints.
|
|
48
|
+
size:
|
|
49
|
+
size === "1024x1024" || size === "1024x1536" || size === "1536x1024"
|
|
50
|
+
? size
|
|
51
|
+
: DEFAULT_IMAGE_GENERATION_SIZE,
|
|
46
52
|
};
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
export function imageGenerationCostMethod(
|
|
50
56
|
options: ImageGenerationOptions | undefined,
|
|
51
57
|
result?: QueryOutput,
|
|
52
|
-
): string
|
|
53
|
-
if (
|
|
54
|
-
result?.content.some(
|
|
55
|
-
(item) => item.type === "image" && item.generation?.route === "codex-responses",
|
|
56
|
-
)
|
|
57
|
-
)
|
|
58
|
-
return null;
|
|
58
|
+
): string {
|
|
59
59
|
if (
|
|
60
|
-
result?.content.some(
|
|
61
|
-
(item) => item.type === "image" && item.generation?.route === "codex-images",
|
|
62
|
-
)
|
|
60
|
+
result?.content.some((item) => item.type === "image" && item.generation?.usage !== undefined)
|
|
63
61
|
) {
|
|
64
62
|
return "estimated:gpt-image-2:reported-usage:public-prices:conservative";
|
|
65
63
|
}
|
|
66
64
|
const resolved = resolveImageGenerationOptions(options);
|
|
67
|
-
if (IMAGE_OUTPUT_COST_MICRO_USD[resolved.quality][resolved.size] === undefined) return null;
|
|
68
65
|
return `assumed:${CODEX_IMAGE_GENERATION_MODEL}:${resolved.quality}:${resolved.size}:png`;
|
|
69
66
|
}
|
|
70
67
|
|
|
@@ -75,14 +72,10 @@ export function estimateImageGenerationCostMicroUsd(
|
|
|
75
72
|
const images = result.content.filter((item) => item.type === "image");
|
|
76
73
|
const imageCount = images.length;
|
|
77
74
|
if (imageCount === 0) return null;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (images.some((image) => image.generation?.route === "codex-images")) {
|
|
82
|
-
// The standalone response's aggregate usage is carried by its first image only.
|
|
75
|
+
const usage = images.find((image) => image.generation?.usage)?.generation?.usage;
|
|
76
|
+
if (usage) {
|
|
77
|
+
// Response-level image usage is carried by its first image only.
|
|
83
78
|
// Do not multiply it by output count or price it as the requested text driver.
|
|
84
|
-
const usage = images.find((image) => image.generation?.usage)?.generation?.usage;
|
|
85
|
-
if (!usage) return null;
|
|
86
79
|
// Public GPT Image 2 rates verified 2026-09-08: text input $5/M, image input
|
|
87
80
|
// $8/M, output $30/M. Amounts below are microUSD, so no /1M conversion.
|
|
88
81
|
// https://developers.openai.com/api/docs/pricing#image-generation
|
|
@@ -99,5 +92,5 @@ export function estimateImageGenerationCostMicroUsd(
|
|
|
99
92
|
}
|
|
100
93
|
const resolved = resolveImageGenerationOptions(options);
|
|
101
94
|
const unitCost = IMAGE_OUTPUT_COST_MICRO_USD[resolved.quality][resolved.size];
|
|
102
|
-
return
|
|
95
|
+
return imageCount * unitCost;
|
|
103
96
|
}
|
|
@@ -19,14 +19,16 @@ it("logs observed standalone settings separately from requested controls without
|
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
describe("qgrid response log formatting", () => {
|
|
22
|
-
it("keeps custom requests and
|
|
22
|
+
it("keeps custom requests and observations separate from fallback price assumptions", () => {
|
|
23
23
|
const args = { prompt: "draw in 9:20", imageGenerationOptions: { size: "1152x2560" } };
|
|
24
24
|
const generation = { route: "codex-responses" as const, size: "841x1870", quality: "low" };
|
|
25
25
|
const observed = JSON.parse(imageGenerationToolArgs(args, { generation }));
|
|
26
26
|
expect(observed.requestedOptions.size).toBe("1152x2560");
|
|
27
27
|
expect(observed.observedGeneration).toEqual(generation);
|
|
28
28
|
expect(observed).not.toHaveProperty("pricingAssumption");
|
|
29
|
-
expect(JSON.parse(imageGenerationToolArgs(args))).
|
|
29
|
+
expect(JSON.parse(imageGenerationToolArgs(args)).pricingAssumption).toEqual({
|
|
30
|
+
model: "gpt-image-2", quality: "medium", size: "1536x1024",
|
|
31
|
+
});
|
|
30
32
|
});
|
|
31
33
|
it("logs the returned MIME and requested output format", () => {
|
|
32
34
|
expect(formatImagePartForLog({ type: "image", data: "webp", mediaType: "image/webp" })).toContain("data:image/webp;base64,webp");
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CODEX_IMAGE_GENERATION_MODEL,
|
|
3
|
-
imageGenerationCostMethod,
|
|
4
3
|
resolveImageGenerationOptions,
|
|
5
4
|
} from "./qgrid-image-generation";
|
|
6
5
|
import {
|
|
@@ -63,15 +62,13 @@ export function imageGenerationToolArgs(
|
|
|
63
62
|
...(args.imageGenerationOptions ? { requestedOptions: args.imageGenerationOptions } : {}),
|
|
64
63
|
...(options.generation
|
|
65
64
|
? { observedGeneration: options.generation }
|
|
66
|
-
:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
: {}),
|
|
65
|
+
: {
|
|
66
|
+
pricingAssumption: {
|
|
67
|
+
model: CODEX_IMAGE_GENERATION_MODEL,
|
|
68
|
+
quality: resolved.quality,
|
|
69
|
+
size: resolved.size,
|
|
70
|
+
},
|
|
71
|
+
}),
|
|
75
72
|
});
|
|
76
73
|
}
|
|
77
74
|
|
|
@@ -436,13 +436,15 @@ describe("qgrid run lifecycle TTFT", () => {
|
|
|
436
436
|
);
|
|
437
437
|
});
|
|
438
438
|
|
|
439
|
-
it("
|
|
439
|
+
it("stores the hosted per-image estimate when image usage is absent", async () => {
|
|
440
440
|
await afterQuery(
|
|
441
441
|
10,
|
|
442
442
|
2,
|
|
443
443
|
{ prompt: "draw", imageGeneration: true },
|
|
444
444
|
queryOutput({
|
|
445
|
-
content: [{ type: "image", data: "iVBORw0KGgoBAgM", revisedPrompt: "green triangle"
|
|
445
|
+
content: [{ type: "image", data: "iVBORw0KGgoBAgM", revisedPrompt: "green triangle",
|
|
446
|
+
generation: { route: "codex-responses", size: "841x1870" },
|
|
447
|
+
}],
|
|
446
448
|
}),
|
|
447
449
|
);
|
|
448
450
|
|
|
@@ -456,36 +458,67 @@ describe("qgrid run lifecycle TTFT", () => {
|
|
|
456
458
|
);
|
|
457
459
|
});
|
|
458
460
|
|
|
459
|
-
it(
|
|
461
|
+
it.each([
|
|
462
|
+
["openai/gpt-5.5", "transparent"],
|
|
463
|
+
["openai/gpt-image-2", "opaque"],
|
|
464
|
+
] as const)("keeps standalone %s accounting out of text usage and records image usage once", async (model, background) => {
|
|
460
465
|
const generation = {
|
|
461
466
|
route: "codex-images" as const, model: "gpt-image-2" as const,
|
|
462
|
-
size: "1254x1254", quality: "medium", background
|
|
467
|
+
size: "1254x1254", quality: "medium", background,
|
|
463
468
|
};
|
|
464
469
|
aggregateStepUsageMock.mockResolvedValueOnce({
|
|
465
470
|
input_tokens: 0, output_tokens: 0, cache_read_tokens: 0, cache_creation_tokens: 0,
|
|
466
471
|
duration_ms: 120, fallback_count: 0, cost_usd: 0, cost_source: "pricing_table",
|
|
467
472
|
});
|
|
468
473
|
await afterQuery(10, 0, {
|
|
469
|
-
prompt: "draw", model
|
|
470
|
-
imageGenerationOptions: { quality: "high", size: "1024x1024", background
|
|
474
|
+
prompt: "draw", model, imageGeneration: true,
|
|
475
|
+
imageGenerationOptions: { quality: "high", size: "1024x1024", background },
|
|
471
476
|
}, queryOutput({
|
|
472
|
-
model: "gpt-image-2",
|
|
477
|
+
model: "gpt-image-2", costUsd: 0,
|
|
473
478
|
usage: { input_tokens: 0, output_tokens: 0, reasoning_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 },
|
|
474
479
|
content: [
|
|
475
480
|
{ type: "image", data: "first", generation: { ...generation, usage: {
|
|
476
481
|
input_tokens: 10, output_tokens: 100, total_tokens: 110,
|
|
482
|
+
input_tokens_details: { text_tokens: 4, image_tokens: 6 },
|
|
477
483
|
} } },
|
|
478
484
|
{ type: "image", data: "second", generation },
|
|
479
485
|
],
|
|
480
486
|
}));
|
|
481
487
|
expect(appendStepMock).toHaveBeenCalledWith(10, expect.objectContaining({
|
|
482
|
-
type: "generate", model_name: "openai/gpt-image-2", requested_model_name:
|
|
488
|
+
type: "generate", model_name: "openai/gpt-image-2", requested_model_name: model,
|
|
483
489
|
input_tokens: 0, output_tokens: 0, cost_usd: 0,
|
|
484
490
|
}));
|
|
485
491
|
expect(finishRunMock).toHaveBeenCalledWith(10, expect.objectContaining({
|
|
486
|
-
input_tokens: 0, output_tokens: 0, cost_usd: 0, image_cost_usd:
|
|
492
|
+
input_tokens: 0, output_tokens: 0, cost_usd: 0, image_cost_usd: 3_068,
|
|
493
|
+
image_cost_method: "estimated:gpt-image-2:reported-usage:public-prices:conservative",
|
|
494
|
+
}));
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it("records auto chat driver cost and standalone image usage cost separately", async () => {
|
|
498
|
+
aggregateStepUsageMock.mockResolvedValueOnce({
|
|
499
|
+
input_tokens: 100, output_tokens: 20, cache_read_tokens: 0, cache_creation_tokens: 0,
|
|
500
|
+
duration_ms: 120, fallback_count: 0, cost_usd: 20, cost_source: "pricing_table",
|
|
501
|
+
});
|
|
502
|
+
await afterQuery(10, 0, {
|
|
503
|
+
prompt: "draw", model: "openai/gpt-6-luna", imageGeneration: "auto",
|
|
504
|
+
}, queryOutput({
|
|
505
|
+
model: "gpt-6-luna", costUsd: 0.00002,
|
|
506
|
+
usage: { input_tokens: 100, output_tokens: 20, reasoning_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 },
|
|
507
|
+
content: [{ type: "image", data: "image", generation: {
|
|
508
|
+
route: "codex-images", model: "gpt-image-2", background: "opaque",
|
|
509
|
+
usage: {
|
|
510
|
+
input_tokens: 10, output_tokens: 100, total_tokens: 110,
|
|
511
|
+
input_tokens_details: { text_tokens: 4, image_tokens: 6 },
|
|
512
|
+
},
|
|
513
|
+
} }],
|
|
514
|
+
}));
|
|
515
|
+
expect(finishRunMock).toHaveBeenCalledWith(10, expect.objectContaining({
|
|
516
|
+
model_name: "openai/gpt-6-luna", requested_model_name: "openai/gpt-6-luna",
|
|
517
|
+
input_tokens: 100, output_tokens: 20, cost_usd: 20, image_cost_usd: 3_068,
|
|
518
|
+
is_image_generation: true,
|
|
487
519
|
image_cost_method: "estimated:gpt-image-2:reported-usage:public-prices:conservative",
|
|
488
520
|
}));
|
|
521
|
+
expect(appendStepMock.mock.calls.filter(([, step]) => step.type === "generate")).toHaveLength(1);
|
|
489
522
|
});
|
|
490
523
|
|
|
491
524
|
it("uses requested image quality and size for cost estimate", async () => {
|
|
@@ -609,6 +642,7 @@ describe("qgrid run lifecycle TTFT", () => {
|
|
|
609
642
|
|
|
610
643
|
describe("qgrid run lifecycle terminal rollup", () => {
|
|
611
644
|
beforeEach(() => {
|
|
645
|
+
appendStepMock.mockReset().mockResolvedValue(1);
|
|
612
646
|
aggregateStepUsageMock.mockReset().mockResolvedValue({
|
|
613
647
|
input_tokens: 120,
|
|
614
648
|
output_tokens: 30,
|
|
@@ -624,6 +658,50 @@ describe("qgrid run lifecycle terminal rollup", () => {
|
|
|
624
658
|
finishRunMock.mockReset();
|
|
625
659
|
});
|
|
626
660
|
|
|
661
|
+
it.each(["error", "aborted"] as const)("records a completed chat stage once before %s rollup", async (status) => {
|
|
662
|
+
const args = { prompt: "draw", model: "openai/gpt-6-astra", imageGeneration: "auto" as const };
|
|
663
|
+
const result = queryOutput({ model: "gpt-6-luna", requestedModel: "gpt-6-astra" });
|
|
664
|
+
const completedStep = { stepIndex: 2, result };
|
|
665
|
+
if (status === "error") await finishRunWithError(10, "image failed", args, completedStep);
|
|
666
|
+
else await finishRunAborted(10, args, completedStep);
|
|
667
|
+
expect(appendStepMock).toHaveBeenCalledOnce();
|
|
668
|
+
expect(appendStepMock).toHaveBeenCalledWith(10, expect.objectContaining({
|
|
669
|
+
step_index: 2, type: "generate", model_name: "openai/gpt-6-luna",
|
|
670
|
+
requested_model_name: "openai/gpt-6-astra", input_tokens: 5, output_tokens: 7,
|
|
671
|
+
cost_usd: 1_000, cost_source: "pricing_table", ttft_ms: 39,
|
|
672
|
+
}));
|
|
673
|
+
expect(appendStepMock.mock.invocationCallOrder[0]).toBeLessThan(aggregateStepUsageMock.mock.invocationCallOrder[0]!);
|
|
674
|
+
expect(finishRunMock).toHaveBeenCalledOnce();
|
|
675
|
+
expect(finishRunMock).toHaveBeenCalledWith(10, expect.objectContaining({
|
|
676
|
+
status, error_message: status === "error" ? "image failed" : "client disconnected",
|
|
677
|
+
token_name: "tok-A", model_name: "openai/gpt-6-luna", requested_model_name: "openai/gpt-6-astra",
|
|
678
|
+
// Existing steps and the final completed driver are rolled up together.
|
|
679
|
+
input_tokens: 120, output_tokens: 30, cost_usd: 7_500, cost_source: "mixed",
|
|
680
|
+
}));
|
|
681
|
+
const finish = finishRunMock.mock.calls[0]![1];
|
|
682
|
+
expect(finish.image_cost_usd).toBeUndefined();
|
|
683
|
+
expect(finish.image_cost_method).toBeUndefined();
|
|
684
|
+
expect(finish.is_image_generation).toBeUndefined();
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
it("still attempts aggregation and terminal status when the completed driver step cannot be logged", async () => {
|
|
688
|
+
appendStepMock.mockRejectedValueOnce(new Error("step write failed"));
|
|
689
|
+
await expect(finishRunWithError(10, "image failed", {
|
|
690
|
+
prompt: "draw", model: "openai/gpt-6-luna",
|
|
691
|
+
}, { stepIndex: 0, result: queryOutput({ model: "gpt-6-luna" }) })).resolves.toBeUndefined();
|
|
692
|
+
expect(aggregateStepUsageMock).toHaveBeenCalledWith(10);
|
|
693
|
+
expect(finishRunMock).toHaveBeenCalledWith(10, expect.objectContaining({
|
|
694
|
+
status: "error", error_message: "image failed", model_name: "openai/gpt-6-luna", token_name: "tok-A",
|
|
695
|
+
}));
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
it("does not replace the original failure when terminal persistence also fails", async () => {
|
|
699
|
+
finishRunMock.mockRejectedValueOnce(new Error("terminal write failed"));
|
|
700
|
+
await expect(finishRunWithError(10, "image failed", { prompt: "draw" }, {
|
|
701
|
+
stepIndex: 0, result: queryOutput(),
|
|
702
|
+
})).resolves.toBeUndefined();
|
|
703
|
+
});
|
|
704
|
+
|
|
627
705
|
it("keeps incurred step usage and cost when a run ends in error", async () => {
|
|
628
706
|
await finishRunWithError(10, "provider failed", {
|
|
629
707
|
prompt: "continue",
|