@leiboss/dsh-voiceover 0.0.0-stage → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +187 -2
  3. package/client/client.js +985 -0
  4. package/cordis.patch.yml +13 -0
  5. package/dist/api.d.ts +52 -0
  6. package/dist/api.d.ts.map +1 -0
  7. package/dist/api.js +301 -0
  8. package/dist/api.js.map +1 -0
  9. package/dist/cli.d.ts +40 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +303 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/client.js +985 -0
  14. package/dist/http-types.d.ts +26 -0
  15. package/dist/http-types.d.ts.map +1 -0
  16. package/dist/http-types.js +2 -0
  17. package/dist/http-types.js.map +1 -0
  18. package/dist/index.d.ts +60 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +410 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/llm.d.ts +60 -0
  23. package/dist/llm.d.ts.map +1 -0
  24. package/dist/llm.js +113 -0
  25. package/dist/llm.js.map +1 -0
  26. package/dist/operations.d.ts +41 -0
  27. package/dist/operations.d.ts.map +1 -0
  28. package/dist/operations.js +77 -0
  29. package/dist/operations.js.map +1 -0
  30. package/dist/panel.d.ts +42 -0
  31. package/dist/panel.d.ts.map +1 -0
  32. package/dist/panel.js +212 -0
  33. package/dist/panel.js.map +1 -0
  34. package/dist/pipeline.d.ts +141 -0
  35. package/dist/pipeline.d.ts.map +1 -0
  36. package/dist/pipeline.js +519 -0
  37. package/dist/pipeline.js.map +1 -0
  38. package/dist/prompts.d.ts +58 -0
  39. package/dist/prompts.d.ts.map +1 -0
  40. package/dist/prompts.js +169 -0
  41. package/dist/prompts.js.map +1 -0
  42. package/dist/serve-entry.d.ts +3 -0
  43. package/dist/serve-entry.d.ts.map +1 -0
  44. package/dist/serve-entry.js +11 -0
  45. package/dist/serve-entry.js.map +1 -0
  46. package/dist/snapshot.d.ts +48 -0
  47. package/dist/snapshot.d.ts.map +1 -0
  48. package/dist/snapshot.js +82 -0
  49. package/dist/snapshot.js.map +1 -0
  50. package/dist/subtitle.d.ts +49 -0
  51. package/dist/subtitle.d.ts.map +1 -0
  52. package/dist/subtitle.js +163 -0
  53. package/dist/subtitle.js.map +1 -0
  54. package/dist/types.d.ts +148 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +10 -0
  57. package/dist/types.js.map +1 -0
  58. package/dist/util.d.ts +16 -0
  59. package/dist/util.d.ts.map +1 -0
  60. package/dist/util.js +47 -0
  61. package/dist/util.js.map +1 -0
  62. package/dist/validate.d.ts +15 -0
  63. package/dist/validate.d.ts.map +1 -0
  64. package/dist/validate.js +186 -0
  65. package/dist/validate.js.map +1 -0
  66. package/dist/version.d.ts +3 -0
  67. package/dist/version.d.ts.map +1 -0
  68. package/dist/version.js +3 -0
  69. package/dist/version.js.map +1 -0
  70. package/package.json +83 -3
  71. package/schemas/manifest.schema.json +132 -0
  72. package/schemas/outline.schema.json +40 -0
