@wibly/sdk 0.1.1 → 0.1.2
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/CHANGELOG.md +9 -0
- package/package.json +4 -4
- package/src/client.ts +90 -26
- package/src/control.ts +6 -0
- package/src/index.ts +3 -1
- package/src/inference.ts +7 -99
- package/src/time.ts +17 -1
- package/src/voice.ts +21 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# `@wibly/sdk` — Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.2 — 2026-06-08
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `session.host.abort()` — a universal mid-game abort. Emits the new
|
|
8
|
+
`host.abort` control verb; the Runtime force-jumps to the manifest's
|
|
9
|
+
`workflow.abortPhaseId` (or the last declared phase when omitted) from
|
|
10
|
+
any phase. Games no longer need per-phase abort transitions.
|
|
11
|
+
|
|
3
12
|
## 0.1.1 — 2026-05-30
|
|
4
13
|
|
|
5
14
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wibly/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Wibly @wibly/sdk",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
"access": "public"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@wibly/internal-manifest": "0.1.
|
|
22
|
-
"@wibly/internal-protocol": "0.1.
|
|
23
|
-
"@wibly/internal-shared": "0.1.
|
|
21
|
+
"@wibly/internal-manifest": "0.1.2",
|
|
22
|
+
"@wibly/internal-protocol": "0.1.2",
|
|
23
|
+
"@wibly/internal-shared": "0.1.2",
|
|
24
24
|
"zod": "^3.25.76"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
package/src/client.ts
CHANGED
|
@@ -181,6 +181,25 @@ export type Session = {
|
|
|
181
181
|
readonly advancePhase: (
|
|
182
182
|
detail?: unknown,
|
|
183
183
|
) => Result<{ readonly id: string }, SdkError>;
|
|
184
|
+
/**
|
|
185
|
+
* Universal mid-game abort. The Runtime force-jumps to the
|
|
186
|
+
* manifest's abort/terminal phase (`workflow.abortPhaseId`, else
|
|
187
|
+
* the last declared phase) from wherever the session currently is.
|
|
188
|
+
* No per-phase wiring required.
|
|
189
|
+
*/
|
|
190
|
+
readonly abort: () => Result<{ readonly id: string }, SdkError>;
|
|
191
|
+
readonly reportTtsPlayback: (detail: {
|
|
192
|
+
readonly state: 'active' | 'idle';
|
|
193
|
+
}) => Result<{ readonly id: string }, SdkError>;
|
|
194
|
+
readonly reportTtsBeat: (detail: {
|
|
195
|
+
readonly beatId: string;
|
|
196
|
+
readonly event: 'clip_start' | 'clip_end';
|
|
197
|
+
readonly clipId?: string;
|
|
198
|
+
readonly reveal?: {
|
|
199
|
+
readonly kind: 'none' | 'player_submission';
|
|
200
|
+
readonly playerId?: string;
|
|
201
|
+
};
|
|
202
|
+
}) => Result<{ readonly id: string }, SdkError>;
|
|
184
203
|
readonly reclaim: () => Result<{ readonly id: string }, SdkError>;
|
|
185
204
|
};
|
|
186
205
|
readonly inference: SessionInference;
|
|
@@ -310,9 +329,11 @@ export const createSession = (config: SessionConfig): Session => {
|
|
|
310
329
|
? 'host'
|
|
311
330
|
: 'system';
|
|
312
331
|
store.setSessionPaused(true, via);
|
|
332
|
+
time.freeze();
|
|
313
333
|
}
|
|
314
334
|
if (payload.transition === 'continued') {
|
|
315
335
|
store.setSessionPaused(false, null);
|
|
336
|
+
time.unfreeze();
|
|
316
337
|
}
|
|
317
338
|
if (payload.transition === 'seat.recovery_code_issued') {
|
|
318
339
|
const detail = payload.detail;
|
|
@@ -481,6 +502,8 @@ export const createSession = (config: SessionConfig): Session => {
|
|
|
481
502
|
audioBase64: string;
|
|
482
503
|
contentType?: unknown;
|
|
483
504
|
durationMs?: unknown;
|
|
505
|
+
beat?: unknown;
|
|
506
|
+
cues?: unknown;
|
|
484
507
|
},
|
|
485
508
|
caption: string | null | undefined,
|
|
486
509
|
): void => {
|
|
@@ -499,6 +522,11 @@ export const createSession = (config: SessionConfig): Session => {
|
|
|
499
522
|
? data.durationMs
|
|
500
523
|
: estimateAudioDurationMs(data.audioBase64),
|
|
501
524
|
caption: caption === undefined ? null : caption,
|
|
525
|
+
// Carry in-phase beat + lip-sync cues through to the Host shell.
|
|
526
|
+
// These drive Experience reveal choreography via `reportTtsBeat`
|
|
527
|
+
// → `onTtsBeat`; dropping them here silently breaks beat reveals.
|
|
528
|
+
...(data.beat !== undefined ? { beat: data.beat } : {}),
|
|
529
|
+
...(data.cues !== undefined ? { cues: data.cues } : {}),
|
|
502
530
|
},
|
|
503
531
|
});
|
|
504
532
|
};
|
|
@@ -510,37 +538,57 @@ export const createSession = (config: SessionConfig): Session => {
|
|
|
510
538
|
contentType?: string;
|
|
511
539
|
durationMs?: number;
|
|
512
540
|
kind?: string;
|
|
541
|
+
beat?: unknown;
|
|
542
|
+
cues?: unknown;
|
|
513
543
|
} | null;
|
|
514
|
-
if (!data
|
|
515
|
-
const causeId = data.causeMessageId as unknown as MessageId;
|
|
516
|
-
const pending = pendingVoice.get(causeId);
|
|
517
|
-
const caption = pending?.caption;
|
|
518
|
-
const audioBase64 = data.audioBase64;
|
|
544
|
+
if (!data) return;
|
|
519
545
|
|
|
520
|
-
|
|
546
|
+
const audioBase64 = data.audioBase64;
|
|
547
|
+
const isSpeakResult =
|
|
521
548
|
payload.eventType === 'voice.speak.result' &&
|
|
522
|
-
typeof audioBase64 === 'string'
|
|
523
|
-
|
|
549
|
+
typeof audioBase64 === 'string';
|
|
550
|
+
|
|
551
|
+
const causeId = (() => {
|
|
552
|
+
if (typeof data.causeMessageId === 'string' && data.causeMessageId.length > 0) {
|
|
553
|
+
return data.causeMessageId as MessageId;
|
|
554
|
+
}
|
|
555
|
+
if (isSpeakResult) {
|
|
556
|
+
return `voice-server-${audioBase64.length}-${audioBase64.slice(0, 12)}` as MessageId;
|
|
557
|
+
}
|
|
558
|
+
return null;
|
|
559
|
+
})();
|
|
560
|
+
|
|
561
|
+
if (isSpeakResult && causeId !== null) {
|
|
562
|
+
const pending =
|
|
563
|
+
typeof data.causeMessageId === 'string' && data.causeMessageId.length > 0
|
|
564
|
+
? pendingVoice.get(data.causeMessageId as MessageId)
|
|
565
|
+
: undefined;
|
|
524
566
|
dispatchVoiceAudio(
|
|
525
567
|
causeId,
|
|
526
568
|
{
|
|
527
569
|
audioBase64,
|
|
528
570
|
contentType: data.contentType,
|
|
529
571
|
durationMs: data.durationMs,
|
|
572
|
+
beat: data.beat,
|
|
573
|
+
cues: data.cues,
|
|
530
574
|
},
|
|
531
|
-
caption ?? null,
|
|
575
|
+
pending?.caption ?? null,
|
|
532
576
|
);
|
|
533
577
|
}
|
|
534
578
|
|
|
579
|
+
if (typeof data.causeMessageId !== 'string' || data.causeMessageId.length === 0) {
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const causeMessageId = data.causeMessageId as MessageId;
|
|
584
|
+
const pending = pendingVoice.get(causeMessageId);
|
|
585
|
+
|
|
535
586
|
if (pending === undefined) return;
|
|
536
587
|
pending.cancelTimeout();
|
|
537
|
-
pendingVoice.delete(
|
|
538
|
-
transport.confirmSend(
|
|
588
|
+
pendingVoice.delete(causeMessageId);
|
|
589
|
+
transport.confirmSend(causeMessageId);
|
|
539
590
|
|
|
540
|
-
if (
|
|
541
|
-
payload.eventType === 'voice.speak.result' &&
|
|
542
|
-
typeof audioBase64 === 'string'
|
|
543
|
-
) {
|
|
591
|
+
if (isSpeakResult) {
|
|
544
592
|
pending.resolve(
|
|
545
593
|
ok({
|
|
546
594
|
audioBase64,
|
|
@@ -711,6 +759,32 @@ export const createSession = (config: SessionConfig): Session => {
|
|
|
711
759
|
const sent = transport.send('emit', payload);
|
|
712
760
|
return ok({ id: sent.id as unknown as string });
|
|
713
761
|
},
|
|
762
|
+
abort: () => {
|
|
763
|
+
const payload = buildHostEmitPayload(
|
|
764
|
+
config.sessionId,
|
|
765
|
+
HOST_EVENT_TYPES.abort,
|
|
766
|
+
);
|
|
767
|
+
const sent = transport.send('emit', payload);
|
|
768
|
+
return ok({ id: sent.id as unknown as string });
|
|
769
|
+
},
|
|
770
|
+
reportTtsPlayback: (detail) => {
|
|
771
|
+
const payload = buildHostEmitPayload(
|
|
772
|
+
config.sessionId,
|
|
773
|
+
HOST_EVENT_TYPES.ttsPlayback,
|
|
774
|
+
detail,
|
|
775
|
+
);
|
|
776
|
+
const sent = transport.send('emit', payload);
|
|
777
|
+
return ok({ id: sent.id as unknown as string });
|
|
778
|
+
},
|
|
779
|
+
reportTtsBeat: (detail) => {
|
|
780
|
+
const payload = buildHostEmitPayload(
|
|
781
|
+
config.sessionId,
|
|
782
|
+
HOST_EVENT_TYPES.ttsBeat,
|
|
783
|
+
detail,
|
|
784
|
+
);
|
|
785
|
+
const sent = transport.send('emit', payload);
|
|
786
|
+
return ok({ id: sent.id as unknown as string });
|
|
787
|
+
},
|
|
714
788
|
reclaim: () => {
|
|
715
789
|
const payload = buildHostEmitPayload(
|
|
716
790
|
config.sessionId,
|
|
@@ -805,7 +879,7 @@ const buildInferenceVerbs = (
|
|
|
805
879
|
});
|
|
806
880
|
const sent = transport.send('emit', {
|
|
807
881
|
sessionId,
|
|
808
|
-
eventType: `${INFERENCE_EVENT_PREFIX}${input.
|
|
882
|
+
eventType: `${INFERENCE_EVENT_PREFIX}${input.templateId}`,
|
|
809
883
|
data: serialised,
|
|
810
884
|
});
|
|
811
885
|
pending.set(sent.id, resolveFn);
|
|
@@ -824,16 +898,6 @@ const buildInferenceVerbs = (
|
|
|
824
898
|
};
|
|
825
899
|
return {
|
|
826
900
|
call,
|
|
827
|
-
// Convenience wrappers map to the most-common manifest `CallKind`s
|
|
828
|
-
// (per `@platform/manifest`'s `CallKindSchema` and
|
|
829
|
-
// `docs/conventions/prompt-composition.md`). Bundles that need a
|
|
830
|
-
// less-common kind (`host_resolve`, `host_recap`, `judge_funniness`,
|
|
831
|
-
// `compose_clue`, `narrate_event`) call `inference.call({ callKind,
|
|
832
|
-
// ... })` directly — the wrappers are a comfort for the dominant
|
|
833
|
-
// open-phase + judge paths, not an exhaustive cover.
|
|
834
|
-
host: (input) => call({ ...input, callKind: 'host_open_phase' }),
|
|
835
|
-
judge: (input) => call({ ...input, callKind: 'host_judge' }),
|
|
836
|
-
classify: (input) => call({ ...input, callKind: 'classify' }),
|
|
837
901
|
};
|
|
838
902
|
};
|
|
839
903
|
|
package/src/control.ts
CHANGED
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
* - `host.resume` — resume from pause.
|
|
16
16
|
* - `host.advancePhase` — request the next phase. The Runtime
|
|
17
17
|
* may reject if no transition matches.
|
|
18
|
+
* - `host.abort` — universal mid-game abort. The Runtime
|
|
19
|
+
* force-jumps to the manifest's abort/
|
|
20
|
+
* terminal phase (no per-phase wiring).
|
|
18
21
|
* - `host.reclaim` — reclaim the host slot from a hung
|
|
19
22
|
* host (the player who fires this
|
|
20
23
|
* becomes the new host if allowed).
|
|
@@ -26,6 +29,9 @@ export const HOST_EVENT_TYPES = {
|
|
|
26
29
|
pause: 'host.pause',
|
|
27
30
|
resume: 'host.resume',
|
|
28
31
|
advancePhase: 'host.advancePhase',
|
|
32
|
+
abort: 'host.abort',
|
|
33
|
+
ttsPlayback: 'host.ttsPlayback',
|
|
34
|
+
ttsBeat: 'host.ttsBeat',
|
|
29
35
|
reclaim: 'host.reclaim',
|
|
30
36
|
} as const;
|
|
31
37
|
|
package/src/index.ts
CHANGED
|
@@ -67,7 +67,6 @@ export {
|
|
|
67
67
|
buildInferenceRequest,
|
|
68
68
|
type InferenceCallInput,
|
|
69
69
|
type InferenceCallSuccess,
|
|
70
|
-
type SdkCallKind,
|
|
71
70
|
type SdkQualityTier,
|
|
72
71
|
type SerialisedInferenceRequest,
|
|
73
72
|
type SessionInference,
|
|
@@ -79,6 +78,9 @@ export {
|
|
|
79
78
|
type SessionVoice,
|
|
80
79
|
type SpeakInput,
|
|
81
80
|
type SpeakSuccess,
|
|
81
|
+
type TtsBeatMeta,
|
|
82
|
+
type TtsBeatReveal,
|
|
83
|
+
type VoiceAudioPayload,
|
|
82
84
|
} from './voice.js';
|
|
83
85
|
|
|
84
86
|
export {
|
package/src/inference.ts
CHANGED
|
@@ -1,98 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Inference verbs
|
|
3
|
-
* helpers: session.inference.host(slots), .judge(slots),
|
|
4
|
-
* .classify(slots), etc. — calls the Gateway through the Runtime
|
|
5
|
-
* (the SDK never holds the gateway-auth key).").
|
|
6
|
-
*
|
|
7
|
-
* The chunk-B7 contract is:
|
|
8
|
-
*
|
|
9
|
-
* 1. The caller invokes `session.inference.<verb>({ slots, output,
|
|
10
|
-
* qualityTier? })`.
|
|
11
|
-
* 2. The SDK serialises any caller-declared Zod output schema to
|
|
12
|
-
* JSON Schema (via `@platform/shared/json-schema.ts`).
|
|
13
|
-
* 3. The SDK sends an `emit` frame with a reserved `inference.*`
|
|
14
|
-
* event type. The Runtime (chunk B8a) receives it, signs the
|
|
15
|
-
* request, forwards to the Gateway, and emits a follow-up
|
|
16
|
-
* `event` frame with the result keyed by the original message
|
|
17
|
-
* id.
|
|
18
|
-
* 4. The SDK matches the inbound event against the pending call's
|
|
19
|
-
* id and resolves the typed result.
|
|
20
|
-
*
|
|
21
|
-
* **Why `emit` and not a new wire kind?** The protocol already
|
|
22
|
-
* carries `emit` for asynchronous client → server work; reserving an
|
|
23
|
-
* `inference.*` namespace on `eventType` keeps the surface compact
|
|
24
|
-
* and avoids a `PROTOCOL_VERSION` bump. The chunk-B8a Runtime is
|
|
25
|
-
* what enforces the gating + signing.
|
|
26
|
-
*
|
|
27
|
-
* Until chunk B8a wires the Runtime-side handler, the SDK's
|
|
28
|
-
* inference verbs return `Err({ kind: 'runtime_not_wired' })`. The
|
|
29
|
-
* SDK still serialises the schema + payload so the chunk-B7 surface
|
|
30
|
-
* is testable (the JSON Schema + outbound `emit` shape are
|
|
31
|
-
* regression-protected); the runtime-side roundtrip lights up with
|
|
32
|
-
* B8a.
|
|
2
|
+
* Inference verbs — game code calls by manifest `templateId`.
|
|
33
3
|
*/
|
|
34
4
|
|
|
5
|
+
import type { QualityTier } from '@wibly/internal-manifest';
|
|
35
6
|
import type { z, ZodTypeAny } from 'zod';
|
|
36
7
|
|
|
37
8
|
import { zodToJsonSchema, type JsonSchema } from '@wibly/internal-shared';
|
|
38
9
|
import type { Result } from '@wibly/internal-shared';
|
|
39
|
-
import type { CallKind, QualityTier } from '@wibly/internal-manifest';
|
|
40
10
|
|
|
41
11
|
import type { SdkError } from './errors.js';
|
|
42
12
|
|
|
43
|
-
/**
|
|
44
|
-
* Quality tier surfaced on the SDK boundary. Type-only re-export of
|
|
45
|
-
* `@platform/manifest`'s `QualityTier` so the manifest stays the
|
|
46
|
-
* single source of truth for the on-the-wire enum. The `import type`
|
|
47
|
-
* is erased at compile time, so the SDK still does not pull in the
|
|
48
|
-
* manifest's Zod runtime — only the type literals.
|
|
49
|
-
*
|
|
50
|
-
* The B7 close note ("kept as a string literal here so the SDK
|
|
51
|
-
* doesn't pull in the manifest's full Zod runtime") was right about
|
|
52
|
-
* the runtime concern but wrong to duplicate the literals — the
|
|
53
|
-
* duplication had already drifted from the manifest's enum at chunk
|
|
54
|
-
* close. Type-only re-export gives the same runtime weight (zero)
|
|
55
|
-
* with the contract honoured at compile time.
|
|
56
|
-
*/
|
|
57
|
-
export type SdkQualityTier = QualityTier;
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Recognised call kinds. Type-only re-export of `@platform/manifest`'s
|
|
61
|
-
* `CallKind`; the manifest's `CallKindSchema` (enumerating
|
|
62
|
-
* `host_open_phase`, `host_judge`, `host_resolve`, `host_recap`,
|
|
63
|
-
* `judge_funniness`, `narrate_event`, `classify`, `compose_clue`) is
|
|
64
|
-
* the canonical wire-side set. New `callKind`s land in
|
|
65
|
-
* `@platform/manifest` and `docs/conventions/prompt-composition.md`
|
|
66
|
-
* in the same commit per chunk B4's convention; the SDK's enum
|
|
67
|
-
* follows automatically because it's a type alias.
|
|
68
|
-
*
|
|
69
|
-
* The trailing `(string & {})` widening preserves the chunk-B7
|
|
70
|
-
* authoring-aid behaviour ("the SDK accepts any string at runtime")
|
|
71
|
-
* while keeping the literal set as IntelliSense-discoverable
|
|
72
|
-
* autocompletes for in-tree game bundles.
|
|
73
|
-
*/
|
|
74
|
-
export type SdkCallKind = CallKind | (string & {});
|
|
75
|
-
|
|
76
13
|
export type InferenceCallInput<TOutput extends ZodTypeAny> = {
|
|
77
|
-
readonly
|
|
14
|
+
readonly templateId: string;
|
|
78
15
|
readonly slots: Readonly<Record<string, unknown>>;
|
|
79
16
|
readonly output?: TOutput;
|
|
80
|
-
readonly qualityTier?: SdkQualityTier;
|
|
81
|
-
/**
|
|
82
|
-
* Optional idempotency key. The Runtime forwards it to the
|
|
83
|
-
* Gateway's `metadata.idempotencyKey`. Use case: a host that
|
|
84
|
-
* wants to retry without double-billing.
|
|
85
|
-
*/
|
|
86
17
|
readonly idempotencyKey?: string;
|
|
87
18
|
};
|
|
88
19
|
|
|
89
20
|
export type InferenceCallSuccess<TOutput extends ZodTypeAny> = {
|
|
90
|
-
/** Raw model output (the Gateway's `output`). */
|
|
91
21
|
readonly output: string;
|
|
92
|
-
/** Parsed structured response. `null` if no schema was provided. */
|
|
93
22
|
readonly structured: TOutput extends ZodTypeAny ? z.infer<TOutput> | null : null;
|
|
94
|
-
/** Gateway usage block. Surface for debug; the operator-side
|
|
95
|
-
* dashboards use the audit ledger instead. */
|
|
96
23
|
readonly usage: {
|
|
97
24
|
readonly model: string;
|
|
98
25
|
readonly tokensIn: number;
|
|
@@ -103,8 +30,7 @@ export type InferenceCallSuccess<TOutput extends ZodTypeAny> = {
|
|
|
103
30
|
};
|
|
104
31
|
|
|
105
32
|
export type SerialisedInferenceRequest = {
|
|
106
|
-
readonly
|
|
107
|
-
readonly qualityTier: SdkQualityTier;
|
|
33
|
+
readonly templateId: string;
|
|
108
34
|
readonly slots: Readonly<Record<string, unknown>>;
|
|
109
35
|
readonly outputSchema: JsonSchema | undefined;
|
|
110
36
|
readonly idempotencyKey: string | undefined;
|
|
@@ -112,37 +38,19 @@ export type SerialisedInferenceRequest = {
|
|
|
112
38
|
|
|
113
39
|
export const INFERENCE_EVENT_PREFIX = 'inference.' as const;
|
|
114
40
|
|
|
115
|
-
/**
|
|
116
|
-
* Build the wire payload for an inference call. Surfaced as a pure
|
|
117
|
-
* helper so the testkit can assert the serialised shape without
|
|
118
|
-
* spinning a transport.
|
|
119
|
-
*/
|
|
120
41
|
export const buildInferenceRequest = <TOutput extends ZodTypeAny>(
|
|
121
42
|
input: InferenceCallInput<TOutput>,
|
|
122
43
|
): SerialisedInferenceRequest => ({
|
|
123
|
-
|
|
124
|
-
qualityTier: input.qualityTier ?? 'standard',
|
|
44
|
+
templateId: input.templateId,
|
|
125
45
|
slots: input.slots,
|
|
126
46
|
outputSchema: input.output ? zodToJsonSchema(input.output) : undefined,
|
|
127
47
|
idempotencyKey: input.idempotencyKey,
|
|
128
48
|
});
|
|
129
49
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
* `client.ts` to the live transport; the Runtime wires up the
|
|
133
|
-
* server-side response under chunk B8a.
|
|
134
|
-
*/
|
|
50
|
+
export type SdkQualityTier = QualityTier;
|
|
51
|
+
|
|
135
52
|
export type SessionInference = {
|
|
136
53
|
readonly call: <TOutput extends ZodTypeAny = ZodTypeAny>(
|
|
137
54
|
input: InferenceCallInput<TOutput>,
|
|
138
55
|
) => Promise<Result<InferenceCallSuccess<TOutput>, SdkError>>;
|
|
139
|
-
readonly host: <TOutput extends ZodTypeAny = ZodTypeAny>(
|
|
140
|
-
input: Omit<InferenceCallInput<TOutput>, 'callKind'>,
|
|
141
|
-
) => Promise<Result<InferenceCallSuccess<TOutput>, SdkError>>;
|
|
142
|
-
readonly judge: <TOutput extends ZodTypeAny = ZodTypeAny>(
|
|
143
|
-
input: Omit<InferenceCallInput<TOutput>, 'callKind'>,
|
|
144
|
-
) => Promise<Result<InferenceCallSuccess<TOutput>, SdkError>>;
|
|
145
|
-
readonly classify: <TOutput extends ZodTypeAny = ZodTypeAny>(
|
|
146
|
-
input: Omit<InferenceCallInput<TOutput>, 'callKind'>,
|
|
147
|
-
) => Promise<Result<InferenceCallSuccess<TOutput>, SdkError>>;
|
|
148
56
|
};
|
package/src/time.ts
CHANGED
|
@@ -24,6 +24,10 @@ export type ServerTimeSource = {
|
|
|
24
24
|
readonly recordedEvents: () => readonly RecordedEvent[];
|
|
25
25
|
/** Internal: update the measured skew on a fresh pong. */
|
|
26
26
|
readonly updateSkew: (skewMs: number) => void;
|
|
27
|
+
/** Freeze `serverNow()` while the session is paused. */
|
|
28
|
+
readonly freeze: () => void;
|
|
29
|
+
/** Resume advancing `serverNow()` after a pause. */
|
|
30
|
+
readonly unfreeze: () => void;
|
|
27
31
|
};
|
|
28
32
|
|
|
29
33
|
export type RecordedEvent = {
|
|
@@ -40,10 +44,14 @@ export const createServerTimeSource = (
|
|
|
40
44
|
): ServerTimeSource => {
|
|
41
45
|
const nowFn = opts.now ?? (() => Date.now());
|
|
42
46
|
let skew = 0;
|
|
47
|
+
let frozenServerNow: number | null = null;
|
|
43
48
|
const events: RecordedEvent[] = [];
|
|
44
49
|
|
|
45
50
|
return {
|
|
46
|
-
serverNow: () =>
|
|
51
|
+
serverNow: () => {
|
|
52
|
+
if (frozenServerNow !== null) return frozenServerNow;
|
|
53
|
+
return nowFn() + skew;
|
|
54
|
+
},
|
|
47
55
|
recordEvent: (eventId) => {
|
|
48
56
|
const clientTs = nowFn();
|
|
49
57
|
events.push({ eventId, clientTs });
|
|
@@ -54,5 +62,13 @@ export const createServerTimeSource = (
|
|
|
54
62
|
updateSkew: (newSkew) => {
|
|
55
63
|
skew = newSkew;
|
|
56
64
|
},
|
|
65
|
+
freeze: () => {
|
|
66
|
+
if (frozenServerNow === null) {
|
|
67
|
+
frozenServerNow = nowFn() + skew;
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
unfreeze: () => {
|
|
71
|
+
frozenServerNow = null;
|
|
72
|
+
},
|
|
57
73
|
};
|
|
58
74
|
};
|
package/src/voice.ts
CHANGED
|
@@ -47,6 +47,27 @@ export type SpeakSuccess = {
|
|
|
47
47
|
readonly durationMs: number;
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
+
/** Optional beat metadata for in-phase TTS choreography (chunk B26). */
|
|
51
|
+
export type TtsBeatReveal = {
|
|
52
|
+
readonly kind: 'none' | 'player_submission';
|
|
53
|
+
readonly playerId?: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type TtsBeatMeta = {
|
|
57
|
+
readonly beatId: string;
|
|
58
|
+
readonly sequence: number;
|
|
59
|
+
readonly reveal?: TtsBeatReveal;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** Wire shape on `voice.audio` events (Host + Experience bundles). */
|
|
63
|
+
export type VoiceAudioPayload = SpeakSuccess & {
|
|
64
|
+
readonly id?: string;
|
|
65
|
+
readonly causeMessageId?: string;
|
|
66
|
+
readonly caption?: string | null;
|
|
67
|
+
readonly beat?: TtsBeatMeta;
|
|
68
|
+
readonly cues?: ReadonlyArray<{ readonly at: number; readonly kind: string }>;
|
|
69
|
+
};
|
|
70
|
+
|
|
50
71
|
export type SessionVoice = {
|
|
51
72
|
readonly speak: (input: SpeakInput) => Promise<Result<SpeakSuccess, SdkError>>;
|
|
52
73
|
};
|