@qvac/harness 0.8.0 → 0.8.6

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 (94) hide show
  1. package/CHANGELOG.md +97 -1
  2. package/README.md +49 -0
  3. package/dist-lib/index.d.ts +31 -2
  4. package/dist-lib/index.js +365 -53
  5. package/dist-lib/lib/audio-transcode-bare.d.ts +1 -1
  6. package/dist-lib/lib/audio-transcode-bare.js +22 -25
  7. package/dist-lib/lib/audio-transcode-node.d.ts +1 -1
  8. package/dist-lib/lib/audio-transcode-node.js +1 -3
  9. package/dist-lib/lib/capabilities/audio-resample.d.ts +1 -1
  10. package/dist-lib/lib/capabilities/audio-resample.js +7 -8
  11. package/dist-lib/lib/capabilities/completion.d.ts +5 -2
  12. package/dist-lib/lib/capabilities/completion.js +22 -4
  13. package/dist-lib/lib/capabilities/download.d.ts +3 -1
  14. package/dist-lib/lib/capabilities/download.js +1 -1
  15. package/dist-lib/lib/capabilities/preview-model-source.d.ts +1 -0
  16. package/dist-lib/lib/capabilities/preview-model-source.js +1 -0
  17. package/dist-lib/lib/capabilities/usage-breakdown.js +4 -1
  18. package/dist-lib/lib/capabilities/voice.d.ts +5 -1
  19. package/dist-lib/lib/capabilities/voice.js +94 -26
  20. package/dist-lib/lib/connect.d.ts +4 -1
  21. package/dist-lib/lib/connect.js +14 -1
  22. package/dist-lib/lib/contract-types.d.ts +10 -0
  23. package/dist-lib/lib/docx/index.d.ts +1 -1
  24. package/dist-lib/lib/docx/index.js +16 -24
  25. package/dist-lib/lib/models/download-gate.d.ts +20 -0
  26. package/dist-lib/lib/models/download-gate.js +73 -0
  27. package/dist-lib/lib/models/memory-cap.d.ts +13 -0
  28. package/dist-lib/lib/models/memory-cap.js +21 -0
  29. package/dist-lib/lib/models/model-catalog-constants.d.ts +3 -1
  30. package/dist-lib/lib/models/model-catalog-constants.js +9 -1
  31. package/dist-lib/lib/models/model-catalog.d.ts +6 -2
  32. package/dist-lib/lib/models/model-catalog.js +35 -3
  33. package/dist-lib/lib/models/model-handle.d.ts +8 -1
  34. package/dist-lib/lib/models/model-handle.js +20 -2
  35. package/dist-lib/lib/models/model-list.d.ts +8 -1
  36. package/dist-lib/lib/models/model-list.js +23 -0
  37. package/dist-lib/lib/models/model-pool.d.ts +33 -5
  38. package/dist-lib/lib/models/model-pool.js +97 -29
  39. package/dist-lib/lib/office/extract.d.ts +9 -0
  40. package/dist-lib/lib/office/extract.js +19 -0
  41. package/dist-lib/lib/office/index.d.ts +1 -1
  42. package/dist-lib/lib/office/index.js +74 -19
  43. package/dist-lib/lib/serve.js +18 -2
  44. package/dist-lib/lib/tools/exec/adapters/python/module-distributions.js +0 -3
  45. package/dist-lib/lib/tools/exec/adapters/python/python-adapter.d.ts +7 -50
  46. package/dist-lib/lib/tools/exec/adapters/python/python-adapter.js +38 -695
  47. package/dist-lib/lib/tools/exec/adapters/python/run-session.js +1 -85
  48. package/dist-lib/lib/tools/exec.js +9 -20
  49. package/dist-lib/lib/tools/generate-image.d.ts +4 -2
  50. package/dist-lib/lib/tools/generate-image.js +5 -2
  51. package/dist-lib/lib/tools/generate-music.d.ts +4 -2
  52. package/dist-lib/lib/tools/generate-music.js +5 -2
  53. package/dist-lib/lib/tools/office/ops.d.ts +13 -0
  54. package/dist-lib/lib/tools/office/ops.js +100 -0
  55. package/dist-lib/lib/tools/office.d.ts +12 -0
  56. package/dist-lib/lib/tools/office.js +486 -0
  57. package/dist-lib/lib/tools/translate.d.ts +3 -1
  58. package/dist-lib/lib/tools/translate.js +6 -1
  59. package/dist-lib/schema/harness.js +41 -3
  60. package/dist-lib/spec/bind.js +9 -0
  61. package/dist-lib/spec/calls.js +3 -0
  62. package/dist-lib/spec/hrpc/hrpc.json +39 -0
  63. package/dist-lib/spec/hrpc/index.js +39 -3
  64. package/dist-lib/spec/hrpc/messages.js +93 -10
  65. package/dist-lib/spec/hyperschema/index.js +93 -10
  66. package/dist-lib/spec/hyperschema/schema.json +57 -1
  67. package/dist-lib/spec/hyperschema/types.d.ts +19 -2
  68. package/package.json +13 -9
  69. package/wheels/manifest.json +1 -160
  70. package/worker/harness-sidecar.mjs +3 -1
  71. package/dist-lib/lib/office/pptx.d.ts +0 -1
  72. package/dist-lib/lib/office/pptx.js +0 -26
  73. package/dist-lib/lib/office/python.d.ts +0 -8
  74. package/dist-lib/lib/office/python.js +0 -32
  75. package/dist-lib/lib/office/xlsx.d.ts +0 -1
  76. package/dist-lib/lib/office/xlsx.js +0 -26
  77. package/dist-lib/lib/tools/exec/adapters/python/create-effect.d.ts +0 -7
  78. package/dist-lib/lib/tools/exec/adapters/python/create-effect.js +0 -318
  79. package/wheels/cffi-2.0.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl +0 -0
  80. package/wheels/cryptography-47.0.0-cp314-abi3-pyemscripten_2026_0_wasm32.whl +0 -0
  81. package/wheels/defusedxml-0.7.1-py2.py3-none-any.whl +0 -0
  82. package/wheels/et_xmlfile-2.0.0-py3-none-any.whl +0 -0
  83. package/wheels/fonttools-4.62.1-py3-none-any.whl +0 -0
  84. package/wheels/fpdf2-2.8.8-py3-none-any.whl +0 -0
  85. package/wheels/lxml-6.0.2-cp314-cp314-pyemscripten_2026_0_wasm32.whl +0 -0
  86. package/wheels/openpyxl-3.1.5-py2.py3-none-any.whl +0 -0
  87. package/wheels/pillow-12.2.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl +0 -0
  88. package/wheels/pycparser-3.0-py3-none-any.whl +0 -0
  89. package/wheels/pypdf-6.15.0-py3-none-any.whl +0 -0
  90. package/wheels/python_docx-1.2.0-py3-none-any.whl +0 -0
  91. package/wheels/python_pptx-1.0.2-py3-none-any.whl +0 -0
  92. package/wheels/six-1.17.0-py2.py3-none-any.whl +0 -0
  93. package/wheels/typing_extensions-4.15.0-py3-none-any.whl +0 -0
  94. package/wheels/xlsxwriter-3.2.9-py3-none-any.whl +0 -0
