@qvac/harness 0.7.2 → 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 (111) hide show
  1. package/CHANGELOG.md +70 -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 +46 -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/dist-lib/lib/tools/weather-lookup/geocode.d.ts +26 -0
  106. package/dist-lib/lib/tools/weather-lookup/geocode.js +110 -0
  107. package/dist-lib/lib/tools/weather-lookup/place.js +5 -1
  108. package/dist-lib/lib/tools/weather-lookup.d.ts +2 -1
  109. package/dist-lib/lib/tools/weather-lookup.js +115 -84
  110. package/package.json +29 -1
  111. package/worker/harness-sidecar.mjs +3 -1
@@ -0,0 +1,82 @@
1
+ import b4a from 'b4a';
2
+ import { stageInputs } from '../exec/adapters/python/attachments.js';
3
+ import { WHERE_IDS_ARE } from './request.js';
4
+ const IMAGE_REF = /!\[([^\]\n]*)\]\(\s*<?([^)\s>]+)>?(?:\s+"[^"]*")?\s*\)/g;
5
+ const LINKED = /^(?:data:|https?:)/i;
6
+ const LOOKS_LIKE_ID = /^[0-9a-f]{6,}$/i;
7
+ const FIRST_HEADING = /^#{1,6}\s.*$/m;
8
+ function mimeOf(bytes) {
9
+ if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47) {
10
+ return 'image/png';
11
+ }
12
+ return bytes[0] === 0xff && bytes[1] === 0xd8 ? 'image/jpeg' : null;
13
+ }
14
+ function notAnImage(id, bytes) {
15
+ const name = id ?? 'the uploaded file';
16
+ if (b4a.toString(bytes.subarray(0, 5), 'latin1') === '%PDF-') {
17
+ return `${name} is a PDF, not an image; create makes a new PDF from markdown, so read that PDF and write its content into the markdown`;
18
+ }
19
+ return `${name} is not a PNG or JPEG image`;
20
+ }
21
+ function withIds(markdown, ids) {
22
+ const missing = ids.filter((id) => !markdown.includes(`](${id})`));
23
+ if (!missing.length) {
24
+ return markdown;
25
+ }
26
+ const images = missing.map((id) => `![](${id})`).join('\n\n');
27
+ const heading = FIRST_HEADING.exec(markdown);
28
+ if (!heading) {
29
+ return `${images}\n\n${markdown}`;
30
+ }
31
+ const end = heading.index + heading[0].length;
32
+ return `${markdown.slice(0, end)}\n\n${images}\n\n${markdown.slice(end).replace(/^\n+/, '')}`;
33
+ }
34
+ async function refOf(alt, target, ctx) {
35
+ const id = target.replace(/^attachment(?:Id)?:/i, '');
36
+ const found = await ctx.fetchAttachment?.({ attachmentId: id, meta: true, signal: ctx.signal });
37
+ if (found) {
38
+ return { alt, attachmentId: id };
39
+ }
40
+ if (LOOKS_LIKE_ID.test(id)) {
41
+ return {
42
+ error: `image ${id} not found in this chat; ${WHERE_IDS_ARE}, or name the user's uploaded image by its file name`
43
+ };
44
+ }
45
+ return { alt, attachmentId: undefined };
46
+ }
47
+ async function stage(refs, ctx) {
48
+ if (!ctx.fetchAttachment) {
49
+ return { error: 'this device cannot read chat images' };
50
+ }
51
+ return stageInputs(ctx.fetchAttachment, refs.map((ref, i) => ({ attachmentId: ref.attachmentId, path: `image-${i}.png` })), { turnImages: ctx.turnImages, signal: ctx.signal });
52
+ }
53
+ export async function placeImages(request, ctx) {
54
+ const markdown = withIds(request.markdown, request.ids);
55
+ const matches = [...markdown.matchAll(IMAGE_REF)].filter((match) => !LINKED.test(match[2] ?? ''));
56
+ if (!matches.length) {
57
+ return { ...request, markdown, ids: [] };
58
+ }
59
+ const refs = [];
60
+ for (const match of matches) {
61
+ const ref = await refOf(match[1] ?? '', match[2] ?? '', ctx);
62
+ if ('error' in ref) {
63
+ return ref;
64
+ }
65
+ refs.push(ref);
66
+ }
67
+ const staged = await stage(refs, ctx);
68
+ if ('error' in staged) {
69
+ return staged;
70
+ }
71
+ const uris = [];
72
+ for (const [i, file] of staged.files.entries()) {
73
+ const mime = mimeOf(file.bytes);
74
+ if (!mime) {
75
+ return { error: notAnImage(refs[i]?.attachmentId, file.bytes) };
76
+ }
77
+ uris.push(`data:${mime};base64,${b4a.toString(file.bytes, 'base64')}`);
78
+ }
79
+ let n = 0;
80
+ const placed = markdown.replace(IMAGE_REF, (whole, alt, target) => LINKED.test(target) ? whole : `\n\n![${alt}](${uris[n++] ?? target})\n\n`);
81
+ return { ...request, markdown: placed, ids: [] };
82
+ }
@@ -0,0 +1,63 @@
1
+ export declare const PDF_ACTIONS: readonly ['create', 'merge', 'split', 'rotate', 'watermark', 'fill', 'encrypt', 'decrypt', 'text'];
2
+ export declare const PAGE_SIZES: readonly ['A4', 'A5', 'Letter', 'Legal'];
3
+ export declare const DEGREES: readonly [90, 180, 270];
4
+ export declare const WHERE_IDS_ARE = "copy the id from the [Attached file \"\u2026\" \u2014 attachmentId: \u2026] line or an earlier tool result";
5
+ export type Action = (typeof PDF_ACTIONS)[number];
6
+ export type PageSize = (typeof PAGE_SIZES)[number];
7
+ export type FieldValue = string | number | boolean | string[];
8
+ export interface Permissions {
9
+ print?: boolean;
10
+ modify?: boolean;
11
+ copy?: boolean;
12
+ annotate?: boolean;
13
+ fillForms?: boolean;
14
+ extract?: boolean;
15
+ assemble?: boolean;
16
+ printHighQuality?: boolean;
17
+ }
18
+ interface Common {
19
+ readonly ids: readonly string[];
20
+ readonly password: string | undefined;
21
+ readonly output: string | undefined;
22
+ }
23
+ export type PdfRequest = (Common & {
24
+ readonly action: 'read';
25
+ }) | (Common & {
26
+ readonly action: 'create';
27
+ readonly markdown: string;
28
+ readonly pageSize: PageSize | undefined;
29
+ readonly landscape: boolean;
30
+ readonly pageNumbers: boolean;
31
+ }) | (Common & {
32
+ readonly action: 'merge' | 'decrypt' | 'text';
33
+ }) | (Common & {
34
+ readonly action: 'split';
35
+ readonly pages: readonly number[];
36
+ }) | (Common & {
37
+ readonly action: 'rotate';
38
+ readonly degrees: number;
39
+ readonly pages: readonly number[] | undefined;
40
+ }) | (Common & {
41
+ readonly action: 'watermark';
42
+ readonly text: string;
43
+ readonly opacity: number | undefined;
44
+ readonly position: 'over' | 'under' | undefined;
45
+ readonly pages: readonly number[] | undefined;
46
+ }) | (Common & {
47
+ readonly action: 'fill';
48
+ readonly values: Record<string, FieldValue>;
49
+ }) | (Common & {
50
+ readonly action: 'encrypt';
51
+ readonly ownerPassword: string | undefined;
52
+ readonly permissions: Permissions | undefined;
53
+ });
54
+ export type CreateRequest = Extract<PdfRequest, {
55
+ action: 'create';
56
+ }>;
57
+ export declare function parsePages(value: unknown): number[] | string;
58
+ type Args = Record<string, unknown>;
59
+ export declare function impliedAction(args: Args): Action | 'read';
60
+ export declare function pageSizeOf(value: unknown): PageSize | undefined;
61
+ export declare function quarterTurn(value: unknown): number | undefined;
62
+ export declare function parseRequest(args: Record<string, unknown>): PdfRequest | string;
63
+ export {};
@@ -0,0 +1,335 @@
1
+ import { isRecord } from '../../guards.js';
2
+ export const PDF_ACTIONS = [
3
+ 'create',
4
+ 'merge',
5
+ 'split',
6
+ 'rotate',
7
+ 'watermark',
8
+ 'fill',
9
+ 'encrypt',
10
+ 'decrypt',
11
+ 'text'
12
+ ];
13
+ export const PAGE_SIZES = ['A4', 'A5', 'Letter', 'Legal'];
14
+ export const DEGREES = [90, 180, 270];
15
+ export const WHERE_IDS_ARE = 'copy the id from the [Attached file "…" — attachmentId: …] line or an earlier tool result';
16
+ const MAX_PAGE_SPAN = 10_000;
17
+ const PAGES_HINT = 'pages must be page numbers counted from 1, like [1, 2, 3]';
18
+ function pageRange(part) {
19
+ const range = /^\s*(\d+)\s*(?:-\s*(\d+)\s*)?$/.exec(part);
20
+ if (!range) {
21
+ return null;
22
+ }
23
+ const from = Number(range[1]);
24
+ const to = Number(range[2] ?? range[1]);
25
+ if (Math.abs(to - from) > MAX_PAGE_SPAN) {
26
+ return null;
27
+ }
28
+ const step = from <= to ? 1 : -1;
29
+ const pages = [from];
30
+ for (let page = from; page !== to; page += step) {
31
+ pages.push(page + step);
32
+ }
33
+ return pages;
34
+ }
35
+ function pageItem(item) {
36
+ if (typeof item === 'number') {
37
+ return Number.isInteger(item) ? [item] : null;
38
+ }
39
+ if (typeof item !== 'string')
40
+ return null;
41
+ const pages = [];
42
+ for (const part of item.split(',')) {
43
+ const range = pageRange(part);
44
+ if (!range) {
45
+ return null;
46
+ }
47
+ pages.push(...range);
48
+ }
49
+ return pages;
50
+ }
51
+ export function parsePages(value) {
52
+ const items = Array.isArray(value) ? value : [value];
53
+ if (!items.length) {
54
+ return PAGES_HINT;
55
+ }
56
+ const pages = [];
57
+ for (const item of items) {
58
+ const parsed = pageItem(item);
59
+ if (!parsed) {
60
+ return PAGES_HINT;
61
+ }
62
+ pages.push(...parsed);
63
+ }
64
+ return pages;
65
+ }
66
+ function optionalPages(value) {
67
+ if (value === undefined || value === null)
68
+ return undefined;
69
+ const pages = parsePages(value);
70
+ return typeof pages === 'string' ? { error: pages } : pages;
71
+ }
72
+ function textOf(value) {
73
+ if (typeof value === 'number') {
74
+ return String(value);
75
+ }
76
+ return typeof value === 'string' && value.trim() ? value : undefined;
77
+ }
78
+ function idsOf(args) {
79
+ const list = [];
80
+ if (args.attachmentId !== undefined && args.attachmentId !== null) {
81
+ list.push(args.attachmentId);
82
+ }
83
+ if (Array.isArray(args.attachmentIds)) {
84
+ list.push(...args.attachmentIds);
85
+ }
86
+ else if (args.attachmentIds !== undefined && args.attachmentIds !== null) {
87
+ return { error: 'attachmentIds must be an array of ids' };
88
+ }
89
+ const ids = [];
90
+ for (const id of list) {
91
+ if (typeof id !== 'string' || !id.trim()) {
92
+ return { error: 'attachment ids must be non-empty strings' };
93
+ }
94
+ if (!ids.includes(id.trim())) {
95
+ ids.push(id.trim());
96
+ }
97
+ }
98
+ return ids;
99
+ }
100
+ function isAction(name) {
101
+ return PDF_ACTIONS.some((action) => action === name);
102
+ }
103
+ function hasOutput(args, extension) {
104
+ return typeof args.output === 'string' && args.output.trim().toLowerCase().endsWith(extension);
105
+ }
106
+ function idCount(args) {
107
+ const many = Array.isArray(args.attachmentIds) ? args.attachmentIds.length : 0;
108
+ return many + (typeof args.attachmentId === 'string' ? 1 : 0);
109
+ }
110
+ const IMPLIED = [
111
+ [(args) => typeof args.markdown === 'string', 'create'],
112
+ [(args) => args.degrees !== undefined, 'rotate'],
113
+ [(args) => args.values !== undefined, 'fill'],
114
+ [(args) => typeof args.text === 'string', 'watermark'],
115
+ [(args) => args.ownerPassword !== undefined || args.permissions !== undefined, 'encrypt'],
116
+ [(args) => hasOutput(args, '.txt'), 'text'],
117
+ [(args) => hasOutput(args, '.pdf') && idCount(args) > 1, 'merge'],
118
+ [(args) => hasOutput(args, '.pdf') && args.pages !== undefined, 'split']
119
+ ];
120
+ export function impliedAction(args) {
121
+ return IMPLIED.find(([applies]) => applies(args))?.[1] ?? 'read';
122
+ }
123
+ function actionOf(args) {
124
+ const { action } = args;
125
+ if (action === undefined || action === null || action === '' || action === 'read') {
126
+ return impliedAction(args);
127
+ }
128
+ if (typeof action !== 'string') {
129
+ return { error: 'action must be a string' };
130
+ }
131
+ const name = action.trim().toLowerCase();
132
+ if (isAction(name)) {
133
+ return name;
134
+ }
135
+ return {
136
+ error: `unknown action "${action}"; actions are ${PDF_ACTIONS.join(', ')}, or leave action out to read a PDF`
137
+ };
138
+ }
139
+ export function pageSizeOf(value) {
140
+ if (typeof value !== 'string')
141
+ return undefined;
142
+ return PAGE_SIZES.find((size) => size.toLowerCase() === value.trim().toLowerCase());
143
+ }
144
+ export function quarterTurn(value) {
145
+ const degrees = Number(value);
146
+ if (!Number.isInteger(degrees)) {
147
+ return undefined;
148
+ }
149
+ const turn = ((degrees % 360) + 360) % 360;
150
+ return DEGREES.find((allowed) => allowed === turn);
151
+ }
152
+ function fieldValues(value) {
153
+ if (!isRecord(value) || Array.isArray(value))
154
+ return undefined;
155
+ const values = {};
156
+ for (const [name, entry] of Object.entries(value)) {
157
+ const ok = typeof entry === 'string' ||
158
+ typeof entry === 'number' ||
159
+ typeof entry === 'boolean' ||
160
+ (Array.isArray(entry) && entry.every((item) => typeof item === 'string'));
161
+ if (!ok) {
162
+ return undefined;
163
+ }
164
+ values[name] = entry;
165
+ }
166
+ return Object.keys(values).length ? values : undefined;
167
+ }
168
+ function permissionsOf(value) {
169
+ if (!isRecord(value))
170
+ return undefined;
171
+ const permissions = {};
172
+ for (const key of [
173
+ 'print',
174
+ 'modify',
175
+ 'copy',
176
+ 'annotate',
177
+ 'fillForms',
178
+ 'extract',
179
+ 'assemble',
180
+ 'printHighQuality'
181
+ ]) {
182
+ const allowed = value[key];
183
+ if (typeof allowed === 'boolean') {
184
+ permissions[key] = allowed;
185
+ }
186
+ }
187
+ return permissions;
188
+ }
189
+ function countError(action, count) {
190
+ if (action === 'create') {
191
+ return null;
192
+ }
193
+ if (!count) {
194
+ const create = action === 'read' ? ', or action "create" with markdown to make one' : '';
195
+ return `pass the attachmentId of a PDF (${WHERE_IDS_ARE})${create}`;
196
+ }
197
+ if (action === 'merge' && count < 2) {
198
+ return 'merge needs attachmentIds with at least two PDFs, in the order to join them';
199
+ }
200
+ if (action !== 'read' && action !== 'merge' && count > 1) {
201
+ return `${action} works on one PDF; pass a single attachmentId`;
202
+ }
203
+ return null;
204
+ }
205
+ function unquoted(markdown) {
206
+ const trimmed = markdown.trim();
207
+ if (!trimmed.startsWith('"') || !trimmed.endsWith('"')) {
208
+ return markdown;
209
+ }
210
+ try {
211
+ const inner = JSON.parse(trimmed);
212
+ return typeof inner === 'string' ? inner : markdown;
213
+ }
214
+ catch {
215
+ return markdown;
216
+ }
217
+ }
218
+ function createRequest(common, args) {
219
+ const written = textOf(args.markdown);
220
+ const markdown = written && unquoted(written);
221
+ if (!markdown) {
222
+ return { error: 'create needs markdown: the whole document as Markdown' };
223
+ }
224
+ const pageSize = pageSizeOf(args.pageSize);
225
+ if (args.pageSize !== undefined && !pageSize) {
226
+ return { error: `pageSize must be one of ${PAGE_SIZES.join(', ')}` };
227
+ }
228
+ return {
229
+ ...common,
230
+ action: 'create',
231
+ markdown,
232
+ pageSize,
233
+ landscape: args.landscape === true,
234
+ pageNumbers: args.pageNumbers === true
235
+ };
236
+ }
237
+ function watermarkRequest(common, args) {
238
+ const text = textOf(args.text);
239
+ if (!text) {
240
+ return { error: 'watermark needs text' };
241
+ }
242
+ const opacity = args.opacity === undefined ? undefined : Number(args.opacity);
243
+ if (opacity !== undefined && !(opacity >= 0 && opacity <= 1)) {
244
+ return { error: 'opacity must be between 0 and 1' };
245
+ }
246
+ const { position } = args;
247
+ if (position !== undefined && position !== 'over' && position !== 'under') {
248
+ return { error: 'position must be "over" or "under"' };
249
+ }
250
+ const pages = optionalPages(args.pages);
251
+ if (isError(pages)) {
252
+ return pages;
253
+ }
254
+ return { ...common, action: 'watermark', text, opacity, position, pages };
255
+ }
256
+ function pagedRequest(common, action, args) {
257
+ const pages = optionalPages(args.pages);
258
+ if (isError(pages)) {
259
+ return pages;
260
+ }
261
+ if (action === 'split') {
262
+ if (!pages) {
263
+ return { error: 'split needs pages: the page numbers to keep, counted from 1' };
264
+ }
265
+ return { ...common, action, pages };
266
+ }
267
+ const degrees = quarterTurn(args.degrees);
268
+ if (degrees === undefined) {
269
+ return { error: 'degrees must be 90, 180 or 270, clockwise' };
270
+ }
271
+ return { ...common, action, degrees, pages };
272
+ }
273
+ function actionRequest(action, common, args) {
274
+ switch (action) {
275
+ case 'create':
276
+ return createRequest(common, args);
277
+ case 'split':
278
+ case 'rotate':
279
+ return pagedRequest(common, action, args);
280
+ case 'watermark':
281
+ return watermarkRequest(common, args);
282
+ case 'fill': {
283
+ const values = fieldValues(args.values);
284
+ if (!values) {
285
+ return {
286
+ error: 'fill needs values: { "field name": value } using the field names a read returns'
287
+ };
288
+ }
289
+ return { ...common, action, values };
290
+ }
291
+ case 'encrypt':
292
+ if (!common.password) {
293
+ return { error: 'encrypt needs password: the password the user chose' };
294
+ }
295
+ return {
296
+ ...common,
297
+ action,
298
+ ownerPassword: textOf(args.ownerPassword),
299
+ permissions: permissionsOf(args.permissions)
300
+ };
301
+ case 'decrypt':
302
+ if (!common.password) {
303
+ return { error: 'decrypt needs password: ask the user for it, never guess one' };
304
+ }
305
+ return { ...common, action };
306
+ default:
307
+ return { ...common, action };
308
+ }
309
+ }
310
+ function isError(value) {
311
+ return isRecord(value) && 'error' in value && typeof value.error === 'string';
312
+ }
313
+ export function parseRequest(args) {
314
+ const action = actionOf(args);
315
+ if (isError(action)) {
316
+ return action.error;
317
+ }
318
+ const ids = idsOf(args);
319
+ if (isError(ids)) {
320
+ return ids.error;
321
+ }
322
+ const counted = countError(action, ids.length);
323
+ if (counted) {
324
+ return counted;
325
+ }
326
+ if (args.password !== undefined && args.password !== null && !textOf(args.password)) {
327
+ return 'password must be a non-empty string';
328
+ }
329
+ if (args.output !== undefined && args.output !== null && !textOf(args.output)) {
330
+ return 'output must be a file name';
331
+ }
332
+ const output = textOf(args.output)?.trim();
333
+ const request = actionRequest(action, { ids, password: textOf(args.password), output }, args);
334
+ return isError(request) ? request.error : request;
335
+ }
@@ -0,0 +1,6 @@
1
+ import type { ToolDefinition } from '../capabilities/completion.ts';
2
+ import { type Pdfium, type Source } from './pdf/build.ts';
3
+ import { type PdfRequest } from './pdf/request.ts';
4
+ export { parsePages, parseRequest } from './pdf/request.ts';
5
+ export declare function outputName(request: PdfRequest, inputs: readonly Source[]): string;
6
+ export declare function createPdfTool(load?: () => Promise<Pdfium | null>): ToolDefinition;