@cartanova/qgrid-cli 2.10.5 → 2.10.7
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 +26 -7
- package/bundle/dist/application/qgrid/qgrid.dispatcher.js +12 -7
- 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 +5 -3
- package/bundle/dist/application/sonamu.generated.js +4 -1
- package/bundle/dist/application/sonamu.generated.sso.js +3 -1
- package/bundle/dist/i18n/sd.generated.js +4 -3
- package/bundle/dist/migrations/20261016155350_alter_request_logs_add1_alter1.js +15 -0
- 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/common/model-cost.js +14 -3
- 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 +105 -9
- package/bundle/src/application/qgrid/qgrid-run-lifecycle.ts +47 -5
- package/bundle/src/application/qgrid/qgrid.dispatcher.test.ts +160 -2
- package/bundle/src/application/qgrid/qgrid.dispatcher.ts +40 -18
- package/bundle/src/application/qgrid/qgrid.frame.test.ts +74 -0
- package/bundle/src/application/qgrid/qgrid.frame.ts +39 -4
- package/bundle/src/application/request-log/request-log.entity.json +11 -2
- package/bundle/src/application/request-log/request-log.model.ts +4 -0
- package/bundle/src/application/sonamu.generated.http +2 -2
- package/bundle/src/application/sonamu.generated.sso.ts +2 -0
- package/bundle/src/application/sonamu.generated.ts +4 -0
- package/bundle/src/i18n/sd.generated.ts +3 -2
- package/bundle/src/migrations/20261016155350_alter_request_logs_add1_alter1.ts +15 -0
- package/bundle/src/utils/providers/common/model-cost.test.ts +28 -0
- package/bundle/src/utils/providers/common/model-cost.ts +19 -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-Bw2s88Ot.css +1 -0
- package/bundle/web-dist/client/assets/{index-Dxw7DiXF.js → index-CwqqPF4F.js} +4 -4
- package/bundle/web-dist/client/assets/logs-CZgl1RSu.js +1 -0
- package/bundle/web-dist/client/assets/{monit-E8eGW1Sj.js → monit-TarvUWiE.js} +1 -1
- package/bundle/web-dist/client/assets/request-model-CojtinqK.js +1 -0
- package/bundle/web-dist/client/assets/{routes-7spLfIGZ.js → routes-BXR6OLah.js} +2 -2
- package/bundle/web-dist/client/assets/{services.generated-jnPHYMER.js → services.generated-BsieU37H.js} +1 -1
- package/bundle/web-dist/client/assets/{settings-BdosZFNC.js → settings-B1y5MNSU.js} +1 -1
- package/bundle/web-dist/client/assets/show-CG-BAdhS.js +2 -0
- package/bundle/web-dist/client/assets/{tokens-CBdHb2_U.js → tokens-D9tAWBEW.js} +1 -1
- package/bundle/web-dist/client/assets/{use-oauth-login-flow-Cyn3YPqW.js → use-oauth-login-flow-DxdOhz4a.js} +1 -1
- package/bundle/web-dist/client/assets/{use-update-token-mutation-DQCwCYfR.js → use-update-token-mutation-DAq0F8p-.js} +1 -1
- package/bundle/web-dist/client/index.html +3 -3
- 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-Dtd0NR4r.js} +2 -2
- package/bundle/web-dist/server/assets/{logs-CaaWO9-Z.js → logs-l91avYif.js} +33 -21
- package/bundle/web-dist/server/assets/{monit-CBil92fJ.js → monit-DvzribSE.js} +4 -4
- package/bundle/web-dist/server/assets/{plus-Bvdj8Pwx.js → plus-KZrECewT.js} +1 -1
- package/bundle/web-dist/server/assets/{react-dom-CyuShs9K.js → react-dom-B4a6-uuR.js} +1 -1
- package/bundle/web-dist/server/assets/{refresh-cw-3QAiSWBd.js → refresh-cw-BND93JrF.js} +1 -1
- package/bundle/web-dist/server/assets/{request-model-CnCeaNLE.js → request-model-CbqfnQOJ.js} +19 -1
- package/bundle/web-dist/server/assets/{routes-DtVf9yqo.js → routes-BAPWTizT.js} +6 -7
- package/bundle/web-dist/server/assets/{services.generated-FPwsG9p3.js → services.generated-CEbGjsI0.js} +3 -2
- package/bundle/web-dist/server/assets/{settings-DeHNwoMv.js → settings-BZR5HSAL.js} +3 -3
- package/bundle/web-dist/server/assets/{show-8NNdRxhM.js → show-CxVZTAdv.js} +17 -21
- package/bundle/web-dist/server/assets/{tokens-odgS2-9q.js → tokens-D1hb4eKk.js} +4 -4
- package/bundle/web-dist/server/assets/{use-oauth-login-flow-DTQKdYBO.js → use-oauth-login-flow-Cvdy0zF8.js} +1 -1
- package/bundle/web-dist/server/assets/{use-update-token-mutation-xvXbPrCI.js → use-update-token-mutation-CfYmk_4_.js} +1 -1
- package/bundle/web-dist/server/entry-server.generated.js +13 -10
- package/package.json +1 -1
- package/skills/qgrid/SKILL.md +9 -8
- package/skills/qgrid/references/ai-sdk-provider-contract.md +27 -17
- package/skills/qgrid/references/decision-rationale.md +8 -8
- package/skills/qgrid/references/openai-codex-runtime.md +19 -11
- package/skills/qgrid/references/prompt-cache-and-usage.md +3 -0
- package/skills/qgrid/references/request-log-run-lifecycle.md +14 -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/index-CvG4UB9v.css +0 -1
- package/bundle/web-dist/client/assets/logs-3n_U4qnI.js +0 -1
- package/bundle/web-dist/client/assets/request-model-D4CZPmnG.js +0 -1
- package/bundle/web-dist/client/assets/show-b2PFNI3D.js +0 -2
|
@@ -43,6 +43,7 @@ export const requestLogSubsetQueries = {
|
|
|
43
43
|
image_cost_usd: "request_logs.image_cost_usd",
|
|
44
44
|
image_cost_method: "request_logs.image_cost_method",
|
|
45
45
|
effort: "request_logs.effort",
|
|
46
|
+
service_tier: "request_logs.service_tier",
|
|
46
47
|
history: "request_logs.history",
|
|
47
48
|
tools: "request_logs.tools",
|
|
48
49
|
status: "request_logs.status",
|
|
@@ -75,6 +76,7 @@ export const requestLogSubsetQueries = {
|
|
|
75
76
|
image_cost_usd: "request_logs.image_cost_usd",
|
|
76
77
|
image_cost_method: "request_logs.image_cost_method",
|
|
77
78
|
effort: "request_logs.effort",
|
|
79
|
+
service_tier: "request_logs.service_tier",
|
|
78
80
|
status: "request_logs.status",
|
|
79
81
|
response_json_ok: "request_logs.response_json_ok",
|
|
80
82
|
tool_call_count: "request_logs.tool_call_count",
|
|
@@ -167,6 +167,7 @@ export const RequestLogBaseSchema = z.object({
|
|
|
167
167
|
cost_source: z.string().max(20).nullable(),
|
|
168
168
|
image_cost_usd: z.int().nullable(),
|
|
169
169
|
image_cost_method: z.string().max(100).nullable(),
|
|
170
|
+
service_tier: z.string().max(20).nullable(),
|
|
170
171
|
effort: z.string().max(10).nullable(),
|
|
171
172
|
history: HistoryItems.nullable(),
|
|
172
173
|
tools: ToolDefinitions.nullable(),
|
|
@@ -201,6 +202,7 @@ export type RequestLogBaseSchema = z.infer<typeof RequestLogBaseSchema> & {
|
|
|
201
202
|
"cost_source",
|
|
202
203
|
"image_cost_usd",
|
|
203
204
|
"image_cost_method",
|
|
205
|
+
"service_tier",
|
|
204
206
|
"effort",
|
|
205
207
|
"history",
|
|
206
208
|
"tools",
|
|
@@ -405,6 +407,7 @@ export const RequestLogSubsetA = z.object({
|
|
|
405
407
|
image_cost_usd: z.int().nullable(),
|
|
406
408
|
image_cost_method: z.string().max(100).nullable(),
|
|
407
409
|
effort: z.string().max(10).nullable(),
|
|
410
|
+
service_tier: z.string().max(20).nullable(),
|
|
408
411
|
history: HistoryItems.nullable(),
|
|
409
412
|
tools: ToolDefinitions.nullable(),
|
|
410
413
|
status: RequestLogStatus,
|
|
@@ -436,6 +439,7 @@ export const RequestLogSubsetP = z.object({
|
|
|
436
439
|
image_cost_usd: z.int().nullable(),
|
|
437
440
|
image_cost_method: z.string().max(100).nullable(),
|
|
438
441
|
effort: z.string().max(10).nullable(),
|
|
442
|
+
service_tier: z.string().max(20).nullable(),
|
|
439
443
|
status: RequestLogStatus,
|
|
440
444
|
response_json_ok: z.boolean().nullable(),
|
|
441
445
|
tool_call_count: z.int(),
|
|
@@ -230,8 +230,9 @@ const entityLabels = {
|
|
|
230
230
|
"entity.RequestLog.ttft_ms": "first gen TTFT(ms)",
|
|
231
231
|
"entity.RequestLog.cost_usd": "API 비용",
|
|
232
232
|
"entity.RequestLog.cost_source": "비용 출처(provider/pricing_table/mixed)",
|
|
233
|
-
"entity.RequestLog.image_cost_usd": "이미지 생성
|
|
234
|
-
"entity.RequestLog.image_cost_method": "이미지 비용
|
|
233
|
+
"entity.RequestLog.image_cost_usd": "이미지 생성 비용",
|
|
234
|
+
"entity.RequestLog.image_cost_method": "이미지 비용 계산 방식",
|
|
235
|
+
"entity.RequestLog.service_tier": "Requested OpenAI service tier",
|
|
235
236
|
"entity.RequestLog.effort": "effort level",
|
|
236
237
|
"entity.RequestLog.history": "inject_items 히스토리",
|
|
237
238
|
"entity.RequestLog.tools": "요청에 장착된 tool 정의 목록",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Knex } from "knex";
|
|
2
|
+
|
|
3
|
+
export async function up(knex: Knex): Promise<void> {
|
|
4
|
+
await knex.schema.alterTable("request_logs", (table) => {
|
|
5
|
+
// add
|
|
6
|
+
table.string("service_tier", 20).nullable();
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function down(knex: Knex): Promise<void> {
|
|
11
|
+
await knex.schema.alterTable("request_logs", (table) => {
|
|
12
|
+
// rollback - add
|
|
13
|
+
table.dropColumns("service_tier");
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -3,6 +3,34 @@ import { describe, expect, it } from "vitest";
|
|
|
3
3
|
import { calculateCostUsd, getModelCosts } from "./model-cost";
|
|
4
4
|
|
|
5
5
|
describe("calculateCostUsd", () => {
|
|
6
|
+
it.each([
|
|
7
|
+
["gpt-6-astra", 2.6],
|
|
8
|
+
["gpt-6.1-sol", 0.5],
|
|
9
|
+
["gpt-6-sol", 0.52],
|
|
10
|
+
["gpt-6-luna", 0.026],
|
|
11
|
+
["gpt-5.6-sol", 1.04],
|
|
12
|
+
["gpt-5.6-terra", 0.56],
|
|
13
|
+
["gpt-5.6-luna", 0.056],
|
|
14
|
+
])("prices %s fast input, cached input, cache writes and output", (model, expected) => {
|
|
15
|
+
expect(calculateCostUsd(`openai/${model}`, {
|
|
16
|
+
inputTokens: 160_000, cachedInputTokens: 100_000,
|
|
17
|
+
cacheCreationInputTokens: 40_000, outputTokens: 10_000,
|
|
18
|
+
}, "fast")).toBeCloseTo(expected, 10);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("combines Fast API rates with long-context rates, not subscription usage multipliers", () => {
|
|
22
|
+
expect(calculateCostUsd("openai/gpt-6.1-sol", {
|
|
23
|
+
inputTokens: 300_000, cachedInputTokens: 200_000, outputTokens: 10_000,
|
|
24
|
+
}, "fast")).toBeCloseTo(1.18, 10);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("keeps standard pricing for omitted/flex tiers and Anthropic", () => {
|
|
28
|
+
const usage = { inputTokens: 1_000, outputTokens: 1_000 };
|
|
29
|
+
expect(calculateCostUsd("gpt-6.1-sol", usage)).toBeCloseTo(0.012, 10);
|
|
30
|
+
expect(calculateCostUsd("gpt-6.1-sol", usage, "flex")).toBeCloseTo(0.012, 10);
|
|
31
|
+
expect(calculateCostUsd("claude-sonnet-5-5", usage, "fast")).toBeCloseTo(0.012, 10);
|
|
32
|
+
});
|
|
33
|
+
|
|
6
34
|
it.each([
|
|
7
35
|
[272_000, 2.77],
|
|
8
36
|
[272_001, 5.51502],
|
|
@@ -237,6 +237,19 @@ export function getModelCosts(model: string): ModelCosts {
|
|
|
237
237
|
return OPENAI_COSTS[normalizedModel] ?? ANTHROPIC_COSTS[normalizedModel] ?? DEFAULT_COSTS;
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
+
// Verified Fast API rates for the currently served Codex models (2026-10-16).
|
|
241
|
+
// Applies to all token categories and long context; not the 2.5x subscription quota multiplier.
|
|
242
|
+
// https://developers.openai.com/api/docs/pricing
|
|
243
|
+
const OPENAI_FAST_MULTIPLIERS: Record<string, number> = {
|
|
244
|
+
"gpt-6-astra": 2,
|
|
245
|
+
"gpt-6.1-sol": 2,
|
|
246
|
+
"gpt-6-sol": 2,
|
|
247
|
+
"gpt-6-luna": 2,
|
|
248
|
+
"gpt-5.6-sol": 2,
|
|
249
|
+
"gpt-5.6-terra": 2,
|
|
250
|
+
"gpt-5.6-luna": 2,
|
|
251
|
+
};
|
|
252
|
+
|
|
240
253
|
export function calculateCostUsd(
|
|
241
254
|
model: string,
|
|
242
255
|
usage: {
|
|
@@ -247,6 +260,7 @@ export function calculateCostUsd(
|
|
|
247
260
|
cacheCreationInputTokens5m?: number;
|
|
248
261
|
cacheCreationInputTokens1h?: number;
|
|
249
262
|
},
|
|
263
|
+
serviceTier?: string,
|
|
250
264
|
): number {
|
|
251
265
|
const costs = getModelCosts(model);
|
|
252
266
|
const cachedInput = usage.cachedInputTokens ?? 0;
|
|
@@ -281,12 +295,15 @@ export function calculateCostUsd(
|
|
|
281
295
|
(isLongContext ? lc.inputMultiplier : 1);
|
|
282
296
|
const outputRate = costs.outputTokens * (isLongContext ? lc.outputMultiplier : 1);
|
|
283
297
|
|
|
284
|
-
|
|
298
|
+
const standardCost =
|
|
285
299
|
(nonCachedInput / 1_000_000) * inputRate +
|
|
286
300
|
(usage.outputTokens / 1_000_000) * outputRate +
|
|
287
301
|
(cachedInput / 1_000_000) * cachedRate +
|
|
288
302
|
(unclassifiedCacheCreationInput / 1_000_000) * cacheCreationFallbackRate +
|
|
289
303
|
(classifiedCacheCreationInput5m / 1_000_000) * cacheCreation5mRate +
|
|
290
|
-
(classifiedCacheCreationInput1h / 1_000_000) * cacheCreation1hRate
|
|
304
|
+
(classifiedCacheCreationInput1h / 1_000_000) * cacheCreation1hRate;
|
|
305
|
+
const normalizedModel = model.split("/").pop() ?? model;
|
|
306
|
+
return (
|
|
307
|
+
standardCost * (serviceTier === "fast" ? (OPENAI_FAST_MULTIPLIERS[normalizedModel] ?? 1) : 1)
|
|
291
308
|
);
|
|
292
309
|
}
|
|
@@ -27,7 +27,7 @@ describe("OpenAI Codex backend protocol", () => {
|
|
|
27
27
|
} })).toMatchObject({ generation: { route: "codex-responses", size: "841x1870" } });
|
|
28
28
|
});
|
|
29
29
|
it("distinguishes partial function items from completed arguments", () => {
|
|
30
|
-
const item = { type: "function_call", call_id: "call", name: "
|
|
30
|
+
const item = { type: "function_call", call_id: "call", name: "generate_image", arguments: "{}" };
|
|
31
31
|
expect(normalizeOpenAIEvent({ type: "response.output_item.added", item })).toEqual({ type: "output-item", item, completed: false });
|
|
32
32
|
expect(normalizeOpenAIEvent({ type: "response.output_item.done", item })).toEqual({ type: "output-item", item, completed: true });
|
|
33
33
|
});
|
|
@@ -149,4 +149,26 @@ describe("OpenAI Codex backend protocol", () => {
|
|
|
149
149
|
}),
|
|
150
150
|
).toEqual({ type: "image", id: "img", base64: "aGVsbG8=", mimeType: "image/png", revisedPrompt: "better", generation: { route: "codex-responses" } });
|
|
151
151
|
});
|
|
152
|
+
|
|
153
|
+
it("preserves zero image-tool usage without treating it as missing", () => {
|
|
154
|
+
const usage = { input_tokens: 0, output_tokens: 0, total_tokens: 0 };
|
|
155
|
+
expect(normalizeOpenAIEvent({ type: "response.completed", response: {
|
|
156
|
+
id: "r", tool_usage: { image_gen: usage },
|
|
157
|
+
} })).toEqual({
|
|
158
|
+
type: "completed", responseId: "r",
|
|
159
|
+
imageGeneration: { route: "codex-responses", usage },
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it.each([undefined, null, {}, { input_tokens: "4", output_tokens: 1, total_tokens: 5 }, { input_tokens: -1, output_tokens: 1, total_tokens: 0 }])(
|
|
164
|
+
"ignores missing or malformed image-tool usage: %j", (usage) => {
|
|
165
|
+
expect(normalizeOpenAIEvent({ type: "response.completed", response: {
|
|
166
|
+
id: "r", tool_usage: { image_gen: usage },
|
|
167
|
+
tools: [{ type: "image_generation", model: "gpt-image-2-codex", size: "auto", quality: "auto" }],
|
|
168
|
+
} })).toEqual({
|
|
169
|
+
type: "completed", responseId: "r",
|
|
170
|
+
imageGeneration: { route: "codex-responses", model: "gpt-image-2-codex" },
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
);
|
|
152
174
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { ImageGenerationMetadata } from "../../../application/qgrid/qgrid.types";
|
|
4
4
|
import { type OpenAIEffort } from "../common/effort";
|
|
5
5
|
|
|
6
6
|
export const CHATGPT_CODEX_RESPONSES_URL =
|
|
@@ -180,7 +180,13 @@ export type OpenAINormalizedEvent =
|
|
|
180
180
|
generation?: ImageGenerationMetadata;
|
|
181
181
|
}
|
|
182
182
|
| { type: "output-item"; item: OpenAIResponseItem; completed?: boolean }
|
|
183
|
-
| {
|
|
183
|
+
| {
|
|
184
|
+
type: "completed";
|
|
185
|
+
responseId: string;
|
|
186
|
+
usage?: OpenAIUsage;
|
|
187
|
+
model?: string;
|
|
188
|
+
imageGeneration?: ImageGenerationMetadata;
|
|
189
|
+
}
|
|
184
190
|
| { type: "error"; error: OpenAIProtocolError };
|
|
185
191
|
|
|
186
192
|
export class OpenAIProtocolError extends Error {
|
|
@@ -286,11 +292,27 @@ export function normalizeOpenAIEvent(raw: unknown): OpenAINormalizedEvent | unde
|
|
|
286
292
|
}
|
|
287
293
|
if (type === "response.completed") {
|
|
288
294
|
const response = (event.response ?? {}) as Record<string, unknown>;
|
|
295
|
+
const toolUsage = response.tool_usage as Record<string, unknown> | undefined;
|
|
296
|
+
const parsedImageUsage = ImageGenerationMetadata.shape.usage.safeParse(toolUsage?.image_gen);
|
|
297
|
+
const imageUsage = parsedImageUsage.success ? parsedImageUsage.data : undefined;
|
|
298
|
+
const imageTool = Array.isArray(response.tools)
|
|
299
|
+
? response.tools.find((tool) => tool?.type === "image_generation")
|
|
300
|
+
: undefined;
|
|
301
|
+
const imageModel = typeof imageTool?.model === "string" ? imageTool.model : undefined;
|
|
289
302
|
return {
|
|
290
303
|
type: "completed",
|
|
291
304
|
responseId: String(response.id ?? ""),
|
|
292
305
|
...(response.usage ? { usage: usageFrom(response.usage) } : {}),
|
|
293
306
|
...(typeof response.model === "string" ? { model: response.model } : {}),
|
|
307
|
+
...(imageUsage || imageModel
|
|
308
|
+
? {
|
|
309
|
+
imageGeneration: {
|
|
310
|
+
route: "codex-responses" as const,
|
|
311
|
+
...(imageModel ? { model: imageModel } : {}),
|
|
312
|
+
...(imageUsage ? { usage: imageUsage } : {}),
|
|
313
|
+
},
|
|
314
|
+
}
|
|
315
|
+
: {}),
|
|
294
316
|
};
|
|
295
317
|
}
|
|
296
318
|
if (type === "error" || type === "response.failed" || type === "response.incomplete") {
|
|
@@ -29,6 +29,21 @@ async function collect<T>(iterable: AsyncIterable<T>): Promise<T[]> {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
describe("OpenAI direct HTTPS client", () => {
|
|
32
|
+
it.each([true, { background: "opaque" }])("keeps chat-model images on Responses (%j)", async (imageGeneration) => {
|
|
33
|
+
const fetchMock = vi.fn<typeof fetch>(async () =>
|
|
34
|
+
sse({ type: "response.completed", response: { id: "hosted-image" } }),
|
|
35
|
+
);
|
|
36
|
+
const client = new OpenAIDirectClient({
|
|
37
|
+
credentials: { accessToken: "access", accountId: "acct" }, fetch: fetchMock,
|
|
38
|
+
});
|
|
39
|
+
await collect(client.responses({ model: "gpt-6-luna", history: [], imageGeneration }));
|
|
40
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
41
|
+
expect(fetchMock.mock.calls[0]?.[0]).toBe(CHATGPT_CODEX_RESPONSES_URL);
|
|
42
|
+
expect(JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body))).toMatchObject({
|
|
43
|
+
model: "gpt-6-luna", tools: [expect.objectContaining({ type: "image_generation" })],
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
32
47
|
it("posts the pinned body and Codex identity without live calls", async () => {
|
|
33
48
|
const fetchMock = vi.fn<typeof fetch>(async () =>
|
|
34
49
|
sse(
|
|
@@ -450,8 +450,9 @@ export class OpenAIDirectClient {
|
|
|
450
450
|
|
|
451
451
|
responses(options: OpenAIResponsesOptions, signal?: AbortSignal): OpenAIEventStream {
|
|
452
452
|
if (
|
|
453
|
-
|
|
454
|
-
options.imageGeneration
|
|
453
|
+
options.model === "gpt-image-2" ||
|
|
454
|
+
(typeof options.imageGeneration === "object" &&
|
|
455
|
+
options.imageGeneration.background === "transparent")
|
|
455
456
|
) {
|
|
456
457
|
return this.standaloneImages(options, signal);
|
|
457
458
|
}
|
|
@@ -498,10 +499,19 @@ export class OpenAIDirectClient {
|
|
|
498
499
|
continue;
|
|
499
500
|
}
|
|
500
501
|
if (!response.ok) throw await responseError(response);
|
|
501
|
-
const images = await readStandaloneImageResponse(
|
|
502
|
+
const images = await readStandaloneImageResponse(
|
|
503
|
+
response,
|
|
504
|
+
typeof options.imageGeneration === "object" &&
|
|
505
|
+
options.imageGeneration.background === "transparent",
|
|
506
|
+
);
|
|
502
507
|
signal?.throwIfAborted();
|
|
503
508
|
yield* images;
|
|
504
|
-
|
|
509
|
+
const firstImage = images[0];
|
|
510
|
+
yield {
|
|
511
|
+
type: "completed",
|
|
512
|
+
responseId: requestId,
|
|
513
|
+
model: firstImage?.type === "image" ? firstImage.generation?.model : "gpt-image-2",
|
|
514
|
+
};
|
|
505
515
|
return;
|
|
506
516
|
}
|
|
507
517
|
}
|