@cartanova/qgrid-cli 2.5.3 → 2.5.5

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 (65) hide show
  1. package/README.ko.md +7 -2
  2. package/README.md +7 -2
  3. package/bundle/dist/application/monit/monit.frame.js +3 -3
  4. package/bundle/dist/application/qgrid/oauth.js +1 -1
  5. package/bundle/dist/application/qgrid/qgrid.dispatcher.js +36 -14
  6. package/bundle/dist/application/qgrid/qgrid.frame.js +19 -5
  7. package/bundle/dist/application/qgrid/tool-emulation-schema.js +93 -0
  8. package/bundle/dist/application/qgrid/tool-emulation.js +116 -96
  9. package/bundle/dist/application/request-log/request-log.model.js +3 -3
  10. package/bundle/dist/application/request-log-step/request-log-step.model.js +2 -2
  11. package/bundle/dist/application/token/token.model.js +2 -2
  12. package/bundle/dist/server-error-handler.js +15 -0
  13. package/bundle/dist/sonamu.config.js +4 -7
  14. package/bundle/dist/utils/providers/anthropic/anthropic-dispatcher.js +4 -3
  15. package/bundle/dist/utils/providers/anthropic/claude-session.js +7 -5
  16. package/bundle/dist/utils/providers/common/json-schema-keywords.js +77 -0
  17. package/bundle/dist/utils/providers/common/schema-validation.js +178 -0
  18. package/bundle/dist/utils/providers/common/strictifier.js +332 -42
  19. package/bundle/dist/utils/providers/openai/codex-worker.js +16 -8
  20. package/bundle/dist/utils/providers/openai/openai-dispatcher.js +5 -37
  21. package/bundle/dist/utils/providers/openai/openai-worker-pool-config.js +37 -0
  22. package/bundle/src/application/monit/monit.frame.ts +1 -1
  23. package/bundle/src/application/qgrid/qgrid-run-lifecycle.test.ts +103 -0
  24. package/bundle/src/application/qgrid/qgrid.dispatcher.test.ts +337 -0
  25. package/bundle/src/application/qgrid/qgrid.dispatcher.ts +45 -25
  26. package/bundle/src/application/qgrid/qgrid.frame.test.ts +580 -8
  27. package/bundle/src/application/qgrid/qgrid.frame.ts +28 -1
  28. package/bundle/src/application/qgrid/tool-emulation-schema.test.ts +188 -0
  29. package/bundle/src/application/qgrid/tool-emulation-schema.ts +177 -0
  30. package/bundle/src/application/qgrid/tool-emulation.test.ts +285 -67
  31. package/bundle/src/application/qgrid/tool-emulation.ts +199 -102
  32. package/bundle/src/server-error-handler.ts +22 -0
  33. package/bundle/src/sonamu.config.test.ts +37 -0
  34. package/bundle/src/sonamu.config.ts +2 -5
  35. package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.test.ts +44 -0
  36. package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.ts +2 -2
  37. package/bundle/src/utils/providers/anthropic/claude-session.test.ts +10 -0
  38. package/bundle/src/utils/providers/anthropic/claude-session.ts +6 -1
  39. package/bundle/src/utils/providers/common/json-schema-keywords.ts +72 -0
  40. package/bundle/src/utils/providers/common/schema-validation.test.ts +284 -0
  41. package/bundle/src/utils/providers/common/schema-validation.ts +308 -0
  42. package/bundle/src/utils/providers/common/strictifier.test.ts +746 -0
  43. package/bundle/src/utils/providers/common/strictifier.ts +520 -52
  44. package/bundle/src/utils/providers/openai/codex-worker.test.ts +31 -0
  45. package/bundle/src/utils/providers/openai/codex-worker.ts +12 -4
  46. package/bundle/src/utils/providers/openai/openai-dispatcher.test.ts +6 -4
  47. package/bundle/src/utils/providers/openai/openai-dispatcher.ts +6 -79
  48. package/bundle/src/utils/providers/openai/openai-worker-pool-config.ts +80 -0
  49. package/bundle/web-dist/client/assets/{index-DXdGBfB8.js → index-CN_kMswh.js} +3 -3
  50. package/bundle/web-dist/client/assets/{logs-D_j1yxC4.js → logs-6nYj9OKL.js} +1 -1
  51. package/bundle/web-dist/client/assets/{monit-CIToyoxW.js → monit-CaPH9Uwc.js} +1 -1
  52. package/bundle/web-dist/client/assets/{routes-Dd21A-wi.js → routes-Dh5HPi-b.js} +1 -1
  53. package/bundle/web-dist/client/assets/{show-DfePWRbc.js → show-Be_LVd9d.js} +1 -1
  54. package/bundle/web-dist/client/assets/{tokens-B4MV2fdk.js → tokens-CAi3zu_0.js} +1 -1
  55. package/bundle/web-dist/client/index.html +1 -1
  56. package/bundle/web-dist/server/entry-server.generated.js +1 -1
  57. package/package.json +1 -1
  58. package/skills/qgrid/references/ai-sdk-provider-contract.md +34 -2
  59. package/skills/qgrid/references/anthropic-claude-code-runtime.md +3 -0
  60. package/skills/qgrid/references/cli-env-and-server-boot.md +1 -1
  61. package/skills/qgrid/references/decision-rationale.md +10 -1
  62. package/skills/qgrid/references/docs-workflow.md +9 -0
  63. package/skills/qgrid/references/repo-map.md +2 -1
  64. package/skills/qgrid/references/tool-calling-and-multiturn.md +145 -6
  65. package/skills/qgrid/references/verification-and-debugging.md +16 -1
