dsh-realtime 0.1.1 → 0.2.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/lib/error.d.ts CHANGED
@@ -26,9 +26,51 @@ export declare const REALTIME_ERROR_CODES: Readonly<{
26
26
  PROVIDER_ERROR: "PROVIDER_ERROR";
27
27
  /** A recorded session could not be read as a recording. */
28
28
  INVALID_RECORDING: "INVALID_RECORDING";
29
+ /**
30
+ * The capability is not configured yet — an expected state on a fresh install, not a fault.
31
+ *
32
+ * Distinct from {@link REALTIME_ERROR_CODES.CREDENTIAL_REJECTED}: absent is fixed by supplying a
33
+ * value, unusable by replacing one.
34
+ */
35
+ NOT_CONFIGURED: "NOT_CONFIGURED";
36
+ /** A credential is present and the provider refused it. Replace it; retrying cannot help. */
37
+ CREDENTIAL_REJECTED: "CREDENTIAL_REJECTED";
38
+ /** The credential is valid but the account cannot use what was requested (model, region, scope). */
39
+ NOT_ENTITLED: "NOT_ENTITLED";
40
+ /** The account cannot pay for the request — an exhausted balance or a hard billing limit. */
41
+ INSUFFICIENT_CREDIT: "INSUFFICIENT_CREDIT";
42
+ /** The provider throttled the request. Repeating later may succeed. */
43
+ RATE_LIMITED: "RATE_LIMITED";
44
+ /** The provider was asked something and did not answer within the bound. */
45
+ PROVIDER_TIMEOUT: "PROVIDER_TIMEOUT";
46
+ /** The transport failed — a socket error, not a decision the provider made. */
47
+ NETWORK: "NETWORK";
29
48
  }>;
30
49
  /** One of {@link REALTIME_ERROR_CODES}. */
31
50
  export type RealtimeErrorCode = (typeof REALTIME_ERROR_CODES)[keyof typeof REALTIME_ERROR_CODES];
51
+ /**
52
+ * What a caller can act on, carried as structure rather than prose.
53
+ *
54
+ * A failure a user cannot act on is a failure report that wasted their time. Every field here exists
55
+ * so the *class* of a failure survives translation: an agent can relay `remedy` verbatim, a settings
56
+ * surface can highlight `setting`, a retry loop can consult `retryable`, and a human reading a log
57
+ * can see the provider's own `providerCode` instead of a paraphrase of it.
58
+ *
59
+ * Never carries secret material. `providerCode` is the provider's own error code, never a credential.
60
+ */
61
+ export interface RealtimeFailureDetail {
62
+ /**
63
+ * What to do about it, written to be relayed **verbatim** to whoever is trying to use the feature.
64
+ * One sentence, imperative, no jargon.
65
+ */
66
+ remedy?: string;
67
+ /** The configuration key that must be supplied, when the failure is a configuration state. */
68
+ setting?: string;
69
+ /** Whether repeating the identical call could succeed without any change. */
70
+ retryable?: boolean;
71
+ /** The provider's own error code, verbatim. Never a translated or inferred value. */
72
+ providerCode?: string;
73
+ }
32
74
  /**
33
75
  * A typed seam failure.
34
76
  *
@@ -38,11 +80,15 @@ export type RealtimeErrorCode = (typeof REALTIME_ERROR_CODES)[keyof typeof REALT
38
80
  export declare class RealtimeError extends Error {
39
81
  /** Stable machine code. Branch on this, never on `message`. */
40
82
  readonly code: RealtimeErrorCode;
83
+ /** What a caller can act on, when the failure is one they can act on. */
84
+ readonly detail: RealtimeFailureDetail | undefined;
41
85
  /**
42
86
  * @param message - non-empty human-readable summary. Must not contain secret material.
43
87
  * @param code - one of {@link REALTIME_ERROR_CODES}.
44
- * @param options - optional `cause`.
88
+ * @param options - optional `cause`, and an optional structured `detail`.
45
89
  */
46
- constructor(message: string, code: RealtimeErrorCode, options?: ErrorOptions);
90
+ constructor(message: string, code: RealtimeErrorCode, options?: ErrorOptions & {
91
+ detail?: RealtimeFailureDetail;
92
+ });
47
93
  }
