dsh-office-tools 1.0.4 → 1.0.5
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 +3 -1
- package/README.zh.md +3 -1
- package/dsh.plugin.json +2 -2
- package/lib/index.js +89 -35
- package/package.json +13 -9
package/README.md
CHANGED
|
@@ -61,6 +61,8 @@ pnpm install && pnpm run check # typecheck + tests + build
|
|
|
61
61
|
pnpm run test:e2e # real-composition E2E (sandbox-policy + fs-sandbox + tools)
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
Compatibility: DSH
|
|
64
|
+
Compatibility: the declared DSH 0.1.x ranges and `>=0.2.0-rc.1 <0.3.0-0`; exact tested releases live in `package.json` (`dsh.compatibility.dshReleases`). CI exercises both 0.1.2-rc.1 and 0.2.0-rc.1. DSH 0.3.x is not declared supported.
|
|
65
|
+
|
|
66
|
+
Generated packages are limited to 50 MiB. Up to 200 PPT slides (including an optional title slide) are accepted; linked images remain external. See [architecture and migration plan](docs/ARCHITECTURE.zh.md) for the binary-write seam needed for true embedding.
|
|
65
67
|
|
|
66
68
|
Further reading: [DEVELOPMENT.md](docs/DEVELOPMENT.md) · [ROADMAP.md](docs/ROADMAP.md) · [hub-registration.md](docs/hub-registration.md) · MIT [License](LICENSE)
|
package/README.zh.md
CHANGED
|
@@ -61,6 +61,8 @@ pnpm install && pnpm run check # typecheck + tests + build
|
|
|
61
61
|
pnpm run test:e2e # 真实组合 E2E(sandbox-policy + fs-sandbox + tools)
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
兼容性:声明的 DSH 0.1.x 区间,以及 `>=0.2.0-rc.1 <0.3.0-0`;逐版本的实测记录见 `package.json`(`dsh.compatibility.dshReleases`)。CI 验证 0.1.2-rc.1 和 0.2.0-rc.1,尚未声明支持 DSH 0.3.x。
|
|
65
|
+
|
|
66
|
+
生成文件最多 50 MiB;PPT 最多 200 页(包含可选封面)。图片仍为外部链接。分层架构与真实图片嵌入的迁移方案见 [架构分析](docs/ARCHITECTURE.zh.md)。
|
|
65
67
|
|
|
66
68
|
更多文档:[DEVELOPMENT.md](docs/DEVELOPMENT.md) · [ROADMAP.md](docs/ROADMAP.md) · [hub-registration.md](docs/hub-registration.md) · MIT [License](LICENSE)
|
package/dsh.plugin.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-office-tools",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"description": "Office tools for the model: create, read, and update Word (.docx), Excel (.xlsx), and PowerPoint (.pptx) files inside the session workspace through the official DSH filesystem service.",
|
|
6
6
|
"engines": {
|
|
7
|
-
"dsh": "
|
|
7
|
+
"dsh": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0 || ^0.1.5-alpha.0 || >=0.2.0-rc.1 <0.3.0-0"
|
|
8
8
|
},
|
|
9
9
|
"contributes": {
|
|
10
10
|
"tools": [
|
package/lib/index.js
CHANGED
|
@@ -52,15 +52,49 @@ var DOS_DATE = 2026 - 1980 << 9 | 1 << 5 | 1;
|
|
|
52
52
|
var LOCAL_HEADER_BYTES = 30;
|
|
53
53
|
var CENTRAL_HEADER_BYTES = 46;
|
|
54
54
|
var EOCD_BYTES = 22;
|
|
55
|
-
var
|
|
55
|
+
var MAX_SAFE_U16 = 32639;
|
|
56
|
+
var CRC_SUFFIX_BYTES = 8;
|
|
57
|
+
function nextSafeField(value) {
|
|
58
|
+
let candidate = value;
|
|
59
|
+
for (let byte = 0; byte < 4; byte += 1) {
|
|
60
|
+
const unit = 2 ** (8 * byte);
|
|
61
|
+
if (Math.floor(candidate / unit) % 256 > 127) {
|
|
62
|
+
const band = unit * 256;
|
|
63
|
+
candidate = (Math.floor(candidate / band) + 1) * band;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (!safeField(candidate)) throw new Error("ASCII-safe ZIP field exceeds its representable range");
|
|
67
|
+
return candidate;
|
|
68
|
+
}
|
|
69
|
+
function safeCrcSuffix(state) {
|
|
70
|
+
for (let variant = 0; variant < 256; variant += 1) {
|
|
71
|
+
let folded = state;
|
|
72
|
+
let suffix = "";
|
|
73
|
+
for (let bit = 0; bit < CRC_SUFFIX_BYTES; bit += 1) {
|
|
74
|
+
const byte = variant & 1 << bit ? 9 : 32;
|
|
75
|
+
suffix += String.fromCharCode(byte);
|
|
76
|
+
folded = crcFold(folded, byte);
|
|
77
|
+
}
|
|
78
|
+
const crc = (folded ^ 4294967295) >>> 0;
|
|
79
|
+
if (safeField(crc)) return { suffix, crc };
|
|
80
|
+
}
|
|
81
|
+
throw new Error("internal error: trailing XML whitespace cannot produce a byte-safe CRC");
|
|
82
|
+
}
|
|
56
83
|
function extraField(total) {
|
|
57
84
|
if (total === 0) return "";
|
|
58
85
|
return u16(0) + u16(total - 4) + "\0".repeat(total - 4);
|
|
59
86
|
}
|
|
60
87
|
function feasibleExtraLength(end) {
|
|
61
88
|
if (safeField(end)) return 0;
|
|
62
|
-
|
|
63
|
-
|
|
89
|
+
let total = nextSafeField(Math.max(4, nextSafeField(end) - end));
|
|
90
|
+
while (total <= MAX_SAFE_U16) {
|
|
91
|
+
if (!safeField(total - 4)) {
|
|
92
|
+
total = nextSafeField(total + 4);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const next = nextSafeField(end + total);
|
|
96
|
+
if (next === end + total) return total;
|
|
97
|
+
total = nextSafeField(total + next - (end + total));
|
|
64
98
|
}
|
|
65
99
|
return void 0;
|
|
66
100
|
}
|
|
@@ -69,49 +103,46 @@ function planEntries(parts) {
|
|
|
69
103
|
let offset = 0;
|
|
70
104
|
for (const part of parts) {
|
|
71
105
|
assertAscii(part.name, "zip entry name");
|
|
106
|
+
assertAscii(part.content, "zip entry content");
|
|
72
107
|
const nameLength = part.name.length;
|
|
73
108
|
const headerSize = LOCAL_HEADER_BYTES + nameLength;
|
|
74
109
|
const baseLength = part.content.length;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
let state = contentCrcState(part.content);
|
|
78
|
-
for (let pad = 0; pad <= MAX_PLAN_PAD && chosen === void 0; pad += 1) {
|
|
79
|
-
if (pad > 0) state = crcFold(state, 10);
|
|
80
|
-
const length = baseLength + pad;
|
|
81
|
-
if (!safeField(length)) continue;
|
|
82
|
-
const crc = (state ^ 4294967295) >>> 0;
|
|
83
|
-
if (!safeField(crc)) continue;
|
|
84
|
-
const extraLength = feasibleExtraLength(minEnd + pad);
|
|
85
|
-
if (extraLength === void 0) continue;
|
|
86
|
-
const content = pad === 0 ? part.content : part.content + "\n".repeat(pad);
|
|
87
|
-
chosen = { content, crc, extraLength, end: minEnd + pad + extraLength };
|
|
110
|
+
if (!safeField(nameLength) || nameLength > MAX_SAFE_U16) {
|
|
111
|
+
throw new Error(`zip entry name is too long or has a non-byte-safe length: "${part.name}"`);
|
|
88
112
|
}
|
|
89
|
-
|
|
90
|
-
|
|
113
|
+
let length = nextSafeField(baseLength + CRC_SUFFIX_BYTES);
|
|
114
|
+
let extraLength;
|
|
115
|
+
for (; ; ) {
|
|
116
|
+
const end = offset + headerSize + length;
|
|
117
|
+
if (end > MAX_OFFICE_FILE_BYTES) throw new Error("generated Office package exceeds the 50 MiB write budget");
|
|
118
|
+
extraLength = feasibleExtraLength(end);
|
|
119
|
+
if (extraLength !== void 0) break;
|
|
120
|
+
length = nextSafeField(length + Math.max(1, nextSafeField(end) - end - MAX_SAFE_U16));
|
|
91
121
|
}
|
|
122
|
+
const pad = length - baseLength - CRC_SUFFIX_BYTES;
|
|
123
|
+
let state = contentCrcState(part.content);
|
|
124
|
+
for (let index = 0; index < pad; index += 1) state = crcFold(state, 10);
|
|
125
|
+
const { suffix, crc } = safeCrcSuffix(state);
|
|
92
126
|
planned.push({
|
|
93
127
|
name: part.name,
|
|
94
|
-
content:
|
|
95
|
-
crc
|
|
128
|
+
content: part.content + "\n".repeat(pad) + suffix,
|
|
129
|
+
crc,
|
|
96
130
|
offset,
|
|
97
|
-
localExtraLength:
|
|
131
|
+
localExtraLength: extraLength,
|
|
98
132
|
commentLength: 0,
|
|
99
133
|
centralExtraLength: 0
|
|
100
134
|
});
|
|
101
|
-
offset
|
|
135
|
+
offset += headerSize + extraLength + length;
|
|
102
136
|
}
|
|
103
137
|
const baseDirectorySize = planned.reduce((sum, entry) => sum + CENTRAL_HEADER_BYTES + entry.name.length, 0);
|
|
104
|
-
const extraCandidates = [0];
|
|
105
|
-
for (let total = 4; total <= 127; total += 1) extraCandidates.push(total);
|
|
106
138
|
let commentLength = -1;
|
|
107
139
|
let centralExtraLength = -1;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
140
|
+
for (let comment = 0; comment <= MAX_SAFE_U16; comment = nextSafeField(comment + 1)) {
|
|
141
|
+
const extra = feasibleExtraLength(baseDirectorySize + comment);
|
|
142
|
+
if (extra !== void 0) {
|
|
143
|
+
centralExtraLength = extra;
|
|
144
|
+
commentLength = comment;
|
|
145
|
+
break;
|
|
115
146
|
}
|
|
116
147
|
}
|
|
117
148
|
if (commentLength < 0 || centralExtraLength < 0) {
|
|
@@ -125,10 +156,29 @@ function planEntries(parts) {
|
|
|
125
156
|
return { entries: planned, directoryOffset: offset };
|
|
126
157
|
}
|
|
127
158
|
function buildAsciiZip(parts) {
|
|
128
|
-
if (parts.length === 0 || parts.length >
|
|
129
|
-
throw new Error(`an ASCII-safe package holds 1
|
|
130
|
-
}
|
|
131
|
-
const
|
|
159
|
+
if (parts.length === 0 || parts.length > MAX_SAFE_U16) {
|
|
160
|
+
throw new Error(`an ASCII-safe package holds 1..${MAX_SAFE_U16} entries, got ${parts.length}`);
|
|
161
|
+
}
|
|
162
|
+
const count = nextSafeField(parts.length);
|
|
163
|
+
const names = new Set(parts.map((part) => part.name));
|
|
164
|
+
if (names.size !== parts.length) throw new Error("cannot write a zip archive with duplicate entry names");
|
|
165
|
+
const expanded = [...parts];
|
|
166
|
+
const paddingNames = [];
|
|
167
|
+
for (let index = 1; expanded.length < count; index += 1) {
|
|
168
|
+
const name2 = `dshPadding/pad${index}.xml`;
|
|
169
|
+
if (names.has(name2)) continue;
|
|
170
|
+
names.add(name2);
|
|
171
|
+
paddingNames.push(name2);
|
|
172
|
+
expanded.push({ name: name2, content: '<padding xmlns="urn:dsh-office-tools:padding"/>' });
|
|
173
|
+
}
|
|
174
|
+
const typesIndex = expanded.findIndex((part) => part.name === "[Content_Types].xml");
|
|
175
|
+
if (paddingNames.length > 0 && typesIndex >= 0 && !/<Default\b[^>]*\bExtension=["']xml["']/i.test(expanded[typesIndex].content)) {
|
|
176
|
+
const original = expanded[typesIndex];
|
|
177
|
+
const overrides = paddingNames.map((name2) => `<Override PartName="/${name2}" ContentType="application/xml"/>`).join("");
|
|
178
|
+
if (!/<\/(?:\w+:)?Types\s*>/.test(original.content)) throw new Error("cannot add padding parts to malformed content types");
|
|
179
|
+
expanded[typesIndex] = { ...original, content: original.content.replace(/<\/(?:\w+:)?Types\s*>/, `${overrides}$&`) };
|
|
180
|
+
}
|
|
181
|
+
const plan = planEntries(expanded);
|
|
132
182
|
const planned = plan.entries;
|
|
133
183
|
const local = [];
|
|
134
184
|
const central = [];
|
|
@@ -187,6 +237,9 @@ function buildAsciiZip(parts) {
|
|
|
187
237
|
if (!safeField(directorySize) || !safeField(directoryOffset)) {
|
|
188
238
|
throw new Error("internal error: the planned central directory fields are not ASCII-safe");
|
|
189
239
|
}
|
|
240
|
+
if (directoryOffset + directorySize + EOCD_BYTES > MAX_OFFICE_FILE_BYTES) {
|
|
241
|
+
throw new Error("generated Office package exceeds the 50 MiB write budget");
|
|
242
|
+
}
|
|
190
243
|
const eocd = [
|
|
191
244
|
"PK",
|
|
192
245
|
u16(0),
|
|
@@ -1391,6 +1444,7 @@ async function buildPptxText(args, exec, ctx, deck) {
|
|
|
1391
1444
|
}
|
|
1392
1445
|
const slideCount = builds.length;
|
|
1393
1446
|
if (slideCount === 0) throw new Error("ppt_create needs a title or at least one slide");
|
|
1447
|
+
if (slideCount > 200) throw new Error("too many slides including the title slide (maximum 200)");
|
|
1394
1448
|
for (const [slideIndex, build] of builds.entries()) {
|
|
1395
1449
|
const images = build.spec.images ?? [];
|
|
1396
1450
|
build.images = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-office-tools",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
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",
|
|
@@ -18,14 +18,15 @@
|
|
|
18
18
|
"patch": "./cordis.patch.yml"
|
|
19
19
|
},
|
|
20
20
|
"compatibility": {
|
|
21
|
-
"dsh": "
|
|
21
|
+
"dsh": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0 || ^0.1.5-alpha.0 || >=0.2.0-rc.1 <0.3.0-0",
|
|
22
22
|
"dshReleases": {
|
|
23
23
|
"0.1.1-rc.2": "compatible",
|
|
24
24
|
"0.1.2-alpha.4": "compatible",
|
|
25
25
|
"0.1.2-alpha.5": "compatible",
|
|
26
26
|
"0.1.2-rc.1": "compatible",
|
|
27
27
|
"0.1.5-rc.1": "compatible",
|
|
28
|
-
"0.1.5-rc.2": "compatible"
|
|
28
|
+
"0.1.5-rc.2": "compatible",
|
|
29
|
+
"0.2.0-rc.1": "compatible"
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
},
|
|
@@ -48,11 +49,11 @@
|
|
|
48
49
|
},
|
|
49
50
|
"peerDependencies": {
|
|
50
51
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
51
|
-
"@deepseek-ai/dsh-agent": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0 || ^0.1.5-alpha.0",
|
|
52
|
-
"@deepseek-ai/dsh-fs": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0 || ^0.1.5-alpha.0",
|
|
53
|
-
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0 || ^0.1.5-alpha.0",
|
|
54
|
-
"@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0 || ^0.1.5-alpha.0",
|
|
55
|
-
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0 || ^0.1.5-alpha.0"
|
|
52
|
+
"@deepseek-ai/dsh-agent": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0 || ^0.1.5-alpha.0 || >=0.2.0-rc.1 <0.3.0-0",
|
|
53
|
+
"@deepseek-ai/dsh-fs": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0 || ^0.1.5-alpha.0 || >=0.2.0-rc.1 <0.3.0-0",
|
|
54
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0 || ^0.1.5-alpha.0 || >=0.2.0-rc.1 <0.3.0-0",
|
|
55
|
+
"@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0 || ^0.1.5-alpha.0 || >=0.2.0-rc.1 <0.3.0-0",
|
|
56
|
+
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.1.1-rc.0 || ^0.1.2-alpha.0 || ^0.1.5-alpha.0 || >=0.2.0-rc.1 <0.3.0-0"
|
|
56
57
|
},
|
|
57
58
|
"peerDependenciesMeta": {
|
|
58
59
|
"@deepseek-ai/cordis": {
|
|
@@ -87,8 +88,10 @@
|
|
|
87
88
|
"@deepseek-ai/dsh-tools": "0.1.2-rc.1",
|
|
88
89
|
"@deepseek-ai/schemastery": "^3.18.2",
|
|
89
90
|
"@types/node": "^24.0.0",
|
|
91
|
+
"@types/semver": "7.7.1",
|
|
90
92
|
"esbuild": "^0.25.0",
|
|
91
93
|
"jszip": "^3.10.1",
|
|
94
|
+
"semver": "7.8.5",
|
|
92
95
|
"typescript": "^5.6.0",
|
|
93
96
|
"vitest": "^4.1.8",
|
|
94
97
|
"xlsx": "^0.18.5"
|
|
@@ -150,7 +153,8 @@
|
|
|
150
153
|
"0.1.2-alpha.5",
|
|
151
154
|
"0.1.2-rc.1",
|
|
152
155
|
"0.1.5-rc.1",
|
|
153
|
-
"0.1.5-rc.2"
|
|
156
|
+
"0.1.5-rc.2",
|
|
157
|
+
"0.2.0-rc.1"
|
|
154
158
|
]
|
|
155
159
|
},
|
|
156
160
|
"capability": {
|