qingflow-mcp 0.4.0 → 0.4.2
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.md +7 -4
- package/dist/qingflow-client.js +46 -28
- package/dist/server.js +607 -96
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@ This MCP server wraps Qingflow OpenAPI for:
|
|
|
5
5
|
- `qf_apps_list`
|
|
6
6
|
- `qf_form_get`
|
|
7
7
|
- `qf_field_resolve`
|
|
8
|
+
- `qf_value_probe`
|
|
8
9
|
- `qf_query_plan`
|
|
9
10
|
- `qf_records_list`
|
|
10
11
|
- `qf_record_get`
|
|
@@ -108,7 +109,7 @@ npm i -g git+https://github.com/853046310/qingflow-mcp.git
|
|
|
108
109
|
Install from npm (pinned version):
|
|
109
110
|
|
|
110
111
|
```bash
|
|
111
|
-
npm i -g qingflow-mcp@0.4.
|
|
112
|
+
npm i -g qingflow-mcp@0.4.2
|
|
112
113
|
```
|
|
113
114
|
|
|
114
115
|
Or one-click installer:
|
|
@@ -145,8 +146,10 @@ MCP client config example:
|
|
|
145
146
|
|
|
146
147
|
1. `qf_apps_list` to pick app.
|
|
147
148
|
2. `qf_form_get` to inspect field ids/titles.
|
|
148
|
-
3. `
|
|
149
|
-
4.
|
|
149
|
+
3. `qf_field_resolve` for field-name to `que_id` mapping.
|
|
150
|
+
4. `qf_value_probe` when the agent needs candidate field values and explicit match evidence.
|
|
151
|
+
5. `qf_record_create` or `qf_record_update`.
|
|
152
|
+
6. If create/update returns only `request_id`, call `qf_operation_get` to resolve async result.
|
|
150
153
|
|
|
151
154
|
Full calling contract (Chinese):
|
|
152
155
|
|
|
@@ -214,7 +217,7 @@ Deterministic read protocol (list/summary/aggregate):
|
|
|
214
217
|
- objects must be native JSON objects
|
|
215
218
|
- booleans must be native JSON booleans
|
|
216
219
|
- unknown fields are rejected by the MCP boundary
|
|
217
|
-
7. Use `qf_query_plan` as the only preflight tool when the agent is unsure about arguments. It can normalize loose/model-shaped inputs before a real query is issued.
|
|
220
|
+
7. Use `qf_query_plan` as the only preflight tool when the agent is unsure about arguments. It can normalize loose/model-shaped inputs before a real query is issued, but runtime aliases like `from`/`to`/`dateFrom`/`dateTo`/`searchKey`/`searchKeys` are rejected.
|
|
218
221
|
|
|
219
222
|
For `qf_query(summary)` and `qf_records_aggregate`, read `data.summary.completeness` / `data.completeness` before concluding:
|
|
220
223
|
|
package/dist/qingflow-client.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { request as httpRequest } from "node:http";
|
|
2
|
+
import { request as httpsRequest } from "node:https";
|
|
1
3
|
export class QingflowApiError extends Error {
|
|
2
4
|
errCode;
|
|
3
5
|
errMsg;
|
|
@@ -94,25 +96,25 @@ export class QingflowClient {
|
|
|
94
96
|
if (options.userId) {
|
|
95
97
|
headers.userId = options.userId;
|
|
96
98
|
}
|
|
97
|
-
|
|
98
|
-
method: params.method,
|
|
99
|
-
headers
|
|
100
|
-
};
|
|
99
|
+
let requestBody;
|
|
101
100
|
if (options.body !== undefined) {
|
|
102
101
|
headers["content-type"] = "application/json";
|
|
103
|
-
|
|
102
|
+
requestBody = JSON.stringify(options.body);
|
|
103
|
+
headers["content-length"] = String(Buffer.byteLength(requestBody));
|
|
104
104
|
}
|
|
105
|
-
const controller = new AbortController();
|
|
106
|
-
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
107
|
-
init.signal = controller.signal;
|
|
108
105
|
try {
|
|
109
|
-
const response = await
|
|
110
|
-
|
|
106
|
+
const response = await sendHttpRequest(url, {
|
|
107
|
+
method: params.method,
|
|
108
|
+
headers,
|
|
109
|
+
body: requestBody,
|
|
110
|
+
timeoutMs: this.timeoutMs
|
|
111
|
+
});
|
|
112
|
+
const text = response.text;
|
|
111
113
|
const data = safeJsonParse(text);
|
|
112
|
-
if (
|
|
114
|
+
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
113
115
|
throw new QingflowApiError({
|
|
114
|
-
message: `Qingflow HTTP ${response.
|
|
115
|
-
httpStatus: response.
|
|
116
|
+
message: `Qingflow HTTP ${response.statusCode}`,
|
|
117
|
+
httpStatus: response.statusCode,
|
|
116
118
|
errMsg: extractErrMsg(data, text),
|
|
117
119
|
details: data ?? text
|
|
118
120
|
});
|
|
@@ -120,7 +122,7 @@ export class QingflowClient {
|
|
|
120
122
|
if (!data || typeof data !== "object") {
|
|
121
123
|
throw new QingflowApiError({
|
|
122
124
|
message: "Qingflow response is not JSON object",
|
|
123
|
-
httpStatus: response.
|
|
125
|
+
httpStatus: response.statusCode,
|
|
124
126
|
details: text
|
|
125
127
|
});
|
|
126
128
|
}
|
|
@@ -128,7 +130,7 @@ export class QingflowClient {
|
|
|
128
130
|
if (parsed.errCode === null) {
|
|
129
131
|
throw new QingflowApiError({
|
|
130
132
|
message: "Qingflow response missing code field",
|
|
131
|
-
httpStatus: response.
|
|
133
|
+
httpStatus: response.statusCode,
|
|
132
134
|
details: data
|
|
133
135
|
});
|
|
134
136
|
}
|
|
@@ -137,7 +139,7 @@ export class QingflowClient {
|
|
|
137
139
|
message: `Qingflow API error ${parsed.errCode}: ${parsed.errMsg}`,
|
|
138
140
|
errCode: parsed.errCode,
|
|
139
141
|
errMsg: parsed.errMsg,
|
|
140
|
-
httpStatus: response.
|
|
142
|
+
httpStatus: response.statusCode,
|
|
141
143
|
details: data
|
|
142
144
|
});
|
|
143
145
|
}
|
|
@@ -151,9 +153,9 @@ export class QingflowClient {
|
|
|
151
153
|
if (error instanceof QingflowApiError) {
|
|
152
154
|
throw error;
|
|
153
155
|
}
|
|
154
|
-
if (error instanceof Error && error.
|
|
156
|
+
if (error instanceof Error && error.message.startsWith("Qingflow request timeout after")) {
|
|
155
157
|
throw new QingflowApiError({
|
|
156
|
-
message:
|
|
158
|
+
message: error.message
|
|
157
159
|
});
|
|
158
160
|
}
|
|
159
161
|
throw new QingflowApiError({
|
|
@@ -161,9 +163,6 @@ export class QingflowClient {
|
|
|
161
163
|
details: error
|
|
162
164
|
});
|
|
163
165
|
}
|
|
164
|
-
finally {
|
|
165
|
-
clearTimeout(timer);
|
|
166
|
-
}
|
|
167
166
|
}
|
|
168
167
|
}
|
|
169
168
|
function normalizeBaseUrl(url) {
|
|
@@ -254,12 +253,31 @@ function toFiniteNumber(value) {
|
|
|
254
253
|
}
|
|
255
254
|
return null;
|
|
256
255
|
}
|
|
257
|
-
function
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
256
|
+
async function sendHttpRequest(url, params) {
|
|
257
|
+
const requestImpl = url.protocol === "https:" ? httpsRequest : httpRequest;
|
|
258
|
+
return await new Promise((resolve, reject) => {
|
|
259
|
+
const req = requestImpl(url, {
|
|
260
|
+
method: params.method,
|
|
261
|
+
headers: params.headers
|
|
262
|
+
}, (res) => {
|
|
263
|
+
const chunks = [];
|
|
264
|
+
res.on("data", (chunk) => {
|
|
265
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
266
|
+
});
|
|
267
|
+
res.on("end", () => {
|
|
268
|
+
resolve({
|
|
269
|
+
statusCode: res.statusCode ?? 0,
|
|
270
|
+
text: Buffer.concat(chunks).toString("utf8")
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
req.setTimeout(params.timeoutMs, () => {
|
|
275
|
+
req.destroy(new Error(`Qingflow request timeout after ${params.timeoutMs}ms`));
|
|
276
|
+
});
|
|
277
|
+
req.on("error", reject);
|
|
278
|
+
if (params.body !== undefined) {
|
|
279
|
+
req.write(params.body);
|
|
280
|
+
}
|
|
281
|
+
req.end();
|
|
264
282
|
});
|
|
265
283
|
}
|
package/dist/server.js
CHANGED
|
@@ -66,7 +66,7 @@ const ADAPTIVE_TARGET_PAGE_MS = toPositiveInt(process.env.QINGFLOW_ADAPTIVE_TARG
|
|
|
66
66
|
const MAX_LIST_ITEMS_BYTES = toPositiveInt(process.env.QINGFLOW_LIST_MAX_ITEMS_BYTES) ?? 400000;
|
|
67
67
|
const REQUEST_TIMEOUT_MS = toPositiveInt(process.env.QINGFLOW_REQUEST_TIMEOUT_MS) ?? 18000;
|
|
68
68
|
const EXECUTION_BUDGET_MS = toPositiveInt(process.env.QINGFLOW_EXECUTION_BUDGET_MS) ?? 20000;
|
|
69
|
-
const SERVER_VERSION = "0.4.
|
|
69
|
+
const SERVER_VERSION = "0.4.2";
|
|
70
70
|
const accessToken = process.env.QINGFLOW_ACCESS_TOKEN;
|
|
71
71
|
const baseUrl = process.env.QINGFLOW_BASE_URL;
|
|
72
72
|
if (!accessToken) {
|
|
@@ -137,6 +137,8 @@ const apiMetaSchema = z.object({
|
|
|
137
137
|
base_url: z.string()
|
|
138
138
|
});
|
|
139
139
|
const outputProfileSchema = z.enum(["compact", "verbose"]);
|
|
140
|
+
const matchModeValues = ["exact", "normalized", "contains", "prefix", "fuzzy"];
|
|
141
|
+
const matchModeSchema = z.enum(matchModeValues);
|
|
140
142
|
const completenessSchema = z.object({
|
|
141
143
|
result_amount: z.number().int().nonnegative(),
|
|
142
144
|
returned_items: z.number().int().nonnegative(),
|
|
@@ -156,7 +158,12 @@ const completenessSchema = z.object({
|
|
|
156
158
|
output_page_complete: z.boolean().optional(),
|
|
157
159
|
raw_next_page_token: z.string().nullable().optional(),
|
|
158
160
|
output_next_page_token: z.string().nullable().optional(),
|
|
159
|
-
stop_reason: z.string().nullable().optional()
|
|
161
|
+
stop_reason: z.string().nullable().optional(),
|
|
162
|
+
provider_result_amount: z.number().int().nonnegative().nullable().optional(),
|
|
163
|
+
source_record_count: z.number().int().nonnegative().optional(),
|
|
164
|
+
effective_record_count: z.number().int().nonnegative().optional(),
|
|
165
|
+
returned_group_count: z.number().int().nonnegative().optional(),
|
|
166
|
+
total_group_count: z.number().int().nonnegative().optional()
|
|
160
167
|
});
|
|
161
168
|
const evidenceSchema = z.object({
|
|
162
169
|
query_id: z.string(),
|
|
@@ -237,7 +244,7 @@ const publicFieldSelectorSchema = z.union([publicStringSchema, z.number().int()]
|
|
|
237
244
|
const publicSortItemSchema = z.object({
|
|
238
245
|
que_id: publicFieldSelectorSchema,
|
|
239
246
|
ascend: z.boolean().optional()
|
|
240
|
-
});
|
|
247
|
+
}).strict();
|
|
241
248
|
const publicFilterItemSchema = z.object({
|
|
242
249
|
que_id: publicFieldSelectorSchema.optional(),
|
|
243
250
|
search_key: publicStringSchema.optional(),
|
|
@@ -247,17 +254,17 @@ const publicFilterItemSchema = z.object({
|
|
|
247
254
|
scope: z.number().int().optional(),
|
|
248
255
|
search_options: z.array(publicFieldSelectorSchema).optional(),
|
|
249
256
|
search_user_ids: z.array(publicStringSchema).optional()
|
|
250
|
-
});
|
|
257
|
+
}).strict();
|
|
251
258
|
const publicTimeRangeSchema = z.object({
|
|
252
259
|
column: publicFieldSelectorSchema,
|
|
253
260
|
from: publicStringSchema.optional(),
|
|
254
261
|
to: publicStringSchema.optional(),
|
|
255
262
|
timezone: publicStringSchema.optional()
|
|
256
|
-
});
|
|
263
|
+
}).strict();
|
|
257
264
|
const publicStatPolicySchema = z.object({
|
|
258
265
|
include_negative: z.boolean().optional(),
|
|
259
266
|
include_null: z.boolean().optional()
|
|
260
|
-
});
|
|
267
|
+
}).strict();
|
|
261
268
|
const publicAnswerInputSchema = z.object({
|
|
262
269
|
que_id: publicFieldSelectorSchema.optional(),
|
|
263
270
|
queId: publicFieldSelectorSchema.optional(),
|
|
@@ -342,7 +349,8 @@ const listInputPublicSchema = z
|
|
|
342
349
|
include_answers: z.boolean().optional(),
|
|
343
350
|
strict_full: z.boolean().optional(),
|
|
344
351
|
output_profile: outputProfileSchema.optional()
|
|
345
|
-
})
|
|
352
|
+
})
|
|
353
|
+
.strict();
|
|
346
354
|
const listInputSchema = z
|
|
347
355
|
.preprocess(normalizeListInput, z.object({
|
|
348
356
|
app_key: z.string().min(1).optional(),
|
|
@@ -603,7 +611,8 @@ const queryInputPublicSchema = z
|
|
|
603
611
|
scan_max_pages: z.number().int().positive().max(500).optional(),
|
|
604
612
|
strict_full: z.boolean().optional(),
|
|
605
613
|
output_profile: outputProfileSchema.optional()
|
|
606
|
-
})
|
|
614
|
+
})
|
|
615
|
+
.strict();
|
|
607
616
|
const queryInputSchema = z
|
|
608
617
|
.preprocess(normalizeQueryInput, z.object({
|
|
609
618
|
query_mode: z.enum(["auto", "list", "record", "summary"]).optional(),
|
|
@@ -785,7 +794,8 @@ const aggregateInputPublicSchema = z
|
|
|
785
794
|
max_groups: z.number().int().positive().max(2000).optional(),
|
|
786
795
|
strict_full: z.boolean().optional(),
|
|
787
796
|
output_profile: outputProfileSchema.optional()
|
|
788
|
-
})
|
|
797
|
+
})
|
|
798
|
+
.strict();
|
|
789
799
|
const aggregateInputSchema = z
|
|
790
800
|
.preprocess(normalizeAggregateInput, z.object({
|
|
791
801
|
app_key: z.string().min(1),
|
|
@@ -936,6 +946,60 @@ const fieldResolveOutputSchema = z.object({
|
|
|
936
946
|
}),
|
|
937
947
|
meta: apiMetaSchema
|
|
938
948
|
});
|
|
949
|
+
const valueProbeInputPublicSchema = z
|
|
950
|
+
.object({
|
|
951
|
+
app_key: publicStringSchema,
|
|
952
|
+
field: publicFieldSelectorSchema,
|
|
953
|
+
query: publicStringSchema.optional(),
|
|
954
|
+
match_mode: matchModeSchema.optional(),
|
|
955
|
+
limit: z.number().int().positive().max(50).optional(),
|
|
956
|
+
scan_max_pages: z.number().int().positive().max(20).optional(),
|
|
957
|
+
page_size: z.number().int().positive().max(200).optional()
|
|
958
|
+
})
|
|
959
|
+
.strict();
|
|
960
|
+
const valueProbeInputSchema = z.preprocess(normalizeValueProbeInput, z
|
|
961
|
+
.object({
|
|
962
|
+
app_key: z.string().min(1),
|
|
963
|
+
field: z.union([z.string().min(1), z.number().int()]),
|
|
964
|
+
query: z.string().optional(),
|
|
965
|
+
match_mode: matchModeSchema.optional(),
|
|
966
|
+
limit: z.number().int().positive().max(50).optional(),
|
|
967
|
+
scan_max_pages: z.number().int().positive().max(20).optional(),
|
|
968
|
+
page_size: z.number().int().positive().max(200).optional()
|
|
969
|
+
})
|
|
970
|
+
.strict());
|
|
971
|
+
const valueProbeOutputSchema = z.object({
|
|
972
|
+
ok: z.literal(true),
|
|
973
|
+
data: z.object({
|
|
974
|
+
app_key: z.string(),
|
|
975
|
+
field: z.object({
|
|
976
|
+
requested: z.string(),
|
|
977
|
+
que_id: z.union([z.string(), z.number()]),
|
|
978
|
+
que_title: z.string().nullable(),
|
|
979
|
+
que_type: z.unknown()
|
|
980
|
+
}),
|
|
981
|
+
query: z.string().nullable(),
|
|
982
|
+
requested_match_mode: matchModeSchema,
|
|
983
|
+
effective_match_mode: matchModeSchema,
|
|
984
|
+
provider_translation: z.string(),
|
|
985
|
+
scanned_pages: z.number().int().nonnegative(),
|
|
986
|
+
scanned_records: z.number().int().nonnegative(),
|
|
987
|
+
provider_result_amount: z.number().int().nonnegative().nullable(),
|
|
988
|
+
candidates: z.array(z.object({
|
|
989
|
+
value: z.unknown(),
|
|
990
|
+
display_value: z.string(),
|
|
991
|
+
count: z.number().int().nonnegative(),
|
|
992
|
+
match_strength: z.number().min(0).max(1),
|
|
993
|
+
matched_texts: z.array(z.string()),
|
|
994
|
+
matched_as: z.string()
|
|
995
|
+
})),
|
|
996
|
+
matched_values: z.array(z.unknown())
|
|
997
|
+
}),
|
|
998
|
+
meta: z.object({
|
|
999
|
+
version: z.string(),
|
|
1000
|
+
generated_at: z.string()
|
|
1001
|
+
})
|
|
1002
|
+
});
|
|
939
1003
|
const queryPlanInputPublicSchema = z
|
|
940
1004
|
.object({
|
|
941
1005
|
tool: publicStringSchema,
|
|
@@ -1072,7 +1136,8 @@ const exportInputPublicSchema = z
|
|
|
1072
1136
|
output_profile: outputProfileSchema.optional(),
|
|
1073
1137
|
export_dir: publicStringSchema.optional(),
|
|
1074
1138
|
file_name: publicStringSchema.optional()
|
|
1075
|
-
})
|
|
1139
|
+
})
|
|
1140
|
+
.strict();
|
|
1076
1141
|
const exportInputSchema = z.preprocess(normalizeExportInput, z.object({
|
|
1077
1142
|
app_key: z.string().min(1).optional(),
|
|
1078
1143
|
user_id: z.string().min(1).optional(),
|
|
@@ -1168,13 +1233,7 @@ const exportOutputSchema = z.object({
|
|
|
1168
1233
|
meta: apiMetaSchema.optional()
|
|
1169
1234
|
});
|
|
1170
1235
|
const canonicalFieldRefSchema = publicFieldSelectorSchema;
|
|
1171
|
-
const canonicalMatchModeSchema =
|
|
1172
|
-
"exact",
|
|
1173
|
-
"normalized",
|
|
1174
|
-
"contains",
|
|
1175
|
-
"prefix",
|
|
1176
|
-
"fuzzy"
|
|
1177
|
-
]);
|
|
1236
|
+
const canonicalMatchModeSchema = matchModeSchema;
|
|
1178
1237
|
const canonicalWhereOpSchema = z.enum([
|
|
1179
1238
|
"eq",
|
|
1180
1239
|
"neq",
|
|
@@ -1199,6 +1258,7 @@ const canonicalWhereItemSchema = z
|
|
|
1199
1258
|
to: z.union([z.string(), z.number()]).optional(),
|
|
1200
1259
|
match: canonicalMatchModeSchema.optional()
|
|
1201
1260
|
})
|
|
1261
|
+
.strict()
|
|
1202
1262
|
.superRefine((value, ctx) => {
|
|
1203
1263
|
if (value.op === "between") {
|
|
1204
1264
|
if (value.from === undefined && value.to === undefined) {
|
|
@@ -1228,13 +1288,16 @@ const canonicalWhereItemSchema = z
|
|
|
1228
1288
|
});
|
|
1229
1289
|
}
|
|
1230
1290
|
});
|
|
1231
|
-
const canonicalSortItemSchema = z
|
|
1291
|
+
const canonicalSortItemSchema = z
|
|
1292
|
+
.object({
|
|
1232
1293
|
field: canonicalFieldRefSchema,
|
|
1233
1294
|
direction: canonicalSortDirectionSchema.optional()
|
|
1234
|
-
})
|
|
1295
|
+
})
|
|
1296
|
+
.strict();
|
|
1235
1297
|
const canonicalSelectSchema = z.array(canonicalFieldRefSchema).min(1).max(MAX_COLUMN_LIMIT);
|
|
1236
1298
|
const canonicalGroupBySchema = z.array(canonicalFieldRefSchema).min(1).max(20);
|
|
1237
|
-
const canonicalBaseQueryInputSchema = z
|
|
1299
|
+
const canonicalBaseQueryInputSchema = z
|
|
1300
|
+
.object({
|
|
1238
1301
|
app_key: z.string().min(1),
|
|
1239
1302
|
select: canonicalSelectSchema.optional(),
|
|
1240
1303
|
where: z.array(canonicalWhereItemSchema).optional(),
|
|
@@ -1263,27 +1326,35 @@ const canonicalBaseQueryInputSchema = z.object({
|
|
|
1263
1326
|
"cc"
|
|
1264
1327
|
])
|
|
1265
1328
|
.optional()
|
|
1266
|
-
})
|
|
1267
|
-
|
|
1329
|
+
})
|
|
1330
|
+
.strict();
|
|
1331
|
+
const canonicalPlanInputSchema = z
|
|
1332
|
+
.object({
|
|
1268
1333
|
kind: z.enum(["rows", "record", "aggregate", "export", "mutate"]),
|
|
1269
1334
|
query: z.record(z.unknown()).optional(),
|
|
1270
1335
|
action: z.record(z.unknown()).optional(),
|
|
1271
1336
|
probe: z.boolean().optional(),
|
|
1272
1337
|
resolve_fields: z.boolean().optional()
|
|
1273
|
-
})
|
|
1274
|
-
|
|
1338
|
+
})
|
|
1339
|
+
.strict();
|
|
1340
|
+
const canonicalRowsInputSchema = canonicalBaseQueryInputSchema
|
|
1341
|
+
.extend({
|
|
1275
1342
|
select: canonicalSelectSchema
|
|
1276
|
-
})
|
|
1277
|
-
|
|
1343
|
+
})
|
|
1344
|
+
.strict();
|
|
1345
|
+
const canonicalRecordInputSchema = z
|
|
1346
|
+
.object({
|
|
1278
1347
|
apply_id: canonicalFieldRefSchema,
|
|
1279
1348
|
select: canonicalSelectSchema,
|
|
1280
1349
|
output_profile: outputProfileSchema.optional()
|
|
1281
|
-
})
|
|
1350
|
+
})
|
|
1351
|
+
.strict();
|
|
1282
1352
|
const canonicalAggregateMetricInputSchema = z
|
|
1283
1353
|
.object({
|
|
1284
1354
|
column: canonicalFieldRefSchema.optional(),
|
|
1285
1355
|
op: z.enum(["count", "sum", "avg", "min", "max"])
|
|
1286
1356
|
})
|
|
1357
|
+
.strict()
|
|
1287
1358
|
.superRefine((value, ctx) => {
|
|
1288
1359
|
if (value.op !== "count" && value.column === undefined) {
|
|
1289
1360
|
ctx.addIssue({
|
|
@@ -1292,13 +1363,16 @@ const canonicalAggregateMetricInputSchema = z
|
|
|
1292
1363
|
});
|
|
1293
1364
|
}
|
|
1294
1365
|
});
|
|
1295
|
-
const canonicalAggregateInputSchema = canonicalBaseQueryInputSchema
|
|
1366
|
+
const canonicalAggregateInputSchema = canonicalBaseQueryInputSchema
|
|
1367
|
+
.extend({
|
|
1296
1368
|
group_by: canonicalGroupBySchema,
|
|
1297
1369
|
metrics: z.array(canonicalAggregateMetricInputSchema).min(1).max(10),
|
|
1298
1370
|
time_bucket: z.enum(["day", "week", "month"]).optional(),
|
|
1299
1371
|
top_n: z.number().int().positive().max(2000).optional()
|
|
1300
|
-
})
|
|
1301
|
-
|
|
1372
|
+
})
|
|
1373
|
+
.strict();
|
|
1374
|
+
const canonicalMutateInputSchema = z
|
|
1375
|
+
.object({
|
|
1302
1376
|
action: z.enum(["create", "update"]),
|
|
1303
1377
|
app_key: z.string().min(1).optional(),
|
|
1304
1378
|
apply_id: canonicalFieldRefSchema.optional(),
|
|
@@ -1306,24 +1380,33 @@ const canonicalMutateInputSchema = z.object({
|
|
|
1306
1380
|
fields: z.record(z.unknown()).optional(),
|
|
1307
1381
|
answers: z.array(publicAnswerInputSchema).optional(),
|
|
1308
1382
|
force_refresh_form: z.boolean().optional()
|
|
1309
|
-
})
|
|
1310
|
-
|
|
1383
|
+
})
|
|
1384
|
+
.strict();
|
|
1385
|
+
const canonicalExportInputSchema = canonicalBaseQueryInputSchema
|
|
1386
|
+
.extend({
|
|
1311
1387
|
select: canonicalSelectSchema,
|
|
1312
1388
|
format: z.enum(["csv", "json"]).optional(),
|
|
1313
1389
|
file_name: z.string().min(1).optional(),
|
|
1314
1390
|
export_dir: z.string().min(1).optional()
|
|
1315
|
-
})
|
|
1391
|
+
})
|
|
1392
|
+
.strict();
|
|
1316
1393
|
const canonicalResourceRefSchema = z.object({
|
|
1317
1394
|
uri: z.string(),
|
|
1318
1395
|
name: z.string(),
|
|
1319
1396
|
mime_type: z.string(),
|
|
1320
1397
|
description: z.string().nullable().optional()
|
|
1321
1398
|
});
|
|
1399
|
+
const canonicalExecutionPlanSchema = z.object({
|
|
1400
|
+
tool: z.string().nullable(),
|
|
1401
|
+
arguments: z.record(z.unknown()).nullable(),
|
|
1402
|
+
direct_execute: z.boolean()
|
|
1403
|
+
});
|
|
1322
1404
|
const canonicalPlanOutputSchema = z.object({
|
|
1323
1405
|
ok: z.literal(true),
|
|
1324
1406
|
data: z.object({
|
|
1325
1407
|
kind: z.string(),
|
|
1326
1408
|
normalized_query: z.record(z.unknown()),
|
|
1409
|
+
plan: canonicalExecutionPlanSchema,
|
|
1327
1410
|
internal_tool: z.string().nullable(),
|
|
1328
1411
|
internal_arguments: z.record(z.unknown()).nullable(),
|
|
1329
1412
|
field_mapping: z.array(z.record(z.unknown())),
|
|
@@ -1382,6 +1465,9 @@ const canonicalAggregateOutputSchema = z.object({
|
|
|
1382
1465
|
primary_metric_column: z.string().nullable(),
|
|
1383
1466
|
summary: z.object({
|
|
1384
1467
|
record_count: z.number().int().nonnegative(),
|
|
1468
|
+
provider_result_amount: z.number().int().nonnegative().nullable(),
|
|
1469
|
+
returned_group_count: z.number().int().nonnegative(),
|
|
1470
|
+
total_group_count: z.number().int().nonnegative(),
|
|
1385
1471
|
metrics_by_column: z.record(z.record(z.union([z.number(), z.null()])))
|
|
1386
1472
|
}),
|
|
1387
1473
|
groups: z.array(z.record(z.unknown())),
|
|
@@ -1567,6 +1653,25 @@ server.registerTool("qf_field_resolve", {
|
|
|
1567
1653
|
return errorResult(error);
|
|
1568
1654
|
}
|
|
1569
1655
|
});
|
|
1656
|
+
server.registerTool("qf_value_probe", {
|
|
1657
|
+
title: "Qingflow Value Probe",
|
|
1658
|
+
description: "Probe likely field values for one app field, with explicit match mode and matched value evidence.",
|
|
1659
|
+
inputSchema: valueProbeInputPublicSchema,
|
|
1660
|
+
outputSchema: valueProbeOutputSchema,
|
|
1661
|
+
annotations: {
|
|
1662
|
+
readOnlyHint: true,
|
|
1663
|
+
idempotentHint: true
|
|
1664
|
+
}
|
|
1665
|
+
}, async (args) => {
|
|
1666
|
+
try {
|
|
1667
|
+
const parsedArgs = valueProbeInputSchema.parse(args);
|
|
1668
|
+
const payload = await executeValueProbe(parsedArgs);
|
|
1669
|
+
return okResult(payload, `Probed values for ${parsedArgs.app_key}:${String(parsedArgs.field)}`);
|
|
1670
|
+
}
|
|
1671
|
+
catch (error) {
|
|
1672
|
+
return errorResult(error);
|
|
1673
|
+
}
|
|
1674
|
+
});
|
|
1570
1675
|
server.registerTool("qf_query_plan", {
|
|
1571
1676
|
title: "Qingflow Query Plan",
|
|
1572
1677
|
description: "Preflight query arguments: normalize inputs, validate required fields, resolve mappings and estimate scan limits before execution.",
|
|
@@ -1598,6 +1703,7 @@ server.registerTool("qf.query.plan", {
|
|
|
1598
1703
|
}, async (args) => {
|
|
1599
1704
|
try {
|
|
1600
1705
|
const parsedArgs = canonicalPlanInputSchema.parse(args);
|
|
1706
|
+
const canonicalTool = canonicalExecutorToolForKind(parsedArgs.kind);
|
|
1601
1707
|
const normalizedLoose = normalizeCanonicalLooseInput(parsedArgs.kind, parsedArgs.kind === "mutate" ? parsedArgs.action : parsedArgs.query);
|
|
1602
1708
|
let normalizedQuery = normalizedLoose;
|
|
1603
1709
|
let internalTool = parsedArgs.kind === "rows"
|
|
@@ -1664,6 +1770,11 @@ server.registerTool("qf.query.plan", {
|
|
|
1664
1770
|
data: {
|
|
1665
1771
|
kind: parsedArgs.kind,
|
|
1666
1772
|
normalized_query: normalizedQuery,
|
|
1773
|
+
plan: {
|
|
1774
|
+
tool: canonicalTool,
|
|
1775
|
+
arguments: normalizedQuery,
|
|
1776
|
+
direct_execute: false
|
|
1777
|
+
},
|
|
1667
1778
|
internal_tool: internalTool,
|
|
1668
1779
|
internal_arguments: internalArguments,
|
|
1669
1780
|
field_mapping: [],
|
|
@@ -1700,6 +1811,13 @@ server.registerTool("qf.query.plan", {
|
|
|
1700
1811
|
data: {
|
|
1701
1812
|
kind: parsedArgs.kind,
|
|
1702
1813
|
normalized_query: normalizedQuery,
|
|
1814
|
+
plan: {
|
|
1815
|
+
tool: canonicalTool,
|
|
1816
|
+
arguments: normalizedQuery,
|
|
1817
|
+
direct_execute: parsedArgs.kind === "mutate"
|
|
1818
|
+
? true
|
|
1819
|
+
: Boolean(plannedPayload?.data.validation.valid && internalArguments)
|
|
1820
|
+
},
|
|
1703
1821
|
internal_tool: internalTool,
|
|
1704
1822
|
internal_arguments: internalArguments,
|
|
1705
1823
|
field_mapping: plannedPayload?.data.field_mapping ?? [],
|
|
@@ -1912,6 +2030,9 @@ server.registerTool("qf.query.aggregate", {
|
|
|
1912
2030
|
metrics_by_column: asObject(group.metrics) ?? {}
|
|
1913
2031
|
};
|
|
1914
2032
|
});
|
|
2033
|
+
const providerResultAmount = toNonNegativeInt(completeness.provider_result_amount ?? completeness.result_amount);
|
|
2034
|
+
const returnedGroupCount = toNonNegativeInt(completeness.returned_group_count) ?? groups.length;
|
|
2035
|
+
const totalGroupCount = toNonNegativeInt(completeness.total_group_count) ?? groups.length;
|
|
1915
2036
|
const snapshotUri = buildQuerySnapshotResourceUri(queryId);
|
|
1916
2037
|
const normalizedUri = buildQueryNormalizedResourceUri(queryId);
|
|
1917
2038
|
const snapshot = {
|
|
@@ -1921,6 +2042,9 @@ server.registerTool("qf.query.aggregate", {
|
|
|
1921
2042
|
primary_metric_column: built.primaryMetricColumn,
|
|
1922
2043
|
summary: {
|
|
1923
2044
|
record_count: toNonNegativeInt(data.summary.total_count) ?? 0,
|
|
2045
|
+
provider_result_amount: providerResultAmount,
|
|
2046
|
+
returned_group_count: returnedGroupCount,
|
|
2047
|
+
total_group_count: totalGroupCount,
|
|
1924
2048
|
metrics_by_column: summaryMetrics
|
|
1925
2049
|
},
|
|
1926
2050
|
groups,
|
|
@@ -1948,6 +2072,9 @@ server.registerTool("qf.query.aggregate", {
|
|
|
1948
2072
|
primary_metric_column: built.primaryMetricColumn,
|
|
1949
2073
|
summary: {
|
|
1950
2074
|
record_count: toNonNegativeInt(data.summary.total_count) ?? 0,
|
|
2075
|
+
provider_result_amount: providerResultAmount,
|
|
2076
|
+
returned_group_count: returnedGroupCount,
|
|
2077
|
+
total_group_count: totalGroupCount,
|
|
1951
2078
|
metrics_by_column: summaryMetrics
|
|
1952
2079
|
},
|
|
1953
2080
|
groups,
|
|
@@ -3017,6 +3144,88 @@ function applyAliases(obj, aliases) {
|
|
|
3017
3144
|
}
|
|
3018
3145
|
return out;
|
|
3019
3146
|
}
|
|
3147
|
+
const FORBIDDEN_FILTER_RUNTIME_ALIASES = {
|
|
3148
|
+
from: "min_value",
|
|
3149
|
+
to: "max_value",
|
|
3150
|
+
dateFrom: "min_value",
|
|
3151
|
+
dateTo: "max_value",
|
|
3152
|
+
searchKey: "search_key",
|
|
3153
|
+
searchKeys: "search_keys"
|
|
3154
|
+
};
|
|
3155
|
+
const FORBIDDEN_TOP_LEVEL_TIME_ALIASES = {
|
|
3156
|
+
from: "time_range.from",
|
|
3157
|
+
to: "time_range.to",
|
|
3158
|
+
dateFrom: "time_range.from",
|
|
3159
|
+
dateTo: "time_range.to"
|
|
3160
|
+
};
|
|
3161
|
+
function throwForbiddenRuntimeAliasError(params) {
|
|
3162
|
+
throw new InputValidationError({
|
|
3163
|
+
message: `${params.tool} no longer accepts runtime alias "${params.alias}" at ${params.path}`,
|
|
3164
|
+
errorCode: "FORBIDDEN_RUNTIME_ALIAS",
|
|
3165
|
+
fixHint: params.fixHint,
|
|
3166
|
+
details: {
|
|
3167
|
+
tool: params.tool,
|
|
3168
|
+
path: params.path,
|
|
3169
|
+
alias: params.alias,
|
|
3170
|
+
replacement: params.replacement
|
|
3171
|
+
}
|
|
3172
|
+
});
|
|
3173
|
+
}
|
|
3174
|
+
function assertNoForbiddenAliases(obj, aliasMap, params) {
|
|
3175
|
+
for (const [alias, replacement] of Object.entries(aliasMap)) {
|
|
3176
|
+
if (obj[alias] !== undefined) {
|
|
3177
|
+
throwForbiddenRuntimeAliasError({
|
|
3178
|
+
tool: params.tool,
|
|
3179
|
+
path: `${params.pathPrefix}.${alias}`,
|
|
3180
|
+
alias,
|
|
3181
|
+
replacement,
|
|
3182
|
+
fixHint: params.fixHint
|
|
3183
|
+
});
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
function assertNoLegacyFilterAliases(value, tool) {
|
|
3188
|
+
const parsed = parseJsonLikeDeep(value);
|
|
3189
|
+
const list = Array.isArray(parsed) ? parsed : parsed === undefined || parsed === null ? [] : [parsed];
|
|
3190
|
+
for (let index = 0; index < list.length; index += 1) {
|
|
3191
|
+
const item = list[index];
|
|
3192
|
+
const obj = asObject(item);
|
|
3193
|
+
if (!obj) {
|
|
3194
|
+
continue;
|
|
3195
|
+
}
|
|
3196
|
+
assertNoForbiddenAliases(obj, FORBIDDEN_FILTER_RUNTIME_ALIASES, {
|
|
3197
|
+
tool,
|
|
3198
|
+
pathPrefix: `filters[${index}]`,
|
|
3199
|
+
fixHint: 'Use legacy filter keys "min_value"/"max_value"/"search_key"/"search_keys", or switch to canonical qf.query.* where clauses.'
|
|
3200
|
+
});
|
|
3201
|
+
const valueObject = asObject(parseJsonLikeDeep(obj.value));
|
|
3202
|
+
if (!valueObject) {
|
|
3203
|
+
continue;
|
|
3204
|
+
}
|
|
3205
|
+
assertNoForbiddenAliases(valueObject, FORBIDDEN_FILTER_RUNTIME_ALIASES, {
|
|
3206
|
+
tool,
|
|
3207
|
+
pathPrefix: `filters[${index}].value`,
|
|
3208
|
+
fixHint: 'Use legacy filter keys "min_value"/"max_value"/"search_key"/"search_keys", or switch to canonical qf.query.* where clauses.'
|
|
3209
|
+
});
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
function assertNoTopLevelTimeAliases(obj, tool) {
|
|
3213
|
+
assertNoForbiddenAliases(obj, FORBIDDEN_TOP_LEVEL_TIME_ALIASES, {
|
|
3214
|
+
tool,
|
|
3215
|
+
pathPrefix: "arguments",
|
|
3216
|
+
fixHint: 'Use time_range: {"column": ..., "from": "...", "to": "..."} instead of top-level date aliases.'
|
|
3217
|
+
});
|
|
3218
|
+
}
|
|
3219
|
+
function assertNoValueProbeAliases(obj, tool) {
|
|
3220
|
+
assertNoForbiddenAliases(obj, {
|
|
3221
|
+
searchKey: "query",
|
|
3222
|
+
searchKeys: "query"
|
|
3223
|
+
}, {
|
|
3224
|
+
tool,
|
|
3225
|
+
pathPrefix: "arguments",
|
|
3226
|
+
fixHint: 'Use "query" instead of "searchKey"/"searchKeys".'
|
|
3227
|
+
});
|
|
3228
|
+
}
|
|
3020
3229
|
function normalizeToolSpecInput(raw) {
|
|
3021
3230
|
const parsedRoot = parseJsonLikeDeep(raw);
|
|
3022
3231
|
const obj = asObject(parsedRoot);
|
|
@@ -3089,12 +3298,31 @@ function buildToolSpecCatalog() {
|
|
|
3089
3298
|
top_k: 3
|
|
3090
3299
|
}
|
|
3091
3300
|
},
|
|
3301
|
+
{
|
|
3302
|
+
tool: "qf_value_probe",
|
|
3303
|
+
required: ["app_key", "field"],
|
|
3304
|
+
limits: {
|
|
3305
|
+
limit_max: 50,
|
|
3306
|
+
scan_max_pages_max: 20,
|
|
3307
|
+
page_size_max: 200,
|
|
3308
|
+
match_mode: Array.from(matchModeValues),
|
|
3309
|
+
input_contract: 'strict JSON only; use field plus optional query and match_mode. "searchKey"/"searchKeys" are rejected.'
|
|
3310
|
+
},
|
|
3311
|
+
aliases: {},
|
|
3312
|
+
minimal_example: {
|
|
3313
|
+
app_key: "21b3d559",
|
|
3314
|
+
field: "归属部门",
|
|
3315
|
+
query: "北斗",
|
|
3316
|
+
match_mode: "contains",
|
|
3317
|
+
limit: 10
|
|
3318
|
+
}
|
|
3319
|
+
},
|
|
3092
3320
|
{
|
|
3093
3321
|
tool: "qf_query_plan",
|
|
3094
3322
|
required: ["tool"],
|
|
3095
3323
|
limits: {
|
|
3096
3324
|
tool: "qf_records_list|qf_record_get|qf_query|qf_records_aggregate|qf_records_batch_get|qf_export_csv|qf_export_json",
|
|
3097
|
-
input_contract:
|
|
3325
|
+
input_contract: 'strict JSON only; arguments must be a native JSON object. Target tools reject runtime aliases like from/to/dateFrom/dateTo/searchKey/searchKeys.'
|
|
3098
3326
|
},
|
|
3099
3327
|
aliases: {},
|
|
3100
3328
|
minimal_example: {
|
|
@@ -3112,7 +3340,7 @@ function buildToolSpecCatalog() {
|
|
|
3112
3340
|
required: ["kind"],
|
|
3113
3341
|
limits: {
|
|
3114
3342
|
kind: "rows|record|aggregate|export|mutate",
|
|
3115
|
-
input_contract: "plan accepts loose model-shaped query objects
|
|
3343
|
+
input_contract: "plan accepts loose model-shaped query objects, but rejects runtime aliases like from/to/dateFrom/dateTo/searchKey/searchKeys before execution"
|
|
3116
3344
|
},
|
|
3117
3345
|
aliases: {},
|
|
3118
3346
|
minimal_example: {
|
|
@@ -3221,7 +3449,7 @@ function buildToolSpecCatalog() {
|
|
|
3221
3449
|
max_columns_max: MAX_COLUMN_LIMIT,
|
|
3222
3450
|
select_columns_max: MAX_COLUMN_LIMIT,
|
|
3223
3451
|
output_profile: "compact|verbose (default compact)",
|
|
3224
|
-
input_contract: "strict JSON only; numbers/arrays/objects/booleans must use native JSON types"
|
|
3452
|
+
input_contract: "strict JSON only; numbers/arrays/objects/booleans must use native JSON types, and runtime aliases from/to/dateFrom/dateTo/searchKey/searchKeys are rejected"
|
|
3225
3453
|
},
|
|
3226
3454
|
aliases: {},
|
|
3227
3455
|
minimal_example: {
|
|
@@ -3276,7 +3504,7 @@ function buildToolSpecCatalog() {
|
|
|
3276
3504
|
max_rows_max: EXPORT_MAX_ROWS,
|
|
3277
3505
|
max_columns_max: MAX_COLUMN_LIMIT,
|
|
3278
3506
|
select_columns_max: MAX_COLUMN_LIMIT,
|
|
3279
|
-
input_contract: "strict JSON only; select_columns/time_range must use native JSON types"
|
|
3507
|
+
input_contract: "strict JSON only; select_columns/time_range must use native JSON types, and runtime aliases from/to/dateFrom/dateTo/searchKey/searchKeys are rejected"
|
|
3280
3508
|
},
|
|
3281
3509
|
aliases: {},
|
|
3282
3510
|
minimal_example: {
|
|
@@ -3298,7 +3526,7 @@ function buildToolSpecCatalog() {
|
|
|
3298
3526
|
max_rows_max: EXPORT_MAX_ROWS,
|
|
3299
3527
|
max_columns_max: MAX_COLUMN_LIMIT,
|
|
3300
3528
|
select_columns_max: MAX_COLUMN_LIMIT,
|
|
3301
|
-
input_contract: "strict JSON only; select_columns/time_range must use native JSON types"
|
|
3529
|
+
input_contract: "strict JSON only; select_columns/time_range must use native JSON types, and runtime aliases from/to/dateFrom/dateTo/searchKey/searchKeys are rejected"
|
|
3302
3530
|
},
|
|
3303
3531
|
aliases: {},
|
|
3304
3532
|
minimal_example: {
|
|
@@ -3327,7 +3555,7 @@ function buildToolSpecCatalog() {
|
|
|
3327
3555
|
max_columns_max: MAX_COLUMN_LIMIT,
|
|
3328
3556
|
select_columns_max: MAX_COLUMN_LIMIT,
|
|
3329
3557
|
output_profile: "compact|verbose (default compact)",
|
|
3330
|
-
input_contract: "strict JSON only; select_columns/time_range/stat_policy must use native JSON types"
|
|
3558
|
+
input_contract: "strict JSON only; select_columns/time_range/stat_policy must use native JSON types, and runtime aliases from/to/dateFrom/dateTo/searchKey/searchKeys are rejected"
|
|
3331
3559
|
},
|
|
3332
3560
|
aliases: {},
|
|
3333
3561
|
minimal_example: {
|
|
@@ -3357,7 +3585,7 @@ function buildToolSpecCatalog() {
|
|
|
3357
3585
|
metrics_supported: ["count", "sum", "avg", "min", "max"],
|
|
3358
3586
|
time_bucket_supported: ["day", "week", "month"],
|
|
3359
3587
|
output_profile: "compact|verbose (default compact)",
|
|
3360
|
-
input_contract: "strict JSON only; group_by/amount_columns/time_range must use native JSON types"
|
|
3588
|
+
input_contract: "strict JSON only; group_by/amount_columns/time_range must use native JSON types, and runtime aliases from/to/dateFrom/dateTo/searchKey/searchKeys are rejected"
|
|
3361
3589
|
},
|
|
3362
3590
|
aliases: {},
|
|
3363
3591
|
minimal_example: {
|
|
@@ -3455,6 +3683,8 @@ function normalizeListInput(raw) {
|
|
|
3455
3683
|
if (!obj) {
|
|
3456
3684
|
return parsedRoot;
|
|
3457
3685
|
}
|
|
3686
|
+
assertNoTopLevelTimeAliases(obj, "qf_records_list");
|
|
3687
|
+
assertNoLegacyFilterAliases(obj.filters, "qf_records_list");
|
|
3458
3688
|
const normalizedObj = applyAliases(obj, COMMON_INPUT_ALIASES);
|
|
3459
3689
|
const selectColumns = normalizedObj.select_columns ?? normalizedObj.keep_columns;
|
|
3460
3690
|
const timeRange = buildFriendlyTimeRangeInput(normalizedObj);
|
|
@@ -3500,6 +3730,8 @@ function normalizeQueryInput(raw) {
|
|
|
3500
3730
|
if (!obj) {
|
|
3501
3731
|
return parsedRoot;
|
|
3502
3732
|
}
|
|
3733
|
+
assertNoTopLevelTimeAliases(obj, "qf_query");
|
|
3734
|
+
assertNoLegacyFilterAliases(obj.filters, "qf_query");
|
|
3503
3735
|
const normalizedObj = applyAliases(obj, COMMON_INPUT_ALIASES);
|
|
3504
3736
|
const selectColumns = normalizedObj.select_columns ?? normalizedObj.keep_columns;
|
|
3505
3737
|
const timeRange = buildFriendlyTimeRangeInput(normalizedObj);
|
|
@@ -3532,6 +3764,8 @@ function normalizeAggregateInput(raw) {
|
|
|
3532
3764
|
if (!obj) {
|
|
3533
3765
|
return parsedRoot;
|
|
3534
3766
|
}
|
|
3767
|
+
assertNoTopLevelTimeAliases(obj, "qf_records_aggregate");
|
|
3768
|
+
assertNoLegacyFilterAliases(obj.filters, "qf_records_aggregate");
|
|
3535
3769
|
const normalizedObj = applyAliases(obj, COMMON_INPUT_ALIASES);
|
|
3536
3770
|
const timeRange = buildFriendlyTimeRangeInput(normalizedObj);
|
|
3537
3771
|
const amountColumns = normalizeAmountColumnsInput(normalizedObj.amount_columns ?? normalizedObj.amount_column);
|
|
@@ -3579,6 +3813,38 @@ function normalizeFieldResolveInput(raw) {
|
|
|
3579
3813
|
fuzzy: coerceBooleanLike(normalizedObj.fuzzy)
|
|
3580
3814
|
};
|
|
3581
3815
|
}
|
|
3816
|
+
function normalizeValueProbeInput(raw) {
|
|
3817
|
+
const parsedRoot = parseJsonLikeDeep(raw);
|
|
3818
|
+
const obj = asObject(parsedRoot);
|
|
3819
|
+
if (!obj) {
|
|
3820
|
+
return parsedRoot;
|
|
3821
|
+
}
|
|
3822
|
+
assertNoValueProbeAliases(obj, "qf_value_probe");
|
|
3823
|
+
const normalizedObj = applyAliases(obj, {
|
|
3824
|
+
appKey: "app_key",
|
|
3825
|
+
fieldId: "field",
|
|
3826
|
+
fieldTitle: "field",
|
|
3827
|
+
name: "field",
|
|
3828
|
+
value: "query",
|
|
3829
|
+
search: "query",
|
|
3830
|
+
prefix: "query",
|
|
3831
|
+
match: "match_mode",
|
|
3832
|
+
matchMode: "match_mode",
|
|
3833
|
+
scanMaxPages: "scan_max_pages",
|
|
3834
|
+
pageSize: "page_size"
|
|
3835
|
+
});
|
|
3836
|
+
return {
|
|
3837
|
+
...normalizedObj,
|
|
3838
|
+
field: normalizeSelectorInputValue(normalizedObj.field),
|
|
3839
|
+
query: normalizedObj.query !== undefined ? coerceStringLike(normalizedObj.query) : undefined,
|
|
3840
|
+
match_mode: typeof normalizedObj.match_mode === "string" && normalizedObj.match_mode.trim()
|
|
3841
|
+
? normalizedObj.match_mode.trim().toLowerCase()
|
|
3842
|
+
: undefined,
|
|
3843
|
+
limit: coerceNumberLike(normalizedObj.limit),
|
|
3844
|
+
scan_max_pages: coerceNumberLike(normalizedObj.scan_max_pages),
|
|
3845
|
+
page_size: coerceNumberLike(normalizedObj.page_size)
|
|
3846
|
+
};
|
|
3847
|
+
}
|
|
3582
3848
|
function normalizeQueryPlanInput(raw) {
|
|
3583
3849
|
const parsedRoot = parseJsonLikeDeep(raw);
|
|
3584
3850
|
const obj = asObject(parsedRoot);
|
|
@@ -3621,6 +3887,8 @@ function normalizeExportInput(raw) {
|
|
|
3621
3887
|
if (!obj) {
|
|
3622
3888
|
return parsedRoot;
|
|
3623
3889
|
}
|
|
3890
|
+
assertNoTopLevelTimeAliases(obj, "qf_export");
|
|
3891
|
+
assertNoLegacyFilterAliases(obj.filters, "qf_export");
|
|
3624
3892
|
const normalizedObj = applyAliases(obj, COMMON_INPUT_ALIASES);
|
|
3625
3893
|
const selectColumns = normalizedObj.select_columns ?? normalizedObj.keep_columns;
|
|
3626
3894
|
const timeRange = buildFriendlyTimeRangeInput(normalizedObj);
|
|
@@ -3800,8 +4068,6 @@ function normalizeFiltersInput(value) {
|
|
|
3800
4068
|
column: "que_id",
|
|
3801
4069
|
columnId: "que_id",
|
|
3802
4070
|
columnTitle: "que_title",
|
|
3803
|
-
searchKey: "search_key",
|
|
3804
|
-
searchKeys: "search_keys",
|
|
3805
4071
|
minValue: "min_value",
|
|
3806
4072
|
maxValue: "max_value",
|
|
3807
4073
|
compareType: "compare_type",
|
|
@@ -3810,10 +4076,8 @@ function normalizeFiltersInput(value) {
|
|
|
3810
4076
|
users: "search_user_ids",
|
|
3811
4077
|
options: "search_options",
|
|
3812
4078
|
start: "min_value",
|
|
3813
|
-
from: "min_value",
|
|
3814
4079
|
min: "min_value",
|
|
3815
4080
|
end: "max_value",
|
|
3816
|
-
to: "max_value",
|
|
3817
4081
|
max: "max_value"
|
|
3818
4082
|
});
|
|
3819
4083
|
const normalizedCompareType = typeof normalizedObj.compare_type === "string"
|
|
@@ -3835,17 +4099,11 @@ function normalizeFiltersInput(value) {
|
|
|
3835
4099
|
if (valueObject) {
|
|
3836
4100
|
const valueAliases = applyAliases(valueObject, {
|
|
3837
4101
|
start: "min_value",
|
|
3838
|
-
from: "min_value",
|
|
3839
4102
|
min: "min_value",
|
|
3840
4103
|
date_from: "min_value",
|
|
3841
|
-
dateFrom: "min_value",
|
|
3842
4104
|
end: "max_value",
|
|
3843
|
-
to: "max_value",
|
|
3844
4105
|
max: "max_value",
|
|
3845
4106
|
date_to: "max_value",
|
|
3846
|
-
dateTo: "max_value",
|
|
3847
|
-
searchKey: "search_key",
|
|
3848
|
-
searchKeys: "search_keys",
|
|
3849
4107
|
searchOptions: "search_options",
|
|
3850
4108
|
searchUserIds: "search_user_ids"
|
|
3851
4109
|
});
|
|
@@ -4010,8 +4268,8 @@ function buildFriendlyTimeRangeInput(obj) {
|
|
|
4010
4268
|
const normalizedRawTimeRange = normalizeTimeRangeInput(obj.time_range);
|
|
4011
4269
|
const normalizedTimeRange = asObject(normalizedRawTimeRange);
|
|
4012
4270
|
const columnCandidate = firstPresent(obj.date_field, obj.dateField, obj.time_field, obj.timeField, obj.time_column, obj.timeColumn, obj.date_column, obj.dateColumn, normalizedTimeRange?.column);
|
|
4013
|
-
const fromCandidate = firstPresent(obj.date_from, obj.
|
|
4014
|
-
const toCandidate = firstPresent(obj.date_to, obj.
|
|
4271
|
+
const fromCandidate = firstPresent(obj.date_from, obj.time_from, obj.timeFrom, obj.start, obj.start_date, obj.startDate, normalizedTimeRange?.from);
|
|
4272
|
+
const toCandidate = firstPresent(obj.date_to, obj.time_to, obj.timeTo, obj.end, obj.end_date, obj.endDate, normalizedTimeRange?.to);
|
|
4015
4273
|
const timezoneCandidate = firstPresent(obj.timezone, obj.timeZone, obj.tz, normalizedTimeRange?.timezone);
|
|
4016
4274
|
if (columnCandidate === undefined &&
|
|
4017
4275
|
fromCandidate === undefined &&
|
|
@@ -4280,7 +4538,14 @@ function buildExtendedCompleteness(params) {
|
|
|
4280
4538
|
output_page_complete: outputPageComplete,
|
|
4281
4539
|
raw_next_page_token: params.rawNextPageToken ?? params.nextPageToken,
|
|
4282
4540
|
output_next_page_token: params.outputNextPageToken ?? null,
|
|
4283
|
-
stop_reason: params.stopReason ?? null
|
|
4541
|
+
stop_reason: params.stopReason ?? null,
|
|
4542
|
+
provider_result_amount: params.providerResultAmount ?? params.resultAmount,
|
|
4543
|
+
source_record_count: params.sourceRecordCount ?? params.returnedItems,
|
|
4544
|
+
effective_record_count: params.effectiveRecordCount ?? params.returnedItems,
|
|
4545
|
+
...(params.returnedGroupCount !== undefined
|
|
4546
|
+
? { returned_group_count: params.returnedGroupCount }
|
|
4547
|
+
: {}),
|
|
4548
|
+
...(params.totalGroupCount !== undefined ? { total_group_count: params.totalGroupCount } : {})
|
|
4284
4549
|
};
|
|
4285
4550
|
}
|
|
4286
4551
|
function normalizePlanToolName(tool) {
|
|
@@ -5070,6 +5335,10 @@ function buildRecordGetArgsFromQuery(args) {
|
|
|
5070
5335
|
}
|
|
5071
5336
|
function normalizeCanonicalLooseInput(kind, raw) {
|
|
5072
5337
|
const parsed = asObject(parseJsonLikeDeep(raw)) ?? {};
|
|
5338
|
+
if (kind === "rows" || kind === "aggregate" || kind === "export") {
|
|
5339
|
+
assertNoTopLevelTimeAliases(parsed, "qf.query.plan");
|
|
5340
|
+
assertNoLegacyFilterAliases(parsed.filters, "qf.query.plan");
|
|
5341
|
+
}
|
|
5073
5342
|
const out = { ...parsed };
|
|
5074
5343
|
const assignAlias = (target, aliases) => {
|
|
5075
5344
|
if (out[target] !== undefined) {
|
|
@@ -5208,6 +5477,39 @@ function normalizeCanonicalLooseInput(kind, raw) {
|
|
|
5208
5477
|
};
|
|
5209
5478
|
});
|
|
5210
5479
|
}
|
|
5480
|
+
return pickCanonicalLooseFields(kind, out);
|
|
5481
|
+
}
|
|
5482
|
+
function pickCanonicalLooseFields(kind, value) {
|
|
5483
|
+
const commonKeys = [
|
|
5484
|
+
"app_key",
|
|
5485
|
+
"select",
|
|
5486
|
+
"where",
|
|
5487
|
+
"sort",
|
|
5488
|
+
"limit",
|
|
5489
|
+
"cursor",
|
|
5490
|
+
"page_size",
|
|
5491
|
+
"requested_pages",
|
|
5492
|
+
"scan_max_pages",
|
|
5493
|
+
"strict_full",
|
|
5494
|
+
"output_profile",
|
|
5495
|
+
"user_id",
|
|
5496
|
+
"mode"
|
|
5497
|
+
];
|
|
5498
|
+
const allowedKeys = kind === "rows"
|
|
5499
|
+
? commonKeys
|
|
5500
|
+
: kind === "record"
|
|
5501
|
+
? ["apply_id", "select", "output_profile"]
|
|
5502
|
+
: kind === "aggregate"
|
|
5503
|
+
? [...commonKeys.filter((item) => item !== "select"), "group_by", "metrics", "time_bucket", "top_n"]
|
|
5504
|
+
: kind === "export"
|
|
5505
|
+
? [...commonKeys, "format", "file_name", "export_dir"]
|
|
5506
|
+
: ["action", "app_key", "apply_id", "user_id", "fields", "answers", "force_refresh_form"];
|
|
5507
|
+
const out = {};
|
|
5508
|
+
for (const key of allowedKeys) {
|
|
5509
|
+
if (value[key] !== undefined) {
|
|
5510
|
+
out[key] = value[key];
|
|
5511
|
+
}
|
|
5512
|
+
}
|
|
5211
5513
|
return out;
|
|
5212
5514
|
}
|
|
5213
5515
|
function legacyFiltersToCanonicalWhere(filters, timeRange) {
|
|
@@ -5414,7 +5716,7 @@ async function buildCanonicalAggregateExecution(input) {
|
|
|
5414
5716
|
filters: translation.filters,
|
|
5415
5717
|
sort: translateCanonicalSortToLegacy(input.sort),
|
|
5416
5718
|
group_by: input.group_by,
|
|
5417
|
-
amount_columns: amountColumns,
|
|
5719
|
+
...(amountColumns.length > 0 ? { amount_columns: amountColumns } : {}),
|
|
5418
5720
|
metrics: metricNames,
|
|
5419
5721
|
time_bucket: input.time_bucket,
|
|
5420
5722
|
max_groups: input.top_n,
|
|
@@ -5475,7 +5777,9 @@ async function buildCanonicalExportExecution(input) {
|
|
|
5475
5777
|
scan_max_pages: input.scan_max_pages ?? (input.requested_pages ?? EXPORT_DEFAULT_PAGES),
|
|
5476
5778
|
strict_full: input.strict_full ?? false,
|
|
5477
5779
|
mode: input.mode ?? "all",
|
|
5478
|
-
format
|
|
5780
|
+
format,
|
|
5781
|
+
...(input.file_name ? { file_name: input.file_name } : {}),
|
|
5782
|
+
...(input.export_dir ? { export_dir: input.export_dir } : {})
|
|
5479
5783
|
},
|
|
5480
5784
|
internalTool: format === "csv" ? "qf_export_csv" : "qf_export_json",
|
|
5481
5785
|
internalArguments,
|
|
@@ -5495,7 +5799,10 @@ async function buildCanonicalMutateExecution(input) {
|
|
|
5495
5799
|
normalizedQuery: {
|
|
5496
5800
|
action: input.action,
|
|
5497
5801
|
app_key: input.app_key ?? null,
|
|
5498
|
-
|
|
5802
|
+
user_id: input.user_id ?? null,
|
|
5803
|
+
fields: input.fields ?? null,
|
|
5804
|
+
answers: input.answers ?? null,
|
|
5805
|
+
force_refresh_form: input.force_refresh_form ?? false
|
|
5499
5806
|
},
|
|
5500
5807
|
internalTool: "qf_record_create",
|
|
5501
5808
|
internalArguments
|
|
@@ -5514,12 +5821,30 @@ async function buildCanonicalMutateExecution(input) {
|
|
|
5514
5821
|
action: input.action,
|
|
5515
5822
|
app_key: input.app_key ?? null,
|
|
5516
5823
|
apply_id: input.apply_id ?? null,
|
|
5517
|
-
|
|
5824
|
+
user_id: input.user_id ?? null,
|
|
5825
|
+
fields: input.fields ?? null,
|
|
5826
|
+
answers: input.answers ?? null,
|
|
5827
|
+
force_refresh_form: input.force_refresh_form ?? false
|
|
5518
5828
|
},
|
|
5519
5829
|
internalTool: "qf_record_update",
|
|
5520
5830
|
internalArguments
|
|
5521
5831
|
};
|
|
5522
5832
|
}
|
|
5833
|
+
function canonicalExecutorToolForKind(kind) {
|
|
5834
|
+
if (kind === "rows") {
|
|
5835
|
+
return "qf.query.rows";
|
|
5836
|
+
}
|
|
5837
|
+
if (kind === "record") {
|
|
5838
|
+
return "qf.query.record";
|
|
5839
|
+
}
|
|
5840
|
+
if (kind === "aggregate") {
|
|
5841
|
+
return "qf.query.aggregate";
|
|
5842
|
+
}
|
|
5843
|
+
if (kind === "export") {
|
|
5844
|
+
return "qf.query.export";
|
|
5845
|
+
}
|
|
5846
|
+
return "qf.records.mutate";
|
|
5847
|
+
}
|
|
5523
5848
|
async function executeFieldResolve(args) {
|
|
5524
5849
|
const response = await getFormCached(args.app_key, undefined, false);
|
|
5525
5850
|
const index = buildFieldIndex(response.result);
|
|
@@ -5544,6 +5869,42 @@ async function executeFieldResolve(args) {
|
|
|
5544
5869
|
meta: buildMeta(response)
|
|
5545
5870
|
};
|
|
5546
5871
|
}
|
|
5872
|
+
async function executeValueProbe(args) {
|
|
5873
|
+
const details = await collectFieldValueCandidatesDetailed({
|
|
5874
|
+
appKey: args.app_key,
|
|
5875
|
+
field: String(args.field),
|
|
5876
|
+
prefix: args.query,
|
|
5877
|
+
match: args.match_mode,
|
|
5878
|
+
limit: args.limit,
|
|
5879
|
+
scanMaxPages: args.scan_max_pages,
|
|
5880
|
+
pageSize: args.page_size
|
|
5881
|
+
});
|
|
5882
|
+
return {
|
|
5883
|
+
ok: true,
|
|
5884
|
+
data: {
|
|
5885
|
+
app_key: args.app_key,
|
|
5886
|
+
field: {
|
|
5887
|
+
requested: details.field.requested,
|
|
5888
|
+
que_id: details.field.que_id,
|
|
5889
|
+
que_title: details.field.que_title,
|
|
5890
|
+
que_type: details.field.que_type
|
|
5891
|
+
},
|
|
5892
|
+
query: details.query || null,
|
|
5893
|
+
requested_match_mode: details.requested_match_mode,
|
|
5894
|
+
effective_match_mode: details.effective_match_mode,
|
|
5895
|
+
provider_translation: details.provider_translation,
|
|
5896
|
+
scanned_pages: details.scanned_pages,
|
|
5897
|
+
scanned_records: details.scanned_records,
|
|
5898
|
+
provider_result_amount: details.provider_result_amount,
|
|
5899
|
+
candidates: details.candidates,
|
|
5900
|
+
matched_values: details.candidates.map((item) => item.value)
|
|
5901
|
+
},
|
|
5902
|
+
meta: {
|
|
5903
|
+
version: SERVER_VERSION,
|
|
5904
|
+
generated_at: new Date().toISOString()
|
|
5905
|
+
}
|
|
5906
|
+
};
|
|
5907
|
+
}
|
|
5547
5908
|
async function executeQueryPlan(args) {
|
|
5548
5909
|
const normalizedTool = normalizePlanToolName(args.tool);
|
|
5549
5910
|
const rawArguments = asObject(parseJsonLikeDeep(args.arguments)) ?? {};
|
|
@@ -5648,7 +6009,10 @@ async function executeRecordsBatchGet(args) {
|
|
|
5648
6009
|
is_complete: missingApplyIds.length === 0,
|
|
5649
6010
|
partial: missingApplyIds.length > 0,
|
|
5650
6011
|
omitted_items: missingApplyIds.length,
|
|
5651
|
-
omitted_chars: 0
|
|
6012
|
+
omitted_chars: 0,
|
|
6013
|
+
provider_result_amount: requestedApplyIds.length,
|
|
6014
|
+
source_record_count: rows.length,
|
|
6015
|
+
effective_record_count: rows.length
|
|
5652
6016
|
};
|
|
5653
6017
|
const evidence = buildEvidencePayload({
|
|
5654
6018
|
query_id: queryId,
|
|
@@ -5862,7 +6226,10 @@ async function executeRecordsExport(format, args) {
|
|
|
5862
6226
|
is_complete: !hasMore && omittedItems === 0,
|
|
5863
6227
|
partial: hasMore || omittedItems > 0,
|
|
5864
6228
|
omitted_items: omittedItems,
|
|
5865
|
-
omitted_chars: 0
|
|
6229
|
+
omitted_chars: 0,
|
|
6230
|
+
provider_result_amount: knownResultAmount,
|
|
6231
|
+
source_record_count: rows.length,
|
|
6232
|
+
effective_record_count: rows.length
|
|
5866
6233
|
};
|
|
5867
6234
|
const evidence = buildEvidencePayload({
|
|
5868
6235
|
query_id: queryId,
|
|
@@ -6085,7 +6452,10 @@ async function executeRecordsList(args) {
|
|
|
6085
6452
|
is_complete: isComplete,
|
|
6086
6453
|
partial: !isComplete,
|
|
6087
6454
|
omitted_items: omittedItems,
|
|
6088
|
-
omitted_chars: fittedRows.omittedChars
|
|
6455
|
+
omitted_chars: fittedRows.omittedChars,
|
|
6456
|
+
provider_result_amount: knownResultAmount,
|
|
6457
|
+
source_record_count: collectedRawItems.length,
|
|
6458
|
+
effective_record_count: fittedRows.items.length
|
|
6089
6459
|
};
|
|
6090
6460
|
const listState = {
|
|
6091
6461
|
query_id: queryId,
|
|
@@ -6197,7 +6567,10 @@ async function executeRecordGet(args) {
|
|
|
6197
6567
|
is_complete: true,
|
|
6198
6568
|
partial: false,
|
|
6199
6569
|
omitted_items: 0,
|
|
6200
|
-
omitted_chars: 0
|
|
6570
|
+
omitted_chars: 0,
|
|
6571
|
+
provider_result_amount: 1,
|
|
6572
|
+
source_record_count: 1,
|
|
6573
|
+
effective_record_count: 1
|
|
6201
6574
|
};
|
|
6202
6575
|
const evidence = {
|
|
6203
6576
|
query_id: queryId,
|
|
@@ -6622,7 +6995,10 @@ async function executeRecordsSummary(args) {
|
|
|
6622
6995
|
outputPageComplete,
|
|
6623
6996
|
rawNextPageToken,
|
|
6624
6997
|
outputNextPageToken: null,
|
|
6625
|
-
stopReason
|
|
6998
|
+
stopReason,
|
|
6999
|
+
providerResultAmount: knownResultAmount,
|
|
7000
|
+
sourceRecordCount: scannedRecords,
|
|
7001
|
+
effectiveRecordCount: scannedRecords
|
|
6626
7002
|
});
|
|
6627
7003
|
const evidence = buildEvidencePayload(listState, sourcePages);
|
|
6628
7004
|
if (strictFull && !rawScanComplete) {
|
|
@@ -6924,7 +7300,12 @@ async function executeRecordsAggregate(args) {
|
|
|
6924
7300
|
outputPageComplete,
|
|
6925
7301
|
rawNextPageToken,
|
|
6926
7302
|
outputNextPageToken: null,
|
|
6927
|
-
stopReason
|
|
7303
|
+
stopReason,
|
|
7304
|
+
providerResultAmount: knownResultAmount,
|
|
7305
|
+
sourceRecordCount: scannedRecords,
|
|
7306
|
+
effectiveRecordCount: scannedRecords,
|
|
7307
|
+
returnedGroupCount: Math.min(groupsTotal, maxGroups),
|
|
7308
|
+
totalGroupCount: groupsTotal
|
|
6928
7309
|
});
|
|
6929
7310
|
const evidence = buildEvidencePayload(listState, sourcePages);
|
|
6930
7311
|
if (strictFull && !completeness.is_complete) {
|
|
@@ -7528,38 +7909,77 @@ async function completeFieldCandidates(appKey, prefix) {
|
|
|
7528
7909
|
.slice(0, 20);
|
|
7529
7910
|
}
|
|
7530
7911
|
async function collectFieldValueCandidates(params) {
|
|
7912
|
+
const details = await collectFieldValueCandidatesDetailed(params);
|
|
7913
|
+
return details.candidates;
|
|
7914
|
+
}
|
|
7915
|
+
async function collectFieldValueCandidatesDetailed(params) {
|
|
7531
7916
|
const appKey = params.appKey.trim();
|
|
7532
7917
|
const fieldSelector = params.field.trim();
|
|
7533
7918
|
if (!appKey || !fieldSelector) {
|
|
7534
|
-
|
|
7919
|
+
throw new InputValidationError({
|
|
7920
|
+
message: "app_key and field are required for qf_value_probe",
|
|
7921
|
+
errorCode: "MISSING_REQUIRED_FIELD",
|
|
7922
|
+
fixHint: "Pass app_key plus field (que_id or field title).",
|
|
7923
|
+
details: {
|
|
7924
|
+
app_key: appKey || null,
|
|
7925
|
+
field: fieldSelector || null
|
|
7926
|
+
}
|
|
7927
|
+
});
|
|
7535
7928
|
}
|
|
7536
7929
|
const form = await getFormCached(appKey, undefined, false);
|
|
7537
7930
|
const index = buildFieldIndex(form.result);
|
|
7538
7931
|
const column = resolveSummaryColumn(fieldSelector, index, "field");
|
|
7539
7932
|
const counts = new Map();
|
|
7540
7933
|
let currentPage = 1;
|
|
7541
|
-
|
|
7934
|
+
let scannedPages = 0;
|
|
7935
|
+
let scannedRecords = 0;
|
|
7936
|
+
let providerResultAmount = null;
|
|
7937
|
+
const query = (params.prefix ?? "").trim();
|
|
7938
|
+
const requestedMatchMode = normalizeMatchMode(params.match);
|
|
7939
|
+
const limit = params.limit ?? 20;
|
|
7940
|
+
const scanMaxPages = params.scanMaxPages ?? 5;
|
|
7941
|
+
const pageSize = params.pageSize ?? 50;
|
|
7942
|
+
for (let fetched = 0; fetched < scanMaxPages; fetched += 1) {
|
|
7542
7943
|
const payload = buildListPayload({
|
|
7543
7944
|
pageNum: currentPage,
|
|
7544
|
-
pageSize
|
|
7945
|
+
pageSize,
|
|
7545
7946
|
mode: "all"
|
|
7546
7947
|
});
|
|
7547
7948
|
const response = await client.listRecords(appKey, payload, {});
|
|
7548
7949
|
const result = asObject(response.result);
|
|
7549
7950
|
const rawItems = asArray(result?.result);
|
|
7951
|
+
providerResultAmount = providerResultAmount ?? toNonNegativeInt(result?.resultAmount);
|
|
7952
|
+
scannedPages += 1;
|
|
7953
|
+
scannedRecords += rawItems.length;
|
|
7550
7954
|
for (const rawItem of rawItems) {
|
|
7551
7955
|
const record = asObject(rawItem) ?? {};
|
|
7552
7956
|
const value = extractSummaryColumnValue(asArray(record.answers), column);
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
7957
|
+
if (value === null || value === undefined) {
|
|
7958
|
+
continue;
|
|
7959
|
+
}
|
|
7960
|
+
const normalized = normalizeProbeValue(value);
|
|
7961
|
+
if (!normalized.display_value) {
|
|
7962
|
+
continue;
|
|
7963
|
+
}
|
|
7964
|
+
const matchDetails = scoreProbeValueMatch(value, query, requestedMatchMode);
|
|
7965
|
+
const existing = counts.get(normalized.key);
|
|
7966
|
+
if (existing) {
|
|
7967
|
+
existing.count += 1;
|
|
7968
|
+
if (matchDetails.match_strength > existing.match_strength) {
|
|
7969
|
+
existing.match_strength = matchDetails.match_strength;
|
|
7970
|
+
existing.matched_texts = matchDetails.matched_texts;
|
|
7971
|
+
existing.matched_as = matchDetails.matched_as;
|
|
7561
7972
|
}
|
|
7562
|
-
|
|
7973
|
+
}
|
|
7974
|
+
else {
|
|
7975
|
+
counts.set(normalized.key, {
|
|
7976
|
+
value: normalized.value,
|
|
7977
|
+
display_value: normalized.display_value,
|
|
7978
|
+
count: 1,
|
|
7979
|
+
match_strength: matchDetails.match_strength,
|
|
7980
|
+
matched_texts: matchDetails.matched_texts,
|
|
7981
|
+
matched_as: matchDetails.matched_as
|
|
7982
|
+
});
|
|
7563
7983
|
}
|
|
7564
7984
|
}
|
|
7565
7985
|
const pageAmount = toPositiveInt(result?.pageAmount);
|
|
@@ -7568,20 +7988,21 @@ async function collectFieldValueCandidates(params) {
|
|
|
7568
7988
|
}
|
|
7569
7989
|
currentPage += 1;
|
|
7570
7990
|
}
|
|
7571
|
-
const
|
|
7572
|
-
const match = (params.match ?? "contains").trim().toLowerCase();
|
|
7573
|
-
const limit = params.limit ?? 20;
|
|
7574
|
-
return Array.from(counts.entries())
|
|
7575
|
-
.map(([value, count]) => ({
|
|
7576
|
-
value,
|
|
7577
|
-
count,
|
|
7578
|
-
match_strength: normalizedPrefix.length === 0
|
|
7579
|
-
? 1
|
|
7580
|
-
: scoreTextMatch(value, normalizedPrefix, match)
|
|
7581
|
-
}))
|
|
7991
|
+
const candidates = Array.from(counts.values())
|
|
7582
7992
|
.filter((item) => item.match_strength > 0)
|
|
7583
7993
|
.sort((a, b) => b.match_strength - a.match_strength || b.count - a.count)
|
|
7584
7994
|
.slice(0, limit);
|
|
7995
|
+
return {
|
|
7996
|
+
field: column,
|
|
7997
|
+
query,
|
|
7998
|
+
requested_match_mode: requestedMatchMode,
|
|
7999
|
+
effective_match_mode: requestedMatchMode,
|
|
8000
|
+
provider_translation: "local_distinct_scan",
|
|
8001
|
+
scanned_pages: scannedPages,
|
|
8002
|
+
scanned_records: scannedRecords,
|
|
8003
|
+
provider_result_amount: providerResultAmount,
|
|
8004
|
+
candidates
|
|
8005
|
+
};
|
|
7585
8006
|
}
|
|
7586
8007
|
function scoreTextMatch(candidate, prefix, match) {
|
|
7587
8008
|
const normalizedCandidate = candidate.trim().toLowerCase();
|
|
@@ -7602,6 +8023,84 @@ function scoreTextMatch(candidate, prefix, match) {
|
|
|
7602
8023
|
}
|
|
7603
8024
|
return normalizedCandidate.includes(prefix) ? 0.9 : 0;
|
|
7604
8025
|
}
|
|
8026
|
+
function normalizeMatchMode(value) {
|
|
8027
|
+
if (typeof value === "string") {
|
|
8028
|
+
const normalized = value.trim().toLowerCase();
|
|
8029
|
+
if (matchModeValues.includes(normalized)) {
|
|
8030
|
+
return normalized;
|
|
8031
|
+
}
|
|
8032
|
+
}
|
|
8033
|
+
return "contains";
|
|
8034
|
+
}
|
|
8035
|
+
function normalizeProbeValue(value) {
|
|
8036
|
+
if (value === null || value === undefined) {
|
|
8037
|
+
return {
|
|
8038
|
+
key: "null",
|
|
8039
|
+
value,
|
|
8040
|
+
display_value: ""
|
|
8041
|
+
};
|
|
8042
|
+
}
|
|
8043
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
8044
|
+
return {
|
|
8045
|
+
key: stableJson(value),
|
|
8046
|
+
value,
|
|
8047
|
+
display_value: String(value).trim()
|
|
8048
|
+
};
|
|
8049
|
+
}
|
|
8050
|
+
return {
|
|
8051
|
+
key: stableJson(value),
|
|
8052
|
+
value,
|
|
8053
|
+
display_value: stableJson(value)
|
|
8054
|
+
};
|
|
8055
|
+
}
|
|
8056
|
+
function scoreProbeValueMatch(value, query, matchMode) {
|
|
8057
|
+
if (!query) {
|
|
8058
|
+
return {
|
|
8059
|
+
match_strength: 1,
|
|
8060
|
+
matched_texts: [],
|
|
8061
|
+
matched_as: "all"
|
|
8062
|
+
};
|
|
8063
|
+
}
|
|
8064
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
8065
|
+
const normalized = normalizeProbeValue(value);
|
|
8066
|
+
let bestScore = scoreTextMatch(normalized.display_value.toLowerCase(), normalizedQuery, matchMode);
|
|
8067
|
+
let matchedTexts = bestScore > 0 ? [normalized.display_value] : [];
|
|
8068
|
+
let matchedAs = bestScore > 0 ? matchMode : "none";
|
|
8069
|
+
const leafTexts = flattenProbeLeafTexts(value);
|
|
8070
|
+
for (const leaf of leafTexts) {
|
|
8071
|
+
const score = scoreTextMatch(leaf.toLowerCase(), normalizedQuery, matchMode);
|
|
8072
|
+
if (score > bestScore) {
|
|
8073
|
+
bestScore = score;
|
|
8074
|
+
matchedTexts = [leaf];
|
|
8075
|
+
matchedAs = Array.isArray(value)
|
|
8076
|
+
? `array_${matchMode}`
|
|
8077
|
+
: value && typeof value === "object"
|
|
8078
|
+
? `object_${matchMode}`
|
|
8079
|
+
: matchMode;
|
|
8080
|
+
}
|
|
8081
|
+
}
|
|
8082
|
+
return {
|
|
8083
|
+
match_strength: Number(Math.min(1, Math.max(0, bestScore)).toFixed(4)),
|
|
8084
|
+
matched_texts: uniqueStringList(matchedTexts),
|
|
8085
|
+
matched_as: matchedAs
|
|
8086
|
+
};
|
|
8087
|
+
}
|
|
8088
|
+
function flattenProbeLeafTexts(value, depth = 0) {
|
|
8089
|
+
if (depth > 4 || value === null || value === undefined) {
|
|
8090
|
+
return [];
|
|
8091
|
+
}
|
|
8092
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
8093
|
+
const text = String(value).trim();
|
|
8094
|
+
return text ? [text] : [];
|
|
8095
|
+
}
|
|
8096
|
+
if (Array.isArray(value)) {
|
|
8097
|
+
return uniqueStringList(value.flatMap((item) => flattenProbeLeafTexts(item, depth + 1)));
|
|
8098
|
+
}
|
|
8099
|
+
if (typeof value === "object") {
|
|
8100
|
+
return uniqueStringList(Object.values(value).flatMap((item) => flattenProbeLeafTexts(item, depth + 1)));
|
|
8101
|
+
}
|
|
8102
|
+
return [];
|
|
8103
|
+
}
|
|
7605
8104
|
async function completeFieldValueCandidates(appKey, field, prefix, match) {
|
|
7606
8105
|
const candidates = await collectFieldValueCandidates({
|
|
7607
8106
|
appKey,
|
|
@@ -7610,7 +8109,7 @@ async function completeFieldValueCandidates(appKey, field, prefix, match) {
|
|
|
7610
8109
|
match,
|
|
7611
8110
|
limit: 10
|
|
7612
8111
|
});
|
|
7613
|
-
return candidates.map((item) => item.
|
|
8112
|
+
return candidates.map((item) => item.display_value);
|
|
7614
8113
|
}
|
|
7615
8114
|
function completeQueryIdCandidates(prefix) {
|
|
7616
8115
|
cleanupArtifactCaches();
|
|
@@ -7657,17 +8156,29 @@ async function readFieldValuesResource(uri, variables) {
|
|
|
7657
8156
|
if (!appKey || !field) {
|
|
7658
8157
|
throw new Error("app_key and field are required");
|
|
7659
8158
|
}
|
|
7660
|
-
|
|
7661
|
-
|
|
8159
|
+
const details = await collectFieldValueCandidatesDetailed({
|
|
8160
|
+
appKey,
|
|
7662
8161
|
field,
|
|
7663
|
-
match,
|
|
7664
8162
|
prefix,
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
7670
|
-
|
|
8163
|
+
match
|
|
8164
|
+
});
|
|
8165
|
+
return jsonResourceResponse(uri, {
|
|
8166
|
+
app_key: appKey,
|
|
8167
|
+
field: {
|
|
8168
|
+
requested: details.field.requested,
|
|
8169
|
+
que_id: details.field.que_id,
|
|
8170
|
+
que_title: details.field.que_title,
|
|
8171
|
+
que_type: details.field.que_type
|
|
8172
|
+
},
|
|
8173
|
+
query: details.query || null,
|
|
8174
|
+
requested_match_mode: details.requested_match_mode,
|
|
8175
|
+
effective_match_mode: details.effective_match_mode,
|
|
8176
|
+
provider_translation: details.provider_translation,
|
|
8177
|
+
scanned_pages: details.scanned_pages,
|
|
8178
|
+
scanned_records: details.scanned_records,
|
|
8179
|
+
provider_result_amount: details.provider_result_amount,
|
|
8180
|
+
candidates: details.candidates,
|
|
8181
|
+
matched_values: details.candidates.map((item) => item.value)
|
|
7671
8182
|
});
|
|
7672
8183
|
}
|
|
7673
8184
|
async function readQueryNormalizedResource(uri, variables) {
|