@hypit/hypit 0.1.13 → 0.2.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.
- package/README.md +37 -3
- package/bin/hypit.mjs +0 -2
- package/dist/public/browser-capture.d.ts +9 -3
- package/dist/public/caption.d.ts +2 -0
- package/dist/public/narrative.d.ts +2 -0
- package/dist/public/runtime-kit.d.ts +2 -2
- package/dist/public/speech.d.ts +2 -0
- package/examples/semantic-composition/packages/responsive-explainer/README.md +1 -1
- package/package.json +2 -1
- package/packages/browser-capture/README.md +9 -2
- package/packages/browser-capture/package.json +5 -0
- package/packages/browser-capture/src/browser.ts +22 -8
- package/packages/browser-capture/src/index.ts +8 -2
- package/packages/caption/README.md +1 -1
- package/packages/caption/src/display.ts +1 -0
- package/packages/caption-fine/README.md +19 -5
- package/packages/caption-fine/package.json +1 -0
- package/packages/caption-fine/src/joined-box.ts +114 -0
- package/packages/caption-fine/src/manifest.ts +1 -1
- package/packages/caption-fine/src/render.ts +99 -92
- package/packages/caption-fine/src/spacing.ts +8 -61
- package/packages/caption-fine-studio/src/index.ts +2 -4
- package/packages/cli/README.md +3 -2
- package/packages/cli/package.json +1 -0
- package/packages/cli/src/arguments.ts +2 -2
- package/packages/cli/src/command.ts +1 -1
- package/packages/cli/src/commands/environment.ts +21 -9
- package/packages/cli/src/machine-view.ts +1 -1
- package/packages/cli/src/main.ts +2 -1
- package/packages/cli/src/oauth.ts +50 -7
- package/packages/cli/src/output.ts +1 -0
- package/packages/credential-store-file/README.md +60 -0
- package/packages/credential-store-file/package.json +21 -0
- package/packages/credential-store-file/src/activation.ts +29 -0
- package/packages/credential-store-file/src/index.ts +1 -0
- package/packages/credential-store-file/src/store.ts +89 -0
- package/packages/credential-store-os/src/store.ts +5 -1
- package/packages/fonts-open/src/surface.ts +8 -2
- package/packages/hyperframes/README.md +16 -2
- package/packages/hyperframes/src/browser-program.ts +6 -1
- package/packages/hyperframes/src/document.ts +31 -21
- package/packages/hyperframes/src/project.ts +11 -20
- package/packages/media-execution/README.md +7 -0
- package/packages/media-execution/src/execute.ts +5 -5
- package/packages/media-execution/src/index.ts +1 -1
- package/packages/media-execution/src/process-env.ts +20 -0
- package/packages/media-execution/src/surface.ts +83 -76
- package/packages/narrative/README.md +3 -1
- package/packages/narrative/src/schema.ts +1 -0
- package/packages/narrative/src/types.ts +2 -0
- package/packages/package-loader-node/README.md +10 -0
- package/packages/package-loader-node/src/index.ts +1 -0
- package/packages/package-loader-node/src/loader.ts +25 -5
- package/packages/package-loader-node/src/location.ts +11 -2
- package/packages/provider-hyperframes-local/README.md +81 -8
- package/packages/provider-hyperframes-local/package.json +13 -3
- package/packages/provider-hyperframes-local/src/activation.ts +20 -6
- package/packages/provider-hyperframes-local/src/browser-install.ts +5 -0
- package/packages/provider-hyperframes-local/src/browser.ts +118 -0
- package/packages/provider-hyperframes-local/src/capture-bootstrap.ts +4 -6
- package/packages/provider-hyperframes-local/src/capture-exit.ts +24 -0
- package/packages/provider-hyperframes-local/src/capture-process.ts +29 -52
- package/packages/provider-hyperframes-local/src/capture-worker.ts +2 -0
- package/packages/provider-hyperframes-local/src/capture.ts +12 -5
- package/packages/provider-hyperframes-local/src/opaque-capture.ts +46 -18
- package/packages/provider-hyperframes-local/src/options.ts +2 -2
- package/packages/provider-hyperframes-local/src/process-tree.ts +82 -0
- package/packages/provider-hyperframes-local/src/process.ts +22 -0
- package/packages/provider-hyperframes-local/src/program.ts +25 -33
- package/packages/provider-hyperframes-local/src/provider.ts +4 -7
- package/packages/provider-hyperframes-local/src/render.ts +9 -4
- package/packages/provider-whisperx-local/README.md +26 -10
- package/packages/provider-whisperx-local/src/activation.ts +10 -1
- package/packages/provider-whisperx-local/src/program.ts +8 -4
- package/packages/runtime-host-node/README.md +5 -0
- package/packages/runtime-host-node/src/index.ts +8 -2
- package/packages/runtime-host-node/src/packages.ts +13 -7
- package/packages/runtime-kit/README.md +6 -2
- package/packages/runtime-kit/src/index.ts +2 -2
- package/packages/runtime-local/README.md +16 -1
- package/packages/runtime-local/src/config.ts +1 -1
- package/packages/runtime-local/src/credentials.ts +10 -5
- package/packages/runtime-local/src/host.ts +2 -0
- package/packages/runtime-local/src/index.ts +1 -0
- package/packages/runtime-local/src/programs.ts +53 -17
- package/packages/script/README.md +81 -34
- package/packages/script/bin/migrate-0.2.mjs +90 -0
- package/packages/script/src/edit.ts +28 -60
- package/packages/script/src/lexical.ts +59 -130
- package/packages/script/src/manifest.ts +11 -11
- package/packages/script/src/narrative.ts +4 -2
- package/packages/script/src/parser.ts +140 -193
- package/packages/script/src/types.ts +2 -3
- package/packages/studio/src/preview/runtime-shim.ts +17 -3
- package/packages/studio/src/ui/code.ts +1 -1
- package/packages/studio/src/ui/main.ts +2 -2
- package/packages/studio/src/ui/markers.ts +3 -3
- package/packages/studio/src/ui/selection.ts +1 -1
- package/packages/studio/src/ui/syntax.ts +42 -21
- package/packages/temporal-markup/README.md +1 -1
- package/packages/video-cli/README.md +14 -6
- package/packages/video-cli/package.json +1 -0
- package/packages/video-cli/src/capture.ts +17 -2
- package/packages/video-cli/src/creation.ts +4 -6
- package/packages/video-cli/src/distribution.ts +3 -10
- package/packages/video-cli/src/media.ts +10 -3
- package/packages/video-cli/src/version.ts +1 -1
- package/packages/whisperx/README.md +56 -3
- package/packages/whisperx/src/component.ts +2 -5
- package/packages/whisperx/src/evidence.ts +4 -3
- package/packages/whisperx/src/index.ts +1 -0
- package/packages/whisperx/src/manifest.ts +2 -3
- package/packages/whisperx/src/surface.ts +3 -6
- package/packages/whisperx/src/types.ts +9 -1
- package/packages/yt-dlp/README.md +10 -3
- package/packages/yt-dlp/package.json +5 -0
- package/packages/yt-dlp/src/download.ts +11 -29
- package/packages/yt-dlp/src/environment.ts +36 -0
- package/packages/yt-dlp/src/index.ts +1 -0
- package/services/whisperx/README.md +45 -12
- package/services/whisperx/src/hypit_whisperx_service/application.py +3 -0
- package/services/whisperx/src/hypit_whisperx_service/check.py +7 -0
- package/services/whisperx/src/hypit_whisperx_service/config.py +7 -0
- package/services/whisperx/src/hypit_whisperx_service/engine.py +21 -5
- package/services/whisperx/src/hypit_whisperx_service/models.py +131 -0
- package/services/whisperx/src/hypit_whisperx_service/prepare.py +21 -3
- package/services/whisperx/src/hypit_whisperx_service/resources.py +19 -3
- package/services/yt-dlp/README.md +14 -15
- package/services/yt-dlp/pyproject.toml +1 -1
- package/services/yt-dlp/uv.lock +513 -2
|
@@ -1,54 +1,46 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { join } from "node:path";
|
|
2
3
|
|
|
3
4
|
import { probeMediaToolchain } from "@hypit/media-execution";
|
|
4
5
|
import type { ManagedProgram, ManagedProgramState } from "@hypit/runtime-kit";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
return new Promise((resolve) => {
|
|
8
|
-
execFile(executable, [...args], { timeout: 15_000, shell: false, windowsHide: true }, (error, stdout, stderr) => {
|
|
9
|
-
resolve(error === null
|
|
10
|
-
? { ok: true, output: stdout.trim() }
|
|
11
|
-
: { ok: false, output: (stderr.trim() || error.message).split("\n").at(-1) ?? "" });
|
|
12
|
-
});
|
|
13
|
-
});
|
|
14
|
-
}
|
|
6
|
+
import { browserCacheDirectory, browserDownloadBaseUrl, browserDownloadUrl, browserExecutablePath, configuredBrowserPath, requireBrowserExecutable, selectedBrowserVersion } from "./browser.js";
|
|
7
|
+
import type { BrowserOptions } from "./browser.js";
|
|
15
8
|
|
|
16
9
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* when this Provider is selected.
|
|
10
|
+
* This Provider owns browser selection and preparation. Probes never install;
|
|
11
|
+
* rendering receives the same selected executable instead of invoking engine discovery.
|
|
20
12
|
*/
|
|
21
13
|
export function localHyperframesBrowserProgram(
|
|
22
|
-
input: {
|
|
14
|
+
input: BrowserOptions & {
|
|
23
15
|
readonly id: string;
|
|
24
16
|
readonly nodePath: string;
|
|
25
|
-
readonly hyperframesCliPath: string | (() => string);
|
|
26
17
|
readonly ffprobePath: string;
|
|
27
18
|
readonly ffmpegPath?: string;
|
|
28
19
|
},
|
|
29
20
|
): ManagedProgram {
|
|
30
|
-
const
|
|
21
|
+
const version = selectedBrowserVersion(input);
|
|
22
|
+
const selectedPath = browserExecutablePath(input);
|
|
23
|
+
const baseUrl = browserDownloadBaseUrl(input);
|
|
31
24
|
const probeBrowser = async (): Promise<ManagedProgramState> => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const located = await run(input.nodePath, [executable, "browser", "path"]);
|
|
35
|
-
if (!located.ok) return { state: "down", detail: `HyperFrames browser is unavailable: ${located.output}` };
|
|
36
|
-
const path = located.output.trim();
|
|
37
|
-
if (path.length === 0 || path.includes("\n") || path.includes("\r")) {
|
|
38
|
-
return { state: "mismatch", detail: "HyperFrames returned an invalid browser path" };
|
|
39
|
-
}
|
|
40
|
-
const version = await run(path, ["--version"]);
|
|
41
|
-
if (!version.ok || version.output.length === 0) {
|
|
42
|
-
return { state: "mismatch", detail: `HyperFrames browser cannot start: ${version.output}` };
|
|
43
|
-
}
|
|
25
|
+
try { await requireBrowserExecutable(selectedPath, version); }
|
|
26
|
+
catch (error) { return { state: "down", detail: error instanceof Error ? error.message : String(error) }; }
|
|
44
27
|
return { state: "ready" };
|
|
45
28
|
};
|
|
46
29
|
return {
|
|
47
30
|
id: input.id,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
31
|
+
...(configuredBrowserPath(input) === undefined ? {
|
|
32
|
+
// Projects sharing this installation also share the existing Program lifecycle lock/logs.
|
|
33
|
+
stateRoot: join(browserCacheDirectory(input), ".hypit-render-program"),
|
|
34
|
+
installation: {
|
|
35
|
+
probe: probeBrowser,
|
|
36
|
+
commands: [{ label: `Install Chrome Headless Shell ${version} at ${selectedPath} from ${browserDownloadUrl(input)}`, command: input.nodePath, args: [
|
|
37
|
+
"--import", import.meta.resolve("tsx"),
|
|
38
|
+
"--import", new URL("./capture-bootstrap.ts", import.meta.url).href,
|
|
39
|
+
fileURLToPath(new URL("./browser-install.ts", import.meta.url)), browserCacheDirectory(input), version!,
|
|
40
|
+
...(baseUrl === undefined ? [] : [baseUrl]),
|
|
41
|
+
] }],
|
|
42
|
+
},
|
|
43
|
+
} : {}),
|
|
52
44
|
async probe(): Promise<ManagedProgramState> {
|
|
53
45
|
const browser = await probeBrowser();
|
|
54
46
|
if (browser.state !== "ready") return browser;
|
|
@@ -2,7 +2,6 @@ import { defineEndpointPackage } from "@hypit/endpoint-kit";
|
|
|
2
2
|
import { mediaTypes } from "@hypit/media";
|
|
3
3
|
import { renderHyperframesCapabilities, verifyHyperframesVisualRequest } from "@hypit/render-hyperframes";
|
|
4
4
|
import { canonicalize } from "@hypit/protocol";
|
|
5
|
-
import { resolveNodePackageExecutable } from "@hypit/package-loader-node";
|
|
6
5
|
import { renderHyperframesVisual, resolveExecutionOptions, renderWorkerLimit } from "./render.js";
|
|
7
6
|
import type { HyperframesExecutionOptions } from "./options.js";
|
|
8
7
|
import { renderProgressReporter } from "./progress.js";
|
|
@@ -14,17 +13,12 @@ export type CreateLocalHyperframesProviderOptions = HyperframesExecutionOptions
|
|
|
14
13
|
readonly pool?: string;
|
|
15
14
|
/** Used by managed browser installation, not frame capture. */
|
|
16
15
|
readonly nodePath?: string;
|
|
17
|
-
readonly hyperframesCliPath?: string;
|
|
18
16
|
/** Whole render requests admitted concurrently; independent of frame workers. */
|
|
19
17
|
readonly defaultConcurrency?: number;
|
|
20
18
|
/** Shared Chrome slots across Need executions using this pool. */
|
|
21
19
|
readonly browserCapacity?: number;
|
|
22
20
|
};
|
|
23
21
|
|
|
24
|
-
export function defaultHyperframesCliPath(): string {
|
|
25
|
-
return resolveNodePackageExecutable("hyperframes", "hyperframes", { from: import.meta.url });
|
|
26
|
-
}
|
|
27
|
-
|
|
28
22
|
export function createLocalHyperframesProvider(config: CreateLocalHyperframesProviderOptions) {
|
|
29
23
|
const execution = resolveExecutionOptions(config);
|
|
30
24
|
const pool = config.pool ?? config.instance ?? "hyperframes.local";
|
|
@@ -62,7 +56,10 @@ export function createLocalHyperframesProvider(config: CreateLocalHyperframesPro
|
|
|
62
56
|
: request.range.endFrameExclusive - request.range.startFrame;
|
|
63
57
|
const progress = renderProgressReporter(context.reportProgress, frameCount);
|
|
64
58
|
try {
|
|
65
|
-
const visual = await renderHyperframesVisual(request, { ...config,
|
|
59
|
+
const visual = await renderHyperframesVisual(request, { ...config,
|
|
60
|
+
...(execution.chromePath === undefined ? { browserVersion: execution.browserVersion! } : { chromePath: execution.chromePath }),
|
|
61
|
+
browserCacheDirectory: execution.browserCacheDirectory,
|
|
62
|
+
workers: execution.workers, maxWorkers,
|
|
66
63
|
resources: context.resources, onProgress: progress.onProgress,
|
|
67
64
|
...(context.reportDiagnostic === undefined ? {} : { onDiagnostic: context.reportDiagnostic }) });
|
|
68
65
|
return { value: { kind: "inline", value: canonicalize(visual) } };
|
|
@@ -6,7 +6,7 @@ import type { EndpointInvocationContext } from "@hypit/endpoint-kit";
|
|
|
6
6
|
import { stageHyperframesProject } from "@hypit/hyperframes/project";
|
|
7
7
|
import { sealRenderedVisual } from "@hypit/media";
|
|
8
8
|
import type { MediaFrameRange, RenderedVisual } from "@hypit/media";
|
|
9
|
-
import {
|
|
9
|
+
import { verifyCompositableSurfaceFile } from "@hypit/media-execution";
|
|
10
10
|
import { verifyHyperframesVisualRequest } from "@hypit/render-hyperframes";
|
|
11
11
|
import type { HyperframesVisualRequest } from "@hypit/render-hyperframes";
|
|
12
12
|
import { isStreamingResourceStore } from "@hypit/runtime";
|
|
@@ -15,6 +15,7 @@ import { assert, positiveInteger } from "./process.js";
|
|
|
15
15
|
import { runCaptureProcess } from "./capture-process.js";
|
|
16
16
|
import { finished } from "node:stream/promises";
|
|
17
17
|
import { autoWorkerLimit } from "./concurrency.js";
|
|
18
|
+
import { browserCacheDirectory, browserExecutablePath, configuredBrowserPath, requireBrowserExecutable, selectedBrowserVersion } from "./browser.js";
|
|
18
19
|
|
|
19
20
|
export type HyperframesRenderProgress =
|
|
20
21
|
| { readonly phase: "staging" | "encoding" | "storing"; readonly elapsedMs: number }
|
|
@@ -43,6 +44,9 @@ export function resolveExecutionOptions(options: HyperframesExecutionOptions) {
|
|
|
43
44
|
const browserGpu = options.browserGpu ?? "hardware";
|
|
44
45
|
assert(["auto", "software", "hardware"].includes(browserGpu), "HyperFrames browserGpu is invalid");
|
|
45
46
|
return {
|
|
47
|
+
chromePath: configuredBrowserPath(options),
|
|
48
|
+
browserVersion: selectedBrowserVersion(options),
|
|
49
|
+
browserCacheDirectory: browserCacheDirectory(options),
|
|
46
50
|
workers,
|
|
47
51
|
maxWorkers: workers === "auto" ? positiveInteger(options.maxWorkers ?? autoWorkerLimit(), "maxWorkers") : workers,
|
|
48
52
|
quality, browserGpu,
|
|
@@ -71,7 +75,7 @@ export async function renderHyperframesVisual(
|
|
|
71
75
|
options: RenderHyperframesVisualOptions,
|
|
72
76
|
): Promise<RenderedVisual> {
|
|
73
77
|
verifyHyperframesVisualRequest(request);
|
|
74
|
-
const config = resolveExecutionOptions(options);
|
|
78
|
+
const config = { ...resolveExecutionOptions(options), chromePath: browserExecutablePath(options) };
|
|
75
79
|
const { document } = request;
|
|
76
80
|
const range = request.range ?? { startFrame: 0, endFrameExclusive: document.frameCount };
|
|
77
81
|
const frameCount = range.endFrameExclusive - range.startFrame;
|
|
@@ -97,8 +101,9 @@ export async function renderHyperframesVisual(
|
|
|
97
101
|
};
|
|
98
102
|
try {
|
|
99
103
|
signal.throwIfAborted();
|
|
104
|
+
await requireBrowserExecutable(config.chromePath, config.browserVersion);
|
|
100
105
|
await options.onDiagnostic?.({ level: "info", message:
|
|
101
|
-
`Render ${frameCount} frames; workers ${config.workers} (limit ${renderWorkerLimit(config, frameCount, document.frameRate.numerator / document.frameRate.denominator)}); opaque fast PNG; quality ${config.quality}; GPU ${config.browserGpu}; encoder ${config.ffmpegPath}` });
|
|
106
|
+
`Render ${frameCount} frames; workers ${config.workers} (limit ${renderWorkerLimit(config, frameCount, document.frameRate.numerator / document.frameRate.denominator)}); opaque fast PNG; quality ${config.quality}; GPU ${config.browserGpu}; browser ${config.chromePath}; encoder ${config.ffmpegPath}` });
|
|
102
107
|
options.onProgress?.({ phase: "staging", elapsedMs: 0 });
|
|
103
108
|
work = await mkdtemp(join(tmpdir(), "hypit-hyperframes-local-"));
|
|
104
109
|
await stageHyperframesProject({ document, directory: work, signal,
|
|
@@ -109,7 +114,7 @@ export async function renderHyperframesVisual(
|
|
|
109
114
|
assert(bytes !== undefined, `HyperFrames Artifact ${artifact.resource} is unavailable`);
|
|
110
115
|
return bytes;
|
|
111
116
|
},
|
|
112
|
-
validateSurface: (surface,
|
|
117
|
+
validateSurface: (surface, path, probeSignal) => verifyCompositableSurfaceFile({ surface, path,
|
|
113
118
|
ffprobePath: config.ffprobePath, processTimeoutMs: config.processTimeoutMs,
|
|
114
119
|
maxProbeOutputBytes: config.maxProcessOutputBytes, signal: probeSignal! }),
|
|
115
120
|
});
|
|
@@ -43,7 +43,8 @@ For multilingual transcription with quality as the priority and suitable hardwar
|
|
|
43
43
|
"expectedModel": "large-v3",
|
|
44
44
|
"expectedDevice": "cuda",
|
|
45
45
|
"expectedCompute": "float16",
|
|
46
|
-
"expectedBatchSize": 4
|
|
46
|
+
"expectedBatchSize": 4,
|
|
47
|
+
"alignmentLanguages": ["zh", "en"]
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
```
|
|
@@ -57,7 +58,11 @@ or hosted service for the actual language, material and time available rather th
|
|
|
57
58
|
successively larger models as a routine sequence.
|
|
58
59
|
|
|
59
60
|
`expectedModel` selects speech recognition. WhisperX separately loads the language-specific
|
|
60
|
-
alignment model when that language is first requested.
|
|
61
|
+
alignment model from prepared local resources when that language is first requested. Set
|
|
62
|
+
`alignmentLanguages` to the language codes needed by this production before preparation. This is a
|
|
63
|
+
preparation demand, not a central language whitelist; already cached supported languages remain usable.
|
|
64
|
+
Omission prepares only ASR and sentence data, and does not imply every alignment model is installed.
|
|
65
|
+
A larger ASR model can improve the words
|
|
61
66
|
fed into alignment, but does not select a larger aligner or guarantee better timing by itself.
|
|
62
67
|
The health response identifies the loaded ASR configuration; it does not establish that every
|
|
63
68
|
language's alignment weights are cached. See [WhisperX usage](https://github.com/m-bain/whisperX#usage-)
|
|
@@ -65,8 +70,9 @@ and [faster-whisper deployment](https://github.com/SYSTRAN/faster-whisper#usage)
|
|
|
65
70
|
|
|
66
71
|
## Preparation and downloads
|
|
67
72
|
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
An explicit `programs up` or `runtime up` prepares and starts the selected packaged service in the
|
|
74
|
+
machine Program Home. Selecting an Endpoint alone does not install it. The environment is reused
|
|
75
|
+
across projects and sessions. The current local package and
|
|
70
76
|
service are trusted code; this is not a community-plugin sandbox.
|
|
71
77
|
|
|
72
78
|
`hypit paths` reports the machine `hostState`. The managed installation lives below
|
|
@@ -75,21 +81,31 @@ service are trusted code; this is not a community-plugin sandbox.
|
|
|
75
81
|
the service host includes its port. The default service is `http://127.0.0.1:8765` and exposes
|
|
76
82
|
its configuration through `/health`. Inspect an existing Profile's address and expected settings
|
|
77
83
|
when locating that service. A custom `serviceCommand` supplies its own installation and start command.
|
|
78
|
-
`hypit programs
|
|
79
|
-
and
|
|
84
|
+
`hypit programs prepare --endpoint <instance>` prepares the configured ASR model, requested alignment
|
|
85
|
+
models and sentence data without starting a process. It also works when the service is already online.
|
|
86
|
+
`programs up` prepares and starts the selected Programs; `programs status` reports process readiness.
|
|
87
|
+
Adding a language requires preparation, not a service restart, when its cache location is unchanged.
|
|
88
|
+
`modelCacheDirectory` optionally selects a root with `huggingface/` and `torch/` subdirectories;
|
|
89
|
+
relative paths resolve against the Runtime data root. Omission retains the upstream Hugging Face and
|
|
90
|
+
torch caches and their environment settings. Changing a running service's cache selection requires
|
|
91
|
+
an explicit restart when idle, just like changing its model or hardware. The managed installation
|
|
92
|
+
does not require a global `whisperx` shell command.
|
|
80
93
|
|
|
81
94
|
Preparation and service processes inherit the environment of the command starting them. Set
|
|
82
95
|
network and cache variables there before `programs up` or `runtime up`. A service already running
|
|
83
96
|
retains its earlier environment. Inspect its reported log before deciding whether a selected
|
|
84
97
|
Program needs restarting, and account for active work using it.
|
|
85
98
|
|
|
99
|
+
If NLTK refuses a proxied fetch during preparation, follow the service’s
|
|
100
|
+
[explicit proxy preparation](../../services/whisperx/README.md#preparing-sentence-data-through-a-proxy).
|
|
101
|
+
|
|
86
102
|
Preparation commands write `install.log`; the running service writes `program.log`, with stderr in
|
|
87
103
|
`program.err.log` on Windows. Inspect the stderr file for Python model-loading and download messages.
|
|
88
104
|
`programs status` reports these files as `installationLogPath`, `logPath` and `errorLogPath` when they
|
|
89
|
-
exist, even before installation finishes. Preparation notices name the Python environment and
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
105
|
+
exist, even before installation finishes. Preparation notices name the Python environment and selected model/language resources separately. The service logs the start and completion of ASR loading, transcription,
|
|
106
|
+
language-alignment model loading and alignment, with elapsed times. Downloads happen only in
|
|
107
|
+
preparation; startup and inference only load local resources. A missing resource fails with a
|
|
108
|
+
preparation instruction. Transfer details come from the underlying client during preparation.
|
|
93
109
|
The service health endpoint becomes available after ASR loading. A startup readiness wait expiring
|
|
94
110
|
can leave that process still loading. Its PID is recorded when spawned; repeated `up` observes it,
|
|
95
111
|
and `programs down` can stop it during loading. PID liveness and service readiness are separate facts.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
1
2
|
import {
|
|
2
3
|
createRuntimeEndpointAdapterFacet,
|
|
3
4
|
runtimeConfigExact,
|
|
@@ -18,7 +19,7 @@ const localWhisperXRuntimeAdapter = createRuntimeEndpointAdapterFacet({
|
|
|
18
19
|
"baseUrl", "expectedModel", "expectedDevice", "expectedCompute", "expectedBatchSize",
|
|
19
20
|
"expectedServiceVersion", "expectedWhisperXVersion",
|
|
20
21
|
"defaultConcurrency", "requestTimeoutMs", "maxResponseBytes",
|
|
21
|
-
"serviceCommand",
|
|
22
|
+
"serviceCommand", "alignmentLanguages", "modelCacheDirectory",
|
|
22
23
|
], "local WhisperX");
|
|
23
24
|
const baseUrl = runtimeConfigString(config.baseUrl, "WhisperX baseUrl");
|
|
24
25
|
if (baseUrl !== undefined) {
|
|
@@ -37,6 +38,12 @@ const localWhisperXRuntimeAdapter = createRuntimeEndpointAdapterFacet({
|
|
|
37
38
|
const defaultConcurrency = runtimeConfigPositiveInteger(config.defaultConcurrency, "WhisperX defaultConcurrency");
|
|
38
39
|
const requestTimeoutMs = runtimeConfigPositiveInteger(config.requestTimeoutMs, "WhisperX requestTimeoutMs");
|
|
39
40
|
const maxResponseBytes = runtimeConfigPositiveInteger(config.maxResponseBytes, "WhisperX maxResponseBytes");
|
|
41
|
+
const modelCacheDirectory = runtimeConfigString(config.modelCacheDirectory, "WhisperX modelCacheDirectory");
|
|
42
|
+
const alignmentLanguages = config.alignmentLanguages;
|
|
43
|
+
if (alignmentLanguages !== undefined && (!Array.isArray(alignmentLanguages)
|
|
44
|
+
|| alignmentLanguages.some((item) => typeof item !== "string" || !/^[a-z]+$/u.test(item)))) {
|
|
45
|
+
throw new Error("WhisperX alignmentLanguages must be an array of lowercase language codes");
|
|
46
|
+
}
|
|
40
47
|
const serviceCommandValue = config.serviceCommand;
|
|
41
48
|
if (serviceCommandValue !== undefined
|
|
42
49
|
&& (!Array.isArray(serviceCommandValue) || serviceCommandValue.length === 0
|
|
@@ -80,6 +87,8 @@ const localWhisperXRuntimeAdapter = createRuntimeEndpointAdapterFacet({
|
|
|
80
87
|
expectedServiceVersion: selectedServiceVersion,
|
|
81
88
|
expectedWhisperXVersion: selectedWhisperXVersion,
|
|
82
89
|
...(serviceCommand === undefined ? {} : { serviceCommand }),
|
|
90
|
+
...(alignmentLanguages === undefined ? {} : { alignmentLanguages: alignmentLanguages as string[] }),
|
|
91
|
+
...(modelCacheDirectory === undefined ? {} : { modelCacheDirectory: resolve(context.dataRoot, modelCacheDirectory) }),
|
|
83
92
|
}),
|
|
84
93
|
};
|
|
85
94
|
},
|
|
@@ -52,6 +52,8 @@ export type LocalWhisperXProgramOptions = {
|
|
|
52
52
|
readonly expectedServiceVersion: string;
|
|
53
53
|
readonly expectedWhisperXVersion: string;
|
|
54
54
|
readonly serviceCommand?: ManagedProgramCommand;
|
|
55
|
+
readonly alignmentLanguages?: readonly string[];
|
|
56
|
+
readonly modelCacheDirectory?: string;
|
|
55
57
|
};
|
|
56
58
|
|
|
57
59
|
export function localWhisperXProgram(options: LocalWhisperXProgramOptions): ManagedProgram {
|
|
@@ -85,11 +87,13 @@ export function localWhisperXProgram(options: LocalWhisperXProgramOptions): Mana
|
|
|
85
87
|
HYPIT_WHISPERX_COMPUTE: expected.compute,
|
|
86
88
|
HYPIT_WHISPERX_BATCH_SIZE: String(expected.batchSize),
|
|
87
89
|
HYPIT_WHISPERX_NLTK_DATA: nltkData,
|
|
90
|
+
...(options.modelCacheDirectory === undefined ? {} : { HYPIT_WHISPERX_MODEL_CACHE: options.modelCacheDirectory }),
|
|
91
|
+
HYPIT_WHISPERX_ALIGNMENT_LANGUAGES: (options.alignmentLanguages ?? []).join(" "),
|
|
88
92
|
};
|
|
89
93
|
const check: ManagedProgramCommand = {
|
|
90
94
|
command: pythonEnvironmentCommand(environment, "hypit-whisperx-check"),
|
|
91
|
-
args: [],
|
|
92
|
-
env:
|
|
95
|
+
args: ["--models"],
|
|
96
|
+
env: serviceEnvironment,
|
|
93
97
|
};
|
|
94
98
|
const managedStart: ManagedProgramCommand = {
|
|
95
99
|
command: pythonEnvironmentCommand(environment, "hypit-whisperx-service"),
|
|
@@ -131,10 +135,10 @@ export function localWhisperXProgram(options: LocalWhisperXProgramOptions): Mana
|
|
|
131
135
|
args: ["sync", "--project", localWhisperXManagedProject, "--frozen", "--no-editable"],
|
|
132
136
|
env: { UV_PROJECT_ENVIRONMENT: environment },
|
|
133
137
|
}, {
|
|
134
|
-
label:
|
|
138
|
+
label: `Prepare WhisperX ${expected.model} and alignment resources for ${(options.alignmentLanguages ?? []).join(", ") || "no selected languages"}`,
|
|
135
139
|
command: pythonEnvironmentCommand(environment, "hypit-whisperx-prepare"),
|
|
136
140
|
args: ["--nltk-data", nltkData],
|
|
137
|
-
env:
|
|
141
|
+
env: serviceEnvironment,
|
|
138
142
|
}],
|
|
139
143
|
},
|
|
140
144
|
} : {}),
|
|
@@ -53,3 +53,8 @@ to start execution, then `const runtime = await host.createRuntime(); await runt
|
|
|
53
53
|
to submit work. `RuntimeHostExecution` does not run a second embedded Worker loop. A local carrier
|
|
54
54
|
may stop accepting new Builds and drain; its assigned Builds stay in place and all carriers continue
|
|
55
55
|
using the same resource accounting. Process policy belongs to the local Runtime, not this Host ABI.
|
|
56
|
+
|
|
57
|
+
`prepareHostPackages` accepts exact specifier strings or `{ specifier, env }` installation inputs.
|
|
58
|
+
The optional environment is applied only to that npm child process, merged over its inherited
|
|
59
|
+
environment. It is neither persisted as installation status nor returned in package reports.
|
|
60
|
+
The shared installer does not interpret SDK-specific variables; their owning packages supply them.
|
|
@@ -70,17 +70,20 @@ export type BuildView = {
|
|
|
70
70
|
}[];
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
-
export type
|
|
73
|
+
export type RuntimeHostCredentialDescription = {
|
|
74
74
|
readonly endpoint: string;
|
|
75
75
|
readonly slot: string;
|
|
76
76
|
readonly label: string;
|
|
77
77
|
readonly kind: "secret" | "json";
|
|
78
78
|
readonly ref: CredentialRef;
|
|
79
79
|
readonly acquisition?: CredentialAcquisition;
|
|
80
|
-
readonly configured: boolean;
|
|
81
80
|
readonly writable: boolean;
|
|
82
81
|
};
|
|
83
82
|
|
|
83
|
+
export type RuntimeHostCredentialStatus = RuntimeHostCredentialDescription & {
|
|
84
|
+
readonly configured: boolean;
|
|
85
|
+
};
|
|
86
|
+
|
|
84
87
|
export type RuntimeHostControl = {
|
|
85
88
|
/** Build evidence while active. Finished evidence is read through its Result Repository. */
|
|
86
89
|
logs?(build: string, lines: number): Promise<import("@hypit/runtime").ExecutionLogView | undefined>;
|
|
@@ -105,6 +108,8 @@ export type RuntimeHostResultControl = {
|
|
|
105
108
|
};
|
|
106
109
|
|
|
107
110
|
export type RuntimeHostCredentialControl = {
|
|
111
|
+
/** Endpoint declarations and Store write capability; never reads an existing secret. */
|
|
112
|
+
describeCredentials(endpoint?: string): Promise<readonly RuntimeHostCredentialDescription[]>;
|
|
108
113
|
credentials(endpoint?: string): Promise<readonly RuntimeHostCredentialStatus[]>;
|
|
109
114
|
putCredential(endpoint: string, slot: string, secret: string): Promise<RuntimeHostCredentialStatus>;
|
|
110
115
|
deleteCredential(endpoint: string, slot: string): Promise<{
|
|
@@ -240,6 +245,7 @@ export type RuntimeController = {
|
|
|
240
245
|
down(options?: { readonly maxWaitMs?: number }): Promise<RuntimeWorkerState>;
|
|
241
246
|
};
|
|
242
247
|
readonly programs: {
|
|
248
|
+
prepare(options?: { readonly endpoints?: readonly string[]; readonly onProgress?: (event: ManagedProgramProgress) => void }): Promise<{ readonly dataRoot: string; readonly programs: readonly ManagedProgramReport[] }>;
|
|
243
249
|
up(options: {
|
|
244
250
|
readonly maxWaitMs?: number;
|
|
245
251
|
readonly onProgress?: (event: ManagedProgramProgress) => void;
|
|
@@ -73,7 +73,7 @@ export async function inspectHostPackage(
|
|
|
73
73
|
: undefined;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
async function runNpm(root: string, specifiers: readonly string[], logPath: string): Promise<void> {
|
|
76
|
+
async function runNpm(root: string, specifiers: readonly string[], logPath: string, env?: Readonly<Record<string, string>>): Promise<void> {
|
|
77
77
|
// npm's prefix and cwd must denote the same physical project (not /tmp vs /private/tmp).
|
|
78
78
|
const cwd = await realpath(root);
|
|
79
79
|
const npmArgs = [
|
|
@@ -96,6 +96,7 @@ async function runNpm(root: string, specifiers: readonly string[], logPath: stri
|
|
|
96
96
|
cwd,
|
|
97
97
|
shell: false,
|
|
98
98
|
windowsHide: true,
|
|
99
|
+
env: { ...process.env, ...env },
|
|
99
100
|
stdio: ["ignore", log.fd, log.fd],
|
|
100
101
|
});
|
|
101
102
|
child.on("error", (error) => reject(new Error(`Cannot start npm: ${error.message}. Log: ${logPath}`, { cause: error })));
|
|
@@ -114,21 +115,26 @@ async function runNpm(root: string, specifiers: readonly string[], logPath: stri
|
|
|
114
115
|
* package.json, lockfile and dependencies; Hypit keeps no parallel inventory.
|
|
115
116
|
*/
|
|
116
117
|
export async function prepareHostPackages(
|
|
117
|
-
specifiers: readonly string[],
|
|
118
|
+
specifiers: readonly (string | { readonly specifier: string; readonly env?: Readonly<Record<string, string>> })[],
|
|
118
119
|
options: {
|
|
119
120
|
readonly root: string;
|
|
120
121
|
readonly onProgress?: (event: HostPackageProgress) => void;
|
|
121
122
|
},
|
|
122
123
|
): Promise<readonly HostPackageReport[]> {
|
|
123
124
|
const root = resolve(options.root);
|
|
124
|
-
const bySpecifier = new Map<string, RegistryPackageSpec>();
|
|
125
|
+
const bySpecifier = new Map<string, RegistryPackageSpec & { readonly env?: Readonly<Record<string, string>> }>();
|
|
125
126
|
for (const specifier of specifiers) {
|
|
126
|
-
const parsed = parseRegistryPackageSpec(specifier);
|
|
127
|
-
bySpecifier.
|
|
127
|
+
const parsed = parseRegistryPackageSpec(typeof specifier === "string" ? specifier : specifier.specifier);
|
|
128
|
+
const env = { ...bySpecifier.get(parsed.specifier)?.env };
|
|
129
|
+
for (const [key, value] of Object.entries(typeof specifier === "string" ? {} : specifier.env ?? {})) {
|
|
130
|
+
if (env[key] !== undefined && env[key] !== value) throw new Error(`Conflicting installation environment ${key} for ${parsed.specifier}`);
|
|
131
|
+
env[key] = value;
|
|
132
|
+
}
|
|
133
|
+
bySpecifier.set(parsed.specifier, { ...parsed, ...(Object.keys(env).length === 0 ? {} : { env }) });
|
|
128
134
|
}
|
|
129
135
|
const required = [...bySpecifier.values()].sort((left, right) => left.specifier.localeCompare(right.specifier));
|
|
130
136
|
const reports: HostPackageReport[] = [];
|
|
131
|
-
for (const item of required) {
|
|
137
|
+
for (const { env, ...item } of required) {
|
|
132
138
|
const installation = externalPackageInstallRoot(root, item.name, item.version);
|
|
133
139
|
options.onProgress?.({ ...item, phase: "checking" });
|
|
134
140
|
const missing = await installedVersion(installation, item.name) !== item.version;
|
|
@@ -142,7 +148,7 @@ export async function prepareHostPackages(
|
|
|
142
148
|
dependencies: { [item.name]: item.version },
|
|
143
149
|
}, null, 2)}\n`, "utf8");
|
|
144
150
|
options.onProgress?.({ ...item, phase: "installing", logPath });
|
|
145
|
-
await runNpm(installation, [item.specifier], logPath);
|
|
151
|
+
await runNpm(installation, [item.specifier], logPath, env);
|
|
146
152
|
}
|
|
147
153
|
const version = await installedVersion(installation, item.name);
|
|
148
154
|
if (version !== item.version) {
|
|
@@ -43,8 +43,12 @@ video package by name.
|
|
|
43
43
|
|
|
44
44
|
`ManagedProgram.installation.prepareBeforeStart` lets a Provider reconcile its installed environment
|
|
45
45
|
before a cold start, even when the installation probe already passes. The declared commands use the
|
|
46
|
-
Provider's ordinary package manager.
|
|
47
|
-
|
|
46
|
+
Provider's ordinary package manager. `programs prepare` runs the declared installation independently
|
|
47
|
+
of process startup; `programs up` also checks its resources when the process is already healthy.
|
|
48
|
+
The installation probe covers the owner's selected execution resources, while the process probe
|
|
49
|
+
reports service readiness. Cold reconciliation still applies only before starting a new process.
|
|
50
|
+
Runtime does not name models, inspect source files or infer implementation versions. Preparation
|
|
51
|
+
may acquire resources; execution must consume already prepared resources and report what is missing.
|
|
48
52
|
`ManagedProgramCommand.label` optionally names a command's purpose for progress and log headings.
|
|
49
53
|
It is display text supplied by the Program owner, not a phase to persist or interpret. Runtime
|
|
50
54
|
reports generic process/probe facts; the service itself owns domain-specific progress in its logs.
|
|
@@ -42,11 +42,11 @@ export type ManagedProgramCommand = {
|
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
export type ManagedProgramInstallation = {
|
|
45
|
-
/**
|
|
45
|
+
/** Read-only inspection of the selected environment and resources; no downloads or receipts. */
|
|
46
46
|
probe(): Promise<ManagedProgramState>;
|
|
47
47
|
/** Reconcile an installed environment before a cold start, using its package manager's cache. */
|
|
48
48
|
readonly prepareBeforeStart?: boolean;
|
|
49
|
-
/**
|
|
49
|
+
/** Explicit preparation commands, run for missing resources or cold reconciliation above. */
|
|
50
50
|
readonly commands: readonly ManagedProgramCommand[];
|
|
51
51
|
};
|
|
52
52
|
|
|
@@ -242,7 +242,12 @@ controller returned by `await host.controller()`, and calls `build(...)` on the
|
|
|
242
242
|
`await host.createRuntime()`. There is one production process-lifecycle path.
|
|
243
243
|
|
|
244
244
|
Managed Programs have independent lifetimes. A healthy WhisperX service keeps its model loaded;
|
|
245
|
-
`
|
|
245
|
+
`programs prepare --endpoint <instance>` prepares selected resources without starting or stopping
|
|
246
|
+
that service. Its installation probe is independent of process health, so an online service does not
|
|
247
|
+
hide a newly requested language model. `programs up` prepares missing resources and starts the service
|
|
248
|
+
if needed. `prepareBeforeStart` additionally reconciles a cold installation through the package
|
|
249
|
+
manager's source-aware synchronization. The Provider owns the resource choices and commands;
|
|
250
|
+
Runtime only invokes them under the existing lifecycle lock.
|
|
246
251
|
`runtime down` stops the coordinator and asks its executors to stop; Programs are stopped separately.
|
|
247
252
|
Distribution changes and shell environment changes still concern the coordinator's bootstrap process.
|
|
248
253
|
Inspect active work before restarting it. Environment-backed credentials use that inherited process
|
|
@@ -258,3 +263,13 @@ capacity release and every completed Result. They use the production execution p
|
|
|
258
263
|
This is a load experiment rather than part of every package regression. It reports fixture preparation,
|
|
259
264
|
local progress, completion, executor RSS and cleanup separately; total test time also includes creating
|
|
260
265
|
and removing the temporary Result repositories. Neither suite uses a completion-time performance target.
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
### Credential management without reading the old value
|
|
269
|
+
|
|
270
|
+
`openCredentials(endpoint)` opens only the selected Endpoint's credential control.
|
|
271
|
+
`describeCredentials(endpoint)` returns its declared slots and each Store's write capability without
|
|
272
|
+
resolving secrets. `credentials(endpoint)` also reads current values to report status and propagates
|
|
273
|
+
read failures. Login and logout use the former: a damaged old credential cannot prevent replacement
|
|
274
|
+
or deletion. Successful writes and deletions report their operation's result without rereading the
|
|
275
|
+
secret. Execution still resolves credentials normally and reports Store errors.
|
|
@@ -462,7 +462,7 @@ export async function prepareRuntimeConfigPackages(
|
|
|
462
462
|
...document.endpoints.map((item) => item.use),
|
|
463
463
|
...credentials.credentials.map((item) => item.use),
|
|
464
464
|
], options.distributionPackageRoot);
|
|
465
|
-
return await prepareHostPackages(requirements
|
|
465
|
+
return await prepareHostPackages(requirements, {
|
|
466
466
|
root: hypitHostPackageRoot(options.hostStateRoot),
|
|
467
467
|
...(options.onProgress === undefined ? {} : { onProgress: options.onProgress }),
|
|
468
468
|
});
|
|
@@ -28,16 +28,21 @@ export function createLocalCredentialControl(
|
|
|
28
28
|
: `Endpoint ${endpoint} repeats credential slot ${slot}`);
|
|
29
29
|
return matches[0]!;
|
|
30
30
|
};
|
|
31
|
-
const
|
|
31
|
+
const describe = async (item: typeof descriptions[number]) => ({
|
|
32
32
|
...structuredClone(item),
|
|
33
|
-
configured: await options.credentialStore.resolve(item.ref) !== undefined,
|
|
34
33
|
writable: await writableCredentialStore(options.credentialStore, item.ref) !== undefined,
|
|
35
34
|
});
|
|
36
35
|
|
|
37
36
|
return {
|
|
37
|
+
async describeCredentials(endpoint) {
|
|
38
|
+
return await Promise.all(descriptions.filter((item) => endpoint === undefined || item.endpoint === endpoint).map(describe));
|
|
39
|
+
},
|
|
38
40
|
async credentials(endpoint) {
|
|
39
41
|
const selected = descriptions.filter((item) => endpoint === undefined || item.endpoint === endpoint);
|
|
40
|
-
return await Promise.all(selected.map(
|
|
42
|
+
return await Promise.all(selected.map(async (item) => ({
|
|
43
|
+
...await describe(item),
|
|
44
|
+
configured: await options.credentialStore.resolve(item.ref) !== undefined,
|
|
45
|
+
})));
|
|
41
46
|
},
|
|
42
47
|
async putCredential(endpoint, slot, secret) {
|
|
43
48
|
assert(secret.length > 0, "credential secret is empty");
|
|
@@ -45,14 +50,14 @@ export function createLocalCredentialControl(
|
|
|
45
50
|
const store = await writableCredentialStore(options.credentialStore, item.ref);
|
|
46
51
|
assert(store !== undefined, `CredentialStore ${item.ref.store} is not writable`);
|
|
47
52
|
await store.put(item.ref, { secret });
|
|
48
|
-
return
|
|
53
|
+
return { ...structuredClone(item), writable: true, configured: true };
|
|
49
54
|
},
|
|
50
55
|
async deleteCredential(endpoint, slot) {
|
|
51
56
|
const item = credential(endpoint, slot);
|
|
52
57
|
const store = await writableCredentialStore(options.credentialStore, item.ref);
|
|
53
58
|
assert(store !== undefined, `CredentialStore ${item.ref.store} is not writable`);
|
|
54
59
|
const deleted = await store.delete(item.ref);
|
|
55
|
-
return { deleted, credential:
|
|
60
|
+
return { deleted, credential: { ...structuredClone(item), writable: true, configured: false } };
|
|
56
61
|
},
|
|
57
62
|
close() {
|
|
58
63
|
return options.close?.();
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
} from "./config.js";
|
|
24
24
|
import {
|
|
25
25
|
bringManagedProgramsUp,
|
|
26
|
+
prepareManagedPrograms,
|
|
26
27
|
reportManagedPrograms,
|
|
27
28
|
takeManagedProgramsDown,
|
|
28
29
|
} from "./programs.js";
|
|
@@ -82,6 +83,7 @@ export async function openLocalRuntimeHost(
|
|
|
82
83
|
),
|
|
83
84
|
},
|
|
84
85
|
programs: {
|
|
86
|
+
prepare: async (programOptions) => await prepareManagedPrograms(profile, { ...programOptions, packageRoot, ...distribution }),
|
|
85
87
|
up: async (programOptions) => await bringManagedProgramsUp(profile, { ...programOptions, packageRoot, ...distribution }),
|
|
86
88
|
down: async (scope) => await takeManagedProgramsDown(profile, { ...scope, packageRoot, ...distribution }),
|
|
87
89
|
report: async (scope) => await reportManagedPrograms(profile, { ...scope, packageRoot, ...distribution }),
|