@butlerbot/sdk 0.0.16 → 0.0.18-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +8 -0
- package/dist/config.js +6 -1
- package/dist/index.d.ts +3 -1
- package/dist/modules/conversation.d.ts +22 -11
- package/dist/modules/conversation.js +12 -4
- package/dist/modules/voice/client_link.d.ts +69 -0
- package/dist/modules/voice/client_link.js +275 -0
- package/dist/modules/voice/index.d.ts +2 -0
- package/dist/modules/voice/index.js +20 -0
- package/dist/modules/voice/types.d.ts +80 -0
- package/dist/modules/voice/types.js +12 -0
- package/dist/types/response/v5/ai_response_v5.d.ts +250 -0
- package/dist/types/response/v5/ai_response_v5.js +5 -0
- package/dist/types/response/v5/dialogue_response_v5.d.ts +20 -0
- package/dist/types/response/v5/dialogue_response_v5.js +2 -0
- package/dist/types/response/v5/index.d.ts +3 -0
- package/dist/types/response/v5/index.js +19 -0
- package/dist/types/response/v5/turn_registry_v5.d.ts +7 -0
- package/dist/types/response/v5/turn_registry_v5.js +2 -0
- package/dist/types/type_registry.d.ts +1 -0
- package/dist/types/type_registry.js +1 -0
- package/package.json +45 -45
package/dist/config.d.ts
CHANGED
|
@@ -9,6 +9,9 @@ export declare const CONFIG: {
|
|
|
9
9
|
v4: {
|
|
10
10
|
base: string;
|
|
11
11
|
};
|
|
12
|
+
v5: {
|
|
13
|
+
base: string;
|
|
14
|
+
};
|
|
12
15
|
};
|
|
13
16
|
history: {
|
|
14
17
|
chat: {
|
|
@@ -27,6 +30,10 @@ export declare const CONFIG: {
|
|
|
27
30
|
base: string;
|
|
28
31
|
stream: string;
|
|
29
32
|
};
|
|
33
|
+
v5: {
|
|
34
|
+
base: string;
|
|
35
|
+
stream: string;
|
|
36
|
+
};
|
|
30
37
|
};
|
|
31
38
|
usage: {
|
|
32
39
|
policy: {
|
|
@@ -37,3 +44,4 @@ export declare const CONFIG: {
|
|
|
37
44
|
};
|
|
38
45
|
};
|
|
39
46
|
};
|
|
47
|
+
export type APIPath = keyof typeof CONFIG.paths.conversation;
|
package/dist/config.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.CONFIG = {
|
|
|
8
8
|
conversation: {
|
|
9
9
|
v3: { base: "/api/alfred/v3/chat" },
|
|
10
10
|
v4: { base: "/api/alfred/v4/chat" },
|
|
11
|
+
v5: { base: "/api/alfred/v5/chat" },
|
|
11
12
|
},
|
|
12
13
|
history: {
|
|
13
14
|
chat: {
|
|
@@ -21,7 +22,11 @@ exports.CONFIG = {
|
|
|
21
22
|
v4: {
|
|
22
23
|
base: "/api/alfred/v4/chat/progress",
|
|
23
24
|
stream: "/api/alfred/v4/chat/progress/stream",
|
|
24
|
-
}
|
|
25
|
+
},
|
|
26
|
+
v5: {
|
|
27
|
+
base: "/api/alfred/v5/chat/progress",
|
|
28
|
+
stream: "/api/alfred/v5/chat/progress/stream",
|
|
29
|
+
},
|
|
25
30
|
},
|
|
26
31
|
usage: {
|
|
27
32
|
policy: {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { APIPath } from "./config";
|
|
1
2
|
import { Conversation, ConversationOptions } from "./modules/conversation";
|
|
2
3
|
import { UsagePolicyDataOptions } from "./modules/usage";
|
|
3
4
|
type OptionalApiKey<T> = Omit<T, "apiKey"> & {
|
|
@@ -20,9 +21,10 @@ export declare class ButlerBotClient {
|
|
|
20
21
|
/** Checks the health of the server returning true if server is alive */
|
|
21
22
|
healthCheck(healthCheckPath?: string): Promise<boolean>;
|
|
22
23
|
/** Spawns a new Conversation, inherits api key and server URL */
|
|
23
|
-
createConversation(config?: OptionalApiKey<ConversationOptions
|
|
24
|
+
createConversation<V extends APIPath = "v4">(config?: OptionalApiKey<ConversationOptions<V>>): Conversation<V>;
|
|
24
25
|
/** Get current usage policy data */
|
|
25
26
|
getUsagePolicyData(config: OptionalApiKey<UsagePolicyDataOptions>): Promise<import("./types/usage/policy").UsagePolicyData>;
|
|
26
27
|
}
|
|
27
28
|
export * from "./types/type_registry";
|
|
28
29
|
export { Conversation, ConversationOptions };
|
|
30
|
+
export type { APIPath };
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { EventSource } from "eventsource";
|
|
2
|
-
import {
|
|
3
|
-
import { RequestResponseV4 } from "../types/type_registry";
|
|
2
|
+
import { APIPath } from "../config";
|
|
3
|
+
import { RequestResponseV3, RequestResponseV4 } from "../types/type_registry";
|
|
4
|
+
import { RequestResponseV5 } from "../types/response/v5/dialogue_response_v5";
|
|
4
5
|
import { ConversationStateResponse } from "../types/state/convo_state_response";
|
|
5
6
|
import { TurnProgressEntry } from "../types/response/v4/turn_registry_v4";
|
|
7
|
+
import { TurnProgressEntryV5 } from "../types/response/v5/turn_registry_v5";
|
|
6
8
|
export type DialogueRequestParams = {
|
|
7
9
|
/** Unique identifier for the chat session */
|
|
8
10
|
chatId?: string;
|
|
@@ -18,8 +20,17 @@ export type DialogueRequestParams = {
|
|
|
18
20
|
personality?: string;
|
|
19
21
|
};
|
|
20
22
|
export type DialogueRequestOptions = Omit<Omit<DialogueRequestParams, "message">, "chatId">;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
export interface RequestResponseByVersion {
|
|
24
|
+
v3: RequestResponseV3;
|
|
25
|
+
v4: RequestResponseV4;
|
|
26
|
+
v5: RequestResponseV5;
|
|
27
|
+
}
|
|
28
|
+
interface TurnProgressEntryByVersion {
|
|
29
|
+
v4: TurnProgressEntry;
|
|
30
|
+
v5: TurnProgressEntryV5;
|
|
31
|
+
}
|
|
32
|
+
export type TurnProgressEntryForVersion<V extends APIPath> = V extends keyof TurnProgressEntryByVersion ? TurnProgressEntryByVersion[V] : TurnProgressEntry;
|
|
33
|
+
export type ConversationOptions<V extends APIPath = "v4"> = {
|
|
23
34
|
/** The conversation ID to load the conversation from, server will error if this convo id doesn't exist */
|
|
24
35
|
convoId?: string;
|
|
25
36
|
/** The API key to use */
|
|
@@ -42,17 +53,17 @@ export type ConversationOptions = {
|
|
|
42
53
|
/** Whether to enable debug logs */
|
|
43
54
|
debug?: boolean;
|
|
44
55
|
/** The API version to use */
|
|
45
|
-
chatApiV?:
|
|
56
|
+
chatApiV?: V;
|
|
46
57
|
};
|
|
47
|
-
export
|
|
48
|
-
export declare class Conversation {
|
|
58
|
+
export declare class Conversation<V extends APIPath = "v4"> {
|
|
49
59
|
convoId?: string;
|
|
50
60
|
apiKey: string;
|
|
51
61
|
private debug;
|
|
62
|
+
private chatApiV;
|
|
52
63
|
private options?;
|
|
53
64
|
private events;
|
|
54
65
|
private endpoints;
|
|
55
|
-
constructor(config: ConversationOptions);
|
|
66
|
+
constructor(config: ConversationOptions<V>);
|
|
56
67
|
/**
|
|
57
68
|
* Sets the endpoint where the request is sent to, appended to server URL
|
|
58
69
|
* @deprecated Use setConversationEndpoint() instead
|
|
@@ -121,7 +132,7 @@ export declare class Conversation {
|
|
|
121
132
|
/** Fetches the conversation state from the server, including message history and metadata */
|
|
122
133
|
fetchState(): Promise<ConversationStateResponse>;
|
|
123
134
|
/** Fetches the conversation progress stream from the server */
|
|
124
|
-
fetchProgressStream(cb: (chunk:
|
|
135
|
+
fetchProgressStream(cb: (chunk: RequestResponseByVersion[V]) => any): EventSource;
|
|
125
136
|
/**
|
|
126
137
|
* Fetches the conversation progress from the server
|
|
127
138
|
* Returns undefined if no active turn progress
|
|
@@ -129,8 +140,8 @@ export declare class Conversation {
|
|
|
129
140
|
fetchProgress(options?: {
|
|
130
141
|
lastEventId?: string;
|
|
131
142
|
includeCompleted?: boolean;
|
|
132
|
-
}): Promise<
|
|
143
|
+
}): Promise<TurnProgressEntryForVersion<V>[] | undefined>;
|
|
133
144
|
/** Sends a message into the conversation */
|
|
134
|
-
send(message: string, cb: (chunk:
|
|
145
|
+
send(message: string, cb: (chunk: RequestResponseByVersion[V]) => any, options?: DialogueRequestOptions): EventSource;
|
|
135
146
|
}
|
|
136
147
|
export {};
|
|
@@ -16,14 +16,17 @@ const url_formatter_1 = require("../util/url_formatter");
|
|
|
16
16
|
const DEFAULT_CONVO_API_V = "v4";
|
|
17
17
|
class Conversation {
|
|
18
18
|
constructor(config) {
|
|
19
|
+
var _a, _b;
|
|
19
20
|
this.events = new Map();
|
|
20
21
|
this.convoId = config.convoId;
|
|
22
|
+
this.chatApiV = (config.chatApiV || DEFAULT_CONVO_API_V);
|
|
21
23
|
const serverUrl = config.serverUrl || config_1.CONFIG.server;
|
|
24
|
+
const progressConfig = config_1.CONFIG.paths.progress[this.chatApiV];
|
|
22
25
|
this.endpoints = {
|
|
23
|
-
conversation: serverUrl + (config.convoPath || config.path || config_1.CONFIG.paths.conversation[
|
|
26
|
+
conversation: serverUrl + (config.convoPath || config.path || config_1.CONFIG.paths.conversation[this.chatApiV].base),
|
|
24
27
|
history: serverUrl + (config.historyPath || config_1.CONFIG.paths.history.chat.v1.base),
|
|
25
|
-
progressStream: serverUrl + (config.progressStreamPath || config_1.CONFIG.paths.progress.v4.stream),
|
|
26
|
-
progress: serverUrl + (config.progressPath || config_1.CONFIG.paths.progress.v4.base),
|
|
28
|
+
progressStream: serverUrl + (config.progressStreamPath || ((_a = progressConfig === null || progressConfig === void 0 ? void 0 : progressConfig.stream) !== null && _a !== void 0 ? _a : config_1.CONFIG.paths.progress.v4.stream)),
|
|
29
|
+
progress: serverUrl + (config.progressPath || ((_b = progressConfig === null || progressConfig === void 0 ? void 0 : progressConfig.base) !== null && _b !== void 0 ? _b : config_1.CONFIG.paths.progress.v4.base)),
|
|
27
30
|
};
|
|
28
31
|
this.apiKey = config.apiKey;
|
|
29
32
|
this.debug = config.debug || false;
|
|
@@ -236,7 +239,12 @@ class Conversation {
|
|
|
236
239
|
return __awaiter(this, void 0, void 0, function* () {
|
|
237
240
|
if (!this.convoId)
|
|
238
241
|
throw new Error("Conversation ID is not set");
|
|
239
|
-
const
|
|
242
|
+
const payload = {
|
|
243
|
+
chatId: this.convoId,
|
|
244
|
+
};
|
|
245
|
+
if ((options === null || options === void 0 ? void 0 : options.includeCompleted) !== undefined)
|
|
246
|
+
payload["includeCompleted"] = options.includeCompleted;
|
|
247
|
+
const url = (0, url_formatter_1.formatURL)(this.endpoints.progress, payload, { apiKey: this.apiKey, debug: this.debug });
|
|
240
248
|
const response = yield fetch(url, { headers: (options === null || options === void 0 ? void 0 : options.lastEventId) ? { "last-event-id": options.lastEventId } : undefined });
|
|
241
249
|
if (!response.ok) {
|
|
242
250
|
const errorText = yield response.text();
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { AudioClosePayload, AudioCodec, AudioOpenPayload, AudioOpenedPayload, AudioWakewordPayload, ClientLinkCapabilities, ClientLinkChannel, ClientLinkFrame, ClientLinkIdentity, ErrorPayload, InboundAudioFrame, WelcomePayload } from "./types";
|
|
3
|
+
type ClientLinkEventMap = {
|
|
4
|
+
connected: [];
|
|
5
|
+
authenticated: [WelcomePayload];
|
|
6
|
+
disconnected: [{
|
|
7
|
+
code?: number;
|
|
8
|
+
explicit: boolean;
|
|
9
|
+
}];
|
|
10
|
+
error: [Error | ErrorPayload];
|
|
11
|
+
"audio.opened": [AudioOpenedPayload];
|
|
12
|
+
"audio.closed": [{
|
|
13
|
+
roomId?: string;
|
|
14
|
+
roomExternalRef?: string;
|
|
15
|
+
}];
|
|
16
|
+
"audio.frame": [InboundAudioFrame];
|
|
17
|
+
"audio.wakeword": [AudioWakewordPayload];
|
|
18
|
+
};
|
|
19
|
+
type ClientLinkEvent = keyof ClientLinkEventMap;
|
|
20
|
+
type ClientLinkListener<T extends ClientLinkEvent> = (...args: ClientLinkEventMap[T]) => void;
|
|
21
|
+
export type ClientLinkOptions = {
|
|
22
|
+
url: string;
|
|
23
|
+
token: string;
|
|
24
|
+
identity: ClientLinkIdentity;
|
|
25
|
+
capabilities?: ClientLinkCapabilities;
|
|
26
|
+
pingIntervalMs?: number;
|
|
27
|
+
autoReconnect?: boolean;
|
|
28
|
+
reconnectInitialDelayMs?: number;
|
|
29
|
+
debug?: boolean;
|
|
30
|
+
};
|
|
31
|
+
export declare class ClientLink {
|
|
32
|
+
private readonly options;
|
|
33
|
+
private ws;
|
|
34
|
+
private authenticated;
|
|
35
|
+
private explicitClose;
|
|
36
|
+
private reconnectAttempts;
|
|
37
|
+
private pingTimer;
|
|
38
|
+
private reconnectTimer;
|
|
39
|
+
private desiredAudioOpen;
|
|
40
|
+
private currentRoomId;
|
|
41
|
+
private outboundSeq;
|
|
42
|
+
private listeners;
|
|
43
|
+
constructor(options: ClientLinkOptions);
|
|
44
|
+
on<T extends ClientLinkEvent>(event: T, listener: ClientLinkListener<T>): this;
|
|
45
|
+
off<T extends ClientLinkEvent>(event: T, listener: ClientLinkListener<T>): this;
|
|
46
|
+
connect(): Promise<WelcomePayload>;
|
|
47
|
+
close(): void;
|
|
48
|
+
isAuthenticated(): boolean;
|
|
49
|
+
get roomId(): string | null;
|
|
50
|
+
openAudio(payload: AudioOpenPayload): void;
|
|
51
|
+
closeAudio(): void;
|
|
52
|
+
sendAudioFrame(data: Buffer, codec?: AudioCodec, flags?: {
|
|
53
|
+
activityStart?: boolean;
|
|
54
|
+
activityEnd?: boolean;
|
|
55
|
+
}): void;
|
|
56
|
+
sendWakeword(payload: AudioWakewordPayload): void;
|
|
57
|
+
private openSocket;
|
|
58
|
+
private handleText;
|
|
59
|
+
private handleControlFrame;
|
|
60
|
+
private handleAudioFrame;
|
|
61
|
+
private handleBinary;
|
|
62
|
+
private sendFrame;
|
|
63
|
+
private startPingLoop;
|
|
64
|
+
private clearPingTimer;
|
|
65
|
+
private scheduleReconnect;
|
|
66
|
+
private clearReconnectTimer;
|
|
67
|
+
private emit;
|
|
68
|
+
}
|
|
69
|
+
export type { AudioClosePayload, AudioCodec, AudioOpenPayload, AudioOpenedPayload, AudioWakewordPayload, ClientLinkCapabilities, ClientLinkChannel, ClientLinkFrame, ClientLinkIdentity, ErrorPayload, InboundAudioFrame, WelcomePayload, };
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ClientLink = void 0;
|
|
7
|
+
const node_buffer_1 = require("node:buffer");
|
|
8
|
+
const ws_1 = __importDefault(require("ws"));
|
|
9
|
+
const types_1 = require("./types");
|
|
10
|
+
class ClientLink {
|
|
11
|
+
constructor(options) {
|
|
12
|
+
var _a, _b, _c, _d, _e;
|
|
13
|
+
this.ws = null;
|
|
14
|
+
this.authenticated = false;
|
|
15
|
+
this.explicitClose = false;
|
|
16
|
+
this.reconnectAttempts = 0;
|
|
17
|
+
this.pingTimer = null;
|
|
18
|
+
this.reconnectTimer = null;
|
|
19
|
+
this.desiredAudioOpen = null;
|
|
20
|
+
this.currentRoomId = null;
|
|
21
|
+
this.outboundSeq = 0;
|
|
22
|
+
this.listeners = {
|
|
23
|
+
connected: new Set(),
|
|
24
|
+
authenticated: new Set(),
|
|
25
|
+
disconnected: new Set(),
|
|
26
|
+
error: new Set(),
|
|
27
|
+
"audio.opened": new Set(),
|
|
28
|
+
"audio.closed": new Set(),
|
|
29
|
+
"audio.frame": new Set(),
|
|
30
|
+
"audio.wakeword": new Set(),
|
|
31
|
+
};
|
|
32
|
+
this.options = Object.assign(Object.assign({}, options), { capabilities: (_a = options.capabilities) !== null && _a !== void 0 ? _a : { channels: ["control", "audio"], wakeword: "server" }, pingIntervalMs: (_b = options.pingIntervalMs) !== null && _b !== void 0 ? _b : 25000, autoReconnect: (_c = options.autoReconnect) !== null && _c !== void 0 ? _c : true, reconnectInitialDelayMs: (_d = options.reconnectInitialDelayMs) !== null && _d !== void 0 ? _d : 1000, debug: (_e = options.debug) !== null && _e !== void 0 ? _e : false });
|
|
33
|
+
}
|
|
34
|
+
on(event, listener) {
|
|
35
|
+
this.listeners[event].add(listener);
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
off(event, listener) {
|
|
39
|
+
this.listeners[event].delete(listener);
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
connect() {
|
|
43
|
+
this.explicitClose = false;
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const handleAuth = (payload) => {
|
|
46
|
+
this.off("authenticated", handleAuth);
|
|
47
|
+
this.off("error", handleError);
|
|
48
|
+
resolve(payload);
|
|
49
|
+
};
|
|
50
|
+
const handleError = (error) => {
|
|
51
|
+
this.off("authenticated", handleAuth);
|
|
52
|
+
this.off("error", handleError);
|
|
53
|
+
reject(error instanceof Error ? error : new Error(`${error.code}: ${error.message}`));
|
|
54
|
+
};
|
|
55
|
+
this.on("authenticated", handleAuth);
|
|
56
|
+
this.on("error", handleError);
|
|
57
|
+
this.openSocket();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
close() {
|
|
61
|
+
this.explicitClose = true;
|
|
62
|
+
this.clearReconnectTimer();
|
|
63
|
+
this.clearPingTimer();
|
|
64
|
+
if (!this.ws)
|
|
65
|
+
return;
|
|
66
|
+
if (this.ws.readyState === ws_1.default.OPEN) {
|
|
67
|
+
this.sendFrame("control", "bye", {});
|
|
68
|
+
}
|
|
69
|
+
this.ws.close();
|
|
70
|
+
this.ws = null;
|
|
71
|
+
}
|
|
72
|
+
isAuthenticated() {
|
|
73
|
+
return this.authenticated;
|
|
74
|
+
}
|
|
75
|
+
get roomId() {
|
|
76
|
+
return this.currentRoomId;
|
|
77
|
+
}
|
|
78
|
+
openAudio(payload) {
|
|
79
|
+
this.desiredAudioOpen = payload;
|
|
80
|
+
this.sendFrame("audio", "audio.open", payload);
|
|
81
|
+
}
|
|
82
|
+
closeAudio() {
|
|
83
|
+
if (!this.desiredAudioOpen)
|
|
84
|
+
return;
|
|
85
|
+
const payload = {
|
|
86
|
+
roomExternalRef: this.desiredAudioOpen.roomExternalRef,
|
|
87
|
+
};
|
|
88
|
+
this.desiredAudioOpen = null;
|
|
89
|
+
this.currentRoomId = null;
|
|
90
|
+
this.sendFrame("audio", "audio.close", payload);
|
|
91
|
+
}
|
|
92
|
+
sendAudioFrame(data, codec = "pcm16", flags = {}) {
|
|
93
|
+
if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
|
|
94
|
+
return;
|
|
95
|
+
const header = node_buffer_1.Buffer.allocUnsafe(types_1.AUDIO_HEADER_BYTES);
|
|
96
|
+
this.outboundSeq = (this.outboundSeq + 1) & 0xffff;
|
|
97
|
+
let flagByte = 0;
|
|
98
|
+
if (flags.activityStart)
|
|
99
|
+
flagByte |= types_1.AUDIO_FLAG.ACTIVITY_START;
|
|
100
|
+
if (flags.activityEnd)
|
|
101
|
+
flagByte |= types_1.AUDIO_FLAG.ACTIVITY_END;
|
|
102
|
+
header.writeUInt16BE(this.outboundSeq, 0);
|
|
103
|
+
header.writeUInt8(codec === "pcm16" ? types_1.AUDIO_CODEC_BYTE.pcm16 : types_1.AUDIO_CODEC_BYTE.opus, 2);
|
|
104
|
+
header.writeUInt8(flagByte, 3);
|
|
105
|
+
this.ws.send(concatBuffers(header, data));
|
|
106
|
+
}
|
|
107
|
+
sendWakeword(payload) {
|
|
108
|
+
this.sendFrame("audio", "audio.wakeword", payload);
|
|
109
|
+
}
|
|
110
|
+
openSocket() {
|
|
111
|
+
this.clearReconnectTimer();
|
|
112
|
+
this.authenticated = false;
|
|
113
|
+
this.ws = new ws_1.default(this.options.url);
|
|
114
|
+
this.ws.on("open", () => {
|
|
115
|
+
this.emit("connected");
|
|
116
|
+
const hello = {
|
|
117
|
+
token: this.options.token,
|
|
118
|
+
identity: this.options.identity,
|
|
119
|
+
capabilities: this.options.capabilities,
|
|
120
|
+
};
|
|
121
|
+
this.sendFrame("control", "hello", hello);
|
|
122
|
+
});
|
|
123
|
+
this.ws.on("message", (data, isBinary) => {
|
|
124
|
+
if (isBinary) {
|
|
125
|
+
this.handleBinary(rawDataToBuffer(data));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this.handleText(data.toString("utf8"));
|
|
129
|
+
});
|
|
130
|
+
this.ws.on("close", (code) => {
|
|
131
|
+
this.authenticated = false;
|
|
132
|
+
this.clearPingTimer();
|
|
133
|
+
this.emit("disconnected", { code, explicit: this.explicitClose });
|
|
134
|
+
if (!this.explicitClose && this.options.autoReconnect) {
|
|
135
|
+
this.scheduleReconnect();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
this.ws.on("error", (error) => {
|
|
139
|
+
this.emit("error", error);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
handleText(raw) {
|
|
143
|
+
let frame;
|
|
144
|
+
try {
|
|
145
|
+
frame = JSON.parse(raw);
|
|
146
|
+
}
|
|
147
|
+
catch (_a) {
|
|
148
|
+
this.emit("error", new Error("Invalid JSON frame received from ClientLink server"));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (frame.channel === "control") {
|
|
152
|
+
this.handleControlFrame(frame);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (frame.channel === "audio") {
|
|
156
|
+
this.handleAudioFrame(frame);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
handleControlFrame(frame) {
|
|
160
|
+
switch (frame.type) {
|
|
161
|
+
case "welcome": {
|
|
162
|
+
const payload = frame.payload;
|
|
163
|
+
this.authenticated = true;
|
|
164
|
+
this.reconnectAttempts = 0;
|
|
165
|
+
this.startPingLoop();
|
|
166
|
+
this.emit("authenticated", payload);
|
|
167
|
+
if (this.desiredAudioOpen) {
|
|
168
|
+
this.sendFrame("audio", "audio.open", this.desiredAudioOpen);
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
case "pong":
|
|
173
|
+
return;
|
|
174
|
+
case "error":
|
|
175
|
+
this.emit("error", frame.payload);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
handleAudioFrame(frame) {
|
|
180
|
+
switch (frame.type) {
|
|
181
|
+
case "audio.opened": {
|
|
182
|
+
const payload = frame.payload;
|
|
183
|
+
this.currentRoomId = payload.roomId;
|
|
184
|
+
this.emit("audio.opened", payload);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
case "audio.closed": {
|
|
188
|
+
const payload = frame.payload;
|
|
189
|
+
this.currentRoomId = null;
|
|
190
|
+
this.emit("audio.closed", payload);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
case "audio.wakeword":
|
|
194
|
+
this.emit("audio.wakeword", frame.payload);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
handleBinary(data) {
|
|
199
|
+
if (data.length < types_1.AUDIO_HEADER_BYTES)
|
|
200
|
+
return;
|
|
201
|
+
const seqNum = data.readUInt16BE(0);
|
|
202
|
+
const codecByte = data.readUInt8(2);
|
|
203
|
+
const flags = data.readUInt8(3);
|
|
204
|
+
const payload = data.subarray(types_1.AUDIO_HEADER_BYTES);
|
|
205
|
+
this.emit("audio.frame", {
|
|
206
|
+
speakerId: "assistant",
|
|
207
|
+
data: payload,
|
|
208
|
+
codec: codecByte === types_1.AUDIO_CODEC_BYTE.pcm16 ? "pcm16" : "opus",
|
|
209
|
+
seqNum,
|
|
210
|
+
activityStart: (flags & types_1.AUDIO_FLAG.ACTIVITY_START) !== 0,
|
|
211
|
+
activityEnd: (flags & types_1.AUDIO_FLAG.ACTIVITY_END) !== 0,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
sendFrame(channel, type, payload) {
|
|
215
|
+
if (!this.ws || this.ws.readyState !== ws_1.default.OPEN)
|
|
216
|
+
return;
|
|
217
|
+
const frame = { channel, type, payload };
|
|
218
|
+
this.ws.send(JSON.stringify(frame));
|
|
219
|
+
}
|
|
220
|
+
startPingLoop() {
|
|
221
|
+
this.clearPingTimer();
|
|
222
|
+
this.pingTimer = setInterval(() => {
|
|
223
|
+
const payload = { ts: Date.now() };
|
|
224
|
+
this.sendFrame("control", "ping", payload);
|
|
225
|
+
}, this.options.pingIntervalMs);
|
|
226
|
+
}
|
|
227
|
+
clearPingTimer() {
|
|
228
|
+
if (!this.pingTimer)
|
|
229
|
+
return;
|
|
230
|
+
clearInterval(this.pingTimer);
|
|
231
|
+
this.pingTimer = null;
|
|
232
|
+
}
|
|
233
|
+
scheduleReconnect() {
|
|
234
|
+
this.clearReconnectTimer();
|
|
235
|
+
const delay = Math.min(30000, this.options.reconnectInitialDelayMs * Math.pow(2, this.reconnectAttempts));
|
|
236
|
+
this.reconnectAttempts += 1;
|
|
237
|
+
this.reconnectTimer = setTimeout(() => this.openSocket(), delay);
|
|
238
|
+
}
|
|
239
|
+
clearReconnectTimer() {
|
|
240
|
+
if (!this.reconnectTimer)
|
|
241
|
+
return;
|
|
242
|
+
clearTimeout(this.reconnectTimer);
|
|
243
|
+
this.reconnectTimer = null;
|
|
244
|
+
}
|
|
245
|
+
emit(event, ...args) {
|
|
246
|
+
for (const listener of this.listeners[event]) {
|
|
247
|
+
try {
|
|
248
|
+
listener(...args);
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
if (this.options.debug) {
|
|
252
|
+
console.warn(`[ClientLink:${event}] listener failed`, error);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
exports.ClientLink = ClientLink;
|
|
259
|
+
function concatBuffers(left, right) {
|
|
260
|
+
const out = node_buffer_1.Buffer.allocUnsafe(left.length + right.length);
|
|
261
|
+
left.copy(out, 0);
|
|
262
|
+
right.copy(out, left.length);
|
|
263
|
+
return out;
|
|
264
|
+
}
|
|
265
|
+
function rawDataToBuffer(data) {
|
|
266
|
+
if (node_buffer_1.Buffer.isBuffer(data))
|
|
267
|
+
return data;
|
|
268
|
+
if (data instanceof ArrayBuffer)
|
|
269
|
+
return node_buffer_1.Buffer.from(data);
|
|
270
|
+
if (Array.isArray(data)) {
|
|
271
|
+
return node_buffer_1.Buffer.concat(data.map((part) => rawDataToBuffer(part)));
|
|
272
|
+
}
|
|
273
|
+
const view = data;
|
|
274
|
+
return node_buffer_1.Buffer.from(view.buffer, view.byteOffset, view.byteLength);
|
|
275
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.ClientLink = void 0;
|
|
18
|
+
var client_link_1 = require("./client_link");
|
|
19
|
+
Object.defineProperty(exports, "ClientLink", { enumerable: true, get: function () { return client_link_1.ClientLink; } });
|
|
20
|
+
__exportStar(require("./types"), exports);
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Buffer } from "node:buffer";
|
|
2
|
+
export type ClientLinkKind = "discord-bot" | "desktop" | "mobile" | "device" | "browser";
|
|
3
|
+
export type ClientLinkChannel = "control" | "audio" | "tools" | "events";
|
|
4
|
+
export type ClientLinkCapabilities = {
|
|
5
|
+
channels: ClientLinkChannel[];
|
|
6
|
+
/** Whether the client handles wakeword detection itself, or wants the server to */
|
|
7
|
+
wakeword?: "client" | "server";
|
|
8
|
+
};
|
|
9
|
+
export type ClientLinkIdentity = {
|
|
10
|
+
/** Stable UUID across reconnects for the same logical client */
|
|
11
|
+
linkId: string;
|
|
12
|
+
kind: ClientLinkKind;
|
|
13
|
+
displayName: string;
|
|
14
|
+
};
|
|
15
|
+
/** Text JSON frame envelope */
|
|
16
|
+
export type ClientLinkFrame = {
|
|
17
|
+
channel: ClientLinkChannel;
|
|
18
|
+
type: string;
|
|
19
|
+
payload: unknown;
|
|
20
|
+
};
|
|
21
|
+
export declare const AUDIO_HEADER_BYTES = 4;
|
|
22
|
+
export declare const AUDIO_CODEC_BYTE: {
|
|
23
|
+
readonly opus: 0;
|
|
24
|
+
readonly pcm16: 1;
|
|
25
|
+
};
|
|
26
|
+
export declare const AUDIO_FLAG: {
|
|
27
|
+
readonly ACTIVITY_START: 1;
|
|
28
|
+
readonly ACTIVITY_END: 2;
|
|
29
|
+
};
|
|
30
|
+
export type AudioCodec = "opus" | "pcm16";
|
|
31
|
+
export type HelloPayload = {
|
|
32
|
+
/** JWT access token for the user that owns this link */
|
|
33
|
+
token: string;
|
|
34
|
+
identity: ClientLinkIdentity;
|
|
35
|
+
capabilities: ClientLinkCapabilities;
|
|
36
|
+
};
|
|
37
|
+
export type WelcomePayload = {
|
|
38
|
+
linkId: string;
|
|
39
|
+
serverVersion: string;
|
|
40
|
+
};
|
|
41
|
+
export type PingPayload = {
|
|
42
|
+
ts: number;
|
|
43
|
+
};
|
|
44
|
+
export type PongPayload = {
|
|
45
|
+
ts: number;
|
|
46
|
+
serverTs: number;
|
|
47
|
+
};
|
|
48
|
+
export type ErrorPayload = {
|
|
49
|
+
code: string;
|
|
50
|
+
message: string;
|
|
51
|
+
};
|
|
52
|
+
export type VoiceRoomMode = "off" | "transcribe" | "realtime";
|
|
53
|
+
export type AudioOpenPayload = {
|
|
54
|
+
roomExternalRef: string;
|
|
55
|
+
roomKind: "discord" | "phone" | "device";
|
|
56
|
+
mode?: VoiceRoomMode;
|
|
57
|
+
wakewordHandledBy?: "client" | "server";
|
|
58
|
+
};
|
|
59
|
+
export type AudioClosePayload = {
|
|
60
|
+
roomExternalRef: string;
|
|
61
|
+
};
|
|
62
|
+
export type AudioOpenedPayload = {
|
|
63
|
+
roomId: string;
|
|
64
|
+
mode: VoiceRoomMode;
|
|
65
|
+
inputSampleRate: number;
|
|
66
|
+
outputSampleRate: number;
|
|
67
|
+
};
|
|
68
|
+
export type AudioWakewordPayload = {
|
|
69
|
+
speakerId: string;
|
|
70
|
+
confidence: number;
|
|
71
|
+
};
|
|
72
|
+
export type InboundAudioFrame = {
|
|
73
|
+
/** Server currently emits a fixed "assistant" speakerId for outbound model audio */
|
|
74
|
+
speakerId: string;
|
|
75
|
+
data: Buffer;
|
|
76
|
+
codec: AudioCodec;
|
|
77
|
+
seqNum: number;
|
|
78
|
+
activityStart: boolean;
|
|
79
|
+
activityEnd: boolean;
|
|
80
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AUDIO_FLAG = exports.AUDIO_CODEC_BYTE = exports.AUDIO_HEADER_BYTES = void 0;
|
|
4
|
+
exports.AUDIO_HEADER_BYTES = 4;
|
|
5
|
+
exports.AUDIO_CODEC_BYTE = {
|
|
6
|
+
opus: 0,
|
|
7
|
+
pcm16: 1,
|
|
8
|
+
};
|
|
9
|
+
exports.AUDIO_FLAG = {
|
|
10
|
+
ACTIVITY_START: 0b00000001,
|
|
11
|
+
ACTIVITY_END: 0b00000010,
|
|
12
|
+
};
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/** Display information for a participant */
|
|
2
|
+
export type DisplayEntity = {
|
|
3
|
+
name?: string;
|
|
4
|
+
avatarUrl?: string;
|
|
5
|
+
};
|
|
6
|
+
/** Rich content that can be attached to tool status updates.
|
|
7
|
+
* Clients can render these if supported, or fall back to just the `label` field on ToolStatus. */
|
|
8
|
+
export type ToolStatusContent = {
|
|
9
|
+
type: "text";
|
|
10
|
+
text: string;
|
|
11
|
+
} | {
|
|
12
|
+
type: "code";
|
|
13
|
+
language: string;
|
|
14
|
+
code: string;
|
|
15
|
+
} | {
|
|
16
|
+
type: "image";
|
|
17
|
+
url: string;
|
|
18
|
+
alt?: string;
|
|
19
|
+
} | {
|
|
20
|
+
type: "progress";
|
|
21
|
+
current: number;
|
|
22
|
+
total: number;
|
|
23
|
+
label?: string;
|
|
24
|
+
} | {
|
|
25
|
+
type: "data";
|
|
26
|
+
data: Record<string, unknown>;
|
|
27
|
+
};
|
|
28
|
+
/** Tool execution status. **/
|
|
29
|
+
export type ToolStatus = {
|
|
30
|
+
/** Unique ID for this tool execution */
|
|
31
|
+
id: string;
|
|
32
|
+
/** Parent tool ID (for nested tool calls, e.g., agent sub-tools) */
|
|
33
|
+
parentId?: string;
|
|
34
|
+
/** Top-level status label — always available, suitable for minimal UI */
|
|
35
|
+
label: string;
|
|
36
|
+
/** Current state of the tool execution */
|
|
37
|
+
state: "running" | "completed" | "failed";
|
|
38
|
+
/** Optional rich content for clients that support it */
|
|
39
|
+
content?: ToolStatusContent[];
|
|
40
|
+
/** Whether this tool call is ending the conversation */
|
|
41
|
+
endingConvo?: boolean;
|
|
42
|
+
};
|
|
43
|
+
export type BaseResponseMetadata = {
|
|
44
|
+
/** Unique response ID */
|
|
45
|
+
responseId: string;
|
|
46
|
+
/** Model display name (e.g., "GPT-5") */
|
|
47
|
+
model: string;
|
|
48
|
+
/** Actual model ID (e.g., "openai/gpt-5.2") */
|
|
49
|
+
modelId: string;
|
|
50
|
+
/** Timestamp of first action (first stream chunk) */
|
|
51
|
+
firstTimestamp: number;
|
|
52
|
+
/** Timestamp of last action */
|
|
53
|
+
timestamp: number;
|
|
54
|
+
/** The participant who generated this response */
|
|
55
|
+
participantId?: string;
|
|
56
|
+
};
|
|
57
|
+
export type MessagePayload = {
|
|
58
|
+
/** The message content */
|
|
59
|
+
message: string;
|
|
60
|
+
/** The UUID of this message */
|
|
61
|
+
messageId: string;
|
|
62
|
+
/** Whether this message chunk is the final one */
|
|
63
|
+
completed: boolean;
|
|
64
|
+
};
|
|
65
|
+
export type ReasoningPayload = {
|
|
66
|
+
/** The reasoning content */
|
|
67
|
+
reasoning: string;
|
|
68
|
+
/** The UUID of this reasoning */
|
|
69
|
+
reasoningId: string;
|
|
70
|
+
/** Whether this reasoning chunk is the final one */
|
|
71
|
+
completed: boolean;
|
|
72
|
+
};
|
|
73
|
+
export type FilePayload = {
|
|
74
|
+
/** The file URL */
|
|
75
|
+
url?: string;
|
|
76
|
+
/** The media type of the file */
|
|
77
|
+
mediaType: string;
|
|
78
|
+
};
|
|
79
|
+
export type ToolPayload = {
|
|
80
|
+
/** The tool status */
|
|
81
|
+
status: ToolStatus;
|
|
82
|
+
};
|
|
83
|
+
export type ConvoStatusPayload = {
|
|
84
|
+
/** The state of the conversation.
|
|
85
|
+
* - `disabled`: The conversation has been permanently disabled and cannot be restarted.
|
|
86
|
+
* - `stopped`: The conversation has been stopped but can be restarted.
|
|
87
|
+
* - `started`: The conversation has been started/restarted.
|
|
88
|
+
*/
|
|
89
|
+
state?: "disabled" | "stopped" | "started";
|
|
90
|
+
/** Short summary generated for this conversation */
|
|
91
|
+
shortSummary?: string;
|
|
92
|
+
};
|
|
93
|
+
export type ResponseStatusPayload = {
|
|
94
|
+
completed: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* The reason why the engine finished the turn
|
|
97
|
+
*
|
|
98
|
+
* Can be one of the following:
|
|
99
|
+
* - `done`: model finished the turn
|
|
100
|
+
* - `error`: turn stopped because of an error
|
|
101
|
+
* - `max_steps`: turn finished because it reached the maximum number of steps defined in the conversation policy
|
|
102
|
+
* - `other`: turn stopped for other reasons
|
|
103
|
+
*/
|
|
104
|
+
turnFinishReason?: EngineTurnFinishReason;
|
|
105
|
+
/** Rich response metadata — only present on the final `completed: true` event.
|
|
106
|
+
* Populated by the post-processor after the response finishes and usage is available. */
|
|
107
|
+
metadata?: ResponseMetadata;
|
|
108
|
+
};
|
|
109
|
+
/** Token usage information */
|
|
110
|
+
export type TokenUsage = {
|
|
111
|
+
/** The total number of input (prompt) tokens used. */
|
|
112
|
+
inputTokens: number;
|
|
113
|
+
/** Detailed information about the input tokens. */
|
|
114
|
+
inputTokensDetails: {
|
|
115
|
+
/** The number of cached input tokens read. */
|
|
116
|
+
cachedTokens: number;
|
|
117
|
+
};
|
|
118
|
+
/** The total number of output (completion) tokens used. */
|
|
119
|
+
outputTokens: number;
|
|
120
|
+
/** Detailed information about the output tokens. */
|
|
121
|
+
outputTokensDetails: {
|
|
122
|
+
/** The number of reasoning tokens used. */
|
|
123
|
+
reasoningTokens: number;
|
|
124
|
+
};
|
|
125
|
+
/** The total number of tokens used. */
|
|
126
|
+
totalTokens: number;
|
|
127
|
+
/** Total cost of the turn in USD */
|
|
128
|
+
cost?: number | null;
|
|
129
|
+
/** Upstream cost breakdown from the provider. */
|
|
130
|
+
costDetails?: {
|
|
131
|
+
/** Total upstream inference cost. */
|
|
132
|
+
upstreamInferenceCost?: number | null;
|
|
133
|
+
/** Upstream input (prompt) cost. */
|
|
134
|
+
upstreamInferenceInputCost: number;
|
|
135
|
+
/** Upstream output (completion) cost. */
|
|
136
|
+
upstreamInferenceOutputCost: number;
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
export type EngineTurnFinishReason = "error" | "done" | "max_steps" | "other";
|
|
140
|
+
/** A user message event */
|
|
141
|
+
export type UserMessageEvent = {
|
|
142
|
+
type: "user_message";
|
|
143
|
+
payload: MessagePayload;
|
|
144
|
+
metadata: BaseResponseMetadata & {
|
|
145
|
+
userId: string;
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
export type MessageEvent = {
|
|
149
|
+
type: "message";
|
|
150
|
+
payload: MessagePayload;
|
|
151
|
+
metadata: BaseResponseMetadata & {
|
|
152
|
+
display?: DisplayEntity;
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
export type ReasoningEvent = {
|
|
156
|
+
type: "reasoning";
|
|
157
|
+
payload: ReasoningPayload;
|
|
158
|
+
metadata: BaseResponseMetadata;
|
|
159
|
+
};
|
|
160
|
+
export type FileEvent = {
|
|
161
|
+
type: "file";
|
|
162
|
+
payload: FilePayload & {
|
|
163
|
+
base64?: string;
|
|
164
|
+
};
|
|
165
|
+
metadata: BaseResponseMetadata & {
|
|
166
|
+
display?: DisplayEntity;
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
export type ToolEvent = {
|
|
170
|
+
type: "tool";
|
|
171
|
+
event: "call";
|
|
172
|
+
payload: ToolPayload;
|
|
173
|
+
metadata: BaseResponseMetadata;
|
|
174
|
+
} | {
|
|
175
|
+
type: "tool";
|
|
176
|
+
event: "input";
|
|
177
|
+
payload: {
|
|
178
|
+
toolName: string;
|
|
179
|
+
toolCallId: string;
|
|
180
|
+
input: string;
|
|
181
|
+
completed: boolean;
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
export type ResponseStatusEvent = {
|
|
185
|
+
type: "response_status";
|
|
186
|
+
payload: ResponseStatusPayload;
|
|
187
|
+
};
|
|
188
|
+
export type ConvoStatusEvent = {
|
|
189
|
+
type: "convo_status";
|
|
190
|
+
payload: ConvoStatusPayload;
|
|
191
|
+
};
|
|
192
|
+
/** A single routing attempt */
|
|
193
|
+
export type RoutingAttempt = {
|
|
194
|
+
/** The model ID attempted. */
|
|
195
|
+
model: string;
|
|
196
|
+
/** The provider slug (e.g., "anthropic", "google"). */
|
|
197
|
+
provider: string;
|
|
198
|
+
/** HTTP status code from the attempt. */
|
|
199
|
+
status: number;
|
|
200
|
+
};
|
|
201
|
+
/** Routing metadata — shows how the request was routed across providers.
|
|
202
|
+
* The last entry in `attempts` is the provider that ultimately served the response. */
|
|
203
|
+
export type RoutingMetadata = {
|
|
204
|
+
/** Routing strategy used (e.g., "auto", "direct", "pareto"). */
|
|
205
|
+
strategy: string;
|
|
206
|
+
/** Region the request was routed to. */
|
|
207
|
+
region: string | null;
|
|
208
|
+
/** All routing attempts in order — last entry is the successful provider. */
|
|
209
|
+
attempts: RoutingAttempt[];
|
|
210
|
+
/** Human-readable routing summary. */
|
|
211
|
+
summary: string;
|
|
212
|
+
};
|
|
213
|
+
/** Detailed metadata about a completed response. **/
|
|
214
|
+
export type ResponseMetadata = {
|
|
215
|
+
/** Unique response ID */
|
|
216
|
+
responseId: string;
|
|
217
|
+
/** Timestamp of first action (first stream chunk) */
|
|
218
|
+
firstTimestamp: number;
|
|
219
|
+
/** Timestamp of final response metadata creation */
|
|
220
|
+
timestamp: number;
|
|
221
|
+
/** The participant who generated this response */
|
|
222
|
+
participantId?: string;
|
|
223
|
+
/** Human-readable model name (e.g. "Claude-Sonnet") */
|
|
224
|
+
model: string;
|
|
225
|
+
/** Provider model identifier (e.g. "openai/gpt-5.2") */
|
|
226
|
+
modelId: string;
|
|
227
|
+
/** Model-specific information */
|
|
228
|
+
modelInfo: {
|
|
229
|
+
/** Maximum context length for this model */
|
|
230
|
+
contextLimit?: number;
|
|
231
|
+
};
|
|
232
|
+
/** Token usage details including cost from the provider (OpenRouter). */
|
|
233
|
+
usage: TokenUsage;
|
|
234
|
+
/** Compact estimated attribution of prompt/tool/message tokens for UI/debugging. */
|
|
235
|
+
usageBreakdown?: Record<string, unknown>;
|
|
236
|
+
/** Response timing */
|
|
237
|
+
timing: {
|
|
238
|
+
/** Milliseconds from turn start to first streamed event */
|
|
239
|
+
firstEventMs: number;
|
|
240
|
+
/** Total turn time in milliseconds */
|
|
241
|
+
totalMs: number;
|
|
242
|
+
};
|
|
243
|
+
/** Routing metadata — which providers were tried and who served the response. */
|
|
244
|
+
routing?: RoutingMetadata;
|
|
245
|
+
};
|
|
246
|
+
/** All possible events emitted during a conversation turn */
|
|
247
|
+
export type ConversationEvent = UserMessageEvent | MessageEvent | ReasoningEvent | FileEvent | ToolEvent | ResponseStatusEvent | ConvoStatusEvent;
|
|
248
|
+
/** Callback for streaming conversation events */
|
|
249
|
+
export type ConversationEventCallback = (event: ConversationEvent) => void;
|
|
250
|
+
export type AIResponseV5 = ConversationEvent;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AIResponseV5 } from "./ai_response_v5";
|
|
2
|
+
import { ErrorCode } from "../../error";
|
|
3
|
+
export type RequestFailResponseV5 = {
|
|
4
|
+
success: false;
|
|
5
|
+
data: {
|
|
6
|
+
code: ErrorCode;
|
|
7
|
+
error: string;
|
|
8
|
+
message: string;
|
|
9
|
+
quitStream?: boolean;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export type RequestSuccessResponseV5 = {
|
|
13
|
+
success: true;
|
|
14
|
+
data: {
|
|
15
|
+
response: AIResponseV5;
|
|
16
|
+
convoId?: string;
|
|
17
|
+
quitStream?: boolean;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export type RequestResponseV5 = RequestFailResponseV5 | RequestSuccessResponseV5;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./dialogue_response_v5"), exports);
|
|
18
|
+
__exportStar(require("./ai_response_v5"), exports);
|
|
19
|
+
__exportStar(require("./turn_registry_v5"), exports);
|
|
@@ -16,4 +16,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./response/v3"), exports);
|
|
18
18
|
__exportStar(require("./response/v4"), exports);
|
|
19
|
+
__exportStar(require("./response/v5"), exports);
|
|
19
20
|
__exportStar(require("./error"), exports);
|
package/package.json
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@butlerbot/sdk",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "The official ButlerBot SDK",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
|
-
"files": [
|
|
8
|
-
"dist/**/*"
|
|
9
|
-
],
|
|
10
|
-
"scripts": {
|
|
11
|
-
"build": "tsc",
|
|
12
|
-
"pack": "npm run build && npm pack --pack-destination=\"G:\\tarballs\"",
|
|
13
|
-
"update": "npm run build && npm publish --access public",
|
|
14
|
-
"update:alpha": "npm run build && npm publish --access public --tag alpha",
|
|
15
|
-
"test": "jest"
|
|
16
|
-
},
|
|
17
|
-
"keywords": [
|
|
18
|
-
"butlerbot",
|
|
19
|
-
"alfred",
|
|
20
|
-
"sdk",
|
|
21
|
-
"chatbot",
|
|
22
|
-
"ai",
|
|
23
|
-
"butler"
|
|
24
|
-
],
|
|
25
|
-
"author": "Fragly",
|
|
26
|
-
"license": "MIT",
|
|
27
|
-
"repository": {
|
|
28
|
-
"type": "git",
|
|
29
|
-
"url": "git+https://github.com/isdevco/alfred5_sdk.git"
|
|
30
|
-
},
|
|
31
|
-
"bugs": {
|
|
32
|
-
"url": "https://github.com/isdevco/alfred5_sdk/issues"
|
|
33
|
-
},
|
|
34
|
-
"homepage": "https://butlerbot.net",
|
|
35
|
-
"engines": {
|
|
36
|
-
"node": ">=14.0.0"
|
|
37
|
-
},
|
|
38
|
-
"devDependencies": {
|
|
39
|
-
"@types/node": "^22.13.10",
|
|
40
|
-
"typescript": "^5.8.2"
|
|
41
|
-
},
|
|
42
|
-
"dependencies": {
|
|
43
|
-
"eventsource": "^3.0.5"
|
|
44
|
-
}
|
|
45
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@butlerbot/sdk",
|
|
3
|
+
"version": "0.0.18-alpha.0",
|
|
4
|
+
"description": "The official ButlerBot SDK",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist/**/*"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"pack": "npm run build && npm pack --pack-destination=\"G:\\tarballs\"",
|
|
13
|
+
"update": "npm run build && npm publish --access public",
|
|
14
|
+
"update:alpha": "npm run build && npm publish --access public --tag alpha",
|
|
15
|
+
"test": "jest"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"butlerbot",
|
|
19
|
+
"alfred",
|
|
20
|
+
"sdk",
|
|
21
|
+
"chatbot",
|
|
22
|
+
"ai",
|
|
23
|
+
"butler"
|
|
24
|
+
],
|
|
25
|
+
"author": "Fragly",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/isdevco/alfred5_sdk.git"
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/isdevco/alfred5_sdk/issues"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://butlerbot.net",
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=14.0.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^22.13.10",
|
|
40
|
+
"typescript": "^5.8.2"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"eventsource": "^3.0.5"
|
|
44
|
+
}
|
|
45
|
+
}
|