@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.
Files changed (79) hide show
  1. package/bundle/dist/application/monit/monit.frame.js +1 -1
  2. package/bundle/dist/application/qgrid/expired-token-reminder.js +1 -1
  3. package/bundle/dist/application/qgrid/oauth.js +1 -1
  4. package/bundle/dist/application/qgrid/qgrid-image-generation.js +7 -11
  5. package/bundle/dist/application/qgrid/qgrid-response-format.js +4 -4
  6. package/bundle/dist/application/qgrid/qgrid-run-lifecycle.js +24 -7
  7. package/bundle/dist/application/qgrid/qgrid.dispatcher.js +8 -3
  8. package/bundle/dist/application/qgrid/qgrid.frame.js +24 -10
  9. package/bundle/dist/application/qgrid/token-death.js +1 -1
  10. package/bundle/dist/application/qgrid/token-window-keepalive.js +1 -1
  11. package/bundle/dist/application/qgrid/tool-emulation.js +1 -1
  12. package/bundle/dist/application/request-log/request-log.model.js +2 -2
  13. package/bundle/dist/i18n/sd.generated.js +3 -3
  14. package/bundle/dist/sonamu.config.js +2 -2
  15. package/bundle/dist/utils/providers/anthropic/anthropic-dispatcher.js +1 -1
  16. package/bundle/dist/utils/providers/anthropic/anthropic-thinking.js +1 -1
  17. package/bundle/dist/utils/providers/openai/openai-backend-protocol.js +13 -2
  18. package/bundle/dist/utils/providers/openai/openai-direct-client.js +5 -4
  19. package/bundle/dist/utils/providers/openai/openai-dispatcher.js +111 -57
  20. package/bundle/dist/utils/providers/openai/openai-image-generation.js +25 -17
  21. package/bundle/dist/utils/providers/openai/openai-refresh.js +1 -1
  22. package/bundle/src/application/qgrid/qgrid-image-generation.test.ts +19 -8
  23. package/bundle/src/application/qgrid/qgrid-image-generation.ts +17 -24
  24. package/bundle/src/application/qgrid/qgrid-response-format.test.ts +4 -2
  25. package/bundle/src/application/qgrid/qgrid-response-format.ts +7 -10
  26. package/bundle/src/application/qgrid/qgrid-run-lifecycle.test.ts +87 -9
  27. package/bundle/src/application/qgrid/qgrid-run-lifecycle.ts +45 -5
  28. package/bundle/src/application/qgrid/qgrid.dispatcher.test.ts +136 -2
  29. package/bundle/src/application/qgrid/qgrid.dispatcher.ts +11 -1
  30. package/bundle/src/application/qgrid/qgrid.frame.test.ts +73 -0
  31. package/bundle/src/application/qgrid/qgrid.frame.ts +39 -4
  32. package/bundle/src/application/request-log/request-log.entity.json +2 -2
  33. package/bundle/src/application/sonamu.generated.http +2 -2
  34. package/bundle/src/i18n/sd.generated.ts +2 -2
  35. package/bundle/src/utils/providers/openai/openai-backend-protocol.test.ts +23 -1
  36. package/bundle/src/utils/providers/openai/openai-backend-protocol.ts +24 -2
  37. package/bundle/src/utils/providers/openai/openai-direct-client.test.ts +15 -0
  38. package/bundle/src/utils/providers/openai/openai-direct-client.ts +14 -4
  39. package/bundle/src/utils/providers/openai/openai-dispatcher.test.ts +223 -32
  40. package/bundle/src/utils/providers/openai/openai-dispatcher.ts +174 -72
  41. package/bundle/src/utils/providers/openai/openai-image-generation.test.ts +89 -0
  42. package/bundle/src/utils/providers/openai/openai-image-generation.ts +26 -15
  43. package/bundle/web-dist/client/assets/cost-DH2jZbrw.js +1 -0
  44. package/bundle/web-dist/client/assets/{index-Dxw7DiXF.js → index-D9ryIyL-.js} +4 -4
  45. package/bundle/web-dist/client/assets/logs-CLf2VNfk.js +1 -0
  46. package/bundle/web-dist/client/assets/{monit-E8eGW1Sj.js → monit-DojxOLj6.js} +1 -1
  47. package/bundle/web-dist/client/assets/{routes-7spLfIGZ.js → routes-DXJhNvbI.js} +2 -2
  48. package/bundle/web-dist/client/assets/{services.generated-jnPHYMER.js → services.generated-DqFYgWsY.js} +1 -1
  49. package/bundle/web-dist/client/assets/{settings-BdosZFNC.js → settings-bopyz9pa.js} +1 -1
  50. package/bundle/web-dist/client/assets/{show-b2PFNI3D.js → show-DbLD3MzB.js} +1 -1
  51. package/bundle/web-dist/client/assets/{tokens-CBdHb2_U.js → tokens-2-AIi9sZ.js} +1 -1
  52. package/bundle/web-dist/client/assets/{use-oauth-login-flow-Cyn3YPqW.js → use-oauth-login-flow-CnqTWs_H.js} +1 -1
  53. package/bundle/web-dist/client/assets/{use-update-token-mutation-DQCwCYfR.js → use-update-token-mutation-Cxb9K0fM.js} +1 -1
  54. package/bundle/web-dist/client/index.html +2 -2
  55. package/bundle/web-dist/server/assets/{cost-59jBdx-I.js → cost-eEHeTRT5.js} +4 -5
  56. package/bundle/web-dist/server/assets/{lazyRouteComponent-CUR-kWzI.js → lazyRouteComponent-Bi0fYGLs.js} +2 -2
  57. package/bundle/web-dist/server/assets/{logs-CaaWO9-Z.js → logs-CIVFxfoi.js} +10 -9
  58. package/bundle/web-dist/server/assets/{monit-CBil92fJ.js → monit-DBXezg7R.js} +4 -4
  59. package/bundle/web-dist/server/assets/{plus-Bvdj8Pwx.js → plus-BH-t4cm_.js} +1 -1
  60. package/bundle/web-dist/server/assets/{react-dom-CyuShs9K.js → react-dom-CZ2eynd8.js} +1 -1
  61. package/bundle/web-dist/server/assets/{refresh-cw-3QAiSWBd.js → refresh-cw-CosAdcz0.js} +1 -1
  62. package/bundle/web-dist/server/assets/{routes-DtVf9yqo.js → routes-COWYIvA-.js} +6 -7
  63. package/bundle/web-dist/server/assets/{services.generated-FPwsG9p3.js → services.generated-607iwhMI.js} +2 -2
  64. package/bundle/web-dist/server/assets/{settings-DeHNwoMv.js → settings-C_6dpK5p.js} +3 -3
  65. package/bundle/web-dist/server/assets/{show-8NNdRxhM.js → show-DY396-LD.js} +8 -20
  66. package/bundle/web-dist/server/assets/{tokens-odgS2-9q.js → tokens-COvV7qUP.js} +4 -4
  67. package/bundle/web-dist/server/assets/{use-oauth-login-flow-DTQKdYBO.js → use-oauth-login-flow-VYCYf3uv.js} +1 -1
  68. package/bundle/web-dist/server/assets/{use-update-token-mutation-xvXbPrCI.js → use-update-token-mutation-D8XYV5of.js} +1 -1
  69. package/bundle/web-dist/server/entry-server.generated.js +10 -10
  70. package/package.json +1 -1
  71. package/skills/qgrid/SKILL.md +9 -8
  72. package/skills/qgrid/references/ai-sdk-provider-contract.md +25 -16
  73. package/skills/qgrid/references/decision-rationale.md +8 -8
  74. package/skills/qgrid/references/openai-codex-runtime.md +19 -11
  75. package/skills/qgrid/references/request-log-run-lifecycle.md +4 -2
  76. package/skills/qgrid/references/sonamu-api-web-flow.md +2 -2
  77. package/skills/qgrid/references/verification-and-debugging.md +23 -9
  78. package/bundle/web-dist/client/assets/cost-CY2BJEPC.js +0 -1
  79. package/bundle/web-dist/client/assets/logs-3n_U4qnI.js +0 -1
@@ -1,7 +1,7 @@
1
- import { A as require_react, E as useQuery, X as __toESM, i as RequestLogStepService, k as require_jsx_runtime, l as isSonamuError, q as __commonJSMin, r as RequestLogService } from "./services.generated-FPwsG9p3.js";
2
- import { a as Link } from "./lazyRouteComponent-CUR-kWzI.js";
1
+ import { A as require_react, E as useQuery, X as __toESM, i as RequestLogStepService, k as require_jsx_runtime, l as isSonamuError, q as __commonJSMin, r as RequestLogService } from "./services.generated-607iwhMI.js";
2
+ import { a as Link } from "./lazyRouteComponent-Bi0fYGLs.js";
3
3
  import { c as lucideX, d as Markdown, t as Route, u as remarkGfm } from "../entry-server.generated.js";