@@ -105,72 +105,6 @@ async function runOnce(py, code, opts) {
105
105
  }
106
106
  return retry;
107
107
  }
108
- const FIT_DECKS = `
109
- def _qvac_fit_decks(names):
110
- try:
111
- from pptx import Presentation
112
- from pptx.oxml.ns import qn
113
- except Exception:
114
- return
115
- for name in names:
116
- if not name.lower().endswith('.pptx'):
117
- continue
118
- try:
119
- prs = Presentation(name)
120
- except Exception:
121
- continue
122
- slide_height = prs.slide_height
123
- changed = False
124
- for slide in prs.slides:
125
- for shape in slide.shapes:
126
- if not shape.has_text_frame:
127
- continue
128
- tf = shape.text_frame
129
- text = tf.text or ''
130
- if not text.strip():
131
- continue
132
- tf.word_wrap = True
133
- # Estimated wrapped lines against the box: a character at N pt is
134
- # about 0.5 N wide and a line about 1.2 N tall. Rough on purpose —
135
- # it only has to pick a scale, and the renderer does the real layout.
136
- width_pt = (shape.width or 0) / 12700.0
137
- height_pt = (shape.height or slide_height) / 12700.0
138
- lines = 0
139
- tallest = 0.0
140
- for p in tf.paragraphs:
141
- # An unsized paragraph inherits from the layout, and the bundled
142
- # template's body is 28pt — assuming something smaller here is
143
- # what let a seven-bullet slide measure as fitting.
144
- size = p.font.size.pt if p.font.size is not None else 28.0
145
- tallest = max(tallest, size)
146
- per_line = max(1.0, width_pt / (size * 0.5))
147
- lines += max(1, int(len(p.text or '') / per_line) + 1)
148
- needed = lines * max(tallest, 12.0) * 1.2
149
- scale = 1.0 if needed <= height_pt or height_pt <= 0 else height_pt / needed
150
- # 55% is the floor: below that the deck is unreadable from a room and
151
- # the honest answer is fewer bullets, not smaller type.
152
- scale = max(0.55, min(1.0, scale))
153
- bodyPr = tf._txBody.bodyPr
154
- for tag in ('a:normAutofit', 'a:spAutoFit', 'a:noAutofit'):
155
- found = bodyPr.find(qn(tag))
156
- if found is not None:
157
- bodyPr.remove(found)
158
- bodyPr.append(
159
- bodyPr.makeelement(
160
- qn('a:normAutofit'),
161
- {
162
- 'fontScale': str(int(scale * 100000)),
163
- 'lnSpcReduction': '0' if scale >= 1.0 else '10000'
164
- }
165
- )
166
- )
167
- changed = True
168
- if changed:
169
- try:
170
- prs.save(name)
171
- except Exception:
172
- pass
173
- `;
174
108
  class InstallTimeoutError extends Error {
175
109
  }
