@laplace.live/persona-sdk 1.4.0 → 1.6.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.
@@ -1,4 +1,4 @@
1
- import { finiteOr, isFiniteNumber, isRecord, nonEmptyString } from "./guards.js";
1
+ import { finiteOr, isFiniteNumber, isRecord, keysOf, nonEmptyString } from "./guards.js";
2
2
  import { clamp } from "./limits.js";
3
3
  export const LIP_SYNC_VOWELS = ['A', 'I', 'U', 'E', 'O'];
4
4
  export const LIP_SYNC_PHONEMES = [...LIP_SYNC_VOWELS, 'S'];
@@ -56,20 +56,6 @@ export function healLipSyncConfig(raw) {
56
56
  export function isLipSyncMode(value) {
57
57
  return LIP_SYNC_MODES.some(mode => mode === value);
58
58
  }
59
- export const VOICE_INPUT_NAMES = [
60
- 'VoiceVolume',
61
- 'VoiceFrequency',
62
- 'VoiceVolumePlusMouthOpen',
63
- 'VoiceFrequencyPlusMouthSmile',
64
- 'VoiceA',
65
- 'VoiceI',
66
- 'VoiceU',
67
- 'VoiceE',
68
- 'VoiceO',
69
- 'VoiceSilence',
70
- 'VoiceMouthOpen',
71
- 'VoiceMouthSpread',
72
- ];
73
59
  export const VOICE_INPUT_RANGES = {
74
60
  VoiceVolume: [0, 1],
75
61
  VoiceFrequency: [0, 1],
@@ -84,6 +70,7 @@ export const VOICE_INPUT_RANGES = {
84
70
  VoiceMouthOpen: [0, 1],
85
71
  VoiceMouthSpread: [-1, 1],
86
72
  };
73
+ export const VOICE_INPUT_NAMES = keysOf(VOICE_INPUT_RANGES);
87
74
  /** Semantic import aliases; source analyzer amplitudes need not match Persona's. */
88
75
  export const NIZIMA_VOICE_ALIASES = {
89
76
  LipSyncVolume: 'VoiceVolume',
@@ -1,11 +1,16 @@
1
- /** Locales shipped in the compiled catalogs (`pnpm i18n`). */
2
- export declare const LOCALES: readonly ["en", "ja", "zh-CN", "zh-TW"];
3
- export type Locale = (typeof LOCALES)[number];
1
+ export type Locale = keyof typeof LOCALE_LABELS;
4
2
  /** `ui.language` setting: an explicit locale, or follow the OS language. */
5
3
  export type LanguageSetting = 'system' | Locale;
6
4
  /** `ui.theme` setting: an explicit appearance, or follow the OS. */
7
5
  export type ThemeSetting = 'system' | 'light' | 'dark';
8
6
  /** Native-language display names for the language picker (deliberately untranslated). */
9
- export declare const LOCALE_LABELS: Record<Locale, string>;
7
+ export declare const LOCALE_LABELS: {
8
+ en: string;
9
+ ja: string;
10
+ 'zh-CN': string;
11
+ 'zh-TW': string;
12
+ };
13
+ /** Locales shipped in the compiled catalogs (`pnpm i18n`). */
14
+ export declare const LOCALES: readonly Locale[];
10
15
  /** Best supported locale for a BCP 47-ish tag: exact match first, then fuzzy per language. */
11
16
  export declare function matchLocaleTag(tag: string): Locale;
@@ -1,5 +1,4 @@
1
- /** Locales shipped in the compiled catalogs (`pnpm i18n`). */
2
- export const LOCALES = ['en', 'ja', 'zh-CN', 'zh-TW'];
1
+ import { isOneOf, keysOf } from "./guards.js";
3
2
  /** Native-language display names for the language picker (deliberately untranslated). */
4
3
  export const LOCALE_LABELS = {
5
4
  en: 'English',
@@ -7,9 +6,11 @@ export const LOCALE_LABELS = {
7
6
  'zh-CN': '简体中文',
8
7
  'zh-TW': '繁體中文',
9
8
  };
9
+ /** Locales shipped in the compiled catalogs (`pnpm i18n`). */
10
+ export const LOCALES = keysOf(LOCALE_LABELS);
10
11
  /** Best supported locale for a BCP 47-ish tag: exact match first, then fuzzy per language. */
11
12
  export function matchLocaleTag(tag) {
12
- if (LOCALES.includes(tag))
13
+ if (isOneOf(tag, LOCALES))
13
14
  return tag;
14
15
  const t = tag.toLowerCase();
15
16
  // Most systems/browsers report `zh-TW`/`zh-HK` with no `hant` subtag, so match the regions too.
@@ -0,0 +1,15 @@
1
+ import type { AssetRef, SceneTransition } from '../wire/types.ts';
2
+ export declare const SCENE_TRANSITION_TYPES: readonly ["cut", "fade", "wipe", "circle", "image", "video"];
3
+ export declare const SCENE_TRANSITION_DURATION_MIN_MS = 100;
4
+ export declare const SCENE_TRANSITION_DURATION_MAX_MS = 10000;
5
+ export declare const SCENE_TRANSITION_FADE_DEFAULT_MS = 300;
6
+ export declare const SCENE_TRANSITION_SWITCH_POINT_MIN = 0.05;
7
+ export declare const SCENE_TRANSITION_SWITCH_POINT_MAX = 0.95;
8
+ /** The destination scene's entrance effect; existing scenes keep an immediate cut. */
9
+ export declare function defaultSceneTransition(): SceneTransition;
10
+ /** The media kind a transition type plays, or null for the built-in effects. */
11
+ export declare function sceneTransitionMedia(type: SceneTransition['type']): 'image' | 'video' | null;
12
+ /** Whether `asset` can back the transition: an available image or video, matching the type when it plays media. */
13
+ export declare function sceneTransitionAssetUsable(transition: SceneTransition, asset: Pick<AssetRef, 'kind' | 'exists'> | null | undefined): boolean;
14
+ /** Heal saved transition settings independently, retaining media while another effect is selected. */
15
+ export declare function healSceneTransition(raw: unknown): SceneTransition;
@@ -0,0 +1,48 @@
1
+ import { finiteOr, isOneOf, isRecord, nonEmptyString } from "./guards.js";
2
+ import { clamp, hexColorOr } from "./limits.js";
3
+ export const SCENE_TRANSITION_TYPES = ['cut', 'fade', 'wipe', 'circle', 'image', 'video'];
4
+ export const SCENE_TRANSITION_DURATION_MIN_MS = 100;
5
+ export const SCENE_TRANSITION_DURATION_MAX_MS = 10_000;
6
+ export const SCENE_TRANSITION_FADE_DEFAULT_MS = 300;
7
+ export const SCENE_TRANSITION_SWITCH_POINT_MIN = 0.05;
8
+ export const SCENE_TRANSITION_SWITCH_POINT_MAX = 0.95;
9
+ /** The destination scene's entrance effect; existing scenes keep an immediate cut. */
10
+ export function defaultSceneTransition() {
11
+ return {
12
+ type: 'cut',
13
+ durationMs: 1000,
14
+ color: '#000000',
15
+ assetId: null,
16
+ switchPoint: 0.5,
17
+ autoFade: false,
18
+ fadeInMs: SCENE_TRANSITION_FADE_DEFAULT_MS,
19
+ fadeOutMs: SCENE_TRANSITION_FADE_DEFAULT_MS,
20
+ };
21
+ }
22
+ /** The media kind a transition type plays, or null for the built-in effects. */
23
+ export function sceneTransitionMedia(type) {
24
+ return type === 'image' || type === 'video' ? type : null;
25
+ }
26
+ /** Whether `asset` can back the transition: an available image or video, matching the type when it plays media. */
27
+ export function sceneTransitionAssetUsable(transition, asset) {
28
+ if (!asset?.exists || (asset.kind !== 'image' && asset.kind !== 'video'))
29
+ return false;
30
+ const media = sceneTransitionMedia(transition.type);
31
+ return media === null || asset.kind === media;
32
+ }
33
+ /** Heal saved transition settings independently, retaining media while another effect is selected. */
34
+ export function healSceneTransition(raw) {
35
+ const d = defaultSceneTransition();
36
+ if (!isRecord(raw))
37
+ return d;
38
+ return {
39
+ type: isOneOf(raw.type, SCENE_TRANSITION_TYPES) ? raw.type : d.type,
40
+ durationMs: clamp(finiteOr(raw.durationMs, d.durationMs), SCENE_TRANSITION_DURATION_MIN_MS, SCENE_TRANSITION_DURATION_MAX_MS),
41
+ color: hexColorOr(raw.color, d.color),
42
+ assetId: nonEmptyString(raw.assetId),
43
+ switchPoint: clamp(finiteOr(raw.switchPoint, d.switchPoint), SCENE_TRANSITION_SWITCH_POINT_MIN, SCENE_TRANSITION_SWITCH_POINT_MAX),
44
+ autoFade: typeof raw.autoFade === 'boolean' ? raw.autoFade : d.autoFade,
45
+ fadeInMs: clamp(finiteOr(raw.fadeInMs, d.fadeInMs), 0, SCENE_TRANSITION_DURATION_MAX_MS),
46
+ fadeOutMs: clamp(finiteOr(raw.fadeOutMs, d.fadeOutMs), 0, SCENE_TRANSITION_DURATION_MAX_MS),
47
+ };
48
+ }
@@ -1,10 +1,11 @@
1
+ import { isOneOf } from "../values/guards.js";
1
2
  /** Every error the server can return, as string discriminants (never numeric bands). */
2
3
  export const API_ERROR_CODES = [
3
4
  'parse-error',
4
5
  'invalid-request',
5
6
  'unknown-method',
6
7
  'invalid-params',
7
- /** A referenced id (scene, instance, model, asset, hotkey, shortcut, expression) resolves to nothing. */
8
+ /** A referenced id (scene, instance, model, asset, hotkey, automation, expression) resolves to nothing. */
8
9
  'not-found',
9
10
  /** The instance's model format cannot perform this method (e.g. MToon on Live2D). */
10
11
  'unsupported-for-format',
@@ -19,7 +20,7 @@ export const API_ERROR_CODES = [
19
20
  'internal',
20
21
  ];
21
22
  export function isApiErrorCode(v) {
22
- return typeof v === 'string' && API_ERROR_CODES.includes(v);
23
+ return isOneOf(v, API_ERROR_CODES);
23
24
  }
24
25
  /** Thrown by {@link PersonaClient.call} when the server answers with an error envelope. */
25
26
  export class PersonaApiError extends Error {
@@ -1,7 +1,7 @@
1
- import type { ExpressionPersistence, HotkeyState, ModelFormat, PoseStatus, SceneState, Settings, ShortcutInfo, TrackingStatus } from './types.ts';
1
+ import type { AutomationInfo, ExpressionPersistence, HotkeyState, ModelFormat, PoseStatus, SceneState, Settings, TrackingStatus } from './types.ts';
2
2
  /** Every push event a session can subscribe to, with its payload. */
3
3
  export interface EventMap {
4
- /** Any scene mutation: create/delete/rename/activate/shortcut and persisted scene edits. */
4
+ /** Any scene mutation: create/delete/rename/activate and persisted scene edits. */
5
5
  'scene.changed': SceneState;
6
6
  'settings.changed': {
7
7
  settings: Settings;
@@ -10,9 +10,9 @@ export interface EventMap {
10
10
  modelId: string | null;
11
11
  config: HotkeyState;
12
12
  };
13
- /** The app-shortcut list, its names, or its OS registrations changed. */
14
- 'shortcut.state': {
15
- shortcuts: ShortcutInfo[];
13
+ /** The automation list, its names, or its OS registrations changed. */
14
+ 'automation.state': {
15
+ automations: AutomationInfo[];
16
16
  };
17
17
  /** Network channel aggregates; `sources` includes webcam status and is optional for older servers. */
18
18
  'tracking.status': {
@@ -1,10 +1,11 @@
1
+ import { isOneOf } from "../values/guards.js";
1
2
  // Built from a `Record<EventName, …>` so a new EventMap key that is missing
2
3
  // here fails the build instead of silently failing `isEventName`.
3
4
  export const EVENT_NAMES = Object.keys({
4
5
  'scene.changed': true,
5
6
  'settings.changed': true,
6
7
  'hotkey.state': true,
7
- 'shortcut.state': true,
8
+ 'automation.state': true,
8
9
  'tracking.status': true,
9
10
  'instance.loaded': true,
10
11
  'expression.changed': true,
@@ -18,5 +19,5 @@ export const EVENT_NAMES = Object.keys({
18
19
  'expression.persistence': true,
19
20
  });
20
21
  export function isEventName(v) {
21
- return typeof v === 'string' && EVENT_NAMES.includes(v);
22
+ return isOneOf(v, EVENT_NAMES);
22
23
  }
@@ -3,7 +3,7 @@ import type { ControllerMovementConfig, ControllerState } from '../values/contro
3
3
  import type { LipSyncCalibrationCommand, LipSyncConfig, LipSyncMode, LipSyncState } from '../values/lipsync.ts';
4
4
  import type { SceneInspection } from '../values/stage-info.ts';
5
5
  import type { EventName } from './events.ts';
6
- import type { AnchorOption, AppCapability, AssetKind, AssetRef, Attach, AttachHeadAngle, BindingInput, Expression, ExpressionPersistence, HandTrackingMode, HotkeyConfig, HotkeyState, InjectEntry, InjectTarget, InstanceRuntime, JsonValue, LayerEffectKey, LayerEffects, LayerEffectsPatch, MediaPipeConfig, ModelInfo, ModelRef, MotionGroup, MToonTuning, ObjectContent, ObjectLightOverride, ObjectSpace, Place2D, Place3D, PlayingMotion, PoseSourceId, PoseStatus, Scene, SceneItem, ScenePatch, SceneState, ScreenPlacement, Settings, SettingsPatch, ShortcutInfo, TrackingSourceConfig, TrackingSourceId, TrackingSourceKind, TrackingStatus, VrmPlacement } from './types.ts';
6
+ import type { AnchorOption, AppCapability, AssetKind, AssetRef, Attach, AttachHeadAngle, AutomationInfo, BindingInput, Expression, ExpressionPersistence, HandTrackingMode, HotkeyConfig, HotkeyState, InjectEntry, InjectTarget, InstanceRuntime, JsonValue, LayerEffectKey, LayerEffects, LayerEffectsPatch, MediaPipeConfig, ModelInfo, ModelRef, MotionGroup, MToonTuning, ObjectContent, ObjectLightOverride, ObjectSpace, Place2D, Place3D, PlayingMotion, PoseSourceId, PoseStatus, Scene, SceneItem, ScenePatch, SceneState, ScreenPlacement, Settings, SettingsPatch, TrackingSourceConfig, TrackingSourceId, TrackingSourceKind, TrackingStatus, VrmPlacement } from './types.ts';
7
7
  /** Marker for methods that take no parameters; the client lets you omit the argument. */
8
8
  export type EmptyRequest = Record<never, never>;
9
9
  /** Marker for methods whose success response carries no data. */
@@ -36,12 +36,6 @@ export interface SceneDeleteRequest {
36
36
  sceneId: string;
37
37
  }
38
38
  export type SceneDeleteResponse = SceneState;
39
- export interface SceneSetShortcutRequest {
40
- sceneId: string;
41
- /** Electron accelerator string, or null to clear. */
42
- accelerator: string | null;
43
- }
44
- export type SceneSetShortcutResponse = SceneState;
45
39
  export type ScenePatchRequest = ScenePatch;
46
40
  export interface ScenePatchResponse {
47
41
  scene: Scene;
@@ -295,14 +289,14 @@ export interface HotkeyTriggerRequest {
295
289
  hotkeyId: string;
296
290
  }
297
291
  export type HotkeyTriggerResponse = EmptyResponse;
298
- export type ShortcutListRequest = EmptyRequest;
299
- export interface ShortcutListResponse {
300
- shortcuts: ShortcutInfo[];
292
+ export type AutomationListRequest = EmptyRequest;
293
+ export interface AutomationListResponse {
294
+ automations: AutomationInfo[];
301
295
  }
302
- export interface ShortcutTriggerRequest {
303
- shortcutId: string;
296
+ export interface AutomationRunRequest {
297
+ automationId: string;
304
298
  }
305
- export type ShortcutTriggerResponse = EmptyResponse;
299
+ export type AutomationRunResponse = EmptyResponse;
306
300
  export type ModelListRequest = EmptyRequest;
307
301
  export interface ModelListResponse {
308
302
  models: ModelRef[];
@@ -668,10 +662,6 @@ export interface MethodMap {
668
662
  request: SceneDeleteRequest;
669
663
  response: SceneDeleteResponse;
670
664
  };
671
- 'scene.setShortcut': {
672
- request: SceneSetShortcutRequest;
673
- response: SceneSetShortcutResponse;
674
- };
675
665
  /** Live edit of the active scene's sections; applies next frame, persists debounced. */
676
666
  'scene.patch': {
677
667
  request: ScenePatchRequest;
@@ -724,7 +714,7 @@ export interface MethodMap {
724
714
  request: InstanceSetPrimaryRequest;
725
715
  response: InstanceSetPrimaryResponse;
726
716
  };
727
- /** Drives the desktop's editing selection (outline + Model tab); ephemeral, never persisted. */
717
+ /** Drives the desktop's editing selection (handles and inspector); ephemeral, never persisted. */
728
718
  'selection.set': {
729
719
  request: SelectionSetRequest;
730
720
  response: SelectionSetResponse;
@@ -842,13 +832,13 @@ export interface MethodMap {
842
832
  request: HotkeyTriggerRequest;
843
833
  response: HotkeyTriggerResponse;
844
834
  };
845
- 'shortcut.list': {
846
- request: ShortcutListRequest;
847
- response: ShortcutListResponse;
835
+ 'automation.list': {
836
+ request: AutomationListRequest;
837
+ response: AutomationListResponse;
848
838
  };
849
- 'shortcut.trigger': {
850
- request: ShortcutTriggerRequest;
851
- response: ShortcutTriggerResponse;
839
+ 'automation.run': {
840
+ request: AutomationRunRequest;
841
+ response: AutomationRunResponse;
852
842
  };
853
843
  'model.list': {
854
844
  request: ModelListRequest;
@@ -1,10 +1,12 @@
1
1
  import type { InjectTarget } from './types.ts';
2
2
  /** Bumped on breaking wire changes; the server reports it in `hello` and clients warn on mismatch. */
3
- export declare const PROTOCOL_VERSION = 3;
3
+ export declare const PROTOCOL_VERSION = 4;
4
4
  /** Default port the Persona API server listens on (user-configurable in the app). */
5
5
  export declare const DEFAULT_API_PORT = 25034;
6
6
  /** Default host clients dial: the server binds loopback unless LAN is opted in. */
7
7
  export declare const DEFAULT_API_HOST = "127.0.0.1";
8
+ /** Scene activation waits for asset preparation before committing the new scene. */
9
+ export declare const SCENE_ACTIVATION_TIMEOUT_MS = 120000;
8
10
  /** Server close code: the session's API key was revoked. Terminal — the client must not redial. */
9
11
  export declare const CLOSE_KEY_REVOKED = 4001;
10
12
  /** Server close code: the user disconnected this session from Persona's settings. Terminal — the client must not redial. */
@@ -1,9 +1,11 @@
1
1
  /** Bumped on breaking wire changes; the server reports it in `hello` and clients warn on mismatch. */
2
- export const PROTOCOL_VERSION = 3;
2
+ export const PROTOCOL_VERSION = 4;
3
3
  /** Default port the Persona API server listens on (user-configurable in the app). */
4
4
  export const DEFAULT_API_PORT = 25034;
5
5
  /** Default host clients dial: the server binds loopback unless LAN is opted in. */
6
6
  export const DEFAULT_API_HOST = '127.0.0.1';
7
+ /** Scene activation waits for asset preparation before committing the new scene. */
8
+ export const SCENE_ACTIVATION_TIMEOUT_MS = 120_000;
7
9
  /** Server close code: the session's API key was revoked. Terminal — the client must not redial. */
8
10
  export const CLOSE_KEY_REVOKED = 4001;
9
11
  /** Server close code: the user disconnected this session from Persona's settings. Terminal — the client must not redial. */
@@ -1,6 +1,24 @@
1
1
  import * as z from 'zod';
2
2
  import type { MethodName } from './methods.ts';
3
3
  import type { HotkeyConfig, ObjectContent } from './types.ts';
4
+ /** A complete transition replacement; scene.patch remains stage-owned for its other slices. */
5
+ export declare const SceneTransitionSchema: z.ZodObject<{
6
+ type: z.ZodEnum<{
7
+ circle: "circle";
8
+ image: "image";
9
+ video: "video";
10
+ cut: "cut";
11
+ fade: "fade";
12
+ wipe: "wipe";
13
+ }>;
14
+ durationMs: z.ZodNumber;
15
+ color: z.ZodString;
16
+ assetId: z.ZodNullable<z.ZodString>;
17
+ switchPoint: z.ZodNumber;
18
+ autoFade: z.ZodDefault<z.ZodBoolean>;
19
+ fadeInMs: z.ZodDefault<z.ZodNumber>;
20
+ fadeOutMs: z.ZodDefault<z.ZodNumber>;
21
+ }, z.core.$strip>;
4
22
  export declare const InjectTargetSchema: z.ZodObject<{
5
23
  type: z.ZodEnum<{
6
24
  input: "input";
@@ -41,7 +59,6 @@ export declare const SettingsPatchSchema: z.ZodObject<{
41
59
  performance: z.ZodOptional<z.ZodObject<{
42
60
  showFps: z.ZodOptional<z.ZodBoolean>;
43
61
  fpsLimit: z.ZodOptional<z.ZodNumber>;
44
- selectionOutline: z.ZodOptional<z.ZodBoolean>;
45
62
  effectsQuality: z.ZodOptional<z.ZodEnum<{
46
63
  low: "low";
47
64
  medium: "medium";
@@ -128,10 +145,6 @@ export declare const requestSchemas: {
128
145
  'scene.delete': z.ZodObject<{
129
146
  sceneId: z.ZodString;
130
147
  }, z.core.$strip>;
131
- 'scene.setShortcut': z.ZodObject<{
132
- sceneId: z.ZodString;
133
- accelerator: z.ZodNullable<z.ZodString>;
134
- }, z.core.$strip>;
135
148
  'instance.add': z.ZodObject<{
136
149
  modelId: z.ZodString;
137
150
  }, z.core.$strip>;
@@ -164,8 +177,8 @@ export declare const requestSchemas: {
164
177
  'hotkey.trigger': z.ZodObject<{
165
178
  hotkeyId: z.ZodString;
166
179
  }, z.core.$strip>;
167
- 'shortcut.trigger': z.ZodObject<{
168
- shortcutId: z.ZodString;
180
+ 'automation.run': z.ZodObject<{
181
+ automationId: z.ZodString;
169
182
  }, z.core.$strip>;
170
183
  'tracking.addSource': z.ZodObject<{
171
184
  kind: z.ZodEnum<{
@@ -248,7 +261,6 @@ export declare const requestSchemas: {
248
261
  performance: z.ZodOptional<z.ZodObject<{
249
262
  showFps: z.ZodOptional<z.ZodBoolean>;
250
263
  fpsLimit: z.ZodOptional<z.ZodNumber>;
251
- selectionOutline: z.ZodOptional<z.ZodBoolean>;
252
264
  effectsQuality: z.ZodOptional<z.ZodEnum<{
253
265
  low: "low";
254
266
  medium: "medium";
@@ -1,15 +1,27 @@
1
1
  import * as z from 'zod';
2
2
  import { CONTROLLER_DEAD_ZONE_MAX } from "../values/controller.js";
3
3
  import { isRecord } from "../values/guards.js";
4
- import { SPEECH_URL_MAX_LENGTH, STORAGE_KEY_MAX_LENGTH, STORAGE_VALUE_MAX_LENGTH } from "../values/limits.js";
4
+ import { SCENE_COLOR_RE, SPEECH_URL_MAX_LENGTH, STORAGE_KEY_MAX_LENGTH, STORAGE_VALUE_MAX_LENGTH, } from "../values/limits.js";
5
5
  import { LIP_SYNC_CALIBRATION_ACTIONS, LIP_SYNC_GAIN_MAX, LIP_SYNC_MODES, LIP_SYNC_NOISE_GATE_MIN, LIP_SYNC_PHONEMES, LIP_SYNC_SMOOTHING_MAX, } from "../values/lipsync.js";
6
- import { ASSET_KINDS, POSE_SOURCE_IDS, TRACKING_SOURCE_IDS, TRACKING_SOURCE_KINDS } from "./types.js";
6
+ import { SCENE_TRANSITION_DURATION_MAX_MS, SCENE_TRANSITION_DURATION_MIN_MS, SCENE_TRANSITION_FADE_DEFAULT_MS, SCENE_TRANSITION_SWITCH_POINT_MAX, SCENE_TRANSITION_SWITCH_POINT_MIN, SCENE_TRANSITION_TYPES, } from "../values/scene-transition.js";
7
+ import { ASSET_KINDS, EFFECTS_QUALITY_LEVELS, INJECT_TARGET_TYPES, LIVE2D_ENGINES, MEDIAPIPE_DELEGATES, POSE_SOURCE_IDS, TRACKING_SOURCE_IDS, TRACKING_SOURCE_KINDS, } from "./types.js";
7
8
  // Runtime validation for the request side of the wire. Schemas exist for the
8
9
  // methods whose params the app's main process consumes directly; methods without
9
10
  // one are stage-owned — the renderer validates and heals them (same rules as the
10
11
  // panel). Fields marked z.custom are deliberately gated shallowly here because
11
12
  // the app heals them into shape server-side.
12
13
  const nonEmpty = z.string().min(1);
14
+ /** A complete transition replacement; scene.patch remains stage-owned for its other slices. */
15
+ export const SceneTransitionSchema = z.object({
16
+ type: z.enum(SCENE_TRANSITION_TYPES),
17
+ durationMs: z.number().min(SCENE_TRANSITION_DURATION_MIN_MS).max(SCENE_TRANSITION_DURATION_MAX_MS),
18
+ color: z.string().regex(SCENE_COLOR_RE),
19
+ assetId: nonEmpty.refine(v => v.trim() !== '', 'assetId must not be blank').nullable(),
20
+ switchPoint: z.number().min(SCENE_TRANSITION_SWITCH_POINT_MIN).max(SCENE_TRANSITION_SWITCH_POINT_MAX),
21
+ autoFade: z.boolean().default(false),
22
+ fadeInMs: z.number().min(0).max(SCENE_TRANSITION_DURATION_MAX_MS).default(SCENE_TRANSITION_FADE_DEFAULT_MS),
23
+ fadeOutMs: z.number().min(0).max(SCENE_TRANSITION_DURATION_MAX_MS).default(SCENE_TRANSITION_FADE_DEFAULT_MS),
24
+ });
13
25
  const port = z.number().int().min(1).max(65535);
14
26
  const lipSyncCalibration = z.discriminatedUnion('action', [
15
27
  z.object({ action: z.enum(LIP_SYNC_CALIBRATION_ACTIONS) }),
@@ -28,10 +40,10 @@ const mediapipePatch = z.object({
28
40
  face: z.boolean().optional(),
29
41
  hands: z.boolean().optional(),
30
42
  body: z.boolean().optional(),
31
- delegate: z.enum(['CPU', 'GPU']).optional(),
43
+ delegate: z.enum(MEDIAPIPE_DELEGATES).optional(),
32
44
  });
33
45
  export const InjectTargetSchema = z.object({
34
- type: z.enum(['input', 'live2d-param', 'vrm-expression']),
46
+ type: z.enum(INJECT_TARGET_TYPES),
35
47
  id: nonEmpty,
36
48
  instanceId: nonEmpty.optional(),
37
49
  });
@@ -48,10 +60,9 @@ export const SettingsPatchSchema = z.object({
48
60
  .object({
49
61
  showFps: z.boolean().optional(),
50
62
  fpsLimit: z.number().optional(),
51
- selectionOutline: z.boolean().optional(),
52
- effectsQuality: z.enum(['high', 'medium', 'low']).optional(),
63
+ effectsQuality: z.enum(EFFECTS_QUALITY_LEVELS).optional(),
53
64
  renderScale: z.number().optional(),
54
- live2dEngine: z.enum(['pixi', 'three']).optional(),
65
+ live2dEngine: z.enum(LIVE2D_ENGINES).optional(),
55
66
  })
56
67
  .optional(),
57
68
  });
@@ -95,7 +106,6 @@ export const requestSchemas = {
95
106
  'scene.duplicate': z.object({ sceneId: nonEmpty }),
96
107
  'scene.rename': z.object({ sceneId: nonEmpty, name: nonEmpty }),
97
108
  'scene.delete': z.object({ sceneId: nonEmpty }),
98
- 'scene.setShortcut': z.object({ sceneId: nonEmpty, accelerator: nonEmpty.nullable() }),
99
109
  'instance.add': z.object({ modelId: nonEmpty }),
100
110
  'instance.setModel': z.object({ instanceId: nonEmpty, modelId: nonEmpty }),
101
111
  'object.add': z.object({
@@ -114,7 +124,7 @@ export const requestSchemas = {
114
124
  config: z.custom(isRecord, 'config must be a hotkey config'),
115
125
  }),
116
126
  'hotkey.trigger': z.object({ hotkeyId: nonEmpty }),
117
- 'shortcut.trigger': z.object({ shortcutId: nonEmpty }),
127
+ 'automation.run': z.object({ automationId: nonEmpty }),
118
128
  'tracking.addSource': z.object({
119
129
  // Straight off the kind registry, for the same reason as ASSET_KINDS below: a schema
120
130
  // narrower than the constant answers invalid-params for a kind the app itself accepts.