@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.
- package/LICENSE +21 -0
- package/README.md +172 -2
- package/cordis.patch.yml +7 -0
- package/lib/client.js +16766 -0
- package/lib/contract-envelope.d.ts +91 -0
- package/lib/contract-envelope.js +106 -0
- package/lib/contract-layout.d.ts +130 -0
- package/lib/contract-layout.js +138 -0
- package/lib/contract-refs.d.ts +39 -0
- package/lib/contract-refs.js +9 -0
- package/lib/contract-version.d.ts +311 -0
- package/lib/contract-version.js +270 -0
- package/lib/contract-wire.d.ts +147 -0
- package/lib/contract-wire.js +99 -0
- package/lib/contracts.d.ts +12 -0
- package/lib/contracts.js +12 -0
- package/lib/types/audit.d.ts +42 -0
- package/lib/types/audit.js +39 -0
- package/lib/types/client/ProjectImportMenu.d.ts +30 -0
- package/lib/types/client/ProjectImportMenu.js +70 -0
- package/lib/types/client/SheetPanel.d.ts +33 -0
- package/lib/types/client/SheetPanel.js +1766 -0
- package/lib/types/client/SheetToggle.d.ts +18 -0
- package/lib/types/client/SheetToggle.js +26 -0
- package/lib/types/client/index.d.ts +15 -0
- package/lib/types/client/index.js +110 -0
- package/lib/types/client/persistence/changes.d.ts +15 -0
- package/lib/types/client/persistence/changes.js +51 -0
- package/lib/types/client/persistence/ids.d.ts +34 -0
- package/lib/types/client/persistence/ids.js +48 -0
- package/lib/types/client/persistence/merge.d.ts +35 -0
- package/lib/types/client/persistence/merge.js +362 -0
- package/lib/types/client/store.d.ts +61 -0
- package/lib/types/client/store.js +152 -0
- package/lib/types/client/surface-context.d.ts +27 -0
- package/lib/types/client/surface-context.js +104 -0
- package/lib/types/client/surface-control.d.ts +2 -0
- package/lib/types/client/surface-control.js +12 -0
- package/lib/types/client/transport/workbookApi.d.ts +159 -0
- package/lib/types/client/transport/workbookApi.js +246 -0
- package/lib/types/client/univer/StandaloneWorkbookPage.d.ts +5 -0
- package/lib/types/client/univer/StandaloneWorkbookPage.js +7 -0
- package/lib/types/client/univer/WorkbookCanvas.d.ts +40 -0
- package/lib/types/client/univer/WorkbookCanvas.js +87 -0
- package/lib/types/client/univer/WorkbookView.d.ts +3 -0
- package/lib/types/client/univer/WorkbookView.js +2 -0
- package/lib/types/client/univer/loader.d.ts +18 -0
- package/lib/types/client/univer/loader.js +81 -0
- package/lib/types/client/univer/standalone-entry.d.ts +1 -0
- package/lib/types/client/univer/standalone-entry.js +19 -0
- package/lib/types/client/versions/VersionChrome.d.ts +33 -0
- package/lib/types/client/versions/VersionChrome.js +21 -0
- package/lib/types/client/versions/inspect.d.ts +70 -0
- package/lib/types/client/versions/inspect.js +88 -0
- package/lib/types/client/versions/preview-decoration.d.ts +11 -0
- package/lib/types/client/versions/preview-decoration.js +54 -0
- package/lib/types/client/versions/records.d.ts +19 -0
- package/lib/types/client/versions/records.js +70 -0
- package/lib/types/client/workspace-host.d.ts +61 -0
- package/lib/types/client/workspace-host.js +124 -0
- package/lib/types/collaboration-hub.d.ts +31 -0
- package/lib/types/collaboration-hub.js +69 -0
- package/lib/types/contract/ids.d.ts +24 -0
- package/lib/types/contract/ids.js +46 -0
- package/lib/types/contract/mutations.d.ts +25 -0
- package/lib/types/contract/mutations.js +782 -0
- package/lib/types/contract/operations.d.ts +54 -0
- package/lib/types/contract/operations.js +203 -0
- package/lib/types/contract/snapshots.d.ts +176 -0
- package/lib/types/contract/snapshots.js +351 -0
- package/lib/types/contract/univer-api-catalog.d.ts +29 -0
- package/lib/types/contract/univer-api-catalog.js +264 -0
- package/lib/types/errors.d.ts +8 -0
- package/lib/types/errors.js +12 -0
- package/lib/types/http/assets-routes.d.ts +3 -0
- package/lib/types/http/assets-routes.js +169 -0
- package/lib/types/http/collaboration.d.ts +10 -0
- package/lib/types/http/collaboration.js +154 -0
- package/lib/types/http/history.d.ts +15 -0
- package/lib/types/http/history.js +134 -0
- package/lib/types/http/project-files.d.ts +79 -0
- package/lib/types/http/project-files.js +206 -0
- package/lib/types/http/project-scope.d.ts +50 -0
- package/lib/types/http/project-scope.js +52 -0
- package/lib/types/http/routes.d.ts +98 -0
- package/lib/types/http/routes.js +323 -0
- package/lib/types/http/standalone.d.ts +9 -0
- package/lib/types/http/standalone.js +31 -0
- package/lib/types/http/trunk.d.ts +10 -0
- package/lib/types/http/trunk.js +322 -0
- package/lib/types/http/worktree.d.ts +9 -0
- package/lib/types/http/worktree.js +145 -0
- package/lib/types/index.d.ts +18 -0
- package/lib/types/index.js +156 -0
- package/lib/types/invariant.d.ts +15 -0
- package/lib/types/invariant.js +15 -0
- package/lib/types/request-params.d.ts +21 -0
- package/lib/types/request-params.js +76 -0
- package/lib/types/store/paths.d.ts +68 -0
- package/lib/types/store/paths.js +148 -0
- package/lib/types/store/project.d.ts +88 -0
- package/lib/types/store/project.js +120 -0
- package/lib/types/store/read.d.ts +110 -0
- package/lib/types/store/read.js +404 -0
- package/lib/types/store/retention.d.ts +27 -0
- package/lib/types/store/retention.js +111 -0
- package/lib/types/store/write.d.ts +22 -0
- package/lib/types/store/write.js +210 -0
- package/lib/types/tools/arguments.d.ts +40 -0
- package/lib/types/tools/arguments.js +130 -0
- package/lib/types/tools/cells.d.ts +50 -0
- package/lib/types/tools/cells.js +298 -0
- package/lib/types/tools/index.d.ts +2 -0
- package/lib/types/tools/index.js +250 -0
- package/lib/types/tools/render.d.ts +47 -0
- package/lib/types/tools/render.js +290 -0
- package/lib/types/tools/univer-api.d.ts +18 -0
- package/lib/types/tools/univer-api.js +198 -0
- package/lib/types/tools/workbook-catalog.d.ts +3 -0
- package/lib/types/tools/workbook-catalog.js +83 -0
- package/lib/types/tools/workbook-edit.d.ts +192 -0
- package/lib/types/tools/workbook-edit.js +912 -0
- package/lib/types/tools/workbook-find.d.ts +11 -0
- package/lib/types/tools/workbook-find.js +89 -0
- package/lib/types/tools/workbook-history.d.ts +2 -0
- package/lib/types/tools/workbook-history.js +59 -0
- package/lib/types/tools/workbook-read.d.ts +21 -0
- package/lib/types/tools/workbook-read.js +204 -0
- package/lib/types/types.d.ts +377 -0
- package/lib/types/types.js +34 -0
- package/lib/types/univer/apply-operation.d.ts +14 -0
- package/lib/types/univer/apply-operation.js +398 -0
- package/lib/types/univer/chunk.d.ts +68 -0
- package/lib/types/univer/chunk.js +16 -0
- package/lib/types/univer/decoration.d.ts +34 -0
- package/lib/types/univer/decoration.js +88 -0
- package/lib/types/univer/diagnostics.d.ts +31 -0
- package/lib/types/univer/diagnostics.js +141 -0
- package/lib/types/univer/engine.d.ts +33 -0
- package/lib/types/univer/engine.js +338 -0
- package/lib/types/univer/entry.d.ts +8 -0
- package/lib/types/univer/entry.js +11 -0
- package/lib/types/univer/facade.d.ts +107 -0
- package/lib/types/univer/facade.js +1 -0
- package/lib/types/univer/fonts.d.ts +202 -0
- package/lib/types/univer/fonts.js +140 -0
- package/lib/types/univer/plugins/collaboration.d.ts +23 -0
- package/lib/types/univer/plugins/collaboration.js +47 -0
- package/lib/types/univer/plugins/export.d.ts +39 -0
- package/lib/types/univer/plugins/export.js +246 -0
- package/lib/types/univer/plugins/history.d.ts +24 -0
- package/lib/types/univer/plugins/history.js +50 -0
- package/lib/types/univer/plugins/version.d.ts +13 -0
- package/lib/types/univer/plugins/version.js +13 -0
- package/lib/types/version/diff.d.ts +36 -0
- package/lib/types/version/diff.js +156 -0
- package/lib/types/version/grouping.d.ts +7 -0
- package/lib/types/version/grouping.js +94 -0
- package/lib/types/version/records.d.ts +17 -0
- package/lib/types/version/records.js +102 -0
- package/lib/types/version/session.d.ts +36 -0
- package/lib/types/version/session.js +89 -0
- package/lib/types/version/summary.d.ts +24 -0
- package/lib/types/version/summary.js +111 -0
- package/lib/types/workbook-manager.d.ts +191 -0
- package/lib/types/workbook-manager.js +935 -0
- package/lib/types/xlsx-export-worker.d.ts +5 -0
- package/lib/types/xlsx-export-worker.js +24 -0
- package/lib/types/xlsx-export.d.ts +21 -0
- package/lib/types/xlsx-export.js +371 -0
- package/lib/types/xlsx-import-worker.d.ts +1 -0
- package/lib/types/xlsx-import-worker.js +19 -0
- package/lib/univer-client.js +690 -0
- package/lib/workbook-page.js +23826 -0
- package/lib/xlsx-export-worker.js +104 -0
- package/lib/xlsx-import-worker.js +46 -0
- package/package.json +106 -3
- package/src/audit.ts +58 -0
- package/src/client/ProjectImportMenu.tsx +100 -0
- package/src/client/SheetPanel.module.css +1578 -0
- package/src/client/SheetPanel.tsx +1934 -0
- package/src/client/SheetToggle.module.css +73 -0
- package/src/client/SheetToggle.tsx +65 -0
- package/src/client/contract.d.ts +19 -0
- package/src/client/index.ts +131 -0
- package/src/client/persistence/changes.ts +58 -0
- package/src/client/persistence/ids.ts +60 -0
- package/src/client/persistence/merge.ts +392 -0
- package/src/client/store.ts +188 -0
- package/src/client/surface-context.ts +123 -0
- package/src/client/surface-control.ts +13 -0
- package/src/client/transport/workbookApi.ts +355 -0
- package/src/client/univer/SheetView.module.css +110 -0
- package/src/client/univer/StandaloneWorkbookPage.module.css +8 -0
- package/src/client/univer/StandaloneWorkbookPage.tsx +15 -0
- package/src/client/univer/WorkbookCanvas.tsx +128 -0
- package/src/client/univer/WorkbookView.module.css +30 -0
- package/src/client/univer/WorkbookView.tsx +3 -0
- package/src/client/univer/loader.ts +90 -0
- package/src/client/univer/react-dom-client.d.ts +10 -0
- package/src/client/univer/standalone-entry.tsx +19 -0
- package/src/client/univer/standalone.css +12 -0
- package/src/client/univer/univer-locales.d.ts +89 -0
- package/src/client/univer/univer-modules.d.ts +26 -0
- package/src/client/versions/VersionChrome.tsx +114 -0
- package/src/client/versions/inspect.ts +138 -0
- package/src/client/versions/preview-decoration.ts +54 -0
- package/src/client/versions/records.ts +78 -0
- package/src/client/workspace-host.ts +147 -0
- package/src/collaboration-hub.ts +83 -0
- package/src/contract/ids.ts +50 -0
- package/src/contract/mutations.ts +791 -0
- package/src/contract/operations.ts +221 -0
- package/src/contract/snapshots.ts +383 -0
- package/src/contract/univer-api-catalog.ts +289 -0
- package/src/css-modules.d.ts +6 -0
- package/src/errors.ts +25 -0
- package/src/http/assets-routes.ts +177 -0
- package/src/http/collaboration.ts +144 -0
- package/src/http/history.ts +151 -0
- package/src/http/project-files.ts +227 -0
- package/src/http/project-scope.ts +77 -0
- package/src/http/routes.ts +364 -0
- package/src/http/standalone.ts +32 -0
- package/src/http/trunk.ts +322 -0
- package/src/http/worktree.ts +119 -0
- package/src/index.ts +165 -0
- package/src/invariant.ts +17 -0
- package/src/request-params.ts +92 -0
- package/src/store/paths.ts +165 -0
- package/src/store/project.ts +137 -0
- package/src/store/read.ts +454 -0
- package/src/store/retention.ts +106 -0
- package/src/store/write.ts +230 -0
- package/src/tools/arguments.ts +128 -0
- package/src/tools/cells.ts +298 -0
- package/src/tools/index.ts +274 -0
- package/src/tools/render.ts +301 -0
- package/src/tools/univer-api.ts +243 -0
- package/src/tools/workbook-catalog.ts +94 -0
- package/src/tools/workbook-edit.ts +987 -0
- package/src/tools/workbook-find.ts +80 -0
- package/src/tools/workbook-history.ts +63 -0
- package/src/tools/workbook-read.ts +210 -0
- package/src/types.ts +432 -0
- package/src/univer/apply-operation.ts +369 -0
- package/src/univer/chunk.ts +77 -0
- package/src/univer/decoration.ts +111 -0
- package/src/univer/diagnostics.ts +136 -0
- package/src/univer/engine.ts +370 -0
- package/src/univer/entry.ts +14 -0
- package/src/univer/facade.ts +91 -0
- package/src/univer/fonts.ts +148 -0
- package/src/univer/overrides.css +33 -0
- package/src/univer/plugins/collaboration.ts +47 -0
- package/src/univer/plugins/export.ts +236 -0
- package/src/univer/plugins/history.ts +49 -0
- package/src/univer/plugins/version.ts +13 -0
- package/src/version/diff.ts +169 -0
- package/src/version/grouping.ts +96 -0
- package/src/version/records.ts +113 -0
- package/src/version/session.ts +90 -0
- package/src/version/summary.ts +117 -0
- package/src/workbook-manager.ts +1111 -0
- package/src/xlsx-export-worker.ts +34 -0
- package/src/xlsx-export.ts +373 -0
- package/src/xlsx-import-worker.ts +30 -0
|
@@ -0,0 +1,274 @@
|
|
|
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 type { Context } from '@deepseek-ai/cordis'
|
|
11
|
+
import { rollbackWorkbook } from '../workbook-manager.ts'
|
|
12
|
+
import { projectScopeOf, runInProject, type SessionRegistry } from '../store/project.ts'
|
|
13
|
+
import { optionalRevision, parseIndex } from '../request-params.ts'
|
|
14
|
+
import { lineageOfArgs } from '../audit.ts'
|
|
15
|
+
import { UNIVER_API_CATALOG, UNIVER_API_VERSION } from '../contract/univer-api-catalog.ts'
|
|
16
|
+
import { optionalAuditText, optionalIntent, optionalString, parseNonEmptyString, workbookIdOf } from './arguments.ts'
|
|
17
|
+
import {
|
|
18
|
+
asRecordOrUndefined,
|
|
19
|
+
asJsonValue,
|
|
20
|
+
renderCatalogResult,
|
|
21
|
+
renderEditResult,
|
|
22
|
+
renderFindResult,
|
|
23
|
+
renderHistoryResult,
|
|
24
|
+
renderReadResult,
|
|
25
|
+
renderSummaryAndValue,
|
|
26
|
+
spillLargeToolResult,
|
|
27
|
+
} from './render.ts'
|
|
28
|
+
import { asApiArguments, executeUniverApi, parseUniverApiAction } from './univer-api.ts'
|
|
29
|
+
import { executeWorkbookCatalog } from './workbook-catalog.ts'
|
|
30
|
+
import { executeWorkbookEdit } from './workbook-edit.ts'
|
|
31
|
+
import { findWorkbookContent } from './workbook-find.ts'
|
|
32
|
+
import { executeHistoryQuery } from './workbook-history.ts'
|
|
33
|
+
import { readWorkbookTarget } from './workbook-read.ts'
|
|
34
|
+
|
|
35
|
+
/** Register every workbook tool the Agent may call. */
|
|
36
|
+
/** What `ctx.tools.register` accepts: the plugin's wrapper must produce the same shape. */
|
|
37
|
+
type ToolDefinition = Parameters<Context['tools']['register']>[0]
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The Agent Session a tool call runs in.
|
|
41
|
+
*
|
|
42
|
+
* `agent.id` **is** the Session id (`Agent` is the live face of one Session), and
|
|
43
|
+
* it is the same id the browser sends as `x-dsh-session` — which is what lets the
|
|
44
|
+
* two halves name one directory. Empty when the call runs in no Agent at all.
|
|
45
|
+
*/
|
|
46
|
+
function sessionIdOf(exec: unknown): string {
|
|
47
|
+
return String((exec as { agent?: { id?: unknown } } | undefined)?.agent?.id ?? '')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function registerWorkbookTools(ctx: Context): void {
|
|
51
|
+
// Every tool runs inside the calling Session's project, established once
|
|
52
|
+
// here rather than in each `execute`: the store root is read deep inside
|
|
53
|
+
// `workbook-manager`, and a second registration path that forgot the scope
|
|
54
|
+
// would read and write another project's workbooks.
|
|
55
|
+
const register = (definition: ToolDefinition): void => {
|
|
56
|
+
// The project comes from the Session **header**, through the Host's own
|
|
57
|
+
// registry — never from the calling Agent's Session object, whose `cwd` is
|
|
58
|
+
// not a field it has (see `store/project.ts`). When no Host service names a
|
|
59
|
+
// project (a test harness, an embedding with no policy) the wrapper leaves
|
|
60
|
+
// the scope unset instead of inventing one.
|
|
61
|
+
const projectOf = (exec: unknown): string | undefined =>
|
|
62
|
+
projectScopeOf((ctx as unknown as { sessions?: SessionRegistry }).sessions, sessionIdOf(exec))
|
|
63
|
+
ctx.tools.register({
|
|
64
|
+
...definition,
|
|
65
|
+
execute: (args, exec) => {
|
|
66
|
+
const project = projectOf(exec)
|
|
67
|
+
const run = async (): Promise<unknown> => definition.execute(args, exec)
|
|
68
|
+
return project === undefined ? run() : runInProject(project, run)
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
register(defineTool({
|
|
73
|
+
name: 'workbook_catalog',
|
|
74
|
+
description: '工作簿目录与生命周期入口。action=list、create、rename、delete;任何数据读写都必须显式指定 workbookId。未指定 workbookId 时,存储文件名(= id)按 `yyyymmdd-中文名含义` 生成,中文名 ≤25 字。',
|
|
75
|
+
parameters: {
|
|
76
|
+
action: { type: 'string', required: true, enum: ['list', 'create', 'rename', 'delete'] },
|
|
77
|
+
workbookId: { type: 'string', description: 'create 时可指定稳定 id;rename/delete 时必填。指定时请遵循同一命名规则:`yyyymmdd-中文名含义`(如 20261002-销售汇总),中文名 ≤25 字;省略则由 name 自动生成。' },
|
|
78
|
+
name: { type: 'string', description: 'create/rename 时的工作簿名称,也是自动生成的 id 里那半段中文名的来源(≤25 字)。' },
|
|
79
|
+
confirm: { type: 'boolean', description: 'delete 必须显式传 true' },
|
|
80
|
+
},
|
|
81
|
+
output: { schema: { type: 'json' }, render: (_args, value) => renderCatalogResult(value) },
|
|
82
|
+
async execute(args, exec) {
|
|
83
|
+
return asJsonValue(await spillLargeToolResult('workbook_catalog', await executeWorkbookCatalog(args)))
|
|
84
|
+
},
|
|
85
|
+
}))
|
|
86
|
+
|
|
87
|
+
register(defineTool({
|
|
88
|
+
name: 'workbook_read',
|
|
89
|
+
description: '读取明确工作簿内的 workbook、sheet、range 或 cells。sheet 可填写 sheet、sheetId 或 sheetName;单 sheet 工作簿可省略。target.kind=workbook 会返回 sheetName ↔ sheetId 映射与每个 sheet 的规模,先用本工具取得 revision,再将其作为 workbook_edit 的 baseRevision。',
|
|
90
|
+
parameters: {
|
|
91
|
+
workbookId: { type: 'string', required: true, description: '工作簿 id(必填);先用 workbook_catalog(action=list) 获取' },
|
|
92
|
+
target: {
|
|
93
|
+
type: 'json',
|
|
94
|
+
required: true,
|
|
95
|
+
description: '目标:{ kind: workbook|sheet|range|cells, sheet?|sheetId?|sheetName?, range?, cells? }。sheet 可用 id 或名称;range 使用 0-based 坐标且结束坐标 inclusive;cells 为 [{row,column}]。',
|
|
96
|
+
},
|
|
97
|
+
include: { type: 'array', items: { type: 'string' }, description: '可选:values、formulas、cells、styles、snapshot、backfill。range 读取默认只返回 values;formulas / cells 是同一份数据的另两种视图,会成倍占用回显预算,仅在确实需要时按需索取。backfill 仅对 kind=workbook 有效:为每个 sheet 返回数据区行数、列数、结论行数、run 时间、来源与 schema 更新时间,以及 complete 提示,用于判断工作簿是否回填完整。' },
|
|
98
|
+
},
|
|
99
|
+
output: { schema: { type: 'json' }, render: (_args, value) => renderReadResult(value) },
|
|
100
|
+
async execute(args) {
|
|
101
|
+
return asJsonValue(await spillLargeToolResult('workbook_read', await readWorkbookTarget(workbookIdOf(args.workbookId), args.target, args.include)))
|
|
102
|
+
},
|
|
103
|
+
}))
|
|
104
|
+
|
|
105
|
+
register(defineTool({
|
|
106
|
+
name: 'workbook_find',
|
|
107
|
+
description: '在指定工作簿中查找 sheet 名、单元格值或公式。sheet 可填写 id 或名称。不会修改任何内容。',
|
|
108
|
+
parameters: {
|
|
109
|
+
workbookId: { type: 'string', required: true, description: '工作簿 id(必填);先用 workbook_catalog(action=list) 获取' },
|
|
110
|
+
query: { type: 'string', required: true, description: '查找文本;match=regex 时为正则表达式' },
|
|
111
|
+
scope: { type: 'string', enum: ['sheets', 'cells', 'formulas'], description: '默认 cells' },
|
|
112
|
+
match: { type: 'string', enum: ['contains', 'exact', 'regex'], description: '默认 contains' },
|
|
113
|
+
sheet: { type: 'string', description: '可选 sheet id 或名称;也接受 sheetId/sheetName' },
|
|
114
|
+
range: { type: 'json', description: '可选 0-based 搜索范围(结束坐标 inclusive)' },
|
|
115
|
+
limit: { type: 'number', description: '默认 100,最多 500' },
|
|
116
|
+
},
|
|
117
|
+
output: { schema: { type: 'json' }, render: (_args, value) => renderFindResult(value) },
|
|
118
|
+
async execute(args) {
|
|
119
|
+
return asJsonValue(await spillLargeToolResult('workbook_find', await findWorkbookContent(workbookIdOf(args.workbookId), args)))
|
|
120
|
+
},
|
|
121
|
+
}))
|
|
122
|
+
|
|
123
|
+
register(defineTool({
|
|
124
|
+
name: 'workbook_edit',
|
|
125
|
+
description: '唯一的业务写入口。一个语义 versions 分组落为一个可回放 Version,分组内 commands 是按顺序记录的 Step;Host 负责 revision、Version 和并发控制,Agent 不得传 versionId。command 的 sheet、sheetId、sheetName 可互换;单 sheet 工作簿可省略 sheet。兼容 commands 简写:整组命令视为一个 Version。批量结果落表优先用 import_file 或 update_cells + sourcePath(一次命令 + 一个本地文件路径),不要把大数据逐格内联进 values。',
|
|
126
|
+
parameters: {
|
|
127
|
+
workbookId: { type: 'string', required: true, description: '工作簿 id(必填);先用 workbook_catalog(action=list) 获取' },
|
|
128
|
+
baseRevision: { type: 'number', description: '可选的乐观锁,仅在需要阻止覆盖他人修改时传;不传表示接受当前主线。只做单元格写入的计划在本值过期时会自动变基到最新 revision 并返回 transformed: true;含行/列结构操作的计划才会返回 REVISION_CONFLICT,此时重新读取后重试整个计划' },
|
|
129
|
+
opId: { type: 'string', description: '幂等请求 id;多个 Version 分组会派生稳定子 id' },
|
|
130
|
+
intent: { type: 'string', description: '本次编辑的简短业务意图,作为未单独声明 intent 的分组审计信息' },
|
|
131
|
+
agentRunId: { type: 'string', description: '可选 Agent 运行关联 id;仅用于审计同一次运行产生的多个 Version' },
|
|
132
|
+
sessionId: { type: 'string', description: '关联的 DSH session id' },
|
|
133
|
+
turnId: { type: 'string', description: '当前对话轮次 id' },
|
|
134
|
+
intentId: { type: 'string', description: '关联的意图 id' },
|
|
135
|
+
taskId: { type: 'string', description: '关联的业务任务 id' },
|
|
136
|
+
subtaskId: { type: 'string', description: '关联的业务子任务 id' },
|
|
137
|
+
parentSubtaskId: { type: 'string', description: '关联的父子任务 id' },
|
|
138
|
+
runId: { type: 'string', description: '本次执行尝试 id' },
|
|
139
|
+
traceId: { type: 'string', description: '可选技术追踪 id' },
|
|
140
|
+
domain: { type: 'string', description: '业务域,例如 data' },
|
|
141
|
+
dryRun: { type: 'boolean', description: 'true 时用与正式提交完全相同的校验路径在内存中预检,不写入历史;返回 revisionBefore/revisionAfter/affectedSheets/affectedCellCount/warnings 预检清单' },
|
|
142
|
+
versions: {
|
|
143
|
+
type: 'array',
|
|
144
|
+
items: { type: 'json' },
|
|
145
|
+
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 条命令。整个编辑计划一次性提交。',
|
|
146
|
+
},
|
|
147
|
+
commands: {
|
|
148
|
+
type: 'array',
|
|
149
|
+
items: { type: 'json' },
|
|
150
|
+
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。',
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
output: { schema: { type: 'json' }, render: (_args, value) => renderEditResult(value) },
|
|
154
|
+
async execute(args, exec) {
|
|
155
|
+
return asJsonValue(await spillLargeToolResult('workbook_edit', await executeWorkbookEdit({
|
|
156
|
+
workbookId: workbookIdOf(args.workbookId),
|
|
157
|
+
versions: args.versions,
|
|
158
|
+
commands: args.commands,
|
|
159
|
+
baseRevision: optionalRevision(args.baseRevision),
|
|
160
|
+
opId: optionalString(args.opId),
|
|
161
|
+
intent: optionalIntent(args.intent),
|
|
162
|
+
agentRunId: optionalAuditText(args.agentRunId, 'agentRunId', 160),
|
|
163
|
+
lineage: lineageOfArgs(args, exec),
|
|
164
|
+
dryRun: args.dryRun === true,
|
|
165
|
+
})))
|
|
166
|
+
},
|
|
167
|
+
}))
|
|
168
|
+
|
|
169
|
+
register(defineTool({
|
|
170
|
+
name: 'workbook_history',
|
|
171
|
+
description: '历史查询入口。action=list 返回 Version(默认)及其隐藏 Step;diff 比较两个 revision;replay 读取某个 revision 或其中一个 step 的可回放快照。',
|
|
172
|
+
parameters: {
|
|
173
|
+
workbookId: { type: 'string', required: true, description: '工作簿 id(必填);先用 workbook_catalog(action=list) 获取' },
|
|
174
|
+
action: { type: 'string', enum: ['list', 'diff', 'replay'], description: '默认 list' },
|
|
175
|
+
limit: { type: 'number', description: 'list 默认 20、最多 100' },
|
|
176
|
+
fromRevision: { type: 'number', description: 'diff 起始 revision' },
|
|
177
|
+
toRevision: { type: 'number', description: 'diff 终止 revision,或 replay 目标 revision' },
|
|
178
|
+
revision: { type: 'number', description: 'replay 目标 revision(优先于 toRevision)' },
|
|
179
|
+
step: { type: 'number', description: 'replay 中的 0-based Step;省略则读取整个 Version 末尾' },
|
|
180
|
+
},
|
|
181
|
+
output: { schema: { type: 'json' }, render: (_args, value) => renderHistoryResult(value) },
|
|
182
|
+
async execute(args, exec) {
|
|
183
|
+
return asJsonValue(await spillLargeToolResult('workbook_history', await executeHistoryQuery(workbookIdOf(args.workbookId), args)))
|
|
184
|
+
},
|
|
185
|
+
}))
|
|
186
|
+
|
|
187
|
+
register(defineTool({
|
|
188
|
+
name: 'workbook_restore',
|
|
189
|
+
description: '将 workbook 恢复到某个可回放 revision,并作为新的恢复 Version 记录。原先后续版本保留为 superseded 历史,不会被删除。',
|
|
190
|
+
parameters: {
|
|
191
|
+
workbookId: { type: 'string', required: true, description: '工作簿 id(必填);先用 workbook_catalog(action=list) 获取' },
|
|
192
|
+
targetRevision: { type: 'number', required: true, description: '要恢复到的 revision' },
|
|
193
|
+
baseRevision: { type: 'number', description: '当前 revision,用于并发保护' },
|
|
194
|
+
opId: { type: 'string', description: '可选幂等恢复操作 id' },
|
|
195
|
+
sessionId: { type: 'string', description: '关联的 DSH session id' },
|
|
196
|
+
turnId: { type: 'string', description: '当前对话轮次 id' },
|
|
197
|
+
intentId: { type: 'string', description: '关联的意图 id' },
|
|
198
|
+
taskId: { type: 'string', description: '关联的业务任务 id' },
|
|
199
|
+
subtaskId: { type: 'string', description: '关联的业务子任务 id' },
|
|
200
|
+
parentSubtaskId: { type: 'string', description: '关联的父子任务 id' },
|
|
201
|
+
runId: { type: 'string', description: '本次执行尝试 id' },
|
|
202
|
+
traceId: { type: 'string', description: '可选技术追踪 id' },
|
|
203
|
+
domain: { type: 'string', description: '业务域,例如 data' },
|
|
204
|
+
},
|
|
205
|
+
output: { schema: { type: 'json' }, render: (_args, value) => renderSummaryAndValue(`工作簿已恢复:${String(asRecordOrUndefined(value)?.workbookId ?? '')} revision=${String(asRecordOrUndefined(value)?.revision ?? 0)}`, value) },
|
|
206
|
+
async execute(args, exec) {
|
|
207
|
+
const id = workbookIdOf(args.workbookId)
|
|
208
|
+
const result = await rollbackWorkbook({
|
|
209
|
+
workbookId: id,
|
|
210
|
+
targetRevision: parseIndex(args.targetRevision, 'targetRevision'),
|
|
211
|
+
expectedRevision: optionalRevision(args.baseRevision),
|
|
212
|
+
actor: 'agent',
|
|
213
|
+
opId: optionalString(args.opId),
|
|
214
|
+
lineage: lineageOfArgs(args, exec),
|
|
215
|
+
})
|
|
216
|
+
return asJsonValue({ ok: true, workbookId: id, revision: result.meta.revision ?? 0, operation: result.operation })
|
|
217
|
+
},
|
|
218
|
+
}))
|
|
219
|
+
|
|
220
|
+
register(defineTool({
|
|
221
|
+
name: 'univer_api',
|
|
222
|
+
description: 'Univer Adapter Registry。action=catalog/search 发现已注册的安全能力;action=invoke 调用其中一个适配器。它不是任意 JavaScript 或原始 Injector 入口;所有写入仍会落为可回放历史。',
|
|
223
|
+
parameters: {
|
|
224
|
+
action: { type: 'string', required: true, enum: ['catalog', 'search', 'invoke'] },
|
|
225
|
+
query: { type: 'string', description: 'search 的关键词' },
|
|
226
|
+
workbookId: { type: 'string', description: 'invoke 时必填' },
|
|
227
|
+
api: { type: 'string', description: 'invoke 时必填;先用 catalog/search 确认' },
|
|
228
|
+
arguments: { type: 'json', description: 'invoke 参数,结构由 catalog 定义' },
|
|
229
|
+
baseRevision: { type: 'number', description: '写入适配器的并发基准' },
|
|
230
|
+
opId: { type: 'string', description: '写入适配器的幂等 id' },
|
|
231
|
+
sessionId: { type: 'string', description: '关联的 DSH session id' },
|
|
232
|
+
turnId: { type: 'string', description: '当前对话轮次 id' },
|
|
233
|
+
intentId: { type: 'string', description: '关联的意图 id' },
|
|
234
|
+
taskId: { type: 'string', description: '关联的业务任务 id' },
|
|
235
|
+
subtaskId: { type: 'string', description: '关联的业务子任务 id' },
|
|
236
|
+
parentSubtaskId: { type: 'string', description: '关联的父子任务 id' },
|
|
237
|
+
runId: { type: 'string', description: '本次执行尝试 id' },
|
|
238
|
+
traceId: { type: 'string', description: '可选技术追踪 id' },
|
|
239
|
+
domain: { type: 'string', description: '业务域,例如 data' },
|
|
240
|
+
},
|
|
241
|
+
output: {
|
|
242
|
+
schema: { type: 'json' },
|
|
243
|
+
render: (args, value) => {
|
|
244
|
+
const record = asRecordOrUndefined(value) ?? {}
|
|
245
|
+
const action = asRecordOrUndefined(args)?.action
|
|
246
|
+
const summary = action === 'catalog'
|
|
247
|
+
? `Univer API 目录(version=${String(record.version ?? '')},${Array.isArray(record.catalog) ? record.catalog.length : 0} 项)`
|
|
248
|
+
: action === 'search'
|
|
249
|
+
? `搜索到 ${Array.isArray(record.results) ? record.results.length : 0} 个 Univer API`
|
|
250
|
+
: `Univer Adapter 调用已完成:workbookId=${String(record.workbookId ?? '')} revision=${String(record.revision ?? 0)}`
|
|
251
|
+
return renderSummaryAndValue(summary, value)
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
async execute(args, exec) {
|
|
255
|
+
const action = parseUniverApiAction(args.action)
|
|
256
|
+
if (action === 'catalog') return asJsonValue(await spillLargeToolResult('univer_api', { ok: true, version: UNIVER_API_VERSION, catalog: UNIVER_API_CATALOG }))
|
|
257
|
+
if (action === 'search') {
|
|
258
|
+
const query = parseNonEmptyString(args.query, 'query').toLowerCase()
|
|
259
|
+
const results = UNIVER_API_CATALOG.filter(definition => (definition.api + ' ' + definition.description + ' ' + (definition.operation ?? '')).toLowerCase().includes(query))
|
|
260
|
+
return asJsonValue(await spillLargeToolResult('univer_api', { ok: true, version: UNIVER_API_VERSION, query, results }))
|
|
261
|
+
}
|
|
262
|
+
const result = await executeUniverApi({
|
|
263
|
+
workbookId: workbookIdOf(args.workbookId),
|
|
264
|
+
api: parseNonEmptyString(args.api, 'api'),
|
|
265
|
+
arguments: asApiArguments(args.arguments),
|
|
266
|
+
expectedRevision: optionalRevision(args.baseRevision),
|
|
267
|
+
opId: optionalString(args.opId),
|
|
268
|
+
lineage: lineageOfArgs(args, exec),
|
|
269
|
+
})
|
|
270
|
+
return asJsonValue(await spillLargeToolResult('univer_api', result))
|
|
271
|
+
},
|
|
272
|
+
}))
|
|
273
|
+
|
|
274
|
+
}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a tool result reaches the model (docs/issue/0022 P3b).
|
|
3
|
+
*
|
|
4
|
+
* A tool answers with text blocks, and a large value is spilled to disk with a
|
|
5
|
+
* pointer instead of being inlined into the conversation. Both decisions are
|
|
6
|
+
* about *presentation*, so they sit together and neither tool owns them.
|
|
7
|
+
*/
|
|
8
|
+
import { randomUUID } from 'node:crypto'
|
|
9
|
+
import { mkdir, writeFile } from 'node:fs/promises'
|
|
10
|
+
import { join } from 'node:path'
|
|
11
|
+
import type { JsonValue } from '@deepseek-ai/dsh-util-values'
|
|
12
|
+
import { formatCellReference } from '#contracts'
|
|
13
|
+
import { operationSummary } from '../contract/operations.ts'
|
|
14
|
+
import { toolOutputRoot } from '../store/paths.ts'
|
|
15
|
+
import { pruneToolOutput } from '../store/retention.ts'
|
|
16
|
+
import type { WorkbookOperation } from '../types.ts'
|
|
17
|
+
|
|
18
|
+
export interface ToolTextBlock { type: 'text'; text: string }
|
|
19
|
+
|
|
20
|
+
export function textBlock(text: string): ToolTextBlock {
|
|
21
|
+
return { type: 'text', text }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function asRecordOrUndefined(value: unknown): Record<string, unknown> | undefined {
|
|
25
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : undefined
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function renderSummaryAndValue(summary: string, value: unknown): ToolTextBlock[] {
|
|
29
|
+
const detailPath = asRecordOrUndefined(value)?.detailPath
|
|
30
|
+
const json = JSON.stringify(value, null, 2)
|
|
31
|
+
const header = summary.trim().length === 0 ? '' : `${summary}\n\n`
|
|
32
|
+
if (json.length <= TOOL_RESULT_INLINE_LIMIT) return [textBlock(`${header}${json}`)]
|
|
33
|
+
const preview = json.slice(0, TOOL_RESULT_INLINE_LIMIT)
|
|
34
|
+
const tail = typeof detailPath === 'string'
|
|
35
|
+
? `\n…(结果过大,已截断)\n完整结果:${detailPath}(用 read 工具读取完整 JSON)`
|
|
36
|
+
: '\n…(结果过大,已截断)'
|
|
37
|
+
return [textBlock(`${header}${preview}${tail}`)]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Model-facing tool output.
|
|
42
|
+
*
|
|
43
|
+
* DSH treats `output.render` as the only content the model sees
|
|
44
|
+
* (harness `createSuccessResult`), so a constant string silently drops the
|
|
45
|
+
* whole workbook payload. These helpers keep a readable summary plus the real
|
|
46
|
+
* JSON inline, and oversized results are spilled to
|
|
47
|
+
* `<storage root>/tool-output/*.json` during `execute` and reported as
|
|
48
|
+
* `detailPath` instead of being truncated away.
|
|
49
|
+
*/
|
|
50
|
+
export const TOOL_RESULT_INLINE_LIMIT = 24_000
|
|
51
|
+
|
|
52
|
+
/** Spill an oversized canonical value so the model can still page through it. */
|
|
53
|
+
export async function spillLargeToolResult(toolName: string, value: unknown): Promise<Record<string, unknown>> {
|
|
54
|
+
const record = asRecordOrUndefined(value) ?? { value }
|
|
55
|
+
if (JSON.stringify(record).length <= TOOL_RESULT_INLINE_LIMIT) return record
|
|
56
|
+
const directory = toolOutputRoot()
|
|
57
|
+
const detailPath = join(directory, `${toolName}-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}.json`)
|
|
58
|
+
await mkdir(directory, { recursive: true })
|
|
59
|
+
await writeFile(detailPath, JSON.stringify(value, null, 2), 'utf8')
|
|
60
|
+
// Keep the directory bounded. Nothing used to, so these accumulated for the
|
|
61
|
+
// life of the installation.
|
|
62
|
+
await pruneToolOutput()
|
|
63
|
+
return { ...record, detailPath }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Keep large write payloads out of the model/tool transcript. */
|
|
67
|
+
export function compactToolValue(value: unknown): unknown {
|
|
68
|
+
if (Array.isArray(value)) return value.map(compactToolValue)
|
|
69
|
+
if (typeof value !== 'object' || value === null) return value
|
|
70
|
+
const record = value as Record<string, unknown>
|
|
71
|
+
const result: Record<string, unknown> = {}
|
|
72
|
+
for (const [key, item] of Object.entries(record)) {
|
|
73
|
+
if (key === 'operation' && isOperationRecord(item)) result[key] = operationReceipt(item)
|
|
74
|
+
else if ((key === 'operations' || key === 'rebasedOperations') && Array.isArray(item)) {
|
|
75
|
+
result[key] = item.map(candidate => isOperationRecord(candidate) ? operationReceipt(candidate) : compactToolValue(candidate))
|
|
76
|
+
}
|
|
77
|
+
else result[key] = compactToolValue(item)
|
|
78
|
+
}
|
|
79
|
+
return result
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function asJsonValue(value: unknown): JsonValue {
|
|
83
|
+
const serialized = JSON.stringify(compactToolValue(value))
|
|
84
|
+
if (serialized === undefined) throw new Error('工具结果无法序列化')
|
|
85
|
+
return JSON.parse(serialized) as JsonValue
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function isOperationRecord(value: unknown): value is WorkbookOperation {
|
|
89
|
+
if (typeof value !== 'object' || value === null) return false
|
|
90
|
+
const operation = value as Partial<WorkbookOperation>
|
|
91
|
+
return typeof operation.opId === 'string' && typeof operation.workbookId === 'string'
|
|
92
|
+
&& typeof operation.revision === 'number' && typeof operation.type === 'string'
|
|
93
|
+
&& typeof operation.timestamp === 'string' && typeof operation.payload === 'object'
|
|
94
|
+
&& operation.payload !== null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function operationReceipt(operation: WorkbookOperation): Record<string, unknown> {
|
|
98
|
+
const payload = operation.payload
|
|
99
|
+
const cells = Array.isArray(payload.cells) ? payload.cells : []
|
|
100
|
+
const range = payload.range ?? payload.merge
|
|
101
|
+
const sheetRef = payload.sheetId ?? payload.sheetName
|
|
102
|
+
const payloads = operation.type === 'batch' ? (payload.operations ?? []).map(step => step.payload) : [payload]
|
|
103
|
+
const sheetIds = [...new Set(payloads.map(item => item.sheetId ?? item.sheetName).filter((value): value is string => typeof value === 'string'))]
|
|
104
|
+
const totalCellCount = operation.type === 'batch'
|
|
105
|
+
? payloads.reduce((total, item) => total + (Array.isArray(item.cells) ? item.cells.length : 0), 0)
|
|
106
|
+
: cells.length
|
|
107
|
+
const totalRowCount = operation.type === 'batch'
|
|
108
|
+
? payloads.reduce((total, item) => total + (Array.isArray(item.rows) ? item.rows.length : 0), 0)
|
|
109
|
+
: Array.isArray(payload.rows) ? payload.rows.length : 0
|
|
110
|
+
return {
|
|
111
|
+
opId: operation.opId,
|
|
112
|
+
workbookId: operation.workbookId,
|
|
113
|
+
revision: operation.revision,
|
|
114
|
+
parentRevision: operation.parentRevision ?? Math.max(0, operation.revision - 1),
|
|
115
|
+
...(operation.versionId === undefined ? {} : { versionId: operation.versionId }),
|
|
116
|
+
...(operation.versionStatus === undefined ? {} : { versionStatus: operation.versionStatus }),
|
|
117
|
+
...(operation.versionSummary === undefined ? {} : { versionSummary: operation.versionSummary }),
|
|
118
|
+
...(operation.versionIntent === undefined ? {} : { versionIntent: operation.versionIntent }),
|
|
119
|
+
...(operation.agentRunId === undefined ? {} : { agentRunId: operation.agentRunId }),
|
|
120
|
+
...(operation.lineage === undefined ? {} : { lineage: operation.lineage }),
|
|
121
|
+
...(operation.restoredFromRevision === undefined ? {} : { restoredFromRevision: operation.restoredFromRevision }),
|
|
122
|
+
actor: operation.actor,
|
|
123
|
+
type: operation.type,
|
|
124
|
+
timestamp: operation.timestamp,
|
|
125
|
+
summary: operationSummary(operation),
|
|
126
|
+
...(sheetRef === undefined ? {} : { sheetId: sheetRef }),
|
|
127
|
+
...(sheetIds.length > 0 && operation.type === 'batch' ? { sheetIds } : {}),
|
|
128
|
+
...(totalCellCount > 0 ? { cellCount: totalCellCount } : {}),
|
|
129
|
+
...(totalRowCount > 0 ? { rowCount: totalRowCount } : {}),
|
|
130
|
+
...(range === undefined ? {} : { range }),
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface SheetOverview {
|
|
135
|
+
id: string
|
|
136
|
+
name: string
|
|
137
|
+
rowCount: number
|
|
138
|
+
columnCount: number
|
|
139
|
+
maxRow: number | null
|
|
140
|
+
maxColumn: number | null
|
|
141
|
+
nonEmptyCells: number
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function isNonEmptyCell(cell: unknown): boolean {
|
|
145
|
+
const record = asRecordOrUndefined(cell)
|
|
146
|
+
if (record === undefined) return false
|
|
147
|
+
if (typeof record.f === 'string' && record.f.length > 0) return true
|
|
148
|
+
return record.v !== undefined && record.v !== null && record.v !== ''
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Compact per-sheet map (`name ↔ id` + size) that Agent callers use to address sheets. */
|
|
152
|
+
export function sheetOverviews(snapshot: unknown): SheetOverview[] {
|
|
153
|
+
const root = asRecordOrUndefined(snapshot) ?? {}
|
|
154
|
+
const sheets = asRecordOrUndefined(root.sheets) ?? {}
|
|
155
|
+
const order = Array.isArray(root.sheetOrder)
|
|
156
|
+
? root.sheetOrder.filter((item): item is string => typeof item === 'string')
|
|
157
|
+
: Object.keys(sheets)
|
|
158
|
+
const result: SheetOverview[] = []
|
|
159
|
+
for (const id of order) {
|
|
160
|
+
const sheet = asRecordOrUndefined(sheets[id])
|
|
161
|
+
if (sheet === undefined) continue
|
|
162
|
+
let maxRow = -1
|
|
163
|
+
let maxColumn = -1
|
|
164
|
+
let nonEmptyCells = 0
|
|
165
|
+
const cellData = asRecordOrUndefined(sheet.cellData)
|
|
166
|
+
if (cellData !== undefined) {
|
|
167
|
+
for (const [rowKey, line] of Object.entries(cellData)) {
|
|
168
|
+
const row = Number(rowKey)
|
|
169
|
+
const cells = asRecordOrUndefined(line)
|
|
170
|
+
if (!Number.isInteger(row) || cells === undefined) continue
|
|
171
|
+
for (const [columnKey, cell] of Object.entries(cells)) {
|
|
172
|
+
const column = Number(columnKey)
|
|
173
|
+
if (!Number.isInteger(column)) continue
|
|
174
|
+
maxRow = Math.max(maxRow, row)
|
|
175
|
+
maxColumn = Math.max(maxColumn, column)
|
|
176
|
+
if (isNonEmptyCell(cell)) nonEmptyCells += 1
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
result.push({
|
|
181
|
+
id,
|
|
182
|
+
name: typeof sheet.name === 'string' ? sheet.name : id,
|
|
183
|
+
rowCount: typeof sheet.rowCount === 'number' ? sheet.rowCount : 0,
|
|
184
|
+
columnCount: typeof sheet.columnCount === 'number' ? sheet.columnCount : 0,
|
|
185
|
+
maxRow: maxRow < 0 ? null : maxRow,
|
|
186
|
+
maxColumn: maxColumn < 0 ? null : maxColumn,
|
|
187
|
+
nonEmptyCells,
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
return result
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function sheetMapSummary(sheets: readonly SheetOverview[]): string {
|
|
194
|
+
if (sheets.length === 0) return '当前工作簿没有 sheet。'
|
|
195
|
+
return sheets.map(sheet => {
|
|
196
|
+
// `== null` on purpose: callers that summarise a sheet without reading its
|
|
197
|
+
// cells leave these absent rather than null, and `workbook_catalog` was
|
|
198
|
+
// printing `rows 0-undefined × cols 0-undefined` because of it.
|
|
199
|
+
const extent = sheet.maxRow == null || sheet.maxColumn == null
|
|
200
|
+
? '暂无数据'
|
|
201
|
+
: `数据 rows 0-${sheet.maxRow} × cols 0-${sheet.maxColumn}`
|
|
202
|
+
return `- ${sheet.name} ↔ ${sheet.id}(${extent},非空 ${sheet.nonEmptyCells})`
|
|
203
|
+
}).join('\n')
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Inline `名称(id)` list used by self-healing error messages. */
|
|
207
|
+
export function sheetReferenceList(snapshot: unknown): string {
|
|
208
|
+
const sheets = sheetOverviews(snapshot)
|
|
209
|
+
if (sheets.length === 0) return '(当前工作簿没有 sheet)'
|
|
210
|
+
return sheets.map(sheet => `${sheet.name}(${sheet.id})`).join(' / ')
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function renderCatalogResult(value: unknown): ToolTextBlock[] {
|
|
214
|
+
const record = asRecordOrUndefined(value) ?? {}
|
|
215
|
+
const action = typeof record.action === 'string' ? record.action : 'list'
|
|
216
|
+
if (Array.isArray(record.workbooks)) {
|
|
217
|
+
const workbooks = record.workbooks as Array<Record<string, unknown>>
|
|
218
|
+
const lines = workbooks.map(item => `- ${String(item.id)}(${String(item.name)})revision=${String(item.revision ?? 0)} sheets=${String(item.sheetCount ?? 0)} updatedAt=${String(item.updatedAt ?? '')}`)
|
|
219
|
+
return renderSummaryAndValue(workbooks.length === 0 ? '没有工作簿;请用 workbook_catalog(action=create) 创建。' : `共 ${workbooks.length} 个工作簿:\n${lines.join('\n')}`, value)
|
|
220
|
+
}
|
|
221
|
+
const workbook = asRecordOrUndefined(record.workbook)
|
|
222
|
+
if (workbook !== undefined) {
|
|
223
|
+
const sheets = Array.isArray(workbook.sheets) ? workbook.sheets as SheetOverview[] : []
|
|
224
|
+
return renderSummaryAndValue(`工作簿已就绪:${String(workbook.id)}(${String(workbook.name)})revision=${String(workbook.revision ?? 0)}\n${sheetMapSummary(sheets)}`, value)
|
|
225
|
+
}
|
|
226
|
+
return renderSummaryAndValue(`工作簿目录操作已完成:action=${action}`, value)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function renderReadResult(value: unknown): ToolTextBlock[] {
|
|
230
|
+
const record = asRecordOrUndefined(value) ?? {}
|
|
231
|
+
const parts = [`workbookId: ${String(record.workbookId ?? '')},revision: ${String(record.revision ?? 0)}`]
|
|
232
|
+
const workbook = asRecordOrUndefined(record.workbook)
|
|
233
|
+
if (workbook !== undefined) {
|
|
234
|
+
const sheets = Array.isArray(workbook.sheets) ? workbook.sheets as SheetOverview[] : []
|
|
235
|
+
parts.push(`工作簿:${String(workbook.name ?? '')},sheet 数:${sheets.length}`)
|
|
236
|
+
parts.push(sheetMapSummary(sheets))
|
|
237
|
+
}
|
|
238
|
+
const sheet = asRecordOrUndefined(record.sheet)
|
|
239
|
+
if (sheet !== undefined) parts.push(`sheet:${String(sheet.name ?? '')} ↔ ${String(sheet.id ?? '')}`)
|
|
240
|
+
const range = asRecordOrUndefined(record.range)
|
|
241
|
+
if (range !== undefined && typeof range.startRow === 'number') {
|
|
242
|
+
parts.push(`range:rows ${range.startRow}-${range.endRow} × cols ${range.startColumn}-${range.endColumn}(0-based,end inclusive)`)
|
|
243
|
+
}
|
|
244
|
+
if (Array.isArray(record.values)) {
|
|
245
|
+
const first = record.values[0]
|
|
246
|
+
parts.push(`values:${record.values.length} 行 × ${Array.isArray(first) ? first.length : 0} 列`)
|
|
247
|
+
} else if (Array.isArray(record.cells)) {
|
|
248
|
+
parts.push(`cells:${record.cells.length} 个`)
|
|
249
|
+
}
|
|
250
|
+
if (typeof record.detailPath === 'string') parts.push(`完整结果:${record.detailPath}`)
|
|
251
|
+
return renderSummaryAndValue(parts.join('\n'), value)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function renderFindResult(value: unknown): ToolTextBlock[] {
|
|
255
|
+
const record = asRecordOrUndefined(value) ?? {}
|
|
256
|
+
const results = Array.isArray(record.results) ? record.results as Array<Record<string, unknown>> : []
|
|
257
|
+
const lines = results.slice(0, 20).map(item => `- ${formatCellReference({
|
|
258
|
+
sheet: typeof item.sheetName === 'string' ? item.sheetName : typeof item.sheetId === 'string' ? item.sheetId : '',
|
|
259
|
+
row: typeof item.row === 'number' ? item.row : 0,
|
|
260
|
+
column: typeof item.column === 'number' ? item.column : 0,
|
|
261
|
+
})} = ${JSON.stringify(item.value ?? null)}`)
|
|
262
|
+
const summary = `匹配 ${results.length} 处${record.truncated === true ? '(已截断,请缩小范围或提高 limit)' : ''}(query=${String(record.query ?? '')},scope=${String(record.scope ?? '')})`
|
|
263
|
+
return renderSummaryAndValue(lines.length === 0 ? summary : `${summary}\n${lines.join('\n')}`, value)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function renderHistoryResult(value: unknown): ToolTextBlock[] {
|
|
267
|
+
const record = asRecordOrUndefined(value) ?? {}
|
|
268
|
+
if (Array.isArray(record.versions)) {
|
|
269
|
+
const versions = record.versions as Array<Record<string, unknown>>
|
|
270
|
+
const lines = versions.map(item => `- revision=${String(item.revision)} status=${String(item.status ?? '')} steps=${String(item.stepCount ?? '')} ${String(item.summary ?? '')}`)
|
|
271
|
+
return renderSummaryAndValue(`workbookId: ${String(record.workbookId ?? '')},revision: ${String(record.revision ?? 0)},Version ${versions.length} 个\n${lines.join('\n')}`, value)
|
|
272
|
+
}
|
|
273
|
+
return renderSummaryAndValue(`历史查询已完成:action=${String(record.action ?? '')},workbookId=${String(record.workbookId ?? '')}`, value)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function renderEditResult(value: unknown): ToolTextBlock[] {
|
|
277
|
+
const record = asRecordOrUndefined(value) ?? {}
|
|
278
|
+
const dryRun = record.dryRun === true
|
|
279
|
+
const lines = [
|
|
280
|
+
dryRun
|
|
281
|
+
? `编辑预检通过,尚未写入(revision 保持 ${String(record.revision ?? record.revisionBefore ?? 0)})。`
|
|
282
|
+
: `工作簿编辑已提交(revision ${String(record.revisionBefore ?? '?')} → ${String(record.revisionAfter ?? record.revision ?? '?')})。`,
|
|
283
|
+
`workbookId: ${String(record.workbookId ?? '')}`,
|
|
284
|
+
`Version ${String(record.versionCount ?? 0)} 个 / 命令 ${String(record.commandCount ?? 0)} 条,影响 sheet:${Array.isArray(record.affectedSheets) && record.affectedSheets.length > 0 ? (record.affectedSheets as unknown[]).join('、') : '(无)'},单元格 ${String(record.affectedCellCount ?? 0)} 个`,
|
|
285
|
+
]
|
|
286
|
+
if (record.replayed === true) lines.push('该 opId 之前已提交,本次为幂等重放。')
|
|
287
|
+
if (record.transformed === true) {
|
|
288
|
+
const rebased = Array.isArray(record.rebasedOperations) ? record.rebasedOperations.length : 0
|
|
289
|
+
lines.push(`提交前检测到 ${rebased} 个并发编辑(均为单元格写入),已自动变基到最新 revision 后提交;行/列结构未变化,本计划的坐标仍然有效。`)
|
|
290
|
+
}
|
|
291
|
+
if (Array.isArray(record.warnings) && record.warnings.length > 0) {
|
|
292
|
+
lines.push('warnings:')
|
|
293
|
+
for (const warning of record.warnings as unknown[]) lines.push(`- ${String(warning)}`)
|
|
294
|
+
}
|
|
295
|
+
if (dryRun && Array.isArray(record.versions)) {
|
|
296
|
+
for (const version of record.versions as Array<Record<string, unknown>>) {
|
|
297
|
+
lines.push(`- ${String(version.summary ?? '')}:${String(version.stepCount ?? 0)} 个 step`)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return renderSummaryAndValue(lines.join('\n'), value)
|
|
301
|
+
}
|