@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
|
@@ -89,6 +89,7 @@ export async function beforeQuery(args: QueryInput): Promise<{
|
|
|
89
89
|
system_prompt: args.system,
|
|
90
90
|
requested_model_name: args.model,
|
|
91
91
|
effort: args.thinking === false ? undefined : args.effort,
|
|
92
|
+
service_tier: args.model?.startsWith("openai/") ? args.serviceTier : undefined,
|
|
92
93
|
project_name: args.projectName,
|
|
93
94
|
history: filterHistoryForStorage(args.history),
|
|
94
95
|
tools: args.tools?.length ? args.tools : undefined,
|
|
@@ -100,12 +101,12 @@ export async function beforeQuery(args: QueryInput): Promise<{
|
|
|
100
101
|
return { requestLogId, stepIndex };
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
|
|
104
|
+
async function appendGenerateStep(
|
|
104
105
|
requestLogId: number,
|
|
105
106
|
stepIndex: number,
|
|
106
107
|
args: QueryInput,
|
|
107
108
|
result: QueryOutput,
|
|
108
|
-
)
|
|
109
|
+
) {
|
|
109
110
|
// provider가 canonical model을 돌려줘도 요청 route 자체([1m] 등)는 보존한다.
|
|
110
111
|
const requestedModelName = withProviderPrefix(
|
|
111
112
|
args.model,
|
|
@@ -132,6 +133,21 @@ export async function afterQuery(
|
|
|
132
133
|
ttft_ms: result.ttftMs,
|
|
133
134
|
finish_reason: result.finishReason,
|
|
134
135
|
});
|
|
136
|
+
return { requestedModelName, servedModelName };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function afterQuery(
|
|
140
|
+
requestLogId: number,
|
|
141
|
+
stepIndex: number,
|
|
142
|
+
args: QueryInput,
|
|
143
|
+
result: QueryOutput,
|
|
144
|
+
): Promise<RunLifecycleResult> {
|
|
145
|
+
const { requestedModelName, servedModelName } = await appendGenerateStep(
|
|
146
|
+
requestLogId,
|
|
147
|
+
stepIndex,
|
|
148
|
+
args,
|
|
149
|
+
result,
|
|
150
|
+
);
|
|
135
151
|
|
|
136
152
|
const imageParts = getImageParts(result);
|
|
137
153
|
const imageCostMicroUsd = estimateImageGenerationCostMicroUsd(
|
|
@@ -182,6 +198,7 @@ export async function afterQuery(
|
|
|
182
198
|
requested_model_name: requestedModelName,
|
|
183
199
|
model_name: servedModelName,
|
|
184
200
|
fallback_count: agg.fallback_count,
|
|
201
|
+
service_tier: args.model?.startsWith("openai/") ? (args.serviceTier ?? null) : null,
|
|
185
202
|
cost_usd: agg.cost_usd,
|
|
186
203
|
cost_source: agg.cost_source,
|
|
187
204
|
image_cost_usd: imageCostMicroUsd,
|
|
@@ -200,12 +217,17 @@ export async function finishRunWithError(
|
|
|
200
217
|
requestLogId: number,
|
|
201
218
|
errorMessage: string,
|
|
202
219
|
args?: QueryInput,
|
|
220
|
+
completedStep?: { stepIndex: number; result: QueryOutput },
|
|
203
221
|
): Promise<void> {
|
|
204
|
-
await finishTerminalRun(requestLogId, "error", errorMessage, args);
|
|
222
|
+
await finishTerminalRun(requestLogId, "error", errorMessage, args, completedStep);
|
|
205
223
|
}
|
|
206
224
|
|
|
207
|
-
export async function finishRunAborted(
|
|
208
|
-
|
|
225
|
+
export async function finishRunAborted(
|
|
226
|
+
requestLogId: number,
|
|
227
|
+
args?: QueryInput,
|
|
228
|
+
completedStep?: { stepIndex: number; result: QueryOutput },
|
|
229
|
+
): Promise<void> {
|
|
230
|
+
await finishTerminalRun(requestLogId, "aborted", "client disconnected", args, completedStep);
|
|
209
231
|
}
|
|
210
232
|
|
|
211
233
|
async function finishTerminalRun(
|
|
@@ -213,8 +235,18 @@ async function finishTerminalRun(
|
|
|
213
235
|
status: "error" | "aborted",
|
|
214
236
|
errorMessage: string,
|
|
215
237
|
args?: QueryInput,
|
|
238
|
+
completedStep?: { stepIndex: number; result: QueryOutput },
|
|
216
239
|
): Promise<void> {
|
|
217
240
|
try {
|
|
241
|
+
if (completedStep && args) {
|
|
242
|
+
try {
|
|
243
|
+
await appendGenerateStep(requestLogId, completedStep.stepIndex, args, completedStep.result);
|
|
244
|
+
} catch (e) {
|
|
245
|
+
logger.error(
|
|
246
|
+
`appendGenerateStep failed while finishing ${status}: ${(e as Error).message}`,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
218
250
|
let aggregate: Awaited<ReturnType<typeof RequestLogModel.aggregateStepUsage>> | undefined;
|
|
219
251
|
try {
|
|
220
252
|
aggregate = await RequestLogModel.aggregateStepUsage(requestLogId);
|
|
@@ -226,6 +258,16 @@ async function finishTerminalRun(
|
|
|
226
258
|
status,
|
|
227
259
|
error_message: errorMessage,
|
|
228
260
|
history: args ? filterHistoryForStorage(args.history) : undefined,
|
|
261
|
+
...(completedStep
|
|
262
|
+
? {
|
|
263
|
+
token_name: completedStep.result.tokenName,
|
|
264
|
+
requested_model_name: withProviderPrefix(
|
|
265
|
+
args?.model,
|
|
266
|
+
args?.model ?? completedStep.result.requestedModel ?? completedStep.result.model,
|
|
267
|
+
),
|
|
268
|
+
model_name: withProviderPrefix(args?.model, completedStep.result.model),
|
|
269
|
+
}
|
|
270
|
+
: {}),
|
|
229
271
|
...(aggregate
|
|
230
272
|
? {
|
|
231
273
|
input_tokens: aggregate.input_tokens,
|
|
@@ -6,10 +6,12 @@ import {
|
|
|
6
6
|
type GenerateStreamCallbacks,
|
|
7
7
|
} from "../../utils/providers/common/provider-dispatcher";
|
|
8
8
|
import { systemHash } from "./conv-routing";
|
|
9
|
-
import { OpenAIDispatcher } from "../../utils/providers/openai/openai-dispatcher";
|
|
9
|
+
import { ImageGenerationError, OpenAIDispatcher } from "../../utils/providers/openai/openai-dispatcher";
|
|
10
10
|
import { normalizeOpenAIEvent } from "../../utils/providers/openai/openai-backend-protocol";
|
|
11
|
-
import { buildStrictOutputSchema, QgridDispatcherClass } from "./qgrid.dispatcher";
|
|
11
|
+
import { buildStrictOutputSchema, completedDriverFromImageFailure, QgridDispatcherClass } from "./qgrid.dispatcher";
|
|
12
12
|
import { QueryInput, type QueryOutput } from "./qgrid.types";
|
|
13
|
+
import { PNG } from "pngjs";
|
|
14
|
+
import { estimateImageGenerationCostMicroUsd } from "./qgrid-image-generation";
|
|
13
15
|
import { type TokenSubsetA } from "../sonamu.generated";
|
|
14
16
|
|
|
15
17
|
function providerResult(overrides: Partial<GenerateResult> = {}): GenerateResult {
|
|
@@ -36,6 +38,162 @@ function deeplyNestedOutputSchema(depth: number): string {
|
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
describe("QgridDispatcherClass", () => {
|
|
41
|
+
it.each([false, true])("applies Fast pricing to OpenAI query (stream=%s)", async (stream) => {
|
|
42
|
+
const dispatcher = new QgridDispatcherClass();
|
|
43
|
+
const result = providerResult({
|
|
44
|
+
model: "gpt-6.1-sol",
|
|
45
|
+
usage: { totalTokens: 2000, inputTokens: 1000, cachedInputTokens: 500, outputTokens: 1000, reasoningOutputTokens: 0 },
|
|
46
|
+
});
|
|
47
|
+
dispatcher.openaiDispatcher = {
|
|
48
|
+
generate: vi.fn(async () => result),
|
|
49
|
+
generateStream: vi.fn(async (_req, cb) => cb.onComplete(result)),
|
|
50
|
+
} as never;
|
|
51
|
+
const input = { prompt: "hi", model: "openai/gpt-6.1-sol", serviceTier: "fast" as const };
|
|
52
|
+
let output: QueryOutput | undefined;
|
|
53
|
+
if (stream) {
|
|
54
|
+
await dispatcher.queryStream(input, {
|
|
55
|
+
onDelta: () => {}, onComplete: (value) => { output = value; }, onError: (error) => { throw error; },
|
|
56
|
+
});
|
|
57
|
+
} else {
|
|
58
|
+
output = await dispatcher.query(input);
|
|
59
|
+
}
|
|
60
|
+
expect(output?.costUsd).toBeCloseTo(0.0221, 10);
|
|
61
|
+
expect(output?.costSource).toBe("pricing_table");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("prices only the completed chat driver from an image failure", () => {
|
|
65
|
+
const error = new ImageGenerationError("incomplete", "images unavailable");
|
|
66
|
+
error.completedDriver = providerResult({
|
|
67
|
+
model: "gpt-6-luna", requestedModel: "gpt-6-astra", ttftMs: 4,
|
|
68
|
+
usage: { totalTokens: 120, inputTokens: 100, outputTokens: 20, cachedInputTokens: 10, reasoningOutputTokens: 5 },
|
|
69
|
+
});
|
|
70
|
+
const result = completedDriverFromImageFailure(error)!;
|
|
71
|
+
expect(result).toMatchObject({
|
|
72
|
+
model: "gpt-6-luna", requestedModel: "gpt-6-astra", tokenName: "provider/test",
|
|
73
|
+
usage: { input_tokens: 100, output_tokens: 20, cache_read_input_tokens: 10, reasoning_tokens: 5 },
|
|
74
|
+
costSource: "pricing_table", finishReason: "stop", durationMs: 12, ttftMs: 4,
|
|
75
|
+
content: [{ type: "text", text: "ok" }],
|
|
76
|
+
});
|
|
77
|
+
expect(result.costUsd).toBeCloseTo(0.0000191, 10);
|
|
78
|
+
expect(completedDriverFromImageFailure(error, "fast")?.costUsd).toBeCloseTo(0.0000382, 10);
|
|
79
|
+
expect(estimateImageGenerationCostMicroUsd(result, undefined)).toBeNull();
|
|
80
|
+
expect(result.runContext).toBeUndefined();
|
|
81
|
+
expect(completedDriverFromImageFailure(new Error("failed"))).toBeUndefined();
|
|
82
|
+
expect(completedDriverFromImageFailure(new ImageGenerationError("gate", "invalid"))).toBeUndefined();
|
|
83
|
+
});
|
|
84
|
+
it.each([
|
|
85
|
+
[false, 0, "generations"],
|
|
86
|
+
[false, 2, "edits"],
|
|
87
|
+
[true, 1, "edits"],
|
|
88
|
+
] as const)("auto chat uses Images %s/%s/%s and keeps both usages", async (transparent, referenceCount, endpoint) => {
|
|
89
|
+
const png = new PNG({ width: 2, height: 1 });
|
|
90
|
+
png.data.set([0, 0, 0, transparent ? 0 : 255, 255, 0, 0, 255]);
|
|
91
|
+
const imageUsage = {
|
|
92
|
+
input_tokens: 10, output_tokens: 100, total_tokens: 110,
|
|
93
|
+
input_tokens_details: { text_tokens: 4, image_tokens: 6 },
|
|
94
|
+
};
|
|
95
|
+
const fetchMock = vi.fn(async (url: string | URL | Request, _init?: RequestInit) => {
|
|
96
|
+
if (String(url).endsWith("/responses")) {
|
|
97
|
+
const events = [
|
|
98
|
+
{ type: "response.output_item.done", item: {
|
|
99
|
+
type: "function_call", call_id: "image-call", name: "generate_image",
|
|
100
|
+
arguments: JSON.stringify({ prompt: "A red square", transparent_background: transparent, num_last_images_to_include: referenceCount }),
|
|
101
|
+
} },
|
|
102
|
+
{ type: "response.completed", response: {
|
|
103
|
+
id: "driver", model: "gpt-6-luna",
|
|
104
|
+
usage: { input_tokens: 100, output_tokens: 20, total_tokens: 120 },
|
|
105
|
+
} },
|
|
106
|
+
];
|
|
107
|
+
return new Response(events.map((event) => `data: ${JSON.stringify(event)}\n\n`).join(""), { headers: { "content-type": "text/event-stream" } });
|
|
108
|
+
}
|
|
109
|
+
return Response.json({
|
|
110
|
+
data: [{ b64_json: PNG.sync.write(png).toString("base64") }],
|
|
111
|
+
background: transparent ? "transparent" : "opaque", quality: "medium", usage: imageUsage,
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
const provider = new OpenAIDispatcher("https", { fetch: fetchMock });
|
|
115
|
+
await provider.onTokenAdded(1, "chat-token", {
|
|
116
|
+
accessToken: "test-access", refreshToken: "", accountId: "test-account", accessTokenExpiresAt: Date.now() + 60000,
|
|
117
|
+
});
|
|
118
|
+
const dispatcher = new QgridDispatcherClass();
|
|
119
|
+
dispatcher.openaiDispatcher = provider;
|
|
120
|
+
try {
|
|
121
|
+
const result = await dispatcher.query({
|
|
122
|
+
model: "openai/gpt-6-luna", prompt: "Draw or edit a square", imageGeneration: "auto",
|
|
123
|
+
history: JSON.stringify([{ type: "message", role: "user", content: [{ type: "input_image", image_url: "https://example.com/old.png" }] }]),
|
|
124
|
+
input: [{ type: "image", url: "https://example.com/new.png" }],
|
|
125
|
+
});
|
|
126
|
+
expect(fetchMock.mock.calls.map(([url]) => url)).toEqual([
|
|
127
|
+
"https://chatgpt.com/backend-api/codex/responses",
|
|
128
|
+
`https://chatgpt.com/backend-api/codex/images/${endpoint}`,
|
|
129
|
+
]);
|
|
130
|
+
const driverInit = fetchMock.mock.calls[0]![1]!;
|
|
131
|
+
const imageInit = fetchMock.mock.calls[1]![1]!;
|
|
132
|
+
expect(JSON.parse(String(driverInit.body)).tools).toEqual([
|
|
133
|
+
expect.objectContaining({ type: "function", name: "generate_image" }),
|
|
134
|
+
]);
|
|
135
|
+
const imageBody = JSON.parse(String(imageInit.body));
|
|
136
|
+
expect(imageBody).toMatchObject({ model: "gpt-image-2", background: transparent ? "transparent" : "opaque" });
|
|
137
|
+
expect(imageBody.prompt).toContain("A red square");
|
|
138
|
+
expect(imageBody.prompt).not.toContain("Draw or edit a square");
|
|
139
|
+
const refs = ["https://example.com/old.png", "https://example.com/new.png"].slice(-referenceCount);
|
|
140
|
+
expect(imageBody.images).toEqual(referenceCount ? refs.map((image_url) => ({ image_url })) : undefined);
|
|
141
|
+
expect(imageInit.signal).toBe(driverInit.signal);
|
|
142
|
+
expect(new Headers(imageInit.headers).get("authorization")).toBe(new Headers(driverInit.headers).get("authorization"));
|
|
143
|
+
expect(result).toMatchObject({ model: "gpt-6-luna", usage: { input_tokens: 100, output_tokens: 20 } });
|
|
144
|
+
expect(result.costUsd).toBeCloseTo(0.00002, 8);
|
|
145
|
+
expect(result.content.find((item) => item.type === "image")).toMatchObject({ generation: { route: "codex-images", usage: imageUsage } });
|
|
146
|
+
expect(estimateImageGenerationCostMicroUsd(result, undefined)).toBe(3068);
|
|
147
|
+
} finally {
|
|
148
|
+
await provider.stop();
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("routes the image model straight to subscription edits and keeps image usage separate", async () => {
|
|
153
|
+
const png = new PNG({ width: 1, height: 1 });
|
|
154
|
+
png.data.set([255, 0, 0, 255]);
|
|
155
|
+
const base64 = PNG.sync.write(png).toString("base64");
|
|
156
|
+
const usage = {
|
|
157
|
+
input_tokens: 10, output_tokens: 100, total_tokens: 110,
|
|
158
|
+
input_tokens_details: { text_tokens: 4, image_tokens: 6 },
|
|
159
|
+
};
|
|
160
|
+
const fetchMock = vi.fn(async () => new Response(JSON.stringify({
|
|
161
|
+
data: [{ b64_json: base64 }], usage, background: "opaque", quality: "medium",
|
|
162
|
+
})));
|
|
163
|
+
const provider = new OpenAIDispatcher("https", { fetch: fetchMock });
|
|
164
|
+
await provider.onTokenAdded(1, "test", {
|
|
165
|
+
accessToken: "unused", refreshToken: "", accountId: "unused",
|
|
166
|
+
accessTokenExpiresAt: Date.now() + 60000,
|
|
167
|
+
});
|
|
168
|
+
const dispatcher = new QgridDispatcherClass();
|
|
169
|
+
dispatcher.openaiDispatcher = provider;
|
|
170
|
+
try {
|
|
171
|
+
const result = await dispatcher.query({
|
|
172
|
+
model: "openai/gpt-image-2", prompt: "recolor", imageGeneration: true,
|
|
173
|
+
input: [
|
|
174
|
+
{ type: "text", text: "recolor", text_elements: [] },
|
|
175
|
+
{ type: "image", url: `data:image/png;base64,${base64}` },
|
|
176
|
+
],
|
|
177
|
+
});
|
|
178
|
+
expect(fetchMock).toHaveBeenCalledOnce();
|
|
179
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
180
|
+
"https://chatgpt.com/backend-api/codex/images/edits",
|
|
181
|
+
expect.objectContaining({ method: "POST", signal: expect.any(AbortSignal) }),
|
|
182
|
+
);
|
|
183
|
+
expect(result).toMatchObject({
|
|
184
|
+
model: "gpt-image-2", costUsd: 0,
|
|
185
|
+
usage: { input_tokens: 0, output_tokens: 0 },
|
|
186
|
+
content: expect.arrayContaining([expect.objectContaining({
|
|
187
|
+
type: "image", mediaType: "image/png",
|
|
188
|
+
generation: { route: "codex-images", model: "gpt-image-2", background: "opaque", size: "1x1", quality: "medium", usage },
|
|
189
|
+
})]),
|
|
190
|
+
});
|
|
191
|
+
expect(result.runContext?.threadCoord).toBeUndefined();
|
|
192
|
+
} finally {
|
|
193
|
+
await provider.stop();
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
39
197
|
it.each(["generate", "stream"])("%s yields a completed tool-call message before a speculative later answer", async (mode) => {
|
|
40
198
|
const tool = JSON.stringify({result: {action: "tool_call", answer: null, toolCalls: [{toolName: "lookupMarker", args: "{}"}]}});
|
|
41
199
|
const answer = JSON.stringify({result: {action: "answer", answer: {marker: ""}, toolCalls: null}});
|
|
@@ -17,7 +17,10 @@ import {
|
|
|
17
17
|
serializeAndValidateDispatchSchema,
|
|
18
18
|
} from "../../utils/providers/common/schema-validation";
|
|
19
19
|
import { strictify } from "../../utils/providers/common/strictifier";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
ImageGenerationError,
|
|
22
|
+
type OpenAIDispatcher,
|
|
23
|
+
} from "../../utils/providers/openai/openai-dispatcher";
|
|
21
24
|
import { type TokenSubsetA } from "../sonamu.generated";
|
|
22
25
|
import { decideConvRouting, issueConvContext } from "./conv-routing";
|
|
23
26
|
import {
|
|
@@ -124,7 +127,7 @@ export class QgridDispatcherClass {
|
|
|
124
127
|
? undefined
|
|
125
128
|
: issueConvContext(result.threadCoord, decision, result.threadCoord.workerId);
|
|
126
129
|
|
|
127
|
-
return applyToolCallEmulation(toEmulationResult(result), input.tools, {
|
|
130
|
+
return applyToolCallEmulation(toEmulationResult(result, input.serviceTier), input.tools, {
|
|
128
131
|
threadCoord: coord,
|
|
129
132
|
images: result.images,
|
|
130
133
|
answerKind,
|
|
@@ -203,14 +206,18 @@ export class QgridDispatcherClass {
|
|
|
203
206
|
onComplete: (turnResult) => {
|
|
204
207
|
this.recordCompleted("openai", turnResult.tokenName);
|
|
205
208
|
cb.onComplete(
|
|
206
|
-
applyToolCallEmulation(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
209
|
+
applyToolCallEmulation(
|
|
210
|
+
toEmulationResult(turnResult, input.serviceTier),
|
|
211
|
+
input.tools,
|
|
212
|
+
{
|
|
213
|
+
threadCoord: issueConvContext(
|
|
214
|
+
turnResult.threadCoord,
|
|
215
|
+
decision,
|
|
216
|
+
turnResult.threadCoord.workerId,
|
|
217
|
+
),
|
|
218
|
+
answerKind,
|
|
219
|
+
},
|
|
220
|
+
),
|
|
214
221
|
);
|
|
215
222
|
},
|
|
216
223
|
onError: cb.onError,
|
|
@@ -325,9 +332,20 @@ function buildRawOutputSchema(
|
|
|
325
332
|
: callerOutputSchema;
|
|
326
333
|
}
|
|
327
334
|
|
|
335
|
+
export function completedDriverFromImageFailure(
|
|
336
|
+
error: unknown,
|
|
337
|
+
serviceTier?: string,
|
|
338
|
+
): QueryOutput | undefined {
|
|
339
|
+
if (!(error instanceof ImageGenerationError) || !error.completedDriver) return undefined;
|
|
340
|
+
return applyToolCallEmulation(toEmulationResult(error.completedDriver, serviceTier), undefined, {
|
|
341
|
+
answerKind: "text",
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
328
345
|
// Anthropic adapter 는 cache creation 을 inputTokens 에 포함해 표준화하고, 별도 필드에도 보존한다.
|
|
329
346
|
function toEmulationResult(
|
|
330
347
|
result: GenerateResult,
|
|
348
|
+
serviceTier?: string,
|
|
331
349
|
): Omit<QueryOutput, "content" | "finishReason" | "runContext"> {
|
|
332
350
|
const hasProviderCost = result.costUsd !== undefined && result.costUsd > 0;
|
|
333
351
|
return {
|
|
@@ -349,14 +367,18 @@ function toEmulationResult(
|
|
|
349
367
|
ttftMs: result.ttftMs ?? 0,
|
|
350
368
|
costUsd: hasProviderCost
|
|
351
369
|
? result.costUsd!
|
|
352
|
-
: calculateCostUsd(
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
370
|
+
: calculateCostUsd(
|
|
371
|
+
result.model,
|
|
372
|
+
{
|
|
373
|
+
inputTokens: result.usage.inputTokens,
|
|
374
|
+
outputTokens: result.usage.outputTokens,
|
|
375
|
+
cachedInputTokens: result.usage.cachedInputTokens,
|
|
376
|
+
cacheCreationInputTokens: result.usage.cacheCreationInputTokens ?? 0,
|
|
377
|
+
cacheCreationInputTokens5m: result.usage.cacheCreationInputTokens5m,
|
|
378
|
+
cacheCreationInputTokens1h: result.usage.cacheCreationInputTokens1h,
|
|
379
|
+
},
|
|
380
|
+
serviceTier,
|
|
381
|
+
),
|
|
360
382
|
costSource: hasProviderCost ? "provider" : "pricing_table",
|
|
361
383
|
};
|
|
362
384
|
}
|
|
@@ -5,6 +5,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
5
5
|
|
|
6
6
|
import { QgridFrame } from "./qgrid.frame";
|
|
7
7
|
import { QueryInput } from "./qgrid.types";
|
|
8
|
+
import { ImageGenerationError } from "../../utils/providers/openai/openai-dispatcher";
|
|
8
9
|
|
|
9
10
|
const {
|
|
10
11
|
findOneMock,
|
|
@@ -213,6 +214,7 @@ describe("QgridFrame.query request logging", () => {
|
|
|
213
214
|
beforeQueryMock.mockReset().mockResolvedValue({ requestLogId: 41, stepIndex: 0 });
|
|
214
215
|
afterQueryMock.mockReset().mockResolvedValue({});
|
|
215
216
|
finishRunWithErrorMock.mockReset();
|
|
217
|
+
finishRunAbortedMock.mockReset();
|
|
216
218
|
dispatcherQueryMock.mockReset();
|
|
217
219
|
findActiveByProviderAndNameMock.mockReset();
|
|
218
220
|
});
|
|
@@ -385,6 +387,46 @@ describe("QgridFrame.query request logging", () => {
|
|
|
385
387
|
expect(finishRunWithErrorMock).toHaveBeenCalledWith(41, "provider failed", args);
|
|
386
388
|
});
|
|
387
389
|
|
|
390
|
+
it.each(["error", "abort", "logger-disabled"])("handles completed driver accounting on image %s", async (mode) => {
|
|
391
|
+
const requestRaw = Object.assign(new EventEmitter(), { aborted: false });
|
|
392
|
+
const responseRaw = Object.assign(new EventEmitter(), { destroyed: false, writableEnded: false });
|
|
393
|
+
vi.spyOn(Sonamu, "getContext").mockReturnValue({
|
|
394
|
+
transport: "http", request: { raw: requestRaw }, reply: { raw: responseRaw },
|
|
395
|
+
} as never);
|
|
396
|
+
const error = new ImageGenerationError("incomplete", "image failed");
|
|
397
|
+
error.completedDriver = {
|
|
398
|
+
text: "drawing", tokenName: "one", model: "gpt-6-luna", durationMs: 20, ttftMs: 2,
|
|
399
|
+
threadCoord: { workerId: 1, threadId: "", epoch: -1 },
|
|
400
|
+
usage: { inputTokens: 100, outputTokens: 20, cachedInputTokens: 0, reasoningOutputTokens: 0, totalTokens: 120 },
|
|
401
|
+
};
|
|
402
|
+
dispatcherQueryMock.mockImplementationOnce(async () => {
|
|
403
|
+
if (mode === "abort") { responseRaw.destroyed = true; responseRaw.emit("close"); }
|
|
404
|
+
throw error;
|
|
405
|
+
});
|
|
406
|
+
const args = {
|
|
407
|
+
prompt: "draw", model: "openai/gpt-6-luna", imageGeneration: "auto" as const,
|
|
408
|
+
serviceTier: "fast" as const,
|
|
409
|
+
...(mode === "logger-disabled" ? { logger: false } : {}),
|
|
410
|
+
};
|
|
411
|
+
await expect(QgridFrame.query(args)).rejects.toBe(error);
|
|
412
|
+
expect(afterQueryMock).not.toHaveBeenCalled();
|
|
413
|
+
if (mode === "logger-disabled") {
|
|
414
|
+
expect(beforeQueryMock).not.toHaveBeenCalled();
|
|
415
|
+
expect(finishRunWithErrorMock).not.toHaveBeenCalled();
|
|
416
|
+
expect(finishRunAbortedMock).not.toHaveBeenCalled();
|
|
417
|
+
} else {
|
|
418
|
+
const completedStep = expect.objectContaining({
|
|
419
|
+
stepIndex: 0,
|
|
420
|
+
result: expect.objectContaining({
|
|
421
|
+
model: "gpt-6-luna", tokenName: "one", costUsd: 0.00004,
|
|
422
|
+
usage: expect.objectContaining({ input_tokens: 100, output_tokens: 20 }),
|
|
423
|
+
}),
|
|
424
|
+
});
|
|
425
|
+
if (mode === "abort") expect(finishRunAbortedMock).toHaveBeenCalledWith(41, args, completedStep);
|
|
426
|
+
else expect(finishRunWithErrorMock).toHaveBeenCalledWith(41, "image failed", args, completedStep);
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
388
430
|
it("marks an incoherent structured envelope failure as a request-log error", async () => {
|
|
389
431
|
const error = new Error(
|
|
390
432
|
"tool-call emulation: structured response envelope is missing required keys: toolCalls",
|
|
@@ -775,6 +817,32 @@ describe("QgridFrame.query request logging", () => {
|
|
|
775
817
|
expect(QueryInput.safeParse({ prompt: "hi", logger: false }).success).toBe(true);
|
|
776
818
|
});
|
|
777
819
|
|
|
820
|
+
it.each([
|
|
821
|
+
{ imageGeneration: undefined },
|
|
822
|
+
{ imageGeneration: "auto" as const },
|
|
823
|
+
{ imageGeneration: true, tools: [{ name: "lookup", inputSchema: { type: "object" } }] },
|
|
824
|
+
{ imageGeneration: true, jsonSchema: '{"type":"object"}' },
|
|
825
|
+
{ imageGeneration: true, imageGenerationOptions: { outputFormat: "webp" as const } },
|
|
826
|
+
])("rejects unsupported standalone image requests before logging: %j", async (options) => {
|
|
827
|
+
await expect(QgridFrame.query({
|
|
828
|
+
prompt: "draw", model: "openai/gpt-image-2", ...options,
|
|
829
|
+
})).rejects.toMatchObject({ statusCode: 400 });
|
|
830
|
+
expect(beforeQueryMock).not.toHaveBeenCalled();
|
|
831
|
+
expect(dispatcherQueryMock).not.toHaveBeenCalled();
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
it.each([
|
|
835
|
+
{ imageGenerationOptions: { outputFormat: "webp" as const } },
|
|
836
|
+
{ tools: [{ name: "lookup", inputSchema: { type: "object" } }] },
|
|
837
|
+
{ jsonSchema: '{"type":"object"}' },
|
|
838
|
+
])("rejects unsupported auto image controls before logging: %j", async (options) => {
|
|
839
|
+
await expect(QgridFrame.query({
|
|
840
|
+
prompt: "draw", model: "openai/gpt-6-luna", imageGeneration: "auto", ...options,
|
|
841
|
+
})).rejects.toMatchObject({ statusCode: 400 });
|
|
842
|
+
expect(beforeQueryMock).not.toHaveBeenCalled();
|
|
843
|
+
expect(dispatcherQueryMock).not.toHaveBeenCalled();
|
|
844
|
+
});
|
|
845
|
+
|
|
778
846
|
it("accepts bounded positive integer timeout values only", () => {
|
|
779
847
|
expect(QueryInput.safeParse({ prompt: "hi", timeout: 360_000 }).success).toBe(true);
|
|
780
848
|
expect(QueryInput.safeParse({ prompt: "hi", timeout: 0 }).success).toBe(false);
|
|
@@ -789,6 +857,12 @@ describe("QgridFrame.prepareStream", () => {
|
|
|
789
857
|
requestLogSaveMock.mockReset();
|
|
790
858
|
});
|
|
791
859
|
|
|
860
|
+
it("rejects an image model without a streaming image flag", async () => {
|
|
861
|
+
await expect(QgridFrame.prepareStream({
|
|
862
|
+
prompt: "draw", model: "openai/gpt-image-2",
|
|
863
|
+
})).rejects.toMatchObject({ statusCode: 400 });
|
|
864
|
+
});
|
|
865
|
+
|
|
792
866
|
it.each([true, "auto"] as const)("rejects imageGeneration=%s before creating an SSE stream", async (imageGeneration) => {
|
|
793
867
|
await expect(
|
|
794
868
|
QgridFrame.prepareStream({
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
refreshAccessToken,
|
|
38
38
|
RefreshFailedError,
|
|
39
39
|
} from "./oauth";
|
|
40
|
+
import { CODEX_IMAGE_GENERATION_MODEL } from "./qgrid-image-generation";
|
|
40
41
|
import {
|
|
41
42
|
afterQuery,
|
|
42
43
|
beforeQuery,
|
|
@@ -45,6 +46,7 @@ import {
|
|
|
45
46
|
} from "./qgrid-run-lifecycle";
|
|
46
47
|
import {
|
|
47
48
|
buildAndValidateStrictOutputSchema,
|
|
49
|
+
completedDriverFromImageFailure,
|
|
48
50
|
type InternalQueryInput,
|
|
49
51
|
QgridDispatcher,
|
|
50
52
|
} from "./qgrid.dispatcher";
|
|
@@ -145,12 +147,35 @@ function parsePastedOAuthResult(input: string): { code: string; state: string }
|
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
function rejectImageGenerationStream(args: QueryInput): void {
|
|
148
|
-
if (!args.imageGeneration) return;
|
|
150
|
+
if (!args.imageGeneration && args.model !== `openai/${CODEX_IMAGE_GENERATION_MODEL}`) return;
|
|
149
151
|
throw new BadRequestException(
|
|
150
|
-
"qgrid: imageGeneration is not supported with streaming; use query
|
|
152
|
+
"qgrid: imageGeneration is not supported with streaming; use query or generateImage with qgrid.image() instead." as LocalizedString,
|
|
151
153
|
);
|
|
152
154
|
}
|
|
153
155
|
|
|
156
|
+
function rejectInvalidImageQuery(args: QueryInput): void {
|
|
157
|
+
const standalone = args.model === `openai/${CODEX_IMAGE_GENERATION_MODEL}`;
|
|
158
|
+
if (!standalone && args.imageGeneration !== "auto") return;
|
|
159
|
+
if (standalone && args.imageGeneration !== true) {
|
|
160
|
+
throw new BadRequestException(
|
|
161
|
+
"qgrid: the image model requires imageGeneration: true; use qgrid.image() with generateImage." as LocalizedString,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
if (args.tools?.length || args.jsonSchema !== undefined) {
|
|
165
|
+
throw new BadRequestException(
|
|
166
|
+
"qgrid: automatic and standalone image generation do not support caller tools or structured output." as LocalizedString,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
if (
|
|
170
|
+
args.imageGenerationOptions?.outputFormat &&
|
|
171
|
+
args.imageGenerationOptions.outputFormat !== "png"
|
|
172
|
+
) {
|
|
173
|
+
throw new BadRequestException(
|
|
174
|
+
"qgrid: automatic and standalone image generation only support PNG output." as LocalizedString,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
154
179
|
// anthropic 은 --json-schema 를 쓰지 않으므로(SON-532) strict 변환·argv 제한 없이 원형의
|
|
155
180
|
// 구문·복잡도만 검증된다 — provider 분기는 buildAndValidateStrictOutputSchema 가 소유한다.
|
|
156
181
|
function rejectInvalidCallerSchemas(args: QueryInput): void {
|
|
@@ -247,6 +272,7 @@ class QgridFrameClass extends BaseFrameClass {
|
|
|
247
272
|
|
|
248
273
|
@api({ httpMethod: "POST", clients: ["axios", "tanstack-mutation"] })
|
|
249
274
|
async query(args: QueryInput): Promise<QueryOutput> {
|
|
275
|
+
rejectInvalidImageQuery(args);
|
|
250
276
|
rejectInvalidCallerSchemas(args);
|
|
251
277
|
const resolvedArgs = await resolveTokenName(args);
|
|
252
278
|
const disconnect = createHttpDisconnectHandle();
|
|
@@ -260,10 +286,19 @@ class QgridFrameClass extends BaseFrameClass {
|
|
|
260
286
|
try {
|
|
261
287
|
result = await QgridDispatcher.query(resolvedArgs, disconnect.signal);
|
|
262
288
|
} catch (e) {
|
|
289
|
+
const completedDriver = completedDriverFromImageFailure(e, resolvedArgs.serviceTier);
|
|
290
|
+
const completedStep = completedDriver
|
|
291
|
+
? ([{ stepIndex, result: completedDriver }] as const)
|
|
292
|
+
: ([] as const);
|
|
263
293
|
if (disconnect.signal.aborted) {
|
|
264
|
-
await finishRunAborted(requestLogId, resolvedArgs);
|
|
294
|
+
await finishRunAborted(requestLogId, resolvedArgs, ...completedStep);
|
|
265
295
|
} else {
|
|
266
|
-
await finishRunWithError(
|
|
296
|
+
await finishRunWithError(
|
|
297
|
+
requestLogId,
|
|
298
|
+
(e as Error).message,
|
|
299
|
+
resolvedArgs,
|
|
300
|
+
...completedStep,
|
|
301
|
+
);
|
|
267
302
|
}
|
|
268
303
|
throw e;
|
|
269
304
|
}
|
|
@@ -134,14 +134,21 @@
|
|
|
134
134
|
"name": "image_cost_usd",
|
|
135
135
|
"type": "integer",
|
|
136
136
|
"nullable": true,
|
|
137
|
-
"desc": "이미지 생성
|
|
137
|
+
"desc": "이미지 생성 비용"
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
"name": "image_cost_method",
|
|
141
141
|
"type": "string",
|
|
142
142
|
"length": 100,
|
|
143
143
|
"nullable": true,
|
|
144
|
-
"desc": "이미지 비용
|
|
144
|
+
"desc": "이미지 비용 계산 방식"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"name": "service_tier",
|
|
148
|
+
"type": "string",
|
|
149
|
+
"length": 20,
|
|
150
|
+
"nullable": true,
|
|
151
|
+
"desc": "Requested OpenAI service tier"
|
|
145
152
|
},
|
|
146
153
|
{
|
|
147
154
|
"name": "effort",
|
|
@@ -288,6 +295,7 @@
|
|
|
288
295
|
"image_cost_usd",
|
|
289
296
|
"image_cost_method",
|
|
290
297
|
"effort",
|
|
298
|
+
"service_tier",
|
|
291
299
|
"history",
|
|
292
300
|
"tools",
|
|
293
301
|
"status",
|
|
@@ -318,6 +326,7 @@
|
|
|
318
326
|
"image_cost_usd",
|
|
319
327
|
"image_cost_method",
|
|
320
328
|
"effort",
|
|
329
|
+
"service_tier",
|
|
321
330
|
"status",
|
|
322
331
|
"response_json_ok",
|
|
323
332
|
"tool_call_count",
|
|
@@ -481,6 +481,7 @@ class RequestLogModelClass extends BaseModelClass<
|
|
|
481
481
|
system_prompt?: string | null;
|
|
482
482
|
requested_model_name?: string | null;
|
|
483
483
|
effort?: string | null;
|
|
484
|
+
service_tier?: string | null;
|
|
484
485
|
project_name?: string | null;
|
|
485
486
|
history?: unknown;
|
|
486
487
|
tools?: ToolDefinitions;
|
|
@@ -496,6 +497,7 @@ class RequestLogModelClass extends BaseModelClass<
|
|
|
496
497
|
requested_model_name: params.requested_model_name ?? null,
|
|
497
498
|
model_name: null,
|
|
498
499
|
effort: params.effort ?? null,
|
|
500
|
+
service_tier: params.service_tier ?? null,
|
|
499
501
|
project_name: params.project_name ?? null,
|
|
500
502
|
status: "running",
|
|
501
503
|
response: "",
|
|
@@ -546,6 +548,7 @@ class RequestLogModelClass extends BaseModelClass<
|
|
|
546
548
|
duration_ms?: number;
|
|
547
549
|
requested_model_name?: string | null;
|
|
548
550
|
model_name?: string | null;
|
|
551
|
+
service_tier?: string | null;
|
|
549
552
|
fallback_count?: number | null;
|
|
550
553
|
cost_usd?: number | null;
|
|
551
554
|
cost_source?: string | null;
|
|
@@ -574,6 +577,7 @@ class RequestLogModelClass extends BaseModelClass<
|
|
|
574
577
|
"duration_ms",
|
|
575
578
|
"requested_model_name",
|
|
576
579
|
"model_name",
|
|
580
|
+
"service_tier",
|
|
577
581
|
"fallback_count",
|
|
578
582
|
"cost_usd",
|
|
579
583
|
"cost_source",
|
|
@@ -320,7 +320,7 @@ Content-Type: application/json
|
|
|
320
320
|
"imageGeneration": false,
|
|
321
321
|
"imageGenerationOptions": {
|
|
322
322
|
"quality": "low",
|
|
323
|
-
"size": "
|
|
323
|
+
"size": "auto",
|
|
324
324
|
"background": "auto",
|
|
325
325
|
"outputFormat": "png"
|
|
326
326
|
}
|
|
@@ -385,7 +385,7 @@ Content-Type: application/json
|
|
|
385
385
|
"imageGeneration": false,
|
|
386
386
|
"imageGenerationOptions": {
|
|
387
387
|
"quality": "low",
|
|
388
|
-
"size": "
|
|
388
|
+
"size": "auto",
|
|
389
389
|
"background": "auto",
|
|
390
390
|
"outputFormat": "png"
|
|
391
391
|
}
|