@cartanova/qgrid-cli 2.5.3 → 2.5.4

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 (62) 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 +121 -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/schema-validation.js +178 -0
  17. package/bundle/dist/utils/providers/common/strictifier.js +329 -42
  18. package/bundle/dist/utils/providers/openai/codex-worker.js +16 -8
  19. package/bundle/dist/utils/providers/openai/openai-dispatcher.js +5 -37
  20. package/bundle/dist/utils/providers/openai/openai-worker-pool-config.js +37 -0
  21. package/bundle/src/application/monit/monit.frame.ts +1 -1
  22. package/bundle/src/application/qgrid/qgrid-run-lifecycle.test.ts +103 -0
  23. package/bundle/src/application/qgrid/qgrid.dispatcher.test.ts +337 -0
  24. package/bundle/src/application/qgrid/qgrid.dispatcher.ts +45 -25
  25. package/bundle/src/application/qgrid/qgrid.frame.test.ts +580 -8
  26. package/bundle/src/application/qgrid/qgrid.frame.ts +28 -1
  27. package/bundle/src/application/qgrid/tool-emulation-schema.test.ts +188 -0
  28. package/bundle/src/application/qgrid/tool-emulation-schema.ts +176 -0
  29. package/bundle/src/application/qgrid/tool-emulation.test.ts +285 -67
  30. package/bundle/src/application/qgrid/tool-emulation.ts +199 -102
  31. package/bundle/src/server-error-handler.ts +22 -0
  32. package/bundle/src/sonamu.config.test.ts +37 -0
  33. package/bundle/src/sonamu.config.ts +2 -5
  34. package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.test.ts +44 -0
  35. package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.ts +2 -2
  36. package/bundle/src/utils/providers/anthropic/claude-session.test.ts +10 -0
  37. package/bundle/src/utils/providers/anthropic/claude-session.ts +6 -1
  38. package/bundle/src/utils/providers/common/schema-validation.test.ts +284 -0
  39. package/bundle/src/utils/providers/common/schema-validation.ts +308 -0
  40. package/bundle/src/utils/providers/common/strictifier.test.ts +746 -0
  41. package/bundle/src/utils/providers/common/strictifier.ts +513 -52
  42. package/bundle/src/utils/providers/openai/codex-worker.test.ts +31 -0
  43. package/bundle/src/utils/providers/openai/codex-worker.ts +12 -4
  44. package/bundle/src/utils/providers/openai/openai-dispatcher.test.ts +6 -4
  45. package/bundle/src/utils/providers/openai/openai-dispatcher.ts +6 -79
  46. package/bundle/src/utils/providers/openai/openai-worker-pool-config.ts +80 -0
  47. package/bundle/web-dist/client/assets/{index-DXdGBfB8.js → index-DiY5V2e3.js} +3 -3
  48. package/bundle/web-dist/client/assets/{logs-D_j1yxC4.js → logs-UZ4Cvdmc.js} +1 -1
  49. package/bundle/web-dist/client/assets/{monit-CIToyoxW.js → monit-C5cJo-HD.js} +1 -1
  50. package/bundle/web-dist/client/assets/{routes-Dd21A-wi.js → routes-DEKEmoQN.js} +1 -1
  51. package/bundle/web-dist/client/assets/{show-DfePWRbc.js → show-DnX6b8KG.js} +1 -1
  52. package/bundle/web-dist/client/assets/{tokens-B4MV2fdk.js → tokens-Ct0FGD4b.js} +1 -1
  53. package/bundle/web-dist/client/index.html +1 -1
  54. package/bundle/web-dist/server/entry-server.generated.js +1 -1
  55. package/package.json +1 -1
  56. package/skills/qgrid/references/ai-sdk-provider-contract.md +34 -2
  57. package/skills/qgrid/references/anthropic-claude-code-runtime.md +3 -0
  58. package/skills/qgrid/references/cli-env-and-server-boot.md +1 -1
  59. package/skills/qgrid/references/decision-rationale.md +10 -1
  60. package/skills/qgrid/references/repo-map.md +2 -1
  61. package/skills/qgrid/references/tool-calling-and-multiturn.md +33 -6
  62. package/skills/qgrid/references/verification-and-debugging.md +16 -1