@@ -49,7 +49,16 @@ Sources:
49
49
  Key decisions:
50
50
 
51
51
  - qgrid tool calling is intentionally implemented through structured-output emulation, not native provider tool use. The provider call returns a structured action with tool calls; the AI SDK loop executes tools and sends follow-up turns.
52
- - Tools and `jsonSchema` are mutually exclusive at the qgrid dispatcher boundary because both need the provider structured-output slot.
52
+ - Tools and `jsonSchema` share one provider structured-output slot by composing
53
+ an action envelope on every turn. Its final `answer` branch embeds the user's
54
+ schema; its `tool_call` branch carries emulated AI SDK calls. This keeps final
55
+ output constrained even when tools are available but never called.
56
+ - Final output is identified by `action: "answer"`, not by tool presence or turn
57
+ count. Structured answers are serialized as JSON text for AI SDK
58
+ `Output.object` parsing, while tools-only callers retain their legacy string
59
+ answer and tolerant parse fallback.
60
+ - AI SDK `toolChoice` remains outside qgrid's wire contract. Do not imply that
61
+ qgrid transports or enforces it.
53
62
  - Tool-call request logs are a multi-step run: create run, append generate/tool steps, then finish run. This makes AI SDK multi-step behavior visible in the dashboard instead of logging only one opaque completion.
54
63
  - Tool results update the existing `tool_call` step by `request_log_id + tool_call_id`. They are not logged as a second unrelated completion row.
55
64
  - The server infers single-turn completion versus an open tool run from context and finish reason. The removed caller-selected `logMode` contract must not be recreated.
@@ -40,3 +40,12 @@ When a repo doc establishes or changes durable rationale for a qgrid feature, re
40
40
  - `docs/qgrid-doc-map.json` maps each durable topic to code, canonical skill references, and the matching Notion page.
41
41
  - Code and tests remain the source of truth. Update the skill and Notion together when a code change alters a documented contract.
42
42
  - Keep dated operational snapshots, internal server addresses, and other environment-specific facts in Notion. Keep reusable runtime contracts in the skill.
43
+
44
+ ## Retract pre-release caveats after shipping
45
+
46
+ Documenting a contract before it ships is fine, but the caveat must be retracted once it does. Wording like "not deployed yet", "based on the working tree", "migration is still untracked", or "package version is still X" becomes a false statement the moment the release lands, and `pnpm qgrid-docs:check` will not catch it — that script validates the skill mirror and `docs/qgrid-doc-map.json` paths only, never Notion body text.
47
+
48
+ - When you add a pre-release caveat, record the retraction in the release page at the same time. Do not rely on memory.
49
+ - After a release, search the wiki for `미배포`, `배포되지 않았`, `working tree`, `untracked`, `아직`, and the previous version number.
50
+ - Update each page's baseline commit to the release commit. A stale baseline undermines every claim below it.
51
+ - This happened once already: caveats added 2026-07-23 survived the 2.4.9 and 2.5.0 releases and left six pages asserting the opposite of reality for several days.
@@ -28,7 +28,8 @@ Use this map to orient qgrid work quickly.
28
28
  - Cost/usage accounting: `packages/api/src/utils/providers/common/model-cost.ts`.
29
29
  - Weighted token selection: `packages/api/src/utils/providers/common/smooth-weighted-round-robin.ts`.
30
30
  - Boot order and startup migrations: `packages/api/src/server-bootstrap.ts`, `packages/api/src/startup-migrations.ts`.
