@components-kit/open-workbook 0.1.7 → 0.1.9
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/assets/backend/dist/runtime-service.d.ts +7 -7
- package/assets/backend/dist/runtime-service.d.ts.map +1 -1
- package/assets/backend/dist/runtime-service.js +15 -11
- package/assets/backend/dist/runtime-service.js.map +1 -1
- package/assets/excel-addin/dist/excel-executor.js +1 -1
- package/assets/excel-addin/dist/taskpane.bundle.js +6 -6
- package/assets/excel-addin/manifest.xml +1 -1
- package/assets/instructions/open-workbook-excel/SKILL.md +11 -7
- package/assets/instructions/open-workbook-excel/references/performance.md +9 -0
- package/assets/instructions/open-workbook-excel/references/tool-selection.md +17 -7
- package/assets/mcp-server/dist/index.js +2443 -119
- package/assets/mcp-server/dist/index.js.map +1 -1
- package/dist/index.js +17 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
|
@@ -14,6 +14,9 @@ const standalone = hasArg("--standalone") || process.env.OPEN_WORKBOOK_MCP_STAND
|
|
|
14
14
|
const catalogOptions = {
|
|
15
15
|
includePreview: process.env.OPEN_WORKBOOK_PREVIEW_TOOLS === "1"
|
|
16
16
|
};
|
|
17
|
+
const toolProfile = readArg("--tool-profile") ?? process.env.OPEN_WORKBOOK_TOOL_PROFILE ?? "full";
|
|
18
|
+
const explicitToolNames = parseToolNameList(readArg("--tools") ?? process.env.OPEN_WORKBOOK_TOOLS);
|
|
19
|
+
const disabledToolNames = parseToolNameList(readArg("--disable-tools") ?? process.env.OPEN_WORKBOOK_DISABLE_TOOLS);
|
|
17
20
|
const STYLE_DIMENSIONS = [
|
|
18
21
|
"columnWidths",
|
|
19
22
|
"rowHeights",
|
|
@@ -44,8 +47,106 @@ const STYLE_COPY_TOOL_DIMENSIONS = {
|
|
|
44
47
|
"excel.style.copy_page_layout": "pageLayout",
|
|
45
48
|
"excel.style.copy_hidden_rows_columns": "hiddenRowsColumns"
|
|
46
49
|
};
|
|
50
|
+
const COMPACT_PROFILE_TOOLS = new Set([
|
|
51
|
+
"excel.runtime.get_status",
|
|
52
|
+
"excel.runtime.get_capabilities",
|
|
53
|
+
"excel.runtime.get_active_context",
|
|
54
|
+
"excel.runtime.get_selection",
|
|
55
|
+
"excel.runtime.connect_addin",
|
|
56
|
+
"excel.runtime.ping_addin",
|
|
57
|
+
"excel.runtime.set_active_workbook",
|
|
58
|
+
"excel.runtime.set_active_sheet",
|
|
59
|
+
"excel.workbook.list_open_workbooks",
|
|
60
|
+
"excel.workbook.get_workbook_info",
|
|
61
|
+
"excel.workbook.get_workbook_map",
|
|
62
|
+
"excel.workbook.get_summary",
|
|
63
|
+
"excel.workbook.get_used_range_summary",
|
|
64
|
+
"excel.workbook.detect_external_changes",
|
|
65
|
+
"excel.workbook.calculate",
|
|
66
|
+
"excel.workbook.save",
|
|
67
|
+
"excel.sheet.list",
|
|
68
|
+
"excel.sheet.get_info",
|
|
69
|
+
"excel.sheet.get_summary",
|
|
70
|
+
"excel.sheet.get_used_range",
|
|
71
|
+
"excel.lookup.search_workbook",
|
|
72
|
+
"excel.lookup.find_headers",
|
|
73
|
+
"excel.lookup.find_tables_by_columns",
|
|
74
|
+
"excel.lookup.find_entity",
|
|
75
|
+
"excel.lookup.resolve_range",
|
|
76
|
+
"excel.lookup.inspect_match",
|
|
77
|
+
"excel.range.get_summary",
|
|
78
|
+
"excel.range.read_compact",
|
|
79
|
+
"excel.range.find_errors",
|
|
80
|
+
"excel.table.list",
|
|
81
|
+
"excel.table.get_info",
|
|
82
|
+
"excel.table.get_schema",
|
|
83
|
+
"excel.table.read_compact",
|
|
84
|
+
"excel.batch.validate",
|
|
85
|
+
"excel.batch.preflight",
|
|
86
|
+
"excel.batch.dry_run",
|
|
87
|
+
"excel.batch.apply",
|
|
88
|
+
"excel.batch.submit",
|
|
89
|
+
"excel.batch.submit_chunked",
|
|
90
|
+
"excel.job.list",
|
|
91
|
+
"excel.job.get",
|
|
92
|
+
"excel.job.wait",
|
|
93
|
+
"excel.workflow.prepare_session",
|
|
94
|
+
"excel.workflow.create_formula_sheet",
|
|
95
|
+
"excel.workflow.create_template_report",
|
|
96
|
+
"excel.workflow.create_pivot_chart_summary",
|
|
97
|
+
"excel.workflow.repair_formula_errors",
|
|
98
|
+
"excel.workflow.preview_risky_edit",
|
|
99
|
+
"excel.plan.create",
|
|
100
|
+
"excel.plan.preview",
|
|
101
|
+
"excel.plan.apply",
|
|
102
|
+
"excel.plan.rollback",
|
|
103
|
+
"excel.compact.get_resource",
|
|
104
|
+
"excel.compact.list_resources",
|
|
105
|
+
"excel.compact.clear_resources",
|
|
106
|
+
"excel.compact.get_cache_status",
|
|
107
|
+
"excel.compact.clear_cache",
|
|
108
|
+
"excel.snapshot.create",
|
|
109
|
+
"excel.snapshot.get_compact",
|
|
110
|
+
"excel.snapshot.compare_compact",
|
|
111
|
+
"excel.diff.summarize",
|
|
112
|
+
"excel.diff.get_compact",
|
|
113
|
+
"excel.validate.compact",
|
|
114
|
+
"excel.validate.no_formula_errors",
|
|
115
|
+
"excel.validate.no_broken_references",
|
|
116
|
+
"excel.validate.no_unintended_changes",
|
|
117
|
+
"excel.collab.get_status",
|
|
118
|
+
"excel.transaction.list",
|
|
119
|
+
"excel.transaction.get",
|
|
120
|
+
"excel.transaction.wait",
|
|
121
|
+
"excel.transaction.preview_rollback",
|
|
122
|
+
"excel.transaction.rollback"
|
|
123
|
+
]);
|
|
124
|
+
const READ_ONLY_PROFILE_TOOLS = new Set([...COMPACT_PROFILE_TOOLS].filter((name) => name.startsWith("excel.runtime.") ||
|
|
125
|
+
name.startsWith("excel.workbook.get_") ||
|
|
126
|
+
name === "excel.workbook.list_open_workbooks" ||
|
|
127
|
+
name.startsWith("excel.sheet.") ||
|
|
128
|
+
name.startsWith("excel.lookup.") ||
|
|
129
|
+
name.startsWith("excel.range.read_") ||
|
|
130
|
+
name === "excel.range.get_summary" ||
|
|
131
|
+
name === "excel.range.find_errors" ||
|
|
132
|
+
name.startsWith("excel.table.get_") ||
|
|
133
|
+
name === "excel.table.list" ||
|
|
134
|
+
name === "excel.table.read_compact" ||
|
|
135
|
+
name.startsWith("excel.compact.") ||
|
|
136
|
+
name.startsWith("excel.snapshot.get_") ||
|
|
137
|
+
name === "excel.snapshot.compare_compact" ||
|
|
138
|
+
name.startsWith("excel.diff.") ||
|
|
139
|
+
name.startsWith("excel.validate.") ||
|
|
140
|
+
name === "excel.collab.get_status"));
|
|
47
141
|
const runtime = await createRuntimeFacade();
|
|
48
|
-
const runtimeVersion = process.env.OPEN_WORKBOOK_VERSION ?? "0.1.
|
|
142
|
+
const runtimeVersion = process.env.OPEN_WORKBOOK_VERSION ?? "0.1.9";
|
|
143
|
+
const COMPACT_RESOURCE_LIMIT = 100;
|
|
144
|
+
const COMPACT_DEFAULT_RESOURCE_THRESHOLD_BYTES = 24_000;
|
|
145
|
+
const compactResources = new Map();
|
|
146
|
+
const compactCache = new Map();
|
|
147
|
+
let compactCacheInvalidationCount = 0;
|
|
148
|
+
let compactCacheLastInvalidatedAt;
|
|
149
|
+
let compactLastObservedEventId;
|
|
49
150
|
const server = new McpServer({
|
|
50
151
|
name: "open-workbook",
|
|
51
152
|
version: runtimeVersion
|
|
@@ -55,6 +156,7 @@ registerWorkbookTools(server);
|
|
|
55
156
|
registerBackupTools(server);
|
|
56
157
|
registerSheetTools(server);
|
|
57
158
|
registerRangeTools(server);
|
|
159
|
+
registerLookupTools(server);
|
|
58
160
|
registerBatchTools(server);
|
|
59
161
|
registerWorkflowTools(server);
|
|
60
162
|
registerPlanTools(server);
|
|
@@ -81,6 +183,7 @@ registerRepairTools(server);
|
|
|
81
183
|
registerSnapshotTools(server);
|
|
82
184
|
registerDiffTools(server);
|
|
83
185
|
registerEventTools(server);
|
|
186
|
+
registerCompactTools(server);
|
|
84
187
|
registerResources(server);
|
|
85
188
|
registerPrompts(server);
|
|
86
189
|
await server.connect(new StdioServerTransport());
|
|
@@ -211,6 +314,7 @@ function registerResources(mcp) {
|
|
|
211
314
|
const planId = resourceVariable(variables, "plan_id");
|
|
212
315
|
return runtime.getPlanDiffResource(workbookId, planId);
|
|
213
316
|
});
|
|
317
|
+
registerJsonTemplateResource(mcp, "compact detail resource", "excel://compact/{resource_id}", "Stored compact-context detail payload returned by token-saving Open Workbook tools.", async (_uri, variables) => getCompactResource(resourceVariable(variables, "resource_id")));
|
|
214
318
|
}
|
|
215
319
|
function registerJsonResource(mcp, name, uri, description, read) {
|
|
216
320
|
mcp.registerResource(name, uri, {
|
|
@@ -371,14 +475,15 @@ function registerRuntimeTools(mcp) {
|
|
|
371
475
|
title: "Get Open Workbook capabilities",
|
|
372
476
|
description: "Return complete tool/resource/prompt catalog status and runtime capability metadata.",
|
|
373
477
|
inputSchema: {
|
|
374
|
-
includePreview: z.boolean().optional()
|
|
478
|
+
includePreview: z.boolean().optional(),
|
|
479
|
+
includeFullCatalog: z.boolean().optional()
|
|
375
480
|
},
|
|
376
481
|
annotations: {
|
|
377
482
|
readOnlyHint: true,
|
|
378
483
|
destructiveHint: false,
|
|
379
484
|
openWorldHint: false
|
|
380
485
|
}
|
|
381
|
-
}, async ({ includePreview }) => jsonResult(
|
|
486
|
+
}, async ({ includePreview, includeFullCatalog }) => jsonResult(runtimeCapabilities(includePreview, includeFullCatalog)));
|
|
382
487
|
registerMcpTool(mcp, "excel.runtime.get_active_context", {
|
|
383
488
|
title: "Get active Excel context",
|
|
384
489
|
description: "Return active workbook context from the connected Excel add-in.",
|
|
@@ -424,6 +529,44 @@ function registerRuntimeTools(mcp) {
|
|
|
424
529
|
}
|
|
425
530
|
}, async ({ sheetName }) => jsonResult(await runtime.setActiveSheet(sheetName)));
|
|
426
531
|
}
|
|
532
|
+
function runtimeCapabilities(includePreview, includeFullCatalog) {
|
|
533
|
+
const capabilities = runtime.getCapabilities(includePreview === undefined ? {} : { includePreview });
|
|
534
|
+
if (toolProfile === "full" || includeFullCatalog === true) {
|
|
535
|
+
return capabilities;
|
|
536
|
+
}
|
|
537
|
+
const typed = capabilities;
|
|
538
|
+
const catalog = typed.catalog;
|
|
539
|
+
const exposedToolNames = exposedProfileToolNames();
|
|
540
|
+
return {
|
|
541
|
+
...typed,
|
|
542
|
+
catalog: catalog
|
|
543
|
+
? {
|
|
544
|
+
total: catalog.total,
|
|
545
|
+
stable: catalog.stable,
|
|
546
|
+
preview: catalog.preview,
|
|
547
|
+
planned: catalog.planned,
|
|
548
|
+
unsupported: catalog.unsupported,
|
|
549
|
+
profile: toolProfile,
|
|
550
|
+
exposed: exposedToolNames.length,
|
|
551
|
+
tools: exposedToolNames
|
|
552
|
+
}
|
|
553
|
+
: undefined,
|
|
554
|
+
resources: Array.isArray(typed.resources) ? { count: typed.resources.length } : typed.resources,
|
|
555
|
+
prompts: Array.isArray(typed.prompts) ? { count: typed.prompts.length } : typed.prompts
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
function exposedProfileToolNames() {
|
|
559
|
+
if (explicitToolNames !== undefined) {
|
|
560
|
+
return [...explicitToolNames].filter((name) => shouldExposeMcpTool(name)).sort();
|
|
561
|
+
}
|
|
562
|
+
if (toolProfile === "compact") {
|
|
563
|
+
return [...COMPACT_PROFILE_TOOLS].filter((name) => shouldExposeMcpTool(name)).sort();
|
|
564
|
+
}
|
|
565
|
+
if (toolProfile === "read-only" || toolProfile === "readonly") {
|
|
566
|
+
return [...READ_ONLY_PROFILE_TOOLS].filter((name) => shouldExposeMcpTool(name)).sort();
|
|
567
|
+
}
|
|
568
|
+
return [];
|
|
569
|
+
}
|
|
427
570
|
function registerWorkbookTools(mcp) {
|
|
428
571
|
registerMcpTool(mcp, "excel.workbook.list_open_workbooks", {
|
|
429
572
|
title: "List open Excel workbooks",
|
|
@@ -460,6 +603,26 @@ function registerWorkbookTools(mcp) {
|
|
|
460
603
|
openWorldHint: false
|
|
461
604
|
}
|
|
462
605
|
}, async () => jsonResult(await runtime.getWorkbookMap()));
|
|
606
|
+
registerMcpTool(mcp, "excel.workbook.get_summary", {
|
|
607
|
+
title: "Get compact workbook summary",
|
|
608
|
+
description: "Return compact workbook structure, sheet counts, table names, used-range dimensions, and token telemetry without cell bodies.",
|
|
609
|
+
inputSchema: { workbookId: z.string().optional() },
|
|
610
|
+
annotations: {
|
|
611
|
+
readOnlyHint: true,
|
|
612
|
+
destructiveHint: false,
|
|
613
|
+
openWorldHint: false
|
|
614
|
+
}
|
|
615
|
+
}, async ({ workbookId }) => jsonResult(await workbookSummary(workbookId)));
|
|
616
|
+
registerMcpTool(mcp, "excel.workbook.get_used_range_summary", {
|
|
617
|
+
title: "Get compact workbook used-range summary",
|
|
618
|
+
description: "Return used-range dimensions for workbook sheets without loading cell payloads.",
|
|
619
|
+
inputSchema: { workbookId: z.string().optional() },
|
|
620
|
+
annotations: {
|
|
621
|
+
readOnlyHint: true,
|
|
622
|
+
destructiveHint: false,
|
|
623
|
+
openWorldHint: false
|
|
624
|
+
}
|
|
625
|
+
}, async ({ workbookId }) => jsonResult(await workbookUsedRangeSummary(workbookId)));
|
|
463
626
|
registerMcpTool(mcp, "excel.workbook.snapshot", {
|
|
464
627
|
title: "Create workbook snapshot",
|
|
465
628
|
description: "Capture a restorable snapshot of used ranges or specific ranges.",
|
|
@@ -879,6 +1042,12 @@ function registerSheetTools(mcp) {
|
|
|
879
1042
|
inputSchema: { sheetName: z.string() },
|
|
880
1043
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
881
1044
|
}, async ({ sheetName }) => jsonResult(await selectSheetInfo(sheetName)));
|
|
1045
|
+
registerMcpTool(mcp, "excel.sheet.get_summary", {
|
|
1046
|
+
title: "Get compact worksheet summary",
|
|
1047
|
+
description: "Return one worksheet's used-range dimensions, table names, and token telemetry without cell bodies.",
|
|
1048
|
+
inputSchema: { workbookId: z.string().optional(), sheetName: z.string() },
|
|
1049
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
1050
|
+
}, async ({ workbookId, sheetName }) => jsonResult(await sheetSummary(sheetName, workbookId)));
|
|
882
1051
|
registerMcpTool(mcp, "excel.sheet.get_used_range", {
|
|
883
1052
|
title: "Get worksheet used range",
|
|
884
1053
|
description: "Return the used range for a worksheet.",
|
|
@@ -986,6 +1155,46 @@ function registerRangeTools(mcp) {
|
|
|
986
1155
|
}
|
|
987
1156
|
}, async ({ workbookId, sheetName, address }) => jsonResult(await readRangeSnapshot(workbookId, sheetName, address, rangeReadFacets(name))));
|
|
988
1157
|
}
|
|
1158
|
+
const compactReadSchema = {
|
|
1159
|
+
...readSchema,
|
|
1160
|
+
mode: z.enum(["window", "summary", "sample"]).optional(),
|
|
1161
|
+
rowOffset: z.number().int().min(0).optional(),
|
|
1162
|
+
columnOffset: z.number().int().min(0).optional(),
|
|
1163
|
+
maxRows: z.number().int().min(0).max(10000).optional(),
|
|
1164
|
+
maxColumns: z.number().int().min(0).max(1000).optional(),
|
|
1165
|
+
maxCells: z.number().int().min(0).max(1000000).optional(),
|
|
1166
|
+
maxPayloadBytes: z.number().int().min(0).optional(),
|
|
1167
|
+
maxEstimatedTokens: z.number().int().min(0).optional(),
|
|
1168
|
+
includeValues: z.boolean().optional(),
|
|
1169
|
+
includeFormulas: z.boolean().optional(),
|
|
1170
|
+
includeText: z.boolean().optional(),
|
|
1171
|
+
includeNumberFormats: z.boolean().optional(),
|
|
1172
|
+
includeStyles: z.boolean().optional()
|
|
1173
|
+
};
|
|
1174
|
+
registerMcpTool(mcp, "excel.range.read_compact", {
|
|
1175
|
+
title: "Read compact Excel range",
|
|
1176
|
+
description: "Read a bounded values-first range window with opt-in facets, truncation metadata, and token telemetry.",
|
|
1177
|
+
inputSchema: compactReadSchema,
|
|
1178
|
+
annotations: {
|
|
1179
|
+
readOnlyHint: true,
|
|
1180
|
+
destructiveHint: false,
|
|
1181
|
+
openWorldHint: false
|
|
1182
|
+
}
|
|
1183
|
+
}, async (args) => jsonResult(await compactRangeRead(args)));
|
|
1184
|
+
registerMcpTool(mcp, "excel.range.get_summary", {
|
|
1185
|
+
title: "Get compact range summary",
|
|
1186
|
+
description: "Return range dimensions, cell count, default compact-read window, and token telemetry without cell bodies.",
|
|
1187
|
+
inputSchema: {
|
|
1188
|
+
workbookId: z.string(),
|
|
1189
|
+
sheetName: z.string(),
|
|
1190
|
+
address: z.string()
|
|
1191
|
+
},
|
|
1192
|
+
annotations: {
|
|
1193
|
+
readOnlyHint: true,
|
|
1194
|
+
destructiveHint: false,
|
|
1195
|
+
openWorldHint: false
|
|
1196
|
+
}
|
|
1197
|
+
}, async ({ workbookId, sheetName, address }) => jsonResult(rangeSummary(workbookId, sheetName, address)));
|
|
989
1198
|
for (const [name, method] of [
|
|
990
1199
|
["excel.range.read_hyperlinks", "range.read_hyperlinks"],
|
|
991
1200
|
["excel.range.read_comments", "range.read_comments"],
|
|
@@ -1214,6 +1423,111 @@ function registerRangeTools(mcp) {
|
|
|
1214
1423
|
}));
|
|
1215
1424
|
}
|
|
1216
1425
|
}
|
|
1426
|
+
function registerLookupTools(mcp) {
|
|
1427
|
+
const budgetSchema = {
|
|
1428
|
+
maxRows: z.number().int().min(0).max(10000).optional(),
|
|
1429
|
+
maxColumns: z.number().int().min(0).max(1000).optional(),
|
|
1430
|
+
maxCells: z.number().int().min(0).max(1000000).optional(),
|
|
1431
|
+
maxPayloadBytes: z.number().int().min(0).optional(),
|
|
1432
|
+
maxEstimatedTokens: z.number().int().min(0).optional()
|
|
1433
|
+
};
|
|
1434
|
+
const lookupAnnotations = { readOnlyHint: true, destructiveHint: false, openWorldHint: false };
|
|
1435
|
+
registerMcpTool(mcp, "excel.lookup.search_workbook", {
|
|
1436
|
+
title: "Search workbook compactly",
|
|
1437
|
+
description: "Search sheet names, table schemas, and used ranges across a workbook, returning ranked compact matches instead of full sheets.",
|
|
1438
|
+
inputSchema: {
|
|
1439
|
+
workbookId: z.string(),
|
|
1440
|
+
query: z.string(),
|
|
1441
|
+
sheetNames: z.array(z.string()).optional(),
|
|
1442
|
+
includeSheets: z.boolean().optional(),
|
|
1443
|
+
includeTables: z.boolean().optional(),
|
|
1444
|
+
completeMatch: z.boolean().optional(),
|
|
1445
|
+
matchCase: z.boolean().optional(),
|
|
1446
|
+
maxMatches: z.number().int().min(1).max(1000).optional(),
|
|
1447
|
+
maxPreviewRows: z.number().int().min(0).max(25).optional(),
|
|
1448
|
+
...budgetSchema
|
|
1449
|
+
},
|
|
1450
|
+
annotations: lookupAnnotations
|
|
1451
|
+
}, async (args) => jsonResult(await lookupSearchWorkbook(args)));
|
|
1452
|
+
registerMcpTool(mcp, "excel.lookup.find_headers", {
|
|
1453
|
+
title: "Find workbook headers compactly",
|
|
1454
|
+
description: "Find matching table columns and likely header cells from bounded top-of-sheet scans.",
|
|
1455
|
+
inputSchema: {
|
|
1456
|
+
workbookId: z.string(),
|
|
1457
|
+
query: z.string().optional(),
|
|
1458
|
+
headers: z.array(z.string()).optional(),
|
|
1459
|
+
sheetNames: z.array(z.string()).optional(),
|
|
1460
|
+
maxRowsPerSheet: z.number().int().min(1).max(100).optional(),
|
|
1461
|
+
maxMatches: z.number().int().min(1).max(1000).optional(),
|
|
1462
|
+
...budgetSchema
|
|
1463
|
+
},
|
|
1464
|
+
annotations: lookupAnnotations
|
|
1465
|
+
}, async (args) => jsonResult(await lookupFindHeaders(args)));
|
|
1466
|
+
registerMcpTool(mcp, "excel.lookup.find_tables_by_columns", {
|
|
1467
|
+
title: "Find tables by column set",
|
|
1468
|
+
description: "Rank workbook tables by required and optional column names without reading table rows.",
|
|
1469
|
+
inputSchema: {
|
|
1470
|
+
workbookId: z.string(),
|
|
1471
|
+
requiredColumns: z.array(z.string()),
|
|
1472
|
+
optionalColumns: z.array(z.string()).optional(),
|
|
1473
|
+
minScore: z.number().min(0).max(1).optional(),
|
|
1474
|
+
maxMatches: z.number().int().min(1).max(1000).optional(),
|
|
1475
|
+
...budgetSchema
|
|
1476
|
+
},
|
|
1477
|
+
annotations: lookupAnnotations
|
|
1478
|
+
}, async (args) => jsonResult(await lookupFindTablesByColumns(args)));
|
|
1479
|
+
registerMcpTool(mcp, "excel.lookup.find_entity", {
|
|
1480
|
+
title: "Find entity compactly",
|
|
1481
|
+
description: "Locate a text, number, date, or generic entity across workbook used ranges with compact ranked results.",
|
|
1482
|
+
inputSchema: {
|
|
1483
|
+
workbookId: z.string(),
|
|
1484
|
+
entity: z.string(),
|
|
1485
|
+
kind: z.enum(["text", "number", "date", "any"]).optional(),
|
|
1486
|
+
sheetNames: z.array(z.string()).optional(),
|
|
1487
|
+
completeMatch: z.boolean().optional(),
|
|
1488
|
+
matchCase: z.boolean().optional(),
|
|
1489
|
+
maxMatches: z.number().int().min(1).max(1000).optional(),
|
|
1490
|
+
maxPreviewRows: z.number().int().min(0).max(25).optional(),
|
|
1491
|
+
...budgetSchema
|
|
1492
|
+
},
|
|
1493
|
+
annotations: lookupAnnotations
|
|
1494
|
+
}, async (args) => jsonResult(await lookupFindEntity(args)));
|
|
1495
|
+
registerMcpTool(mcp, "excel.lookup.resolve_range", {
|
|
1496
|
+
title: "Resolve workbook range target",
|
|
1497
|
+
description: "Resolve a natural target string to a table, column, header, entity, sheet, or A1 range candidate before reading data.",
|
|
1498
|
+
inputSchema: {
|
|
1499
|
+
workbookId: z.string(),
|
|
1500
|
+
target: z.string(),
|
|
1501
|
+
kind: z.enum(["table", "column", "header", "entity", "range", "any"]).optional(),
|
|
1502
|
+
preferredSheetName: z.string().optional(),
|
|
1503
|
+
preferredTableName: z.string().optional(),
|
|
1504
|
+
maxMatches: z.number().int().min(1).max(1000).optional(),
|
|
1505
|
+
...budgetSchema
|
|
1506
|
+
},
|
|
1507
|
+
annotations: lookupAnnotations
|
|
1508
|
+
}, async (args) => jsonResult(await lookupResolveRange(args)));
|
|
1509
|
+
registerMcpTool(mcp, "excel.lookup.inspect_match", {
|
|
1510
|
+
title: "Inspect lookup match compactly",
|
|
1511
|
+
description: "Read a bounded preview for one lookup match, preserving compact telemetry and avoiding broad sheet reads.",
|
|
1512
|
+
inputSchema: {
|
|
1513
|
+
workbookId: z.string(),
|
|
1514
|
+
matchId: z.string().optional(),
|
|
1515
|
+
kind: z.enum(["sheet", "table", "column", "header", "entity", "range"]).optional(),
|
|
1516
|
+
sheetName: z.string().optional(),
|
|
1517
|
+
tableName: z.string().optional(),
|
|
1518
|
+
columnName: z.string().optional(),
|
|
1519
|
+
address: z.string().optional(),
|
|
1520
|
+
maxRows: z.number().int().min(0).max(10000).optional(),
|
|
1521
|
+
maxColumns: z.number().int().min(0).max(1000).optional(),
|
|
1522
|
+
includeValues: z.boolean().optional(),
|
|
1523
|
+
includeFormulas: z.boolean().optional(),
|
|
1524
|
+
includeText: z.boolean().optional(),
|
|
1525
|
+
maxPayloadBytes: z.number().int().min(0).optional(),
|
|
1526
|
+
maxEstimatedTokens: z.number().int().min(0).optional()
|
|
1527
|
+
},
|
|
1528
|
+
annotations: lookupAnnotations
|
|
1529
|
+
}, async (args) => jsonResult(await lookupInspectMatch(args)));
|
|
1530
|
+
}
|
|
1217
1531
|
function registerBatchTools(mcp) {
|
|
1218
1532
|
registerMcpTool(mcp, "excel.batch.validate", {
|
|
1219
1533
|
title: "Validate Excel batch",
|
|
@@ -1909,7 +2223,7 @@ async function workflowPreflight(workbookId, includePreview = true) {
|
|
|
1909
2223
|
return {
|
|
1910
2224
|
status,
|
|
1911
2225
|
activeContext,
|
|
1912
|
-
capabilities:
|
|
2226
|
+
capabilities: runtimeCapabilities(includePreview),
|
|
1913
2227
|
workbookMap: await runtime.getWorkbookMap(),
|
|
1914
2228
|
collaboration: runtime.getCollaborationStatus(resolvedWorkbookId),
|
|
1915
2229
|
workbookId: resolvedWorkbookId
|
|
@@ -2596,6 +2910,12 @@ function registerTableTools(mcp) {
|
|
|
2596
2910
|
inputSchema: tableSelectorSchema(),
|
|
2597
2911
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
2598
2912
|
}, async (args) => jsonResult(await runtime.getTableInfo(tableSelector(args))));
|
|
2913
|
+
registerMcpTool(mcp, "excel.table.get_schema", {
|
|
2914
|
+
title: "Get compact Excel table schema",
|
|
2915
|
+
description: "Return table columns, dimensions, style flags, and token telemetry without row data.",
|
|
2916
|
+
inputSchema: tableSelectorSchema(),
|
|
2917
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
2918
|
+
}, async (args) => jsonResult(await tableSchema(tableSelector(args))));
|
|
2599
2919
|
registerMcpTool(mcp, "excel.table.read", {
|
|
2600
2920
|
title: "Read Excel table",
|
|
2601
2921
|
description: "Read table headers, selected data facets, optional columns, optional row page, and metadata.",
|
|
@@ -2611,6 +2931,26 @@ function registerTableTools(mcp) {
|
|
|
2611
2931
|
},
|
|
2612
2932
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
2613
2933
|
}, async (args) => jsonResult(await runtime.readTable(tableReadRequest(args))));
|
|
2934
|
+
registerMcpTool(mcp, "excel.table.read_compact", {
|
|
2935
|
+
title: "Read compact Excel table",
|
|
2936
|
+
description: "Read a bounded values-first table page with projection, opt-in facets, truncation metadata, and token telemetry.",
|
|
2937
|
+
inputSchema: {
|
|
2938
|
+
...tableSelectorSchema(),
|
|
2939
|
+
mode: z.enum(["window", "summary", "sample"]).optional(),
|
|
2940
|
+
includeValues: z.boolean().optional(),
|
|
2941
|
+
includeFormulas: z.boolean().optional(),
|
|
2942
|
+
includeText: z.boolean().optional(),
|
|
2943
|
+
includeNumberFormats: z.boolean().optional(),
|
|
2944
|
+
columns: z.array(z.union([z.string(), z.number().int().min(0)])).optional(),
|
|
2945
|
+
rowOffset: z.number().int().min(0).optional(),
|
|
2946
|
+
maxRows: z.number().int().min(0).max(10000).optional(),
|
|
2947
|
+
maxColumns: z.number().int().min(0).max(1000).optional(),
|
|
2948
|
+
maxCells: z.number().int().min(0).max(1000000).optional(),
|
|
2949
|
+
maxPayloadBytes: z.number().int().min(0).optional(),
|
|
2950
|
+
maxEstimatedTokens: z.number().int().min(0).optional()
|
|
2951
|
+
},
|
|
2952
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
2953
|
+
}, async (args) => jsonResult(await compactTableRead(args)));
|
|
2614
2954
|
registerMcpTool(mcp, "excel.table.create", {
|
|
2615
2955
|
title: "Create Excel table",
|
|
2616
2956
|
description: "Create a structured table from a range, optionally writing values first.",
|
|
@@ -3641,6 +3981,36 @@ function registerValidateTools(mcp) {
|
|
|
3641
3981
|
inputSchema: { workbookId: z.string() },
|
|
3642
3982
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
3643
3983
|
}, async ({ workbookId }) => jsonResult(await runtime.validateWorkbook({ workbookId: workbookId })));
|
|
3984
|
+
registerMcpTool(mcp, "excel.validate.compact", {
|
|
3985
|
+
title: "Run compact validation",
|
|
3986
|
+
description: "Run a validator and return concise issue counts/examples with full details stored behind a compact resource URI.",
|
|
3987
|
+
inputSchema: {
|
|
3988
|
+
workbookId: z.string(),
|
|
3989
|
+
validator: z.enum([
|
|
3990
|
+
"workbook",
|
|
3991
|
+
"sheet",
|
|
3992
|
+
"formulas",
|
|
3993
|
+
"styles",
|
|
3994
|
+
"tables",
|
|
3995
|
+
"filters",
|
|
3996
|
+
"print_layout",
|
|
3997
|
+
"no_broken_references",
|
|
3998
|
+
"no_formula_errors",
|
|
3999
|
+
"no_unintended_changes"
|
|
4000
|
+
]),
|
|
4001
|
+
sheetName: z.string().optional(),
|
|
4002
|
+
address: z.string().optional(),
|
|
4003
|
+
tableName: z.string().optional(),
|
|
4004
|
+
templateId: z.string().optional(),
|
|
4005
|
+
snapshotId: z.string().optional(),
|
|
4006
|
+
leftSnapshotId: z.string().optional(),
|
|
4007
|
+
rightSnapshotId: z.string().optional(),
|
|
4008
|
+
maxIssues: z.number().int().min(0).max(100).optional(),
|
|
4009
|
+
maxPayloadBytes: z.number().int().min(0).optional(),
|
|
4010
|
+
maxEstimatedTokens: z.number().int().min(0).optional()
|
|
4011
|
+
},
|
|
4012
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
4013
|
+
}, async (args) => jsonResult(await compactValidation(args)));
|
|
3644
4014
|
registerMcpTool(mcp, "excel.validate.sheet", {
|
|
3645
4015
|
title: "Validate sheet",
|
|
3646
4016
|
description: "Validate one worksheet's used range and formula-error state.",
|
|
@@ -3861,13 +4231,13 @@ function registerSnapshotTools(mcp) {
|
|
|
3861
4231
|
if (!existing.ok || !("snapshot" in existing)) {
|
|
3862
4232
|
return jsonResult(existing);
|
|
3863
4233
|
}
|
|
3864
|
-
return jsonResult(await runtime.createWorkbookSnapshot({
|
|
4234
|
+
return jsonResult(compactSnapshotCreationResult(await runtime.createWorkbookSnapshot({
|
|
3865
4235
|
workbookId: existing.snapshot.workbookId,
|
|
3866
4236
|
reason: args.reason ?? `Refresh snapshot ${args.snapshotId}`,
|
|
3867
4237
|
ranges: existing.snapshot.affectedRanges
|
|
3868
|
-
}));
|
|
4238
|
+
})));
|
|
3869
4239
|
}
|
|
3870
|
-
return jsonResult(await runtime.createWorkbookSnapshot(snapshotRequest(args.workbookId, args.reason, args.ranges)));
|
|
4240
|
+
return jsonResult(compactSnapshotCreationResult(await runtime.createWorkbookSnapshot(snapshotRequest(args.workbookId, args.reason, args.ranges))));
|
|
3871
4241
|
});
|
|
3872
4242
|
}
|
|
3873
4243
|
registerMcpTool(mcp, "excel.snapshot.get", {
|
|
@@ -3876,6 +4246,12 @@ function registerSnapshotTools(mcp) {
|
|
|
3876
4246
|
inputSchema: { snapshotId: z.string() },
|
|
3877
4247
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
3878
4248
|
}, async ({ snapshotId }) => jsonResult(runtime.getSnapshot(snapshotId)));
|
|
4249
|
+
registerMcpTool(mcp, "excel.snapshot.get_compact", {
|
|
4250
|
+
title: "Get compact snapshot",
|
|
4251
|
+
description: "Return snapshot metadata and store full snapshot payload behind a compact resource URI.",
|
|
4252
|
+
inputSchema: { snapshotId: z.string(), maxPayloadBytes: z.number().int().min(0).optional(), maxEstimatedTokens: z.number().int().min(0).optional() },
|
|
4253
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
4254
|
+
}, ({ snapshotId, maxPayloadBytes, maxEstimatedTokens }) => jsonResult(compactSnapshot(snapshotId, maxPayloadBytes, maxEstimatedTokens)));
|
|
3879
4255
|
registerMcpTool(mcp, "excel.snapshot.list", {
|
|
3880
4256
|
title: "List snapshots",
|
|
3881
4257
|
description: "List stored snapshots for a workbook.",
|
|
@@ -3891,6 +4267,17 @@ function registerSnapshotTools(mcp) {
|
|
|
3891
4267
|
},
|
|
3892
4268
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
3893
4269
|
}, async ({ leftSnapshotId, rightSnapshotId }) => jsonResult(runtime.compareSnapshots(leftSnapshotId, rightSnapshotId)));
|
|
4270
|
+
registerMcpTool(mcp, "excel.snapshot.compare_compact", {
|
|
4271
|
+
title: "Compare snapshots compactly",
|
|
4272
|
+
description: "Return a compact snapshot diff summary and store full diff details behind a compact resource URI.",
|
|
4273
|
+
inputSchema: {
|
|
4274
|
+
leftSnapshotId: z.string(),
|
|
4275
|
+
rightSnapshotId: z.string(),
|
|
4276
|
+
maxPayloadBytes: z.number().int().min(0).optional(),
|
|
4277
|
+
maxEstimatedTokens: z.number().int().min(0).optional()
|
|
4278
|
+
},
|
|
4279
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
4280
|
+
}, ({ leftSnapshotId, rightSnapshotId, maxPayloadBytes, maxEstimatedTokens }) => jsonResult(compactSnapshotDiff(leftSnapshotId, rightSnapshotId, maxPayloadBytes, maxEstimatedTokens)));
|
|
3894
4281
|
registerMcpTool(mcp, "excel.snapshot.invalidate", {
|
|
3895
4282
|
title: "Invalidate snapshot",
|
|
3896
4283
|
description: "Mark a snapshot as stale without deleting it.",
|
|
@@ -3919,6 +4306,17 @@ function registerDiffTools(mcp) {
|
|
|
3919
4306
|
return jsonResult(name.endsWith("export_json") ? { ok: true, json: JSON.stringify(diff, null, 2) } : diff);
|
|
3920
4307
|
});
|
|
3921
4308
|
}
|
|
4309
|
+
registerMcpTool(mcp, "excel.diff.get_compact", {
|
|
4310
|
+
title: "Get compact diff",
|
|
4311
|
+
description: "Return a compact diff summary and store full diff details behind a compact resource URI.",
|
|
4312
|
+
inputSchema: {
|
|
4313
|
+
leftSnapshotId: z.string(),
|
|
4314
|
+
rightSnapshotId: z.string(),
|
|
4315
|
+
maxPayloadBytes: z.number().int().min(0).optional(),
|
|
4316
|
+
maxEstimatedTokens: z.number().int().min(0).optional()
|
|
4317
|
+
},
|
|
4318
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
4319
|
+
}, ({ leftSnapshotId, rightSnapshotId, maxPayloadBytes, maxEstimatedTokens }) => jsonResult(compactSnapshotDiff(leftSnapshotId, rightSnapshotId, maxPayloadBytes, maxEstimatedTokens)));
|
|
3922
4320
|
registerMcpTool(mcp, "excel.diff.export_html", {
|
|
3923
4321
|
title: "Export diff HTML",
|
|
3924
4322
|
description: "Return a small HTML representation of a snapshot diff.",
|
|
@@ -3965,6 +4363,53 @@ function registerEventTools(mcp) {
|
|
|
3965
4363
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
3966
4364
|
}, async ({ debounceMs }) => jsonResult(runtime.setEventDebounce(debounceMs)));
|
|
3967
4365
|
}
|
|
4366
|
+
function registerCompactTools(mcp) {
|
|
4367
|
+
registerMcpTool(mcp, "excel.compact.get_resource", {
|
|
4368
|
+
title: "Get compact detail resource",
|
|
4369
|
+
description: "Fetch stored compact detail metadata by default. Set includePayload only when full detail is required.",
|
|
4370
|
+
inputSchema: {
|
|
4371
|
+
resourceId: z.string().optional(),
|
|
4372
|
+
resourceUri: z.string().optional(),
|
|
4373
|
+
mode: z.enum(["metadata", "preview", "page", "full"]).optional(),
|
|
4374
|
+
includePayload: z.boolean().optional(),
|
|
4375
|
+
maxPayloadBytes: z.number().int().min(0).optional(),
|
|
4376
|
+
maxEstimatedTokens: z.number().int().min(0).optional(),
|
|
4377
|
+
offset: z.number().int().min(0).optional(),
|
|
4378
|
+
limit: z.number().int().min(1).max(24000).optional()
|
|
4379
|
+
},
|
|
4380
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
4381
|
+
}, ({ resourceId, resourceUri, mode, includePayload, maxPayloadBytes, maxEstimatedTokens, offset, limit }) => jsonResult(getCompactResource(resourceId ?? compactResourceIdFromUri(resourceUri ?? ""), compactResourceReadOptions({ mode, includePayload, maxPayloadBytes, maxEstimatedTokens, offset, limit }))));
|
|
4382
|
+
registerMcpTool(mcp, "excel.compact.list_resources", {
|
|
4383
|
+
title: "List compact detail resources",
|
|
4384
|
+
description: "List stored compact detail resources without returning their payloads.",
|
|
4385
|
+
inputSchema: { kind: z.string().optional(), limit: z.number().int().min(1).max(COMPACT_RESOURCE_LIMIT).optional() },
|
|
4386
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
4387
|
+
}, ({ kind, limit }) => jsonResult(listCompactResources(kind, limit)));
|
|
4388
|
+
registerMcpTool(mcp, "excel.compact.delete_resource", {
|
|
4389
|
+
title: "Delete compact detail resource",
|
|
4390
|
+
description: "Delete one stored compact detail payload.",
|
|
4391
|
+
inputSchema: { resourceId: z.string().optional(), resourceUri: z.string().optional() },
|
|
4392
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
4393
|
+
}, ({ resourceId, resourceUri }) => jsonResult(deleteCompactResource(resourceId ?? compactResourceIdFromUri(resourceUri ?? ""))));
|
|
4394
|
+
registerMcpTool(mcp, "excel.compact.clear_resources", {
|
|
4395
|
+
title: "Clear compact detail resources",
|
|
4396
|
+
description: "Clear stored compact detail payloads from this MCP process.",
|
|
4397
|
+
inputSchema: { kind: z.string().optional() },
|
|
4398
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
4399
|
+
}, ({ kind }) => jsonResult(clearCompactResources(kind)));
|
|
4400
|
+
registerMcpTool(mcp, "excel.compact.get_cache_status", {
|
|
4401
|
+
title: "Get compact cache status",
|
|
4402
|
+
description: "Return compact summary/schema cache size and invalidation metadata.",
|
|
4403
|
+
inputSchema: {},
|
|
4404
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
4405
|
+
}, () => jsonResult(getCompactCacheStatus()));
|
|
4406
|
+
registerMcpTool(mcp, "excel.compact.clear_cache", {
|
|
4407
|
+
title: "Clear compact cache",
|
|
4408
|
+
description: "Clear compact summary/schema cache entries from this MCP process.",
|
|
4409
|
+
inputSchema: {},
|
|
4410
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
4411
|
+
}, () => jsonResult(clearCompactCache("manual")));
|
|
4412
|
+
}
|
|
3968
4413
|
function rangeReadFacets(toolName) {
|
|
3969
4414
|
switch (toolName) {
|
|
3970
4415
|
case "excel.range.read_values":
|
|
@@ -4048,137 +4493,1652 @@ function tableReadRequest(args) {
|
|
|
4048
4493
|
}
|
|
4049
4494
|
return request;
|
|
4050
4495
|
}
|
|
4051
|
-
function
|
|
4052
|
-
const
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
column: z.union([z.string(), z.number().int().min(0)]),
|
|
4077
|
-
criteria: z.record(z.string(), z.any())
|
|
4078
|
-
}))
|
|
4079
|
-
};
|
|
4080
|
-
}
|
|
4081
|
-
function tableSortSchema() {
|
|
4496
|
+
function withCompactTelemetry(payload, options) {
|
|
4497
|
+
const originalPayloadBytes = Buffer.byteLength(JSON.stringify(payload), "utf8");
|
|
4498
|
+
const originalEstimatedTokens = Math.ceil(originalPayloadBytes / 4);
|
|
4499
|
+
const maxPayloadBytes = options.maxPayloadBytes ?? (options.budgetSummary !== undefined ? COMPACT_DEFAULT_RESOURCE_THRESHOLD_BYTES : undefined);
|
|
4500
|
+
const overBudget = (maxPayloadBytes !== undefined && originalPayloadBytes > maxPayloadBytes) ||
|
|
4501
|
+
(options.maxEstimatedTokens !== undefined && originalEstimatedTokens > options.maxEstimatedTokens);
|
|
4502
|
+
const stored = options.storeResource || overBudget
|
|
4503
|
+
? storeCompactResource(options.resourceKind ?? "generic", options.resourcePayload ?? payload, options.resourceTitle)
|
|
4504
|
+
: undefined;
|
|
4505
|
+
const output = overBudget && options.budgetSummary !== undefined
|
|
4506
|
+
? {
|
|
4507
|
+
...options.budgetSummary,
|
|
4508
|
+
budgetExceeded: true,
|
|
4509
|
+
warnings: [
|
|
4510
|
+
...asWarningArray(options.budgetSummary.warnings),
|
|
4511
|
+
{
|
|
4512
|
+
code: "COMPACT_BUDGET_EXCEEDED",
|
|
4513
|
+
message: "Full compact detail exceeded the response budget and was stored behind resourceUri."
|
|
4514
|
+
}
|
|
4515
|
+
],
|
|
4516
|
+
resourcePayloadBytes: originalPayloadBytes,
|
|
4517
|
+
resourceEstimatedTokens: originalEstimatedTokens
|
|
4518
|
+
}
|
|
4519
|
+
: payload;
|
|
4520
|
+
const payloadBytes = Buffer.byteLength(JSON.stringify(output), "utf8");
|
|
4082
4521
|
return {
|
|
4083
|
-
...
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
})),
|
|
4091
|
-
matchCase: z.boolean().optional(),
|
|
4092
|
-
method: z.enum(["PinYin", "StrokeCount"]).optional()
|
|
4522
|
+
...output,
|
|
4523
|
+
payloadBytes,
|
|
4524
|
+
estimatedTokens: Math.ceil(payloadBytes / 4),
|
|
4525
|
+
truncated: options.truncated ?? false,
|
|
4526
|
+
detailLevel: options.detailLevel,
|
|
4527
|
+
...(options.nextPage !== undefined ? { nextPage: options.nextPage } : {}),
|
|
4528
|
+
...(stored !== undefined ? { resourceUri: stored.uri } : options.resourceUri !== undefined ? { resourceUri: options.resourceUri } : {})
|
|
4093
4529
|
};
|
|
4094
4530
|
}
|
|
4095
|
-
function
|
|
4096
|
-
return
|
|
4097
|
-
workbookId: z.string(),
|
|
4098
|
-
name: z.string(),
|
|
4099
|
-
sheetName: z.string().optional()
|
|
4100
|
-
};
|
|
4531
|
+
function asWarningArray(value) {
|
|
4532
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "object" && item !== null) : [];
|
|
4101
4533
|
}
|
|
4102
|
-
function
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4534
|
+
function storeCompactResource(kind, payload, title) {
|
|
4535
|
+
const resourceId = makeId("compact");
|
|
4536
|
+
const payloadBytes = Buffer.byteLength(JSON.stringify(payload), "utf8");
|
|
4537
|
+
const resource = {
|
|
4538
|
+
resourceId,
|
|
4539
|
+
uri: `excel://compact/${resourceId}`,
|
|
4540
|
+
kind,
|
|
4541
|
+
title,
|
|
4542
|
+
createdAt: new Date().toISOString(),
|
|
4543
|
+
payloadBytes,
|
|
4544
|
+
estimatedTokens: Math.ceil(payloadBytes / 4),
|
|
4545
|
+
payload
|
|
4109
4546
|
};
|
|
4547
|
+
compactResources.set(resourceId, resource);
|
|
4548
|
+
while (compactResources.size > COMPACT_RESOURCE_LIMIT) {
|
|
4549
|
+
const oldestKey = compactResources.keys().next().value;
|
|
4550
|
+
if (oldestKey === undefined) {
|
|
4551
|
+
break;
|
|
4552
|
+
}
|
|
4553
|
+
compactResources.delete(oldestKey);
|
|
4554
|
+
}
|
|
4555
|
+
return resource;
|
|
4110
4556
|
}
|
|
4111
|
-
function
|
|
4112
|
-
const
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
};
|
|
4116
|
-
if (args.sheetName !== undefined) {
|
|
4117
|
-
request.sheetName = args.sheetName;
|
|
4557
|
+
function getCompactResource(resourceId, options = {}) {
|
|
4558
|
+
const resource = compactResources.get(compactResourceIdFromUri(resourceId));
|
|
4559
|
+
if (!resource) {
|
|
4560
|
+
return { ok: false, error: { code: "COMPACT_RESOURCE_NOT_FOUND", message: "Compact detail resource was not found or has expired." } };
|
|
4118
4561
|
}
|
|
4119
|
-
|
|
4562
|
+
const { payload, ...summary } = resource;
|
|
4563
|
+
const mode = options.mode ?? (options.includePayload === true ? "full" : "metadata");
|
|
4564
|
+
if (mode === "metadata") {
|
|
4565
|
+
return {
|
|
4566
|
+
ok: true,
|
|
4567
|
+
...summary,
|
|
4568
|
+
payloadAvailable: true,
|
|
4569
|
+
payloadIncluded: false,
|
|
4570
|
+
message: "Use mode=preview or mode=page for bounded inspection, or mode=full/includePayload=true for full detail."
|
|
4571
|
+
};
|
|
4572
|
+
}
|
|
4573
|
+
if (mode === "preview" || mode === "page") {
|
|
4574
|
+
const serialized = JSON.stringify(payload, null, 2);
|
|
4575
|
+
const defaultLimit = mode === "preview" ? 4000 : COMPACT_DEFAULT_RESOURCE_THRESHOLD_BYTES;
|
|
4576
|
+
const limit = Math.max(1, Math.min(options.limit ?? defaultLimit, COMPACT_DEFAULT_RESOURCE_THRESHOLD_BYTES));
|
|
4577
|
+
const offset = Math.max(0, Math.min(options.offset ?? 0, serialized.length));
|
|
4578
|
+
const text = serialized.slice(offset, offset + limit);
|
|
4579
|
+
const nextOffset = offset + text.length < serialized.length ? offset + text.length : undefined;
|
|
4580
|
+
return {
|
|
4581
|
+
ok: true,
|
|
4582
|
+
...summary,
|
|
4583
|
+
payloadAvailable: true,
|
|
4584
|
+
payloadIncluded: false,
|
|
4585
|
+
mode,
|
|
4586
|
+
format: "json-text",
|
|
4587
|
+
offset,
|
|
4588
|
+
limit,
|
|
4589
|
+
text,
|
|
4590
|
+
textBytes: Buffer.byteLength(text, "utf8"),
|
|
4591
|
+
totalCharacters: serialized.length,
|
|
4592
|
+
totalBytes: Buffer.byteLength(serialized, "utf8"),
|
|
4593
|
+
...(nextOffset !== undefined ? { nextOffset } : {})
|
|
4594
|
+
};
|
|
4595
|
+
}
|
|
4596
|
+
const overBudget = (options.maxPayloadBytes !== undefined && resource.payloadBytes > options.maxPayloadBytes) ||
|
|
4597
|
+
(options.maxEstimatedTokens !== undefined && resource.estimatedTokens > options.maxEstimatedTokens);
|
|
4598
|
+
if (overBudget) {
|
|
4599
|
+
return {
|
|
4600
|
+
ok: true,
|
|
4601
|
+
...summary,
|
|
4602
|
+
payloadAvailable: true,
|
|
4603
|
+
payloadIncluded: false,
|
|
4604
|
+
budgetExceeded: true,
|
|
4605
|
+
warnings: [
|
|
4606
|
+
{
|
|
4607
|
+
code: "COMPACT_RESOURCE_BUDGET_EXCEEDED",
|
|
4608
|
+
message: "Stored detail exceeded the requested response budget. Raise the budget or inspect a smaller resource."
|
|
4609
|
+
}
|
|
4610
|
+
]
|
|
4611
|
+
};
|
|
4612
|
+
}
|
|
4613
|
+
return { ok: true, ...summary, payloadAvailable: true, payloadIncluded: true, payload };
|
|
4120
4614
|
}
|
|
4121
|
-
function
|
|
4122
|
-
const
|
|
4123
|
-
if (
|
|
4124
|
-
|
|
4615
|
+
function compactResourceReadOptions(options) {
|
|
4616
|
+
const normalized = {};
|
|
4617
|
+
if (options.mode !== undefined) {
|
|
4618
|
+
normalized.mode = options.mode;
|
|
4125
4619
|
}
|
|
4126
|
-
if (
|
|
4127
|
-
|
|
4620
|
+
if (options.includePayload !== undefined) {
|
|
4621
|
+
normalized.includePayload = options.includePayload;
|
|
4128
4622
|
}
|
|
4129
|
-
if (
|
|
4130
|
-
|
|
4623
|
+
if (options.maxPayloadBytes !== undefined) {
|
|
4624
|
+
normalized.maxPayloadBytes = options.maxPayloadBytes;
|
|
4131
4625
|
}
|
|
4132
|
-
if (
|
|
4133
|
-
|
|
4626
|
+
if (options.maxEstimatedTokens !== undefined) {
|
|
4627
|
+
normalized.maxEstimatedTokens = options.maxEstimatedTokens;
|
|
4134
4628
|
}
|
|
4135
|
-
|
|
4629
|
+
if (options.offset !== undefined) {
|
|
4630
|
+
normalized.offset = options.offset;
|
|
4631
|
+
}
|
|
4632
|
+
if (options.limit !== undefined) {
|
|
4633
|
+
normalized.limit = options.limit;
|
|
4634
|
+
}
|
|
4635
|
+
return normalized;
|
|
4136
4636
|
}
|
|
4137
|
-
function
|
|
4138
|
-
|
|
4637
|
+
function listCompactResources(kind, limit = COMPACT_RESOURCE_LIMIT) {
|
|
4638
|
+
const resources = [...compactResources.values()]
|
|
4639
|
+
.filter((resource) => kind === undefined || resource.kind === kind)
|
|
4640
|
+
.slice(-limit)
|
|
4641
|
+
.map(({ payload, ...summary }) => summary);
|
|
4642
|
+
return { ok: true, resources };
|
|
4139
4643
|
}
|
|
4140
|
-
function
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
regionName: z.string()
|
|
4144
|
-
};
|
|
4644
|
+
function deleteCompactResource(resourceId) {
|
|
4645
|
+
const normalized = compactResourceIdFromUri(resourceId);
|
|
4646
|
+
return { ok: compactResources.delete(normalized), resourceId: normalized };
|
|
4145
4647
|
}
|
|
4146
|
-
function
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4648
|
+
function clearCompactResources(kind) {
|
|
4649
|
+
let deleted = 0;
|
|
4650
|
+
for (const [resourceId, resource] of compactResources) {
|
|
4651
|
+
if (kind === undefined || resource.kind === kind) {
|
|
4652
|
+
compactResources.delete(resourceId);
|
|
4653
|
+
deleted += 1;
|
|
4654
|
+
}
|
|
4655
|
+
}
|
|
4656
|
+
return { ok: true, deleted };
|
|
4151
4657
|
}
|
|
4152
|
-
function
|
|
4153
|
-
return
|
|
4154
|
-
workbookId: z.string(),
|
|
4155
|
-
pivotTableName: z.string()
|
|
4156
|
-
};
|
|
4658
|
+
function compactResourceIdFromUri(value) {
|
|
4659
|
+
return value.startsWith("excel://compact/") ? value.slice("excel://compact/".length) : value;
|
|
4157
4660
|
}
|
|
4158
|
-
function
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4661
|
+
async function compactCacheValue(key, producer) {
|
|
4662
|
+
await invalidateCompactCacheForWorkbookEvents();
|
|
4663
|
+
const cached = compactCache.get(key);
|
|
4664
|
+
if (cached !== undefined) {
|
|
4665
|
+
return cached.value;
|
|
4666
|
+
}
|
|
4667
|
+
const value = await producer();
|
|
4668
|
+
compactCache.set(key, { key, createdAt: new Date().toISOString(), value });
|
|
4669
|
+
return value;
|
|
4163
4670
|
}
|
|
4164
|
-
function
|
|
4671
|
+
async function invalidateCompactCacheForWorkbookEvents() {
|
|
4672
|
+
try {
|
|
4673
|
+
const recent = await runtime.getRecentEvents(1);
|
|
4674
|
+
const event = recent.events?.[0];
|
|
4675
|
+
if (!event?.eventId || event.eventId === compactLastObservedEventId) {
|
|
4676
|
+
return;
|
|
4677
|
+
}
|
|
4678
|
+
compactLastObservedEventId = event.eventId;
|
|
4679
|
+
if (event.method !== "addin.heartbeat") {
|
|
4680
|
+
clearCompactCache(`event:${event.method ?? "unknown"}`);
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
catch {
|
|
4684
|
+
// Compact caching must never make read-only workbook discovery fail.
|
|
4685
|
+
}
|
|
4686
|
+
}
|
|
4687
|
+
function getCompactCacheStatus() {
|
|
4165
4688
|
return {
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
...(args.expectedDataFields !== undefined ? { expectedDataFields: args.expectedDataFields } : {}),
|
|
4172
|
-
...(args.expectedDataFieldSettings !== undefined ? { expectedDataFieldSettings: args.expectedDataFieldSettings } : {}),
|
|
4173
|
-
...(args.expectedLayout !== undefined ? { expectedLayout: args.expectedLayout } : {})
|
|
4689
|
+
ok: true,
|
|
4690
|
+
size: compactCache.size,
|
|
4691
|
+
invalidationCount: compactCacheInvalidationCount,
|
|
4692
|
+
lastInvalidatedAt: compactCacheLastInvalidatedAt,
|
|
4693
|
+
keys: [...compactCache.keys()]
|
|
4174
4694
|
};
|
|
4175
4695
|
}
|
|
4176
|
-
function
|
|
4177
|
-
const
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4696
|
+
function clearCompactCache(reason) {
|
|
4697
|
+
const cleared = compactCache.size;
|
|
4698
|
+
compactCache.clear();
|
|
4699
|
+
compactCacheInvalidationCount += 1;
|
|
4700
|
+
compactCacheLastInvalidatedAt = new Date().toISOString();
|
|
4701
|
+
return { ok: true, cleared, reason, invalidationCount: compactCacheInvalidationCount, invalidatedAt: compactCacheLastInvalidatedAt };
|
|
4702
|
+
}
|
|
4703
|
+
async function workbookSummary(workbookId) {
|
|
4704
|
+
return compactCacheValue(`workbookSummary:${workbookId ?? "active"}`, async () => {
|
|
4705
|
+
const result = await runtime.getWorkbookMap();
|
|
4706
|
+
const map = "map" in result ? result.map : undefined;
|
|
4707
|
+
if (!result.ok || !map) {
|
|
4708
|
+
return withCompactTelemetry({ ok: false, workbookId, source: result }, { detailLevel: "summary" });
|
|
4709
|
+
}
|
|
4710
|
+
const sheets = map.sheets ?? [];
|
|
4711
|
+
const tableNames = sheets.flatMap((sheet) => (sheet.tables ?? []).map((table) => table.name));
|
|
4712
|
+
const usedRangeCells = sheets.reduce((sum, sheet) => sum + usedRangeCellCount(sheet.usedRange), 0);
|
|
4713
|
+
return withCompactTelemetry({
|
|
4714
|
+
ok: true,
|
|
4715
|
+
workbook: map.workbook,
|
|
4716
|
+
workbookId: workbookId ?? map.workbook?.workbookId,
|
|
4717
|
+
sheetCount: sheets.length,
|
|
4718
|
+
tableCount: tableNames.length,
|
|
4719
|
+
usedRangeCells,
|
|
4720
|
+
sheets: sheets.map((sheet) => ({
|
|
4721
|
+
name: sheet.name,
|
|
4722
|
+
position: sheet.position,
|
|
4723
|
+
visibility: sheet.visibility,
|
|
4724
|
+
usedRange: sheet.usedRange,
|
|
4725
|
+
tableCount: sheet.tables?.length ?? 0,
|
|
4726
|
+
tables: (sheet.tables ?? []).map((table) => table.name)
|
|
4727
|
+
}))
|
|
4728
|
+
}, { detailLevel: "summary" });
|
|
4729
|
+
});
|
|
4730
|
+
}
|
|
4731
|
+
async function workbookUsedRangeSummary(workbookId) {
|
|
4732
|
+
return compactCacheValue(`workbookUsedRangeSummary:${workbookId ?? "active"}`, async () => {
|
|
4733
|
+
const result = await runtime.getWorkbookMap();
|
|
4734
|
+
const map = "map" in result ? result.map : undefined;
|
|
4735
|
+
if (!result.ok || !map) {
|
|
4736
|
+
return withCompactTelemetry({ ok: false, workbookId, source: result }, { detailLevel: "summary" });
|
|
4737
|
+
}
|
|
4738
|
+
const sheets = map.sheets ?? [];
|
|
4739
|
+
return withCompactTelemetry({
|
|
4740
|
+
ok: true,
|
|
4741
|
+
workbookId: workbookId ?? map.workbook?.workbookId,
|
|
4742
|
+
usedRanges: sheets.map((sheet) => ({
|
|
4743
|
+
sheetName: sheet.name,
|
|
4744
|
+
usedRange: sheet.usedRange,
|
|
4745
|
+
cellCount: usedRangeCellCount(sheet.usedRange)
|
|
4746
|
+
})),
|
|
4747
|
+
totalCells: sheets.reduce((sum, sheet) => sum + usedRangeCellCount(sheet.usedRange), 0)
|
|
4748
|
+
}, { detailLevel: "summary" });
|
|
4749
|
+
});
|
|
4750
|
+
}
|
|
4751
|
+
async function sheetSummary(sheetName, workbookId) {
|
|
4752
|
+
return compactCacheValue(`sheetSummary:${workbookId ?? "active"}:${sheetName}`, async () => {
|
|
4753
|
+
const info = await selectSheetInfo(sheetName);
|
|
4754
|
+
const workbook = info.result.map?.workbook;
|
|
4755
|
+
return withCompactTelemetry({
|
|
4756
|
+
ok: info.ok,
|
|
4757
|
+
workbookId: workbookId ?? workbook?.workbookId,
|
|
4758
|
+
sheetName,
|
|
4759
|
+
usedRange: info.sheet?.usedRange,
|
|
4760
|
+
cellCount: usedRangeCellCount(info.sheet?.usedRange),
|
|
4761
|
+
tableCount: info.sheet?.tables?.length ?? 0,
|
|
4762
|
+
tables: (info.sheet?.tables ?? []).map((table) => table.name),
|
|
4763
|
+
sheet: info.sheet
|
|
4764
|
+
}, { detailLevel: "summary" });
|
|
4765
|
+
});
|
|
4766
|
+
}
|
|
4767
|
+
function rangeSummary(workbookId, sheetName, address) {
|
|
4768
|
+
const parsed = parseCompactA1Address(address);
|
|
4769
|
+
const rowCount = parsed.endRow - parsed.startRow + 1;
|
|
4770
|
+
const columnCount = parsed.endColumn - parsed.startColumn + 1;
|
|
4771
|
+
const defaultRows = Math.min(rowCount, 50);
|
|
4772
|
+
const defaultColumns = Math.min(columnCount, 25);
|
|
4773
|
+
return withCompactTelemetry({
|
|
4774
|
+
ok: true,
|
|
4775
|
+
workbookId,
|
|
4776
|
+
sheetName,
|
|
4777
|
+
address,
|
|
4778
|
+
rowCount,
|
|
4779
|
+
columnCount,
|
|
4780
|
+
cellCount: rowCount * columnCount,
|
|
4781
|
+
defaultCompactWindow: {
|
|
4782
|
+
address: compactWindowAddress(address, 0, 0, defaultRows, defaultColumns),
|
|
4783
|
+
rowCount: defaultRows,
|
|
4784
|
+
columnCount: defaultColumns,
|
|
4785
|
+
cellCount: defaultRows * defaultColumns
|
|
4786
|
+
}
|
|
4787
|
+
}, { detailLevel: "summary", truncated: rowCount > defaultRows || columnCount > defaultColumns });
|
|
4788
|
+
}
|
|
4789
|
+
async function compactRangeRead(args) {
|
|
4790
|
+
const mode = args.mode ?? "window";
|
|
4791
|
+
const parsed = parseCompactA1Address(args.address);
|
|
4792
|
+
const sourceRowCount = parsed.endRow - parsed.startRow + 1;
|
|
4793
|
+
const sourceColumnCount = parsed.endColumn - parsed.startColumn + 1;
|
|
4794
|
+
const rowOffset = Math.min(args.rowOffset ?? 0, sourceRowCount);
|
|
4795
|
+
const columnOffset = Math.min(args.columnOffset ?? 0, sourceColumnCount);
|
|
4796
|
+
const maxRows = args.maxRows ?? 50;
|
|
4797
|
+
const maxColumns = args.maxColumns ?? 25;
|
|
4798
|
+
const availableRows = Math.max(0, sourceRowCount - rowOffset);
|
|
4799
|
+
const availableColumns = Math.max(0, sourceColumnCount - columnOffset);
|
|
4800
|
+
const columnLimit = Math.min(maxColumns, availableColumns);
|
|
4801
|
+
const maxRowsByCells = args.maxCells !== undefined && columnLimit > 0 ? Math.floor(args.maxCells / columnLimit) : maxRows;
|
|
4802
|
+
const rowLimit = Math.min(maxRows, maxRowsByCells, availableRows);
|
|
4803
|
+
const windowAddress = rowLimit > 0 && columnLimit > 0
|
|
4804
|
+
? compactWindowAddress(args.address, rowOffset, columnOffset, rowLimit, columnLimit)
|
|
4805
|
+
: compactWindowAddress(args.address, rowOffset, columnOffset, 1, 1);
|
|
4806
|
+
const truncated = rowOffset + rowLimit < sourceRowCount || columnOffset + columnLimit < sourceColumnCount;
|
|
4807
|
+
const summary = {
|
|
4808
|
+
workbookId: args.workbookId,
|
|
4809
|
+
sheetName: args.sheetName,
|
|
4810
|
+
address: args.address,
|
|
4811
|
+
mode,
|
|
4812
|
+
source: {
|
|
4813
|
+
rowCount: sourceRowCount,
|
|
4814
|
+
columnCount: sourceColumnCount,
|
|
4815
|
+
cellCount: sourceRowCount * sourceColumnCount
|
|
4816
|
+
},
|
|
4817
|
+
window: {
|
|
4818
|
+
address: windowAddress,
|
|
4819
|
+
rowOffset,
|
|
4820
|
+
columnOffset,
|
|
4821
|
+
rowCount: rowLimit,
|
|
4822
|
+
columnCount: columnLimit,
|
|
4823
|
+
cellCount: rowLimit * columnLimit
|
|
4824
|
+
},
|
|
4825
|
+
sampled: mode === "sample"
|
|
4826
|
+
};
|
|
4827
|
+
const nextPage = truncated ? { rowOffset: rowOffset + rowLimit, columnOffset } : undefined;
|
|
4828
|
+
if (mode === "summary" || rowLimit === 0 || columnLimit === 0) {
|
|
4829
|
+
return withCompactTelemetry({ ok: true, ...summary }, { detailLevel: "summary", truncated, nextPage, maxPayloadBytes: args.maxPayloadBytes, maxEstimatedTokens: args.maxEstimatedTokens, resourceKind: "read", resourceTitle: "Compact range summary", budgetSummary: { ok: true, ...summary } });
|
|
4830
|
+
}
|
|
4831
|
+
const facets = compactRangeFacets(args);
|
|
4832
|
+
if (facets.length === 0) {
|
|
4833
|
+
return withCompactTelemetry({ ok: true, ...summary }, { detailLevel: "compact", truncated, nextPage, maxPayloadBytes: args.maxPayloadBytes, maxEstimatedTokens: args.maxEstimatedTokens, resourceKind: "read", resourceTitle: "Compact range read", budgetSummary: { ok: true, ...summary } });
|
|
4834
|
+
}
|
|
4835
|
+
if (mode === "sample" && sourceRowCount > rowLimit) {
|
|
4836
|
+
const samples = await Promise.all(compactSampleWindows(sourceRowCount, rowLimit).map(async (sample) => {
|
|
4837
|
+
const sampleAddress = compactWindowAddress(args.address, sample.rowOffset, columnOffset, sample.rowCount, columnLimit);
|
|
4838
|
+
const sampleResult = await readRangeSnapshot(args.workbookId, args.sheetName, sampleAddress, facets);
|
|
4839
|
+
const sampleSnapshot = (sampleResult.data ?? [])[0]?.snapshot;
|
|
4840
|
+
const compactSnapshot = compactRangeSnapshotPayload(sampleSnapshot, facets);
|
|
4841
|
+
return {
|
|
4842
|
+
label: sample.label,
|
|
4843
|
+
rowOffset: sample.rowOffset,
|
|
4844
|
+
rowCount: sample.rowCount,
|
|
4845
|
+
address: sampleAddress,
|
|
4846
|
+
...compactSnapshot,
|
|
4847
|
+
ok: sampleResult.ok,
|
|
4848
|
+
warnings: sampleResult.warnings ?? []
|
|
4849
|
+
};
|
|
4850
|
+
}));
|
|
4851
|
+
return withCompactTelemetry({ ok: true, ...summary, samples }, {
|
|
4852
|
+
detailLevel: "compact",
|
|
4853
|
+
truncated,
|
|
4854
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
4855
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
4856
|
+
resourceKind: "read",
|
|
4857
|
+
resourceTitle: "Compact range sample",
|
|
4858
|
+
budgetSummary: { ok: true, ...summary, sampleCount: samples.length }
|
|
4859
|
+
});
|
|
4860
|
+
}
|
|
4861
|
+
const result = await readRangeSnapshot(args.workbookId, args.sheetName, windowAddress, facets);
|
|
4862
|
+
if (!result.ok) {
|
|
4863
|
+
return withCompactTelemetry({ ok: false, ...summary, source: result }, { detailLevel: "compact", truncated, nextPage, maxPayloadBytes: args.maxPayloadBytes, maxEstimatedTokens: args.maxEstimatedTokens, resourceKind: "read", resourceTitle: "Compact range read error", budgetSummary: { ok: false, ...summary } });
|
|
4864
|
+
}
|
|
4865
|
+
const snapshot = (result.data ?? [])[0]?.snapshot;
|
|
4866
|
+
const compactSnapshot = compactRangeSnapshotPayload(snapshot, facets);
|
|
4867
|
+
return withCompactTelemetry({
|
|
4868
|
+
ok: true,
|
|
4869
|
+
...summary,
|
|
4870
|
+
...compactSnapshot,
|
|
4871
|
+
warnings: result.warnings ?? [],
|
|
4872
|
+
telemetry: result.telemetry
|
|
4873
|
+
}, {
|
|
4874
|
+
detailLevel: "compact",
|
|
4875
|
+
truncated,
|
|
4876
|
+
nextPage,
|
|
4877
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
4878
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
4879
|
+
resourceKind: "read",
|
|
4880
|
+
resourceTitle: "Compact range read",
|
|
4881
|
+
budgetSummary: { ok: true, ...summary, warnings: result.warnings ?? [] }
|
|
4882
|
+
});
|
|
4883
|
+
}
|
|
4884
|
+
async function tableSchema(selector) {
|
|
4885
|
+
return compactCacheValue(`tableSchema:${selector.workbookId}:${selector.tableName}`, async () => {
|
|
4886
|
+
const result = await runtime.getTableInfo(selector);
|
|
4887
|
+
const info = result.info;
|
|
4888
|
+
return withCompactTelemetry({
|
|
4889
|
+
ok: Boolean(result.ok && info),
|
|
4890
|
+
workbookId: selector.workbookId,
|
|
4891
|
+
tableName: selector.tableName,
|
|
4892
|
+
schema: info ? tableInfoSchema(info) : undefined,
|
|
4893
|
+
source: info ? undefined : result
|
|
4894
|
+
}, { detailLevel: "summary" });
|
|
4895
|
+
});
|
|
4896
|
+
}
|
|
4897
|
+
function compactRangeSnapshotPayload(snapshot, facets) {
|
|
4898
|
+
if (!snapshot || typeof snapshot !== "object") {
|
|
4899
|
+
return {};
|
|
4900
|
+
}
|
|
4901
|
+
const matrixEntries = [
|
|
4902
|
+
["values", snapshot.values],
|
|
4903
|
+
["formulas", snapshot.formulas],
|
|
4904
|
+
["text", snapshot.text],
|
|
4905
|
+
["numberFormat", snapshot.numberFormat],
|
|
4906
|
+
["style", snapshot.style]
|
|
4907
|
+
];
|
|
4908
|
+
const requestedEntries = matrixEntries.filter(([name, value]) => compactFacetOutputRequested(name, facets) && Array.isArray(value));
|
|
4909
|
+
const bounds = compactMatrixBounds(requestedEntries.map(([, value]) => value));
|
|
4910
|
+
const output = {};
|
|
4911
|
+
if (snapshot.fingerprint !== undefined) {
|
|
4912
|
+
output.fingerprint = snapshot.fingerprint;
|
|
4913
|
+
}
|
|
4914
|
+
for (const [name, value] of requestedEntries) {
|
|
4915
|
+
output[name] = trimMatrixToBounds(value, bounds);
|
|
4916
|
+
}
|
|
4917
|
+
if (bounds.sourceRows > bounds.rows || bounds.sourceColumns > bounds.columns) {
|
|
4918
|
+
output.omittedEmpty = {
|
|
4919
|
+
trailingRows: Math.max(0, bounds.sourceRows - bounds.rows),
|
|
4920
|
+
trailingColumns: Math.max(0, bounds.sourceColumns - bounds.columns),
|
|
4921
|
+
sourceRows: bounds.sourceRows,
|
|
4922
|
+
sourceColumns: bounds.sourceColumns,
|
|
4923
|
+
returnedRows: bounds.rows,
|
|
4924
|
+
returnedColumns: bounds.columns
|
|
4925
|
+
};
|
|
4926
|
+
}
|
|
4927
|
+
return output;
|
|
4928
|
+
}
|
|
4929
|
+
function compactFacetOutputRequested(name, facets) {
|
|
4930
|
+
if (name === "numberFormat") {
|
|
4931
|
+
return facets.includes("numberFormat");
|
|
4932
|
+
}
|
|
4933
|
+
if (name === "style") {
|
|
4934
|
+
return facets.includes("style");
|
|
4935
|
+
}
|
|
4936
|
+
return facets.includes(name);
|
|
4937
|
+
}
|
|
4938
|
+
function compactMatrixBounds(matrices) {
|
|
4939
|
+
const sourceRows = matrices.reduce((max, matrix) => Math.max(max, matrix.length), 0);
|
|
4940
|
+
const sourceColumns = matrices.reduce((max, matrix) => Math.max(max, ...matrix.map((row) => Array.isArray(row) ? row.length : 0)), 0);
|
|
4941
|
+
let lastRow = -1;
|
|
4942
|
+
let lastColumn = -1;
|
|
4943
|
+
for (const matrix of matrices) {
|
|
4944
|
+
for (let rowIndex = 0; rowIndex < matrix.length; rowIndex += 1) {
|
|
4945
|
+
const row = matrix[rowIndex];
|
|
4946
|
+
if (!Array.isArray(row)) {
|
|
4947
|
+
continue;
|
|
4948
|
+
}
|
|
4949
|
+
for (let columnIndex = 0; columnIndex < row.length; columnIndex += 1) {
|
|
4950
|
+
if (!isCompactEmptyCell(row[columnIndex])) {
|
|
4951
|
+
lastRow = Math.max(lastRow, rowIndex);
|
|
4952
|
+
lastColumn = Math.max(lastColumn, columnIndex);
|
|
4953
|
+
}
|
|
4954
|
+
}
|
|
4955
|
+
}
|
|
4956
|
+
}
|
|
4957
|
+
return {
|
|
4958
|
+
sourceRows,
|
|
4959
|
+
sourceColumns,
|
|
4960
|
+
rows: lastRow + 1,
|
|
4961
|
+
columns: lastColumn + 1
|
|
4962
|
+
};
|
|
4963
|
+
}
|
|
4964
|
+
function trimMatrixToBounds(matrix, bounds) {
|
|
4965
|
+
if (bounds.rows === 0 || bounds.columns === 0) {
|
|
4966
|
+
return [];
|
|
4967
|
+
}
|
|
4968
|
+
return matrix.slice(0, bounds.rows).map((row) => Array.isArray(row) ? row.slice(0, bounds.columns) : []);
|
|
4969
|
+
}
|
|
4970
|
+
function isCompactEmptyCell(value) {
|
|
4971
|
+
if (value === undefined || value === null || value === "") {
|
|
4972
|
+
return true;
|
|
4973
|
+
}
|
|
4974
|
+
if (Array.isArray(value)) {
|
|
4975
|
+
return value.every(isCompactEmptyCell);
|
|
4976
|
+
}
|
|
4977
|
+
if (typeof value === "object") {
|
|
4978
|
+
return Object.keys(value).length === 0;
|
|
4979
|
+
}
|
|
4980
|
+
return false;
|
|
4981
|
+
}
|
|
4982
|
+
function compactTablePayload(table) {
|
|
4983
|
+
if (!table || typeof table !== "object") {
|
|
4984
|
+
return {};
|
|
4985
|
+
}
|
|
4986
|
+
const matrixEntries = [
|
|
4987
|
+
["values", table.values],
|
|
4988
|
+
["formulas", table.formulas],
|
|
4989
|
+
["text", table.text],
|
|
4990
|
+
["numberFormat", table.numberFormat]
|
|
4991
|
+
];
|
|
4992
|
+
const requestedEntries = matrixEntries.filter(([, value]) => Array.isArray(value));
|
|
4993
|
+
const rowBounds = compactTableRowBounds(requestedEntries.map(([, value]) => value));
|
|
4994
|
+
const output = {};
|
|
4995
|
+
if (table.headers !== undefined) {
|
|
4996
|
+
output.headers = table.headers;
|
|
4997
|
+
}
|
|
4998
|
+
for (const [name, value] of requestedEntries) {
|
|
4999
|
+
output[name] = value.slice(0, rowBounds.rows);
|
|
5000
|
+
}
|
|
5001
|
+
if (rowBounds.sourceRows > rowBounds.rows) {
|
|
5002
|
+
output.omittedEmpty = {
|
|
5003
|
+
trailingRows: rowBounds.sourceRows - rowBounds.rows,
|
|
5004
|
+
sourceRows: rowBounds.sourceRows,
|
|
5005
|
+
returnedRows: rowBounds.rows
|
|
5006
|
+
};
|
|
5007
|
+
}
|
|
5008
|
+
return output;
|
|
5009
|
+
}
|
|
5010
|
+
function compactTableRowBounds(matrices) {
|
|
5011
|
+
const sourceRows = matrices.reduce((max, matrix) => Math.max(max, matrix.length), 0);
|
|
5012
|
+
let lastRow = -1;
|
|
5013
|
+
for (const matrix of matrices) {
|
|
5014
|
+
for (let rowIndex = 0; rowIndex < matrix.length; rowIndex += 1) {
|
|
5015
|
+
const row = matrix[rowIndex];
|
|
5016
|
+
if (Array.isArray(row) && row.some((cell) => !isCompactEmptyCell(cell))) {
|
|
5017
|
+
lastRow = Math.max(lastRow, rowIndex);
|
|
5018
|
+
}
|
|
5019
|
+
}
|
|
5020
|
+
}
|
|
5021
|
+
return { sourceRows, rows: lastRow + 1 };
|
|
5022
|
+
}
|
|
5023
|
+
async function compactTableRead(args) {
|
|
5024
|
+
const mode = args.mode ?? "window";
|
|
5025
|
+
const schemaResult = await runtime.getTableInfo(tableSelector(args));
|
|
5026
|
+
const info = schemaResult.info;
|
|
5027
|
+
if (!schemaResult.ok || !info) {
|
|
5028
|
+
return withCompactTelemetry({ ok: false, workbookId: args.workbookId, tableName: args.tableName, source: schemaResult }, { detailLevel: "summary" });
|
|
5029
|
+
}
|
|
5030
|
+
const selectedColumns = compactTableColumns(info, args.columns, args.maxColumns);
|
|
5031
|
+
const rowOffset = Math.min(args.rowOffset ?? 0, info.rowCount);
|
|
5032
|
+
const maxRows = args.maxRows ?? 50;
|
|
5033
|
+
const maxRowsByCells = args.maxCells !== undefined && selectedColumns.length > 0 ? Math.floor(args.maxCells / selectedColumns.length) : maxRows;
|
|
5034
|
+
const rowLimit = Math.min(maxRows, maxRowsByCells, Math.max(0, info.rowCount - rowOffset));
|
|
5035
|
+
const truncated = rowOffset + rowLimit < info.rowCount || selectedColumns.length < info.columnCount;
|
|
5036
|
+
const summary = {
|
|
5037
|
+
workbookId: args.workbookId,
|
|
5038
|
+
tableName: args.tableName,
|
|
5039
|
+
mode,
|
|
5040
|
+
schema: tableInfoSchema(info),
|
|
5041
|
+
rowOffset,
|
|
5042
|
+
rowLimit,
|
|
5043
|
+
projectedColumns: selectedColumns,
|
|
5044
|
+
sampled: mode === "sample"
|
|
5045
|
+
};
|
|
5046
|
+
const nextPage = truncated && rowOffset + rowLimit < info.rowCount ? { rowOffset: rowOffset + rowLimit } : undefined;
|
|
5047
|
+
if (mode === "summary" || rowLimit === 0 || selectedColumns.length === 0) {
|
|
5048
|
+
return withCompactTelemetry({ ok: true, ...summary }, { detailLevel: "summary", truncated, nextPage, maxPayloadBytes: args.maxPayloadBytes, maxEstimatedTokens: args.maxEstimatedTokens, resourceKind: "read", resourceTitle: "Compact table summary", budgetSummary: { ok: true, ...summary } });
|
|
5049
|
+
}
|
|
5050
|
+
if (mode === "sample" && info.rowCount > rowLimit) {
|
|
5051
|
+
const samples = await Promise.all(compactSampleWindows(info.rowCount, rowLimit).map(async (sample) => {
|
|
5052
|
+
const sampleResult = await runtime.readTable({
|
|
5053
|
+
...tableSelector(args),
|
|
5054
|
+
includeValues: args.includeValues ?? true,
|
|
5055
|
+
includeFormulas: args.includeFormulas === true,
|
|
5056
|
+
includeText: args.includeText === true,
|
|
5057
|
+
includeNumberFormats: args.includeNumberFormats === true,
|
|
5058
|
+
columns: selectedColumns.map((column) => column.name),
|
|
5059
|
+
rowOffset: sample.rowOffset,
|
|
5060
|
+
rowLimit: sample.rowCount
|
|
5061
|
+
});
|
|
5062
|
+
const sampleTable = sampleResult.table;
|
|
5063
|
+
const compactTable = compactTablePayload(sampleTable);
|
|
5064
|
+
return {
|
|
5065
|
+
label: sample.label,
|
|
5066
|
+
rowOffset: sample.rowOffset,
|
|
5067
|
+
rowCount: sample.rowCount,
|
|
5068
|
+
ok: sampleResult.ok,
|
|
5069
|
+
...compactTable
|
|
5070
|
+
};
|
|
5071
|
+
}));
|
|
5072
|
+
return withCompactTelemetry({ ok: true, ...summary, samples }, {
|
|
5073
|
+
detailLevel: "compact",
|
|
5074
|
+
truncated,
|
|
5075
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
5076
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
5077
|
+
resourceKind: "read",
|
|
5078
|
+
resourceTitle: "Compact table sample",
|
|
5079
|
+
budgetSummary: { ok: true, ...summary, sampleCount: samples.length }
|
|
5080
|
+
});
|
|
5081
|
+
}
|
|
5082
|
+
const result = await runtime.readTable({
|
|
5083
|
+
...tableSelector(args),
|
|
5084
|
+
includeValues: args.includeValues ?? true,
|
|
5085
|
+
includeFormulas: args.includeFormulas === true,
|
|
5086
|
+
includeText: args.includeText === true,
|
|
5087
|
+
includeNumberFormats: args.includeNumberFormats === true,
|
|
5088
|
+
columns: selectedColumns.map((column) => column.name),
|
|
5089
|
+
rowOffset,
|
|
5090
|
+
rowLimit
|
|
5091
|
+
});
|
|
5092
|
+
const table = result.table;
|
|
5093
|
+
const compactTable = compactTablePayload(table);
|
|
5094
|
+
return withCompactTelemetry({
|
|
5095
|
+
ok: Boolean(result.ok),
|
|
5096
|
+
...summary,
|
|
5097
|
+
...compactTable,
|
|
5098
|
+
source: result.ok ? undefined : result
|
|
5099
|
+
}, {
|
|
5100
|
+
detailLevel: "compact",
|
|
5101
|
+
truncated,
|
|
5102
|
+
nextPage,
|
|
5103
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
5104
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
5105
|
+
resourceKind: "read",
|
|
5106
|
+
resourceTitle: "Compact table read",
|
|
5107
|
+
budgetSummary: { ok: Boolean(result.ok), ...summary }
|
|
5108
|
+
});
|
|
5109
|
+
}
|
|
5110
|
+
async function lookupSearchWorkbook(args) {
|
|
5111
|
+
const workbookId = args.workbookId;
|
|
5112
|
+
const context = await lookupWorkbookContext(workbookId, args.sheetNames);
|
|
5113
|
+
if (!context.ok) {
|
|
5114
|
+
return withCompactTelemetry({ ok: false, workbookId, query: args.query, source: context.source }, { detailLevel: "summary" });
|
|
5115
|
+
}
|
|
5116
|
+
const matches = [];
|
|
5117
|
+
if (args.includeSheets !== false) {
|
|
5118
|
+
for (const sheet of context.sheets) {
|
|
5119
|
+
if (lookupMatches(String(sheet.name), args.query, args)) {
|
|
5120
|
+
matches.push(lookupMatch({
|
|
5121
|
+
kind: "sheet",
|
|
5122
|
+
sheetName: sheet.name,
|
|
5123
|
+
address: sheet.usedRange?.address,
|
|
5124
|
+
score: lookupScore(sheet.name, args.query, args.completeMatch),
|
|
5125
|
+
reason: "sheet name"
|
|
5126
|
+
}));
|
|
5127
|
+
}
|
|
5128
|
+
}
|
|
5129
|
+
}
|
|
5130
|
+
if (args.includeTables !== false) {
|
|
5131
|
+
matches.push(...lookupTableMatches(context.tables, args.query, args));
|
|
5132
|
+
}
|
|
5133
|
+
matches.push(...await lookupUsedRangeMatches(workbookId, context.sheets, args.query, args, "range"));
|
|
5134
|
+
return compactLookupResponse({
|
|
5135
|
+
workbookId,
|
|
5136
|
+
query: args.query,
|
|
5137
|
+
matches,
|
|
5138
|
+
maxMatches: args.maxMatches ?? 25,
|
|
5139
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
5140
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
5141
|
+
resourceTitle: `Workbook lookup: ${args.query}`
|
|
5142
|
+
});
|
|
5143
|
+
}
|
|
5144
|
+
async function lookupFindHeaders(args) {
|
|
5145
|
+
const workbookId = args.workbookId;
|
|
5146
|
+
const terms = lookupTerms(args.headers ?? (args.query ? [args.query] : []));
|
|
5147
|
+
const context = await lookupWorkbookContext(workbookId, args.sheetNames);
|
|
5148
|
+
if (!context.ok) {
|
|
5149
|
+
return withCompactTelemetry({ ok: false, workbookId, query: args.query, headers: args.headers, source: context.source }, { detailLevel: "summary" });
|
|
5150
|
+
}
|
|
5151
|
+
const matches = [];
|
|
5152
|
+
for (const table of context.tables) {
|
|
5153
|
+
for (const column of table.columns ?? []) {
|
|
5154
|
+
const columnName = String(column.name ?? "");
|
|
5155
|
+
if (terms.length === 0 || terms.some((term) => lookupMatches(columnName, term, args))) {
|
|
5156
|
+
matches.push(lookupMatch({
|
|
5157
|
+
kind: "header",
|
|
5158
|
+
sheetName: table.sheetName,
|
|
5159
|
+
tableName: table.tableName,
|
|
5160
|
+
columnName,
|
|
5161
|
+
address: table.headerAddress ?? table.address,
|
|
5162
|
+
score: terms.length === 0 ? 0.55 : Math.max(...terms.map((term) => lookupScore(columnName, term, false))),
|
|
5163
|
+
reason: "table header",
|
|
5164
|
+
schema: { tableName: table.tableName, column }
|
|
5165
|
+
}));
|
|
5166
|
+
}
|
|
5167
|
+
}
|
|
5168
|
+
}
|
|
5169
|
+
const maxRowsPerSheet = args.maxRowsPerSheet ?? 10;
|
|
5170
|
+
const maxColumns = args.maxColumns ?? 50;
|
|
5171
|
+
for (const sheet of context.sheets) {
|
|
5172
|
+
const usedAddress = sheet.usedRange?.address;
|
|
5173
|
+
if (!usedAddress) {
|
|
5174
|
+
continue;
|
|
5175
|
+
}
|
|
5176
|
+
try {
|
|
5177
|
+
const result = await compactRangeRead({
|
|
5178
|
+
workbookId,
|
|
5179
|
+
sheetName: sheet.name,
|
|
5180
|
+
address: usedAddress,
|
|
5181
|
+
mode: "window",
|
|
5182
|
+
maxRows: maxRowsPerSheet,
|
|
5183
|
+
maxColumns,
|
|
5184
|
+
includeValues: true,
|
|
5185
|
+
includeText: true
|
|
5186
|
+
});
|
|
5187
|
+
const rows = lookupRowsFromCompactRead(result);
|
|
5188
|
+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex += 1) {
|
|
5189
|
+
for (let columnIndex = 0; columnIndex < (rows[rowIndex] ?? []).length; columnIndex += 1) {
|
|
5190
|
+
const value = rows[rowIndex]?.[columnIndex];
|
|
5191
|
+
const text = value === undefined || value === null ? "" : String(value);
|
|
5192
|
+
if (text === "" || (terms.length > 0 && !terms.some((term) => lookupMatches(text, term, args)))) {
|
|
5193
|
+
continue;
|
|
5194
|
+
}
|
|
5195
|
+
matches.push(lookupMatch({
|
|
5196
|
+
kind: "header",
|
|
5197
|
+
sheetName: sheet.name,
|
|
5198
|
+
address: compactWindowAddress(usedAddress, rowIndex, columnIndex, 1, 1),
|
|
5199
|
+
columnName: text,
|
|
5200
|
+
score: terms.length === 0 ? Math.max(0.15, 0.5 - rowIndex * 0.03) : Math.max(...terms.map((term) => lookupScore(text, term, false))),
|
|
5201
|
+
reason: `bounded sheet header scan row ${rowIndex + 1}`
|
|
5202
|
+
}));
|
|
5203
|
+
}
|
|
5204
|
+
}
|
|
5205
|
+
}
|
|
5206
|
+
catch {
|
|
5207
|
+
// Lookup should remain useful even when one sheet cannot be scanned.
|
|
5208
|
+
}
|
|
5209
|
+
}
|
|
5210
|
+
return compactLookupResponse({
|
|
5211
|
+
workbookId,
|
|
5212
|
+
query: args.query,
|
|
5213
|
+
headers: args.headers,
|
|
5214
|
+
matches,
|
|
5215
|
+
maxMatches: args.maxMatches ?? 25,
|
|
5216
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
5217
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
5218
|
+
resourceTitle: "Header lookup"
|
|
5219
|
+
});
|
|
5220
|
+
}
|
|
5221
|
+
async function lookupFindTablesByColumns(args) {
|
|
5222
|
+
const workbookId = args.workbookId;
|
|
5223
|
+
const context = await lookupWorkbookContext(workbookId);
|
|
5224
|
+
if (!context.ok) {
|
|
5225
|
+
return withCompactTelemetry({ ok: false, workbookId, requiredColumns: args.requiredColumns, source: context.source }, { detailLevel: "summary" });
|
|
5226
|
+
}
|
|
5227
|
+
const required = lookupTerms(args.requiredColumns);
|
|
5228
|
+
const optional = lookupTerms(args.optionalColumns ?? []);
|
|
5229
|
+
const matches = context.tables
|
|
5230
|
+
.map((table) => {
|
|
5231
|
+
const columns = (table.columns ?? []).map((column) => String(column.name ?? ""));
|
|
5232
|
+
const requiredHits = required.filter((term) => columns.some((column) => lookupNormalized(column) === term || lookupNormalized(column).includes(term)));
|
|
5233
|
+
if (required.length > 0 && requiredHits.length < required.length) {
|
|
5234
|
+
return undefined;
|
|
5235
|
+
}
|
|
5236
|
+
const optionalHits = optional.filter((term) => columns.some((column) => lookupNormalized(column) === term || lookupNormalized(column).includes(term)));
|
|
5237
|
+
const denominator = Math.max(1, required.length + optional.length * 0.5);
|
|
5238
|
+
const score = Math.min(1, (requiredHits.length + optionalHits.length * 0.5) / denominator);
|
|
5239
|
+
if (score < (args.minScore ?? 0)) {
|
|
5240
|
+
return undefined;
|
|
5241
|
+
}
|
|
5242
|
+
return lookupMatch({
|
|
5243
|
+
kind: "table",
|
|
5244
|
+
sheetName: table.sheetName,
|
|
5245
|
+
tableName: table.tableName,
|
|
5246
|
+
address: table.address,
|
|
5247
|
+
score,
|
|
5248
|
+
reason: `matched ${requiredHits.length}/${required.length} required and ${optionalHits.length}/${optional.length} optional columns`,
|
|
5249
|
+
schema: tableInfoSchema(table)
|
|
5250
|
+
});
|
|
5251
|
+
})
|
|
5252
|
+
.filter((match) => match !== undefined);
|
|
5253
|
+
return compactLookupResponse({
|
|
5254
|
+
workbookId,
|
|
5255
|
+
requiredColumns: args.requiredColumns,
|
|
5256
|
+
optionalColumns: args.optionalColumns,
|
|
5257
|
+
matches,
|
|
5258
|
+
maxMatches: args.maxMatches ?? 25,
|
|
5259
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
5260
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
5261
|
+
resourceTitle: "Table column lookup"
|
|
5262
|
+
});
|
|
5263
|
+
}
|
|
5264
|
+
async function lookupFindEntity(args) {
|
|
5265
|
+
const workbookId = args.workbookId;
|
|
5266
|
+
const context = await lookupWorkbookContext(workbookId, args.sheetNames);
|
|
5267
|
+
if (!context.ok) {
|
|
5268
|
+
return withCompactTelemetry({ ok: false, workbookId, entity: args.entity, source: context.source }, { detailLevel: "summary" });
|
|
5269
|
+
}
|
|
5270
|
+
const matches = await lookupUsedRangeMatches(workbookId, context.sheets, args.entity, args, "entity");
|
|
5271
|
+
return compactLookupResponse({
|
|
5272
|
+
workbookId,
|
|
5273
|
+
entity: args.entity,
|
|
5274
|
+
entityKind: args.kind ?? "any",
|
|
5275
|
+
matches,
|
|
5276
|
+
maxMatches: args.maxMatches ?? 25,
|
|
5277
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
5278
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
5279
|
+
resourceTitle: `Entity lookup: ${args.entity}`
|
|
5280
|
+
});
|
|
5281
|
+
}
|
|
5282
|
+
async function lookupResolveRange(args) {
|
|
5283
|
+
const workbookId = args.workbookId;
|
|
5284
|
+
const kind = args.kind ?? "any";
|
|
5285
|
+
const context = await lookupWorkbookContext(workbookId, args.preferredSheetName ? [args.preferredSheetName] : undefined);
|
|
5286
|
+
if (!context.ok) {
|
|
5287
|
+
return withCompactTelemetry({ ok: false, workbookId, target: args.target, source: context.source }, { detailLevel: "summary" });
|
|
5288
|
+
}
|
|
5289
|
+
const matches = [];
|
|
5290
|
+
const parsedRange = lookupParseRangeTarget(args.target, args.preferredSheetName);
|
|
5291
|
+
if ((kind === "any" || kind === "range") && parsedRange) {
|
|
5292
|
+
matches.push(lookupMatch({
|
|
5293
|
+
kind: "range",
|
|
5294
|
+
sheetName: parsedRange.sheetName,
|
|
5295
|
+
address: parsedRange.address,
|
|
5296
|
+
score: 1,
|
|
5297
|
+
reason: "explicit A1 range"
|
|
5298
|
+
}));
|
|
5299
|
+
}
|
|
5300
|
+
if (kind === "any" || kind === "table" || kind === "column") {
|
|
5301
|
+
for (const table of context.tables) {
|
|
5302
|
+
if (args.preferredTableName && table.tableName !== args.preferredTableName) {
|
|
5303
|
+
continue;
|
|
5304
|
+
}
|
|
5305
|
+
if ((kind === "any" || kind === "table") && lookupMatches(String(table.tableName), args.target, args)) {
|
|
5306
|
+
matches.push(lookupMatch({
|
|
5307
|
+
kind: "table",
|
|
5308
|
+
sheetName: table.sheetName,
|
|
5309
|
+
tableName: table.tableName,
|
|
5310
|
+
address: table.address,
|
|
5311
|
+
score: lookupScore(table.tableName, args.target, false),
|
|
5312
|
+
reason: "table name",
|
|
5313
|
+
schema: tableInfoSchema(table)
|
|
5314
|
+
}));
|
|
5315
|
+
}
|
|
5316
|
+
if (kind === "any" || kind === "column") {
|
|
5317
|
+
for (const column of table.columns ?? []) {
|
|
5318
|
+
if (lookupMatches(String(column.name), args.target, args)) {
|
|
5319
|
+
matches.push(lookupMatch({
|
|
5320
|
+
kind: "column",
|
|
5321
|
+
sheetName: table.sheetName,
|
|
5322
|
+
tableName: table.tableName,
|
|
5323
|
+
columnName: column.name,
|
|
5324
|
+
address: table.address,
|
|
5325
|
+
score: lookupScore(column.name, args.target, false),
|
|
5326
|
+
reason: "table column",
|
|
5327
|
+
schema: { tableName: table.tableName, column }
|
|
5328
|
+
}));
|
|
5329
|
+
}
|
|
5330
|
+
}
|
|
5331
|
+
}
|
|
5332
|
+
}
|
|
5333
|
+
}
|
|
5334
|
+
if (kind === "any" || kind === "header") {
|
|
5335
|
+
const headerRequest = {
|
|
5336
|
+
workbookId,
|
|
5337
|
+
query: args.target,
|
|
5338
|
+
maxRowsPerSheet: 10
|
|
5339
|
+
};
|
|
5340
|
+
if (args.preferredSheetName !== undefined) {
|
|
5341
|
+
headerRequest.sheetNames = [args.preferredSheetName];
|
|
5342
|
+
}
|
|
5343
|
+
if (args.maxMatches !== undefined) {
|
|
5344
|
+
headerRequest.maxMatches = args.maxMatches;
|
|
5345
|
+
}
|
|
5346
|
+
const headerResult = await lookupFindHeaders(headerRequest);
|
|
5347
|
+
matches.push(...(headerResult.matches ?? []));
|
|
5348
|
+
}
|
|
5349
|
+
if (kind === "any" || kind === "entity") {
|
|
5350
|
+
matches.push(...await lookupUsedRangeMatches(workbookId, context.sheets, args.target, args, "entity"));
|
|
5351
|
+
}
|
|
5352
|
+
return compactLookupResponse({
|
|
5353
|
+
workbookId,
|
|
5354
|
+
target: args.target,
|
|
5355
|
+
targetKind: kind,
|
|
5356
|
+
matches,
|
|
5357
|
+
maxMatches: args.maxMatches ?? 10,
|
|
5358
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
5359
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
5360
|
+
resourceTitle: `Range resolution: ${args.target}`
|
|
5361
|
+
});
|
|
5362
|
+
}
|
|
5363
|
+
async function lookupInspectMatch(args) {
|
|
5364
|
+
const decoded = args.matchId ? lookupDecodeMatchId(args.matchId) : undefined;
|
|
5365
|
+
const match = { ...decoded, ...args };
|
|
5366
|
+
const workbookId = args.workbookId;
|
|
5367
|
+
if (match.tableName) {
|
|
5368
|
+
const request = {
|
|
5369
|
+
workbookId,
|
|
5370
|
+
tableName: match.tableName,
|
|
5371
|
+
mode: "window",
|
|
5372
|
+
maxRows: args.maxRows ?? 10,
|
|
5373
|
+
maxColumns: args.maxColumns ?? 25,
|
|
5374
|
+
includeValues: args.includeValues ?? true,
|
|
5375
|
+
includeFormulas: args.includeFormulas === true,
|
|
5376
|
+
includeText: args.includeText === true
|
|
5377
|
+
};
|
|
5378
|
+
if (match.columnName !== undefined) {
|
|
5379
|
+
request.columns = [match.columnName];
|
|
5380
|
+
}
|
|
5381
|
+
if (args.maxPayloadBytes !== undefined) {
|
|
5382
|
+
request.maxPayloadBytes = args.maxPayloadBytes;
|
|
5383
|
+
}
|
|
5384
|
+
if (args.maxEstimatedTokens !== undefined) {
|
|
5385
|
+
request.maxEstimatedTokens = args.maxEstimatedTokens;
|
|
5386
|
+
}
|
|
5387
|
+
return compactTableRead(request);
|
|
5388
|
+
}
|
|
5389
|
+
let sheetName = match.sheetName;
|
|
5390
|
+
let address = match.address;
|
|
5391
|
+
if (sheetName && !address) {
|
|
5392
|
+
const context = await lookupWorkbookContext(workbookId, [sheetName]);
|
|
5393
|
+
address = context.sheets[0]?.usedRange?.address;
|
|
5394
|
+
}
|
|
5395
|
+
if (sheetName && address) {
|
|
5396
|
+
const request = {
|
|
5397
|
+
workbookId,
|
|
5398
|
+
sheetName,
|
|
5399
|
+
address,
|
|
5400
|
+
mode: "window",
|
|
5401
|
+
maxRows: args.maxRows ?? 10,
|
|
5402
|
+
maxColumns: args.maxColumns ?? 25,
|
|
5403
|
+
includeValues: args.includeValues ?? true,
|
|
5404
|
+
includeFormulas: args.includeFormulas === true,
|
|
5405
|
+
includeText: args.includeText === true
|
|
5406
|
+
};
|
|
5407
|
+
if (args.maxPayloadBytes !== undefined) {
|
|
5408
|
+
request.maxPayloadBytes = args.maxPayloadBytes;
|
|
5409
|
+
}
|
|
5410
|
+
if (args.maxEstimatedTokens !== undefined) {
|
|
5411
|
+
request.maxEstimatedTokens = args.maxEstimatedTokens;
|
|
5412
|
+
}
|
|
5413
|
+
return compactRangeRead(request);
|
|
5414
|
+
}
|
|
5415
|
+
return withCompactTelemetry({
|
|
5416
|
+
ok: false,
|
|
5417
|
+
workbookId,
|
|
5418
|
+
error: {
|
|
5419
|
+
code: "LOOKUP_MATCH_TARGET_REQUIRED",
|
|
5420
|
+
message: "Provide matchId from a lookup response or direct sheetName/address/tableName fields."
|
|
5421
|
+
}
|
|
5422
|
+
}, { detailLevel: "summary" });
|
|
5423
|
+
}
|
|
5424
|
+
async function compactValidation(args) {
|
|
5425
|
+
const workbookId = args.workbookId;
|
|
5426
|
+
const report = await runCompactValidator(args, workbookId);
|
|
5427
|
+
const issues = Array.isArray(report.issues) ? report.issues : [];
|
|
5428
|
+
const maxIssues = args.maxIssues ?? 5;
|
|
5429
|
+
const summary = {
|
|
5430
|
+
ok: report.ok,
|
|
5431
|
+
workbookId,
|
|
5432
|
+
validator: args.validator,
|
|
5433
|
+
scope: report.scope,
|
|
5434
|
+
issueCount: report.issueCount ?? issues.length,
|
|
5435
|
+
severityCounts: compactSeverityCounts(issues),
|
|
5436
|
+
categories: compactIssueCategories(issues),
|
|
5437
|
+
examples: issues.slice(0, maxIssues).map(compactIssueExample),
|
|
5438
|
+
examplesTruncated: issues.length > maxIssues
|
|
5439
|
+
};
|
|
5440
|
+
return withCompactTelemetry(summary, {
|
|
5441
|
+
detailLevel: "summary",
|
|
5442
|
+
truncated: issues.length > maxIssues,
|
|
5443
|
+
storeResource: true,
|
|
5444
|
+
resourceKind: "validation",
|
|
5445
|
+
resourceTitle: `Validation detail: ${args.validator}`,
|
|
5446
|
+
resourcePayload: report,
|
|
5447
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
5448
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
5449
|
+
budgetSummary: summary
|
|
5450
|
+
});
|
|
5451
|
+
}
|
|
5452
|
+
function compactSnapshot(snapshotId, maxPayloadBytes, maxEstimatedTokens) {
|
|
5453
|
+
const result = runtime.getSnapshot(snapshotId);
|
|
5454
|
+
const snapshot = result.snapshot;
|
|
5455
|
+
const summary = snapshot
|
|
5456
|
+
? {
|
|
5457
|
+
ok: true,
|
|
5458
|
+
snapshotId,
|
|
5459
|
+
workbookId: snapshot.workbookId,
|
|
5460
|
+
createdAt: snapshot.createdAt,
|
|
5461
|
+
reason: snapshot.reason,
|
|
5462
|
+
affectedRangeCount: snapshot.affectedRanges?.length ?? 0,
|
|
5463
|
+
affectedRanges: snapshot.affectedRanges,
|
|
5464
|
+
payloadRangeCount: snapshot.payload?.rangeSnapshots?.length ?? 0
|
|
5465
|
+
}
|
|
5466
|
+
: { ok: false, snapshotId, error: result.error };
|
|
5467
|
+
return withCompactTelemetry(summary, {
|
|
5468
|
+
detailLevel: "summary",
|
|
5469
|
+
truncated: false,
|
|
5470
|
+
storeResource: Boolean(snapshot),
|
|
5471
|
+
resourceKind: "snapshot",
|
|
5472
|
+
resourceTitle: `Snapshot detail: ${snapshotId}`,
|
|
5473
|
+
resourcePayload: result,
|
|
5474
|
+
maxPayloadBytes,
|
|
5475
|
+
maxEstimatedTokens,
|
|
5476
|
+
budgetSummary: summary
|
|
5477
|
+
});
|
|
5478
|
+
}
|
|
5479
|
+
function compactSnapshotCreationResult(result) {
|
|
5480
|
+
if (toolProfile === "full") {
|
|
5481
|
+
return result;
|
|
5482
|
+
}
|
|
5483
|
+
const snapshot = result?.snapshot;
|
|
5484
|
+
if (!snapshot) {
|
|
5485
|
+
return result;
|
|
5486
|
+
}
|
|
5487
|
+
const summary = snapshotSummary(snapshot);
|
|
5488
|
+
const stored = storeCompactResource("snapshot", result, `Snapshot detail: ${summary.snapshotId}`);
|
|
5489
|
+
return withCompactTelemetry({
|
|
5490
|
+
ok: result.ok,
|
|
5491
|
+
snapshot: summary
|
|
5492
|
+
}, {
|
|
5493
|
+
detailLevel: "summary",
|
|
5494
|
+
resourceUri: stored.uri,
|
|
5495
|
+
resourceKind: "snapshot",
|
|
5496
|
+
resourceTitle: `Snapshot detail: ${summary.snapshotId}`
|
|
5497
|
+
});
|
|
5498
|
+
}
|
|
5499
|
+
function snapshotSummary(snapshot) {
|
|
5500
|
+
return {
|
|
5501
|
+
snapshotId: snapshot.snapshotId,
|
|
5502
|
+
workbookId: snapshot.workbookId,
|
|
5503
|
+
createdAt: snapshot.createdAt,
|
|
5504
|
+
reason: snapshot.reason,
|
|
5505
|
+
affectedRangeCount: snapshot.affectedRanges?.length ?? 0,
|
|
5506
|
+
affectedRanges: snapshot.affectedRanges,
|
|
5507
|
+
payloadRangeCount: snapshot.payload?.rangeSnapshots?.length ?? snapshot.rangeSnapshots?.length ?? 0
|
|
5508
|
+
};
|
|
5509
|
+
}
|
|
5510
|
+
function compactSnapshotDiff(leftSnapshotId, rightSnapshotId, maxPayloadBytes, maxEstimatedTokens) {
|
|
5511
|
+
const result = runtime.compareSnapshots(leftSnapshotId, rightSnapshotId);
|
|
5512
|
+
const diff = result.diff;
|
|
5513
|
+
const summary = diff
|
|
5514
|
+
? {
|
|
5515
|
+
ok: true,
|
|
5516
|
+
leftSnapshotId,
|
|
5517
|
+
rightSnapshotId,
|
|
5518
|
+
title: diff.summary?.title,
|
|
5519
|
+
changedRangeCount: diff.summary?.changedRanges?.length ?? diff.changedRanges?.length ?? 0,
|
|
5520
|
+
cellsChanged: diff.summary?.cellsChanged ?? diff.cellsChanged,
|
|
5521
|
+
formulasChanged: diff.summary?.formulasChanged ?? diff.formulasChanged,
|
|
5522
|
+
stylesChanged: diff.summary?.stylesChanged ?? diff.stylesChanged,
|
|
5523
|
+
tablesChanged: diff.summary?.tablesChanged ?? diff.tablesChanged,
|
|
5524
|
+
sheetsChanged: diff.summary?.sheetsChanged ?? diff.sheetsChanged,
|
|
5525
|
+
destructiveLevel: diff.summary?.destructiveLevel ?? diff.destructiveLevel,
|
|
5526
|
+
changedRanges: (diff.summary?.changedRanges ?? diff.changedRanges ?? []).slice(0, 20),
|
|
5527
|
+
changedRangesTruncated: (diff.summary?.changedRanges ?? diff.changedRanges ?? []).length > 20
|
|
5528
|
+
}
|
|
5529
|
+
: { ok: false, leftSnapshotId, rightSnapshotId, error: result.error };
|
|
5530
|
+
return withCompactTelemetry(summary, {
|
|
5531
|
+
detailLevel: "summary",
|
|
5532
|
+
truncated: Boolean(summary.changedRangesTruncated),
|
|
5533
|
+
storeResource: Boolean(diff),
|
|
5534
|
+
resourceKind: "diff",
|
|
5535
|
+
resourceTitle: `Snapshot diff: ${leftSnapshotId}..${rightSnapshotId}`,
|
|
5536
|
+
resourcePayload: result,
|
|
5537
|
+
maxPayloadBytes,
|
|
5538
|
+
maxEstimatedTokens,
|
|
5539
|
+
budgetSummary: summary
|
|
5540
|
+
});
|
|
5541
|
+
}
|
|
5542
|
+
async function runCompactValidator(args, workbookId) {
|
|
5543
|
+
switch (args.validator) {
|
|
5544
|
+
case "workbook":
|
|
5545
|
+
return runtime.validateWorkbook({ workbookId });
|
|
5546
|
+
case "sheet":
|
|
5547
|
+
return runtime.validateSheet({ workbookId, sheetName: requiredCompactArg(args.sheetName, "sheetName") });
|
|
5548
|
+
case "formulas":
|
|
5549
|
+
return runtime.validateFormulas({ workbookId, ...compactOptional({ sheetName: args.sheetName, address: args.address }) });
|
|
5550
|
+
case "styles":
|
|
5551
|
+
return runtime.validateStyles(compactStylesValidationRequest(workbookId, args));
|
|
5552
|
+
case "tables":
|
|
5553
|
+
return runtime.validateTables(compactTablesValidationRequest(workbookId, args));
|
|
5554
|
+
case "filters":
|
|
5555
|
+
return runtime.validateFilters({ workbookId, ...compactOptional({ tableName: args.tableName }) });
|
|
5556
|
+
case "print_layout":
|
|
5557
|
+
return runtime.validatePrintLayout(compactPrintLayoutValidationRequest(workbookId, args));
|
|
5558
|
+
case "no_broken_references":
|
|
5559
|
+
return runtime.validateNoBrokenReferences({ workbookId, ...compactOptional({ sheetName: args.sheetName, address: args.address }) });
|
|
5560
|
+
case "no_formula_errors":
|
|
5561
|
+
return runtime.validateNoFormulaErrors({ workbookId, ...compactOptional({ sheetName: args.sheetName, address: args.address }) });
|
|
5562
|
+
case "no_unintended_changes":
|
|
5563
|
+
return runtime.validateNoUnintendedChanges(compactUnintendedChangesValidationRequest(workbookId, args));
|
|
5564
|
+
default:
|
|
5565
|
+
return {
|
|
5566
|
+
ok: false,
|
|
5567
|
+
workbookId,
|
|
5568
|
+
scope: args.validator,
|
|
5569
|
+
issueCount: 1,
|
|
5570
|
+
issues: [
|
|
5571
|
+
{
|
|
5572
|
+
code: "VALIDATOR_UNSUPPORTED",
|
|
5573
|
+
severity: "error",
|
|
5574
|
+
category: "validation",
|
|
5575
|
+
message: `Unsupported compact validator: ${args.validator}`
|
|
5576
|
+
}
|
|
5577
|
+
]
|
|
5578
|
+
};
|
|
5579
|
+
}
|
|
5580
|
+
}
|
|
5581
|
+
function compactSeverityCounts(issues) {
|
|
5582
|
+
const counts = {};
|
|
5583
|
+
for (const issue of issues) {
|
|
5584
|
+
const severity = typeof issue === "object" && issue !== null && "severity" in issue ? String(issue.severity) : "unknown";
|
|
5585
|
+
counts[severity] = (counts[severity] ?? 0) + 1;
|
|
5586
|
+
}
|
|
5587
|
+
return counts;
|
|
5588
|
+
}
|
|
5589
|
+
function compactStylesValidationRequest(workbookId, args) {
|
|
5590
|
+
const request = { workbookId };
|
|
5591
|
+
if (args.sheetName !== undefined) {
|
|
5592
|
+
request.sheetName = args.sheetName;
|
|
5593
|
+
}
|
|
5594
|
+
if (args.templateId !== undefined) {
|
|
5595
|
+
request.templateId = args.templateId;
|
|
5596
|
+
}
|
|
5597
|
+
if (args.targetSheetName !== undefined) {
|
|
5598
|
+
request.targetSheetName = args.targetSheetName;
|
|
5599
|
+
}
|
|
5600
|
+
return request;
|
|
5601
|
+
}
|
|
5602
|
+
function compactTablesValidationRequest(workbookId, args) {
|
|
5603
|
+
const request = { workbookId };
|
|
5604
|
+
if (args.tableName !== undefined) {
|
|
5605
|
+
request.tableName = args.tableName;
|
|
5606
|
+
}
|
|
5607
|
+
if (args.templateId !== undefined) {
|
|
5608
|
+
request.templateId = args.templateId;
|
|
5609
|
+
}
|
|
5610
|
+
return request;
|
|
5611
|
+
}
|
|
5612
|
+
function compactPrintLayoutValidationRequest(workbookId, args) {
|
|
5613
|
+
const request = { workbookId };
|
|
5614
|
+
if (args.templateId !== undefined) {
|
|
5615
|
+
request.templateId = args.templateId;
|
|
5616
|
+
}
|
|
5617
|
+
if (args.targetSheetName !== undefined) {
|
|
5618
|
+
request.targetSheetName = args.targetSheetName;
|
|
5619
|
+
}
|
|
5620
|
+
return request;
|
|
5621
|
+
}
|
|
5622
|
+
function compactUnintendedChangesValidationRequest(workbookId, args) {
|
|
5623
|
+
const request = { workbookId };
|
|
5624
|
+
if (args.snapshotId !== undefined) {
|
|
5625
|
+
request.snapshotId = args.snapshotId;
|
|
5626
|
+
}
|
|
5627
|
+
if (args.leftSnapshotId !== undefined) {
|
|
5628
|
+
request.leftSnapshotId = args.leftSnapshotId;
|
|
5629
|
+
}
|
|
5630
|
+
if (args.rightSnapshotId !== undefined) {
|
|
5631
|
+
request.rightSnapshotId = args.rightSnapshotId;
|
|
5632
|
+
}
|
|
5633
|
+
return request;
|
|
5634
|
+
}
|
|
5635
|
+
function compactIssueCategories(issues) {
|
|
5636
|
+
return [...new Set(issues.map((issue) => typeof issue === "object" && issue !== null && "category" in issue ? String(issue.category) : "unknown"))];
|
|
5637
|
+
}
|
|
5638
|
+
function compactIssueExample(issue) {
|
|
5639
|
+
if (typeof issue !== "object" || issue === null) {
|
|
5640
|
+
return issue;
|
|
5641
|
+
}
|
|
5642
|
+
const typed = issue;
|
|
5643
|
+
return {
|
|
5644
|
+
code: typed.code,
|
|
5645
|
+
severity: typed.severity,
|
|
5646
|
+
category: typed.category,
|
|
5647
|
+
message: typed.message,
|
|
5648
|
+
target: typed.target
|
|
5649
|
+
};
|
|
5650
|
+
}
|
|
5651
|
+
function requiredCompactArg(value, name) {
|
|
5652
|
+
if (value === undefined || value === "") {
|
|
5653
|
+
throw new Error(`${name} is required for this compact validator.`);
|
|
5654
|
+
}
|
|
5655
|
+
return value;
|
|
5656
|
+
}
|
|
5657
|
+
function usedRangeCellCount(usedRange) {
|
|
5658
|
+
return (usedRange?.rowCount ?? 0) * (usedRange?.columnCount ?? 0);
|
|
5659
|
+
}
|
|
5660
|
+
function tableInfoSchema(info) {
|
|
5661
|
+
return {
|
|
5662
|
+
workbookId: info.workbookId,
|
|
5663
|
+
tableName: info.tableName,
|
|
5664
|
+
sheetName: info.sheetName,
|
|
5665
|
+
address: info.address,
|
|
5666
|
+
headerAddress: info.headerAddress,
|
|
5667
|
+
rowCount: info.rowCount,
|
|
5668
|
+
columnCount: info.columnCount,
|
|
5669
|
+
columns: info.columns ?? [],
|
|
5670
|
+
style: info.style,
|
|
5671
|
+
showHeaders: info.showHeaders,
|
|
5672
|
+
showTotals: info.showTotals,
|
|
5673
|
+
showFilterButton: info.showFilterButton,
|
|
5674
|
+
showBandedRows: info.showBandedRows,
|
|
5675
|
+
showBandedColumns: info.showBandedColumns,
|
|
5676
|
+
hasFilters: info.filters !== undefined,
|
|
5677
|
+
hasSort: info.sort !== undefined
|
|
5678
|
+
};
|
|
5679
|
+
}
|
|
5680
|
+
async function lookupWorkbookContext(workbookId, sheetNames) {
|
|
5681
|
+
const result = await runtime.getWorkbookMap();
|
|
5682
|
+
const map = "map" in result ? result.map : undefined;
|
|
5683
|
+
if (!result.ok || !map) {
|
|
5684
|
+
return { ok: false, workbookId, source: result, sheets: [], tables: [] };
|
|
5685
|
+
}
|
|
5686
|
+
const requestedSheets = new Set((sheetNames ?? []).map(lookupNormalized));
|
|
5687
|
+
const sheets = (map.sheets ?? []).filter((sheet) => requestedSheets.size === 0 || requestedSheets.has(lookupNormalized(sheet.name)));
|
|
5688
|
+
const tableRefs = sheets.flatMap((sheet) => (sheet.tables ?? []).map((table) => ({
|
|
5689
|
+
sheetName: sheet.name,
|
|
5690
|
+
tableName: table.name ?? table.tableName
|
|
5691
|
+
}))).filter((table) => table.tableName);
|
|
5692
|
+
const tables = await Promise.all(tableRefs.map(async (table) => {
|
|
5693
|
+
try {
|
|
5694
|
+
const infoResult = await runtime.getTableInfo({ workbookId, tableName: table.tableName });
|
|
5695
|
+
const info = infoResult.info;
|
|
5696
|
+
return info ? { ...info, sheetName: info.sheetName ?? table.sheetName } : undefined;
|
|
5697
|
+
}
|
|
5698
|
+
catch {
|
|
5699
|
+
return undefined;
|
|
5700
|
+
}
|
|
5701
|
+
}));
|
|
5702
|
+
return {
|
|
5703
|
+
ok: true,
|
|
5704
|
+
workbookId,
|
|
5705
|
+
workbook: map.workbook,
|
|
5706
|
+
sheets,
|
|
5707
|
+
tables: tables.filter((table) => table !== undefined)
|
|
5708
|
+
};
|
|
5709
|
+
}
|
|
5710
|
+
function lookupTableMatches(tables, query, options) {
|
|
5711
|
+
const matches = [];
|
|
5712
|
+
for (const table of tables) {
|
|
5713
|
+
const schema = tableInfoSchema(table);
|
|
5714
|
+
if (lookupMatches(String(table.tableName), query, options)) {
|
|
5715
|
+
matches.push(lookupMatch({
|
|
5716
|
+
kind: "table",
|
|
5717
|
+
sheetName: table.sheetName,
|
|
5718
|
+
tableName: table.tableName,
|
|
5719
|
+
address: table.address,
|
|
5720
|
+
score: lookupScore(table.tableName, query, options.completeMatch),
|
|
5721
|
+
reason: "table name",
|
|
5722
|
+
schema
|
|
5723
|
+
}));
|
|
5724
|
+
}
|
|
5725
|
+
for (const column of table.columns ?? []) {
|
|
5726
|
+
if (lookupMatches(String(column.name), query, options)) {
|
|
5727
|
+
matches.push(lookupMatch({
|
|
5728
|
+
kind: "column",
|
|
5729
|
+
sheetName: table.sheetName,
|
|
5730
|
+
tableName: table.tableName,
|
|
5731
|
+
columnName: column.name,
|
|
5732
|
+
address: table.address,
|
|
5733
|
+
score: lookupScore(column.name, query, options.completeMatch),
|
|
5734
|
+
reason: "table column",
|
|
5735
|
+
schema: { tableName: table.tableName, column }
|
|
5736
|
+
}));
|
|
5737
|
+
}
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
5740
|
+
return matches;
|
|
5741
|
+
}
|
|
5742
|
+
async function lookupUsedRangeMatches(workbookId, sheets, query, options, kind) {
|
|
5743
|
+
const matches = [];
|
|
5744
|
+
const lookupOptions = options;
|
|
5745
|
+
for (const sheet of sheets) {
|
|
5746
|
+
const address = sheet.usedRange?.address;
|
|
5747
|
+
if (!address) {
|
|
5748
|
+
continue;
|
|
5749
|
+
}
|
|
5750
|
+
try {
|
|
5751
|
+
const result = await runtime.readRangeMetadata("range.search", {
|
|
5752
|
+
workbookId,
|
|
5753
|
+
sheetName: sheet.name,
|
|
5754
|
+
address,
|
|
5755
|
+
text: query,
|
|
5756
|
+
completeMatch: lookupOptions.completeMatch,
|
|
5757
|
+
matchCase: lookupOptions.matchCase
|
|
5758
|
+
});
|
|
5759
|
+
const found = result.matches;
|
|
5760
|
+
if (!found?.address || found.isNullObject) {
|
|
5761
|
+
continue;
|
|
5762
|
+
}
|
|
5763
|
+
const match = lookupMatch({
|
|
5764
|
+
kind,
|
|
5765
|
+
sheetName: sheet.name,
|
|
5766
|
+
address: found.address,
|
|
5767
|
+
score: lookupOptions.completeMatch ? 0.92 : 0.82,
|
|
5768
|
+
reason: `${kind === "entity" ? "entity" : "range"} text match`,
|
|
5769
|
+
preview: {
|
|
5770
|
+
areaCount: found.areaCount,
|
|
5771
|
+
cellCount: found.cellCount
|
|
5772
|
+
}
|
|
5773
|
+
});
|
|
5774
|
+
if ((lookupOptions.maxPreviewRows ?? 0) > 0) {
|
|
5775
|
+
match.preview = await lookupPreviewRange(workbookId, sheet.name, found.address, lookupOptions.maxPreviewRows ?? 3);
|
|
5776
|
+
}
|
|
5777
|
+
matches.push(match);
|
|
5778
|
+
}
|
|
5779
|
+
catch {
|
|
5780
|
+
// Ignore sheet-local search failures; other sheets and metadata can still guide the model.
|
|
5781
|
+
}
|
|
5782
|
+
}
|
|
5783
|
+
return matches;
|
|
5784
|
+
}
|
|
5785
|
+
async function lookupPreviewRange(workbookId, sheetName, address, maxRows) {
|
|
5786
|
+
try {
|
|
5787
|
+
const result = await compactRangeRead({
|
|
5788
|
+
workbookId,
|
|
5789
|
+
sheetName,
|
|
5790
|
+
address,
|
|
5791
|
+
mode: "window",
|
|
5792
|
+
maxRows,
|
|
5793
|
+
maxColumns: 10,
|
|
5794
|
+
includeValues: true,
|
|
5795
|
+
includeText: true
|
|
5796
|
+
});
|
|
5797
|
+
return {
|
|
5798
|
+
address: result.window?.address ?? address,
|
|
5799
|
+
values: result.values,
|
|
5800
|
+
text: result.text
|
|
5801
|
+
};
|
|
5802
|
+
}
|
|
5803
|
+
catch {
|
|
5804
|
+
return { address };
|
|
5805
|
+
}
|
|
5806
|
+
}
|
|
5807
|
+
function compactLookupResponse(args) {
|
|
5808
|
+
const allMatches = lookupSortAndDedupe(args.matches);
|
|
5809
|
+
const shownMatches = allMatches.slice(0, args.maxMatches);
|
|
5810
|
+
const summary = {
|
|
5811
|
+
ok: true,
|
|
5812
|
+
workbookId: args.workbookId,
|
|
5813
|
+
...Object.fromEntries(Object.entries(args).filter(([key]) => !["workbookId", "matches", "maxMatches", "maxPayloadBytes", "maxEstimatedTokens", "resourceTitle"].includes(key))),
|
|
5814
|
+
matchCount: allMatches.length,
|
|
5815
|
+
matches: shownMatches,
|
|
5816
|
+
matchesTruncated: shownMatches.length < allMatches.length
|
|
5817
|
+
};
|
|
5818
|
+
return withCompactTelemetry(summary, {
|
|
5819
|
+
detailLevel: "summary",
|
|
5820
|
+
truncated: shownMatches.length < allMatches.length,
|
|
5821
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
5822
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
5823
|
+
resourceKind: "summary",
|
|
5824
|
+
resourceTitle: args.resourceTitle,
|
|
5825
|
+
resourcePayload: { ...summary, matches: allMatches, matchesTruncated: false },
|
|
5826
|
+
budgetSummary: { ...summary, matches: shownMatches.map(({ preview, schema, ...match }) => match) }
|
|
5827
|
+
});
|
|
5828
|
+
}
|
|
5829
|
+
function lookupMatch(match) {
|
|
5830
|
+
return {
|
|
5831
|
+
...match,
|
|
5832
|
+
score: Number(match.score.toFixed(3)),
|
|
5833
|
+
matchId: lookupEncodeMatchId(match)
|
|
5834
|
+
};
|
|
5835
|
+
}
|
|
5836
|
+
function lookupSortAndDedupe(matches) {
|
|
5837
|
+
const best = new Map();
|
|
5838
|
+
for (const match of matches) {
|
|
5839
|
+
const key = [match.kind, match.sheetName, match.tableName, match.columnName, match.address].map((value) => value ?? "").join("|");
|
|
5840
|
+
const existing = best.get(key);
|
|
5841
|
+
if (!existing || match.score > existing.score) {
|
|
5842
|
+
best.set(key, match);
|
|
5843
|
+
}
|
|
5844
|
+
}
|
|
5845
|
+
return [...best.values()].sort((left, right) => right.score - left.score ||
|
|
5846
|
+
String(left.sheetName ?? "").localeCompare(String(right.sheetName ?? "")) ||
|
|
5847
|
+
String(left.tableName ?? "").localeCompare(String(right.tableName ?? "")) ||
|
|
5848
|
+
String(left.address ?? "").localeCompare(String(right.address ?? "")));
|
|
5849
|
+
}
|
|
5850
|
+
function lookupTerms(values) {
|
|
5851
|
+
return values.map(lookupNormalized).filter((value) => value !== "");
|
|
5852
|
+
}
|
|
5853
|
+
function lookupMatches(candidate, query, options) {
|
|
5854
|
+
const lookupOptions = options;
|
|
5855
|
+
const left = lookupOptions.matchCase ? candidate.trim() : lookupNormalized(candidate);
|
|
5856
|
+
const right = lookupOptions.matchCase ? query.trim() : lookupNormalized(query);
|
|
5857
|
+
return lookupOptions.completeMatch ? left === right : left.includes(right);
|
|
5858
|
+
}
|
|
5859
|
+
function lookupScore(candidate, query, completeMatch) {
|
|
5860
|
+
const left = lookupNormalized(candidate);
|
|
5861
|
+
const right = lookupNormalized(query);
|
|
5862
|
+
if (left === right) {
|
|
5863
|
+
return 1;
|
|
5864
|
+
}
|
|
5865
|
+
if (completeMatch) {
|
|
5866
|
+
return 0;
|
|
5867
|
+
}
|
|
5868
|
+
if (left.startsWith(right)) {
|
|
5869
|
+
return 0.9;
|
|
5870
|
+
}
|
|
5871
|
+
return left.includes(right) ? 0.75 : 0;
|
|
5872
|
+
}
|
|
5873
|
+
function lookupNormalized(value) {
|
|
5874
|
+
return String(value ?? "").trim().toLowerCase();
|
|
5875
|
+
}
|
|
5876
|
+
function lookupRowsFromCompactRead(result) {
|
|
5877
|
+
const typed = result;
|
|
5878
|
+
return Array.isArray(typed.text) ? typed.text : Array.isArray(typed.values) ? typed.values : [];
|
|
5879
|
+
}
|
|
5880
|
+
function lookupParseRangeTarget(target, preferredSheetName) {
|
|
5881
|
+
const parts = target.split("!");
|
|
5882
|
+
const sheetName = parts.length > 1 ? parts.slice(0, -1).join("!").replace(/^'|'$/g, "") : preferredSheetName;
|
|
5883
|
+
const address = parts.length > 1 ? parts[parts.length - 1] : target;
|
|
5884
|
+
if (address === undefined) {
|
|
5885
|
+
return undefined;
|
|
5886
|
+
}
|
|
5887
|
+
try {
|
|
5888
|
+
parseCompactA1Address(address);
|
|
5889
|
+
return sheetName ? { sheetName, address } : undefined;
|
|
5890
|
+
}
|
|
5891
|
+
catch {
|
|
5892
|
+
return undefined;
|
|
5893
|
+
}
|
|
5894
|
+
}
|
|
5895
|
+
function lookupEncodeMatchId(match) {
|
|
5896
|
+
const payload = JSON.stringify({
|
|
5897
|
+
kind: match.kind,
|
|
5898
|
+
sheetName: match.sheetName,
|
|
5899
|
+
tableName: match.tableName,
|
|
5900
|
+
columnName: match.columnName,
|
|
5901
|
+
address: match.address
|
|
5902
|
+
});
|
|
5903
|
+
return `lookup:${Buffer.from(payload, "utf8").toString("base64url")}`;
|
|
5904
|
+
}
|
|
5905
|
+
function lookupDecodeMatchId(matchId) {
|
|
5906
|
+
if (!matchId.startsWith("lookup:")) {
|
|
5907
|
+
return undefined;
|
|
5908
|
+
}
|
|
5909
|
+
try {
|
|
5910
|
+
return JSON.parse(Buffer.from(matchId.slice("lookup:".length), "base64url").toString("utf8"));
|
|
5911
|
+
}
|
|
5912
|
+
catch {
|
|
5913
|
+
return undefined;
|
|
5914
|
+
}
|
|
5915
|
+
}
|
|
5916
|
+
function compactTableColumns(info, requested, maxColumns) {
|
|
5917
|
+
const columns = info.columns ?? [];
|
|
5918
|
+
if (requested?.length) {
|
|
5919
|
+
return columns.filter((column) => requested.some((item) => item === column.name || item === column.index || item === column.id));
|
|
5920
|
+
}
|
|
5921
|
+
return columns.slice(0, maxColumns ?? 25);
|
|
5922
|
+
}
|
|
5923
|
+
function compactRangeFacets(args) {
|
|
5924
|
+
const facets = [];
|
|
5925
|
+
if (args.includeValues !== false) {
|
|
5926
|
+
facets.push("values");
|
|
5927
|
+
}
|
|
5928
|
+
if (args.includeFormulas === true) {
|
|
5929
|
+
facets.push("formulas");
|
|
5930
|
+
}
|
|
5931
|
+
if (args.includeText === true) {
|
|
5932
|
+
facets.push("text");
|
|
5933
|
+
}
|
|
5934
|
+
if (args.includeNumberFormats === true) {
|
|
5935
|
+
facets.push("numberFormat");
|
|
5936
|
+
}
|
|
5937
|
+
if (args.includeStyles === true) {
|
|
5938
|
+
facets.push("style");
|
|
5939
|
+
}
|
|
5940
|
+
return facets;
|
|
5941
|
+
}
|
|
5942
|
+
function compactSampleWindows(totalRows, requestedRows) {
|
|
5943
|
+
const sampleRows = Math.max(1, Math.min(totalRows, requestedRows));
|
|
5944
|
+
if (totalRows <= sampleRows) {
|
|
5945
|
+
return [{ label: "head", rowOffset: 0, rowCount: totalRows }];
|
|
5946
|
+
}
|
|
5947
|
+
const headRows = Math.max(1, Math.floor(sampleRows / 3));
|
|
5948
|
+
const middleRows = Math.max(1, Math.floor(sampleRows / 3));
|
|
5949
|
+
const tailRows = Math.max(1, sampleRows - headRows - middleRows);
|
|
5950
|
+
const middleOffset = Math.max(headRows, Math.floor((totalRows - middleRows) / 2));
|
|
5951
|
+
const tailOffset = Math.max(middleOffset + middleRows, totalRows - tailRows);
|
|
5952
|
+
const windows = [
|
|
5953
|
+
{ label: "head", rowOffset: 0, rowCount: headRows },
|
|
5954
|
+
{ label: "middle", rowOffset: middleOffset, rowCount: Math.min(middleRows, totalRows - middleOffset) },
|
|
5955
|
+
{ label: "tail", rowOffset: tailOffset, rowCount: Math.min(tailRows, totalRows - tailOffset) }
|
|
5956
|
+
];
|
|
5957
|
+
return windows.filter((window, index, all) => window.rowCount > 0 &&
|
|
5958
|
+
all.findIndex((candidate) => rangesOverlap(candidate.rowOffset, candidate.rowCount, window.rowOffset, window.rowCount)) === index);
|
|
5959
|
+
}
|
|
5960
|
+
function rangesOverlap(leftOffset, leftCount, rightOffset, rightCount) {
|
|
5961
|
+
const leftEnd = leftOffset + leftCount;
|
|
5962
|
+
const rightEnd = rightOffset + rightCount;
|
|
5963
|
+
return leftOffset < rightEnd && rightOffset < leftEnd;
|
|
5964
|
+
}
|
|
5965
|
+
function compactWindowAddress(address, rowOffset, columnOffset, rowCount, columnCount) {
|
|
5966
|
+
const parsed = parseCompactA1Address(address);
|
|
5967
|
+
const startRow = parsed.startRow + rowOffset;
|
|
5968
|
+
const startColumn = parsed.startColumn + columnOffset;
|
|
5969
|
+
const endRow = startRow + Math.max(rowCount, 1) - 1;
|
|
5970
|
+
const endColumn = startColumn + Math.max(columnCount, 1) - 1;
|
|
5971
|
+
const start = `${compactColumnName(startColumn)}${startRow}`;
|
|
5972
|
+
const end = `${compactColumnName(endColumn)}${endRow}`;
|
|
5973
|
+
return start === end ? start : `${start}:${end}`;
|
|
5974
|
+
}
|
|
5975
|
+
function parseCompactA1Address(address) {
|
|
5976
|
+
const range = stripResourceSheetName(address).trim();
|
|
5977
|
+
const match = /^(?<startCol>[A-Z]+)(?<startRow>\d+)(?::(?<endCol>[A-Z]+)(?<endRow>\d+))?$/i.exec(range);
|
|
5978
|
+
if (!match?.groups?.startCol || !match.groups.startRow) {
|
|
5979
|
+
throw new Error(`Invalid A1 address: ${address}`);
|
|
5980
|
+
}
|
|
5981
|
+
const startColumn = compactColumnNumber(match.groups.startCol);
|
|
5982
|
+
const startRow = Number(match.groups.startRow);
|
|
5983
|
+
const endColumn = compactColumnNumber(match.groups.endCol ?? match.groups.startCol);
|
|
5984
|
+
const endRow = Number(match.groups.endRow ?? match.groups.startRow);
|
|
5985
|
+
if (startRow < 1 || endRow < startRow || endColumn < startColumn) {
|
|
5986
|
+
throw new Error(`Invalid A1 range bounds: ${address}`);
|
|
5987
|
+
}
|
|
5988
|
+
return { startRow, startColumn, endRow, endColumn };
|
|
5989
|
+
}
|
|
5990
|
+
function compactColumnNumber(columnName) {
|
|
5991
|
+
let value = 0;
|
|
5992
|
+
for (const char of columnName.toUpperCase()) {
|
|
5993
|
+
const code = char.charCodeAt(0);
|
|
5994
|
+
if (code < 65 || code > 90) {
|
|
5995
|
+
throw new Error(`Invalid column name: ${columnName}`);
|
|
5996
|
+
}
|
|
5997
|
+
value = value * 26 + (code - 64);
|
|
5998
|
+
}
|
|
5999
|
+
return value;
|
|
6000
|
+
}
|
|
6001
|
+
function compactColumnName(columnNumber) {
|
|
6002
|
+
let value = "";
|
|
6003
|
+
let n = columnNumber;
|
|
6004
|
+
while (n > 0) {
|
|
6005
|
+
n -= 1;
|
|
6006
|
+
value = String.fromCharCode(65 + (n % 26)) + value;
|
|
6007
|
+
n = Math.floor(n / 26);
|
|
6008
|
+
}
|
|
6009
|
+
return value;
|
|
6010
|
+
}
|
|
6011
|
+
function tableCreateRequest(args) {
|
|
6012
|
+
const request = {
|
|
6013
|
+
workbookId: args.workbookId,
|
|
6014
|
+
sheetName: args.sheetName,
|
|
6015
|
+
address: args.address,
|
|
6016
|
+
hasHeaders: args.hasHeaders
|
|
6017
|
+
};
|
|
6018
|
+
if (args.tableName !== undefined) {
|
|
6019
|
+
request.tableName = args.tableName;
|
|
6020
|
+
}
|
|
6021
|
+
if (args.values !== undefined) {
|
|
6022
|
+
request.values = args.values;
|
|
6023
|
+
}
|
|
6024
|
+
if (args.style !== undefined) {
|
|
6025
|
+
request.style = args.style;
|
|
6026
|
+
}
|
|
6027
|
+
if (args.showTotals !== undefined) {
|
|
6028
|
+
request.showTotals = args.showTotals;
|
|
6029
|
+
}
|
|
6030
|
+
return request;
|
|
6031
|
+
}
|
|
6032
|
+
function tableFilterSchema() {
|
|
6033
|
+
return {
|
|
6034
|
+
...tableSelectorSchema(),
|
|
6035
|
+
filters: z.array(z.object({
|
|
6036
|
+
column: z.union([z.string(), z.number().int().min(0)]),
|
|
6037
|
+
criteria: z.record(z.string(), z.any())
|
|
6038
|
+
}))
|
|
6039
|
+
};
|
|
6040
|
+
}
|
|
6041
|
+
function tableSortSchema() {
|
|
6042
|
+
return {
|
|
6043
|
+
...tableSelectorSchema(),
|
|
6044
|
+
fields: z.array(z.object({
|
|
6045
|
+
key: z.number().int().min(0),
|
|
6046
|
+
ascending: z.boolean().optional(),
|
|
6047
|
+
sortOn: z.enum(["Value", "CellColor", "FontColor", "Icon"]).optional(),
|
|
6048
|
+
color: z.string().optional(),
|
|
6049
|
+
dataOption: z.enum(["Normal", "TextAsNumber"]).optional()
|
|
6050
|
+
})),
|
|
6051
|
+
matchCase: z.boolean().optional(),
|
|
6052
|
+
method: z.enum(["PinYin", "StrokeCount"]).optional()
|
|
6053
|
+
};
|
|
6054
|
+
}
|
|
6055
|
+
function nameSelectorSchema() {
|
|
6056
|
+
return {
|
|
6057
|
+
workbookId: z.string(),
|
|
6058
|
+
name: z.string(),
|
|
6059
|
+
sheetName: z.string().optional()
|
|
6060
|
+
};
|
|
6061
|
+
}
|
|
6062
|
+
function nameMutationSchema() {
|
|
6063
|
+
return {
|
|
6064
|
+
...nameSelectorSchema(),
|
|
6065
|
+
reference: z.string().optional(),
|
|
6066
|
+
formula: z.string().optional(),
|
|
6067
|
+
comment: z.string().optional(),
|
|
6068
|
+
visible: z.boolean().optional()
|
|
6069
|
+
};
|
|
6070
|
+
}
|
|
6071
|
+
function nameSelector(args) {
|
|
6072
|
+
const request = {
|
|
6073
|
+
workbookId: args.workbookId,
|
|
6074
|
+
name: args.name
|
|
6075
|
+
};
|
|
6076
|
+
if (args.sheetName !== undefined) {
|
|
6077
|
+
request.sheetName = args.sheetName;
|
|
6078
|
+
}
|
|
6079
|
+
return request;
|
|
6080
|
+
}
|
|
6081
|
+
function nameCreateRequest(args) {
|
|
6082
|
+
const request = nameSelector(args);
|
|
6083
|
+
if (args.reference !== undefined) {
|
|
6084
|
+
request.reference = args.reference;
|
|
6085
|
+
}
|
|
6086
|
+
if (args.formula !== undefined) {
|
|
6087
|
+
request.formula = args.formula;
|
|
6088
|
+
}
|
|
6089
|
+
if (args.comment !== undefined) {
|
|
6090
|
+
request.comment = args.comment;
|
|
6091
|
+
}
|
|
6092
|
+
if (args.visible !== undefined) {
|
|
6093
|
+
request.visible = args.visible;
|
|
6094
|
+
}
|
|
6095
|
+
return request;
|
|
6096
|
+
}
|
|
6097
|
+
function nameUpdateRequest(args) {
|
|
6098
|
+
return nameCreateRequest(args);
|
|
6099
|
+
}
|
|
6100
|
+
function regionSelectorSchema() {
|
|
6101
|
+
return {
|
|
6102
|
+
workbookId: z.string(),
|
|
6103
|
+
regionName: z.string()
|
|
6104
|
+
};
|
|
6105
|
+
}
|
|
6106
|
+
function regionSelector(args) {
|
|
6107
|
+
return {
|
|
6108
|
+
workbookId: args.workbookId,
|
|
6109
|
+
regionName: args.regionName
|
|
6110
|
+
};
|
|
6111
|
+
}
|
|
6112
|
+
function pivotSelectorSchema() {
|
|
6113
|
+
return {
|
|
6114
|
+
workbookId: z.string(),
|
|
6115
|
+
pivotTableName: z.string()
|
|
6116
|
+
};
|
|
6117
|
+
}
|
|
6118
|
+
function pivotSelector(args) {
|
|
6119
|
+
return {
|
|
6120
|
+
workbookId: args.workbookId,
|
|
6121
|
+
pivotTableName: args.pivotTableName
|
|
6122
|
+
};
|
|
6123
|
+
}
|
|
6124
|
+
function pivotValidateSourceRequest(args) {
|
|
6125
|
+
return {
|
|
6126
|
+
...pivotSelector(args),
|
|
6127
|
+
...(args.expectedFields !== undefined ? { expectedFields: args.expectedFields } : {}),
|
|
6128
|
+
...(args.expectedRowFields !== undefined ? { expectedRowFields: args.expectedRowFields } : {}),
|
|
6129
|
+
...(args.expectedColumnFields !== undefined ? { expectedColumnFields: args.expectedColumnFields } : {}),
|
|
6130
|
+
...(args.expectedFilterFields !== undefined ? { expectedFilterFields: args.expectedFilterFields } : {}),
|
|
6131
|
+
...(args.expectedDataFields !== undefined ? { expectedDataFields: args.expectedDataFields } : {}),
|
|
6132
|
+
...(args.expectedDataFieldSettings !== undefined ? { expectedDataFieldSettings: args.expectedDataFieldSettings } : {}),
|
|
6133
|
+
...(args.expectedLayout !== undefined ? { expectedLayout: args.expectedLayout } : {})
|
|
6134
|
+
};
|
|
6135
|
+
}
|
|
6136
|
+
function pivotCreateRequest(args) {
|
|
6137
|
+
const request = {
|
|
6138
|
+
workbookId: args.workbookId,
|
|
6139
|
+
pivotTableName: args.pivotTableName,
|
|
6140
|
+
destinationSheetName: args.destinationSheetName,
|
|
6141
|
+
destinationAddress: args.destinationAddress
|
|
4182
6142
|
};
|
|
4183
6143
|
if (args.sourceSheetName !== undefined) {
|
|
4184
6144
|
request.sourceSheetName = args.sourceSheetName;
|
|
@@ -4639,10 +6599,83 @@ function escapeHtml(value) {
|
|
|
4639
6599
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
4640
6600
|
}
|
|
4641
6601
|
function registerMcpTool(mcp, name, config, callback) {
|
|
4642
|
-
if (!
|
|
6602
|
+
if (!shouldExposeMcpTool(name)) {
|
|
4643
6603
|
return;
|
|
4644
6604
|
}
|
|
4645
|
-
mcp.registerTool(name, config,
|
|
6605
|
+
mcp.registerTool(name, config, async (args, extra) => {
|
|
6606
|
+
const result = await callback(args, extra);
|
|
6607
|
+
const decorated = isWorkbookMutatingMcpTool(name) ? addCompactMutationProof(result) : result;
|
|
6608
|
+
if (isWorkbookMutatingMcpTool(name)) {
|
|
6609
|
+
clearCompactCache(`mutation:${name}`);
|
|
6610
|
+
}
|
|
6611
|
+
return enforceCompactResultBudget(name, decorated);
|
|
6612
|
+
});
|
|
6613
|
+
}
|
|
6614
|
+
function enforceCompactResultBudget(toolName, result) {
|
|
6615
|
+
if (toolProfile === "full" || !isJsonTextResult(result)) {
|
|
6616
|
+
return result;
|
|
6617
|
+
}
|
|
6618
|
+
if (toolName === "excel.compact.get_resource") {
|
|
6619
|
+
return result;
|
|
6620
|
+
}
|
|
6621
|
+
const payloadBytes = Buffer.byteLength(result.content[0].text, "utf8");
|
|
6622
|
+
if (payloadBytes <= COMPACT_DEFAULT_RESOURCE_THRESHOLD_BYTES) {
|
|
6623
|
+
return result;
|
|
6624
|
+
}
|
|
6625
|
+
try {
|
|
6626
|
+
const payload = JSON.parse(result.content[0].text);
|
|
6627
|
+
if (payload.resourceUri !== undefined && payload.budgetExceeded === true) {
|
|
6628
|
+
return result;
|
|
6629
|
+
}
|
|
6630
|
+
const stored = storeCompactResource(compactResourceKindForTool(toolName), payload, `Full result: ${toolName}`);
|
|
6631
|
+
const summary = compactResultSummary(toolName, payload, payloadBytes, stored);
|
|
6632
|
+
return jsonResult(summary);
|
|
6633
|
+
}
|
|
6634
|
+
catch {
|
|
6635
|
+
const stored = storeCompactResource("generic", result.content[0].text, `Full text result: ${toolName}`);
|
|
6636
|
+
return jsonResult({
|
|
6637
|
+
ok: true,
|
|
6638
|
+
toolName,
|
|
6639
|
+
detailLevel: "summary",
|
|
6640
|
+
budgetExceeded: true,
|
|
6641
|
+
payloadBytes,
|
|
6642
|
+
estimatedTokens: Math.ceil(payloadBytes / 4),
|
|
6643
|
+
resourcePayloadBytes: stored.payloadBytes,
|
|
6644
|
+
resourceEstimatedTokens: stored.estimatedTokens,
|
|
6645
|
+
resourceUri: stored.uri,
|
|
6646
|
+
warnings: [
|
|
6647
|
+
{
|
|
6648
|
+
code: "COMPACT_RESULT_BUDGET_EXCEEDED",
|
|
6649
|
+
message: "Tool result exceeded compact response budget and was stored behind resourceUri."
|
|
6650
|
+
}
|
|
6651
|
+
]
|
|
6652
|
+
});
|
|
6653
|
+
}
|
|
6654
|
+
}
|
|
6655
|
+
function shouldExposeMcpTool(name) {
|
|
6656
|
+
if (!isToolExposed(name, catalogOptions)) {
|
|
6657
|
+
return false;
|
|
6658
|
+
}
|
|
6659
|
+
if (explicitToolNames !== undefined && !explicitToolNames.has(name)) {
|
|
6660
|
+
return false;
|
|
6661
|
+
}
|
|
6662
|
+
if (disabledToolNames?.has(name)) {
|
|
6663
|
+
return false;
|
|
6664
|
+
}
|
|
6665
|
+
if (explicitToolNames !== undefined) {
|
|
6666
|
+
return true;
|
|
6667
|
+
}
|
|
6668
|
+
if (toolProfile === "full") {
|
|
6669
|
+
return true;
|
|
6670
|
+
}
|
|
6671
|
+
if (toolProfile === "compact") {
|
|
6672
|
+
return COMPACT_PROFILE_TOOLS.has(name);
|
|
6673
|
+
}
|
|
6674
|
+
if (toolProfile === "read-only" || toolProfile === "readonly") {
|
|
6675
|
+
return READ_ONLY_PROFILE_TOOLS.has(name);
|
|
6676
|
+
}
|
|
6677
|
+
console.error(`Unknown OPEN_WORKBOOK_TOOL_PROFILE=${toolProfile}; falling back to full MCP tool surface.`);
|
|
6678
|
+
return true;
|
|
4646
6679
|
}
|
|
4647
6680
|
function jsonResult(value) {
|
|
4648
6681
|
return {
|
|
@@ -4654,6 +6687,287 @@ function jsonResult(value) {
|
|
|
4654
6687
|
]
|
|
4655
6688
|
};
|
|
4656
6689
|
}
|
|
6690
|
+
function isWorkbookMutatingMcpTool(name) {
|
|
6691
|
+
const namespace = name.split(".")[1];
|
|
6692
|
+
if (!namespace || name.startsWith("excel.compact.")) {
|
|
6693
|
+
return false;
|
|
6694
|
+
}
|
|
6695
|
+
if (namespace === "workbook") {
|
|
6696
|
+
return /\.(calculate|save|save_as|restore_backup|close|embed|import)/.test(name);
|
|
6697
|
+
}
|
|
6698
|
+
if (namespace === "plan") {
|
|
6699
|
+
return /\.(apply|rollback)/.test(name);
|
|
6700
|
+
}
|
|
6701
|
+
if (namespace === "transaction") {
|
|
6702
|
+
return /\.(rollback|rollback_chain)/.test(name);
|
|
6703
|
+
}
|
|
6704
|
+
if (!new Set(["sheet", "range", "batch", "workflow", "template", "style", "formula", "table", "filter", "sort", "pivot", "chart", "names", "region", "repair", "clean"]).has(namespace)) {
|
|
6705
|
+
return false;
|
|
6706
|
+
}
|
|
6707
|
+
if (name === "excel.workflow.preview_risky_edit") {
|
|
6708
|
+
return true;
|
|
6709
|
+
}
|
|
6710
|
+
return /\.(set_|write_|create|copy|rename|delete|move|hide|unhide|protect|unprotect|clear|apply|repair|fill|append|update|resize|sort|save|restore|close|insert|merge|unmerge|lock|unlock|convert|calculate|recalculate|register|unregister|commit|rollback|cancel|refresh|invalidate|parse|normalize|trim|remove|standardize|split|import|embed)/.test(name);
|
|
6711
|
+
}
|
|
6712
|
+
function addCompactMutationProof(result) {
|
|
6713
|
+
if (!isJsonTextResult(result)) {
|
|
6714
|
+
return result;
|
|
6715
|
+
}
|
|
6716
|
+
try {
|
|
6717
|
+
const payload = JSON.parse(result.content[0].text);
|
|
6718
|
+
if (payload.compactProof !== undefined) {
|
|
6719
|
+
return result;
|
|
6720
|
+
}
|
|
6721
|
+
const compactProof = summarizeMutationProof(payload);
|
|
6722
|
+
if (toolProfile === "full") {
|
|
6723
|
+
return jsonResult({
|
|
6724
|
+
...payload,
|
|
6725
|
+
compactProof
|
|
6726
|
+
});
|
|
6727
|
+
}
|
|
6728
|
+
const stored = storeCompactResource("mutation", payload, "Full mutation result");
|
|
6729
|
+
return jsonResult({
|
|
6730
|
+
...compactMutationPayload(payload),
|
|
6731
|
+
compactProof,
|
|
6732
|
+
resourceUri: stored.uri
|
|
6733
|
+
});
|
|
6734
|
+
}
|
|
6735
|
+
catch {
|
|
6736
|
+
return result;
|
|
6737
|
+
}
|
|
6738
|
+
}
|
|
6739
|
+
function isJsonTextResult(result) {
|
|
6740
|
+
return Boolean(result &&
|
|
6741
|
+
typeof result === "object" &&
|
|
6742
|
+
Array.isArray(result.content) &&
|
|
6743
|
+
result.content[0]?.text !== undefined);
|
|
6744
|
+
}
|
|
6745
|
+
function summarizeMutationProof(payload) {
|
|
6746
|
+
const diffSummary = payload.diffSummary;
|
|
6747
|
+
const telemetry = payload.telemetry;
|
|
6748
|
+
const warnings = Array.isArray(payload.warnings) ? payload.warnings : [];
|
|
6749
|
+
const validation = validationSummary(payload);
|
|
6750
|
+
return {
|
|
6751
|
+
ok: payload.ok,
|
|
6752
|
+
transactionId: payload.transactionId,
|
|
6753
|
+
transactionStatus: payload.transactionStatus,
|
|
6754
|
+
taskId: payload.taskId,
|
|
6755
|
+
rollbackAvailable: payload.rollbackAvailable,
|
|
6756
|
+
backups: payload.backups,
|
|
6757
|
+
changedRanges: diffSummary?.changedRanges,
|
|
6758
|
+
cellsChanged: diffSummary?.cellsChanged ?? telemetry?.cellsWritten,
|
|
6759
|
+
formulasChanged: diffSummary?.formulasChanged,
|
|
6760
|
+
stylesChanged: diffSummary?.stylesChanged,
|
|
6761
|
+
tablesChanged: diffSummary?.tablesChanged,
|
|
6762
|
+
sheetsChanged: diffSummary?.sheetsChanged,
|
|
6763
|
+
warnings: warnings.length,
|
|
6764
|
+
validation,
|
|
6765
|
+
payloadBytes: Buffer.byteLength(JSON.stringify(payload), "utf8"),
|
|
6766
|
+
estimatedTokens: Math.ceil(Buffer.byteLength(JSON.stringify(payload), "utf8") / 4)
|
|
6767
|
+
};
|
|
6768
|
+
}
|
|
6769
|
+
function compactMutationPayload(payload) {
|
|
6770
|
+
const output = {};
|
|
6771
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
6772
|
+
if (key === "beforeSnapshot" || key === "afterSnapshot") {
|
|
6773
|
+
output[key] = value && typeof value === "object" ? snapshotSummary(value) : value;
|
|
6774
|
+
continue;
|
|
6775
|
+
}
|
|
6776
|
+
if (key === "backup") {
|
|
6777
|
+
output[key] = summarizeBackup(value);
|
|
6778
|
+
continue;
|
|
6779
|
+
}
|
|
6780
|
+
if (key === "diff") {
|
|
6781
|
+
output[key] = summarizeDiffResult(value);
|
|
6782
|
+
continue;
|
|
6783
|
+
}
|
|
6784
|
+
if (key === "planPreview") {
|
|
6785
|
+
output[key] = summarizePlanPreview(value);
|
|
6786
|
+
continue;
|
|
6787
|
+
}
|
|
6788
|
+
if (key === "applyResult") {
|
|
6789
|
+
output[key] = summarizeOperationResult(value);
|
|
6790
|
+
continue;
|
|
6791
|
+
}
|
|
6792
|
+
if (key === "beforeSnapshotResult") {
|
|
6793
|
+
output[key] = summarizeOperationResult(value);
|
|
6794
|
+
continue;
|
|
6795
|
+
}
|
|
6796
|
+
output[key] = value;
|
|
6797
|
+
}
|
|
6798
|
+
return output;
|
|
6799
|
+
}
|
|
6800
|
+
function summarizeBackup(value) {
|
|
6801
|
+
if (!value || typeof value !== "object") {
|
|
6802
|
+
return value;
|
|
6803
|
+
}
|
|
6804
|
+
const record = value;
|
|
6805
|
+
return {
|
|
6806
|
+
backupId: record.backupId,
|
|
6807
|
+
workbookId: record.workbookId,
|
|
6808
|
+
kind: record.kind,
|
|
6809
|
+
reason: record.reason,
|
|
6810
|
+
createdAt: record.createdAt,
|
|
6811
|
+
affectedRangeCount: record.affectedRanges?.length ?? 0,
|
|
6812
|
+
affectedRanges: record.affectedRanges,
|
|
6813
|
+
payloadRef: record.payloadRef
|
|
6814
|
+
};
|
|
6815
|
+
}
|
|
6816
|
+
function summarizeDiffResult(value) {
|
|
6817
|
+
if (!value || typeof value !== "object") {
|
|
6818
|
+
return value;
|
|
6819
|
+
}
|
|
6820
|
+
const record = value;
|
|
6821
|
+
const diff = record.diff ?? record;
|
|
6822
|
+
return {
|
|
6823
|
+
ok: record.ok,
|
|
6824
|
+
diffId: record.diffId,
|
|
6825
|
+
summary: diff.summary ?? {
|
|
6826
|
+
title: diff.title,
|
|
6827
|
+
changedRanges: Array.isArray(diff.changedRanges) ? diff.changedRanges.slice(0, 20) : undefined,
|
|
6828
|
+
changedRangesTruncated: Array.isArray(diff.changedRanges) && diff.changedRanges.length > 20,
|
|
6829
|
+
cellsChanged: diff.cellsChanged,
|
|
6830
|
+
formulasChanged: diff.formulasChanged,
|
|
6831
|
+
stylesChanged: diff.stylesChanged,
|
|
6832
|
+
tablesChanged: diff.tablesChanged,
|
|
6833
|
+
sheetsChanged: diff.sheetsChanged,
|
|
6834
|
+
destructiveLevel: diff.destructiveLevel
|
|
6835
|
+
}
|
|
6836
|
+
};
|
|
6837
|
+
}
|
|
6838
|
+
function summarizePlanPreview(value) {
|
|
6839
|
+
if (!value || typeof value !== "object") {
|
|
6840
|
+
return value;
|
|
6841
|
+
}
|
|
6842
|
+
const record = value;
|
|
6843
|
+
return {
|
|
6844
|
+
ok: record.ok,
|
|
6845
|
+
planId: record.planId,
|
|
6846
|
+
operationCount: record.operationCount ?? record.operations?.length,
|
|
6847
|
+
requiredBackupCount: record.requiredBackups?.length ?? 0,
|
|
6848
|
+
requiredBackups: record.requiredBackups,
|
|
6849
|
+
diffSummary: record.diffSummary ?? summarizeDiffResult(record.diff),
|
|
6850
|
+
confirmationRequired: record.confirmationRequired,
|
|
6851
|
+
confirmationToken: record.confirmationToken,
|
|
6852
|
+
warnings: Array.isArray(record.warnings) ? record.warnings.length : record.warnings
|
|
6853
|
+
};
|
|
6854
|
+
}
|
|
6855
|
+
function summarizeOperationResult(value) {
|
|
6856
|
+
if (!value || typeof value !== "object") {
|
|
6857
|
+
return value;
|
|
6858
|
+
}
|
|
6859
|
+
const record = value;
|
|
6860
|
+
return {
|
|
6861
|
+
ok: record.ok,
|
|
6862
|
+
transactionId: record.transactionId,
|
|
6863
|
+
transactionStatus: record.transactionStatus,
|
|
6864
|
+
rollbackAvailable: record.rollbackAvailable,
|
|
6865
|
+
backups: record.backups,
|
|
6866
|
+
backup: summarizeBackup(record.backup),
|
|
6867
|
+
warnings: Array.isArray(record.warnings) ? record.warnings.length : record.warnings,
|
|
6868
|
+
telemetry: record.telemetry,
|
|
6869
|
+
error: record.error
|
|
6870
|
+
};
|
|
6871
|
+
}
|
|
6872
|
+
function compactResourceKindForTool(toolName) {
|
|
6873
|
+
if (toolName.includes(".snapshot.")) {
|
|
6874
|
+
return "snapshot";
|
|
6875
|
+
}
|
|
6876
|
+
if (toolName.includes(".diff.")) {
|
|
6877
|
+
return "diff";
|
|
6878
|
+
}
|
|
6879
|
+
if (toolName.includes(".validate.")) {
|
|
6880
|
+
return "validation";
|
|
6881
|
+
}
|
|
6882
|
+
if (isWorkbookMutatingMcpTool(toolName)) {
|
|
6883
|
+
return "mutation";
|
|
6884
|
+
}
|
|
6885
|
+
if (toolName.includes(".read") || toolName.includes(".lookup.") || toolName.includes(".summary") || toolName.includes(".schema")) {
|
|
6886
|
+
return "read";
|
|
6887
|
+
}
|
|
6888
|
+
return "generic";
|
|
6889
|
+
}
|
|
6890
|
+
function compactResultSummary(toolName, payload, payloadBytes, stored) {
|
|
6891
|
+
return {
|
|
6892
|
+
ok: payload.ok ?? true,
|
|
6893
|
+
toolName,
|
|
6894
|
+
detailLevel: "summary",
|
|
6895
|
+
budgetExceeded: true,
|
|
6896
|
+
payloadBytes: Math.min(payloadBytes, COMPACT_DEFAULT_RESOURCE_THRESHOLD_BYTES),
|
|
6897
|
+
estimatedTokens: Math.ceil(Math.min(payloadBytes, COMPACT_DEFAULT_RESOURCE_THRESHOLD_BYTES) / 4),
|
|
6898
|
+
resourcePayloadBytes: stored.payloadBytes,
|
|
6899
|
+
resourceEstimatedTokens: stored.estimatedTokens,
|
|
6900
|
+
resourceUri: stored.uri,
|
|
6901
|
+
summary: summarizePayloadForBudget(payload),
|
|
6902
|
+
warnings: [
|
|
6903
|
+
...asWarningArray(payload.warnings),
|
|
6904
|
+
{
|
|
6905
|
+
code: "COMPACT_RESULT_BUDGET_EXCEEDED",
|
|
6906
|
+
message: "Tool result exceeded compact response budget and was stored behind resourceUri."
|
|
6907
|
+
}
|
|
6908
|
+
]
|
|
6909
|
+
};
|
|
6910
|
+
}
|
|
6911
|
+
function summarizePayloadForBudget(payload) {
|
|
6912
|
+
return {
|
|
6913
|
+
transactionId: payload.transactionId,
|
|
6914
|
+
transactionStatus: payload.transactionStatus,
|
|
6915
|
+
taskId: payload.taskId,
|
|
6916
|
+
rollbackAvailable: payload.rollbackAvailable,
|
|
6917
|
+
backups: payload.backups,
|
|
6918
|
+
backup: summarizeBackup(payload.backup),
|
|
6919
|
+
snapshot: payload.snapshot && typeof payload.snapshot === "object" ? snapshotSummary(payload.snapshot) : undefined,
|
|
6920
|
+
diffSummary: payload.diffSummary ?? summarizeDiffResult(payload.diff),
|
|
6921
|
+
compactProof: payload.compactProof,
|
|
6922
|
+
telemetry: payload.telemetry,
|
|
6923
|
+
validation: validationSummary(payload),
|
|
6924
|
+
table: summarizeTablePayload(payload.table),
|
|
6925
|
+
source: summarizeSourcePayload(payload)
|
|
6926
|
+
};
|
|
6927
|
+
}
|
|
6928
|
+
function summarizeTablePayload(value) {
|
|
6929
|
+
if (!value || typeof value !== "object") {
|
|
6930
|
+
return undefined;
|
|
6931
|
+
}
|
|
6932
|
+
const table = value;
|
|
6933
|
+
return {
|
|
6934
|
+
info: table.info
|
|
6935
|
+
? {
|
|
6936
|
+
workbookId: table.info.workbookId,
|
|
6937
|
+
tableName: table.info.tableName,
|
|
6938
|
+
rowCount: table.info.rowCount,
|
|
6939
|
+
columnCount: table.info.columnCount
|
|
6940
|
+
}
|
|
6941
|
+
: undefined,
|
|
6942
|
+
headerCount: Array.isArray(table.headers?.[0]) ? table.headers[0].length : undefined,
|
|
6943
|
+
rowCount: Array.isArray(table.values) ? table.values.length : undefined,
|
|
6944
|
+
hasValues: Array.isArray(table.values),
|
|
6945
|
+
hasFormulas: Array.isArray(table.formulas),
|
|
6946
|
+
hasText: Array.isArray(table.text),
|
|
6947
|
+
hasNumberFormat: Array.isArray(table.numberFormat)
|
|
6948
|
+
};
|
|
6949
|
+
}
|
|
6950
|
+
function summarizeSourcePayload(payload) {
|
|
6951
|
+
if (Array.isArray(payload.data)) {
|
|
6952
|
+
return {
|
|
6953
|
+
dataCount: payload.data.length,
|
|
6954
|
+
snapshotCount: payload.data.filter((item) => Boolean(item.snapshot)).length
|
|
6955
|
+
};
|
|
6956
|
+
}
|
|
6957
|
+
return undefined;
|
|
6958
|
+
}
|
|
6959
|
+
function validationSummary(payload) {
|
|
6960
|
+
const issueCount = typeof payload.issueCount === "number" ? payload.issueCount : undefined;
|
|
6961
|
+
const issues = Array.isArray(payload.issues) ? payload.issues : undefined;
|
|
6962
|
+
const severityCounts = (payload.severityCounts ?? payload.summary);
|
|
6963
|
+
if (issueCount === undefined && issues === undefined && severityCounts === undefined) {
|
|
6964
|
+
return undefined;
|
|
6965
|
+
}
|
|
6966
|
+
return {
|
|
6967
|
+
issueCount: issueCount ?? issues?.length ?? 0,
|
|
6968
|
+
severityCounts
|
|
6969
|
+
};
|
|
6970
|
+
}
|
|
4657
6971
|
function jsonResource(uri, value) {
|
|
4658
6972
|
return {
|
|
4659
6973
|
contents: [
|
|
@@ -4691,6 +7005,16 @@ function readArg(name) {
|
|
|
4691
7005
|
}
|
|
4692
7006
|
return undefined;
|
|
4693
7007
|
}
|
|
7008
|
+
function parseToolNameList(value) {
|
|
7009
|
+
if (value === undefined || value.trim() === "") {
|
|
7010
|
+
return undefined;
|
|
7011
|
+
}
|
|
7012
|
+
const names = value
|
|
7013
|
+
.split(",")
|
|
7014
|
+
.map((item) => item.trim())
|
|
7015
|
+
.filter(Boolean);
|
|
7016
|
+
return names.length > 0 ? new Set(names) : undefined;
|
|
7017
|
+
}
|
|
4694
7018
|
function trimTrailingSlash(value) {
|
|
4695
7019
|
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
4696
7020
|
}
|