qingflow-mcp 0.3.13 → 0.3.14
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 +2 -2
- package/dist/server.js +795 -103
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -46,6 +46,8 @@ class InputValidationError extends Error {
|
|
|
46
46
|
}
|
|
47
47
|
const FORM_CACHE_TTL_MS = Number(process.env.QINGFLOW_FORM_CACHE_TTL_MS ?? "300000");
|
|
48
48
|
const formCache = new Map();
|
|
49
|
+
const CONTINUATION_CACHE_TTL_MS = Number(process.env.QINGFLOW_CONTINUATION_CACHE_TTL_MS ?? "900000");
|
|
50
|
+
const continuationCache = new Map();
|
|
49
51
|
const DEFAULT_PAGE_SIZE = 50;
|
|
50
52
|
const DEFAULT_SCAN_MAX_PAGES = 10;
|
|
51
53
|
const DEFAULT_ROW_LIMIT = 200;
|
|
@@ -61,7 +63,7 @@ const ADAPTIVE_TARGET_PAGE_MS = toPositiveInt(process.env.QINGFLOW_ADAPTIVE_TARG
|
|
|
61
63
|
const MAX_LIST_ITEMS_BYTES = toPositiveInt(process.env.QINGFLOW_LIST_MAX_ITEMS_BYTES) ?? 400000;
|
|
62
64
|
const REQUEST_TIMEOUT_MS = toPositiveInt(process.env.QINGFLOW_REQUEST_TIMEOUT_MS) ?? 18000;
|
|
63
65
|
const EXECUTION_BUDGET_MS = toPositiveInt(process.env.QINGFLOW_EXECUTION_BUDGET_MS) ?? 20000;
|
|
64
|
-
const SERVER_VERSION = "0.3.
|
|
66
|
+
const SERVER_VERSION = "0.3.14";
|
|
65
67
|
const accessToken = process.env.QINGFLOW_ACCESS_TOKEN;
|
|
66
68
|
const baseUrl = process.env.QINGFLOW_BASE_URL;
|
|
67
69
|
if (!accessToken) {
|
|
@@ -227,6 +229,78 @@ const formSuccessOutputSchema = z.object({
|
|
|
227
229
|
meta: apiMetaSchema
|
|
228
230
|
});
|
|
229
231
|
const formOutputSchema = formSuccessOutputSchema;
|
|
232
|
+
const stringLikeSchema = z.string().min(1);
|
|
233
|
+
const columnSelectorLikeSchema = z.union([stringLikeSchema, z.number().int()]);
|
|
234
|
+
const columnReferenceLikeSchema = z.union([
|
|
235
|
+
columnSelectorLikeSchema,
|
|
236
|
+
z
|
|
237
|
+
.object({
|
|
238
|
+
que_id: columnSelectorLikeSchema.optional(),
|
|
239
|
+
queId: columnSelectorLikeSchema.optional()
|
|
240
|
+
})
|
|
241
|
+
.passthrough()
|
|
242
|
+
]);
|
|
243
|
+
const booleanLikeSchema = z.union([z.boolean(), stringLikeSchema]);
|
|
244
|
+
function positiveIntLikeSchema(max) {
|
|
245
|
+
const base = z.number().int().positive();
|
|
246
|
+
return z.union([max !== undefined ? base.max(max) : base, stringLikeSchema]);
|
|
247
|
+
}
|
|
248
|
+
function nonNegativeIntLikeSchema(max) {
|
|
249
|
+
const base = z.number().int().nonnegative();
|
|
250
|
+
return z.union([max !== undefined ? base.max(max) : base, stringLikeSchema]);
|
|
251
|
+
}
|
|
252
|
+
const publicSortItemSchema = z
|
|
253
|
+
.object({
|
|
254
|
+
que_id: columnSelectorLikeSchema,
|
|
255
|
+
ascend: booleanLikeSchema.optional()
|
|
256
|
+
})
|
|
257
|
+
.passthrough();
|
|
258
|
+
const publicFilterItemSchema = z
|
|
259
|
+
.object({
|
|
260
|
+
que_id: columnSelectorLikeSchema.optional(),
|
|
261
|
+
search_key: stringLikeSchema.optional(),
|
|
262
|
+
search_keys: z.union([z.array(stringLikeSchema), stringLikeSchema]).optional(),
|
|
263
|
+
min_value: stringLikeSchema.optional(),
|
|
264
|
+
max_value: stringLikeSchema.optional(),
|
|
265
|
+
scope: z.union([z.number().int(), stringLikeSchema]).optional(),
|
|
266
|
+
search_options: z.union([z.array(columnSelectorLikeSchema), stringLikeSchema]).optional(),
|
|
267
|
+
search_user_ids: z.union([z.array(stringLikeSchema), stringLikeSchema]).optional()
|
|
268
|
+
})
|
|
269
|
+
.passthrough();
|
|
270
|
+
const publicTimeRangeSchema = z
|
|
271
|
+
.object({
|
|
272
|
+
column: columnSelectorLikeSchema,
|
|
273
|
+
from: stringLikeSchema.optional(),
|
|
274
|
+
to: stringLikeSchema.optional(),
|
|
275
|
+
timezone: stringLikeSchema.optional()
|
|
276
|
+
})
|
|
277
|
+
.passthrough();
|
|
278
|
+
const publicStatPolicySchema = z
|
|
279
|
+
.object({
|
|
280
|
+
include_negative: booleanLikeSchema.optional(),
|
|
281
|
+
include_null: booleanLikeSchema.optional()
|
|
282
|
+
})
|
|
283
|
+
.passthrough();
|
|
284
|
+
const publicAnswerInputSchema = z
|
|
285
|
+
.object({
|
|
286
|
+
que_id: columnSelectorLikeSchema.optional(),
|
|
287
|
+
queId: columnSelectorLikeSchema.optional(),
|
|
288
|
+
que_title: stringLikeSchema.optional(),
|
|
289
|
+
queTitle: stringLikeSchema.optional(),
|
|
290
|
+
que_type: z.unknown().optional(),
|
|
291
|
+
queType: z.unknown().optional(),
|
|
292
|
+
value: z.unknown().optional(),
|
|
293
|
+
values: z.union([z.array(z.unknown()), stringLikeSchema]).optional(),
|
|
294
|
+
table_values: z.union([z.array(z.array(z.unknown())), stringLikeSchema]).optional(),
|
|
295
|
+
tableValues: z.union([z.array(z.array(z.unknown())), stringLikeSchema]).optional()
|
|
296
|
+
})
|
|
297
|
+
.passthrough();
|
|
298
|
+
const toolSpecInputPublicSchema = z
|
|
299
|
+
.object({
|
|
300
|
+
tool_name: stringLikeSchema.optional(),
|
|
301
|
+
include_all: booleanLikeSchema.optional()
|
|
302
|
+
})
|
|
303
|
+
.passthrough();
|
|
230
304
|
const toolSpecInputSchema = z.preprocess(normalizeToolSpecInput, z.object({
|
|
231
305
|
tool_name: z.string().min(1).optional(),
|
|
232
306
|
include_all: z.boolean().optional()
|
|
@@ -250,6 +324,52 @@ const toolSpecOutputSchema = z.object({
|
|
|
250
324
|
generated_at: z.string()
|
|
251
325
|
})
|
|
252
326
|
});
|
|
327
|
+
const listInputPublicSchema = z
|
|
328
|
+
.object({
|
|
329
|
+
app_key: stringLikeSchema.optional(),
|
|
330
|
+
user_id: stringLikeSchema.optional(),
|
|
331
|
+
page_num: positiveIntLikeSchema().optional(),
|
|
332
|
+
page_token: stringLikeSchema.optional(),
|
|
333
|
+
page_size: positiveIntLikeSchema(200).optional(),
|
|
334
|
+
requested_pages: positiveIntLikeSchema(500).optional(),
|
|
335
|
+
scan_max_pages: positiveIntLikeSchema(500).optional(),
|
|
336
|
+
mode: z
|
|
337
|
+
.enum([
|
|
338
|
+
"todo",
|
|
339
|
+
"done",
|
|
340
|
+
"mine_approved",
|
|
341
|
+
"mine_rejected",
|
|
342
|
+
"mine_draft",
|
|
343
|
+
"mine_need_improve",
|
|
344
|
+
"mine_processing",
|
|
345
|
+
"all",
|
|
346
|
+
"all_approved",
|
|
347
|
+
"all_rejected",
|
|
348
|
+
"all_processing",
|
|
349
|
+
"cc"
|
|
350
|
+
])
|
|
351
|
+
.optional(),
|
|
352
|
+
type: z.union([z.number().int().min(1).max(12), stringLikeSchema]).optional(),
|
|
353
|
+
keyword: z.string().optional(),
|
|
354
|
+
query_logic: z.union([z.enum(["and", "or"]), stringLikeSchema]).optional(),
|
|
355
|
+
apply_ids: z.union([z.array(z.union([z.string(), z.number()])), stringLikeSchema]).optional(),
|
|
356
|
+
sort: z.union([z.array(publicSortItemSchema), stringLikeSchema]).optional(),
|
|
357
|
+
filters: z.union([z.array(publicFilterItemSchema), stringLikeSchema]).optional(),
|
|
358
|
+
time_range: z.union([publicTimeRangeSchema, stringLikeSchema]).optional(),
|
|
359
|
+
max_rows: positiveIntLikeSchema(200).optional(),
|
|
360
|
+
max_items: positiveIntLikeSchema(200).optional(),
|
|
361
|
+
max_columns: positiveIntLikeSchema(MAX_COLUMN_LIMIT).optional(),
|
|
362
|
+
select_columns: z
|
|
363
|
+
.union([
|
|
364
|
+
z.array(columnReferenceLikeSchema).min(1).max(MAX_COLUMN_LIMIT),
|
|
365
|
+
stringLikeSchema
|
|
366
|
+
])
|
|
367
|
+
.optional(),
|
|
368
|
+
include_answers: booleanLikeSchema.optional(),
|
|
369
|
+
strict_full: booleanLikeSchema.optional(),
|
|
370
|
+
output_profile: z.union([outputProfileSchema, stringLikeSchema]).optional()
|
|
371
|
+
})
|
|
372
|
+
.passthrough();
|
|
253
373
|
const listInputSchema = z
|
|
254
374
|
.preprocess(normalizeListInput, z.object({
|
|
255
375
|
app_key: z.string().min(1).optional(),
|
|
@@ -350,6 +470,19 @@ const listSuccessOutputSchema = z.object({
|
|
|
350
470
|
meta: apiMetaSchema.optional()
|
|
351
471
|
});
|
|
352
472
|
const listOutputSchema = listSuccessOutputSchema;
|
|
473
|
+
const recordGetInputPublicSchema = z
|
|
474
|
+
.object({
|
|
475
|
+
apply_id: columnSelectorLikeSchema.optional(),
|
|
476
|
+
max_columns: positiveIntLikeSchema(MAX_COLUMN_LIMIT).optional(),
|
|
477
|
+
select_columns: z
|
|
478
|
+
.union([
|
|
479
|
+
z.array(columnReferenceLikeSchema).min(1).max(MAX_COLUMN_LIMIT),
|
|
480
|
+
stringLikeSchema
|
|
481
|
+
])
|
|
482
|
+
.optional(),
|
|
483
|
+
output_profile: z.union([outputProfileSchema, stringLikeSchema]).optional()
|
|
484
|
+
})
|
|
485
|
+
.passthrough();
|
|
353
486
|
const recordGetInputSchema = z.preprocess(normalizeRecordGetInput, z.object({
|
|
354
487
|
apply_id: z.union([z.string().min(1), z.number().int()]),
|
|
355
488
|
max_columns: z.number().int().positive().max(MAX_COLUMN_LIMIT).optional(),
|
|
@@ -382,6 +515,16 @@ const recordGetSuccessOutputSchema = z.object({
|
|
|
382
515
|
meta: apiMetaSchema.optional()
|
|
383
516
|
});
|
|
384
517
|
const recordGetOutputSchema = recordGetSuccessOutputSchema;
|
|
518
|
+
const createInputPublicSchema = z
|
|
519
|
+
.object({
|
|
520
|
+
app_key: stringLikeSchema.optional(),
|
|
521
|
+
user_id: stringLikeSchema.optional(),
|
|
522
|
+
force_refresh_form: booleanLikeSchema.optional(),
|
|
523
|
+
apply_user: z.union([z.record(z.unknown()), stringLikeSchema]).optional(),
|
|
524
|
+
answers: z.union([z.array(publicAnswerInputSchema), stringLikeSchema]).optional(),
|
|
525
|
+
fields: z.union([z.record(z.unknown()), stringLikeSchema]).optional()
|
|
526
|
+
})
|
|
527
|
+
.passthrough();
|
|
385
528
|
const createInputSchema = z
|
|
386
529
|
.object({
|
|
387
530
|
app_key: z.string().min(1),
|
|
@@ -411,6 +554,16 @@ const createSuccessOutputSchema = z.object({
|
|
|
411
554
|
meta: apiMetaSchema
|
|
412
555
|
});
|
|
413
556
|
const createOutputSchema = createSuccessOutputSchema;
|
|
557
|
+
const updateInputPublicSchema = z
|
|
558
|
+
.object({
|
|
559
|
+
apply_id: columnSelectorLikeSchema.optional(),
|
|
560
|
+
app_key: stringLikeSchema.optional(),
|
|
561
|
+
user_id: stringLikeSchema.optional(),
|
|
562
|
+
force_refresh_form: booleanLikeSchema.optional(),
|
|
563
|
+
answers: z.union([z.array(publicAnswerInputSchema), stringLikeSchema]).optional(),
|
|
564
|
+
fields: z.union([z.record(z.unknown()), stringLikeSchema]).optional()
|
|
565
|
+
})
|
|
566
|
+
.passthrough();
|
|
414
567
|
const updateInputSchema = z
|
|
415
568
|
.object({
|
|
416
569
|
apply_id: z.union([z.string().min(1), z.number().int()]),
|
|
@@ -441,6 +594,56 @@ const operationSuccessOutputSchema = z.object({
|
|
|
441
594
|
meta: apiMetaSchema
|
|
442
595
|
});
|
|
443
596
|
const operationOutputSchema = operationSuccessOutputSchema;
|
|
597
|
+
const queryInputPublicSchema = z
|
|
598
|
+
.object({
|
|
599
|
+
query_mode: z.union([z.enum(["auto", "list", "record", "summary"]), stringLikeSchema]).optional(),
|
|
600
|
+
app_key: stringLikeSchema.optional(),
|
|
601
|
+
apply_id: columnSelectorLikeSchema.optional(),
|
|
602
|
+
user_id: stringLikeSchema.optional(),
|
|
603
|
+
page_num: positiveIntLikeSchema().optional(),
|
|
604
|
+
page_token: stringLikeSchema.optional(),
|
|
605
|
+
page_size: positiveIntLikeSchema(200).optional(),
|
|
606
|
+
requested_pages: positiveIntLikeSchema(500).optional(),
|
|
607
|
+
mode: z
|
|
608
|
+
.enum([
|
|
609
|
+
"todo",
|
|
610
|
+
"done",
|
|
611
|
+
"mine_approved",
|
|
612
|
+
"mine_rejected",
|
|
613
|
+
"mine_draft",
|
|
614
|
+
"mine_need_improve",
|
|
615
|
+
"mine_processing",
|
|
616
|
+
"all",
|
|
617
|
+
"all_approved",
|
|
618
|
+
"all_rejected",
|
|
619
|
+
"all_processing",
|
|
620
|
+
"cc"
|
|
621
|
+
])
|
|
622
|
+
.optional(),
|
|
623
|
+
type: z.union([z.number().int().min(1).max(12), stringLikeSchema]).optional(),
|
|
624
|
+
keyword: z.string().optional(),
|
|
625
|
+
query_logic: z.union([z.enum(["and", "or"]), stringLikeSchema]).optional(),
|
|
626
|
+
apply_ids: z.union([z.array(z.union([z.string(), z.number()])), stringLikeSchema]).optional(),
|
|
627
|
+
sort: z.union([z.array(publicSortItemSchema), stringLikeSchema]).optional(),
|
|
628
|
+
filters: z.union([z.array(publicFilterItemSchema), stringLikeSchema]).optional(),
|
|
629
|
+
max_rows: positiveIntLikeSchema(200).optional(),
|
|
630
|
+
max_items: positiveIntLikeSchema(200).optional(),
|
|
631
|
+
max_columns: positiveIntLikeSchema(MAX_COLUMN_LIMIT).optional(),
|
|
632
|
+
select_columns: z
|
|
633
|
+
.union([
|
|
634
|
+
z.array(columnReferenceLikeSchema).min(1).max(MAX_COLUMN_LIMIT),
|
|
635
|
+
stringLikeSchema
|
|
636
|
+
])
|
|
637
|
+
.optional(),
|
|
638
|
+
include_answers: booleanLikeSchema.optional(),
|
|
639
|
+
amount_column: z.union([columnReferenceLikeSchema, stringLikeSchema]).optional(),
|
|
640
|
+
time_range: z.union([publicTimeRangeSchema, stringLikeSchema]).optional(),
|
|
641
|
+
stat_policy: z.union([publicStatPolicySchema, stringLikeSchema]).optional(),
|
|
642
|
+
scan_max_pages: positiveIntLikeSchema(500).optional(),
|
|
643
|
+
strict_full: booleanLikeSchema.optional(),
|
|
644
|
+
output_profile: z.union([outputProfileSchema, stringLikeSchema]).optional()
|
|
645
|
+
})
|
|
646
|
+
.passthrough();
|
|
444
647
|
const queryInputSchema = z
|
|
445
648
|
.preprocess(normalizeQueryInput, z.object({
|
|
446
649
|
query_mode: z.enum(["auto", "list", "record", "summary"]).optional(),
|
|
@@ -580,6 +783,58 @@ const querySuccessOutputSchema = z.object({
|
|
|
580
783
|
meta: apiMetaSchema.optional()
|
|
581
784
|
});
|
|
582
785
|
const queryOutputSchema = querySuccessOutputSchema;
|
|
786
|
+
const aggregateInputPublicSchema = z
|
|
787
|
+
.object({
|
|
788
|
+
app_key: stringLikeSchema.optional(),
|
|
789
|
+
user_id: stringLikeSchema.optional(),
|
|
790
|
+
page_num: positiveIntLikeSchema().optional(),
|
|
791
|
+
page_token: stringLikeSchema.optional(),
|
|
792
|
+
page_size: positiveIntLikeSchema(200).optional(),
|
|
793
|
+
requested_pages: positiveIntLikeSchema(500).optional(),
|
|
794
|
+
scan_max_pages: positiveIntLikeSchema(500).optional(),
|
|
795
|
+
mode: z
|
|
796
|
+
.enum([
|
|
797
|
+
"todo",
|
|
798
|
+
"done",
|
|
799
|
+
"mine_approved",
|
|
800
|
+
"mine_rejected",
|
|
801
|
+
"mine_draft",
|
|
802
|
+
"mine_need_improve",
|
|
803
|
+
"mine_processing",
|
|
804
|
+
"all",
|
|
805
|
+
"all_approved",
|
|
806
|
+
"all_rejected",
|
|
807
|
+
"all_processing",
|
|
808
|
+
"cc"
|
|
809
|
+
])
|
|
810
|
+
.optional(),
|
|
811
|
+
type: z.union([z.number().int().min(1).max(12), stringLikeSchema]).optional(),
|
|
812
|
+
keyword: z.string().optional(),
|
|
813
|
+
query_logic: z.union([z.enum(["and", "or"]), stringLikeSchema]).optional(),
|
|
814
|
+
apply_ids: z.union([z.array(z.union([z.string(), z.number()])), stringLikeSchema]).optional(),
|
|
815
|
+
sort: z.union([z.array(publicSortItemSchema), stringLikeSchema]).optional(),
|
|
816
|
+
filters: z.union([z.array(publicFilterItemSchema), stringLikeSchema]).optional(),
|
|
817
|
+
time_range: z.union([publicTimeRangeSchema, stringLikeSchema]).optional(),
|
|
818
|
+
group_by: z
|
|
819
|
+
.union([z.array(columnReferenceLikeSchema).min(1).max(20), stringLikeSchema])
|
|
820
|
+
.optional(),
|
|
821
|
+
amount_column: z.union([columnReferenceLikeSchema, stringLikeSchema]).optional(),
|
|
822
|
+
amount_columns: z
|
|
823
|
+
.union([z.array(columnReferenceLikeSchema).min(1).max(5), stringLikeSchema])
|
|
824
|
+
.optional(),
|
|
825
|
+
metrics: z
|
|
826
|
+
.union([
|
|
827
|
+
z.array(z.enum(["count", "sum", "avg", "min", "max"])).min(1).max(5),
|
|
828
|
+
stringLikeSchema
|
|
829
|
+
])
|
|
830
|
+
.optional(),
|
|
831
|
+
time_bucket: z.union([z.enum(["day", "week", "month"]), stringLikeSchema]).optional(),
|
|
832
|
+
stat_policy: z.union([publicStatPolicySchema, stringLikeSchema]).optional(),
|
|
833
|
+
max_groups: positiveIntLikeSchema(2000).optional(),
|
|
834
|
+
strict_full: booleanLikeSchema.optional(),
|
|
835
|
+
output_profile: z.union([outputProfileSchema, stringLikeSchema]).optional()
|
|
836
|
+
})
|
|
837
|
+
.passthrough();
|
|
583
838
|
const aggregateInputSchema = z
|
|
584
839
|
.preprocess(normalizeAggregateInput, z.object({
|
|
585
840
|
app_key: z.string().min(1),
|
|
@@ -695,6 +950,15 @@ const aggregateOutputSchema = z.object({
|
|
|
695
950
|
...queryContractFields,
|
|
696
951
|
meta: apiMetaSchema.optional()
|
|
697
952
|
});
|
|
953
|
+
const fieldResolveInputPublicSchema = z
|
|
954
|
+
.object({
|
|
955
|
+
app_key: stringLikeSchema.optional(),
|
|
956
|
+
query: columnSelectorLikeSchema.optional(),
|
|
957
|
+
queries: z.union([z.array(columnSelectorLikeSchema).min(1).max(50), stringLikeSchema]).optional(),
|
|
958
|
+
top_k: positiveIntLikeSchema(10).optional(),
|
|
959
|
+
fuzzy: booleanLikeSchema.optional()
|
|
960
|
+
})
|
|
961
|
+
.passthrough();
|
|
698
962
|
const fieldResolveInputSchema = z.preprocess(normalizeFieldResolveInput, z.object({
|
|
699
963
|
app_key: z.string().min(1),
|
|
700
964
|
query: z.union([z.string().min(1), z.number().int()]).optional(),
|
|
@@ -722,6 +986,14 @@ const fieldResolveOutputSchema = z.object({
|
|
|
722
986
|
}),
|
|
723
987
|
meta: apiMetaSchema
|
|
724
988
|
});
|
|
989
|
+
const queryPlanInputPublicSchema = z
|
|
990
|
+
.object({
|
|
991
|
+
tool: stringLikeSchema.optional(),
|
|
992
|
+
arguments: z.union([z.record(z.unknown()), stringLikeSchema]).optional(),
|
|
993
|
+
resolve_fields: booleanLikeSchema.optional(),
|
|
994
|
+
probe: booleanLikeSchema.optional()
|
|
995
|
+
})
|
|
996
|
+
.passthrough();
|
|
725
997
|
const queryPlanInputSchema = z.preprocess(normalizeQueryPlanInput, z.object({
|
|
726
998
|
tool: z.string().min(1),
|
|
727
999
|
arguments: z.record(z.unknown()).optional(),
|
|
@@ -761,13 +1033,31 @@ const queryPlanOutputSchema = z.object({
|
|
|
761
1033
|
page_amount: z.number().int().nonnegative().nullable()
|
|
762
1034
|
})
|
|
763
1035
|
.nullable()
|
|
764
|
-
})
|
|
1036
|
+
}),
|
|
1037
|
+
ready_for_final_conclusion: z.boolean(),
|
|
1038
|
+
final_conclusion_blockers: z.array(z.string()),
|
|
1039
|
+
recommended_next_actions: z.array(z.string())
|
|
765
1040
|
}),
|
|
766
1041
|
meta: z.object({
|
|
767
1042
|
version: z.string(),
|
|
768
1043
|
generated_at: z.string()
|
|
769
1044
|
})
|
|
770
1045
|
});
|
|
1046
|
+
const batchGetInputPublicSchema = z
|
|
1047
|
+
.object({
|
|
1048
|
+
app_key: stringLikeSchema.optional(),
|
|
1049
|
+
user_id: stringLikeSchema.optional(),
|
|
1050
|
+
apply_ids: z.union([z.array(columnSelectorLikeSchema).min(1).max(200), stringLikeSchema]).optional(),
|
|
1051
|
+
select_columns: z
|
|
1052
|
+
.union([
|
|
1053
|
+
z.array(columnReferenceLikeSchema).min(1).max(MAX_COLUMN_LIMIT),
|
|
1054
|
+
stringLikeSchema
|
|
1055
|
+
])
|
|
1056
|
+
.optional(),
|
|
1057
|
+
max_columns: positiveIntLikeSchema(MAX_COLUMN_LIMIT).optional(),
|
|
1058
|
+
output_profile: z.union([outputProfileSchema, stringLikeSchema]).optional()
|
|
1059
|
+
})
|
|
1060
|
+
.passthrough();
|
|
771
1061
|
const batchGetInputSchema = z.preprocess(normalizeBatchGetInput, z.object({
|
|
772
1062
|
app_key: z.string().min(1),
|
|
773
1063
|
user_id: z.string().min(1).optional(),
|
|
@@ -799,6 +1089,52 @@ const batchGetOutputSchema = z.object({
|
|
|
799
1089
|
...queryContractFields,
|
|
800
1090
|
meta: apiMetaSchema.optional()
|
|
801
1091
|
});
|
|
1092
|
+
const exportInputPublicSchema = z
|
|
1093
|
+
.object({
|
|
1094
|
+
app_key: stringLikeSchema.optional(),
|
|
1095
|
+
user_id: stringLikeSchema.optional(),
|
|
1096
|
+
page_num: positiveIntLikeSchema().optional(),
|
|
1097
|
+
page_token: stringLikeSchema.optional(),
|
|
1098
|
+
page_size: positiveIntLikeSchema(200).optional(),
|
|
1099
|
+
requested_pages: positiveIntLikeSchema(500).optional(),
|
|
1100
|
+
scan_max_pages: positiveIntLikeSchema(500).optional(),
|
|
1101
|
+
mode: z
|
|
1102
|
+
.enum([
|
|
1103
|
+
"todo",
|
|
1104
|
+
"done",
|
|
1105
|
+
"mine_approved",
|
|
1106
|
+
"mine_rejected",
|
|
1107
|
+
"mine_draft",
|
|
1108
|
+
"mine_need_improve",
|
|
1109
|
+
"mine_processing",
|
|
1110
|
+
"all",
|
|
1111
|
+
"all_approved",
|
|
1112
|
+
"all_rejected",
|
|
1113
|
+
"all_processing",
|
|
1114
|
+
"cc"
|
|
1115
|
+
])
|
|
1116
|
+
.optional(),
|
|
1117
|
+
type: z.union([z.number().int().min(1).max(12), stringLikeSchema]).optional(),
|
|
1118
|
+
keyword: z.string().optional(),
|
|
1119
|
+
query_logic: z.union([z.enum(["and", "or"]), stringLikeSchema]).optional(),
|
|
1120
|
+
apply_ids: z.union([z.array(z.union([z.string(), z.number()])), stringLikeSchema]).optional(),
|
|
1121
|
+
sort: z.union([z.array(publicSortItemSchema), stringLikeSchema]).optional(),
|
|
1122
|
+
filters: z.union([z.array(publicFilterItemSchema), stringLikeSchema]).optional(),
|
|
1123
|
+
time_range: z.union([publicTimeRangeSchema, stringLikeSchema]).optional(),
|
|
1124
|
+
max_rows: positiveIntLikeSchema(EXPORT_MAX_ROWS).optional(),
|
|
1125
|
+
max_columns: positiveIntLikeSchema(MAX_COLUMN_LIMIT).optional(),
|
|
1126
|
+
select_columns: z
|
|
1127
|
+
.union([
|
|
1128
|
+
z.array(columnReferenceLikeSchema).min(1).max(MAX_COLUMN_LIMIT),
|
|
1129
|
+
stringLikeSchema
|
|
1130
|
+
])
|
|
1131
|
+
.optional(),
|
|
1132
|
+
strict_full: booleanLikeSchema.optional(),
|
|
1133
|
+
output_profile: z.union([outputProfileSchema, stringLikeSchema]).optional(),
|
|
1134
|
+
export_dir: stringLikeSchema.optional(),
|
|
1135
|
+
file_name: stringLikeSchema.optional()
|
|
1136
|
+
})
|
|
1137
|
+
.passthrough();
|
|
802
1138
|
const exportInputSchema = z.preprocess(normalizeExportInput, z.object({
|
|
803
1139
|
app_key: z.string().min(1).optional(),
|
|
804
1140
|
user_id: z.string().min(1).optional(),
|
|
@@ -895,7 +1231,7 @@ const exportOutputSchema = z.object({
|
|
|
895
1231
|
server.registerTool("qf_tool_spec_get", {
|
|
896
1232
|
title: "Qingflow Tool Spec Get",
|
|
897
1233
|
description: "Return MCP tool parameter requirements, limits, aliases and minimal examples for agent prompt grounding.",
|
|
898
|
-
inputSchema:
|
|
1234
|
+
inputSchema: toolSpecInputPublicSchema,
|
|
899
1235
|
outputSchema: toolSpecOutputSchema,
|
|
900
1236
|
annotations: {
|
|
901
1237
|
readOnlyHint: true,
|
|
@@ -903,9 +1239,10 @@ server.registerTool("qf_tool_spec_get", {
|
|
|
903
1239
|
}
|
|
904
1240
|
}, async (args) => {
|
|
905
1241
|
try {
|
|
1242
|
+
const parsedArgs = toolSpecInputSchema.parse(args);
|
|
906
1243
|
const allSpecs = buildToolSpecCatalog();
|
|
907
|
-
const requested =
|
|
908
|
-
const includeAll =
|
|
1244
|
+
const requested = parsedArgs.tool_name?.trim() ?? null;
|
|
1245
|
+
const includeAll = parsedArgs.include_all ?? false;
|
|
909
1246
|
const normalizedRequested = requested?.toLowerCase() ?? null;
|
|
910
1247
|
let tools = allSpecs;
|
|
911
1248
|
if (normalizedRequested && !includeAll) {
|
|
@@ -1018,7 +1355,7 @@ server.registerTool("qf_form_get", {
|
|
|
1018
1355
|
server.registerTool("qf_field_resolve", {
|
|
1019
1356
|
title: "Qingflow Field Resolve",
|
|
1020
1357
|
description: "Resolve natural language field names/aliases into stable que_id mappings for one app.",
|
|
1021
|
-
inputSchema:
|
|
1358
|
+
inputSchema: fieldResolveInputPublicSchema,
|
|
1022
1359
|
outputSchema: fieldResolveOutputSchema,
|
|
1023
1360
|
annotations: {
|
|
1024
1361
|
readOnlyHint: true,
|
|
@@ -1026,8 +1363,9 @@ server.registerTool("qf_field_resolve", {
|
|
|
1026
1363
|
}
|
|
1027
1364
|
}, async (args) => {
|
|
1028
1365
|
try {
|
|
1029
|
-
const
|
|
1030
|
-
|
|
1366
|
+
const parsedArgs = fieldResolveInputSchema.parse(args);
|
|
1367
|
+
const payload = await executeFieldResolve(parsedArgs);
|
|
1368
|
+
return okResult(payload, `Resolved fields for ${parsedArgs.app_key}`);
|
|
1031
1369
|
}
|
|
1032
1370
|
catch (error) {
|
|
1033
1371
|
return errorResult(error);
|
|
@@ -1036,7 +1374,7 @@ server.registerTool("qf_field_resolve", {
|
|
|
1036
1374
|
server.registerTool("qf_query_plan", {
|
|
1037
1375
|
title: "Qingflow Query Plan",
|
|
1038
1376
|
description: "Preflight query arguments: normalize inputs, validate required fields, resolve mappings and estimate scan limits before execution.",
|
|
1039
|
-
inputSchema:
|
|
1377
|
+
inputSchema: queryPlanInputPublicSchema,
|
|
1040
1378
|
outputSchema: queryPlanOutputSchema,
|
|
1041
1379
|
annotations: {
|
|
1042
1380
|
readOnlyHint: true,
|
|
@@ -1044,8 +1382,9 @@ server.registerTool("qf_query_plan", {
|
|
|
1044
1382
|
}
|
|
1045
1383
|
}, async (args) => {
|
|
1046
1384
|
try {
|
|
1047
|
-
const
|
|
1048
|
-
|
|
1385
|
+
const parsedArgs = queryPlanInputSchema.parse(args);
|
|
1386
|
+
const payload = await executeQueryPlan(parsedArgs);
|
|
1387
|
+
return okResult(payload, `Planned ${parsedArgs.tool}`);
|
|
1049
1388
|
}
|
|
1050
1389
|
catch (error) {
|
|
1051
1390
|
return errorResult(error);
|
|
@@ -1054,7 +1393,7 @@ server.registerTool("qf_query_plan", {
|
|
|
1054
1393
|
server.registerTool("qf_records_list", {
|
|
1055
1394
|
title: "Qingflow Records List",
|
|
1056
1395
|
description: "List records with pagination, filters and sorting.",
|
|
1057
|
-
inputSchema:
|
|
1396
|
+
inputSchema: listInputPublicSchema,
|
|
1058
1397
|
outputSchema: listOutputSchema,
|
|
1059
1398
|
annotations: {
|
|
1060
1399
|
readOnlyHint: true,
|
|
@@ -1062,7 +1401,8 @@ server.registerTool("qf_records_list", {
|
|
|
1062
1401
|
}
|
|
1063
1402
|
}, async (args) => {
|
|
1064
1403
|
try {
|
|
1065
|
-
const
|
|
1404
|
+
const parsedArgs = listInputSchema.parse(args);
|
|
1405
|
+
const executed = await executeRecordsList(parsedArgs);
|
|
1066
1406
|
return okResult(executed.payload, executed.message);
|
|
1067
1407
|
}
|
|
1068
1408
|
catch (error) {
|
|
@@ -1072,7 +1412,7 @@ server.registerTool("qf_records_list", {
|
|
|
1072
1412
|
server.registerTool("qf_record_get", {
|
|
1073
1413
|
title: "Qingflow Record Get",
|
|
1074
1414
|
description: "Get one record by applyId.",
|
|
1075
|
-
inputSchema:
|
|
1415
|
+
inputSchema: recordGetInputPublicSchema,
|
|
1076
1416
|
outputSchema: recordGetOutputSchema,
|
|
1077
1417
|
annotations: {
|
|
1078
1418
|
readOnlyHint: true,
|
|
@@ -1080,7 +1420,8 @@ server.registerTool("qf_record_get", {
|
|
|
1080
1420
|
}
|
|
1081
1421
|
}, async (args) => {
|
|
1082
1422
|
try {
|
|
1083
|
-
const
|
|
1423
|
+
const parsedArgs = recordGetInputSchema.parse(args);
|
|
1424
|
+
const executed = await executeRecordGet(parsedArgs);
|
|
1084
1425
|
return okResult(executed.payload, executed.message);
|
|
1085
1426
|
}
|
|
1086
1427
|
catch (error) {
|
|
@@ -1090,7 +1431,7 @@ server.registerTool("qf_record_get", {
|
|
|
1090
1431
|
server.registerTool("qf_records_batch_get", {
|
|
1091
1432
|
title: "Qingflow Records Batch Get",
|
|
1092
1433
|
description: "Fetch multiple records by apply_ids in one call and return strict flat rows.",
|
|
1093
|
-
inputSchema:
|
|
1434
|
+
inputSchema: batchGetInputPublicSchema,
|
|
1094
1435
|
outputSchema: batchGetOutputSchema,
|
|
1095
1436
|
annotations: {
|
|
1096
1437
|
readOnlyHint: true,
|
|
@@ -1098,7 +1439,8 @@ server.registerTool("qf_records_batch_get", {
|
|
|
1098
1439
|
}
|
|
1099
1440
|
}, async (args) => {
|
|
1100
1441
|
try {
|
|
1101
|
-
const
|
|
1442
|
+
const parsedArgs = batchGetInputSchema.parse(args);
|
|
1443
|
+
const payload = await executeRecordsBatchGet(parsedArgs);
|
|
1102
1444
|
return okResult(payload.payload, payload.message);
|
|
1103
1445
|
}
|
|
1104
1446
|
catch (error) {
|
|
@@ -1108,7 +1450,7 @@ server.registerTool("qf_records_batch_get", {
|
|
|
1108
1450
|
server.registerTool("qf_export_csv", {
|
|
1109
1451
|
title: "Qingflow Export CSV",
|
|
1110
1452
|
description: "Export list query result to a CSV file and return file path + summary instead of large inline payloads.",
|
|
1111
|
-
inputSchema:
|
|
1453
|
+
inputSchema: exportInputPublicSchema,
|
|
1112
1454
|
outputSchema: exportOutputSchema,
|
|
1113
1455
|
annotations: {
|
|
1114
1456
|
readOnlyHint: true,
|
|
@@ -1116,7 +1458,8 @@ server.registerTool("qf_export_csv", {
|
|
|
1116
1458
|
}
|
|
1117
1459
|
}, async (args) => {
|
|
1118
1460
|
try {
|
|
1119
|
-
const
|
|
1461
|
+
const parsedArgs = exportInputSchema.parse(args);
|
|
1462
|
+
const executed = await executeRecordsExport("csv", parsedArgs);
|
|
1120
1463
|
return okResult(executed.payload, executed.message);
|
|
1121
1464
|
}
|
|
1122
1465
|
catch (error) {
|
|
@@ -1126,7 +1469,7 @@ server.registerTool("qf_export_csv", {
|
|
|
1126
1469
|
server.registerTool("qf_export_json", {
|
|
1127
1470
|
title: "Qingflow Export JSON",
|
|
1128
1471
|
description: "Export list query result to a JSON file and return file path + summary instead of large inline payloads.",
|
|
1129
|
-
inputSchema:
|
|
1472
|
+
inputSchema: exportInputPublicSchema,
|
|
1130
1473
|
outputSchema: exportOutputSchema,
|
|
1131
1474
|
annotations: {
|
|
1132
1475
|
readOnlyHint: true,
|
|
@@ -1134,7 +1477,8 @@ server.registerTool("qf_export_json", {
|
|
|
1134
1477
|
}
|
|
1135
1478
|
}, async (args) => {
|
|
1136
1479
|
try {
|
|
1137
|
-
const
|
|
1480
|
+
const parsedArgs = exportInputSchema.parse(args);
|
|
1481
|
+
const executed = await executeRecordsExport("json", parsedArgs);
|
|
1138
1482
|
return okResult(executed.payload, executed.message);
|
|
1139
1483
|
}
|
|
1140
1484
|
catch (error) {
|
|
@@ -1144,7 +1488,7 @@ server.registerTool("qf_export_json", {
|
|
|
1144
1488
|
server.registerTool("qf_query", {
|
|
1145
1489
|
title: "Qingflow Unified Query",
|
|
1146
1490
|
description: "Unified read entry for list/record/summary. Use query_mode=auto to route automatically.",
|
|
1147
|
-
inputSchema:
|
|
1491
|
+
inputSchema: queryInputPublicSchema,
|
|
1148
1492
|
outputSchema: queryOutputSchema,
|
|
1149
1493
|
annotations: {
|
|
1150
1494
|
readOnlyHint: true,
|
|
@@ -1152,9 +1496,10 @@ server.registerTool("qf_query", {
|
|
|
1152
1496
|
}
|
|
1153
1497
|
}, async (args) => {
|
|
1154
1498
|
try {
|
|
1155
|
-
const
|
|
1499
|
+
const parsedArgs = queryInputSchema.parse(args);
|
|
1500
|
+
const routedMode = resolveQueryMode(parsedArgs);
|
|
1156
1501
|
if (routedMode === "record") {
|
|
1157
|
-
const recordArgs = buildRecordGetArgsFromQuery(
|
|
1502
|
+
const recordArgs = buildRecordGetArgsFromQuery(parsedArgs);
|
|
1158
1503
|
const executed = await executeRecordGet(recordArgs);
|
|
1159
1504
|
const completeness = executed.completeness;
|
|
1160
1505
|
const evidence = executed.evidence;
|
|
@@ -1183,7 +1528,7 @@ server.registerTool("qf_query", {
|
|
|
1183
1528
|
}, executed.message);
|
|
1184
1529
|
}
|
|
1185
1530
|
if (routedMode === "summary") {
|
|
1186
|
-
const executed = await executeRecordsSummary(
|
|
1531
|
+
const executed = await executeRecordsSummary(parsedArgs);
|
|
1187
1532
|
const completeness = executed.completeness;
|
|
1188
1533
|
const evidence = executed.evidence;
|
|
1189
1534
|
return okResult({
|
|
@@ -1210,7 +1555,7 @@ server.registerTool("qf_query", {
|
|
|
1210
1555
|
: {})
|
|
1211
1556
|
}, executed.message);
|
|
1212
1557
|
}
|
|
1213
|
-
const listArgs = buildListArgsFromQuery(
|
|
1558
|
+
const listArgs = buildListArgsFromQuery(parsedArgs);
|
|
1214
1559
|
const executed = await executeRecordsList(listArgs);
|
|
1215
1560
|
const completeness = executed.completeness;
|
|
1216
1561
|
const evidence = executed.evidence;
|
|
@@ -1245,7 +1590,7 @@ server.registerTool("qf_query", {
|
|
|
1245
1590
|
server.registerTool("qf_record_create", {
|
|
1246
1591
|
title: "Qingflow Record Create",
|
|
1247
1592
|
description: "Create one record. Supports explicit answers and ergonomic fields mapping (title or queId).",
|
|
1248
|
-
inputSchema:
|
|
1593
|
+
inputSchema: createInputPublicSchema,
|
|
1249
1594
|
outputSchema: createOutputSchema,
|
|
1250
1595
|
annotations: {
|
|
1251
1596
|
readOnlyHint: false,
|
|
@@ -1253,22 +1598,23 @@ server.registerTool("qf_record_create", {
|
|
|
1253
1598
|
}
|
|
1254
1599
|
}, async (args) => {
|
|
1255
1600
|
try {
|
|
1256
|
-
const
|
|
1257
|
-
|
|
1601
|
+
const parsedArgs = createInputSchema.parse(args);
|
|
1602
|
+
const form = needsFormResolution(parsedArgs.fields) || Boolean(parsedArgs.force_refresh_form)
|
|
1603
|
+
? await getFormCached(parsedArgs.app_key, parsedArgs.user_id, Boolean(parsedArgs.force_refresh_form))
|
|
1258
1604
|
: null;
|
|
1259
1605
|
const normalizedAnswers = resolveAnswers({
|
|
1260
|
-
explicitAnswers:
|
|
1261
|
-
fields:
|
|
1606
|
+
explicitAnswers: parsedArgs.answers,
|
|
1607
|
+
fields: parsedArgs.fields,
|
|
1262
1608
|
form: form?.result
|
|
1263
1609
|
});
|
|
1264
1610
|
const payload = {
|
|
1265
1611
|
answers: normalizedAnswers
|
|
1266
1612
|
};
|
|
1267
|
-
if (
|
|
1268
|
-
payload.applyUser =
|
|
1613
|
+
if (parsedArgs.apply_user) {
|
|
1614
|
+
payload.applyUser = parsedArgs.apply_user;
|
|
1269
1615
|
}
|
|
1270
|
-
const response = await client.createRecord(
|
|
1271
|
-
userId:
|
|
1616
|
+
const response = await client.createRecord(parsedArgs.app_key, payload, {
|
|
1617
|
+
userId: parsedArgs.user_id
|
|
1272
1618
|
});
|
|
1273
1619
|
const result = asObject(response.result);
|
|
1274
1620
|
return okResult({
|
|
@@ -1279,7 +1625,7 @@ server.registerTool("qf_record_create", {
|
|
|
1279
1625
|
async_hint: "Use qf_operation_get with request_id when apply_id is null."
|
|
1280
1626
|
},
|
|
1281
1627
|
meta: buildMeta(response)
|
|
1282
|
-
}, `Create request sent for app ${
|
|
1628
|
+
}, `Create request sent for app ${parsedArgs.app_key}`);
|
|
1283
1629
|
}
|
|
1284
1630
|
catch (error) {
|
|
1285
1631
|
return errorResult(error);
|
|
@@ -1288,7 +1634,7 @@ server.registerTool("qf_record_create", {
|
|
|
1288
1634
|
server.registerTool("qf_record_update", {
|
|
1289
1635
|
title: "Qingflow Record Update",
|
|
1290
1636
|
description: "Patch one record by applyId with explicit answers or ergonomic fields mapping.",
|
|
1291
|
-
inputSchema:
|
|
1637
|
+
inputSchema: updateInputPublicSchema,
|
|
1292
1638
|
outputSchema: updateOutputSchema,
|
|
1293
1639
|
annotations: {
|
|
1294
1640
|
readOnlyHint: false,
|
|
@@ -1296,19 +1642,20 @@ server.registerTool("qf_record_update", {
|
|
|
1296
1642
|
}
|
|
1297
1643
|
}, async (args) => {
|
|
1298
1644
|
try {
|
|
1299
|
-
const
|
|
1300
|
-
|
|
1645
|
+
const parsedArgs = updateInputSchema.parse(args);
|
|
1646
|
+
const requiresForm = needsFormResolution(parsedArgs.fields);
|
|
1647
|
+
if (requiresForm && !parsedArgs.app_key) {
|
|
1301
1648
|
throw new Error("app_key is required when fields uses title-based keys");
|
|
1302
1649
|
}
|
|
1303
|
-
const form = requiresForm &&
|
|
1304
|
-
? await getFormCached(
|
|
1650
|
+
const form = requiresForm && parsedArgs.app_key
|
|
1651
|
+
? await getFormCached(parsedArgs.app_key, parsedArgs.user_id, Boolean(parsedArgs.force_refresh_form))
|
|
1305
1652
|
: null;
|
|
1306
1653
|
const normalizedAnswers = resolveAnswers({
|
|
1307
|
-
explicitAnswers:
|
|
1308
|
-
fields:
|
|
1654
|
+
explicitAnswers: parsedArgs.answers,
|
|
1655
|
+
fields: parsedArgs.fields,
|
|
1309
1656
|
form: form?.result
|
|
1310
1657
|
});
|
|
1311
|
-
const response = await client.updateRecord(String(
|
|
1658
|
+
const response = await client.updateRecord(String(parsedArgs.apply_id), { answers: normalizedAnswers }, { userId: parsedArgs.user_id });
|
|
1312
1659
|
const result = asObject(response.result);
|
|
1313
1660
|
return okResult({
|
|
1314
1661
|
ok: true,
|
|
@@ -1317,7 +1664,7 @@ server.registerTool("qf_record_update", {
|
|
|
1317
1664
|
async_hint: "Use qf_operation_get with request_id to fetch update result when needed."
|
|
1318
1665
|
},
|
|
1319
1666
|
meta: buildMeta(response)
|
|
1320
|
-
}, `Update request sent for apply ${String(
|
|
1667
|
+
}, `Update request sent for apply ${String(parsedArgs.apply_id)}`);
|
|
1321
1668
|
}
|
|
1322
1669
|
catch (error) {
|
|
1323
1670
|
return errorResult(error);
|
|
@@ -1351,7 +1698,7 @@ server.registerTool("qf_operation_get", {
|
|
|
1351
1698
|
server.registerTool("qf_records_aggregate", {
|
|
1352
1699
|
title: "Qingflow Records Aggregate",
|
|
1353
1700
|
description: "Aggregate records by group_by columns with optional amount metrics. Designed for deterministic, auditable statistics.",
|
|
1354
|
-
inputSchema:
|
|
1701
|
+
inputSchema: aggregateInputPublicSchema,
|
|
1355
1702
|
outputSchema: aggregateOutputSchema,
|
|
1356
1703
|
annotations: {
|
|
1357
1704
|
readOnlyHint: true,
|
|
@@ -1359,7 +1706,8 @@ server.registerTool("qf_records_aggregate", {
|
|
|
1359
1706
|
}
|
|
1360
1707
|
}, async (args) => {
|
|
1361
1708
|
try {
|
|
1362
|
-
const
|
|
1709
|
+
const parsedArgs = aggregateInputSchema.parse(args);
|
|
1710
|
+
const executed = await executeRecordsAggregate(parsedArgs);
|
|
1363
1711
|
return okResult(executed.payload, executed.message);
|
|
1364
1712
|
}
|
|
1365
1713
|
catch (error) {
|
|
@@ -1653,6 +2001,9 @@ const COMMON_INPUT_ALIASES = {
|
|
|
1653
2001
|
pageNum: "page_num",
|
|
1654
2002
|
pageSize: "page_size",
|
|
1655
2003
|
pageToken: "page_token",
|
|
2004
|
+
rawNextPageToken: "page_token",
|
|
2005
|
+
raw_next_page_token: "page_token",
|
|
2006
|
+
rawPageToken: "page_token",
|
|
1656
2007
|
requestedPages: "requested_pages",
|
|
1657
2008
|
scanMaxPages: "scan_max_pages",
|
|
1658
2009
|
queryMode: "query_mode",
|
|
@@ -2801,12 +3152,104 @@ function decodeContinuationToken(token) {
|
|
|
2801
3152
|
if (!appKey || !nextPageNum || !pageSize) {
|
|
2802
3153
|
throw new Error("Invalid page_token payload");
|
|
2803
3154
|
}
|
|
3155
|
+
const resumeKind = obj?.resume_kind === "summary" || obj?.resume_kind === "aggregate"
|
|
3156
|
+
? obj.resume_kind
|
|
3157
|
+
: undefined;
|
|
3158
|
+
const resumeId = asNullableString(obj?.resume_id) ?? undefined;
|
|
2804
3159
|
return {
|
|
2805
3160
|
app_key: appKey,
|
|
2806
3161
|
next_page_num: nextPageNum,
|
|
2807
|
-
page_size: pageSize
|
|
3162
|
+
page_size: pageSize,
|
|
3163
|
+
...(resumeKind ? { resume_kind: resumeKind } : {}),
|
|
3164
|
+
...(resumeId ? { resume_id: resumeId } : {})
|
|
2808
3165
|
};
|
|
2809
3166
|
}
|
|
3167
|
+
function resolveContinuationPayload(pageToken, appKey) {
|
|
3168
|
+
if (!pageToken) {
|
|
3169
|
+
return null;
|
|
3170
|
+
}
|
|
3171
|
+
const payload = decodeContinuationToken(pageToken);
|
|
3172
|
+
if (payload.app_key !== appKey) {
|
|
3173
|
+
throw new Error(`page_token app_key mismatch: token for ${payload.app_key}, request for ${appKey}`);
|
|
3174
|
+
}
|
|
3175
|
+
return payload;
|
|
3176
|
+
}
|
|
3177
|
+
function buildQueryFingerprint(value) {
|
|
3178
|
+
return stableJson(value);
|
|
3179
|
+
}
|
|
3180
|
+
function loadContinuationState(kind, payload, queryFingerprint, tool) {
|
|
3181
|
+
if (!payload) {
|
|
3182
|
+
return null;
|
|
3183
|
+
}
|
|
3184
|
+
if (payload.resume_kind !== kind || !payload.resume_id) {
|
|
3185
|
+
throw new InputValidationError({
|
|
3186
|
+
message: `${tool} received a page_token that cannot resume aggregated state`,
|
|
3187
|
+
errorCode: "INVALID_PAGE_TOKEN",
|
|
3188
|
+
fixHint: `Reuse the raw_next_page_token returned by ${tool}, or restart the query without page_token.`,
|
|
3189
|
+
details: {
|
|
3190
|
+
tool,
|
|
3191
|
+
expected_resume_kind: kind,
|
|
3192
|
+
received_resume_kind: payload.resume_kind ?? null
|
|
3193
|
+
}
|
|
3194
|
+
});
|
|
3195
|
+
}
|
|
3196
|
+
const state = getContinuationState(kind, payload.resume_id);
|
|
3197
|
+
if (!state) {
|
|
3198
|
+
throw new InputValidationError({
|
|
3199
|
+
message: `${tool} continuation state expired`,
|
|
3200
|
+
errorCode: "CONTINUATION_EXPIRED",
|
|
3201
|
+
fixHint: "Restart the query without page_token to rebuild the aggregate from page 1.",
|
|
3202
|
+
details: {
|
|
3203
|
+
tool,
|
|
3204
|
+
resume_id: payload.resume_id
|
|
3205
|
+
}
|
|
3206
|
+
});
|
|
3207
|
+
}
|
|
3208
|
+
if (state.query_fingerprint !== queryFingerprint) {
|
|
3209
|
+
throw new InputValidationError({
|
|
3210
|
+
message: `${tool} page_token no longer matches the current query arguments`,
|
|
3211
|
+
errorCode: "CONTINUATION_MISMATCH",
|
|
3212
|
+
fixHint: "When continuing a summary/aggregate query, keep app_key, filters, time_range, grouping, selected columns and stat options unchanged.",
|
|
3213
|
+
details: {
|
|
3214
|
+
tool,
|
|
3215
|
+
resume_id: payload.resume_id
|
|
3216
|
+
}
|
|
3217
|
+
});
|
|
3218
|
+
}
|
|
3219
|
+
return {
|
|
3220
|
+
resumeId: payload.resume_id,
|
|
3221
|
+
state
|
|
3222
|
+
};
|
|
3223
|
+
}
|
|
3224
|
+
function setContinuationState(kind, state, resumeId) {
|
|
3225
|
+
const id = resumeId ?? randomUUID();
|
|
3226
|
+
continuationCache.set(id, {
|
|
3227
|
+
kind,
|
|
3228
|
+
state: state,
|
|
3229
|
+
expiresAt: Date.now() + CONTINUATION_CACHE_TTL_MS
|
|
3230
|
+
});
|
|
3231
|
+
return id;
|
|
3232
|
+
}
|
|
3233
|
+
function getContinuationState(kind, resumeId) {
|
|
3234
|
+
const hit = continuationCache.get(resumeId);
|
|
3235
|
+
if (!hit) {
|
|
3236
|
+
return null;
|
|
3237
|
+
}
|
|
3238
|
+
if (hit.expiresAt <= Date.now()) {
|
|
3239
|
+
continuationCache.delete(resumeId);
|
|
3240
|
+
return null;
|
|
3241
|
+
}
|
|
3242
|
+
if (hit.kind !== kind) {
|
|
3243
|
+
throw new Error(`page_token continuation kind mismatch: expected ${kind}, got ${hit.kind}`);
|
|
3244
|
+
}
|
|
3245
|
+
return hit.state;
|
|
3246
|
+
}
|
|
3247
|
+
function deleteContinuationState(resumeId) {
|
|
3248
|
+
if (!resumeId) {
|
|
3249
|
+
return;
|
|
3250
|
+
}
|
|
3251
|
+
continuationCache.delete(resumeId);
|
|
3252
|
+
}
|
|
2810
3253
|
function isExecutionBudgetExceeded(startedAt) {
|
|
2811
3254
|
return Date.now() - startedAt >= EXECUTION_BUDGET_MS;
|
|
2812
3255
|
}
|
|
@@ -3221,6 +3664,54 @@ async function estimatePlanExecution(params) {
|
|
|
3221
3664
|
probe: probeResult
|
|
3222
3665
|
};
|
|
3223
3666
|
}
|
|
3667
|
+
function assessPlanReadiness(params) {
|
|
3668
|
+
const blockers = [];
|
|
3669
|
+
const actions = [];
|
|
3670
|
+
if (!params.validation.valid) {
|
|
3671
|
+
blockers.push("arguments are not valid");
|
|
3672
|
+
actions.push("Fix missing_required and warnings before execution.");
|
|
3673
|
+
}
|
|
3674
|
+
const unresolved = params.fieldMapping.filter((item) => !item.resolved);
|
|
3675
|
+
if (unresolved.length > 0) {
|
|
3676
|
+
blockers.push(`unresolved fields: ${unresolved.map((item) => `${item.role}:${item.requested}`).join(", ")}`);
|
|
3677
|
+
actions.push("Use qf_form_get or qf_field_resolve to resolve field ids before execution.");
|
|
3678
|
+
}
|
|
3679
|
+
const tool = params.tool;
|
|
3680
|
+
const routedMode = tool === "qf_query"
|
|
3681
|
+
? resolveQueryMode(params.normalizedArguments)
|
|
3682
|
+
: null;
|
|
3683
|
+
const strictFull = params.normalizedArguments.strict_full === true;
|
|
3684
|
+
const scanLimit = resolveScanLimit(params.estimate.requested_pages ?? 1, params.estimate.scan_max_pages ?? params.estimate.requested_pages ?? 1);
|
|
3685
|
+
const pageSize = params.estimate.page_size ?? null;
|
|
3686
|
+
const probeResultAmount = params.estimate.probe?.result_amount ?? null;
|
|
3687
|
+
const requiredPagesFromProbe = probeResultAmount !== null && pageSize !== null
|
|
3688
|
+
? Math.max(1, Math.ceil(probeResultAmount / pageSize))
|
|
3689
|
+
: null;
|
|
3690
|
+
if (tool === "qf_records_list" || (tool === "qf_query" && routedMode === "list")) {
|
|
3691
|
+
blockers.push("list mode is not a safe final-analysis endpoint");
|
|
3692
|
+
actions.push("Use qf_query(summary) or qf_records_aggregate for final statistics.");
|
|
3693
|
+
}
|
|
3694
|
+
if (tool === "qf_records_batch_get" || tool === "qf_export_csv" || tool === "qf_export_json") {
|
|
3695
|
+
blockers.push(`${tool} is a data retrieval/export endpoint, not a final-analysis endpoint`);
|
|
3696
|
+
actions.push("Use qf_query(summary) or qf_records_aggregate for final statistics.");
|
|
3697
|
+
}
|
|
3698
|
+
if (tool === "qf_records_aggregate" || (tool === "qf_query" && routedMode === "summary")) {
|
|
3699
|
+
if (!strictFull) {
|
|
3700
|
+
blockers.push("strict_full must be true for final conclusions");
|
|
3701
|
+
actions.push("Set strict_full=true so incomplete raw scans fail with NEED_MORE_DATA.");
|
|
3702
|
+
}
|
|
3703
|
+
if (requiredPagesFromProbe !== null && scanLimit < requiredPagesFromProbe) {
|
|
3704
|
+
blockers.push(`scan budget is smaller than estimated page count (${scanLimit} < ${requiredPagesFromProbe})`);
|
|
3705
|
+
actions.push("Increase requested_pages/scan_max_pages or continue with raw_next_page_token.");
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
actions.push("After execution, still verify completeness.raw_scan_complete=true before concluding.");
|
|
3709
|
+
return {
|
|
3710
|
+
ready_for_final_conclusion: blockers.length === 0,
|
|
3711
|
+
final_conclusion_blockers: uniqueStringList(blockers),
|
|
3712
|
+
recommended_next_actions: uniqueStringList(actions)
|
|
3713
|
+
};
|
|
3714
|
+
}
|
|
3224
3715
|
function scoreFieldMatches(requested, fields, fuzzy, topK) {
|
|
3225
3716
|
const normalizedRequested = requested.trim().toLowerCase();
|
|
3226
3717
|
const requestedIsId = isNumericKey(normalizedRequested);
|
|
@@ -3669,6 +4160,18 @@ async function executeQueryPlan(args) {
|
|
|
3669
4160
|
probe: args.probe !== false,
|
|
3670
4161
|
warnings: validation.warnings
|
|
3671
4162
|
});
|
|
4163
|
+
const readiness = assessPlanReadiness({
|
|
4164
|
+
tool: normalizedTool,
|
|
4165
|
+
normalizedArguments,
|
|
4166
|
+
validation,
|
|
4167
|
+
fieldMapping: fieldMapping.map((item) => ({
|
|
4168
|
+
role: item.role,
|
|
4169
|
+
requested: item.requested,
|
|
4170
|
+
resolved: item.resolved,
|
|
4171
|
+
reason: item.reason
|
|
4172
|
+
})),
|
|
4173
|
+
estimate
|
|
4174
|
+
});
|
|
3672
4175
|
return {
|
|
3673
4176
|
ok: true,
|
|
3674
4177
|
data: {
|
|
@@ -3676,7 +4179,10 @@ async function executeQueryPlan(args) {
|
|
|
3676
4179
|
normalized_arguments: normalizedArguments,
|
|
3677
4180
|
validation,
|
|
3678
4181
|
field_mapping: fieldMapping,
|
|
3679
|
-
estimate
|
|
4182
|
+
estimate,
|
|
4183
|
+
ready_for_final_conclusion: readiness.ready_for_final_conclusion,
|
|
4184
|
+
final_conclusion_blockers: readiness.final_conclusion_blockers,
|
|
4185
|
+
recommended_next_actions: readiness.recommended_next_actions
|
|
3680
4186
|
},
|
|
3681
4187
|
meta: {
|
|
3682
4188
|
version: SERVER_VERSION,
|
|
@@ -4327,6 +4833,108 @@ async function executeRecordGet(args) {
|
|
|
4327
4833
|
outputProfile
|
|
4328
4834
|
};
|
|
4329
4835
|
}
|
|
4836
|
+
function normalizeSortForFingerprint(sort) {
|
|
4837
|
+
return (sort ?? []).map((item) => ({
|
|
4838
|
+
que_id: String(item.que_id),
|
|
4839
|
+
ascend: item.ascend !== false
|
|
4840
|
+
}));
|
|
4841
|
+
}
|
|
4842
|
+
function cloneByDayBuckets(source) {
|
|
4843
|
+
return source.map(([day, bucket]) => [day, { count: bucket.count, amount: bucket.amount }]);
|
|
4844
|
+
}
|
|
4845
|
+
function cloneMetricAccumulator(accumulator) {
|
|
4846
|
+
return {
|
|
4847
|
+
count: accumulator.count,
|
|
4848
|
+
sum: accumulator.sum,
|
|
4849
|
+
min: accumulator.min,
|
|
4850
|
+
max: accumulator.max
|
|
4851
|
+
};
|
|
4852
|
+
}
|
|
4853
|
+
function serializeMetricAccumulatorMap(map) {
|
|
4854
|
+
return Array.from(map.entries()).map(([key, value]) => [key, cloneMetricAccumulator(value)]);
|
|
4855
|
+
}
|
|
4856
|
+
function restoreMetricAccumulatorMap(values) {
|
|
4857
|
+
return new Map(values.map(([key, value]) => [
|
|
4858
|
+
key,
|
|
4859
|
+
{
|
|
4860
|
+
count: value.count,
|
|
4861
|
+
sum: value.sum,
|
|
4862
|
+
min: value.min,
|
|
4863
|
+
max: value.max
|
|
4864
|
+
}
|
|
4865
|
+
]));
|
|
4866
|
+
}
|
|
4867
|
+
function serializeAggregateGroupStats(groupStats) {
|
|
4868
|
+
return Array.from(groupStats.entries()).map(([key, bucket]) => ({
|
|
4869
|
+
key,
|
|
4870
|
+
group: bucket.group,
|
|
4871
|
+
count: bucket.count,
|
|
4872
|
+
amount: bucket.amount,
|
|
4873
|
+
metrics: serializeMetricAccumulatorMap(bucket.metrics)
|
|
4874
|
+
}));
|
|
4875
|
+
}
|
|
4876
|
+
function restoreAggregateGroupStats(values) {
|
|
4877
|
+
return new Map(values.map((item) => [
|
|
4878
|
+
item.key,
|
|
4879
|
+
{
|
|
4880
|
+
group: item.group,
|
|
4881
|
+
count: item.count,
|
|
4882
|
+
amount: item.amount,
|
|
4883
|
+
metrics: restoreMetricAccumulatorMap(item.metrics)
|
|
4884
|
+
}
|
|
4885
|
+
]));
|
|
4886
|
+
}
|
|
4887
|
+
function buildSummaryContinuationFingerprint(params) {
|
|
4888
|
+
return buildQueryFingerprint({
|
|
4889
|
+
kind: "summary",
|
|
4890
|
+
app_key: params.app_key,
|
|
4891
|
+
mode: params.mode ?? null,
|
|
4892
|
+
type: params.type ?? null,
|
|
4893
|
+
keyword: params.keyword ?? null,
|
|
4894
|
+
query_logic: params.query_logic ?? null,
|
|
4895
|
+
apply_ids: uniqueStringList((params.apply_ids ?? []).map((item) => String(item))),
|
|
4896
|
+
sort: normalizeSortForFingerprint(params.sort),
|
|
4897
|
+
filters: params.filters,
|
|
4898
|
+
select_columns: params.select_columns.map((item) => String(item.que_id)),
|
|
4899
|
+
amount_column: params.amount_column ? String(params.amount_column.que_id) : null,
|
|
4900
|
+
time_range: params.time_column
|
|
4901
|
+
? {
|
|
4902
|
+
column: String(params.time_column.que_id),
|
|
4903
|
+
from: params.time_range?.from ?? null,
|
|
4904
|
+
to: params.time_range?.to ?? null,
|
|
4905
|
+
timezone: params.time_range?.timezone ?? null
|
|
4906
|
+
}
|
|
4907
|
+
: null,
|
|
4908
|
+
stat_policy: params.stat_policy,
|
|
4909
|
+
row_cap: params.row_cap
|
|
4910
|
+
});
|
|
4911
|
+
}
|
|
4912
|
+
function buildAggregateContinuationFingerprint(params) {
|
|
4913
|
+
return buildQueryFingerprint({
|
|
4914
|
+
kind: "aggregate",
|
|
4915
|
+
app_key: params.app_key,
|
|
4916
|
+
mode: params.mode ?? null,
|
|
4917
|
+
type: params.type ?? null,
|
|
4918
|
+
keyword: params.keyword ?? null,
|
|
4919
|
+
query_logic: params.query_logic ?? null,
|
|
4920
|
+
apply_ids: uniqueStringList((params.apply_ids ?? []).map((item) => String(item))),
|
|
4921
|
+
sort: normalizeSortForFingerprint(params.sort),
|
|
4922
|
+
filters: params.filters,
|
|
4923
|
+
group_by: params.group_by.map((item) => String(item.que_id)),
|
|
4924
|
+
amount_columns: params.amount_columns.map((item) => String(item.que_id)),
|
|
4925
|
+
metrics: params.metrics,
|
|
4926
|
+
time_range: params.time_column
|
|
4927
|
+
? {
|
|
4928
|
+
column: String(params.time_column.que_id),
|
|
4929
|
+
from: params.time_range?.from ?? null,
|
|
4930
|
+
to: params.time_range?.to ?? null,
|
|
4931
|
+
timezone: params.time_range?.timezone ?? null
|
|
4932
|
+
}
|
|
4933
|
+
: null,
|
|
4934
|
+
time_bucket: params.time_bucket,
|
|
4935
|
+
stat_policy: params.stat_policy
|
|
4936
|
+
});
|
|
4937
|
+
}
|
|
4330
4938
|
async function executeRecordsSummary(args) {
|
|
4331
4939
|
if (!args.app_key) {
|
|
4332
4940
|
throw missingRequiredFieldError({
|
|
@@ -4343,13 +4951,13 @@ async function executeRecordsSummary(args) {
|
|
|
4343
4951
|
});
|
|
4344
4952
|
}
|
|
4345
4953
|
const outputProfile = resolveOutputProfile(args.output_profile);
|
|
4346
|
-
const queryId = randomUUID();
|
|
4347
4954
|
const strictFull = args.strict_full ?? true;
|
|
4348
4955
|
const includeNegative = args.stat_policy?.include_negative ?? true;
|
|
4349
4956
|
const includeNull = args.stat_policy?.include_null ?? false;
|
|
4350
4957
|
const scanMaxPages = args.scan_max_pages ?? DEFAULT_SCAN_MAX_PAGES;
|
|
4351
4958
|
const requestedPages = args.requested_pages ?? scanMaxPages;
|
|
4352
|
-
const
|
|
4959
|
+
const continuationPayload = resolveContinuationPayload(args.page_token, args.app_key);
|
|
4960
|
+
const startPage = continuationPayload?.next_page_num ?? args.page_num ?? 1;
|
|
4353
4961
|
const pageSize = args.page_size ?? DEFAULT_PAGE_SIZE;
|
|
4354
4962
|
const adaptivePaging = createAdaptivePagingState(pageSize);
|
|
4355
4963
|
const rowCap = Math.min(args.max_rows ?? DEFAULT_ROW_LIMIT, DEFAULT_ROW_LIMIT);
|
|
@@ -4375,6 +4983,27 @@ async function executeRecordsSummary(args) {
|
|
|
4375
4983
|
});
|
|
4376
4984
|
}
|
|
4377
4985
|
validateDateRangeFilters(summaryFilters, index, "qf_query(summary)");
|
|
4986
|
+
const queryFingerprint = buildSummaryContinuationFingerprint({
|
|
4987
|
+
app_key: args.app_key,
|
|
4988
|
+
mode: args.mode,
|
|
4989
|
+
type: args.type,
|
|
4990
|
+
keyword: args.keyword,
|
|
4991
|
+
query_logic: args.query_logic,
|
|
4992
|
+
apply_ids: args.apply_ids,
|
|
4993
|
+
sort: normalizedSort,
|
|
4994
|
+
filters: echoFilters(summaryFilters),
|
|
4995
|
+
select_columns: effectiveColumns,
|
|
4996
|
+
amount_column: amountColumn,
|
|
4997
|
+
time_column: timeColumn,
|
|
4998
|
+
time_range: args.time_range,
|
|
4999
|
+
stat_policy: {
|
|
5000
|
+
include_negative: includeNegative,
|
|
5001
|
+
include_null: includeNull
|
|
5002
|
+
},
|
|
5003
|
+
row_cap: rowCap
|
|
5004
|
+
});
|
|
5005
|
+
const resumed = loadContinuationState("summary", continuationPayload, queryFingerprint, "qf_query(summary)");
|
|
5006
|
+
const queryId = resumed?.state.query_id ?? randomUUID();
|
|
4378
5007
|
const listState = {
|
|
4379
5008
|
query_id: queryId,
|
|
4380
5009
|
app_key: args.app_key,
|
|
@@ -4391,20 +5020,23 @@ async function executeRecordsSummary(args) {
|
|
|
4391
5020
|
};
|
|
4392
5021
|
let currentPage = startPage;
|
|
4393
5022
|
const startedAt = Date.now();
|
|
4394
|
-
|
|
4395
|
-
let
|
|
5023
|
+
const callScanLimit = resolveScanLimit(requestedPages, scanMaxPages);
|
|
5024
|
+
let scannedPagesThisCall = 0;
|
|
5025
|
+
let scannedPagesTotal = resumed?.state.source_pages.length ?? 0;
|
|
5026
|
+
let scannedRecords = resumed?.state.scanned_records ?? 0;
|
|
4396
5027
|
let hasMore = false;
|
|
4397
5028
|
let nextPageNum = null;
|
|
4398
5029
|
let resultAmount = null;
|
|
4399
5030
|
let summaryMeta = null;
|
|
4400
5031
|
let stopReason = null;
|
|
4401
|
-
let totalAmount = 0;
|
|
4402
|
-
let missingCount = 0;
|
|
4403
|
-
const sourcePages = [];
|
|
4404
|
-
const
|
|
4405
|
-
const
|
|
4406
|
-
|
|
4407
|
-
|
|
5032
|
+
let totalAmount = resumed?.state.total_amount ?? 0;
|
|
5033
|
+
let missingCount = resumed?.state.missing_count ?? 0;
|
|
5034
|
+
const sourcePages = resumed ? [...resumed.state.source_pages] : [];
|
|
5035
|
+
const totalScanLimit = (resumed?.state.scan_limit_total ?? 0) + callScanLimit;
|
|
5036
|
+
const rows = resumed ? [...resumed.state.rows] : [];
|
|
5037
|
+
const byDay = new Map(resumed ? cloneByDayBuckets(resumed.state.by_day) : []);
|
|
5038
|
+
while (scannedPagesThisCall < callScanLimit) {
|
|
5039
|
+
if (scannedPagesThisCall > 0 && isExecutionBudgetExceeded(startedAt)) {
|
|
4408
5040
|
hasMore = true;
|
|
4409
5041
|
nextPageNum = currentPage;
|
|
4410
5042
|
stopReason = "execution_budget";
|
|
@@ -4426,7 +5058,8 @@ async function executeRecordsSummary(args) {
|
|
|
4426
5058
|
const response = await client.listRecords(args.app_key, payload, { userId: args.user_id });
|
|
4427
5059
|
const fetchMs = Date.now() - fetchStartedAt;
|
|
4428
5060
|
summaryMeta = summaryMeta ?? buildMeta(response);
|
|
4429
|
-
|
|
5061
|
+
scannedPagesThisCall += 1;
|
|
5062
|
+
scannedPagesTotal += 1;
|
|
4430
5063
|
sourcePages.push(currentPage);
|
|
4431
5064
|
const result = asObject(response.result);
|
|
4432
5065
|
const rawItems = asArray(result?.result);
|
|
@@ -4474,8 +5107,8 @@ async function executeRecordsSummary(args) {
|
|
|
4474
5107
|
}
|
|
4475
5108
|
const adaptiveDecision = applyAdaptivePaging({
|
|
4476
5109
|
state: adaptivePaging,
|
|
4477
|
-
fetchedPages:
|
|
4478
|
-
requestedPages,
|
|
5110
|
+
fetchedPages: scannedPagesThisCall,
|
|
5111
|
+
requestedPages: callScanLimit,
|
|
4479
5112
|
fetchMs,
|
|
4480
5113
|
startedAt
|
|
4481
5114
|
});
|
|
@@ -4533,33 +5166,49 @@ async function executeRecordsSummary(args) {
|
|
|
4533
5166
|
}
|
|
4534
5167
|
const knownResultAmount = resultAmount ?? scannedRecords;
|
|
4535
5168
|
const omittedSourceItems = Math.max(0, knownResultAmount - scannedRecords);
|
|
4536
|
-
|
|
4537
|
-
|
|
5169
|
+
let rawNextPageToken = null;
|
|
5170
|
+
const rawScanComplete = !hasMore && omittedSourceItems === 0;
|
|
5171
|
+
if (!rawScanComplete && nextPageNum) {
|
|
5172
|
+
const resumeId = setContinuationState("summary", {
|
|
5173
|
+
query_id: queryId,
|
|
5174
|
+
query_fingerprint: queryFingerprint,
|
|
5175
|
+
scanned_records: scannedRecords,
|
|
5176
|
+
total_amount: totalAmount,
|
|
5177
|
+
missing_count: missingCount,
|
|
5178
|
+
by_day: cloneByDayBuckets(Array.from(byDay.entries())),
|
|
5179
|
+
rows: [...rows],
|
|
5180
|
+
source_pages: [...sourcePages],
|
|
5181
|
+
scan_limit_total: totalScanLimit
|
|
5182
|
+
}, resumed?.resumeId);
|
|
5183
|
+
rawNextPageToken = encodeContinuationToken({
|
|
4538
5184
|
app_key: args.app_key,
|
|
4539
5185
|
next_page_num: nextPageNum,
|
|
4540
|
-
page_size: adaptivePaging.current_page_size
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
5186
|
+
page_size: adaptivePaging.current_page_size,
|
|
5187
|
+
resume_kind: "summary",
|
|
5188
|
+
resume_id: resumeId
|
|
5189
|
+
});
|
|
5190
|
+
}
|
|
5191
|
+
else {
|
|
5192
|
+
deleteContinuationState(resumed?.resumeId);
|
|
5193
|
+
}
|
|
4544
5194
|
const outputPageComplete = rows.length >= scannedRecords;
|
|
4545
|
-
const scanLimit = resolveScanLimit(requestedPages, scanMaxPages);
|
|
4546
5195
|
const scanLimitHit = !rawScanComplete &&
|
|
4547
|
-
(
|
|
5196
|
+
(scannedPagesThisCall >= callScanLimit ||
|
|
4548
5197
|
stopReason === "execution_budget" ||
|
|
4549
5198
|
stopReason === "adaptive_budget");
|
|
4550
5199
|
const completeness = buildExtendedCompleteness({
|
|
4551
5200
|
resultAmount: knownResultAmount,
|
|
4552
5201
|
returnedItems: scannedRecords,
|
|
4553
|
-
fetchedPages:
|
|
4554
|
-
requestedPages,
|
|
5202
|
+
fetchedPages: scannedPagesTotal,
|
|
5203
|
+
requestedPages: totalScanLimit,
|
|
4555
5204
|
hasMore,
|
|
4556
5205
|
nextPageToken: rawNextPageToken,
|
|
4557
5206
|
omittedItems: omittedSourceItems,
|
|
4558
5207
|
omittedChars: 0,
|
|
4559
5208
|
rawScanComplete,
|
|
4560
5209
|
scanLimitHit,
|
|
4561
|
-
scannedPages,
|
|
4562
|
-
scanLimit,
|
|
5210
|
+
scannedPages: scannedPagesTotal,
|
|
5211
|
+
scanLimit: totalScanLimit,
|
|
4563
5212
|
outputPageComplete,
|
|
4564
5213
|
rawNextPageToken,
|
|
4565
5214
|
outputNextPageToken: null,
|
|
@@ -4605,11 +5254,11 @@ async function executeRecordsSummary(args) {
|
|
|
4605
5254
|
},
|
|
4606
5255
|
execution: {
|
|
4607
5256
|
scanned_records: scannedRecords,
|
|
4608
|
-
scanned_pages:
|
|
5257
|
+
scanned_pages: scannedPagesTotal,
|
|
4609
5258
|
truncated: !completeness.is_complete,
|
|
4610
5259
|
row_cap: rowCap,
|
|
4611
5260
|
column_cap: args.max_columns ?? null,
|
|
4612
|
-
scan_max_pages:
|
|
5261
|
+
scan_max_pages: totalScanLimit
|
|
4613
5262
|
}
|
|
4614
5263
|
}
|
|
4615
5264
|
}
|
|
@@ -4625,7 +5274,6 @@ async function executeRecordsSummary(args) {
|
|
|
4625
5274
|
};
|
|
4626
5275
|
}
|
|
4627
5276
|
async function executeRecordsAggregate(args) {
|
|
4628
|
-
const queryId = randomUUID();
|
|
4629
5277
|
const strictFull = args.strict_full ?? true;
|
|
4630
5278
|
const outputProfile = resolveOutputProfile(args.output_profile);
|
|
4631
5279
|
const includeNegative = args.stat_policy?.include_negative ?? true;
|
|
@@ -4634,7 +5282,8 @@ async function executeRecordsAggregate(args) {
|
|
|
4634
5282
|
const adaptivePaging = createAdaptivePagingState(pageSize);
|
|
4635
5283
|
const scanMaxPages = args.scan_max_pages ?? DEFAULT_SCAN_MAX_PAGES;
|
|
4636
5284
|
const requestedPages = args.requested_pages ?? scanMaxPages;
|
|
4637
|
-
const
|
|
5285
|
+
const continuationPayload = resolveContinuationPayload(args.page_token, args.app_key);
|
|
5286
|
+
const startPage = continuationPayload?.next_page_num ?? args.page_num ?? 1;
|
|
4638
5287
|
const maxGroups = args.max_groups ?? 200;
|
|
4639
5288
|
const timezone = args.time_range?.timezone ?? "Asia/Shanghai";
|
|
4640
5289
|
const timeBucket = args.time_bucket ?? null;
|
|
@@ -4662,6 +5311,28 @@ async function executeRecordsAggregate(args) {
|
|
|
4662
5311
|
});
|
|
4663
5312
|
}
|
|
4664
5313
|
validateDateRangeFilters(aggregateFilters, index, "qf_records_aggregate");
|
|
5314
|
+
const queryFingerprint = buildAggregateContinuationFingerprint({
|
|
5315
|
+
app_key: args.app_key,
|
|
5316
|
+
mode: args.mode,
|
|
5317
|
+
type: args.type,
|
|
5318
|
+
keyword: args.keyword,
|
|
5319
|
+
query_logic: args.query_logic,
|
|
5320
|
+
apply_ids: args.apply_ids,
|
|
5321
|
+
sort: normalizedSort,
|
|
5322
|
+
filters: echoFilters(aggregateFilters),
|
|
5323
|
+
group_by: groupColumns,
|
|
5324
|
+
amount_columns: amountColumns,
|
|
5325
|
+
metrics,
|
|
5326
|
+
time_column: timeColumn,
|
|
5327
|
+
time_range: args.time_range,
|
|
5328
|
+
time_bucket: timeBucket,
|
|
5329
|
+
stat_policy: {
|
|
5330
|
+
include_negative: includeNegative,
|
|
5331
|
+
include_null: includeNull
|
|
5332
|
+
}
|
|
5333
|
+
});
|
|
5334
|
+
const resumed = loadContinuationState("aggregate", continuationPayload, queryFingerprint, "qf_records_aggregate");
|
|
5335
|
+
const queryId = resumed?.state.query_id ?? randomUUID();
|
|
4665
5336
|
const listState = {
|
|
4666
5337
|
query_id: queryId,
|
|
4667
5338
|
app_key: args.app_key,
|
|
@@ -4682,19 +5353,24 @@ async function executeRecordsAggregate(args) {
|
|
|
4682
5353
|
};
|
|
4683
5354
|
let currentPage = startPage;
|
|
4684
5355
|
const startedAt = Date.now();
|
|
4685
|
-
|
|
4686
|
-
let
|
|
5356
|
+
const callScanLimit = resolveScanLimit(requestedPages, scanMaxPages);
|
|
5357
|
+
let scannedPagesThisCall = 0;
|
|
5358
|
+
let scannedPagesTotal = resumed?.state.source_pages.length ?? 0;
|
|
5359
|
+
let scannedRecords = resumed?.state.scanned_records ?? 0;
|
|
4687
5360
|
let hasMore = false;
|
|
4688
5361
|
let nextPageNum = null;
|
|
4689
5362
|
let resultAmount = null;
|
|
4690
5363
|
let responseMeta = null;
|
|
4691
5364
|
let stopReason = null;
|
|
4692
|
-
let totalAmount = 0;
|
|
4693
|
-
const sourcePages = [];
|
|
4694
|
-
const
|
|
4695
|
-
const
|
|
4696
|
-
|
|
4697
|
-
|
|
5365
|
+
let totalAmount = resumed?.state.total_amount ?? 0;
|
|
5366
|
+
const sourcePages = resumed ? [...resumed.state.source_pages] : [];
|
|
5367
|
+
const totalScanLimit = (resumed?.state.scan_limit_total ?? 0) + callScanLimit;
|
|
5368
|
+
const groupStats = resumed ? restoreAggregateGroupStats(resumed.state.group_stats) : new Map();
|
|
5369
|
+
const summaryMetricStats = resumed
|
|
5370
|
+
? restoreMetricAccumulatorMap(resumed.state.summary_metric_stats)
|
|
5371
|
+
: new Map();
|
|
5372
|
+
while (scannedPagesThisCall < callScanLimit) {
|
|
5373
|
+
if (scannedPagesThisCall > 0 && isExecutionBudgetExceeded(startedAt)) {
|
|
4698
5374
|
hasMore = true;
|
|
4699
5375
|
nextPageNum = currentPage;
|
|
4700
5376
|
stopReason = "execution_budget";
|
|
@@ -4716,7 +5392,8 @@ async function executeRecordsAggregate(args) {
|
|
|
4716
5392
|
const response = await client.listRecords(args.app_key, payload, { userId: args.user_id });
|
|
4717
5393
|
const fetchMs = Date.now() - fetchStartedAt;
|
|
4718
5394
|
responseMeta = responseMeta ?? buildMeta(response);
|
|
4719
|
-
|
|
5395
|
+
scannedPagesThisCall += 1;
|
|
5396
|
+
scannedPagesTotal += 1;
|
|
4720
5397
|
sourcePages.push(currentPage);
|
|
4721
5398
|
const result = asObject(response.result);
|
|
4722
5399
|
const rawItems = asArray(result?.result);
|
|
@@ -4770,8 +5447,8 @@ async function executeRecordsAggregate(args) {
|
|
|
4770
5447
|
}
|
|
4771
5448
|
const adaptiveDecision = applyAdaptivePaging({
|
|
4772
5449
|
state: adaptivePaging,
|
|
4773
|
-
fetchedPages:
|
|
4774
|
-
requestedPages,
|
|
5450
|
+
fetchedPages: scannedPagesThisCall,
|
|
5451
|
+
requestedPages: callScanLimit,
|
|
4775
5452
|
fetchMs,
|
|
4776
5453
|
startedAt
|
|
4777
5454
|
});
|
|
@@ -4791,34 +5468,49 @@ async function executeRecordsAggregate(args) {
|
|
|
4791
5468
|
}
|
|
4792
5469
|
const knownResultAmount = resultAmount ?? scannedRecords;
|
|
4793
5470
|
const omittedSourceItems = Math.max(0, knownResultAmount - scannedRecords);
|
|
4794
|
-
|
|
4795
|
-
? encodeContinuationToken({
|
|
4796
|
-
app_key: args.app_key,
|
|
4797
|
-
next_page_num: nextPageNum,
|
|
4798
|
-
page_size: adaptivePaging.current_page_size
|
|
4799
|
-
})
|
|
4800
|
-
: null;
|
|
5471
|
+
let rawNextPageToken = null;
|
|
4801
5472
|
const groupsTotal = groupStats.size;
|
|
4802
5473
|
const rawScanComplete = !hasMore && omittedSourceItems === 0;
|
|
5474
|
+
if (!rawScanComplete && nextPageNum) {
|
|
5475
|
+
const resumeId = setContinuationState("aggregate", {
|
|
5476
|
+
query_id: queryId,
|
|
5477
|
+
query_fingerprint: queryFingerprint,
|
|
5478
|
+
scanned_records: scannedRecords,
|
|
5479
|
+
total_amount: totalAmount,
|
|
5480
|
+
source_pages: [...sourcePages],
|
|
5481
|
+
scan_limit_total: totalScanLimit,
|
|
5482
|
+
group_stats: serializeAggregateGroupStats(groupStats),
|
|
5483
|
+
summary_metric_stats: serializeMetricAccumulatorMap(summaryMetricStats)
|
|
5484
|
+
}, resumed?.resumeId);
|
|
5485
|
+
rawNextPageToken = encodeContinuationToken({
|
|
5486
|
+
app_key: args.app_key,
|
|
5487
|
+
next_page_num: nextPageNum,
|
|
5488
|
+
page_size: adaptivePaging.current_page_size,
|
|
5489
|
+
resume_kind: "aggregate",
|
|
5490
|
+
resume_id: resumeId
|
|
5491
|
+
});
|
|
5492
|
+
}
|
|
5493
|
+
else {
|
|
5494
|
+
deleteContinuationState(resumed?.resumeId);
|
|
5495
|
+
}
|
|
4803
5496
|
const outputPageComplete = groupsTotal <= maxGroups;
|
|
4804
|
-
const scanLimit = resolveScanLimit(requestedPages, scanMaxPages);
|
|
4805
5497
|
const scanLimitHit = !rawScanComplete &&
|
|
4806
|
-
(
|
|
5498
|
+
(scannedPagesThisCall >= callScanLimit ||
|
|
4807
5499
|
stopReason === "execution_budget" ||
|
|
4808
5500
|
stopReason === "adaptive_budget");
|
|
4809
5501
|
const completeness = buildExtendedCompleteness({
|
|
4810
5502
|
resultAmount: knownResultAmount,
|
|
4811
5503
|
returnedItems: scannedRecords,
|
|
4812
|
-
fetchedPages:
|
|
4813
|
-
requestedPages,
|
|
5504
|
+
fetchedPages: scannedPagesTotal,
|
|
5505
|
+
requestedPages: totalScanLimit,
|
|
4814
5506
|
hasMore,
|
|
4815
5507
|
nextPageToken: rawNextPageToken,
|
|
4816
5508
|
omittedItems: omittedSourceItems,
|
|
4817
5509
|
omittedChars: 0,
|
|
4818
5510
|
rawScanComplete,
|
|
4819
5511
|
scanLimitHit,
|
|
4820
|
-
scannedPages,
|
|
4821
|
-
scanLimit,
|
|
5512
|
+
scannedPages: scannedPagesTotal,
|
|
5513
|
+
scanLimit: totalScanLimit,
|
|
4822
5514
|
outputPageComplete,
|
|
4823
5515
|
rawNextPageToken,
|
|
4824
5516
|
outputNextPageToken: null,
|