package/dist/llm.js ADDED
@@ -0,0 +1,113 @@
1
+ /**
2
+ * The model boundary. The pipeline depends on this narrow `Completer` port, not
3
+ * on any particular provider, so it runs identically inside DSH (host adapter),
4
+ * against an OpenAI-compatible endpoint, and in tests (a scripted fake).
5
+ */
6
+ import { repairPrompt } from "./prompts.js";
7
+ import { ValidationError } from "./validate.js";
8
+ /** Failure with a routable code; callers distinguish retryable from terminal. */
9
+ export class LlmError extends Error {
10
+ code;
11
+ retryable;
12
+ constructor(code, message, retryable) {
13
+ super(message);
14
+ this.name = 'LlmError';
15
+ this.code = code;
16
+ this.retryable = retryable;
17
+ }
18
+ }
19
+ /**
20
+ * Pull one JSON value out of a model reply. Tolerates fences and leading prose,
21
+ * but never invents structure: a reply without a JSON object is an error.
22
+ */
23
+ export function extractJson(text) {
24
+ const fenced = /```(?:json)?\s*([\s\S]*?)```/i.exec(text);
25
+ const body = (fenced?.[1] ?? text).trim();
26
+ const start = body.indexOf('{');
27
+ const end = body.lastIndexOf('}');
28
+ if (start < 0 || end <= start) {
29
+ throw new LlmError('LLM_BAD_JSON', 'The model reply contained no JSON object.', true);
30
+ }
31
+ try {
32
+ return JSON.parse(body.slice(start, end + 1));
33
+ }
34
+ catch (error) {
35
+ throw new LlmError('LLM_BAD_JSON', `The model reply was not valid JSON: ${String(error)}`, true);
36
+ }
37
+ }
38
+ /**
39
+ * Ask for JSON, validate it, and repair structure once before giving up. The
40
+ * repair pass never re-runs the task, only the shape, so a bad outline cannot
41
+ * silently become a different outline.
42
+ */
43
+ export async function callJson(completer, prompt, validate, options = {}) {
44
+ const repairRounds = options.repairRounds ?? 1;
45
+ let previous = await completer.complete({
46
+ system: prompt.system,
47
+ user: prompt.user,
48
+ temperature: options.temperature,
49
+ signal: options.signal,
50
+ });
51
+ let lastError;
52
+ for (let attempt = 0; attempt <= repairRounds; attempt += 1) {
53
+ try {
54
+ return validate(extractJson(previous));
55
+ }
56
+ catch (error) {
57
+ lastError = error;
58
+ if (attempt === repairRounds)
59
+ break;
60
+ const detail = error instanceof ValidationError ? error.message : String(error);
61
+ const repair = repairPrompt({ error: detail, previous });
62
+ previous = await completer.complete({
63
+ system: repair.system,
64
+ user: repair.user,
65
+ temperature: 0,
66
+ signal: options.signal,
67
+ });
68
+ }
69
+ }
70
+ const detail = lastError instanceof ValidationError ? lastError.message : String(lastError);
71
+ throw new LlmError('LLM_SCHEMA_INVALID', `The model reply did not satisfy the schema: ${detail}`, true);
72
+ }
73
+ /**
74
+ * OpenAI-compatible chat completions. Injecting `fetchImpl` keeps every test
75
+ * offline and lets the same adapter serve a local gateway.
76
+ */
77
+ export function httpCompleter(options) {
78
+ const doFetch = options.fetchImpl ?? fetch;
79
+ const endpoint = `${options.baseUrl.replace(/\/+$/, '')}/chat/completions`;
80
+ return {
81
+ model: options.label ?? options.model,
82
+ async complete(request) {
83
+ const signal = request.signal ?? AbortSignal.timeout(options.timeoutMs ?? 120_000);
84
+ const response = await doFetch(endpoint, {
85
+ method: 'POST',
86
+ signal,
87
+ headers: {
88
+ 'content-type': 'application/json',
89
+ ...(options.apiKey === undefined ? {} : { authorization: `Bearer ${options.apiKey}` }),
90
+ },
91
+ body: JSON.stringify({
92
+ model: options.model,
93
+ temperature: request.temperature ?? 0.3,
94
+ messages: [
95
+ { role: 'system', content: request.system },
96
+ { role: 'user', content: request.user },
97
+ ],
98
+ }),
99
+ });
100
+ if (!response.ok) {
101
+ const body = await response.text().catch(() => '');
102
+ throw new LlmError('LLM_HTTP_FAILED', `The model endpoint returned ${response.status}: ${body.slice(0, 200)}`, response.status >= 500 || response.status === 429);
103
+ }
104
+ const payload = (await response.json());
105
+ const content = payload.choices?.[0]?.message?.content;
106
+ if (typeof content !== 'string' || content.length === 0) {
107
+ throw new LlmError('LLM_EMPTY_REPLY', 'The model endpoint returned an empty message.', true);
108
+ }
109
+ return content;
110
+ },
111
+ };
112
+ }
113
+ //# sourceMappingURL=llm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm.js","sourceRoot":"","sources":["../src/llm.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import { type PipelineDeps, type ProjectState, type StyleProfile } from './pipeline.ts';
2
+ /** Everything needed to reproduce a project run from persisted state. */
3
+ export interface RerunPlan {
4
+ text: string;
5
+ title: string;
6
+ voice: string;
7
+ rate: string;
8
+ style: StyleProfile;
9
+ network: boolean;
10
+ }
11
+ /** Result of one rerun. */
12
+ export interface RerunResult {
13
+ projectId: string;
14
+ state: ProjectState;
15
+ /** Chapters that were already done and therefore skipped. */
16
+ skipped: string[];
17
+ }
18
+ /**
19
+ * Rebuild the run options for an existing project from what it persisted.
20
+ *
21
+ * The source text comes from `source/normalized.md` (not from the caller), so a
22
+ * rerun cannot silently use a different document.
23
+ */
24
+ export declare function loadRerunPlan(outputDir: string, projectId: string): Promise<{
25
+ plan: RerunPlan;
26
+ state: ProjectState;
27
+ } | undefined>;
28
+ /**
29
+ * Resume a project: every finished chapter is skipped, failed or missing ones
30
+ * run again. Refuses when the project key no longer matches, because writing
31
+ * into a directory that other settings produced would mix two projects.
32
+ */
33
+ export declare function rerunProject(deps: PipelineDeps, input: {
34
+ outputDir: string;
35
+ projectId: string;
36
+ auditSample?: number;
37
+ signal?: AbortSignal;
38
+ /** Restrict the rerun to these chapters; omitted means "every unfinished one". */
39
+ chapters?: readonly string[];
40
+ }): Promise<RerunResult>;
41
+ //# sourceMappingURL=operations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../src/operations.ts"],"names":[],"mappings":"AAUA,OAAO,EAAc,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAA;AAGnG,yEAAyE;AACzE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,YAAY,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;CACjB;AAKD,2BAA2B;AAC3B,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,YAAY,CAAA;IACnB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,GAAG,SAAS,CAAC,CA2B/D;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,YAAY,EAClB,KAAK,EAAE;IACL,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC7B,GACA,OAAO,CAAC,WAAW,CAAC,CAwBtB"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Operations shared by every caller.
3
+ *
4
+ * `user-actions.md` requires one implementation per operation: the agent Tool and
5
+ * the panel button must not each own a copy of the logic. Both reach the
6
+ * functions here — the Tool directly, the panel through a POST route — so a
7
+ * rerun behaves identically whichever way the user asks for it.
8
+ */
9
+ import { readFile } from 'node:fs/promises';
10
+ import { join } from 'node:path';
11
+ import { runProject } from "./pipeline.js";
12
+ import { readJson } from "./util.js";
13
+ /** Matches project ids: the first 12 hex characters of a content hash. */
14
+ const PROJECT_ID = /^[0-9a-f]{8,24}$/;
15
+ /**
16
+ * Rebuild the run options for an existing project from what it persisted.
17
+ *
18
+ * The source text comes from `source/normalized.md` (not from the caller), so a
19
+ * rerun cannot silently use a different document.
20
+ */
21
+ export async function loadRerunPlan(outputDir, projectId) {
22
+ if (!PROJECT_ID.test(projectId))
23
+ return undefined;
24
+ const projectDir = join(outputDir, projectId);
25
+ const state = await readJson(join(projectDir, 'state', 'state.json'));
26
+ if (state === undefined)
27
+ return undefined;
28
+ let text;
29
+ try {
30
+ text = await readFile(join(projectDir, 'source', 'normalized.md'), 'utf8');
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ // Old states predate `style`; the default is the same one the pipeline uses,
36
+ // so a project created before this field existed can still resume.
37
+ const style = (state.style ?? 'news');
38
+ return {
39
+ state,
40
+ plan: {
41
+ text,
42
+ title: state.outline.title,
43
+ voice: state.engine.voice,
44
+ rate: state.engine.rate,
45
+ style,
46
+ network: state.capability.network,
47
+ },
48
+ };
49
+ }
50
+ /**
51
+ * Resume a project: every finished chapter is skipped, failed or missing ones
52
+ * run again. Refuses when the project key no longer matches, because writing
53
+ * into a directory that other settings produced would mix two projects.
54
+ */
55
+ export async function rerunProject(deps, input) {
56
+ const loaded = await loadRerunPlan(input.outputDir, input.projectId);
57
+ if (loaded === undefined)
58
+ throw new Error(`Unknown project: ${input.projectId}`);
59
+ const { plan } = loaded;
60
+ const result = await runProject(deps, {
61
+ outputDir: input.outputDir,
62
+ title: plan.title,
63
+ text: plan.text,
64
+ voice: plan.voice,
65
+ rate: plan.rate,
66
+ style: plan.style,
67
+ network: plan.network,
68
+ auditSample: input.auditSample ?? 2,
69
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
70
+ ...(input.chapters === undefined ? {} : { only: input.chapters }),
71
+ });
72
+ if (result.projectId !== input.projectId) {
73
+ throw new Error(`Project ${input.projectId} can no longer be resumed: its prompt or settings changed and the key now resolves to ${result.projectId}. Create a new project instead.`);
74
+ }
75
+ return { projectId: result.projectId, state: result.state, skipped: result.skipped };
76
+ }
77
+ //# sourceMappingURL=operations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operations.js","sourceRoot":"","sources":["../src/operations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import type { ConnectionFence, HttpRouteRegistrar } from './http-types.ts';
2
+ import { type ProjectSnapshot } from './snapshot.ts';
3
+ export type { ConnectionFence, RegisterableRoute, HttpRouteRegistrar } from './http-types.ts';
4
+ export type { ProjectSnapshot as PanelSnapshot, SnapshotChapter as PanelChapter } from './snapshot.ts';
5
+ /** Path prefix owned by this plugin. */
6
+ export declare const PANEL_ROUTE_PREFIX = "/voiceover/panel";
7
+ /** Injection points; tests pass a fake host. */
8
+ export interface PanelRouteOptions {
9
+ outputDir: string;
10
+ connection?: ConnectionFence;
11
+ /** Resolves one chapter's audio file to a signed URL, when a route is mounted. */
12
+ audioUrlFor?: (filePath: string) => string | undefined;
13
+ /**
14
+ * Resume a project. The panel's button and the `voiceover_rerun` Tool both
15
+ * call the same implementation through this hook.
16
+ */
17
+ onRerun?: (projectId: string, chapter?: string) => void | Promise<void>;
18
+ debug?: (message: string) => void;
19
+ }
20
+ /**
21
+ * Convert SRT to WebVTT.
22
+ *
23
+ * Browsers only accept WebVTT in a `<track>`, and several players prefer it;
24
+ * the only structural differences are the header and the millisecond separator.
25
+ */
26
+ export declare function srtToVtt(srt: string): string;
27
+ /** Serve one project's persisted progress as JSON. */
28
+ export declare class PanelRoute {
29
+ private readonly options;
30
+ constructor(options: PanelRouteOptions);
31
+ /** Build a snapshot from `state/state.json`; undefined when unknown. */
32
+ snapshot(projectId: string): Promise<ProjectSnapshot | undefined>;
33
+ /** Register on the host router; the disposer unregisters the route. */
34
+ register(server: HttpRouteRegistrar): () => void;
35
+ /** Handle one request. Never throws: every path answers or closes. */
36
+ private handle;
37
+ /** Write one text response (subtitles) with no caching. */
38
+ private answerText;
39
+ /** Write one JSON response with no caching. */
40
+ private answer;
41
+ }
42
+ //# sourceMappingURL=panel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../src/panel.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,eAAe,EAAqB,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAC7F,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AAErE,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAC7F,YAAY,EAAE,eAAe,IAAI,aAAa,EAAE,eAAe,IAAI,YAAY,EAAE,MAAM,eAAe,CAAA;AAEtG,wCAAwC;AACxC,eAAO,MAAM,kBAAkB,qBAAqB,CAAA;AAEpD,gDAAgD;AAChD,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,kFAAkF;IAClF,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;IACtD;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvE,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAClC;AAuBD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG5C;AACD,sDAAsD;AACtD,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;gBAE/B,OAAO,EAAE,iBAAiB;IAItC,wEAAwE;IAClE,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;IAMvE,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,IAAI;IAQhD,sEAAsE;YACxD,MAAM;IAiHpB,2DAA2D;IAC3D,OAAO,CAAC,UAAU;IAclB,+CAA+C;IAC/C,OAAO,CAAC,MAAM;CAaf"}
package/dist/panel.js ADDED
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Read-only JSON endpoint behind the conversation panel.
3
+ *
4
+ * The panel cannot read structured tool return values (a tool result exposes
5
+ * only its rendered text), and polling a Tool would fight the conversation. So
6
+ * the panel parses the project id out of the rendered result and polls this
7
+ * route instead, which reads the same persisted `state.json` the pipeline
8
+ * writes.
9
+ *
10
+ * Security matches the engine's audio route: the host's connection fence runs
11
+ * first, the path is strictly `<prefix>/<projectId>.json`, and nothing outside
12
+ * the project state file is readable.
13
+ */
14
+ import { readFile } from 'node:fs/promises';
15
+ import { join } from 'node:path';
16
+ import { projectSnapshot } from "./snapshot.js";
17
+ /** Path prefix owned by this plugin. */
18
+ export const PANEL_ROUTE_PREFIX = '/voiceover/panel';
19
+ /** Read a small JSON body; undefined means "sent something unusable". */
20
+ async function readJsonBody(req) {
21
+ const chunks = [];
22
+ let size = 0;
23
+ for await (const chunk of req) {
24
+ const buffer = chunk;
25
+ size += buffer.length;
26
+ // A rerun request carries one chapter id at most.
27
+ if (size > 64 * 1024)
28
+ return undefined;
29
+ chunks.push(buffer);
30
+ }
31
+ if (chunks.length === 0)
32
+ return {};
33
+ try {
34
+ const value = JSON.parse(Buffer.concat(chunks).toString('utf8'));
35
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
36
+ ? value
37
+ : undefined;
38
+ }
39
+ catch {
40
+ return undefined;
41
+ }
42
+ }
43
+ /**
44
+ * Convert SRT to WebVTT.
45
+ *
46
+ * Browsers only accept WebVTT in a `<track>`, and several players prefer it;
47
+ * the only structural differences are the header and the millisecond separator.
48
+ */
49
+ export function srtToVtt(srt) {
50
+ const body = srt.replace(/\r\n/g, '\n').replace(/(\d{2}:\d{2}:\d{2}),(\d{3})/g, '$1.$2');
51
+ return `WEBVTT\n\n${body}`;
52
+ }
53
+ /** Serve one project's persisted progress as JSON. */
54
+ export class PanelRoute {
55
+ options;
56
+ constructor(options) {
57
+ this.options = options;
58
+ }
59
+ /** Build a snapshot from `state/state.json`; undefined when unknown. */
60
+ async snapshot(projectId) {
61
+ return projectSnapshot(this.options.outputDir, projectId, {
62
+ ...(this.options.audioUrlFor === undefined ? {} : { audioUrlFor: this.options.audioUrlFor }),
63
+ });
64
+ }
65
+ /** Register on the host router; the disposer unregisters the route. */
66
+ register(server) {
67
+ return server.register({
68
+ kind: 'prefix',
69
+ path: PANEL_ROUTE_PREFIX,
70
+ handler: (req, res) => this.handle(req, res),
71
+ });
72
+ }
73
+ /** Handle one request. Never throws: every path answers or closes. */
74
+ async handle(req, res) {
75
+ // The fence runs before routing: an unauthenticated request learns nothing
76
+ // about which projects exist.
77
+ const rejection = this.options.connection?.requestRejection?.({ headers: req.headers });
78
+ if (rejection !== undefined) {
79
+ this.answer(res, rejection, { error: 'unauthorized' });
80
+ return;
81
+ }
82
+ const path = (req.url ?? '/').split('?')[0] ?? '/';
83
+ if (!path.startsWith(`${PANEL_ROUTE_PREFIX}/`)) {
84
+ this.answer(res, 404, { error: 'not found' });
85
+ return;
86
+ }
87
+ const name = path.slice(PANEL_ROUTE_PREFIX.length + 1);
88
+ const snapshotMatch = /^([0-9a-f]{8,24})\.json$/.exec(name);
89
+ if (snapshotMatch !== null) {
90
+ if (req.method !== 'GET') {
91
+ this.answer(res, 405, { error: 'method not allowed' });
92
+ return;
93
+ }
94
+ const projectId = snapshotMatch[1] ?? '';
95
+ const snapshot = await this.snapshot(projectId);
96
+ if (snapshot === undefined) {
97
+ this.answer(res, 404, { error: 'unknown project' });
98
+ return;
99
+ }
100
+ this.options.debug?.(`panel served ${projectId}`);
101
+ this.answer(res, 200, snapshot);
102
+ return;
103
+ }
104
+ const rerunMatch = /^([0-9a-f]{8,24})\/rerun$/.exec(name);
105
+ if (rerunMatch !== null) {
106
+ if (req.method !== 'POST') {
107
+ this.answer(res, 405, { error: 'method not allowed' });
108
+ return;
109
+ }
110
+ const onRerun = this.options.onRerun;
111
+ if (onRerun === undefined) {
112
+ this.answer(res, 501, { error: 'rerun is not available in this host' });
113
+ return;
114
+ }
115
+ const projectId = rerunMatch[1] ?? '';
116
+ const snapshot = await this.snapshot(projectId);
117
+ if (snapshot === undefined) {
118
+ this.answer(res, 404, { error: 'unknown project' });
119
+ return;
120
+ }
121
+ const body = await readJsonBody(req);
122
+ if (body === undefined) {
123
+ this.answer(res, 400, { error: 'invalid JSON body' });
124
+ return;
125
+ }
126
+ const chapter = typeof body.chapter === 'string' ? body.chapter : undefined;
127
+ if (chapter !== undefined && !/^ch-[0-9]{2,3}$/.test(chapter)) {
128
+ this.answer(res, 400, { error: 'chapter must look like ch-03' });
129
+ return;
130
+ }
131
+ try {
132
+ await onRerun(projectId, chapter);
133
+ this.options.debug?.(`panel rerun requested ${projectId}`);
134
+ this.answer(res, 202, { ok: true, projectId, ...(chapter === undefined ? {} : { chapter }) });
135
+ }
136
+ catch (error) {
137
+ this.answer(res, 500, {
138
+ error: error instanceof Error ? error.message.slice(0, 200) : 'rerun failed',
139
+ });
140
+ }
141
+ return;
142
+ }
143
+ // Subtitles, so the panel can show the current line while playing and a
144
+ // caller can pull a WebVTT track for a browser player.
145
+ const subtitleMatch = /^([0-9a-f]{8,24})\/subtitles\/(ch-[0-9]{2,3})(\.vtt)?$/.exec(name);
146
+ if (subtitleMatch !== null) {
147
+ if (req.method !== 'GET') {
148
+ this.answer(res, 405, { error: 'method not allowed' });
149
+ return;
150
+ }
151
+ const projectId = subtitleMatch[1] ?? '';
152
+ const chapterId = subtitleMatch[2] ?? '';
153
+ const asVtt = subtitleMatch[3] !== undefined;
154
+ const snapshot = await this.snapshot(projectId);
155
+ if (snapshot === undefined) {
156
+ this.answer(res, 404, { error: 'unknown project' });
157
+ return;
158
+ }
159
+ const chapter = snapshot.chapters.find((entry) => entry.id === chapterId);
160
+ if (chapter?.subtitles === undefined) {
161
+ this.answer(res, 404, { error: 'this chapter has no subtitles' });
162
+ return;
163
+ }
164
+ const projectDir = join(this.options.outputDir, projectId);
165
+ const filePath = join(projectDir, chapter.subtitles);
166
+ if (!filePath.startsWith(projectDir)) {
167
+ this.answer(res, 400, { error: 'bad subtitle path' });
168
+ return;
169
+ }
170
+ let text;
171
+ try {
172
+ text = await readFile(filePath, 'utf8');
173
+ }
174
+ catch {
175
+ this.answer(res, 404, { error: 'subtitle file missing' });
176
+ return;
177
+ }
178
+ this.answerText(res, asVtt ? 'text/vtt; charset=utf-8' : 'text/plain; charset=utf-8', asVtt ? srtToVtt(text) : text);
179
+ return;
180
+ }
181
+ this.answer(res, 400, { error: 'expected /<projectId>.json, /<projectId>/rerun or /<projectId>/subtitles/<chapter>' });
182
+ }
183
+ /** Write one text response (subtitles) with no caching. */
184
+ answerText(res, contentType, body) {
185
+ if (res.headersSent) {
186
+ res.end();
187
+ return;
188
+ }
189
+ const payload = Buffer.from(body, 'utf8');
190
+ res.writeHead(200, {
191
+ 'content-type': contentType,
192
+ 'cache-control': 'no-store',
193
+ 'content-length': String(payload.length),
194
+ });
195
+ res.end(payload);
196
+ }
197
+ /** Write one JSON response with no caching. */
198
+ answer(res, status, body) {
199
+ if (res.headersSent) {
200
+ res.end();
201
+ return;
202
+ }
203
+ const payload = `${JSON.stringify(body)}\n`;
204
+ res.writeHead(status, {
205
+ 'content-type': 'application/json; charset=utf-8',
206
+ 'cache-control': 'no-store',
207
+ 'content-length': Buffer.byteLength(payload),
208
+ });
209
+ res.end(payload);
210
+ }
211
+ }
212
+ //# sourceMappingURL=panel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"panel.js","sourceRoot":"","sources":["../src/panel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,141 @@
1
+ import { type Completer } from './llm.ts';
2
+ import { type Capability, type ChapterRecord, type EngineInfo, type Manifest, type Outline, type OutlineChapter, type SynthResult, type Synthesizer } from './types.ts';
3
+ /** How the script should sound. */
4
+ export type StyleProfile = 'lecture' | 'news' | 'story' | 'docs';
5
+ /** Collaborators the pipeline needs, all injectable. */
6
+ export interface PipelineDeps {
7
+ /** Absent means "no model": the original text is spoken as written. */
8
+ completer?: Completer;
9
+ synth: Synthesizer;
10
+ now?: () => Date;
11
+ log?: (event: string, fields?: Record<string, unknown>) => void;
12
+ }
13
+ /** One project run. */
14
+ export interface ProjectOptions {
15
+ outputDir: string;
16
+ title: string;
17
+ text: string;
18
+ voice: string;
19
+ rate: string;
20
+ style: StyleProfile;
21
+ /** Whether the network reached Edge-TTS at planning time (recorded, not guessed). */
22
+ network: boolean;
23
+ /** Chapters below this index are always audited; the rest are sampled. */
24
+ auditSample?: number;
25
+ maxBlocks?: number;
26
+ signal?: AbortSignal;
27
+ /** Force a stage to run again even when the hashes match. */
28
+ rerun?: 'source' | 'script' | 'synth';
29
+ /**
30
+ * Process only these chapters. Every other chapter keeps its recorded state
31
+ * untouched, which is what a single-chapter rerun needs.
32
+ */
33
+ only?: readonly string[];
34
+ /** Target characters per chapter when planning without a model. */
35
+ chapterChars?: number;
36
+ }
37
+ /** Persisted per-project state (never part of the manifest contract). */
38
+ export interface ProjectState {
39
+ projectId: string;
40
+ sourceHash: string;
41
+ promptVersion: string;
42
+ engine: EngineInfo;
43
+ capability: Capability;
44
+ /** Spoken style, persisted so a rerun reproduces the same project key. */
45
+ style: StyleProfile;
46
+ outline: Outline;
47
+ chapters: ChapterRecord[];
48
+ /** sha256 of each chapter's input text, for resume decisions. */
49
+ inputHashes: Record<string, string>;
50
+ warnings: string[];
51
+ createdAt: string;
52
+ }
53
+ /** What a completed run returns. */
54
+ export interface ProjectResult {
55
+ projectId: string;
56
+ projectDir: string;
57
+ manifest: Manifest;
58
+ state: ProjectState;
59
+ /** Chapters that were skipped because their inputs were unchanged. */
60
+ skipped: string[];
61
+ }
62
+ /**
63
+ * Canonical text used for identity and planning.
64
+ *
65
+ * Every caller (plugin tool, daemon, rerun) must key projects by this string,
66
+ * not by the raw input: the persisted source file differs from the original by
67
+ * a trailing newline, and identity must survive that round trip.
68
+ */
69
+ export declare function normalizeSource(text: string): string;
70
+ /** Compute the resume key for one chapter. */
71
+ export declare function chapterInputHash(text: string, style: StyleProfile): string;
72
+ /** Compute the project identity from everything that changes its output. */
73
+ export declare function projectKey(input: {
74
+ text: string;
75
+ voice: string;
76
+ rate: string;
77
+ style: StyleProfile;
78
+ }): string;
79
+ /**
80
+ * Deterministic outline when no model is available: pack paragraphs into
81
+ * chapters of roughly {@link FALLBACK_CHAPTER_CHARS} characters.
82
+ */
83
+ export declare function fallbackOutline(input: {
84
+ title: string;
85
+ paragraphs: string[];
86
+ chapterChars?: number;
87
+ }): Outline;
88
+ /** Plan chapters with the model, verifying coverage and falling back on failure. */
89
+ export declare function planOutline(deps: PipelineDeps, input: {
90
+ title: string;
91
+ paragraphs: string[];
92
+ maxBlocks?: number;
93
+ chapterChars?: number;
94
+ }): Promise<{
95
+ outline: Outline;
96
+ warnings: string[];
97
+ }>;
98
+ /** Result of adapting one chapter. */
99
+ export interface AdaptResult {
100
+ script: string;
101
+ changes: unknown[];
102
+ risks: unknown[];
103
+ }
104
+ /** Run stage P1 (and one repair round when the audit rejects the result). */
105
+ export declare function adaptChapter(deps: PipelineDeps, input: {
106
+ docTitle: string;
107
+ chapter: OutlineChapter;
108
+ chapterNo: number;
109
+ chapterTotal: number;
110
+ text: string;
111
+ style: StyleProfile;
112
+ prevSummary: string;
113
+ nextSummary: string;
114
+ issues?: string[];
115
+ }): Promise<AdaptResult>;
116
+ /** One audit issue, kept only as far as the pipeline needs it. */
117
+ export interface AuditIssue {
118
+ severity: 'high' | 'medium' | 'low';
119
+ kind: string;
120
+ fix: string;
121
+ }
122
+ /** Run stage P4. Returns the blocking issues (high severity only). */
123
+ export declare function auditChapter(deps: PipelineDeps, input: {
124
+ source: string;
125
+ script: string;
126
+ }): Promise<AuditIssue[]>;
127
+ /**
128
+ * Run (or resume) one project. Chapter failures are recorded, not thrown: a
129
+ * partial delivery with an honest manifest beats losing the whole job.
130
+ */
131
+ export declare function runProject(deps: PipelineDeps, options: ProjectOptions): Promise<ProjectResult>;
132
+ /** Assemble the manifest from persisted chapter records. */
133
+ export declare function buildManifest(input: {
134
+ state: ProjectState;
135
+ outline: Map<string, OutlineChapter>;
136
+ title: string;
137
+ now: () => Date;
138
+ }): Manifest;
139
+ /** Re-exported so a caller can type a fake synthesizer without the host. */
140
+ export type { SynthResult, Synthesizer };
141
+ //# sourceMappingURL=pipeline.d.ts.map