dsh-office-tools 0.2.0 → 1.0.2
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/README.md +44 -19
- package/README.zh.md +43 -19
- package/dsh.plugin.json +4 -3
- package/lib/index.js +2076 -89549
- package/lib/types/asciizip.d.ts +95 -0
- package/lib/types/fschannel.d.ts +91 -0
- package/lib/types/imgsize.d.ts +14 -0
- package/lib/types/index.d.ts +9 -7
- package/lib/types/paths.d.ts +36 -0
- package/lib/types/tools/excel.d.ts +10 -5
- package/lib/types/tools/ppt.d.ts +66 -5
- package/lib/types/tools/shared.d.ts +14 -2
- package/lib/types/tools/word-update.d.ts +13 -0
- package/lib/types/tools/word.d.ts +63 -3
- package/package.json +74 -19
- package/lib/index.js.map +0 -7
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-contained OOXML zip container (1.0.0).
|
|
3
|
+
*
|
|
4
|
+
* Two halves:
|
|
5
|
+
*
|
|
6
|
+
* - `buildAsciiZip` WRITES a package whose every byte is at most 0x7F. All
|
|
7
|
+
* entries are STOREd and the planner pads each XML part with trailing
|
|
8
|
+
* newlines (legal after the root element) until every CRC-32, size, and
|
|
9
|
+
* offset field of the archive is itself ASCII-safe. That makes the whole
|
|
10
|
+
* document a pure-ASCII string, so it can travel through any UTF-8 text
|
|
11
|
+
* channel — specifically the official `ctx.fs.writeText` service — and land
|
|
12
|
+
* on disk byte-identical. No compression, no binary parts: this is the
|
|
13
|
+
* contract that keeps the plugin free of direct file-system access while
|
|
14
|
+
* still producing real .docx/.xlsx/.pptx files every Office suite opens.
|
|
15
|
+
*
|
|
16
|
+
* - `readZip` READS any real-world OOXML package: STORE and DEFLATE entries
|
|
17
|
+
* (via node:zlib), bounded by the same declared-size budgets the jszip
|
|
18
|
+
* guard enforced in <=0.6.x — per-entry, total, and entry-count caps are
|
|
19
|
+
* checked against the central directory BEFORE anything is inflated.
|
|
20
|
+
*/
|
|
21
|
+
/** Hard cap for reading an existing Office file into memory. */
|
|
22
|
+
export declare const MAX_OFFICE_FILE_BYTES: number;
|
|
23
|
+
/** Declared uncompressed ceiling for one zip entry inside an Office file. */
|
|
24
|
+
export declare const MAX_ZIP_ENTRY_BYTES: number;
|
|
25
|
+
/** Declared uncompressed ceiling summed over all entries of one archive. */
|
|
26
|
+
export declare const MAX_ZIP_TOTAL_BYTES: number;
|
|
27
|
+
/** Maximum entries (files + directories) in one archive. */
|
|
28
|
+
export declare const MAX_ZIP_ENTRIES = 100000;
|
|
29
|
+
/** Overridable budgets so tests can trip the guard with tiny values. */
|
|
30
|
+
export interface ZipGuardLimits {
|
|
31
|
+
maxEntryBytes?: number;
|
|
32
|
+
maxTotalBytes?: number;
|
|
33
|
+
maxEntries?: number;
|
|
34
|
+
}
|
|
35
|
+
/** ASCII-safe = every byte at most 0x7F, so UTF-8 encoding is the identity. */
|
|
36
|
+
export declare function assertAscii(value: string, label: string): void;
|
|
37
|
+
export interface ZipPart {
|
|
38
|
+
name: string;
|
|
39
|
+
content: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Build the whole package as one ASCII-safe string: local STORE entries in
|
|
43
|
+
* order, the central directory, and the end-of-central-directory record.
|
|
44
|
+
*/
|
|
45
|
+
export declare function buildAsciiZip(parts: ZipPart[]): string;
|
|
46
|
+
export interface ZipEntryInfo {
|
|
47
|
+
name: string;
|
|
48
|
+
method: number;
|
|
49
|
+
compressedSize: number;
|
|
50
|
+
uncompressedSize: number;
|
|
51
|
+
localOffset: number;
|
|
52
|
+
}
|
|
53
|
+
export declare class ZipArchive {
|
|
54
|
+
private readonly bytes;
|
|
55
|
+
readonly entries: ReadonlyMap<string, ZipEntryInfo>;
|
|
56
|
+
constructor(bytes: Uint8Array, entries: Map<string, ZipEntryInfo>);
|
|
57
|
+
has(name: string): boolean;
|
|
58
|
+
entryNames(): string[];
|
|
59
|
+
entryCount(): number;
|
|
60
|
+
/**
|
|
61
|
+
* Raw (still compressed) bytes of one entry, located through its own local
|
|
62
|
+
* header so central-directory offsets are never trusted for slicing.
|
|
63
|
+
*/
|
|
64
|
+
entryBytes(name: string): Uint8Array;
|
|
65
|
+
/** Inflated (stored or decompressed) bytes of one entry, bounded by its declared size. */
|
|
66
|
+
private entryData;
|
|
67
|
+
/** Decode one entry as UTF-8 text after inflating it when needed. */
|
|
68
|
+
entryText(name: string): string;
|
|
69
|
+
/** True when an entry's INFLATED content can round-trip an ASCII-safe rewrite. */
|
|
70
|
+
entryIsAsciiSafe(name: string): boolean;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Parse and guard one archive: the end-of-central-directory record is found
|
|
74
|
+
* by scanning backwards, every entry's DECLARED uncompressed size is checked
|
|
75
|
+
* against the budgets before anything is inflated, and every local slice is
|
|
76
|
+
* re-derived from the entry's own local header.
|
|
77
|
+
*/
|
|
78
|
+
export declare function readZip(bytes: Uint8Array, limits?: ZipGuardLimits): ZipArchive;
|
|
79
|
+
/**
|
|
80
|
+
* OOXML parts are plain element trees; a DOCTYPE/ENTITY declaration is never
|
|
81
|
+
* legitimate in one. Our extractors never resolve entities, but we refuse to
|
|
82
|
+
* look at such a part at all so entity-expansion payloads die at the door.
|
|
83
|
+
*/
|
|
84
|
+
export declare function assertNoXmlDtd(xml: string, label: string): void;
|
|
85
|
+
/**
|
|
86
|
+
* Read one zip part as text and refuse DTD/entity-bearing XML before any
|
|
87
|
+
* caller parses it. Returns null when the archive has no such part.
|
|
88
|
+
*/
|
|
89
|
+
export declare function readZipXmlPart(zip: ZipArchive, name: string): string | null;
|
|
90
|
+
/**
|
|
91
|
+
* Materialize every file entry as text for an ASCII-safe re-emit. Any entry
|
|
92
|
+
* with binary (non-ASCII) bytes throws: the re-publish path goes through the
|
|
93
|
+
* official UTF-8 text channel and cannot round-trip binary parts.
|
|
94
|
+
*/
|
|
95
|
+
export declare function asciiPartsOf(zip: ZipArchive): ZipPart[];
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The official file-system channel (1.0.0).
|
|
3
|
+
*
|
|
4
|
+
* Every byte this plugin reads or writes goes through the host's `ctx.fs`
|
|
5
|
+
* service (`@deepseek-ai/dsh-fs`): reads arrive as raw byte arrays via
|
|
6
|
+
* `readBytes`, writes leave as UTF-8 text via `writeText` — which is exactly
|
|
7
|
+
* why `buildAsciiZip` keeps generated packages pure ASCII. The plugin itself
|
|
8
|
+
* never touches the file system directly: workspace containment, symlink
|
|
9
|
+
* resolution, and atomic publication all belong to the backend, and this
|
|
10
|
+
* module only adds the Office-tool path policy on top (extension allow-lists,
|
|
11
|
+
* size caps, overwrite refusal, display paths). Writes carry the per-call
|
|
12
|
+
* sandbox policy (`ctx.sandboxPolicy.resolve({ session })` when a confining
|
|
13
|
+
* backend is mounted) so sandboxing backends fence them by the session's
|
|
14
|
+
* effective policy rather than the deployment default.
|
|
15
|
+
*/
|
|
16
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
17
|
+
import type { FileSystem, FsTarget } from '@deepseek-ai/dsh-fs';
|
|
18
|
+
import type { ToolRunContext } from '@deepseek-ai/dsh-tools';
|
|
19
|
+
/** Cap for text materialized into a single tool result. */
|
|
20
|
+
export declare const MAX_TEXT_CHARS = 200000;
|
|
21
|
+
/** Cap for worksheet cells materialized into a single tool result. */
|
|
22
|
+
export declare const MAX_READ_CELLS = 200000;
|
|
23
|
+
/** Cap for worksheet cells accepted by one create/update call. */
|
|
24
|
+
export declare const MAX_WRITE_CELLS = 200000;
|
|
25
|
+
/** The slice of the Cordis context this suite consumes: the official fs service. */
|
|
26
|
+
export interface FsContext {
|
|
27
|
+
fs: FileSystem;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The per-call sandbox execution policy `ctx.fs.writeText` accepts as its
|
|
31
|
+
* fifth parameter (`SandboxExecutionPolicy` from `@deepseek-ai/dsh-sandbox`):
|
|
32
|
+
* a sandboxing backend fences the write by it, the bare backend ignores it,
|
|
33
|
+
* and omitting it leaves the backend its deployment default — whose writable
|
|
34
|
+
* roots need not include this session's workspace. Derived from the official
|
|
35
|
+
* `writeText` signature so the plugin never depends on `@deepseek-ai/dsh-sandbox`
|
|
36
|
+
* directly.
|
|
37
|
+
*/
|
|
38
|
+
export type OfficeWritePolicy = NonNullable<Parameters<FileSystem['writeText']>[4]>;
|
|
39
|
+
/**
|
|
40
|
+
* Resolve the per-call write policy the way the official fs tool layer does:
|
|
41
|
+
* skip when the mounted backend does not confine (`fs.sandboxMode` undefined),
|
|
42
|
+
* otherwise ask the shared `sandboxPolicy` service for the calling session.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveWritePolicy(ctx: Context & FsContext, exec: ToolRunContext): Promise<OfficeWritePolicy | undefined>;
|
|
45
|
+
export interface ResolvedOfficePath {
|
|
46
|
+
/** The path exactly as the model passed it. */
|
|
47
|
+
input: string;
|
|
48
|
+
/** The backend's stable target for every subsequent operation. */
|
|
49
|
+
target: FsTarget;
|
|
50
|
+
/** The backend's canonical absolute process path (display and extension source). */
|
|
51
|
+
absolute: string;
|
|
52
|
+
/** Path rendered back to the model (workspace-relative when possible). */
|
|
53
|
+
display: string;
|
|
54
|
+
/** Lowercased extension including the leading dot. */
|
|
55
|
+
ext: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolve one model-supplied path to a workspace-confined backend target.
|
|
59
|
+
*
|
|
60
|
+
* @param exec - the running tool call (carries the session cwd + abort signal).
|
|
61
|
+
* @param rawPath - the model-supplied path string.
|
|
62
|
+
* @param allowedExts - acceptable lowercased extensions WITH dots (e.g. `.docx`).
|
|
63
|
+
* @param mustExist - when true, stat the target and refuse anything but a regular file.
|
|
64
|
+
* @param writePolicy - the per-call write policy, when a sandbox controller is
|
|
65
|
+
* mounted; its `workspaceRoot` is the authoritative workspace the write is
|
|
66
|
+
* fenced to, so relative paths resolve against it exactly like the official
|
|
67
|
+
* fs tools do. Omitted (reads, bare backends) keeps the session cwd.
|
|
68
|
+
*/
|
|
69
|
+
export declare function resolveOfficePath(exec: ToolRunContext, ctx: FsContext, rawPath: string, allowedExts: readonly string[], mustExist: boolean, writePolicy?: OfficeWritePolicy): Promise<ResolvedOfficePath>;
|
|
70
|
+
/**
|
|
71
|
+
* Read a bounded Office file through the backend, observing tool-call
|
|
72
|
+
* cancellation and refusing files above the hard cap before any transfer.
|
|
73
|
+
*/
|
|
74
|
+
export declare function readOfficeBytes(exec: ToolRunContext, ctx: FsContext, target: FsTarget): Promise<{
|
|
75
|
+
bytes: Uint8Array;
|
|
76
|
+
sizeBytes: number;
|
|
77
|
+
}>;
|
|
78
|
+
/**
|
|
79
|
+
* Publish one generated package: the text is pure ASCII (asserted by the zip
|
|
80
|
+
* planner), so the backend's atomic UTF-8 write lands it on disk
|
|
81
|
+
* byte-identical. The per-call write policy rides along as `writeText`'s
|
|
82
|
+
* fifth parameter so a sandboxing backend fences the write by the session's
|
|
83
|
+
* effective policy instead of its deployment default. Returns the on-disk size.
|
|
84
|
+
*/
|
|
85
|
+
export declare function saveOfficeText(exec: ToolRunContext, ctx: FsContext, target: FsTarget, text: string, writePolicy?: OfficeWritePolicy): Promise<number>;
|
|
86
|
+
/**
|
|
87
|
+
* Reject an overwrite when `overwrite` is false and the target already
|
|
88
|
+
* exists. Callers use this BEFORE doing expensive generation so the model
|
|
89
|
+
* gets a fast refusal instead of wasted work.
|
|
90
|
+
*/
|
|
91
|
+
export declare function assertMayCreate(exec: ToolRunContext, ctx: FsContext, target: FsTarget, overwrite: boolean): Promise<void>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intrinsic image dimensions from file headers (1.0.0). PNG, JPEG, and GIF
|
|
3
|
+
* expose their pixel size in the first few hundred of bytes, so the linked
|
|
4
|
+
* image placement can default to natural size — and compute contain/cover
|
|
5
|
+
* crops — by reading the workspace file through the official fs channel and
|
|
6
|
+
* inspecting bytes directly. No image decoder, no dependency, no signal.
|
|
7
|
+
*/
|
|
8
|
+
/** Returned dimensions in pixels; undefined when the bytes are not a recognized image. */
|
|
9
|
+
export interface ImageSize {
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
}
|
|
13
|
+
/** Sniff the pixel size of a PNG/JPEG/GIF byte buffer; undefined otherwise. */
|
|
14
|
+
export declare function sniffImageSize(bytes: Uint8Array): ImageSize | undefined;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-office-tools host plugin.
|
|
3
3
|
*
|
|
4
|
-
* Registers
|
|
4
|
+
* Registers eight model-facing tools on `ctx.tools`:
|
|
5
5
|
*
|
|
6
|
-
* word_create / word_read
|
|
6
|
+
* word_create / word_read / word_update
|
|
7
7
|
* excel_create / excel_read / excel_update
|
|
8
8
|
* ppt_create / ppt_read
|
|
9
9
|
*
|
|
10
10
|
* All file access is confined to the calling agent's session workspace and
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* flows exclusively through the official `ctx.fs` service — reads as raw
|
|
12
|
+
* bytes, writes as UTF-8 text (generated packages are pure ASCII by
|
|
13
|
+
* construction). Every registration is wrapped in `ctx.effect` so Cordis
|
|
14
|
+
* disposes the tools with the plugin fiber.
|
|
13
15
|
*
|
|
14
|
-
* The PowerPoint pair is config-gated: dedicated presentation plugins such
|
|
15
|
-
* dsh-ppt register a colliding `ppt_create`, and DSH refuses duplicate tool
|
|
16
|
+
* The PowerPoint pair is config-gated: dedicated presentation plugins such
|
|
17
|
+
* as dsh-ppt register a colliding `ppt_create`, and DSH refuses duplicate tool
|
|
16
18
|
* names at startup, so profiles running one of those set `enablePptTools: false`
|
|
17
19
|
* to load this plugin for Word/Excel only.
|
|
18
20
|
*/
|
|
@@ -20,7 +22,7 @@ import type { Context } from '@deepseek-ai/cordis';
|
|
|
20
22
|
import z from '@deepseek-ai/schemastery';
|
|
21
23
|
/** Plugin identity for cordis.yml rows. */
|
|
22
24
|
export declare const name = "dsh-office-tools";
|
|
23
|
-
/** The tool registry
|
|
25
|
+
/** The tool registry and the official filesystem service are the only runtime services required. */
|
|
24
26
|
export declare const inject: string[];
|
|
25
27
|
/** Host plugin configuration, validated at load by the Loader. */
|
|
26
28
|
export interface Config {
|
package/lib/types/paths.d.ts
CHANGED
|
@@ -8,9 +8,21 @@
|
|
|
8
8
|
* inside it, and a realpath check on the nearest existing ancestor closes the
|
|
9
9
|
* symlink escape hatch.
|
|
10
10
|
*/
|
|
11
|
+
import JSZip from 'jszip';
|
|
11
12
|
import type { ToolRunContext } from '@deepseek-ai/dsh-tools';
|
|
12
13
|
/** Hard cap for reading an existing Office file into memory. */
|
|
13
14
|
export declare const MAX_OFFICE_FILE_BYTES: number;
|
|
15
|
+
/**
|
|
16
|
+
* Declared uncompressed ceiling for one zip entry inside an Office file. The
|
|
17
|
+
* 50 MiB read cap only bounds the COMPRESSED bytes; deflate can expand a file
|
|
18
|
+
* a thousandfold, so {@link loadZipGuarded} also checks the archive's own
|
|
19
|
+
* declared sizes before any entry is inflated.
|
|
20
|
+
*/
|
|
21
|
+
export declare const MAX_ZIP_ENTRY_BYTES: number;
|
|
22
|
+
/** Declared uncompressed ceiling summed over all entries of one archive. */
|
|
23
|
+
export declare const MAX_ZIP_TOTAL_BYTES: number;
|
|
24
|
+
/** Maximum entries (files + directories) in one archive. */
|
|
25
|
+
export declare const MAX_ZIP_ENTRIES = 100000;
|
|
14
26
|
/** Cap for text materialized into a single tool result. */
|
|
15
27
|
export declare const MAX_TEXT_CHARS = 200000;
|
|
16
28
|
/** Cap for worksheet cells materialized into a single tool result. */
|
|
@@ -43,6 +55,30 @@ export declare function readOfficeBuffer(path: string, signal: AbortSignal): Pro
|
|
|
43
55
|
buffer: Buffer;
|
|
44
56
|
sizeBytes: number;
|
|
45
57
|
}>;
|
|
58
|
+
/** Overridable budgets so tests can trip the guard with tiny values. */
|
|
59
|
+
export interface ZipGuardLimits {
|
|
60
|
+
maxEntryBytes?: number;
|
|
61
|
+
maxTotalBytes?: number;
|
|
62
|
+
maxEntries?: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Load a zip-backed Office file with jszip and refuse archives whose central
|
|
66
|
+
* directory already declares more uncompressed content than we are ever
|
|
67
|
+
* willing to inflate (the zip-bomb guard), or more entries than we will walk.
|
|
68
|
+
* Returns the loaded instance so callers never parse the buffer twice.
|
|
69
|
+
*/
|
|
70
|
+
export declare function loadZipGuarded(buffer: Buffer, signal: AbortSignal, limits?: ZipGuardLimits): Promise<JSZip>;
|
|
71
|
+
/**
|
|
72
|
+
* OOXML parts are plain element trees; a DOCTYPE/ENTITY declaration is never
|
|
73
|
+
* legitimate in one. Our extractors never resolve entities, but we refuse to
|
|
74
|
+
* look at such a part at all so entity-expansion payloads die at the door.
|
|
75
|
+
*/
|
|
76
|
+
export declare function assertNoXmlDtd(xml: string, label: string): void;
|
|
77
|
+
/**
|
|
78
|
+
* Read one zip part as text and refuse DTD/entity-bearing XML before any
|
|
79
|
+
* caller parses it. Returns null when the archive has no such part.
|
|
80
|
+
*/
|
|
81
|
+
export declare function readZipXmlPart(zip: JSZip, name: string, signal: AbortSignal): Promise<string | null>;
|
|
46
82
|
/**
|
|
47
83
|
* Write a buffer through a same-directory temp file + rename so a failed
|
|
48
84
|
* generation never leaves a half-written Office document behind.
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Excel (.xlsx) tools over
|
|
3
|
-
* `
|
|
4
|
-
* `excel_update` replaces/creates whole sheets
|
|
5
|
-
* values into an existing workbook.
|
|
2
|
+
* Excel (.xlsx) tools over the self-contained OOXML container (1.0.0):
|
|
3
|
+
* `excel_create` writes a new workbook, `excel_read` materializes sheets as
|
|
4
|
+
* rows of scalar cells, and `excel_update` replaces/creates whole sheets
|
|
5
|
+
* and/or writes individual cell values into an existing workbook. Reads
|
|
6
|
+
* accept any real-world package (STORE and DEFLATE); writes and rewrites are
|
|
7
|
+
* ASCII-safe STORE packages published through the official fs channel, with
|
|
8
|
+
* strings written inline and '=…' strings materialized as real `<f>` formula
|
|
9
|
+
* cells exactly like the SheetJS-era behavior the tests pin.
|
|
6
10
|
*/
|
|
7
11
|
import type { Context } from '@deepseek-ai/cordis';
|
|
8
|
-
|
|
12
|
+
import type { FsContext } from '../fschannel.ts';
|
|
13
|
+
export declare function registerExcelTools(ctx: Context & FsContext): () => void;
|
package/lib/types/tools/ppt.d.ts
CHANGED
|
@@ -1,8 +1,69 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PowerPoint (.pptx) tools
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* PowerPoint (.pptx) tools over the self-contained OOXML container (1.0.0).
|
|
3
|
+
*
|
|
4
|
+
* `ppt_create` builds a widescreen deck as an ASCII-safe STORE package
|
|
5
|
+
* published through the official fs channel — no third-party library, no
|
|
6
|
+
* binary package parts. Pictures are LINKED (`a:blip r:link`): the model
|
|
7
|
+
* fully controls placement (x/y/w/h inches, contain/cover cropping, alt
|
|
8
|
+
* text), the image file stays a workspace artifact, and intrinsic sizes are
|
|
9
|
+
* sniffed from PNG/JPEG/GIF headers so omitted dimensions default to natural
|
|
10
|
+
* size. The create result echoes every element's landing geometry, and both
|
|
11
|
+
* tools render a text wireframe sketch so the model can SEE the composition.
|
|
12
|
+
*
|
|
13
|
+
* `ppt_read` reads any real-world deck and returns, per slide in document
|
|
14
|
+
* order: paragraphs, tables, notes, image counts/alt texts — plus an
|
|
15
|
+
* `elements` array with every shape's bounding box in inches and the deck's
|
|
16
|
+
* canvas size. True raster rendering stays out of scope on purpose: it would
|
|
17
|
+
* need a headless renderer (a subprocess plus a binary dependency), which is
|
|
18
|
+
* exactly what the dependency-free, no-commands contract forbids.
|
|
6
19
|
*/
|
|
7
20
|
import type { Context } from '@deepseek-ai/cordis';
|
|
8
|
-
|
|
21
|
+
import type { ToolRunContext } from '@deepseek-ai/dsh-tools';
|
|
22
|
+
import type { FsContext, ResolvedOfficePath } from '../fschannel.ts';
|
|
23
|
+
interface SlideImageSpec {
|
|
24
|
+
path: string;
|
|
25
|
+
x?: number;
|
|
26
|
+
y?: number;
|
|
27
|
+
w?: number;
|
|
28
|
+
h?: number;
|
|
29
|
+
sizing?: 'contain' | 'cover';
|
|
30
|
+
alt?: string;
|
|
31
|
+
}
|
|
32
|
+
interface SlideSpec {
|
|
33
|
+
title?: string;
|
|
34
|
+
paragraphs?: string[];
|
|
35
|
+
bullets?: string[];
|
|
36
|
+
notes?: string;
|
|
37
|
+
images?: SlideImageSpec[];
|
|
38
|
+
}
|
|
39
|
+
interface PptCreateArgs {
|
|
40
|
+
path: string;
|
|
41
|
+
title?: string;
|
|
42
|
+
slides?: SlideSpec[];
|
|
43
|
+
overwrite?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** One placed element, echoed by create and parsed back by read. */
|
|
46
|
+
export interface SlideElementBox {
|
|
47
|
+
type: 'text' | 'bullets' | 'image' | 'table';
|
|
48
|
+
xIn: number;
|
|
49
|
+
yIn: number;
|
|
50
|
+
wIn: number;
|
|
51
|
+
hIn: number;
|
|
52
|
+
text?: string;
|
|
53
|
+
items?: string[];
|
|
54
|
+
alt?: string;
|
|
55
|
+
path?: string;
|
|
56
|
+
sizing?: 'contain' | 'cover';
|
|
57
|
+
}
|
|
58
|
+
/** Build the whole deck as an ASCII-safe package plus the layout echo. */
|
|
59
|
+
export declare function buildPptxText(args: PptCreateArgs, exec: ToolRunContext, ctx: FsContext, deck: ResolvedOfficePath): Promise<{
|
|
60
|
+
text: string;
|
|
61
|
+
layout: Array<{
|
|
62
|
+
index: number;
|
|
63
|
+
elements: SlideElementBox[];
|
|
64
|
+
}>;
|
|
65
|
+
}>;
|
|
66
|
+
/** Render one slide as an ASCII wireframe: the canvas frame plus boxes. */
|
|
67
|
+
export declare function sketchSlide(widthIn: number, heightIn: number, elements: Array<Pick<SlideElementBox, 'type' | 'xIn' | 'yIn' | 'wIn' | 'hIn' | 'text'>>): string;
|
|
68
|
+
export declare function registerPptTools(ctx: Context & FsContext): () => void;
|
|
69
|
+
export {};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared schema fragments
|
|
3
|
-
* the schemas in one place keeps the
|
|
2
|
+
* Shared schema fragments, cell types, and the ASCII-safe XML encoders for
|
|
3
|
+
* the Office tool suite. Keeping the schemas in one place keeps the eight
|
|
4
|
+
* tool contracts consistent; the encoders guarantee that generated XML stays
|
|
5
|
+
* pure ASCII (non-ASCII text becomes decimal character references), which is
|
|
6
|
+
* the invariant the ASCII-safe zip writer builds on.
|
|
4
7
|
*/
|
|
5
8
|
/** One cell value accepted by the Excel tools. */
|
|
6
9
|
export declare const CELL_VALUE_SCHEMA: {
|
|
@@ -46,3 +49,12 @@ export declare const FILE_RESULT_SCHEMA: {
|
|
|
46
49
|
};
|
|
47
50
|
export type CellValue = string | number | boolean | null;
|
|
48
51
|
export type CellRow = CellValue[];
|
|
52
|
+
/** Escape text for XML element content, keeping the output pure ASCII. */
|
|
53
|
+
export declare function encodeXmlText(value: string): string;
|
|
54
|
+
/** Escape text for a double-quoted XML attribute value, keeping it pure ASCII. */
|
|
55
|
+
export declare function encodeXmlAttribute(value: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* Decode the XML entities that can legally appear in OOXML text content: the
|
|
58
|
+
* five predefined names plus decimal/hex character references.
|
|
59
|
+
*/
|
|
60
|
+
export declare function decodeXmlEntities(value: string): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `word_update`: append paragraphs, bullets, and one table to an existing
|
|
3
|
+
* .docx (1.0.0). The appended body children are generated by the same part
|
|
4
|
+
* builder word_create uses, then spliced into the document's body right
|
|
5
|
+
* before its trailing `<w:sectPr>`. The archive is re-emitted as an
|
|
6
|
+
* ASCII-safe STORE package through the official fs channel, so updates are
|
|
7
|
+
* limited to packages whose every part can round-trip as ASCII text — always
|
|
8
|
+
* true for files these tools write, and for any OOXML package that carries
|
|
9
|
+
* no binary media.
|
|
10
|
+
*/
|
|
11
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
12
|
+
import type { FsContext } from '../fschannel.ts';
|
|
13
|
+
export declare function registerWordUpdate(ctx: Context & FsContext): () => void;
|
|
@@ -1,6 +1,66 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Word (.docx) tools
|
|
3
|
-
*
|
|
2
|
+
* Word (.docx) tools over the self-contained OOXML container (1.0.0):
|
|
3
|
+
* `word_create` builds a styled document part set directly (no third-party
|
|
4
|
+
* library — the package is an ASCII-safe STORE zip written through the
|
|
5
|
+
* official fs channel); `word_read` extracts plain text or markdown with the
|
|
6
|
+
* same in-house regex extractor whose behavior is pinned to mammoth 1.11.0's
|
|
7
|
+
* raw-text output by `tests/word-parity.spec.ts`.
|
|
4
8
|
*/
|
|
5
9
|
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
-
|
|
10
|
+
import type { FsContext } from '../fschannel.ts';
|
|
11
|
+
export interface WordCreateArgs {
|
|
12
|
+
path: string;
|
|
13
|
+
title?: string;
|
|
14
|
+
paragraphs?: string[];
|
|
15
|
+
bullets?: string[];
|
|
16
|
+
table?: {
|
|
17
|
+
headers: string[];
|
|
18
|
+
rows: string[][];
|
|
19
|
+
};
|
|
20
|
+
overwrite?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface WordUpdateArgs {
|
|
23
|
+
path: string;
|
|
24
|
+
paragraphs?: string[];
|
|
25
|
+
bullets?: string[];
|
|
26
|
+
table?: {
|
|
27
|
+
headers: string[];
|
|
28
|
+
rows: string[][];
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export declare function wordCreateCounts(args: WordUpdateArgs & {
|
|
32
|
+
title?: string;
|
|
33
|
+
}): {
|
|
34
|
+
paragraphs: number;
|
|
35
|
+
cells: number;
|
|
36
|
+
};
|
|
37
|
+
/** Build the complete .docx package as an ASCII-safe zip string. */
|
|
38
|
+
export declare function buildDocxText(args: WordUpdateArgs & {
|
|
39
|
+
title?: string;
|
|
40
|
+
}): string;
|
|
41
|
+
/**
|
|
42
|
+
* Extract mammoth-compatible plain text from a `word/document.xml` string:
|
|
43
|
+
* every paragraph — body, table cell, hyperlink, or textbox — contributes its
|
|
44
|
+
* runs followed by `\n\n`, including the final and empty paragraphs.
|
|
45
|
+
*/
|
|
46
|
+
export declare function extractDocxText(documentXml: string): string;
|
|
47
|
+
/**
|
|
48
|
+
* Extract structured markdown from a `word/document.xml` string: Title and
|
|
49
|
+
* Heading1-6 render as `#`..`######`, numbered/bullet paragraphs as indented
|
|
50
|
+
* `- ` items, tables as markdown tables, everything else as plain blocks —
|
|
51
|
+
* inline rules identical to {@link extractDocxText}.
|
|
52
|
+
*/
|
|
53
|
+
export declare function extractDocxMarkdown(documentXml: string): string;
|
|
54
|
+
/**
|
|
55
|
+
* Body children for an append, generated by the same part builder
|
|
56
|
+
* word_create uses (so paragraphs, bullets, and tables come out identical)
|
|
57
|
+
* and extracted from the throwaway document minus its trailing `<w:sectPr>`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function buildAppendFragment(args: WordUpdateArgs): string;
|
|
60
|
+
/**
|
|
61
|
+
* Splice new body children into `word/document.xml`: appended content goes
|
|
62
|
+
* right before the trailing `<w:sectPr>` (page setup must stay the last body
|
|
63
|
+
* child), or before `</w:body>` when the document has no section properties.
|
|
64
|
+
*/
|
|
65
|
+
export declare function appendBeforeSectPr(documentXml: string, addition: string): string;
|
|
66
|
+
export declare function registerWordTools(ctx: Context & FsContext): () => void;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-office-tools",
|
|
3
|
-
"version": "0.2
|
|
4
|
-
"description": "DSH model-facing Office tools: create, read, and update Word (.docx), Excel (.xlsx), and PowerPoint (.pptx) files inside the session workspace.",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "DSH model-facing Office tools: create, read, and update Word (.docx), Excel (.xlsx), and PowerPoint (.pptx) files inside the session workspace, entirely through the official DSH filesystem service.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|
|
@@ -16,10 +16,20 @@
|
|
|
16
16
|
"dsh": {
|
|
17
17
|
"bundle": {
|
|
18
18
|
"patch": "./cordis.patch.yml"
|
|
19
|
+
},
|
|
20
|
+
"compatibility": {
|
|
21
|
+
"dsh": ">=0.1.0-rc.6",
|
|
22
|
+
"dshReleases": {
|
|
23
|
+
"0.1.1-rc.2": "compatible",
|
|
24
|
+
"0.1.2-alpha.4": "compatible",
|
|
25
|
+
"0.1.2-alpha.5": "compatible",
|
|
26
|
+
"0.1.2-rc.1": "compatible"
|
|
27
|
+
}
|
|
19
28
|
}
|
|
20
29
|
},
|
|
21
30
|
"files": [
|
|
22
|
-
"lib",
|
|
31
|
+
"lib/*.js",
|
|
32
|
+
"lib/types",
|
|
23
33
|
"cordis.patch.yml",
|
|
24
34
|
"dsh.plugin.json",
|
|
25
35
|
"README.md",
|
|
@@ -28,16 +38,18 @@
|
|
|
28
38
|
],
|
|
29
39
|
"scripts": {
|
|
30
40
|
"build": "node build.mjs",
|
|
31
|
-
"
|
|
41
|
+
"types": "tsc -p tsconfig.json",
|
|
42
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
32
43
|
"test": "vitest run",
|
|
33
|
-
"check": "pnpm run typecheck && pnpm run test && pnpm run build"
|
|
44
|
+
"check": "pnpm run typecheck && pnpm run types && pnpm run test && pnpm run build"
|
|
34
45
|
},
|
|
35
46
|
"peerDependencies": {
|
|
36
47
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
37
|
-
"@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
|
|
38
|
-
"@deepseek-ai/dsh-
|
|
39
|
-
"@deepseek-ai/dsh-
|
|
40
|
-
"@deepseek-ai/dsh-
|
|
48
|
+
"@deepseek-ai/dsh-agent": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0",
|
|
49
|
+
"@deepseek-ai/dsh-fs": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0",
|
|
50
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0",
|
|
51
|
+
"@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0",
|
|
52
|
+
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0"
|
|
41
53
|
},
|
|
42
54
|
"peerDependenciesMeta": {
|
|
43
55
|
"@deepseek-ai/cordis": {
|
|
@@ -46,6 +58,9 @@
|
|
|
46
58
|
"@deepseek-ai/dsh-agent": {
|
|
47
59
|
"optional": true
|
|
48
60
|
},
|
|
61
|
+
"@deepseek-ai/dsh-fs": {
|
|
62
|
+
"optional": true
|
|
63
|
+
},
|
|
49
64
|
"@deepseek-ai/dsh-llm": {
|
|
50
65
|
"optional": true
|
|
51
66
|
},
|
|
@@ -58,20 +73,18 @@
|
|
|
58
73
|
},
|
|
59
74
|
"devDependencies": {
|
|
60
75
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
61
|
-
"@deepseek-ai/dsh-agent": "
|
|
62
|
-
"@deepseek-ai/dsh-
|
|
63
|
-
"@deepseek-ai/dsh-
|
|
64
|
-
"@deepseek-ai/dsh-
|
|
65
|
-
"@deepseek-ai/
|
|
76
|
+
"@deepseek-ai/dsh-agent": "0.1.2-rc.1",
|
|
77
|
+
"@deepseek-ai/dsh-fs": "0.1.2-rc.1",
|
|
78
|
+
"@deepseek-ai/dsh-llm": "0.1.2-rc.1",
|
|
79
|
+
"@deepseek-ai/dsh-session": "0.1.2-rc.1",
|
|
80
|
+
"@deepseek-ai/dsh-tools": "0.1.2-rc.1",
|
|
81
|
+
"@deepseek-ai/schemastery": "^3.18.2",
|
|
66
82
|
"@types/node": "^24.0.0",
|
|
67
|
-
"docx": "^9.5.1",
|
|
68
83
|
"esbuild": "^0.25.0",
|
|
69
84
|
"jszip": "^3.10.1",
|
|
70
|
-
"mammoth": "^1.11.0",
|
|
71
|
-
"pptxgenjs": "^4.0.1",
|
|
72
85
|
"typescript": "^5.6.0",
|
|
73
86
|
"vitest": "^4.1.8",
|
|
74
|
-
"xlsx": "
|
|
87
|
+
"xlsx": "^0.18.5"
|
|
75
88
|
},
|
|
76
89
|
"author": "kw78",
|
|
77
90
|
"homepage": "https://github.com/kw78/dsh-office-tools#readme",
|
|
@@ -101,5 +114,47 @@
|
|
|
101
114
|
"publishConfig": {
|
|
102
115
|
"access": "public"
|
|
103
116
|
},
|
|
104
|
-
"packageManager": "pnpm@
|
|
117
|
+
"packageManager": "pnpm@10.34.5",
|
|
118
|
+
"dshWorkshop": {
|
|
119
|
+
"schema": "omdsh-workshop-package/v1",
|
|
120
|
+
"type": "plugin",
|
|
121
|
+
"integration": {
|
|
122
|
+
"protocol": "harness-profile",
|
|
123
|
+
"artifact": "cordis.patch.yml"
|
|
124
|
+
},
|
|
125
|
+
"install": {
|
|
126
|
+
"mode": "transactional",
|
|
127
|
+
"adapter": "profile-bundle",
|
|
128
|
+
"failurePolicy": "generation-rollback",
|
|
129
|
+
"touchesCurrentBeforeActivation": false
|
|
130
|
+
},
|
|
131
|
+
"lifecycle": {
|
|
132
|
+
"activation": "restart-profile",
|
|
133
|
+
"dispose": "supported"
|
|
134
|
+
},
|
|
135
|
+
"permissions": [
|
|
136
|
+
"files:workspace-read",
|
|
137
|
+
"files:workspace-write"
|
|
138
|
+
],
|
|
139
|
+
"compatibility": {
|
|
140
|
+
"dshVersions": [
|
|
141
|
+
"0.1.1-rc.2",
|
|
142
|
+
"0.1.2-alpha.4",
|
|
143
|
+
"0.1.2-alpha.5",
|
|
144
|
+
"0.1.2-rc.1"
|
|
145
|
+
]
|
|
146
|
+
},
|
|
147
|
+
"capability": {
|
|
148
|
+
"id": "word_create",
|
|
149
|
+
"kind": "tool",
|
|
150
|
+
"invocation": "word_create({\"path\": \"hub-check.docx\", \"paragraphs\": [\"dsh-hub intake check\"]}) in a session with workspace cwd",
|
|
151
|
+
"expected": "a real .docx (first two bytes PK) exists at the workspace path; the result reports sizeBytes > 0; word_read returns 'dsh-hub intake check'"
|
|
152
|
+
},
|
|
153
|
+
"evidence": {
|
|
154
|
+
"install": null,
|
|
155
|
+
"failureIsolation": null,
|
|
156
|
+
"hotReload": null,
|
|
157
|
+
"remove": null
|
|
158
|
+
}
|
|
159
|
+
}
|
|
105
160
|
}
|