31
- - Tool-call emulation: `packages/api/src/application/qgrid/tool-emulation.ts`.
31
+ - Tool-call schema composition: `packages/api/src/application/qgrid/tool-emulation-schema.ts`.
32
+ - Tool-call response decoding: `packages/api/src/application/qgrid/tool-emulation.ts`.
32
33
 
33
34
  ## Web/Sonamu hot spots
34
35
 
@@ -24,6 +24,11 @@ qgrid supports the AI SDK `tools` interface, but it does not use native provider
24
24
  - qgrid maps that structured object back into AI SDK `tool-call` content.
25
25
  - The AI SDK executes the actual tool functions and calls qgrid again with tool results.
26
26
 
27
+ `generateText` and `streamText` default to `stepCountIs(1)`. When an executable
28
+ tool can run before the final answer, callers must set a bounded `stopWhen`
29
+ (for example `stepCountIs(3)`) or the AI SDK stops after the first tool-call
30
+ step and cannot produce the final text or `Output.object` value.
31
+
27
32
  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
33
 
29
34
  ## Client Flow
@@ -36,7 +41,9 @@ When `options.tools` contains function tools:
36
41
  2. Convert them with `toQgridTool`.
37
42
  3. Send `tools` in `/api/qgrid/query` or `/api/qgrid/prepareStream`.
38
43
  4. Forward the per-call `logger` switch when request logging is disabled.
39
- 5. Do not send `jsonSchema` response format at the same time. The server infers whether the logged request becomes a multi-step run.
44
+ 5. When a top-level object response format is present, send `jsonSchema` alongside
45
+ `tools`. The server composes both schemas and infers whether the logged request
46
+ becomes a multi-step run.
40
47
 
41
48
  When qgrid responds with `finishReason: "tool-calls"`:
42
49
 
@@ -57,9 +64,10 @@ The registry is keyed per pending run so concurrent `generateText(...tools...)`
57
64
 
58
65
  ## Server Schema Emulation
59
66
 
60
- `packages/api/src/application/qgrid/tool-emulation.ts` builds the tool-call schema.
67
+ `packages/api/src/application/qgrid/tool-emulation-schema.ts` builds the tool-call schema.
68
+ `tool-emulation.ts` decodes the model's action envelope into qgrid answer/tool-call content.
61
69
 
62
- The schema shape is:
70
+ The tools-only schema shape is:
63
71
 
