@cartanova/qgrid-cli 2.8.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/bundle/dist/application/qgrid/expired-token-reminder.js +9 -8
- package/bundle/dist/application/qgrid/qgrid.frame.js +8 -14
- package/bundle/dist/application/qgrid/token-death.js +12 -31
- package/bundle/dist/application/qgrid/token-trigger-setup.js +2 -1
- package/bundle/dist/application/sonamu.generated.js +3 -1
- package/bundle/dist/application/sonamu.generated.sso.js +2 -1
- package/bundle/dist/application/token/token.model.js +66 -47
- package/bundle/dist/application/token/token.types.js +2 -1
- package/bundle/dist/i18n/sd.generated.js +2 -1
- package/bundle/dist/migrations/20260901163614_alter_tokens_add1_alter1.js +15 -0
- package/bundle/dist/utils/providers/anthropic/anthropic-constants.js +13 -3
- package/bundle/dist/utils/providers/anthropic/anthropic-quota.js +3 -2
- package/bundle/dist/utils/providers/common/model-cost.js +9 -11
- package/bundle/dist/utils/providers/openai/openai-refresh.js +6 -4
- package/bundle/src/application/qgrid/expired-token-reminder.test.ts +8 -6
- package/bundle/src/application/qgrid/expired-token-reminder.ts +9 -8
- package/bundle/src/application/qgrid/qgrid.frame.test.ts +22 -0
- package/bundle/src/application/qgrid/qgrid.frame.ts +8 -15
- package/bundle/src/application/qgrid/token-death.test.ts +62 -58
- package/bundle/src/application/qgrid/token-death.ts +15 -39
- package/bundle/src/application/qgrid/token-trigger-setup.test.ts +6 -0
- package/bundle/src/application/qgrid/token-trigger-setup.ts +1 -0
- package/bundle/src/application/sonamu.generated.http +0 -27
- package/bundle/src/application/sonamu.generated.sso.ts +1 -0
- package/bundle/src/application/sonamu.generated.ts +3 -0
- package/bundle/src/application/token/token.entity.json +7 -0
- package/bundle/src/application/token/token.model.test.ts +112 -37
- package/bundle/src/application/token/token.model.ts +76 -48
- package/bundle/src/application/token/token.types.ts +1 -0
- package/bundle/src/i18n/sd.generated.ts +1 -0
- package/bundle/src/migrations/20260901163614_alter_tokens_add1_alter1.ts +15 -0
- package/bundle/src/utils/providers/anthropic/anthropic-constants.test.ts +25 -1
- package/bundle/src/utils/providers/anthropic/anthropic-constants.ts +23 -4
- package/bundle/src/utils/providers/anthropic/anthropic-quota.test.ts +7 -1
- package/bundle/src/utils/providers/anthropic/anthropic-quota.ts +3 -1
- package/bundle/src/utils/providers/anthropic/claude-session.test.ts +14 -10
- package/bundle/src/utils/providers/common/model-cost.test.ts +19 -19
- package/bundle/src/utils/providers/common/model-cost.ts +19 -16
- package/bundle/src/utils/providers/openai/openai-refresh.test.ts +16 -7
- package/bundle/src/utils/providers/openai/openai-refresh.ts +2 -2
- package/bundle/web-dist/client/assets/{index-D208L33c.js → index-DDSQ_6GL.js} +4 -4
- package/bundle/web-dist/client/assets/{logs-noN5Aka6.js → logs-D2OMhBlx.js} +1 -1
- package/bundle/web-dist/client/assets/{monit-Ber13xcA.js → monit-CYLYHrc-.js} +1 -1
- package/bundle/web-dist/client/assets/{routes-3_xetpSK.js → routes-DlG0sJHR.js} +2 -2
- package/bundle/web-dist/client/assets/{services.generated-Da1Eq5_R.js → services.generated-C6jfEwfK.js} +1 -1
- package/bundle/web-dist/client/assets/{settings-C5sHCg8X.js → settings-BeCBndh_.js} +1 -1
- package/bundle/web-dist/client/assets/{show-BhTgGnkc.js → show-BdmzN1As.js} +1 -1
- package/bundle/web-dist/client/assets/{tokens-R7obwdAV.js → tokens-KCfMOeTH.js} +1 -1
- package/bundle/web-dist/client/assets/{use-oauth-login-flow-CRAqRZXX.js → use-oauth-login-flow-2dSjIvBk.js} +1 -1
- package/bundle/web-dist/client/assets/{use-update-token-mutation-CZ57EKA1.js → use-update-token-mutation-BIvO70-M.js} +1 -1
- package/bundle/web-dist/client/index.html +2 -2
- package/bundle/web-dist/server/assets/{lazyRouteComponent-V1S8gqMS.js → lazyRouteComponent-C_k4R5n6.js} +2 -2
- package/bundle/web-dist/server/assets/{logs-2Tkqcncq.js → logs-lBOU5eUP.js} +2 -2
- package/bundle/web-dist/server/assets/{monit-DrJ15xJL.js → monit-BW2X1D23.js} +2 -2
- package/bundle/web-dist/server/assets/{plus-DCai02Na.js → plus-DilmHifT.js} +1 -1
- package/bundle/web-dist/server/assets/{react-dom-BFiG5Daf.js → react-dom-CHRRsKwb.js} +1 -1
- package/bundle/web-dist/server/assets/{routes-Ct3I9263.js → routes-CxMHXfdH.js} +5 -5
- package/bundle/web-dist/server/assets/{services.generated-y7xVOTCo.js → services.generated-buX3cJzR.js} +1 -9
- package/bundle/web-dist/server/assets/{settings-JznSjwGQ.js → settings-CFoXQaAg.js} +3 -3
- package/bundle/web-dist/server/assets/{show-D8kuAFbc.js → show-CcZJO6WF.js} +2 -2
- package/bundle/web-dist/server/assets/{tokens-PR42jF1J.js → tokens-C32lycTe.js} +6 -5
- package/bundle/web-dist/server/assets/{use-oauth-login-flow-C-33Z5HA.js → use-oauth-login-flow-DCjxIa80.js} +1 -1
- package/bundle/web-dist/server/assets/{use-update-token-mutation-BNaRPVaX.js → use-update-token-mutation-CqK8i8Fa.js} +1 -1
- package/bundle/web-dist/server/entry-server.generated.js +46 -23
- package/package.json +1 -1
- package/skills/qgrid/SKILL.md +1 -1
- package/skills/qgrid/references/ai-sdk-provider-contract.md +3 -3
- package/skills/qgrid/references/anthropic-claude-code-runtime.md +13 -9
- package/skills/qgrid/references/cli-env-and-server-boot.md +1 -1
- package/skills/qgrid/references/decision-rationale.md +2 -1
- package/skills/qgrid/references/request-log-run-lifecycle.md +1 -1
- package/skills/qgrid/references/sonamu-api-web-flow.md +2 -0
- package/skills/qgrid/references/token-auth-quota-lifecycle.md +17 -13
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as matchMutation, B as __require, C as infiniteQueryBehavior, D as functionalUpdate, E as notifyManager, F as skipToken, I as focusManager, L as Subscribable, M as noop$1, N as partialMatchKey, O as hashKey, P as resolveStaleTime, R as __commonJSMin, S as Mutation, T as onlineManager, V as __toESM, b as require_jsx_runtime, c as SD, d as _enum, f as number$1, j as matchQuery, k as hashQueryKeyByOptions, l as SUPPORTED_LOCALES, m as string$2, n as QgridService, o as TokenService, p as object, u as setLocale, v as QueryClientProvider, w as Query, x as require_react, z as __exportAll } from "./assets/services.generated-
|
|
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-
|
|
3
|
-
import { n as require_shim, t as require_react_dom } from "./assets/react-dom-
|
|
1
|
+
import { A as matchMutation, B as __require, C as infiniteQueryBehavior, D as functionalUpdate, E as notifyManager, F as skipToken, I as focusManager, L as Subscribable, M as noop$1, N as partialMatchKey, O as hashKey, P as resolveStaleTime, R as __commonJSMin, S as Mutation, T as onlineManager, V as __toESM, b as require_jsx_runtime, c as SD, d as _enum, f as number$1, j as matchQuery, k as hashQueryKeyByOptions, l as SUPPORTED_LOCALES, m as string$2, n as QgridService, o as TokenService, p as object, u as setLocale, v as QueryClientProvider, w as Query, x as require_react, z as __exportAll } from "./assets/services.generated-buX3cJzR.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-C_k4R5n6.js";
|
|
3
|
+
import { n as require_shim, t as require_react_dom } from "./assets/react-dom-CHRRsKwb.js";
|
|
4
4
|
import minpath from "node:path";
|
|
5
5
|
import minproc from "node:process";
|
|
6
6
|
import { fileURLToPath as urlToPath } from "node:url";
|
|
@@ -46080,15 +46080,35 @@ function tokenTargetPayload(tokenName) {
|
|
|
46080
46080
|
//#endregion
|
|
46081
46081
|
//#region src/components/qgrid/ChatWidget.tsx
|
|
46082
46082
|
var CHAT_PROJECT_NAME = "qgrid_chat";
|
|
46083
|
-
var DEFAULT_MODEL = "
|
|
46084
|
-
var
|
|
46085
|
-
"
|
|
46086
|
-
|
|
46087
|
-
|
|
46088
|
-
|
|
46089
|
-
|
|
46090
|
-
|
|
46091
|
-
|
|
46083
|
+
var DEFAULT_MODEL = "anthropic/claude-fable-5-1";
|
|
46084
|
+
var MODEL_PRESET_GROUPS = [{
|
|
46085
|
+
label: "OpenAI",
|
|
46086
|
+
models: [
|
|
46087
|
+
"openai/gpt-5.6-terra",
|
|
46088
|
+
"openai/gpt-5.6-sol",
|
|
46089
|
+
"openai/gpt-5.6-luna",
|
|
46090
|
+
"openai/gpt-5.5",
|
|
46091
|
+
"openai/gpt-5.3-codex-spark"
|
|
46092
|
+
]
|
|
46093
|
+
}, {
|
|
46094
|
+
label: "Anthropic",
|
|
46095
|
+
models: [
|
|
46096
|
+
"anthropic/claude-fable-5-1",
|
|
46097
|
+
"anthropic/claude-fable-5",
|
|
46098
|
+
"anthropic/claude-opus-5",
|
|
46099
|
+
"anthropic/claude-sonnet-5",
|
|
46100
|
+
"anthropic/claude-opus-4-8",
|
|
46101
|
+
"anthropic/claude-opus-4-7",
|
|
46102
|
+
"anthropic/claude-opus-4-6",
|
|
46103
|
+
"anthropic/claude-opus-4-5",
|
|
46104
|
+
"anthropic/claude-sonnet-4-6",
|
|
46105
|
+
"anthropic/claude-sonnet-4-5",
|
|
46106
|
+
"anthropic/claude-haiku-4-5",
|
|
46107
|
+
"anthropic/claude-opus-4-1",
|
|
46108
|
+
"anthropic/claude-opus-4",
|
|
46109
|
+
"anthropic/claude-sonnet-4"
|
|
46110
|
+
]
|
|
46111
|
+
}];
|
|
46092
46112
|
var EFFORT_PRESETS = [
|
|
46093
46113
|
"low",
|
|
46094
46114
|
"medium",
|
|
@@ -46385,10 +46405,13 @@ function ChatWidget() {
|
|
|
46385
46405
|
onChange: (e) => setModel(e.target.value),
|
|
46386
46406
|
disabled: busy,
|
|
46387
46407
|
className: "min-w-0 flex-1 rounded-xl border border-sand-200/80 bg-white px-2 py-1.5 text-[12px] text-sand-800 focus:outline-none focus:border-sienna-300 disabled:opacity-50",
|
|
46388
|
-
children:
|
|
46389
|
-
|
|
46390
|
-
children: m
|
|
46391
|
-
|
|
46408
|
+
children: MODEL_PRESET_GROUPS.map((group) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("optgroup", {
|
|
46409
|
+
label: group.label,
|
|
46410
|
+
children: group.models.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
46411
|
+
value: m,
|
|
46412
|
+
children: m
|
|
46413
|
+
}, m))
|
|
46414
|
+
}, group.label))
|
|
46392
46415
|
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", {
|
|
46393
46416
|
value: effort,
|
|
46394
46417
|
onChange: (e) => setEffort(e.target.value),
|
|
@@ -46625,7 +46648,7 @@ function ChatWidget() {
|
|
|
46625
46648
|
}
|
|
46626
46649
|
//#endregion
|
|
46627
46650
|
//#region src/components/Sidebar.tsx
|
|
46628
|
-
var qgridVersion = "2.
|
|
46651
|
+
var qgridVersion = "2.9.0";
|
|
46629
46652
|
function Sidebar({ className }) {
|
|
46630
46653
|
const pathname = useRouterState({ select: (s) => s.location.pathname });
|
|
46631
46654
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", {
|
|
@@ -68510,15 +68533,15 @@ function RootComponent() {
|
|
|
68510
68533
|
}
|
|
68511
68534
|
//#endregion
|
|
68512
68535
|
//#region src/routes/tokens.tsx
|
|
68513
|
-
var $$splitComponentImporter$5 = () => import("./assets/tokens-
|
|
68536
|
+
var $$splitComponentImporter$5 = () => import("./assets/tokens-C32lycTe.js");
|
|
68514
68537
|
var Route$5 = createFileRoute("/tokens")({ component: lazyRouteComponent($$splitComponentImporter$5, "component") });
|
|
68515
68538
|
//#endregion
|
|
68516
68539
|
//#region src/routes/settings.tsx
|
|
68517
|
-
var $$splitComponentImporter$4 = () => import("./assets/settings-
|
|
68540
|
+
var $$splitComponentImporter$4 = () => import("./assets/settings-CFoXQaAg.js");
|
|
68518
68541
|
var Route$4 = createFileRoute("/settings")({ component: lazyRouteComponent($$splitComponentImporter$4, "component") });
|
|
68519
68542
|
//#endregion
|
|
68520
68543
|
//#region src/routes/monit.tsx
|
|
68521
|
-
var $$splitComponentImporter$3 = () => import("./assets/monit-
|
|
68544
|
+
var $$splitComponentImporter$3 = () => import("./assets/monit-BW2X1D23.js");
|
|
68522
68545
|
var monitSearchSchema = object({ level: _enum([
|
|
68523
68546
|
"all",
|
|
68524
68547
|
"warn",
|
|
@@ -68530,7 +68553,7 @@ var Route$3 = createFileRoute("/monit")({
|
|
|
68530
68553
|
});
|
|
68531
68554
|
//#endregion
|
|
68532
68555
|
//#region src/routes/logs.tsx
|
|
68533
|
-
var $$splitComponentImporter$2 = () => import("./assets/logs-
|
|
68556
|
+
var $$splitComponentImporter$2 = () => import("./assets/logs-lBOU5eUP.js");
|
|
68534
68557
|
var logsSearchSchema = object({
|
|
68535
68558
|
token: string$2().optional(),
|
|
68536
68559
|
project: string$2().optional(),
|
|
@@ -68544,11 +68567,11 @@ var Route$2 = createFileRoute("/logs")({
|
|
|
68544
68567
|
});
|
|
68545
68568
|
//#endregion
|
|
68546
68569
|
//#region src/routes/index.tsx
|
|
68547
|
-
var $$splitComponentImporter$1 = () => import("./assets/routes-
|
|
68570
|
+
var $$splitComponentImporter$1 = () => import("./assets/routes-CxMHXfdH.js");
|
|
68548
68571
|
var Route$1 = createFileRoute("/")({ component: lazyRouteComponent($$splitComponentImporter$1, "component") });
|
|
68549
68572
|
//#endregion
|
|
68550
68573
|
//#region src/routes/requests/show.tsx
|
|
68551
|
-
var $$splitComponentImporter = () => import("./assets/show-
|
|
68574
|
+
var $$splitComponentImporter = () => import("./assets/show-CcZJO6WF.js");
|
|
68552
68575
|
var showSearchSchema = object({ id: number$1() });
|
|
68553
68576
|
var Route = createFileRoute("/requests/show")({
|
|
68554
68577
|
validateSearch: showSearchSchema,
|
package/package.json
CHANGED
package/skills/qgrid/SKILL.md
CHANGED
|
@@ -47,7 +47,7 @@ Request logging is enabled by default. Use `providerOptions.qgrid.logger: false`
|
|
|
47
47
|
- 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.
|
|
48
48
|
- 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.
|
|
49
49
|
- Route provider models by prefix: `openai/*` goes to the OpenAI Codex runtime, `anthropic/*` goes to the Anthropic Claude Code runtime. Prefix-less model fallback is not implemented.
|
|
50
|
-
- Treat Fable 5 safety fallback as upstream Claude Code behavior, not qgrid routing: a classifier refusal can retry on Opus 4.8. Do not add a second qgrid retry. Preserve requested Fable versus actual serving Opus, fallback history, and provider-reported cost; a fresh Claude Code process means the fallback is not sticky across qgrid requests.
|
|
50
|
+
- Treat Fable (5 and 5.1) safety fallback as upstream Claude Code behavior, not qgrid routing: a classifier refusal can retry on Opus 5 or Opus 4.8 depending on refusal category. Do not add a second qgrid retry. Preserve requested Fable versus actual serving Opus, fallback history, and provider-reported cost; a fresh Claude Code process means the fallback is not sticky across qgrid requests.
|
|
51
51
|
- Treat dashboard work as Sonamu API/model/generated-client/web work, not isolated frontend work.
|
|
52
52
|
- 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.
|
|
53
53
|
- 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.
|
|
@@ -213,10 +213,10 @@ For tools plus structured output, qgrid serializes the final schema-constrained
|
|
|
213
213
|
|
|
214
214
|
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.
|
|
215
215
|
|
|
216
|
-
For a successful Fable 5
|
|
216
|
+
For a successful Fable refusal fallback (Fable 5 or 5.1):
|
|
217
217
|
|
|
218
|
-
- `response.modelId` and `providerMetadata.qgrid.model` are the actual serving model,
|
|
219
|
-
- `providerMetadata.qgrid.requestedModel` remains `claude-fable-5`.
|
|
218
|
+
- `response.modelId` and `providerMetadata.qgrid.model` are the actual serving model, an Opus model such as `claude-opus-5` or `claude-opus-4-8`.
|
|
219
|
+
- `providerMetadata.qgrid.requestedModel` remains the requested Fable model, for example `claude-fable-5-1`.
|
|
220
220
|
- `providerMetadata.qgrid.modelFallbacks` preserves the refusal route and optional category/explanation.
|
|
221
221
|
- `providerMetadata.qgrid.costSource` reports whether cost came from Claude Code or qgrid's pricing table. Prefer the provider-reported combined cost for this path.
|
|
222
222
|
|
|
@@ -45,7 +45,8 @@ Anthropic tokens live in an in-memory `Map<tokenId, PooledToken>`.
|
|
|
45
45
|
Quota threshold:
|
|
46
46
|
|
|
47
47
|
- `quota_threshold` checks `five_hour.utilization` for every model and also checks
|
|
48
|
-
`seven_day_overage_included.utilization` for Fable
|
|
48
|
+
`seven_day_overage_included.utilization` for the Fable family (Fable 5 and 5.1,
|
|
49
|
+
`isFableFamilyModel` in `anthropic-constants.ts`), using the higher utilization.
|
|
49
50
|
- A runtime quota-exhausted result invalidates every 60-second usage-cache key used before and
|
|
50
51
|
after a possible OAuth refresh, so the next untargeted request rechecks usage and can select
|
|
51
52
|
another token.
|
|
@@ -76,7 +77,7 @@ claude -p
|
|
|
76
77
|
--model <canonical-model-or-1m-suffix>
|
|
77
78
|
--system-prompt <text> # small system prompt
|
|
78
79
|
--system-prompt-file <path> # large system prompt
|
|
79
|
-
--thinking disabled # omitted for Fable 5 and Opus 5
|
|
80
|
+
--thinking disabled # omitted for Fable 5/5.1 and Opus 5
|
|
80
81
|
--effort <effort-or-low>
|
|
81
82
|
--disable-slash-commands
|
|
82
83
|
--session-id <uuid>
|
|
@@ -96,7 +97,7 @@ Important details:
|
|
|
96
97
|
- Large system prompts over 64 KiB are written to a temporary file to avoid argv `E2BIG` — this
|
|
97
98
|
same branch absorbs large injected schema contracts, so the old 64 KiB schema argv limit no
|
|
98
99
|
longer applies (only the global 512 KiB caller-schema complexity limit remains).
|
|
99
|
-
- `--thinking disabled`, `MAX_THINKING_TOKENS=0`, and adaptive thinking env suppression keep thinking off for existing models. Fable 5
|
|
100
|
+
- `--thinking disabled`, `MAX_THINKING_TOKENS=0`, and adaptive thinking env suppression keep thinking off for existing models. Fable 5 and Fable 5.1 require always-on adaptive thinking (CLI catalog `rejects_disabled_thinking`). Opus 5 defaults to adaptive thinking and rejects disabled thinking at `xhigh`/`max` effort. qgrid omits all three suppressors for these models (`usesAdaptiveThinking`) and uses `effort` to control depth.
|
|
100
101
|
|
|
101
102
|
## Spawn env
|
|
102
103
|
|
|
@@ -109,13 +110,13 @@ Included env:
|
|
|
109
110
|
- `CLAUDE_CODE_OAUTH_TOKEN`
|
|
110
111
|
- `CLAUDE_CONFIG_DIR`
|
|
111
112
|
- `CLAUDE_CODE_DISABLE_AUTO_MEMORY=1`
|
|
112
|
-
- `CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING=1` except for Fable 5 and Opus 5
|
|
113
|
+
- `CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING=1` except for Fable 5/5.1 and Opus 5
|
|
113
114
|
- `CLAUDE_CODE_DISABLE_CLAUDE_MDS=1`
|
|
114
115
|
- `CLAUDE_CODE_DISABLE_TERMINAL_TITLE=1`
|
|
115
116
|
- `CLAUDE_CODE_DISABLE_BUNDLED_SKILLS=1`
|
|
116
117
|
- `CLAUDE_CODE_DISABLE_WORKFLOWS=1`
|
|
117
118
|
- `CLAUDE_CODE_ATTRIBUTION_HEADER=0`
|
|
118
|
-
- `MAX_THINKING_TOKENS=0` except for Fable 5 and Opus 5
|
|
119
|
+
- `MAX_THINKING_TOKENS=0` except for Fable 5/5.1 and Opus 5
|
|
119
120
|
- `CLAUDE_CODE_DISABLE_1M_CONTEXT=1` when model does not support qgrid's 1M path
|
|
120
121
|
- `MAX_STRUCTURED_OUTPUT_RETRIES` for streaming structured output only (not non-streaming `generate`)
|
|
121
122
|
|
|
@@ -184,11 +185,13 @@ cacheCreationInputTokens1h = cache_creation.ephemeral_1h_input_tokens
|
|
|
184
185
|
|
|
185
186
|
## Fable refusal fallback
|
|
186
187
|
|
|
187
|
-
Fable 5
|
|
188
|
+
Fable 5 and Fable 5.1 run upstream safety classifiers. A refusal is a successful HTTP response with
|
|
188
189
|
`stop_reason: "refusal"`, not an HTTP/provider error, and can happen before output or after
|
|
189
|
-
partial streamed output. Claude Code handles the refusal fallback and can retry on Opus
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
partial streamed output. Claude Code handles the refusal fallback and can retry on another Opus
|
|
191
|
+
model; the current CLI routes by refusal category (bio → Opus 5, cyber → Opus 4.8). Do not
|
|
192
|
+
hardcode the fallback target in qgrid: read it from the stream evidence below. This is distinct
|
|
193
|
+
from the CLI `--fallback-model` flag, which is for overload on the default model. qgrid does not
|
|
194
|
+
configure either mechanism or add another retry.
|
|
192
195
|
|
|
193
196
|
The stream adapter must preserve all observable routing evidence:
|
|
194
197
|
|
|
@@ -251,6 +254,7 @@ Model normalization strips provider prefix and `[1m]` for canonical model/cost k
|
|
|
251
254
|
|
|
252
255
|
qgrid's exact 1M support set currently includes:
|
|
253
256
|
|
|
257
|
+
- `claude-fable-5-1`
|
|
254
258
|
- `claude-fable-5`
|
|
255
259
|
- `claude-opus-5`
|
|
256
260
|
- `claude-sonnet-5`
|
|
@@ -66,7 +66,7 @@ When helping a user set up qgrid locally, check for `QGRID_PROJECT_NAME` or conf
|
|
|
66
66
|
|---|---:|---|
|
|
67
67
|
| `QGRID_OPENAI_TRANSPORT` | `websocket` | OpenAI transport selector (`https` or `websocket`). Invalid values fail at dispatcher configuration time. This is the only OpenAI runtime knob; there is no concurrency cap. |
|
|
68
68
|
| `QGRID_OPENAI_AUTOSCALE` / `QGRID_OPENAI_MIN_WORKERS_PER_TOKEN` / `QGRID_OPENAI_MAX_WORKERS_PER_TOKEN` / `QGRID_OPENAI_PERMITS_PER_TOKEN` | ignored | Removed. OpenAI requests run uncapped like Anthropic (stateless per-request execution); setting these has no effect. |
|
|
69
|
-
| `QGRID_TOKEN_WINDOW_KEEPALIVE_ENABLED`
|
|
69
|
+
| `QGRID_TOKEN_WINDOW_KEEPALIVE_ENABLED` | off | Marks this instance as the Anthropic 5-hour window keepalive runner (2.8.0, `ea7feb3`). Only the literal `true` enables it; set it on exactly one instance per shared DB so developer instances do not fire duplicates. Two more gates apply: the global setting `qgrid.tokenWindowKeepaliveEnabled` (`false` turns everything off; changing it reschedules immediately) and the per-token `tokens.keepalive_enabled` flag (default `false`, dashboard settings-panel toggle). In 2.7.6 this env was a default-on global switch with no per-token opt-in. |
|
|
70
70
|
| `MAX_STRUCTURED_OUTPUT_RETRIES` | `1` for structured Anthropic streaming calls | Claude Code structured-output retry count for streaming only, clamped to at least 1 by qgrid. Non-streaming `generate` leaves the variable unset and uses Claude Code's default retry budget. |
|
|
71
71
|
|
|
72
72
|
## Server boot lifecycle
|
|
@@ -33,7 +33,7 @@ Key decisions:
|
|
|
33
33
|
- AI SDK's standard `timeout` owns the client-side total budget and reaches custom providers only as an `AbortSignal`. `providerOptions.qgrid.timeoutMs` separately owns the Anthropic server-side Claude process timer. For non-stream calls, the qgrid SDK may derive its own request-scoped Undici header/body budget from that explicit qgrid value (`timeoutMs + 60s`), but must not mutate the global dispatcher or pretend the original AI SDK timeout number is available.
|
|
34
34
|
- `projectName` is provider config/payload camelCase. `project_name` is the Sonamu/database request-log column. Prefer `QGRID_PROJECT_NAME` as the project-wide default so request logs and metrics remain filterable at volume.
|
|
35
35
|
- `fallbackModels` may exist as a typed future option, but it is not a server wire contract until the server implements fallback routing. Do not forward it as behavior just because the type exists.
|
|
36
|
-
- Fable
|
|
36
|
+
- Fable refusal fallback is an upstream Claude Code safety path to another Opus model (currently Opus 5 or Opus 4.8 by refusal category), not qgrid's reserved `fallbackModels` feature or Claude Code's overload-only `--fallback-model` flag. qgrid observes and reports the actual route and provider cost; it must not retry again or hardcode the target.
|
|
37
37
|
- The SDK should stay a thin adapter. Server APIs own request-log lifecycle, provider dispatch, structured-output emulation, and provider runtime details.
|
|
38
38
|
- Request logging defaults to enabled. `providerOptions.qgrid.logger: false` is the single per-generation opt-out for both native qgrid logs and `createQgridLogger` telemetry logs; it must not alter generation or tool behavior.
|
|
39
39
|
|
|
@@ -144,6 +144,7 @@ Key decisions:
|
|
|
144
144
|
Fable-only `seven_day_overage_included.utilization` when applicable; OpenAI uses
|
|
145
145
|
`primary.usedPercent`.
|
|
146
146
|
- Threshold gates are by token id, not token name. Names are display/logging labels and can change.
|
|
147
|
+
- Token window keepalive is opt-in at two levels since 2.8.0 (`ea7feb3`): an instance fires only with env `QGRID_TOKEN_WINDOW_KEEPALIVE_ENABLED=true`, and only for tokens with `keepalive_enabled = true`. The runner gate exists because developer instances sharing the production DB fired duplicate pings under the 2.7.6 default-on switch; the per-token flag lets shared or credit-metered accounts stay out. The cost is that upgrades start with zero targets until both are set.
|
|
147
148
|
- Lookup failure is fail-open with logs/metrics. Hard failure happens only when usage was read successfully and the token is over threshold.
|
|
148
149
|
- OpenAI applies the gate during token selection. An affinity-preferred token over threshold must fall back to another eligible token when possible.
|
|
149
150
|
|
|
@@ -48,7 +48,7 @@ Tool calls are produced through qgrid structured-output emulation, not native pr
|
|
|
48
48
|
|
|
49
49
|
New request logs store routed model identity in the existing model fields; they do not add a separate provider column:
|
|
50
50
|
|
|
51
|
-
- While the parent is running, `requested_model_name` is the full requested id such as `openai/gpt-5.
|
|
51
|
+
- While the parent is running, `requested_model_name` is the full requested id such as `openai/gpt-5.6-terra` and `model_name` is `NULL`.
|
|
52
52
|
- Completed parent and generate-step model values use full `provider/model` ids.
|
|
53
53
|
- `requested_model_name` preserves the exact requested route, including modifiers such as Anthropic `[1m]`; `model_name` is the actual canonical serving route. They differ after a Fable refusal fallback, for example requested Fable versus serving Opus.
|
|
54
54
|
- A completed multi-step parent uses the final turn's requested and serving models. It never stores the literal model value `mixed`; usage and cost still aggregate across steps, and `cost_source` may be `mixed`.
|
|
@@ -64,6 +64,8 @@ Tool contract visibility: the detail page renders a Tools panel between Response
|
|
|
64
64
|
|
|
65
65
|
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.
|
|
66
66
|
|
|
67
|
+
Chat model presets: the model `<select>` lists the served subset of `QgridSupportedModel` grouped by provider (`MODEL_PRESET_GROUPS` in `ChatWidget.tsx`), newest first, and defaults to `anthropic/claude-fable-5-1`. Ids that the ChatGPT-subscription Codex route no longer serves (`gpt-5.4` and `gpt-5.4-mini` retired 2026-08-31 with `gpt-5.6-terra` / `gpt-5.6-luna` as replacements; `gpt-5.2` and `gpt-5.3-codex` removed earlier) and the catalog-less `claude-sonnet-4-7` stay in the SDK type but are excluded here. web does not depend on ai-sdk, so a new model must be added in both places.
|
|
68
|
+
|
|
67
69
|
The monit tab's vitals strip shows per-provider in-flight counts, an in-flight sparkline, token chips with quota gauges (fed by the dispatcher's 60-second rate-limits cache; `resetsAt` arrives as ms epoch), and a 1-hour per-provider request/error/cache-hit stats line polled separately every 30 seconds.
|
|
68
70
|
|
|
69
71
|
Reference input images for image generation render in the detail view under the user prompt from the first synthetic `image_generation` step's `tool_args.inputImages`. Keep the prompt itself text-only, redact large base64 in request JSON views, and avoid rendering duplicate input previews for multi-output image turns.
|
|
@@ -34,11 +34,13 @@ Use this reference before changing token storage, OAuth flows, token activation,
|
|
|
34
34
|
- `credentials`: JSONB credentials.
|
|
35
35
|
- `name`: display/logging name.
|
|
36
36
|
- `active`: whether provider dispatchers may use it.
|
|
37
|
+
- `reauth_required`: whether a permanent refresh failure proved that OAuth login must be repeated.
|
|
37
38
|
- `ord`: dashboard ordering.
|
|
38
39
|
- `quota_threshold`: nullable integer percentage, validated as 1..100 when present.
|
|
39
40
|
- `weight`: non-null integer 1..100, database default 1. Relative share for weighted round-robin routing of new requests. Weight does not enable or disable a token; that stays on `active`.
|
|
40
41
|
|
|
41
|
-
On creation, `TokenModel.save` applies
|
|
42
|
+
On creation, the database defaults `reauth_required = false`; `TokenModel.save` also applies `quota_threshold = 80` and `weight = 1` independently for any field not provided (skipped entirely when `id` is present).
|
|
43
|
+
`TokenModel.save` is internal only; callers use the curated qgrid token APIs so they cannot write lifecycle fields directly.
|
|
42
44
|
|
|
43
45
|
OpenAI credentials:
|
|
44
46
|
|
|
@@ -77,7 +79,7 @@ Duplicate account replacement is account-identifier based:
|
|
|
77
79
|
- `addToken(provider, credentials, name)`: saves a token directly.
|
|
78
80
|
- `updateToken(id, name?, quotaThreshold?, weight?)`: partial field update through `TokenModel.updateFields`; omitted fields preserve stored values, so one control cannot overwrite another control's setting. `weight` is validated as an integer 1..100. The web client uses a hand-written `useUpdateTokenMutation` hook instead of the generated tanstack-mutation client for this endpoint.
|
|
79
81
|
- `removeToken(id)`: deletes token row.
|
|
80
|
-
- `toggleToken(id)`: flips `active
|
|
82
|
+
- `toggleToken(id)`: atomically flips `active`; it refuses to reactivate a token with `reauth_required = true`, because only OAuth can restore a dead login.
|
|
81
83
|
- `stats()`: reports dispatcher cache stats.
|
|
82
84
|
- `health()`: reports active token cache count and subscriber status.
|
|
83
85
|
- `usage(tokenId?)`: returns provider usage/rate-limit summary.
|
|
@@ -113,8 +115,8 @@ Usage/quota:
|
|
|
113
115
|
|
|
114
116
|
- Usage API is `https://api.anthropic.com/api/oauth/usage`.
|
|
115
117
|
- qgrid caches Anthropic usage by access-token suffix for 60 seconds.
|
|
116
|
-
- Quota threshold uses `five_hour.utilization`; Fable 5
|
|
117
|
-
`seven_day_overage_included.utilization`, and either window can exclude the token.
|
|
118
|
+
- Quota threshold uses `five_hour.utilization`; Fable family models (Fable 5 and 5.1) additionally
|
|
119
|
+
use the usage-credit `seven_day_overage_included.utilization`, and either window can exclude the token.
|
|
118
120
|
- Usage lookup failure is fail-open for routing.
|
|
119
121
|
|
|
120
122
|
## OpenAI OAuth And Refresh Flow
|
|
@@ -154,7 +156,7 @@ Triggers notify on:
|
|
|
154
156
|
|
|
155
157
|
- INSERT
|
|
156
158
|
- DELETE
|
|
157
|
-
- UPDATE when `active`, `credentials`, `provider`, `name`, or `quota_threshold` changes.
|
|
159
|
+
- UPDATE when `active`, `reauth_required`, `credentials`, `provider`, `name`, or `quota_threshold` changes.
|
|
158
160
|
- UPDATE when `weight` changes, through a separate `tokens_weight_changed_upd` trigger owned by the versioned migration `20260710090000_alter_tokens_add_weight.ts`. The boot-time setup SQL intentionally leaves `weight` out of its WHEN clause (test-enforced in `token-trigger-setup.test.ts`) so exactly one trigger fires per weight-only change.
|
|
159
161
|
|
|
160
162
|
`TokenSubscriber`:
|
|
@@ -201,14 +203,15 @@ Anthropic token changes only affect the in-memory token pool because every reque
|
|
|
201
203
|
When a refresh fails in a way that only re-login can fix, qgrid removes the token from routing instead of letting every subsequent request 401.
|
|
202
204
|
|
|
203
205
|
- Detection lives at the existing refresh failure points, not a background scanner. OpenAI classifies on the error code (`refresh_token_expired`, `refresh_token_reused`, `refresh_token_invalidated`) regardless of HTTP status; Anthropic treats refresh 400/401 as auth-dead.
|
|
204
|
-
- Before deactivating, qgrid
|
|
205
|
-
-
|
|
206
|
-
-
|
|
206
|
+
- Before deactivating, qgrid locks the token row and confirms the full credentials used by the failed attempt still match the stored credentials. A successful concurrent refresh therefore wins without a stale failure killing the rotated login. `QgridFrame.refreshToken` also dedups concurrent refreshes per token.
|
|
207
|
+
- Auth death is one conditional `reauth_required=false → true` transaction. Provider-scoped advisory locking serializes the last-active decision, and the update also sets `active=false` when another active token exists. The process whose update affects a row is the only one that sends the Slack death notification, so a shared database produces exactly one alert per event. Manual active toggles never set `reauth_required`, and an expired inactive token cannot be reactivated without completing OAuth.
|
|
208
|
+
- A successful refresh clears a concurrent stale `reauth_required` mark but preserves `active`, so it never overrides an operator's manual inactive choice.
|
|
209
|
+
- The provider's last active token is marked `reauth_required=true` but never auto-deactivated. A systemic failure (client_id revocation, OAuth contract change) would otherwise empty the pool; qgrid logs an error, keeps the token, and sends its own urgent Slack alert. The persisted flag gates repeat alerts across every instance.
|
|
207
210
|
- The other notification is registration: every completed login sends one alert, whether the account is new or replacing an existing row through the account dedup. Manually toggling a token back to active is not a registration and sends nothing.
|
|
208
|
-
- Quiet hours suppress non-urgent Slack notifications: weekends, plus 20:00–08:00 on weekdays, in `Asia/Seoul`. All three parts are settings (`slack.quietFromHour`, `slack.quietUntilHour`, `slack.notifyOnWeekends`), defaulting to that window. The hour is computed through `Intl` with an explicit timezone rather than `getHours()` — dev0 runs in UTC, where "20:00" would land at 05:00 Korean time. Expiry alerts repeat, so one dropped in quiet hours returns on the first cycle of the next working window. The last-active-token death alert sets `urgent: true` and ignores quiet hours: it means
|
|
211
|
+
- Quiet hours suppress non-urgent Slack notifications: weekends, plus 20:00–08:00 on weekdays, in `Asia/Seoul`. All three parts are settings (`slack.quietFromHour`, `slack.quietUntilHour`, `slack.notifyOnWeekends`), defaulting to that window. The hour is computed through `Intl` with an explicit timezone rather than `getHours()` — dev0 runs in UTC, where "20:00" would land at 05:00 Korean time. Expiry alerts repeat, so one dropped in quiet hours returns on the first cycle of the next working window. The last-active-token death alert sets `urgent: true` and ignores quiet hours: it means that provider has no usable login, which cannot wait for the next working day.
|
|
209
212
|
- `slack.enabled` exists because holidays cannot be expressed as a recurring rule — substitute holidays, one-off national days, and company closures change every year, so an operator turns notifications off directly instead. It gates the same non-urgent path as quiet hours, which keeps the escape hatch for pool-empty alerts identical in both mechanisms.
|
|
210
213
|
- Notifications are fail-open: unset `SLACK_BOT_TOKEN`/`SLACK_CHANNEL_ID` is a silent no-op, and send failures (including HTTP 200 with `ok:false`) only warn. Payloads carry an internal reason code such as `anthropic:400`, never the raw provider response body.
|
|
211
|
-
- `expired-token-reminder.ts` repeats the session-expiry alert for tokens
|
|
214
|
+
- `expired-token-reminder.ts` repeats the session-expiry alert only for tokens with `reauth_required = true`; manually inactive tokens are not expired sessions. It reuses the same `세션 만료` title rather than inventing a second name — there is one event, so a differently-named alert would read as a separate problem. It runs once at startup and then every `SLACK_EXPIRY_REMINDER_INTERVAL_MINUTES` (unset or 0 disables), batches every expired token into one message, and sends nothing when none require re-login. The startup run matters because a restart is exactly when someone is watching, and waiting a full interval to learn a token is already dead is backwards; the cost is one extra alert per restart — a periodic "all clear" trains people to ignore the channel.
|
|
212
215
|
- Owners are mentioned through `SLACK_USER_MAP` (`tokenName:SlackUserId`) on every token notification, not just the reminder. The map is explicit rather than resolved through `users.list`: Slack handles diverge from token names often enough (`haze`→`haze.lee`, `byeongjun`→`potados`) that auto-matching drops the mention silently, and an alert nobody is tagged in has no one to act on it. Shared accounts are left out of the map on purpose and appear by name only. Each instance sends its own reminder — unlike deactivation there is no state change to gate on, and a few duplicates a day cost less than a locking scheme.
|
|
213
216
|
|
|
214
217
|
## Quota Threshold Semantics
|
|
@@ -243,12 +246,13 @@ When a refresh fails in a way that only re-login can fix, qgrid removes the toke
|
|
|
243
246
|
|
|
244
247
|
Anthropic's 5-hour usage window anchors on the first request, so an unused token has no window at all — `five_hour.resets_at` is null until something is sent, and windows are per-account and independent. The practical cost is that starting work starts the clock: burn the quota in two hours and the remaining three are dead.
|
|
245
248
|
|
|
246
|
-
Keepalive breaks that coupling by burning the window clock during idle time. Every active Anthropic token gets one cheap request as soon as its window expires, so windows always run and a workday lands mid-window rather than on a boundary. Across an eight-hour day this raises the number of windows the day can touch from a fixed 2 to an expected 2.6.
|
|
249
|
+
Keepalive breaks that coupling by burning the window clock during idle time. Every keepalive-enabled active Anthropic token gets one cheap request as soon as its window expires, so windows always run and a workday lands mid-window rather than on a boundary. Across an eight-hour day this raises the number of windows the day can touch from a fixed 2 to an expected 2.6.
|
|
247
250
|
|
|
248
|
-
-
|
|
251
|
+
- Since 2.8.0 (`ea7feb3`) keepalive is opt-in at two levels. The runner instance must have env `QGRID_TOKEN_WINDOW_KEEPALIVE_ENABLED=true` (default off; set it on one instance per shared DB), and a token is a target only when `tokens.keepalive_enabled` is `true` (default `false`, toggled in the dashboard settings panel). The global setting `qgrid.tokenWindowKeepaliveEnabled` is a kill switch (`false` disables everything), not a substitute for either gate. In 2.7.6 every instance fired for every active token.
|
|
252
|
+
- Operational trap: the 2.8.0 migration adds `keepalive_enabled` as default `false` without backfilling existing tokens. After upgrading, both the runner env and the per-token toggles must be set, or keepalive silently schedules zero tokens. This happened on dev0 after the 2026-08-31 self-update; a lingering 2.7.6 instance masked it until 2026-09-01. Diagnose with `GET /api/setting/listSettings` (`keepaliveRunnerEnabled`), `GET /api/token/findMany?subset=A` (`keepalive_enabled`), and the latest request log under project `qgrid-token-window-keepalive`.
|
|
249
253
|
- The next fire is scheduled from the token's real `resetsAt` rather than a fixed five-hour timer, so a provider-side change to the window length is followed automatically.
|
|
250
254
|
- Per-token fire guards prevent repeats: 61 seconds after a successful fire (the usage API caches for a minute, so an immediate re-read would still show the old window) and five hours after a failure. Guards survive rescheduling — only timers are replaced — and the guard is recorded before the request is sent so a reschedule mid-flight cannot fire a second time.
|
|
251
255
|
- Rescheduling happens only when target-set membership changes (active/provider). Credential rotation updates the token row and fires the change notification, but it does not change membership and must not trigger a sweep; otherwise every OAuth refresh would re-poll usage for every token.
|
|
252
|
-
-
|
|
256
|
+
- Only runner instances fire. If more than one instance is marked as runner, a ping into an already-open window does nothing, so the duplicate is harmless and cheaper than a locking scheme — the same reasoning the expiry reminder uses.
|
|
253
257
|
- Fires are logged under the `qgrid-token-window-keepalive` project name so they can be excluded from traffic statistics.
|
|
254
258
|
- Known limits: the first sweep after boot has no concurrency cap, so N empty windows mean N simultaneous fires; and a failed fire costs that token five hours of window misalignment.
|