@rimelabs/sdk 0.1.0-alpha.21 → 0.1.0-alpha.23
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 +25 -0
- package/README.md +6 -0
- package/dist/realtime/client.js +3 -1
- package/dist/realtime/history.d.ts +4 -0
- package/dist/realtime/history.js +87 -0
- package/dist/realtime/protocol.d.ts +22 -2
- package/dist/realtime/protocol.js +44 -1
- package/dist/realtime/session.d.ts +6 -0
- package/dist/realtime/session.js +81 -6
- package/dist/realtime/transport.d.ts +2 -0
- package/dist/realtime/transport.js +76 -11
- package/dist/realtime/types.d.ts +33 -2
- package/docs/realtime.md +61 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.0-alpha.23](https://github.com/rimelabs/rime-sdk/compare/typescript-v0.1.0-alpha.22...typescript-v0.1.0-alpha.23) (2026-10-27)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* preserve Prism handshake error categories and diagnostics ([#110](https://github.com/rimelabs/rime-sdk/issues/110)) ([75568b2](https://github.com/rimelabs/rime-sdk/commit/75568b278afddd265a0a4f2c26c716ccb6b29514))
|
|
9
|
+
|
|
10
|
+
## [0.1.0-alpha.22](https://github.com/rimelabs/rime-sdk/compare/typescript-v0.1.0-alpha.21...typescript-v0.1.0-alpha.22) (2026-10-24)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### ⚠ BREAKING CHANGES
|
|
14
|
+
|
|
15
|
+
* Rust SessionInfo struct literals must add truncation: None. The optional field reports the policy published by the server.
|
|
16
|
+
* Go Prism service faults now match ErrRealtime instead of ErrStream. Rust service faults now use ErrorKind::Realtime instead of Stream. Keep fault inspection for code, scope, and retry classification.
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* add Prism conversation history operations ([#108](https://github.com/rimelabs/rime-sdk/issues/108)) ([1f33051](https://github.com/rimelabs/rime-sdk/commit/1f330517163cd521bd03321a0728295f84da0316))
|
|
21
|
+
* add Prism history truncation controls ([#109](https://github.com/rimelabs/rime-sdk/issues/109)) ([b8b1aa1](https://github.com/rimelabs/rime-sdk/commit/b8b1aa172e69df650bfeda274bb91329f7e5bc52))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Bug Fixes
|
|
25
|
+
|
|
26
|
+
* distinguish Prism faults and correct SDK release guidance ([#102](https://github.com/rimelabs/rime-sdk/issues/102)) ([b152abb](https://github.com/rimelabs/rime-sdk/commit/b152abb1ac4e5dabb92e747c04a6bc70aa72b60e))
|
|
27
|
+
|
|
3
28
|
## [0.1.0-alpha.21](https://github.com/rimelabs/rime-sdk/compare/typescript-v0.1.0-alpha.20...typescript-v0.1.0-alpha.21) (2026-10-23)
|
|
4
29
|
|
|
5
30
|
|
package/README.md
CHANGED
|
@@ -28,6 +28,12 @@ STT use the SDK and Node.js built-ins.
|
|
|
28
28
|
|
|
29
29
|
## Prism: send recorded speech
|
|
30
30
|
|
|
31
|
+
If Prism refuses the connection with HTTP 429, `realtime.connect()` throws
|
|
32
|
+
`RimeResourceLimitError`. HTTP 503 throws `RimeUnavailableError`. Handshake
|
|
33
|
+
errors include the HTTP status, the server request ID when supplied, and a
|
|
34
|
+
short server explanation when available. Missing or invalid error bodies use
|
|
35
|
+
a default message. The SDK does not retry the connection automatically.
|
|
36
|
+
|
|
31
37
|
With `RIME_API_KEY` set, the SDK connects to
|
|
32
38
|
`wss://prism.api.rime.ai/v1/realtime` and uses the server's default voice.
|
|
33
39
|
Pass `endpoint` or `voice` to select another deployment or voice.
|
package/dist/realtime/client.js
CHANGED
|
@@ -4,7 +4,7 @@ import { cancellation, scope } from "./async.js";
|
|
|
4
4
|
import { RealtimeSession } from "./session.js";
|
|
5
5
|
import { RealtimeConnection } from "./transport.js";
|
|
6
6
|
import { toolSettings } from "./settings.js";
|
|
7
|
-
import { responseMetadata } from "./protocol.js";
|
|
7
|
+
import { responseMetadata, truncationSettings } from "./protocol.js";
|
|
8
8
|
const DEFAULT_ENDPOINT = "wss://prism.api.rime.ai/v1/realtime";
|
|
9
9
|
function endpoint(value) {
|
|
10
10
|
let url;
|
|
@@ -60,6 +60,8 @@ export class Realtime {
|
|
|
60
60
|
turn_detection: { interrupt_response: options.interruptOnSpeech ?? true },
|
|
61
61
|
tools: toolSettings(options.tools ?? []),
|
|
62
62
|
};
|
|
63
|
+
if (options.truncation !== undefined)
|
|
64
|
+
settings.truncation = truncationSettings(options.truncation);
|
|
63
65
|
if (options.turnDetection !== undefined) {
|
|
64
66
|
const detection = options.turnDetection;
|
|
65
67
|
if (!detection ||
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { RimeInputError, RimeStreamError } from "../errors.js";
|
|
2
|
+
function utf8(value) {
|
|
3
|
+
return typeof value === "string" && !/[\uD800-\uDFFF]/u.test(value);
|
|
4
|
+
}
|
|
5
|
+
export function itemSettings(item) {
|
|
6
|
+
if (!item || typeof item !== "object")
|
|
7
|
+
throw new RimeInputError("history item must be an object");
|
|
8
|
+
const value = { type: item.type };
|
|
9
|
+
if (item.id !== undefined) {
|
|
10
|
+
if (!utf8(item.id) || [...item.id].length < 1 || [...item.id].length > 32)
|
|
11
|
+
throw new RimeInputError("item.id must contain 1 to 32 characters");
|
|
12
|
+
value.id = item.id;
|
|
13
|
+
}
|
|
14
|
+
switch (item.type) {
|
|
15
|
+
case "message":
|
|
16
|
+
if (!["user", "assistant", "system"].includes(item.role) ||
|
|
17
|
+
!utf8(item.text) ||
|
|
18
|
+
[...item.text].length > 4000)
|
|
19
|
+
throw new RimeInputError("invalid history message");
|
|
20
|
+
value.role = item.role;
|
|
21
|
+
value.text = item.text;
|
|
22
|
+
break;
|
|
23
|
+
case "function_call":
|
|
24
|
+
value.call_id = item.callId;
|
|
25
|
+
value.name = item.name;
|
|
26
|
+
value.arguments = item.arguments;
|
|
27
|
+
if (!item.callId || !item.name)
|
|
28
|
+
throw new RimeInputError("past calls require callId and name");
|
|
29
|
+
break;
|
|
30
|
+
case "function_call_output":
|
|
31
|
+
value.call_id = item.callId;
|
|
32
|
+
value.output = item.output;
|
|
33
|
+
if (!item.callId)
|
|
34
|
+
throw new RimeInputError("tool results require callId");
|
|
35
|
+
break;
|
|
36
|
+
default:
|
|
37
|
+
throw new RimeInputError("invalid history item type");
|
|
38
|
+
}
|
|
39
|
+
if (!Object.values(value).every(utf8))
|
|
40
|
+
throw new RimeInputError("history fields must be valid UTF-8 strings");
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
export function historyId(value) {
|
|
44
|
+
if (!utf8(value) || !value)
|
|
45
|
+
throw new RimeInputError("history ID must be nonempty valid UTF-8");
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
export function listedItem(value) {
|
|
49
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
50
|
+
throw new RimeStreamError("invalid history item");
|
|
51
|
+
const o = value;
|
|
52
|
+
const field = (name) => {
|
|
53
|
+
if (typeof o[name] !== "string")
|
|
54
|
+
throw new RimeStreamError("invalid history field " + name);
|
|
55
|
+
if ((name === "call_id" || name === "name") && !o[name])
|
|
56
|
+
throw new RimeStreamError("invalid history identity");
|
|
57
|
+
return o[name];
|
|
58
|
+
};
|
|
59
|
+
const id = field("id");
|
|
60
|
+
if (!id)
|
|
61
|
+
throw new RimeStreamError("invalid history item id");
|
|
62
|
+
switch (o.type) {
|
|
63
|
+
case "message": {
|
|
64
|
+
const role = field("role");
|
|
65
|
+
if (role !== "user" && role !== "assistant" && role !== "system")
|
|
66
|
+
throw new RimeStreamError("invalid history role");
|
|
67
|
+
return Object.freeze({ type: o.type, id, role, text: field("text") });
|
|
68
|
+
}
|
|
69
|
+
case "function_call":
|
|
70
|
+
return Object.freeze({
|
|
71
|
+
type: o.type,
|
|
72
|
+
id,
|
|
73
|
+
callId: field("call_id"),
|
|
74
|
+
name: field("name"),
|
|
75
|
+
arguments: field("arguments"),
|
|
76
|
+
});
|
|
77
|
+
case "function_call_output":
|
|
78
|
+
return Object.freeze({
|
|
79
|
+
type: o.type,
|
|
80
|
+
id,
|
|
81
|
+
callId: field("call_id"),
|
|
82
|
+
output: field("output"),
|
|
83
|
+
});
|
|
84
|
+
default:
|
|
85
|
+
throw new RimeStreamError("invalid history item type");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import * as t from "./types.js";
|
|
2
2
|
export interface SessionSettings {
|
|
3
|
+
truncation?: "auto" | "disabled" | {
|
|
4
|
+
type: "retention_ratio";
|
|
5
|
+
retention_ratio: number;
|
|
6
|
+
};
|
|
3
7
|
modalities: ("text" | "audio")[];
|
|
4
8
|
input_audio_format: "pcm16";
|
|
5
9
|
turn_detection: {
|
|
@@ -23,6 +27,7 @@ export interface SessionSettings {
|
|
|
23
27
|
/** A copy of the client's labels for every reply: keys to strings. */
|
|
24
28
|
export declare function responseMetadata(value: unknown): t.Labels;
|
|
25
29
|
export interface SessionPatch {
|
|
30
|
+
truncation?: SessionSettings["truncation"];
|
|
26
31
|
voice?: string;
|
|
27
32
|
instructions?: string;
|
|
28
33
|
tools?: SessionSettings["tools"];
|
|
@@ -31,6 +36,7 @@ export interface SessionPatch {
|
|
|
31
36
|
};
|
|
32
37
|
}
|
|
33
38
|
export interface SessionView {
|
|
39
|
+
truncation: t.Truncation | null;
|
|
34
40
|
settingsRevision: number | null;
|
|
35
41
|
id: string;
|
|
36
42
|
requestId: string | null;
|
|
@@ -55,9 +61,18 @@ export interface ItemCreated {
|
|
|
55
61
|
itemId: string;
|
|
56
62
|
}
|
|
57
63
|
export interface ToolResultCreated {
|
|
64
|
+
itemId: string | null;
|
|
58
65
|
kind: "tool.result.created";
|
|
59
66
|
callId: string;
|
|
60
67
|
}
|
|
68
|
+
export interface HistoryListed {
|
|
69
|
+
kind: "history.listed";
|
|
70
|
+
items: readonly t.HistoryItem[];
|
|
71
|
+
}
|
|
72
|
+
export interface ItemDeleted {
|
|
73
|
+
kind: "item.deleted";
|
|
74
|
+
itemId: string;
|
|
75
|
+
}
|
|
61
76
|
export interface AudioCleared {
|
|
62
77
|
kind: "audio.cleared";
|
|
63
78
|
}
|
|
@@ -69,14 +84,14 @@ export interface ItemTruncated {
|
|
|
69
84
|
export interface ResponseEnded extends t.ResponseEnded {
|
|
70
85
|
drainToken: string | null;
|
|
71
86
|
}
|
|
72
|
-
export type Acknowledgment = SessionUpdated | ItemCreated | ToolResultCreated | AudioCleared | ItemTruncated | t.ResponseStarted | ResponseEnded;
|
|
87
|
+
export type Acknowledgment = SessionUpdated | HistoryListed | ItemDeleted | ItemCreated | ToolResultCreated | AudioCleared | ItemTruncated | t.ResponseStarted | ResponseEnded;
|
|
73
88
|
export type ServerPayload = SessionCreated | Acknowledgment | t.InputEvent<t.NormalizedRealtimeFault> | t.MessageStarted | t.TextDelta | t.TextDone | t.AudioDelta | t.AudioDone | t.ToolCall | t.FaultEvent<t.NormalizedRealtimeFault>;
|
|
74
89
|
export interface ServerEvent {
|
|
75
90
|
eventId: string;
|
|
76
91
|
requestId: string | null;
|
|
77
92
|
payload: ServerPayload;
|
|
78
93
|
}
|
|
79
|
-
export type RequestKind = "session.update" | "conversation.item.create" | "response.create" | "response.cancel" | "input_audio_buffer.clear" | "conversation.item.truncate";
|
|
94
|
+
export type RequestKind = "session.update" | "conversation.item.create" | "prism.conversation.get" | "conversation.item.delete" | "response.create" | "response.cancel" | "input_audio_buffer.clear" | "conversation.item.truncate";
|
|
80
95
|
/** A request carries its result type and checks it before exposing the result. */
|
|
81
96
|
export declare class Request<T extends Acknowledgment> {
|
|
82
97
|
readonly kind: RequestKind;
|
|
@@ -87,6 +102,10 @@ export declare class Request<T extends Acknowledgment> {
|
|
|
87
102
|
check(event: Acknowledgment): void;
|
|
88
103
|
}
|
|
89
104
|
export declare const requests: {
|
|
105
|
+
historyItem: Request<ItemCreated>;
|
|
106
|
+
historyResult: Request<ToolResultCreated>;
|
|
107
|
+
history: Request<HistoryListed>;
|
|
108
|
+
delete: Request<ItemDeleted>;
|
|
90
109
|
update: Request<SessionUpdated>;
|
|
91
110
|
item: Request<ItemCreated>;
|
|
92
111
|
toolResult: Request<ToolResultCreated>;
|
|
@@ -96,4 +115,5 @@ export declare const requests: {
|
|
|
96
115
|
clear: Request<AudioCleared>;
|
|
97
116
|
truncate: Request<ItemTruncated>;
|
|
98
117
|
};
|
|
118
|
+
export declare function truncationSettings(value: t.Truncation): NonNullable<SessionSettings["truncation"]>;
|
|
99
119
|
export declare function decode(raw: string, sessionId: string | null): ServerEvent | null;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/** Decode only the protocol fields the SDK uses. Unknown events and fields stay open. */
|
|
3
3
|
import { RimeInputError, RimeStreamError } from "../errors.js";
|
|
4
4
|
import { normalizeFault } from "./faults.js";
|
|
5
|
+
import { listedItem } from "./history.js";
|
|
5
6
|
/** A copy of the client's labels for every reply: keys to strings. */
|
|
6
7
|
export function responseMetadata(value) {
|
|
7
8
|
if (!value ||
|
|
@@ -31,6 +32,10 @@ export class Request {
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
export const requests = {
|
|
35
|
+
historyItem: new Request("conversation.item.create", (e) => e.kind === "item.created"),
|
|
36
|
+
historyResult: new Request("conversation.item.create", (e) => e.kind === "tool.result.created"),
|
|
37
|
+
history: new Request("prism.conversation.get", (e) => e.kind === "history.listed"),
|
|
38
|
+
delete: new Request("conversation.item.delete", (e) => e.kind === "item.deleted"),
|
|
34
39
|
update: new Request("session.update", (e) => e.kind === "session.updated"),
|
|
35
40
|
item: new Request("conversation.item.create", (e) => e.kind === "item.created"),
|
|
36
41
|
toolResult: new Request("conversation.item.create", (e) => e.kind === "tool.result.created"),
|
|
@@ -79,6 +84,29 @@ function duration(value, field) {
|
|
|
79
84
|
return invalid(field);
|
|
80
85
|
return value;
|
|
81
86
|
}
|
|
87
|
+
export function truncationSettings(value) {
|
|
88
|
+
if (value === "auto" || value === "disabled")
|
|
89
|
+
return value;
|
|
90
|
+
if (value &&
|
|
91
|
+
typeof value === "object" &&
|
|
92
|
+
!Array.isArray(value) &&
|
|
93
|
+
Object.keys(value).every((key) => key === "retentionRatio") &&
|
|
94
|
+
typeof value.retentionRatio === "number" &&
|
|
95
|
+
value.retentionRatio > 0 &&
|
|
96
|
+
value.retentionRatio <= 1)
|
|
97
|
+
return { type: "retention_ratio", retention_ratio: value.retentionRatio };
|
|
98
|
+
throw new RimeInputError("truncation must be auto, disabled, or a retentionRatio above zero and at most one");
|
|
99
|
+
}
|
|
100
|
+
function truncation(value) {
|
|
101
|
+
if (value === "auto" || value === "disabled")
|
|
102
|
+
return value;
|
|
103
|
+
const o = object(value, "session.truncation");
|
|
104
|
+
if (o.type !== "retention_ratio" ||
|
|
105
|
+
typeof o.retention_ratio !== "number" ||
|
|
106
|
+
!(o.retention_ratio > 0 && o.retention_ratio <= 1))
|
|
107
|
+
return invalid("session.truncation");
|
|
108
|
+
return Object.freeze({ retentionRatio: o.retention_ratio });
|
|
109
|
+
}
|
|
82
110
|
function session(value) {
|
|
83
111
|
const view = object(value, "session");
|
|
84
112
|
const detection = view.turn_detection === undefined
|
|
@@ -116,6 +144,7 @@ function session(value) {
|
|
|
116
144
|
invalid("session.prism_effective_interrupt_response");
|
|
117
145
|
return {
|
|
118
146
|
settingsRevision: revision(view),
|
|
147
|
+
truncation: view.truncation === undefined ? null : truncation(view.truncation),
|
|
119
148
|
id: id(view.id, "session.id"),
|
|
120
149
|
requestId: optionalString(view.request_id, "session.request_id"),
|
|
121
150
|
responseMetadata: view.prism_response_metadata === undefined
|
|
@@ -221,15 +250,29 @@ function decodeValue(value, sessionId) {
|
|
|
221
250
|
if (item.type === "function_call_output")
|
|
222
251
|
payload = {
|
|
223
252
|
kind: "tool.result.created",
|
|
253
|
+
itemId: optionalString(item.id, "item.id"),
|
|
224
254
|
callId: id(item.call_id, "item.call_id"),
|
|
225
255
|
};
|
|
226
256
|
else {
|
|
227
|
-
if (item.type !== undefined &&
|
|
257
|
+
if (item.type !== undefined &&
|
|
258
|
+
item.type !== "message" &&
|
|
259
|
+
item.type !== "function_call")
|
|
228
260
|
invalid("item.type");
|
|
229
261
|
payload = { kind: "item.created", itemId: id(item.id, "item.id") };
|
|
230
262
|
}
|
|
231
263
|
break;
|
|
232
264
|
}
|
|
265
|
+
case "prism.conversation":
|
|
266
|
+
if (!Array.isArray(event.items))
|
|
267
|
+
return invalid("items");
|
|
268
|
+
payload = {
|
|
269
|
+
kind: "history.listed",
|
|
270
|
+
items: Object.freeze(event.items.map(listedItem)),
|
|
271
|
+
};
|
|
272
|
+
break;
|
|
273
|
+
case "conversation.item.deleted":
|
|
274
|
+
payload = { kind: "item.deleted", itemId: id(event.item_id, "item_id") };
|
|
275
|
+
break;
|
|
233
276
|
case "input_audio_buffer.cleared":
|
|
234
277
|
payload = { kind: "audio.cleared" };
|
|
235
278
|
break;
|
|
@@ -58,6 +58,12 @@ export declare class RealtimeSession {
|
|
|
58
58
|
update(options?: t.RealtimeUpdateOptions): Promise<t.SessionInfo>;
|
|
59
59
|
sendText(value: string, options?: t.RealtimeOperationOptions): Promise<t.ResponseRef>;
|
|
60
60
|
requestReply(options?: t.ReplyOptions): Promise<t.ResponseRef>;
|
|
61
|
+
/** Read the items the next reply would see, in server order. */
|
|
62
|
+
getHistory(options?: t.RealtimeOperationOptions): Promise<readonly t.HistoryItem[]>;
|
|
63
|
+
/** Hide an item from future replies. A call and its result are hidden together. */
|
|
64
|
+
deleteItem(itemId: string, options?: t.RealtimeOperationOptions): Promise<void>;
|
|
65
|
+
/** Add history without a reply. The server owns placement and identity checks. */
|
|
66
|
+
createItem(item: t.HistoryItem, options?: t.HistoryCreateOptions): Promise<t.ItemRef>;
|
|
61
67
|
addMessage(role: "user" | "assistant", value: string, options?: t.RealtimeOperationOptions): Promise<t.ItemRef>;
|
|
62
68
|
submitToolResult(call: t.ToolCallRef, output: string, options?: t.RealtimeOperationOptions): Promise<void>;
|
|
63
69
|
continueReply(parent: t.ResponseRef, options?: t.RealtimeOperationOptions): Promise<t.ResponseRef>;
|
package/dist/realtime/session.js
CHANGED
|
@@ -10,6 +10,7 @@ import { formatOf } from "./audio.js";
|
|
|
10
10
|
import { InputConverter } from "../pcm.js";
|
|
11
11
|
import * as t from "./types.js";
|
|
12
12
|
import * as p from "./protocol.js";
|
|
13
|
+
import { historyId, itemSettings } from "./history.js";
|
|
13
14
|
import { InterruptionPolicies, toolSettings } from "./settings.js";
|
|
14
15
|
const NOT_READY = new Set([
|
|
15
16
|
"typed_turn_busy",
|
|
@@ -268,6 +269,15 @@ export class RealtimeSession {
|
|
|
268
269
|
request,
|
|
269
270
|
result,
|
|
270
271
|
target,
|
|
272
|
+
requiresItemId: request === p.requests.historyItem ||
|
|
273
|
+
request === p.requests.historyResult,
|
|
274
|
+
expectedItemId: body.item?.id,
|
|
275
|
+
toolOutputDigest: request.kind === "conversation.item.create" &&
|
|
276
|
+
body.item?.type === "function_call_output"
|
|
277
|
+
? createHash("sha256")
|
|
278
|
+
.update(body.item.output)
|
|
279
|
+
.digest("hex")
|
|
280
|
+
: undefined,
|
|
271
281
|
interruptOnSpeech: request.kind === "session.update"
|
|
272
282
|
? body.session.turn_detection?.interrupt_response
|
|
273
283
|
: undefined,
|
|
@@ -367,6 +377,8 @@ export class RealtimeSession {
|
|
|
367
377
|
* Cancellation after submission does not undo an accepted update. */
|
|
368
378
|
async update(options = {}) {
|
|
369
379
|
const settings = {};
|
|
380
|
+
if (options.truncation !== undefined)
|
|
381
|
+
settings.truncation = p.truncationSettings(options.truncation);
|
|
370
382
|
for (const name of ["instructions", "voice"]) {
|
|
371
383
|
const value = options[name];
|
|
372
384
|
if (value !== undefined) {
|
|
@@ -409,18 +421,52 @@ export class RealtimeSession {
|
|
|
409
421
|
}
|
|
410
422
|
return this.operation(options, (signal) => this.create(body, signal));
|
|
411
423
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
424
|
+
/** Read the items the next reply would see, in server order. */
|
|
425
|
+
async getHistory(options = {}) {
|
|
426
|
+
return this.operation(options, async (signal) => (await this.request(p.requests.history, {}, signal)).items);
|
|
427
|
+
}
|
|
428
|
+
/** Hide an item from future replies. A call and its result are hidden together. */
|
|
429
|
+
async deleteItem(itemId, options = {}) {
|
|
430
|
+
if (typeof itemId !== "string" || !itemId)
|
|
431
|
+
throw new RimeInputError("itemId must be nonempty");
|
|
432
|
+
historyId(itemId);
|
|
433
|
+
return this.operation(options, async (signal) => {
|
|
434
|
+
await this.request(p.requests.delete, { item_id: itemId }, signal, itemId);
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
/** Add history without a reply. The server owns placement and identity checks. */
|
|
438
|
+
async createItem(item, options = {}) {
|
|
439
|
+
const value = itemSettings(item);
|
|
440
|
+
const body = { item: value };
|
|
441
|
+
if (options.previousItemId !== undefined) {
|
|
442
|
+
if (typeof options.previousItemId !== "string" ||
|
|
443
|
+
!options.previousItemId ||
|
|
444
|
+
item.type === "function_call_output")
|
|
445
|
+
throw new RimeInputError("invalid previousItemId for history item");
|
|
446
|
+
body.previous_item_id = historyId(options.previousItemId);
|
|
447
|
+
}
|
|
416
448
|
return this.operation(options, async (signal) => {
|
|
417
|
-
const event =
|
|
449
|
+
const event = value.type === "function_call_output"
|
|
450
|
+
? await this.request(p.requests.historyResult, body, signal, value.call_id)
|
|
451
|
+
: await this.request(p.requests.historyItem, body, signal);
|
|
452
|
+
if (!event.itemId ||
|
|
453
|
+
(value.id !== undefined && event.itemId !== value.id)) {
|
|
454
|
+
const error = new RimeStreamError("history acknowledgment names an invalid item");
|
|
455
|
+
this.fail(error);
|
|
456
|
+
throw error;
|
|
457
|
+
}
|
|
418
458
|
return Object.freeze({
|
|
419
459
|
sessionId: this.info.sessionId,
|
|
420
460
|
itemId: event.itemId,
|
|
421
461
|
});
|
|
422
462
|
});
|
|
423
463
|
}
|
|
464
|
+
async addMessage(role, value, options = {}) {
|
|
465
|
+
if (role !== "user" && role !== "assistant")
|
|
466
|
+
throw new RimeInputError("History role must be user or assistant");
|
|
467
|
+
text(value);
|
|
468
|
+
return this.createItem({ type: "message", role, text: value }, { signal: options.signal });
|
|
469
|
+
}
|
|
424
470
|
async submitToolResult(call, output, options = {}) {
|
|
425
471
|
const ref = { ...call };
|
|
426
472
|
this.response(ref);
|
|
@@ -609,6 +655,8 @@ export class RealtimeSession {
|
|
|
609
655
|
const { payload, requestId: echo } = event;
|
|
610
656
|
// Check echoed acknowledgments before changing response or item state.
|
|
611
657
|
if (payload.kind === "session.updated" ||
|
|
658
|
+
payload.kind === "history.listed" ||
|
|
659
|
+
payload.kind === "item.deleted" ||
|
|
612
660
|
payload.kind === "item.created" ||
|
|
613
661
|
payload.kind === "tool.result.created" ||
|
|
614
662
|
payload.kind === "response.started") {
|
|
@@ -616,6 +664,13 @@ export class RealtimeSession {
|
|
|
616
664
|
if (pending &&
|
|
617
665
|
!(payload.kind === "item.created" && pending.request.allowsItemCreated))
|
|
618
666
|
pending.request.check(payload);
|
|
667
|
+
if (pending?.requiresItemId &&
|
|
668
|
+
((payload.kind !== "item.created" &&
|
|
669
|
+
payload.kind !== "tool.result.created") ||
|
|
670
|
+
!payload.itemId ||
|
|
671
|
+
(pending.expectedItemId !== undefined &&
|
|
672
|
+
pending.expectedItemId !== payload.itemId)))
|
|
673
|
+
throw new RimeStreamError("history acknowledgment names an invalid item");
|
|
619
674
|
if (pending &&
|
|
620
675
|
payload.kind === "tool.result.created" &&
|
|
621
676
|
pending.target !== payload.callId)
|
|
@@ -645,6 +700,7 @@ export class RealtimeSession {
|
|
|
645
700
|
toolContinuationTimeoutS: view.toolContinuationTimeoutS,
|
|
646
701
|
turnDetection: view.turnDetection,
|
|
647
702
|
settingsRevision: view.settingsRevision,
|
|
703
|
+
truncation: view.truncation,
|
|
648
704
|
requestId: view.requestId,
|
|
649
705
|
responseMetadata: view.responseMetadata,
|
|
650
706
|
});
|
|
@@ -652,9 +708,28 @@ export class RealtimeSession {
|
|
|
652
708
|
this.settle(echo, { ...payload, info: this.sessionInfo });
|
|
653
709
|
return;
|
|
654
710
|
}
|
|
655
|
-
case "
|
|
711
|
+
case "history.listed":
|
|
712
|
+
this.settle(echo, payload);
|
|
713
|
+
return;
|
|
714
|
+
case "item.deleted": {
|
|
715
|
+
const pending = this.pending.get(echo ?? "");
|
|
716
|
+
if (pending && pending.target !== payload.itemId)
|
|
717
|
+
throw new RimeStreamError("deletion acknowledgment names another item");
|
|
656
718
|
this.settle(echo, payload);
|
|
657
719
|
return;
|
|
720
|
+
}
|
|
721
|
+
case "tool.result.created": {
|
|
722
|
+
const pending = this.pending.get(echo ?? "");
|
|
723
|
+
if (pending?.toolOutputDigest !== undefined)
|
|
724
|
+
for (const response of this.responses.values()) {
|
|
725
|
+
if (response.calls.get(payload.callId) === null) {
|
|
726
|
+
response.calls.set(payload.callId, pending.toolOutputDigest);
|
|
727
|
+
response.changed.set();
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
this.settle(echo, payload);
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
658
733
|
case "item.created": {
|
|
659
734
|
const pending = this.pending.get(echo ?? "");
|
|
660
735
|
if (pending?.request.allowsItemCreated)
|
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
import WebSocket from "ws";
|
|
2
|
-
import { RimeAuthenticationError, RimeStreamError, RimeUnavailableError, } from "../errors.js";
|
|
2
|
+
import { RimeAuthenticationError, RimeResourceLimitError, RimeStreamError, RimeUnavailableError, } from "../errors.js";
|
|
3
3
|
import { Deferred } from "./async.js";
|
|
4
|
+
function handshakeError(status, body, requestId) {
|
|
5
|
+
let errorType = RimeUnavailableError;
|
|
6
|
+
let reason = "Realtime endpoint rejected the connection";
|
|
7
|
+
if ([401, 403].includes(status)) {
|
|
8
|
+
errorType = RimeAuthenticationError;
|
|
9
|
+
reason = "Realtime endpoint rejected credentials";
|
|
10
|
+
}
|
|
11
|
+
else if (status === 429) {
|
|
12
|
+
errorType = RimeResourceLimitError;
|
|
13
|
+
reason = "Realtime connection or rate limit reached";
|
|
14
|
+
}
|
|
15
|
+
else if (status === 503) {
|
|
16
|
+
reason = "Realtime endpoint is unavailable";
|
|
17
|
+
}
|
|
18
|
+
let message = `${reason} (HTTP ${status})`;
|
|
19
|
+
if (body.length > 0 && body.length < 1024) {
|
|
20
|
+
try {
|
|
21
|
+
let detail = new TextDecoder("utf-8", { fatal: true })
|
|
22
|
+
.decode(body)
|
|
23
|
+
.trim();
|
|
24
|
+
if (typeof detail === "string" && /^[{[]/.test(detail))
|
|
25
|
+
detail = JSON.parse(detail)?.error?.message;
|
|
26
|
+
if (typeof detail === "string" &&
|
|
27
|
+
!detail.trimStart().startsWith("<") &&
|
|
28
|
+
!/[\x00-\x08\x0b\x0c\x0e-\x1f]/.test(detail)) {
|
|
29
|
+
detail = detail.trim().replace(/\s+/gu, " ");
|
|
30
|
+
if (detail)
|
|
31
|
+
message += `: ${detail}`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Invalid response bodies do not change the HTTP error category.
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return new errorType(message, requestId);
|
|
39
|
+
}
|
|
4
40
|
/** Private transport. The public declarations do not require ws types. */
|
|
5
41
|
export class RealtimeConnection {
|
|
6
42
|
socket;
|
|
@@ -8,6 +44,8 @@ export class RealtimeConnection {
|
|
|
8
44
|
ended = new Deferred();
|
|
9
45
|
closing = null;
|
|
10
46
|
connected = false;
|
|
47
|
+
failed = false;
|
|
48
|
+
refusal;
|
|
11
49
|
ready = this.opened.promise;
|
|
12
50
|
onMessage = () => { };
|
|
13
51
|
onFailure = () => { };
|
|
@@ -26,17 +64,39 @@ export class RealtimeConnection {
|
|
|
26
64
|
this.opened.resolve();
|
|
27
65
|
});
|
|
28
66
|
this.socket.on("unexpected-response", (request, response) => {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
67
|
+
const chunks = [];
|
|
68
|
+
let size = 0;
|
|
69
|
+
let finished = false;
|
|
70
|
+
const finish = (complete = false) => {
|
|
71
|
+
if (finished)
|
|
72
|
+
return;
|
|
73
|
+
finished = true;
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
this.refusal = undefined;
|
|
76
|
+
const id = response.headers["x-request-id"];
|
|
77
|
+
this.failure(handshakeError(response.statusCode ?? 0, complete ? Buffer.concat(chunks) : Buffer.alloc(0), typeof id === "string" ? id : null));
|
|
78
|
+
response.destroy();
|
|
79
|
+
request.destroy();
|
|
80
|
+
this.socket.terminate();
|
|
81
|
+
};
|
|
82
|
+
const timer = setTimeout(finish, 1000);
|
|
83
|
+
this.refusal = finish;
|
|
84
|
+
response.on("data", (chunk) => {
|
|
85
|
+
size += chunk.length;
|
|
86
|
+
if (size >= 1024)
|
|
87
|
+
finish();
|
|
88
|
+
else
|
|
89
|
+
chunks.push(chunk);
|
|
90
|
+
});
|
|
91
|
+
response.on("end", () => finish(true));
|
|
92
|
+
response.on("error", () => finish());
|
|
93
|
+
response.on("aborted", () => finish());
|
|
36
94
|
});
|
|
37
|
-
this.socket.on("error", () => this.
|
|
38
|
-
?
|
|
39
|
-
:
|
|
95
|
+
this.socket.on("error", () => this.refusal
|
|
96
|
+
? this.refusal()
|
|
97
|
+
: this.failure(!this.connected
|
|
98
|
+
? new RimeUnavailableError("Realtime handshake failed")
|
|
99
|
+
: new RimeStreamError("Invalid event or lost realtime connection")));
|
|
40
100
|
this.socket.on("message", (data, binary) => {
|
|
41
101
|
if (binary) {
|
|
42
102
|
this.failure(new RimeStreamError("Expected a JSON text event"));
|
|
@@ -45,11 +105,15 @@ export class RealtimeConnection {
|
|
|
45
105
|
this.onMessage(data.toString());
|
|
46
106
|
});
|
|
47
107
|
this.socket.on("close", () => {
|
|
108
|
+
this.refusal?.();
|
|
48
109
|
this.ended.resolve();
|
|
49
110
|
this.failure(new RimeStreamError("Realtime connection closed; reconnect is not automatic"));
|
|
50
111
|
});
|
|
51
112
|
}
|
|
52
113
|
failure(error) {
|
|
114
|
+
if (this.failed)
|
|
115
|
+
return;
|
|
116
|
+
this.failed = true;
|
|
53
117
|
this.opened.reject(error);
|
|
54
118
|
this.onFailure(error);
|
|
55
119
|
}
|
|
@@ -66,6 +130,7 @@ export class RealtimeConnection {
|
|
|
66
130
|
close() {
|
|
67
131
|
if (!this.closing) {
|
|
68
132
|
this.closing = (async () => {
|
|
133
|
+
this.refusal?.();
|
|
69
134
|
if (this.socket.readyState === WebSocket.CLOSED)
|
|
70
135
|
return;
|
|
71
136
|
const timer = setTimeout(() => this.socket.terminate(), 2000);
|
package/dist/realtime/types.d.ts
CHANGED
|
@@ -40,6 +40,10 @@ export interface ToolDefinition {
|
|
|
40
40
|
readonly description?: string;
|
|
41
41
|
readonly prismFiller?: PrismFiller;
|
|
42
42
|
}
|
|
43
|
+
/** History policy when the context fills. The server uses auto when omitted. */
|
|
44
|
+
export type Truncation = "auto" | "disabled" | {
|
|
45
|
+
readonly retentionRatio: number;
|
|
46
|
+
};
|
|
43
47
|
export type TurnEagerness = "low" | "medium" | "high" | "auto";
|
|
44
48
|
/** Omitted fields use the server's turn timing defaults. */
|
|
45
49
|
export interface TurnDetection {
|
|
@@ -54,6 +58,7 @@ export interface TurnDetectionInfo {
|
|
|
54
58
|
readonly endOfTurnTimeoutMs: number | null;
|
|
55
59
|
}
|
|
56
60
|
export interface RealtimeConnectOptions extends RealtimeOperationOptions {
|
|
61
|
+
readonly truncation?: Truncation;
|
|
57
62
|
/** Defaults to wss://prism.api.rime.ai/v1/realtime. */
|
|
58
63
|
readonly endpoint?: string;
|
|
59
64
|
readonly model?: "prism";
|
|
@@ -64,8 +69,9 @@ export interface RealtimeConnectOptions extends RealtimeOperationOptions {
|
|
|
64
69
|
readonly turnDetection?: TurnDetection;
|
|
65
70
|
readonly timeouts?: RealtimeTimeouts;
|
|
66
71
|
/** Sent as the upgrade's `x-request-id`. It must be nonblank printable ASCII, or
|
|
67
|
-
* `connect` throws `RimeInputError`. Prism
|
|
68
|
-
*
|
|
72
|
+
* `connect` throws `RimeInputError`. Prism replaces IDs longer than 128 characters.
|
|
73
|
+
* A gateway can replace shorter IDs too. `info.requestId` reports the ID Prism
|
|
74
|
+
* actually used.
|
|
69
75
|
* Omitted or `null` sends none. */
|
|
70
76
|
readonly requestId?: string | null;
|
|
71
77
|
/** Labels every reply's `response.started` carries in `metadata`, such as
|
|
@@ -75,6 +81,7 @@ export interface RealtimeConnectOptions extends RealtimeOperationOptions {
|
|
|
75
81
|
}
|
|
76
82
|
/** Omitted fields keep their values. Empty instructions or tools clear them. */
|
|
77
83
|
export interface RealtimeUpdateOptions extends RealtimeOperationOptions {
|
|
84
|
+
readonly truncation?: Truncation;
|
|
78
85
|
readonly instructions?: string;
|
|
79
86
|
readonly tools?: readonly ToolDefinition[];
|
|
80
87
|
readonly voice?: string;
|
|
@@ -88,6 +95,28 @@ export interface AudioChunk {
|
|
|
88
95
|
readonly data: Uint8Array;
|
|
89
96
|
readonly format?: PCMFormat;
|
|
90
97
|
}
|
|
98
|
+
/** An item without audio. IDs are optional on input and present in history results. */
|
|
99
|
+
export type HistoryItem = {
|
|
100
|
+
readonly type: "message";
|
|
101
|
+
readonly id?: string;
|
|
102
|
+
readonly role: "user" | "assistant" | "system";
|
|
103
|
+
readonly text: string;
|
|
104
|
+
} | {
|
|
105
|
+
readonly type: "function_call";
|
|
106
|
+
readonly id?: string;
|
|
107
|
+
readonly callId: string;
|
|
108
|
+
readonly name: string;
|
|
109
|
+
readonly arguments: string;
|
|
110
|
+
} | {
|
|
111
|
+
readonly type: "function_call_output";
|
|
112
|
+
readonly id?: string;
|
|
113
|
+
readonly callId: string;
|
|
114
|
+
readonly output: string;
|
|
115
|
+
};
|
|
116
|
+
export interface HistoryCreateOptions extends RealtimeOperationOptions {
|
|
117
|
+
/** Omit to append, root to insert first, or an item ID to follow its whole turn. */
|
|
118
|
+
readonly previousItemId?: string;
|
|
119
|
+
}
|
|
91
120
|
export interface ResponseRef {
|
|
92
121
|
readonly sessionId: string;
|
|
93
122
|
readonly responseId: string;
|
|
@@ -106,6 +135,8 @@ export interface OutputRef {
|
|
|
106
135
|
readonly contentIndex: number;
|
|
107
136
|
}
|
|
108
137
|
export interface SessionInfo {
|
|
138
|
+
/** Null when an older server does not publish its history policy. */
|
|
139
|
+
readonly truncation?: Truncation | null;
|
|
109
140
|
/** Settings revision published by the server; null when unavailable.
|
|
110
141
|
* Optional on caller-created values for compatibility. */
|
|
111
142
|
readonly settingsRevision?: number | null;
|
package/docs/realtime.md
CHANGED
|
@@ -169,7 +169,7 @@ Use `session.setResponseMetadata` to replace reply labels.
|
|
|
169
169
|
| `turnDetection` | No | Omitted settings use the server's turn timing defaults. |
|
|
170
170
|
| `timeouts` | No | `{ connectS: 10, readyS: 30, requestS: 10 }`. Each field is optional. Units are seconds. |
|
|
171
171
|
| `model` | No | `"prism"`, the only accepted value. |
|
|
172
|
-
| `requestId` | No |
|
|
172
|
+
| `requestId` | No | Sent as `x-request-id`. It must be nonblank printable ASCII, or the SDK throws `RimeInputError`. Prism replaces IDs longer than 128 characters. A gateway can replace shorter IDs too. Read `session.info.requestId` for the ID Prism actually used. |
|
|
173
173
|
| `responseMetadata` | No | Labels every reply carries in `response.started.metadata`, such as `{ step: "3" }`: up to 16 strings, keys up to 64 characters and values up to 512, no key starting with `prism_`. Prism refuses labels that break these limits, or keys starting with `prism_`, with `RimeRealtimeError` code `invalid_session`. |
|
|
174
174
|
| `signal` | No | An `AbortSignal` that cancels connection setup. It does not cancel an established session. |
|
|
175
175
|
|
|
@@ -276,6 +276,9 @@ Every method returns a promise. Arguments without `?` are required.
|
|
|
276
276
|
| `sendAudio(chunk, options?)` | Sends microphone audio with transport backpressure. |
|
|
277
277
|
| `requestReply(options?)` | Requests a reply without new user input; returns `ResponseRef`. Options can include `instruction` and `toolCall`. |
|
|
278
278
|
| `addMessage(role, text, options?)` | Adds user or assistant history without a reply; returns `ItemRef` after acknowledgment. |
|
|
279
|
+
| `getHistory(options?)` | Returns the ordered `readonly HistoryItem[]` that the next reply would see. |
|
|
280
|
+
| `createItem(item, options?)` | Adds a message, past tool call, or tool result without a reply; returns `ItemRef` after acknowledgment. Options can include `previousItemId`. |
|
|
281
|
+
| `deleteItem(itemId, options?)` | Hides an item from later replies and waits for acknowledgment. |
|
|
279
282
|
| `submitToolResult(call, output, options?)` | Records a string result and waits for acknowledgment. |
|
|
280
283
|
| `continueReply(parent, options?)` | Waits for parent completion and all tool result acknowledgments, then returns a new `ResponseRef`. |
|
|
281
284
|
| `cancel(response, options?)` | Requests generation cancellation and waits for a terminal response or refusal. |
|
|
@@ -285,12 +288,17 @@ Every method returns a promise. Arguments without `?` are required.
|
|
|
285
288
|
| `close()` | Closes the session and releases its resources. |
|
|
286
289
|
|
|
287
290
|
Operation options accept `signal?: AbortSignal`. `requestReply` accepts its
|
|
288
|
-
instruction, tool call, and signal in one object.
|
|
289
|
-
`sendText`, `requestReply`,
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
291
|
+
instruction, tool call, and signal in one object. `update` returns
|
|
292
|
+
`Promise<SessionInfo>`. `sendText`, `requestReply`, and `continueReply` return
|
|
293
|
+
`Promise<ResponseRef>`. `addMessage` and `createItem` return `Promise<ItemRef>`.
|
|
294
|
+
`getHistory` returns `Promise<readonly HistoryItem[]>`. The other methods return
|
|
295
|
+
`Promise<void>`.
|
|
296
|
+
|
|
297
|
+
`sendText` and `addMessage` require nonblank text of at most 4,000 Unicode
|
|
298
|
+
characters. `addMessage` accepts `"user"` and `"assistant"` roles. `createItem`
|
|
299
|
+
also accepts `"system"` messages and allows blank message text, with the same
|
|
300
|
+
length limit. A reply instruction applies to one response; it does not replace
|
|
301
|
+
session instructions.
|
|
294
302
|
|
|
295
303
|
A returned `ResponseRef` means generation started. Read `response.ended` for the
|
|
296
304
|
terminal status. Generation can finish before cancellation takes effect, so a
|
|
@@ -502,8 +510,7 @@ text or audio, which the application must handle before another attempt.
|
|
|
502
510
|
|
|
503
511
|
For `context_length_exceeded`, the server message contains token counts.
|
|
504
512
|
Unchanged attempts will fail until the conversation is shorter. Start a new
|
|
505
|
-
session with shorter context.
|
|
506
|
-
this SDK does not yet expose `conversation.item.delete`.
|
|
513
|
+
session with shorter context. Use the history deletion method to remove items before retrying.
|
|
507
514
|
|
|
508
515
|
Malformed fields in known events and mismatched request acknowledgments close
|
|
509
516
|
the session with `RimeStreamError`. Unknown event kinds and extra fields remain
|
|
@@ -535,3 +542,48 @@ revisions can be ambiguous. The SDK preserves earlier known settings and raises
|
|
|
535
542
|
a resource limit error for an ambiguous reply instead of guessing. A later
|
|
536
543
|
acknowledgment with a revision establishes a new known setting. Replies without
|
|
537
544
|
a revision use the current acknowledged setting.
|
|
545
|
+
|
|
546
|
+
## Conversation history operations
|
|
547
|
+
|
|
548
|
+
Available in the next SDK release. The Prism server must support history operations.
|
|
549
|
+
Read history to obtain the items the next reply would see, in server order.
|
|
550
|
+
Create messages with user, assistant, or system roles, past function calls, and
|
|
551
|
+
function results. Creating an item does not start a reply or execute a tool.
|
|
552
|
+
Message text is limited to 4000 Unicode characters. Optional client item IDs have
|
|
553
|
+
1 to 32 characters. Tool arguments and results preserve the supplied strings.
|
|
554
|
+
|
|
555
|
+
Omit the insertion anchor to append, use `root` to insert first, or give an item
|
|
556
|
+
ID to insert after its whole turn. Results go after their calls and cannot take
|
|
557
|
+
an anchor. Add a past call before adding its result.
|
|
558
|
+
|
|
559
|
+
Deletion hides items from subsequent replies and keeps the conversation record.
|
|
560
|
+
Deleting a call or its result hides both. An active reply keeps its original
|
|
561
|
+
context. A deleted active call still needs a result and continuation through the
|
|
562
|
+
existing tool APIs. Deleting an already deleted item succeeds; an unknown ID fails.
|
|
563
|
+
History excludes replies still running and their inputs until those replies end.
|
|
564
|
+
Requests work without typed-turn readiness. A canceled submitted request can still
|
|
565
|
+
take effect; the SDK tracks its acknowledgment and does not replay it.
|
|
566
|
+
|
|
567
|
+
```text
|
|
568
|
+
const item = await session.createItem({ type: "message", role: "system", text: "Transferred to billing." }, { previousItemId: "root" });
|
|
569
|
+
const items = await session.getHistory();
|
|
570
|
+
await session.deleteItem(item.itemId);
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
## History truncation
|
|
574
|
+
|
|
575
|
+
Available in the next SDK release. Set `truncation` at connection setup or during
|
|
576
|
+
an existing session. Omit it to preserve compatibility with older Prism servers.
|
|
577
|
+
The server must support truncation before you send this setting.
|
|
578
|
+
|
|
579
|
+
`auto` drops the oldest turns and retains 0.8 of the available history capacity.
|
|
580
|
+
`disabled` keeps all history and reports `context_length_exceeded` when it no
|
|
581
|
+
longer fits. A retention ratio above zero and at most one selects how much history
|
|
582
|
+
capacity to retain. Instructions and tools remain. This setting does not delete
|
|
583
|
+
items from the conversation record. Session information reports the acknowledged
|
|
584
|
+
policy in `session.info.truncation`, or `null` when an older server does not
|
|
585
|
+
publish it.
|
|
586
|
+
|
|
587
|
+
```text
|
|
588
|
+
await session.update({ truncation: { retentionRatio: 0.9 } });
|
|
589
|
+
```
|