64
72
  ```json
65
73
  {
@@ -83,7 +91,125 @@ The schema explicitly tells the model:
83
91
 
84
92
  `qgrid.dispatcher.ts` strictifies this schema through `buildStrictOutputSchema` before it reaches provider dispatchers.
85
93
 
86
- If tools and `jsonSchema` are both present, dispatcher rejects the request.
94
+ ## Composing Tools With A User Output Schema
95
+
96
+ This section explains why `tool-emulation-schema.ts` exists at all. Read it
97
+ before changing schema composition; the mechanics look arbitrary without the
98
+ failure it was built to prevent.
99
+
100
+ ### The problem
101
+
102
+ AI SDK hands the provider `tools` and `responseFormat` independently on every
103
+ step, and gives no "this is the final turn" flag. Before 2.5.4, qgrid saw tools
104
+ and dropped the user schema, so `answer` stayed a free-form string:
105
+
106
+ ```json
107
+ { "action": "answer", "answer": "string | null", "toolCalls": "…" }
108
+ ```
109
+
110
+ `strict: true` was in force, but only over that outer envelope. A model could
111
+ therefore return valid JSON with trailing garbage and still satisfy the schema:
112
+
113
+ ```jsonc
114
+ // Envelope-valid. answer is a string, so the trailing marker is legal.
115
+ { "action": "answer", "answer": "{\"title\":\"...\"}<|proto_end|>", "toolCalls": null }
116
+ ```
117
+
118
+ Measured on the Medpath `translateBatch()` path: 5 of 10 raw Terra responses
119
+ were corrupted this way, while the same workload with a schema and no tools
120
+ failed 0 of 385 times. The model was fine; nothing constrained the final answer.
121
+
122
+ ### The fix, and why it needs rebasing
123
+
124
+ Embed the user schema in the `answer` branch so the provider's
125
+ constrained-output machinery enforces it — the same machinery that scored
126
+ 385/385. The model still chooses `tool_call` to continue the loop or `answer` to
127
+ finish, and qgrid never adds an extra turn to "finalize".
128
+
129
+ Embedding cannot be a verbatim nest, because `$ref` is document-absolute. `#`
130
+ means the root of the *final* document, not the root of the fragment it was
131
+ written in. Nest a caller schema unchanged and its pointers silently retarget
132
+ the envelope:
133
+
134
+ ```jsonc
135
+ // Caller sends this. "#/$defs/Person" resolves inside their own document.
136
+ {
137
+ "type": "object",
138
+ "properties": { "author": { "$ref": "#/$defs/Person" } },
139
+ "$defs": { "Person": { "type": "object", "properties": { "name": { "type": "string" } } } }
140
+ }
141
+ ```
142
+
143
+ Nested verbatim, `#/$defs/Person` now points at the envelope root, where no
144
+ `Person` exists — a broken reference, or worse, a coincidental match against an
145
+ unrelated definition. Validation would then check the wrong shape and say
146
+ nothing.
147
+
148
+ So composition does two things: park the caller schema under a reserved name,
149
+ and rewrite every local pointer to the new base.
150
+
151
+ ```jsonc
152
+ {
153
+ "type": "object",
154
+ "properties": {
155
+ "action": { "enum": ["answer", "tool_call"] },
156
+ // string branch becomes a reference to the caller's schema
157
+ "answer": { "anyOf": [{ "$ref": "#/$defs/__qgrid_user_output" }, { "type": "null" }] },
158
+ "toolCalls": { "…": "unchanged" }
159
+ },
160
+ "$defs": {
161
+ "__qgrid_user_output": {
162
+ "type": "object",
163
+ "properties": { "author": { "$ref": "#/$defs/__qgrid_user_output/$defs/Person" } },
164
+ "$defs": { "Person": { "type": "object", "properties": { "name": { "type": "string" } } } }
165
+ }
166
+ }
167
+ }
168
+ ```
169
+
170
+ Pointer rewriting is mechanical — `#` gains the namespace prefix:
171
+
172
+ | Caller writes | Becomes |
173
+ | --- | --- |
174
+ | `#` | `#/$defs/__qgrid_user_output` |
175
+ | `#/$defs/Person` | `#/$defs/__qgrid_user_output/$defs/Person` |
176
+
177
+ ### Why the traversal is exhaustive
178
+
179
+ A missed position means an un-rebased `$ref` shipping to the provider, so the
180
+ rewriter must visit every place a subschema can hide. JSON Schema puts them in
181
+ three shapes — name→schema maps (`properties`, `$defs`), schema arrays
182
+ (`anyOf`, `prefixItems`), and single slots (`items`, `not`, `if`) — plus Draft-7
183
+ `dependencies`, whose value is either a schema or a list of property names.
184
+
185
+ That position list is shared: `utils/providers/common/json-schema-keywords.ts`
186
+ owns it, and the rebaser, the normalization scan, and the strict rewrite all
187
+ read from it. They previously kept private copies and had already drifted
188
+ (`additionalItems` was rebased but not scanned). Add a new keyword there, not in
189
+ a consumer.
190
+
191
+ Consumers still differ in what they *do* per position, and that stays local: the
192
+ strictifier marks positions like `not` and `if` non-normalizable because
193
+ tightening them would change caller semantics.
194
+
195
+ ### What is rejected, and why silence is not an option
196
+
197
+ Composition fails loudly on `$id`, `id`, `$anchor`, `$dynamicAnchor`,
198
+ `$recursiveAnchor`, `$dynamicRef`, and `$recursiveRef`. Each one breaks the
199
+ premise that a pointer's meaning survives relocation: `$id` declares a new base
200
+ URI so descendant refs stop resolving against the document root, and dynamic
201
+ refs pick their target from runtime scope. There is no correct rewrite, and a
202
+ wrong one would validate the wrong shape without complaint — so callers get an
203
+ HTTP 400 naming the offending path instead.
204
+
205
+ Non-object top-level schemas are also rejected. Only `type: "object"` roots can
206
+ occupy the `answer` branch.
207
+
208
+ ### Compatibility
209
+
210
+ `answer` falls back to `{ "type": "string" }` whenever no user schema is present.
211
+ That single branch is why tools-only calls and older SDKs keep working against a
212
+ 2.5.4 server unchanged.
87
213
 
88
214
  ## Provider-Specific Path
89
215
 
@@ -111,9 +237,14 @@ In both providers, native provider tools are not the public abstraction. The pub
111
237
  `applyToolCallEmulation` handles provider text:
112
238
 
113
239
  - No tools: return normal text content.