176
110
  function installSource(py, packages) {
@@ -213,21 +147,6 @@ function listFiles(py, dir) {
213
147
  function newDeliverables(before, after) {
214
148
  return [...after].filter((name) => !before.has(name) && DELIVERABLE.test(name));
215
149
  }
216
- function fitDecks(py, outputs) {
217
- if (!outputs.some((name) => name.toLowerCase().endsWith('.pptx'))) {
218
- return false;
219
- }
220
- try {
221
- py.runPython(`${FIT_DECKS}\n_qvac_fit_decks(${JSON.stringify(outputs)})`);
222
- return false;
223
- }
224
- catch (error) {
225
- hlog('python: deck fit skipped', {
226
- err: errorMessage(error)
227
- });
228
- return isPyodideFatal(error);
229
- }
230
- }
231
150
  export async function performRun(py, spec, opts = {}) {
232
151
  const isCancelled = opts.isCancelled ?? (() => false);
233
152
  const ask = opts.askConnect ?? (async () => false);
@@ -297,10 +216,7 @@ export async function performRun(py, spec, opts = {}) {
297
216
  maxOutputChars: spec.maxOutputChars,
298
217
  access
299
218
  });
300
- let evict = fatal;
301
- if (result.exitCode === 0) {
302
- evict = fitDecks(py, spec.outputs ?? []);
303
- }
219
+ const evict = fatal;
304
220
  if (evict) {
305
221
  return { result: { ...result, missing: spec.outputs ?? [] }, evict };
306
222
  }
@@ -1,8 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { AbortedError } from '../helpers/race-abort.js';
4
- import { checkPdfBuildSource, checkBareLengthArgs, checkPptxBuildSource, checkPythonSource, checkUnstagedRead, createPythonAdapter, isPythonLanguage, parseInputs, parseOutputs, parsePackages, parseWorkdir, pinned, runsThroughPyodide, sandboxInstallError, unwrapPythonSource } from './exec/adapters/python/python-adapter.js';
5
- import { pythonCreateEffect } from './exec/adapters/python/create-effect.js';
4
+ import { checkPythonSource, createPythonAdapter, isPythonLanguage, officeImportError, parseInputs, parseOutputs, parsePackages, parseWorkdir, pinned, runsThroughPyodide, sandboxInstallError, unwrapPythonSource } from './exec/adapters/python/python-adapter.js';
6
5
  import { namedHosts } from './exec/adapters/python/named-hosts.js';
7
6
  import { pythonReachesNetwork } from './exec/adapters/python/net-shim.js';
8
7
  import { createShellAdapter } from './exec/adapters/shell/shell-adapter.js';
@@ -422,7 +421,7 @@ export function createExecTool({ spawn, killProcess, loadPyodide, disablePyodide
422
421
  return 'read';
423
422
  }
424
423
  if (runsThroughPyodide(args.language, disablePyodide)) {
425
- return pythonCreateEffect(args);
424
+ return 'write';
426
425
  }
427
426
  return shellEffect(String(args.command ?? '').trim(), ctx?.toolOps);
428
427
  },
@@ -459,6 +458,13 @@ export function createExecTool({ spawn, killProcess, loadPyodide, disablePyodide
459
458
  if (packages && 'error' in packages) {
460
459
  return packages.error;
461
460
  }
461
+ const office = officeImportError(unwrapPythonSource(command) ?? command, {
462
+ packages,
463
+ toolNames: ctx?.toolNames
464
+ });
465
+ if (office) {
466
+ return office;
467
+ }
462
468
  const refusal = ctx?.sandboxMode === true ? sandboxInstallError(command, packages) : null;
463
469
  if (refusal) {
464
470
  return refusal;
@@ -468,23 +474,6 @@ export function createExecTool({ spawn, killProcess, loadPyodide, disablePyodide
468
474
  if (sourceError) {
469
475
  return sourceError;
470
476
  }
471
- const pptxError = checkPptxBuildSource(unwrapPythonSource(command) ?? command, outputs, packages);
472
- if (pptxError) {
473
- return pptxError;
474
- }
475
- const pdfError = checkPdfBuildSource(unwrapPythonSource(command) ?? command, outputs, packages);
476
- if (pdfError) {
477
- return pdfError;
478
- }
479
- const bareLength = checkBareLengthArgs(unwrapPythonSource(command) ?? command, packages);
480
- if (bareLength) {
481
- return bareLength;
482
- }
483
- const inputs = parseInputs(rawArgs.inputs);
484
- const unstagedRead = checkUnstagedRead(unwrapPythonSource(command) ?? command, Array.isArray(inputs) ? inputs : undefined);
485
- if (unstagedRead) {
486
- return unstagedRead;
487
- }
488
477
  }
489
478
  }
490
479
  const parsed = parsePackages(rawArgs.packages);
@@ -3,6 +3,7 @@ import type { ToolDefinition } from '../capabilities/completion.ts';
3
3
  import type { ModelSrc } from '../models/context.ts';
4
4
  import { type ImageClamps } from '../models/model-catalog.ts';
5
5
  import { type LoadTracker, type TrackedLoad } from '../models/model-pool.ts';
6
+ import type { HoldFn } from '../models/download-gate.ts';
6
7
  import { type ModelRegistrySdk } from '../models/model-source.ts';
7
8
  export declare const MAX_DIM = 1024;
8
9
  export declare const DIM_STEP = 64;
@@ -68,7 +69,8 @@ export interface DiffusionRuntimeOptions extends ImageModelSet {
68
69
  loadSdk: () => Promise<DiffusionSdk>;
69
70
  onProgress?: (model: string, progress: Record<string, unknown>) => void;
70
71
  onLoaded?: (model: string) => void;
71
- onBeforeLoad?: (signal?: BareAbortSignal) => Promise<void>;
72
+ onBeforeLoad?: (signal: BareAbortSignal | undefined, srcs: ReadonlyArray<string>) => Promise<void>;
73
+ hold?: HoldFn;
72
74
  loads?: LoadTracker;
73
75
  stallMs?: number;
74
76
  }
@@ -87,7 +89,7 @@ export interface DiffusionRuntime {
87
89
  }
88
90
  export declare function generationPercentTick(onPercent: (percentage: number) => void, encodePhases?: number): (tick: DiffusionTick) => void;
89
91
  export declare const IMAGE_LOAD_STALL_ERROR = "image model download stalled";
90
- export declare function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBeforeLoad, loads, stallMs, ...base }: DiffusionRuntimeOptions): DiffusionRuntime;
92
+ export declare function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBeforeLoad, hold, loads, stallMs, ...base }: DiffusionRuntimeOptions): DiffusionRuntime;
91
93
  export declare const BUSY_ERROR = "an image generation is already running; try again when it finishes";
92
94
  export interface ImageToolError {
93
95
  error: string;
@@ -39,7 +39,7 @@ function diffusionProgressName(set) {
39
39
  return entry?.name ?? set.model;
40
40
  }
41
41
  export const IMAGE_LOAD_STALL_ERROR = 'image model download stalled';
42
- export function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBeforeLoad, loads = createLoadTracker(), stallMs = IMAGE_LOAD_STALL_MS, ...base }) {
42
+ export function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBeforeLoad, hold, loads = createLoadTracker(), stallMs = IMAGE_LOAD_STALL_MS, ...base }) {
43
43
  let loading = null;
44
44
  let residentFingerprint = null;
45
45
  let busy = false;
@@ -76,13 +76,16 @@ export function createDiffusionRuntime({ loadSdk, onProgress, onLoaded, onBefore
76
76
  ensureLoaded: async (sdk, signal, modelChoice) => {
77
77
  const set = (modelChoice && imageModelFor(modelChoice)) || base;
78
78
  const fingerprint = fingerprintOf(set);
79
+ if (hold && !(loading && residentFingerprint === fingerprint)) {
80
+ await hold(diffusionProgressName(set), 'diffusion', signal);
81
+ }
79
82
  await lock.lock();
80
83
  try {
81
84
  if (loading && residentFingerprint !== fingerprint) {
82
85
  await doUnload();
83
86
  }
84
87
  if (!loading && onBeforeLoad) {
85
- await onBeforeLoad(signal);
88
+ await onBeforeLoad(signal, [set.model, set.llmModel, set.vaeModel].filter((src) => src !== undefined));
86
89
  }
87
90
  residentFingerprint = fingerprint;
88
91
  loading ??= (async () => {
@@ -2,6 +2,7 @@ import type { AbortSignal as BareAbortSignal } from 'bare-abort-controller';
2
2
  import type { MusicSettings, ToolDefinition, ToolExecuteContext } from '../capabilities/completion.ts';
3
3
  import type { ModelSrc } from '../models/context.ts';
4
4
  import { type LoadTracker, type TrackedLoad } from '../models/model-pool.ts';
5
+ import type { HoldFn } from '../models/download-gate.ts';
5
6
  import { type ModelRegistrySdk } from '../models/model-source.ts';
6
7
  export declare const MAX_DURATION_SECONDS = 300;
7
8
  export declare const MIN_DURATION_SECONDS = 1;
@@ -75,7 +76,8 @@ export interface MusicRuntimeOptions extends MusicModelStages {
75
76
  inferenceSteps?: number;
76
77
  useGpu?: boolean;
77
78
  onProgress?: (model: string, progress: Record<string, unknown>) => void;
78
- onBeforeLoad?: (signal?: BareAbortSignal) => Promise<void>;
79
+ onBeforeLoad?: (signal: BareAbortSignal | undefined, srcs: ReadonlyArray<string>) => Promise<void>;
80
+ hold?: HoldFn;
79
81
  loads?: LoadTracker;
80
82
  }
81
83
  export interface LoadedMusicModel {
@@ -97,7 +99,7 @@ export interface MusicRuntime {
97
99
  release(): void;
98
100
  }
99
101
  export declare function musicPercentTick(onPercent: (percentage: number) => void): (tick: MusicTick) => void;
100
- export declare function createMusicRuntime({ loadSdk, onProgress, onBeforeLoad, loads, inferenceSteps: baseSteps, useGpu, ...base }: MusicRuntimeOptions): MusicRuntime;
102
+ export declare function createMusicRuntime({ loadSdk, onProgress, onBeforeLoad, hold, loads, inferenceSteps: baseSteps, useGpu, ...base }: MusicRuntimeOptions): MusicRuntime;
101
103
  export declare const BUSY_ERROR = "a music generation is already running; try again when it finishes";
102
104
  export interface MusicToolError {
103
105
  error: string;
@@ -49,7 +49,7 @@ function musicProgressName(dit) {
49
49
  const entry = AUDIOGEN_CATALOG.find((candidate) => candidate.dit.src === dit || candidate.dit.name === dit);
50
50
  return entry?.name ?? dit;
51
51
  }
52
- export function createMusicRuntime({ loadSdk, onProgress, onBeforeLoad, loads = createLoadTracker(), inferenceSteps: baseSteps, useGpu = true, ...base }) {
52
+ export function createMusicRuntime({ loadSdk, onProgress, onBeforeLoad, hold, loads = createLoadTracker(), inferenceSteps: baseSteps, useGpu = true, ...base }) {
53
53
  let loading = null;
54
54
  let residentFingerprint = null;
55
55
  let busy = false;
@@ -89,13 +89,16 @@ export function createMusicRuntime({ loadSdk, onProgress, onBeforeLoad, loads =
89
89
  const stages = stagesFor(modelChoice, base);
90
90
  const effectiveSteps = steps ?? baseSteps;
91
91
  const fingerprint = fingerprintOf(stages, effectiveSteps);
92
+ if (hold && !(loading && residentFingerprint === fingerprint)) {
93
+ await hold(musicProgressName(stages.dit), 'audiogen', signal);
94
+ }
92
95
  await lock.lock();
93
96
  try {
94
97
  if (loading && residentFingerprint !== fingerprint) {
95
98
  await doUnload();
96
99
  }
97
100
  if (!loading && onBeforeLoad) {
98
- await onBeforeLoad(signal);
101
+ await onBeforeLoad(signal, [stages.dit, stages.textEnc, stages.lm, stages.vae]);
99
102
  }
100
103
  residentFingerprint = fingerprint;
101
104
  loading ??= (async () => {
@@ -0,0 +1,13 @@
1
+ export type Slot = string | readonly string[];
2
+ export interface OpSpec {
3
+ readonly slots: readonly Slot[];
4
+ readonly inches?: boolean;
5
+ readonly query?: boolean;
6
+ readonly aliases?: Readonly<Record<string, string>>;
7
+ }
8
+ export declare const PPTX_OPS: Readonly<Record<string, OpSpec>>;
9
+ export declare const DOCX_OPS: Readonly<Record<string, OpSpec>>;
10
+ export declare const XLSX_OPS: Readonly<Record<string, OpSpec>>;
11
+ export declare function opKeys(spec: OpSpec): string[];
12
+ export declare function withAliases(spec: OpSpec, op: Readonly<Record<string, unknown>>): Record<string, unknown>;
13
+ export declare function opArguments(spec: OpSpec, op: Readonly<Record<string, unknown>>, toEmu: (inches: number) => number): unknown[];
@@ -0,0 +1,100 @@
1
+ const TABLE_INDEX = { index: 'table' };
2
+ const BOX = ['x', 'y', 'width', 'height'];
3
+ const CHART = ['type', 'categories', 'series', 'title', 'stacked', 'legend', 'name'];
4
+ export const PPTX_OPS = {
5
+ read: { slots: [], query: true },
6
+ layouts: { slots: [], query: true },
7
+ setText: { slots: [['slide', 'shape'], 'text'] },
8
+ setCellText: { slots: [['slide', 'shape', 'row', 'column'], 'text'] },
9
+ setStyle: { slots: [['slide', 'shape'], 'style', ['paragraph']] },
10
+ addSlide: { slots: [['like', 'layout', 'at', 'title', 'body']] },
11
+ addPicture: { slots: ['slide', 'image', [...BOX, 'name', 'description']], inches: true },
12
+ addTextBox: { slots: ['slide', [...BOX, 'text', 'style', 'fill', 'name']], inches: true },
13
+ addTable: { slots: ['slide', [...BOX, 'rows', 'header', 'name']], inches: true },
14
+ addChart: { slots: ['slide', [...BOX, ...CHART]], inches: true },
15
+ setChartData: {
16
+ slots: [
17
+ ['slide', 'shape'],
18
+ ['categories', 'series']
19
+ ]
20
+ },
21
+ setChartTitle: { slots: [['slide', 'shape'], 'title'] },
22
+ setBackground: { slots: ['slide', 'color'] },
23
+ deleteSlide: { slots: ['slide'] },
24
+ moveSlide: { slots: ['from', ['to']] }
25
+ };
26
+ export const DOCX_OPS = {
27
+ read: { slots: [], query: true },
28
+ sections: { slots: [], query: true },
29
+ paragraphStyles: { slots: [], query: true },
30
+ setText: { slots: ['index', 'text'] },
31
+ insertParagraphs: { slots: ['at', 'paragraphs'] },
32
+ appendParagraphs: { slots: ['paragraphs'] },
33
+ replaceSection: { slots: ['index', ['title', 'body']] },
34
+ deleteSection: { slots: ['index'] },
35
+ deleteBlocks: { slots: ['index', ['count']] },
36
+ formatParagraph: { slots: ['index', 'format'] },
37
+ formatStyle: { slots: ['name', 'format'] },
38
+ addTable: { slots: ['at', ['rows', 'header', 'style']] },
39
+ setCell: { slots: [['table', 'row', 'column'], 'text'], aliases: TABLE_INDEX },
40
+ addRow: { slots: ['table', 'values', ['at']], aliases: TABLE_INDEX },
41
+ deleteRow: { slots: [['table', 'row']], aliases: TABLE_INDEX },
42
+ addImage: {
43
+ slots: ['at', 'image', ['width', 'height', 'align', 'name', 'description']],
44
+ inches: true
45
+ },
46
+ addChart: { slots: ['at', ['width', 'height', ...CHART]], inches: true },
47
+ setChartData: { slots: ['index', ['categories', 'series'], ['chart']] },
48
+ setChartTitle: { slots: ['index', 'title', ['chart']] }
49
+ };
50
+ export const XLSX_OPS = {
51
+ sheets: { slots: [], query: true },
52
+ read: { slots: ['sheet', ['range', 'limit']], query: true },
53
+ get: { slots: ['sheet', 'ref'], query: true },
54
+ setCells: { slots: ['sheet', 'cells'] },
55
+ appendRows: { slots: ['sheet', 'rows'] },
56
+ insertRows: { slots: ['sheet', ['at', 'rows', 'count']] },
57
+ deleteRows: { slots: ['sheet', ['at', 'count']] },
58
+ deleteColumns: { slots: ['sheet', ['column', 'count']] },
59
+ setColumnWidth: { slots: ['sheet', ['column', 'width']] },
60
+ addSheet: { slots: ['name', ['at']] },
61
+ renameSheet: { slots: ['sheet', 'name'] },
62
+ deleteSheet: { slots: ['sheet'] },
63
+ addImage: {
64
+ slots: ['sheet', 'image', ['cell', 'width', 'height', 'name', 'description']],
65
+ inches: true
66
+ },
67
+ removeMacros: { slots: [] }
68
+ };
69
+ const LENGTHS = new Set(BOX);
70
+ export function opKeys(spec) {
71
+ const keys = spec.slots.flatMap((slot) => (typeof slot === 'string' ? [slot] : [...slot]));
72
+ return [...keys, ...Object.keys(spec.aliases ?? {})];
73
+ }
74
+ export function withAliases(spec, op) {
75
+ const named = { ...op };
76
+ for (const [alias, key] of Object.entries(spec.aliases ?? {})) {
77
+ if (alias in named && named[key] === undefined) {
78
+ named[key] = named[alias];
79
+ }
80
+ delete named[alias];
81
+ }
82
+ return named;
83
+ }
84
+ export function opArguments(spec, op, toEmu) {
85
+ const value = (key) => {
86
+ const raw = op[key];
87
+ return spec.inches && LENGTHS.has(key) && typeof raw === 'number' ? toEmu(raw) : raw;
88
+ };
89
+ const args = spec.slots.map((slot) => {
90
+ if (typeof slot === 'string') {
91
+ return value(slot);
92
+ }
93
+ const present = slot.filter((key) => op[key] !== undefined);
94
+ return present.length ? Object.fromEntries(present.map((key) => [key, value(key)])) : undefined;
95
+ });
96
+ while (args.length && args.at(-1) === undefined) {
97
+ args.pop();
98
+ }
99
+ return args;
100
+ }
@@ -0,0 +1,12 @@
1
+ import type { ToolDefinition } from '../capabilities/completion.ts';
2
+ import { pptx, xlsx } from '../office/engine/index.ts';
3
+ type Deck = ReturnType<typeof pptx.open>;
4
+ type Workbook = ReturnType<typeof xlsx.open>;
5
+ type FormatName = 'pptx' | 'docx' | 'xlsx';
6
+ type Args = Record<string, unknown>;
7
+ export declare function shapeFor(deck: Deck, op: Args): Args;
8
+ export declare function sheetFor(book: Workbook, op: Args, created: boolean): Args;
9
+ export declare function normalizeArgs(args: Args): Args;
10
+ export declare function revisedName(format: FormatName, fileName: string): string;
11
+ export declare function createOfficeTools(): ToolDefinition[];
12
+ export {};