@lincyaw/kernel 0.0.0-stage → 1.0.0-rc.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.
Files changed (128) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +32 -2
  3. package/package.json +41 -3
  4. package/src/access.ts +18 -0
  5. package/src/anchors.ts +58 -0
  6. package/src/authoring.ts +56 -0
  7. package/src/blocks.ts +19 -0
  8. package/src/checks.ts +35 -0
  9. package/src/component-code.ts +6 -0
  10. package/src/contracts/agent.ts +76 -0
  11. package/src/contracts/anchors.ts +46 -0
  12. package/src/contracts/authoring-actions.ts +60 -0
  13. package/src/contracts/authoring-clients.ts +213 -0
  14. package/src/contracts/authoring.ts +180 -0
  15. package/src/contracts/checks.ts +29 -0
  16. package/src/contracts/classroom.ts +49 -0
  17. package/src/contracts/diagnostics.ts +37 -0
  18. package/src/contracts/document-audio.ts +150 -0
  19. package/src/contracts/document-comments.ts +31 -0
  20. package/src/contracts/document-history.ts +63 -0
  21. package/src/contracts/document-image.ts +32 -0
  22. package/src/contracts/document-reads.ts +57 -0
  23. package/src/contracts/document-voice.ts +44 -0
  24. package/src/contracts/document.ts +240 -0
  25. package/src/contracts/execution.ts +118 -0
  26. package/src/contracts/locale.ts +3 -0
  27. package/src/contracts/offline.ts +2 -0
  28. package/src/contracts/operations.ts +8 -0
  29. package/src/contracts/plugin.ts +183 -0
  30. package/src/contracts/presenter-panels.ts +17 -0
  31. package/src/contracts/preview.ts +386 -0
  32. package/src/contracts/production-assets.ts +61 -0
  33. package/src/contracts/production.ts +109 -0
  34. package/src/contracts/projects.ts +59 -0
  35. package/src/contracts/server.ts +108 -0
  36. package/src/contracts/service.ts +8 -0
  37. package/src/contracts/speech-settings.ts +16 -0
  38. package/src/contracts/speech.ts +149 -0
  39. package/src/contracts/steps.ts +15 -0
  40. package/src/contracts/transcription-adoption.ts +79 -0
  41. package/src/contracts/transcription.ts +112 -0
  42. package/src/contracts/voice.ts +97 -0
  43. package/src/contracts/voiceover-execution.ts +68 -0
  44. package/src/contracts/workspace.ts +458 -0
  45. package/src/contracts.ts +35 -0
  46. package/src/diagnostics.ts +7 -0
  47. package/src/document-authoring-actions.ts +47 -0
  48. package/src/document-caption-timings.ts +64 -0
  49. package/src/document-colors.ts +25 -0
  50. package/src/document-comments.ts +118 -0
  51. package/src/document-components.ts +251 -0
  52. package/src/document-operations.ts +132 -0
  53. package/src/document-pages.ts +215 -0
  54. package/src/document-reads.ts +81 -0
  55. package/src/document-schema.ts +85 -0
  56. package/src/document-voices.ts +153 -0
  57. package/src/document.ts +252 -0
  58. package/src/durations.ts +7 -0
  59. package/src/errors.ts +15 -0
  60. package/src/events.ts +32 -0
  61. package/src/execution.ts +25 -0
  62. package/src/fingerprint.ts +31 -0
  63. package/src/follow.ts +33 -0
  64. package/src/git.ts +12 -0
  65. package/src/html.ts +41 -0
  66. package/src/index.ts +82 -0
  67. package/src/ir.ts +367 -0
  68. package/src/jupyter-kernel.ts +9 -0
  69. package/src/kernel.ts +190 -0
  70. package/src/keyboard.ts +43 -0
  71. package/src/locale.ts +13 -0
  72. package/src/lock.ts +81 -0
  73. package/src/logging.ts +11 -0
  74. package/src/lower-myst.ts +312 -0
  75. package/src/node/assets.ts +23 -0
  76. package/src/node/authoring-actions.ts +50 -0
  77. package/src/node/authoring-clients.ts +14 -0
  78. package/src/node/document-audio.ts +22 -0
  79. package/src/node/document-history.ts +11 -0
  80. package/src/node/document-image.ts +8 -0
  81. package/src/node/document-reads.ts +10 -0
  82. package/src/node/document-voice.ts +7 -0
  83. package/src/node/ffmpeg.ts +29 -0
  84. package/src/node/file-version.ts +6 -0
  85. package/src/node/fonts.ts +22 -0
  86. package/src/node/index.ts +29 -0
  87. package/src/node/installed-packages.ts +11 -0
  88. package/src/node/lines.ts +24 -0
  89. package/src/node/locale.ts +7 -0
  90. package/src/node/network.ts +11 -0
  91. package/src/node/operator-speech.ts +91 -0
  92. package/src/node/packages.ts +74 -0
  93. package/src/node/paths.ts +7 -0
  94. package/src/node/projects.ts +7 -0
  95. package/src/node/resources.ts +86 -0
  96. package/src/node/revision.ts +6 -0
  97. package/src/node/run-directory.ts +70 -0
  98. package/src/node/strip-types.mjs +15 -0
  99. package/src/node/transcription-adoption.ts +9 -0
  100. package/src/node/transcription.ts +10 -0
  101. package/src/node/voiceover-routes.ts +73 -0
  102. package/src/node/voiceover.ts +199 -0
  103. package/src/page-source.ts +359 -0
  104. package/src/parameters.ts +81 -0
  105. package/src/preview-edit.ts +18 -0
  106. package/src/preview-events.ts +57 -0
  107. package/src/preview-frame.ts +1220 -0
  108. package/src/props.ts +17 -0
  109. package/src/published.ts +11 -0
  110. package/src/push-stream.ts +67 -0
  111. package/src/reading.ts +96 -0
  112. package/src/recording.ts +38 -0
  113. package/src/renderer-components.ts +30 -0
  114. package/src/requests.ts +28 -0
  115. package/src/responses.ts +49 -0
  116. package/src/run-outputs.ts +15 -0
  117. package/src/runner.py +454 -0
  118. package/src/services.ts +146 -0
  119. package/src/slides.ts +36 -0
  120. package/src/speech-words.ts +76 -0
  121. package/src/speech.ts +13 -0
  122. package/src/step-keys.ts +38 -0
  123. package/src/steps.ts +7 -0
  124. package/src/transcription-adoption.ts +105 -0
  125. package/src/uri-decoding.ts +8 -0
  126. package/src/urls.ts +13 -0
  127. package/src/views.ts +47 -0
  128. package/src/voice.ts +86 -0
