@hypit/hypit 0.1.14 → 0.2.1
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 +1 -1
- 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/provider-package/hypit.runtime.json +2 -2
- 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/src/arguments.ts +2 -2
- package/packages/cli/src/command.ts +1 -1
- package/packages/cli/src/commands/environment.ts +9 -2
- package/packages/cli/src/machine-view.ts +1 -1
- package/packages/cli/src/output.ts +1 -0
- package/packages/credential-store-file/README.md +3 -1
- package/packages/credential-store-os/README.md +4 -0
- package/packages/credential-store-os/src/store.ts +5 -1
- package/packages/credential-store-platform/README.md +62 -0
- package/packages/credential-store-platform/package.json +23 -0
- package/packages/credential-store-platform/src/activation.ts +39 -0
- package/packages/credential-store-platform/src/index.ts +1 -0
- package/packages/credential-store-platform/src/store.ts +89 -0
- 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/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/src/index.ts +1 -0
- package/packages/runtime-kit/README.md +6 -2
- package/packages/runtime-kit/src/index.ts +2 -2
- package/packages/runtime-local/README.md +6 -1
- 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/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 +9 -5
- package/packages/video-cli/src/capture.ts +17 -2
- package/packages/video-cli/src/creation.ts +4 -6
- package/packages/video-cli/src/media.ts +10 -3
- 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 +9 -3
- package/packages/yt-dlp/package.json +2 -1
- package/packages/yt-dlp/src/download.ts +10 -24
- 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
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { join, resolve } from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
createRuntimeCredentialStoreAdapterFacet,
|
|
4
|
+
runtimeConfigExact,
|
|
5
|
+
runtimeConfigObject,
|
|
6
|
+
runtimeConfigString,
|
|
7
|
+
} from "@hypit/runtime-kit";
|
|
8
|
+
import { PlatformCredentialStore } from "./store.js";
|
|
9
|
+
|
|
10
|
+
const platformCredentialStoreAdapter = createRuntimeCredentialStoreAdapterFacet({
|
|
11
|
+
use: "@hypit/credential-store-platform",
|
|
12
|
+
validate(context) {
|
|
13
|
+
const config = runtimeConfigObject(context.config, "platform CredentialStore");
|
|
14
|
+
runtimeConfigExact(config, ["path", "service"], "platform CredentialStore");
|
|
15
|
+
runtimeConfigString(config.path, "platform credential path");
|
|
16
|
+
runtimeConfigString(config.service, "platform credential service");
|
|
17
|
+
},
|
|
18
|
+
open(context) {
|
|
19
|
+
const config = runtimeConfigObject(context.config, "platform CredentialStore");
|
|
20
|
+
const path = runtimeConfigString(config.path, "platform credential path");
|
|
21
|
+
const service = runtimeConfigString(config.service, "platform credential service");
|
|
22
|
+
return {
|
|
23
|
+
value: new PlatformCredentialStore({
|
|
24
|
+
// Linux uses the same directory the file Store uses by default, so a Profile
|
|
25
|
+
// that switches between them on Linux finds the credential it stored.
|
|
26
|
+
directory: path === undefined
|
|
27
|
+
? join(context.hostStateRoot, "credentials")
|
|
28
|
+
: resolve(context.hostStateRoot, path),
|
|
29
|
+
...(service === undefined ? {} : { service }),
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const hypitPackage = {
|
|
36
|
+
format: "hypit.node-package@1" as const,
|
|
37
|
+
hostFacets: [platformCredentialStoreAdapter],
|
|
38
|
+
};
|
|
39
|
+
export default hypitPackage;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { PlatformCredentialStore } from "./store.js";
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {
|
|
2
|
+
credentialRef,
|
|
3
|
+
verifyCredentialRef,
|
|
4
|
+
} from "@hypit/runtime";
|
|
5
|
+
import type {
|
|
6
|
+
CredentialRef,
|
|
7
|
+
CredentialValue,
|
|
8
|
+
WritableCredentialStore,
|
|
9
|
+
} from "@hypit/runtime";
|
|
10
|
+
import { FileCredentialStore } from "@hypit/credential-store-file";
|
|
11
|
+
import { OsCredentialStore } from "@hypit/credential-store-os";
|
|
12
|
+
import type {
|
|
13
|
+
OsCredentialDeleter,
|
|
14
|
+
OsCredentialReader,
|
|
15
|
+
OsCredentialWriter,
|
|
16
|
+
} from "@hypit/credential-store-os";
|
|
17
|
+
|
|
18
|
+
export type PlatformCredentialStoreOptions = {
|
|
19
|
+
/** Directory holding the documents this Store selects on Linux. */
|
|
20
|
+
readonly directory: string;
|
|
21
|
+
/** Service the locker's entries are filed under; the OS Store's own default applies where absent. */
|
|
22
|
+
readonly service?: string;
|
|
23
|
+
/**
|
|
24
|
+
* The platform to select for, defaulting to the running one. A Profile never states this: it is
|
|
25
|
+
* separate so the selection can be exercised on one machine for every platform.
|
|
26
|
+
*/
|
|
27
|
+
readonly platform?: NodeJS.Platform;
|
|
28
|
+
/** Locker backend to read and write with, where the platform's own locker is not the one to use. */
|
|
29
|
+
readonly locker?: {
|
|
30
|
+
readonly read: OsCredentialReader;
|
|
31
|
+
readonly write: OsCredentialWriter;
|
|
32
|
+
readonly remove: OsCredentialDeleter;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* An explicitly selected storage policy: OS credentials on macOS/Windows, files on Linux.
|
|
38
|
+
* The selected backend's errors propagate; other platforms are unsupported by this package.
|
|
39
|
+
*/
|
|
40
|
+
export class PlatformCredentialStore implements WritableCredentialStore {
|
|
41
|
+
readonly #delegate: WritableCredentialStore;
|
|
42
|
+
readonly #backing: "os" | "file";
|
|
43
|
+
|
|
44
|
+
constructor(options: PlatformCredentialStoreOptions) {
|
|
45
|
+
const platform = options.platform ?? process.platform;
|
|
46
|
+
if (platform === "darwin" || platform === "win32") {
|
|
47
|
+
this.#backing = "os";
|
|
48
|
+
this.#delegate = new OsCredentialStore({
|
|
49
|
+
...(options.service === undefined ? {} : { service: options.service }),
|
|
50
|
+
...options.locker,
|
|
51
|
+
});
|
|
52
|
+
} else if (platform === "linux") {
|
|
53
|
+
this.#backing = "file";
|
|
54
|
+
this.#delegate = new FileCredentialStore(options.directory);
|
|
55
|
+
} else {
|
|
56
|
+
throw new Error(`Platform CredentialStore does not support ${platform}; select an explicit CredentialStore supported by this host`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
owns(ref: CredentialRef): boolean {
|
|
61
|
+
return ref.store === "platform";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Each delegate owns its own name and refuses refs filed under another, so a ref this Store owns
|
|
66
|
+
* is retargeted before the delegate sees it. Only the name changes; the opaque key is untouched.
|
|
67
|
+
*/
|
|
68
|
+
#translated(ref: CredentialRef): CredentialRef {
|
|
69
|
+
return credentialRef(this.#backing, ref.key);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async resolve(ref: CredentialRef): Promise<CredentialValue | undefined> {
|
|
73
|
+
verifyCredentialRef(ref);
|
|
74
|
+
if (!this.owns(ref)) return undefined;
|
|
75
|
+
return await this.#delegate.resolve(this.#translated(ref));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async put(ref: CredentialRef, value: CredentialValue): Promise<void> {
|
|
79
|
+
verifyCredentialRef(ref);
|
|
80
|
+
if (!this.owns(ref)) throw new Error(`Platform CredentialStore does not own ${ref.store}`);
|
|
81
|
+
await this.#delegate.put(this.#translated(ref), value);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async delete(ref: CredentialRef): Promise<boolean> {
|
|
85
|
+
verifyCredentialRef(ref);
|
|
86
|
+
if (!this.owns(ref)) throw new Error(`Platform CredentialStore does not own ${ref.store}`);
|
|
87
|
+
return await this.#delegate.delete(this.#translated(ref));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -26,4 +26,6 @@ no Tokens. Authored order is independent of gaps, overlap or placement order in
|
|
|
26
26
|
Caption uses this content query and the Narrative's display correspondence to select complete
|
|
27
27
|
subtitle units. Timeline projection uses the same semantic references to locate events in physical
|
|
28
28
|
time. Neither operation substitutes for the other. Token lookup returns Tokens, not reconstructed
|
|
29
|
-
source prose:
|
|
29
|
+
source prose: comments and source-preserving edits remain the author language's concern. Caption
|
|
30
|
+
Display Words separately carry `separatorBefore` (`""` or `" "`) so consumers can reconstruct the
|
|
31
|
+
authored display spelling without guessing from Token boundaries or importing an author parser.
|
|
@@ -29,6 +29,7 @@ export const narrativeExcerptSchema: ValueSchema = object({
|
|
|
29
29
|
tokenStart: { schema: integer }, tokenEndExclusive: { schema: integer },
|
|
30
30
|
});
|
|
31
31
|
const captionDisplayWord = object({
|
|
32
|
+
separatorBefore: { schema: { kind: "string", enum: ["", " "] } },
|
|
32
33
|
id: { schema: string }, unitId: { schema: string }, segmentId: { schema: string },
|
|
33
34
|
turnId: { schema: string }, role: { schema: string, optional: true }, text: { schema: string },
|
|
34
35
|
attributes: { schema: { kind: "array", items: object({
|
|
@@ -50,6 +50,8 @@ export type NarrativeMomentRef = NarrativeMoment & {
|
|
|
50
50
|
|
|
51
51
|
/** One author-visible word surface. Punctuation owned by the surface is preserved. */
|
|
52
52
|
export type CaptionDisplayWord = {
|
|
53
|
+
/** Authored display separator from the preceding word; ignored at a displayed line start. */
|
|
54
|
+
readonly separatorBefore: "" | " ";
|
|
53
55
|
readonly id: string;
|
|
54
56
|
readonly unitId: string;
|
|
55
57
|
readonly segmentId: string;
|
|
@@ -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
|
} : {}),
|
|
@@ -245,6 +245,7 @@ export type RuntimeController = {
|
|
|
245
245
|
down(options?: { readonly maxWaitMs?: number }): Promise<RuntimeWorkerState>;
|
|
246
246
|
};
|
|
247
247
|
readonly programs: {
|
|
248
|
+
prepare(options?: { readonly endpoints?: readonly string[]; readonly onProgress?: (event: ManagedProgramProgress) => void }): Promise<{ readonly dataRoot: string; readonly programs: readonly ManagedProgramReport[] }>;
|
|
248
249
|
up(options: {
|
|
249
250
|
readonly maxWaitMs?: number;
|
|
250
251
|
readonly onProgress?: (event: ManagedProgramProgress) => void;
|
|
@@ -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
|
|
@@ -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 }),
|
|
@@ -293,6 +293,8 @@ async function bringUpOwned(
|
|
|
293
293
|
onProgress?.({ id: program.id, phase: "checking" });
|
|
294
294
|
const initial = await program.probe();
|
|
295
295
|
if (initial.state === "ready") {
|
|
296
|
+
const prepared = await prepareOwned(root, program, endpoint, onProgress);
|
|
297
|
+
if (prepared.state.state !== "ready") return prepared;
|
|
296
298
|
onProgress?.({ id: program.id, phase: "ready" });
|
|
297
299
|
return { ...base, action: "already-running", state: initial };
|
|
298
300
|
}
|
|
@@ -306,6 +308,10 @@ async function bringUpOwned(
|
|
|
306
308
|
const installLogPath = join(directory(root, program), "install.log");
|
|
307
309
|
const existingPid = await readPid(root, program);
|
|
308
310
|
if (existingPid !== undefined && processAlive(existingPid)) {
|
|
311
|
+
if (program.installation !== undefined) {
|
|
312
|
+
const prepared = await prepareOwned(root, program, endpoint, onProgress);
|
|
313
|
+
if (prepared.state.state !== "ready") return prepared;
|
|
314
|
+
}
|
|
309
315
|
release();
|
|
310
316
|
onProgress?.({ id: program.id, phase: "waiting", logPath,
|
|
311
317
|
detail: `observing existing process ${existingPid}` });
|
|
@@ -320,23 +326,9 @@ async function bringUpOwned(
|
|
|
320
326
|
}
|
|
321
327
|
if (existingPid !== undefined) await rm(join(directory(root, program), "process.pid"), { force: true });
|
|
322
328
|
if (program.installation !== undefined) {
|
|
323
|
-
const
|
|
324
|
-
if (
|
|
325
|
-
|
|
326
|
-
await rotateLog(installLogPath);
|
|
327
|
-
for (const command of program.installation.commands) {
|
|
328
|
-
onProgress?.({ id: program.id, phase: "installing", logPath: installLogPath, detail: commandLabel(command) });
|
|
329
|
-
const result = await run(root, command, installLogPath);
|
|
330
|
-
if (!result.ok) {
|
|
331
|
-
return { ...base, action: "unchanged", state: initial, logPath: installLogPath, detail: `${command.command} failed: ${result.detail}` };
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
const after = await program.installation.probe();
|
|
335
|
-
if (after.state !== "ready") {
|
|
336
|
-
return { ...base, action: "unchanged", state: initial, logPath: installLogPath, detail: `installation is ${after.state}: ${after.detail}` };
|
|
337
|
-
}
|
|
338
|
-
installed = true;
|
|
339
|
-
}
|
|
329
|
+
const prepared = await prepareOwned(root, program, endpoint, onProgress, program.installation.prepareBeforeStart === true);
|
|
330
|
+
if (prepared.state.state !== "ready") return prepared;
|
|
331
|
+
installed = prepared.action === "installed";
|
|
340
332
|
}
|
|
341
333
|
if (program.start === undefined) {
|
|
342
334
|
// Nothing to keep running: installation was the whole job.
|
|
@@ -435,6 +427,50 @@ async function bringUp(
|
|
|
435
427
|
} finally { release(); }
|
|
436
428
|
}
|
|
437
429
|
|
|
430
|
+
/** Resource preparation is independent of process readiness. Caller owns the lifecycle lock. */
|
|
431
|
+
async function prepareOwned(
|
|
432
|
+
root: string, program: ManagedProgram, endpoint: string,
|
|
433
|
+
onProgress?: (event: ManagedProgramProgress) => void, reconcile = false,
|
|
434
|
+
): Promise<ManagedProgramReport> {
|
|
435
|
+
const base = { id: program.id, endpoint };
|
|
436
|
+
const installation = program.installation;
|
|
437
|
+
if (installation === undefined) return { ...base, action: "unchanged", state: await program.probe() };
|
|
438
|
+
const logPath = join(directory(root, program), "install.log");
|
|
439
|
+
const before = await installation.probe();
|
|
440
|
+
if (before.state === "ready" && !reconcile) return { ...base, action: "unchanged", state: before };
|
|
441
|
+
await mkdir(root, { recursive: true });
|
|
442
|
+
await rotateLog(logPath);
|
|
443
|
+
for (const command of installation.commands) {
|
|
444
|
+
onProgress?.({ id: program.id, phase: "installing", logPath, detail: commandLabel(command) });
|
|
445
|
+
const result = await run(root, command, logPath);
|
|
446
|
+
if (!result.ok) return { ...base, action: "unchanged", state: { state: "down", detail: `${command.command} failed: ${result.detail}` }, installationLogPath: logPath };
|
|
447
|
+
}
|
|
448
|
+
const state = await installation.probe();
|
|
449
|
+
return { ...base, action: state.state === "ready" ? "installed" : "unchanged", state, installationLogPath: logPath };
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** Prepare selected resources without starting, stopping or trusting a running process. */
|
|
453
|
+
export async function prepareManagedPrograms(
|
|
454
|
+
path: string, options: ManagedProgramOptions = {},
|
|
455
|
+
): Promise<{ readonly dataRoot: string; readonly programs: readonly ManagedProgramReport[] }> {
|
|
456
|
+
const { dataRoot, programs } = await declaredManagedPrograms(path, options);
|
|
457
|
+
const reports = await Promise.all(independentPrograms(programs).map(async ({ instance, program }): Promise<ManagedProgramReport> => {
|
|
458
|
+
const home = directory(dataRoot, program);
|
|
459
|
+
await mkdir(home, { recursive: true });
|
|
460
|
+
const { tryProgramLock } = await import("./program-lock.js");
|
|
461
|
+
const release = tryProgramLock(home);
|
|
462
|
+
if (release === undefined) return { id: program.id, endpoint: instance, action: "unchanged", state: { state: "down", detail: "another command owns this Program's preparation; inspect its logs" } };
|
|
463
|
+
try {
|
|
464
|
+
options.onProgress?.({ id: program.id, phase: "checking" });
|
|
465
|
+
const report = await prepareOwned(dataRoot, program, instance, options.onProgress);
|
|
466
|
+
if (report.state.state === "ready") options.onProgress?.({ id: program.id, phase: "ready" });
|
|
467
|
+
return report;
|
|
468
|
+
}
|
|
469
|
+
finally { release(); }
|
|
470
|
+
}));
|
|
471
|
+
return { dataRoot, programs: reports };
|
|
472
|
+
}
|
|
473
|
+
|
|
438
474
|
/** Install when needed and start every external program the Runtime Profile implies. */
|
|
439
475
|
export async function bringManagedProgramsUp(
|
|
440
476
|
path: string,
|