@qvac/harness 0.7.3 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +45 -1
  2. package/NOTICE.md +16 -0
  3. package/dist-lib/lib/capabilities/completion.d.ts +1 -0
  4. package/dist-lib/lib/capabilities/completion.js +28 -8
  5. package/dist-lib/lib/capabilities/tool-turns.d.ts +3 -0
  6. package/dist-lib/lib/capabilities/tool-turns.js +7 -0
  7. package/dist-lib/lib/context-window.js +2 -2
  8. package/dist-lib/lib/helpers/run-outcome.d.ts +4 -1
  9. package/dist-lib/lib/helpers/run-outcome.js +14 -8
  10. package/dist-lib/lib/helpers/tool-call-markup.js +4 -4
  11. package/dist-lib/lib/hooks/built-in/arg-repair.js +7 -0
  12. package/dist-lib/lib/office/engine/codes.d.ts +62 -0
  13. package/dist-lib/lib/office/engine/codes.js +61 -0
  14. package/dist-lib/lib/office/engine/docx/document.d.ts +216 -0
  15. package/dist-lib/lib/office/engine/docx/document.js +1152 -0
  16. package/dist-lib/lib/office/engine/docx/index.d.ts +7 -0
  17. package/dist-lib/lib/office/engine/docx/index.js +8 -0
  18. package/dist-lib/lib/office/engine/docx/styles.d.ts +31 -0
  19. package/dist-lib/lib/office/engine/docx/styles.js +173 -0
  20. package/dist-lib/lib/office/engine/docx/template.d.ts +18 -0
  21. package/dist-lib/lib/office/engine/docx/template.js +133 -0
  22. package/dist-lib/lib/office/engine/docx/text.d.ts +55 -0
  23. package/dist-lib/lib/office/engine/docx/text.js +538 -0
  24. package/dist-lib/lib/office/engine/drawingml/chart.d.ts +63 -0
  25. package/dist-lib/lib/office/engine/drawingml/chart.js +539 -0
  26. package/dist-lib/lib/office/engine/drawingml/text.d.ts +35 -0
  27. package/dist-lib/lib/office/engine/drawingml/text.js +352 -0
  28. package/dist-lib/lib/office/engine/errors.d.ts +6 -0
  29. package/dist-lib/lib/office/engine/errors.js +12 -0
  30. package/dist-lib/lib/office/engine/image.d.ts +8 -0
  31. package/dist-lib/lib/office/engine/image.js +48 -0
  32. package/dist-lib/lib/office/engine/index.d.ts +12 -0
  33. package/dist-lib/lib/office/engine/index.js +7 -0
  34. package/dist-lib/lib/office/engine/opc/content-types.d.ts +17 -0
  35. package/dist-lib/lib/office/engine/opc/content-types.js +74 -0
  36. package/dist-lib/lib/office/engine/opc/media.d.ts +13 -0
  37. package/dist-lib/lib/office/engine/opc/media.js +11 -0
  38. package/dist-lib/lib/office/engine/opc/package.d.ts +51 -0
  39. package/dist-lib/lib/office/engine/opc/package.js +324 -0
  40. package/dist-lib/lib/office/engine/opc/paths.d.ts +7 -0
  41. package/dist-lib/lib/office/engine/opc/paths.js +46 -0
  42. package/dist-lib/lib/office/engine/opc/properties.d.ts +7 -0
  43. package/dist-lib/lib/office/engine/opc/properties.js +75 -0
  44. package/dist-lib/lib/office/engine/opc/relationships.d.ts +23 -0
  45. package/dist-lib/lib/office/engine/opc/relationships.js +65 -0
  46. package/dist-lib/lib/office/engine/opc/zip.d.ts +15 -0
  47. package/dist-lib/lib/office/engine/opc/zip.js +264 -0
  48. package/dist-lib/lib/office/engine/pptx/fit.d.ts +12 -0
  49. package/dist-lib/lib/office/engine/pptx/fit.js +37 -0
  50. package/dist-lib/lib/office/engine/pptx/index.d.ts +8 -0
  51. package/dist-lib/lib/office/engine/pptx/index.js +8 -0
  52. package/dist-lib/lib/office/engine/pptx/inherit.d.ts +29 -0
  53. package/dist-lib/lib/office/engine/pptx/inherit.js +155 -0
  54. package/dist-lib/lib/office/engine/pptx/notes.d.ts +5 -0
  55. package/dist-lib/lib/office/engine/pptx/notes.js +85 -0
  56. package/dist-lib/lib/office/engine/pptx/presentation.d.ts +54 -0
  57. package/dist-lib/lib/office/engine/pptx/presentation.js +749 -0
  58. package/dist-lib/lib/office/engine/pptx/read.d.ts +9 -0
  59. package/dist-lib/lib/office/engine/pptx/read.js +66 -0
  60. package/dist-lib/lib/office/engine/pptx/shapes.d.ts +52 -0
  61. package/dist-lib/lib/office/engine/pptx/shapes.js +276 -0
  62. package/dist-lib/lib/office/engine/pptx/slides.d.ts +27 -0
  63. package/dist-lib/lib/office/engine/pptx/slides.js +123 -0
  64. package/dist-lib/lib/office/engine/pptx/template.d.ts +20 -0
  65. package/dist-lib/lib/office/engine/pptx/template.js +391 -0
  66. package/dist-lib/lib/office/engine/pptx/types.d.ts +120 -0
  67. package/dist-lib/lib/office/engine/pptx/types.js +1 -0
  68. package/dist-lib/lib/office/engine/units.d.ts +3 -0
  69. package/dist-lib/lib/office/engine/units.js +12 -0
  70. package/dist-lib/lib/office/engine/utf8.d.ts +2 -0
  71. package/dist-lib/lib/office/engine/utf8.js +103 -0
  72. package/dist-lib/lib/office/engine/xlsx/formula.d.ts +49 -0
  73. package/dist-lib/lib/office/engine/xlsx/formula.js +381 -0
  74. package/dist-lib/lib/office/engine/xlsx/index.d.ts +8 -0
  75. package/dist-lib/lib/office/engine/xlsx/index.js +8 -0
  76. package/dist-lib/lib/office/engine/xlsx/refs.d.ts +22 -0
  77. package/dist-lib/lib/office/engine/xlsx/refs.js +82 -0
  78. package/dist-lib/lib/office/engine/xlsx/reshape.d.ts +21 -0
  79. package/dist-lib/lib/office/engine/xlsx/reshape.js +424 -0
  80. package/dist-lib/lib/office/engine/xlsx/sheet.d.ts +108 -0
  81. package/dist-lib/lib/office/engine/xlsx/sheet.js +662 -0
  82. package/dist-lib/lib/office/engine/xlsx/strings.d.ts +15 -0
  83. package/dist-lib/lib/office/engine/xlsx/strings.js +93 -0
  84. package/dist-lib/lib/office/engine/xlsx/styles.d.ts +38 -0
  85. package/dist-lib/lib/office/engine/xlsx/styles.js +369 -0
  86. package/dist-lib/lib/office/engine/xlsx/template.d.ts +7 -0
  87. package/dist-lib/lib/office/engine/xlsx/template.js +54 -0
  88. package/dist-lib/lib/office/engine/xlsx/workbook.d.ts +161 -0
  89. package/dist-lib/lib/office/engine/xlsx/workbook.js +850 -0
  90. package/dist-lib/lib/office/engine/xlsx/write.d.ts +2 -0
  91. package/dist-lib/lib/office/engine/xlsx/write.js +42 -0
  92. package/dist-lib/lib/office/engine/xml.d.ts +45 -0
  93. package/dist-lib/lib/office/engine/xml.js +258 -0
  94. package/dist-lib/lib/tools/exec/scope.d.ts +2 -0
  95. package/dist-lib/lib/tools/exec/scope.js +33 -2
  96. package/dist-lib/lib/tools/pdf/build.d.ts +43 -0
  97. package/dist-lib/lib/tools/pdf/build.js +150 -0
  98. package/dist-lib/lib/tools/pdf/images.d.ts +7 -0
  99. package/dist-lib/lib/tools/pdf/images.js +82 -0
  100. package/dist-lib/lib/tools/pdf/request.d.ts +63 -0
  101. package/dist-lib/lib/tools/pdf/request.js +335 -0
  102. package/dist-lib/lib/tools/pdf.d.ts +6 -0
  103. package/dist-lib/lib/tools/pdf.js +302 -0
  104. package/dist-lib/lib/tools/skill.js +14 -0
  105. package/package.json +25 -1
  106. package/worker/harness-sidecar.mjs +3 -1
