@tita-agent/dsh-data-table 0.0.0-stage → 0.2.0

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.
Files changed (267) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -2
  3. package/cordis.patch.yml +7 -0
  4. package/lib/client.js +16766 -0
  5. package/lib/contract-envelope.d.ts +91 -0
  6. package/lib/contract-envelope.js +106 -0
  7. package/lib/contract-layout.d.ts +130 -0
  8. package/lib/contract-layout.js +138 -0
  9. package/lib/contract-refs.d.ts +39 -0
  10. package/lib/contract-refs.js +9 -0
  11. package/lib/contract-version.d.ts +311 -0
  12. package/lib/contract-version.js +270 -0
  13. package/lib/contract-wire.d.ts +147 -0
  14. package/lib/contract-wire.js +99 -0
  15. package/lib/contracts.d.ts +12 -0
  16. package/lib/contracts.js +12 -0
  17. package/lib/types/audit.d.ts +42 -0
  18. package/lib/types/audit.js +39 -0
  19. package/lib/types/client/ProjectImportMenu.d.ts +30 -0
  20. package/lib/types/client/ProjectImportMenu.js +70 -0
  21. package/lib/types/client/SheetPanel.d.ts +33 -0
  22. package/lib/types/client/SheetPanel.js +1766 -0
  23. package/lib/types/client/SheetToggle.d.ts +18 -0
  24. package/lib/types/client/SheetToggle.js +26 -0
  25. package/lib/types/client/index.d.ts +15 -0
  26. package/lib/types/client/index.js +110 -0
  27. package/lib/types/client/persistence/changes.d.ts +15 -0
  28. package/lib/types/client/persistence/changes.js +51 -0
  29. package/lib/types/client/persistence/ids.d.ts +34 -0
  30. package/lib/types/client/persistence/ids.js +48 -0
  31. package/lib/types/client/persistence/merge.d.ts +35 -0
  32. package/lib/types/client/persistence/merge.js +362 -0
  33. package/lib/types/client/store.d.ts +61 -0
  34. package/lib/types/client/store.js +152 -0
  35. package/lib/types/client/surface-context.d.ts +27 -0
  36. package/lib/types/client/surface-context.js +104 -0
  37. package/lib/types/client/surface-control.d.ts +2 -0
  38. package/lib/types/client/surface-control.js +12 -0
  39. package/lib/types/client/transport/workbookApi.d.ts +159 -0
  40. package/lib/types/client/transport/workbookApi.js +246 -0
  41. package/lib/types/client/univer/StandaloneWorkbookPage.d.ts +5 -0
  42. package/lib/types/client/univer/StandaloneWorkbookPage.js +7 -0
  43. package/lib/types/client/univer/WorkbookCanvas.d.ts +40 -0
  44. package/lib/types/client/univer/WorkbookCanvas.js +87 -0
  45. package/lib/types/client/univer/WorkbookView.d.ts +3 -0
  46. package/lib/types/client/univer/WorkbookView.js +2 -0
  47. package/lib/types/client/univer/loader.d.ts +18 -0
  48. package/lib/types/client/univer/loader.js +81 -0
  49. package/lib/types/client/univer/standalone-entry.d.ts +1 -0
  50. package/lib/types/client/univer/standalone-entry.js +19 -0
  51. package/lib/types/client/versions/VersionChrome.d.ts +33 -0
  52. package/lib/types/client/versions/VersionChrome.js +21 -0
  53. package/lib/types/client/versions/inspect.d.ts +70 -0
  54. package/lib/types/client/versions/inspect.js +88 -0
  55. package/lib/types/client/versions/preview-decoration.d.ts +11 -0
  56. package/lib/types/client/versions/preview-decoration.js +54 -0
  57. package/lib/types/client/versions/records.d.ts +19 -0
  58. package/lib/types/client/versions/records.js +70 -0
  59. package/lib/types/client/workspace-host.d.ts +61 -0
  60. package/lib/types/client/workspace-host.js +124 -0
  61. package/lib/types/collaboration-hub.d.ts +31 -0
  62. package/lib/types/collaboration-hub.js +69 -0
  63. package/lib/types/contract/ids.d.ts +24 -0
  64. package/lib/types/contract/ids.js +46 -0
  65. package/lib/types/contract/mutations.d.ts +25 -0
  66. package/lib/types/contract/mutations.js +782 -0
  67. package/lib/types/contract/operations.d.ts +54 -0
  68. package/lib/types/contract/operations.js +203 -0
  69. package/lib/types/contract/snapshots.d.ts +176 -0
  70. package/lib/types/contract/snapshots.js +351 -0
  71. package/lib/types/contract/univer-api-catalog.d.ts +29 -0
  72. package/lib/types/contract/univer-api-catalog.js +264 -0
  73. package/lib/types/errors.d.ts +8 -0
  74. package/lib/types/errors.js +12 -0
  75. package/lib/types/http/assets-routes.d.ts +3 -0
  76. package/lib/types/http/assets-routes.js +169 -0
  77. package/lib/types/http/collaboration.d.ts +10 -0
  78. package/lib/types/http/collaboration.js +154 -0
  79. package/lib/types/http/history.d.ts +15 -0
  80. package/lib/types/http/history.js +134 -0
  81. package/lib/types/http/project-files.d.ts +79 -0
  82. package/lib/types/http/project-files.js +206 -0
  83. package/lib/types/http/project-scope.d.ts +50 -0
  84. package/lib/types/http/project-scope.js +52 -0
  85. package/lib/types/http/routes.d.ts +98 -0
  86. package/lib/types/http/routes.js +323 -0
  87. package/lib/types/http/standalone.d.ts +9 -0
  88. package/lib/types/http/standalone.js +31 -0
  89. package/lib/types/http/trunk.d.ts +10 -0
  90. package/lib/types/http/trunk.js +322 -0
  91. package/lib/types/http/worktree.d.ts +9 -0
  92. package/lib/types/http/worktree.js +145 -0
  93. package/lib/types/index.d.ts +18 -0
  94. package/lib/types/index.js +156 -0
  95. package/lib/types/invariant.d.ts +15 -0
  96. package/lib/types/invariant.js +15 -0
  97. package/lib/types/request-params.d.ts +21 -0
  98. package/lib/types/request-params.js +76 -0
  99. package/lib/types/store/paths.d.ts +68 -0
  100. package/lib/types/store/paths.js +148 -0
  101. package/lib/types/store/project.d.ts +88 -0
  102. package/lib/types/store/project.js +120 -0
  103. package/lib/types/store/read.d.ts +110 -0
  104. package/lib/types/store/read.js +404 -0
  105. package/lib/types/store/retention.d.ts +27 -0
  106. package/lib/types/store/retention.js +111 -0
  107. package/lib/types/store/write.d.ts +22 -0
  108. package/lib/types/store/write.js +210 -0
  109. package/lib/types/tools/arguments.d.ts +40 -0
  110. package/lib/types/tools/arguments.js +130 -0
  111. package/lib/types/tools/cells.d.ts +50 -0
  112. package/lib/types/tools/cells.js +298 -0
  113. package/lib/types/tools/index.d.ts +2 -0
  114. package/lib/types/tools/index.js +250 -0
  115. package/lib/types/tools/render.d.ts +47 -0
  116. package/lib/types/tools/render.js +290 -0
  117. package/lib/types/tools/univer-api.d.ts +18 -0
  118. package/lib/types/tools/univer-api.js +198 -0
  119. package/lib/types/tools/workbook-catalog.d.ts +3 -0
  120. package/lib/types/tools/workbook-catalog.js +83 -0
  121. package/lib/types/tools/workbook-edit.d.ts +192 -0
  122. package/lib/types/tools/workbook-edit.js +912 -0
  123. package/lib/types/tools/workbook-find.d.ts +11 -0
  124. package/lib/types/tools/workbook-find.js +89 -0
  125. package/lib/types/tools/workbook-history.d.ts +2 -0
  126. package/lib/types/tools/workbook-history.js +59 -0
  127. package/lib/types/tools/workbook-read.d.ts +21 -0
  128. package/lib/types/tools/workbook-read.js +204 -0
  129. package/lib/types/types.d.ts +377 -0
  130. package/lib/types/types.js +34 -0
  131. package/lib/types/univer/apply-operation.d.ts +14 -0
  132. package/lib/types/univer/apply-operation.js +398 -0
  133. package/lib/types/univer/chunk.d.ts +68 -0
  134. package/lib/types/univer/chunk.js +16 -0
  135. package/lib/types/univer/decoration.d.ts +34 -0
  136. package/lib/types/univer/decoration.js +88 -0
  137. package/lib/types/univer/diagnostics.d.ts +31 -0
  138. package/lib/types/univer/diagnostics.js +141 -0
  139. package/lib/types/univer/engine.d.ts +33 -0
  140. package/lib/types/univer/engine.js +338 -0
  141. package/lib/types/univer/entry.d.ts +8 -0
  142. package/lib/types/univer/entry.js +11 -0
  143. package/lib/types/univer/facade.d.ts +107 -0
  144. package/lib/types/univer/facade.js +1 -0
  145. package/lib/types/univer/fonts.d.ts +202 -0
  146. package/lib/types/univer/fonts.js +140 -0
  147. package/lib/types/univer/plugins/collaboration.d.ts +23 -0
  148. package/lib/types/univer/plugins/collaboration.js +47 -0
  149. package/lib/types/univer/plugins/export.d.ts +39 -0
  150. package/lib/types/univer/plugins/export.js +246 -0
  151. package/lib/types/univer/plugins/history.d.ts +24 -0
  152. package/lib/types/univer/plugins/history.js +50 -0
  153. package/lib/types/univer/plugins/version.d.ts +13 -0
  154. package/lib/types/univer/plugins/version.js +13 -0
  155. package/lib/types/version/diff.d.ts +36 -0
  156. package/lib/types/version/diff.js +156 -0
  157. package/lib/types/version/grouping.d.ts +7 -0
  158. package/lib/types/version/grouping.js +94 -0
  159. package/lib/types/version/records.d.ts +17 -0
  160. package/lib/types/version/records.js +102 -0
  161. package/lib/types/version/session.d.ts +36 -0
  162. package/lib/types/version/session.js +89 -0
  163. package/lib/types/version/summary.d.ts +24 -0
  164. package/lib/types/version/summary.js +111 -0
  165. package/lib/types/workbook-manager.d.ts +191 -0
  166. package/lib/types/workbook-manager.js +935 -0
  167. package/lib/types/xlsx-export-worker.d.ts +5 -0
  168. package/lib/types/xlsx-export-worker.js +24 -0
  169. package/lib/types/xlsx-export.d.ts +21 -0
  170. package/lib/types/xlsx-export.js +371 -0
  171. package/lib/types/xlsx-import-worker.d.ts +1 -0
  172. package/lib/types/xlsx-import-worker.js +19 -0
  173. package/lib/univer-client.js +690 -0
  174. package/lib/workbook-page.js +23826 -0
  175. package/lib/xlsx-export-worker.js +104 -0
  176. package/lib/xlsx-import-worker.js +46 -0
  177. package/package.json +106 -3
  178. package/src/audit.ts +58 -0
  179. package/src/client/ProjectImportMenu.tsx +100 -0
  180. package/src/client/SheetPanel.module.css +1578 -0
  181. package/src/client/SheetPanel.tsx +1934 -0
  182. package/src/client/SheetToggle.module.css +73 -0
  183. package/src/client/SheetToggle.tsx +65 -0
  184. package/src/client/contract.d.ts +19 -0
  185. package/src/client/index.ts +131 -0
  186. package/src/client/persistence/changes.ts +58 -0
  187. package/src/client/persistence/ids.ts +60 -0
  188. package/src/client/persistence/merge.ts +392 -0
  189. package/src/client/store.ts +188 -0
  190. package/src/client/surface-context.ts +123 -0
  191. package/src/client/surface-control.ts +13 -0
  192. package/src/client/transport/workbookApi.ts +355 -0
  193. package/src/client/univer/SheetView.module.css +110 -0
  194. package/src/client/univer/StandaloneWorkbookPage.module.css +8 -0
  195. package/src/client/univer/StandaloneWorkbookPage.tsx +15 -0
  196. package/src/client/univer/WorkbookCanvas.tsx +128 -0
  197. package/src/client/univer/WorkbookView.module.css +30 -0
  198. package/src/client/univer/WorkbookView.tsx +3 -0
  199. package/src/client/univer/loader.ts +90 -0
  200. package/src/client/univer/react-dom-client.d.ts +10 -0
  201. package/src/client/univer/standalone-entry.tsx +19 -0
  202. package/src/client/univer/standalone.css +12 -0
  203. package/src/client/univer/univer-locales.d.ts +89 -0
  204. package/src/client/univer/univer-modules.d.ts +26 -0
  205. package/src/client/versions/VersionChrome.tsx +114 -0
  206. package/src/client/versions/inspect.ts +138 -0
  207. package/src/client/versions/preview-decoration.ts +54 -0
  208. package/src/client/versions/records.ts +78 -0
  209. package/src/client/workspace-host.ts +147 -0
  210. package/src/collaboration-hub.ts +83 -0
  211. package/src/contract/ids.ts +50 -0
  212. package/src/contract/mutations.ts +791 -0
  213. package/src/contract/operations.ts +221 -0
  214. package/src/contract/snapshots.ts +383 -0
  215. package/src/contract/univer-api-catalog.ts +289 -0
  216. package/src/css-modules.d.ts +6 -0
  217. package/src/errors.ts +25 -0
  218. package/src/http/assets-routes.ts +177 -0
  219. package/src/http/collaboration.ts +144 -0
  220. package/src/http/history.ts +151 -0
  221. package/src/http/project-files.ts +227 -0
  222. package/src/http/project-scope.ts +77 -0
  223. package/src/http/routes.ts +364 -0
  224. package/src/http/standalone.ts +32 -0
  225. package/src/http/trunk.ts +322 -0
  226. package/src/http/worktree.ts +119 -0
  227. package/src/index.ts +165 -0
  228. package/src/invariant.ts +17 -0
  229. package/src/request-params.ts +92 -0
  230. package/src/store/paths.ts +165 -0
  231. package/src/store/project.ts +137 -0
  232. package/src/store/read.ts +454 -0
  233. package/src/store/retention.ts +106 -0
  234. package/src/store/write.ts +230 -0
  235. package/src/tools/arguments.ts +128 -0
  236. package/src/tools/cells.ts +298 -0
  237. package/src/tools/index.ts +274 -0
  238. package/src/tools/render.ts +301 -0
  239. package/src/tools/univer-api.ts +243 -0
  240. package/src/tools/workbook-catalog.ts +94 -0
  241. package/src/tools/workbook-edit.ts +987 -0
  242. package/src/tools/workbook-find.ts +80 -0
  243. package/src/tools/workbook-history.ts +63 -0
  244. package/src/tools/workbook-read.ts +210 -0
  245. package/src/types.ts +432 -0
  246. package/src/univer/apply-operation.ts +369 -0
  247. package/src/univer/chunk.ts +77 -0
  248. package/src/univer/decoration.ts +111 -0
  249. package/src/univer/diagnostics.ts +136 -0
  250. package/src/univer/engine.ts +370 -0
  251. package/src/univer/entry.ts +14 -0
  252. package/src/univer/facade.ts +91 -0
  253. package/src/univer/fonts.ts +148 -0
  254. package/src/univer/overrides.css +33 -0
  255. package/src/univer/plugins/collaboration.ts +47 -0
  256. package/src/univer/plugins/export.ts +236 -0
  257. package/src/univer/plugins/history.ts +49 -0
  258. package/src/univer/plugins/version.ts +13 -0
  259. package/src/version/diff.ts +169 -0
  260. package/src/version/grouping.ts +96 -0
  261. package/src/version/records.ts +113 -0
  262. package/src/version/session.ts +90 -0
  263. package/src/version/summary.ts +117 -0
  264. package/src/workbook-manager.ts +1111 -0
  265. package/src/xlsx-export-worker.ts +34 -0
  266. package/src/xlsx-export.ts +373 -0
  267. package/src/xlsx-import-worker.ts +30 -0
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Payload vocabulary shared by the two tools that write to a workbook
3
+ * (docs/issue/0022 P3b).
4
+ *
5
+ * `workbook_edit` and `univer_api` both accept a value matrix plus a range, both
6
+ * accept cells whose value comes from a file, both declare a table's columns, and
7
+ * both size a row or column. That translation lives here rather than in either
8
+ * tool: a tool importing the other tool is exactly the coupling this phase
9
+ * exists to prevent, and every helper here is about building a payload, not about
10
+ * which endpoint received it.
11
+ */
12
+ import { statSync } from 'node:fs';
13
+ import { readFile } from 'node:fs/promises';
14
+ import { resolve } from 'node:path';
15
+ import { SHEET_MAX_COLUMNS, SHEET_MAX_NAME, SHEET_MAX_ROWS, } from "../types.js";
16
+ import { parseA1Range, parseIndex, parseRange } from "../request-params.js";
17
+ import { asToolArguments, parseNonEmptyString } from "./arguments.js";
18
+ /** Sheet column types a modelled table may declare. */
19
+ const COLUMN_TYPES = ['string', 'number', 'boolean', 'date', 'currency'];
20
+ export function parseCells(value) {
21
+ if (!Array.isArray(value) || value.length === 0)
22
+ throw new Error('cells 必须是非空数组');
23
+ if (value.length > SHEET_MAX_ROWS * SHEET_MAX_COLUMNS)
24
+ throw new Error('一次修改的单元格数量超过上限');
25
+ return value.map((item, index) => {
26
+ if (typeof item !== 'object' || item === null || Array.isArray(item))
27
+ throw new Error(`第 ${index + 1} 个 cell 不是对象`);
28
+ const raw = item;
29
+ const cellValue = raw.value;
30
+ if (cellValue !== null && typeof cellValue !== 'string' && typeof cellValue !== 'number' && typeof cellValue !== 'boolean') {
31
+ throw new Error(`第 ${index + 1} 个 cell 的 value 必须是 string/number/boolean/null`);
32
+ }
33
+ if (typeof cellValue === 'number' && !Number.isFinite(cellValue))
34
+ throw new Error(`第 ${index + 1} 个 cell 的 value 不是有限数字`);
35
+ if (raw.formula !== undefined && typeof raw.formula !== 'string')
36
+ throw new Error(`第 ${index + 1} 个 cell 的 formula 必须是字符串`);
37
+ return {
38
+ row: parseIndex(raw.row, `cells[${index}].row`),
39
+ column: parseIndex(raw.column, `cells[${index}].column`),
40
+ value: cellValue,
41
+ ...(raw.formula === undefined ? {} : { formula: raw.formula }),
42
+ };
43
+ });
44
+ }
45
+ /**
46
+ * Resolve `cells[].valueFromFile` into a literal value (docs/issue/0011 I4 / T7).
47
+ *
48
+ * Long text had to be inlined into the tool call, and the same SQL was retyped
49
+ * into every workbook that needed it. A cell may now name a local text file
50
+ * instead. The content is read here and handed to the ordinary `parseCells`
51
+ * validation, so nothing bypasses the existing cell limits.
52
+ */
53
+ export async function resolveCellFileValues(value, label) {
54
+ return Promise.all(value.map(async (item, index) => {
55
+ if (typeof item !== 'object' || item === null || Array.isArray(item))
56
+ return item;
57
+ const raw = item;
58
+ if (raw.valueFromFile === undefined || raw.valueFromFile === null)
59
+ return item;
60
+ const path = resolve(parseNonEmptyString(raw.valueFromFile, `${label}.cells[${index}].valueFromFile`));
61
+ let info;
62
+ try {
63
+ info = statSync(path);
64
+ }
65
+ catch {
66
+ throw new Error(`${label}.cells[${index}].valueFromFile 文件不存在:${path}`);
67
+ }
68
+ if (!info.isFile())
69
+ throw new Error(`${label}.cells[${index}].valueFromFile 不是文件:${path}`);
70
+ if (info.size > TEXT_CELL_MAX_BYTES) {
71
+ throw new Error(`${label}.cells[${index}].valueFromFile 文件过大(${info.size} 字节,上限 ${TEXT_CELL_MAX_BYTES})`);
72
+ }
73
+ const content = await readFile(path, 'utf8');
74
+ const { valueFromFile: _consumed, ...rest } = raw;
75
+ // An explicit `value` still wins, so the field is a fallback rather than an
76
+ // override that could silently disagree with the caller.
77
+ return raw.value === undefined ? { ...rest, value: content } : rest;
78
+ }));
79
+ }
80
+ /**
81
+ * Resolve the target range of a value matrix.
82
+ *
83
+ * The matrix size is unambiguous, so it is the authority: `range` only needs
84
+ * to anchor the write, and a `range` whose declared end disagrees with
85
+ * `values` is corrected and reported as a warning instead of failing a large
86
+ * batch. End coordinates are inclusive, which the tool description now states.
87
+ */
88
+ export function parseValueRange(value, matrix, label, warnings = []) {
89
+ const rowCount = matrix.length;
90
+ const columnCount = matrix[0]?.length ?? 0;
91
+ const derived = (startRow, startColumn) => parseRange({ startRow, startColumn, endRow: startRow + rowCount - 1, endColumn: startColumn + columnCount - 1 });
92
+ if (value === undefined || value === null)
93
+ return derived(1, 0);
94
+ let startRow;
95
+ let startColumn;
96
+ let declaredEndRow;
97
+ let declaredEndColumn;
98
+ if (typeof value === 'string') {
99
+ const parsed = parseA1Range(value);
100
+ startRow = parsed.startRow;
101
+ startColumn = parsed.startColumn;
102
+ declaredEndRow = parsed.endRow;
103
+ declaredEndColumn = parsed.endColumn;
104
+ }
105
+ else {
106
+ const raw = asToolArguments(value, label + '.range');
107
+ startRow = raw.startRow === undefined ? 1 : parseIndex(raw.startRow, label + '.range.startRow');
108
+ startColumn = raw.startColumn === undefined ? 0 : parseIndex(raw.startColumn, label + '.range.startColumn');
109
+ declaredEndRow = raw.endRow === undefined ? undefined : parseIndex(raw.endRow, label + '.range.endRow');
110
+ declaredEndColumn = raw.endColumn === undefined ? undefined : parseIndex(raw.endColumn, label + '.range.endColumn');
111
+ }
112
+ const target = derived(startRow, startColumn);
113
+ if (declaredEndRow === undefined && declaredEndColumn === undefined)
114
+ return target;
115
+ if (declaredEndRow === target.endRow && declaredEndColumn === target.endColumn)
116
+ return target;
117
+ // Supplying only the range origin is enough. A declared end that disagrees
118
+ // is downgraded to a warning: the values matrix already fixes the extent.
119
+ const declared = { endRow: declaredEndRow ?? target.endRow, endColumn: declaredEndColumn ?? target.endColumn };
120
+ if (declared.endRow < target.startRow || declared.endColumn < target.startColumn) {
121
+ throw new Error(`${label}.range 结束位置不能小于开始位置:range 为 rows ${startRow}-${declared.endRow} × cols ${startColumn}-${declared.endColumn}`);
122
+ }
123
+ const declaredRows = declared.endRow - target.startRow + 1;
124
+ const declaredColumns = declared.endColumn - target.startColumn + 1;
125
+ warnings.push(`${label}.range 与 values 尺寸不一致:range 为 ${declaredRows}×${declaredColumns}(rows ${startRow}-${declared.endRow} × cols ${startColumn}-${declared.endColumn}),values 为 ${rowCount}×${columnCount};已按 values 落位为 rows ${target.startRow}-${target.endRow} × cols ${target.startColumn}-${target.endColumn}(end 坐标 inclusive)`);
126
+ return target;
127
+ }
128
+ export function parseApiValues(matrix, range) {
129
+ const rowCount = range.endRow - range.startRow + 1;
130
+ const columnCount = range.endColumn - range.startColumn + 1;
131
+ if (matrix.length !== rowCount)
132
+ throw new Error(`values 行数必须为 ${rowCount}`);
133
+ const cells = [];
134
+ for (const [rowOffset, line] of matrix.entries()) {
135
+ if (line.length !== columnCount)
136
+ throw new Error(`values[${rowOffset}] 列数必须为 ${columnCount}`);
137
+ for (const [columnOffset, cell] of line.entries()) {
138
+ if (cell !== null && typeof cell !== 'string' && typeof cell !== 'number' && typeof cell !== 'boolean') {
139
+ throw new Error(`values[${rowOffset}][${columnOffset}] 必须是 string/number/boolean/null`);
140
+ }
141
+ if (typeof cell === 'number' && !Number.isFinite(cell))
142
+ throw new Error('values 不能包含非有限数字');
143
+ cells.push({ row: range.startRow + rowOffset, column: range.startColumn + columnOffset, value: cell });
144
+ }
145
+ }
146
+ return cells;
147
+ }
148
+ /** A single cell of long text (typically a SQL body) read from a local file. */
149
+ export const TEXT_CELL_MAX_BYTES = 1024 * 1024;
150
+ export function parseFreeze(value) {
151
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
152
+ throw new Error('freeze 必须是对象');
153
+ const raw = value;
154
+ const rows = raw.rows === undefined ? 0 : parseIndex(raw.rows, 'freeze.rows');
155
+ const columns = raw.columns === undefined ? 0 : parseIndex(raw.columns, 'freeze.columns');
156
+ return { rows, columns };
157
+ }
158
+ export function parseSort(value) {
159
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
160
+ throw new Error('sort 必须是对象');
161
+ const raw = value;
162
+ const direction = raw.direction === undefined ? 'asc' : raw.direction;
163
+ if (direction !== 'asc' && direction !== 'desc')
164
+ throw new Error('sort.direction 必须是 asc 或 desc');
165
+ return { column: parseIndex(raw.column, 'sort.column'), direction };
166
+ }
167
+ export function cellValue(type, value) {
168
+ switch (type) {
169
+ case 'number':
170
+ case 'currency': {
171
+ const numeric = typeof value === 'number' ? value : Number(value);
172
+ return Number.isFinite(numeric) ? numeric : String(value);
173
+ }
174
+ case 'boolean':
175
+ return typeof value === 'boolean' ? value : String(value) === 'true';
176
+ case 'date':
177
+ return value instanceof Date ? value.toISOString().slice(0, 10) : String(value);
178
+ default:
179
+ return String(value);
180
+ }
181
+ }
182
+ /** Validate one sheet argument; throws a model-facing message on bad input. */
183
+ export function validateSheet(args) {
184
+ const name = args.name === undefined || args.name === null ? undefined : parseNonEmptyString(args.name, 'name');
185
+ if (name !== undefined && name.length > SHEET_MAX_NAME)
186
+ throw new Error(`表格名称过长(最多 ${SHEET_MAX_NAME} 字符)`);
187
+ if (args.columns !== undefined && !Array.isArray(args.columns))
188
+ throw new Error('columns 必须是数组;已有 sheet 可省略,Host 会从表头推断');
189
+ const rawColumns = Array.isArray(args.columns) ? args.columns : [];
190
+ if (rawColumns.length === 0 && !Array.isArray(args.rows))
191
+ throw new Error('至少需要一列');
192
+ if (rawColumns.length > SHEET_MAX_COLUMNS)
193
+ throw new Error(`列数超过上限(最多 ${SHEET_MAX_COLUMNS} 列)`);
194
+ const keys = new Set();
195
+ const columns = rawColumns.map((column, index) => {
196
+ // Column-definition errors have to state the accepted structure and echo
197
+ // what arrived: "列定义必须是对象" alone cost a debugging round in the
198
+ // original session (docs/issue/0011 I7 / T6).
199
+ if (typeof column !== 'object' || column === null || Array.isArray(column)) {
200
+ throw new Error(`列定义必须是对象:columns[${index}] 期望 { key, label, type },实际收到 ${JSON.stringify(column) ?? String(column)}`);
201
+ }
202
+ const raw = column;
203
+ if (typeof raw.key !== 'string' || !/^[A-Za-z0-9_]+$/.test(raw.key)) {
204
+ throw new Error(`列 key 必须是仅含字母、数字、下划线的非空字符串:columns[${index}] 期望 { key, label, type },实际收到 ${JSON.stringify(raw)}`);
205
+ }
206
+ if (keys.has(raw.key))
207
+ throw new Error(`列 key 重复:${raw.key}(columns[${index}])`);
208
+ keys.add(raw.key);
209
+ if (typeof raw.label !== 'string' || raw.label.length === 0) {
210
+ throw new Error(`列 ${raw.key} 缺少 label:期望 { key: ${JSON.stringify(raw.key)}, label: string, type: string },实际收到 ${JSON.stringify(raw)}`);
211
+ }
212
+ if (!COLUMN_TYPES.includes(raw.type)) {
213
+ throw new Error(`列 ${raw.key} 类型未知:${String(raw.type)};type 只能是 ${COLUMN_TYPES.join('、')}`);
214
+ }
215
+ return { key: raw.key, label: raw.label, type: raw.type };
216
+ });
217
+ if (!Array.isArray(args.rows))
218
+ throw new Error('rows 必须是数组');
219
+ if (args.rows.length > SHEET_MAX_ROWS) {
220
+ throw new Error(`行数超过上限(最多 ${SHEET_MAX_ROWS} 行);请使用分页查询或拆成多个 workbook`);
221
+ }
222
+ const rows = args.rows.every(row => Array.isArray(row))
223
+ ? args.rows.map((row, index) => {
224
+ if (columns.length === 0)
225
+ throw new Error('rows 使用二维数组时必须同时提供 columns');
226
+ const values = row;
227
+ if (values.length > columns.length)
228
+ throw new Error(`第 ${index + 1} 行列数超过 columns 定义`);
229
+ return Object.fromEntries(columns.map((column, columnIndex) => [column.key, values[columnIndex] ?? null]));
230
+ })
231
+ : args.rows.map((row, index) => {
232
+ if (typeof row !== 'object' || row === null || Array.isArray(row)) {
233
+ throw new Error(`第 ${index + 1} 行不是对象`);
234
+ }
235
+ return row;
236
+ });
237
+ return { name, columns, rows };
238
+ }
239
+ export function chunkRows(rows, size) {
240
+ const chunks = [];
241
+ for (let start = 0; start < rows.length; start += size)
242
+ chunks.push(rows.slice(start, start + size));
243
+ return chunks;
244
+ }
245
+ export function dimensionRange(index, dimension) {
246
+ return dimension === 'column'
247
+ ? { startRow: 0, startColumn: index, endRow: 0, endColumn: index }
248
+ : { startRow: index, startColumn: 0, endRow: index, endColumn: 0 };
249
+ }
250
+ /**
251
+ * Row/column structural commands accept either an explicit `range` (matching
252
+ * the documented command vocabulary) or the older `index`/`startRow` +
253
+ * `count` pair. Missing position arguments report the full accepted contract
254
+ * instead of the single `startRow` field the old implementation demanded.
255
+ */
256
+ export function dimensionCommandRange(raw, index, dimension) {
257
+ const label = `commands[${index}]`;
258
+ const positionField = dimension === 'row' ? 'startRow' : 'startColumn';
259
+ const endField = dimension === 'row' ? 'endRow' : 'endColumn';
260
+ const explicit = raw.range;
261
+ if (typeof explicit === 'string') {
262
+ const parsed = parseA1Range(explicit);
263
+ return dimension === 'row'
264
+ ? { startRow: parsed.startRow, startColumn: 0, endRow: parsed.endRow, endColumn: 0 }
265
+ : { startRow: 0, startColumn: parsed.startColumn, endRow: 0, endColumn: parsed.endColumn };
266
+ }
267
+ const range = explicit === undefined || explicit === null ? undefined : asToolArguments(explicit, `${label}.range`);
268
+ const startValue = range?.[positionField] ?? raw[positionField] ?? raw.index;
269
+ if (startValue === undefined || startValue === null) {
270
+ throw new Error(`${label} 缺少${dimension === 'row' ? '行' : '列'}位置:请传 ${label}.range:{${positionField},${endField}}(0-based,结束坐标 inclusive),或传 ${label}.index / ${label}.${positionField} 搭配 count`);
271
+ }
272
+ const start = parseIndex(startValue, `${label}.${positionField}`);
273
+ const rawEnd = range?.[endField];
274
+ const end = rawEnd === undefined || rawEnd === null ? start : parseIndex(rawEnd, `${label}.${endField}`);
275
+ if (end < start)
276
+ throw new Error(`${label}.range 结束位置不能小于开始位置(${start} > ${end})`);
277
+ return dimension === 'row'
278
+ ? { startRow: start, startColumn: 0, endRow: end, endColumn: 0 }
279
+ : { startRow: 0, startColumn: start, endRow: 0, endColumn: end };
280
+ }
281
+ export function parseFiniteNumber(value, label) {
282
+ if (typeof value !== 'number' || !Number.isFinite(value))
283
+ throw new Error(`${label} 必须是有限数字`);
284
+ return value;
285
+ }
286
+ export function parseHyperlink(value) {
287
+ if (typeof value !== 'string' || value.trim().length === 0)
288
+ throw new Error('url 必须是非空字符串');
289
+ const url = value.trim();
290
+ if (!/^(?:https?:|mailto:|file:|#)/i.test(url))
291
+ throw new Error('url 只支持 http(s)、mailto、file 或 sheet 内部链接');
292
+ return url;
293
+ }
294
+ export function parseHyperlinkLabel(value) {
295
+ if (typeof value !== 'string')
296
+ throw new Error('label 必须是字符串');
297
+ return value;
298
+ }
@@ -0,0 +1,2 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ export declare function registerWorkbookTools(ctx: Context): void;
@@ -0,0 +1,250 @@
1
+ /**
2
+ * The Agent tool surface of dsh-data-table (docs/issue/0022 P3b).
3
+ *
4
+ * Seven tools, one registry. Each `execute` delegates to a module beside this
5
+ * one, so the registry is a list of names, schemas and renderers — the part a
6
+ * reviewer reads to see what the Agent may do — and never the part that decides
7
+ * how a workbook changes.
8
+ */
9
+ import { defineTool } from '@deepseek-ai/dsh-tools';
10
+ import { rollbackWorkbook } from "../workbook-manager.js";
11
+ import { projectScopeOf, runInProject } from "../store/project.js";
12
+ import { optionalRevision, parseIndex } from "../request-params.js";
13
+ import { lineageOfArgs } from "../audit.js";
14
+ import { UNIVER_API_CATALOG, UNIVER_API_VERSION } from "../contract/univer-api-catalog.js";
15
+ import { optionalAuditText, optionalIntent, optionalString, parseNonEmptyString, workbookIdOf } from "./arguments.js";
16
+ import { asRecordOrUndefined, asJsonValue, renderCatalogResult, renderEditResult, renderFindResult, renderHistoryResult, renderReadResult, renderSummaryAndValue, spillLargeToolResult, } from "./render.js";
17
+ import { asApiArguments, executeUniverApi, parseUniverApiAction } from "./univer-api.js";
18
+ import { executeWorkbookCatalog } from "./workbook-catalog.js";
19
+ import { executeWorkbookEdit } from "./workbook-edit.js";
20
+ import { findWorkbookContent } from "./workbook-find.js";
21
+ import { executeHistoryQuery } from "./workbook-history.js";
22
+ import { readWorkbookTarget } from "./workbook-read.js";
23
+ /**
24
+ * The Agent Session a tool call runs in.
25
+ *
26
+ * `agent.id` **is** the Session id (`Agent` is the live face of one Session), and
27
+ * it is the same id the browser sends as `x-dsh-session` — which is what lets the
28
+ * two halves name one directory. Empty when the call runs in no Agent at all.
29
+ */
30
+ function sessionIdOf(exec) {
31
+ return String(exec?.agent?.id ?? '');
32
+ }
33
+ export function registerWorkbookTools(ctx) {
34
+ // Every tool runs inside the calling Session's project, established once
35
+ // here rather than in each `execute`: the store root is read deep inside
36
+ // `workbook-manager`, and a second registration path that forgot the scope
37
+ // would read and write another project's workbooks.
38
+ const register = (definition) => {
39
+ // The project comes from the Session **header**, through the Host's own
40
+ // registry — never from the calling Agent's Session object, whose `cwd` is
41
+ // not a field it has (see `store/project.ts`). When no Host service names a
42
+ // project (a test harness, an embedding with no policy) the wrapper leaves
43
+ // the scope unset instead of inventing one.
44
+ const projectOf = (exec) => projectScopeOf(ctx.sessions, sessionIdOf(exec));
45
+ ctx.tools.register({
46
+ ...definition,
47
+ execute: (args, exec) => {
48
+ const project = projectOf(exec);
49
+ const run = async () => definition.execute(args, exec);
50
+ return project === undefined ? run() : runInProject(project, run);
51
+ },
52
+ });
53
+ };
54
+ register(defineTool({
55
+ name: 'workbook_catalog',
56
+ description: '工作簿目录与生命周期入口。action=list、create、rename、delete;任何数据读写都必须显式指定 workbookId。未指定 workbookId 时,存储文件名(= id)按 `yyyymmdd-中文名含义` 生成,中文名 ≤25 字。',
57
+ parameters: {
58
+ action: { type: 'string', required: true, enum: ['list', 'create', 'rename', 'delete'] },
59
+ workbookId: { type: 'string', description: 'create 时可指定稳定 id;rename/delete 时必填。指定时请遵循同一命名规则:`yyyymmdd-中文名含义`(如 20261002-销售汇总),中文名 ≤25 字;省略则由 name 自动生成。' },
60
+ name: { type: 'string', description: 'create/rename 时的工作簿名称,也是自动生成的 id 里那半段中文名的来源(≤25 字)。' },
61
+ confirm: { type: 'boolean', description: 'delete 必须显式传 true' },
62
+ },
63
+ output: { schema: { type: 'json' }, render: (_args, value) => renderCatalogResult(value) },
64
+ async execute(args, exec) {
65
+ return asJsonValue(await spillLargeToolResult('workbook_catalog', await executeWorkbookCatalog(args)));
66
+ },
67
+ }));
68
+ register(defineTool({
69
+ name: 'workbook_read',
70
+ description: '读取明确工作簿内的 workbook、sheet、range 或 cells。sheet 可填写 sheet、sheetId 或 sheetName;单 sheet 工作簿可省略。target.kind=workbook 会返回 sheetName ↔ sheetId 映射与每个 sheet 的规模,先用本工具取得 revision,再将其作为 workbook_edit 的 baseRevision。',
71
+ parameters: {
72
+ workbookId: { type: 'string', required: true, description: '工作簿 id(必填);先用 workbook_catalog(action=list) 获取' },
73
+ target: {
74
+ type: 'json',
75
+ required: true,
76
+ description: '目标:{ kind: workbook|sheet|range|cells, sheet?|sheetId?|sheetName?, range?, cells? }。sheet 可用 id 或名称;range 使用 0-based 坐标且结束坐标 inclusive;cells 为 [{row,column}]。',
77
+ },
78
+ include: { type: 'array', items: { type: 'string' }, description: '可选:values、formulas、cells、styles、snapshot、backfill。range 读取默认只返回 values;formulas / cells 是同一份数据的另两种视图,会成倍占用回显预算,仅在确实需要时按需索取。backfill 仅对 kind=workbook 有效:为每个 sheet 返回数据区行数、列数、结论行数、run 时间、来源与 schema 更新时间,以及 complete 提示,用于判断工作簿是否回填完整。' },
79
+ },
80
+ output: { schema: { type: 'json' }, render: (_args, value) => renderReadResult(value) },
81
+ async execute(args) {
82
+ return asJsonValue(await spillLargeToolResult('workbook_read', await readWorkbookTarget(workbookIdOf(args.workbookId), args.target, args.include)));
83
+ },
84
+ }));
85
+ register(defineTool({
86
+ name: 'workbook_find',
87
+ description: '在指定工作簿中查找 sheet 名、单元格值或公式。sheet 可填写 id 或名称。不会修改任何内容。',
88
+ parameters: {
89
+ workbookId: { type: 'string', required: true, description: '工作簿 id(必填);先用 workbook_catalog(action=list) 获取' },
90
+ query: { type: 'string', required: true, description: '查找文本;match=regex 时为正则表达式' },
91
+ scope: { type: 'string', enum: ['sheets', 'cells', 'formulas'], description: '默认 cells' },
92
+ match: { type: 'string', enum: ['contains', 'exact', 'regex'], description: '默认 contains' },
93
+ sheet: { type: 'string', description: '可选 sheet id 或名称;也接受 sheetId/sheetName' },
94
+ range: { type: 'json', description: '可选 0-based 搜索范围(结束坐标 inclusive)' },
95
+ limit: { type: 'number', description: '默认 100,最多 500' },
96
+ },
97
+ output: { schema: { type: 'json' }, render: (_args, value) => renderFindResult(value) },
98
+ async execute(args) {
99
+ return asJsonValue(await spillLargeToolResult('workbook_find', await findWorkbookContent(workbookIdOf(args.workbookId), args)));
100
+ },
101
+ }));
102
+ register(defineTool({
103
+ name: 'workbook_edit',
104
+ description: '唯一的业务写入口。一个语义 versions 分组落为一个可回放 Version,分组内 commands 是按顺序记录的 Step;Host 负责 revision、Version 和并发控制,Agent 不得传 versionId。command 的 sheet、sheetId、sheetName 可互换;单 sheet 工作簿可省略 sheet。兼容 commands 简写:整组命令视为一个 Version。批量结果落表优先用 import_file 或 update_cells + sourcePath(一次命令 + 一个本地文件路径),不要把大数据逐格内联进 values。',
105
+ parameters: {
106
+ workbookId: { type: 'string', required: true, description: '工作簿 id(必填);先用 workbook_catalog(action=list) 获取' },
107
+ baseRevision: { type: 'number', description: '可选的乐观锁,仅在需要阻止覆盖他人修改时传;不传表示接受当前主线。只做单元格写入的计划在本值过期时会自动变基到最新 revision 并返回 transformed: true;含行/列结构操作的计划才会返回 REVISION_CONFLICT,此时重新读取后重试整个计划' },
108
+ opId: { type: 'string', description: '幂等请求 id;多个 Version 分组会派生稳定子 id' },
109
+ intent: { type: 'string', description: '本次编辑的简短业务意图,作为未单独声明 intent 的分组审计信息' },
110
+ agentRunId: { type: 'string', description: '可选 Agent 运行关联 id;仅用于审计同一次运行产生的多个 Version' },
111
+ sessionId: { type: 'string', description: '关联的 DSH session id' },
112
+ turnId: { type: 'string', description: '当前对话轮次 id' },
113
+ intentId: { type: 'string', description: '关联的意图 id' },
114
+ taskId: { type: 'string', description: '关联的业务任务 id' },
115
+ subtaskId: { type: 'string', description: '关联的业务子任务 id' },
116
+ parentSubtaskId: { type: 'string', description: '关联的父子任务 id' },
117
+ runId: { type: 'string', description: '本次执行尝试 id' },
118
+ traceId: { type: 'string', description: '可选技术追踪 id' },
119
+ domain: { type: 'string', description: '业务域,例如 data' },
120
+ dryRun: { type: 'boolean', description: 'true 时用与正式提交完全相同的校验路径在内存中预检,不写入历史;返回 revisionBefore/revisionAfter/affectedSheets/affectedCellCount/warnings 预检清单' },
121
+ versions: {
122
+ type: 'array',
123
+ items: { type: 'json' },
124
+ description: '推荐。1–20 个语义 Version 分组,每项为 { summary, intent?, commands }。summary 是用户可读的变更说明;commands 中的 1–100 条命令成为该 Version 的有序 Step。append_rows 超过 1000 行会自动拆成多个 Step;append_rows、update_cells、import_file 都可传 sourcePath 从本地 CSV/JSON/JSONL/XLSX/XLS 导入(≤128 MiB、最多 100000 行数据 × 50 列);所有分组总数最多 100 条命令。整个编辑计划一次性提交。',
125
+ },
126
+ commands: {
127
+ type: 'array',
128
+ items: { type: 'json' },
129
+ description: '兼容简写。1–100 条声明式命令,整组作为一个 Version;与 versions 二选一。type 支持 update_cells、import_file、clear_range、delete_cells、insert_rows、delete_rows、insert_columns、delete_columns、create_sheet、duplicate_sheet、delete_sheet、rename_sheet、append_rows、format_cells、merge_cells、unmerge_cells、freeze_panes、set_column_width、set_row_height、sort_range、set_hyperlink、remove_hyperlink。duplicate_sheet 用于跨工作簿复用 sheet 布局:{ type: duplicate_sheet, sheet, name, sheetId?, fromWorkbookId? } 会新建一个 sheet 并复制源 sheet 的单元格内容(标题块、表头行、长 SQL 文本都算)与列宽;fromWorkbookId 省略时取当前工作簿,源 sheet 必须已存在(同一批次内刚创建的 sheet 不在此列);样式、合并、冻结、数据校验不复制。命令字段可使用 sheet/sheetId/sheetName、fromWorkbookId、range、cells、values、data、name、index、count、columns、rows、sourcePath、sourceFormat、startRow、startColumn、headerRowIndex、verifyHeader、verifyCapacity、allowTruncate、style、freeze、sort、size、url、label、meta。update_cells 的单个 cell 还可给 valueFromFile(本地文本文件路径,≤1 MiB),用于把长文本(典型是 SQL)从文件写进单元格而不必内联进工具调用;同给 value 时 value 优先。range 的结束坐标 startRow/endRow/startColumn/endColumn 都是 inclusive(0-based):想写 4 行 2 列请用 {startRow:4,startColumn:0,endRow:7,endColumn:1}。update_cells 省略 range 时按 values 从 startRow/startColumn 推导,两者都省略则默认落在第 1 行第 0 列(表头下方的首个数据行,会覆盖该处已有内容;要往数据区末尾追加请用 append_rows,要指定起点请显式传 startRow/startColumn);range 与 values 冲突时以 values 为准并在返回值 warnings 中提示。批量导入通道:update_cells 省略 values 并给 sourcePath 时按文件数据落位;{ type: import_file, sheet, startRow, startColumn, sourcePath, sourceFormat?, verifyCapacity?, allowTruncate? } 是推荐的一次性导入命令,startRow 是必填的 0-based 起点,源文件第一行作为列名、只写入数据行,且第 0 行不必是表头,回执含 workbookId/revisionBefore/revisionAfter/sheetId/range/rowsWritten/cellsWritten/sourceRowCount/truncated/firstRow/lastRow/valuesHash 与自动扩容说明;源文件超出可写区间时默认报错,只有显式传 allowTruncate: true 才会截断导入(回执 truncated: true 并在 warnings 说明)。写入越界(含 update_cells)会自动扩容 rowCount,无需 insert_rows。append_rows 默认要求第 0 行就是表头且列名一致;报告型 sheet 请用 headerRowIndex 指向真实列头行(数据写在它下面,列头行本身不被覆盖),或用 import_file / update_cells + sourcePath;verifyHeader: false 只跳过列名文本比对,列数仍需一致。行上限 SHEET_MAX_ROWS=100000(物理行 0–100000,row 0 可为表头),单条命令超过 SHEET_MAX_APPEND_ROWS=1000 行时 append_rows 会自动拆成多个 Step。insert_rows/delete_rows 接受 range:{startRow,endRow} 或 index/startRow + count,insert_columns/delete_columns 接受 range:{startColumn,endColumn} 或 index/startColumn + count;count 缺省时按 range 跨度推导。create_sheet 可省略 sheetId(Host 会生成稳定 id),同一批次内后续命令可按 sheet 名引用新建的 sheet。同一 cell 在本次计划中被多次写入会返回 warnings。',
130
+ },
131
+ },
132
+ output: { schema: { type: 'json' }, render: (_args, value) => renderEditResult(value) },
133
+ async execute(args, exec) {
134
+ return asJsonValue(await spillLargeToolResult('workbook_edit', await executeWorkbookEdit({
135
+ workbookId: workbookIdOf(args.workbookId),
136
+ versions: args.versions,
137
+ commands: args.commands,
138
+ baseRevision: optionalRevision(args.baseRevision),
139
+ opId: optionalString(args.opId),
140
+ intent: optionalIntent(args.intent),
141
+ agentRunId: optionalAuditText(args.agentRunId, 'agentRunId', 160),
142
+ lineage: lineageOfArgs(args, exec),
143
+ dryRun: args.dryRun === true,
144
+ })));
145
+ },
146
+ }));
147
+ register(defineTool({
148
+ name: 'workbook_history',
149
+ description: '历史查询入口。action=list 返回 Version(默认)及其隐藏 Step;diff 比较两个 revision;replay 读取某个 revision 或其中一个 step 的可回放快照。',
150
+ parameters: {
151
+ workbookId: { type: 'string', required: true, description: '工作簿 id(必填);先用 workbook_catalog(action=list) 获取' },
152
+ action: { type: 'string', enum: ['list', 'diff', 'replay'], description: '默认 list' },
153
+ limit: { type: 'number', description: 'list 默认 20、最多 100' },
154
+ fromRevision: { type: 'number', description: 'diff 起始 revision' },
155
+ toRevision: { type: 'number', description: 'diff 终止 revision,或 replay 目标 revision' },
156
+ revision: { type: 'number', description: 'replay 目标 revision(优先于 toRevision)' },
157
+ step: { type: 'number', description: 'replay 中的 0-based Step;省略则读取整个 Version 末尾' },
158
+ },
159
+ output: { schema: { type: 'json' }, render: (_args, value) => renderHistoryResult(value) },
160
+ async execute(args, exec) {
161
+ return asJsonValue(await spillLargeToolResult('workbook_history', await executeHistoryQuery(workbookIdOf(args.workbookId), args)));
162
+ },
163
+ }));
164
+ register(defineTool({
165
+ name: 'workbook_restore',
166
+ description: '将 workbook 恢复到某个可回放 revision,并作为新的恢复 Version 记录。原先后续版本保留为 superseded 历史,不会被删除。',
167
+ parameters: {
168
+ workbookId: { type: 'string', required: true, description: '工作簿 id(必填);先用 workbook_catalog(action=list) 获取' },
169
+ targetRevision: { type: 'number', required: true, description: '要恢复到的 revision' },
170
+ baseRevision: { type: 'number', description: '当前 revision,用于并发保护' },
171
+ opId: { type: 'string', description: '可选幂等恢复操作 id' },
172
+ sessionId: { type: 'string', description: '关联的 DSH session id' },
173
+ turnId: { type: 'string', description: '当前对话轮次 id' },
174
+ intentId: { type: 'string', description: '关联的意图 id' },
175
+ taskId: { type: 'string', description: '关联的业务任务 id' },
176
+ subtaskId: { type: 'string', description: '关联的业务子任务 id' },
177
+ parentSubtaskId: { type: 'string', description: '关联的父子任务 id' },
178
+ runId: { type: 'string', description: '本次执行尝试 id' },
179
+ traceId: { type: 'string', description: '可选技术追踪 id' },
180
+ domain: { type: 'string', description: '业务域,例如 data' },
181
+ },
182
+ output: { schema: { type: 'json' }, render: (_args, value) => renderSummaryAndValue(`工作簿已恢复:${String(asRecordOrUndefined(value)?.workbookId ?? '')} revision=${String(asRecordOrUndefined(value)?.revision ?? 0)}`, value) },
183
+ async execute(args, exec) {
184
+ const id = workbookIdOf(args.workbookId);
185
+ const result = await rollbackWorkbook({
186
+ workbookId: id,
187
+ targetRevision: parseIndex(args.targetRevision, 'targetRevision'),
188
+ expectedRevision: optionalRevision(args.baseRevision),
189
+ actor: 'agent',
190
+ opId: optionalString(args.opId),
191
+ lineage: lineageOfArgs(args, exec),
192
+ });
193
+ return asJsonValue({ ok: true, workbookId: id, revision: result.meta.revision ?? 0, operation: result.operation });
194
+ },
195
+ }));
196
+ register(defineTool({
197
+ name: 'univer_api',
198
+ description: 'Univer Adapter Registry。action=catalog/search 发现已注册的安全能力;action=invoke 调用其中一个适配器。它不是任意 JavaScript 或原始 Injector 入口;所有写入仍会落为可回放历史。',
199
+ parameters: {
200
+ action: { type: 'string', required: true, enum: ['catalog', 'search', 'invoke'] },
201
+ query: { type: 'string', description: 'search 的关键词' },
202
+ workbookId: { type: 'string', description: 'invoke 时必填' },
203
+ api: { type: 'string', description: 'invoke 时必填;先用 catalog/search 确认' },
204
+ arguments: { type: 'json', description: 'invoke 参数,结构由 catalog 定义' },
205
+ baseRevision: { type: 'number', description: '写入适配器的并发基准' },
206
+ opId: { type: 'string', description: '写入适配器的幂等 id' },
207
+ sessionId: { type: 'string', description: '关联的 DSH session id' },
208
+ turnId: { type: 'string', description: '当前对话轮次 id' },
209
+ intentId: { type: 'string', description: '关联的意图 id' },
210
+ taskId: { type: 'string', description: '关联的业务任务 id' },
211
+ subtaskId: { type: 'string', description: '关联的业务子任务 id' },
212
+ parentSubtaskId: { type: 'string', description: '关联的父子任务 id' },
213
+ runId: { type: 'string', description: '本次执行尝试 id' },
214
+ traceId: { type: 'string', description: '可选技术追踪 id' },
215
+ domain: { type: 'string', description: '业务域,例如 data' },
216
+ },
217
+ output: {
218
+ schema: { type: 'json' },
219
+ render: (args, value) => {
220
+ const record = asRecordOrUndefined(value) ?? {};
221
+ const action = asRecordOrUndefined(args)?.action;
222
+ const summary = action === 'catalog'
223
+ ? `Univer API 目录(version=${String(record.version ?? '')},${Array.isArray(record.catalog) ? record.catalog.length : 0} 项)`
224
+ : action === 'search'
225
+ ? `搜索到 ${Array.isArray(record.results) ? record.results.length : 0} 个 Univer API`
226
+ : `Univer Adapter 调用已完成:workbookId=${String(record.workbookId ?? '')} revision=${String(record.revision ?? 0)}`;
227
+ return renderSummaryAndValue(summary, value);
228
+ },
229
+ },
230
+ async execute(args, exec) {
231
+ const action = parseUniverApiAction(args.action);
232
+ if (action === 'catalog')
233
+ return asJsonValue(await spillLargeToolResult('univer_api', { ok: true, version: UNIVER_API_VERSION, catalog: UNIVER_API_CATALOG }));
234
+ if (action === 'search') {
235
+ const query = parseNonEmptyString(args.query, 'query').toLowerCase();
236
+ const results = UNIVER_API_CATALOG.filter(definition => (definition.api + ' ' + definition.description + ' ' + (definition.operation ?? '')).toLowerCase().includes(query));
237
+ return asJsonValue(await spillLargeToolResult('univer_api', { ok: true, version: UNIVER_API_VERSION, query, results }));
238
+ }
239
+ const result = await executeUniverApi({
240
+ workbookId: workbookIdOf(args.workbookId),
241
+ api: parseNonEmptyString(args.api, 'api'),
242
+ arguments: asApiArguments(args.arguments),
243
+ expectedRevision: optionalRevision(args.baseRevision),
244
+ opId: optionalString(args.opId),
245
+ lineage: lineageOfArgs(args, exec),
246
+ });
247
+ return asJsonValue(await spillLargeToolResult('univer_api', result));
248
+ },
249
+ }));
250
+ }
@@ -0,0 +1,47 @@
1
+ import type { JsonValue } from '@deepseek-ai/dsh-util-values';
2
+ import type { WorkbookOperation } from '../types.ts';
3
+ export interface ToolTextBlock {
4
+ type: 'text';
5
+ text: string;
6
+ }
7
+ export declare function textBlock(text: string): ToolTextBlock;
8
+ export declare function asRecordOrUndefined(value: unknown): Record<string, unknown> | undefined;
9
+ export declare function renderSummaryAndValue(summary: string, value: unknown): ToolTextBlock[];
10
+ /**
11
+ * Model-facing tool output.
12
+ *
13
+ * DSH treats `output.render` as the only content the model sees
14
+ * (harness `createSuccessResult`), so a constant string silently drops the
15
+ * whole workbook payload. These helpers keep a readable summary plus the real
16
+ * JSON inline, and oversized results are spilled to
17
+ * `<storage root>/tool-output/*.json` during `execute` and reported as
18
+ * `detailPath` instead of being truncated away.
19
+ */
20
+ export declare const TOOL_RESULT_INLINE_LIMIT = 24000;
21
+ /** Spill an oversized canonical value so the model can still page through it. */
22
+ export declare function spillLargeToolResult(toolName: string, value: unknown): Promise<Record<string, unknown>>;
23
+ /** Keep large write payloads out of the model/tool transcript. */
24
+ export declare function compactToolValue(value: unknown): unknown;
25
+ export declare function asJsonValue(value: unknown): JsonValue;
26
+ export declare function isOperationRecord(value: unknown): value is WorkbookOperation;
27
+ export declare function operationReceipt(operation: WorkbookOperation): Record<string, unknown>;
28
+ export interface SheetOverview {
29
+ id: string;
30
+ name: string;
31
+ rowCount: number;
32
+ columnCount: number;
33
+ maxRow: number | null;
34
+ maxColumn: number | null;
35
+ nonEmptyCells: number;
36
+ }
37
+ export declare function isNonEmptyCell(cell: unknown): boolean;
38
+ /** Compact per-sheet map (`name ↔ id` + size) that Agent callers use to address sheets. */
39
+ export declare function sheetOverviews(snapshot: unknown): SheetOverview[];
40
+ export declare function sheetMapSummary(sheets: readonly SheetOverview[]): string;
41
+ /** Inline `名称(id)` list used by self-healing error messages. */
42
+ export declare function sheetReferenceList(snapshot: unknown): string;
43
+ export declare function renderCatalogResult(value: unknown): ToolTextBlock[];
44
+ export declare function renderReadResult(value: unknown): ToolTextBlock[];
45
+ export declare function renderFindResult(value: unknown): ToolTextBlock[];
46
+ export declare function renderHistoryResult(value: unknown): ToolTextBlock[];
47
+ export declare function renderEditResult(value: unknown): ToolTextBlock[];