@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.
- package/CHANGELOG.md +97 -1
- package/README.md +49 -0
- package/dist-lib/index.d.ts +31 -2
- package/dist-lib/index.js +365 -53
- package/dist-lib/lib/audio-transcode-bare.d.ts +1 -1
- package/dist-lib/lib/audio-transcode-bare.js +22 -25
- package/dist-lib/lib/audio-transcode-node.d.ts +1 -1
- package/dist-lib/lib/audio-transcode-node.js +1 -3
- package/dist-lib/lib/capabilities/audio-resample.d.ts +1 -1
- package/dist-lib/lib/capabilities/audio-resample.js +7 -8
- package/dist-lib/lib/capabilities/completion.d.ts +5 -2
- package/dist-lib/lib/capabilities/completion.js +22 -4
- package/dist-lib/lib/capabilities/download.d.ts +3 -1
- package/dist-lib/lib/capabilities/download.js +1 -1
- package/dist-lib/lib/capabilities/preview-model-source.d.ts +1 -0
- package/dist-lib/lib/capabilities/preview-model-source.js +1 -0
- package/dist-lib/lib/capabilities/usage-breakdown.js +4 -1
- package/dist-lib/lib/capabilities/voice.d.ts +5 -1
- package/dist-lib/lib/capabilities/voice.js +94 -26
- package/dist-lib/lib/connect.d.ts +4 -1
- package/dist-lib/lib/connect.js +14 -1
- package/dist-lib/lib/contract-types.d.ts +10 -0
- package/dist-lib/lib/docx/index.d.ts +1 -1
- package/dist-lib/lib/docx/index.js +16 -24
- package/dist-lib/lib/models/download-gate.d.ts +20 -0
- package/dist-lib/lib/models/download-gate.js +73 -0
- package/dist-lib/lib/models/memory-cap.d.ts +13 -0
- package/dist-lib/lib/models/memory-cap.js +21 -0
- package/dist-lib/lib/models/model-catalog-constants.d.ts +3 -1
- package/dist-lib/lib/models/model-catalog-constants.js +9 -1
- package/dist-lib/lib/models/model-catalog.d.ts +6 -2
- package/dist-lib/lib/models/model-catalog.js +35 -3
- package/dist-lib/lib/models/model-handle.d.ts +8 -1
- package/dist-lib/lib/models/model-handle.js +20 -2
- package/dist-lib/lib/models/model-list.d.ts +8 -1
- package/dist-lib/lib/models/model-list.js +23 -0
- package/dist-lib/lib/models/model-pool.d.ts +33 -5
- package/dist-lib/lib/models/model-pool.js +97 -29
- package/dist-lib/lib/office/extract.d.ts +9 -0
- package/dist-lib/lib/office/extract.js +19 -0
- package/dist-lib/lib/office/index.d.ts +1 -1
- package/dist-lib/lib/office/index.js +74 -19
- package/dist-lib/lib/serve.js +18 -2
- package/dist-lib/lib/tools/exec/adapters/python/module-distributions.js +0 -3
- package/dist-lib/lib/tools/exec/adapters/python/python-adapter.d.ts +7 -50
- package/dist-lib/lib/tools/exec/adapters/python/python-adapter.js +38 -695
- package/dist-lib/lib/tools/exec/adapters/python/run-session.js +1 -85
- package/dist-lib/lib/tools/exec.js +9 -20
- package/dist-lib/lib/tools/generate-image.d.ts +4 -2
- package/dist-lib/lib/tools/generate-image.js +5 -2
- package/dist-lib/lib/tools/generate-music.d.ts +4 -2
- package/dist-lib/lib/tools/generate-music.js +5 -2
- package/dist-lib/lib/tools/office/ops.d.ts +13 -0
- package/dist-lib/lib/tools/office/ops.js +100 -0
- package/dist-lib/lib/tools/office.d.ts +12 -0
- package/dist-lib/lib/tools/office.js +486 -0
- package/dist-lib/lib/tools/translate.d.ts +3 -1
- package/dist-lib/lib/tools/translate.js +6 -1
- package/dist-lib/schema/harness.js +41 -3
- package/dist-lib/spec/bind.js +9 -0
- package/dist-lib/spec/calls.js +3 -0
- package/dist-lib/spec/hrpc/hrpc.json +39 -0
- package/dist-lib/spec/hrpc/index.js +39 -3
- package/dist-lib/spec/hrpc/messages.js +93 -10
- package/dist-lib/spec/hyperschema/index.js +93 -10
- package/dist-lib/spec/hyperschema/schema.json +57 -1
- package/dist-lib/spec/hyperschema/types.d.ts +19 -2
- package/package.json +13 -9
- package/wheels/manifest.json +1 -160
- package/worker/harness-sidecar.mjs +3 -1
- package/dist-lib/lib/office/pptx.d.ts +0 -1
- package/dist-lib/lib/office/pptx.js +0 -26
- package/dist-lib/lib/office/python.d.ts +0 -8
- package/dist-lib/lib/office/python.js +0 -32
- package/dist-lib/lib/office/xlsx.d.ts +0 -1
- package/dist-lib/lib/office/xlsx.js +0 -26
- package/dist-lib/lib/tools/exec/adapters/python/create-effect.d.ts +0 -7
- package/dist-lib/lib/tools/exec/adapters/python/create-effect.js +0 -318
- package/wheels/cffi-2.0.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl +0 -0
- package/wheels/cryptography-47.0.0-cp314-abi3-pyemscripten_2026_0_wasm32.whl +0 -0
- package/wheels/defusedxml-0.7.1-py2.py3-none-any.whl +0 -0
- package/wheels/et_xmlfile-2.0.0-py3-none-any.whl +0 -0
- package/wheels/fonttools-4.62.1-py3-none-any.whl +0 -0
- package/wheels/fpdf2-2.8.8-py3-none-any.whl +0 -0
- package/wheels/lxml-6.0.2-cp314-cp314-pyemscripten_2026_0_wasm32.whl +0 -0
- package/wheels/openpyxl-3.1.5-py2.py3-none-any.whl +0 -0
- package/wheels/pillow-12.2.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl +0 -0
- package/wheels/pycparser-3.0-py3-none-any.whl +0 -0
- package/wheels/pypdf-6.15.0-py3-none-any.whl +0 -0
- package/wheels/python_docx-1.2.0-py3-none-any.whl +0 -0
- package/wheels/python_pptx-1.0.2-py3-none-any.whl +0 -0
- package/wheels/six-1.17.0-py2.py3-none-any.whl +0 -0
- package/wheels/typing_extensions-4.15.0-py3-none-any.whl +0 -0
- 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
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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 {};
|