48
94
  //# sourceMappingURL=error.d.ts.map
@@ -1 +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"}
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;;IAS3D;;;;;OAKG;;IAEH,6FAA6F;;IAE7F,oGAAoG;;IAEpG,6FAA6F;;IAE7F,uEAAuE;;IAEvE,4EAA4E;;IAE5E,+EAA+E;;EAE/E,CAAA;AAEF,2CAA2C;AAC3C,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,OAAO,oBAAoB,CAAC,CAAA;AAEhG;;;;;;;;;GASG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8FAA8F;IAC9F,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,qFAAqF;IACrF,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AA4BD;;;;;GAKG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACtC,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAA;IAEhC,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,qBAAqB,GAAG,SAAS,CAAA;IAElD;;;;OAIG;IACH,YAAY,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG;QAAE,MAAM,CAAC,EAAE,qBAAqB,CAAA;KAAE,EAchH;CACF"}
package/lib/error.js CHANGED
@@ -26,7 +26,56 @@ export const REALTIME_ERROR_CODES = Object.freeze({
26
26
  PROVIDER_ERROR: 'PROVIDER_ERROR',
27
27
  /** A recorded session could not be read as a recording. */
28
28
  INVALID_RECORDING: 'INVALID_RECORDING',
29
+ // ---------------------------------------------------------------------------------------------
30
+ // Failure taxonomy. These exist so a consumer can branch on the *class* of a failure rather than
31
+ // parse prose: a setup state, a rejected credential, an account that cannot pay, and a throttle
32
+ // need four different responses, and collapsing them is what makes onboarding feel unfinished.
33
+ // ---------------------------------------------------------------------------------------------
34
+ /**
35
+ * The capability is not configured yet — an expected state on a fresh install, not a fault.
36
+ *
37
+ * Distinct from {@link REALTIME_ERROR_CODES.CREDENTIAL_REJECTED}: absent is fixed by supplying a
38
+ * value, unusable by replacing one.
39
+ */
40
+ NOT_CONFIGURED: 'NOT_CONFIGURED',
41
+ /** A credential is present and the provider refused it. Replace it; retrying cannot help. */
42
+ CREDENTIAL_REJECTED: 'CREDENTIAL_REJECTED',
43
+ /** The credential is valid but the account cannot use what was requested (model, region, scope). */
44
+ NOT_ENTITLED: 'NOT_ENTITLED',
45
+ /** The account cannot pay for the request — an exhausted balance or a hard billing limit. */
46
+ INSUFFICIENT_CREDIT: 'INSUFFICIENT_CREDIT',
47
+ /** The provider throttled the request. Repeating later may succeed. */
48
+ RATE_LIMITED: 'RATE_LIMITED',
49
+ /** The provider was asked something and did not answer within the bound. */
50
+ PROVIDER_TIMEOUT: 'PROVIDER_TIMEOUT',
51
+ /** The transport failed — a socket error, not a decision the provider made. */
52
+ NETWORK: 'NETWORK',
53
+ });
54
+ /** Expected `typeof` for each optional detail field. Data, so the check costs one loop and not a branch each. */
55
+ const DETAIL_FIELD_TYPES = Object.freeze({
56
+ remedy: 'string',
57
+ setting: 'string',
58
+ providerCode: 'string',
59
+ retryable: 'boolean',
29
60
  });
61
+ /**
62
+ * Reject a malformed detail before it is attached to a failure.
63
+ * @param detail - candidate detail.
64
+ * @throws TypeError naming the offending field.
65
+ */
66
+ function assertDetail(detail) {
67
+ for (const [field, expected] of Object.entries(DETAIL_FIELD_TYPES)) {
68
+ const value = detail[field];
69
+ if (value === undefined)
70
+ continue;
71
+ if (typeof value !== expected) {
72
+ throw new TypeError(`RealtimeError detail.${field} must be a ${expected}`);
73
+ }
74
+ if (expected === 'string' && value === '') {
75
+ throw new TypeError(`RealtimeError detail.${field} must be non-empty when supplied`);
76
+ }
77
+ }
78
+ }
30
79
  /**
31
80
  * A typed seam failure.
32
81
  *
@@ -36,10 +85,12 @@ export const REALTIME_ERROR_CODES = Object.freeze({
36
85
  export class RealtimeError extends Error {
37
86
  /** Stable machine code. Branch on this, never on `message`. */