4
- import { n as formatMicroUsd, t as cacheHitRate } from "./cost-59jBdx-I.js";
4
+ import { n as formatMicroUsd, t as cacheHitRate } from "./cost-eEHeTRT5.js";
5
5
  import { t as requestModelDisplay } from "./request-model-CnCeaNLE.js";
6
6
  //#region ../../node_modules/.pnpm/@babel+runtime@7.29.2/node_modules/@babel/runtime/helpers/extends.js
7
7
  var require_extends = /* @__PURE__ */ __commonJSMin(((exports, module) => {
@@ -2194,15 +2194,11 @@ function MetricsPanel({ data, toolCallCount }) {
2194
2194
  value: formatTokensPerSec(data.output_tokens, data.duration_ms, data.ttft_ms, tokensPerSecEnabled)
2195
2195
  }),
2196
2196
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Metric, {
2197
- label: "Driver Cost",
2197
+ label: "Driver",
2198
2198
  value: data.cost_usd !== null ? formatMicroUsd(data.cost_usd) : "—"
2199
2199
  }),
2200
2200
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Metric, {
2201
- label: "Cost Source",
2202
- value: data.cost_source ?? "legacy"
2203
- }),
2204
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Metric, {
2205
- label: "Image Cost",
2201
+ label: "Image",
2206
2202
  value: data.image_cost_usd !== null ? formatMicroUsd(data.image_cost_usd) : "—"
2207
2203
  }),
