@cartanova/qgrid-cli 2.3.8 → 2.4.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 +131 -0
- package/README.md +60 -44
- package/bundle/web-dist/client/assets/{index-OQd6CclQ.js → index-DVdnvcvo.js} +3 -3
- package/bundle/web-dist/client/assets/{logs-8T91eoLY.js → logs-B82PGnxD.js} +1 -1
- package/bundle/web-dist/client/assets/{routes--GqHRT2A.js → routes-CvCEcpZ7.js} +1 -1
- package/bundle/web-dist/client/assets/{show-cHMucIDC.js → show-DWRh--ML.js} +1 -1
- package/bundle/web-dist/client/assets/{tokens-BXAJUeCJ.js → tokens-DOu_ZFUL.js} +1 -1
- package/bundle/web-dist/client/index.html +1 -1
- package/bundle/web-dist/server/entry-server.generated.js +1 -1
- package/dist/cli.js +86 -5
- package/package.json +7 -3
- package/postinstall.mjs +119 -0
- package/skills/qgrid/SKILL.md +54 -0
- package/skills/qgrid/agents/openai.yaml +4 -0
- package/skills/qgrid/references/ai-sdk-provider-contract.md +100 -0
- package/skills/qgrid/references/anthropic-claude-code-runtime.md +170 -0
- package/skills/qgrid/references/cli-env-and-server-boot.md +68 -0
- package/skills/qgrid/references/decision-rationale.md +179 -0
- package/skills/qgrid/references/docs-workflow.md +32 -0
- package/skills/qgrid/references/openai-codex-runtime.md +153 -0
- package/skills/qgrid/references/prompt-cache-and-usage.md +101 -0
- package/skills/qgrid/references/provider-runtime-differences.md +54 -0
- package/skills/qgrid/references/repo-map.md +37 -0
- package/skills/qgrid/references/request-log-run-lifecycle.md +76 -0
- package/skills/qgrid/references/sonamu-api-web-flow.md +47 -0
- package/skills/qgrid/references/token-auth-quota-lifecycle.md +205 -0
- package/skills/qgrid/references/tool-calling-and-multiturn.md +144 -0
- package/skills/qgrid/references/verification-and-debugging.md +193 -0
- package/bundle/dist/migrations/20260705140615_alter_request_log_steps_alter1.js +0 -14
- package/bundle/src/migrations/20260705140615_alter_request_log_steps_alter1.ts +0 -13
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Sonamu API And Web Flow
|
|
2
|
+
|
|
3
|
+
Use this reference for dashboard/API changes.
|
|
4
|
+
|
|
5
|
+
qgrid dashboard work is usually a Sonamu API/model/generated-client/web change, not an isolated React change.
|
|
6
|
+
|
|
7
|
+
## Full path to inspect
|
|
8
|
+
|
|
9
|
+
1. Entity and model definitions in `packages/api/src/application/**`.
|
|
10
|
+
2. API frame/model methods decorated with `@api` or `@stream`.
|
|
11
|
+
3. Generated Sonamu files such as `packages/api/src/application/sonamu.generated*`.
|
|
12
|
+
4. Web API consumers and generated client/types.
|
|
13
|
+
5. Dashboard routes/components in `packages/web/src/**`.
|
|
14
|
+
|
|
15
|
+
## Common patterns
|
|
16
|
+
|
|
17
|
+
- Entity fields live in `*.entity.json`.
|
|
18
|
+
- Model methods live in `*.model.ts`.
|
|
19
|
+
- API frame methods for qgrid live in `qgrid.frame.ts`.
|
|
20
|
+
- i18n generated files may change when entity labels or static data change.
|
|
21
|
+
- Web components under `packages/web/src/components/qgrid` consume generated API shapes.
|
|
22
|
+
- The main dashboard sidebar version should stay synced to `packages/cli/package.json`. `packages/web/vite.config.ts` reads that package version and exposes it as `__QGRID_CLI_VERSION__`; `Sidebar.tsx` displays it.
|
|
23
|
+
|
|
24
|
+
## Request log dashboard
|
|
25
|
+
|
|
26
|
+
Request log changes usually touch:
|
|
27
|
+
|
|
28
|
+
- `request-log.entity.json`
|
|
29
|
+
- `request-log.model.ts`
|
|
30
|
+
- `request-log.types.ts`
|
|
31
|
+
- `request-log-step.*`
|
|
32
|
+
- `qgrid-run-lifecycle.ts`
|
|
33
|
+
- `RequestLogTable.tsx`
|
|
34
|
+
- `routes/requests/show.tsx`
|
|
35
|
+
|
|
36
|
+
Check list and detail views. Avoid loading large text columns in list views unless intentionally needed.
|
|
37
|
+
|
|
38
|
+
Image-generation request logs add `is_image_generation`, `image_cost_usd`, and `image_cost_method`. The list view displays driver plus image cost as the total cost cell; the detail view shows driver cost and image cost separately and renders generated image data URLs from response/tool-step content.
|
|
39
|
+
|
|
40
|
+
## Verification
|
|
41
|
+
|
|
42
|
+
After API shape changes:
|
|
43
|
+
|
|
44
|
+
- Regenerate or inspect generated Sonamu files according to repo workflow.
|
|
45
|
+
- Run targeted API tests.
|
|
46
|
+
- Check web consumers compile against generated types.
|
|
47
|
+
- Verify dashboard display for null/legacy values when changing stored request log fields.
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Token Auth Quota Lifecycle
|
|
2
|
+
|
|
3
|
+
Use this reference before changing token storage, OAuth flows, token activation, token subscriber behavior, quota thresholds, usage APIs, or provider token routing.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- Source files
|
|
8
|
+
- Token table contract
|
|
9
|
+
- Token APIs
|
|
10
|
+
- Anthropic OAuth flow
|
|
11
|
+
- OpenAI OAuth and refresh flow
|
|
12
|
+
- Token sync and reconcile
|
|
13
|
+
- Provider runtime consequences
|
|
14
|
+
- Quota threshold semantics
|
|
15
|
+
|
|
16
|
+
## Source Files
|
|
17
|
+
|
|
18
|
+
- Token model/types: `packages/api/src/application/token/token.model.ts`, `token.types.ts`, `token.entity.json`.
|
|
19
|
+
- qgrid API frame and OAuth endpoints: `packages/api/src/application/qgrid/qgrid.frame.ts`.
|
|
20
|
+
- Anthropic OAuth utilities and usage API: `packages/api/src/application/qgrid/oauth.ts`.
|
|
21
|
+
- Token DB trigger setup: `packages/api/src/application/qgrid/token-trigger-setup.ts`.
|
|
22
|
+
- Token subscriber: `packages/api/src/application/qgrid/token-subscriber.ts`.
|
|
23
|
+
- OpenAI refresh/quota: `packages/api/src/utils/providers/openai/openai-refresh.ts`, `openai-quota.ts`.
|
|
24
|
+
- Anthropic quota: `packages/api/src/utils/providers/anthropic/anthropic-quota.ts`.
|
|
25
|
+
- Provider token event handlers: `openai-dispatcher.ts`, `anthropic-dispatcher.ts`.
|
|
26
|
+
|
|
27
|
+
## Token Table Contract
|
|
28
|
+
|
|
29
|
+
`tokens` stores:
|
|
30
|
+
|
|
31
|
+
- `provider`: provider string such as `openai` or `anthropic`.
|
|
32
|
+
- `credentials`: JSONB credentials.
|
|
33
|
+
- `name`: display/logging name.
|
|
34
|
+
- `active`: whether provider dispatchers may use it.
|
|
35
|
+
- `ord`: dashboard ordering.
|
|
36
|
+
- `quota_threshold`: nullable integer percentage, validated as 1..100 when present.
|
|
37
|
+
|
|
38
|
+
On creation, `TokenModel.save` applies default `quota_threshold = 80` unless `id` or `quota_threshold` is already provided.
|
|
39
|
+
|
|
40
|
+
OpenAI credentials:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
{
|
|
44
|
+
accessToken: string;
|
|
45
|
+
refreshToken: string;
|
|
46
|
+
idToken?: string;
|
|
47
|
+
accessTokenExpiresAt: number;
|
|
48
|
+
idTokenExpiresAt?: number;
|
|
49
|
+
accountId: string;
|
|
50
|
+
planType?: string;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Anthropic credentials:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
{
|
|
58
|
+
accessToken: string;
|
|
59
|
+
refreshToken: string;
|
|
60
|
+
expiresAt: number;
|
|
61
|
+
accountUuid: string;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Duplicate account replacement is account-identifier based:
|
|
66
|
+
|
|
67
|
+
- OpenAI: `credentials.accountId`.
|
|
68
|
+
- Anthropic: `credentials.accountUuid`.
|
|
69
|
+
|
|
70
|
+
## Token APIs
|
|
71
|
+
|
|
72
|
+
`QgridFrame` exposes token APIs:
|
|
73
|
+
|
|
74
|
+
- `addToken(provider, credentials, name)`: saves a token directly.
|
|
75
|
+
- `updateToken(id, name?, quotaThreshold?)`: updates name and/or quota threshold, preserving provider/credentials.
|
|
76
|
+
- `removeToken(id)`: deletes token row.
|
|
77
|
+
- `toggleToken(id)`: flips `active`.
|
|
78
|
+
- `stats()`: reports dispatcher cache stats.
|
|
79
|
+
- `health()`: reports active token cache count and subscriber status.
|
|
80
|
+
- `usage(tokenId?)`: returns provider usage/rate-limit summary.
|
|
81
|
+
|
|
82
|
+
When updating a token, preserve existing provider and credentials unless intentionally rotating credentials.
|
|
83
|
+
|
|
84
|
+
## Anthropic OAuth Flow
|
|
85
|
+
|
|
86
|
+
Anthropic OAuth is implemented directly in qgrid.
|
|
87
|
+
|
|
88
|
+
1. `oauthStart(name)` generates PKCE verifier/challenge/state.
|
|
89
|
+
2. qgrid builds the Claude OAuth authorize URL with Claude Code-like scopes.
|
|
90
|
+
3. Pending state is cached under `oauth:state:<state>` for 5 minutes.
|
|
91
|
+
4. `/callback` calls `handleOAuthCallback(code, state, reply)`.
|
|
92
|
+
5. qgrid exchanges the code for tokens.
|
|
93
|
+
6. Existing Anthropic tokens with the same `accountUuid` are deleted.
|
|
94
|
+
7. qgrid saves a new `provider: "anthropic"` token.
|
|
95
|
+
|
|
96
|
+
`QGRID_PUBLIC_BASE_URL` controls the public callback base. If unset, callback defaults to `http://localhost:${PORT}/callback`.
|
|
97
|
+
|
|
98
|
+
Refresh:
|
|
99
|
+
|
|
100
|
+
- `refreshToken(token)` uses the stored refresh token and `refreshAccessToken`.
|
|
101
|
+
- Refreshed credentials are saved back through `TokenModel.save`.
|
|
102
|
+
- `usage()` refreshes expired Anthropic access tokens before usage lookup when a refresh token exists.
|
|
103
|
+
- `AnthropicDispatcher` also preemptively refreshes when expiration is within 60 seconds.
|
|
104
|
+
|
|
105
|
+
Usage/quota:
|
|
106
|
+
|
|
107
|
+
- Usage API is `https://api.anthropic.com/api/oauth/usage`.
|
|
108
|
+
- qgrid caches Anthropic usage by access-token suffix for 60 seconds.
|
|
109
|
+
- Quota threshold uses `five_hour.utilization`.
|
|
110
|
+
- Usage lookup failure is fail-open for routing.
|
|
111
|
+
|
|
112
|
+
## OpenAI OAuth And Refresh Flow
|
|
113
|
+
|
|
114
|
+
OpenAI OAuth is delegated to Codex app-server.
|
|
115
|
+
|
|
116
|
+
Browser registration:
|
|
117
|
+
|
|
118
|
+
1. `oauthStartOpenAI(name)` calls `OpenAIDispatcher.startBrowserLogin(name)`.
|
|
119
|
+
2. Dispatcher creates a temporary `CodexAppServerWorker`.
|
|
120
|
+
3. Worker spawns `codex app-server` and calls `account/login/start` with `type: "chatgpt"`.
|
|
121
|
+
4. qgrid returns Codex's `authUrl`.
|
|
122
|
+
5. In the background, dispatcher waits for `account/login/completed`.
|
|
123
|
+
6. Worker reads managed credentials from its `auth.json`.
|
|
124
|
+
7. Existing OpenAI tokens with the same `accountId` are deleted.
|
|
125
|
+
8. qgrid saves a new `provider: "openai"` token.
|
|
126
|
+
9. Temporary worker is killed; pending browser login times out after 5 minutes.
|
|
127
|
+
|
|
128
|
+
Worker login:
|
|
129
|
+
|
|
130
|
+
- Normal OpenAI workers call `account/login/start` with `type: "chatgptAuthTokens"`, `accessToken`, `chatgptAccountId`, and optional `chatgptPlanType`.
|
|
131
|
+
|
|
132
|
+
Refresh:
|
|
133
|
+
|
|
134
|
+
- Codex can send server-request `account/chatgptAuthTokens/refresh`.
|
|
135
|
+
- qgrid handles it through `handleChatgptAuthTokensRefresh(tokenId)`.
|
|
136
|
+
- Refresh is deduplicated per token with an in-flight promise.
|
|
137
|
+
- Refresh has a 5-second minimum interval; too-soon calls read current DB credentials.
|
|
138
|
+
- Refresh token rotation is saved immediately. If DB save fails after rotation, treat it as token-death risk because the old refresh token may already be invalid.
|
|
139
|
+
|
|
140
|
+
Usage/quota:
|
|
141
|
+
|
|
142
|
+
- OpenAI quota threshold uses Codex `account/rateLimits/read`.
|
|
143
|
+
- qgrid reads rate limits through a ready worker.
|
|
144
|
+
- Rate limit data is cached for 60 seconds.
|
|
145
|
+
- Threshold uses primary `usedPercent`.
|
|
146
|
+
- Usage lookup failure is fail-open for routing.
|
|
147
|
+
|
|
148
|
+
## Token Sync And Reconcile
|
|
149
|
+
|
|
150
|
+
On server start, qgrid creates PostgreSQL triggers for `tokens_changed`.
|
|
151
|
+
|
|
152
|
+
Triggers notify on:
|
|
153
|
+
|
|
154
|
+
- INSERT
|
|
155
|
+
- DELETE
|
|
156
|
+
- UPDATE when `active`, `credentials`, `provider`, `name`, or `quota_threshold` changes.
|
|
157
|
+
|
|
158
|
+
`TokenSubscriber`:
|
|
159
|
+
|
|
160
|
+
- connects to PostgreSQL and `LISTEN`s on `tokens_changed`;
|
|
161
|
+
- reconnects with jittered exponential backoff up to 30 seconds;
|
|
162
|
+
- runs periodic reconcile every 10 minutes because LISTEN/NOTIFY can miss changes while disconnected;
|
|
163
|
+
- replaces dispatcher cache from `TokenModel.findActive("A")`.
|
|
164
|
+
|
|
165
|
+
On DELETE or missing row:
|
|
166
|
+
|
|
167
|
+
- remove from `QgridDispatcher.tokens`;
|
|
168
|
+
- call OpenAI `onTokenRemoved`;
|
|
169
|
+
- call Anthropic `onTokenRemoved`.
|
|
170
|
+
|
|
171
|
+
OpenAI row changes:
|
|
172
|
+
|
|
173
|
+
- active INSERT: `onTokenAdded` spawns workers.
|
|
174
|
+
- active UPDATE: `onTokenUpdated`, then `onTokenActivated`.
|
|
175
|
+
- inactive row: `onTokenDeactivated`.
|
|
176
|
+
- reconcile uses `replaceTokens`, which removes absent tokens, updates existing tokens, adds new tokens, and activates rows present in active DB set.
|
|
177
|
+
|
|
178
|
+
Anthropic row changes:
|
|
179
|
+
|
|
180
|
+
- active INSERT: add to in-memory pool.
|
|
181
|
+
- active UPDATE: update in-memory pool.
|
|
182
|
+
- inactive row: remove from pool.
|
|
183
|
+
- reconcile uses active Anthropic rows to replace the in-memory pool.
|
|
184
|
+
|
|
185
|
+
## Provider Runtime Consequences
|
|
186
|
+
|
|
187
|
+
OpenAI token changes can create, update, deactivate, or kill persistent Codex workers.
|
|
188
|
+
|
|
189
|
+
- In-place OpenAI worker update is allowed only when account id and plan type match.
|
|
190
|
+
- Otherwise workers are killed and respawned.
|
|
191
|
+
- If no ready active OpenAI workers remain, queued OpenAI requests are rejected.
|
|
192
|
+
|
|
193
|
+
Anthropic token changes only affect the in-memory token pool because every request fresh-spawns Claude Code.
|
|
194
|
+
|
|
195
|
+
## Quota Threshold Semantics
|
|
196
|
+
|
|
197
|
+
`quota_threshold` is a routing gate, not a hard external-provider guarantee.
|
|
198
|
+
|
|
199
|
+
- Null means no threshold gate.
|
|
200
|
+
- Lookup failure is fail-open.
|
|
201
|
+
- Individual over-threshold tokens are excluded from selection.
|
|
202
|
+
- If all ready/eligible tokens are over threshold, qgrid throws `QuotaThresholdExceededError`.
|
|
203
|
+
- Log messages use `quota_threshold gate` with reasons such as `over_threshold`, `lookup_fail_open`, and `all_exceeded`.
|
|
204
|
+
|
|
205
|
+
OpenAI queue items keep an `excludedTokenIds` set so a token found over threshold for a request is not immediately reselected for that same queued request.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Tool Calling And Multi-Turn
|
|
2
|
+
|
|
3
|
+
Use this reference before changing qgrid tool calling, AI SDK multi-step behavior, tool-result follow-ups, or request-log tool steps.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- High-level contract
|
|
8
|
+
- Client flow
|
|
9
|
+
- Server schema emulation
|
|
10
|
+
- Provider-specific path
|
|
11
|
+
- Applying emulated tool calls
|
|
12
|
+
- Multi-step and multi-turn
|
|
13
|
+
- Request logs
|
|
14
|
+
|
|
15
|
+
## High-Level Contract
|
|
16
|
+
|
|
17
|
+
qgrid supports the AI SDK `tools` interface, but it does not use native provider tool calling.
|
|
18
|
+
|
|
19
|
+
- Users write normal AI SDK code with `generateText`, `streamText`, `tools`, `stopWhen`, and related AI SDK APIs.
|
|
20
|
+
- The AI SDK owns client-side tool execution.
|
|
21
|
+
- qgrid owns LLM turns and request logging.
|
|
22
|
+
- qgrid turns AI SDK tools into a strict structured-output schema.
|
|
23
|
+
- The model emits a structured object describing either a final answer or requested client-side tool calls.
|
|
24
|
+
- qgrid maps that structured object back into AI SDK `tool-call` content.
|
|
25
|
+
- The AI SDK executes the actual tool functions and calls qgrid again with tool results.
|
|
26
|
+
|
|
27
|
+
Do not describe this as OpenAI native function calling, Claude native tool use, or Codex/Claude executing client tools. The tools are client-side AI SDK tools requested through structured-output emulation.
|
|
28
|
+
|
|
29
|
+
## Client Flow
|
|
30
|
+
|
|
31
|
+
`packages/ai-sdk/src/index.ts` implements AI SDK `LanguageModelV3`.
|
|
32
|
+
|
|
33
|
+
When `options.tools` contains function tools:
|
|
34
|
+
|
|
35
|
+
1. Filter function tools.
|
|
36
|
+
2. Convert them with `toQgridTool`.
|
|
37
|
+
3. Send `tools` in `/api/qgrid/query` or `/api/qgrid/prepareStream`.
|
|
38
|
+
4. Set `logMode: "run"`.
|
|
39
|
+
5. Do not send `jsonSchema` response format at the same time.
|
|
40
|
+
|
|
41
|
+
When qgrid responds with `finishReason: "tool-calls"`:
|
|
42
|
+
|
|
43
|
+
1. Map qgrid `content: [{ type: "tool-call" }]` to AI SDK tool-call content.
|
|
44
|
+
2. Store `clientRun` with `runContext` and pending tool-call IDs.
|
|
45
|
+
3. Return finish reason `{ unified: "tool-calls", raw: "tool_call" }`.
|
|
46
|
+
|
|
47
|
+
On the next AI SDK call, if prompt history contains tool results for all pending IDs:
|
|
48
|
+
|
|
49
|
+
1. Extract tool results from AI SDK history.
|
|
50
|
+
2. Send previous `runContext`.
|
|
51
|
+
3. Send `toolResults`.
|
|
52
|
+
4. Keep `logMode: "run"`.
|
|
53
|
+
|
|
54
|
+
If pending tool calls are not satisfied, qgrid warns and clears `clientRun` instead of attaching stale run context.
|
|
55
|
+
|
|
56
|
+
## Server Schema Emulation
|
|
57
|
+
|
|
58
|
+
`packages/api/src/application/qgrid/tool-emulation.ts` builds the tool-call schema.
|
|
59
|
+
|
|
60
|
+
The schema shape is:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"action": "answer | tool_call",
|
|
65
|
+
"answer": "string | null",
|
|
66
|
+
"toolCalls": [
|
|
67
|
+
{
|
|
68
|
+
"toolName": "one of the AI SDK tool names",
|
|
69
|
+
"args": "JSON string"
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The schema explicitly tells the model:
|
|
76
|
+
|
|
77
|
+
- use `tool_call` when client-side tool execution is needed;
|
|
78
|
+
- use `answer` only for a final answer;
|
|
79
|
+
- do not invoke listed tools as native Claude Code tools;
|
|
80
|
+
- put tool arguments in `args` as a JSON string.
|
|
81
|
+
|
|
82
|
+
`qgrid.dispatcher.ts` strictifies this schema through `buildStrictOutputSchema` before it reaches provider dispatchers.
|
|
83
|
+
|
|
84
|
+
If tools and `jsonSchema` are both present, dispatcher rejects the request.
|
|
85
|
+
|
|
86
|
+
## Provider-Specific Path
|
|
87
|
+
|
|
88
|
+
OpenAI/Codex:
|
|
89
|
+
|
|
90
|
+
- qgrid does not register AI SDK tools as Codex native tools.
|
|
91
|
+
- Codex built-in tools are disabled in worker config.
|
|
92
|
+
- qgrid sends the strict tool-call schema as `outputSchema` in `turn/start`.
|
|
93
|
+
- Codex returns text matching the structured schema.
|
|
94
|
+
- qgrid parses that text and maps it to qgrid `tool-call` content.
|
|
95
|
+
|
|
96
|
+
Anthropic/Claude Code:
|
|
97
|
+
|
|
98
|
+
- qgrid does not allow client tools as Claude Code native tools.
|
|
99
|
+
- Claude is spawned with `--tools ""`.
|
|
100
|
+
- When a schema exists, qgrid permits only `StructuredOutput` with `--allowed-tools StructuredOutput`.
|
|
101
|
+
- qgrid passes the strict tool-call schema through `--json-schema`.
|
|
102
|
+
- The stream adapter preserves `StructuredOutput` tool input as final structured text.
|
|
103
|
+
- qgrid parses that text and maps it to qgrid `tool-call` content.
|
|
104
|
+
|
|
105
|
+
In both providers, native provider tools are not the public abstraction. The public abstraction is AI SDK tools plus qgrid structured-output emulation.
|
|
106
|
+
|
|
107
|
+
## Applying Emulated Tool Calls
|
|
108
|
+
|
|
109
|
+
`applyToolCallEmulation` handles provider text:
|
|
110
|
+
|
|
111
|
+
- No tools: return normal text content.
|
|
112
|
+
- Tools present and JSON parse fails: warn and fall back to text with `finishReason: "stop"`.
|
|
113
|
+
- `action: "tool_call"`: validate tool names, generate qgrid `toolCallId`, return `finishReason: "tool-calls"`.
|
|
114
|
+
- `action: "answer"`: return final text with `finishReason: "stop"`.
|
|
115
|
+
|
|
116
|
+
Unknown tool names throw.
|
|
117
|
+
|
|
118
|
+
## Multi-Step And Multi-Turn
|
|
119
|
+
|
|
120
|
+
AI SDK multi-step behavior is client-orchestrated:
|
|
121
|
+
|
|
122
|
+
1. qgrid returns emulated tool calls.
|
|
123
|
+
2. AI SDK executes tools locally.
|
|
124
|
+
3. AI SDK calls qgrid again with tool results.
|
|
125
|
+
4. qgrid asks the model to continue from those results.
|
|
126
|
+
|
|
127
|
+
This is different from a provider-native tool loop. Neither Codex nor Claude Code executes the AI SDK tool functions.
|
|
128
|
+
|
|
129
|
+
OpenAI/Codex follow-ups can reuse the same Codex thread when `runContext.threadCoord` remains valid. For tool-result follow-up turns, qgrid sends delta input containing tool result text to the existing thread; cold fallback injects full history.
|
|
130
|
+
|
|
131
|
+
Anthropic follow-ups do not reuse Claude sessions. qgrid starts a fresh Claude process, replays flattened full history, and sends the tool-result continuation input.
|
|
132
|
+
|
|
133
|
+
## Request Logs
|
|
134
|
+
|
|
135
|
+
Tool runs use `logMode: "run"`.
|
|
136
|
+
|
|
137
|
+
- `beforeQuery` creates or continues a request log run.
|
|
138
|
+
- A generate step is recorded after every LLM turn.
|
|
139
|
+
- When `finishReason` is `tool-calls`, qgrid appends pending tool-call step rows.
|
|
140
|
+
- The follow-up request fills tool-call results or errors.
|
|
141
|
+
- The final `stop` response aggregates step usage and finishes the run.
|
|
142
|
+
- Stale runs with no follow-up are marked error after 30 minutes.
|
|
143
|
+
|
|
144
|
+
Use `projectName` for tool-heavy workloads so request logs can be filtered by task/workflow and metrics remain interpretable.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Verification And Debugging
|
|
2
|
+
|
|
3
|
+
Use this reference to choose tests, smoke scripts, and first debugging targets for qgrid changes.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- Package scripts
|
|
8
|
+
- Targeted test matrix
|
|
9
|
+
- Smoke scripts
|
|
10
|
+
- Common error triage
|
|
11
|
+
- Debugging rules
|
|
12
|
+
|
|
13
|
+
## Package Scripts
|
|
14
|
+
|
|
15
|
+
Root:
|
|
16
|
+
|
|
17
|
+
- `pnpm check`: oxlint plus oxfmt check for the repo.
|
|
18
|
+
- `pnpm build`: recursive package build.
|
|
19
|
+
- `pnpm dev`: parallel package dev servers.
|
|
20
|
+
|
|
21
|
+
API package `qgrid-api`:
|
|
22
|
+
|
|
23
|
+
- `pnpm --filter qgrid-api test`: Vitest run.
|
|
24
|
+
- `pnpm --filter qgrid-api test:watch`: standalone watch mode.
|
|
25
|
+
- `pnpm --filter qgrid-api build`: Sonamu build.
|
|
26
|
+
- `pnpm --filter qgrid-api sonamu`: run Sonamu CLI.
|
|
27
|
+
|
|
28
|
+
AI SDK package `@cartanova/qgrid-ai-sdk`:
|
|
29
|
+
|
|
30
|
+
- `pnpm --filter @cartanova/qgrid-ai-sdk test`.
|
|
31
|
+
- `pnpm --filter @cartanova/qgrid-ai-sdk build`.
|
|
32
|
+
- `pnpm --filter @cartanova/qgrid-ai-sdk e2e`.
|
|
33
|
+
- `pnpm --filter @cartanova/qgrid-ai-sdk e2e:logger`.
|
|
34
|
+
|
|
35
|
+
CLI package `@cartanova/qgrid-cli`:
|
|
36
|
+
|
|
37
|
+
- `pnpm --filter @cartanova/qgrid-cli build`.
|
|
38
|
+
- `pnpm --filter @cartanova/qgrid-cli bundle` builds the API first and copies the server bundle.
|
|
39
|
+
|
|
40
|
+
Web package `qgrid-web`:
|
|
41
|
+
|
|
42
|
+
- `pnpm --filter qgrid-web dev`.
|
|
43
|
+
- `pnpm --filter qgrid-web preview`.
|
|
44
|
+
|
|
45
|
+
`packages/sdk` is deprecated. Do not use its tests as active public-SDK verification unless explicitly doing legacy work.
|
|
46
|
+
|
|
47
|
+
## Targeted Test Matrix
|
|
48
|
+
|
|
49
|
+
Choose by affected area:
|
|
50
|
+
|
|
51
|
+
| Area | Primary tests |
|
|
52
|
+
|---|---|
|
|
53
|
+
| AI SDK provider request/response mapping | `packages/ai-sdk/src/index.test.ts` |
|
|
54
|
+
| AI SDK logger integration | `packages/ai-sdk/src/logger.test.ts` |
|
|
55
|
+
| qgrid provider routing and strict schema | `packages/api/src/application/qgrid/qgrid.dispatcher.test.ts` |
|
|
56
|
+
| conversation/thread reuse routing | `packages/api/src/application/qgrid/conv-routing.test.ts` |
|
|
57
|
+
| tool-call emulation | `packages/api/src/application/qgrid/tool-emulation.test.ts` |
|
|
58
|
+
| request-log run lifecycle | `packages/api/src/application/qgrid/qgrid-run-lifecycle.test.ts` |
|
|
59
|
+
| qgrid frame API behavior | `packages/api/src/application/qgrid/qgrid.frame.test.ts` |
|
|
60
|
+
| token subscriber and LISTEN/NOTIFY handling | `packages/api/src/application/qgrid/token-subscriber.test.ts` |
|
|
61
|
+
| token defaults/validation | `packages/api/src/application/token/token.model.test.ts`, `token.types.test.ts` |
|
|
62
|
+
| request log queries/legacy normalization | `packages/api/src/application/request-log/request-log.model.test.ts` |
|
|
63
|
+
| OpenAI dispatcher routing/queue/quota/reuse | `packages/api/src/utils/providers/openai/openai-dispatcher.test.ts` |
|
|
64
|
+
| OpenAI Codex worker RPC/turn behavior | `packages/api/src/utils/providers/openai/codex-worker.test.ts` |
|
|
65
|
+
| OpenAI quota parser | `packages/api/src/utils/providers/openai/openai-quota.test.ts` |
|
|
66
|
+
| Anthropic dispatcher token/quota/session behavior | `packages/api/src/utils/providers/anthropic/anthropic-dispatcher.test.ts` |
|
|
67
|
+
| Anthropic Claude args/env/session isolation | `packages/api/src/utils/providers/anthropic/claude-session.test.ts` |
|
|
68
|
+
| Anthropic stream-json parsing/structured output | `packages/api/src/utils/providers/anthropic/stream-json-adapter.test.ts` |
|
|
69
|
+
| Anthropic model constants/1M suffix | `packages/api/src/utils/providers/anthropic/anthropic-constants.test.ts` |
|
|
70
|
+
| Anthropic quota parser | `packages/api/src/utils/providers/anthropic/anthropic-quota.test.ts` |
|
|
71
|
+
| shared cost accounting | `packages/api/src/utils/providers/common/model-cost.test.ts` |
|
|
72
|
+
| TTFT tracker | `packages/api/src/utils/providers/common/ttft.test.ts` |
|
|
73
|
+
|
|
74
|
+
Run focused Vitest files from the owning package when possible, then broaden if the change crosses package boundaries.
|
|
75
|
+
|
|
76
|
+
## Smoke Scripts
|
|
77
|
+
|
|
78
|
+
Smoke scripts assume a running qgrid server and real registered tokens. They can consume subscription quota; do not run them casually.
|
|
79
|
+
|
|
80
|
+
OpenAI direct qgrid API:
|
|
81
|
+
|
|
82
|
+
- `scripts/smoke-test-openai.ts`
|
|
83
|
+
- Requires qgrid server and active OpenAI token.
|
|
84
|
+
- Uses `QGRID_URL`, default `http://localhost:44900`.
|
|
85
|
+
- Covers health, text generation, structured output, and qgrid-native tool-call content.
|
|
86
|
+
|
|
87
|
+
Anthropic qgrid path:
|
|
88
|
+
|
|
89
|
+
- `packages/api/scripts/smoke-test-anthropic.ts`
|
|
90
|
+
- Requires active Anthropic OAuth token.
|
|
91
|
+
- Uses `QGRID_URL`, `QGRID_ANTHROPIC_MODEL`, `QGRID_ANTHROPIC_MODELS`.
|
|
92
|
+
- Optional flags include `--full`, `--legacy-full`, and `--structured-stream-probe`.
|
|
93
|
+
|
|
94
|
+
AI SDK structured output:
|
|
95
|
+
|
|
96
|
+
- `scripts/qgrid-structured-ai-sdk-smoke.ts`
|
|
97
|
+
- Uses local `packages/ai-sdk/src/index`.
|
|
98
|
+
- Env: `QGRID_URL`, `SMOKE_MODEL`, `SMOKE_CASES`, `SMOKE_REPEAT`.
|
|
99
|
+
- Useful for streamObject/structured-output regressions.
|
|
100
|
+
|
|
101
|
+
Image generation:
|
|
102
|
+
|
|
103
|
+
- `packages/api/scripts/smoke-test-image-generation.ts`
|
|
104
|
+
- Human-initiated only.
|
|
105
|
+
- Requires active OpenAI token with image entitlement and applicable migration.
|
|
106
|
+
- Env: `QGRID_URL`, `QGRID_OPENAI_MODEL`.
|
|
107
|
+
- Not part of CI; it consumes ChatGPT subscription quota.
|
|
108
|
+
|
|
109
|
+
Other scripts under `scripts/smoke-test-*` and `scripts/debug-*` are ad hoc probes. Read the script header and env vars before running.
|
|
110
|
+
|
|
111
|
+
## Common Error Triage
|
|
112
|
+
|
|
113
|
+
`NO_OPENAI_WORKERS`:
|
|
114
|
+
|
|
115
|
+
- Check active OpenAI tokens, worker startup logs, Codex CLI availability, and `OpenAIDispatcher.readyWorkerCount`.
|
|
116
|
+
- If tokens were recently changed, inspect `TokenSubscriber` status and reconcile behavior.
|
|
117
|
+
|
|
118
|
+
`NO_ACTIVE_WORKERS`:
|
|
119
|
+
|
|
120
|
+
- OpenAI workers exist but active tokens were deactivated.
|
|
121
|
+
- Check token `active` state and token subscriber event handling.
|
|
122
|
+
|
|
123
|
+
`SERVER_BUSY`:
|
|
124
|
+
|
|
125
|
+
- OpenAI worker queue is full or a queued request timed out.
|
|
126
|
+
- Check `QGRID_WORKERS_PER_TOKEN`, worker readiness, long-running turns, and queue pressure.
|
|
127
|
+
|
|
128
|
+
`QuotaThresholdExceededError` or `quota_threshold gate: all_exceeded`:
|
|
129
|
+
|
|
130
|
+
- All eligible tokens are above their `quota_threshold`.
|
|
131
|
+
- Check token thresholds and provider usage/rate-limit APIs.
|
|
132
|
+
- Lookup failures are fail-open, so all-exceeded means qgrid got usable over-threshold signals.
|
|
133
|
+
|
|
134
|
+
`Direct LLM API fallback not implemented`:
|
|
135
|
+
|
|
136
|
+
- Model likely lacks `openai/` or `anthropic/` prefix.
|
|
137
|
+
- Do not silently route prefix-less Claude model names to Anthropic.
|
|
138
|
+
|
|
139
|
+
`No anthropic tokens available`:
|
|
140
|
+
|
|
141
|
+
- Anthropic token pool is empty.
|
|
142
|
+
- Check active Anthropic tokens, subscriber reconcile, and token provider values.
|
|
143
|
+
|
|
144
|
+
`Claude session closed without result`:
|
|
145
|
+
|
|
146
|
+
- Claude child exited without a result line.
|
|
147
|
+
- Check stderr suffix in the error, spawn args, OAuth token validity, `CLAUDE_CONFIG_DIR` isolation, and CLI version.
|
|
148
|
+
|
|
149
|
+
`error_max_structured_output_retries`:
|
|
150
|
+
|
|
151
|
+
- Claude Code structured output failed after retries.
|
|
152
|
+
- Treat as a real error, not a recoverable partial result.
|
|
153
|
+
- Check schema strictness and `stream-json-adapter` behavior.
|
|
154
|
+
|
|
155
|
+
`qgrid: imageGeneration is not supported with streamText`:
|
|
156
|
+
|
|
157
|
+
- Image generation is non-stream only. Use `generateText`.
|
|
158
|
+
|
|
159
|
+
`image generation is not supported on the Anthropic route`:
|
|
160
|
+
|
|
161
|
+
- Image generation is OpenAI/Codex-only.
|
|
162
|
+
|
|
163
|
+
`imageGeneration was requested but the response contained no image`:
|
|
164
|
+
|
|
165
|
+
- AI SDK version-skew guard. The client requested image generation, but the server returned text-only content. Check server version and the OpenAI image-generation path.
|
|
166
|
+
|
|
167
|
+
OpenAI `ImageGenerationError` kinds:
|
|
168
|
+
|
|
169
|
+
- `gate`: capability/model check failed before the turn.
|
|
170
|
+
- `not_called`: model completed but did not call Codex `image_generation`.
|
|
171
|
+
- `incomplete`: image tool was attempted but no completed base64 image was produced.
|
|
172
|
+
|
|
173
|
+
`worker not ready`:
|
|
174
|
+
|
|
175
|
+
- OpenAI Codex worker RPC is missing or not initialized.
|
|
176
|
+
- Check worker spawn/login failures and restart logs.
|
|
177
|
+
|
|
178
|
+
`OpenAI refresh failed`:
|
|
179
|
+
|
|
180
|
+
- Inspect response body for refresh token death codes.
|
|
181
|
+
- If refresh token rotated but DB save failed, treat the token as at risk and require re-registration.
|
|
182
|
+
|
|
183
|
+
`re-login required`:
|
|
184
|
+
|
|
185
|
+
- Anthropic usage refresh failed and qgrid could not recover with stored refresh token.
|
|
186
|
+
|
|
187
|
+
## Debugging Rules
|
|
188
|
+
|
|
189
|
+
- Start from the package-specific tests before running smoke scripts.
|
|
190
|
+
- For provider issues, identify route by model prefix first.
|
|
191
|
+
- For cache issues, inspect `threadCoord`, `systemHash`, provider route, and usage semantics before assuming provider cache failure.
|
|
192
|
+
- For dashboard metric issues, verify qgrid-standard usage fields before changing UI formulas.
|
|
193
|
+
- For Sonamu shape changes, verify API generated files and web consumers together.
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import "../_virtual/_rolldown/runtime.js";
|
|
2
|
-
//#region src/migrations/20260705140615_alter_request_log_steps_alter1.ts
|
|
3
|
-
async function up(knex) {
|
|
4
|
-
await knex.raw(`CREATE INDEX request_log_steps_request_log_id_index ON request_log_steps USING btree(request_log_id ASC NULLS LAST);`);
|
|
5
|
-
}
|
|
6
|
-
async function down(knex) {
|
|
7
|
-
await knex.schema.alterTable("request_log_steps", (table) => {
|
|
8
|
-
table.dropIndex(["request_log_id"], "request_log_steps_request_log_id_index");
|
|
9
|
-
});
|
|
10
|
-
}
|
|
11
|
-
//#endregion
|
|
12
|
-
export { down, up };
|
|
13
|
-
|
|
14
|
-
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMjAyNjA3MDUxNDA2MTVfYWx0ZXJfcmVxdWVzdF9sb2dfc3RlcHNfYWx0ZXIxLmpzIiwibmFtZXMiOltdLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9taWdyYXRpb25zLzIwMjYwNzA1MTQwNjE1X2FsdGVyX3JlcXVlc3RfbG9nX3N0ZXBzX2FsdGVyMS50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyB0eXBlIEtuZXggfSBmcm9tIFwia25leFwiO1xuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gdXAoa25leDogS25leCk6IFByb21pc2U8dm9pZD4ge1xuICBhd2FpdCBrbmV4LnJhdyhcbiAgICBgQ1JFQVRFIElOREVYIHJlcXVlc3RfbG9nX3N0ZXBzX3JlcXVlc3RfbG9nX2lkX2luZGV4IE9OIHJlcXVlc3RfbG9nX3N0ZXBzIFVTSU5HIGJ0cmVlKHJlcXVlc3RfbG9nX2lkIEFTQyBOVUxMUyBMQVNUKTtgLFxuICApO1xufVxuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZG93bihrbmV4OiBLbmV4KTogUHJvbWlzZTx2b2lkPiB7XG4gIGF3YWl0IGtuZXguc2NoZW1hLmFsdGVyVGFibGUoXCJyZXF1ZXN0X2xvZ19zdGVwc1wiLCAodGFibGUpID0+IHtcbiAgICB0YWJsZS5kcm9wSW5kZXgoW1wicmVxdWVzdF9sb2dfaWRcIl0sIFwicmVxdWVzdF9sb2dfc3RlcHNfcmVxdWVzdF9sb2dfaWRfaW5kZXhcIik7XG4gIH0pO1xufVxuIl0sIm1hcHBpbmdzIjoiOztBQUVBLGVBQXNCLEdBQUcsTUFBMkI7QUFDbEQsT0FBTSxLQUFLLElBQ1QsdUhBQ0Q7O0FBR0gsZUFBc0IsS0FBSyxNQUEyQjtBQUNwRCxPQUFNLEtBQUssT0FBTyxXQUFXLHNCQUFzQixVQUFVO0FBQzNELFFBQU0sVUFBVSxDQUFDLGlCQUFpQixFQUFFLHlDQUF5QztHQUM3RSJ9
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { type Knex } from "knex";
|
|
2
|
-
|
|
3
|
-
export async function up(knex: Knex): Promise<void> {
|
|
4
|
-
await knex.raw(
|
|
5
|
-
`CREATE INDEX request_log_steps_request_log_id_index ON request_log_steps USING btree(request_log_id ASC NULLS LAST);`,
|
|
6
|
-
);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export async function down(knex: Knex): Promise<void> {
|
|
10
|
-
await knex.schema.alterTable("request_log_steps", (table) => {
|
|
11
|
-
table.dropIndex(["request_log_id"], "request_log_steps_request_log_id_index");
|
|
12
|
-
});
|
|
13
|
-
}
|