@components-kit/open-workbook 0.1.7 → 0.1.9

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