114
- - Tools present and JSON parse fails: warn and fall back to text with `finishReason: "stop"`.
240
+ - Tools-only mode and JSON parse failure: preserve the legacy warning and text
241
+ fallback with `finishReason: "stop"`.
242
+ - Tools plus a user schema and malformed or incoherent envelope: return an
243
+ explicit structured-output error; do not rescue it as successful text.
115
244
  - `action: "tool_call"`: validate tool names, generate qgrid `toolCallId`, return `finishReason: "tool-calls"`.
116
- - `action: "answer"`: return final text with `finishReason: "stop"`.
245
+ - `action: "answer"` in tools-only mode: return the string answer as final text.
246
+ - `action: "answer"` with a user schema: JSON-serialize the structured `answer`
247
+ as final text so AI SDK can parse and validate `Output.object`.
117
248
 
118
249
  Unknown tool names throw.
119
250
 
@@ -128,6 +259,14 @@ AI SDK multi-step behavior is client-orchestrated:
128
259
 
129
260
  This is different from a provider-native tool loop. Neither Codex nor Claude Code executes the AI SDK tool functions.
130
261
 
262
+ The composed schema is reapplied to tool-result follow-up turns. Final output is
263
+ identified by `action: "answer"` rather than by whether a tool happened to be
264
+ available or called. AI SDK `toolChoice` is not transported or enforced by
265
+ qgrid; the model selects between the envelope's answer and tool-call actions.
266
+
267
+ The combined contract requires qgrid server 2.5.4 and
268
+ `@cartanova/qgrid-ai-sdk` 2.5.4.
269
+
131
270
  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.
132
271
 
133
272
  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.
@@ -30,6 +30,7 @@ AI SDK package `@cartanova/qgrid-ai-sdk`:
30
30
  - `pnpm --filter @cartanova/qgrid-ai-sdk test`.
31
31
  - `pnpm --filter @cartanova/qgrid-ai-sdk build`.
32
32
  - `pnpm --filter @cartanova/qgrid-ai-sdk e2e`.
33
+ - `pnpm --filter @cartanova/qgrid-ai-sdk e2e:tools-output`.
33
34
  - `pnpm --filter @cartanova/qgrid-ai-sdk e2e:logger`.
34
35
 
35
36
  CLI package `@cartanova/qgrid-cli`:
@@ -54,7 +55,7 @@ Choose by affected area:
54
55
  | AI SDK logger integration | `packages/ai-sdk/src/logger.test.ts` |
55
56
  | qgrid provider routing and strict schema | `packages/api/src/application/qgrid/qgrid.dispatcher.test.ts` |
56
57
  | 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
+ | tool-call emulation | `packages/api/src/application/qgrid/tool-emulation.test.ts`, `tool-emulation-schema.test.ts` |
58
59
  | request-log run lifecycle | `packages/api/src/application/qgrid/qgrid-run-lifecycle.test.ts` |
59
60
  | qgrid frame API behavior | `packages/api/src/application/qgrid/qgrid.frame.test.ts` |
60
61
  | token subscriber and LISTEN/NOTIFY handling | `packages/api/src/application/qgrid/token-subscriber.test.ts` |
@@ -101,6 +102,20 @@ AI SDK structured output:
101
102
  - Env: `QGRID_URL`, `SMOKE_MODEL`, `SMOKE_CASES`, `SMOKE_REPEAT`.
102
103
  - Uses AI SDK 6 `streamText` with `Output.object`; useful for structured-output regressions.
103
104
 
105
+ AI SDK tools plus structured output:
106
+
107
+ - `packages/ai-sdk/e2e/tools-structured-output.ts`
108
+ - This is the public-path release acceptance matrix: `qgrid()` HTTP/SSE,
109
+ `generateText`/`streamText`, OpenAI/Anthropic, direct final answers, and one
110
+ client-tool result followed by `Output.object`.
111
+ - It consumes real provider quota and requires the explicit
112
+ `QGRID_REAL_PROVIDER_ACCEPTANCE=1` opt-in.
113
+ - Uses `QGRID_URL`, `QGRID_ACCEPTANCE_OPENAI_MODEL`, and
114
+ `QGRID_ACCEPTANCE_ANTHROPIC_MODEL`. OpenAI direct-final coverage defaults to
115
+ 10 generate plus 10 stream attempts and can be changed with
116
+ `QGRID_ACCEPTANCE_OPENAI_DIRECT_REPEATS_PER_MODE`. Request logs use the stable
117
+ project name `qgrid-ai-sdk-tools-output-acceptance`.
118
+
104
119
  Image generation:
105
120
 
106
121
  - `packages/api/scripts/smoke-test-image-generation.ts`