38
87
  code;
88
+ /** What a caller can act on, when the failure is one they can act on. */
89
+ detail;
39
90
  /**
40
91
  * @param message - non-empty human-readable summary. Must not contain secret material.
41
92
  * @param code - one of {@link REALTIME_ERROR_CODES}.
42
- * @param options - optional `cause`.
93
+ * @param options - optional `cause`, and an optional structured `detail`.
43
94
  */
44
95
  constructor(message, code, options) {
45
96
  if (typeof message !== 'string' || message.length === 0) {
@@ -48,9 +99,13 @@ export class RealtimeError extends Error {
48
99
  if (typeof code !== 'string' || code.length === 0) {
49
100
  throw new TypeError('RealtimeError code must be a non-empty string');
50
101
  }
102
+ if (options?.detail !== undefined) {
103
+ assertDetail(options.detail);
104
+ }
51
105
  super(message, options);
52
106
  this.name = 'RealtimeError';
53
107
  this.code = code;
108
+ this.detail = options?.detail;
54
109
  }
55
110
  }
56
111
  //# sourceMappingURL=error.js.map
package/lib/error.js.map CHANGED
@@ -1 +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"}
1
+ {"version":3,"file":"error.js","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"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"}
package/lib/types.d.ts CHANGED
@@ -144,20 +144,32 @@ export interface RealtimeSession {
144
144
  unmuteInput(): void;
145
145
  /**
146
146
  * Return a delegated result for the model to **speak aloud**.
147
+ *
148
+ * Resolves on the provider's **acknowledgement** when it answers a delegation. With no
149
+ * `delegationId` there is no acknowledgement to wait for: the provider accepts a session-wide append
150
+ * in silence, because it does not begin applying context at a bare `session.start`. That form
151
+ * therefore resolves on the write and is **best-effort** — it is applied once audio has flowed, and
152
+ * this seam cannot say when. It is not a way to speak unprompted.
147
153
  * @param content - plain text, non-empty and within {@link MAX_APPEND_CHARS}.
148
- * @param delegationId - the delegation this answers, or `undefined` for session-wide context.
154
+ * @param delegationId - the delegation this answers, or `undefined` for best-effort session context.
149
155
  */
150
156
  appendCommentary(content: string, delegationId?: string): Promise<void>;
151
157
  /**
152
158
  * Add context the model may use **without speaking it** — progress, facts, intermediate state.
159
+ *
160
+ * With no `delegationId` this resolves on the write and is best-effort, for the reason recorded on
161
+ * {@link appendCommentary}: the provider does not acknowledge a session-wide append.
153
162
  * @param content - plain text, non-empty and within {@link MAX_APPEND_CHARS}.
154
- * @param delegationId - the delegation this relates to, or `undefined` for session-wide context.
163
+ * @param delegationId - the delegation this relates to, or `undefined` for best-effort session context.
155
164
  */
156
165
  appendThinking(content: string, delegationId?: string): Promise<void>;
157
166
  /**
158
167
  * Steer the live conversation's behaviour (tone, brevity, policy) without speaking anything.
168
+ *
169
+ * With no `delegationId` this resolves on the write and is best-effort, for the reason recorded on
170
+ * {@link appendCommentary}.
159
171
  * @param content - plain text, non-empty and within {@link MAX_APPEND_CHARS}.
160
- * @param delegationId - `undefined` for session-wide steering.
172
+ * @param delegationId - `undefined` for best-effort session-wide steering.
161
173
  */
162
174
  appendInstructions(content: string, delegationId?: string): Promise<void>;
163
175
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/lib/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAsLH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAA"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkMH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-realtime",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Realtime voice capability seam for DeepSeek Harness: a provider registry plus the session adapter base.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
package/src/error.ts CHANGED
@@ -27,11 +27,87 @@ export const REALTIME_ERROR_CODES = Object.freeze({
27
27
  PROVIDER_ERROR: 'PROVIDER_ERROR',
28
28
  /** A recorded session could not be read as a recording. */
29
29
  INVALID_RECORDING: 'INVALID_RECORDING',
30
+
31
+ // ---------------------------------------------------------------------------------------------
32
+ // Failure taxonomy. These exist so a consumer can branch on the *class* of a failure rather than
33
+ // parse prose: a setup state, a rejected credential, an account that cannot pay, and a throttle
34
+ // need four different responses, and collapsing them is what makes onboarding feel unfinished.
35
+ // ---------------------------------------------------------------------------------------------
36
+
37
+ /**
38
+ * The capability is not configured yet — an expected state on a fresh install, not a fault.
39
+ *
40
+ * Distinct from {@link REALTIME_ERROR_CODES.CREDENTIAL_REJECTED}: absent is fixed by supplying a
41
+ * value, unusable by replacing one.
42
+ */
43
+ NOT_CONFIGURED: 'NOT_CONFIGURED',
44
+ /** A credential is present and the provider refused it. Replace it; retrying cannot help. */
45
+ CREDENTIAL_REJECTED: 'CREDENTIAL_REJECTED',
46
+ /** The credential is valid but the account cannot use what was requested (model, region, scope). */
47
+ NOT_ENTITLED: 'NOT_ENTITLED',
48
+ /** The account cannot pay for the request — an exhausted balance or a hard billing limit. */
49
+ INSUFFICIENT_CREDIT: 'INSUFFICIENT_CREDIT',
50
+ /** The provider throttled the request. Repeating later may succeed. */
51
+ RATE_LIMITED: 'RATE_LIMITED',
52
+ /** The provider was asked something and did not answer within the bound. */
53
+ PROVIDER_TIMEOUT: 'PROVIDER_TIMEOUT',
54
+ /** The transport failed — a socket error, not a decision the provider made. */
55
+ NETWORK: 'NETWORK',
30
56
  })
31
57
 
32
58
  /** One of {@link REALTIME_ERROR_CODES}. */
33
59
  export type RealtimeErrorCode = (typeof REALTIME_ERROR_CODES)[keyof typeof REALTIME_ERROR_CODES]
34
60
 
61
+ /**
62
+ * What a caller can act on, carried as structure rather than prose.
63
+ *
64
+ * A failure a user cannot act on is a failure report that wasted their time. Every field here exists
65
+ * so the *class* of a failure survives translation: an agent can relay `remedy` verbatim, a settings
66
+ * surface can highlight `setting`, a retry loop can consult `retryable`, and a human reading a log
67
+ * can see the provider's own `providerCode` instead of a paraphrase of it.
68
+ *
69
+ * Never carries secret material. `providerCode` is the provider's own error code, never a credential.
70
+ */
71
+ export interface RealtimeFailureDetail {
72
+ /**
73
+ * What to do about it, written to be relayed **verbatim** to whoever is trying to use the feature.
74
+ * One sentence, imperative, no jargon.
75
+ */
76
+ remedy?: string
77
+ /** The configuration key that must be supplied, when the failure is a configuration state. */
78
+ setting?: string
79
+ /** Whether repeating the identical call could succeed without any change. */
80
+ retryable?: boolean
81
+ /** The provider's own error code, verbatim. Never a translated or inferred value. */
82
+ providerCode?: string
83
+ }
84
+
85
+ /** Expected `typeof` for each optional detail field. Data, so the check costs one loop and not a branch each. */
86
+ const DETAIL_FIELD_TYPES = Object.freeze({
87
+ remedy: 'string',
88
+ setting: 'string',
89
+ providerCode: 'string',
90
+ retryable: 'boolean',
91
+ })
92
+
93
+ /**
94
+ * Reject a malformed detail before it is attached to a failure.
95
+ * @param detail - candidate detail.
96
+ * @throws TypeError naming the offending field.
97
+ */
98
+ function assertDetail(detail: RealtimeFailureDetail): void {
99
+ for (const [field, expected] of Object.entries(DETAIL_FIELD_TYPES)) {
100
+ const value = (detail as Record<string, unknown>)[field]
101
+ if (value === undefined) continue
102
+ if (typeof value !== expected) {
103
+ throw new TypeError(`RealtimeError detail.${field} must be a ${expected}`)
104
+ }
105
+ if (expected === 'string' && value === '') {
106
+ throw new TypeError(`RealtimeError detail.${field} must be non-empty when supplied`)
107
+ }
108
+ }
109
+ }
110
+
35
111
  /**
36
112
  * A typed seam failure.
37
113
  *
@@ -42,20 +118,27 @@ export class RealtimeError extends Error {
42
118
  /** Stable machine code. Branch on this, never on `message`. */
43
119
  readonly code: RealtimeErrorCode
44
120
 
121
+ /** What a caller can act on, when the failure is one they can act on. */
122
+ readonly detail: RealtimeFailureDetail | undefined
123
+
45
124
  /**
46
125
  * @param message - non-empty human-readable summary. Must not contain secret material.
47
126
  * @param code - one of {@link REALTIME_ERROR_CODES}.
48
- * @param options - optional `cause`.
127
+ * @param options - optional `cause`, and an optional structured `detail`.
49
128
  */
50
- constructor(message: string, code: RealtimeErrorCode, options?: ErrorOptions) {
129
+ constructor(message: string, code: RealtimeErrorCode, options?: ErrorOptions & { detail?: RealtimeFailureDetail }) {
51
130
  if (typeof message !== 'string' || message.length === 0) {
52
131
  throw new TypeError('RealtimeError message must be a non-empty string')
53
132
  }
54
133
  if (typeof code !== 'string' || code.length === 0) {
55
134
  throw new TypeError('RealtimeError code must be a non-empty string')
56
135
  }
136
+ if (options?.detail !== undefined) {
137
+ assertDetail(options.detail)
138
+ }
57
139
  super(message, options)
58
140
  this.name = 'RealtimeError'
59
141
  this.code = code
142
+ this.detail = options?.detail
60
143
  }
61
144
  }
package/src/types.ts CHANGED
@@ -159,22 +159,34 @@ export interface RealtimeSession {
159
159
 
160
160
  /**
161
161
  * Return a delegated result for the model to **speak aloud**.
162
+ *
163
+ * Resolves on the provider's **acknowledgement** when it answers a delegation. With no
164
+ * `delegationId` there is no acknowledgement to wait for: the provider accepts a session-wide append
165
+ * in silence, because it does not begin applying context at a bare `session.start`. That form
166
+ * therefore resolves on the write and is **best-effort** — it is applied once audio has flowed, and
167
+ * this seam cannot say when. It is not a way to speak unprompted.
162
168
  * @param content - plain text, non-empty and within {@link MAX_APPEND_CHARS}.
163
- * @param delegationId - the delegation this answers, or `undefined` for session-wide context.
169
+ * @param delegationId - the delegation this answers, or `undefined` for best-effort session context.
164
170
  */
165
171
  appendCommentary(content: string, delegationId?: string): Promise<void>
166
172
 
167
173
  /**
168
174
  * Add context the model may use **without speaking it** — progress, facts, intermediate state.
175
+ *
176
+ * With no `delegationId` this resolves on the write and is best-effort, for the reason recorded on
177
+ * {@link appendCommentary}: the provider does not acknowledge a session-wide append.
169
178
  * @param content - plain text, non-empty and within {@link MAX_APPEND_CHARS}.
170
- * @param delegationId - the delegation this relates to, or `undefined` for session-wide context.
179
+ * @param delegationId - the delegation this relates to, or `undefined` for best-effort session context.
171
180
  */
172
181
  appendThinking(content: string, delegationId?: string): Promise<void>
173
182
 
174
183
  /**
175
184
  * Steer the live conversation's behaviour (tone, brevity, policy) without speaking anything.
185
+ *
186
+ * With no `delegationId` this resolves on the write and is best-effort, for the reason recorded on
187
+ * {@link appendCommentary}.
176
188
  * @param content - plain text, non-empty and within {@link MAX_APPEND_CHARS}.
177
- * @param delegationId - `undefined` for session-wide steering.
189
+ * @param delegationId - `undefined` for best-effort session-wide steering.
178
190
  */
179
191
  appendInstructions(content: string, delegationId?: string): Promise<void>
180
192