@components-kit/open-workbook 0.1.6 → 0.1.8
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.js +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 +1693 -2
- package/assets/mcp-server/dist/index.js.map +1 -1
- package/dist/index.js +36 -10
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
|
@@ -45,7 +45,14 @@ const STYLE_COPY_TOOL_DIMENSIONS = {
|
|
|
45
45
|
"excel.style.copy_hidden_rows_columns": "hiddenRowsColumns"
|
|
46
46
|
};
|
|
47
47
|
const runtime = await createRuntimeFacade();
|
|
48
|
-
const runtimeVersion = process.env.OPEN_WORKBOOK_VERSION ?? "0.1.
|
|
48
|
+
const runtimeVersion = process.env.OPEN_WORKBOOK_VERSION ?? "0.1.8";
|
|
49
|
+
const COMPACT_RESOURCE_LIMIT = 100;
|
|
50
|
+
const COMPACT_DEFAULT_RESOURCE_THRESHOLD_BYTES = 24_000;
|
|
51
|
+
const compactResources = new Map();
|
|
52
|
+
const compactCache = new Map();
|
|
53
|
+
let compactCacheInvalidationCount = 0;
|
|
54
|
+
let compactCacheLastInvalidatedAt;
|
|
55
|
+
let compactLastObservedEventId;
|
|
49
56
|
const server = new McpServer({
|
|
50
57
|
name: "open-workbook",
|
|
51
58
|
version: runtimeVersion
|
|
@@ -55,6 +62,7 @@ registerWorkbookTools(server);
|
|
|
55
62
|
registerBackupTools(server);
|
|
56
63
|
registerSheetTools(server);
|
|
57
64
|
registerRangeTools(server);
|
|
65
|
+
registerLookupTools(server);
|
|
58
66
|
registerBatchTools(server);
|
|
59
67
|
registerWorkflowTools(server);
|
|
60
68
|
registerPlanTools(server);
|
|
@@ -81,6 +89,7 @@ registerRepairTools(server);
|
|
|
81
89
|
registerSnapshotTools(server);
|
|
82
90
|
registerDiffTools(server);
|
|
83
91
|
registerEventTools(server);
|
|
92
|
+
registerCompactTools(server);
|
|
84
93
|
registerResources(server);
|
|
85
94
|
registerPrompts(server);
|
|
86
95
|
await server.connect(new StdioServerTransport());
|
|
@@ -211,6 +220,7 @@ function registerResources(mcp) {
|
|
|
211
220
|
const planId = resourceVariable(variables, "plan_id");
|
|
212
221
|
return runtime.getPlanDiffResource(workbookId, planId);
|
|
213
222
|
});
|
|
223
|
+
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
224
|
}
|
|
215
225
|
function registerJsonResource(mcp, name, uri, description, read) {
|
|
216
226
|
mcp.registerResource(name, uri, {
|
|
@@ -460,6 +470,26 @@ function registerWorkbookTools(mcp) {
|
|
|
460
470
|
openWorldHint: false
|
|
461
471
|
}
|
|
462
472
|
}, async () => jsonResult(await runtime.getWorkbookMap()));
|
|
473
|
+
registerMcpTool(mcp, "excel.workbook.get_summary", {
|
|
474
|
+
title: "Get compact workbook summary",
|
|
475
|
+
description: "Return compact workbook structure, sheet counts, table names, used-range dimensions, and token telemetry without cell bodies.",
|
|
476
|
+
inputSchema: { workbookId: z.string().optional() },
|
|
477
|
+
annotations: {
|
|
478
|
+
readOnlyHint: true,
|
|
479
|
+
destructiveHint: false,
|
|
480
|
+
openWorldHint: false
|
|
481
|
+
}
|
|
482
|
+
}, async ({ workbookId }) => jsonResult(await workbookSummary(workbookId)));
|
|
483
|
+
registerMcpTool(mcp, "excel.workbook.get_used_range_summary", {
|
|
484
|
+
title: "Get compact workbook used-range summary",
|
|
485
|
+
description: "Return used-range dimensions for workbook sheets without loading cell payloads.",
|
|
486
|
+
inputSchema: { workbookId: z.string().optional() },
|
|
487
|
+
annotations: {
|
|
488
|
+
readOnlyHint: true,
|
|
489
|
+
destructiveHint: false,
|
|
490
|
+
openWorldHint: false
|
|
491
|
+
}
|
|
492
|
+
}, async ({ workbookId }) => jsonResult(await workbookUsedRangeSummary(workbookId)));
|
|
463
493
|
registerMcpTool(mcp, "excel.workbook.snapshot", {
|
|
464
494
|
title: "Create workbook snapshot",
|
|
465
495
|
description: "Capture a restorable snapshot of used ranges or specific ranges.",
|
|
@@ -879,6 +909,12 @@ function registerSheetTools(mcp) {
|
|
|
879
909
|
inputSchema: { sheetName: z.string() },
|
|
880
910
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
881
911
|
}, async ({ sheetName }) => jsonResult(await selectSheetInfo(sheetName)));
|
|
912
|
+
registerMcpTool(mcp, "excel.sheet.get_summary", {
|
|
913
|
+
title: "Get compact worksheet summary",
|
|
914
|
+
description: "Return one worksheet's used-range dimensions, table names, and token telemetry without cell bodies.",
|
|
915
|
+
inputSchema: { workbookId: z.string().optional(), sheetName: z.string() },
|
|
916
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
917
|
+
}, async ({ workbookId, sheetName }) => jsonResult(await sheetSummary(sheetName, workbookId)));
|
|
882
918
|
registerMcpTool(mcp, "excel.sheet.get_used_range", {
|
|
883
919
|
title: "Get worksheet used range",
|
|
884
920
|
description: "Return the used range for a worksheet.",
|
|
@@ -986,6 +1022,46 @@ function registerRangeTools(mcp) {
|
|
|
986
1022
|
}
|
|
987
1023
|
}, async ({ workbookId, sheetName, address }) => jsonResult(await readRangeSnapshot(workbookId, sheetName, address, rangeReadFacets(name))));
|
|
988
1024
|
}
|
|
1025
|
+
const compactReadSchema = {
|
|
1026
|
+
...readSchema,
|
|
1027
|
+
mode: z.enum(["window", "summary", "sample"]).optional(),
|
|
1028
|
+
rowOffset: z.number().int().min(0).optional(),
|
|
1029
|
+
columnOffset: z.number().int().min(0).optional(),
|
|
1030
|
+
maxRows: z.number().int().min(0).max(10000).optional(),
|
|
1031
|
+
maxColumns: z.number().int().min(0).max(1000).optional(),
|
|
1032
|
+
maxCells: z.number().int().min(0).max(1000000).optional(),
|
|
1033
|
+
maxPayloadBytes: z.number().int().min(0).optional(),
|
|
1034
|
+
maxEstimatedTokens: z.number().int().min(0).optional(),
|
|
1035
|
+
includeValues: z.boolean().optional(),
|
|
1036
|
+
includeFormulas: z.boolean().optional(),
|
|
1037
|
+
includeText: z.boolean().optional(),
|
|
1038
|
+
includeNumberFormats: z.boolean().optional(),
|
|
1039
|
+
includeStyles: z.boolean().optional()
|
|
1040
|
+
};
|
|
1041
|
+
registerMcpTool(mcp, "excel.range.read_compact", {
|
|
1042
|
+
title: "Read compact Excel range",
|
|
1043
|
+
description: "Read a bounded values-first range window with opt-in facets, truncation metadata, and token telemetry.",
|
|
1044
|
+
inputSchema: compactReadSchema,
|
|
1045
|
+
annotations: {
|
|
1046
|
+
readOnlyHint: true,
|
|
1047
|
+
destructiveHint: false,
|
|
1048
|
+
openWorldHint: false
|
|
1049
|
+
}
|
|
1050
|
+
}, async (args) => jsonResult(await compactRangeRead(args)));
|
|
1051
|
+
registerMcpTool(mcp, "excel.range.get_summary", {
|
|
1052
|
+
title: "Get compact range summary",
|
|
1053
|
+
description: "Return range dimensions, cell count, default compact-read window, and token telemetry without cell bodies.",
|
|
1054
|
+
inputSchema: {
|
|
1055
|
+
workbookId: z.string(),
|
|
1056
|
+
sheetName: z.string(),
|
|
1057
|
+
address: z.string()
|
|
1058
|
+
},
|
|
1059
|
+
annotations: {
|
|
1060
|
+
readOnlyHint: true,
|
|
1061
|
+
destructiveHint: false,
|
|
1062
|
+
openWorldHint: false
|
|
1063
|
+
}
|
|
1064
|
+
}, async ({ workbookId, sheetName, address }) => jsonResult(rangeSummary(workbookId, sheetName, address)));
|
|
989
1065
|
for (const [name, method] of [
|
|
990
1066
|
["excel.range.read_hyperlinks", "range.read_hyperlinks"],
|
|
991
1067
|
["excel.range.read_comments", "range.read_comments"],
|
|
@@ -1214,6 +1290,111 @@ function registerRangeTools(mcp) {
|
|
|
1214
1290
|
}));
|
|
1215
1291
|
}
|
|
1216
1292
|
}
|
|
1293
|
+
function registerLookupTools(mcp) {
|
|
1294
|
+
const budgetSchema = {
|
|
1295
|
+
maxRows: z.number().int().min(0).max(10000).optional(),
|
|
1296
|
+
maxColumns: z.number().int().min(0).max(1000).optional(),
|
|
1297
|
+
maxCells: z.number().int().min(0).max(1000000).optional(),
|
|
1298
|
+
maxPayloadBytes: z.number().int().min(0).optional(),
|
|
1299
|
+
maxEstimatedTokens: z.number().int().min(0).optional()
|
|
1300
|
+
};
|
|
1301
|
+
const lookupAnnotations = { readOnlyHint: true, destructiveHint: false, openWorldHint: false };
|
|
1302
|
+
registerMcpTool(mcp, "excel.lookup.search_workbook", {
|
|
1303
|
+
title: "Search workbook compactly",
|
|
1304
|
+
description: "Search sheet names, table schemas, and used ranges across a workbook, returning ranked compact matches instead of full sheets.",
|
|
1305
|
+
inputSchema: {
|
|
1306
|
+
workbookId: z.string(),
|
|
1307
|
+
query: z.string(),
|
|
1308
|
+
sheetNames: z.array(z.string()).optional(),
|
|
1309
|
+
includeSheets: z.boolean().optional(),
|
|
1310
|
+
includeTables: z.boolean().optional(),
|
|
1311
|
+
completeMatch: z.boolean().optional(),
|
|
1312
|
+
matchCase: z.boolean().optional(),
|
|
1313
|
+
maxMatches: z.number().int().min(1).max(1000).optional(),
|
|
1314
|
+
maxPreviewRows: z.number().int().min(0).max(25).optional(),
|
|
1315
|
+
...budgetSchema
|
|
1316
|
+
},
|
|
1317
|
+
annotations: lookupAnnotations
|
|
1318
|
+
}, async (args) => jsonResult(await lookupSearchWorkbook(args)));
|
|
1319
|
+
registerMcpTool(mcp, "excel.lookup.find_headers", {
|
|
1320
|
+
title: "Find workbook headers compactly",
|
|
1321
|
+
description: "Find matching table columns and likely header cells from bounded top-of-sheet scans.",
|
|
1322
|
+
inputSchema: {
|
|
1323
|
+
workbookId: z.string(),
|
|
1324
|
+
query: z.string().optional(),
|
|
1325
|
+
headers: z.array(z.string()).optional(),
|
|
1326
|
+
sheetNames: z.array(z.string()).optional(),
|
|
1327
|
+
maxRowsPerSheet: z.number().int().min(1).max(100).optional(),
|
|
1328
|
+
maxMatches: z.number().int().min(1).max(1000).optional(),
|
|
1329
|
+
...budgetSchema
|
|
1330
|
+
},
|
|
1331
|
+
annotations: lookupAnnotations
|
|
1332
|
+
}, async (args) => jsonResult(await lookupFindHeaders(args)));
|
|
1333
|
+
registerMcpTool(mcp, "excel.lookup.find_tables_by_columns", {
|
|
1334
|
+
title: "Find tables by column set",
|
|
1335
|
+
description: "Rank workbook tables by required and optional column names without reading table rows.",
|
|
1336
|
+
inputSchema: {
|
|
1337
|
+
workbookId: z.string(),
|
|
1338
|
+
requiredColumns: z.array(z.string()),
|
|
1339
|
+
optionalColumns: z.array(z.string()).optional(),
|
|
1340
|
+
minScore: z.number().min(0).max(1).optional(),
|
|
1341
|
+
maxMatches: z.number().int().min(1).max(1000).optional(),
|
|
1342
|
+
...budgetSchema
|
|
1343
|
+
},
|
|
1344
|
+
annotations: lookupAnnotations
|
|
1345
|
+
}, async (args) => jsonResult(await lookupFindTablesByColumns(args)));
|
|
1346
|
+
registerMcpTool(mcp, "excel.lookup.find_entity", {
|
|
1347
|
+
title: "Find entity compactly",
|
|
1348
|
+
description: "Locate a text, number, date, or generic entity across workbook used ranges with compact ranked results.",
|
|
1349
|
+
inputSchema: {
|
|
1350
|
+
workbookId: z.string(),
|
|
1351
|
+
entity: z.string(),
|
|
1352
|
+
kind: z.enum(["text", "number", "date", "any"]).optional(),
|
|
1353
|
+
sheetNames: z.array(z.string()).optional(),
|
|
1354
|
+
completeMatch: z.boolean().optional(),
|
|
1355
|
+
matchCase: z.boolean().optional(),
|
|
1356
|
+
maxMatches: z.number().int().min(1).max(1000).optional(),
|
|
1357
|
+
maxPreviewRows: z.number().int().min(0).max(25).optional(),
|
|
1358
|
+
...budgetSchema
|
|
1359
|
+
},
|
|
1360
|
+
annotations: lookupAnnotations
|
|
1361
|
+
}, async (args) => jsonResult(await lookupFindEntity(args)));
|
|
1362
|
+
registerMcpTool(mcp, "excel.lookup.resolve_range", {
|
|
1363
|
+
title: "Resolve workbook range target",
|
|
1364
|
+
description: "Resolve a natural target string to a table, column, header, entity, sheet, or A1 range candidate before reading data.",
|
|
1365
|
+
inputSchema: {
|
|
1366
|
+
workbookId: z.string(),
|
|
1367
|
+
target: z.string(),
|
|
1368
|
+
kind: z.enum(["table", "column", "header", "entity", "range", "any"]).optional(),
|
|
1369
|
+
preferredSheetName: z.string().optional(),
|
|
1370
|
+
preferredTableName: z.string().optional(),
|
|
1371
|
+
maxMatches: z.number().int().min(1).max(1000).optional(),
|
|
1372
|
+
...budgetSchema
|
|
1373
|
+
},
|
|
1374
|
+
annotations: lookupAnnotations
|
|
1375
|
+
}, async (args) => jsonResult(await lookupResolveRange(args)));
|
|
1376
|
+
registerMcpTool(mcp, "excel.lookup.inspect_match", {
|
|
1377
|
+
title: "Inspect lookup match compactly",
|
|
1378
|
+
description: "Read a bounded preview for one lookup match, preserving compact telemetry and avoiding broad sheet reads.",
|
|
1379
|
+
inputSchema: {
|
|
1380
|
+
workbookId: z.string(),
|
|
1381
|
+
matchId: z.string().optional(),
|
|
1382
|
+
kind: z.enum(["sheet", "table", "column", "header", "entity", "range"]).optional(),
|
|
1383
|
+
sheetName: z.string().optional(),
|
|
1384
|
+
tableName: z.string().optional(),
|
|
1385
|
+
columnName: z.string().optional(),
|
|
1386
|
+
address: z.string().optional(),
|
|
1387
|
+
maxRows: z.number().int().min(0).max(10000).optional(),
|
|
1388
|
+
maxColumns: z.number().int().min(0).max(1000).optional(),
|
|
1389
|
+
includeValues: z.boolean().optional(),
|
|
1390
|
+
includeFormulas: z.boolean().optional(),
|
|
1391
|
+
includeText: z.boolean().optional(),
|
|
1392
|
+
maxPayloadBytes: z.number().int().min(0).optional(),
|
|
1393
|
+
maxEstimatedTokens: z.number().int().min(0).optional()
|
|
1394
|
+
},
|
|
1395
|
+
annotations: lookupAnnotations
|
|
1396
|
+
}, async (args) => jsonResult(await lookupInspectMatch(args)));
|
|
1397
|
+
}
|
|
1217
1398
|
function registerBatchTools(mcp) {
|
|
1218
1399
|
registerMcpTool(mcp, "excel.batch.validate", {
|
|
1219
1400
|
title: "Validate Excel batch",
|
|
@@ -2596,6 +2777,12 @@ function registerTableTools(mcp) {
|
|
|
2596
2777
|
inputSchema: tableSelectorSchema(),
|
|
2597
2778
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
2598
2779
|
}, async (args) => jsonResult(await runtime.getTableInfo(tableSelector(args))));
|
|
2780
|
+
registerMcpTool(mcp, "excel.table.get_schema", {
|
|
2781
|
+
title: "Get compact Excel table schema",
|
|
2782
|
+
description: "Return table columns, dimensions, style flags, and token telemetry without row data.",
|
|
2783
|
+
inputSchema: tableSelectorSchema(),
|
|
2784
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
2785
|
+
}, async (args) => jsonResult(await tableSchema(tableSelector(args))));
|
|
2599
2786
|
registerMcpTool(mcp, "excel.table.read", {
|
|
2600
2787
|
title: "Read Excel table",
|
|
2601
2788
|
description: "Read table headers, selected data facets, optional columns, optional row page, and metadata.",
|
|
@@ -2611,6 +2798,26 @@ function registerTableTools(mcp) {
|
|
|
2611
2798
|
},
|
|
2612
2799
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
2613
2800
|
}, async (args) => jsonResult(await runtime.readTable(tableReadRequest(args))));
|
|
2801
|
+
registerMcpTool(mcp, "excel.table.read_compact", {
|
|
2802
|
+
title: "Read compact Excel table",
|
|
2803
|
+
description: "Read a bounded values-first table page with projection, opt-in facets, truncation metadata, and token telemetry.",
|
|
2804
|
+
inputSchema: {
|
|
2805
|
+
...tableSelectorSchema(),
|
|
2806
|
+
mode: z.enum(["window", "summary", "sample"]).optional(),
|
|
2807
|
+
includeValues: z.boolean().optional(),
|
|
2808
|
+
includeFormulas: z.boolean().optional(),
|
|
2809
|
+
includeText: z.boolean().optional(),
|
|
2810
|
+
includeNumberFormats: z.boolean().optional(),
|
|
2811
|
+
columns: z.array(z.union([z.string(), z.number().int().min(0)])).optional(),
|
|
2812
|
+
rowOffset: z.number().int().min(0).optional(),
|
|
2813
|
+
maxRows: z.number().int().min(0).max(10000).optional(),
|
|
2814
|
+
maxColumns: z.number().int().min(0).max(1000).optional(),
|
|
2815
|
+
maxCells: z.number().int().min(0).max(1000000).optional(),
|
|
2816
|
+
maxPayloadBytes: z.number().int().min(0).optional(),
|
|
2817
|
+
maxEstimatedTokens: z.number().int().min(0).optional()
|
|
2818
|
+
},
|
|
2819
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
2820
|
+
}, async (args) => jsonResult(await compactTableRead(args)));
|
|
2614
2821
|
registerMcpTool(mcp, "excel.table.create", {
|
|
2615
2822
|
title: "Create Excel table",
|
|
2616
2823
|
description: "Create a structured table from a range, optionally writing values first.",
|
|
@@ -3641,6 +3848,36 @@ function registerValidateTools(mcp) {
|
|
|
3641
3848
|
inputSchema: { workbookId: z.string() },
|
|
3642
3849
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
3643
3850
|
}, async ({ workbookId }) => jsonResult(await runtime.validateWorkbook({ workbookId: workbookId })));
|
|
3851
|
+
registerMcpTool(mcp, "excel.validate.compact", {
|
|
3852
|
+
title: "Run compact validation",
|
|
3853
|
+
description: "Run a validator and return concise issue counts/examples with full details stored behind a compact resource URI.",
|
|
3854
|
+
inputSchema: {
|
|
3855
|
+
workbookId: z.string(),
|
|
3856
|
+
validator: z.enum([
|
|
3857
|
+
"workbook",
|
|
3858
|
+
"sheet",
|
|
3859
|
+
"formulas",
|
|
3860
|
+
"styles",
|
|
3861
|
+
"tables",
|
|
3862
|
+
"filters",
|
|
3863
|
+
"print_layout",
|
|
3864
|
+
"no_broken_references",
|
|
3865
|
+
"no_formula_errors",
|
|
3866
|
+
"no_unintended_changes"
|
|
3867
|
+
]),
|
|
3868
|
+
sheetName: z.string().optional(),
|
|
3869
|
+
address: z.string().optional(),
|
|
3870
|
+
tableName: z.string().optional(),
|
|
3871
|
+
templateId: z.string().optional(),
|
|
3872
|
+
snapshotId: z.string().optional(),
|
|
3873
|
+
leftSnapshotId: z.string().optional(),
|
|
3874
|
+
rightSnapshotId: z.string().optional(),
|
|
3875
|
+
maxIssues: z.number().int().min(0).max(100).optional(),
|
|
3876
|
+
maxPayloadBytes: z.number().int().min(0).optional(),
|
|
3877
|
+
maxEstimatedTokens: z.number().int().min(0).optional()
|
|
3878
|
+
},
|
|
3879
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
3880
|
+
}, async (args) => jsonResult(await compactValidation(args)));
|
|
3644
3881
|
registerMcpTool(mcp, "excel.validate.sheet", {
|
|
3645
3882
|
title: "Validate sheet",
|
|
3646
3883
|
description: "Validate one worksheet's used range and formula-error state.",
|
|
@@ -3876,6 +4113,12 @@ function registerSnapshotTools(mcp) {
|
|
|
3876
4113
|
inputSchema: { snapshotId: z.string() },
|
|
3877
4114
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
3878
4115
|
}, async ({ snapshotId }) => jsonResult(runtime.getSnapshot(snapshotId)));
|
|
4116
|
+
registerMcpTool(mcp, "excel.snapshot.get_compact", {
|
|
4117
|
+
title: "Get compact snapshot",
|
|
4118
|
+
description: "Return snapshot metadata and store full snapshot payload behind a compact resource URI.",
|
|
4119
|
+
inputSchema: { snapshotId: z.string(), maxPayloadBytes: z.number().int().min(0).optional(), maxEstimatedTokens: z.number().int().min(0).optional() },
|
|
4120
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
4121
|
+
}, ({ snapshotId, maxPayloadBytes, maxEstimatedTokens }) => jsonResult(compactSnapshot(snapshotId, maxPayloadBytes, maxEstimatedTokens)));
|
|
3879
4122
|
registerMcpTool(mcp, "excel.snapshot.list", {
|
|
3880
4123
|
title: "List snapshots",
|
|
3881
4124
|
description: "List stored snapshots for a workbook.",
|
|
@@ -3891,6 +4134,17 @@ function registerSnapshotTools(mcp) {
|
|
|
3891
4134
|
},
|
|
3892
4135
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
3893
4136
|
}, async ({ leftSnapshotId, rightSnapshotId }) => jsonResult(runtime.compareSnapshots(leftSnapshotId, rightSnapshotId)));
|
|
4137
|
+
registerMcpTool(mcp, "excel.snapshot.compare_compact", {
|
|
4138
|
+
title: "Compare snapshots compactly",
|
|
4139
|
+
description: "Return a compact snapshot diff summary and store full diff details behind a compact resource URI.",
|
|
4140
|
+
inputSchema: {
|
|
4141
|
+
leftSnapshotId: z.string(),
|
|
4142
|
+
rightSnapshotId: z.string(),
|
|
4143
|
+
maxPayloadBytes: z.number().int().min(0).optional(),
|
|
4144
|
+
maxEstimatedTokens: z.number().int().min(0).optional()
|
|
4145
|
+
},
|
|
4146
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
4147
|
+
}, ({ leftSnapshotId, rightSnapshotId, maxPayloadBytes, maxEstimatedTokens }) => jsonResult(compactSnapshotDiff(leftSnapshotId, rightSnapshotId, maxPayloadBytes, maxEstimatedTokens)));
|
|
3894
4148
|
registerMcpTool(mcp, "excel.snapshot.invalidate", {
|
|
3895
4149
|
title: "Invalidate snapshot",
|
|
3896
4150
|
description: "Mark a snapshot as stale without deleting it.",
|
|
@@ -3919,6 +4173,17 @@ function registerDiffTools(mcp) {
|
|
|
3919
4173
|
return jsonResult(name.endsWith("export_json") ? { ok: true, json: JSON.stringify(diff, null, 2) } : diff);
|
|
3920
4174
|
});
|
|
3921
4175
|
}
|
|
4176
|
+
registerMcpTool(mcp, "excel.diff.get_compact", {
|
|
4177
|
+
title: "Get compact diff",
|
|
4178
|
+
description: "Return a compact diff summary and store full diff details behind a compact resource URI.",
|
|
4179
|
+
inputSchema: {
|
|
4180
|
+
leftSnapshotId: z.string(),
|
|
4181
|
+
rightSnapshotId: z.string(),
|
|
4182
|
+
maxPayloadBytes: z.number().int().min(0).optional(),
|
|
4183
|
+
maxEstimatedTokens: z.number().int().min(0).optional()
|
|
4184
|
+
},
|
|
4185
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
4186
|
+
}, ({ leftSnapshotId, rightSnapshotId, maxPayloadBytes, maxEstimatedTokens }) => jsonResult(compactSnapshotDiff(leftSnapshotId, rightSnapshotId, maxPayloadBytes, maxEstimatedTokens)));
|
|
3922
4187
|
registerMcpTool(mcp, "excel.diff.export_html", {
|
|
3923
4188
|
title: "Export diff HTML",
|
|
3924
4189
|
description: "Return a small HTML representation of a snapshot diff.",
|
|
@@ -3965,6 +4230,44 @@ function registerEventTools(mcp) {
|
|
|
3965
4230
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
3966
4231
|
}, async ({ debounceMs }) => jsonResult(runtime.setEventDebounce(debounceMs)));
|
|
3967
4232
|
}
|
|
4233
|
+
function registerCompactTools(mcp) {
|
|
4234
|
+
registerMcpTool(mcp, "excel.compact.get_resource", {
|
|
4235
|
+
title: "Get compact detail resource",
|
|
4236
|
+
description: "Fetch a stored compact detail payload by resource id or excel://compact URI.",
|
|
4237
|
+
inputSchema: { resourceId: z.string().optional(), resourceUri: z.string().optional() },
|
|
4238
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
4239
|
+
}, ({ resourceId, resourceUri }) => jsonResult(getCompactResource(resourceId ?? compactResourceIdFromUri(resourceUri ?? ""))));
|
|
4240
|
+
registerMcpTool(mcp, "excel.compact.list_resources", {
|
|
4241
|
+
title: "List compact detail resources",
|
|
4242
|
+
description: "List stored compact detail resources without returning their payloads.",
|
|
4243
|
+
inputSchema: { kind: z.string().optional(), limit: z.number().int().min(1).max(COMPACT_RESOURCE_LIMIT).optional() },
|
|
4244
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
4245
|
+
}, ({ kind, limit }) => jsonResult(listCompactResources(kind, limit)));
|
|
4246
|
+
registerMcpTool(mcp, "excel.compact.delete_resource", {
|
|
4247
|
+
title: "Delete compact detail resource",
|
|
4248
|
+
description: "Delete one stored compact detail payload.",
|
|
4249
|
+
inputSchema: { resourceId: z.string().optional(), resourceUri: z.string().optional() },
|
|
4250
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
4251
|
+
}, ({ resourceId, resourceUri }) => jsonResult(deleteCompactResource(resourceId ?? compactResourceIdFromUri(resourceUri ?? ""))));
|
|
4252
|
+
registerMcpTool(mcp, "excel.compact.clear_resources", {
|
|
4253
|
+
title: "Clear compact detail resources",
|
|
4254
|
+
description: "Clear stored compact detail payloads from this MCP process.",
|
|
4255
|
+
inputSchema: { kind: z.string().optional() },
|
|
4256
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
4257
|
+
}, ({ kind }) => jsonResult(clearCompactResources(kind)));
|
|
4258
|
+
registerMcpTool(mcp, "excel.compact.get_cache_status", {
|
|
4259
|
+
title: "Get compact cache status",
|
|
4260
|
+
description: "Return compact summary/schema cache size and invalidation metadata.",
|
|
4261
|
+
inputSchema: {},
|
|
4262
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
4263
|
+
}, () => jsonResult(getCompactCacheStatus()));
|
|
4264
|
+
registerMcpTool(mcp, "excel.compact.clear_cache", {
|
|
4265
|
+
title: "Clear compact cache",
|
|
4266
|
+
description: "Clear compact summary/schema cache entries from this MCP process.",
|
|
4267
|
+
inputSchema: {},
|
|
4268
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
4269
|
+
}, () => jsonResult(clearCompactCache("manual")));
|
|
4270
|
+
}
|
|
3968
4271
|
function rangeReadFacets(toolName) {
|
|
3969
4272
|
switch (toolName) {
|
|
3970
4273
|
case "excel.range.read_values":
|
|
@@ -4048,6 +4351,1305 @@ function tableReadRequest(args) {
|
|
|
4048
4351
|
}
|
|
4049
4352
|
return request;
|
|
4050
4353
|
}
|
|
4354
|
+
function withCompactTelemetry(payload, options) {
|
|
4355
|
+
const originalPayloadBytes = Buffer.byteLength(JSON.stringify(payload), "utf8");
|
|
4356
|
+
const originalEstimatedTokens = Math.ceil(originalPayloadBytes / 4);
|
|
4357
|
+
const maxPayloadBytes = options.maxPayloadBytes ?? (options.budgetSummary !== undefined ? COMPACT_DEFAULT_RESOURCE_THRESHOLD_BYTES : undefined);
|
|
4358
|
+
const overBudget = (maxPayloadBytes !== undefined && originalPayloadBytes > maxPayloadBytes) ||
|
|
4359
|
+
(options.maxEstimatedTokens !== undefined && originalEstimatedTokens > options.maxEstimatedTokens);
|
|
4360
|
+
const stored = options.storeResource || overBudget
|
|
4361
|
+
? storeCompactResource(options.resourceKind ?? "generic", options.resourcePayload ?? payload, options.resourceTitle)
|
|
4362
|
+
: undefined;
|
|
4363
|
+
const output = overBudget && options.budgetSummary !== undefined
|
|
4364
|
+
? {
|
|
4365
|
+
...options.budgetSummary,
|
|
4366
|
+
budgetExceeded: true,
|
|
4367
|
+
warnings: [
|
|
4368
|
+
...asWarningArray(options.budgetSummary.warnings),
|
|
4369
|
+
{
|
|
4370
|
+
code: "COMPACT_BUDGET_EXCEEDED",
|
|
4371
|
+
message: "Full compact detail exceeded the response budget and was stored behind resourceUri."
|
|
4372
|
+
}
|
|
4373
|
+
],
|
|
4374
|
+
resourcePayloadBytes: originalPayloadBytes,
|
|
4375
|
+
resourceEstimatedTokens: originalEstimatedTokens
|
|
4376
|
+
}
|
|
4377
|
+
: payload;
|
|
4378
|
+
const payloadBytes = Buffer.byteLength(JSON.stringify(output), "utf8");
|
|
4379
|
+
return {
|
|
4380
|
+
...output,
|
|
4381
|
+
payloadBytes,
|
|
4382
|
+
estimatedTokens: Math.ceil(payloadBytes / 4),
|
|
4383
|
+
truncated: options.truncated ?? false,
|
|
4384
|
+
detailLevel: options.detailLevel,
|
|
4385
|
+
...(options.nextPage !== undefined ? { nextPage: options.nextPage } : {}),
|
|
4386
|
+
...(stored !== undefined ? { resourceUri: stored.uri } : options.resourceUri !== undefined ? { resourceUri: options.resourceUri } : {})
|
|
4387
|
+
};
|
|
4388
|
+
}
|
|
4389
|
+
function asWarningArray(value) {
|
|
4390
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "object" && item !== null) : [];
|
|
4391
|
+
}
|
|
4392
|
+
function storeCompactResource(kind, payload, title) {
|
|
4393
|
+
const resourceId = makeId("compact");
|
|
4394
|
+
const payloadBytes = Buffer.byteLength(JSON.stringify(payload), "utf8");
|
|
4395
|
+
const resource = {
|
|
4396
|
+
resourceId,
|
|
4397
|
+
uri: `excel://compact/${resourceId}`,
|
|
4398
|
+
kind,
|
|
4399
|
+
title,
|
|
4400
|
+
createdAt: new Date().toISOString(),
|
|
4401
|
+
payloadBytes,
|
|
4402
|
+
estimatedTokens: Math.ceil(payloadBytes / 4),
|
|
4403
|
+
payload
|
|
4404
|
+
};
|
|
4405
|
+
compactResources.set(resourceId, resource);
|
|
4406
|
+
while (compactResources.size > COMPACT_RESOURCE_LIMIT) {
|
|
4407
|
+
const oldestKey = compactResources.keys().next().value;
|
|
4408
|
+
if (oldestKey === undefined) {
|
|
4409
|
+
break;
|
|
4410
|
+
}
|
|
4411
|
+
compactResources.delete(oldestKey);
|
|
4412
|
+
}
|
|
4413
|
+
return resource;
|
|
4414
|
+
}
|
|
4415
|
+
function getCompactResource(resourceId) {
|
|
4416
|
+
const resource = compactResources.get(compactResourceIdFromUri(resourceId));
|
|
4417
|
+
if (!resource) {
|
|
4418
|
+
return { ok: false, error: { code: "COMPACT_RESOURCE_NOT_FOUND", message: "Compact detail resource was not found or has expired." } };
|
|
4419
|
+
}
|
|
4420
|
+
return { ok: true, ...resource };
|
|
4421
|
+
}
|
|
4422
|
+
function listCompactResources(kind, limit = COMPACT_RESOURCE_LIMIT) {
|
|
4423
|
+
const resources = [...compactResources.values()]
|
|
4424
|
+
.filter((resource) => kind === undefined || resource.kind === kind)
|
|
4425
|
+
.slice(-limit)
|
|
4426
|
+
.map(({ payload, ...summary }) => summary);
|
|
4427
|
+
return { ok: true, resources };
|
|
4428
|
+
}
|
|
4429
|
+
function deleteCompactResource(resourceId) {
|
|
4430
|
+
const normalized = compactResourceIdFromUri(resourceId);
|
|
4431
|
+
return { ok: compactResources.delete(normalized), resourceId: normalized };
|
|
4432
|
+
}
|
|
4433
|
+
function clearCompactResources(kind) {
|
|
4434
|
+
let deleted = 0;
|
|
4435
|
+
for (const [resourceId, resource] of compactResources) {
|
|
4436
|
+
if (kind === undefined || resource.kind === kind) {
|
|
4437
|
+
compactResources.delete(resourceId);
|
|
4438
|
+
deleted += 1;
|
|
4439
|
+
}
|
|
4440
|
+
}
|
|
4441
|
+
return { ok: true, deleted };
|
|
4442
|
+
}
|
|
4443
|
+
function compactResourceIdFromUri(value) {
|
|
4444
|
+
return value.startsWith("excel://compact/") ? value.slice("excel://compact/".length) : value;
|
|
4445
|
+
}
|
|
4446
|
+
async function compactCacheValue(key, producer) {
|
|
4447
|
+
await invalidateCompactCacheForWorkbookEvents();
|
|
4448
|
+
const cached = compactCache.get(key);
|
|
4449
|
+
if (cached !== undefined) {
|
|
4450
|
+
return cached.value;
|
|
4451
|
+
}
|
|
4452
|
+
const value = await producer();
|
|
4453
|
+
compactCache.set(key, { key, createdAt: new Date().toISOString(), value });
|
|
4454
|
+
return value;
|
|
4455
|
+
}
|
|
4456
|
+
async function invalidateCompactCacheForWorkbookEvents() {
|
|
4457
|
+
try {
|
|
4458
|
+
const recent = await runtime.getRecentEvents(1);
|
|
4459
|
+
const event = recent.events?.[0];
|
|
4460
|
+
if (!event?.eventId || event.eventId === compactLastObservedEventId) {
|
|
4461
|
+
return;
|
|
4462
|
+
}
|
|
4463
|
+
compactLastObservedEventId = event.eventId;
|
|
4464
|
+
if (event.method !== "addin.heartbeat") {
|
|
4465
|
+
clearCompactCache(`event:${event.method ?? "unknown"}`);
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
catch {
|
|
4469
|
+
// Compact caching must never make read-only workbook discovery fail.
|
|
4470
|
+
}
|
|
4471
|
+
}
|
|
4472
|
+
function getCompactCacheStatus() {
|
|
4473
|
+
return {
|
|
4474
|
+
ok: true,
|
|
4475
|
+
size: compactCache.size,
|
|
4476
|
+
invalidationCount: compactCacheInvalidationCount,
|
|
4477
|
+
lastInvalidatedAt: compactCacheLastInvalidatedAt,
|
|
4478
|
+
keys: [...compactCache.keys()]
|
|
4479
|
+
};
|
|
4480
|
+
}
|
|
4481
|
+
function clearCompactCache(reason) {
|
|
4482
|
+
const cleared = compactCache.size;
|
|
4483
|
+
compactCache.clear();
|
|
4484
|
+
compactCacheInvalidationCount += 1;
|
|
4485
|
+
compactCacheLastInvalidatedAt = new Date().toISOString();
|
|
4486
|
+
return { ok: true, cleared, reason, invalidationCount: compactCacheInvalidationCount, invalidatedAt: compactCacheLastInvalidatedAt };
|
|
4487
|
+
}
|
|
4488
|
+
async function workbookSummary(workbookId) {
|
|
4489
|
+
return compactCacheValue(`workbookSummary:${workbookId ?? "active"}`, async () => {
|
|
4490
|
+
const result = await runtime.getWorkbookMap();
|
|
4491
|
+
const map = "map" in result ? result.map : undefined;
|
|
4492
|
+
if (!result.ok || !map) {
|
|
4493
|
+
return withCompactTelemetry({ ok: false, workbookId, source: result }, { detailLevel: "summary" });
|
|
4494
|
+
}
|
|
4495
|
+
const sheets = map.sheets ?? [];
|
|
4496
|
+
const tableNames = sheets.flatMap((sheet) => (sheet.tables ?? []).map((table) => table.name));
|
|
4497
|
+
const usedRangeCells = sheets.reduce((sum, sheet) => sum + usedRangeCellCount(sheet.usedRange), 0);
|
|
4498
|
+
return withCompactTelemetry({
|
|
4499
|
+
ok: true,
|
|
4500
|
+
workbook: map.workbook,
|
|
4501
|
+
workbookId: workbookId ?? map.workbook?.workbookId,
|
|
4502
|
+
sheetCount: sheets.length,
|
|
4503
|
+
tableCount: tableNames.length,
|
|
4504
|
+
usedRangeCells,
|
|
4505
|
+
sheets: sheets.map((sheet) => ({
|
|
4506
|
+
name: sheet.name,
|
|
4507
|
+
position: sheet.position,
|
|
4508
|
+
visibility: sheet.visibility,
|
|
4509
|
+
usedRange: sheet.usedRange,
|
|
4510
|
+
tableCount: sheet.tables?.length ?? 0,
|
|
4511
|
+
tables: (sheet.tables ?? []).map((table) => table.name)
|
|
4512
|
+
}))
|
|
4513
|
+
}, { detailLevel: "summary" });
|
|
4514
|
+
});
|
|
4515
|
+
}
|
|
4516
|
+
async function workbookUsedRangeSummary(workbookId) {
|
|
4517
|
+
return compactCacheValue(`workbookUsedRangeSummary:${workbookId ?? "active"}`, async () => {
|
|
4518
|
+
const result = await runtime.getWorkbookMap();
|
|
4519
|
+
const map = "map" in result ? result.map : undefined;
|
|
4520
|
+
if (!result.ok || !map) {
|
|
4521
|
+
return withCompactTelemetry({ ok: false, workbookId, source: result }, { detailLevel: "summary" });
|
|
4522
|
+
}
|
|
4523
|
+
const sheets = map.sheets ?? [];
|
|
4524
|
+
return withCompactTelemetry({
|
|
4525
|
+
ok: true,
|
|
4526
|
+
workbookId: workbookId ?? map.workbook?.workbookId,
|
|
4527
|
+
usedRanges: sheets.map((sheet) => ({
|
|
4528
|
+
sheetName: sheet.name,
|
|
4529
|
+
usedRange: sheet.usedRange,
|
|
4530
|
+
cellCount: usedRangeCellCount(sheet.usedRange)
|
|
4531
|
+
})),
|
|
4532
|
+
totalCells: sheets.reduce((sum, sheet) => sum + usedRangeCellCount(sheet.usedRange), 0)
|
|
4533
|
+
}, { detailLevel: "summary" });
|
|
4534
|
+
});
|
|
4535
|
+
}
|
|
4536
|
+
async function sheetSummary(sheetName, workbookId) {
|
|
4537
|
+
return compactCacheValue(`sheetSummary:${workbookId ?? "active"}:${sheetName}`, async () => {
|
|
4538
|
+
const info = await selectSheetInfo(sheetName);
|
|
4539
|
+
const workbook = info.result.map?.workbook;
|
|
4540
|
+
return withCompactTelemetry({
|
|
4541
|
+
ok: info.ok,
|
|
4542
|
+
workbookId: workbookId ?? workbook?.workbookId,
|
|
4543
|
+
sheetName,
|
|
4544
|
+
usedRange: info.sheet?.usedRange,
|
|
4545
|
+
cellCount: usedRangeCellCount(info.sheet?.usedRange),
|
|
4546
|
+
tableCount: info.sheet?.tables?.length ?? 0,
|
|
4547
|
+
tables: (info.sheet?.tables ?? []).map((table) => table.name),
|
|
4548
|
+
sheet: info.sheet
|
|
4549
|
+
}, { detailLevel: "summary" });
|
|
4550
|
+
});
|
|
4551
|
+
}
|
|
4552
|
+
function rangeSummary(workbookId, sheetName, address) {
|
|
4553
|
+
const parsed = parseCompactA1Address(address);
|
|
4554
|
+
const rowCount = parsed.endRow - parsed.startRow + 1;
|
|
4555
|
+
const columnCount = parsed.endColumn - parsed.startColumn + 1;
|
|
4556
|
+
const defaultRows = Math.min(rowCount, 50);
|
|
4557
|
+
const defaultColumns = Math.min(columnCount, 25);
|
|
4558
|
+
return withCompactTelemetry({
|
|
4559
|
+
ok: true,
|
|
4560
|
+
workbookId,
|
|
4561
|
+
sheetName,
|
|
4562
|
+
address,
|
|
4563
|
+
rowCount,
|
|
4564
|
+
columnCount,
|
|
4565
|
+
cellCount: rowCount * columnCount,
|
|
4566
|
+
defaultCompactWindow: {
|
|
4567
|
+
address: compactWindowAddress(address, 0, 0, defaultRows, defaultColumns),
|
|
4568
|
+
rowCount: defaultRows,
|
|
4569
|
+
columnCount: defaultColumns,
|
|
4570
|
+
cellCount: defaultRows * defaultColumns
|
|
4571
|
+
}
|
|
4572
|
+
}, { detailLevel: "summary", truncated: rowCount > defaultRows || columnCount > defaultColumns });
|
|
4573
|
+
}
|
|
4574
|
+
async function compactRangeRead(args) {
|
|
4575
|
+
const mode = args.mode ?? "window";
|
|
4576
|
+
const parsed = parseCompactA1Address(args.address);
|
|
4577
|
+
const sourceRowCount = parsed.endRow - parsed.startRow + 1;
|
|
4578
|
+
const sourceColumnCount = parsed.endColumn - parsed.startColumn + 1;
|
|
4579
|
+
const rowOffset = Math.min(args.rowOffset ?? 0, sourceRowCount);
|
|
4580
|
+
const columnOffset = Math.min(args.columnOffset ?? 0, sourceColumnCount);
|
|
4581
|
+
const maxRows = args.maxRows ?? 50;
|
|
4582
|
+
const maxColumns = args.maxColumns ?? 25;
|
|
4583
|
+
const availableRows = Math.max(0, sourceRowCount - rowOffset);
|
|
4584
|
+
const availableColumns = Math.max(0, sourceColumnCount - columnOffset);
|
|
4585
|
+
const columnLimit = Math.min(maxColumns, availableColumns);
|
|
4586
|
+
const maxRowsByCells = args.maxCells !== undefined && columnLimit > 0 ? Math.floor(args.maxCells / columnLimit) : maxRows;
|
|
4587
|
+
const rowLimit = Math.min(maxRows, maxRowsByCells, availableRows);
|
|
4588
|
+
const windowAddress = rowLimit > 0 && columnLimit > 0
|
|
4589
|
+
? compactWindowAddress(args.address, rowOffset, columnOffset, rowLimit, columnLimit)
|
|
4590
|
+
: compactWindowAddress(args.address, rowOffset, columnOffset, 1, 1);
|
|
4591
|
+
const truncated = rowOffset + rowLimit < sourceRowCount || columnOffset + columnLimit < sourceColumnCount;
|
|
4592
|
+
const summary = {
|
|
4593
|
+
workbookId: args.workbookId,
|
|
4594
|
+
sheetName: args.sheetName,
|
|
4595
|
+
address: args.address,
|
|
4596
|
+
mode,
|
|
4597
|
+
source: {
|
|
4598
|
+
rowCount: sourceRowCount,
|
|
4599
|
+
columnCount: sourceColumnCount,
|
|
4600
|
+
cellCount: sourceRowCount * sourceColumnCount
|
|
4601
|
+
},
|
|
4602
|
+
window: {
|
|
4603
|
+
address: windowAddress,
|
|
4604
|
+
rowOffset,
|
|
4605
|
+
columnOffset,
|
|
4606
|
+
rowCount: rowLimit,
|
|
4607
|
+
columnCount: columnLimit,
|
|
4608
|
+
cellCount: rowLimit * columnLimit
|
|
4609
|
+
},
|
|
4610
|
+
sampled: mode === "sample"
|
|
4611
|
+
};
|
|
4612
|
+
const nextPage = truncated ? { rowOffset: rowOffset + rowLimit, columnOffset } : undefined;
|
|
4613
|
+
if (mode === "summary" || rowLimit === 0 || columnLimit === 0) {
|
|
4614
|
+
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 } });
|
|
4615
|
+
}
|
|
4616
|
+
const facets = compactRangeFacets(args);
|
|
4617
|
+
if (facets.length === 0) {
|
|
4618
|
+
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 } });
|
|
4619
|
+
}
|
|
4620
|
+
if (mode === "sample" && sourceRowCount > rowLimit) {
|
|
4621
|
+
const samples = await Promise.all(compactSampleWindows(sourceRowCount, rowLimit).map(async (sample) => {
|
|
4622
|
+
const sampleAddress = compactWindowAddress(args.address, sample.rowOffset, columnOffset, sample.rowCount, columnLimit);
|
|
4623
|
+
const sampleResult = await readRangeSnapshot(args.workbookId, args.sheetName, sampleAddress, facets);
|
|
4624
|
+
const sampleSnapshot = (sampleResult.data ?? [])[0]?.snapshot;
|
|
4625
|
+
return {
|
|
4626
|
+
label: sample.label,
|
|
4627
|
+
rowOffset: sample.rowOffset,
|
|
4628
|
+
rowCount: sample.rowCount,
|
|
4629
|
+
address: sampleAddress,
|
|
4630
|
+
fingerprint: sampleSnapshot?.fingerprint,
|
|
4631
|
+
values: sampleSnapshot?.values,
|
|
4632
|
+
formulas: sampleSnapshot?.formulas,
|
|
4633
|
+
text: sampleSnapshot?.text,
|
|
4634
|
+
numberFormat: sampleSnapshot?.numberFormat,
|
|
4635
|
+
style: sampleSnapshot?.style,
|
|
4636
|
+
ok: sampleResult.ok,
|
|
4637
|
+
warnings: sampleResult.warnings ?? []
|
|
4638
|
+
};
|
|
4639
|
+
}));
|
|
4640
|
+
return withCompactTelemetry({ ok: true, ...summary, samples }, {
|
|
4641
|
+
detailLevel: "compact",
|
|
4642
|
+
truncated,
|
|
4643
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
4644
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
4645
|
+
resourceKind: "read",
|
|
4646
|
+
resourceTitle: "Compact range sample",
|
|
4647
|
+
budgetSummary: { ok: true, ...summary, sampleCount: samples.length }
|
|
4648
|
+
});
|
|
4649
|
+
}
|
|
4650
|
+
const result = await readRangeSnapshot(args.workbookId, args.sheetName, windowAddress, facets);
|
|
4651
|
+
if (!result.ok) {
|
|
4652
|
+
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 } });
|
|
4653
|
+
}
|
|
4654
|
+
const snapshot = (result.data ?? [])[0]?.snapshot;
|
|
4655
|
+
return withCompactTelemetry({
|
|
4656
|
+
ok: true,
|
|
4657
|
+
...summary,
|
|
4658
|
+
fingerprint: snapshot?.fingerprint,
|
|
4659
|
+
values: snapshot?.values,
|
|
4660
|
+
formulas: snapshot?.formulas,
|
|
4661
|
+
text: snapshot?.text,
|
|
4662
|
+
numberFormat: snapshot?.numberFormat,
|
|
4663
|
+
style: snapshot?.style,
|
|
4664
|
+
warnings: result.warnings ?? [],
|
|
4665
|
+
telemetry: result.telemetry
|
|
4666
|
+
}, {
|
|
4667
|
+
detailLevel: "compact",
|
|
4668
|
+
truncated,
|
|
4669
|
+
nextPage,
|
|
4670
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
4671
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
4672
|
+
resourceKind: "read",
|
|
4673
|
+
resourceTitle: "Compact range read",
|
|
4674
|
+
budgetSummary: { ok: true, ...summary, warnings: result.warnings ?? [] }
|
|
4675
|
+
});
|
|
4676
|
+
}
|
|
4677
|
+
async function tableSchema(selector) {
|
|
4678
|
+
return compactCacheValue(`tableSchema:${selector.workbookId}:${selector.tableName}`, async () => {
|
|
4679
|
+
const result = await runtime.getTableInfo(selector);
|
|
4680
|
+
const info = result.info;
|
|
4681
|
+
return withCompactTelemetry({
|
|
4682
|
+
ok: Boolean(result.ok && info),
|
|
4683
|
+
workbookId: selector.workbookId,
|
|
4684
|
+
tableName: selector.tableName,
|
|
4685
|
+
schema: info ? tableInfoSchema(info) : undefined,
|
|
4686
|
+
source: info ? undefined : result
|
|
4687
|
+
}, { detailLevel: "summary" });
|
|
4688
|
+
});
|
|
4689
|
+
}
|
|
4690
|
+
async function compactTableRead(args) {
|
|
4691
|
+
const mode = args.mode ?? "window";
|
|
4692
|
+
const schemaResult = await runtime.getTableInfo(tableSelector(args));
|
|
4693
|
+
const info = schemaResult.info;
|
|
4694
|
+
if (!schemaResult.ok || !info) {
|
|
4695
|
+
return withCompactTelemetry({ ok: false, workbookId: args.workbookId, tableName: args.tableName, source: schemaResult }, { detailLevel: "summary" });
|
|
4696
|
+
}
|
|
4697
|
+
const selectedColumns = compactTableColumns(info, args.columns, args.maxColumns);
|
|
4698
|
+
const rowOffset = Math.min(args.rowOffset ?? 0, info.rowCount);
|
|
4699
|
+
const maxRows = args.maxRows ?? 50;
|
|
4700
|
+
const maxRowsByCells = args.maxCells !== undefined && selectedColumns.length > 0 ? Math.floor(args.maxCells / selectedColumns.length) : maxRows;
|
|
4701
|
+
const rowLimit = Math.min(maxRows, maxRowsByCells, Math.max(0, info.rowCount - rowOffset));
|
|
4702
|
+
const truncated = rowOffset + rowLimit < info.rowCount || selectedColumns.length < info.columnCount;
|
|
4703
|
+
const summary = {
|
|
4704
|
+
workbookId: args.workbookId,
|
|
4705
|
+
tableName: args.tableName,
|
|
4706
|
+
mode,
|
|
4707
|
+
schema: tableInfoSchema(info),
|
|
4708
|
+
rowOffset,
|
|
4709
|
+
rowLimit,
|
|
4710
|
+
projectedColumns: selectedColumns,
|
|
4711
|
+
sampled: mode === "sample"
|
|
4712
|
+
};
|
|
4713
|
+
const nextPage = truncated && rowOffset + rowLimit < info.rowCount ? { rowOffset: rowOffset + rowLimit } : undefined;
|
|
4714
|
+
if (mode === "summary" || rowLimit === 0 || selectedColumns.length === 0) {
|
|
4715
|
+
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 } });
|
|
4716
|
+
}
|
|
4717
|
+
if (mode === "sample" && info.rowCount > rowLimit) {
|
|
4718
|
+
const samples = await Promise.all(compactSampleWindows(info.rowCount, rowLimit).map(async (sample) => {
|
|
4719
|
+
const sampleResult = await runtime.readTable({
|
|
4720
|
+
...tableSelector(args),
|
|
4721
|
+
includeValues: args.includeValues ?? true,
|
|
4722
|
+
includeFormulas: args.includeFormulas === true,
|
|
4723
|
+
includeText: args.includeText === true,
|
|
4724
|
+
includeNumberFormats: args.includeNumberFormats === true,
|
|
4725
|
+
columns: selectedColumns.map((column) => column.name),
|
|
4726
|
+
rowOffset: sample.rowOffset,
|
|
4727
|
+
rowLimit: sample.rowCount
|
|
4728
|
+
});
|
|
4729
|
+
const sampleTable = sampleResult.table;
|
|
4730
|
+
return {
|
|
4731
|
+
label: sample.label,
|
|
4732
|
+
rowOffset: sample.rowOffset,
|
|
4733
|
+
rowCount: sample.rowCount,
|
|
4734
|
+
ok: sampleResult.ok,
|
|
4735
|
+
headers: sampleTable?.headers,
|
|
4736
|
+
values: sampleTable?.values,
|
|
4737
|
+
formulas: sampleTable?.formulas,
|
|
4738
|
+
text: sampleTable?.text,
|
|
4739
|
+
numberFormat: sampleTable?.numberFormat
|
|
4740
|
+
};
|
|
4741
|
+
}));
|
|
4742
|
+
return withCompactTelemetry({ ok: true, ...summary, samples }, {
|
|
4743
|
+
detailLevel: "compact",
|
|
4744
|
+
truncated,
|
|
4745
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
4746
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
4747
|
+
resourceKind: "read",
|
|
4748
|
+
resourceTitle: "Compact table sample",
|
|
4749
|
+
budgetSummary: { ok: true, ...summary, sampleCount: samples.length }
|
|
4750
|
+
});
|
|
4751
|
+
}
|
|
4752
|
+
const result = await runtime.readTable({
|
|
4753
|
+
...tableSelector(args),
|
|
4754
|
+
includeValues: args.includeValues ?? true,
|
|
4755
|
+
includeFormulas: args.includeFormulas === true,
|
|
4756
|
+
includeText: args.includeText === true,
|
|
4757
|
+
includeNumberFormats: args.includeNumberFormats === true,
|
|
4758
|
+
columns: selectedColumns.map((column) => column.name),
|
|
4759
|
+
rowOffset,
|
|
4760
|
+
rowLimit
|
|
4761
|
+
});
|
|
4762
|
+
const table = result.table;
|
|
4763
|
+
return withCompactTelemetry({
|
|
4764
|
+
ok: Boolean(result.ok),
|
|
4765
|
+
...summary,
|
|
4766
|
+
headers: table?.headers,
|
|
4767
|
+
values: table?.values,
|
|
4768
|
+
formulas: table?.formulas,
|
|
4769
|
+
text: table?.text,
|
|
4770
|
+
numberFormat: table?.numberFormat,
|
|
4771
|
+
source: result.ok ? undefined : result
|
|
4772
|
+
}, {
|
|
4773
|
+
detailLevel: "compact",
|
|
4774
|
+
truncated,
|
|
4775
|
+
nextPage,
|
|
4776
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
4777
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
4778
|
+
resourceKind: "read",
|
|
4779
|
+
resourceTitle: "Compact table read",
|
|
4780
|
+
budgetSummary: { ok: Boolean(result.ok), ...summary }
|
|
4781
|
+
});
|
|
4782
|
+
}
|
|
4783
|
+
async function lookupSearchWorkbook(args) {
|
|
4784
|
+
const workbookId = args.workbookId;
|
|
4785
|
+
const context = await lookupWorkbookContext(workbookId, args.sheetNames);
|
|
4786
|
+
if (!context.ok) {
|
|
4787
|
+
return withCompactTelemetry({ ok: false, workbookId, query: args.query, source: context.source }, { detailLevel: "summary" });
|
|
4788
|
+
}
|
|
4789
|
+
const matches = [];
|
|
4790
|
+
if (args.includeSheets !== false) {
|
|
4791
|
+
for (const sheet of context.sheets) {
|
|
4792
|
+
if (lookupMatches(String(sheet.name), args.query, args)) {
|
|
4793
|
+
matches.push(lookupMatch({
|
|
4794
|
+
kind: "sheet",
|
|
4795
|
+
sheetName: sheet.name,
|
|
4796
|
+
address: sheet.usedRange?.address,
|
|
4797
|
+
score: lookupScore(sheet.name, args.query, args.completeMatch),
|
|
4798
|
+
reason: "sheet name"
|
|
4799
|
+
}));
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
}
|
|
4803
|
+
if (args.includeTables !== false) {
|
|
4804
|
+
matches.push(...lookupTableMatches(context.tables, args.query, args));
|
|
4805
|
+
}
|
|
4806
|
+
matches.push(...await lookupUsedRangeMatches(workbookId, context.sheets, args.query, args, "range"));
|
|
4807
|
+
return compactLookupResponse({
|
|
4808
|
+
workbookId,
|
|
4809
|
+
query: args.query,
|
|
4810
|
+
matches,
|
|
4811
|
+
maxMatches: args.maxMatches ?? 25,
|
|
4812
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
4813
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
4814
|
+
resourceTitle: `Workbook lookup: ${args.query}`
|
|
4815
|
+
});
|
|
4816
|
+
}
|
|
4817
|
+
async function lookupFindHeaders(args) {
|
|
4818
|
+
const workbookId = args.workbookId;
|
|
4819
|
+
const terms = lookupTerms(args.headers ?? (args.query ? [args.query] : []));
|
|
4820
|
+
const context = await lookupWorkbookContext(workbookId, args.sheetNames);
|
|
4821
|
+
if (!context.ok) {
|
|
4822
|
+
return withCompactTelemetry({ ok: false, workbookId, query: args.query, headers: args.headers, source: context.source }, { detailLevel: "summary" });
|
|
4823
|
+
}
|
|
4824
|
+
const matches = [];
|
|
4825
|
+
for (const table of context.tables) {
|
|
4826
|
+
for (const column of table.columns ?? []) {
|
|
4827
|
+
const columnName = String(column.name ?? "");
|
|
4828
|
+
if (terms.length === 0 || terms.some((term) => lookupMatches(columnName, term, args))) {
|
|
4829
|
+
matches.push(lookupMatch({
|
|
4830
|
+
kind: "header",
|
|
4831
|
+
sheetName: table.sheetName,
|
|
4832
|
+
tableName: table.tableName,
|
|
4833
|
+
columnName,
|
|
4834
|
+
address: table.headerAddress ?? table.address,
|
|
4835
|
+
score: terms.length === 0 ? 0.55 : Math.max(...terms.map((term) => lookupScore(columnName, term, false))),
|
|
4836
|
+
reason: "table header",
|
|
4837
|
+
schema: { tableName: table.tableName, column }
|
|
4838
|
+
}));
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
}
|
|
4842
|
+
const maxRowsPerSheet = args.maxRowsPerSheet ?? 10;
|
|
4843
|
+
const maxColumns = args.maxColumns ?? 50;
|
|
4844
|
+
for (const sheet of context.sheets) {
|
|
4845
|
+
const usedAddress = sheet.usedRange?.address;
|
|
4846
|
+
if (!usedAddress) {
|
|
4847
|
+
continue;
|
|
4848
|
+
}
|
|
4849
|
+
try {
|
|
4850
|
+
const result = await compactRangeRead({
|
|
4851
|
+
workbookId,
|
|
4852
|
+
sheetName: sheet.name,
|
|
4853
|
+
address: usedAddress,
|
|
4854
|
+
mode: "window",
|
|
4855
|
+
maxRows: maxRowsPerSheet,
|
|
4856
|
+
maxColumns,
|
|
4857
|
+
includeValues: true,
|
|
4858
|
+
includeText: true
|
|
4859
|
+
});
|
|
4860
|
+
const rows = lookupRowsFromCompactRead(result);
|
|
4861
|
+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex += 1) {
|
|
4862
|
+
for (let columnIndex = 0; columnIndex < (rows[rowIndex] ?? []).length; columnIndex += 1) {
|
|
4863
|
+
const value = rows[rowIndex]?.[columnIndex];
|
|
4864
|
+
const text = value === undefined || value === null ? "" : String(value);
|
|
4865
|
+
if (text === "" || (terms.length > 0 && !terms.some((term) => lookupMatches(text, term, args)))) {
|
|
4866
|
+
continue;
|
|
4867
|
+
}
|
|
4868
|
+
matches.push(lookupMatch({
|
|
4869
|
+
kind: "header",
|
|
4870
|
+
sheetName: sheet.name,
|
|
4871
|
+
address: compactWindowAddress(usedAddress, rowIndex, columnIndex, 1, 1),
|
|
4872
|
+
columnName: text,
|
|
4873
|
+
score: terms.length === 0 ? Math.max(0.15, 0.5 - rowIndex * 0.03) : Math.max(...terms.map((term) => lookupScore(text, term, false))),
|
|
4874
|
+
reason: `bounded sheet header scan row ${rowIndex + 1}`
|
|
4875
|
+
}));
|
|
4876
|
+
}
|
|
4877
|
+
}
|
|
4878
|
+
}
|
|
4879
|
+
catch {
|
|
4880
|
+
// Lookup should remain useful even when one sheet cannot be scanned.
|
|
4881
|
+
}
|
|
4882
|
+
}
|
|
4883
|
+
return compactLookupResponse({
|
|
4884
|
+
workbookId,
|
|
4885
|
+
query: args.query,
|
|
4886
|
+
headers: args.headers,
|
|
4887
|
+
matches,
|
|
4888
|
+
maxMatches: args.maxMatches ?? 25,
|
|
4889
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
4890
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
4891
|
+
resourceTitle: "Header lookup"
|
|
4892
|
+
});
|
|
4893
|
+
}
|
|
4894
|
+
async function lookupFindTablesByColumns(args) {
|
|
4895
|
+
const workbookId = args.workbookId;
|
|
4896
|
+
const context = await lookupWorkbookContext(workbookId);
|
|
4897
|
+
if (!context.ok) {
|
|
4898
|
+
return withCompactTelemetry({ ok: false, workbookId, requiredColumns: args.requiredColumns, source: context.source }, { detailLevel: "summary" });
|
|
4899
|
+
}
|
|
4900
|
+
const required = lookupTerms(args.requiredColumns);
|
|
4901
|
+
const optional = lookupTerms(args.optionalColumns ?? []);
|
|
4902
|
+
const matches = context.tables
|
|
4903
|
+
.map((table) => {
|
|
4904
|
+
const columns = (table.columns ?? []).map((column) => String(column.name ?? ""));
|
|
4905
|
+
const requiredHits = required.filter((term) => columns.some((column) => lookupNormalized(column) === term || lookupNormalized(column).includes(term)));
|
|
4906
|
+
if (required.length > 0 && requiredHits.length < required.length) {
|
|
4907
|
+
return undefined;
|
|
4908
|
+
}
|
|
4909
|
+
const optionalHits = optional.filter((term) => columns.some((column) => lookupNormalized(column) === term || lookupNormalized(column).includes(term)));
|
|
4910
|
+
const denominator = Math.max(1, required.length + optional.length * 0.5);
|
|
4911
|
+
const score = Math.min(1, (requiredHits.length + optionalHits.length * 0.5) / denominator);
|
|
4912
|
+
if (score < (args.minScore ?? 0)) {
|
|
4913
|
+
return undefined;
|
|
4914
|
+
}
|
|
4915
|
+
return lookupMatch({
|
|
4916
|
+
kind: "table",
|
|
4917
|
+
sheetName: table.sheetName,
|
|
4918
|
+
tableName: table.tableName,
|
|
4919
|
+
address: table.address,
|
|
4920
|
+
score,
|
|
4921
|
+
reason: `matched ${requiredHits.length}/${required.length} required and ${optionalHits.length}/${optional.length} optional columns`,
|
|
4922
|
+
schema: tableInfoSchema(table)
|
|
4923
|
+
});
|
|
4924
|
+
})
|
|
4925
|
+
.filter((match) => match !== undefined);
|
|
4926
|
+
return compactLookupResponse({
|
|
4927
|
+
workbookId,
|
|
4928
|
+
requiredColumns: args.requiredColumns,
|
|
4929
|
+
optionalColumns: args.optionalColumns,
|
|
4930
|
+
matches,
|
|
4931
|
+
maxMatches: args.maxMatches ?? 25,
|
|
4932
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
4933
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
4934
|
+
resourceTitle: "Table column lookup"
|
|
4935
|
+
});
|
|
4936
|
+
}
|
|
4937
|
+
async function lookupFindEntity(args) {
|
|
4938
|
+
const workbookId = args.workbookId;
|
|
4939
|
+
const context = await lookupWorkbookContext(workbookId, args.sheetNames);
|
|
4940
|
+
if (!context.ok) {
|
|
4941
|
+
return withCompactTelemetry({ ok: false, workbookId, entity: args.entity, source: context.source }, { detailLevel: "summary" });
|
|
4942
|
+
}
|
|
4943
|
+
const matches = await lookupUsedRangeMatches(workbookId, context.sheets, args.entity, args, "entity");
|
|
4944
|
+
return compactLookupResponse({
|
|
4945
|
+
workbookId,
|
|
4946
|
+
entity: args.entity,
|
|
4947
|
+
entityKind: args.kind ?? "any",
|
|
4948
|
+
matches,
|
|
4949
|
+
maxMatches: args.maxMatches ?? 25,
|
|
4950
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
4951
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
4952
|
+
resourceTitle: `Entity lookup: ${args.entity}`
|
|
4953
|
+
});
|
|
4954
|
+
}
|
|
4955
|
+
async function lookupResolveRange(args) {
|
|
4956
|
+
const workbookId = args.workbookId;
|
|
4957
|
+
const kind = args.kind ?? "any";
|
|
4958
|
+
const context = await lookupWorkbookContext(workbookId, args.preferredSheetName ? [args.preferredSheetName] : undefined);
|
|
4959
|
+
if (!context.ok) {
|
|
4960
|
+
return withCompactTelemetry({ ok: false, workbookId, target: args.target, source: context.source }, { detailLevel: "summary" });
|
|
4961
|
+
}
|
|
4962
|
+
const matches = [];
|
|
4963
|
+
const parsedRange = lookupParseRangeTarget(args.target, args.preferredSheetName);
|
|
4964
|
+
if ((kind === "any" || kind === "range") && parsedRange) {
|
|
4965
|
+
matches.push(lookupMatch({
|
|
4966
|
+
kind: "range",
|
|
4967
|
+
sheetName: parsedRange.sheetName,
|
|
4968
|
+
address: parsedRange.address,
|
|
4969
|
+
score: 1,
|
|
4970
|
+
reason: "explicit A1 range"
|
|
4971
|
+
}));
|
|
4972
|
+
}
|
|
4973
|
+
if (kind === "any" || kind === "table" || kind === "column") {
|
|
4974
|
+
for (const table of context.tables) {
|
|
4975
|
+
if (args.preferredTableName && table.tableName !== args.preferredTableName) {
|
|
4976
|
+
continue;
|
|
4977
|
+
}
|
|
4978
|
+
if ((kind === "any" || kind === "table") && lookupMatches(String(table.tableName), args.target, args)) {
|
|
4979
|
+
matches.push(lookupMatch({
|
|
4980
|
+
kind: "table",
|
|
4981
|
+
sheetName: table.sheetName,
|
|
4982
|
+
tableName: table.tableName,
|
|
4983
|
+
address: table.address,
|
|
4984
|
+
score: lookupScore(table.tableName, args.target, false),
|
|
4985
|
+
reason: "table name",
|
|
4986
|
+
schema: tableInfoSchema(table)
|
|
4987
|
+
}));
|
|
4988
|
+
}
|
|
4989
|
+
if (kind === "any" || kind === "column") {
|
|
4990
|
+
for (const column of table.columns ?? []) {
|
|
4991
|
+
if (lookupMatches(String(column.name), args.target, args)) {
|
|
4992
|
+
matches.push(lookupMatch({
|
|
4993
|
+
kind: "column",
|
|
4994
|
+
sheetName: table.sheetName,
|
|
4995
|
+
tableName: table.tableName,
|
|
4996
|
+
columnName: column.name,
|
|
4997
|
+
address: table.address,
|
|
4998
|
+
score: lookupScore(column.name, args.target, false),
|
|
4999
|
+
reason: "table column",
|
|
5000
|
+
schema: { tableName: table.tableName, column }
|
|
5001
|
+
}));
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
}
|
|
5005
|
+
}
|
|
5006
|
+
}
|
|
5007
|
+
if (kind === "any" || kind === "header") {
|
|
5008
|
+
const headerRequest = {
|
|
5009
|
+
workbookId,
|
|
5010
|
+
query: args.target,
|
|
5011
|
+
maxRowsPerSheet: 10
|
|
5012
|
+
};
|
|
5013
|
+
if (args.preferredSheetName !== undefined) {
|
|
5014
|
+
headerRequest.sheetNames = [args.preferredSheetName];
|
|
5015
|
+
}
|
|
5016
|
+
if (args.maxMatches !== undefined) {
|
|
5017
|
+
headerRequest.maxMatches = args.maxMatches;
|
|
5018
|
+
}
|
|
5019
|
+
const headerResult = await lookupFindHeaders(headerRequest);
|
|
5020
|
+
matches.push(...(headerResult.matches ?? []));
|
|
5021
|
+
}
|
|
5022
|
+
if (kind === "any" || kind === "entity") {
|
|
5023
|
+
matches.push(...await lookupUsedRangeMatches(workbookId, context.sheets, args.target, args, "entity"));
|
|
5024
|
+
}
|
|
5025
|
+
return compactLookupResponse({
|
|
5026
|
+
workbookId,
|
|
5027
|
+
target: args.target,
|
|
5028
|
+
targetKind: kind,
|
|
5029
|
+
matches,
|
|
5030
|
+
maxMatches: args.maxMatches ?? 10,
|
|
5031
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
5032
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
5033
|
+
resourceTitle: `Range resolution: ${args.target}`
|
|
5034
|
+
});
|
|
5035
|
+
}
|
|
5036
|
+
async function lookupInspectMatch(args) {
|
|
5037
|
+
const decoded = args.matchId ? lookupDecodeMatchId(args.matchId) : undefined;
|
|
5038
|
+
const match = { ...decoded, ...args };
|
|
5039
|
+
const workbookId = args.workbookId;
|
|
5040
|
+
if (match.tableName) {
|
|
5041
|
+
const request = {
|
|
5042
|
+
workbookId,
|
|
5043
|
+
tableName: match.tableName,
|
|
5044
|
+
mode: "window",
|
|
5045
|
+
maxRows: args.maxRows ?? 10,
|
|
5046
|
+
maxColumns: args.maxColumns ?? 25,
|
|
5047
|
+
includeValues: args.includeValues ?? true,
|
|
5048
|
+
includeFormulas: args.includeFormulas === true,
|
|
5049
|
+
includeText: args.includeText === true
|
|
5050
|
+
};
|
|
5051
|
+
if (match.columnName !== undefined) {
|
|
5052
|
+
request.columns = [match.columnName];
|
|
5053
|
+
}
|
|
5054
|
+
if (args.maxPayloadBytes !== undefined) {
|
|
5055
|
+
request.maxPayloadBytes = args.maxPayloadBytes;
|
|
5056
|
+
}
|
|
5057
|
+
if (args.maxEstimatedTokens !== undefined) {
|
|
5058
|
+
request.maxEstimatedTokens = args.maxEstimatedTokens;
|
|
5059
|
+
}
|
|
5060
|
+
return compactTableRead(request);
|
|
5061
|
+
}
|
|
5062
|
+
let sheetName = match.sheetName;
|
|
5063
|
+
let address = match.address;
|
|
5064
|
+
if (sheetName && !address) {
|
|
5065
|
+
const context = await lookupWorkbookContext(workbookId, [sheetName]);
|
|
5066
|
+
address = context.sheets[0]?.usedRange?.address;
|
|
5067
|
+
}
|
|
5068
|
+
if (sheetName && address) {
|
|
5069
|
+
const request = {
|
|
5070
|
+
workbookId,
|
|
5071
|
+
sheetName,
|
|
5072
|
+
address,
|
|
5073
|
+
mode: "window",
|
|
5074
|
+
maxRows: args.maxRows ?? 10,
|
|
5075
|
+
maxColumns: args.maxColumns ?? 25,
|
|
5076
|
+
includeValues: args.includeValues ?? true,
|
|
5077
|
+
includeFormulas: args.includeFormulas === true,
|
|
5078
|
+
includeText: args.includeText === true
|
|
5079
|
+
};
|
|
5080
|
+
if (args.maxPayloadBytes !== undefined) {
|
|
5081
|
+
request.maxPayloadBytes = args.maxPayloadBytes;
|
|
5082
|
+
}
|
|
5083
|
+
if (args.maxEstimatedTokens !== undefined) {
|
|
5084
|
+
request.maxEstimatedTokens = args.maxEstimatedTokens;
|
|
5085
|
+
}
|
|
5086
|
+
return compactRangeRead(request);
|
|
5087
|
+
}
|
|
5088
|
+
return withCompactTelemetry({
|
|
5089
|
+
ok: false,
|
|
5090
|
+
workbookId,
|
|
5091
|
+
error: {
|
|
5092
|
+
code: "LOOKUP_MATCH_TARGET_REQUIRED",
|
|
5093
|
+
message: "Provide matchId from a lookup response or direct sheetName/address/tableName fields."
|
|
5094
|
+
}
|
|
5095
|
+
}, { detailLevel: "summary" });
|
|
5096
|
+
}
|
|
5097
|
+
async function compactValidation(args) {
|
|
5098
|
+
const workbookId = args.workbookId;
|
|
5099
|
+
const report = await runCompactValidator(args, workbookId);
|
|
5100
|
+
const issues = Array.isArray(report.issues) ? report.issues : [];
|
|
5101
|
+
const maxIssues = args.maxIssues ?? 5;
|
|
5102
|
+
const summary = {
|
|
5103
|
+
ok: report.ok,
|
|
5104
|
+
workbookId,
|
|
5105
|
+
validator: args.validator,
|
|
5106
|
+
scope: report.scope,
|
|
5107
|
+
issueCount: report.issueCount ?? issues.length,
|
|
5108
|
+
severityCounts: compactSeverityCounts(issues),
|
|
5109
|
+
categories: compactIssueCategories(issues),
|
|
5110
|
+
examples: issues.slice(0, maxIssues).map(compactIssueExample),
|
|
5111
|
+
examplesTruncated: issues.length > maxIssues
|
|
5112
|
+
};
|
|
5113
|
+
return withCompactTelemetry(summary, {
|
|
5114
|
+
detailLevel: "summary",
|
|
5115
|
+
truncated: issues.length > maxIssues,
|
|
5116
|
+
storeResource: true,
|
|
5117
|
+
resourceKind: "validation",
|
|
5118
|
+
resourceTitle: `Validation detail: ${args.validator}`,
|
|
5119
|
+
resourcePayload: report,
|
|
5120
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
5121
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
5122
|
+
budgetSummary: summary
|
|
5123
|
+
});
|
|
5124
|
+
}
|
|
5125
|
+
function compactSnapshot(snapshotId, maxPayloadBytes, maxEstimatedTokens) {
|
|
5126
|
+
const result = runtime.getSnapshot(snapshotId);
|
|
5127
|
+
const snapshot = result.snapshot;
|
|
5128
|
+
const summary = snapshot
|
|
5129
|
+
? {
|
|
5130
|
+
ok: true,
|
|
5131
|
+
snapshotId,
|
|
5132
|
+
workbookId: snapshot.workbookId,
|
|
5133
|
+
createdAt: snapshot.createdAt,
|
|
5134
|
+
reason: snapshot.reason,
|
|
5135
|
+
affectedRangeCount: snapshot.affectedRanges?.length ?? 0,
|
|
5136
|
+
affectedRanges: snapshot.affectedRanges,
|
|
5137
|
+
payloadRangeCount: snapshot.payload?.rangeSnapshots?.length ?? 0
|
|
5138
|
+
}
|
|
5139
|
+
: { ok: false, snapshotId, error: result.error };
|
|
5140
|
+
return withCompactTelemetry(summary, {
|
|
5141
|
+
detailLevel: "summary",
|
|
5142
|
+
truncated: false,
|
|
5143
|
+
storeResource: Boolean(snapshot),
|
|
5144
|
+
resourceKind: "snapshot",
|
|
5145
|
+
resourceTitle: `Snapshot detail: ${snapshotId}`,
|
|
5146
|
+
resourcePayload: result,
|
|
5147
|
+
maxPayloadBytes,
|
|
5148
|
+
maxEstimatedTokens,
|
|
5149
|
+
budgetSummary: summary
|
|
5150
|
+
});
|
|
5151
|
+
}
|
|
5152
|
+
function compactSnapshotDiff(leftSnapshotId, rightSnapshotId, maxPayloadBytes, maxEstimatedTokens) {
|
|
5153
|
+
const result = runtime.compareSnapshots(leftSnapshotId, rightSnapshotId);
|
|
5154
|
+
const diff = result.diff;
|
|
5155
|
+
const summary = diff
|
|
5156
|
+
? {
|
|
5157
|
+
ok: true,
|
|
5158
|
+
leftSnapshotId,
|
|
5159
|
+
rightSnapshotId,
|
|
5160
|
+
title: diff.summary?.title,
|
|
5161
|
+
changedRangeCount: diff.summary?.changedRanges?.length ?? diff.changedRanges?.length ?? 0,
|
|
5162
|
+
cellsChanged: diff.summary?.cellsChanged ?? diff.cellsChanged,
|
|
5163
|
+
formulasChanged: diff.summary?.formulasChanged ?? diff.formulasChanged,
|
|
5164
|
+
stylesChanged: diff.summary?.stylesChanged ?? diff.stylesChanged,
|
|
5165
|
+
tablesChanged: diff.summary?.tablesChanged ?? diff.tablesChanged,
|
|
5166
|
+
sheetsChanged: diff.summary?.sheetsChanged ?? diff.sheetsChanged,
|
|
5167
|
+
destructiveLevel: diff.summary?.destructiveLevel ?? diff.destructiveLevel,
|
|
5168
|
+
changedRanges: (diff.summary?.changedRanges ?? diff.changedRanges ?? []).slice(0, 20),
|
|
5169
|
+
changedRangesTruncated: (diff.summary?.changedRanges ?? diff.changedRanges ?? []).length > 20
|
|
5170
|
+
}
|
|
5171
|
+
: { ok: false, leftSnapshotId, rightSnapshotId, error: result.error };
|
|
5172
|
+
return withCompactTelemetry(summary, {
|
|
5173
|
+
detailLevel: "summary",
|
|
5174
|
+
truncated: Boolean(summary.changedRangesTruncated),
|
|
5175
|
+
storeResource: Boolean(diff),
|
|
5176
|
+
resourceKind: "diff",
|
|
5177
|
+
resourceTitle: `Snapshot diff: ${leftSnapshotId}..${rightSnapshotId}`,
|
|
5178
|
+
resourcePayload: result,
|
|
5179
|
+
maxPayloadBytes,
|
|
5180
|
+
maxEstimatedTokens,
|
|
5181
|
+
budgetSummary: summary
|
|
5182
|
+
});
|
|
5183
|
+
}
|
|
5184
|
+
async function runCompactValidator(args, workbookId) {
|
|
5185
|
+
switch (args.validator) {
|
|
5186
|
+
case "workbook":
|
|
5187
|
+
return runtime.validateWorkbook({ workbookId });
|
|
5188
|
+
case "sheet":
|
|
5189
|
+
return runtime.validateSheet({ workbookId, sheetName: requiredCompactArg(args.sheetName, "sheetName") });
|
|
5190
|
+
case "formulas":
|
|
5191
|
+
return runtime.validateFormulas({ workbookId, ...compactOptional({ sheetName: args.sheetName, address: args.address }) });
|
|
5192
|
+
case "styles":
|
|
5193
|
+
return runtime.validateStyles(compactStylesValidationRequest(workbookId, args));
|
|
5194
|
+
case "tables":
|
|
5195
|
+
return runtime.validateTables(compactTablesValidationRequest(workbookId, args));
|
|
5196
|
+
case "filters":
|
|
5197
|
+
return runtime.validateFilters({ workbookId, ...compactOptional({ tableName: args.tableName }) });
|
|
5198
|
+
case "print_layout":
|
|
5199
|
+
return runtime.validatePrintLayout(compactPrintLayoutValidationRequest(workbookId, args));
|
|
5200
|
+
case "no_broken_references":
|
|
5201
|
+
return runtime.validateNoBrokenReferences({ workbookId, ...compactOptional({ sheetName: args.sheetName, address: args.address }) });
|
|
5202
|
+
case "no_formula_errors":
|
|
5203
|
+
return runtime.validateNoFormulaErrors({ workbookId, ...compactOptional({ sheetName: args.sheetName, address: args.address }) });
|
|
5204
|
+
case "no_unintended_changes":
|
|
5205
|
+
return runtime.validateNoUnintendedChanges(compactUnintendedChangesValidationRequest(workbookId, args));
|
|
5206
|
+
default:
|
|
5207
|
+
return {
|
|
5208
|
+
ok: false,
|
|
5209
|
+
workbookId,
|
|
5210
|
+
scope: args.validator,
|
|
5211
|
+
issueCount: 1,
|
|
5212
|
+
issues: [
|
|
5213
|
+
{
|
|
5214
|
+
code: "VALIDATOR_UNSUPPORTED",
|
|
5215
|
+
severity: "error",
|
|
5216
|
+
category: "validation",
|
|
5217
|
+
message: `Unsupported compact validator: ${args.validator}`
|
|
5218
|
+
}
|
|
5219
|
+
]
|
|
5220
|
+
};
|
|
5221
|
+
}
|
|
5222
|
+
}
|
|
5223
|
+
function compactSeverityCounts(issues) {
|
|
5224
|
+
const counts = {};
|
|
5225
|
+
for (const issue of issues) {
|
|
5226
|
+
const severity = typeof issue === "object" && issue !== null && "severity" in issue ? String(issue.severity) : "unknown";
|
|
5227
|
+
counts[severity] = (counts[severity] ?? 0) + 1;
|
|
5228
|
+
}
|
|
5229
|
+
return counts;
|
|
5230
|
+
}
|
|
5231
|
+
function compactStylesValidationRequest(workbookId, args) {
|
|
5232
|
+
const request = { workbookId };
|
|
5233
|
+
if (args.sheetName !== undefined) {
|
|
5234
|
+
request.sheetName = args.sheetName;
|
|
5235
|
+
}
|
|
5236
|
+
if (args.templateId !== undefined) {
|
|
5237
|
+
request.templateId = args.templateId;
|
|
5238
|
+
}
|
|
5239
|
+
if (args.targetSheetName !== undefined) {
|
|
5240
|
+
request.targetSheetName = args.targetSheetName;
|
|
5241
|
+
}
|
|
5242
|
+
return request;
|
|
5243
|
+
}
|
|
5244
|
+
function compactTablesValidationRequest(workbookId, args) {
|
|
5245
|
+
const request = { workbookId };
|
|
5246
|
+
if (args.tableName !== undefined) {
|
|
5247
|
+
request.tableName = args.tableName;
|
|
5248
|
+
}
|
|
5249
|
+
if (args.templateId !== undefined) {
|
|
5250
|
+
request.templateId = args.templateId;
|
|
5251
|
+
}
|
|
5252
|
+
return request;
|
|
5253
|
+
}
|
|
5254
|
+
function compactPrintLayoutValidationRequest(workbookId, args) {
|
|
5255
|
+
const request = { workbookId };
|
|
5256
|
+
if (args.templateId !== undefined) {
|
|
5257
|
+
request.templateId = args.templateId;
|
|
5258
|
+
}
|
|
5259
|
+
if (args.targetSheetName !== undefined) {
|
|
5260
|
+
request.targetSheetName = args.targetSheetName;
|
|
5261
|
+
}
|
|
5262
|
+
return request;
|
|
5263
|
+
}
|
|
5264
|
+
function compactUnintendedChangesValidationRequest(workbookId, args) {
|
|
5265
|
+
const request = { workbookId };
|
|
5266
|
+
if (args.snapshotId !== undefined) {
|
|
5267
|
+
request.snapshotId = args.snapshotId;
|
|
5268
|
+
}
|
|
5269
|
+
if (args.leftSnapshotId !== undefined) {
|
|
5270
|
+
request.leftSnapshotId = args.leftSnapshotId;
|
|
5271
|
+
}
|
|
5272
|
+
if (args.rightSnapshotId !== undefined) {
|
|
5273
|
+
request.rightSnapshotId = args.rightSnapshotId;
|
|
5274
|
+
}
|
|
5275
|
+
return request;
|
|
5276
|
+
}
|
|
5277
|
+
function compactIssueCategories(issues) {
|
|
5278
|
+
return [...new Set(issues.map((issue) => typeof issue === "object" && issue !== null && "category" in issue ? String(issue.category) : "unknown"))];
|
|
5279
|
+
}
|
|
5280
|
+
function compactIssueExample(issue) {
|
|
5281
|
+
if (typeof issue !== "object" || issue === null) {
|
|
5282
|
+
return issue;
|
|
5283
|
+
}
|
|
5284
|
+
const typed = issue;
|
|
5285
|
+
return {
|
|
5286
|
+
code: typed.code,
|
|
5287
|
+
severity: typed.severity,
|
|
5288
|
+
category: typed.category,
|
|
5289
|
+
message: typed.message,
|
|
5290
|
+
target: typed.target
|
|
5291
|
+
};
|
|
5292
|
+
}
|
|
5293
|
+
function requiredCompactArg(value, name) {
|
|
5294
|
+
if (value === undefined || value === "") {
|
|
5295
|
+
throw new Error(`${name} is required for this compact validator.`);
|
|
5296
|
+
}
|
|
5297
|
+
return value;
|
|
5298
|
+
}
|
|
5299
|
+
function usedRangeCellCount(usedRange) {
|
|
5300
|
+
return (usedRange?.rowCount ?? 0) * (usedRange?.columnCount ?? 0);
|
|
5301
|
+
}
|
|
5302
|
+
function tableInfoSchema(info) {
|
|
5303
|
+
return {
|
|
5304
|
+
workbookId: info.workbookId,
|
|
5305
|
+
tableName: info.tableName,
|
|
5306
|
+
sheetName: info.sheetName,
|
|
5307
|
+
address: info.address,
|
|
5308
|
+
headerAddress: info.headerAddress,
|
|
5309
|
+
rowCount: info.rowCount,
|
|
5310
|
+
columnCount: info.columnCount,
|
|
5311
|
+
columns: info.columns ?? [],
|
|
5312
|
+
style: info.style,
|
|
5313
|
+
showHeaders: info.showHeaders,
|
|
5314
|
+
showTotals: info.showTotals,
|
|
5315
|
+
showFilterButton: info.showFilterButton,
|
|
5316
|
+
showBandedRows: info.showBandedRows,
|
|
5317
|
+
showBandedColumns: info.showBandedColumns,
|
|
5318
|
+
hasFilters: info.filters !== undefined,
|
|
5319
|
+
hasSort: info.sort !== undefined
|
|
5320
|
+
};
|
|
5321
|
+
}
|
|
5322
|
+
async function lookupWorkbookContext(workbookId, sheetNames) {
|
|
5323
|
+
const result = await runtime.getWorkbookMap();
|
|
5324
|
+
const map = "map" in result ? result.map : undefined;
|
|
5325
|
+
if (!result.ok || !map) {
|
|
5326
|
+
return { ok: false, workbookId, source: result, sheets: [], tables: [] };
|
|
5327
|
+
}
|
|
5328
|
+
const requestedSheets = new Set((sheetNames ?? []).map(lookupNormalized));
|
|
5329
|
+
const sheets = (map.sheets ?? []).filter((sheet) => requestedSheets.size === 0 || requestedSheets.has(lookupNormalized(sheet.name)));
|
|
5330
|
+
const tableRefs = sheets.flatMap((sheet) => (sheet.tables ?? []).map((table) => ({
|
|
5331
|
+
sheetName: sheet.name,
|
|
5332
|
+
tableName: table.name ?? table.tableName
|
|
5333
|
+
}))).filter((table) => table.tableName);
|
|
5334
|
+
const tables = await Promise.all(tableRefs.map(async (table) => {
|
|
5335
|
+
try {
|
|
5336
|
+
const infoResult = await runtime.getTableInfo({ workbookId, tableName: table.tableName });
|
|
5337
|
+
const info = infoResult.info;
|
|
5338
|
+
return info ? { ...info, sheetName: info.sheetName ?? table.sheetName } : undefined;
|
|
5339
|
+
}
|
|
5340
|
+
catch {
|
|
5341
|
+
return undefined;
|
|
5342
|
+
}
|
|
5343
|
+
}));
|
|
5344
|
+
return {
|
|
5345
|
+
ok: true,
|
|
5346
|
+
workbookId,
|
|
5347
|
+
workbook: map.workbook,
|
|
5348
|
+
sheets,
|
|
5349
|
+
tables: tables.filter((table) => table !== undefined)
|
|
5350
|
+
};
|
|
5351
|
+
}
|
|
5352
|
+
function lookupTableMatches(tables, query, options) {
|
|
5353
|
+
const matches = [];
|
|
5354
|
+
for (const table of tables) {
|
|
5355
|
+
const schema = tableInfoSchema(table);
|
|
5356
|
+
if (lookupMatches(String(table.tableName), query, options)) {
|
|
5357
|
+
matches.push(lookupMatch({
|
|
5358
|
+
kind: "table",
|
|
5359
|
+
sheetName: table.sheetName,
|
|
5360
|
+
tableName: table.tableName,
|
|
5361
|
+
address: table.address,
|
|
5362
|
+
score: lookupScore(table.tableName, query, options.completeMatch),
|
|
5363
|
+
reason: "table name",
|
|
5364
|
+
schema
|
|
5365
|
+
}));
|
|
5366
|
+
}
|
|
5367
|
+
for (const column of table.columns ?? []) {
|
|
5368
|
+
if (lookupMatches(String(column.name), query, options)) {
|
|
5369
|
+
matches.push(lookupMatch({
|
|
5370
|
+
kind: "column",
|
|
5371
|
+
sheetName: table.sheetName,
|
|
5372
|
+
tableName: table.tableName,
|
|
5373
|
+
columnName: column.name,
|
|
5374
|
+
address: table.address,
|
|
5375
|
+
score: lookupScore(column.name, query, options.completeMatch),
|
|
5376
|
+
reason: "table column",
|
|
5377
|
+
schema: { tableName: table.tableName, column }
|
|
5378
|
+
}));
|
|
5379
|
+
}
|
|
5380
|
+
}
|
|
5381
|
+
}
|
|
5382
|
+
return matches;
|
|
5383
|
+
}
|
|
5384
|
+
async function lookupUsedRangeMatches(workbookId, sheets, query, options, kind) {
|
|
5385
|
+
const matches = [];
|
|
5386
|
+
const lookupOptions = options;
|
|
5387
|
+
for (const sheet of sheets) {
|
|
5388
|
+
const address = sheet.usedRange?.address;
|
|
5389
|
+
if (!address) {
|
|
5390
|
+
continue;
|
|
5391
|
+
}
|
|
5392
|
+
try {
|
|
5393
|
+
const result = await runtime.readRangeMetadata("range.search", {
|
|
5394
|
+
workbookId,
|
|
5395
|
+
sheetName: sheet.name,
|
|
5396
|
+
address,
|
|
5397
|
+
text: query,
|
|
5398
|
+
completeMatch: lookupOptions.completeMatch,
|
|
5399
|
+
matchCase: lookupOptions.matchCase
|
|
5400
|
+
});
|
|
5401
|
+
const found = result.matches;
|
|
5402
|
+
if (!found?.address || found.isNullObject) {
|
|
5403
|
+
continue;
|
|
5404
|
+
}
|
|
5405
|
+
const match = lookupMatch({
|
|
5406
|
+
kind,
|
|
5407
|
+
sheetName: sheet.name,
|
|
5408
|
+
address: found.address,
|
|
5409
|
+
score: lookupOptions.completeMatch ? 0.92 : 0.82,
|
|
5410
|
+
reason: `${kind === "entity" ? "entity" : "range"} text match`,
|
|
5411
|
+
preview: {
|
|
5412
|
+
areaCount: found.areaCount,
|
|
5413
|
+
cellCount: found.cellCount
|
|
5414
|
+
}
|
|
5415
|
+
});
|
|
5416
|
+
if ((lookupOptions.maxPreviewRows ?? 0) > 0) {
|
|
5417
|
+
match.preview = await lookupPreviewRange(workbookId, sheet.name, found.address, lookupOptions.maxPreviewRows ?? 3);
|
|
5418
|
+
}
|
|
5419
|
+
matches.push(match);
|
|
5420
|
+
}
|
|
5421
|
+
catch {
|
|
5422
|
+
// Ignore sheet-local search failures; other sheets and metadata can still guide the model.
|
|
5423
|
+
}
|
|
5424
|
+
}
|
|
5425
|
+
return matches;
|
|
5426
|
+
}
|
|
5427
|
+
async function lookupPreviewRange(workbookId, sheetName, address, maxRows) {
|
|
5428
|
+
try {
|
|
5429
|
+
const result = await compactRangeRead({
|
|
5430
|
+
workbookId,
|
|
5431
|
+
sheetName,
|
|
5432
|
+
address,
|
|
5433
|
+
mode: "window",
|
|
5434
|
+
maxRows,
|
|
5435
|
+
maxColumns: 10,
|
|
5436
|
+
includeValues: true,
|
|
5437
|
+
includeText: true
|
|
5438
|
+
});
|
|
5439
|
+
return {
|
|
5440
|
+
address: result.window?.address ?? address,
|
|
5441
|
+
values: result.values,
|
|
5442
|
+
text: result.text
|
|
5443
|
+
};
|
|
5444
|
+
}
|
|
5445
|
+
catch {
|
|
5446
|
+
return { address };
|
|
5447
|
+
}
|
|
5448
|
+
}
|
|
5449
|
+
function compactLookupResponse(args) {
|
|
5450
|
+
const allMatches = lookupSortAndDedupe(args.matches);
|
|
5451
|
+
const shownMatches = allMatches.slice(0, args.maxMatches);
|
|
5452
|
+
const summary = {
|
|
5453
|
+
ok: true,
|
|
5454
|
+
workbookId: args.workbookId,
|
|
5455
|
+
...Object.fromEntries(Object.entries(args).filter(([key]) => !["workbookId", "matches", "maxMatches", "maxPayloadBytes", "maxEstimatedTokens", "resourceTitle"].includes(key))),
|
|
5456
|
+
matchCount: allMatches.length,
|
|
5457
|
+
matches: shownMatches,
|
|
5458
|
+
matchesTruncated: shownMatches.length < allMatches.length
|
|
5459
|
+
};
|
|
5460
|
+
return withCompactTelemetry(summary, {
|
|
5461
|
+
detailLevel: "summary",
|
|
5462
|
+
truncated: shownMatches.length < allMatches.length,
|
|
5463
|
+
maxPayloadBytes: args.maxPayloadBytes,
|
|
5464
|
+
maxEstimatedTokens: args.maxEstimatedTokens,
|
|
5465
|
+
resourceKind: "summary",
|
|
5466
|
+
resourceTitle: args.resourceTitle,
|
|
5467
|
+
resourcePayload: { ...summary, matches: allMatches, matchesTruncated: false },
|
|
5468
|
+
budgetSummary: { ...summary, matches: shownMatches.map(({ preview, schema, ...match }) => match) }
|
|
5469
|
+
});
|
|
5470
|
+
}
|
|
5471
|
+
function lookupMatch(match) {
|
|
5472
|
+
return {
|
|
5473
|
+
...match,
|
|
5474
|
+
score: Number(match.score.toFixed(3)),
|
|
5475
|
+
matchId: lookupEncodeMatchId(match)
|
|
5476
|
+
};
|
|
5477
|
+
}
|
|
5478
|
+
function lookupSortAndDedupe(matches) {
|
|
5479
|
+
const best = new Map();
|
|
5480
|
+
for (const match of matches) {
|
|
5481
|
+
const key = [match.kind, match.sheetName, match.tableName, match.columnName, match.address].map((value) => value ?? "").join("|");
|
|
5482
|
+
const existing = best.get(key);
|
|
5483
|
+
if (!existing || match.score > existing.score) {
|
|
5484
|
+
best.set(key, match);
|
|
5485
|
+
}
|
|
5486
|
+
}
|
|
5487
|
+
return [...best.values()].sort((left, right) => right.score - left.score ||
|
|
5488
|
+
String(left.sheetName ?? "").localeCompare(String(right.sheetName ?? "")) ||
|
|
5489
|
+
String(left.tableName ?? "").localeCompare(String(right.tableName ?? "")) ||
|
|
5490
|
+
String(left.address ?? "").localeCompare(String(right.address ?? "")));
|
|
5491
|
+
}
|
|
5492
|
+
function lookupTerms(values) {
|
|
5493
|
+
return values.map(lookupNormalized).filter((value) => value !== "");
|
|
5494
|
+
}
|
|
5495
|
+
function lookupMatches(candidate, query, options) {
|
|
5496
|
+
const lookupOptions = options;
|
|
5497
|
+
const left = lookupOptions.matchCase ? candidate.trim() : lookupNormalized(candidate);
|
|
5498
|
+
const right = lookupOptions.matchCase ? query.trim() : lookupNormalized(query);
|
|
5499
|
+
return lookupOptions.completeMatch ? left === right : left.includes(right);
|
|
5500
|
+
}
|
|
5501
|
+
function lookupScore(candidate, query, completeMatch) {
|
|
5502
|
+
const left = lookupNormalized(candidate);
|
|
5503
|
+
const right = lookupNormalized(query);
|
|
5504
|
+
if (left === right) {
|
|
5505
|
+
return 1;
|
|
5506
|
+
}
|
|
5507
|
+
if (completeMatch) {
|
|
5508
|
+
return 0;
|
|
5509
|
+
}
|
|
5510
|
+
if (left.startsWith(right)) {
|
|
5511
|
+
return 0.9;
|
|
5512
|
+
}
|
|
5513
|
+
return left.includes(right) ? 0.75 : 0;
|
|
5514
|
+
}
|
|
5515
|
+
function lookupNormalized(value) {
|
|
5516
|
+
return String(value ?? "").trim().toLowerCase();
|
|
5517
|
+
}
|
|
5518
|
+
function lookupRowsFromCompactRead(result) {
|
|
5519
|
+
const typed = result;
|
|
5520
|
+
return Array.isArray(typed.text) ? typed.text : Array.isArray(typed.values) ? typed.values : [];
|
|
5521
|
+
}
|
|
5522
|
+
function lookupParseRangeTarget(target, preferredSheetName) {
|
|
5523
|
+
const parts = target.split("!");
|
|
5524
|
+
const sheetName = parts.length > 1 ? parts.slice(0, -1).join("!").replace(/^'|'$/g, "") : preferredSheetName;
|
|
5525
|
+
const address = parts.length > 1 ? parts[parts.length - 1] : target;
|
|
5526
|
+
if (address === undefined) {
|
|
5527
|
+
return undefined;
|
|
5528
|
+
}
|
|
5529
|
+
try {
|
|
5530
|
+
parseCompactA1Address(address);
|
|
5531
|
+
return sheetName ? { sheetName, address } : undefined;
|
|
5532
|
+
}
|
|
5533
|
+
catch {
|
|
5534
|
+
return undefined;
|
|
5535
|
+
}
|
|
5536
|
+
}
|
|
5537
|
+
function lookupEncodeMatchId(match) {
|
|
5538
|
+
const payload = JSON.stringify({
|
|
5539
|
+
kind: match.kind,
|
|
5540
|
+
sheetName: match.sheetName,
|
|
5541
|
+
tableName: match.tableName,
|
|
5542
|
+
columnName: match.columnName,
|
|
5543
|
+
address: match.address
|
|
5544
|
+
});
|
|
5545
|
+
return `lookup:${Buffer.from(payload, "utf8").toString("base64url")}`;
|
|
5546
|
+
}
|
|
5547
|
+
function lookupDecodeMatchId(matchId) {
|
|
5548
|
+
if (!matchId.startsWith("lookup:")) {
|
|
5549
|
+
return undefined;
|
|
5550
|
+
}
|
|
5551
|
+
try {
|
|
5552
|
+
return JSON.parse(Buffer.from(matchId.slice("lookup:".length), "base64url").toString("utf8"));
|
|
5553
|
+
}
|
|
5554
|
+
catch {
|
|
5555
|
+
return undefined;
|
|
5556
|
+
}
|
|
5557
|
+
}
|
|
5558
|
+
function compactTableColumns(info, requested, maxColumns) {
|
|
5559
|
+
const columns = info.columns ?? [];
|
|
5560
|
+
if (requested?.length) {
|
|
5561
|
+
return columns.filter((column) => requested.some((item) => item === column.name || item === column.index || item === column.id));
|
|
5562
|
+
}
|
|
5563
|
+
return columns.slice(0, maxColumns ?? 25);
|
|
5564
|
+
}
|
|
5565
|
+
function compactRangeFacets(args) {
|
|
5566
|
+
const facets = [];
|
|
5567
|
+
if (args.includeValues !== false) {
|
|
5568
|
+
facets.push("values");
|
|
5569
|
+
}
|
|
5570
|
+
if (args.includeFormulas === true) {
|
|
5571
|
+
facets.push("formulas");
|
|
5572
|
+
}
|
|
5573
|
+
if (args.includeText === true) {
|
|
5574
|
+
facets.push("text");
|
|
5575
|
+
}
|
|
5576
|
+
if (args.includeNumberFormats === true) {
|
|
5577
|
+
facets.push("numberFormat");
|
|
5578
|
+
}
|
|
5579
|
+
if (args.includeStyles === true) {
|
|
5580
|
+
facets.push("style");
|
|
5581
|
+
}
|
|
5582
|
+
return facets;
|
|
5583
|
+
}
|
|
5584
|
+
function compactSampleWindows(totalRows, requestedRows) {
|
|
5585
|
+
const sampleRows = Math.max(1, Math.min(totalRows, requestedRows));
|
|
5586
|
+
if (totalRows <= sampleRows) {
|
|
5587
|
+
return [{ label: "head", rowOffset: 0, rowCount: totalRows }];
|
|
5588
|
+
}
|
|
5589
|
+
const headRows = Math.max(1, Math.floor(sampleRows / 3));
|
|
5590
|
+
const middleRows = Math.max(1, Math.floor(sampleRows / 3));
|
|
5591
|
+
const tailRows = Math.max(1, sampleRows - headRows - middleRows);
|
|
5592
|
+
const middleOffset = Math.max(headRows, Math.floor((totalRows - middleRows) / 2));
|
|
5593
|
+
const tailOffset = Math.max(middleOffset + middleRows, totalRows - tailRows);
|
|
5594
|
+
const windows = [
|
|
5595
|
+
{ label: "head", rowOffset: 0, rowCount: headRows },
|
|
5596
|
+
{ label: "middle", rowOffset: middleOffset, rowCount: Math.min(middleRows, totalRows - middleOffset) },
|
|
5597
|
+
{ label: "tail", rowOffset: tailOffset, rowCount: Math.min(tailRows, totalRows - tailOffset) }
|
|
5598
|
+
];
|
|
5599
|
+
return windows.filter((window, index, all) => window.rowCount > 0 &&
|
|
5600
|
+
all.findIndex((candidate) => rangesOverlap(candidate.rowOffset, candidate.rowCount, window.rowOffset, window.rowCount)) === index);
|
|
5601
|
+
}
|
|
5602
|
+
function rangesOverlap(leftOffset, leftCount, rightOffset, rightCount) {
|
|
5603
|
+
const leftEnd = leftOffset + leftCount;
|
|
5604
|
+
const rightEnd = rightOffset + rightCount;
|
|
5605
|
+
return leftOffset < rightEnd && rightOffset < leftEnd;
|
|
5606
|
+
}
|
|
5607
|
+
function compactWindowAddress(address, rowOffset, columnOffset, rowCount, columnCount) {
|
|
5608
|
+
const parsed = parseCompactA1Address(address);
|
|
5609
|
+
const startRow = parsed.startRow + rowOffset;
|
|
5610
|
+
const startColumn = parsed.startColumn + columnOffset;
|
|
5611
|
+
const endRow = startRow + Math.max(rowCount, 1) - 1;
|
|
5612
|
+
const endColumn = startColumn + Math.max(columnCount, 1) - 1;
|
|
5613
|
+
const start = `${compactColumnName(startColumn)}${startRow}`;
|
|
5614
|
+
const end = `${compactColumnName(endColumn)}${endRow}`;
|
|
5615
|
+
return start === end ? start : `${start}:${end}`;
|
|
5616
|
+
}
|
|
5617
|
+
function parseCompactA1Address(address) {
|
|
5618
|
+
const range = stripResourceSheetName(address).trim();
|
|
5619
|
+
const match = /^(?<startCol>[A-Z]+)(?<startRow>\d+)(?::(?<endCol>[A-Z]+)(?<endRow>\d+))?$/i.exec(range);
|
|
5620
|
+
if (!match?.groups?.startCol || !match.groups.startRow) {
|
|
5621
|
+
throw new Error(`Invalid A1 address: ${address}`);
|
|
5622
|
+
}
|
|
5623
|
+
const startColumn = compactColumnNumber(match.groups.startCol);
|
|
5624
|
+
const startRow = Number(match.groups.startRow);
|
|
5625
|
+
const endColumn = compactColumnNumber(match.groups.endCol ?? match.groups.startCol);
|
|
5626
|
+
const endRow = Number(match.groups.endRow ?? match.groups.startRow);
|
|
5627
|
+
if (startRow < 1 || endRow < startRow || endColumn < startColumn) {
|
|
5628
|
+
throw new Error(`Invalid A1 range bounds: ${address}`);
|
|
5629
|
+
}
|
|
5630
|
+
return { startRow, startColumn, endRow, endColumn };
|
|
5631
|
+
}
|
|
5632
|
+
function compactColumnNumber(columnName) {
|
|
5633
|
+
let value = 0;
|
|
5634
|
+
for (const char of columnName.toUpperCase()) {
|
|
5635
|
+
const code = char.charCodeAt(0);
|
|
5636
|
+
if (code < 65 || code > 90) {
|
|
5637
|
+
throw new Error(`Invalid column name: ${columnName}`);
|
|
5638
|
+
}
|
|
5639
|
+
value = value * 26 + (code - 64);
|
|
5640
|
+
}
|
|
5641
|
+
return value;
|
|
5642
|
+
}
|
|
5643
|
+
function compactColumnName(columnNumber) {
|
|
5644
|
+
let value = "";
|
|
5645
|
+
let n = columnNumber;
|
|
5646
|
+
while (n > 0) {
|
|
5647
|
+
n -= 1;
|
|
5648
|
+
value = String.fromCharCode(65 + (n % 26)) + value;
|
|
5649
|
+
n = Math.floor(n / 26);
|
|
5650
|
+
}
|
|
5651
|
+
return value;
|
|
5652
|
+
}
|
|
4051
5653
|
function tableCreateRequest(args) {
|
|
4052
5654
|
const request = {
|
|
4053
5655
|
workbookId: args.workbookId,
|
|
@@ -4642,7 +6244,14 @@ function registerMcpTool(mcp, name, config, callback) {
|
|
|
4642
6244
|
if (!isToolExposed(name, catalogOptions)) {
|
|
4643
6245
|
return;
|
|
4644
6246
|
}
|
|
4645
|
-
mcp.registerTool(name, config,
|
|
6247
|
+
mcp.registerTool(name, config, async (args, extra) => {
|
|
6248
|
+
const result = await callback(args, extra);
|
|
6249
|
+
const decorated = isWorkbookMutatingMcpTool(name) ? addCompactMutationProof(result) : result;
|
|
6250
|
+
if (isWorkbookMutatingMcpTool(name)) {
|
|
6251
|
+
clearCompactCache(`mutation:${name}`);
|
|
6252
|
+
}
|
|
6253
|
+
return decorated;
|
|
6254
|
+
});
|
|
4646
6255
|
}
|
|
4647
6256
|
function jsonResult(value) {
|
|
4648
6257
|
return {
|
|
@@ -4654,6 +6263,88 @@ function jsonResult(value) {
|
|
|
4654
6263
|
]
|
|
4655
6264
|
};
|
|
4656
6265
|
}
|
|
6266
|
+
function isWorkbookMutatingMcpTool(name) {
|
|
6267
|
+
const namespace = name.split(".")[1];
|
|
6268
|
+
if (!namespace || name.startsWith("excel.compact.")) {
|
|
6269
|
+
return false;
|
|
6270
|
+
}
|
|
6271
|
+
if (namespace === "workbook") {
|
|
6272
|
+
return /\.(calculate|save|save_as|restore_backup|close|embed|import)/.test(name);
|
|
6273
|
+
}
|
|
6274
|
+
if (namespace === "plan") {
|
|
6275
|
+
return /\.(apply|rollback)/.test(name);
|
|
6276
|
+
}
|
|
6277
|
+
if (namespace === "transaction") {
|
|
6278
|
+
return /\.(rollback|rollback_chain)/.test(name);
|
|
6279
|
+
}
|
|
6280
|
+
if (!new Set(["sheet", "range", "batch", "workflow", "template", "style", "formula", "table", "filter", "sort", "pivot", "chart", "names", "region", "repair", "clean"]).has(namespace)) {
|
|
6281
|
+
return false;
|
|
6282
|
+
}
|
|
6283
|
+
if (name === "excel.workflow.preview_risky_edit") {
|
|
6284
|
+
return true;
|
|
6285
|
+
}
|
|
6286
|
+
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);
|
|
6287
|
+
}
|
|
6288
|
+
function addCompactMutationProof(result) {
|
|
6289
|
+
if (!isJsonTextResult(result)) {
|
|
6290
|
+
return result;
|
|
6291
|
+
}
|
|
6292
|
+
try {
|
|
6293
|
+
const payload = JSON.parse(result.content[0].text);
|
|
6294
|
+
if (payload.compactProof !== undefined) {
|
|
6295
|
+
return result;
|
|
6296
|
+
}
|
|
6297
|
+
return jsonResult({
|
|
6298
|
+
...payload,
|
|
6299
|
+
compactProof: summarizeMutationProof(payload)
|
|
6300
|
+
});
|
|
6301
|
+
}
|
|
6302
|
+
catch {
|
|
6303
|
+
return result;
|
|
6304
|
+
}
|
|
6305
|
+
}
|
|
6306
|
+
function isJsonTextResult(result) {
|
|
6307
|
+
return Boolean(result &&
|
|
6308
|
+
typeof result === "object" &&
|
|
6309
|
+
Array.isArray(result.content) &&
|
|
6310
|
+
result.content[0]?.text !== undefined);
|
|
6311
|
+
}
|
|
6312
|
+
function summarizeMutationProof(payload) {
|
|
6313
|
+
const diffSummary = payload.diffSummary;
|
|
6314
|
+
const telemetry = payload.telemetry;
|
|
6315
|
+
const warnings = Array.isArray(payload.warnings) ? payload.warnings : [];
|
|
6316
|
+
const validation = validationSummary(payload);
|
|
6317
|
+
return {
|
|
6318
|
+
ok: payload.ok,
|
|
6319
|
+
transactionId: payload.transactionId,
|
|
6320
|
+
transactionStatus: payload.transactionStatus,
|
|
6321
|
+
taskId: payload.taskId,
|
|
6322
|
+
rollbackAvailable: payload.rollbackAvailable,
|
|
6323
|
+
backups: payload.backups,
|
|
6324
|
+
changedRanges: diffSummary?.changedRanges,
|
|
6325
|
+
cellsChanged: diffSummary?.cellsChanged ?? telemetry?.cellsWritten,
|
|
6326
|
+
formulasChanged: diffSummary?.formulasChanged,
|
|
6327
|
+
stylesChanged: diffSummary?.stylesChanged,
|
|
6328
|
+
tablesChanged: diffSummary?.tablesChanged,
|
|
6329
|
+
sheetsChanged: diffSummary?.sheetsChanged,
|
|
6330
|
+
warnings: warnings.length,
|
|
6331
|
+
validation,
|
|
6332
|
+
payloadBytes: Buffer.byteLength(JSON.stringify(payload), "utf8"),
|
|
6333
|
+
estimatedTokens: Math.ceil(Buffer.byteLength(JSON.stringify(payload), "utf8") / 4)
|
|
6334
|
+
};
|
|
6335
|
+
}
|
|
6336
|
+
function validationSummary(payload) {
|
|
6337
|
+
const issueCount = typeof payload.issueCount === "number" ? payload.issueCount : undefined;
|
|
6338
|
+
const issues = Array.isArray(payload.issues) ? payload.issues : undefined;
|
|
6339
|
+
const severityCounts = (payload.severityCounts ?? payload.summary);
|
|
6340
|
+
if (issueCount === undefined && issues === undefined && severityCounts === undefined) {
|
|
6341
|
+
return undefined;
|
|
6342
|
+
}
|
|
6343
|
+
return {
|
|
6344
|
+
issueCount: issueCount ?? issues?.length ?? 0,
|
|
6345
|
+
severityCounts
|
|
6346
|
+
};
|
|
6347
|
+
}
|
|
4657
6348
|
function jsonResource(uri, value) {
|
|
4658
6349
|
return {
|
|
4659
6350
|
contents: [
|