dsh-realtime 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Travis Driessen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,69 @@
1
- # Temporary Holding Version
1
+ # dsh-realtime
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **A realtime voice capability seam for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness).**
4
+
5
+ DSH ships ~40 `ctx.*` capability seams (`ctx.llm`, `ctx.tools`, `ctx.subagents`, …) and **no voice
6
+ seam of any kind**. This package is the proposal and the implementation of that missing seam: a
7
+ provider registry over the live-session vocabulary, plus the adapter base every voice backend extends.
8
+
9
+ > **Status: staged.** This is a third-party proposal, not an official `@deepseek-ai` package. It is
10
+ > built to the harness's package conventions so it can be contributed upstream without restructuring.
11
+
12
+ ## The model
13
+
14
+ A **provider registry**, mirroring `ctx.llm`:
15
+
16
+ ```ts
17
+ export const name = 'realtime-openai-live'
18
+ export const inject = ['realtime']
19
+
20
+ export function apply(ctx: Context, config: Config) {
21
+ ctx.realtime.registerAdapter(['openai-live'], new OpenAiLiveAdapter(config))
22
+ }
23
+ ```
24
+
25
+ The seam is deliberately thin. It owns three things:
26
+
27
+ | Owned here | Owned by the adapter |
28
+ |---|---|
29
+ | Route registration, all-or-nothing, disposed with the fiber | The transport (WebSocket, WebRTC, in-process) |
30
+ | Provider metadata and advisory model catalogues | Wire encoding and decoding |
31
+ | Append bounds (`MAX_APPEND_CHARS`) and coded failures | Token accounting and provider error mapping |
32
+
33
+ ## What the seam deliberately does not have
34
+
35
+ - **No end-of-utterance call.** Endpointing belongs to the voice provider. A client-side detector
36
+ would be a second, competing turn boundary — and the protocol family this targets has no commit
37
+ event at all, so the absence is enforced upstream, not merely preferred here.
38
+ - **No turn management.** Turn-taking is session state, not conversation state. `sendAudio` pushes
39
+ frames and callbacks deliver what happens; nothing here decides who speaks next.
40
+ - **No auto-answer for delegations.** `onDelegation` hands the consumer a delegation and the
41
+ consumer decides. There is no default path that answers on the consumer's behalf, because a
42
+ default that resolves work the application has not authorised is exactly the failure this design
43
+ refuses to make possible.
44
+
45
+ ## Failure semantics
46
+
47
+ Failures that prevent a session opening are **thrown** from `session()`. Failures after a session
48
+ opened are delivered to `handlers.onError` — a session that dies mid-conversation must not look like
49
+ a rejected request. Codes are stable and branchable; message text is not part of the API.
50
+
51
+ ## Registration and disposal
52
+
53
+ `registerAdapter` validates the whole candidate set before mutating anything, so a rejected
54
+ registration leaves the registry exactly as it was. The registration is created through `ctx.effect`,
55
+ so it is withdrawn with the contributing fiber — HMR unmounts routes rather than leaking them.
56
+
57
+ ## Known limitations and deferred work
58
+
59
+ - **Append bounds are characters, not tokens.** The provider's real limit is 500 tokens. Counting
60
+ tokens exactly would bind this seam to a tokenizer it does not own, so `MAX_APPEND_CHARS` is a
61
+ documented conservative proxy. An adapter that can count tokens should tighten it rather than
62
+ widening the seam's promise.
63
+ - **No session resumption.** The protocol fixes provider, model, voice and delegation mode at
64
+ startup; a seam-level `resume` would advertise a capability the wire cannot honour.
65
+ - **The model catalogue is advisory.** Absence from `listModels` must never become request rejection.
66
+ - **Build layout deviates from the harness's.** A `@deepseek-ai` package compiles to `lib/types` and
67
+ ships a separately bundled `lib/index.js`; this repo compiles flat to `lib/`. The `main`/`types`
68
+ entries and the `.ts`-extension import style match, so the source is portable — but packaging must
69
+ be reconciled before an upstream PR. Tracked here rather than silently diverging.
package/lib/error.d.ts ADDED
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Typed failures for the realtime seam, carrying stable machine codes.
3
+ *
4
+ * Codes are the seam's API: a consumer branches on `code`, never on message text. Adding a code is a
5
+ * minor release; changing what an existing code means is a breaking one.
6
+ *
7
+ * @module dsh-realtime/error
8
+ */
9
+ /** Stable machine codes raised by this seam. */
10
+ export declare const REALTIME_ERROR_CODES: Readonly<{
11
+ /** A registration named an empty or malformed provider route. */
12
+ INVALID_PROVIDER: "INVALID_PROVIDER";
13
+ /** A route already has an adapter registered by another registration. */
14
+ DUPLICATE_PROVIDER: "DUPLICATE_PROVIDER";
15
+ /** Registration was released, so it can no longer replace its routes. */
16
+ REGISTRATION_DISPOSED: "REGISTRATION_DISPOSED";
17
+ /** No adapter is registered for the requested route. */
18
+ NO_ADAPTER: "NO_ADAPTER";
19
+ /** An append was empty, not a string, or over the seam's bound. */
20
+ INVALID_APPEND: "INVALID_APPEND";
21
+ /** The session has already been closed. */
22
+ SESSION_CLOSED: "SESSION_CLOSED";
23
+ /** The credential an adapter needs is absent or unusable. Names the setting, never the value. */
24
+ MISSING_CREDENTIAL: "MISSING_CREDENTIAL";
25
+ /** The provider reported a failure, or an operation it was expected to acknowledge never was. */
26
+ PROVIDER_ERROR: "PROVIDER_ERROR";
27
+ /** A recorded session could not be read as a recording. */
28
+ INVALID_RECORDING: "INVALID_RECORDING";
29
+ }>;
30
+ /** One of {@link REALTIME_ERROR_CODES}. */
31
+ export type RealtimeErrorCode = (typeof REALTIME_ERROR_CODES)[keyof typeof REALTIME_ERROR_CODES];
32
+ /**
33
+ * A typed seam failure.
34
+ *
35
+ * The constructor validates its own arguments rather than trusting callers: a failure raised while
36
+ * reporting a failure is the worst place to discover a malformed argument.
37
+ */
38
+ export declare class RealtimeError extends Error {
39
+ /** Stable machine code. Branch on this, never on `message`. */
40
+ readonly code: RealtimeErrorCode;
41
+ /**
42
+ * @param message - non-empty human-readable summary. Must not contain secret material.
43
+ * @param code - one of {@link REALTIME_ERROR_CODES}.
44
+ * @param options - optional `cause`.
45
+ */
46
+ constructor(message: string, code: RealtimeErrorCode, options?: ErrorOptions);
47
+ }
48
+ //# sourceMappingURL=error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,gDAAgD;AAChD,eAAO,MAAM,oBAAoB;IAC/B,iEAAiE;;IAEjE,yEAAyE;;IAEzE,yEAAyE;;IAEzE,wDAAwD;;IAExD,mEAAmE;;IAEnE,2CAA2C;;IAE3C,iGAAiG;;IAEjG,iGAAiG;;IAEjG,2DAA2D;;EAE3D,CAAA;AAEF,2CAA2C;AAC3C,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,OAAO,oBAAoB,CAAC,CAAA;AAEhG;;;;;GAKG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACtC,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAA;IAEhC;;;;OAIG;IACH,YAAY,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,CAAC,EAAE,YAAY,EAU3E;CACF"}
package/lib/error.js ADDED
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Typed failures for the realtime seam, carrying stable machine codes.
3
+ *
4
+ * Codes are the seam's API: a consumer branches on `code`, never on message text. Adding a code is a
5
+ * minor release; changing what an existing code means is a breaking one.
6
+ *
7
+ * @module dsh-realtime/error
8
+ */
9
+ /** Stable machine codes raised by this seam. */
10
+ export const REALTIME_ERROR_CODES = Object.freeze({
11
+ /** A registration named an empty or malformed provider route. */
12
+ INVALID_PROVIDER: 'INVALID_PROVIDER',
13
+ /** A route already has an adapter registered by another registration. */
14
+ DUPLICATE_PROVIDER: 'DUPLICATE_PROVIDER',
15
+ /** Registration was released, so it can no longer replace its routes. */
16
+ REGISTRATION_DISPOSED: 'REGISTRATION_DISPOSED',
17
+ /** No adapter is registered for the requested route. */
18
+ NO_ADAPTER: 'NO_ADAPTER',
19
+ /** An append was empty, not a string, or over the seam's bound. */
20
+ INVALID_APPEND: 'INVALID_APPEND',
21
+ /** The session has already been closed. */
22
+ SESSION_CLOSED: 'SESSION_CLOSED',
23
+ /** The credential an adapter needs is absent or unusable. Names the setting, never the value. */
24
+ MISSING_CREDENTIAL: 'MISSING_CREDENTIAL',
25
+ /** The provider reported a failure, or an operation it was expected to acknowledge never was. */
26
+ PROVIDER_ERROR: 'PROVIDER_ERROR',
27
+ /** A recorded session could not be read as a recording. */
28
+ INVALID_RECORDING: 'INVALID_RECORDING',
29
+ });
30
+ /**
31
+ * A typed seam failure.
32
+ *
33
+ * The constructor validates its own arguments rather than trusting callers: a failure raised while
34
+ * reporting a failure is the worst place to discover a malformed argument.
35
+ */
36
+ export class RealtimeError extends Error {
37
+ /** Stable machine code. Branch on this, never on `message`. */
38
+ code;
39
+ /**
40
+ * @param message - non-empty human-readable summary. Must not contain secret material.
41
+ * @param code - one of {@link REALTIME_ERROR_CODES}.
42
+ * @param options - optional `cause`.
43
+ */
44
+ constructor(message, code, options) {
45
+ if (typeof message !== 'string' || message.length === 0) {
46
+ throw new TypeError('RealtimeError message must be a non-empty string');
47
+ }
48
+ if (typeof code !== 'string' || code.length === 0) {
49
+ throw new TypeError('RealtimeError code must be a non-empty string');
50
+ }
51
+ super(message, options);
52
+ this.name = 'RealtimeError';
53
+ this.code = code;
54
+ }
55
+ }
56
+ //# sourceMappingURL=error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.js","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,gDAAgD;AAChD,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,CAAC;IAChD,iEAAiE;IACjE,gBAAgB,EAAE,kBAAkB;IACpC,yEAAyE;IACzE,kBAAkB,EAAE,oBAAoB;IACxC,yEAAyE;IACzE,qBAAqB,EAAE,uBAAuB;IAC9C,wDAAwD;IACxD,UAAU,EAAE,YAAY;IACxB,mEAAmE;IACnE,cAAc,EAAE,gBAAgB;IAChC,2CAA2C;IAC3C,cAAc,EAAE,gBAAgB;IAChC,iGAAiG;IACjG,kBAAkB,EAAE,oBAAoB;IACxC,iGAAiG;IACjG,cAAc,EAAE,gBAAgB;IAChC,2DAA2D;IAC3D,iBAAiB,EAAE,mBAAmB;CACvC,CAAC,CAAA;AAKF;;;;;GAKG;AACH,MAAM,OAAO,aAAc,SAAQ,KAAK;IACtC,+DAA+D;IACtD,IAAI,CAAmB;IAEhC;;;;OAIG;IACH,YAAY,OAAe,EAAE,IAAuB,EAAE,OAAsB;QAC1E,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxD,MAAM,IAAI,SAAS,CAAC,kDAAkD,CAAC,CAAA;QACzE,CAAC;QACD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClD,MAAM,IAAI,SAAS,CAAC,+CAA+C,CAAC,CAAA;QACtE,CAAC;QACD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QACvB,IAAI,CAAC,IAAI,GAAG,eAAe,CAAA;QAC3B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;CACF"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Realtime voice seam: a provider registry plus the adapter base every voice backend extends.
3
+ *
4
+ * Exports the `RealtimeRuntime` service as the **default** export (a DeepSeek Harness convention for
5
+ * service packages) and the abstract `RealtimeAdapter` for provider backends. Function plugins
6
+ * must supply `name` / `inject` / `Config` / `apply` and no default export; this package is a
7
+ * service package, so it is mounted as a class plugin and default-exports its service.
8
+ *
9
+ * @module dsh-realtime
10
+ */
11
+ import { Service, type Context } from '@deepseek-ai/cordis';
12
+ import type { RealtimeDelegation, RealtimeModelInfo, RealtimeProviderInfo, RealtimeSession, RealtimeSessionHandlers, RealtimeSessionOptions } from './types.ts';
13
+ export * from './types.ts';
14
+ export * from './error.ts';
15
+ export { RealtimeError, REALTIME_ERROR_CODES } from './error.ts';
16
+ declare module '@deepseek-ai/cordis' {
17
+ interface Context {
18
+ realtime: RealtimeRuntime;
19
+ }
20
+ }
21
+ /**
22
+ * What {@link RealtimeRuntime.registerAdapter} returns: the disposer, plus an atomic route
23
+ * replacement for the same adapter instance.
24
+ */
25
+ export interface AdapterRegistrationHandle {
26
+ /** Release every route this registration currently holds. */
27
+ (): void;
28
+ /**
29
+ * Replace this registration's routes, keeping the same adapter instance. The candidate set is
30
+ * validated in full first — a conflict with another registration or a malformed route throws and
31
+ * leaves the current routes untouched — and the swap is one synchronous section, so no observer
32
+ * can see the registry between release and re-registration.
33
+ *
34
+ * An empty array is legal here (a plugin whose configuration emptied holds zero routes while
35
+ * staying registered), unlike an empty initial registration.
36
+ *
37
+ * Throws `REGISTRATION_DISPOSED` once the registration was released: its routes are gone and its
38
+ * disposer has already run, so anything registered afterwards would have no owner left to release it.
39
+ * @param providers - the complete next route set for this registration.
40
+ */
41
+ replace(providers: string[]): void;
42
+ }
43
+ /**
44
+ * Provider-wire adapter for the realtime session vocabulary.
45
+ *
46
+ * Register implementations with `ctx.realtime.registerAdapter(providers, adapter)`. The single
47
+ * required method is {@link session}; every other method exists so a provider can describe itself
48
+ * without the seam having to special-case it.
49
+ */
50
+ export declare abstract class RealtimeAdapter {
51
+ /**
52
+ * Describe one provider route owned by this adapter.
53
+ * @param provider - a route passed to `registerAdapter()` for this instance.
54
+ * @returns detached display metadata whose `id` must equal `provider`.
55
+ */
56
+ providerInfo(provider: string): RealtimeProviderInfo;
57
+ /**
58
+ * List the voice models this adapter can currently advertise for one owned route.
59
+ *
60
+ * The result is advisory: an adapter may accept unlisted model ids, and consumers must not turn
61
+ * absence into request rejection.
62
+ * @param _provider - one provider route owned by this adapter.
63
+ * @returns discoverable models in adapter-preferred order.
64
+ */
65
+ listModels(_provider: string): Promise<readonly RealtimeModelInfo[]>;
66
+ /**
67
+ * Open one voice session. The only required method.
68
+ *
69
+ * Implementations must honor `options.signal` during establishment, and must throw (rather than
70
+ * resolve with a dead session) when the session cannot open.
71
+ * @param options - the fully-resolved request; `options.provider` selects the registered route.
72
+ * @returns the live session, with `options.handlers` already wired.
73
+ */
74
+ abstract session(options: RealtimeSessionOptions): Promise<RealtimeSession>;
75
+ }
76
+ /**
77
+ * The `realtime` service: an adapter registry over the session vocabulary.
78
+ *
79
+ * Registration is effect-based, so HMR or a disposal unmounts routes with the contributing fiber —
80
+ * there is no separate teardown path that can be forgotten.
81
+ */
82
+ export declare class RealtimeRuntime extends Service {
83
+ private readonly adapters;
84
+ /**
85
+ * @param ctx - the Cordis context this service is mounted on.
86
+ */
87
+ constructor(ctx: Context);
88
+ /**
89
+ * Register an adapter for the given provider routes, all-or-nothing.
90
+ *
91
+ * Disposed with the fiber. Throws `INVALID_PROVIDER` for a malformed route, `DUPLICATE_PROVIDER`
92
+ * if any route is already held by another registration.
93
+ * @param providers - every provider route this adapter should serve.
94
+ * @param adapter - the adapter that opens sessions for those routes.
95
+ * @returns the disposer, carrying {@link AdapterRegistrationHandle.replace}.
96
+ */
97
+ registerAdapter(providers: string[], adapter: RealtimeAdapter): AdapterRegistrationHandle;
98
+ /**
99
+ * Validate one candidate route set for `adapter`, treating routes this registration already holds
100
+ * as available.
101
+ *
102
+ * Nothing is mutated: a rejected candidate leaves the registry exactly as it was, which is what
103
+ * makes {@link AdapterRegistrationHandle.replace} a swap rather than a delete-then-add that can
104
+ * strand the registry empty.
105
+ * @param providers - candidate routes.
106
+ * @param adapter - the adapter that would own them.
107
+ * @param owned - routes this same registration already holds.
108
+ * @returns registrations ready to commit.
109
+ */
110
+ private prepare;
111
+ /**
112
+ * Swap this registration's routes for the prepared ones in one synchronous section, so no
113
+ * observer can see the registry between the release and the re-registration.
114
+ * @param owned - mutable set tracking which routes this registration holds.
115
+ * @param registrations - validated registrations to install.
116
+ */
117
+ private commit;
118
+ /**
119
+ * Describe the provider routes that currently have an adapter.
120
+ * @returns detached provider metadata in registration order.
121
+ */
122
+ listProviders(): RealtimeProviderInfo[];
123
+ /**
124
+ * Resolve the adapter owning one route.
125
+ * @param provider - registered route to look up.
126
+ * @returns that route's registration.
127
+ * @throws RealtimeError `NO_ADAPTER` when the route is unregistered.
128
+ */
129
+ private registration;
130
+ /**
131
+ * Discover the voice models one registered route advertises.
132
+ * @param provider - registered route to inspect.
133
+ * @returns detached model metadata in adapter-preferred order, duplicates removed.
134
+ */
135
+ listModels(provider: string): Promise<RealtimeModelInfo[]>;
136
+ /**
137
+ * Open one voice session through the adapter registered for its route.
138
+ *
139
+ * A field this seam cannot honor is rejected here rather than forwarded as a no-op — the caller
140
+ * learns the request is unsupported before a live conversation depends on it.
141
+ * @param options - the session request; `options.provider` selects the adapter.
142
+ * @returns the adapter's live session.
143
+ * @throws RealtimeError `NO_ADAPTER` for an unregistered route.
144
+ */
145
+ session(options: RealtimeSessionOptions): Promise<RealtimeSession>;
146
+ /**
147
+ * Validate one context append against the seam's bounds.
148
+ *
149
+ * Exposed so an adapter can enforce the same bound the seam promises, instead of each backend
150
+ * re-deriving it. Bound enforcement lives at the operation that makes the decision: a caller that
151
+ * bypasses this cannot silently send an over-long append.
152
+ * @param content - candidate append text.
153
+ * @param maxChars - character ceiling; defaults to the provider's documented bound.
154
+ * @returns the content unchanged, for convenient inline use.
155
+ * @throws RealtimeError `INVALID_APPEND` for a non-string, empty, or over-long value.
156
+ */
157
+ static assertAppendable(content: string, maxChars: number): string;
158
+ }
159
+ /** Re-exported so adapters can type their handler wiring without a second import. */
160
+ export type { RealtimeDelegation, RealtimeSessionHandlers };
161
+ export default RealtimeRuntime;
162
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAE3D,OAAO,KAAK,EACV,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,EACf,uBAAuB,EACvB,sBAAsB,EACvB,MAAM,YAAY,CAAA;AAEnB,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEhE,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,QAAQ,EAAE,eAAe,CAAA;KAC1B;CACF;AAED;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC,6DAA6D;IAC7D,IAAI,IAAI,CAAA;IACR;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;CACnC;AAED;;;;;;GAMG;AACH,8BAAsB,eAAe;IACnC;;;;OAIG;IACH,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB,CAEnD;IAED;;;;;;;OAOG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAEnE;IAED;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;CAC5E;AAQD;;;;;GAKG;AACH,qBAAa,eAAgB,SAAQ,OAAO;IAC1C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyC;IAElE;;OAEG;IACH,YAAY,GAAG,EAAE,OAAO,EAEvB;IAED;;;;;;;;OAQG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,eAAe,GAAG,yBAAyB,CAiCxF;IAED;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,OAAO;IAgCf;;;;;OAKG;IACH,OAAO,CAAC,MAAM;IASd;;;OAGG;IACH,aAAa,IAAI,oBAAoB,EAAE,CAEtC;IAED;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IAQpB;;;;OAIG;IACG,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAe/D;IAED;;;;;;;;OAQG;IACG,OAAO,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,eAAe,CAAC,CAgBvE;IAED;;;;;;;;;;OAUG;IACH,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAWjE;CACF;AAED,qFAAqF;AACrF,YAAY,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,CAAA;eAE5C,eAAe"}
package/lib/index.js ADDED
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Realtime voice seam: a provider registry plus the adapter base every voice backend extends.
3
+ *
4
+ * Exports the `RealtimeRuntime` service as the **default** export (a DeepSeek Harness convention for
5
+ * service packages) and the abstract `RealtimeAdapter` for provider backends. Function plugins
6
+ * must supply `name` / `inject` / `Config` / `apply` and no default export; this package is a
7
+ * service package, so it is mounted as a class plugin and default-exports its service.
8
+ *
9
+ * @module dsh-realtime
10
+ */
11
+ import { Service } from '@deepseek-ai/cordis';
12
+ import { REALTIME_ERROR_CODES, RealtimeError } from './error.js';
13
+ export * from './types.js';
14
+ export * from './error.js';
15
+ export { RealtimeError, REALTIME_ERROR_CODES } from './error.js';
16
+ /**
17
+ * Provider-wire adapter for the realtime session vocabulary.
18
+ *
19
+ * Register implementations with `ctx.realtime.registerAdapter(providers, adapter)`. The single
20
+ * required method is {@link session}; every other method exists so a provider can describe itself
21
+ * without the seam having to special-case it.
22
+ */
23
+ export class RealtimeAdapter {
24
+ /**
25
+ * Describe one provider route owned by this adapter.
26
+ * @param provider - a route passed to `registerAdapter()` for this instance.
27
+ * @returns detached display metadata whose `id` must equal `provider`.
28
+ */
29
+ providerInfo(provider) {
30
+ return { id: provider, name: provider };
31
+ }
32
+ /**
33
+ * List the voice models this adapter can currently advertise for one owned route.
34
+ *
35
+ * The result is advisory: an adapter may accept unlisted model ids, and consumers must not turn
36
+ * absence into request rejection.
37
+ * @param _provider - one provider route owned by this adapter.
38
+ * @returns discoverable models in adapter-preferred order.
39
+ */
40
+ listModels(_provider) {
41
+ return Promise.resolve([]);
42
+ }
43
+ }
44
+ /**
45
+ * The `realtime` service: an adapter registry over the session vocabulary.
46
+ *
47
+ * Registration is effect-based, so HMR or a disposal unmounts routes with the contributing fiber —
48
+ * there is no separate teardown path that can be forgotten.
49
+ */
50
+ export class RealtimeRuntime extends Service {
51
+ adapters = new Map();
52
+ /**
53
+ * @param ctx - the Cordis context this service is mounted on.
54
+ */
55
+ constructor(ctx) {
56
+ super(ctx, 'realtime');
57
+ }
58
+ /**
59
+ * Register an adapter for the given provider routes, all-or-nothing.
60
+ *
61
+ * Disposed with the fiber. Throws `INVALID_PROVIDER` for a malformed route, `DUPLICATE_PROVIDER`
62
+ * if any route is already held by another registration.
63
+ * @param providers - every provider route this adapter should serve.
64
+ * @param adapter - the adapter that opens sessions for those routes.
65
+ * @returns the disposer, carrying {@link AdapterRegistrationHandle.replace}.
66
+ */
67
+ registerAdapter(providers, adapter) {
68
+ if (providers.length === 0) {
69
+ throw new RealtimeError('an adapter must register at least one provider', REALTIME_ERROR_CODES.INVALID_PROVIDER);
70
+ }
71
+ // Routes this registration currently holds; `replace` rewrites it, and the disposer releases
72
+ // whatever it holds at disposal time.
73
+ const owned = new Set();
74
+ // `owned` being empty cannot report disposal on its own, because `replace([])` legally leaves a
75
+ // live registration holding none.
76
+ let released = false;
77
+ const dispose = this.ctx.effect(function* () {
78
+ this.commit(owned, this.prepare(providers, adapter, owned));
79
+ yield () => {
80
+ released = true;
81
+ for (const provider of owned)
82
+ this.adapters.delete(provider);
83
+ owned.clear();
84
+ };
85
+ }.bind(this), 'realtime.registerAdapter()');
86
+ const handle = (() => void dispose());
87
+ handle.replace = (next) => {
88
+ // Registering here would leak: the effect's disposer already ran, so nothing remains to
89
+ // release whatever this call would put in the map.
90
+ if (released) {
91
+ throw new RealtimeError('a disposed adapter registration cannot replace its routes', REALTIME_ERROR_CODES.REGISTRATION_DISPOSED);
92
+ }
93
+ this.commit(owned, this.prepare(next, adapter, owned));
94
+ };
95
+ return handle;
96
+ }
97
+ /**
98
+ * Validate one candidate route set for `adapter`, treating routes this registration already holds
99
+ * as available.
100
+ *
101
+ * Nothing is mutated: a rejected candidate leaves the registry exactly as it was, which is what
102
+ * makes {@link AdapterRegistrationHandle.replace} a swap rather than a delete-then-add that can
103
+ * strand the registry empty.
104
+ * @param providers - candidate routes.
105
+ * @param adapter - the adapter that would own them.
106
+ * @param owned - routes this same registration already holds.
107
+ * @returns registrations ready to commit.
108
+ */
109
+ prepare(providers, adapter, owned) {
110
+ const unique = new Set();
111
+ const registrations = [];
112
+ for (const provider of providers) {
113
+ if (typeof provider !== 'string' || provider.length === 0) {
114
+ throw new RealtimeError('adapter provider names must be non-empty strings', REALTIME_ERROR_CODES.INVALID_PROVIDER);
115
+ }
116
+ if (unique.has(provider) || (this.adapters.has(provider) && !owned.has(provider))) {
117
+ throw new RealtimeError(`an adapter for provider "${provider}" is already registered`, REALTIME_ERROR_CODES.DUPLICATE_PROVIDER);
118
+ }
119
+ const info = adapter.providerInfo(provider);
120
+ if (typeof info.id !== 'string' || info.id !== provider
121
+ || typeof info.name !== 'string' || info.name.length === 0) {
122
+ throw new RealtimeError(`adapter metadata for provider "${provider}" must preserve its id and have a non-empty name`, REALTIME_ERROR_CODES.INVALID_PROVIDER);
123
+ }
124
+ unique.add(provider);
125
+ registrations.push({
126
+ adapter,
127
+ provider: info.description === undefined
128
+ ? { id: info.id, name: info.name }
129
+ : { id: info.id, name: info.name, description: info.description },
130
+ });
131
+ }
132
+ return registrations;
133
+ }
134
+ /**
135
+ * Swap this registration's routes for the prepared ones in one synchronous section, so no
136
+ * observer can see the registry between the release and the re-registration.
137
+ * @param owned - mutable set tracking which routes this registration holds.
138
+ * @param registrations - validated registrations to install.
139
+ */
140
+ commit(owned, registrations) {
141
+ for (const provider of owned)
142
+ this.adapters.delete(provider);
143
+ owned.clear();
144
+ for (const registration of registrations) {
145
+ this.adapters.set(registration.provider.id, registration);
146
+ owned.add(registration.provider.id);
147
+ }
148
+ }
149
+ /**
150
+ * Describe the provider routes that currently have an adapter.
151
+ * @returns detached provider metadata in registration order.
152
+ */
153
+ listProviders() {
154
+ return [...this.adapters.values()].map(({ provider }) => ({ ...provider }));
155
+ }
156
+ /**
157
+ * Resolve the adapter owning one route.
158
+ * @param provider - registered route to look up.
159
+ * @returns that route's registration.
160
+ * @throws RealtimeError `NO_ADAPTER` when the route is unregistered.
161
+ */
162
+ registration(provider) {
163
+ const registration = this.adapters.get(provider);
164
+ if (registration === undefined) {
165
+ throw new RealtimeError(`no realtime adapter registered for provider "${provider}"`, REALTIME_ERROR_CODES.NO_ADAPTER);
166
+ }
167
+ return registration;
168
+ }
169
+ /**
170
+ * Discover the voice models one registered route advertises.
171
+ * @param provider - registered route to inspect.
172
+ * @returns detached model metadata in adapter-preferred order, duplicates removed.
173
+ */
174
+ async listModels(provider) {
175
+ const models = await this.registration(provider).adapter.listModels(provider);
176
+ const seen = new Set();
177
+ const detached = [];
178
+ for (const model of models) {
179
+ if (typeof model.id !== 'string' || model.id.length === 0 || seen.has(model.id))
180
+ continue;
181
+ seen.add(model.id);
182
+ detached.push({
183
+ id: model.id,
184
+ name: typeof model.name === 'string' && model.name.length > 0 ? model.name : model.id,
185
+ ...model.inputModalities === undefined ? {} : { inputModalities: [...model.inputModalities] },
186
+ ...model.outputModalities === undefined ? {} : { outputModalities: [...model.outputModalities] },
187
+ });
188
+ }
189
+ return detached;
190
+ }
191
+ /**
192
+ * Open one voice session through the adapter registered for its route.
193
+ *
194
+ * A field this seam cannot honor is rejected here rather than forwarded as a no-op — the caller
195
+ * learns the request is unsupported before a live conversation depends on it.
196
+ * @param options - the session request; `options.provider` selects the adapter.
197
+ * @returns the adapter's live session.
198
+ * @throws RealtimeError `NO_ADAPTER` for an unregistered route.
199
+ */
200
+ async session(options) {
201
+ if (typeof options.provider !== 'string' || options.provider.length === 0) {
202
+ throw new RealtimeError('a session needs a non-empty provider route', REALTIME_ERROR_CODES.INVALID_PROVIDER);
203
+ }
204
+ if (typeof options.model !== 'string' || options.model.length === 0) {
205
+ throw new RealtimeError('a session needs a non-empty model id', REALTIME_ERROR_CODES.INVALID_PROVIDER);
206
+ }
207
+ if (options.instructions !== undefined && options.instructions.length === 0) {
208
+ throw new RealtimeError('session instructions must be non-empty when supplied', REALTIME_ERROR_CODES.INVALID_APPEND);
209
+ }
210
+ if (options.signal?.aborted) {
211
+ throw new RealtimeError('session establishment was aborted before it started', REALTIME_ERROR_CODES.SESSION_CLOSED, {
212
+ cause: options.signal.reason,
213
+ });
214
+ }
215
+ return await this.registration(options.provider).adapter.session(options);
216
+ }
217
+ /**
218
+ * Validate one context append against the seam's bounds.
219
+ *
220
+ * Exposed so an adapter can enforce the same bound the seam promises, instead of each backend
221
+ * re-deriving it. Bound enforcement lives at the operation that makes the decision: a caller that
222
+ * bypasses this cannot silently send an over-long append.
223
+ * @param content - candidate append text.
224
+ * @param maxChars - character ceiling; defaults to the provider's documented bound.
225
+ * @returns the content unchanged, for convenient inline use.
226
+ * @throws RealtimeError `INVALID_APPEND` for a non-string, empty, or over-long value.
227
+ */
228
+ static assertAppendable(content, maxChars) {
229
+ if (typeof content !== 'string' || content.length === 0) {
230
+ throw new RealtimeError('an append needs non-empty content', REALTIME_ERROR_CODES.INVALID_APPEND);
231
+ }
232
+ if (content.length > maxChars) {
233
+ throw new RealtimeError(`an append of ${content.length} characters exceeds the ${maxChars}-character seam bound`, REALTIME_ERROR_CODES.INVALID_APPEND);
234
+ }
235
+ return content;
236
+ }
237
+ }
238
+ export default RealtimeRuntime;
239
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}