@laplace.live/persona-sdk 0.7.0 → 0.8.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.
- package/dist/values/effect-schema.d.ts +8 -0
- package/dist/values/effect-schema.js +21 -0
- package/dist/values/limits.d.ts +11 -0
- package/dist/values/limits.js +14 -0
- package/dist/wire/envelope.d.ts +3 -0
- package/dist/wire/envelope.js +4 -1
- package/dist/wire/events.d.ts +10 -0
- package/dist/wire/events.js +2 -0
- package/dist/wire/methods.d.ts +126 -1
- package/dist/wire/schemas.d.ts +39 -0
- package/dist/wire/schemas.js +45 -1
- package/dist/wire/types.d.ts +66 -1
- package/dist/wire/types.js +15 -0
- package/package.json +2 -2
|
@@ -18,5 +18,13 @@ export type ToggleEffectKey = {
|
|
|
18
18
|
}[keyof SceneEffects];
|
|
19
19
|
export declare const EFFECT_SPECS: Record<ToggleEffectKey, Readonly<Record<string, EffectParamSpec>>>;
|
|
20
20
|
export declare const TOGGLE_EFFECT_KEYS: readonly ToggleEffectKey[];
|
|
21
|
+
/**
|
|
22
|
+
* Registry effects that render as scene geometry inside the scene pass rather
|
|
23
|
+
* than composing into the post chain. A new scene-space effect must join this
|
|
24
|
+
* list, or its toggle needlessly forces the post path on and rebuilds the graph.
|
|
25
|
+
*/
|
|
26
|
+
export declare const SCENE_SPACE_EFFECT_KEYS: readonly ToggleEffectKey[];
|
|
27
|
+
/** Registry effects the post chain composes — {@link TOGGLE_EFFECT_KEYS} minus the scene-space ones. */
|
|
28
|
+
export declare const POST_EFFECT_KEYS: readonly ToggleEffectKey[];
|
|
21
29
|
/** Every registry effect at its defaults — the derived half of a fresh {@link SceneEffects}. */
|
|
22
30
|
export declare function defaultToggleEffects(): Pick<SceneEffects, ToggleEffectKey>;
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
// 1. Type its values in `SceneEffects` (types.ts) + its interface.
|
|
5
5
|
// 2. Spec it here; label it in the panel's effect-labels.ts (parity-checked).
|
|
6
6
|
// 3. Implement its stage under apps/desktop/src/renderer/vrm/webgpu/effects/.
|
|
7
|
+
// Scene-space effects (geometry, not a post stage — e.g. snow) also join
|
|
8
|
+
// SCENE_SPACE_EFFECT_KEYS below.
|
|
7
9
|
// 4. Drop one `<EffectSection>` line into a panel section.
|
|
8
10
|
// Effects that don't fit the pattern (tone mapping, exposure, LUT, shockwave)
|
|
9
11
|
// stay bespoke — extend the pattern before special-casing a third shape.
|
|
@@ -72,9 +74,28 @@ export const EFFECT_SPECS = {
|
|
|
72
74
|
staticDrops: { default: 0.2, min: 0, max: 3, step: 0.1, digits: 1 },
|
|
73
75
|
glints: { default: 0.6, min: 0, max: 1, step: 0.05 },
|
|
74
76
|
},
|
|
77
|
+
// three's webgpu_compute_particles_snow, rescaled to the stage's metre-scale
|
|
78
|
+
// world. `amount` is the live instance count; buffers allocate the max once.
|
|
79
|
+
snow: {
|
|
80
|
+
amount: { default: 2000, min: 100, max: 10000, step: 100 },
|
|
81
|
+
speed: { default: 1, min: 0, max: 3, step: 0.1, digits: 1, unit: '×' },
|
|
82
|
+
wind: { default: 0, min: -3, max: 3, step: 0.1, digits: 1, unit: 'm/s' },
|
|
83
|
+
sway: { default: 1, min: 0, max: 3, step: 0.1, digits: 1 },
|
|
84
|
+
size: { default: 1, min: 0.4, max: 3, step: 0.05 },
|
|
85
|
+
melt: { default: 6, min: 0.5, max: 30, step: 0.5, digits: 1, unit: 's' },
|
|
86
|
+
opacity: { default: 1, min: 0.1, max: 1, step: 0.05 },
|
|
87
|
+
},
|
|
75
88
|
};
|
|
76
89
|
// Object.keys widens to string[]; the annotation above pins the keys to exactly ToggleEffectKey.
|
|
77
90
|
export const TOGGLE_EFFECT_KEYS = Object.keys(EFFECT_SPECS);
|
|
91
|
+
/**
|
|
92
|
+
* Registry effects that render as scene geometry inside the scene pass rather
|
|
93
|
+
* than composing into the post chain. A new scene-space effect must join this
|
|
94
|
+
* list, or its toggle needlessly forces the post path on and rebuilds the graph.
|
|
95
|
+
*/
|
|
96
|
+
export const SCENE_SPACE_EFFECT_KEYS = ['snow'];
|
|
97
|
+
/** Registry effects the post chain composes — {@link TOGGLE_EFFECT_KEYS} minus the scene-space ones. */
|
|
98
|
+
export const POST_EFFECT_KEYS = TOGGLE_EFFECT_KEYS.filter(key => !SCENE_SPACE_EFFECT_KEYS.includes(key));
|
|
78
99
|
/** Every registry effect at its defaults — the derived half of a fresh {@link SceneEffects}. */
|
|
79
100
|
export function defaultToggleEffects() {
|
|
80
101
|
const out = {};
|
package/dist/values/limits.d.ts
CHANGED
|
@@ -24,6 +24,10 @@ export declare const VRM_SCALE_MAX = 10;
|
|
|
24
24
|
export declare const SCENE_LIGHT_RANGE_MAX = 20;
|
|
25
25
|
/** Past this the 5-tap kernel spreads thin enough that its dither reads as noise. */
|
|
26
26
|
export declare const SCENE_LIGHT_SHADOW_RADIUS_MAX = 16;
|
|
27
|
+
/** The point disk's 32 fixed taps stay dense across a far wider penumbra than 5 dithered ones. */
|
|
28
|
+
export declare const SCENE_LIGHT_POINT_SHADOW_RADIUS_MAX = 32;
|
|
29
|
+
/** Shadow softness (PCF disk radius, in shadow-map texels) ceiling for a light of `type`. */
|
|
30
|
+
export declare function sceneLightShadowRadiusMax(type: SceneLightType): number;
|
|
27
31
|
/**
|
|
28
32
|
* Exp2 falloff is squared, so a stage measured in metres is fully socked in well
|
|
29
33
|
* before Unity's nominal 1 — this keeps the slider's useful band across its width.
|
|
@@ -60,3 +64,10 @@ export declare const DEFAULT_ELASTICITY: NonNullable<Attach['elasticity']>;
|
|
|
60
64
|
export declare const ELASTICITY_STIFFNESS_MAX = 100;
|
|
61
65
|
export declare const ELASTICITY_DAMPING_MAX = 10;
|
|
62
66
|
export declare const ELASTICITY_MAX_SPEED_MAX = 100;
|
|
67
|
+
export declare const STORAGE_KEY_MAX_LENGTH = 128;
|
|
68
|
+
/** Ceiling on one value's JSON-serialized length (UTF-16 units, `JSON.stringify(v).length`). */
|
|
69
|
+
export declare const STORAGE_VALUE_MAX_LENGTH: number;
|
|
70
|
+
/** Keys one API key may hold; a `storage.set` that would exceed it answers `invalid-state`. */
|
|
71
|
+
export declare const STORAGE_KEYS_MAX = 256;
|
|
72
|
+
/** `speech.play` URL ceiling — sized for a ~40 s WAV as a base64 `data:audio/*` payload. */
|
|
73
|
+
export declare const SPEECH_URL_MAX_LENGTH = 8000000;
|
package/dist/values/limits.js
CHANGED
|
@@ -34,6 +34,12 @@ export const VRM_SCALE_MAX = 10;
|
|
|
34
34
|
export const SCENE_LIGHT_RANGE_MAX = 20;
|
|
35
35
|
/** Past this the 5-tap kernel spreads thin enough that its dither reads as noise. */
|
|
36
36
|
export const SCENE_LIGHT_SHADOW_RADIUS_MAX = 16;
|
|
37
|
+
/** The point disk's 32 fixed taps stay dense across a far wider penumbra than 5 dithered ones. */
|
|
38
|
+
export const SCENE_LIGHT_POINT_SHADOW_RADIUS_MAX = 32;
|
|
39
|
+
/** Shadow softness (PCF disk radius, in shadow-map texels) ceiling for a light of `type`. */
|
|
40
|
+
export function sceneLightShadowRadiusMax(type) {
|
|
41
|
+
return type === 'point' ? SCENE_LIGHT_POINT_SHADOW_RADIUS_MAX : SCENE_LIGHT_SHADOW_RADIUS_MAX;
|
|
42
|
+
}
|
|
37
43
|
/**
|
|
38
44
|
* Exp2 falloff is squared, so a stage measured in metres is fully socked in well
|
|
39
45
|
* before Unity's nominal 1 — this keeps the slider's useful band across its width.
|
|
@@ -96,3 +102,11 @@ export const DEFAULT_ELASTICITY = { stiffness: 2, damping: 3, maxSpeed: 2 };
|
|
|
96
102
|
export const ELASTICITY_STIFFNESS_MAX = 100;
|
|
97
103
|
export const ELASTICITY_DAMPING_MAX = 10;
|
|
98
104
|
export const ELASTICITY_MAX_SPEED_MAX = 100;
|
|
105
|
+
// Plugin storage (`storage.*`) — the wire schemas and the server enforce these.
|
|
106
|
+
export const STORAGE_KEY_MAX_LENGTH = 128;
|
|
107
|
+
/** Ceiling on one value's JSON-serialized length (UTF-16 units, `JSON.stringify(v).length`). */
|
|
108
|
+
export const STORAGE_VALUE_MAX_LENGTH = 64 * 1024;
|
|
109
|
+
/** Keys one API key may hold; a `storage.set` that would exceed it answers `invalid-state`. */
|
|
110
|
+
export const STORAGE_KEYS_MAX = 256;
|
|
111
|
+
/** `speech.play` URL ceiling — sized for a ~40 s WAV as a base64 `data:audio/*` payload. */
|
|
112
|
+
export const SPEECH_URL_MAX_LENGTH = 8_000_000;
|
package/dist/wire/envelope.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ApiErrorCode } from './errors.ts';
|
|
2
2
|
import type { EventData, EventName } from './events.ts';
|
|
3
3
|
import type { MethodName, MethodRequest } from './methods.ts';
|
|
4
|
+
import type { AppCapability } from './types.ts';
|
|
4
5
|
export interface RequestMessage<M extends MethodName = MethodName> {
|
|
5
6
|
kind: 'request';
|
|
6
7
|
/** Correlation id, echoed on the response. Non-empty, client-chosen. */
|
|
@@ -38,6 +39,8 @@ export interface AppInfo {
|
|
|
38
39
|
name: string;
|
|
39
40
|
version: string;
|
|
40
41
|
platform: string;
|
|
42
|
+
/** Features to gate on — never version-sniff. Empty when the server predates capability reporting. */
|
|
43
|
+
capabilities: AppCapability[];
|
|
41
44
|
}
|
|
42
45
|
/**
|
|
43
46
|
* Parse one inbound client frame. Returns the request, or an error code telling
|
package/dist/wire/envelope.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isRecord } from "../values/guards.js";
|
|
2
2
|
import { isApiErrorCode } from "./errors.js";
|
|
3
3
|
import { isEventName } from "./events.js";
|
|
4
|
+
import { isAppCapability } from "./types.js";
|
|
4
5
|
/**
|
|
5
6
|
* Parse one inbound client frame. Returns the request, or an error code telling
|
|
6
7
|
* the server what to answer: `parse-error` for junk bytes, `invalid-request`
|
|
@@ -46,7 +47,9 @@ export function parseServerMessage(raw) {
|
|
|
46
47
|
const { name, version, platform } = v.app;
|
|
47
48
|
if (typeof name !== 'string' || typeof version !== 'string' || typeof platform !== 'string')
|
|
48
49
|
return null;
|
|
49
|
-
|
|
50
|
+
// Tolerant both ways: absent on older servers, unknown names from newer ones dropped.
|
|
51
|
+
const capabilities = Array.isArray(v.app.capabilities) ? v.app.capabilities.filter(isAppCapability) : [];
|
|
52
|
+
return { kind: 'hello', protocol: v.protocol, app: { name, version, platform, capabilities } };
|
|
50
53
|
}
|
|
51
54
|
case 'response':
|
|
52
55
|
if (typeof v.id !== 'string' || v.id === '')
|
package/dist/wire/events.d.ts
CHANGED
|
@@ -10,9 +10,11 @@ export interface EventMap {
|
|
|
10
10
|
modelId: string | null;
|
|
11
11
|
config: HotkeyState;
|
|
12
12
|
};
|
|
13
|
+
/** `sources` maps source id → status; optional so pre-multi-source servers still parse. */
|
|
13
14
|
'tracking.status': {
|
|
14
15
|
tracking: TrackingStatus;
|
|
15
16
|
pose: PoseStatus;
|
|
17
|
+
sources?: Record<string, TrackingStatus>;
|
|
16
18
|
};
|
|
17
19
|
/** A model instance finished loading on stage. */
|
|
18
20
|
'instance.loaded': {
|
|
@@ -31,6 +33,14 @@ export interface EventMap {
|
|
|
31
33
|
'motion.ended': {
|
|
32
34
|
instanceId: string;
|
|
33
35
|
};
|
|
36
|
+
/** A `speech.play` utterance began on this instance. */
|
|
37
|
+
'speech.started': {
|
|
38
|
+
instanceId: string;
|
|
39
|
+
};
|
|
40
|
+
/** The utterance finished, failed, was stopped, or was superseded — one `ended` per `started`. */
|
|
41
|
+
'speech.ended': {
|
|
42
|
+
instanceId: string;
|
|
43
|
+
};
|
|
34
44
|
/** True while a scene apply or pipeline warm-up is in flight. */
|
|
35
45
|
'scene.loading': {
|
|
36
46
|
loading: boolean;
|
package/dist/wire/events.js
CHANGED
package/dist/wire/methods.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EventName } from './events.ts';
|
|
2
|
-
import type { AnchorOption, AssetKind, AssetRef, Attach, Expression, ExpressionPersistence, HotkeyConfig, HotkeyState, InjectEntry, InjectTarget, InstanceRuntime, ModelInfo, ModelRef, MotionGroup, MToonTuning, ObjectContent, ObjectLightOverride, ObjectSpace, Place2D, Place3D, PlayingMotion, PoseSourceId, PoseStatus, Scene, SceneItem, ScenePatch, SceneState, ScreenPlacement, Settings, SettingsPatch, TrackingSourceId, TrackingStatus, VrmPlacement } from './types.ts';
|
|
2
|
+
import type { AnchorOption, AppCapability, AssetKind, AssetRef, Attach, Expression, ExpressionPersistence, HotkeyConfig, HotkeyState, InjectEntry, InjectTarget, InstanceRuntime, JsonValue, 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';
|
|
3
3
|
/** Marker for methods that take no parameters; the client lets you omit the argument. */
|
|
4
4
|
export type EmptyRequest = Record<never, never>;
|
|
5
5
|
/** Marker for methods whose success response carries no data. */
|
|
@@ -73,6 +73,13 @@ export interface InstanceSetVisibleRequest {
|
|
|
73
73
|
visible: boolean;
|
|
74
74
|
}
|
|
75
75
|
export type InstanceSetVisibleResponse = EmptyResponse;
|
|
76
|
+
/** Omitted fields keep their binding; null stops tracking that channel for this instance. */
|
|
77
|
+
export interface InstanceSetTrackingSourcesRequest {
|
|
78
|
+
instanceId: string;
|
|
79
|
+
faceSourceId?: string | null;
|
|
80
|
+
poseSourceId?: string | null;
|
|
81
|
+
}
|
|
82
|
+
export type InstanceSetTrackingSourcesResponse = EmptyResponse;
|
|
76
83
|
export interface InstanceReorderRequest {
|
|
77
84
|
/** Full z-order within the scene; ids not listed keep their relative order at the end. */
|
|
78
85
|
orderedInstanceIds: string[];
|
|
@@ -206,6 +213,20 @@ export interface MotionPlayingRequest {
|
|
|
206
213
|
export interface MotionPlayingResponse {
|
|
207
214
|
playing: PlayingMotion | null;
|
|
208
215
|
}
|
|
216
|
+
/** Play a clip with lip-sync. One utterance per model: a new play supersedes the current one. */
|
|
217
|
+
export interface SpeechPlayRequest {
|
|
218
|
+
instanceId?: string;
|
|
219
|
+
/** `https:`/`http:` (local TTS bridges) or an inline `data:audio/*` payload. */
|
|
220
|
+
url: string;
|
|
221
|
+
/** 0..1; the backend's own default when omitted. */
|
|
222
|
+
volume?: number;
|
|
223
|
+
}
|
|
224
|
+
export type SpeechPlayResponse = EmptyResponse;
|
|
225
|
+
/** Stopping a silent model is a silent success. */
|
|
226
|
+
export interface SpeechStopRequest {
|
|
227
|
+
instanceId?: string;
|
|
228
|
+
}
|
|
229
|
+
export type SpeechStopResponse = EmptyResponse;
|
|
209
230
|
export interface HotkeyListRequest {
|
|
210
231
|
modelId?: string;
|
|
211
232
|
}
|
|
@@ -279,10 +300,37 @@ export interface PoseSetPortRequest {
|
|
|
279
300
|
port: number;
|
|
280
301
|
}
|
|
281
302
|
export type PoseSetPortResponse = EmptyResponse;
|
|
303
|
+
export interface TrackingAddSourceRequest {
|
|
304
|
+
kind: TrackingSourceKind;
|
|
305
|
+
name?: string;
|
|
306
|
+
/** Face kinds: pin to a device IP. */
|
|
307
|
+
phoneIp?: string | null;
|
|
308
|
+
/** vmc: the UDP port to listen on; defaults to the next free VMC port. */
|
|
309
|
+
port?: number;
|
|
310
|
+
}
|
|
311
|
+
export interface TrackingAddSourceResponse {
|
|
312
|
+
source: TrackingSourceConfig;
|
|
313
|
+
}
|
|
314
|
+
export interface TrackingUpdateSourceRequest {
|
|
315
|
+
id: string;
|
|
316
|
+
name?: string;
|
|
317
|
+
enabled?: boolean;
|
|
318
|
+
phoneIp?: string | null;
|
|
319
|
+
port?: number;
|
|
320
|
+
}
|
|
321
|
+
export interface TrackingUpdateSourceResponse {
|
|
322
|
+
source: TrackingSourceConfig;
|
|
323
|
+
}
|
|
324
|
+
export interface TrackingRemoveSourceRequest {
|
|
325
|
+
id: string;
|
|
326
|
+
}
|
|
327
|
+
export type TrackingRemoveSourceResponse = EmptyResponse;
|
|
282
328
|
export type TrackingStatusRequest = EmptyRequest;
|
|
283
329
|
export interface TrackingStatusResponse {
|
|
284
330
|
tracking: TrackingStatus;
|
|
285
331
|
pose: PoseStatus;
|
|
332
|
+
/** Per-source status by {@link TrackingSourceConfig.id}. */
|
|
333
|
+
sources: Record<string, TrackingStatus>;
|
|
286
334
|
}
|
|
287
335
|
export interface StageResetTransformRequest {
|
|
288
336
|
instanceId?: string;
|
|
@@ -298,6 +346,8 @@ export interface AppInfoResponse {
|
|
|
298
346
|
version: string;
|
|
299
347
|
platform: string;
|
|
300
348
|
protocol: number;
|
|
349
|
+
/** Same list `hello` carries; gate features on it, never on `version`. */
|
|
350
|
+
capabilities: AppCapability[];
|
|
301
351
|
}
|
|
302
352
|
export type AppLocalAddressesRequest = EmptyRequest;
|
|
303
353
|
export interface AppLocalAddressesResponse {
|
|
@@ -323,6 +373,28 @@ export interface SessionIdentifyRequest {
|
|
|
323
373
|
developer?: string;
|
|
324
374
|
}
|
|
325
375
|
export type SessionIdentifyResponse = EmptyResponse;
|
|
376
|
+
export interface StorageGetRequest {
|
|
377
|
+
key: string;
|
|
378
|
+
}
|
|
379
|
+
export interface StorageGetResponse {
|
|
380
|
+
/** Null when the key was never set (a stored null is indistinguishable by design). */
|
|
381
|
+
value: JsonValue | null;
|
|
382
|
+
}
|
|
383
|
+
export interface StorageSetRequest {
|
|
384
|
+
key: string;
|
|
385
|
+
value: JsonValue;
|
|
386
|
+
}
|
|
387
|
+
export type StorageSetResponse = EmptyResponse;
|
|
388
|
+
/** Deleting an absent key succeeds silently. */
|
|
389
|
+
export interface StorageDeleteRequest {
|
|
390
|
+
key: string;
|
|
391
|
+
}
|
|
392
|
+
export type StorageDeleteResponse = EmptyResponse;
|
|
393
|
+
export type StorageListRequest = EmptyRequest;
|
|
394
|
+
export interface StorageListResponse {
|
|
395
|
+
/** Sorted for stable output. */
|
|
396
|
+
keys: string[];
|
|
397
|
+
}
|
|
326
398
|
export interface EventsSubscribeRequest {
|
|
327
399
|
events: EventName[];
|
|
328
400
|
}
|
|
@@ -413,6 +485,11 @@ export interface MethodMap {
|
|
|
413
485
|
request: InstanceSetVisibleRequest;
|
|
414
486
|
response: InstanceSetVisibleResponse;
|
|
415
487
|
};
|
|
488
|
+
/** Bind this instance's face/pose channels to tracking sources (null stops tracking that channel). */
|
|
489
|
+
'instance.setTrackingSources': {
|
|
490
|
+
request: InstanceSetTrackingSourcesRequest;
|
|
491
|
+
response: InstanceSetTrackingSourcesResponse;
|
|
492
|
+
};
|
|
416
493
|
'instance.reorder': {
|
|
417
494
|
request: InstanceReorderRequest;
|
|
418
495
|
response: InstanceReorderResponse;
|
|
@@ -511,6 +588,18 @@ export interface MethodMap {
|
|
|
511
588
|
request: MotionPlayingRequest;
|
|
512
589
|
response: MotionPlayingResponse;
|
|
513
590
|
};
|
|
591
|
+
/**
|
|
592
|
+
* Lip-synced audio on models advertising the `speech` capability. The response means
|
|
593
|
+
* playback started (else `invalid-state`); `speech.started`/`speech.ended` bracket it.
|
|
594
|
+
*/
|
|
595
|
+
'speech.play': {
|
|
596
|
+
request: SpeechPlayRequest;
|
|
597
|
+
response: SpeechPlayResponse;
|
|
598
|
+
};
|
|
599
|
+
'speech.stop': {
|
|
600
|
+
request: SpeechStopRequest;
|
|
601
|
+
response: SpeechStopResponse;
|
|
602
|
+
};
|
|
514
603
|
'hotkey.list': {
|
|
515
604
|
request: HotkeyListRequest;
|
|
516
605
|
response: HotkeyListResponse;
|
|
@@ -551,18 +640,33 @@ export interface MethodMap {
|
|
|
551
640
|
request: TrackingSetEnabledRequest;
|
|
552
641
|
response: TrackingSetEnabledResponse;
|
|
553
642
|
};
|
|
643
|
+
/** Legacy single-source setter: rewrites the first face source's kind. Prefer the source CRUD below. */
|
|
554
644
|
'tracking.setSource': {
|
|
555
645
|
request: TrackingSetSourceRequest;
|
|
556
646
|
response: TrackingSetSourceResponse;
|
|
557
647
|
};
|
|
648
|
+
'tracking.addSource': {
|
|
649
|
+
request: TrackingAddSourceRequest;
|
|
650
|
+
response: TrackingAddSourceResponse;
|
|
651
|
+
};
|
|
652
|
+
'tracking.updateSource': {
|
|
653
|
+
request: TrackingUpdateSourceRequest;
|
|
654
|
+
response: TrackingUpdateSourceResponse;
|
|
655
|
+
};
|
|
656
|
+
'tracking.removeSource': {
|
|
657
|
+
request: TrackingRemoveSourceRequest;
|
|
658
|
+
response: TrackingRemoveSourceResponse;
|
|
659
|
+
};
|
|
558
660
|
'pose.setEnabled': {
|
|
559
661
|
request: PoseSetEnabledRequest;
|
|
560
662
|
response: PoseSetEnabledResponse;
|
|
561
663
|
};
|
|
664
|
+
/** Legacy: operates on the first vmc source. Prefer the tracking.* source CRUD. */
|
|
562
665
|
'pose.setSource': {
|
|
563
666
|
request: PoseSetSourceRequest;
|
|
564
667
|
response: PoseSetSourceResponse;
|
|
565
668
|
};
|
|
669
|
+
/** Legacy: sets the first vmc source's port. */
|
|
566
670
|
'pose.setPort': {
|
|
567
671
|
request: PoseSetPortRequest;
|
|
568
672
|
response: PoseSetPortResponse;
|
|
@@ -603,6 +707,27 @@ export interface MethodMap {
|
|
|
603
707
|
request: SessionIdentifyRequest;
|
|
604
708
|
response: SessionIdentifyResponse;
|
|
605
709
|
};
|
|
710
|
+
/**
|
|
711
|
+
* Durable key–value storage namespaced by the session's API key; revoking the key deletes
|
|
712
|
+
* it. Limits: `STORAGE_KEY_MAX_LENGTH`, `STORAGE_VALUE_MAX_LENGTH`, `STORAGE_KEYS_MAX`
|
|
713
|
+
* keys (over-quota answers `invalid-state`).
|
|
714
|
+
*/
|
|
715
|
+
'storage.get': {
|
|
716
|
+
request: StorageGetRequest;
|
|
717
|
+
response: StorageGetResponse;
|
|
718
|
+
};
|
|
719
|
+
'storage.set': {
|
|
720
|
+
request: StorageSetRequest;
|
|
721
|
+
response: StorageSetResponse;
|
|
722
|
+
};
|
|
723
|
+
'storage.delete': {
|
|
724
|
+
request: StorageDeleteRequest;
|
|
725
|
+
response: StorageDeleteResponse;
|
|
726
|
+
};
|
|
727
|
+
'storage.list': {
|
|
728
|
+
request: StorageListRequest;
|
|
729
|
+
response: StorageListResponse;
|
|
730
|
+
};
|
|
606
731
|
'events.subscribe': {
|
|
607
732
|
request: EventsSubscribeRequest;
|
|
608
733
|
response: EventsSubscribeResponse;
|
package/dist/wire/schemas.d.ts
CHANGED
|
@@ -93,6 +93,27 @@ export declare const requestSchemas: {
|
|
|
93
93
|
'hotkey.trigger': z.ZodObject<{
|
|
94
94
|
hotkeyId: z.ZodString;
|
|
95
95
|
}, z.core.$strip>;
|
|
96
|
+
'tracking.addSource': z.ZodObject<{
|
|
97
|
+
kind: z.ZodEnum<{
|
|
98
|
+
"vts-ios": "vts-ios";
|
|
99
|
+
"vts-ios-native": "vts-ios-native";
|
|
100
|
+
ifacialmocap: "ifacialmocap";
|
|
101
|
+
vmc: "vmc";
|
|
102
|
+
}>;
|
|
103
|
+
name: z.ZodOptional<z.ZodString>;
|
|
104
|
+
phoneIp: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
105
|
+
port: z.ZodOptional<z.ZodNumber>;
|
|
106
|
+
}, z.core.$strip>;
|
|
107
|
+
'tracking.updateSource': z.ZodObject<{
|
|
108
|
+
id: z.ZodString;
|
|
109
|
+
name: z.ZodOptional<z.ZodString>;
|
|
110
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
111
|
+
phoneIp: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
112
|
+
port: z.ZodOptional<z.ZodNumber>;
|
|
113
|
+
}, z.core.$strip>;
|
|
114
|
+
'tracking.removeSource': z.ZodObject<{
|
|
115
|
+
id: z.ZodString;
|
|
116
|
+
}, z.core.$strip>;
|
|
96
117
|
'model.register': z.ZodObject<{
|
|
97
118
|
path: z.ZodString;
|
|
98
119
|
}, z.core.$strip>;
|
|
@@ -153,6 +174,24 @@ export declare const requestSchemas: {
|
|
|
153
174
|
version: z.ZodOptional<z.ZodString>;
|
|
154
175
|
developer: z.ZodOptional<z.ZodString>;
|
|
155
176
|
}, z.core.$strip>;
|
|
177
|
+
'storage.get': z.ZodObject<{
|
|
178
|
+
key: z.ZodString;
|
|
179
|
+
}, z.core.$strip>;
|
|
180
|
+
'storage.set': z.ZodObject<{
|
|
181
|
+
key: z.ZodString;
|
|
182
|
+
value: z.ZodJSONSchema;
|
|
183
|
+
}, z.core.$strip>;
|
|
184
|
+
'storage.delete': z.ZodObject<{
|
|
185
|
+
key: z.ZodString;
|
|
186
|
+
}, z.core.$strip>;
|
|
187
|
+
'speech.play': z.ZodObject<{
|
|
188
|
+
instanceId: z.ZodOptional<z.ZodString>;
|
|
189
|
+
url: z.ZodString;
|
|
190
|
+
volume: z.ZodOptional<z.ZodNumber>;
|
|
191
|
+
}, z.core.$strip>;
|
|
192
|
+
'speech.stop': z.ZodObject<{
|
|
193
|
+
instanceId: z.ZodOptional<z.ZodString>;
|
|
194
|
+
}, z.core.$strip>;
|
|
156
195
|
'events.subscribe': z.ZodObject<{
|
|
157
196
|
events: z.ZodArray<z.ZodCustom<keyof import("./events.ts").EventMap, keyof import("./events.ts").EventMap>>;
|
|
158
197
|
}, z.core.$strip>;
|
package/dist/wire/schemas.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import * as z from 'zod';
|
|
2
2
|
import { isRecord } from "../values/guards.js";
|
|
3
|
+
import { SPEECH_URL_MAX_LENGTH, STORAGE_KEY_MAX_LENGTH, STORAGE_VALUE_MAX_LENGTH } from "../values/limits.js";
|
|
3
4
|
// Runtime validation for the request side of the wire. Schemas exist for the
|
|
4
5
|
// methods whose params the app's main process consumes directly; methods without
|
|
5
6
|
// one are stage-owned — the renderer validates and heals them (same rules as the
|
|
6
7
|
// panel). Fields marked z.custom are deliberately gated shallowly here because
|
|
7
8
|
// the app heals them into shape server-side.
|
|
8
9
|
const nonEmpty = z.string().min(1);
|
|
10
|
+
const port = z.number().int().min(1).max(65535);
|
|
9
11
|
export const InjectTargetSchema = z.object({
|
|
10
12
|
type: z.enum(['input', 'live2d-param', 'vrm-expression']),
|
|
11
13
|
id: nonEmpty,
|
|
@@ -30,6 +32,25 @@ export const SettingsPatchSchema = z.object({
|
|
|
30
32
|
});
|
|
31
33
|
/** Tolerates unknown names for version skew; the server filters them and reports what took. */
|
|
32
34
|
const lenientEventNameSchema = z.custom(v => typeof v === 'string', 'event names must be strings');
|
|
35
|
+
const storageKey = z.string().min(1).max(STORAGE_KEY_MAX_LENGTH);
|
|
36
|
+
/** JSON-serializable and bounded; the serialized-length cap is what the server persists by. */
|
|
37
|
+
const storageValue = z
|
|
38
|
+
.json()
|
|
39
|
+
.refine(v => JSON.stringify(v).length <= STORAGE_VALUE_MAX_LENGTH, `value exceeds ${STORAGE_VALUE_MAX_LENGTH} serialized chars`);
|
|
40
|
+
/** http(s) covers hosted clips and local TTS bridges; data: carries inline audio only. */
|
|
41
|
+
const speechUrl = z
|
|
42
|
+
.string()
|
|
43
|
+
.min(1)
|
|
44
|
+
.max(SPEECH_URL_MAX_LENGTH)
|
|
45
|
+
.refine(v => {
|
|
46
|
+
try {
|
|
47
|
+
const p = new URL(v).protocol;
|
|
48
|
+
return p === 'https:' || p === 'http:' || (p === 'data:' && v.slice(5, 11) === 'audio/');
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}, 'url must be http(s) or data:audio/*');
|
|
33
54
|
export const requestSchemas = {
|
|
34
55
|
'scene.get': z.object({ sceneId: nonEmpty.optional() }),
|
|
35
56
|
'scene.activate': z.object({ sceneId: nonEmpty }),
|
|
@@ -55,6 +76,20 @@ export const requestSchemas = {
|
|
|
55
76
|
config: z.custom(isRecord, 'config must be a hotkey config'),
|
|
56
77
|
}),
|
|
57
78
|
'hotkey.trigger': z.object({ hotkeyId: nonEmpty }),
|
|
79
|
+
'tracking.addSource': z.object({
|
|
80
|
+
kind: z.enum(['vts-ios', 'vts-ios-native', 'ifacialmocap', 'vmc']),
|
|
81
|
+
name: z.string().optional(),
|
|
82
|
+
phoneIp: nonEmpty.nullable().optional(),
|
|
83
|
+
port: port.optional(),
|
|
84
|
+
}),
|
|
85
|
+
'tracking.updateSource': z.object({
|
|
86
|
+
id: nonEmpty,
|
|
87
|
+
name: z.string().optional(),
|
|
88
|
+
enabled: z.boolean().optional(),
|
|
89
|
+
phoneIp: nonEmpty.nullable().optional(),
|
|
90
|
+
port: port.optional(),
|
|
91
|
+
}),
|
|
92
|
+
'tracking.removeSource': z.object({ id: nonEmpty }),
|
|
58
93
|
'model.register': z.object({ path: nonEmpty }),
|
|
59
94
|
'asset.register': z.object({ path: nonEmpty, want: z.enum(['image', 'video', 'prop', 'ibl', 'lut']).optional() }),
|
|
60
95
|
'settings.patch': z.object({ settings: SettingsPatchSchema }),
|
|
@@ -62,12 +97,21 @@ export const requestSchemas = {
|
|
|
62
97
|
'tracking.setSource': z.object({ source: z.enum(['vts-ios', 'vts-ios-native', 'ifacialmocap']) }),
|
|
63
98
|
'pose.setEnabled': z.object({ enabled: z.boolean() }),
|
|
64
99
|
'pose.setSource': z.object({ source: z.enum(['vmc']) }),
|
|
65
|
-
'pose.setPort': z.object({ port
|
|
100
|
+
'pose.setPort': z.object({ port }),
|
|
66
101
|
'session.identify': z.object({
|
|
67
102
|
name: z.string().trim().min(1).max(64),
|
|
68
103
|
version: z.string().max(32).optional(),
|
|
69
104
|
developer: z.string().max(64).optional(),
|
|
70
105
|
}),
|
|
106
|
+
'storage.get': z.object({ key: storageKey }),
|
|
107
|
+
'storage.set': z.object({ key: storageKey, value: storageValue }),
|
|
108
|
+
'storage.delete': z.object({ key: storageKey }),
|
|
109
|
+
'speech.play': z.object({
|
|
110
|
+
instanceId: nonEmpty.optional(),
|
|
111
|
+
url: speechUrl,
|
|
112
|
+
volume: z.number().min(0).max(1).optional(),
|
|
113
|
+
}),
|
|
114
|
+
'speech.stop': z.object({ instanceId: nonEmpty.optional() }),
|
|
71
115
|
'events.subscribe': z.object({ events: z.array(lenientEventNameSchema).min(1) }),
|
|
72
116
|
'events.unsubscribe': z.object({ events: z.array(lenientEventNameSchema).optional() }),
|
|
73
117
|
'param.inject': z.object({ entries: z.array(InjectEntrySchema).min(1) }),
|
package/dist/wire/types.d.ts
CHANGED
|
@@ -109,6 +109,13 @@ export interface SceneModelItem {
|
|
|
109
109
|
instanceId: string;
|
|
110
110
|
ref: ModelRef;
|
|
111
111
|
visible: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Face source tracking this instance ({@link TrackingSourceConfig.id}); null = its face is untracked.
|
|
114
|
+
* A dangling or disabled id behaves as null. One source may track several instances (mirroring).
|
|
115
|
+
*/
|
|
116
|
+
faceSourceId: string | null;
|
|
117
|
+
/** Body source (vmc) tracking this instance; null = its pose is untracked. New instances bind to each channel's default source. */
|
|
118
|
+
poseSourceId: string | null;
|
|
112
119
|
live2d: ScreenPlacement;
|
|
113
120
|
vrm: VrmPlacement;
|
|
114
121
|
idleAnimation: boolean;
|
|
@@ -371,6 +378,24 @@ export interface SceneDroplets {
|
|
|
371
378
|
/** Specular glint on drops where the frame is transparent, so rain reads over the desktop (0 to 1). */
|
|
372
379
|
glints: number;
|
|
373
380
|
}
|
|
381
|
+
/** Compute-driven snowfall in the 3D scene: flakes drift down, settle on models and props, then melt. */
|
|
382
|
+
export interface SceneSnow {
|
|
383
|
+
enabled: boolean;
|
|
384
|
+
/** How many flakes are alive at once. */
|
|
385
|
+
amount: number;
|
|
386
|
+
/** Fall and drift clock multiplier. */
|
|
387
|
+
speed: number;
|
|
388
|
+
/** Steady sideways drift, in metres per second along the stage's x axis. */
|
|
389
|
+
wind: number;
|
|
390
|
+
/** Horizontal wobble while falling. */
|
|
391
|
+
sway: number;
|
|
392
|
+
/** Flake size multiplier. */
|
|
393
|
+
size: number;
|
|
394
|
+
/** Seconds a settled flake rests before it finishes melting away. */
|
|
395
|
+
melt: number;
|
|
396
|
+
/** Flake opacity. */
|
|
397
|
+
opacity: number;
|
|
398
|
+
}
|
|
374
399
|
/**
|
|
375
400
|
* Post-processing over the rendered 3D frame. Everything off skips the effect
|
|
376
401
|
* chain entirely. Deliberately flat: every toggle-plus-numbers effect sits at
|
|
@@ -391,6 +416,7 @@ export interface SceneEffects {
|
|
|
391
416
|
pixelate: ScenePixelate;
|
|
392
417
|
glitch: SceneGlitch;
|
|
393
418
|
droplets: SceneDroplets;
|
|
419
|
+
snow: SceneSnow;
|
|
394
420
|
}
|
|
395
421
|
/**
|
|
396
422
|
* Image-based lighting for the 3D stage: an environment map, whether to show it
|
|
@@ -438,8 +464,17 @@ export interface ScenePatch {
|
|
|
438
464
|
lights?: SceneLight[];
|
|
439
465
|
environment?: SceneEnvironment;
|
|
440
466
|
}
|
|
467
|
+
/**
|
|
468
|
+
* App-level features a client gates on (never version-sniff): `hello` and
|
|
469
|
+
* `app.info` report them — the per-app mirror of {@link InstanceRuntime.capabilities}.
|
|
470
|
+
*/
|
|
471
|
+
export declare const APP_CAPABILITIES: readonly ["storage", "speech"];
|
|
472
|
+
export type AppCapability = (typeof APP_CAPABILITIES)[number];
|
|
473
|
+
export declare function isAppCapability(v: unknown): v is AppCapability;
|
|
441
474
|
/** What a loaded model instance can do; absent capabilities answer `unsupported-for-format`. */
|
|
442
|
-
export type InstanceCapability = 'motions' | 'expressions' | 'placement-2d' | 'placement-3d' | 'mtoon' | 'idle-clips' | 'pose' | 'live2d-params'
|
|
475
|
+
export type InstanceCapability = 'motions' | 'expressions' | 'placement-2d' | 'placement-3d' | 'mtoon' | 'idle-clips' | 'pose' | 'live2d-params'
|
|
476
|
+
/** `speech.play` lip-sync; engine-dependent, so gate on the runtime, not the format. */
|
|
477
|
+
| 'speech';
|
|
443
478
|
export interface InstanceRuntime {
|
|
444
479
|
instanceId: string;
|
|
445
480
|
kind: 'model' | 'object';
|
|
@@ -503,6 +538,10 @@ export interface ExpressionPersistence {
|
|
|
503
538
|
enabled: boolean;
|
|
504
539
|
saved: string[];
|
|
505
540
|
}
|
|
541
|
+
/** Any JSON-serializable value — what `storage.set` accepts and `storage.get` returns. */
|
|
542
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
543
|
+
[key: string]: JsonValue;
|
|
544
|
+
};
|
|
506
545
|
export declare const TRACKING_SOURCE_IDS: readonly ["vts-ios", "vts-ios-native", "ifacialmocap"];
|
|
507
546
|
export type TrackingSourceId = (typeof TRACKING_SOURCE_IDS)[number];
|
|
508
547
|
/** Body-pose protocols. Every one so far is UDP with a configurable port. */
|
|
@@ -510,6 +549,29 @@ export declare const POSE_SOURCE_IDS: readonly ["vmc"];
|
|
|
510
549
|
export type PoseSourceId = (typeof POSE_SOURCE_IDS)[number];
|
|
511
550
|
export type TrackingStatus = 'off' | 'waiting' | 'tracking' | 'no-face';
|
|
512
551
|
export type PoseStatus = 'off' | 'waiting' | 'tracking';
|
|
552
|
+
/** Every protocol a tracking source instance can speak; face kinds plus the body (vmc) kind. */
|
|
553
|
+
export declare const TRACKING_SOURCE_KINDS: readonly ["vts-ios", "vts-ios-native", "ifacialmocap", "vmc"];
|
|
554
|
+
export type TrackingSourceKind = (typeof TRACKING_SOURCE_KINDS)[number];
|
|
555
|
+
/** Whether a source kind feeds the face channel (vmc is the body channel). */
|
|
556
|
+
export declare function isFaceSourceKind(kind: TrackingSourceKind): kind is TrackingSourceId;
|
|
557
|
+
/**
|
|
558
|
+
* One configured tracking source instance. Several can run at once — one per person —
|
|
559
|
+
* and scene model instances bind to them by id (`SceneModelItem.faceSourceId`/`poseSourceId`).
|
|
560
|
+
*/
|
|
561
|
+
export interface TrackingSourceConfig {
|
|
562
|
+
id: string;
|
|
563
|
+
kind: TrackingSourceKind;
|
|
564
|
+
/** User label shown in lists; '' renders as the kind's own label. */
|
|
565
|
+
name: string;
|
|
566
|
+
enabled: boolean;
|
|
567
|
+
/**
|
|
568
|
+
* Face kinds: accept only this device/sender IP; null accepts any sender not claimed
|
|
569
|
+
* by a pinned instance of the same kind. Ignored by vmc (its senders pick the port).
|
|
570
|
+
*/
|
|
571
|
+
phoneIp: string | null;
|
|
572
|
+
/** vmc only: the UDP port this instance listens on. Null on face kinds — they bind per protocol. */
|
|
573
|
+
port: number | null;
|
|
574
|
+
}
|
|
513
575
|
export declare const EFFECTS_QUALITY_LEVELS: readonly ["low", "medium", "high"];
|
|
514
576
|
export type EffectsQuality = (typeof EFFECTS_QUALITY_LEVELS)[number];
|
|
515
577
|
/**
|
|
@@ -532,10 +594,13 @@ export interface Settings {
|
|
|
532
594
|
selectionOutline: boolean;
|
|
533
595
|
effectsQuality: EffectsQuality;
|
|
534
596
|
};
|
|
597
|
+
/** `source` mirrors the first face source's kind — the pre-multi-source field, kept for old clients. */
|
|
535
598
|
tracking: {
|
|
536
599
|
enabled: boolean;
|
|
537
600
|
source: TrackingSourceId;
|
|
601
|
+
sources: TrackingSourceConfig[];
|
|
538
602
|
};
|
|
603
|
+
/** `source`/`port` mirror the first vmc source — the pre-multi-source fields, kept for old clients. */
|
|
539
604
|
pose: {
|
|
540
605
|
enabled: boolean;
|
|
541
606
|
source: PoseSourceId;
|
package/dist/wire/types.js
CHANGED
|
@@ -7,10 +7,25 @@
|
|
|
7
7
|
* directional light covers with one map, so it gets the smaller of the pair.
|
|
8
8
|
*/
|
|
9
9
|
export const SHADOW_QUALITY_LEVELS = ['off', 'low', 'medium', 'high', 'extra'];
|
|
10
|
+
// ---- Runtime state -------------------------------------------------------------
|
|
11
|
+
/**
|
|
12
|
+
* App-level features a client gates on (never version-sniff): `hello` and
|
|
13
|
+
* `app.info` report them — the per-app mirror of {@link InstanceRuntime.capabilities}.
|
|
14
|
+
*/
|
|
15
|
+
export const APP_CAPABILITIES = ['storage', 'speech'];
|
|
16
|
+
export function isAppCapability(v) {
|
|
17
|
+
return typeof v === 'string' && APP_CAPABILITIES.includes(v);
|
|
18
|
+
}
|
|
10
19
|
// ---- Settings ------------------------------------------------------------------
|
|
11
20
|
export const TRACKING_SOURCE_IDS = ['vts-ios', 'vts-ios-native', 'ifacialmocap'];
|
|
12
21
|
/** Body-pose protocols. Every one so far is UDP with a configurable port. */
|
|
13
22
|
export const POSE_SOURCE_IDS = ['vmc'];
|
|
23
|
+
/** Every protocol a tracking source instance can speak; face kinds plus the body (vmc) kind. */
|
|
24
|
+
export const TRACKING_SOURCE_KINDS = [...TRACKING_SOURCE_IDS, ...POSE_SOURCE_IDS];
|
|
25
|
+
/** Whether a source kind feeds the face channel (vmc is the body channel). */
|
|
26
|
+
export function isFaceSourceKind(kind) {
|
|
27
|
+
return TRACKING_SOURCE_IDS.includes(kind);
|
|
28
|
+
}
|
|
14
29
|
export const EFFECTS_QUALITY_LEVELS = ['low', 'medium', 'high'];
|
|
15
30
|
/**
|
|
16
31
|
* Accepted values for `performance.fpsLimit`; 0 = unlimited. Anything else is snapped
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@laplace.live/persona-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "TypeScript SDK and wire schema for the LAPLACE Persona plugin API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"rimraf": "^6.1.3",
|
|
30
30
|
"typescript": "~6.0.3",
|
|
31
|
-
"vitest": "^4.1.
|
|
31
|
+
"vitest": "^4.1.11"
|
|
32
32
|
},
|
|
33
33
|
"engines": {
|
|
34
34
|
"node": ">=22"
|