@laplace.live/persona-sdk 1.3.0 → 1.5.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 type { ShortcutInfo } from '../wire/types.ts';
1
+ import type { AutomationInfo } from '../wire/types.ts';
2
2
  /** Last path segment, tolerating both `/` and `\` separators. */
3
3
  export declare function fileBasename(path: string): string;
4
4
  /**
@@ -7,7 +7,12 @@ export declare function fileBasename(path: string): string;
7
7
  * so the basename is the only identifier there is.
8
8
  */
9
9
  export declare function motionLabel(file: string): string;
10
- /** English label for one shortcut action kind; kinds newer than this SDK read `Action`. */
11
- export declare function shortcutActionLabel(kind: string): string;
12
- /** Display label for an app shortcut: its title, else its action-kind labels joined with ` + `. */
13
- export declare function shortcutLabel(shortcut: Pick<ShortcutInfo, 'title' | 'actionKinds'>): string;
10
+ /** English label for one automation action kind; kinds newer than this SDK read `Action`. */
11
+ export declare function automationActionLabel(kind: string): string;
12
+ /**
13
+ * Join one automation's action labels: a wait ends a batch, so it reads as an arrow, while
14
+ * actions inside a batch read as ` + `. `kinds` must match `labels` in length and position.
15
+ */
16
+ export declare function joinAutomationActionLabels(labels: readonly string[], kinds: readonly string[]): string;
17
+ /** Display label for an app automation: its title, else its joined action-kind labels. */
18
+ export declare function automationLabel(automation: Pick<AutomationInfo, 'title' | 'actionKinds'>): string;
@@ -12,21 +12,32 @@ export function motionLabel(file) {
12
12
  return base.replace(/\.motion3\.json$|\.exp3\.json$|\.vrma$|\.json$/i, '') || base;
13
13
  }
14
14
  // English fallbacks; localizing clients keep their own map and use these for unknown kinds.