@@ -0,0 +1,188 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { type QgridTool } from "./qgrid.types";
4
+ import { buildToolCallSchema } from "./tool-emulation-schema";
5
+
6
+ const userPath = (...path: Array<string | number>) => [
7
+ "$defs",
8
+ "__qgrid_user_output",
9
+ ...path,
10
+ ];
11
+
12
+ describe("buildToolCallSchema", () => {
13
+ const tools: QgridTool[] = [
14
+ {
15
+ name: "getWeather",
16
+ description: "Get weather for a city",
17
+ inputSchema: {
18
+ type: "object",
19
+ properties: { city: { type: "string" } },
20
+ required: ["city"],
21
+ additionalProperties: false,
22
+ },
23
+ },
24
+ ];
25
+
26
+ it("documents that emulated tools must be requested through StructuredOutput", () => {
27
+ const schema = buildToolCallSchema(tools);
28
+
29
+ expect(schema).toHaveProperty(
30
+ "description",
31
+ expect.stringContaining("Do not invoke listed client tools as native Claude Code tools"),
32
+ );
33
+ expect(schema).toHaveProperty(
34
+ ["properties", "action", "description"],
35
+ expect.stringContaining("tool_call"),
36
+ );
37
+ expect(schema).toHaveProperty(
38
+ ["properties", "toolCalls", "description"],
39
+ expect.stringContaining("Do not call these tools as native"),
40
+ );
41
+ expect(schema).toHaveProperty(
42
+ ["properties", "toolCalls", "anyOf", 0, "items", "properties", "toolName", "description"],
43
+ expect.stringContaining("getWeather"),
44
+ );
45
+ });
46
+
47
+ it("preserves the legacy string answer shape when no user schema is supplied", () => {
48
+ const schema = buildToolCallSchema(tools);
49
+
50
+ expect(schema).not.toHaveProperty("$defs");
51
+ expect(schema).toHaveProperty("properties.answer.anyOf", [
52
+ { type: "string" },
53
+ { type: "null" },
54
+ ]);
55
+ });
56
+
57
+ it("namespaces a user schema and references it from answer", () => {
58
+ const userSchema = {
59
+ type: "object",
60
+ properties: { translated: { type: "string" } },
61
+ };
62
+ const schema = buildToolCallSchema(tools, userSchema);
63
+
64
+ expect(schema).toHaveProperty("properties.answer.anyOf", [
65
+ { $ref: "#/$defs/__qgrid_user_output" },
66
+ { type: "null" },
67
+ ]);
68
+ expect(schema).toHaveProperty(userPath(), userSchema);
69
+ });
70
+
71
+ it("rebases root and nested local refs into the user-schema namespace", () => {
72
+ const schema = buildToolCallSchema(tools, {
73
+ type: "object",
74
+ properties: {
75
+ self: { $ref: "#" },
76
+ item: { $ref: "#/$defs/Item" },
77
+ },
78
+ $defs: {
79
+ Item: {
80
+ type: "object",
81
+ properties: { parent: { $ref: "#" } },
82
+ },
83
+ },
84
+ });
85
+
86
+ expect(schema).toHaveProperty(
87
+ userPath("properties", "self", "$ref"),
88
+ "#/$defs/__qgrid_user_output",
89
+ );
90
+ expect(schema).toHaveProperty(
91
+ userPath("properties", "item", "$ref"),
92
+ "#/$defs/__qgrid_user_output/$defs/Item",
93
+ );
94
+ expect(schema).toHaveProperty(
95
+ userPath("$defs", "Item", "properties", "parent", "$ref"),
96
+ "#/$defs/__qgrid_user_output",
97
+ );
98
+ });
99
+
100
+ it("only traverses schema positions and preserves property names and literal data", () => {
101
+ const literal = {
102
+ $id: "literal-id",
103
+ $ref: "https://example.com/literal.json",
104
+ $dynamicRef: "#literal",
105
+ };
106
+ const schema = buildToolCallSchema(tools, {
107
+ type: "object",
108
+ properties: {
109
+ $id: { type: "string" },
110
+ $ref: { type: "string" },
111
+ payload: {
112
+ type: "object",
113
+ const: literal,
114
+ enum: [literal],
115
+ default: literal,
116
+ examples: [literal],
117
+ },
118
+ },
119
+ patternProperties: {
120
+ "^\\$dynamicRef$": { type: "string" },
121
+ },
122
+ dependentSchemas: {
123
+ $dynamicRef: {
124
+ type: "object",
125
+ properties: { value: { $ref: "#" } },
126
+ },
127
+ },
128
+ });
129
+
130
+ expect(schema).toHaveProperty(userPath("properties", "$id"), { type: "string" });
131
+ expect(schema).toHaveProperty(userPath("properties", "$ref"), { type: "string" });
132
+ expect(schema).toHaveProperty(userPath("patternProperties", "^\\$dynamicRef$"), {
133
+ type: "string",
134
+ });
135
+ expect(schema).toHaveProperty(userPath("properties", "payload", "const"), literal);
136
+ expect(schema).toHaveProperty(userPath("properties", "payload", "enum"), [literal]);
137
+ expect(schema).toHaveProperty(userPath("properties", "payload", "default"), literal);
138
+ expect(schema).toHaveProperty(userPath("properties", "payload", "examples"), [literal]);
139
+ expect(schema).toHaveProperty(
140
+ userPath("dependentSchemas", "$dynamicRef", "properties", "value", "$ref"),
141
+ "#/$defs/__qgrid_user_output",
142
+ );
143
+ });
144
+
145
+ it("rebases draft-07 additionalItems and schema dependencies while preserving property dependencies", () => {
146
+ const schema = buildToolCallSchema(tools, {
147
+ type: "array",
148
+ items: [{ type: "string" }],
149
+ additionalItems: { $ref: "#/$defs/Extra" },
150
+ dependencies: {
151
+ triggerSchema: {
152
+ type: "object",
153
+ properties: { extra: { $ref: "#/$defs/Extra" } },
154
+ },
155
+ triggerProperties: ["first", "$ref", "$id"],
156
+ },
157
+ $defs: {
158
+ Extra: { type: "string" },
159
+ },
160
+ });
161
+
162
+ expect(schema).toHaveProperty(
163
+ userPath("additionalItems", "$ref"),
164
+ "#/$defs/__qgrid_user_output/$defs/Extra",
165
+ );
166
+ expect(schema).toHaveProperty(
167
+ userPath("dependencies", "triggerSchema", "properties", "extra", "$ref"),
168
+ "#/$defs/__qgrid_user_output/$defs/Extra",
169
+ );
170
+ expect(schema).toHaveProperty(
171
+ userPath("dependencies", "triggerProperties"),
172
+ ["first", "$ref", "$id"],
173
+ );
174
+ });
175
+
176
+ it.each([
177
+ [{ $ref: "https://example.com/schema.json" }, "$.$ref"],
178
+ [{ $ref: "#named-anchor" }, "$.$ref"],
179
+ [{ $id: "urn:example:schema", type: "object" }, "$.$id"],
180
+ [{ $dynamicRef: "#node" }, "$.$dynamicRef"],
181
+ [{ $recursiveRef: "#" }, "$.$recursiveRef"],
182
+ [{ $recursiveAnchor: true }, "$.$recursiveAnchor"],
183
+ ])("rejects unsupported reference features with their path", (answerSchema, path) => {
184
+ expect(() => buildToolCallSchema(tools, answerSchema)).toThrowError(
185
+ expect.objectContaining({ path }),
186
+ );
187
+ });
188
+ });
@@ -0,0 +1,176 @@
1
+ import { type JsonValue } from "../../codex-protocol/serde_json/JsonValue";
2
+ import { type QgridTool } from "./qgrid.types";
3
+
4
+ const USER_OUTPUT_DEFINITION = "__qgrid_user_output";
5
+ const USER_OUTPUT_REF = `#/$defs/${USER_OUTPUT_DEFINITION}`;
6
+
7
+ export class ToolSchemaCompositionError extends Error {
8
+ constructor(
9
+ message: string,
10
+ public path: string,
11
+ ) {
12
+ super(`tool schema composition: ${message} (at ${path})`);
13
+ this.name = "ToolSchemaCompositionError";
14
+ }
15
+ }
16
+
17
+ export function buildToolCallSchema(tools: QgridTool[], answerSchema?: JsonValue): JsonValue {
18
+ const toolDescriptions = tools
19
+ .map((tool) => {
20
+ const schema = JSON.stringify(tool.inputSchema);
21
+ return `- ${tool.name}: ${tool.description ?? ""}\n inputSchema: ${schema}`;
22
+ })
23
+ .join("\n");
24
+
25
+ const normalizedAnswerSchema =
26
+ answerSchema === undefined ? undefined : rebaseUserOutputSchema(answerSchema);
27
+
28
+ return {
29
+ type: "object",
30
+ description:
31
+ "Use this schema only through StructuredOutput. Do not invoke listed client tools as native Claude Code tools. To request client-side tool execution, set action to tool_call and include toolCalls. Use action answer only when no further client tool result is needed.",
32
+ properties: {
33
+ action: {
34
+ type: "string",
35
+ enum: ["answer", "tool_call"],
36
+ description:
37
+ "Use tool_call to request client-side tool execution through this structured output. Use answer only for a final answer.",
38
+ },
39
+ answer: {
40
+ anyOf: [
41
+ normalizedAnswerSchema === undefined ? { type: "string" } : { $ref: USER_OUTPUT_REF },
42
+ { type: "null" },
43
+ ],
44
+ },
45
+ toolCalls: {
46
+ description:
47
+ "Client-side tool calls requested through structured output. Do not call these tools as native Claude Code tools.",
48
+ anyOf: [
49
+ {
50
+ type: "array",
51
+ items: {
52
+ type: "object",
53
+ properties: {
54
+ toolName: {
55
+ type: "string",
56
+ enum: tools.map((tool) => tool.name),
57
+ description: `Client-side tool name to request through structured output. Do not call this as a native Claude Code tool.\n${toolDescriptions}`,
58
+ },
59
+ args: {
60
+ type: "string",
61
+ description: "Tool arguments as a JSON string for the client-side tool.",
62
+ },
63
+ },
64
+ required: ["toolName", "args"],
65
+ additionalProperties: false,
66
+ },
67
+ },
68
+ { type: "null" },
69
+ ],
70
+ },
71
+ },
72
+ required: ["action", "answer", "toolCalls"],
73
+ additionalProperties: false,
74
+ ...(normalizedAnswerSchema === undefined
75
+ ? {}
76
+ : {
77
+ $defs: {
78
+ [USER_OUTPUT_DEFINITION]: normalizedAnswerSchema,
79
+ },
80
+ }),
81
+ };
82
+ }
83
+
84
+ function rebaseUserOutputSchema(schema: JsonValue): JsonValue {
85
+ return rewriteSchemaNode(schema, "$");
86
+ }
87
+
88
+ const SCHEMA_MAP_KEYWORDS = new Set([
89
+ "$defs",
90
+ "definitions",
91
+ "dependentSchemas",
92
+ "patternProperties",
93
+ "properties",
94
+ ]);
95
+ const SCHEMA_ARRAY_KEYWORDS = new Set(["allOf", "anyOf", "oneOf", "prefixItems"]);
96
+ const SCHEMA_KEYWORDS = new Set([
97
+ "additionalItems",
98
+ "additionalProperties",
99
+ "contains",
100
+ "contentSchema",
101
+ "else",
102
+ "if",
103
+ "items",
104
+ "not",
105
+ "propertyNames",
106
+ "then",
107
+ "unevaluatedItems",
108
+ "unevaluatedProperties",
109
+ ]);
110
+
111
+ function rewriteSchemaNode(value: JsonValue, path: string): JsonValue {
112
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
113
+
114
+ const source = value as Record<string, JsonValue>;
115
+ const result: Record<string, JsonValue> = { ...source };
116
+
117
+ for (const unsupportedKeyword of ["$id", "$dynamicRef", "$recursiveRef", "$recursiveAnchor"]) {
118
+ if (unsupportedKeyword in source) {
119
+ throw new ToolSchemaCompositionError(
120
+ `${unsupportedKeyword} is not supported`,
121
+ `${path}.${unsupportedKeyword}`,
122
+ );
123
+ }
124
+ }
125
+
126
+ if ("$ref" in source) {
127
+ const ref = source.$ref;
128
+ if (typeof ref !== "string" || (ref !== "#" && !ref.startsWith("#/"))) {
129
+ throw new ToolSchemaCompositionError(
130
+ "only root-relative JSON Pointer $ref values are supported",
131
+ `${path}.$ref`,
132
+ );
133
+ }
134
+ result.$ref = `${USER_OUTPUT_REF}${ref.slice(1)}`;
135
+ }
136
+
137
+ for (const [keyword, child] of Object.entries(source)) {
138
+ const keywordPath = `${path}.${keyword}`;
139
+
140
+ if (SCHEMA_MAP_KEYWORDS.has(keyword) && isJsonObject(child)) {
141
+ result[keyword] = Object.fromEntries(
142
+ Object.entries(child).map(([name, schema]) => [
143
+ name,
144
+ rewriteSchemaNode(schema, `${keywordPath}.${name}`),
145
+ ]),
146
+ ) as JsonValue;
147
+ } else if (keyword === "dependencies" && isJsonObject(child)) {
148
+ result[keyword] = Object.fromEntries(
149
+ Object.entries(child).map(([name, dependency]) => [
150
+ name,
151
+ Array.isArray(dependency)
152
+ ? dependency
153
+ : rewriteSchemaNode(dependency, `${keywordPath}.${name}`),
154
+ ]),
155
+ ) as JsonValue;
156
+ } else if (SCHEMA_ARRAY_KEYWORDS.has(keyword) && Array.isArray(child)) {
157
+ result[keyword] = child.map((schema, index) =>
158
+ rewriteSchemaNode(schema, `${keywordPath}[${index}]`),
159
+ ) as JsonValue;
160
+ } else if (SCHEMA_KEYWORDS.has(keyword)) {
161
+ if (keyword === "items" && Array.isArray(child)) {
162
+ result[keyword] = child.map((schema, index) =>
163
+ rewriteSchemaNode(schema, `${keywordPath}[${index}]`),
164
+ ) as JsonValue;
165
+ } else {
166
+ result[keyword] = rewriteSchemaNode(child, keywordPath);
167
+ }
168
+ }
169
+ }
170
+
171
+ return result as JsonValue;
172
+ }
173
+
174
+ function isJsonObject(value: JsonValue): value is Record<string, JsonValue> {
175
+ return value !== null && typeof value === "object" && !Array.isArray(value);
176
+ }