@@ -0,0 +1,2 @@
1
+ export type EmbeddedValue = string | number | null | undefined;
2
+ export declare function writeWorkbook(rows: EmbeddedValue[][], sheet?: string): Uint8Array;
@@ -0,0 +1,42 @@
1
+ import { Package } from '../opc/package.js';
2
+ import * as xml from '../xml.js';
3
+ import { cell } from './refs.js';
4
+ const SML = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
5
+ const REL_BASE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/';
6
+ const TYPES = {
7
+ workbook: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml',
8
+ worksheet: 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml',
9
+ styles: 'application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml'
10
+ };
11
+ const STYLES = `<styleSheet xmlns="${SML}"><fonts count="1"><font><sz val="11"/><name val="Calibri"/></font></fonts><fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills><borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders><cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs><cellXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/></cellXfs><cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles></styleSheet>`;
12
+ function escape(value) {
13
+ return value
14
+ .replace(/&/g, '&amp;')
15
+ .replace(/</g, '&lt;')
16
+ .replace(/>/g, '&gt;')
17
+ .replace(/"/g, '&quot;');
18
+ }
19
+ function cellXml(value, ref) {
20
+ if (value === null || value === undefined || value === '')
21
+ return '';
22
+ if (typeof value === 'number')
23
+ return `<c r="${ref}"><v>${value}</v></c>`;
24
+ return `<c r="${ref}" t="inlineStr"><is><t xml:space="preserve">${escape(value)}</t></is></c>`;
25
+ }
26
+ function sheetXml(rows) {
27
+ const data = rows
28
+ .map((row, r) => `<row r="${r + 1}">${row.map((value, c) => cellXml(value, cell(r, c))).join('')}</row>`)
29
+ .join('');
30
+ return `<worksheet xmlns="${SML}"><sheetData>${data}</sheetData></worksheet>`;
31
+ }
32
+ export function writeWorkbook(rows, sheet = 'Sheet1') {
33
+ const pkg = Package.create();
34
+ const workbook = `<workbook xmlns="${SML}" xmlns:r="${xml.ns.r}"><sheets><sheet name="${escape(sheet)}" sheetId="1" r:id="rId1"/></sheets></workbook>`;
35
+ pkg.add('/xl/workbook.xml', xml.parse(workbook), TYPES.workbook);
36
+ pkg.add('/xl/worksheets/sheet1.xml', xml.parse(sheetXml(rows)), TYPES.worksheet);
37
+ pkg.add('/xl/styles.xml', xml.parse(STYLES), TYPES.styles);
38
+ pkg.rels('/').add(REL_BASE + 'officeDocument', '/xl/workbook.xml');
39
+ pkg.rels('/xl/workbook.xml').add(REL_BASE + 'worksheet', '/xl/worksheets/sheet1.xml');
40
+ pkg.rels('/xl/workbook.xml').add(REL_BASE + 'styles', '/xl/styles.xml');
41
+ return pkg.save();
42
+ }
@@ -0,0 +1,45 @@
1
+ import { Element, type Document, type Node } from '@xmldom/xmldom';
2
+ export type { Document, Element, Node };
3
+ export declare const ns: {
4
+ readonly a: 'http://schemas.openxmlformats.org/drawingml/2006/main';
5
+ readonly p: 'http://schemas.openxmlformats.org/presentationml/2006/main';
6
+ readonly r: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships';
7
+ readonly w: 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
8
+ readonly p14: 'http://schemas.microsoft.com/office/powerpoint/2010/main';
9
+ readonly wp: 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing';
10
+ readonly pic: 'http://schemas.openxmlformats.org/drawingml/2006/picture';
11
+ readonly c: 'http://schemas.openxmlformats.org/drawingml/2006/chart';
12
+ readonly xdr: 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing';
13
+ readonly x: 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
14
+ readonly rels: 'http://schemas.openxmlformats.org/package/2006/relationships';
15
+ readonly ct: 'http://schemas.openxmlformats.org/package/2006/content-types';
16
+ };
17
+ export type Attributes = Record<string, string | number | undefined | null>;
18
+ export type Build = (local: string, attributes?: Attributes) => Element;
19
+ export declare function decode(bytes: Uint8Array): string;
20
+ export declare function parse(input: Uint8Array | string, name?: string): Document;
21
+ export declare function toString(doc: Document): string;
22
+ export declare function serialize(doc: Document): Uint8Array;
23
+ export declare function root(doc: Document): Element;
24
+ export declare function owner(node: Node): Document;
25
+ export declare function isElement(node: Node | null, namespace?: string, local?: string): node is Element;
26
+ export declare function children(el: Node, namespace?: string, local?: string): Element[];
27
+ export declare function child(el: Node, namespace?: string, local?: string): Element | null;
28
+ export declare function descendants(el: Element | Document, namespace: string, local: string): Element[];
29
+ export declare function text(node: Node | null): string;
30
+ export declare function clean(value: string): string;
31
+ export declare function setText(el: Element, value: string): void;
32
+ export declare function attr(el: Element, name: string): string;
33
+ export declare function setAttributes(el: Element, attributes?: Attributes): Element;
34
+ export declare function named(doc: Document, qualifiedName: string, namespace: string): Element;
35
+ export declare function qualify(doc: Document, namespace: string, local: string): string;
36
+ export declare function builder(doc: Document, namespace: string): Build;
37
+ export interface QualifiedName {
38
+ namespace: string;
39
+ local: string;
40
+ }
41
+ export declare function setNsAttribute(el: Element, name: QualifiedName, value: string): void;
42
+ export declare function insertOrdered(parent: Element, el: Element, order: readonly string[]): Element;
43
+ export declare function remove(node: Node): void;
44
+ export declare function append(parent: Node, node: Element): Element;
45
+ export declare function clone(el: Element): Element;
@@ -0,0 +1,258 @@
1
+ import { DOMParser, XMLSerializer, Element } from '@xmldom/xmldom';
2
+ import { DocsError } from './errors.js';
3
+ import { errorMessage } from '../../guards.js';
4
+ import * as utf8 from './utf8.js';
5
+ export const ns = {
6
+ a: 'http://schemas.openxmlformats.org/drawingml/2006/main',
7
+ p: 'http://schemas.openxmlformats.org/presentationml/2006/main',
8
+ r: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
9
+ w: 'http://schemas.openxmlformats.org/wordprocessingml/2006/main',
10
+ p14: 'http://schemas.microsoft.com/office/powerpoint/2010/main',
11
+ wp: 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing',
12
+ pic: 'http://schemas.openxmlformats.org/drawingml/2006/picture',
13
+ c: 'http://schemas.openxmlformats.org/drawingml/2006/chart',
14
+ xdr: 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing',
15
+ x: 'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
16
+ rels: 'http://schemas.openxmlformats.org/package/2006/relationships',
17
+ ct: 'http://schemas.openxmlformats.org/package/2006/content-types'
18
+ };
19
+ const MAX_DEPTH = 256;
20
+ const NOT_XML = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
21
+ const DECLARATION = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n';
22
+ const UNPREFIXED = [ns.rels, ns.ct, ns.x];
23
+ const PREFIXES = new Map(Object.entries(ns)
24
+ .filter(([, uri]) => !UNPREFIXED.includes(uri))
25
+ .map(([prefix, uri]) => [uri, prefix]));
26
+ const SKIPPED = [
27
+ ['<!--', '-->'],
28
+ ['<![CDATA[', ']]>'],
29
+ ['<?', '?>'],
30
+ ['<!', '>']
31
+ ];
32
+ function skipped(text, at) {
33
+ for (const [open, close] of SKIPPED) {
34
+ if (text.startsWith(open, at)) {
35
+ const end = text.indexOf(close, at + open.length);
36
+ return end === -1 ? -1 : end + close.length;
37
+ }
38
+ }
39
+ return null;
40
+ }
41
+ function tagEnd(text, at) {
42
+ let quote = '';
43
+ for (let i = at; i < text.length; i++) {
44
+ const c = text[i];
45
+ if (quote) {
46
+ quote = c === quote ? '' : quote;
47
+ }
48
+ else if (c === '"' || c === "'") {
49
+ quote = c;
50
+ }
51
+ else if (c === '>') {
52
+ return i;
53
+ }
54
+ }
55
+ return -1;
56
+ }
57
+ function checkDepth(text, name) {
58
+ let depth = 0;
59
+ for (let i = text.indexOf('<'); i !== -1; i = text.indexOf('<', i)) {
60
+ const after = skipped(text, i);
61
+ if (after === -1) {
62
+ return;
63
+ }
64
+ if (after !== null) {
65
+ i = after;
66
+ continue;
67
+ }
68
+ const end = tagEnd(text, i + 1);
69
+ if (end === -1) {
70
+ return;
71
+ }
72
+ if (text[i + 1] === '/') {
73
+ depth--;
74
+ }
75
+ else if (text[end - 1] !== '/') {
76
+ depth++;
77
+ }
78
+ if (depth > MAX_DEPTH) {
79
+ throw new DocsError('UNSAFE_XML', `${name} nests elements more than ${MAX_DEPTH} levels deep`);
80
+ }
81
+ i = end + 1;
82
+ }
83
+ }
84
+ function decodeUtf16(bytes, endian) {
85
+ const littleEndian = endian === 'le';
86
+ let out = '';
87
+ for (let i = 2; i + 1 < bytes.length; i += 2) {
88
+ const code = littleEndian ? bytes[i] | (bytes[i + 1] << 8) : (bytes[i] << 8) | bytes[i + 1];
89
+ out += String.fromCharCode(code);
90
+ }
91
+ return out;
92
+ }
93
+ export function decode(bytes) {
94
+ if (bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) {
95
+ return utf8.decode(bytes.subarray(3));
96
+ }
97
+ if (bytes[0] === 0xff && bytes[1] === 0xfe) {
98
+ return decodeUtf16(bytes, 'le');
99
+ }
100
+ if (bytes[0] === 0xfe && bytes[1] === 0xff) {
101
+ return decodeUtf16(bytes, 'be');
102
+ }
103
+ return utf8.decode(bytes);
104
+ }
105
+ function causeOf(err) {
106
+ return err instanceof Error ? err.cause : undefined;
107
+ }
108
+ export function parse(input, name = 'part') {
109
+ const text = (typeof input === 'string' ? input : decode(input)).replace(/^/, '');
110
+ if (/<!DOCTYPE/i.test(text)) {
111
+ throw new DocsError('UNSAFE_XML', `${name} declares a DOCTYPE, which Office files never do`);
112
+ }
113
+ checkDepth(text, name);
114
+ const invalid = (message) => new DocsError('INVALID_XML', `${name} is not well-formed XML: ${message}`);
115
+ const parser = new DOMParser({
116
+ onError(level, message) {
117
+ if (level !== 'warning') {
118
+ throw invalid(message);
119
+ }
120
+ }
121
+ });
122
+ let doc;
123
+ try {
124
+ doc = parser.parseFromString(text, 'application/xml');
125
+ }
126
+ catch (err) {
127
+ if (err instanceof DocsError) {
128
+ throw err;
129
+ }
130
+ const cause = causeOf(err);
131
+ if (cause instanceof DocsError) {
132
+ throw cause;
133
+ }
134
+ throw invalid(errorMessage(err));
135
+ }
136
+ if (!doc.documentElement) {
137
+ throw new DocsError('INVALID_XML', `${name} has no root element`);
138
+ }
139
+ return doc;
140
+ }
141
+ export function toString(doc) {
142
+ return new XMLSerializer().serializeToString(doc);
143
+ }
144
+ export function serialize(doc) {
145
+ const text = toString(doc);
146
+ if (!text.startsWith('<?xml')) {
147
+ return utf8.encode(DECLARATION + text);
148
+ }
149
+ return utf8.encode(text.replace(/^(<\?xml[^?]*?encoding=)(["'])[^"']*\2/i, '$1$2UTF-8$2'));
150
+ }
151
+ export function root(doc) {
152
+ const el = doc.documentElement;
153
+ if (!el) {
154
+ throw new DocsError('INVALID_XML', 'the part has no root element');
155
+ }
156
+ return el;
157
+ }
158
+ export function owner(node) {
159
+ const doc = node.ownerDocument;
160
+ if (!doc) {
161
+ throw new DocsError('INVALID_XML', 'the element belongs to no document');
162
+ }
163
+ return doc;
164
+ }
165
+ export function isElement(node, namespace, local) {
166
+ return (node instanceof Element &&
167
+ (namespace === undefined || node.namespaceURI === namespace) &&
168
+ (local === undefined || node.localName === local));
169
+ }
170
+ export function children(el, namespace, local) {
171
+ const out = [];
172
+ for (let n = el.firstChild; n; n = n.nextSibling) {
173
+ if (isElement(n, namespace, local)) {
174
+ out.push(n);
175
+ }
176
+ }
177
+ return out;
178
+ }
179
+ export function child(el, namespace, local) {
180
+ for (let n = el.firstChild; n; n = n.nextSibling) {
181
+ if (isElement(n, namespace, local))
182
+ return n;
183
+ }
184
+ return null;
185
+ }
186
+ export function descendants(el, namespace, local) {
187
+ return Array.from(el.getElementsByTagNameNS(namespace, local));
188
+ }
189
+ export function text(node) {
190
+ return node?.textContent ?? '';
191
+ }
192
+ export function clean(value) {
193
+ return value.replace(NOT_XML, '');
194
+ }
195
+ export function setText(el, value) {
196
+ while (el.firstChild) {
197
+ el.removeChild(el.firstChild);
198
+ }
199
+ el.appendChild(owner(el).createTextNode(clean(value)));
200
+ }
201
+ export function attr(el, name) {
202
+ return el.getAttribute(name) ?? '';
203
+ }
204
+ export function setAttributes(el, attributes = {}) {
205
+ for (const [key, value] of Object.entries(attributes)) {
206
+ if (value !== undefined && value !== null) {
207
+ el.setAttribute(key, String(value));
208
+ }
209
+ }
210
+ return el;
211
+ }
212
+ export function named(doc, qualifiedName, namespace) {
213
+ return doc.createElementNS(namespace, qualifiedName);
214
+ }
215
+ export function qualify(doc, namespace, local) {
216
+ const top = root(doc);
217
+ if (top.namespaceURI === namespace && !top.prefix) {
218
+ return local;
219
+ }
220
+ const prefix = top.lookupPrefix(namespace) ?? PREFIXES.get(namespace);
221
+ return prefix ? `${prefix}:${local}` : local;
222
+ }
223
+ export function builder(doc, namespace) {
224
+ return (local, attributes) => setAttributes(named(doc, qualify(doc, namespace, local), namespace), attributes);
225
+ }
226
+ export function setNsAttribute(el, name, value) {
227
+ const doc = owner(el);
228
+ const prefix = root(doc).lookupPrefix(name.namespace) ?? PREFIXES.get(name.namespace) ?? name.local;
229
+ el.setAttributeNS(name.namespace, `${prefix}:${name.local}`, value);
230
+ }
231
+ export function insertOrdered(parent, el, order) {
232
+ const rank = order.indexOf(el.localName ?? '');
233
+ for (let n = parent.firstChild; n; n = n.nextSibling) {
234
+ if (!(n instanceof Element))
235
+ continue;
236
+ const other = order.indexOf(n.localName ?? '');
237
+ if (other > rank || other === -1) {
238
+ parent.insertBefore(el, n);
239
+ return el;
240
+ }
241
+ }
242
+ parent.appendChild(el);
243
+ return el;
244
+ }
245
+ export function remove(node) {
246
+ node.parentNode?.removeChild(node);
247
+ }
248
+ export function append(parent, node) {
249
+ parent.appendChild(node);
250
+ return node;
251
+ }
252
+ export function clone(el) {
253
+ const copy = el.cloneNode(true);
254
+ if (!(copy instanceof Element)) {
255
+ throw new DocsError('INVALID_XML', 'cloning an element produced no element');
256
+ }
257
+ return copy;
258
+ }
@@ -1,3 +1,5 @@
1
1
  export declare function bareBin(token: string): string | null;
2
2
  export declare function firstCommandToken(command: string): string;
3
+ export declare function escapeLiteralDollars(command: string): string;
4
+ export declare const SHELL_EXPANSION_ERROR: string;
3
5
  export declare function sanitizeCommand(command: string): string | null;
@@ -50,12 +50,43 @@ function skipDoubleQuoted(chars, open) {
50
50
  return 'command must not use backtick substitution';
51
51
  }
52
52
  if (chars[i] === '$') {
53
- return 'command must not use shell expansion';
53
+ return SHELL_EXPANSION_ERROR;
54
54
  }
55
55
  i++;
56
56
  }
57
57
  return i + 1;
58
58
  }
59
+ const EXPANDS = /[A-Za-z_{(?#@*!$-]/;
60
+ export function escapeLiteralDollars(command) {
61
+ const chars = [...command];
62
+ let out = '';
63
+ let quoted = false;
64
+ for (let i = 0; i < chars.length; i++) {
65
+ const c = chars[i] ?? '';
66
+ if (c === "'" && !quoted) {
67
+ const end = chars.indexOf("'", i + 1);
68
+ const stop = end === -1 ? chars.length : end + 1;
69
+ out += chars.slice(i, stop).join('');
70
+ i = stop - 1;
71
+ continue;
72
+ }
73
+ if (c === '\\') {
74
+ out += c + (chars[i + 1] ?? '');
75
+ i++;
76
+ continue;
77
+ }
78
+ if (c === '"') {
79
+ quoted = !quoted;
80
+ }
81
+ if (c === '$' && !EXPANDS.test(chars[i + 1] ?? '')) {
82
+ out += '\\';
83
+ }
84
+ out += c;
85
+ }
86
+ return out;
87
+ }
88
+ export const SHELL_EXPANSION_ERROR = 'command must not use shell expansion: $ outside single quotes is expanded by the shell. ' +
89
+ "Put text containing $ (prices, amounts) in single quotes, e.g. content='Total: $1,348'";
59
90
  export function sanitizeCommand(command) {
60
91
  const chars = [...command];
61
92
  let i = 0;
@@ -98,7 +129,7 @@ export function sanitizeCommand(command) {
98
129
  return 'command must not use backtick substitution';
99
130
  }
100
131
  if (c === '$') {
101
- return 'command must not use shell expansion';
132
+ return SHELL_EXPANSION_ERROR;
102
133
  }
103
134
  if (c === ';') {
104
135
  return 'command must not chain with ;';
@@ -0,0 +1,43 @@
1
+ import type { PdfRequest } from './request.ts';
2
+ export type Pdfium = typeof import('bare-pdfium');
3
+ export type Doc = ReturnType<Pdfium['open']>;
4
+ type Field = ReturnType<Doc['fields']>[number];
5
+ export interface Source {
6
+ readonly attachmentId: string;
7
+ readonly fileName: string;
8
+ readonly bytes: Uint8Array;
9
+ }
10
+ export interface Opened {
11
+ readonly source: Source;
12
+ readonly doc: Doc;
13
+ }
14
+ export interface Built {
15
+ readonly bytes: Uint8Array;
16
+ readonly pageCount: number;
17
+ }
18
+ export interface ReadField {
19
+ readonly name: string;
20
+ readonly type: string;
21
+ readonly value: Field['value'];
22
+ readonly options: readonly string[] | undefined;
23
+ readonly readOnly: true | undefined;
24
+ }
25
+ export interface ReadDocument {
26
+ readonly attachmentId: string;
27
+ readonly fileName: string;
28
+ readonly pageCount: number;
29
+ readonly pages: readonly {
30
+ page: number;
31
+ text: string;
32
+ }[];
33
+ readonly fields: readonly ReadField[];
34
+ readonly note: string | undefined;
35
+ readonly truncated: true | undefined;
36
+ }
37
+ export interface ReadDocuments {
38
+ readonly documents: readonly ReadDocument[];
39
+ }
40
+ export declare function pagesLabel(count: number): string;
41
+ export declare function readDocs(opened: readonly Opened[]): ReadDocument | ReadDocuments;
42
+ export declare function build(pdfium: Pdfium, request: PdfRequest, opened: readonly Opened[]): Built | string;
43
+ export {};
@@ -0,0 +1,150 @@
1
+ import b4a from 'b4a';
2
+ const READ_TEXT_LIMIT = 30_000;
3
+ function plural(count, noun) {
4
+ return `${count} ${noun}${count === 1 ? '' : 's'}`;
5
+ }
6
+ export function pagesLabel(count) {
7
+ return plural(count, 'page');
8
+ }
9
+ function zeroBased(opened, pages) {
10
+ if (!pages) {
11
+ return Array.from({ length: opened.doc.pageCount() }, (_, page) => page);
12
+ }
13
+ const count = opened.doc.pageCount();
14
+ const outside = pages.filter((page) => page < 1 || page > count);
15
+ if (outside.length) {
16
+ return `${opened.source.fileName} has ${pagesLabel(count)}, numbered 1 to ${count}; there is no page ${outside.join(', ')}`;
17
+ }
18
+ return pages.map((page) => page - 1);
19
+ }
20
+ function describeField(field) {
21
+ return {
22
+ name: field.name,
23
+ type: field.type,
24
+ value: field.value,
25
+ options: field.options,
26
+ readOnly: field.readOnly || undefined
27
+ };
28
+ }
29
+ function scannedNote(scanned) {
30
+ if (!scanned.length)
31
+ return undefined;
32
+ const one = scanned.length === 1;
33
+ return `${one ? 'page' : 'pages'} ${scanned.join(', ')} ${one ? 'has' : 'have'} no text layer: ${one ? 'it is a scanned image' : 'they are scanned images'}, and there is no OCR here`;
34
+ }
35
+ function readOne({ source, doc }, budget) {
36
+ const pages = [];
37
+ const scanned = [];
38
+ let truncated = false;
39
+ for (const { page, text } of doc.textPages()) {
40
+ const clean = text.replace(/\r\n?/g, '\n').trim();
41
+ if (!clean) {
42
+ scanned.push(page + 1);
43
+ }
44
+ const kept = clean.slice(0, Math.max(0, budget.chars));
45
+ budget.chars -= kept.length;
46
+ truncated ||= kept.length < clean.length;
47
+ if (budget.chars > 0 || kept) {
48
+ pages.push({ page: page + 1, text: kept });
49
+ }
50
+ }
51
+ return {
52
+ attachmentId: source.attachmentId,
53
+ fileName: source.fileName,
54
+ pageCount: doc.pageCount(),
55
+ pages,
56
+ fields: doc.fields().map(describeField),
57
+ note: scannedNote(scanned),
58
+ truncated: truncated || undefined
59
+ };
60
+ }
61
+ export function readDocs(opened) {
62
+ const budget = { chars: READ_TEXT_LIMIT };
63
+ const documents = opened.map((one) => readOne(one, budget));
64
+ const [only] = documents;
65
+ return documents.length === 1 && only ? only : { documents };
66
+ }
67
+ function fromDoc(doc, bytes) {
68
+ return { bytes, pageCount: doc.pageCount() };
69
+ }
70
+ function joinPages(pdfium, parts, pages) {
71
+ const out = pdfium.create();
72
+ try {
73
+ for (const part of parts) {
74
+ out.importPages(part.doc, { pages });
75
+ }
76
+ return fromDoc(out, out.save());
77
+ }
78
+ finally {
79
+ out.close();
80
+ }
81
+ }
82
+ function textFile({ source, doc }) {
83
+ const text = [...doc.textPages()]
84
+ .map((page) => page.text.replace(/\r\n?/g, '\n').trim())
85
+ .join('\n\n');
86
+ if (!text.trim()) {
87
+ return `${source.fileName} has no text layer: its pages are scanned images, and there is no OCR here`;
88
+ }
89
+ return { bytes: b4a.from(text + '\n', 'utf8'), pageCount: doc.pageCount() };
90
+ }
91
+ function create(pdfium, request) {
92
+ const encrypt = request.password ? { password: request.password } : undefined;
93
+ const bytes = pdfium.compose(request.markdown, {
94
+ pageSize: request.pageSize,
95
+ landscape: request.landscape,
96
+ pageNumbers: request.pageNumbers,
97
+ encrypt
98
+ });
99
+ return { bytes, pageCount: pdfium.pageCount(bytes, { password: request.password }) };
100
+ }
101
+ function editOne(opened, request) {
102
+ const { doc } = opened;
103
+ switch (request.action) {
104
+ case 'rotate':
105
+ case 'watermark': {
106
+ const pages = zeroBased(opened, request.pages);
107
+ if (typeof pages === 'string') {
108
+ return pages;
109
+ }
110
+ if (request.action === 'rotate') {
111
+ doc.rotate(request.degrees, { pages });
112
+ }
113
+ else {
114
+ doc.watermark(request.text, { pages, opacity: request.opacity, position: request.position });
115
+ }
116
+ return fromDoc(doc, doc.save());
117
+ }
118
+ case 'fill':
119
+ doc.fillFields(request.values);
120
+ return fromDoc(doc, doc.save());
121
+ case 'encrypt': {
122
+ const password = request.password ?? '';
123
+ const { ownerPassword, permissions } = request;
124
+ return fromDoc(doc, doc.save({ encrypt: { password, ownerPassword, permissions } }));
125
+ }
126
+ case 'decrypt':
127
+ return fromDoc(doc, doc.save({ removeSecurity: true }));
128
+ case 'text':
129
+ return textFile(opened);
130
+ default:
131
+ return `${request.action} is not an edit`;
132
+ }
133
+ }
134
+ export function build(pdfium, request, opened) {
135
+ const [first] = opened;
136
+ if (request.action === 'create') {
137
+ return create(pdfium, request);
138
+ }
139
+ if (!first) {
140
+ return 'no PDF to work on';
141
+ }
142
+ if (request.action === 'merge') {
143
+ return joinPages(pdfium, opened);
144
+ }
145
+ if (request.action !== 'split') {
146
+ return editOne(first, request);
147
+ }
148
+ const pages = zeroBased(first, request.pages);
149
+ return typeof pages === 'string' ? pages : joinPages(pdfium, [first], pages);
150
+ }
@@ -0,0 +1,7 @@
1
+ import type { ToolExecuteContext } from '../../capabilities/completion.ts';
2
+ import { type CreateRequest } from './request.ts';
3
+ interface ToolError {
4
+ readonly error: string;
5
+ }
6
+ export declare function placeImages(request: CreateRequest, ctx: ToolExecuteContext): Promise<CreateRequest | ToolError>;
7
+ export {};