2208
2204
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Metric, {
@@ -2277,8 +2273,8 @@ function formatTokensPerSec(outputTokens, durationMs, ttftMs, enabled) {
2277
2273
  }
2278
2274
  function CompactMetric({ label, value }) {
2279
2275
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2280
- className: "min-w-0",
2281
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2276
+ className: "min-w-0 self-end",
2277
+ children: [label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2282
2278
  className: "text-[10px] uppercase tracking-wider text-sand-400 font-medium truncate",
2283
2279
  children: label
2284
2280
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -2534,14 +2530,7 @@ function StepTreeItem({ entry, tokensPerSecEnabled }) {
2534
2530
  label: "Cache 5m / 1h",
2535
2531
  value: `${generate.cacheCreation5mTokens === null ? "—" : formatNum(generate.cacheCreation5mTokens)} / ${generate.cacheCreation1hTokens === null ? "—" : formatNum(generate.cacheCreation1hTokens)}`
2536
2532
  }),
2537
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CompactMetric, {
2538
- label: "Cost",
2539
- value: generate.costUsd === null ? "—" : formatMicroUsd(generate.costUsd)
2540
- }),
2541
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CompactMetric, {
2542
- label: "Cost Source",
2543
- value: generate.costSource ?? "legacy"
2544
- }),
2533
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CompactMetric, { value: generate.costUsd === null ? "—" : formatMicroUsd(generate.costUsd) }),
2545
2534
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CompactMetric, {
2546
2535
  label: "Reasoning",
2547
2536
  value: generate.reasoningTokens !== null && generate.reasoningTokens !== void 0 ? formatNum(generate.reasoningTokens) : "—"
@@ -2598,7 +2587,6 @@ function buildStepTree(steps) {
2598
2587
  cacheCreation5mTokens: step.cache_creation_5m_tokens,
2599
2588
  cacheCreation1hTokens: step.cache_creation_1h_tokens,
2600
2589
  costUsd: step.cost_usd,
2601
- costSource: step.cost_source,
2602
2590
  durationMs: step.duration_ms,
2603
2591
  ttftMs: step.ttft_ms,
2604
2592
  finishReason: step.finish_reason,
@@ -1,8 +1,8 @@
1
- import { A as require_react, O as useQueryClient, X as __toESM, k as require_jsx_runtime, n as QgridService, o as TokenService } from "./services.generated-FPwsG9p3.js";
1
+ import { A as require_react, O as useQueryClient, X as __toESM, k as require_jsx_runtime, n as QgridService, o as TokenService } from "./services.generated-607iwhMI.js";
2
2
  import { f as lucideKeyRound, o as Q } from "../entry-server.generated.js";
3
- import { t as useUpdateTokenMutation } from "./use-update-token-mutation-xvXbPrCI.js";
4
- import { n as OAuthCodeEntry, t as useOAuthLoginFlow } from "./use-oauth-login-flow-DTQKdYBO.js";
5
- import { t as lucidePlus } from "./plus-Bvdj8Pwx.js";
3
+ import { t as useUpdateTokenMutation } from "./use-update-token-mutation-D8XYV5of.js";
4
+ import { n as OAuthCodeEntry, t as useOAuthLoginFlow } from "./use-oauth-login-flow-VYCYf3uv.js";
5
+ import { t as lucidePlus } from "./plus-BH-t4cm_.js";
6
6
  //#region src/components/qgrid/AddTokenModal.tsx
7
7
  var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
8
8
  var import_jsx_runtime = require_jsx_runtime();
@@ -1,4 +1,4 @@
1
- import { A as require_react, O as useQueryClient, X as __toESM, k as require_jsx_runtime, n as QgridService, o as TokenService } from "./services.generated-FPwsG9p3.js";
1
+ import { A as require_react, O as useQueryClient, X as __toESM, k as require_jsx_runtime, n as QgridService, o as TokenService } from "./services.generated-607iwhMI.js";
2
2
  import { o as Q } from "../entry-server.generated.js";
3
3
  //#region src/components/qgrid/OAuthCodeEntry.tsx
4
4
  var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
@@ -1,4 +1,4 @@
1
- import { T as useMutation, n as QgridService } from "./services.generated-FPwsG9p3.js";
1
+ import { T as useMutation, n as QgridService } from "./services.generated-607iwhMI.js";
2
2
  //#region src/services/token/use-update-token-mutation.ts
3
3
  function useUpdateTokenMutation() {
4
4
  return useMutation({ mutationFn: ({ id, name, quotaThreshold, weight, keepaliveEnabled }) => QgridService.updateToken(id, name, quotaThreshold, weight, keepaliveEnabled) });
@@ -1,6 +1,6 @@
1
- import { A as require_react, B as matchQuery, C as union, D as QueryClientProvider, F as notifyManager, G as focusManager, H as partialMatchKey, I as functionalUpdate, J as __exportAll, K as Subscribable, L as hashKey, M as infiniteQueryBehavior, N as Query, P as onlineManager, R as hashQueryKeyByOptions, S as string$2, U as resolveStaleTime, V as noop$1, W as skipToken, X as __toESM, Y as __require, _ as custom, b as number$1, c as fetch$1, d as SD, f as SUPPORTED_LOCALES, g as boolean$1, h as array, j as Mutation, k as require_jsx_runtime, m as _enum, n as QgridService, o as TokenService, p as setLocale, q as __commonJSMin, s as SonamuQueryMode, u as zArrayable, v as date, w as unknown, x as object, y as int, z as matchMutation } from "./assets/services.generated-FPwsG9p3.js";
2
- import { A as decodePath, C as findRouteMatch, D as createLRUCache, E as processRouteTree, F as last, I as replaceEqualDeep, M as findLast, N as functionalUpdate$1, O as invariant, P as isPromise, S as findFlatMatch, T as processRouteMasks, _ as interpolatePath, a as Link$1, b as trimPath, c as usePrevious$1, d as useRouter, f as getRouterContext, g as cleanPath, h as isNotFound, i as createRootRouteWithContext, j as deepEqual$2, k as createControlledPromise, l as matchContext, m as rootRouteId, n as createFileRoute, p as warning, r as createLazyFileRoute, s as useLayoutEffect$3, t as lazyRouteComponent, u as useRouterState, v as joinPaths, w as findSingleMatch, x as trimPathRight, y as resolvePath } from "./assets/lazyRouteComponent-CUR-kWzI.js";
3
- import { n as require_shim, t as require_react_dom } from "./assets/react-dom-CyuShs9K.js";
1
+ import { A as require_react, B as matchQuery, C as union, D as QueryClientProvider, F as notifyManager, G as focusManager, H as partialMatchKey, I as functionalUpdate, J as __exportAll, K as Subscribable, L as hashKey, M as infiniteQueryBehavior, N as Query, P as onlineManager, R as hashQueryKeyByOptions, S as string$2, U as resolveStaleTime, V as noop$1, W as skipToken, X as __toESM, Y as __require, _ as custom, b as number$1, c as fetch$1, d as SD, f as SUPPORTED_LOCALES, g as boolean$1, h as array, j as Mutation, k as require_jsx_runtime, m as _enum, n as QgridService, o as TokenService, p as setLocale, q as __commonJSMin, s as SonamuQueryMode, u as zArrayable, v as date, w as unknown, x as object, y as int, z as matchMutation } from "./assets/services.generated-607iwhMI.js";
2
+ import { A as decodePath, C as findRouteMatch, D as createLRUCache, E as processRouteTree, F as last, I as replaceEqualDeep, M as findLast, N as functionalUpdate$1, O as invariant, P as isPromise, S as findFlatMatch, T as processRouteMasks, _ as interpolatePath, a as Link$1, b as trimPath, c as usePrevious$1, d as useRouter, f as getRouterContext, g as cleanPath, h as isNotFound, i as createRootRouteWithContext, j as deepEqual$2, k as createControlledPromise, l as matchContext, m as rootRouteId, n as createFileRoute, p as warning, r as createLazyFileRoute, s as useLayoutEffect$3, t as lazyRouteComponent, u as useRouterState, v as joinPaths, w as findSingleMatch, x as trimPathRight, y as resolvePath } from "./assets/lazyRouteComponent-Bi0fYGLs.js";
3
+ import { n as require_shim, t as require_react_dom } from "./assets/react-dom-CZ2eynd8.js";
4
4
  import minpath from "node:path";
5
5
  import minproc from "node:process";
6
6
  import { fileURLToPath as urlToPath } from "node:url";
@@ -46874,7 +46874,7 @@ function ChatWidget() {
46874
46874
  }
46875
46875
  //#endregion
46876
46876
  //#region src/components/Sidebar.tsx
46877
- var qgridVersion = "2.10.5";
46877
+ var qgridVersion = "2.10.6";
46878
46878
  function Sidebar({ className }) {
46879
46879
  const pathname = useRouterState({ select: (s) => s.location.pathname });
46880
46880
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", {
@@ -68752,15 +68752,15 @@ function RootComponent() {
68752
68752
  }
68753
68753
  //#endregion
68754
68754
  //#region src/routes/tokens.tsx
68755
- var $$splitComponentImporter$5 = () => import("./assets/tokens-odgS2-9q.js");
68755
+ var $$splitComponentImporter$5 = () => import("./assets/tokens-COvV7qUP.js");
68756
68756
  var Route$5 = createFileRoute("/tokens")({ component: lazyRouteComponent($$splitComponentImporter$5, "component") });
68757
68757
  //#endregion
68758
68758
  //#region src/routes/settings.tsx
68759
- var $$splitComponentImporter$4 = () => import("./assets/settings-DeHNwoMv.js");
68759
+ var $$splitComponentImporter$4 = () => import("./assets/settings-C_6dpK5p.js");
68760
68760
  var Route$4 = createFileRoute("/settings")({ component: lazyRouteComponent($$splitComponentImporter$4, "component") });
68761
68761
  //#endregion
68762
68762
  //#region src/routes/monit.tsx
68763
- var $$splitComponentImporter$3 = () => import("./assets/monit-CBil92fJ.js");
68763
+ var $$splitComponentImporter$3 = () => import("./assets/monit-DBXezg7R.js");
68764
68764
  var monitSearchSchema = object({ level: _enum([
68765
68765
  "all",
68766
68766
  "warn",
@@ -69137,7 +69137,7 @@ object({
69137
69137
  _enum(["A"]);
69138
69138
  //#endregion
69139
69139
  //#region src/routes/logs.tsx
69140
- var $$splitComponentImporter$2 = () => import("./assets/logs-CaaWO9-Z.js");
69140
+ var $$splitComponentImporter$2 = () => import("./assets/logs-CIVFxfoi.js");
69141
69141
  var logsSearchSchema = object({
69142
69142
  token: string$2().optional(),
69143
69143
  project: string$2().optional(),
@@ -69151,11 +69151,11 @@ var Route$2 = createFileRoute("/logs")({
69151
69151
  });
69152
69152
  //#endregion
69153
69153
  //#region src/routes/index.tsx
69154
- var $$splitComponentImporter$1 = () => import("./assets/routes-DtVf9yqo.js");
69154
+ var $$splitComponentImporter$1 = () => import("./assets/routes-COWYIvA-.js");
69155
69155
  var Route$1 = createFileRoute("/")({ component: lazyRouteComponent($$splitComponentImporter$1, "component") });
69156
69156
  //#endregion
69157
69157
  //#region src/routes/requests/show.tsx
69158
- var $$splitComponentImporter = () => import("./assets/show-8NNdRxhM.js");
69158
+ var $$splitComponentImporter = () => import("./assets/show-DY396-LD.js");
69159
69159
  var showSearchSchema = object({ id: number$1() });
69160
69160
  var Route = createFileRoute("/requests/show")({
69161
69161
  validateSearch: showSearchSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cartanova/qgrid-cli",
3
- "version": "2.10.5",
3
+ "version": "2.10.6",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/cartanova-ai/qgrid"
@@ -38,13 +38,13 @@ When helping a user set up local `@cartanova/qgrid-ai-sdk`, `createQgridLogger`,
38
38
 
39
39
  Prefer `QGRID_PROJECT_NAME` as the project-wide default. Use config `projectName` only for a deliberate per-call/per-provider override. Do not add `projectName` or `project_name` under `providerOptions.qgrid`; current code does not read it there.
40
40
 
41
- When writing AI SDK examples or setup code, import the public `QgridProviderOptions` type and apply `satisfies QgridProviderOptions` to the value under `providerOptions.qgrid`. AI SDK types the outer `providerOptions` as a generic JSON record, so it cannot infer qgrid's option names and values by itself. `QgridProviderOptions` is the inner qgrid namespace type, not the outer record type.
41
+ When writing AI SDK examples or setup code, use `satisfies QgridProviderOptions` for language-model options or `satisfies QgridImageProviderOptions` for image-model options under `providerOptions.qgrid`. AI SDK types the outer `providerOptions` as a generic JSON record, so it cannot infer qgrid's option names and values by itself. These types describe the inner qgrid namespace, not the outer record.
42
42
 
43
43
  Request logging is enabled by default. Use `providerOptions.qgrid.logger: false` only for a call that must create zero qgrid request-log rows. The same option suppresses both qgrid's server-native logging and `createQgridLogger` telemetry logging for that generation; it does not disable generation, AI SDK tool execution, multi-step continuation, or OpenAI cache affinity. The old `logMode` input has been removed and must not appear in setup code or raw qgrid payloads.
44
44
 
45
45
  ## Non-Negotiable Boundaries
46
46
 
47
- - Use `packages/ai-sdk` as the active public SDK surface.
47
+ - Use `packages/ai-sdk` as the active public SDK surface. `qgrid()` implements `LanguageModelV3` for text, tools, and structured output; it has no public image-generation options and does not support general vision. `qgrid.image("openai/gpt-image-2")` implements `ImageModelV3` for standard AI SDK `generateImage`, directly calling subscription Images for generation or reference editing without a chat driver. Dashboard chat registers its image tool internally; do not add a public SDK tool factory. See `references/ai-sdk-provider-contract.md` for image options, usage, and metadata.
48
48
  - Expose thinking only as `providerOptions.qgrid.thinking?: boolean`: omission preserves the model policy, false overrides effort, and unsupported/unverified off requests fail explicitly. See the SDK and provider runtime references for the dated support snapshot. Keep Anthropic thinking controls separate from mandatory configuration and memory isolation. Haiku/Sonnet 5.5 off uses a computed thinking-only native body override with Claude Code >=2.1.295; never inherit or expose arbitrary extra body.
49
49
  - The old v1 SDK package (`packages/sdk`, `@cartanova/qgrid-sdk`) has been removed from the repository. It survives only as a deprecated npm artifact (1.9.0); do not resurrect it or write new code against it.
50
50
  - Let the server infer single-turn versus tool-run request-log lifecycle. Do not recreate caller-selected logging modes in the SDK or API examples.
@@ -54,11 +54,11 @@ Request logging is enabled by default. Use `providerOptions.qgrid.logger: false`
54
54
  - Prefer `getPuri()` for Sonamu model queries. Treat `getDB()` as a legacy escape hatch only when Puri cannot express the required query. Inside `@transactional` methods, all participating queries must use `getPuri()`; `getDB()` does not reuse Sonamu's ambient transaction connection.
55
55
  - Never hand-author migration files for Sonamu-managed schema. In the qgrid source repository, change the entity definition, inspect `sonamu migrate status`, and create migration files with `mise exec -- pnpm --dir packages/api sonamu migrate generate`. Inspect the generated files before applying them. If Sonamu cannot express a required schema change, stop and ask rather than silently replacing its workflow with a custom migration.
56
56
  - Keep OpenAI Codex built-in tools, apps, plugins, skills, web search, shell, and environment instruction blocks disabled unless the user explicitly asks for agentic Codex behavior.
57
- - Treat OpenAI image generation as opt-in outside dashboard chat. In 2.9.8, OpenAI `qgrid_chat` opts into raw API `imageGeneration: "auto"` on every turn; the public AI SDK option remains boolean. Ordinary images use the hosted Responses image tool; a server-executed transparent-image function calls Codex standalone Images directly with subscription credentials. Do not reintroduce a client decision tool followed by another GPT generation request. Hosted image costs are unknown (`null`); standalone usage and known-size legacy responses use public-price estimates. Mark totals as known subtotals when they omit unknown image costs.
57
+ - Treat OpenAI image generation as opt-in outside dashboard chat. OpenAI `qgrid_chat` opts into raw API `imageGeneration: "auto"` on every turn. Auto mode registers only the server-executed `generate_image` function and calls Codex standalone Images for ordinary or transparent output. The raw explicit chat-model `imageGeneration: true` hosted route remains supported separately. The public SDK uses only `qgrid.image()` / `generateImage` for images, calling standalone Images for every background. Do not expose the raw `imageGeneration` or `imageGenerationOptions` fields as language-model options or retain deprecated shims. Do not reintroduce a client decision tool followed by another GPT generation request. Image costs use reported image usage at public token rates when available; otherwise estimate per image from the requested quality/size table. Auto or unsupported sizes use the 1536x1024 price; omitted quality uses medium. Display combined driver and image costs as dollar amounts without Cost labels. Do not show Known/Estimated labels, pricing disclaimers, or cost-source metadata in the dashboard.
58
58
 
59
59
  ## Request log IDs (2.10.0+)
60
60
 
61
- - Successful raw query responses and stream `done` events expose optional top-level `requestLogId`. The AI SDK exposes it as `providerMetadata.qgrid.requestLogId`; await stream metadata after consuming the stream.
61
+ - Successful raw query responses and stream `done` events expose optional top-level `requestLogId`. AI SDK text calls expose it as `providerMetadata.qgrid.requestLogId`; await stream metadata after consuming the stream. `generateImage` exposes each call's ID under `providerMetadata.qgrid.images[]` alongside generation metadata.
62
62
  - The ID identifies the parent request log and stays the same across tool-run steps. It is omitted with `logger: false`. Keep `runContext.requestLogId` for tool continuation only; do not replay the top-level ID as continuation state.
63
63
  - Update both server and AI SDK to 2.10.0 or later to consume the metadata. Older servers may omit the ID. This contract does not add IDs to delta or error events or change `createQgridLogger` for external providers.
64
64
 
@@ -66,17 +66,18 @@ Request logging is enabled by default. Use `providerOptions.qgrid.logger: false`
66
66
 
67
67
  - Raw `imageGeneration: "auto"` permits text-only replies; explicit `true` still requires an image. Both use non-stream requests. Auto mode rejects caller tools and output schemas.
68
68
  - Dashboard chat sends complete text and image history, including prior generated images as user visual references. Do not assume provider conversation state retains image bytes or send assistant `input_image` blocks.
69
- - The model chooses ordinary `image_generation` or `generate_transparent_image`; the server runs the latter directly with the selected token and cancellation signal. Preserve driver usage separately from image usage.
69
+ - Register only `generate_image` for auto mode, with server-validated arguments `prompt: string`, `transparent_background: boolean`, and `num_last_images_to_include: integer` from 0 to 5. Use `strict: false` on the wire, matching Codex; Astra subscription requests fail with `strict: true`. Keep complete server-side validation. The driver sees the complete conversation and writes a self-contained image prompt. Zero references means fresh generation; a positive count selects the last N conversation images in chronological order for editing. Do not forward all historical images automatically.
70
+ - Run the function directly against subscription Images with the selected token and cancellation signal, without a second text-driver request. Preserve driver usage separately from image usage. Do not register the native hosted `image_generation` tool in auto mode.
70
71
  - Request-log model labels show requested model to image model using serving/image-cost metadata. Do not hardcode `gpt-image-2.5` or restore image-generation/assumption badges. In auto mode, mark image generation only when an image was produced.
71
72
  - Read `references/decision-rationale.md` and `references/openai-codex-runtime.md` before changing this routing or its cache boundary.
72
73
 
73
74
  ## Image output formats (2.9.6+)
74
75
 
75
- - Use `imageGenerationOptions.outputFormat: "png" | "jpeg" | "webp"` on the hosted Responses image path. Omission retains PNG.
76
+ - Raw explicit chat-model hosted Responses requests support `imageGenerationOptions.outputFormat: "png" | "jpeg" | "webp"`. Omission retains PNG. Auto chat and public `generateImage` use standalone Images and support PNG only; reject other formats before generation. Public image options use flat `QgridImageProviderOptions`.
76
77
  - Preserve the actual returned MIME type in qgrid content, AI SDK files, and request-log image URLs; do not relabel bytes as the requested format.
77
- - Transparent generation keeps the standalone Codex Images route and accepts PNG only. Reject transparent JPEG/WebP requests before generation.
78
+ - Transparent generation uses the standalone Codex Images route and accepts PNG only. Reject transparent JPEG/WebP requests before generation. Do not convert output or fall back to another route.
78
79
  - Keep the AI SDK standard `GeneratedFile.mediaType: string` contract. Do not add a qgrid-specific image wrapper or conditional return type.
79
- - A 2026-09-29 live probe returned PNG for a prompt-only WebP request and WebP when `outputFormat: "webp"` was supplied. Model prose is not format evidence; inspect file bytes. Chat does not infer output format from prose, and no post-generation conversion is performed.
80
+ - A 2026-09-29 hosted-route probe returned PNG for a prompt-only WebP request and WebP when `outputFormat: "webp"` was supplied. Model prose is not format evidence; inspect file bytes. Chat does not infer output format from prose, and no post-generation conversion is performed.
80
81
 
81
82
  ## Verification
82
83
 
@@ -6,13 +6,26 @@ Use this reference before changing `packages/ai-sdk`.
6
6
 
7
7
  `packages/ai-sdk` is the active public SDK package. The old v1 SDK (`@cartanova/qgrid-sdk`) has been removed from the repository.
8
8
 
9
- qgrid's custom provider implements the AI SDK `LanguageModelV3` contract. General usage should follow the AI SDK docs and normal AI SDK APIs such as `generateText`, `streamText`, tools, structured output, and telemetry. Do not re-document generic AI SDK usage in qgrid-specific docs unless the behavior differs. qgrid docs and this skill should focus on qgrid-specific config, `providerOptions.qgrid`, request logging, runtime routing, cache/session behavior, and provider limitations.
9
+ qgrid's custom provider implements `LanguageModelV3` through `qgrid()` and `ImageModelV3` through `qgrid.image()`. General usage should follow the AI SDK docs and normal AI SDK APIs such as `generateText`, `streamText`, `generateImage`, tools, structured output, and telemetry. Do not re-document generic AI SDK usage in qgrid-specific docs unless the behavior differs. qgrid docs and this skill should focus on qgrid-specific config, `providerOptions.qgrid`, request logging, runtime routing, cache/session behavior, and provider limitations.
10
+
11
+ ## Explicit image model
12
+
13
+ Use `generateImage({ model: qgrid.image("openai/gpt-image-2"), prompt })` for explicit image generation. The image factory returns `ImageModelV3`, accepts `serverUrl` and `projectName`, and sends the existing query endpoint `model: "openai/gpt-image-2", imageGeneration: true`. These image-generation fields are internal wire inputs, not public language-model options. The server calls subscription Codex Images directly, without a chat driver. `qgrid()` / `generateText` handles text, tools, and structured output. Dashboard auto-image mode registers an internal `generate_image` function, which calls the same subscription Images endpoints for ordinary or transparent output after the chat model chooses a prompt and references. Do not add a public SDK image-tool factory.
14
+
15
+ Reference editing uses AI SDK `prompt: { text, images }`, with up to five references. Omitted references use `images/generations`; supplied references use `images/edits`. The new direct interface returns PNG only; unsupported output formats and masks fail before generation. `n` is handled through AI SDK batching with `maxImagesPerCall: 1`; callers cannot override this to send a multi-image backend request. `seed` and `aspectRatio` produce unsupported warnings. Size and quality are hints, never exact-output guarantees; prompt for the intended ratio and preserve observed settings.
16
+
17
+ `gpt-image-2` is the Codex wire request identifier, not proof that the serving weights are Images 2.0. OpenAI announced Images 2.5 in Codex; private-route probes accepted even nonexistent model IDs, so Flare/Sunburst selection is not established. Standalone metadata preserves a reported model, otherwise it falls back to the requested identifier. Do not describe that fallback as an observed serving version. GPT Image 2 and 2.5 have the same published token rates; per-image fallback prices remain GPT Image 2 estimates when usage is absent.
18
+
19
+ Use `QgridImageProviderOptions` with `satisfies` on `providerOptions.qgrid`. Options are flat: `quality`, `background`, `outputFormat: "png"`, `tokenName`, and `logger`. Standard `size` belongs at the `generateImage` top level. Do not send chat effort, thinking, tools, or structured output to the image model.
20
+
21
+ Map image response usage into standard `generateImage().usage`. Preserve per-image generation metadata and request-log IDs under `providerMetadata.qgrid.images[]`: AI SDK 6 merges that array and discards arbitrary provider-level keys. The raw query's top-level usage/cost remain zero chat-driver accounting; server logs price the separate image usage using the existing API-price estimator. Do not duplicate image price calculation in the SDK or count aggregate usage once per output. Without reported image usage, keep standard usage unknown and preserve the server's existing per-image cost fallback. `logger: false`, token targeting, project labeling, and cancellation still apply.
10
22
 
11
23
  For tool calling, qgrid intentionally follows the AI SDK interface while implementing provider calls through qgrid structured-output emulation. Read `tool-calling-and-multiturn.md` before changing tool behavior, `stopWhen`/multi-step behavior, or tool-call request logs.
12
24
 
13
25
  Main files:
14
26
 
15
27
  - `packages/ai-sdk/src/index.ts`: `qgrid()` provider implementation.
28
+ - `packages/ai-sdk/src/image-model.ts`: `qgrid.image()` provider implementation.
16
29
  - `packages/ai-sdk/src/index.types.ts`: public config/options/types.
17
30
  - `packages/ai-sdk/src/utils.ts`: prompt/history/tool conversion.
18
31
  - `packages/ai-sdk/src/logger.ts`: telemetry logger integration.
@@ -55,7 +68,7 @@ const result = await generateText({
55
68
 
56
69
  Do not write `providerOptions: { ... } satisfies QgridProviderOptions`: the exported type describes the nested qgrid options object, not AI SDK's outer provider-name map. Use this typed pattern in qgrid setup guidance and examples so misspelled keys and invalid option values fail at compile time.
57
70
 
58
- `providerOptions.qgrid` supports:
71
+ Language-model `providerOptions.qgrid` supports:
59
72
 
60
73
  - `tokenName`: strict active-token targeting for both providers. Include the provider prefix and
61
74
  match it to the model (`anthropic/yds` with `anthropic/*`, for example). Missing, inactive, or
@@ -70,8 +83,8 @@ Do not write `providerOptions: { ... } satisfies QgridProviderOptions`: the expo
70
83
  - `serviceTier`: OpenAI/Codex route only.
71
84
  - `timeoutMs`: Anthropic server-side Claude Code process timeout in milliseconds. It must be a positive integer no greater than 30 minutes and defaults to 240 seconds. For non-stream `generateText`, the SDK derives request-scoped Undici `headersTimeout` and `bodyTimeout` values as `timeoutMs + 60_000` without changing the process-global dispatcher.
72
85
  - `fallbackModels`: deprecated, ignored compatibility field. The provider emits a standard unsupported-option warning when supplied; remove it from callers. It is not Claude Code's upstream Fable safety-refusal fallback.
73
- - `imageGeneration`: OpenAI/Codex non-stream only. Enables Codex's built-in `image_generation` tool for that request.
74
- - `imageGenerationOptions`: optional `quality: "low" | "medium" | "high"`, `size` (`auto` or positive-integer `WIDTHxHEIGHT`), and `background: "auto" | "opaque" | "transparent"`. Transparent requests use Codex standalone Images with `gpt-image-2`; other requests use the hosted Responses tool. Transparent mode rejects tools/structured output, supports up to five references, validates real transparent and visible PNG pixels, and never invokes background-removal fallback. Size is a positive-integer WIDTHxHEIGHT hint, not an exact-output guarantee; omit it or use auto and put the desired aspect ratio in the prompt. Do not impose public Images API 16-pixel/4K rules on the private subscription route. Both routes expose observations (`codex-responses` or `codex-images`); hosted model is optional. PNG dimensions come from bytes; JPEG/WebP size comes from backend metadata. Hosted image cost is null, and arbitrary/auto size hints never receive a made-up price-table estimate. Returned size/quality may differ from requested settings; read `result.providerMetadata.qgrid.imageGeneration` or per-file `result.content` metadata. `result.files[]` contains bytes/mediaType only. Logs separate `requestedOptions` from `observedGeneration`; image usage is separate from the zero text-driver usage on this route.
86
+
87
+ `imageGeneration` and `imageGenerationOptions` are not public SDK options. Language-model generation and streaming reject either key before transport, directing callers to `generateImage` with `qgrid.image()`. Use flat `QgridImageProviderOptions`; do not add deprecated language-model shims for the removed fields.
75
88
 
76
89
  `providerOptions.qgrid` does not currently support `projectName` or `project_name`. Prefer `QGRID_PROJECT_NAME` for the default project label; use config `projectName` only when a caller needs to override that default.
77
90
 
@@ -131,7 +144,7 @@ const result = await generateText({
131
144
  Fast mode contract:
132
145
 
133
146
  - It is OpenAI/Codex-only. Do not send it to `anthropic/*` models.
134
- - It works on both `generateText` and `streamText`; image generation remains non-stream for unrelated reasons.
147
+ - It works on both `generateText` and `streamText`; it is not an image-model option.
135
148
  - Omit `serviceTier` for normal/default routing. qgrid's current API accepts `"fast"` and `"flex"`; callers should not send Codex's normalized `"priority"` value directly.
136
149
  - qgrid is a pass-through for this selection. Codex applies the tier only when its Fast mode feature is enabled and the selected model advertises support; otherwise Codex can omit the unsupported tier.
137
150
  - Fast mode changes upstream service-tier routing only. It does not change token weights or client concurrency.
@@ -139,7 +152,7 @@ Fast mode contract:
139
152
 
140
153
  ## Request construction
141
154
 
142
- The provider sends `POST /api/qgrid/query` for generate and `prepareStream` plus SSE for stream.
155
+ The language provider sends `POST /api/qgrid/query` for generate and `prepareStream` plus SSE for stream.
143
156
 
144
157
  Payload responsibilities:
145
158
 
@@ -153,9 +166,8 @@ Payload responsibilities:
153
166
  - Preserve and resend `runContext` for tool-call follow-ups.
154
167
  - Let the server infer single-turn completion versus an open tool-call run. Logging-disabled tool calls still use the SDK's local pending-call correlation and continue without a request-log id.
155
168
  - Derive/store/resend opaque OpenAI affinity coordinates by `sessionKey`.
156
- - Send `imageGeneration` and `imageGenerationOptions` when configured.
157
- - Send AI SDK multimodal image/file message parts as qgrid `input` only when `imageGeneration` is enabled. Normal non-image-generation calls keep `user_prompt` text-only and do not forward image input.
158
- - Reject oversized reference-image data URLs before the request leaves the SDK. Reference images travel as JSON data URLs; callers should compress/resize large photos, preferably to WebP/JPEG.
169
+ - Keep language-model prompts text-only. Ignore image parts with an unsupported warning; this adapter does not support general vision.
170
+ - The separate image provider converts `generateImage` prompt references to qgrid `input` and rejects oversized inline reference images before transport. Callers should compress/resize large reference photos, preferably to WebP/JPEG.
159
171
 
160
172
  Tools and `jsonSchema` can be used together as of qgrid 2.5.4. On OpenAI the
161
173
  server composes them into one strict action envelope enforced by constrained
@@ -194,9 +206,7 @@ rejected.
194
206
 
195
207
  When tools are present, qgrid sends tool definitions to the server as `tools`; it does not send them to OpenAI or Anthropic as native provider tools. The server converts them into a strict structured-output schema and maps the model's structured result back into AI SDK `tool-call` content.
196
208
 
197
- Image generation is different from AI SDK client tools: it is an opt-in Codex-hosted tool exposed by qgrid through `providerOptions.qgrid.imageGeneration`. Use `generateText`; `streamText` rejects image generation before opening the stream.
198
-
199
- Reference images for image generation use normal AI SDK multimodal message parts. The provider wraps image/file base64 as `data:${mediaType};base64,...` qgrid `input` items. This is intentionally scoped to image-generation requests, not general vision/chat behavior.
209
+ SDK image generation uses `ImageModelV3`, independently of AI SDK language-model tools. Pass references through `generateImage`'s `prompt: { text, images }`; the image provider wraps inline image bytes as data URLs and accepts HTTPS reference URLs. This supports image generation/editing, not general vision chat. Raw/server image flags remain available for dashboard chat and internal routing; they are not part of `QgridProviderOptions`.
200
210
 
201
211
  ## Response mapping
202
212
 
@@ -206,13 +216,12 @@ Since 2.10.0, `generateText` exposes the parent request-log ID at `result.provid
206
216
 
207
217
  Update both server and SDK to use this metadata. The field is omitted when logging is disabled or an older server does not return it. Tool-run steps share the parent ID; `runContext.requestLogId` remains separate continuation state. This does not expose external-provider telemetry logger IDs or add IDs to stream delta/error events.
208
218
 
209
- qgrid response content maps to AI SDK content:
219
+ Language-model response content maps to AI SDK content:
210
220
 
211
221
  - qgrid `text` -> AI SDK text content.
212
222
  - qgrid `tool-call` -> AI SDK tool-call content.
213
- - qgrid `image` -> AI SDK file content with the returned `mediaType` (`image/png`, `image/jpeg`, or `image/webp`). Older responses without `mediaType` retain the PNG default.
214
223
 
215
- `imageGenerationOptions.outputFormat` accepts `png | jpeg | webp` for the hosted Responses route and maps to `image_generation.output_format`. Omission retains PNG. The standalone transparent route only supports PNG; a transparent request with JPEG/WebP is rejected before generation. Preserve the returned MIME type through response content, SDK file parts, and request-log image data URLs.
224
+ The image provider maps direct Images output to standard AI SDK `image`/`images` and image `usage`, preserving generation metadata under `providerMetadata.qgrid.images[]`. It supports PNG output only. Raw/server hosted Responses output can be PNG/JPEG/WebP; preserve its actual MIME type in raw content and request-log data URLs.
216
225
 
217
226
  `finishReason` maps `tool-calls` when qgrid returns tool calls.
218
227
 
@@ -220,7 +229,7 @@ For tools plus structured output, qgrid serializes the final schema-constrained
220
229
  `answer` as JSON text. AI SDK then parses and validates that text into
221
230
  `Output.object`'s `output`. This path requires both server and SDK 2.5.4 or later.
222
231
 
223
- When `imageGeneration` was requested and the server returns no image part, the AI SDK provider throws a version-skew/error guard instead of silently accepting text-only output.
232
+ The image provider requires exactly one output from route `codex-images` per call and rejects text-only, hosted-route, or multiple-image responses with a server/SDK version-skew guard.
224
233
 
225
234
  For a successful Fable refusal fallback (Fable 5 or 5.1):
226
235
 
@@ -231,20 +231,20 @@ Sources:
231
231
 
232
232
  Key decisions:
233
233
 
234
- - Image generation is OpenAI/Codex-only and request-level opt-in outside dashboard chat. In 2.9.8, chat explicitly opts into raw `imageGeneration: "auto"` each turn. Most requests use the Codex-hosted `image_generation` tool. Transparent backgrounds use Codex standalone Images because the hosted route rejected them in live probes. Do not globally enable image tools for other callers: that would change their tool framing and cache behavior.
234
+ - Image generation is OpenAI/Codex-only and request-level opt-in outside dashboard chat. Chat explicitly opts into raw `imageGeneration: "auto"` each turn. Auto mode registers one internal `generate_image` function and calls standalone Images for ordinary and transparent backgrounds. Raw explicit chat-model `imageGeneration: true` retains its hosted route for non-transparent images. Public SDK image requests use `qgrid.image()` / `generateImage` and standalone Images for any background, following AI SDK's separate `ImageModelV3` contract. The language-model SDK has no image-generation options; the former boolean and nested options are removed without deprecated shims. Do not add a public image-tool factory or globally enable image tools for other callers: that would change their tool framing and cache behavior.
235
235
  - It is non-stream only. Codex returns completed base64 image payloads, not useful image deltas.
236
236
  - Image turns send full input directly and retain no provider conversation state. This prevents base64 payloads from entering reusable state.
237
- - qgrid performs preflight capability/model gates. Explicit `imageGeneration: true` requires an image; `"auto"` allows normal text replies but still rejects attempted image generation without a completed image. The public AI SDK retains its boolean option and image-required contract.
238
- - Returned images are inline base64 content parts for consumers and AI SDK `file` parts. Reference images are accepted through AI SDK multimodal message parts only on the `imageGeneration` path, and are transported as JSON data URLs with SDK-side size guarding.
239
- - `imageGenerationOptions` supports quality, size, and background. Size accepts `auto` or positive-integer `WIDTHxHEIGHT` hints; the subscription backend can ignore them. Put desired ratios in the prompt, report observed output settings, and never silently resize. Hosted image cost is unknown because image usage/model are not reliably exposed. Only legacy responses without observations retain known three-size pricing assumptions. Transparent-route image usage is priced separately, and no background-removal fallback is allowed.
237
+ - qgrid performs preflight capability/model gates. Raw `imageGeneration: true` requires an image; `"auto"` allows normal text replies but still rejects attempted image generation without a completed image. Public `generateImage` requires one direct Images output per provider call; AI SDK batches requests for `n` outputs.
238
+ - Raw returned images are inline base64 content parts. The image SDK maps them to AI SDK `image`/`images` and takes references through standard `prompt: { text, images }`, with JSON data URL size guarding and HTTPS reference URL support. The language SDK ignores image parts with a warning and does not implement general vision.
239
+ - Raw/server `imageGenerationOptions` supports quality, size, and background. The public image SDK uses flat `QgridImageProviderOptions` and standard top-level `size`. The subscription backend can ignore size and quality hints. Put desired ratios in the prompt, report observed output settings, and never silently resize. Reported image usage is priced at public token rates; without it, use the requested quality/size table with a default-size price for auto/custom hints. Preserve observed settings separately from pricing assumptions. No background-removal fallback is allowed.
240
240
  - qgrid does not manage generated or reference images as durable assets through a `generated_images` table or object-storage layer. Current request logs can still contain inline image data URLs for inspection and synthetic `image_generation` tool-call steps; reference input images live in the first synthetic step's `tool_args.inputImages`.
241
- - Image cost is stored separately in `request_logs.image_cost_usd`; `request_logs.cost_usd` remains the Codex driver model token cost. Hosted image cost is unknown (`null`). Standalone image costs are public-price estimates from reported image usage. Only legacy responses without observations and with known size prices retain size-table estimates. List rows and filtered totals mark known subtotals when image costs are unknown; `totalCost.unknownImageCostCount` counts those requests under the same filters.
241
+ - Image cost is stored separately in `request_logs.image_cost_usd`; `request_logs.cost_usd` remains the Codex driver model token cost. Image costs use reported image usage at public token rates, or per-image quality/size estimates when usage is absent. Auto/custom sizes fall back to the 1536x1024 price; omitted quality uses medium. Rows and totals display combined costs as dollar amounts without Cost labels, without Known/Estimated labels or pricing disclaimers. Existing stored null costs are not backfilled; `totalCost.unknownImageCostCount` remains for API compatibility.
242
242
  - Inspect current plans, tests, and implementation before changing behavior; the Codex tool surface can change underneath qgrid.
243
243
 
244
- The 2.9.8 chat design removes the discarded client `request_image_generation` decision step, which asked GPT to choose a tool and then called GPT again for ordinary generation. Native `image_generation` handles ordinary generation directly. A separate qgrid-defined `generate_transparent_image` function is necessary to reach the standalone transparent endpoint, but the server executes it with the same token and cancellation signal without another GPT request. Routing belongs to the server; the chat only sends context and displays results.
244
+ The current chat design registers `generate_image` for both ordinary and transparent images instead of splitting native hosted generation from a transparent-only function. The driver decides whether an image is needed, writes a self-contained prompt, and chooses `transparent_background` and `num_last_images_to_include` (0–5). Zero references starts a fresh image; a positive count selects only the last N conversation images, preserving their chronological order. The server executes the standalone request with the same token and cancellation signal without a second text-driver request. Routing belongs to the server; the chat only sends context and displays results. This preserves conversational interpretation while sharing direct Images execution with explicit SDK generation.
245
245
 
246
246
  Image edits need complete prior image bytes, not just text history or an assumption that the backend remembers earlier outputs. Chat replays uploaded and generated images, representing generated images as user visual references because assistant `input_image` blocks are invalid. Image previews report actual extension, decoded byte size, and natural dimensions. Uploaded images describe the existing resized JPEG sent to the model; generated images retain their returned bytes.
247
247
 
248
- Model labels show requested model to image model using serving metadata or the image model encoded in the logged cost method. The cost-method fallback remains an accounting assumption, not new evidence of the upstream model version. The UI has no image-generation or assumption badge and never fixes the name to `gpt-image-2.5`. Hosted and auto transparent logs retain driver identity and token costs separately from image metadata/cost.
248
+ Model labels show requested model to image model using serving metadata or the image model encoded in the logged cost method. The cost-method fallback remains an accounting assumption, not new evidence of the upstream model version. The UI has no image-generation or assumption badge and never fixes the name to `gpt-image-2.5`. The `gpt-image-2` wire ID also does not prove Images 2.0 serving weights or selectable 2.5 variants. Hosted and auto image logs retain driver identity and token costs separately from image metadata/cost; direct `qgrid.image()` requests have no driver cost.
249
249
 
250
- Prompt wording alone is not a reliable format control: a 2026-09-29 probe returned PNG for a WebP prompt and WebP with explicit `outputFormat: "webp"`. Model prose contradicted the output and must not be used as evidence. Transparent generation remains PNG-only, and this release does not infer format from chat prose or introduce post-generation conversion.
250
+ Prompt wording alone is not a reliable format control: a 2026-09-29 hosted-route probe returned PNG for a WebP prompt and WebP with explicit `outputFormat: "webp"`. Model prose contradicted the output and must not be used as evidence. Auto chat and standalone generation are PNG-only and reject explicit non-PNG options. Do not infer format from chat prose or introduce post-generation conversion or a hosted fallback.
@@ -48,8 +48,10 @@ availability or identical limits on qgrid's ChatGPT-subscription route.
48
48
  ### GPT-6 Astra, Sol, Luna, and GPT-6.1 Sol
49
49
 
50
50
  `openai/gpt-6-astra` uses the existing text generation, streaming and structured-output paths.
51
- The model supports image input, but qgrid keeps its existing SDK restriction: image parts are
52
- forwarded only for opt-in `imageGeneration` reference-image requests, not general vision chat.
51
+ The model supports image input, but qgrid's language-model SDK adapter does not support general
52
+ vision: it ignores image parts with a warning. Image generation/editing references belong to
53
+ `generateImage({ model: qgrid.image(...), prompt: { text, images } })`; dashboard chat uses its
54
+ own raw/server image inputs.
53
55
  Standard API-equivalent prices per million tokens are input $10, cached input $1, cache write
54
56
  $12.50, and output $50. Above 272,000 input tokens, the full request uses 2x input/cache rates and
55
57
  1.5x output rates. These are qgrid's cost estimates, not subscription billing amounts or Fast-tier prices.
@@ -179,23 +181,29 @@ Quota is fetched directly from `https://chatgpt.com/backend-api/wham/usage` with
179
181
 
180
182
  OpenAI usage reports total input, cached input, output, reasoning, and total tokens for the request. Preserve cached input as a subset of input; do not add it to input again when computing cache-hit rate.
181
183
 
182
- Image generation is opt-in and non-stream at the AI SDK interface. The backend still delivers its response through SSE. `imageGenerationOptions.outputFormat` maps to the hosted tool's `output_format` (`png`, `jpeg`, or `webp`); omission retains PNG. Returned bytes determine the MIME type when recognizable, with the reported format as fallback. Preserve that type through qgrid content, AI SDK file parts, and request-log image data URLs. The standalone transparent route remains PNG-only and rejects other output formats. Hosted Responses preserves observed image settings with route `codex-responses`, using PNG byte dimensions or the backend-reported JPEG/WebP size. Unreported model/settings stay absent. Hosted image cost is null because exact image usage/model are unavailable; requested settings cannot establish a price. Legacy responses without observations retain known three-size estimates only.
184
+ Raw/server chat-model image requests are opt-in and non-stream to the caller, while the Responses backend still delivers its response through SSE. Explicit chat-model `imageGeneration: true` retains the hosted image tool for non-transparent requests. On that route, raw `imageGenerationOptions.outputFormat` maps to the hosted tool's `output_format` (`png`, `jpeg`, or `webp`); omission retains PNG. These are not public language-model SDK options: SDK image generation uses `qgrid.image()` / `generateImage` and calls standalone Images directly. Auto chat also calls standalone Images through its internal function and accepts PNG only. Returned bytes determine the MIME type when recognizable, with the reported format as fallback. Preserve that type through qgrid content and request-log image data URLs. Standalone Images rejects other output formats. Hosted Responses preserves observed image settings with route `codex-responses`, using PNG byte dimensions or the backend-reported JPEG/WebP size. Unreported model/settings stay absent. Image costs use reported image usage when available, otherwise the requested quality/size price table. Auto or unsupported sizes use the 1536x1024 price, and omitted quality uses medium. These are API-price estimates, not observed output settings or subscription charges.
183
185
 
184
- ### Transparent image route
186
+ Hosted image accounting reads `response.completed.response.tool_usage.image_gen`, separately from the driver's top-level `response.usage`. Preserve the response-level image usage on the first hosted output only, and obtain a missing image model from the response's `tools` entry. Do not overwrite per-image observed size/quality with that entry's `auto` configuration. On 2026-10-14, a raw subscription response reported image input/output usage and `model: gpt-image-2-codex`, while echoing `quality: auto` and `size: auto` despite explicit outgoing `high`/`1024x1536`. The raw image output was medium/1037x1517. This is an upstream override; qgrid must preserve observations rather than claim the requested controls were honored. Image costs remain GPT Image 2 public-price equivalents, not subscription billing.
185
187
 
186
- Explicit `imageGeneration: true` with `imageGenerationOptions.background: "transparent"` switches from hosted Responses to Codex `images/generations` or `images/edits`. The same selected subscription token, cancellation deadline, in-flight tracking, and one definitive 401 refresh apply. Use JSON content negotiation; do not reuse the Responses SSE Accept header. Never replay an ambiguous generation or fall back to opaque output/IS-Net. This explicit route runs `gpt-image-2` directly, so actual model metadata differs from the requested driver ID and ordinary driver usage stays zero. Image response usage and actual settings are preserved on `generation`; usage belongs to the first output only. Costs remain explicitly estimated from public prices.
188
+ ### Standalone image routes
187
189
 
188
- The image request adapter preserves text/system context and reference images (maximum five), rejects unsupported structured/tool requests before generation, and validates PNG contents with pngjs. Both a fully transparent pixel and visible content are required. Invalid, opaque, empty, or oversized results fail. Returned dimensions come from the decoded PNG; qgrid never silently resizes. A 2026-09-08 live request returned 1254x1254/medium for requested 1024x1024/high. Do not promise exact size/quality enforcement. See `openai-image-generation.ts` and opt-in `transparent`/`transparent-edit` cases in the image smoke script.
190
+ The explicit image-model route `openai/gpt-image-2` with raw `imageGeneration: true` uses standalone Images for opaque, auto, or transparent backgrounds. It is exposed by `qgrid.image()` / AI SDK `generateImage`; the raw flag is an internal transport detail. Public `generateText` has no image-generation options. Reference inputs select `images/edits`, otherwise `images/generations`. Direct image output is PNG-only; decode and validate returned dimensions, applying the transparent-and-visible pixel requirement only when transparency was explicitly requested. Preserve returned background, quality, model, and image usage without claiming requested hints were honored. Image-model requests cannot use chat tools, structured output, auto-image mode, or streaming. The `gpt-image-2` wire identifier does not establish the serving Images version or selectable 2.5 variant.
189
191
 
190
- ### Automatic dashboard chat images (2.9.8)
192
+ Raw/server chat-model requests with explicit `imageGeneration: true` and `imageGenerationOptions.background: "transparent"` switch from hosted Responses to Codex `images/generations` or `images/edits`. The same selected subscription token, cancellation deadline, in-flight tracking, and one definitive 401 refresh apply. Use JSON content negotiation; do not reuse the Responses SSE Accept header. Never replay an ambiguous generation or fall back to opaque output/IS-Net. This explicit route runs `gpt-image-2` directly, so actual model metadata differs from the requested driver ID and ordinary driver usage stays zero. Image response usage and actual settings are preserved on `generation`; usage belongs to the first output only. Costs remain explicitly estimated from public prices.
191
193
 
192
- Raw `QueryInput.imageGeneration` accepts boolean or `"auto"`; the public AI SDK option remains boolean. OpenAI dashboard chat sends `"auto"` through one non-stream POST per turn. The dispatcher offers native `image_generation` plus a qgrid-defined Responses function, `generate_transparent_image`, reserved for transparent backgrounds. It executes that function server-side against standalone Codex Images using the same selected token and abort signal, without another GPT request. The client must not implement a `request_image_generation` decision loop.
194
+ The direct image request adapter preserves text/system context and reference images (maximum five), rejects unsupported structured/tool requests before generation, and validates PNG contents with pngjs. Explicit transparency requires both a fully transparent pixel and visible content; opaque or entirely empty results fail that requirement. Invalid or oversized results fail for every background. Returned dimensions come from the decoded PNG; qgrid never silently resizes. A 2026-09-08 live request returned 1254x1254/medium for requested 1024x1024/high. Do not promise exact size/quality enforcement. See `openai-image-generation.ts` and opt-in `transparent`/`transparent-edit` cases in the image smoke script.
193
195
 
194
- Auto mode accepts text-only completion when no image tool was attempted. Explicit `true` still requires an image, and an attempted image tool that returns no complete image still fails. Caller tools and `outputSchema` are incompatible with auto mode. The transparent function and native image tool must not be mixed in one response; at most one transparent call is allowed.
196
+ ### Automatic dashboard chat images
195
197
 
196
- Auto transparent turns preserve the Responses driver model and token usage; image model, settings, and usage remain separate generation metadata. Do not apply the explicit standalone route's zero-driver accounting to auto turns. Full image history is replayed on each turn without reusable provider conversation state. Global image-tool opt-in remains unchanged outside chat to preserve regular callers' tool framing and cache behavior.
198
+ Raw `QueryInput.imageGeneration` accepts boolean or `"auto"`; these flags are absent from the public AI SDK language-model options. OpenAI dashboard chat sends `"auto"` through one non-stream POST per turn. The dispatcher registers only the qgrid-defined Responses function `generate_image`, with server-validated arguments `prompt: string`, `transparent_background: boolean`, and `num_last_images_to_include: integer` from 0 to 5. Its wire setting is `strict: false`, matching Codex: the 2026-10-14 live Astra probe failed with `strict: true` even after removing bounds, and succeeded with `strict: false` with bounds retained. Keep server-side required-field/type/range/extra-field validation. It does not register the native hosted `image_generation` tool in auto mode. The server executes the function against standalone Codex Images with `model: gpt-image-2`, using the same selected token and abort signal, without another text-driver request. Both ordinary and transparent images use this route. The client must not implement a `request_image_generation` decision loop; no public SDK tool factory is exposed.
197
199
 
198
- The 2026-09-29 hosted-route probe returned PNG for a prompt-only WebP request and WebP for explicit `imageGenerationOptions.outputFormat: "webp"`. Verify bytes rather than model claims. The standalone transparent endpoint still returns PNG; no format conversion is added. See `output/webp-prompt-probe-2026-09-29/verification.md` and `docs/solutions/architecture-patterns/qgrid-native-auto-image-generation-2.9.8.md`.
200
+ The driver receives complete text and image history and writes a self-contained generation/edit prompt. `num_last_images_to_include: 0` starts a fresh generation, even when historical images exist. A positive count selects the last N conversation images in chronological order for editing, up to five. Do not pass all history to the Images endpoint or assume it retains previous images. Use the selected references and the function's prompt for the direct request.
201
+
202
+ Auto mode accepts text-only completion when no image tool was attempted. Explicit `true` still requires an image, and an attempted image tool that returns no complete image still fails. Caller tools and `outputSchema` are incompatible with auto mode. At most one `generate_image` call is allowed per turn. Auto image output is PNG-only; reject explicit JPEG/WebP options before generation rather than converting output or falling back to hosted Responses.
203
+
204
+ Auto image turns preserve the Responses driver model and token usage; image model, settings, and usage remain separate generation metadata. Their combined cost is a chat-driver estimate plus an image-usage estimate. If the image stage fails after the driver completed, carry that completed driver result internally so error/aborted logs still record its usage and price once. Do not attach partial image output or claim a successful image cost. Do not apply the explicit standalone route's zero-driver accounting to auto turns. Full image history is replayed on each turn without reusable provider conversation state. Global image-tool opt-in remains unchanged outside chat to preserve regular callers' tool framing and cache behavior.
205
+
206
+ The 2026-09-29 hosted-route probe returned PNG for a prompt-only WebP request and WebP for the raw/server option `imageGenerationOptions.outputFormat: "webp"`. Verify bytes rather than model claims. Standalone Images still returns PNG; no format conversion is added. See `output/webp-prompt-probe-2026-09-29/verification.md` and `docs/solutions/architecture-patterns/qgrid-native-auto-image-generation-2.9.8.md`.
199
207
 
200
208
  ### Size hints and observed dimensions (2026-10-13)
201
209
 
@@ -82,9 +82,11 @@ OpenAI uses the completed Responses event's per-request usage fields.
82
82
 
83
83
  Anthropic normalizes native mutually exclusive categories by summing input + cache creation + cache read into total input. Prefer positive Claude Code `total_cost_usd`; otherwise calculate from the actual serving model and TTL split.
84
84
 
85
- For image requests, keep `cost_usd` as the Codex driver model token cost. Hosted image output costs use quality/size assumptions because image-tool usage is not exposed. Explicit transparent standalone Images requests run no driver and report zero driver usage/cost; auto transparent turns preserve the Responses driver model and usage while keeping image usage/settings separately. Image usage and actual settings are preserved as `observedGeneration` alongside `requestedOptions` in the synthetic tool log. Their separate image cost uses reported usage with an explicitly conservative public-price estimate. Treat `image_cost_usd` as a price-table estimate that may be inaccurate if Codex changes its underlying image accounting.
85
+ For image requests, keep `cost_usd` as the Codex driver model token cost. Hosted image usage comes from `response.tool_usage.image_gen`, separately from the driver's `response.usage`. Explicit standalone Images requests, including `qgrid.image()`, run no driver and report zero driver usage/cost. Auto image turns preserve the Responses driver model and usage for every background while keeping standalone image usage/settings separately. Image usage and actual settings are preserved as `observedGeneration` alongside `requestedOptions` in the synthetic tool log. Price reported image usage at public token rates; only fall back to requested quality/size estimates when usage is absent. Display combined driver and image costs as dollar amounts without Cost labels; omit pricing disclaimers and cost-source metadata from the dashboard. Treat `image_cost_usd` as an API-price estimate, not subscription billing.
86
86
 
87
- For `imageGeneration: "auto"`, set `is_image_generation` only when returned content actually contains an image; offering image tools is not sufficient. Keep stored `model_name` as the driver for hosted and auto transparent turns. Dashboard requested-to-image labels can derive the image model from serving/image-cost metadata without changing the stored driver or adding image-generation/assumption badges.
87
+ For `imageGeneration: "auto"`, set `is_image_generation` only when returned content actually contains an image; offering the `generate_image` function is not sufficient. Keep stored `model_name` as the driver for hosted and auto image turns. Dashboard requested-to-image labels can derive the image model from serving/image-cost metadata without changing the stored driver or adding image-generation/assumption badges.
88
+
89
+ If the auto image stage fails or is cancelled after the chat driver has completed, preserve the completed driver result as internal failure metadata. The frame converts it with the same usage/pricing mapper used on success; terminal logging writes one generate step before aggregating the run and retains the actual/requested model and token. The parent remains `error` or `aborted` with the original error message. Do not write a completed image step, invent an image cost, expose failure metadata in the HTTP error, or record this step again on success. A driver that never completed and a direct image-only call have no completed chat usage to retain. `logger: false` still creates no logs. Log-writing failures must not replace the original generation failure.
88
90
 
89
91
  Reference images for image generation are not stored in `request_logs.user_prompt`; that field remains the text prompt. For inspection in the detail view, qgrid stores reference image metadata/base64 in the synthetic `image_generation` step's `tool_args.inputImages`. If Codex returns multiple image outputs, store `inputImages` only on the first synthetic image tool step so multi-output logs do not duplicate the same input payload on every output.
90
92
 
@@ -62,13 +62,13 @@ In 2.9.8, list and detail model labels show requested model to image model for g
62
62
 
63
63
  The list header ends its filter row with a reload button (2.10.2+) that calls the generated hooks' `refresh()` for the list and the total-cost query with the current filters; it does not refetch the token/project/model filter options, and TanStack Query's `isRefreshing` disables it while a reload is in flight.
64
64
 
65
- Structured output visibility: the list has an `Fmt` column (`json` badge from `is_structured` vs `text`) and a `broken` badge driven by `response_json_ok = false`; there is no broken-JSON list filter. At review time, all 114,647 succeeded structured rows had `response_json_ok = true` and zero false rows existed; the unindexed zero-result lookup scanned 1,286,016 rows and took about 3 seconds. Before reinstating the filter, validate the failure-signal semantics and index the predicate. The detail page shows a dark Response Type panel (zod expression by default, `type` declaration via toggle) rendered server-side from the stored `json_schema`. Plain-text rows render no panel. Rows written by pre-2.7.2 servers show `text`.
65
+ Structured output visibility: the list has an `Fmt` column (`img` when `is_image_generation` is true, otherwise `json` when `is_structured` is true, otherwise `text`) and a `broken` badge driven by `response_json_ok = false`; there is no broken-JSON list filter. At review time, all 114,647 succeeded structured rows had `response_json_ok = true` and zero false rows existed; the unindexed zero-result lookup scanned 1,286,016 rows and took about 3 seconds. Before reinstating the filter, validate the failure-signal semantics and index the predicate. The detail page shows a dark Response Type panel (zod expression by default, `type` declaration via toggle) rendered server-side from the stored `json_schema`. Plain-text rows render no panel. Rows written by pre-2.7.2 servers show `text`.
66
66
 
67
67
  Tool contract visibility: the detail page renders a Tools panel between Response Type and Steps for requests whose `tools` is non-empty. It starts collapsed and its header shows the tool count plus a responsive, truncated name list built from the detail subset — collapsed views issue no extra query. Expanding fires `toolsView` once and caches it for that request id, since a stored tool contract never changes. Each card shows the name, the full description, the parameter count, and a compact display-only zod shape; enum values past the sixth collapse to `/* +N */`, with the full shape reachable through hover and keyboard focus. All JSON Schema conversion happens server-side — the web renders completed strings and holds no schema logic.
68
68
 
69
69
  Chat token targeting: the dashboard chat panel can pin a conversation to one account. It defaults to automatic distribution and lists only active tokens whose provider matches the selected model, sending the choice as `tokenName` so the request lands on exactly that account with no fallback. Changing either the model or the token resets the existing conversation coordinates, because a thread's prompt cache belongs to the account that built it. A selection that disappears — deactivated or deleted — is cleared automatically, but the selection is preserved while the token list is still loading so a slow fetch does not silently reset the user's choice.
70
70
 
71
- OpenAI chat in 2.9.8 uses one non-stream POST with `imageGeneration: "auto"` per turn, allowing native image generation or an ordinary text response. The server handles transparent image calls directly. Keep the removed image-generation checkbox, background checkbox, automatic-generation label, and client decision tool out of the UI. Replay full text and image history, including generated images as user visual references. Abort in-flight requests when chat unmounts.
71
+ OpenAI chat uses one non-stream POST with `imageGeneration: "auto"` per turn, allowing image generation or an ordinary text response. The server registers one `generate_image` function and directly calls subscription Images for ordinary or transparent output. The driver sees full text and image history, writes the image prompt, and chooses the last 0–5 conversation images to include; zero starts a fresh image. Keep the removed image-generation checkbox, background checkbox, automatic-generation label, and client decision tool out of the UI. Replay full text and image history, including generated images as user visual references. Abort in-flight requests when chat unmounts.
72
72
 
73
73
  Clicking an attached or generated image opens a preview with extension, decoded byte size, natural width/height, original-size viewing, and download. Attachment metadata describes the existing resized JPEG conversion sent by chat; generated image bytes are preserved. Do not label attachment preview metadata as the original upload's metadata.
74
74