15
- const SHORTCUT_ACTION_KIND_LABELS = {
15
+ const AUTOMATION_ACTION_KIND_LABELS = {
16
16
  'effect-toggle': 'Toggle Effect',
17
17
  'effect-params': 'Effect Settings',
18
18
  'camera-pose': 'Camera Position',
19
19
  'reset-camera': 'Reset Camera',
20
20
  'layer-visibility': 'Layer Visibility',
21
21
  'stream-mode': 'Stream Mode',
22
+ delay: 'Wait',
22
23
  };
23
- /** English label for one shortcut action kind; kinds newer than this SDK read `Action`. */
24
- export function shortcutActionLabel(kind) {
25
- return SHORTCUT_ACTION_KIND_LABELS[kind] ?? 'Action';
24
+ /** English label for one automation action kind; kinds newer than this SDK read `Action`. */
25
+ export function automationActionLabel(kind) {
26
+ return AUTOMATION_ACTION_KIND_LABELS[kind] ?? 'Action';
26
27
  }
27
- /** Display label for an app shortcut: its title, else its action-kind labels joined with ` + `. */
28
- export function shortcutLabel(shortcut) {
29
- if (shortcut.title !== null && shortcut.title !== '')
30
- return shortcut.title;
31
- return shortcut.actionKinds.map(shortcutActionLabel).join(' + ') || 'Shortcut';
28
+ /**
29
+ * Join one automation's action labels: a wait ends a batch, so it reads as an arrow, while
30
+ * actions inside a batch read as ` + `. `kinds` must match `labels` in length and position.
31
+ */
32
+ export function joinAutomationActionLabels(labels, kinds) {
33
+ if (labels.length !== kinds.length)
34
+ throw new RangeError('automation action labels and kinds must have the same length');
35
+ return labels.reduce((out, label, i) => out + (i === 0 ? '' : kinds[i] === 'delay' || kinds[i - 1] === 'delay' ? ' → ' : ' + ') + label, '');
36
+ }
37
+ /** Display label for an app automation: its title, else its joined action-kind labels. */
38
+ export function automationLabel(automation) {
39
+ if (automation.title !== null && automation.title !== '')
40
+ return automation.title;
41
+ return (joinAutomationActionLabels(automation.actionKinds.map(automationActionLabel), automation.actionKinds) ||
42
+ 'Automation');
32
43
  }
@@ -72,8 +72,6 @@ export declare function healLipSyncProfile(raw: unknown): LipSyncProfile | null;
72
72
  export declare function healLipSyncConfig(raw: unknown): LipSyncConfig;
73
73
  /** Whether a persisted per-model mode is supported. */
74
74
  export declare function isLipSyncMode(value: unknown): value is LipSyncMode;
75
- export declare const VOICE_INPUT_NAMES: readonly ["VoiceVolume", "VoiceFrequency", "VoiceVolumePlusMouthOpen", "VoiceFrequencyPlusMouthSmile", "VoiceA", "VoiceI", "VoiceU", "VoiceE", "VoiceO", "VoiceSilence", "VoiceMouthOpen", "VoiceMouthSpread"];
76
- export type VoiceInputName = (typeof VOICE_INPUT_NAMES)[number];
77
75
  export declare const VOICE_INPUT_RANGES: {
78
76
  readonly VoiceVolume: readonly [0, 1];
79
77
  readonly VoiceFrequency: readonly [0, 1];
@@ -88,6 +86,8 @@ export declare const VOICE_INPUT_RANGES: {
88
86
  readonly VoiceMouthOpen: readonly [0, 1];
89
87
  readonly VoiceMouthSpread: readonly [-1, 1];
90
88
  };
89
+ export declare const VOICE_INPUT_NAMES: readonly VoiceInputName[];
90
+ export type VoiceInputName = keyof typeof VOICE_INPUT_RANGES;
91
91
  /** Semantic import aliases; source analyzer amplitudes need not match Persona's. */
92
92
  export declare const NIZIMA_VOICE_ALIASES: {
93
93
  readonly LipSyncVolume: "VoiceVolume";
@@ -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,14 @@
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_SWITCH_POINT_MIN = 0.05;
6
+ export declare const SCENE_TRANSITION_SWITCH_POINT_MAX = 0.95;
7
+ /** The destination scene's entrance effect; existing scenes keep an immediate cut. */
8
+ export declare function defaultSceneTransition(): SceneTransition;
9
+ /** The media kind a transition type plays, or null for the built-in effects. */
10
+ export declare function sceneTransitionMedia(type: SceneTransition['type']): 'image' | 'video' | null;
11
+ /** Whether `asset` can back the transition: an available image or video, matching the type when it plays media. */
12
+ export declare function sceneTransitionAssetUsable(transition: SceneTransition, asset: Pick<AssetRef, 'kind' | 'exists'> | null | undefined): boolean;
13
+ /** Heal saved transition settings independently, retaining media while another effect is selected. */
14
+ export declare function healSceneTransition(raw: unknown): SceneTransition;
@@ -0,0 +1,36 @@
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_SWITCH_POINT_MIN = 0.05;
7
+ export const SCENE_TRANSITION_SWITCH_POINT_MAX = 0.95;
8
+ /** The destination scene's entrance effect; existing scenes keep an immediate cut. */
9
+ export function defaultSceneTransition() {
10
+ return { type: 'cut', durationMs: 1000, color: '#000000', assetId: null, switchPoint: 0.5, autoFade: false };
11
+ }
12
+ /** The media kind a transition type plays, or null for the built-in effects. */
13
+ export function sceneTransitionMedia(type) {
14
+ return type === 'image' || type === 'video' ? type : null;
15
+ }
16
+ /** Whether `asset` can back the transition: an available image or video, matching the type when it plays media. */
17
+ export function sceneTransitionAssetUsable(transition, asset) {
18
+ if (!asset?.exists || (asset.kind !== 'image' && asset.kind !== 'video'))
19
+ return false;
20
+ const media = sceneTransitionMedia(transition.type);
21
+ return media === null || asset.kind === media;
22
+ }
23
+ /** Heal saved transition settings independently, retaining media while another effect is selected. */
24
+ export function healSceneTransition(raw) {
25
+ const d = defaultSceneTransition();
26
+ if (!isRecord(raw))
27
+ return d;
28
+ return {
29
+ type: isOneOf(raw.type, SCENE_TRANSITION_TYPES) ? raw.type : d.type,
30
+ durationMs: clamp(finiteOr(raw.durationMs, d.durationMs), SCENE_TRANSITION_DURATION_MIN_MS, SCENE_TRANSITION_DURATION_MAX_MS),
31
+ color: hexColorOr(raw.color, d.color),
32
+ assetId: nonEmptyString(raw.assetId),
33
+ switchPoint: clamp(finiteOr(raw.switchPoint, d.switchPoint), SCENE_TRANSITION_SWITCH_POINT_MIN, SCENE_TRANSITION_SWITCH_POINT_MAX),
34
+ autoFade: typeof raw.autoFade === 'boolean' ? raw.autoFade : d.autoFade,
35
+ };
36
+ }
@@ -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,4 +1,4 @@
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
4
  /** Any scene mutation: create/delete/rename/activate/shortcut and persisted scene edits. */
@@ -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. */
@@ -295,14 +295,14 @@ export interface HotkeyTriggerRequest {
295
295
  hotkeyId: string;
296
296
  }
297
297
  export type HotkeyTriggerResponse = EmptyResponse;
298
- export type ShortcutListRequest = EmptyRequest;
299
- export interface ShortcutListResponse {
300
- shortcuts: ShortcutInfo[];
298
+ export type AutomationListRequest = EmptyRequest;
299
+ export interface AutomationListResponse {
300
+ automations: AutomationInfo[];
301
301
  }
302
- export interface ShortcutTriggerRequest {
303
- shortcutId: string;
302
+ export interface AutomationRunRequest {
303
+ automationId: string;
304
304
  }
305
- export type ShortcutTriggerResponse = EmptyResponse;
305
+ export type AutomationRunResponse = EmptyResponse;
306
306
  export type ModelListRequest = EmptyRequest;
307
307
  export interface ModelListResponse {
308
308
  models: ModelRef[];
@@ -842,13 +842,13 @@ export interface MethodMap {
842
842
  request: HotkeyTriggerRequest;
843
843
  response: HotkeyTriggerResponse;
844
844
  };
845
- 'shortcut.list': {
846
- request: ShortcutListRequest;
847
- response: ShortcutListResponse;
845
+ 'automation.list': {
846
+ request: AutomationListRequest;
847
+ response: AutomationListResponse;
848
848
  };
849
- 'shortcut.trigger': {
850
- request: ShortcutTriggerRequest;
851
- response: ShortcutTriggerResponse;
849
+ 'automation.run': {
850
+ request: AutomationRunRequest;
851
+ response: AutomationRunResponse;
852
852
  };
853
853
  'model.list': {
854
854
  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,22 @@
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
+ }, z.core.$strip>;
4
20
  export declare const InjectTargetSchema: z.ZodObject<{
5
21
  type: z.ZodEnum<{
6
22
  input: "input";
@@ -164,8 +180,8 @@ export declare const requestSchemas: {
164
180
  'hotkey.trigger': z.ZodObject<{
165
181
  hotkeyId: z.ZodString;
166
182
  }, z.core.$strip>;
167
- 'shortcut.trigger': z.ZodObject<{
168
- shortcutId: z.ZodString;
183
+ 'automation.run': z.ZodObject<{
184
+ automationId: z.ZodString;
169
185
  }, z.core.$strip>;
170
186
  'tracking.addSource': z.ZodObject<{
171
187
  kind: z.ZodEnum<{
@@ -1,15 +1,25 @@
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_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
+ });
13
23
  const port = z.number().int().min(1).max(65535);
14
24
  const lipSyncCalibration = z.discriminatedUnion('action', [
15
25
  z.object({ action: z.enum(LIP_SYNC_CALIBRATION_ACTIONS) }),
@@ -28,10 +38,10 @@ const mediapipePatch = z.object({
28
38
  face: z.boolean().optional(),
29
39
  hands: z.boolean().optional(),
30
40
  body: z.boolean().optional(),
31
- delegate: z.enum(['CPU', 'GPU']).optional(),
41
+ delegate: z.enum(MEDIAPIPE_DELEGATES).optional(),
32
42
  });
33
43
  export const InjectTargetSchema = z.object({
34
- type: z.enum(['input', 'live2d-param', 'vrm-expression']),
44
+ type: z.enum(INJECT_TARGET_TYPES),
35
45
  id: nonEmpty,
36
46
  instanceId: nonEmpty.optional(),
37
47
  });
@@ -49,9 +59,9 @@ export const SettingsPatchSchema = z.object({
49
59
  showFps: z.boolean().optional(),
50
60
  fpsLimit: z.number().optional(),
51
61
  selectionOutline: z.boolean().optional(),
52
- effectsQuality: z.enum(['high', 'medium', 'low']).optional(),
62
+ effectsQuality: z.enum(EFFECTS_QUALITY_LEVELS).optional(),
53
63
  renderScale: z.number().optional(),
54
- live2dEngine: z.enum(['pixi', 'three']).optional(),
64
+ live2dEngine: z.enum(LIVE2D_ENGINES).optional(),
55
65
  })
56
66
  .optional(),
57
67
  });
@@ -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.