@@ -0,0 +1,108 @@
1
+ import type { Logger } from './plugin.ts'
2
+ import { defineService } from './service.ts'
3
+ import type { ServiceToken } from './service.ts'
4
+
5
+ export type MemberRole = 'reader' | 'editor' | 'owner'
6
+
7
+ export interface Member {
8
+ id: string
9
+ name: string
10
+ email?: string
11
+ roles: MemberRole[]
12
+ }
13
+
14
+ export interface RequestContext {
15
+ member: Member
16
+ document: string
17
+ rest: string
18
+ }
19
+
20
+ export type RouteHandler = (request: Request, ctx: RequestContext) => Response | Promise<Response>
21
+
22
+ export interface Route {
23
+ role?: MemberRole
24
+ maxBody?: number
25
+ handle: RouteHandler
26
+ }
27
+
28
+ export const MAX_BODY = 1 << 20
29
+
30
+ export interface ProxySpec {
31
+ path: string
32
+ target: string
33
+ headers?: Record<string, string>
34
+ role?: MemberRole
35
+ }
36
+
37
+ export interface ServerContribution {
38
+ routes?: Record<string, Route>
39
+ proxies?: ProxySpec[]
40
+ close?(): void | Promise<void>
41
+ }
42
+
43
+ export interface ServerContext {
44
+ deckDir: string
45
+ workspaceRoot: string
46
+ config: Record<string, unknown>
47
+ settings: Record<string, unknown>
48
+ lang?: string
49
+ service<T>(token: ServiceToken<T>): T | undefined
50
+ secret(name: string): string | undefined
51
+ operatorDir?: string
52
+ home?: boolean
53
+ log: Logger
54
+ }
55
+
56
+ export interface NodeServiceContext {
57
+ deckDir: string
58
+ workspaceRoot: string
59
+ config: Record<string, unknown>
60
+ settings: Record<string, unknown>
61
+ lang?: string
62
+ secret(name: string): string | undefined
63
+ operatorDir?: string
64
+ service?<T>(token: ServiceToken<T>): T | undefined
65
+ log: Logger
66
+ }
67
+
68
+ export type ApprovalKind = 'operate' | 'spend' | 'write' | 'agent'
69
+
70
+ export type ApprovalScope = 'content' | 'workspace'
71
+
72
+ export interface ApprovalRequest {
73
+ kind: ApprovalKind
74
+ scope?: ApprovalScope
75
+ title: string
76
+ detail?: string
77
+ from: string
78
+ member?: string
79
+ }
80
+
81
+ export interface Approvals {
82
+ request(request: ApprovalRequest, signal?: AbortSignal): Promise<boolean>
83
+ }
84
+
85
+ export const APPROVALS = defineService<Approvals>('presmium.approvals')
86
+
87
+ export type AgentMode = 'author'
88
+
89
+ export interface McpAccess {
90
+ url: string
91
+ token: string
92
+ }
93
+
94
+ export interface McpGrant extends McpAccess {
95
+ revoke(): void
96
+ }
97
+
98
+ /** @experimental */
99
+ export interface McpIssueOptions {
100
+ confirmed?: boolean
101
+ document?: string
102
+ }
103
+
104
+ export interface McpEndpoint {
105
+ issue(member: Member, mode: AgentMode, options?: McpIssueOptions): McpGrant
106
+ }
107
+
108
+ export const MCP_ENDPOINT = defineService<McpEndpoint>('presmium.mcpEndpoint')
@@ -0,0 +1,8 @@
1
+ export interface ServiceToken<T> {
2
+ readonly id: string
3
+ readonly __service?: T
4
+ }
5
+
6
+ export function defineService<T>(id: string): ServiceToken<T> {
7
+ return Object.freeze({ id }) as ServiceToken<T>
8
+ }
@@ -0,0 +1,16 @@
1
+ /** Operator configuration is separate from document voice choices. Secret values are write-only. */
2
+ export interface SpeechSettingsAnswer {
3
+ provider: 'volcengine'
4
+ model: 'seed-tts-2.0'
5
+ revision: string
6
+ configured: boolean
7
+ appIdConfigured?: boolean
8
+ source: 'saved' | 'environment' | 'none'
9
+ stored: boolean
10
+ writable: boolean
11
+ verification: 'configuration-only'
12
+ }
13
+
14
+ export type SpeechSettingsChange =
15
+ | { action: 'save', revision: string, apiKey?: string, appId?: string }
16
+ | { action: 'clear', revision: string }
@@ -0,0 +1,149 @@
1
+ import { defineService } from './service.ts'
2
+
3
+ export interface SpeechWord { text: string, start: number, end: number }
4
+
5
+ export interface Synthesis {
6
+ audio: Blob | string
7
+ mime: string
8
+ duration: number
9
+ words?: SpeechWord[]
10
+ key: string
11
+ engine: string
12
+ model: string
13
+ voice: string
14
+ provider: string
15
+ synthetic: true
16
+ }
17
+
18
+ export interface AigcLabel {
19
+ Label: '1'
20
+ ContentProducer: string
21
+ ProduceID: string
22
+ ReservedCode1: string
23
+ ContentPropagator: string
24
+ PropagateID: string
25
+ ReservedCode2: string
26
+ }
27
+
28
+ export interface TranscriptSegment { start: number, end: number, text: string, words: SpeechWord[] }
29
+
30
+ export interface Transcript {
31
+ lang: string
32
+ duration: number
33
+ text: string
34
+ segments: TranscriptSegment[]
35
+ }
36
+
37
+ export interface SpeechEngineInfo {
38
+ id: string
39
+ provider: string
40
+ stt: boolean
41
+ tts: boolean
42
+ wordTimes: boolean
43
+ local: boolean
44
+ /** The engine can train a cloned voice from a sample (`SpeechService.cloneVoice`); absent means it cannot. */
45
+ clone?: boolean
46
+ /** Absent means the provider has not declared a supported authoring range. */
47
+ parameters?: { rate?: { min: number, max: number, step?: number }, styles?: string[] }
48
+ }
49
+
50
+ export interface SpeechVoice {
51
+ engine?: string
52
+ voice: string
53
+ rate?: number
54
+ style?: string
55
+ }
56
+
57
+ export interface VoiceInfo {
58
+ id: string
59
+ label: string
60
+ lang: string[]
61
+ cloned?: boolean
62
+ }
63
+
64
+ /** A provider list may be empty. An unsupported catalogue must not be presented as an empty list. */
65
+ export type VoiceCatalog =
66
+ | { status: 'available', source: 'provider' | 'bundled', voices: VoiceInfo[] }
67
+ | { status: 'unavailable', reason: 'not-configured' | 'unsupported' | 'unknown', voices: VoiceInfo[] }
68
+
69
+ export interface SynthesisRequest {
70
+ engine?: string
71
+ voice: string
72
+ text: string
73
+ lang: string
74
+ rate?: number
75
+ style?: string
76
+ lexicon?: Record<string, string>
77
+ }
78
+
79
+ /**
80
+ * Training a cloned voice. The speaker's consent comes with the request and is registered before anything is sent:
81
+ * `statement` is their recording of the consent statement, kept by the operator, whose sha256 the record holds.
82
+ */
83
+ export interface VoiceCloneRequest {
84
+ engine?: string
85
+ /** The voice to train: a slot the operator owns at the provider (Volcengine's S_…) or a new name, per the engine. */
86
+ voice: string
87
+ /** One speaker, mono, about 10–30 s. */
88
+ sample: Blob
89
+ lang?: string
90
+ /** What the sample says, when known: providers that compare it reject a mismatch. */
91
+ text?: string
92
+ consent: { speaker: string, scope: string, statement: Blob }
93
+ }
94
+
95
+ export type VoiceCloneState = 'training' | 'ready' | 'failed' | 'unknown'
96
+
97
+ export interface VoiceCloneStatus {
98
+ engine: string
99
+ voice: string
100
+ state: VoiceCloneState
101
+ /** How many more trainings the provider allows for this voice. */
102
+ trainingsLeft?: number
103
+ message?: string
104
+ }
105
+
106
+ export interface TranscriptionRequest {
107
+ engine?: string
108
+ audio: Blob
109
+ lang?: string
110
+ hints?: string[]
111
+ }
112
+
113
+ export interface SpeechService {
114
+ engines(): Promise<SpeechEngineInfo[]>
115
+ /** The actual default in this document's scoped service; absent means its identity is unknown. */
116
+ defaultSynthesisEngine?(): Promise<string | undefined>
117
+ voices(engine: string, lang?: string): Promise<VoiceInfo[]>
118
+ /** Lists registered voices without synthesis. Absence means catalogue availability is unknown. */
119
+ voiceCatalog?(engine?: string, lang?: string, signal?: AbortSignal): Promise<VoiceCatalog>
120
+ synthesize(request: SynthesisRequest, signal?: AbortSignal): Promise<Synthesis>
121
+ /** Exact request/configuration lookup with verified audio bytes. Offline reuse requires an unambiguous lock. A miss never calls synthesis or transcription providers. */
122
+ lookup?(request: SynthesisRequest, signal?: AbortSignal): Promise<Synthesis | undefined>
123
+ /** Verifies the current configuration profile, or an explicit offline lock, and the actual audio SHA. */
124
+ cached?(key: string, signal?: AbortSignal): Promise<Synthesis | undefined>
125
+ alignWords?(key: string, options: { lang: string, transcribe?: boolean }, signal?: AbortSignal): Promise<SpeechWord[] | undefined>
126
+ pruneLock?(keep: ReadonlySet<string>): Promise<number>
127
+ transcribe(request: TranscriptionRequest, signal?: AbortSignal): Promise<Transcript>
128
+ /** Registers the consent, then starts training; only for the document's owner. Absent: the service cannot clone. */
129
+ cloneVoice?(request: VoiceCloneRequest, signal?: AbortSignal): Promise<VoiceCloneStatus>
130
+ cloneStatus?(engine: string, voice: string, signal?: AbortSignal): Promise<VoiceCloneStatus>
131
+ }
132
+
133
+ export const SPEECH = defineService<SpeechService>('presmium.speech')
134
+
135
+ /** Stable author-facing reasons; provider payloads and credential values are never UI text. */
136
+ export function speechFailureCode(error: unknown): string | undefined {
137
+ const message = error instanceof Error ? error.message : typeof error === 'string' ? error : ''
138
+ if (/^speech-(authentication|permission|quota|busy|network|empty-audio|invalid-request|provider|cache-failed)$/.test(message)) return message
139
+ if (/quota exceeded.*concurrency|too many requests|answered 429|is busy: more requests/i.test(message)) return 'speech-busy'
140
+ if (/quota exceeded|insufficient.*(balance|credit)|payment required|balance.*insufficient/i.test(message)) return 'speech-quota'
141
+ if (/answered 401|45000010|unauthorized|invalid (?:(?:x-)?api.?key|token)|authentication failed/i.test(message)) return 'speech-authentication'
142
+ if (/EACCES|ENOSPC|EROFS|speech.*cache.*(write|failed)/i.test(message)) return 'speech-cache-failed'
143
+ if (/answered 403|resource.*mismatch|permission denied|access denied|service.*not.*(enabled|activated)/i.test(message)) return 'speech-permission'
144
+ if (/fetch failed|ECONNREFUSED|ENOTFOUND|ETIMEDOUT|ECONNRESET|UND_ERR|network error/i.test(message)) return 'speech-network'
145
+ if (/gave no audio|no audio data/i.test(message)) return 'speech-empty-audio'
146
+ if (/answered 4\d\d|45000001|4500200[01]|invalid argument/i.test(message)) return 'speech-invalid-request'
147
+ if (/answered 5\d\d|55000000/i.test(message)) return 'speech-provider'
148
+ return undefined
149
+ }
@@ -0,0 +1,15 @@
1
+ export interface StepRange {
2
+ from: number
3
+ to?: number
4
+ }
5
+
6
+ export interface StepState {
7
+ active: boolean
8
+ reached: boolean
9
+ done: boolean
10
+ }
11
+
12
+ export interface Cursor {
13
+ slide: number
14
+ step: number
15
+ }
@@ -0,0 +1,79 @@
1
+ import type { CanonicalSource } from '../ir.ts'
2
+ import type { DocumentChangeAnswer } from './workspace.ts'
3
+ import type { NarrationNode, TeachingDocument } from './document.ts'
4
+ import type { Member } from './server.ts'
5
+
6
+ export type TranscriptionAdoptionPart = { type: 'text', text: string } | { type: 'preserve', key: string }
7
+ export interface TranscriptionAdoptionTarget {
8
+ sectionId: string
9
+ stepId: string
10
+ narrationId?: string
11
+ segmentIds: string[]
12
+ content?: TranscriptionAdoptionPart[]
13
+ discardAtoms?: string[]
14
+ shared?: 'edit-shared' | 'fork'
15
+ affectedStepIds?: string[]
16
+ }
17
+ export interface TranscriptionAdoptionDraft {
18
+ id: string
19
+ source: CanonicalSource
20
+ candidate: { id: string, revision: string }
21
+ draftRevision: number
22
+ targets: TranscriptionAdoptionTarget[]
23
+ /** Explicitly confirmed cleanup, only for cues actually removed by this adoption. */
24
+ removeCueTimings?: string[]
25
+ }
26
+ export interface TranscriptionAdoptionApply {
27
+ planId: string
28
+ source: CanonicalSource
29
+ candidate: { id: string, revision: string }
30
+ draftRevision: number
31
+ }
32
+ export interface TranscriptionAdoptionConflict { code: string, target?: number, key?: string, stepIds?: string[] }
33
+ export interface TranscriptionAdoptionPreview {
34
+ targets: { sectionId: string, stepId: string, narrationId?: string, outputNarrationId: string, segmentIds: string[], sourceText: string, before: NarrationNode[], proposed: NarrationNode[], atoms: { key: string, node: Exclude<NarrationNode, { type: 'text' }> }[] }[]
35
+ conflicts: TranscriptionAdoptionConflict[]
36
+ impact: { narrationIds: string[], stepIds: string[], sharedStepIds: string[], forks: { from: string, to: string, stepId: string, cues: { from: string, to: string }[] }[], removedAtoms: string[], removedCueTimings: string[] }
37
+ }
38
+ export interface TranscriptionAdoptionPlan extends TranscriptionAdoptionPreview {
39
+ document: string
40
+ changeId: string
41
+ source: CanonicalSource
42
+ candidate: { id: string, revision: string }
43
+ draftRevision: number
44
+ planId?: string
45
+ expiresAt?: string
46
+ content?: TeachingDocument
47
+ }
48
+ export interface TranscriptionAdoptionAnswer extends DocumentChangeAnswer {
49
+ adoption: { planId: string, candidate: { id: string, revision: string }, draftRevision: number }
50
+ }
51
+ /** Agent-safe surface. An editor HTTP adapter receives its trusted write path separately. */
52
+ export interface DocumentTranscriptionAdoption {
53
+ plan(document: string, draft: TranscriptionAdoptionDraft, member: Member): Promise<TranscriptionAdoptionPlan>
54
+ /** The adoption's checks without writing: plan, draft revision, candidate, current source; `apply` checks everything again. */
55
+ check(document: string, request: TranscriptionAdoptionApply, member: Member): Promise<void>
56
+ apply(document: string, request: TranscriptionAdoptionApply, member: Member): Promise<TranscriptionAdoptionAnswer>
57
+ }
58
+
59
+ const id = { type: 'string', minLength: 1, maxLength: 200, pattern: '^[^\u0000-\u001f]+$', not: { enum: ['__proto__', 'constructor', 'prototype'] } }
60
+ const digest = { type: 'string', pattern: '^[a-f0-9]{64}$' }
61
+ const key = { type: 'string', minLength: 1, maxLength: 512 }
62
+ const source = { type: 'object', properties: { documentId: id, version: { type: 'string', pattern: '^[a-f0-9]{20}$' }, fingerprint: { type: 'string', pattern: '^[a-f0-9]{16}$' } }, required: ['documentId', 'version', 'fingerprint'], additionalProperties: false }
63
+ const candidate = { type: 'object', properties: { id: digest, revision: digest }, required: ['id', 'revision'], additionalProperties: false }
64
+ const draftRevision = { type: 'integer', minimum: 0, maximum: Number.MAX_SAFE_INTEGER }
65
+ const unique = (items: object) => ({ type: 'array', items, uniqueItems: true, maxItems: 1000 })
66
+ export const TRANSCRIPTION_ADOPTION_DRAFT_SCHEMA = {
67
+ type: 'object', additionalProperties: false, required: ['id', 'source', 'candidate', 'draftRevision', 'targets'],
68
+ properties: { id, source, candidate, draftRevision, removeCueTimings: unique(id), targets: { type: 'array', minItems: 1, maxItems: 100, items: {
69
+ type: 'object', additionalProperties: false, required: ['sectionId', 'stepId', 'segmentIds'], properties: {
70
+ sectionId: id, stepId: id, narrationId: id, segmentIds: { ...unique(id), minItems: 1 },
71
+ shared: { enum: ['edit-shared', 'fork'] }, affectedStepIds: unique(id), discardAtoms: unique(key),
72
+ content: { type: 'array', maxItems: 1000, items: { oneOf: [
73
+ { type: 'object', properties: { type: { const: 'text' }, text: { type: 'string', maxLength: 1_048_576 } }, required: ['type', 'text'], additionalProperties: false },
74
+ { type: 'object', properties: { type: { const: 'preserve' }, key }, required: ['type', 'key'], additionalProperties: false },
75
+ ] } },
76
+ },
77
+ } } },
78
+ }
79
+ export const TRANSCRIPTION_ADOPTION_APPLY_SCHEMA = { type: 'object', additionalProperties: false, properties: { planId: id, source, candidate, draftRevision }, required: ['planId', 'source', 'candidate', 'draftRevision'] }
@@ -0,0 +1,112 @@
1
+ import type { CanonicalSource } from '../ir.ts'
2
+ import type { TimeRange } from './document.ts'
3
+ import type { Member } from './server.ts'
4
+ import type { SpeechEngineInfo } from './speech.ts'
5
+ import type { Finding } from './checks.ts'
6
+
7
+ export const TRANSCRIPTION_REQUEST_MAX_BYTES = 32 << 10
8
+
9
+ export interface TranscriptionCapabilities {
10
+ source: CanonicalSource
11
+ available: boolean
12
+ status: 'available' | 'unavailable' | 'unknown'
13
+ reason?: 'speech-unavailable' | 'engine-unavailable' | 'audio-unavailable' | 'services-unavailable'
14
+ /** Provider declaration and local configuration are known; authentication/recognition is not verified. */
15
+ verification: 'configuration-only'
16
+ findings?: Finding[]
17
+ engines: SpeechEngineInfo[]
18
+ range: 'whole-asset'
19
+ limits: { audioBytes: number, durationMs: number, tasks: number, requestBytes: number }
20
+ }
21
+
22
+ /** A confirmed recognition request; no script, clip or voice selection is changed. */
23
+ export interface DocumentTranscriptionRequest {
24
+ intent: string
25
+ source: CanonicalSource
26
+ assetId: string
27
+ audioSha256: string
28
+ engine: string
29
+ lang?: string
30
+ hints?: string[]
31
+ confirmed: true
32
+ }
33
+
34
+ /** Agent input excludes host confirmation; HTTP and Node validate the confirmed form below. */
35
+ export const DOCUMENT_TRANSCRIPTION_INPUT_SCHEMA = {
36
+ type: 'object', additionalProperties: false,
37
+ properties: {
38
+ intent: { type: 'string', pattern: '^[A-Za-z\\d_-]{16,128}$' },
39
+ source: { type: 'object', additionalProperties: false, properties: { documentId: { type: 'string', minLength: 1, maxLength: 200 }, version: { type: 'string', pattern: '^[a-f\\d]{20}$' }, fingerprint: { type: 'string', pattern: '^[a-f\\d]{16}$' } }, required: ['documentId', 'version', 'fingerprint'] },
40
+ assetId: { type: 'string', minLength: 1, maxLength: 200 },
41
+ audioSha256: { type: 'string', pattern: '^[a-f\\d]{64}$' },
42
+ engine: { type: 'string', minLength: 1, maxLength: 128 },
43
+ lang: { type: 'string', minLength: 1, maxLength: 64 },
44
+ hints: { type: 'array', maxItems: 100, items: { type: 'string', maxLength: 200 } },
45
+ },
46
+ required: ['intent', 'source', 'assetId', 'audioSha256', 'engine'],
47
+ } as const
48
+
49
+ export const DOCUMENT_TRANSCRIPTION_REQUEST_SCHEMA = {
50
+ ...DOCUMENT_TRANSCRIPTION_INPUT_SCHEMA,
51
+ properties: { ...DOCUMENT_TRANSCRIPTION_INPUT_SCHEMA.properties, confirmed: { const: true } },
52
+ required: [...DOCUMENT_TRANSCRIPTION_INPUT_SCHEMA.required, 'confirmed'],
53
+ } as const
54
+
55
+ export interface TranscriptionRevision {
56
+ transcriptionId: string
57
+ revision: string
58
+ createdAt: string
59
+ source: CanonicalSource
60
+ audio: { assetId: string, sha256: string, sourceRange: TimeRange }
61
+ request: { engine: string, provider: string, local: boolean, lang?: string, hints: string[], fingerprint: string }
62
+ result: {
63
+ lang: string
64
+ text: string
65
+ segments: { id: string, text: string, startMs?: number, endMs?: number, words?: { text: string, startMs: number, endMs: number }[] }[]
66
+ }
67
+ timing: 'words' | 'segments' | 'none'
68
+ warnings: string[]
69
+ }
70
+
71
+ export type TranscriptionTaskState = 'queued' | 'running' | 'cancelling' | 'cancelled' | 'done' | 'failed'
72
+ export interface TranscriptionTask {
73
+ kind: 'transcription'
74
+ id: string
75
+ intent: string
76
+ document: string
77
+ source: CanonicalSource
78
+ audio: { assetId: string, sha256: string }
79
+ sequence: number
80
+ state: TranscriptionTaskState
81
+ current: boolean
82
+ staleReason?: 'document-changed' | 'document-unavailable' | 'audio-changed'
83
+ progress: { phase: 'queued' | 'recognizing' | 'saving' | 'finished' }
84
+ createdAt: string
85
+ startedAt?: string
86
+ finishedAt?: string
87
+ result?: { transcriptionId: string, revision: string }
88
+ error?: string
89
+ }
90
+
91
+ export interface TranscriptionCancellation { intent: string, state: TranscriptionTaskState, task?: TranscriptionTask }
92
+ export interface TranscriptionListAnswer {
93
+ tasks: TranscriptionTask[]
94
+ issues: { id: string, code: string }[]
95
+ truncated?: true
96
+ }
97
+
98
+ /** Workspace-owned authoring capability, shared by HTTP and agent adapters. */
99
+ export interface DocumentTranscriptions {
100
+ capabilities(document: string, member: Member): Promise<TranscriptionCapabilities>
101
+ start(document: string, request: DocumentTranscriptionRequest, member: Member, signal?: AbortSignal): Promise<TranscriptionTask>
102
+ list(document: string, member: Member): Promise<TranscriptionListAnswer>
103
+ get(document: string, query: { id: string } | { intent: string }, member: Member): Promise<TranscriptionTask>
104
+ readResult(document: string, id: string, revision: string, member: Member): Promise<TranscriptionRevision>
105
+ /** Rechecks current canonical source and the exact original audio, without recognizing it again. */
106
+ verifyResult(document: string, id: string, revision: string, source: CanonicalSource, member: Member): Promise<TranscriptionRevision>
107
+ cancel(document: string, query: { id: string } | { intent: string }, member: Member): Promise<TranscriptionCancellation>
108
+ /** Checks a start with the same path, without storing a task or calling the provider, so an agent asks only for one that would run. */
109
+ checkStart(document: string, request: DocumentTranscriptionRequest, member: Member, signal?: AbortSignal): Promise<void>
110
+ /** Undefined when there is something to cancel; otherwise the unchanged state that cancel would report. */
111
+ checkCancel(document: string, query: { id: string } | { intent: string }, member: Member): Promise<TranscriptionCancellation | undefined>
112
+ }
@@ -0,0 +1,97 @@
1
+ import type { SpeechEngineInfo, SpeechVoice, VoiceCatalog } from './speech.ts'
2
+ import type { CanonicalSource } from '../ir.ts'
3
+ import { defineService } from './service.ts'
4
+
5
+ /** A step number is usable only with the structure binding returned by the current snapshot. */
6
+ export interface VoiceTarget { section: string, step: number, binding: string }
7
+ export type VoiceScope = { kind: 'document' } | { kind: 'page', section: string } | { kind: 'step', target: VoiceTarget } | { kind: 'steps', targets: VoiceTarget[] }
8
+ export interface StepVoiceOverride extends VoiceTarget { voice: SpeechVoice }
9
+ export interface VoiceOverrides { pages: Record<string, SpeechVoice>, steps: StepVoiceOverride[] }
10
+ export interface ResolvedVoice {
11
+ target: VoiceTarget
12
+ voice?: SpeechVoice
13
+ inheritedFrom: 'step' | 'page' | 'document' | 'unset'
14
+ override?: SpeechVoice
15
+ editable: boolean
16
+ reason?: 'cue-step' | 'step-binding-stale' | 'recording-unavailable'
17
+ }
18
+ /** Registered by the actual lecture plugin implementation, never inferred from authored configuration. */
19
+ export const VOICEOVER = defineService<{ version: 1, preview: true }>('presmium.voiceover')
20
+ export interface VoiceoverCapability {
21
+ available: boolean
22
+ reason?: 'not-configured' | 'voice-unset' | 'step-binding-stale' | 'speech-unavailable' | 'voice-unavailable' | 'catalog-unavailable'
23
+ providers: SpeechEngineInfo[]
24
+ defaultEngine?: string
25
+ }
26
+ export interface VoiceoverStepStatus {
27
+ target: VoiceTarget
28
+ stepId?: string
29
+ state: 'ready' | 'missing' | 'blocked' | 'empty'
30
+ utterances: number
31
+ cached: number
32
+ reason?: string
33
+ }
34
+ export interface VoiceoverSnapshot {
35
+ revision: string
36
+ source: CanonicalSource
37
+ capability: VoiceoverCapability
38
+ steps: VoiceoverStepStatus[]
39
+ jobs: VoiceoverJob[]
40
+ /** Real speech units whose source cannot yet be assigned to one full-document step. */
41
+ unboundByPage?: { section: string, state: VoiceoverStepStatus['state'], utterances: number, cached: number, reason?: string }[]
42
+ }
43
+ export type VoiceoverJobState = 'queued' | 'running' | 'cancelling' | 'cancelled' | 'done' | 'failed' | 'stale'
44
+ export interface VoiceoverJob {
45
+ id: string
46
+ sequence: number
47
+ /** Client-owned generation intent, available before the job response is delivered. */
48
+ intent: string
49
+ revision: string
50
+ source: CanonicalSource
51
+ scope: VoiceScope
52
+ state: VoiceoverJobState
53
+ total: number
54
+ completed: number
55
+ failed: number
56
+ createdAt: string
57
+ startedAt?: string
58
+ finishedAt?: string
59
+ error?: string
60
+ }
61
+ export interface VoiceoverRequest { intent: string, revision: string, source: CanonicalSource, scope: VoiceScope, confirmed: boolean }
62
+ /** Cancellation can precede admission; a pre-admission refusal is not a fabricated job. */
63
+ export interface VoiceoverCancellation { intent: string, state: VoiceoverJobState, job?: VoiceoverJob }
64
+ export interface VoicePreviewRequest {
65
+ revision: string
66
+ source: CanonicalSource
67
+ voice: SpeechVoice
68
+ mode: 'sample' | 'step'
69
+ target?: VoiceTarget
70
+ confirmed: boolean
71
+ quote: string
72
+ }
73
+ export type VoicePreviewQuery = Omit<VoicePreviewRequest, 'confirmed' | 'quote' | 'source'>
74
+ export interface VoicePreviewQuote extends VoicePreviewQuery {
75
+ source: CanonicalSource
76
+ text: string
77
+ provider: string
78
+ local: boolean
79
+ cached: boolean
80
+ quote: string
81
+ }
82
+ export interface VoicePreviewAnswer {
83
+ revision: string
84
+ source: CanonicalSource
85
+ voice: SpeechVoice
86
+ text: string
87
+ cached: boolean
88
+ key: string
89
+ audio: string
90
+ duration: number
91
+ provider: string
92
+ }
93
+ export interface VoiceCatalogAnswer {
94
+ engines: SpeechEngineInfo[]
95
+ catalog: VoiceCatalog
96
+ engine?: string
97
+ }
@@ -0,0 +1,68 @@
1
+ import type { CanonicalSource } from '../ir.ts'
2
+ import type { Member } from './server.ts'
3
+ import type { VoiceoverCancellation, VoiceoverJob, VoiceoverRequest, VoiceoverSnapshot, VoicePreviewAnswer, VoicePreviewQuery, VoicePreviewQuote, VoicePreviewRequest } from './voice.ts'
4
+ import { defineService } from './service.ts'
5
+
6
+ export interface VoiceoverStatusQuery { source?: CanonicalSource, revision?: string }
7
+ export type VoiceoverStart = Omit<VoiceoverRequest, 'confirmed'>
8
+ export type VoicePreviewStart = Omit<VoicePreviewRequest, 'confirmed'>
9
+ export type VoicePreviewCheck = VoicePreviewQuery & { source?: CanonicalSource }
10
+ export type VoiceoverJobQuery = { id: string } | { intent: string }
11
+ export interface VoiceoverProgress { completed: number, failed: number, error?: string }
12
+ export interface VoiceoverRunResult extends VoiceoverProgress { state: 'done' | 'failed' | 'stale' | 'cancelled' }
13
+ /** A claim on the existing generation lock. Dispose after execution or failed admission. */
14
+ export interface PreparedVoiceover {
15
+ source: CanonicalSource
16
+ revision: string
17
+ scope: VoiceoverStart['scope']
18
+ total: number
19
+ run(signal: AbortSignal, progress: (value: VoiceoverProgress) => void): Promise<VoiceoverRunResult>
20
+ dispose(): void
21
+ }
22
+ export interface VoicePreviewMedia extends Omit<VoicePreviewAnswer, 'audio'> { bytes: Uint8Array, mediaType: string }
23
+ /** Stateless plugin execution. The host owns intents and jobs; discovery creates neither. */
24
+ export interface VoiceoverExecution {
25
+ status(query: VoiceoverStatusQuery, signal: AbortSignal): Promise<Omit<VoiceoverSnapshot, 'jobs'>>
26
+ prepare(request: VoiceoverStart, signal: AbortSignal): Promise<PreparedVoiceover>
27
+ quote(query: VoicePreviewCheck, signal: AbortSignal): Promise<VoicePreviewQuote>
28
+ preview(request: VoicePreviewStart, signal: AbortSignal): Promise<VoicePreviewMedia>
29
+ }
30
+ export const VOICEOVER_EXECUTION = defineService<VoiceoverExecution>('presmium.voiceoverExecution')
31
+
32
+ export interface VoiceoverObservation extends VoiceoverCancellation { error?: string, current?: boolean | 'unknown' }
33
+ export interface VoiceoverJobsAnswer { document: string, jobs: (VoiceoverJob & { current: boolean | 'unknown' })[], idempotency: 'process', limits: { intents: number, recentJobs: number } }
34
+ export interface VoicePreviewResource extends Omit<VoicePreviewAnswer, 'audio'> { id: string, mediaType: string, byteLength: number, sha256: string }
35
+ export interface VoicePreviewResourceData { resource: VoicePreviewResource, bytes: Uint8Array }
36
+ /** Shared author entry. Adapters obtain spending approval; model input cannot supply confirmation. */
37
+ export interface DocumentVoiceTasks {
38
+ snapshot(document: string, query: VoiceoverStatusQuery, member: Member, signal?: AbortSignal): Promise<VoiceoverSnapshot>
39
+ listJobs(document: string, member: Member): Promise<VoiceoverJobsAnswer>
40
+ getJob(document: string, query: VoiceoverJobQuery, member: Member): Promise<VoiceoverObservation>
41
+ start(document: string, request: VoiceoverStart, member: Member, signal?: AbortSignal): Promise<VoiceoverJob>
42
+ cancel(document: string, query: VoiceoverJobQuery, member: Member): Promise<VoiceoverCancellation>
43
+ quotePreview(document: string, query: VoicePreviewCheck, member: Member, signal?: AbortSignal): Promise<VoicePreviewQuote>
44
+ preview(document: string, request: VoicePreviewStart, member: Member, signal?: AbortSignal): Promise<VoicePreviewResource>
45
+ readPreview(document: string, id: string, member: Member): Promise<VoicePreviewResourceData>
46
+ }
47
+ export const DOCUMENT_VOICE_TASKS = defineService<DocumentVoiceTasks>('presmium.documentVoiceTasks')
48
+
49
+ const source = { type: 'object', additionalProperties: false, properties: { documentId: { type: 'string', minLength: 1, maxLength: 200 }, version: { type: 'string', pattern: '^[a-f\\d]{20}$' }, fingerprint: { type: 'string', pattern: '^[a-f\\d]{16}$' } }, required: ['documentId', 'version', 'fingerprint'] } as const
50
+ const revision = { type: 'string', pattern: '^[a-f\\d]{16}$' } as const
51
+ const target = { type: 'object', additionalProperties: false, properties: { section: { type: 'string', minLength: 1, maxLength: 200 }, step: { type: 'integer', minimum: 0 }, binding: revision }, required: ['section', 'step', 'binding'] } as const
52
+ const scope = { oneOf: [
53
+ { type: 'object', additionalProperties: false, properties: { kind: { const: 'document' } }, required: ['kind'] },
54
+ { type: 'object', additionalProperties: false, properties: { kind: { const: 'page' }, section: target.properties.section }, required: ['kind', 'section'] },
55
+ { type: 'object', additionalProperties: false, properties: { kind: { const: 'step' }, target }, required: ['kind', 'target'] },
56
+ { type: 'object', additionalProperties: false, properties: { kind: { const: 'steps' }, targets: { type: 'array', items: target, minItems: 1, maxItems: 500 } }, required: ['kind', 'targets'] },
57
+ ] } as const
58
+ const intent = { type: 'string', pattern: '^[A-Za-z\\d_-]{16,128}$' } as const
59
+ export const VOICEOVER_STATUS_SCHEMA = { type: 'object', additionalProperties: false, properties: { source, revision } } as const
60
+ export const VOICEOVER_START_SCHEMA = { type: 'object', additionalProperties: false, properties: { intent, source, revision, scope }, required: ['intent', 'source', 'revision', 'scope'] } as const
61
+ export const VOICEOVER_JOB_QUERY_SCHEMA = { oneOf: [
62
+ { type: 'object', additionalProperties: false, properties: { id: { type: 'string', pattern: '^[a-f\\d]{8}-[a-f\\d]{4}-[a-f\\d]{4}-[a-f\\d]{4}-[a-f\\d]{12}$' } }, required: ['id'] },
63
+ { type: 'object', additionalProperties: false, properties: { intent }, required: ['intent'] },
64
+ ] } as const
65
+ const voice = { type: 'object', additionalProperties: false, properties: { voice: { type: 'string', minLength: 1, maxLength: 200 }, engine: { type: 'string', minLength: 1, maxLength: 200 }, rate: { type: 'number', exclusiveMinimum: 0 }, style: { type: 'string', maxLength: 200 } }, required: ['voice'] } as const
66
+ const preview = { revision, source, voice, mode: { enum: ['sample', 'step'] }, target } as const
67
+ export const VOICE_PREVIEW_CHECK_SCHEMA = { type: 'object', additionalProperties: false, properties: preview, required: ['revision', 'voice', 'mode'], allOf: [{ if: { properties: { mode: { const: 'step' } } }, then: { required: ['target'] } }] } as const
68
+ export const VOICE_PREVIEW_START_SCHEMA = { type: 'object', additionalProperties: false, properties: { ...preview, quote: { type: 'string', pattern: '^[a-f\\d]{64}$' } }, required: ['source', 'revision', 'voice', 'mode', 'quote'], allOf: VOICE_PREVIEW_CHECK_SCHEMA.allOf } as const