@realtimex/sdk 1.7.28 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-ORAAYW4C.mjs +271 -0
- package/dist/errors-C98IGxYU.d.mts +168 -0
- package/dist/errors-C98IGxYU.d.ts +168 -0
- package/dist/index.d.mts +8 -2048
- package/dist/index.d.ts +8 -2048
- package/dist/index.js +168 -4723
- package/dist/index.mjs +7 -3783
- package/dist/v1/index.d.mts +3 -56
- package/dist/v1/index.d.ts +3 -56
- package/dist/v1/index.js +2 -756
- package/dist/v1/index.mjs +2 -42
- package/package.json +6 -7
- package/dist/chunk-Z5GAUBIM.mjs +0 -993
- package/dist/errors-NufsyIZ-.d.mts +0 -678
- package/dist/errors-NufsyIZ-.d.ts +0 -678
- package/skills/realtimex-moderator-sdk/SKILL.md +0 -102
- package/skills/realtimex-moderator-sdk/references/activities.md +0 -14
- package/skills/realtimex-moderator-sdk/references/agents.md +0 -13
- package/skills/realtimex-moderator-sdk/references/api-reference/acpagent.md +0 -105
- package/skills/realtimex-moderator-sdk/references/api-reference/activities.md +0 -24
- package/skills/realtimex-moderator-sdk/references/api-reference/agent.md +0 -27
- package/skills/realtimex-moderator-sdk/references/api-reference/api.md +0 -17
- package/skills/realtimex-moderator-sdk/references/api-reference/auth.md +0 -36
- package/skills/realtimex-moderator-sdk/references/api-reference/contract.md +0 -27
- package/skills/realtimex-moderator-sdk/references/api-reference/core.md +0 -40
- package/skills/realtimex-moderator-sdk/references/api-reference/database.md +0 -24
- package/skills/realtimex-moderator-sdk/references/api-reference/index.md +0 -43
- package/skills/realtimex-moderator-sdk/references/api-reference/llm.md +0 -176
- package/skills/realtimex-moderator-sdk/references/api-reference/mcp.md +0 -50
- package/skills/realtimex-moderator-sdk/references/api-reference/port.md +0 -21
- package/skills/realtimex-moderator-sdk/references/api-reference/stt.md +0 -15
- package/skills/realtimex-moderator-sdk/references/api-reference/task.md +0 -62
- package/skills/realtimex-moderator-sdk/references/api-reference/tts.md +0 -18
- package/skills/realtimex-moderator-sdk/references/api-reference/v1-acpauth.md +0 -21
- package/skills/realtimex-moderator-sdk/references/api-reference/v1-acpcommands.md +0 -15
- package/skills/realtimex-moderator-sdk/references/api-reference/v1-admin.md +0 -48
- package/skills/realtimex-moderator-sdk/references/api-reference/v1-auth.md +0 -18
- package/skills/realtimex-moderator-sdk/references/api-reference/v1-channels.md +0 -78
- package/skills/realtimex-moderator-sdk/references/api-reference/v1-credentials.md +0 -27
- package/skills/realtimex-moderator-sdk/references/api-reference/v1-customthemes.md +0 -24
- package/skills/realtimex-moderator-sdk/references/api-reference/v1-desktopbrowser.md +0 -39
- package/skills/realtimex-moderator-sdk/references/api-reference/v1-desktopembed.md +0 -24
- package/skills/realtimex-moderator-sdk/references/api-reference/v1-desktopruntimesessions.md +0 -33
- package/skills/realtimex-moderator-sdk/references/api-reference/v1-document.md +0 -39
- package/skills/realtimex-moderator-sdk/references/api-reference/v1-embed.md +0 -27
- package/skills/realtimex-moderator-sdk/references/api-reference/v1-openai.md +0 -21
- package/skills/realtimex-moderator-sdk/references/api-reference/v1-sttapi.md +0 -12
- package/skills/realtimex-moderator-sdk/references/api-reference/v1-system.md +0 -36
- package/skills/realtimex-moderator-sdk/references/api-reference/v1-thread.md +0 -26
- package/skills/realtimex-moderator-sdk/references/api-reference/v1-users.md +0 -15
- package/skills/realtimex-moderator-sdk/references/api-reference/v1-workspace.md +0 -39
- package/skills/realtimex-moderator-sdk/references/api-reference/webhook.md +0 -13
- package/skills/realtimex-moderator-sdk/references/api-reference.md +0 -1330
- package/skills/realtimex-moderator-sdk/references/app-concepts.md +0 -1276
- package/skills/realtimex-moderator-sdk/references/browser.md +0 -27
- package/skills/realtimex-moderator-sdk/references/channels.md +0 -189
- package/skills/realtimex-moderator-sdk/references/credentials.md +0 -111
- package/skills/realtimex-moderator-sdk/references/known-issues.md +0 -237
- package/skills/realtimex-moderator-sdk/references/llm.md +0 -13
- package/skills/realtimex-moderator-sdk/references/mcp.md +0 -13
- package/skills/realtimex-moderator-sdk/references/permissions.md +0 -30
- package/skills/realtimex-moderator-sdk/references/quickstart.md +0 -16
- package/skills/realtimex-moderator-sdk/references/terminal-sessions.md +0 -34
- package/skills/realtimex-moderator-sdk/references/workspaces.md +0 -20
- package/skills/realtimex-moderator-sdk/scripts/lib/sdk-init.js +0 -171
- package/skills/realtimex-moderator-sdk/scripts/rtx.js +0 -1359
package/dist/index.mjs
CHANGED
|
@@ -6,3689 +6,22 @@ import {
|
|
|
6
6
|
ServerError,
|
|
7
7
|
V1ApiNamespace,
|
|
8
8
|
ValidationError
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
|
|
11
|
-
// src/modules/api.ts
|
|
12
|
-
var PermissionDeniedError = class extends Error {
|
|
13
|
-
constructor(permission, message, code = "PERMISSION_DENIED") {
|
|
14
|
-
super(message || `Permission '${permission}' was denied`);
|
|
15
|
-
this.name = "PermissionDeniedError";
|
|
16
|
-
this.permission = permission;
|
|
17
|
-
this.code = code;
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
var PermissionRequiredError = class extends Error {
|
|
21
|
-
constructor(permission, message, code = "PERMISSION_REQUIRED") {
|
|
22
|
-
super(message || `Permission '${permission}' is required`);
|
|
23
|
-
this.name = "PermissionRequiredError";
|
|
24
|
-
this.permission = permission;
|
|
25
|
-
this.code = code;
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
var ApiModule = class {
|
|
29
|
-
constructor(realtimexUrl, appId, appName, apiKey) {
|
|
30
|
-
this.realtimexUrl = realtimexUrl.replace(/\/$/, "");
|
|
31
|
-
this.appId = appId;
|
|
32
|
-
this.appName = appName || process.env.RTX_APP_NAME || "Local App";
|
|
33
|
-
this.apiKey = apiKey;
|
|
34
|
-
}
|
|
35
|
-
getHeaders() {
|
|
36
|
-
const headers = {
|
|
37
|
-
"Content-Type": "application/json"
|
|
38
|
-
};
|
|
39
|
-
if (this.apiKey) {
|
|
40
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
41
|
-
}
|
|
42
|
-
if (this.appId) {
|
|
43
|
-
headers["x-app-id"] = this.appId;
|
|
44
|
-
}
|
|
45
|
-
return headers;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Request a single permission from Electron via internal API
|
|
49
|
-
*/
|
|
50
|
-
async requestPermission(permission) {
|
|
51
|
-
try {
|
|
52
|
-
const response = await fetch(`${this.realtimexUrl}/api/local-apps/request-permission`, {
|
|
53
|
-
method: "POST",
|
|
54
|
-
headers: { "Content-Type": "application/json" },
|
|
55
|
-
body: JSON.stringify({
|
|
56
|
-
app_id: this.appId,
|
|
57
|
-
app_name: this.appName,
|
|
58
|
-
permission
|
|
59
|
-
})
|
|
60
|
-
});
|
|
61
|
-
const data = await response.json();
|
|
62
|
-
return data.granted === true;
|
|
63
|
-
} catch (error) {
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Make an API call with automatic permission handling
|
|
69
|
-
*/
|
|
70
|
-
async apiCall(method, endpoint, options) {
|
|
71
|
-
const url = `${this.realtimexUrl}${endpoint}`;
|
|
72
|
-
const response = await fetch(url, {
|
|
73
|
-
method,
|
|
74
|
-
headers: this.getHeaders(),
|
|
75
|
-
...options
|
|
76
|
-
});
|
|
77
|
-
const data = await response.json();
|
|
78
|
-
if (response.status === 403) {
|
|
79
|
-
const errorCode = data.error;
|
|
80
|
-
const permission = data.permission;
|
|
81
|
-
const message = data.message;
|
|
82
|
-
if (errorCode === "PERMISSION_REQUIRED" && permission) {
|
|
83
|
-
const granted = await this.requestPermission(permission);
|
|
84
|
-
if (granted) {
|
|
85
|
-
return this.apiCall(method, endpoint, options);
|
|
86
|
-
} else {
|
|
87
|
-
throw new PermissionDeniedError(permission, message);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (errorCode === "PERMISSION_DENIED") {
|
|
91
|
-
throw new PermissionDeniedError(permission, message);
|
|
92
|
-
}
|
|
93
|
-
throw new Error(data.error || "Permission denied");
|
|
94
|
-
}
|
|
95
|
-
if (!response.ok) {
|
|
96
|
-
throw new Error(data.error || `API call failed: ${response.status}`);
|
|
97
|
-
}
|
|
98
|
-
return data;
|
|
99
|
-
}
|
|
100
|
-
async getAgents() {
|
|
101
|
-
const data = await this.apiCall("GET", "/agents");
|
|
102
|
-
return data.agents;
|
|
103
|
-
}
|
|
104
|
-
async getWorkspaces() {
|
|
105
|
-
const data = await this.apiCall("GET", "/workspaces");
|
|
106
|
-
return data.workspaces;
|
|
107
|
-
}
|
|
108
|
-
async getThreads(workspaceSlug) {
|
|
109
|
-
const data = await this.apiCall("GET", `/workspaces/${encodeURIComponent(workspaceSlug)}/threads`);
|
|
110
|
-
return data.threads;
|
|
111
|
-
}
|
|
112
|
-
async getTask(taskUuid) {
|
|
113
|
-
const data = await this.apiCall("GET", `/task/${encodeURIComponent(taskUuid)}`);
|
|
114
|
-
return { ...data.task, runs: data.runs };
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
// src/modules/activities.ts
|
|
119
|
-
var ActivitiesModule = class {
|
|
120
|
-
constructor(realtimexUrl, appId, appName, apiKey) {
|
|
121
|
-
this.baseUrl = realtimexUrl.replace(/\/$/, "");
|
|
122
|
-
this.appId = appId;
|
|
123
|
-
this.appName = appName || process.env.RTX_APP_NAME || "Local App";
|
|
124
|
-
this.apiKey = apiKey;
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Request a single permission from Electron via internal API
|
|
128
|
-
*/
|
|
129
|
-
async requestPermission(permission) {
|
|
130
|
-
try {
|
|
131
|
-
const response = await fetch(`${this.baseUrl}/api/local-apps/request-permission`, {
|
|
132
|
-
method: "POST",
|
|
133
|
-
headers: { "Content-Type": "application/json" },
|
|
134
|
-
body: JSON.stringify({
|
|
135
|
-
app_id: this.appId,
|
|
136
|
-
app_name: this.appName,
|
|
137
|
-
permission
|
|
138
|
-
})
|
|
139
|
-
});
|
|
140
|
-
const data = await response.json();
|
|
141
|
-
return data.granted === true;
|
|
142
|
-
} catch (error) {
|
|
143
|
-
console.error("[SDK] Permission request failed:", error);
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
async request(path, options = {}) {
|
|
148
|
-
const url = `${this.baseUrl}${path}`;
|
|
149
|
-
const headers = {
|
|
150
|
-
"Content-Type": "application/json"
|
|
151
|
-
};
|
|
152
|
-
if (this.apiKey) {
|
|
153
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
154
|
-
}
|
|
155
|
-
if (this.appId) {
|
|
156
|
-
headers["x-app-id"] = this.appId;
|
|
157
|
-
}
|
|
158
|
-
const response = await fetch(url, {
|
|
159
|
-
...options,
|
|
160
|
-
headers: {
|
|
161
|
-
...headers,
|
|
162
|
-
...options.headers
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
const data = await response.json();
|
|
166
|
-
if (response.status === 403) {
|
|
167
|
-
const errorCode = data.error;
|
|
168
|
-
const permission = data.permission;
|
|
169
|
-
const message = data.message;
|
|
170
|
-
if (errorCode === "PERMISSION_REQUIRED" && permission) {
|
|
171
|
-
const granted = await this.requestPermission(permission);
|
|
172
|
-
if (granted) {
|
|
173
|
-
return this.request(path, options);
|
|
174
|
-
} else {
|
|
175
|
-
throw new PermissionDeniedError(permission, message);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (errorCode === "PERMISSION_DENIED") {
|
|
179
|
-
throw new PermissionDeniedError(permission, message);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
if (!response.ok) {
|
|
183
|
-
throw new Error(data.error || `Request failed: ${response.status}`);
|
|
184
|
-
}
|
|
185
|
-
return data;
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Insert a new activity
|
|
189
|
-
*/
|
|
190
|
-
async insert(rawData) {
|
|
191
|
-
const result = await this.request("/activities", {
|
|
192
|
-
method: "POST",
|
|
193
|
-
body: JSON.stringify({ raw_data: rawData })
|
|
194
|
-
});
|
|
195
|
-
return result.data;
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* Update an existing activity
|
|
199
|
-
*/
|
|
200
|
-
async update(id, updates) {
|
|
201
|
-
const result = await this.request(`/activities/${id}`, {
|
|
202
|
-
method: "PATCH",
|
|
203
|
-
body: JSON.stringify(updates)
|
|
204
|
-
});
|
|
205
|
-
return result.data;
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Delete an activity
|
|
209
|
-
*/
|
|
210
|
-
async delete(id) {
|
|
211
|
-
await this.request(`/activities/${id}`, {
|
|
212
|
-
method: "DELETE"
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Get a single activity by ID
|
|
217
|
-
*/
|
|
218
|
-
async get(id) {
|
|
219
|
-
try {
|
|
220
|
-
const result = await this.request(`/activities/${id}`);
|
|
221
|
-
return result.data;
|
|
222
|
-
} catch (error) {
|
|
223
|
-
if (error.message?.includes("not found")) return null;
|
|
224
|
-
throw error;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* List activities with optional filters
|
|
229
|
-
*/
|
|
230
|
-
async list(options) {
|
|
231
|
-
const params = new URLSearchParams();
|
|
232
|
-
if (options?.status) params.set("status", options.status);
|
|
233
|
-
if (options?.limit) params.set("limit", String(options.limit));
|
|
234
|
-
if (options?.offset) params.set("offset", String(options.offset));
|
|
235
|
-
const query = params.toString() ? `?${params}` : "";
|
|
236
|
-
const result = await this.request(`/activities${query}`);
|
|
237
|
-
return result.data;
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
// src/modules/contract.ts
|
|
242
|
-
import { createHash, createHmac, randomUUID } from "crypto";
|
|
243
|
-
var LOCAL_APP_CONTRACT_VERSION = "local-app-contract/v1";
|
|
244
|
-
var CONTRACT_SIGNATURE_HEADER = "x-rtx-contract-signature";
|
|
245
|
-
var CONTRACT_EVENT_ID_HEADER = "x-rtx-event-id";
|
|
246
|
-
var CONTRACT_SIGNATURE_ALGORITHM = "sha256";
|
|
247
|
-
var CONTRACT_ATTEMPT_PREFIX = "run-";
|
|
248
|
-
var CONTRACT_EVENT_ALIASES = {
|
|
249
|
-
"trigger-agent": "task.trigger",
|
|
250
|
-
"task.trigger": "task.trigger",
|
|
251
|
-
ping: "system.ping",
|
|
252
|
-
"system.ping": "system.ping",
|
|
253
|
-
claim: "task.claimed",
|
|
254
|
-
claimed: "task.claimed",
|
|
255
|
-
"task.claimed": "task.claimed",
|
|
256
|
-
"task-start": "task.started",
|
|
257
|
-
start: "task.started",
|
|
258
|
-
"task.started": "task.started",
|
|
259
|
-
"task-progress": "task.progress",
|
|
260
|
-
progress: "task.progress",
|
|
261
|
-
processing: "task.progress",
|
|
262
|
-
"task.progress": "task.progress",
|
|
263
|
-
"task-complete": "task.completed",
|
|
264
|
-
complete: "task.completed",
|
|
265
|
-
completed: "task.completed",
|
|
266
|
-
"task.completed": "task.completed",
|
|
267
|
-
"task-fail": "task.failed",
|
|
268
|
-
fail: "task.failed",
|
|
269
|
-
failed: "task.failed",
|
|
270
|
-
"task.failed": "task.failed",
|
|
271
|
-
"task-cancel": "task.canceled",
|
|
272
|
-
"task-cancelled": "task.canceled",
|
|
273
|
-
"task-canceled": "task.canceled",
|
|
274
|
-
cancel: "task.canceled",
|
|
275
|
-
cancelled: "task.canceled",
|
|
276
|
-
canceled: "task.canceled",
|
|
277
|
-
"task.canceled": "task.canceled"
|
|
278
|
-
};
|
|
279
|
-
var CONTRACT_LEGACY_ACTIONS = {
|
|
280
|
-
"task.trigger": "trigger-agent",
|
|
281
|
-
"system.ping": "ping",
|
|
282
|
-
"task.claimed": "claim",
|
|
283
|
-
"task.started": "start",
|
|
284
|
-
"task.progress": "progress",
|
|
285
|
-
"task.completed": "complete",
|
|
286
|
-
"task.failed": "fail",
|
|
287
|
-
"task.canceled": "cancel"
|
|
288
|
-
};
|
|
289
|
-
function normalizeContractEvent(eventLike) {
|
|
290
|
-
if (!eventLike || typeof eventLike !== "string") return null;
|
|
291
|
-
const normalized = CONTRACT_EVENT_ALIASES[eventLike.trim().toLowerCase()];
|
|
292
|
-
return normalized || null;
|
|
293
|
-
}
|
|
294
|
-
function normalizeAttemptId(attemptLike) {
|
|
295
|
-
if (attemptLike === null || attemptLike === void 0) return void 0;
|
|
296
|
-
if (typeof attemptLike === "number" && Number.isInteger(attemptLike) && attemptLike > 0) {
|
|
297
|
-
return `${CONTRACT_ATTEMPT_PREFIX}${attemptLike}`;
|
|
298
|
-
}
|
|
299
|
-
if (typeof attemptLike !== "string") return void 0;
|
|
300
|
-
const trimmed = attemptLike.trim();
|
|
301
|
-
if (!trimmed) return void 0;
|
|
302
|
-
if (trimmed.startsWith(CONTRACT_ATTEMPT_PREFIX)) return trimmed;
|
|
303
|
-
if (/^\d+$/.test(trimmed)) return `${CONTRACT_ATTEMPT_PREFIX}${trimmed}`;
|
|
304
|
-
return trimmed;
|
|
305
|
-
}
|
|
306
|
-
function parseAttemptRunId(attemptLike) {
|
|
307
|
-
const attemptId = normalizeAttemptId(attemptLike);
|
|
308
|
-
if (!attemptId) return null;
|
|
309
|
-
const matched = attemptId.match(/^run[-_:]?(\d+)$/i);
|
|
310
|
-
if (!matched) return null;
|
|
311
|
-
const value = Number(matched[1]);
|
|
312
|
-
return Number.isInteger(value) && value > 0 ? value : null;
|
|
313
|
-
}
|
|
314
|
-
function hashContractPayload(payload) {
|
|
315
|
-
const normalized = payload && typeof payload === "object" ? payload : { value: payload ?? null };
|
|
316
|
-
return createHash("sha256").update(JSON.stringify(normalized)).digest("hex");
|
|
317
|
-
}
|
|
318
|
-
function createContractEventId() {
|
|
319
|
-
return randomUUID();
|
|
320
|
-
}
|
|
321
|
-
function buildContractSignatureMessage({
|
|
322
|
-
eventId,
|
|
323
|
-
eventType,
|
|
324
|
-
taskId,
|
|
325
|
-
attemptId,
|
|
326
|
-
timestamp,
|
|
327
|
-
payload
|
|
328
|
-
}) {
|
|
329
|
-
return [
|
|
330
|
-
String(eventId || ""),
|
|
331
|
-
String(normalizeContractEvent(String(eventType || "")) || eventType || ""),
|
|
332
|
-
String(taskId || ""),
|
|
333
|
-
String(normalizeAttemptId(attemptId) || ""),
|
|
334
|
-
String(timestamp || ""),
|
|
335
|
-
hashContractPayload(payload ?? {})
|
|
336
|
-
].join(".");
|
|
337
|
-
}
|
|
338
|
-
function signContractEvent(input) {
|
|
339
|
-
const signatureMessage = buildContractSignatureMessage(input);
|
|
340
|
-
const digest = createHmac(CONTRACT_SIGNATURE_ALGORITHM, input.secret).update(signatureMessage).digest("hex");
|
|
341
|
-
return `${CONTRACT_SIGNATURE_ALGORITHM}=${digest}`;
|
|
342
|
-
}
|
|
343
|
-
function canonicalEventToLegacyAction(eventLike) {
|
|
344
|
-
const normalized = normalizeContractEvent(eventLike);
|
|
345
|
-
if (!normalized) return null;
|
|
346
|
-
return CONTRACT_LEGACY_ACTIONS[normalized] || null;
|
|
347
|
-
}
|
|
348
|
-
function buildContractIdempotencyKey({
|
|
349
|
-
taskId,
|
|
350
|
-
eventType,
|
|
351
|
-
eventId,
|
|
352
|
-
attemptId,
|
|
353
|
-
machineId,
|
|
354
|
-
timestamp,
|
|
355
|
-
payload
|
|
356
|
-
}) {
|
|
357
|
-
const canonicalEvent = normalizeContractEvent(eventType) || eventType;
|
|
358
|
-
if (eventId) {
|
|
359
|
-
const eventToken = createHash("sha256").update(String(eventId)).digest("hex");
|
|
360
|
-
return `${taskId}:${canonicalEvent}:event:${eventToken}`;
|
|
361
|
-
}
|
|
362
|
-
const hashInput = {
|
|
363
|
-
task_id: taskId,
|
|
364
|
-
event_type: canonicalEvent,
|
|
365
|
-
attempt_id: normalizeAttemptId(attemptId),
|
|
366
|
-
machine_id: machineId || null,
|
|
367
|
-
timestamp: timestamp || null,
|
|
368
|
-
payload_hash: hashContractPayload(payload ?? {})
|
|
369
|
-
};
|
|
370
|
-
const token = createHash("sha256").update(JSON.stringify(hashInput)).digest("hex");
|
|
371
|
-
return `${taskId}:${canonicalEvent}:hash:${token}`;
|
|
372
|
-
}
|
|
373
|
-
var ContractModule = class {
|
|
374
|
-
constructor(realtimexUrl, appName, appId, apiKey) {
|
|
375
|
-
this.cachedContract = null;
|
|
376
|
-
this.cachedCapabilities = null;
|
|
377
|
-
this.cachedCapabilityCatalogHash = null;
|
|
378
|
-
this.realtimexUrl = realtimexUrl.replace(/\/$/, "");
|
|
379
|
-
this.appName = appName;
|
|
380
|
-
this.appId = appId;
|
|
381
|
-
this.apiKey = apiKey;
|
|
382
|
-
}
|
|
383
|
-
async requestPermission(permission) {
|
|
384
|
-
try {
|
|
385
|
-
const response = await fetch(`${this.realtimexUrl}/api/local-apps/request-permission`, {
|
|
386
|
-
method: "POST",
|
|
387
|
-
headers: { "Content-Type": "application/json" },
|
|
388
|
-
body: JSON.stringify({
|
|
389
|
-
app_id: this.appId,
|
|
390
|
-
app_name: this.appName,
|
|
391
|
-
permission
|
|
392
|
-
})
|
|
393
|
-
});
|
|
394
|
-
const data = await response.json();
|
|
395
|
-
return data.granted === true;
|
|
396
|
-
} catch {
|
|
397
|
-
return false;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
async request(path, options = {}) {
|
|
401
|
-
const url = `${this.realtimexUrl}${path}`;
|
|
402
|
-
const headers = {
|
|
403
|
-
"Content-Type": "application/json",
|
|
404
|
-
...options.headers
|
|
405
|
-
};
|
|
406
|
-
if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
|
|
407
|
-
if (this.appId) headers["x-app-id"] = this.appId;
|
|
408
|
-
const response = await fetch(url, {
|
|
409
|
-
method: options.method || "GET",
|
|
410
|
-
headers,
|
|
411
|
-
body: options.body
|
|
412
|
-
});
|
|
413
|
-
const data = await response.json();
|
|
414
|
-
if (response.status === 403) {
|
|
415
|
-
const errorCode = data.error;
|
|
416
|
-
const permission = data.permission;
|
|
417
|
-
const message = data.message;
|
|
418
|
-
if (errorCode === "PERMISSION_REQUIRED" && permission) {
|
|
419
|
-
const granted = await this.requestPermission(permission);
|
|
420
|
-
if (granted) return this.request(path, options);
|
|
421
|
-
throw new PermissionDeniedError(permission, message);
|
|
422
|
-
}
|
|
423
|
-
if (errorCode === "PERMISSION_DENIED") {
|
|
424
|
-
throw new PermissionDeniedError(permission, message);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
if (!response.ok) {
|
|
428
|
-
throw new Error(data.error || `Request failed: ${response.status}`);
|
|
429
|
-
}
|
|
430
|
-
return data;
|
|
431
|
-
}
|
|
432
|
-
async getLocalAppV1(forceRefresh = false) {
|
|
433
|
-
if (!forceRefresh && this.cachedContract) return this.cachedContract;
|
|
434
|
-
const data = await this.request("/contracts/local-app/v1");
|
|
435
|
-
this.cachedContract = data.contract;
|
|
436
|
-
if (Array.isArray(data.contract?.capabilities)) {
|
|
437
|
-
this.cachedCapabilities = data.contract.capabilities;
|
|
438
|
-
this.cachedCapabilityCatalogHash = data.contract.catalog_hash || null;
|
|
439
|
-
}
|
|
440
|
-
return data.contract;
|
|
441
|
-
}
|
|
442
|
-
async listCapabilities(forceRefresh = false) {
|
|
443
|
-
if (!forceRefresh && this.cachedCapabilities) return this.cachedCapabilities;
|
|
444
|
-
const data = await this.request(
|
|
445
|
-
"/contracts/local-app/v1/capabilities"
|
|
446
|
-
);
|
|
447
|
-
this.cachedCapabilities = Array.isArray(data.capabilities) ? data.capabilities : [];
|
|
448
|
-
this.cachedCapabilityCatalogHash = data.catalog_hash || null;
|
|
449
|
-
return this.cachedCapabilities;
|
|
450
|
-
}
|
|
451
|
-
async searchCapabilities(query) {
|
|
452
|
-
const normalizedQuery = String(query || "").trim();
|
|
453
|
-
if (!normalizedQuery) {
|
|
454
|
-
throw new Error("searchCapabilities requires a non-empty query");
|
|
455
|
-
}
|
|
456
|
-
const encodedQuery = encodeURIComponent(normalizedQuery);
|
|
457
|
-
const data = await this.request(
|
|
458
|
-
`/contracts/local-app/v1/capabilities/search?q=${encodedQuery}`
|
|
459
|
-
);
|
|
460
|
-
return Array.isArray(data.capabilities) ? data.capabilities : [];
|
|
461
|
-
}
|
|
462
|
-
async describeCapability(capabilityId) {
|
|
463
|
-
const normalizedCapabilityId = String(capabilityId || "").trim();
|
|
464
|
-
if (!normalizedCapabilityId) {
|
|
465
|
-
throw new Error("describeCapability requires a non-empty capability id");
|
|
466
|
-
}
|
|
467
|
-
const encodedCapabilityId = encodeURIComponent(normalizedCapabilityId);
|
|
468
|
-
const data = await this.request(
|
|
469
|
-
`/contracts/local-app/v1/capabilities/${encodedCapabilityId}`
|
|
470
|
-
);
|
|
471
|
-
return data.capability;
|
|
472
|
-
}
|
|
473
|
-
// Alias for agentic contract flow naming.
|
|
474
|
-
async search(query) {
|
|
475
|
-
return this.searchCapabilities(query);
|
|
476
|
-
}
|
|
477
|
-
// Alias for agentic contract flow naming.
|
|
478
|
-
async describe(capabilityId) {
|
|
479
|
-
return this.describeCapability(capabilityId);
|
|
480
|
-
}
|
|
481
|
-
async invoke(payload) {
|
|
482
|
-
const capabilityId = String(payload?.capability_id || "").trim();
|
|
483
|
-
if (!capabilityId) {
|
|
484
|
-
throw new Error("invoke requires payload.capability_id");
|
|
485
|
-
}
|
|
486
|
-
if (payload.auto_run && (!payload.agent_name || !payload.workspace_slug)) {
|
|
487
|
-
throw new Error("auto_run requires agent_name and workspace_slug");
|
|
488
|
-
}
|
|
489
|
-
const args = payload.args && typeof payload.args === "object" && !Array.isArray(payload.args) ? { ...payload.args } : {};
|
|
490
|
-
if (!args.capability) {
|
|
491
|
-
args.capability = capabilityId;
|
|
492
|
-
}
|
|
493
|
-
return this.request("/webhooks/realtimex", {
|
|
494
|
-
method: "POST",
|
|
495
|
-
body: JSON.stringify({
|
|
496
|
-
app_name: this.appName,
|
|
497
|
-
app_id: this.appId,
|
|
498
|
-
event: "task.trigger",
|
|
499
|
-
event_id: payload.event_id || createContractEventId(),
|
|
500
|
-
attempt_id: normalizeAttemptId(payload.attempt_id),
|
|
501
|
-
payload: {
|
|
502
|
-
raw_data: args,
|
|
503
|
-
auto_run: payload.auto_run ?? false,
|
|
504
|
-
agent_name: payload.agent_name,
|
|
505
|
-
workspace_slug: payload.workspace_slug,
|
|
506
|
-
thread_slug: payload.thread_slug,
|
|
507
|
-
prompt: payload.prompt ?? ""
|
|
508
|
-
}
|
|
509
|
-
})
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
getCachedCatalogHash() {
|
|
513
|
-
return this.cachedCapabilityCatalogHash;
|
|
514
|
-
}
|
|
515
|
-
clearCache() {
|
|
516
|
-
this.cachedContract = null;
|
|
517
|
-
this.cachedCapabilities = null;
|
|
518
|
-
this.cachedCapabilityCatalogHash = null;
|
|
519
|
-
}
|
|
520
|
-
};
|
|
521
|
-
|
|
522
|
-
// src/modules/webhook.ts
|
|
523
|
-
var WebhookModule = class {
|
|
524
|
-
constructor(realtimexUrl, appName, appId, apiKey) {
|
|
525
|
-
this.realtimexUrl = realtimexUrl.replace(/\/$/, "");
|
|
526
|
-
this.appName = appName;
|
|
527
|
-
this.appId = appId;
|
|
528
|
-
this.apiKey = apiKey;
|
|
529
|
-
}
|
|
530
|
-
/**
|
|
531
|
-
* Request a single permission from Electron via internal API
|
|
532
|
-
*/
|
|
533
|
-
async requestPermission(permission) {
|
|
534
|
-
try {
|
|
535
|
-
const response = await fetch(`${this.realtimexUrl}/api/local-apps/request-permission`, {
|
|
536
|
-
method: "POST",
|
|
537
|
-
headers: { "Content-Type": "application/json" },
|
|
538
|
-
body: JSON.stringify({
|
|
539
|
-
app_id: this.appId,
|
|
540
|
-
app_name: this.appName,
|
|
541
|
-
permission
|
|
542
|
-
})
|
|
543
|
-
});
|
|
544
|
-
const data = await response.json();
|
|
545
|
-
return data.granted === true;
|
|
546
|
-
} catch (error) {
|
|
547
|
-
console.error("[SDK] Permission request failed:", error);
|
|
548
|
-
return false;
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
async request(path, options = {}) {
|
|
552
|
-
const url = `${this.realtimexUrl}${path}`;
|
|
553
|
-
const headers = {
|
|
554
|
-
"Content-Type": "application/json",
|
|
555
|
-
...options.headers
|
|
556
|
-
};
|
|
557
|
-
if (this.apiKey) {
|
|
558
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
559
|
-
}
|
|
560
|
-
if (this.appId) {
|
|
561
|
-
headers["x-app-id"] = this.appId;
|
|
562
|
-
}
|
|
563
|
-
const response = await fetch(url, {
|
|
564
|
-
...options,
|
|
565
|
-
headers
|
|
566
|
-
});
|
|
567
|
-
const data = await response.json();
|
|
568
|
-
if (response.status === 403) {
|
|
569
|
-
const errorCode = data.error;
|
|
570
|
-
const permission = data.permission;
|
|
571
|
-
const message = data.message;
|
|
572
|
-
if (errorCode === "PERMISSION_REQUIRED" && permission) {
|
|
573
|
-
const granted = await this.requestPermission(permission);
|
|
574
|
-
if (granted) {
|
|
575
|
-
return this.request(path, options);
|
|
576
|
-
} else {
|
|
577
|
-
throw new PermissionDeniedError(permission, message);
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
if (errorCode === "PERMISSION_DENIED") {
|
|
581
|
-
throw new PermissionDeniedError(permission, message);
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
if (!response.ok) {
|
|
585
|
-
throw new Error(data.error || `Request failed: ${response.status}`);
|
|
586
|
-
}
|
|
587
|
-
return data;
|
|
588
|
-
}
|
|
589
|
-
async triggerAgent(payload) {
|
|
590
|
-
if (payload.auto_run && (!payload.agent_name || !payload.workspace_slug)) {
|
|
591
|
-
throw new Error("auto_run requires agent_name and workspace_slug");
|
|
592
|
-
}
|
|
593
|
-
return this.request("/webhooks/realtimex", {
|
|
594
|
-
method: "POST",
|
|
595
|
-
body: JSON.stringify({
|
|
596
|
-
app_name: this.appName,
|
|
597
|
-
app_id: this.appId,
|
|
598
|
-
event: "task.trigger",
|
|
599
|
-
event_id: payload.event_id || createContractEventId(),
|
|
600
|
-
attempt_id: normalizeAttemptId(payload.attempt_id),
|
|
601
|
-
payload: {
|
|
602
|
-
raw_data: payload.raw_data,
|
|
603
|
-
auto_run: payload.auto_run ?? false,
|
|
604
|
-
agent_name: payload.agent_name,
|
|
605
|
-
workspace_slug: payload.workspace_slug,
|
|
606
|
-
thread_slug: payload.thread_slug,
|
|
607
|
-
prompt: payload.prompt ?? ""
|
|
608
|
-
}
|
|
609
|
-
})
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
async ping() {
|
|
613
|
-
return this.request("/webhooks/realtimex", {
|
|
614
|
-
method: "POST",
|
|
615
|
-
body: JSON.stringify({
|
|
616
|
-
app_name: this.appName,
|
|
617
|
-
app_id: this.appId,
|
|
618
|
-
event: "system.ping",
|
|
619
|
-
event_id: createContractEventId()
|
|
620
|
-
})
|
|
621
|
-
});
|
|
622
|
-
}
|
|
623
|
-
};
|
|
624
|
-
|
|
625
|
-
// src/modules/task.ts
|
|
626
|
-
var TaskModule = class {
|
|
627
|
-
constructor(realtimexUrl, appName, appId, apiKey) {
|
|
628
|
-
this.realtimexUrl = realtimexUrl.replace(/\/$/, "");
|
|
629
|
-
this.appName = appName;
|
|
630
|
-
this.appId = appId;
|
|
631
|
-
this.apiKey = apiKey;
|
|
632
|
-
this.callbackSecret = process.env.RTX_CONTRACT_CALLBACK_SECRET;
|
|
633
|
-
this.signCallbacksByDefault = process.env.RTX_CONTRACT_SIGN_CALLBACKS === "true";
|
|
634
|
-
}
|
|
635
|
-
/**
|
|
636
|
-
* Configure callback signing behavior.
|
|
637
|
-
*/
|
|
638
|
-
configureContract(config) {
|
|
639
|
-
if (typeof config.callbackSecret === "string") {
|
|
640
|
-
this.callbackSecret = config.callbackSecret;
|
|
641
|
-
}
|
|
642
|
-
if (typeof config.signCallbacksByDefault === "boolean") {
|
|
643
|
-
this.signCallbacksByDefault = config.signCallbacksByDefault;
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
/**
|
|
647
|
-
* Claim a task before processing.
|
|
648
|
-
*/
|
|
649
|
-
async claim(taskUuid, options = {}) {
|
|
650
|
-
return this._sendEvent("task.claimed", taskUuid, {}, options);
|
|
651
|
-
}
|
|
652
|
-
/**
|
|
653
|
-
* Alias for claim()
|
|
654
|
-
*/
|
|
655
|
-
async claimed(taskUuid, options = {}) {
|
|
656
|
-
return this.claim(taskUuid, options);
|
|
657
|
-
}
|
|
658
|
-
/**
|
|
659
|
-
* Mark task as processing.
|
|
660
|
-
* Backward compatible signature: start(taskUuid, machineId?)
|
|
661
|
-
*/
|
|
662
|
-
async start(taskUuid, machineIdOrOptions) {
|
|
663
|
-
return this._sendEvent("task.started", taskUuid, {}, this._normalizeOptions(machineIdOrOptions));
|
|
664
|
-
}
|
|
665
|
-
/**
|
|
666
|
-
* Report incremental task progress.
|
|
667
|
-
*/
|
|
668
|
-
async progress(taskUuid, progressData = {}, options = {}) {
|
|
669
|
-
return this._sendEvent("task.progress", taskUuid, progressData, options);
|
|
670
|
-
}
|
|
671
|
-
/**
|
|
672
|
-
* Mark task as completed with result.
|
|
673
|
-
* Backward compatible signature: complete(taskUuid, result?, machineId?)
|
|
674
|
-
*/
|
|
675
|
-
async complete(taskUuid, result = {}, machineIdOrOptions) {
|
|
676
|
-
return this._sendEvent("task.completed", taskUuid, { result }, this._normalizeOptions(machineIdOrOptions));
|
|
677
|
-
}
|
|
678
|
-
/**
|
|
679
|
-
* Mark task as failed with error.
|
|
680
|
-
* Backward compatible signature: fail(taskUuid, error, machineId?)
|
|
681
|
-
*/
|
|
682
|
-
async fail(taskUuid, error, machineIdOrOptions) {
|
|
683
|
-
return this._sendEvent("task.failed", taskUuid, { error }, this._normalizeOptions(machineIdOrOptions));
|
|
684
|
-
}
|
|
685
|
-
/**
|
|
686
|
-
* Mark task as canceled.
|
|
687
|
-
*/
|
|
688
|
-
async cancel(taskUuid, reason, options = {}) {
|
|
689
|
-
const payload = reason ? { error: reason } : {};
|
|
690
|
-
return this._sendEvent("task.canceled", taskUuid, payload, options);
|
|
691
|
-
}
|
|
692
|
-
_normalizeOptions(machineIdOrOptions) {
|
|
693
|
-
if (!machineIdOrOptions) return {};
|
|
694
|
-
if (typeof machineIdOrOptions === "string") {
|
|
695
|
-
return { machineId: machineIdOrOptions };
|
|
696
|
-
}
|
|
697
|
-
return machineIdOrOptions;
|
|
698
|
-
}
|
|
699
|
-
async _sendEvent(event, taskUuid, eventData = {}, options = {}) {
|
|
700
|
-
if (!taskUuid || !taskUuid.trim()) {
|
|
701
|
-
throw new Error("taskUuid is required");
|
|
702
|
-
}
|
|
703
|
-
const attemptId = normalizeAttemptId(options.attemptId);
|
|
704
|
-
const timestamp = options.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
705
|
-
const eventId = options.eventId || createContractEventId();
|
|
706
|
-
const callbackUrl = options.callbackUrl;
|
|
707
|
-
const targetUrl = callbackUrl || `${this.realtimexUrl}/webhooks/realtimex`;
|
|
708
|
-
const sendingToMainWebhook = !callbackUrl;
|
|
709
|
-
const includeAppAuth = sendingToMainWebhook || targetUrl.startsWith(this.realtimexUrl);
|
|
710
|
-
const payloadData = eventData && typeof eventData === "object" ? eventData : {};
|
|
711
|
-
const headers = { "Content-Type": "application/json" };
|
|
712
|
-
headers[CONTRACT_EVENT_ID_HEADER] = eventId;
|
|
713
|
-
if (includeAppAuth) {
|
|
714
|
-
if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
|
|
715
|
-
if (this.appId) headers["x-app-id"] = this.appId;
|
|
716
|
-
}
|
|
717
|
-
const callbackSecret = options.callbackSecret || this.callbackSecret;
|
|
718
|
-
const shouldSign = options.sign ?? this.signCallbacksByDefault;
|
|
719
|
-
if (shouldSign) {
|
|
720
|
-
if (!callbackSecret) {
|
|
721
|
-
throw new Error(
|
|
722
|
-
"Callback signing is enabled but no callbackSecret is configured. Use task.configureContract({ callbackSecret }) or pass options.callbackSecret."
|
|
723
|
-
);
|
|
724
|
-
}
|
|
725
|
-
headers[CONTRACT_SIGNATURE_HEADER] = signContractEvent({
|
|
726
|
-
secret: callbackSecret,
|
|
727
|
-
eventId,
|
|
728
|
-
eventType: event,
|
|
729
|
-
taskId: taskUuid,
|
|
730
|
-
attemptId,
|
|
731
|
-
timestamp,
|
|
732
|
-
payload: payloadData
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
|
-
const requestBody = sendingToMainWebhook ? {
|
|
736
|
-
app_name: this.appName,
|
|
737
|
-
app_id: this.appId,
|
|
738
|
-
event,
|
|
739
|
-
event_id: eventId,
|
|
740
|
-
attempt_id: attemptId,
|
|
741
|
-
payload: {
|
|
742
|
-
task_uuid: taskUuid,
|
|
743
|
-
machine_id: options.machineId,
|
|
744
|
-
timestamp,
|
|
745
|
-
attempt_id: attemptId,
|
|
746
|
-
...payloadData
|
|
747
|
-
}
|
|
748
|
-
} : {
|
|
749
|
-
event,
|
|
750
|
-
action: canonicalEventToLegacyAction(event),
|
|
751
|
-
event_id: eventId,
|
|
752
|
-
attempt_id: attemptId,
|
|
753
|
-
machine_id: options.machineId,
|
|
754
|
-
user_email: options.userEmail,
|
|
755
|
-
activity_id: options.activityId,
|
|
756
|
-
table_name: options.tableName,
|
|
757
|
-
timestamp,
|
|
758
|
-
data: payloadData
|
|
759
|
-
};
|
|
760
|
-
const response = await fetch(targetUrl, {
|
|
761
|
-
method: "POST",
|
|
762
|
-
headers,
|
|
763
|
-
body: JSON.stringify(requestBody)
|
|
764
|
-
});
|
|
765
|
-
const responseData = await response.json();
|
|
766
|
-
if (!response.ok) throw new Error(responseData.error || `Failed to ${event}`);
|
|
767
|
-
return {
|
|
768
|
-
...responseData,
|
|
769
|
-
task_uuid: responseData.task_uuid || responseData.task_id || taskUuid,
|
|
770
|
-
event_id: responseData.event_id || eventId,
|
|
771
|
-
attempt_id: responseData.attempt_id || attemptId,
|
|
772
|
-
event_type: responseData.event_type || event
|
|
773
|
-
};
|
|
774
|
-
}
|
|
775
|
-
};
|
|
776
|
-
|
|
777
|
-
// src/modules/port.ts
|
|
778
|
-
import * as net from "net";
|
|
779
|
-
var PortModule = class {
|
|
780
|
-
constructor(defaultPort = 8080) {
|
|
781
|
-
this.defaultPort = defaultPort;
|
|
782
|
-
}
|
|
783
|
-
/**
|
|
784
|
-
* Get suggested port from environment (RTX_PORT) or default
|
|
785
|
-
*/
|
|
786
|
-
getSuggestedPort() {
|
|
787
|
-
const envPort = process.env.RTX_PORT;
|
|
788
|
-
return envPort ? parseInt(envPort, 10) : this.defaultPort;
|
|
789
|
-
}
|
|
790
|
-
/**
|
|
791
|
-
* Check if a port is available on a specific host
|
|
792
|
-
*/
|
|
793
|
-
async isPortAvailableOn(port, host) {
|
|
794
|
-
return new Promise((resolve) => {
|
|
795
|
-
const server = net.createServer();
|
|
796
|
-
server.once("error", () => resolve(false));
|
|
797
|
-
server.once("listening", () => {
|
|
798
|
-
server.close();
|
|
799
|
-
resolve(true);
|
|
800
|
-
});
|
|
801
|
-
server.listen(port, host);
|
|
802
|
-
});
|
|
803
|
-
}
|
|
804
|
-
/**
|
|
805
|
-
* Check if a port is available (checks both IPv4 and IPv6)
|
|
806
|
-
* @param port - Port number to check
|
|
807
|
-
* @returns Promise resolving to true if port is available on ALL interfaces
|
|
808
|
-
*/
|
|
809
|
-
async isPortAvailable(port) {
|
|
810
|
-
const ipv4Available = await this.isPortAvailableOn(port, "127.0.0.1");
|
|
811
|
-
if (!ipv4Available) return false;
|
|
812
|
-
const ipv6Available = await this.isPortAvailableOn(port, "::1");
|
|
813
|
-
if (!ipv6Available) return false;
|
|
814
|
-
const allInterfacesAvailable = await this.isPortAvailableOn(port, "0.0.0.0");
|
|
815
|
-
return allInterfacesAvailable;
|
|
816
|
-
}
|
|
817
|
-
/**
|
|
818
|
-
* Find an available port starting from the suggested port
|
|
819
|
-
* @param startPort - Starting port number (default: RTX_PORT or defaultPort)
|
|
820
|
-
* @param maxAttempts - Maximum ports to try (default: 100)
|
|
821
|
-
* @returns Promise resolving to an available port number
|
|
822
|
-
* @throws Error if no available port found in range
|
|
823
|
-
*/
|
|
824
|
-
async findAvailablePort(startPort, maxAttempts = 100) {
|
|
825
|
-
const port = startPort ?? this.getSuggestedPort();
|
|
826
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
827
|
-
const currentPort = port + i;
|
|
828
|
-
if (await this.isPortAvailable(currentPort)) {
|
|
829
|
-
return currentPort;
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
throw new Error(`No available port found in range ${port}-${port + maxAttempts - 1}`);
|
|
833
|
-
}
|
|
834
|
-
/**
|
|
835
|
-
* Get a ready-to-use port
|
|
836
|
-
* Returns the suggested port if available, otherwise finds the next available port
|
|
837
|
-
*
|
|
838
|
-
* @example
|
|
839
|
-
* ```typescript
|
|
840
|
-
* const sdk = new RealtimeXSDK();
|
|
841
|
-
* const port = await sdk.port.getPort();
|
|
842
|
-
* app.listen(port);
|
|
843
|
-
* ```
|
|
844
|
-
*/
|
|
845
|
-
async getPort() {
|
|
846
|
-
const suggested = this.getSuggestedPort();
|
|
847
|
-
if (await this.isPortAvailable(suggested)) {
|
|
848
|
-
return suggested;
|
|
849
|
-
}
|
|
850
|
-
return this.findAvailablePort(suggested + 1);
|
|
851
|
-
}
|
|
852
|
-
};
|
|
853
|
-
|
|
854
|
-
// src/modules/llm.ts
|
|
855
|
-
var LLMPermissionError = class extends PermissionRequiredError {
|
|
856
|
-
constructor(permission, code = "PERMISSION_REQUIRED") {
|
|
857
|
-
super(permission, void 0, code);
|
|
858
|
-
this.name = "LLMPermissionError";
|
|
859
|
-
}
|
|
860
|
-
};
|
|
861
|
-
var LLMProviderError = class extends Error {
|
|
862
|
-
constructor(message, code = "LLM_ERROR") {
|
|
863
|
-
super(message);
|
|
864
|
-
this.code = code;
|
|
865
|
-
this.name = "LLMProviderError";
|
|
866
|
-
}
|
|
867
|
-
};
|
|
868
|
-
var VectorStore = class {
|
|
869
|
-
constructor(baseUrl, appId, appName = "Local App", apiKey) {
|
|
870
|
-
this.baseUrl = baseUrl;
|
|
871
|
-
this.appId = appId;
|
|
872
|
-
this.appName = appName;
|
|
873
|
-
this.apiKey = apiKey;
|
|
874
|
-
}
|
|
875
|
-
get headers() {
|
|
876
|
-
const headers = {
|
|
877
|
-
"Content-Type": "application/json"
|
|
878
|
-
};
|
|
879
|
-
if (this.apiKey) {
|
|
880
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
881
|
-
}
|
|
882
|
-
if (this.appId) {
|
|
883
|
-
headers["x-app-id"] = this.appId;
|
|
884
|
-
}
|
|
885
|
-
return headers;
|
|
886
|
-
}
|
|
887
|
-
/**
|
|
888
|
-
* Request a single permission from Electron via internal API
|
|
889
|
-
*/
|
|
890
|
-
async requestPermission(permission) {
|
|
891
|
-
try {
|
|
892
|
-
const response = await fetch(`${this.baseUrl}/api/local-apps/request-permission`, {
|
|
893
|
-
method: "POST",
|
|
894
|
-
headers: { "Content-Type": "application/json" },
|
|
895
|
-
body: JSON.stringify({
|
|
896
|
-
app_id: this.appId,
|
|
897
|
-
app_name: this.appName,
|
|
898
|
-
permission
|
|
899
|
-
})
|
|
900
|
-
});
|
|
901
|
-
const data = await response.json();
|
|
902
|
-
return data.granted === true;
|
|
903
|
-
} catch (error) {
|
|
904
|
-
return false;
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
/**
|
|
908
|
-
* Internal request wrapper that handles automatic permission prompts
|
|
909
|
-
*/
|
|
910
|
-
async request(method, endpoint, body) {
|
|
911
|
-
const response = await fetch(`${this.baseUrl}${endpoint}`, {
|
|
912
|
-
method,
|
|
913
|
-
headers: this.headers,
|
|
914
|
-
body: body ? JSON.stringify(body) : void 0
|
|
915
|
-
});
|
|
916
|
-
const data = await response.json();
|
|
917
|
-
if (data.code === "PERMISSION_REQUIRED") {
|
|
918
|
-
const permission = data.permission || "vectors.read";
|
|
919
|
-
const granted = await this.requestPermission(permission);
|
|
920
|
-
if (granted) {
|
|
921
|
-
return this.request(method, endpoint, body);
|
|
922
|
-
}
|
|
923
|
-
throw new PermissionDeniedError(permission);
|
|
924
|
-
}
|
|
925
|
-
if (!data.success && data.error) {
|
|
926
|
-
if (data.code === "LLM_ERROR") {
|
|
927
|
-
throw new LLMProviderError(data.error);
|
|
928
|
-
}
|
|
929
|
-
throw new Error(data.error);
|
|
930
|
-
}
|
|
931
|
-
return data;
|
|
932
|
-
}
|
|
933
|
-
/**
|
|
934
|
-
* Upsert (insert or update) vectors into storage
|
|
935
|
-
*
|
|
936
|
-
* @example
|
|
937
|
-
* ```ts
|
|
938
|
-
* await sdk.llm.vectors.upsert([
|
|
939
|
-
* { id: 'chunk-1', vector: embeddings[0], metadata: { text: 'Hello', documentId: 'doc-1' } }
|
|
940
|
-
* ], { workspaceId: 'ws-123' });
|
|
941
|
-
* ```
|
|
942
|
-
*/
|
|
943
|
-
async upsert(vectors, options = {}) {
|
|
944
|
-
return this.request("POST", "/sdk/llm/vectors/upsert", {
|
|
945
|
-
vectors,
|
|
946
|
-
workspaceId: options.workspaceId
|
|
947
|
-
});
|
|
948
|
-
}
|
|
949
|
-
/**
|
|
950
|
-
* Query similar vectors by embedding
|
|
951
|
-
*
|
|
952
|
-
* @example
|
|
953
|
-
* ```ts
|
|
954
|
-
* const results = await sdk.llm.vectors.query(queryVector, {
|
|
955
|
-
* topK: 5,
|
|
956
|
-
* filter: { documentId: 'doc-1' },
|
|
957
|
-
* workspaceId: 'ws-123'
|
|
958
|
-
* });
|
|
959
|
-
* ```
|
|
960
|
-
*/
|
|
961
|
-
async query(vector, options = {}) {
|
|
962
|
-
return this.request("POST", "/sdk/llm/vectors/query", {
|
|
963
|
-
vector,
|
|
964
|
-
topK: options.topK ?? 5,
|
|
965
|
-
filter: options.filter,
|
|
966
|
-
workspaceId: options.workspaceId
|
|
967
|
-
});
|
|
968
|
-
}
|
|
969
|
-
/**
|
|
970
|
-
* Delete vectors from storage
|
|
971
|
-
*
|
|
972
|
-
* Note: Currently only supports deleteAll: true
|
|
973
|
-
* Use workspaceId to scope deletion to a specific workspace
|
|
974
|
-
*
|
|
975
|
-
* @example
|
|
976
|
-
* ```ts
|
|
977
|
-
* await sdk.llm.vectors.delete({ deleteAll: true, workspaceId: 'ws-123' });
|
|
978
|
-
* ```
|
|
979
|
-
*/
|
|
980
|
-
async delete(options) {
|
|
981
|
-
return this.request("POST", "/sdk/llm/vectors/delete", options);
|
|
982
|
-
}
|
|
983
|
-
/**
|
|
984
|
-
* List all available workspaces (namespaces) for this app
|
|
985
|
-
*
|
|
986
|
-
* @example
|
|
987
|
-
* ```ts
|
|
988
|
-
* const { workspaces } = await sdk.llm.vectors.listWorkspaces();
|
|
989
|
-
* console.log('Workspaces:', workspaces);
|
|
990
|
-
* ```
|
|
991
|
-
*/
|
|
992
|
-
async listWorkspaces() {
|
|
993
|
-
return this.request("GET", "/sdk/llm/vectors/workspaces");
|
|
994
|
-
}
|
|
995
|
-
/**
|
|
996
|
-
* Register a custom vector database configuration for this app
|
|
997
|
-
*
|
|
998
|
-
* @example
|
|
999
|
-
* ```ts
|
|
1000
|
-
* await sdk.llm.vectors.registerConfig('lancedb', { });
|
|
1001
|
-
* ```
|
|
1002
|
-
*/
|
|
1003
|
-
async registerConfig(provider, config) {
|
|
1004
|
-
return this.request("POST", "/sdk/llm/vectors/register", {
|
|
1005
|
-
provider,
|
|
1006
|
-
config
|
|
1007
|
-
});
|
|
1008
|
-
}
|
|
1009
|
-
/**
|
|
1010
|
-
* List all supported vector database providers and their configuration requirements
|
|
1011
|
-
*/
|
|
1012
|
-
async listProviders() {
|
|
1013
|
-
return this.request("GET", "/sdk/llm/vectors/providers");
|
|
1014
|
-
}
|
|
1015
|
-
/**
|
|
1016
|
-
* Get the current vector database configuration for this app
|
|
1017
|
-
*
|
|
1018
|
-
* @example
|
|
1019
|
-
* ```ts
|
|
1020
|
-
* const { provider, config } = await sdk.llm.vectors.getConfig();
|
|
1021
|
-
* console.log(`App is using ${provider}`);
|
|
1022
|
-
* ```
|
|
1023
|
-
*/
|
|
1024
|
-
async getConfig() {
|
|
1025
|
-
return this.request("GET", "/sdk/llm/vectors/config");
|
|
1026
|
-
}
|
|
1027
|
-
};
|
|
1028
|
-
var LLMModule = class {
|
|
1029
|
-
constructor(baseUrl, appId, appName = "Local App", apiKey) {
|
|
1030
|
-
this.baseUrl = baseUrl;
|
|
1031
|
-
this.appId = appId;
|
|
1032
|
-
this.appName = appName;
|
|
1033
|
-
this.apiKey = apiKey;
|
|
1034
|
-
this.vectors = new VectorStore(baseUrl, appId, appName, apiKey);
|
|
1035
|
-
}
|
|
1036
|
-
get headers() {
|
|
1037
|
-
const headers = {
|
|
1038
|
-
"Content-Type": "application/json"
|
|
1039
|
-
};
|
|
1040
|
-
if (this.apiKey) {
|
|
1041
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
1042
|
-
}
|
|
1043
|
-
if (this.appId) {
|
|
1044
|
-
headers["x-app-id"] = this.appId;
|
|
1045
|
-
}
|
|
1046
|
-
return headers;
|
|
1047
|
-
}
|
|
1048
|
-
/**
|
|
1049
|
-
* Request a single permission from Electron via internal API
|
|
1050
|
-
*/
|
|
1051
|
-
async requestPermission(permission) {
|
|
1052
|
-
try {
|
|
1053
|
-
const response = await fetch(`${this.baseUrl}/api/local-apps/request-permission`, {
|
|
1054
|
-
method: "POST",
|
|
1055
|
-
headers: { "Content-Type": "application/json" },
|
|
1056
|
-
body: JSON.stringify({
|
|
1057
|
-
app_id: this.appId,
|
|
1058
|
-
app_name: this.appName,
|
|
1059
|
-
permission
|
|
1060
|
-
})
|
|
1061
|
-
});
|
|
1062
|
-
const data = await response.json();
|
|
1063
|
-
return data.granted === true;
|
|
1064
|
-
} catch (error) {
|
|
1065
|
-
return false;
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
/**
|
|
1069
|
-
* Internal request wrapper that handles automatic permission prompts
|
|
1070
|
-
*/
|
|
1071
|
-
async request(method, endpoint, body) {
|
|
1072
|
-
const response = await fetch(`${this.baseUrl}${endpoint}`, {
|
|
1073
|
-
method,
|
|
1074
|
-
headers: this.headers,
|
|
1075
|
-
body: body ? JSON.stringify(body) : void 0
|
|
1076
|
-
});
|
|
1077
|
-
const data = await response.json();
|
|
1078
|
-
if (data.code === "PERMISSION_REQUIRED") {
|
|
1079
|
-
const permission = data.permission || "llm.chat";
|
|
1080
|
-
const granted = await this.requestPermission(permission);
|
|
1081
|
-
if (granted) {
|
|
1082
|
-
return this.request(method, endpoint, body);
|
|
1083
|
-
}
|
|
1084
|
-
throw new PermissionDeniedError(permission);
|
|
1085
|
-
}
|
|
1086
|
-
if (!data.success && data.error) {
|
|
1087
|
-
if (data.code === "LLM_ERROR") {
|
|
1088
|
-
throw new LLMProviderError(data.error);
|
|
1089
|
-
}
|
|
1090
|
-
throw new Error(data.error);
|
|
1091
|
-
}
|
|
1092
|
-
return data;
|
|
1093
|
-
}
|
|
1094
|
-
/**
|
|
1095
|
-
* Get only configured chat (LLM) providers
|
|
1096
|
-
*
|
|
1097
|
-
* @example
|
|
1098
|
-
* ```ts
|
|
1099
|
-
* const { providers } = await sdk.llm.chatProviders();
|
|
1100
|
-
* console.log('Available chat models:', providers[0].models);
|
|
1101
|
-
* ```
|
|
1102
|
-
*/
|
|
1103
|
-
async chatProviders() {
|
|
1104
|
-
return this.request("GET", "/sdk/llm/providers/chat");
|
|
1105
|
-
}
|
|
1106
|
-
/**
|
|
1107
|
-
* Get only configured embedding providers
|
|
1108
|
-
*
|
|
1109
|
-
* @example
|
|
1110
|
-
* ```ts
|
|
1111
|
-
* const { providers } = await sdk.llm.embedProviders();
|
|
1112
|
-
* console.log('Available embedding models:', providers[0].models);
|
|
1113
|
-
* ```
|
|
1114
|
-
*/
|
|
1115
|
-
async embedProviders() {
|
|
1116
|
-
return this.request("GET", "/sdk/llm/providers/embed");
|
|
1117
|
-
}
|
|
1118
|
-
/**
|
|
1119
|
-
* Send a chat completion request (synchronous)
|
|
1120
|
-
*
|
|
1121
|
-
* @example
|
|
1122
|
-
* ```ts
|
|
1123
|
-
* const response = await sdk.llm.chat([
|
|
1124
|
-
* { role: 'system', content: 'You are a helpful assistant.' },
|
|
1125
|
-
* { role: 'user', content: 'Hello!' }
|
|
1126
|
-
* ], { model: 'gpt-4o', temperature: 0.7 });
|
|
1127
|
-
*
|
|
1128
|
-
* console.log(response.response?.content);
|
|
1129
|
-
* ```
|
|
1130
|
-
*/
|
|
1131
|
-
async chat(messages, options = {}) {
|
|
1132
|
-
return this.request("POST", "/sdk/llm/chat", {
|
|
1133
|
-
messages,
|
|
1134
|
-
model: options.model,
|
|
1135
|
-
provider: options.provider,
|
|
1136
|
-
temperature: options.temperature ?? 0.7,
|
|
1137
|
-
max_tokens: options.max_tokens ?? 1e3,
|
|
1138
|
-
response_format: options.response_format
|
|
1139
|
-
});
|
|
1140
|
-
}
|
|
1141
|
-
/**
|
|
1142
|
-
* Send a streaming chat completion request (SSE)
|
|
1143
|
-
*
|
|
1144
|
-
* @example
|
|
1145
|
-
* ```ts
|
|
1146
|
-
* for await (const chunk of sdk.llm.chatStream([
|
|
1147
|
-
* { role: 'user', content: 'Tell me a story' }
|
|
1148
|
-
* ])) {
|
|
1149
|
-
* process.stdout.write(chunk.textResponse || '');
|
|
1150
|
-
* }
|
|
1151
|
-
* ```
|
|
1152
|
-
*/
|
|
1153
|
-
async *chatStream(messages, options = {}) {
|
|
1154
|
-
const response = await fetch(`${this.baseUrl}/sdk/llm/chat/stream`, {
|
|
1155
|
-
method: "POST",
|
|
1156
|
-
headers: {
|
|
1157
|
-
...this.headers,
|
|
1158
|
-
"Accept": "text/event-stream"
|
|
1159
|
-
},
|
|
1160
|
-
body: JSON.stringify({
|
|
1161
|
-
messages,
|
|
1162
|
-
model: options.model,
|
|
1163
|
-
provider: options.provider,
|
|
1164
|
-
temperature: options.temperature ?? 0.7,
|
|
1165
|
-
max_tokens: options.max_tokens ?? 1e3,
|
|
1166
|
-
response_format: options.response_format
|
|
1167
|
-
})
|
|
1168
|
-
});
|
|
1169
|
-
if (!response.ok) {
|
|
1170
|
-
const errorData = await response.json();
|
|
1171
|
-
if (errorData.code === "PERMISSION_REQUIRED") {
|
|
1172
|
-
const permission = errorData.permission || "llm.chat";
|
|
1173
|
-
const granted = await this.requestPermission(permission);
|
|
1174
|
-
if (granted) {
|
|
1175
|
-
yield* this.chatStream(messages, options);
|
|
1176
|
-
return;
|
|
1177
|
-
}
|
|
1178
|
-
throw new PermissionDeniedError(permission);
|
|
1179
|
-
}
|
|
1180
|
-
throw new LLMProviderError(errorData.error || "Stream request failed");
|
|
1181
|
-
}
|
|
1182
|
-
const reader = response.body?.getReader();
|
|
1183
|
-
if (!reader) {
|
|
1184
|
-
throw new LLMProviderError("Response body is not readable");
|
|
1185
|
-
}
|
|
1186
|
-
const decoder = new TextDecoder();
|
|
1187
|
-
let buffer = "";
|
|
1188
|
-
let isErrorEvent = false;
|
|
1189
|
-
try {
|
|
1190
|
-
while (true) {
|
|
1191
|
-
const { done, value } = await reader.read();
|
|
1192
|
-
if (done) break;
|
|
1193
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1194
|
-
const lines = buffer.split("\n");
|
|
1195
|
-
buffer = lines.pop() || "";
|
|
1196
|
-
for (const line of lines) {
|
|
1197
|
-
const trimmedLine = line.trim();
|
|
1198
|
-
if (!trimmedLine || trimmedLine.startsWith(":")) continue;
|
|
1199
|
-
if (trimmedLine.startsWith("event: error")) {
|
|
1200
|
-
isErrorEvent = true;
|
|
1201
|
-
continue;
|
|
1202
|
-
}
|
|
1203
|
-
if (trimmedLine.startsWith("data: ")) {
|
|
1204
|
-
const jsonStr = trimmedLine.slice(6);
|
|
1205
|
-
if (jsonStr === "[DONE]") {
|
|
1206
|
-
isErrorEvent = false;
|
|
1207
|
-
continue;
|
|
1208
|
-
}
|
|
1209
|
-
try {
|
|
1210
|
-
const data = JSON.parse(jsonStr);
|
|
1211
|
-
if (isErrorEvent) {
|
|
1212
|
-
isErrorEvent = false;
|
|
1213
|
-
throw new LLMProviderError(
|
|
1214
|
-
data.error || "Stream error",
|
|
1215
|
-
data.code || "LLM_STREAM_ERROR"
|
|
1216
|
-
);
|
|
1217
|
-
}
|
|
1218
|
-
const chunk = data;
|
|
1219
|
-
if (chunk.error) {
|
|
1220
|
-
throw new LLMProviderError(
|
|
1221
|
-
chunk.message || "Stream error"
|
|
1222
|
-
);
|
|
1223
|
-
}
|
|
1224
|
-
yield chunk;
|
|
1225
|
-
} catch (parseError) {
|
|
1226
|
-
isErrorEvent = false;
|
|
1227
|
-
if (jsonStr !== "[DONE]") {
|
|
1228
|
-
console.warn("[LLM Stream] Parse error:", jsonStr);
|
|
1229
|
-
}
|
|
1230
|
-
if (parseError instanceof LLMProviderError) throw parseError;
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
} finally {
|
|
1236
|
-
reader.releaseLock();
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
/**
|
|
1240
|
-
* Generate vector embeddings from text
|
|
1241
|
-
*
|
|
1242
|
-
* @example
|
|
1243
|
-
* ```ts
|
|
1244
|
-
* // Single text
|
|
1245
|
-
* const { embeddings } = await sdk.llm.embed('Hello world');
|
|
1246
|
-
*
|
|
1247
|
-
* // Multiple texts
|
|
1248
|
-
* const { embeddings } = await sdk.llm.embed(['Hello', 'World']);
|
|
1249
|
-
* ```
|
|
1250
|
-
*/
|
|
1251
|
-
async embed(input, options = {}) {
|
|
1252
|
-
const inputArray = Array.isArray(input) ? input : [input];
|
|
1253
|
-
return this.request("POST", "/sdk/llm/embed", {
|
|
1254
|
-
input: inputArray,
|
|
1255
|
-
provider: options.provider,
|
|
1256
|
-
model: options.model
|
|
1257
|
-
});
|
|
1258
|
-
}
|
|
1259
|
-
/**
|
|
1260
|
-
* Helper: Embed text and store as vectors in one call
|
|
1261
|
-
*
|
|
1262
|
-
* @example
|
|
1263
|
-
* ```ts
|
|
1264
|
-
* await sdk.llm.embedAndStore({
|
|
1265
|
-
* texts: ['Hello world', 'Goodbye world'],
|
|
1266
|
-
* documentId: 'doc-123',
|
|
1267
|
-
* workspaceId: 'ws-456'
|
|
1268
|
-
* });
|
|
1269
|
-
* ```
|
|
1270
|
-
*/
|
|
1271
|
-
async embedAndStore(params) {
|
|
1272
|
-
const { texts, documentId, workspaceId, idPrefix = "chunk", provider, model } = params;
|
|
1273
|
-
const embedResult = await this.embed(texts, { provider, model });
|
|
1274
|
-
if (!embedResult.success || !embedResult.embeddings) {
|
|
1275
|
-
return {
|
|
1276
|
-
success: false,
|
|
1277
|
-
error: embedResult.error || "Embedding failed",
|
|
1278
|
-
code: embedResult.code
|
|
1279
|
-
};
|
|
1280
|
-
}
|
|
1281
|
-
let uniquePrefix = idPrefix;
|
|
1282
|
-
if (idPrefix === "chunk") {
|
|
1283
|
-
const randomSuffix = Math.random().toString(36).substring(2, 6);
|
|
1284
|
-
uniquePrefix = `chunk_${randomSuffix}`;
|
|
1285
|
-
}
|
|
1286
|
-
const vectors = texts.map((text, i) => ({
|
|
1287
|
-
id: `${uniquePrefix}_${i}`,
|
|
1288
|
-
vector: embedResult.embeddings[i],
|
|
1289
|
-
metadata: {
|
|
1290
|
-
text,
|
|
1291
|
-
documentId,
|
|
1292
|
-
workspaceId,
|
|
1293
|
-
embeddingModel: embedResult.model || model || "unknown"
|
|
1294
|
-
}
|
|
1295
|
-
}));
|
|
1296
|
-
return this.vectors.upsert(vectors, { workspaceId });
|
|
1297
|
-
}
|
|
1298
|
-
/**
|
|
1299
|
-
* Helper: Search similar documents by text query
|
|
1300
|
-
*
|
|
1301
|
-
* @example
|
|
1302
|
-
* ```ts
|
|
1303
|
-
* const results = await sdk.llm.search('What is RealtimeX?', {
|
|
1304
|
-
* topK: 5,
|
|
1305
|
-
* workspaceId: 'ws-123'
|
|
1306
|
-
* });
|
|
1307
|
-
*
|
|
1308
|
-
* for (const result of results) {
|
|
1309
|
-
* console.log(result.metadata?.text, result.score);
|
|
1310
|
-
* }
|
|
1311
|
-
* ```
|
|
1312
|
-
*/
|
|
1313
|
-
async search(query, options = {}) {
|
|
1314
|
-
const embedResult = await this.embed(query, {
|
|
1315
|
-
provider: options.provider,
|
|
1316
|
-
model: options.model
|
|
1317
|
-
});
|
|
1318
|
-
if (!embedResult.success || !embedResult.embeddings?.[0]) {
|
|
1319
|
-
throw new LLMProviderError("Failed to embed query");
|
|
1320
|
-
}
|
|
1321
|
-
const queryResult = await this.vectors.query(embedResult.embeddings[0], {
|
|
1322
|
-
...options,
|
|
1323
|
-
model: options.model || embedResult.model
|
|
1324
|
-
});
|
|
1325
|
-
if (!queryResult.success) {
|
|
1326
|
-
throw new LLMProviderError(queryResult.error || "Vector search failed");
|
|
1327
|
-
}
|
|
1328
|
-
return queryResult.results || [];
|
|
1329
|
-
}
|
|
1330
|
-
};
|
|
1331
|
-
|
|
1332
|
-
// src/modules/tts.ts
|
|
1333
|
-
var TTSModule = class {
|
|
1334
|
-
constructor(realtimexUrl, appId, appName, apiKey) {
|
|
1335
|
-
this.baseUrl = realtimexUrl.replace(/\/$/, "");
|
|
1336
|
-
this.appId = appId;
|
|
1337
|
-
this.appName = appName || process.env.RTX_APP_NAME || "Local App";
|
|
1338
|
-
this.apiKey = apiKey;
|
|
1339
|
-
}
|
|
1340
|
-
get headers() {
|
|
1341
|
-
const headers = {
|
|
1342
|
-
"Content-Type": "application/json"
|
|
1343
|
-
};
|
|
1344
|
-
if (this.apiKey) {
|
|
1345
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
1346
|
-
}
|
|
1347
|
-
if (this.appId) {
|
|
1348
|
-
headers["x-app-id"] = this.appId;
|
|
1349
|
-
}
|
|
1350
|
-
return headers;
|
|
1351
|
-
}
|
|
1352
|
-
/**
|
|
1353
|
-
* Request a single permission from Electron via internal API
|
|
1354
|
-
*/
|
|
1355
|
-
async requestPermission(permission) {
|
|
1356
|
-
try {
|
|
1357
|
-
const response = await fetch(`${this.baseUrl}/api/local-apps/request-permission`, {
|
|
1358
|
-
method: "POST",
|
|
1359
|
-
headers: { "Content-Type": "application/json" },
|
|
1360
|
-
body: JSON.stringify({
|
|
1361
|
-
app_id: this.appId,
|
|
1362
|
-
app_name: this.appName,
|
|
1363
|
-
permission
|
|
1364
|
-
})
|
|
1365
|
-
});
|
|
1366
|
-
const data = await response.json();
|
|
1367
|
-
return data.granted === true;
|
|
1368
|
-
} catch (error) {
|
|
1369
|
-
console.error("[SDK] Permission request failed:", error);
|
|
1370
|
-
return false;
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
/**
|
|
1374
|
-
* Internal request wrapper that handles automatic permission prompts
|
|
1375
|
-
*/
|
|
1376
|
-
async request(method, endpoint, body, isStream = false) {
|
|
1377
|
-
const response = await fetch(`${this.baseUrl}${endpoint}`, {
|
|
1378
|
-
method,
|
|
1379
|
-
headers: this.headers,
|
|
1380
|
-
body: body ? JSON.stringify(body) : void 0
|
|
1381
|
-
});
|
|
1382
|
-
if (!response.ok) {
|
|
1383
|
-
const data = await response.json();
|
|
1384
|
-
if (data.code === "PERMISSION_REQUIRED") {
|
|
1385
|
-
const permission = data.permission || "tts.generate";
|
|
1386
|
-
const granted = await this.requestPermission(permission);
|
|
1387
|
-
if (granted) {
|
|
1388
|
-
return this.request(method, endpoint, body, isStream);
|
|
1389
|
-
}
|
|
1390
|
-
throw new PermissionDeniedError(permission);
|
|
1391
|
-
}
|
|
1392
|
-
throw new Error(data.error || `Request failed: ${response.status}`);
|
|
1393
|
-
}
|
|
1394
|
-
if (isStream) {
|
|
1395
|
-
return response.body;
|
|
1396
|
-
}
|
|
1397
|
-
const contentType = response.headers.get("content-type");
|
|
1398
|
-
if (contentType && contentType.includes("application/json")) {
|
|
1399
|
-
return response.json();
|
|
1400
|
-
}
|
|
1401
|
-
return response.arrayBuffer();
|
|
1402
|
-
}
|
|
1403
|
-
/**
|
|
1404
|
-
* Generate speech from text (returns full buffer)
|
|
1405
|
-
*
|
|
1406
|
-
* @example
|
|
1407
|
-
* ```ts
|
|
1408
|
-
* const buffer = await sdk.tts.speak("Hello world");
|
|
1409
|
-
* // Play buffer...
|
|
1410
|
-
* ```
|
|
1411
|
-
*/
|
|
1412
|
-
async speak(text, options = {}) {
|
|
1413
|
-
return this.request("POST", "/sdk/tts", {
|
|
1414
|
-
text,
|
|
1415
|
-
...options
|
|
1416
|
-
});
|
|
1417
|
-
}
|
|
1418
|
-
/**
|
|
1419
|
-
* Generate speech from text with streaming (yields decoded audio chunks)
|
|
1420
|
-
* Uses SSE internally but returns decoded ArrayBuffer chunks for easy playback.
|
|
1421
|
-
*
|
|
1422
|
-
* @example
|
|
1423
|
-
* ```ts
|
|
1424
|
-
* for await (const chunk of sdk.tts.speakStream("Hello world")) {
|
|
1425
|
-
* // chunk.audio is ArrayBuffer (already decoded!)
|
|
1426
|
-
* const blob = new Blob([chunk.audio], { type: chunk.mimeType });
|
|
1427
|
-
* const audio = new Audio(URL.createObjectURL(blob));
|
|
1428
|
-
* await audio.play();
|
|
1429
|
-
* }
|
|
1430
|
-
* ```
|
|
1431
|
-
*/
|
|
1432
|
-
async *speakStream(text, options = {}) {
|
|
1433
|
-
const response = await fetch(`${this.baseUrl}/sdk/tts/stream`, {
|
|
1434
|
-
method: "POST",
|
|
1435
|
-
headers: this.headers,
|
|
1436
|
-
body: JSON.stringify({ text, ...options })
|
|
1437
|
-
});
|
|
1438
|
-
if (!response.ok) {
|
|
1439
|
-
const data = await response.json();
|
|
1440
|
-
if (data.code === "PERMISSION_REQUIRED") {
|
|
1441
|
-
const permission = data.permission || "tts.generate";
|
|
1442
|
-
const granted = await this.requestPermission(permission);
|
|
1443
|
-
if (granted) {
|
|
1444
|
-
yield* this.speakStream(text, options);
|
|
1445
|
-
return;
|
|
1446
|
-
}
|
|
1447
|
-
throw new PermissionDeniedError(permission);
|
|
1448
|
-
}
|
|
1449
|
-
throw new Error(data.error || `Streaming failed: ${response.status}`);
|
|
1450
|
-
}
|
|
1451
|
-
const reader = response.body?.getReader();
|
|
1452
|
-
if (!reader) throw new Error("No response body");
|
|
1453
|
-
const decoder = new TextDecoder();
|
|
1454
|
-
let buffer = "";
|
|
1455
|
-
let eventType = "";
|
|
1456
|
-
try {
|
|
1457
|
-
while (true) {
|
|
1458
|
-
const { done, value } = await reader.read();
|
|
1459
|
-
if (done) break;
|
|
1460
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1461
|
-
const lines = buffer.split("\n");
|
|
1462
|
-
buffer = lines.pop() || "";
|
|
1463
|
-
for (const line of lines) {
|
|
1464
|
-
const trimmedLine = line.trim();
|
|
1465
|
-
if (!trimmedLine) continue;
|
|
1466
|
-
if (trimmedLine.startsWith("event:")) {
|
|
1467
|
-
eventType = trimmedLine.slice(6).trim();
|
|
1468
|
-
} else if (trimmedLine.startsWith("data:")) {
|
|
1469
|
-
const eventData = trimmedLine.slice(5).trim();
|
|
1470
|
-
if (eventType === "chunk" && eventData) {
|
|
1471
|
-
try {
|
|
1472
|
-
const parsed = JSON.parse(eventData);
|
|
1473
|
-
const binaryString = atob(parsed.audio);
|
|
1474
|
-
const bytes = new Uint8Array(binaryString.length);
|
|
1475
|
-
for (let i = 0; i < binaryString.length; i++) {
|
|
1476
|
-
bytes[i] = binaryString.charCodeAt(i);
|
|
1477
|
-
}
|
|
1478
|
-
yield {
|
|
1479
|
-
index: parsed.index,
|
|
1480
|
-
total: parsed.total,
|
|
1481
|
-
audio: bytes.buffer,
|
|
1482
|
-
mimeType: parsed.mimeType
|
|
1483
|
-
};
|
|
1484
|
-
} catch (e) {
|
|
1485
|
-
console.warn("[TTS SDK] Failed to parse chunk:", e);
|
|
1486
|
-
}
|
|
1487
|
-
} else if (eventType === "error" && eventData) {
|
|
1488
|
-
try {
|
|
1489
|
-
const err = JSON.parse(eventData);
|
|
1490
|
-
throw new Error(err.error || "TTS streaming error");
|
|
1491
|
-
} catch (e) {
|
|
1492
|
-
if (e instanceof Error && e.message !== "TTS streaming error") {
|
|
1493
|
-
throw e;
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
eventType = "";
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
}
|
|
1501
|
-
} finally {
|
|
1502
|
-
reader.releaseLock();
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
/**
|
|
1506
|
-
* List available TTS providers with configuration options
|
|
1507
|
-
*/
|
|
1508
|
-
async listProviders() {
|
|
1509
|
-
const data = await this.request("GET", "/sdk/tts/providers");
|
|
1510
|
-
return data.providers || [];
|
|
1511
|
-
}
|
|
1512
|
-
};
|
|
1513
|
-
|
|
1514
|
-
// src/modules/stt.ts
|
|
1515
|
-
var STTModule = class extends ApiModule {
|
|
1516
|
-
/**
|
|
1517
|
-
* Get available STT providers and their models.
|
|
1518
|
-
* @returns Promise resolving to list of providers
|
|
1519
|
-
*/
|
|
1520
|
-
async listProviders() {
|
|
1521
|
-
try {
|
|
1522
|
-
const response = await this.apiCall("GET", "/sdk/stt/providers");
|
|
1523
|
-
if (!response.success) {
|
|
1524
|
-
throw new Error(response.error || "Failed to fetch providers");
|
|
1525
|
-
}
|
|
1526
|
-
return response.providers;
|
|
1527
|
-
} catch (error) {
|
|
1528
|
-
throw new Error(`STT providers fetch failed: ${error.message}`);
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
/**
|
|
1532
|
-
* Listen to microphone and transcribe speech to text.
|
|
1533
|
-
* Performed on the client device (Electron) via the RealtimeX Hub.
|
|
1534
|
-
*
|
|
1535
|
-
* @param options Configuration options for listening
|
|
1536
|
-
* @returns Promise resolving to the transcribed text
|
|
1537
|
-
*/
|
|
1538
|
-
async listen(options) {
|
|
1539
|
-
try {
|
|
1540
|
-
return await this.apiCall("POST", "/sdk/stt/listen", {
|
|
1541
|
-
body: JSON.stringify(options)
|
|
1542
|
-
});
|
|
1543
|
-
} catch (error) {
|
|
1544
|
-
throw new Error(`STT listen failed: ${error.message}`);
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
};
|
|
1548
|
-
|
|
1549
|
-
// src/modules/agent.ts
|
|
1550
|
-
var AgentModule = class {
|
|
1551
|
-
constructor(httpClient) {
|
|
1552
|
-
this.httpClient = httpClient;
|
|
1553
|
-
}
|
|
1554
|
-
/**
|
|
1555
|
-
* Create a new agent session
|
|
1556
|
-
*
|
|
1557
|
-
* @example
|
|
1558
|
-
* ```typescript
|
|
1559
|
-
* const session = await sdk.agent.createSession({
|
|
1560
|
-
* workspace_slug: 'my-workspace',
|
|
1561
|
-
* agent_name: '@agent'
|
|
1562
|
-
* });
|
|
1563
|
-
* ```
|
|
1564
|
-
*/
|
|
1565
|
-
async createSession(options) {
|
|
1566
|
-
const response = await this.httpClient.fetch("/sdk/agent/session", {
|
|
1567
|
-
method: "POST",
|
|
1568
|
-
body: JSON.stringify(options || {})
|
|
1569
|
-
});
|
|
1570
|
-
const data = await response.json();
|
|
1571
|
-
if (!response.ok) {
|
|
1572
|
-
throw new Error(data.error || "Failed to create session");
|
|
1573
|
-
}
|
|
1574
|
-
return data.session;
|
|
1575
|
-
}
|
|
1576
|
-
/**
|
|
1577
|
-
* Chat within a session (synchronous)
|
|
1578
|
-
*
|
|
1579
|
-
* @example
|
|
1580
|
-
* ```typescript
|
|
1581
|
-
* const response = await sdk.agent.chat(sessionId, 'Hello agent!');
|
|
1582
|
-
* console.log(response.text);
|
|
1583
|
-
* ```
|
|
1584
|
-
*/
|
|
1585
|
-
async chat(sessionId, message) {
|
|
1586
|
-
const response = await this.httpClient.fetch(`/sdk/agent/session/${sessionId}/chat`, {
|
|
1587
|
-
method: "POST",
|
|
1588
|
-
body: JSON.stringify({ message })
|
|
1589
|
-
});
|
|
1590
|
-
const data = await response.json();
|
|
1591
|
-
if (!response.ok) {
|
|
1592
|
-
throw new Error(data.error || "Chat request failed");
|
|
1593
|
-
}
|
|
1594
|
-
return data.response;
|
|
1595
|
-
}
|
|
1596
|
-
/**
|
|
1597
|
-
* Stream chat within a session
|
|
1598
|
-
* Returns an async iterator for SSE events
|
|
1599
|
-
*
|
|
1600
|
-
* @example
|
|
1601
|
-
* ```typescript
|
|
1602
|
-
* for await (const event of sdk.agent.streamChat(sessionId, 'Tell me a story')) {
|
|
1603
|
-
* if (event.type === 'agentThought') {
|
|
1604
|
-
* console.log('Thinking:', event.data.thought);
|
|
1605
|
-
* } else if (event.type === 'textResponse') {
|
|
1606
|
-
* console.log('Response:', event.data.textResponse);
|
|
1607
|
-
* }
|
|
1608
|
-
* }
|
|
1609
|
-
* ```
|
|
1610
|
-
*/
|
|
1611
|
-
async *streamChat(sessionId, message) {
|
|
1612
|
-
const response = await this.httpClient.fetch(`/sdk/agent/session/${sessionId}/chat/stream`, {
|
|
1613
|
-
method: "POST",
|
|
1614
|
-
body: JSON.stringify({ message })
|
|
1615
|
-
});
|
|
1616
|
-
if (!response.ok) {
|
|
1617
|
-
const data = await response.json();
|
|
1618
|
-
throw new Error(data.error || "Stream request failed");
|
|
1619
|
-
}
|
|
1620
|
-
if (!response.body) {
|
|
1621
|
-
throw new Error("Response body is null");
|
|
1622
|
-
}
|
|
1623
|
-
const reader = response.body.getReader();
|
|
1624
|
-
const decoder = new TextDecoder();
|
|
1625
|
-
let buffer = "";
|
|
1626
|
-
try {
|
|
1627
|
-
while (true) {
|
|
1628
|
-
const { done, value } = await reader.read();
|
|
1629
|
-
if (done) break;
|
|
1630
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1631
|
-
const lines = buffer.split("\n");
|
|
1632
|
-
buffer = lines.pop() || "";
|
|
1633
|
-
for (const line of lines) {
|
|
1634
|
-
if (line.startsWith("data: ")) {
|
|
1635
|
-
const data = JSON.parse(line.slice(6));
|
|
1636
|
-
yield {
|
|
1637
|
-
type: data.type,
|
|
1638
|
-
data
|
|
1639
|
-
};
|
|
1640
|
-
}
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1643
|
-
} finally {
|
|
1644
|
-
reader.releaseLock();
|
|
1645
|
-
}
|
|
1646
|
-
}
|
|
1647
|
-
/**
|
|
1648
|
-
* Get session information
|
|
1649
|
-
*
|
|
1650
|
-
* @example
|
|
1651
|
-
* ```typescript
|
|
1652
|
-
* const info = await sdk.agent.getSession(sessionId);
|
|
1653
|
-
* console.log(`Messages sent: ${info.message_count}`);
|
|
1654
|
-
* ```
|
|
1655
|
-
*/
|
|
1656
|
-
async getSession(sessionId) {
|
|
1657
|
-
const response = await this.httpClient.fetch(`/sdk/agent/session/${sessionId}`, {
|
|
1658
|
-
method: "GET"
|
|
1659
|
-
});
|
|
1660
|
-
const data = await response.json();
|
|
1661
|
-
if (!response.ok) {
|
|
1662
|
-
throw new Error(data.error || "Failed to get session info");
|
|
1663
|
-
}
|
|
1664
|
-
return data.session;
|
|
1665
|
-
}
|
|
1666
|
-
/**
|
|
1667
|
-
* Close and delete a session
|
|
1668
|
-
* Sessions auto-expire after 1 hour, but you can close them manually
|
|
1669
|
-
*
|
|
1670
|
-
* @example
|
|
1671
|
-
* ```typescript
|
|
1672
|
-
* await sdk.agent.closeSession(sessionId);
|
|
1673
|
-
* ```
|
|
1674
|
-
*/
|
|
1675
|
-
async closeSession(sessionId) {
|
|
1676
|
-
const response = await this.httpClient.fetch(`/sdk/agent/session/${sessionId}`, {
|
|
1677
|
-
method: "DELETE"
|
|
1678
|
-
});
|
|
1679
|
-
const data = await response.json();
|
|
1680
|
-
if (!response.ok) {
|
|
1681
|
-
throw new Error(data.error || "Failed to close session");
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
/**
|
|
1685
|
-
* Helper: Create session and send first message in one call
|
|
1686
|
-
* This is the simplest way to start chatting with an agent
|
|
1687
|
-
*
|
|
1688
|
-
* @example
|
|
1689
|
-
* ```typescript
|
|
1690
|
-
* const { session, response } = await sdk.agent.startChat('Hello agent!');
|
|
1691
|
-
* console.log(response.text);
|
|
1692
|
-
*
|
|
1693
|
-
* // Continue chatting in the same session
|
|
1694
|
-
* const reply = await sdk.agent.chat(session.session_id, 'Tell me more');
|
|
1695
|
-
* ```
|
|
1696
|
-
*/
|
|
1697
|
-
async startChat(message, options) {
|
|
1698
|
-
const session = await this.createSession(options);
|
|
1699
|
-
const response = await this.chat(session.session_id, message);
|
|
1700
|
-
return { session, response };
|
|
1701
|
-
}
|
|
1702
|
-
};
|
|
1703
|
-
|
|
1704
|
-
// src/modules/acpAgent.ts
|
|
1705
|
-
function encodeSessionKey(sessionKey) {
|
|
1706
|
-
return encodeURIComponent(sessionKey);
|
|
1707
|
-
}
|
|
1708
|
-
async function parseJsonResponse(response, fallbackError) {
|
|
1709
|
-
const data = await response.json();
|
|
1710
|
-
if (!response.ok) throw new Error(data.error || fallbackError);
|
|
1711
|
-
return data;
|
|
1712
|
-
}
|
|
1713
|
-
var AcpAgentModule = class {
|
|
1714
|
-
constructor(httpClient) {
|
|
1715
|
-
this.httpClient = httpClient;
|
|
1716
|
-
}
|
|
1717
|
-
/** List available CLI agents. Pass includeModels to get model lists per agent. */
|
|
1718
|
-
async listAgents(opts) {
|
|
1719
|
-
const qs = opts?.includeModels ? "?includeModels=true" : "";
|
|
1720
|
-
const response = await this.httpClient.fetch(`/sdk/acp/agents${qs}`);
|
|
1721
|
-
const data = await parseJsonResponse(
|
|
1722
|
-
response,
|
|
1723
|
-
"Failed to list agents"
|
|
1724
|
-
);
|
|
1725
|
-
return data.agents;
|
|
1726
|
-
}
|
|
1727
|
-
/** Create and initialize a new ACP session. Spawns the CLI agent process. */
|
|
1728
|
-
async createSession(options) {
|
|
1729
|
-
const response = await this.httpClient.fetch("/sdk/acp/session", {
|
|
1730
|
-
method: "POST",
|
|
1731
|
-
body: JSON.stringify(options)
|
|
1732
|
-
});
|
|
1733
|
-
const data = await parseJsonResponse(
|
|
1734
|
-
response,
|
|
1735
|
-
"Failed to create session"
|
|
1736
|
-
);
|
|
1737
|
-
return data.session;
|
|
1738
|
-
}
|
|
1739
|
-
/** Get session status and runtime options. */
|
|
1740
|
-
async getSession(sessionKey) {
|
|
1741
|
-
const response = await this.httpClient.fetch(
|
|
1742
|
-
`/sdk/acp/session/${encodeSessionKey(sessionKey)}`
|
|
1743
|
-
);
|
|
1744
|
-
const data = await parseJsonResponse(
|
|
1745
|
-
response,
|
|
1746
|
-
"Failed to get session"
|
|
1747
|
-
);
|
|
1748
|
-
return data.session;
|
|
1749
|
-
}
|
|
1750
|
-
/** List active ACP sessions owned by this app. */
|
|
1751
|
-
async listSessions() {
|
|
1752
|
-
const response = await this.httpClient.fetch("/sdk/acp/sessions");
|
|
1753
|
-
const data = await parseJsonResponse(
|
|
1754
|
-
response,
|
|
1755
|
-
"Failed to list sessions"
|
|
1756
|
-
);
|
|
1757
|
-
return data.sessions;
|
|
1758
|
-
}
|
|
1759
|
-
/** Update runtime options (applied on next turn). */
|
|
1760
|
-
async patchSession(sessionKey, patch) {
|
|
1761
|
-
const response = await this.httpClient.fetch(
|
|
1762
|
-
`/sdk/acp/session/${encodeSessionKey(sessionKey)}`,
|
|
1763
|
-
{ method: "PATCH", body: JSON.stringify(patch) }
|
|
1764
|
-
);
|
|
1765
|
-
await parseJsonResponse(response, "Failed to update session");
|
|
1766
|
-
}
|
|
1767
|
-
/** Close session and stop the agent process. */
|
|
1768
|
-
async closeSession(sessionKey, reason) {
|
|
1769
|
-
const response = await this.httpClient.fetch(
|
|
1770
|
-
`/sdk/acp/session/${encodeSessionKey(sessionKey)}`,
|
|
1771
|
-
{
|
|
1772
|
-
method: "DELETE",
|
|
1773
|
-
body: reason ? JSON.stringify({ reason }) : void 0
|
|
1774
|
-
}
|
|
1775
|
-
);
|
|
1776
|
-
await parseJsonResponse(response, "Failed to close session");
|
|
1777
|
-
}
|
|
1778
|
-
/**
|
|
1779
|
-
* Synchronous turn — waits for completion, returns full response.
|
|
1780
|
-
* Requires approvalPolicy set on the session (via create or patchSession).
|
|
1781
|
-
*/
|
|
1782
|
-
async chat(sessionKey, message, attachments) {
|
|
1783
|
-
const body = { message };
|
|
1784
|
-
if (attachments?.length) body.attachments = attachments;
|
|
1785
|
-
const response = await this.httpClient.fetch(
|
|
1786
|
-
`/sdk/acp/session/${encodeSessionKey(sessionKey)}/chat`,
|
|
1787
|
-
{ method: "POST", body: JSON.stringify(body) }
|
|
1788
|
-
);
|
|
1789
|
-
const data = await parseJsonResponse(
|
|
1790
|
-
response,
|
|
1791
|
-
"Chat request failed"
|
|
1792
|
-
);
|
|
1793
|
-
return data.response;
|
|
1794
|
-
}
|
|
1795
|
-
/**
|
|
1796
|
-
* Streaming turn via SSE. Yields events as they arrive.
|
|
1797
|
-
*
|
|
1798
|
-
* Uses named SSE events (event: + data: lines). The event type comes
|
|
1799
|
-
* from the `event:` line, not from inside the JSON payload.
|
|
1800
|
-
*
|
|
1801
|
-
* @example
|
|
1802
|
-
* ```typescript
|
|
1803
|
-
* for await (const event of sdk.acpAgent.streamChat(key, 'Explain this')) {
|
|
1804
|
-
* if (event.type === 'text_delta') console.log(event.data.text);
|
|
1805
|
-
* if (event.type === 'permission_request') {
|
|
1806
|
-
* await sdk.acpAgent.resolvePermission(key, {
|
|
1807
|
-
* requestId: event.data.requestId as string,
|
|
1808
|
-
* optionId: 'allow_once',
|
|
1809
|
-
* });
|
|
1810
|
-
* }
|
|
1811
|
-
* }
|
|
1812
|
-
* ```
|
|
1813
|
-
*/
|
|
1814
|
-
async *streamChat(sessionKey, message, attachments) {
|
|
1815
|
-
const body = { message };
|
|
1816
|
-
if (attachments?.length) body.attachments = attachments;
|
|
1817
|
-
const response = await this.httpClient.fetch(
|
|
1818
|
-
`/sdk/acp/session/${encodeSessionKey(sessionKey)}/chat/stream`,
|
|
1819
|
-
{ method: "POST", body: JSON.stringify(body) }
|
|
1820
|
-
);
|
|
1821
|
-
if (!response.ok) {
|
|
1822
|
-
const data = await response.json();
|
|
1823
|
-
throw new Error(data.error || "Stream request failed");
|
|
1824
|
-
}
|
|
1825
|
-
if (!response.body) {
|
|
1826
|
-
throw new Error("Response body is null");
|
|
1827
|
-
}
|
|
1828
|
-
yield* parseNamedSSEStream(response.body);
|
|
1829
|
-
}
|
|
1830
|
-
/** Cancel the active turn on a session. */
|
|
1831
|
-
async cancelTurn(sessionKey, reason) {
|
|
1832
|
-
const response = await this.httpClient.fetch(
|
|
1833
|
-
`/sdk/acp/session/${encodeSessionKey(sessionKey)}/cancel`,
|
|
1834
|
-
{
|
|
1835
|
-
method: "POST",
|
|
1836
|
-
body: reason ? JSON.stringify({ reason }) : void 0
|
|
1837
|
-
}
|
|
1838
|
-
);
|
|
1839
|
-
await parseJsonResponse(response, "Failed to cancel turn");
|
|
1840
|
-
}
|
|
1841
|
-
/** Resolve a pending permission request (call while SSE stream is active). */
|
|
1842
|
-
async resolvePermission(sessionKey, decision) {
|
|
1843
|
-
const response = await this.httpClient.fetch(
|
|
1844
|
-
`/sdk/acp/session/${encodeSessionKey(sessionKey)}/permission`,
|
|
1845
|
-
{ method: "POST", body: JSON.stringify(decision) }
|
|
1846
|
-
);
|
|
1847
|
-
return parseJsonResponse(response, "Failed to resolve permission");
|
|
1848
|
-
}
|
|
1849
|
-
/** Convenience: create session + first sync chat in one call. */
|
|
1850
|
-
async startChat(message, options) {
|
|
1851
|
-
const session = await this.createSession(options);
|
|
1852
|
-
const chatResponse = await this.chat(session.session_key, message);
|
|
1853
|
-
return { session, response: chatResponse };
|
|
1854
|
-
}
|
|
1855
|
-
};
|
|
1856
|
-
async function* parseNamedSSEStream(body) {
|
|
1857
|
-
const reader = body.getReader();
|
|
1858
|
-
const decoder = new TextDecoder();
|
|
1859
|
-
let buffer = "";
|
|
1860
|
-
let currentEvent = "";
|
|
1861
|
-
try {
|
|
1862
|
-
while (true) {
|
|
1863
|
-
const { done, value } = await reader.read();
|
|
1864
|
-
if (done) break;
|
|
1865
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1866
|
-
const lines = buffer.split("\n");
|
|
1867
|
-
buffer = lines.pop() || "";
|
|
1868
|
-
for (const line of lines) {
|
|
1869
|
-
if (line.startsWith("event: ")) {
|
|
1870
|
-
currentEvent = line.slice(7).trim();
|
|
1871
|
-
} else if (line.startsWith("data: ")) {
|
|
1872
|
-
const jsonStr = line.slice(6);
|
|
1873
|
-
const eventType = currentEvent || void 0;
|
|
1874
|
-
currentEvent = "";
|
|
1875
|
-
if (!eventType) continue;
|
|
1876
|
-
try {
|
|
1877
|
-
const data = JSON.parse(jsonStr);
|
|
1878
|
-
yield { type: eventType, data };
|
|
1879
|
-
} catch {
|
|
1880
|
-
}
|
|
1881
|
-
} else if (line === "") {
|
|
1882
|
-
currentEvent = "";
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
} finally {
|
|
1887
|
-
reader.releaseLock();
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
// src/modules/mcp.ts
|
|
1892
|
-
var MCPModule = class extends ApiModule {
|
|
1893
|
-
constructor(realtimexUrl, appId, appName, apiKey) {
|
|
1894
|
-
super(realtimexUrl, appId, appName, apiKey);
|
|
1895
|
-
}
|
|
1896
|
-
/**
|
|
1897
|
-
* List configured MCP servers.
|
|
1898
|
-
* @param provider - Filter by provider: 'local', 'remote', or 'all' (default: 'all')
|
|
1899
|
-
* @returns Array of MCP server objects
|
|
1900
|
-
*/
|
|
1901
|
-
async getServers(provider = "all") {
|
|
1902
|
-
const params = provider !== "all" ? `?provider=${provider}` : "";
|
|
1903
|
-
const data = await this.apiCall("GET", `/sdk/mcp/servers${params}`);
|
|
1904
|
-
return data.servers;
|
|
1905
|
-
}
|
|
1906
|
-
/**
|
|
1907
|
-
* List available tools for a specific MCP server.
|
|
1908
|
-
* @param serverName - The server name (slug)
|
|
1909
|
-
* @param provider - Provider: 'local' or 'remote' (default: 'local')
|
|
1910
|
-
* @returns Array of tool objects with name, description, and input schema
|
|
1911
|
-
*/
|
|
1912
|
-
async getTools(serverName, provider = "local") {
|
|
1913
|
-
const data = await this.apiCall(
|
|
1914
|
-
"GET",
|
|
1915
|
-
`/sdk/mcp/servers/${encodeURIComponent(serverName)}/tools?provider=${provider}`
|
|
1916
|
-
);
|
|
1917
|
-
return data.tools;
|
|
1918
|
-
}
|
|
1919
|
-
/**
|
|
1920
|
-
* Execute a tool on an MCP server.
|
|
1921
|
-
* @param serverName - The server name (slug)
|
|
1922
|
-
* @param toolName - The tool name to execute
|
|
1923
|
-
* @param args - Arguments to pass to the tool (matches tool's input_schema)
|
|
1924
|
-
* @param provider - Provider: 'local' or 'remote' (default: 'local')
|
|
1925
|
-
* @returns Tool execution result
|
|
1926
|
-
*/
|
|
1927
|
-
async executeTool(serverName, toolName, args = {}, provider = "local") {
|
|
1928
|
-
const data = await this.apiCall(
|
|
1929
|
-
"POST",
|
|
1930
|
-
`/sdk/mcp/servers/${encodeURIComponent(serverName)}/tools/${encodeURIComponent(toolName)}/execute?provider=${provider}`,
|
|
1931
|
-
{
|
|
1932
|
-
body: JSON.stringify({ arguments: args })
|
|
1933
|
-
}
|
|
1934
|
-
);
|
|
1935
|
-
return data.result;
|
|
1936
|
-
}
|
|
1937
|
-
};
|
|
1938
|
-
|
|
1939
|
-
// src/modules/http.ts
|
|
1940
|
-
var HttpClient = class {
|
|
1941
|
-
constructor(baseUrl, appId, apiKey) {
|
|
1942
|
-
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
1943
|
-
this.appId = appId;
|
|
1944
|
-
this.apiKey = apiKey;
|
|
1945
|
-
}
|
|
1946
|
-
getAppHeaders() {
|
|
1947
|
-
const headers = {
|
|
1948
|
-
"Content-Type": "application/json"
|
|
1949
|
-
};
|
|
1950
|
-
if (this.appId) headers["x-app-id"] = this.appId;
|
|
1951
|
-
if (this.apiKey) headers["x-api-key"] = this.apiKey;
|
|
1952
|
-
return headers;
|
|
1953
|
-
}
|
|
1954
|
-
async getAccessToken() {
|
|
1955
|
-
if (this.accessToken) return this.accessToken;
|
|
1956
|
-
try {
|
|
1957
|
-
const res = await fetch(`${this.baseUrl}/sdk/auth/token`, {
|
|
1958
|
-
headers: this.getAppHeaders()
|
|
1959
|
-
});
|
|
1960
|
-
const data = await res.json();
|
|
1961
|
-
if (res.ok && data.token) {
|
|
1962
|
-
this.accessToken = data.token;
|
|
1963
|
-
return data.token;
|
|
1964
|
-
}
|
|
1965
|
-
} catch (e) {
|
|
1966
|
-
}
|
|
1967
|
-
return "";
|
|
1968
|
-
}
|
|
1969
|
-
async fetch(endpoint, options = {}) {
|
|
1970
|
-
const url = `${this.baseUrl}${endpoint}`;
|
|
1971
|
-
let token = await this.getAccessToken();
|
|
1972
|
-
const makeRequest = (authToken) => {
|
|
1973
|
-
const headers = {
|
|
1974
|
-
...this.getAppHeaders(),
|
|
1975
|
-
...options.headers || {}
|
|
1976
|
-
};
|
|
1977
|
-
if (authToken) {
|
|
1978
|
-
headers["Authorization"] = `Bearer ${authToken}`;
|
|
1979
|
-
}
|
|
1980
|
-
return fetch(url, { ...options, headers });
|
|
1981
|
-
};
|
|
1982
|
-
let response = await makeRequest(token);
|
|
1983
|
-
if (response.status === 401) {
|
|
1984
|
-
console.log("[RealtimeX SDK] 401 Unauthorized, refreshing access token...");
|
|
1985
|
-
this.accessToken = void 0;
|
|
1986
|
-
const newToken = await this.getAccessToken();
|
|
1987
|
-
if (newToken && newToken !== token) {
|
|
1988
|
-
console.log("[RealtimeX SDK] Retrying request with new token");
|
|
1989
|
-
response = await makeRequest(newToken);
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
return response;
|
|
1993
|
-
}
|
|
1994
|
-
};
|
|
1995
|
-
|
|
1996
|
-
// src/core/errors/ContractErrors.ts
|
|
1997
|
-
var ContractError = class extends Error {
|
|
1998
|
-
constructor(code, message, details) {
|
|
1999
|
-
super(message);
|
|
2000
|
-
this.name = this.constructor.name;
|
|
2001
|
-
this.code = code;
|
|
2002
|
-
this.details = details;
|
|
2003
|
-
}
|
|
2004
|
-
};
|
|
2005
|
-
var ContractValidationError = class extends ContractError {
|
|
2006
|
-
constructor(message, details) {
|
|
2007
|
-
super("contract_validation_error", message, details);
|
|
2008
|
-
}
|
|
2009
|
-
};
|
|
2010
|
-
var ToolValidationError = class extends ContractError {
|
|
2011
|
-
constructor(message, details) {
|
|
2012
|
-
super("tool_validation_error", message, details);
|
|
2013
|
-
}
|
|
2014
|
-
};
|
|
2015
|
-
var ToolNotFoundError = class extends ContractError {
|
|
2016
|
-
constructor(toolName, details) {
|
|
2017
|
-
super("tool_not_found", `Tool not found: ${toolName}`, details);
|
|
2018
|
-
}
|
|
2019
|
-
};
|
|
2020
|
-
var ScopeDeniedError = class extends ContractError {
|
|
2021
|
-
constructor(permission, details) {
|
|
2022
|
-
super("scope_denied", `Missing required permission: ${permission}`, details);
|
|
2023
|
-
}
|
|
2024
|
-
};
|
|
2025
|
-
var RuntimeTransportError = class extends ContractError {
|
|
2026
|
-
constructor(message, statusCode, details) {
|
|
2027
|
-
super("runtime_transport_error", message, details);
|
|
2028
|
-
this.statusCode = statusCode;
|
|
2029
|
-
}
|
|
2030
|
-
};
|
|
2031
|
-
|
|
2032
|
-
// src/core/auth/ScopeGuard.ts
|
|
2033
|
-
var ScopeGuard = class {
|
|
2034
|
-
constructor(scopes = []) {
|
|
2035
|
-
this.scopes = new Set(scopes.filter((scope) => scope.trim().length > 0));
|
|
2036
|
-
}
|
|
2037
|
-
can(permission) {
|
|
2038
|
-
if (!permission) return true;
|
|
2039
|
-
if (this.scopes.size === 0) return true;
|
|
2040
|
-
return this.scopes.has(permission);
|
|
2041
|
-
}
|
|
2042
|
-
assert(permission) {
|
|
2043
|
-
if (!permission) return;
|
|
2044
|
-
if (!this.can(permission)) {
|
|
2045
|
-
throw new ScopeDeniedError(permission);
|
|
2046
|
-
}
|
|
2047
|
-
}
|
|
2048
|
-
};
|
|
2049
|
-
|
|
2050
|
-
// src/core/contract/ContractCache.ts
|
|
2051
|
-
var ContractCache = class {
|
|
2052
|
-
constructor(ttlMs = 3e4) {
|
|
2053
|
-
this.entries = /* @__PURE__ */ new Map();
|
|
2054
|
-
this.ttlMs = ttlMs > 0 ? ttlMs : null;
|
|
2055
|
-
}
|
|
2056
|
-
get(key) {
|
|
2057
|
-
const entry = this.entries.get(key);
|
|
2058
|
-
if (!entry) return null;
|
|
2059
|
-
if (entry.expiresAt !== null && entry.expiresAt <= Date.now()) {
|
|
2060
|
-
this.entries.delete(key);
|
|
2061
|
-
return null;
|
|
2062
|
-
}
|
|
2063
|
-
return entry.value;
|
|
2064
|
-
}
|
|
2065
|
-
set(key, value) {
|
|
2066
|
-
const expiresAt = this.ttlMs === null ? null : Date.now() + this.ttlMs;
|
|
2067
|
-
this.entries.set(key, { value, expiresAt });
|
|
2068
|
-
}
|
|
2069
|
-
clear(key) {
|
|
2070
|
-
if (key) {
|
|
2071
|
-
this.entries.delete(key);
|
|
2072
|
-
return;
|
|
2073
|
-
}
|
|
2074
|
-
this.entries.clear();
|
|
2075
|
-
}
|
|
2076
|
-
};
|
|
2077
|
-
|
|
2078
|
-
// src/core/contract/ContractValidator.ts
|
|
2079
|
-
var LOCAL_APP_CONTRACT_VERSION2 = "local-app-contract/v1";
|
|
2080
|
-
var DEFAULT_SUPPORTED_EVENTS = [
|
|
2081
|
-
"task.trigger",
|
|
2082
|
-
"system.ping",
|
|
2083
|
-
"task.claimed",
|
|
2084
|
-
"task.started",
|
|
2085
|
-
"task.progress",
|
|
2086
|
-
"task.completed",
|
|
2087
|
-
"task.failed",
|
|
2088
|
-
"task.canceled"
|
|
2089
|
-
];
|
|
2090
|
-
function isRecord(value) {
|
|
2091
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2092
|
-
}
|
|
2093
|
-
function asStringArray(value) {
|
|
2094
|
-
if (!Array.isArray(value)) return [];
|
|
2095
|
-
return value.filter((item) => typeof item === "string" && item.trim().length > 0);
|
|
2096
|
-
}
|
|
2097
|
-
function asStringRecord(value) {
|
|
2098
|
-
if (!isRecord(value)) return void 0;
|
|
2099
|
-
const entries = Object.entries(value).filter(
|
|
2100
|
-
([key, item]) => key.trim().length > 0 && typeof item === "string"
|
|
2101
|
-
);
|
|
2102
|
-
if (entries.length === 0) return void 0;
|
|
2103
|
-
return entries.reduce((accumulator, [key, item]) => {
|
|
2104
|
-
accumulator[key] = item;
|
|
2105
|
-
return accumulator;
|
|
2106
|
-
}, {});
|
|
2107
|
-
}
|
|
2108
|
-
function normalizeStrictness(value) {
|
|
2109
|
-
return value === "strict" ? "strict" : "compatible";
|
|
2110
|
-
}
|
|
2111
|
-
function normalizeCallbackRules(value) {
|
|
2112
|
-
if (!isRecord(value)) return void 0;
|
|
2113
|
-
const callback = {};
|
|
2114
|
-
if (typeof value.event_id_header === "string") callback.event_id_header = value.event_id_header;
|
|
2115
|
-
if (typeof value.signature_header === "string") callback.signature_header = value.signature_header;
|
|
2116
|
-
if (typeof value.signature_algorithm === "string") callback.signature_algorithm = value.signature_algorithm;
|
|
2117
|
-
if (typeof value.signature_message === "string") callback.signature_message = value.signature_message;
|
|
2118
|
-
if (typeof value.attempt_id_format === "string") callback.attempt_id_format = value.attempt_id_format;
|
|
2119
|
-
if (typeof value.idempotency === "string") callback.idempotency = value.idempotency;
|
|
2120
|
-
return Object.keys(callback).length > 0 ? callback : void 0;
|
|
2121
|
-
}
|
|
2122
|
-
function normalizeTrigger(value) {
|
|
2123
|
-
if (!isRecord(value)) {
|
|
2124
|
-
return { event: "task.trigger" };
|
|
2125
|
-
}
|
|
2126
|
-
const event = value.event === "task.trigger" ? "task.trigger" : "task.trigger";
|
|
2127
|
-
const route = typeof value.route === "string" && value.route.trim().length > 0 ? value.route : void 0;
|
|
2128
|
-
const payloadTemplate = isRecord(value.payload_template) ? value.payload_template : void 0;
|
|
2129
|
-
return {
|
|
2130
|
-
event,
|
|
2131
|
-
route,
|
|
2132
|
-
payload_template: payloadTemplate
|
|
2133
|
-
};
|
|
2134
|
-
}
|
|
2135
|
-
function normalizeCapability(value) {
|
|
2136
|
-
if (!isRecord(value)) {
|
|
2137
|
-
throw new ContractValidationError("Invalid capability: expected object");
|
|
2138
|
-
}
|
|
2139
|
-
const capabilityId = typeof value.capability_id === "string" ? value.capability_id.trim() : "";
|
|
2140
|
-
const name = typeof value.name === "string" ? value.name.trim() : "";
|
|
2141
|
-
const description = typeof value.description === "string" ? value.description.trim() : "";
|
|
2142
|
-
const permission = typeof value.permission === "string" ? value.permission.trim() : "";
|
|
2143
|
-
if (!capabilityId || !name || !description || !permission) {
|
|
2144
|
-
throw new ContractValidationError("Invalid capability: missing required fields", { capability: value });
|
|
2145
|
-
}
|
|
2146
|
-
if (!isRecord(value.input_schema)) {
|
|
2147
|
-
throw new ContractValidationError("Invalid capability input_schema: expected JSON schema object", {
|
|
2148
|
-
capability_id: capabilityId
|
|
2149
|
-
});
|
|
2150
|
-
}
|
|
2151
|
-
return {
|
|
2152
|
-
capability_id: capabilityId,
|
|
2153
|
-
name,
|
|
2154
|
-
description,
|
|
2155
|
-
input_schema: value.input_schema,
|
|
2156
|
-
output_schema: isRecord(value.output_schema) ? value.output_schema : void 0,
|
|
2157
|
-
permission,
|
|
2158
|
-
trigger: normalizeTrigger(value.trigger)
|
|
2159
|
-
};
|
|
2160
|
-
}
|
|
2161
|
-
function normalizeCapabilities(value) {
|
|
2162
|
-
if (value === void 0 || value === null) return void 0;
|
|
2163
|
-
if (!Array.isArray(value)) {
|
|
2164
|
-
throw new ContractValidationError("Invalid capabilities: expected array");
|
|
2165
|
-
}
|
|
2166
|
-
return value.map(normalizeCapability);
|
|
2167
|
-
}
|
|
2168
|
-
function extractContractRecord(payload) {
|
|
2169
|
-
if (!isRecord(payload)) {
|
|
2170
|
-
throw new ContractValidationError("Invalid contract response: expected object payload");
|
|
2171
|
-
}
|
|
2172
|
-
if (isRecord(payload.contract)) {
|
|
2173
|
-
return payload.contract;
|
|
2174
|
-
}
|
|
2175
|
-
return payload;
|
|
2176
|
-
}
|
|
2177
|
-
function resolveContractVersion(contract) {
|
|
2178
|
-
if (typeof contract.contract_version === "string") return contract.contract_version;
|
|
2179
|
-
if (typeof contract.version === "string") return contract.version;
|
|
2180
|
-
if (typeof contract.id === "string") return contract.id;
|
|
2181
|
-
return "";
|
|
2182
|
-
}
|
|
2183
|
-
function normalizeLocalAppContractV1(payload) {
|
|
2184
|
-
const contract = extractContractRecord(payload);
|
|
2185
|
-
const version = resolveContractVersion(contract);
|
|
2186
|
-
if (!version) {
|
|
2187
|
-
throw new ContractValidationError("Missing contract version in discovery response");
|
|
2188
|
-
}
|
|
2189
|
-
if (version !== LOCAL_APP_CONTRACT_VERSION2) {
|
|
2190
|
-
throw new ContractValidationError("Unsupported contract version", {
|
|
2191
|
-
expected: LOCAL_APP_CONTRACT_VERSION2,
|
|
2192
|
-
received: version
|
|
2193
|
-
});
|
|
2194
|
-
}
|
|
2195
|
-
const supportedContractEvents = asStringArray(contract.supported_contract_events).length > 0 ? asStringArray(contract.supported_contract_events) : asStringArray(contract.supported_events).length > 0 ? asStringArray(contract.supported_events) : DEFAULT_SUPPORTED_EVENTS;
|
|
2196
|
-
return {
|
|
2197
|
-
contract_version: LOCAL_APP_CONTRACT_VERSION2,
|
|
2198
|
-
strictness: normalizeStrictness(contract.strictness),
|
|
2199
|
-
supported_contract_events: supportedContractEvents,
|
|
2200
|
-
supported_legacy_events: asStringArray(contract.supported_legacy_events),
|
|
2201
|
-
aliases: asStringRecord(contract.aliases),
|
|
2202
|
-
status_map: asStringRecord(contract.status_map),
|
|
2203
|
-
legacy_action_map: asStringRecord(contract.legacy_action_map),
|
|
2204
|
-
callback: normalizeCallbackRules(contract.callback),
|
|
2205
|
-
capabilities: normalizeCapabilities(contract.capabilities)
|
|
2206
|
-
};
|
|
2207
|
-
}
|
|
2208
|
-
|
|
2209
|
-
// src/core/contract/ContractClient.ts
|
|
2210
|
-
var ContractClient = class {
|
|
2211
|
-
constructor(httpClient, options = {}) {
|
|
2212
|
-
this.httpClient = httpClient;
|
|
2213
|
-
this.cache = options.cache || new ContractCache();
|
|
2214
|
-
this.cacheKey = options.cacheKey || "local-app-contract/v1";
|
|
2215
|
-
}
|
|
2216
|
-
async getLocalAppV1(forceRefresh = false) {
|
|
2217
|
-
if (!forceRefresh) {
|
|
2218
|
-
const cached = this.cache.get(this.cacheKey);
|
|
2219
|
-
if (cached) return cached;
|
|
2220
|
-
}
|
|
2221
|
-
const response = await this.httpClient.get("/contracts/local-app/v1");
|
|
2222
|
-
const normalized = normalizeLocalAppContractV1(response);
|
|
2223
|
-
this.cache.set(this.cacheKey, normalized);
|
|
2224
|
-
return normalized;
|
|
2225
|
-
}
|
|
2226
|
-
clearCache() {
|
|
2227
|
-
this.cache.clear(this.cacheKey);
|
|
2228
|
-
}
|
|
2229
|
-
};
|
|
2230
|
-
|
|
2231
|
-
// src/core/transport/HttpClient.ts
|
|
2232
|
-
function toRecordHeaders(input = {}) {
|
|
2233
|
-
if (input instanceof Headers) {
|
|
2234
|
-
const normalized = {};
|
|
2235
|
-
input.forEach((value, key) => {
|
|
2236
|
-
normalized[key] = value;
|
|
2237
|
-
});
|
|
2238
|
-
return normalized;
|
|
2239
|
-
}
|
|
2240
|
-
if (Array.isArray(input)) {
|
|
2241
|
-
return Object.fromEntries(input);
|
|
2242
|
-
}
|
|
2243
|
-
return { ...input };
|
|
2244
|
-
}
|
|
2245
|
-
var ContractHttpClient = class {
|
|
2246
|
-
constructor(config) {
|
|
2247
|
-
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
2248
|
-
this.appId = config.appId;
|
|
2249
|
-
this.appName = config.appName;
|
|
2250
|
-
this.apiKey = config.apiKey;
|
|
2251
|
-
this.fetchImpl = config.fetchImpl || fetch;
|
|
2252
|
-
}
|
|
2253
|
-
async get(path, headers = {}) {
|
|
2254
|
-
return this.request(path, {
|
|
2255
|
-
method: "GET",
|
|
2256
|
-
headers
|
|
2257
|
-
});
|
|
2258
|
-
}
|
|
2259
|
-
async post(path, body, headers = {}) {
|
|
2260
|
-
return this.request(path, {
|
|
2261
|
-
method: "POST",
|
|
2262
|
-
headers,
|
|
2263
|
-
body: JSON.stringify(body)
|
|
2264
|
-
});
|
|
2265
|
-
}
|
|
2266
|
-
async request(path, options = {}) {
|
|
2267
|
-
const url = `${this.baseUrl}${path}`;
|
|
2268
|
-
const headers = this.buildHeaders(options.headers);
|
|
2269
|
-
let response;
|
|
2270
|
-
try {
|
|
2271
|
-
response = await this.fetchImpl(url, {
|
|
2272
|
-
...options,
|
|
2273
|
-
headers
|
|
2274
|
-
});
|
|
2275
|
-
} catch (error) {
|
|
2276
|
-
throw new RuntimeTransportError("Failed to reach RealtimeX server", void 0, {
|
|
2277
|
-
cause: error,
|
|
2278
|
-
url
|
|
2279
|
-
});
|
|
2280
|
-
}
|
|
2281
|
-
const payload = await this.parseResponse(response);
|
|
2282
|
-
if (!response.ok) {
|
|
2283
|
-
const errorMessage = payload && typeof payload === "object" && "error" in payload && typeof payload.error === "string" ? payload.error : `Request failed with status ${response.status}`;
|
|
2284
|
-
throw new RuntimeTransportError(errorMessage, response.status, payload);
|
|
2285
|
-
}
|
|
2286
|
-
return payload;
|
|
2287
|
-
}
|
|
2288
|
-
buildHeaders(extraHeaders = {}) {
|
|
2289
|
-
const headers = {
|
|
2290
|
-
"Content-Type": "application/json",
|
|
2291
|
-
...toRecordHeaders(extraHeaders)
|
|
2292
|
-
};
|
|
2293
|
-
if (this.apiKey) {
|
|
2294
|
-
headers.Authorization = `Bearer ${this.apiKey}`;
|
|
2295
|
-
}
|
|
2296
|
-
if (this.appId) {
|
|
2297
|
-
headers["x-app-id"] = this.appId;
|
|
2298
|
-
}
|
|
2299
|
-
if (this.appName) {
|
|
2300
|
-
headers["x-app-name"] = this.appName;
|
|
2301
|
-
}
|
|
2302
|
-
return headers;
|
|
2303
|
-
}
|
|
2304
|
-
async parseResponse(response) {
|
|
2305
|
-
const contentType = response.headers.get("content-type") || "";
|
|
2306
|
-
if (contentType.includes("application/json")) {
|
|
2307
|
-
try {
|
|
2308
|
-
return await response.json();
|
|
2309
|
-
} catch {
|
|
2310
|
-
return {};
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2313
|
-
const text = await response.text();
|
|
2314
|
-
if (!text) return {};
|
|
2315
|
-
try {
|
|
2316
|
-
return JSON.parse(text);
|
|
2317
|
-
} catch {
|
|
2318
|
-
return { text };
|
|
2319
|
-
}
|
|
2320
|
-
}
|
|
2321
|
-
};
|
|
2322
|
-
|
|
2323
|
-
// src/core/tooling/SchemaNormalizer.ts
|
|
2324
|
-
function isRecord2(value) {
|
|
2325
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2326
|
-
}
|
|
2327
|
-
function deepClone(value) {
|
|
2328
|
-
return JSON.parse(JSON.stringify(value));
|
|
2329
|
-
}
|
|
2330
|
-
function normalizeSchema(schema) {
|
|
2331
|
-
if (!schema || !isRecord2(schema)) {
|
|
2332
|
-
return {
|
|
2333
|
-
type: "object",
|
|
2334
|
-
properties: {},
|
|
2335
|
-
additionalProperties: true
|
|
2336
|
-
};
|
|
2337
|
-
}
|
|
2338
|
-
const normalized = deepClone(schema);
|
|
2339
|
-
if (typeof normalized.type !== "string" && !Array.isArray(normalized.type)) {
|
|
2340
|
-
normalized.type = "object";
|
|
2341
|
-
}
|
|
2342
|
-
if (normalized.type === "object" && !isRecord2(normalized.properties)) {
|
|
2343
|
-
normalized.properties = {};
|
|
2344
|
-
}
|
|
2345
|
-
if (Array.isArray(normalized.required)) {
|
|
2346
|
-
normalized.required = normalized.required.filter(
|
|
2347
|
-
(field) => typeof field === "string" && field.trim().length > 0
|
|
2348
|
-
);
|
|
2349
|
-
}
|
|
2350
|
-
return normalized;
|
|
2351
|
-
}
|
|
2352
|
-
|
|
2353
|
-
// src/core/tooling/ToolNamePolicy.ts
|
|
2354
|
-
function normalizeToken(value) {
|
|
2355
|
-
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
2356
|
-
}
|
|
2357
|
-
function toStableToolName(capabilityId, namespace) {
|
|
2358
|
-
const normalizedCapability = normalizeToken(capabilityId.replace(/\./g, "_"));
|
|
2359
|
-
const normalizedNamespace = namespace ? normalizeToken(namespace) : "";
|
|
2360
|
-
if (!normalizedCapability) {
|
|
2361
|
-
return normalizedNamespace ? `${normalizedNamespace}_tool` : "tool";
|
|
2362
|
-
}
|
|
2363
|
-
const combined = normalizedNamespace && !normalizedCapability.startsWith(`${normalizedNamespace}_`) ? `${normalizedNamespace}_${normalizedCapability}` : normalizedCapability;
|
|
2364
|
-
return /^[a-z]/.test(combined) ? combined : `tool_${combined}`;
|
|
2365
|
-
}
|
|
2366
|
-
|
|
2367
|
-
// src/core/tooling/ToolProjector.ts
|
|
2368
|
-
var ToolProjector = class {
|
|
2369
|
-
project(input) {
|
|
2370
|
-
const capabilities = input.contract.capabilities || [];
|
|
2371
|
-
const names = /* @__PURE__ */ new Set();
|
|
2372
|
-
return capabilities.map((capability) => {
|
|
2373
|
-
const baseName = toStableToolName(capability.capability_id, input.namespace || input.appId);
|
|
2374
|
-
const uniqueName = this.ensureUniqueToolName(baseName, names);
|
|
2375
|
-
return {
|
|
2376
|
-
tool_name: uniqueName,
|
|
2377
|
-
title: capability.name,
|
|
2378
|
-
description: capability.description,
|
|
2379
|
-
input_schema: normalizeSchema(capability.input_schema),
|
|
2380
|
-
output_schema: capability.output_schema,
|
|
2381
|
-
permission: capability.permission,
|
|
2382
|
-
capability_id: capability.capability_id,
|
|
2383
|
-
trigger: capability.trigger
|
|
2384
|
-
};
|
|
2385
|
-
});
|
|
2386
|
-
}
|
|
2387
|
-
ensureUniqueToolName(baseName, names) {
|
|
2388
|
-
if (!names.has(baseName)) {
|
|
2389
|
-
names.add(baseName);
|
|
2390
|
-
return baseName;
|
|
2391
|
-
}
|
|
2392
|
-
let index = 2;
|
|
2393
|
-
while (names.has(`${baseName}_${index}`)) {
|
|
2394
|
-
index += 1;
|
|
2395
|
-
}
|
|
2396
|
-
const uniqueName = `${baseName}_${index}`;
|
|
2397
|
-
names.add(uniqueName);
|
|
2398
|
-
return uniqueName;
|
|
2399
|
-
}
|
|
2400
|
-
};
|
|
2401
|
-
|
|
2402
|
-
// src/core/runtime/ExecutionStore.ts
|
|
2403
|
-
var ExecutionStore = class {
|
|
2404
|
-
constructor() {
|
|
2405
|
-
this.registries = /* @__PURE__ */ new Map();
|
|
2406
|
-
}
|
|
2407
|
-
registerTools(registryKey, tools) {
|
|
2408
|
-
const registry = /* @__PURE__ */ new Map();
|
|
2409
|
-
for (const tool of tools) {
|
|
2410
|
-
registry.set(tool.tool_name, tool);
|
|
2411
|
-
}
|
|
2412
|
-
this.registries.set(registryKey, registry);
|
|
2413
|
-
}
|
|
2414
|
-
hasRegistry(registryKey) {
|
|
2415
|
-
return this.registries.has(registryKey);
|
|
2416
|
-
}
|
|
2417
|
-
getTool(registryKey, toolName) {
|
|
2418
|
-
return this.registries.get(registryKey)?.get(toolName);
|
|
2419
|
-
}
|
|
2420
|
-
clear(registryKey) {
|
|
2421
|
-
if (registryKey) {
|
|
2422
|
-
this.registries.delete(registryKey);
|
|
2423
|
-
return;
|
|
2424
|
-
}
|
|
2425
|
-
this.registries.clear();
|
|
2426
|
-
}
|
|
2427
|
-
};
|
|
2428
|
-
|
|
2429
|
-
// src/core/runtime/LifecycleReporter.ts
|
|
2430
|
-
var LifecycleReporter = class {
|
|
2431
|
-
constructor() {
|
|
2432
|
-
this.handlers = /* @__PURE__ */ new Set();
|
|
2433
|
-
}
|
|
2434
|
-
onEvent(handler) {
|
|
2435
|
-
this.handlers.add(handler);
|
|
2436
|
-
return () => {
|
|
2437
|
-
this.handlers.delete(handler);
|
|
2438
|
-
};
|
|
2439
|
-
}
|
|
2440
|
-
emit(event) {
|
|
2441
|
-
for (const handler of this.handlers) {
|
|
2442
|
-
try {
|
|
2443
|
-
handler(event);
|
|
2444
|
-
} catch {
|
|
2445
|
-
}
|
|
2446
|
-
}
|
|
2447
|
-
}
|
|
2448
|
-
};
|
|
2449
|
-
|
|
2450
|
-
// src/core/runtime/RetryPolicy.ts
|
|
2451
|
-
function defaultShouldRetry(error) {
|
|
2452
|
-
if (error && typeof error === "object") {
|
|
2453
|
-
const status = error.statusCode;
|
|
2454
|
-
if (typeof status === "number") {
|
|
2455
|
-
return status >= 500 || status === 429;
|
|
2456
|
-
}
|
|
2457
|
-
}
|
|
2458
|
-
return true;
|
|
2459
|
-
}
|
|
2460
|
-
var RetryPolicy = class {
|
|
2461
|
-
constructor(options = {}) {
|
|
2462
|
-
this.maxAttempts = Math.max(1, options.maxAttempts ?? 3);
|
|
2463
|
-
this.baseDelayMs = Math.max(0, options.baseDelayMs ?? 150);
|
|
2464
|
-
this.maxDelayMs = Math.max(this.baseDelayMs, options.maxDelayMs ?? 2e3);
|
|
2465
|
-
this.shouldRetry = options.shouldRetry || ((error) => defaultShouldRetry(error));
|
|
2466
|
-
}
|
|
2467
|
-
async execute(operation) {
|
|
2468
|
-
let lastError = null;
|
|
2469
|
-
for (let attempt = 1; attempt <= this.maxAttempts; attempt += 1) {
|
|
2470
|
-
try {
|
|
2471
|
-
return await operation(attempt);
|
|
2472
|
-
} catch (error) {
|
|
2473
|
-
lastError = error;
|
|
2474
|
-
const canRetry = attempt < this.maxAttempts && this.shouldRetry(error, attempt);
|
|
2475
|
-
if (!canRetry) throw error;
|
|
2476
|
-
const delayMs = Math.min(this.maxDelayMs, this.baseDelayMs * 2 ** (attempt - 1));
|
|
2477
|
-
if (delayMs > 0) {
|
|
2478
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
2479
|
-
}
|
|
2480
|
-
}
|
|
2481
|
-
}
|
|
2482
|
-
throw lastError instanceof Error ? lastError : new Error("Retry operation failed");
|
|
2483
|
-
}
|
|
2484
|
-
};
|
|
2485
|
-
|
|
2486
|
-
// src/core/runtime/ContractRuntime.ts
|
|
2487
|
-
var LIFECYCLE_EVENTS = /* @__PURE__ */ new Set([
|
|
2488
|
-
"task.claimed",
|
|
2489
|
-
"task.started",
|
|
2490
|
-
"task.progress",
|
|
2491
|
-
"task.completed",
|
|
2492
|
-
"task.failed",
|
|
2493
|
-
"task.canceled"
|
|
2494
|
-
]);
|
|
2495
|
-
var DEFAULT_PROVIDER = "gemini";
|
|
2496
|
-
function isRecord3(value) {
|
|
2497
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2498
|
-
}
|
|
2499
|
-
function toRecord(value) {
|
|
2500
|
-
return isRecord3(value) ? value : {};
|
|
2501
|
-
}
|
|
2502
|
-
function resolveToolCallId(input) {
|
|
2503
|
-
if (typeof input.tool_call_id === "string" && input.tool_call_id.trim().length > 0) {
|
|
2504
|
-
return input.tool_call_id;
|
|
2505
|
-
}
|
|
2506
|
-
const payload = isRecord3(input.payload) ? input.payload : void 0;
|
|
2507
|
-
if (payload && typeof payload.tool_call_id === "string" && payload.tool_call_id.trim().length > 0) {
|
|
2508
|
-
return payload.tool_call_id;
|
|
2509
|
-
}
|
|
2510
|
-
const data = isRecord3(input.data) ? input.data : void 0;
|
|
2511
|
-
if (data && typeof data.tool_call_id === "string" && data.tool_call_id.trim().length > 0) {
|
|
2512
|
-
return data.tool_call_id;
|
|
2513
|
-
}
|
|
2514
|
-
return void 0;
|
|
2515
|
-
}
|
|
2516
|
-
var ContractRuntime = class {
|
|
2517
|
-
constructor(options) {
|
|
2518
|
-
if (!options.baseUrl) {
|
|
2519
|
-
throw new ContractValidationError("ContractRuntime requires baseUrl");
|
|
2520
|
-
}
|
|
2521
|
-
this.appId = options.appId;
|
|
2522
|
-
this.appName = options.appName;
|
|
2523
|
-
this.defaultNamespace = options.namespace;
|
|
2524
|
-
this.httpClient = new ContractHttpClient({
|
|
2525
|
-
baseUrl: options.baseUrl,
|
|
2526
|
-
appId: options.appId,
|
|
2527
|
-
appName: options.appName,
|
|
2528
|
-
apiKey: options.apiKey,
|
|
2529
|
-
fetchImpl: options.fetchImpl
|
|
2530
|
-
});
|
|
2531
|
-
this.contractClient = new ContractClient(this.httpClient, {
|
|
2532
|
-
cache: new ContractCache(options.cacheTtlMs ?? 3e4)
|
|
2533
|
-
});
|
|
2534
|
-
this.toolProjector = new ToolProjector();
|
|
2535
|
-
this.executionStore = new ExecutionStore();
|
|
2536
|
-
this.lifecycleReporter = new LifecycleReporter();
|
|
2537
|
-
this.scopeGuard = new ScopeGuard(options.permissions || []);
|
|
2538
|
-
this.retryPolicy = new RetryPolicy(options.retry);
|
|
2539
|
-
}
|
|
2540
|
-
async getContract(appId) {
|
|
2541
|
-
this.assertAppContext(appId);
|
|
2542
|
-
return this.contractClient.getLocalAppV1(false);
|
|
2543
|
-
}
|
|
2544
|
-
async getTools(input) {
|
|
2545
|
-
this.assertAppContext(input.appId);
|
|
2546
|
-
const contract = await this.contractClient.getLocalAppV1(false);
|
|
2547
|
-
const tools = this.toolProjector.project({
|
|
2548
|
-
contract,
|
|
2549
|
-
provider: input.provider,
|
|
2550
|
-
appId: input.appId,
|
|
2551
|
-
namespace: input.namespace || this.defaultNamespace || input.appId
|
|
2552
|
-
});
|
|
2553
|
-
this.executionStore.registerTools(
|
|
2554
|
-
this.buildRegistryKey(input.appId, input.provider, input.namespace),
|
|
2555
|
-
tools
|
|
2556
|
-
);
|
|
2557
|
-
return tools;
|
|
2558
|
-
}
|
|
2559
|
-
async executeToolCall(call, context) {
|
|
2560
|
-
try {
|
|
2561
|
-
this.assertAppContext(context.appId);
|
|
2562
|
-
const provider = context.provider || DEFAULT_PROVIDER;
|
|
2563
|
-
const namespace = context.namespace || this.defaultNamespace || context.appId;
|
|
2564
|
-
const registryKey = this.buildRegistryKey(context.appId, provider, namespace);
|
|
2565
|
-
if (!this.executionStore.hasRegistry(registryKey)) {
|
|
2566
|
-
await this.getTools({
|
|
2567
|
-
appId: context.appId,
|
|
2568
|
-
provider,
|
|
2569
|
-
namespace
|
|
2570
|
-
});
|
|
2571
|
-
}
|
|
2572
|
-
const tool = this.executionStore.getTool(registryKey, call.tool_name);
|
|
2573
|
-
if (!tool) {
|
|
2574
|
-
throw new ToolNotFoundError(call.tool_name, {
|
|
2575
|
-
registryKey
|
|
2576
|
-
});
|
|
2577
|
-
}
|
|
2578
|
-
this.scopeGuard.assert(tool.permission);
|
|
2579
|
-
const validationErrors = this.validateToolArgs(tool, call.args);
|
|
2580
|
-
if (validationErrors.length > 0) {
|
|
2581
|
-
throw new ToolValidationError("Tool input validation failed", {
|
|
2582
|
-
tool_name: call.tool_name,
|
|
2583
|
-
errors: validationErrors
|
|
2584
|
-
});
|
|
2585
|
-
}
|
|
2586
|
-
const eventId = context.idempotencyKey || call.tool_call_id;
|
|
2587
|
-
const requestBody = this.buildTriggerRequestBody(call, context, tool, eventId);
|
|
2588
|
-
const response = await this.retryPolicy.execute(
|
|
2589
|
-
() => this.httpClient.post(tool.trigger.route || "/webhooks/realtimex", requestBody)
|
|
2590
|
-
);
|
|
2591
|
-
if (response.success === false) {
|
|
2592
|
-
throw new RuntimeTransportError(response.error || "Failed to trigger task", void 0, response);
|
|
2593
|
-
}
|
|
2594
|
-
const taskId = this.resolveTaskId(response);
|
|
2595
|
-
const attemptId = normalizeAttemptId(response.attempt_id);
|
|
2596
|
-
this.tryEmitLifecycleEvent({
|
|
2597
|
-
tool_call_id: call.tool_call_id,
|
|
2598
|
-
task_id: taskId,
|
|
2599
|
-
attempt_id: attemptId,
|
|
2600
|
-
event_type: response.event_type,
|
|
2601
|
-
event_id: response.event_id,
|
|
2602
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2603
|
-
payload: toRecord(response)
|
|
2604
|
-
});
|
|
2605
|
-
if (taskId) {
|
|
2606
|
-
return {
|
|
2607
|
-
status: "queued",
|
|
2608
|
-
tool_call_id: call.tool_call_id,
|
|
2609
|
-
task_id: taskId,
|
|
2610
|
-
attempt_id: attemptId,
|
|
2611
|
-
output: {
|
|
2612
|
-
message: response.message || "Task accepted"
|
|
2613
|
-
}
|
|
2614
|
-
};
|
|
2615
|
-
}
|
|
2616
|
-
return {
|
|
2617
|
-
status: "completed",
|
|
2618
|
-
tool_call_id: call.tool_call_id,
|
|
2619
|
-
output: toRecord(response)
|
|
2620
|
-
};
|
|
2621
|
-
} catch (error) {
|
|
2622
|
-
return this.toFailedResult(call.tool_call_id, error);
|
|
2623
|
-
}
|
|
2624
|
-
}
|
|
2625
|
-
onExecutionEvent(handler) {
|
|
2626
|
-
return this.lifecycleReporter.onEvent(handler);
|
|
2627
|
-
}
|
|
2628
|
-
ingestExecutionEvent(input) {
|
|
2629
|
-
const eventTypeRaw = typeof input.event_type === "string" && input.event_type || typeof input.event === "string" && input.event || "";
|
|
2630
|
-
const normalized = normalizeContractEvent(eventTypeRaw);
|
|
2631
|
-
if (!normalized || !LIFECYCLE_EVENTS.has(normalized)) {
|
|
2632
|
-
return null;
|
|
2633
|
-
}
|
|
2634
|
-
const taskId = typeof input.task_id === "string" && input.task_id || typeof input.task_uuid === "string" && input.task_uuid || "";
|
|
2635
|
-
if (!taskId) return null;
|
|
2636
|
-
const event = {
|
|
2637
|
-
tool_call_id: resolveToolCallId(input) || taskId,
|
|
2638
|
-
task_id: taskId,
|
|
2639
|
-
attempt_id: normalizeAttemptId(input.attempt_id),
|
|
2640
|
-
event_type: normalized,
|
|
2641
|
-
event_id: typeof input.event_id === "string" && input.event_id || `${taskId}:${normalized}:${Date.now()}`,
|
|
2642
|
-
timestamp: typeof input.timestamp === "string" && input.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
|
|
2643
|
-
payload: toRecord(input.payload || input.data)
|
|
2644
|
-
};
|
|
2645
|
-
this.lifecycleReporter.emit(event);
|
|
2646
|
-
return event;
|
|
2647
|
-
}
|
|
2648
|
-
clearCache() {
|
|
2649
|
-
this.contractClient.clearCache();
|
|
2650
|
-
this.executionStore.clear();
|
|
2651
|
-
}
|
|
2652
|
-
assertAppContext(appId) {
|
|
2653
|
-
if (!appId || appId.trim().length === 0) {
|
|
2654
|
-
throw new ContractValidationError("appId is required");
|
|
2655
|
-
}
|
|
2656
|
-
if (this.appId && this.appId !== appId) {
|
|
2657
|
-
throw new ContractValidationError("Runtime appId mismatch", {
|
|
2658
|
-
runtime_app_id: this.appId,
|
|
2659
|
-
requested_app_id: appId
|
|
2660
|
-
});
|
|
2661
|
-
}
|
|
2662
|
-
}
|
|
2663
|
-
buildRegistryKey(appId, provider, namespace) {
|
|
2664
|
-
const resolvedNamespace = namespace || this.defaultNamespace || appId;
|
|
2665
|
-
return `${appId}:${provider}:${resolvedNamespace}`;
|
|
2666
|
-
}
|
|
2667
|
-
resolveTaskId(response) {
|
|
2668
|
-
if (typeof response.task_uuid === "string" && response.task_uuid.trim().length > 0) {
|
|
2669
|
-
return response.task_uuid;
|
|
2670
|
-
}
|
|
2671
|
-
if (typeof response.task_id === "string" && response.task_id.trim().length > 0) {
|
|
2672
|
-
return response.task_id;
|
|
2673
|
-
}
|
|
2674
|
-
return void 0;
|
|
2675
|
-
}
|
|
2676
|
-
buildTriggerRequestBody(call, context, tool, eventId) {
|
|
2677
|
-
const payloadTemplate = isRecord3(tool.trigger.payload_template) ? tool.trigger.payload_template : {};
|
|
2678
|
-
const templateRawData = isRecord3(payloadTemplate.raw_data) ? payloadTemplate.raw_data : {};
|
|
2679
|
-
const payload = {
|
|
2680
|
-
...payloadTemplate,
|
|
2681
|
-
raw_data: {
|
|
2682
|
-
...templateRawData,
|
|
2683
|
-
capability_id: tool.capability_id,
|
|
2684
|
-
tool_name: call.tool_name,
|
|
2685
|
-
tool_call_id: call.tool_call_id,
|
|
2686
|
-
args: call.args,
|
|
2687
|
-
context: {
|
|
2688
|
-
app_id: context.appId,
|
|
2689
|
-
user_id: context.userId,
|
|
2690
|
-
workspace_id: context.workspaceId || null,
|
|
2691
|
-
request_id: context.requestId || null,
|
|
2692
|
-
metadata: context.metadata || null
|
|
2693
|
-
}
|
|
2694
|
-
}
|
|
2695
|
-
};
|
|
2696
|
-
if (typeof payload.auto_run !== "boolean") {
|
|
2697
|
-
payload.auto_run = false;
|
|
2698
|
-
}
|
|
2699
|
-
return {
|
|
2700
|
-
app_name: this.appName,
|
|
2701
|
-
// In API-key dev mode, app_id should be omitted unless explicitly configured
|
|
2702
|
-
// on the runtime. Passing an unknown app_id causes webhook trigger rejection.
|
|
2703
|
-
app_id: this.appId || void 0,
|
|
2704
|
-
event: tool.trigger.event,
|
|
2705
|
-
event_id: eventId,
|
|
2706
|
-
payload
|
|
2707
|
-
};
|
|
2708
|
-
}
|
|
2709
|
-
validateToolArgs(tool, args) {
|
|
2710
|
-
const errors = [];
|
|
2711
|
-
if (!isRecord3(args)) {
|
|
2712
|
-
errors.push("args must be an object");
|
|
2713
|
-
return errors;
|
|
2714
|
-
}
|
|
2715
|
-
const requiredFields = Array.isArray(tool.input_schema.required) ? tool.input_schema.required.filter((field) => typeof field === "string") : [];
|
|
2716
|
-
for (const field of requiredFields) {
|
|
2717
|
-
const value = args[field];
|
|
2718
|
-
if (value === void 0 || value === null || typeof value === "string" && value.trim().length === 0) {
|
|
2719
|
-
errors.push(`Missing required field: ${field}`);
|
|
2720
|
-
}
|
|
2721
|
-
}
|
|
2722
|
-
const properties = isRecord3(tool.input_schema.properties) ? tool.input_schema.properties : {};
|
|
2723
|
-
for (const [field, schemaValue] of Object.entries(properties)) {
|
|
2724
|
-
if (!(field in args)) continue;
|
|
2725
|
-
if (!isRecord3(schemaValue)) continue;
|
|
2726
|
-
const typeValue = schemaValue.type;
|
|
2727
|
-
if (typeof typeValue === "string") {
|
|
2728
|
-
if (!this.matchesType(args[field], typeValue)) {
|
|
2729
|
-
errors.push(`Invalid type for ${field}: expected ${typeValue}`);
|
|
2730
|
-
}
|
|
2731
|
-
}
|
|
2732
|
-
if (Array.isArray(typeValue)) {
|
|
2733
|
-
const valid = typeValue.some(
|
|
2734
|
-
(item) => typeof item === "string" && this.matchesType(args[field], item)
|
|
2735
|
-
);
|
|
2736
|
-
if (!valid) {
|
|
2737
|
-
errors.push(
|
|
2738
|
-
`Invalid type for ${field}: expected one of ${typeValue.filter((item) => typeof item === "string").join(", ")}`
|
|
2739
|
-
);
|
|
2740
|
-
}
|
|
2741
|
-
}
|
|
2742
|
-
}
|
|
2743
|
-
return errors;
|
|
2744
|
-
}
|
|
2745
|
-
matchesType(value, expectedType) {
|
|
2746
|
-
switch (expectedType) {
|
|
2747
|
-
case "string":
|
|
2748
|
-
return typeof value === "string";
|
|
2749
|
-
case "number":
|
|
2750
|
-
return typeof value === "number" && Number.isFinite(value);
|
|
2751
|
-
case "integer":
|
|
2752
|
-
return typeof value === "number" && Number.isInteger(value);
|
|
2753
|
-
case "boolean":
|
|
2754
|
-
return typeof value === "boolean";
|
|
2755
|
-
case "object":
|
|
2756
|
-
return isRecord3(value);
|
|
2757
|
-
case "array":
|
|
2758
|
-
return Array.isArray(value);
|
|
2759
|
-
case "null":
|
|
2760
|
-
return value === null;
|
|
2761
|
-
default:
|
|
2762
|
-
return true;
|
|
2763
|
-
}
|
|
2764
|
-
}
|
|
2765
|
-
tryEmitLifecycleEvent(input) {
|
|
2766
|
-
if (!input.task_id || !input.event_type) return;
|
|
2767
|
-
const normalized = normalizeContractEvent(input.event_type);
|
|
2768
|
-
if (!normalized || !LIFECYCLE_EVENTS.has(normalized)) {
|
|
2769
|
-
return;
|
|
2770
|
-
}
|
|
2771
|
-
this.lifecycleReporter.emit({
|
|
2772
|
-
tool_call_id: input.tool_call_id,
|
|
2773
|
-
task_id: input.task_id,
|
|
2774
|
-
attempt_id: normalizeAttemptId(input.attempt_id),
|
|
2775
|
-
event_type: normalized,
|
|
2776
|
-
event_id: input.event_id || `${input.task_id}:${normalized}:${Date.now()}`,
|
|
2777
|
-
timestamp: input.timestamp,
|
|
2778
|
-
payload: input.payload
|
|
2779
|
-
});
|
|
2780
|
-
}
|
|
2781
|
-
toFailedResult(toolCallId, error) {
|
|
2782
|
-
if (error instanceof ContractError) {
|
|
2783
|
-
const retryable = error instanceof RuntimeTransportError ? error.statusCode === void 0 || error.statusCode >= 500 || error.statusCode === 429 : false;
|
|
2784
|
-
return {
|
|
2785
|
-
status: "failed",
|
|
2786
|
-
tool_call_id: toolCallId,
|
|
2787
|
-
error: {
|
|
2788
|
-
code: error.code,
|
|
2789
|
-
message: error.message,
|
|
2790
|
-
retryable
|
|
2791
|
-
}
|
|
2792
|
-
};
|
|
2793
|
-
}
|
|
2794
|
-
if (error instanceof Error) {
|
|
2795
|
-
return {
|
|
2796
|
-
status: "failed",
|
|
2797
|
-
tool_call_id: toolCallId,
|
|
2798
|
-
error: {
|
|
2799
|
-
code: "runtime_error",
|
|
2800
|
-
message: error.message,
|
|
2801
|
-
retryable: true
|
|
2802
|
-
}
|
|
2803
|
-
};
|
|
2804
|
-
}
|
|
2805
|
-
return {
|
|
2806
|
-
status: "failed",
|
|
2807
|
-
tool_call_id: toolCallId,
|
|
2808
|
-
error: {
|
|
2809
|
-
code: "runtime_error",
|
|
2810
|
-
message: "Unknown runtime error",
|
|
2811
|
-
retryable: true
|
|
2812
|
-
}
|
|
2813
|
-
};
|
|
2814
|
-
}
|
|
2815
|
-
};
|
|
2816
|
-
|
|
2817
|
-
// src/modules/database.ts
|
|
2818
|
-
var DatabaseModule = class {
|
|
2819
|
-
constructor(realtimexUrl, appId, apiKey) {
|
|
2820
|
-
this.baseUrl = realtimexUrl.replace(/\/$/, "");
|
|
2821
|
-
this.appId = appId;
|
|
2822
|
-
this.apiKey = apiKey;
|
|
2823
|
-
}
|
|
2824
|
-
getHeaders() {
|
|
2825
|
-
const headers = {
|
|
2826
|
-
"Content-Type": "application/json"
|
|
2827
|
-
};
|
|
2828
|
-
if (this.apiKey) {
|
|
2829
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
2830
|
-
}
|
|
2831
|
-
if (this.appId) {
|
|
2832
|
-
headers["x-app-id"] = this.appId;
|
|
2833
|
-
}
|
|
2834
|
-
return headers;
|
|
2835
|
-
}
|
|
2836
|
-
/**
|
|
2837
|
-
* Get the Supabase database configuration for this app.
|
|
2838
|
-
* Returns URL, anonKey, mode, and tables.
|
|
2839
|
-
*
|
|
2840
|
-
* @example
|
|
2841
|
-
* ```ts
|
|
2842
|
-
* const config = await sdk.database.getConfig();
|
|
2843
|
-
* const supabase = createClient(config.url, config.anonKey);
|
|
2844
|
-
* ```
|
|
2845
|
-
*/
|
|
2846
|
-
async getConfig() {
|
|
2847
|
-
const response = await fetch(`${this.baseUrl}/sdk/database/config`, {
|
|
2848
|
-
method: "GET",
|
|
2849
|
-
headers: this.getHeaders()
|
|
2850
|
-
});
|
|
2851
|
-
const data = await response.json();
|
|
2852
|
-
if (!response.ok) {
|
|
2853
|
-
throw new Error(data.error || "Failed to get database config");
|
|
2854
|
-
}
|
|
2855
|
-
return data.config;
|
|
2856
|
-
}
|
|
2857
|
-
};
|
|
2858
|
-
|
|
2859
|
-
// src/modules/auth.ts
|
|
2860
|
-
var AuthModule = class {
|
|
2861
|
-
constructor(realtimexUrl, appId, apiKey) {
|
|
2862
|
-
this.baseUrl = realtimexUrl.replace(/\/$/, "");
|
|
2863
|
-
this.appId = appId;
|
|
2864
|
-
this.apiKey = apiKey;
|
|
2865
|
-
}
|
|
2866
|
-
getHeaders() {
|
|
2867
|
-
const headers = {
|
|
2868
|
-
"Content-Type": "application/json"
|
|
2869
|
-
};
|
|
2870
|
-
if (this.apiKey) {
|
|
2871
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
2872
|
-
}
|
|
2873
|
-
if (this.appId) {
|
|
2874
|
-
headers["x-app-id"] = this.appId;
|
|
2875
|
-
}
|
|
2876
|
-
return headers;
|
|
2877
|
-
}
|
|
2878
|
-
/**
|
|
2879
|
-
* Push a Supabase access token to the Main App.
|
|
2880
|
-
* This enables Main App to use the token for:
|
|
2881
|
-
* - Realtime subscriptions (bypass RLS)
|
|
2882
|
-
* - CRUD operations on rtx_activities (bypass RLS)
|
|
2883
|
-
*
|
|
2884
|
-
* @param token - Supabase JWT from supabase.auth.signIn()
|
|
2885
|
-
*
|
|
2886
|
-
* @example
|
|
2887
|
-
* ```ts
|
|
2888
|
-
* const { data } = await supabase.auth.signInWithPassword({ email, password });
|
|
2889
|
-
* await sdk.auth.syncSupabaseToken(data.session.access_token);
|
|
2890
|
-
* ```
|
|
2891
|
-
*/
|
|
2892
|
-
async syncSupabaseToken(token) {
|
|
2893
|
-
if (!token || typeof token !== "string") {
|
|
2894
|
-
throw new Error("Token must be a non-empty string");
|
|
2895
|
-
}
|
|
2896
|
-
const response = await fetch(`${this.baseUrl}/sdk/auth/sync-supabase-token`, {
|
|
2897
|
-
method: "POST",
|
|
2898
|
-
headers: this.getHeaders(),
|
|
2899
|
-
body: JSON.stringify({ token })
|
|
2900
|
-
});
|
|
2901
|
-
const data = await response.json();
|
|
2902
|
-
if (!response.ok) {
|
|
2903
|
-
throw new Error(data.error || "Failed to sync Supabase token");
|
|
2904
|
-
}
|
|
2905
|
-
return data;
|
|
2906
|
-
}
|
|
2907
|
-
/**
|
|
2908
|
-
* Retrieve the current Keycloak access token from Main App.
|
|
2909
|
-
* This is the existing Token Vending Machine endpoint.
|
|
2910
|
-
*
|
|
2911
|
-
* @returns The access token info, or null if no token is available.
|
|
2912
|
-
*/
|
|
2913
|
-
async getAccessToken() {
|
|
2914
|
-
const response = await fetch(`${this.baseUrl}/sdk/auth/token`, {
|
|
2915
|
-
method: "GET",
|
|
2916
|
-
headers: this.getHeaders()
|
|
2917
|
-
});
|
|
2918
|
-
const data = await response.json();
|
|
2919
|
-
if (response.status === 404) {
|
|
2920
|
-
return null;
|
|
2921
|
-
}
|
|
2922
|
-
if (!response.ok) {
|
|
2923
|
-
throw new Error(data.error || "Failed to get access token");
|
|
2924
|
-
}
|
|
2925
|
-
return data;
|
|
2926
|
-
}
|
|
2927
|
-
};
|
|
2928
|
-
|
|
2929
|
-
// src/modules/credentials.ts
|
|
2930
|
-
var CredentialsModule = class {
|
|
2931
|
-
constructor(httpClient) {
|
|
2932
|
-
this.httpClient = httpClient;
|
|
2933
|
-
}
|
|
2934
|
-
/** List available credentials (names and types, no values). */
|
|
2935
|
-
async list() {
|
|
2936
|
-
const response = await this.httpClient.fetch("/sdk/credentials");
|
|
2937
|
-
const data = await response.json();
|
|
2938
|
-
if (!response.ok) {
|
|
2939
|
-
throw new Error(data?.error || "Failed to list credentials");
|
|
2940
|
-
}
|
|
2941
|
-
return data.credentials || [];
|
|
2942
|
-
}
|
|
2943
|
-
/** Get a credential's decrypted payload by name. */
|
|
2944
|
-
async get(name) {
|
|
2945
|
-
const response = await this.httpClient.fetch(
|
|
2946
|
-
`/sdk/credentials/${encodeURIComponent(name)}`
|
|
2947
|
-
);
|
|
2948
|
-
const data = await response.json();
|
|
2949
|
-
if (!response.ok) {
|
|
2950
|
-
throw new Error(data?.error || `Failed to get credential: ${name}`);
|
|
2951
|
-
}
|
|
2952
|
-
return data.credential;
|
|
2953
|
-
}
|
|
2954
|
-
};
|
|
2955
|
-
|
|
2956
|
-
// src/core/auth/AuthProvider.ts
|
|
2957
|
-
var StaticAuthProvider = class {
|
|
2958
|
-
constructor(options = {}) {
|
|
2959
|
-
this.appId = options.appId;
|
|
2960
|
-
this.appName = options.appName;
|
|
2961
|
-
this.apiKey = options.apiKey;
|
|
2962
|
-
}
|
|
2963
|
-
buildHeaders(baseHeaders = {}) {
|
|
2964
|
-
const headers = { ...baseHeaders };
|
|
2965
|
-
if (this.apiKey) {
|
|
2966
|
-
headers.Authorization = `Bearer ${this.apiKey}`;
|
|
2967
|
-
}
|
|
2968
|
-
if (this.appId) {
|
|
2969
|
-
headers["x-app-id"] = this.appId;
|
|
2970
|
-
}
|
|
2971
|
-
if (this.appName) {
|
|
2972
|
-
headers["x-app-name"] = this.appName;
|
|
2973
|
-
}
|
|
2974
|
-
return headers;
|
|
2975
|
-
}
|
|
2976
|
-
};
|
|
2977
|
-
|
|
2978
|
-
// src/adapters/gemini/GeminiToolAdapter.ts
|
|
2979
|
-
function isRecord4(value) {
|
|
2980
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2981
|
-
}
|
|
2982
|
-
function parseArgs(value) {
|
|
2983
|
-
if (isRecord4(value)) return value;
|
|
2984
|
-
if (typeof value === "string") {
|
|
2985
|
-
try {
|
|
2986
|
-
const parsed = JSON.parse(value);
|
|
2987
|
-
return isRecord4(parsed) ? parsed : {};
|
|
2988
|
-
} catch {
|
|
2989
|
-
return {};
|
|
2990
|
-
}
|
|
2991
|
-
}
|
|
2992
|
-
return {};
|
|
2993
|
-
}
|
|
2994
|
-
var GeminiToolAdapter = class {
|
|
2995
|
-
toProviderTools(tools) {
|
|
2996
|
-
return tools.map((tool) => ({
|
|
2997
|
-
name: tool.tool_name,
|
|
2998
|
-
description: tool.description,
|
|
2999
|
-
parameters: tool.input_schema
|
|
3000
|
-
}));
|
|
3001
|
-
}
|
|
3002
|
-
fromProviderToolCall(call) {
|
|
3003
|
-
return {
|
|
3004
|
-
tool_call_id: call.id,
|
|
3005
|
-
tool_name: call.name,
|
|
3006
|
-
args: parseArgs(call.args)
|
|
3007
|
-
};
|
|
3008
|
-
}
|
|
3009
|
-
toProviderResult(result) {
|
|
3010
|
-
return {
|
|
3011
|
-
tool_call_id: result.tool_call_id || result.task_id || "unknown",
|
|
3012
|
-
status: result.status,
|
|
3013
|
-
payload: result.status === "failed" ? { error: result.error } : {
|
|
3014
|
-
...result.output || {},
|
|
3015
|
-
task_id: result.task_id,
|
|
3016
|
-
attempt_id: result.attempt_id
|
|
3017
|
-
}
|
|
3018
|
-
};
|
|
3019
|
-
}
|
|
3020
|
-
};
|
|
3021
|
-
|
|
3022
|
-
// src/adapters/claude/ClaudeToolAdapter.ts
|
|
3023
|
-
var ClaudeToolAdapter = class {
|
|
3024
|
-
toProviderTools(tools) {
|
|
3025
|
-
return tools.map((tool) => ({
|
|
3026
|
-
name: tool.tool_name,
|
|
3027
|
-
description: tool.description,
|
|
3028
|
-
input_schema: tool.input_schema
|
|
3029
|
-
}));
|
|
3030
|
-
}
|
|
3031
|
-
fromProviderToolCall(call) {
|
|
3032
|
-
return {
|
|
3033
|
-
tool_call_id: call.id,
|
|
3034
|
-
tool_name: call.name,
|
|
3035
|
-
args: call.input || {}
|
|
3036
|
-
};
|
|
3037
|
-
}
|
|
3038
|
-
toProviderResult(result) {
|
|
3039
|
-
const contentPayload = result.status === "failed" ? { error: result.error } : {
|
|
3040
|
-
...result.output || {},
|
|
3041
|
-
task_id: result.task_id,
|
|
3042
|
-
attempt_id: result.attempt_id
|
|
3043
|
-
};
|
|
3044
|
-
return {
|
|
3045
|
-
type: "tool_result",
|
|
3046
|
-
tool_use_id: result.tool_call_id || result.task_id || "unknown",
|
|
3047
|
-
content: JSON.stringify(contentPayload),
|
|
3048
|
-
is_error: result.status === "failed" ? true : void 0
|
|
3049
|
-
};
|
|
3050
|
-
}
|
|
3051
|
-
};
|
|
3052
|
-
|
|
3053
|
-
// src/adapters/codex/CodexToolAdapter.ts
|
|
3054
|
-
function isRecord5(value) {
|
|
3055
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3056
|
-
}
|
|
3057
|
-
function parseArguments(value) {
|
|
3058
|
-
if (isRecord5(value)) return value;
|
|
3059
|
-
if (typeof value === "string") {
|
|
3060
|
-
try {
|
|
3061
|
-
const parsed = JSON.parse(value);
|
|
3062
|
-
return isRecord5(parsed) ? parsed : {};
|
|
3063
|
-
} catch {
|
|
3064
|
-
return {};
|
|
3065
|
-
}
|
|
3066
|
-
}
|
|
3067
|
-
return {};
|
|
3068
|
-
}
|
|
3069
|
-
var CodexToolAdapter = class {
|
|
3070
|
-
toProviderTools(tools) {
|
|
3071
|
-
return tools.map((tool) => ({
|
|
3072
|
-
type: "function",
|
|
3073
|
-
function: {
|
|
3074
|
-
name: tool.tool_name,
|
|
3075
|
-
description: tool.description,
|
|
3076
|
-
parameters: tool.input_schema
|
|
3077
|
-
}
|
|
3078
|
-
}));
|
|
3079
|
-
}
|
|
3080
|
-
fromProviderToolCall(call) {
|
|
3081
|
-
return {
|
|
3082
|
-
tool_call_id: call.id,
|
|
3083
|
-
tool_name: call.function.name,
|
|
3084
|
-
args: parseArguments(call.function.arguments)
|
|
3085
|
-
};
|
|
3086
|
-
}
|
|
3087
|
-
toProviderResult(result) {
|
|
3088
|
-
const outputPayload = result.status === "failed" ? { error: result.error } : {
|
|
3089
|
-
...result.output || {},
|
|
3090
|
-
task_id: result.task_id,
|
|
3091
|
-
attempt_id: result.attempt_id
|
|
3092
|
-
};
|
|
3093
|
-
return {
|
|
3094
|
-
tool_call_id: result.tool_call_id || result.task_id || "unknown",
|
|
3095
|
-
output: JSON.stringify(outputPayload),
|
|
3096
|
-
is_error: result.status === "failed" ? true : void 0
|
|
3097
|
-
};
|
|
3098
|
-
}
|
|
3099
|
-
};
|
|
3100
|
-
|
|
3101
|
-
// src/acp/ACPEventMapper.ts
|
|
3102
|
-
function getErrorMessage(error) {
|
|
3103
|
-
if (error instanceof Error) return error.message;
|
|
3104
|
-
if (typeof error === "string") return error;
|
|
3105
|
-
return "Unknown error";
|
|
3106
|
-
}
|
|
3107
|
-
function buildRealtimeXMeta(input) {
|
|
3108
|
-
return {
|
|
3109
|
-
realtimex: {
|
|
3110
|
-
tool_call_id: input.reference.toolCallId,
|
|
3111
|
-
task_id: input.taskId,
|
|
3112
|
-
attempt_id: input.attemptId,
|
|
3113
|
-
event_id: input.eventId,
|
|
3114
|
-
contract_version: "local-app-contract/v1",
|
|
3115
|
-
contract_event: input.contractEvent,
|
|
3116
|
-
cancelled: input.cancelled === true ? true : void 0,
|
|
3117
|
-
app_id: input.reference.appId,
|
|
3118
|
-
user_id: input.reference.userId,
|
|
3119
|
-
workspace_id: input.reference.workspaceId,
|
|
3120
|
-
provider: input.reference.provider,
|
|
3121
|
-
namespace: input.reference.namespace
|
|
3122
|
-
}
|
|
3123
|
-
};
|
|
3124
|
-
}
|
|
3125
|
-
var ACPEventMapper = class {
|
|
3126
|
-
createReference(invocation, context) {
|
|
3127
|
-
return {
|
|
3128
|
-
sessionId: context.sessionId,
|
|
3129
|
-
toolCallId: invocation.toolCallId,
|
|
3130
|
-
appId: context.appId,
|
|
3131
|
-
userId: context.userId,
|
|
3132
|
-
workspaceId: context.workspaceId,
|
|
3133
|
-
provider: context.provider,
|
|
3134
|
-
namespace: context.namespace,
|
|
3135
|
-
title: invocation.title || invocation.toolName,
|
|
3136
|
-
kind: invocation.kind || "execute"
|
|
3137
|
-
};
|
|
3138
|
-
}
|
|
3139
|
-
buildPendingUpdate(invocation, reference) {
|
|
3140
|
-
return {
|
|
3141
|
-
sessionId: reference.sessionId,
|
|
3142
|
-
update: {
|
|
3143
|
-
type: "tool_call",
|
|
3144
|
-
toolCallId: invocation.toolCallId,
|
|
3145
|
-
title: reference.title,
|
|
3146
|
-
kind: reference.kind,
|
|
3147
|
-
status: "pending",
|
|
3148
|
-
rawInput: invocation.args,
|
|
3149
|
-
_meta: buildRealtimeXMeta({
|
|
3150
|
-
reference,
|
|
3151
|
-
contractEvent: "task.trigger"
|
|
3152
|
-
})
|
|
3153
|
-
}
|
|
3154
|
-
};
|
|
3155
|
-
}
|
|
3156
|
-
buildResultUpdate(result, reference) {
|
|
3157
|
-
const status = this.mapExecutionResultStatus(result);
|
|
3158
|
-
const taskId = result.task_id;
|
|
3159
|
-
const attemptId = result.attempt_id;
|
|
3160
|
-
const contractEvent = status === "completed" ? "task.completed" : status === "failed" ? "task.failed" : "task.started";
|
|
3161
|
-
const rawOutput = status === "failed" ? {
|
|
3162
|
-
error: result.error || {
|
|
3163
|
-
code: "runtime_error",
|
|
3164
|
-
message: "Unknown runtime error",
|
|
3165
|
-
retryable: true
|
|
3166
|
-
}
|
|
3167
|
-
} : {
|
|
3168
|
-
...result.output || {},
|
|
3169
|
-
task_id: taskId,
|
|
3170
|
-
attempt_id: attemptId
|
|
3171
|
-
};
|
|
3172
|
-
return {
|
|
3173
|
-
sessionId: reference.sessionId,
|
|
3174
|
-
update: {
|
|
3175
|
-
type: "tool_call_update",
|
|
3176
|
-
toolCallId: reference.toolCallId,
|
|
3177
|
-
status,
|
|
3178
|
-
content: [
|
|
3179
|
-
{
|
|
3180
|
-
type: "text",
|
|
3181
|
-
text: status === "in_progress" ? "Task queued" : status === "completed" ? "Task completed" : "Task failed"
|
|
3182
|
-
}
|
|
3183
|
-
],
|
|
3184
|
-
rawOutput,
|
|
3185
|
-
_meta: buildRealtimeXMeta({
|
|
3186
|
-
reference,
|
|
3187
|
-
taskId,
|
|
3188
|
-
attemptId,
|
|
3189
|
-
contractEvent
|
|
3190
|
-
})
|
|
3191
|
-
}
|
|
3192
|
-
};
|
|
3193
|
-
}
|
|
3194
|
-
buildLifecycleUpdate(event, reference) {
|
|
3195
|
-
const status = this.mapContractEventStatus(event.event_type);
|
|
3196
|
-
if (!status) return null;
|
|
3197
|
-
const payload = event.payload || {};
|
|
3198
|
-
const progressText = this.resolveEventText(event.event_type, payload);
|
|
3199
|
-
const cancelled = event.event_type === "task.canceled";
|
|
3200
|
-
return {
|
|
3201
|
-
sessionId: reference.sessionId,
|
|
3202
|
-
update: {
|
|
3203
|
-
type: "tool_call_update",
|
|
3204
|
-
toolCallId: reference.toolCallId,
|
|
3205
|
-
status,
|
|
3206
|
-
content: progressText ? [
|
|
3207
|
-
{
|
|
3208
|
-
type: "text",
|
|
3209
|
-
text: progressText
|
|
3210
|
-
}
|
|
3211
|
-
] : void 0,
|
|
3212
|
-
rawOutput: payload,
|
|
3213
|
-
_meta: buildRealtimeXMeta({
|
|
3214
|
-
reference,
|
|
3215
|
-
taskId: event.task_id,
|
|
3216
|
-
attemptId: event.attempt_id,
|
|
3217
|
-
eventId: event.event_id,
|
|
3218
|
-
contractEvent: event.event_type,
|
|
3219
|
-
cancelled
|
|
3220
|
-
})
|
|
3221
|
-
}
|
|
3222
|
-
};
|
|
3223
|
-
}
|
|
3224
|
-
buildNotifyFailureUpdate(reference, error) {
|
|
3225
|
-
return {
|
|
3226
|
-
sessionId: reference.sessionId,
|
|
3227
|
-
update: {
|
|
3228
|
-
type: "tool_call_update",
|
|
3229
|
-
toolCallId: reference.toolCallId,
|
|
3230
|
-
status: "failed",
|
|
3231
|
-
content: [
|
|
3232
|
-
{
|
|
3233
|
-
type: "text",
|
|
3234
|
-
text: `Adapter notify failed: ${getErrorMessage(error)}`
|
|
3235
|
-
}
|
|
3236
|
-
],
|
|
3237
|
-
rawOutput: {
|
|
3238
|
-
error: {
|
|
3239
|
-
code: "acp_notify_failed",
|
|
3240
|
-
message: getErrorMessage(error)
|
|
3241
|
-
}
|
|
3242
|
-
},
|
|
3243
|
-
_meta: buildRealtimeXMeta({
|
|
3244
|
-
reference,
|
|
3245
|
-
contractEvent: "adapter.notify_failed"
|
|
3246
|
-
})
|
|
3247
|
-
}
|
|
3248
|
-
};
|
|
3249
|
-
}
|
|
3250
|
-
mapContractEventStatus(eventType) {
|
|
3251
|
-
switch (eventType) {
|
|
3252
|
-
case "task.claimed":
|
|
3253
|
-
case "task.started":
|
|
3254
|
-
case "task.progress":
|
|
3255
|
-
return "in_progress";
|
|
3256
|
-
case "task.completed":
|
|
3257
|
-
return "completed";
|
|
3258
|
-
case "task.failed":
|
|
3259
|
-
case "task.canceled":
|
|
3260
|
-
return "failed";
|
|
3261
|
-
default:
|
|
3262
|
-
return null;
|
|
3263
|
-
}
|
|
3264
|
-
}
|
|
3265
|
-
mapExecutionResultStatus(result) {
|
|
3266
|
-
switch (result.status) {
|
|
3267
|
-
case "completed":
|
|
3268
|
-
return "completed";
|
|
3269
|
-
case "failed":
|
|
3270
|
-
return "failed";
|
|
3271
|
-
case "queued":
|
|
3272
|
-
return "in_progress";
|
|
3273
|
-
default:
|
|
3274
|
-
return "failed";
|
|
3275
|
-
}
|
|
3276
|
-
}
|
|
3277
|
-
resolveEventText(eventType, payload) {
|
|
3278
|
-
const payloadMessage = typeof payload.message === "string" ? payload.message : typeof payload.error === "string" ? payload.error : void 0;
|
|
3279
|
-
if (payloadMessage) return payloadMessage;
|
|
3280
|
-
switch (eventType) {
|
|
3281
|
-
case "task.claimed":
|
|
3282
|
-
return "Task claimed";
|
|
3283
|
-
case "task.started":
|
|
3284
|
-
return "Task started";
|
|
3285
|
-
case "task.progress":
|
|
3286
|
-
return "Task in progress";
|
|
3287
|
-
case "task.completed":
|
|
3288
|
-
return "Task completed";
|
|
3289
|
-
case "task.failed":
|
|
3290
|
-
return "Task failed";
|
|
3291
|
-
case "task.canceled":
|
|
3292
|
-
return "Task canceled";
|
|
3293
|
-
default:
|
|
3294
|
-
return void 0;
|
|
3295
|
-
}
|
|
3296
|
-
}
|
|
3297
|
-
};
|
|
3298
|
-
|
|
3299
|
-
// src/acp/ACPPermissionBridge.ts
|
|
3300
|
-
function isRecord6(value) {
|
|
3301
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3302
|
-
}
|
|
3303
|
-
function normalizePermissionOptions(invocation, buildPermissionOptions) {
|
|
3304
|
-
const built = buildPermissionOptions ? buildPermissionOptions(invocation) : void 0;
|
|
3305
|
-
if (built && built.length > 0) return built;
|
|
3306
|
-
return [
|
|
3307
|
-
{
|
|
3308
|
-
id: "allow_once",
|
|
3309
|
-
label: "Allow once",
|
|
3310
|
-
kind: "allow_once"
|
|
3311
|
-
},
|
|
3312
|
-
{
|
|
3313
|
-
id: "deny_once",
|
|
3314
|
-
label: "Deny",
|
|
3315
|
-
kind: "deny_once"
|
|
3316
|
-
}
|
|
3317
|
-
];
|
|
3318
|
-
}
|
|
3319
|
-
function parseDecision(response) {
|
|
3320
|
-
if (typeof response === "boolean") return response;
|
|
3321
|
-
if (!isRecord6(response)) return false;
|
|
3322
|
-
if (typeof response.allow === "boolean") return response.allow;
|
|
3323
|
-
const decision = typeof response.decision === "string" && response.decision || typeof response.outcome === "string" && response.outcome || typeof response.selected === "string" && response.selected || "";
|
|
3324
|
-
const normalized = decision.trim().toLowerCase();
|
|
3325
|
-
if (!normalized) return false;
|
|
3326
|
-
return normalized === "allow" || normalized === "allow_once" || normalized === "allow_always" || normalized === "approved" || normalized === "granted";
|
|
3327
|
-
}
|
|
3328
|
-
var ACPPermissionBridge = class {
|
|
3329
|
-
constructor(options) {
|
|
3330
|
-
this.notifier = options.notifier;
|
|
3331
|
-
this.enabled = options.enabled;
|
|
3332
|
-
this.buildPermissionOptions = options.buildPermissionOptions;
|
|
3333
|
-
}
|
|
3334
|
-
async requestToolPermission(invocation, context) {
|
|
3335
|
-
if (!this.enabled) return true;
|
|
3336
|
-
if (!this.notifier.request) {
|
|
3337
|
-
return false;
|
|
3338
|
-
}
|
|
3339
|
-
const options = normalizePermissionOptions(invocation, this.buildPermissionOptions);
|
|
3340
|
-
try {
|
|
3341
|
-
const response = await this.notifier.request("session/request_permission", {
|
|
3342
|
-
sessionId: context.sessionId,
|
|
3343
|
-
title: invocation.title || invocation.toolName,
|
|
3344
|
-
description: `Allow tool ${invocation.toolName} to run?`,
|
|
3345
|
-
options,
|
|
3346
|
-
metadata: {
|
|
3347
|
-
tool_call_id: invocation.toolCallId,
|
|
3348
|
-
tool_name: invocation.toolName,
|
|
3349
|
-
app_id: context.appId
|
|
3350
|
-
}
|
|
3351
|
-
});
|
|
3352
|
-
return parseDecision(response);
|
|
3353
|
-
} catch {
|
|
3354
|
-
return false;
|
|
3355
|
-
}
|
|
3356
|
-
}
|
|
3357
|
-
};
|
|
3358
|
-
|
|
3359
|
-
// src/acp/ACPTelemetry.ts
|
|
3360
|
-
var ACPTelemetry = class {
|
|
3361
|
-
constructor(sink) {
|
|
3362
|
-
this.sink = sink;
|
|
3363
|
-
}
|
|
3364
|
-
emit(event) {
|
|
3365
|
-
if (!this.sink) return;
|
|
3366
|
-
try {
|
|
3367
|
-
const output = this.sink.emit(event);
|
|
3368
|
-
if (output && typeof output.then === "function") {
|
|
3369
|
-
void output.catch(() => {
|
|
3370
|
-
});
|
|
3371
|
-
}
|
|
3372
|
-
} catch {
|
|
3373
|
-
}
|
|
3374
|
-
}
|
|
3375
|
-
};
|
|
3376
|
-
|
|
3377
|
-
// src/acp/ACPContractAdapter.ts
|
|
3378
|
-
function isRecord7(value) {
|
|
3379
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3380
|
-
}
|
|
3381
|
-
function getString(value) {
|
|
3382
|
-
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
3383
|
-
}
|
|
3384
|
-
function resolveTaskId(payload) {
|
|
3385
|
-
return getString(payload.task_id) || getString(payload.task_uuid);
|
|
3386
|
-
}
|
|
3387
|
-
function resolveToolCallId2(payload) {
|
|
3388
|
-
const direct = getString(payload.tool_call_id);
|
|
3389
|
-
if (direct) return direct;
|
|
3390
|
-
if (isRecord7(payload.payload)) {
|
|
3391
|
-
const nestedPayload = getString(payload.payload.tool_call_id);
|
|
3392
|
-
if (nestedPayload) return nestedPayload;
|
|
3393
|
-
}
|
|
3394
|
-
if (isRecord7(payload.data)) {
|
|
3395
|
-
const nestedData = getString(payload.data.tool_call_id);
|
|
3396
|
-
if (nestedData) return nestedData;
|
|
3397
|
-
}
|
|
3398
|
-
return void 0;
|
|
3399
|
-
}
|
|
3400
|
-
function isTerminalStatus(status) {
|
|
3401
|
-
return status === "completed" || status === "failed";
|
|
3402
|
-
}
|
|
3403
|
-
var ACPContractAdapter = class {
|
|
3404
|
-
constructor(options) {
|
|
3405
|
-
this.taskReferences = /* @__PURE__ */ new Map();
|
|
3406
|
-
this.toolCallReferences = /* @__PURE__ */ new Map();
|
|
3407
|
-
this.runtime = options.runtime;
|
|
3408
|
-
this.notifier = options.notifier;
|
|
3409
|
-
this.mapper = new ACPEventMapper();
|
|
3410
|
-
this.permissionBridge = new ACPPermissionBridge({
|
|
3411
|
-
notifier: options.notifier,
|
|
3412
|
-
enabled: options.requestPermission === true,
|
|
3413
|
-
buildPermissionOptions: options.buildPermissionOptions
|
|
3414
|
-
});
|
|
3415
|
-
this.telemetry = new ACPTelemetry(options.telemetry);
|
|
3416
|
-
this.runtimeUnsubscribe = this.runtime.onExecutionEvent((event) => {
|
|
3417
|
-
void this.handleRuntimeEvent(event);
|
|
3418
|
-
});
|
|
3419
|
-
}
|
|
3420
|
-
dispose() {
|
|
3421
|
-
this.runtimeUnsubscribe();
|
|
3422
|
-
this.taskReferences.clear();
|
|
3423
|
-
this.toolCallReferences.clear();
|
|
3424
|
-
}
|
|
3425
|
-
async executeTool(invocation, context) {
|
|
3426
|
-
const reference = this.mapper.createReference(invocation, context);
|
|
3427
|
-
this.rememberReference(reference);
|
|
3428
|
-
await this.notify(this.mapper.buildPendingUpdate(invocation, reference));
|
|
3429
|
-
const permissionGranted = await this.permissionBridge.requestToolPermission(
|
|
3430
|
-
invocation,
|
|
3431
|
-
context
|
|
3432
|
-
);
|
|
3433
|
-
this.telemetry.emit({
|
|
3434
|
-
phase: "permission",
|
|
3435
|
-
result: permissionGranted ? "ok" : "failed",
|
|
3436
|
-
sessionId: context.sessionId,
|
|
3437
|
-
toolCallId: invocation.toolCallId,
|
|
3438
|
-
toolName: invocation.toolName
|
|
3439
|
-
});
|
|
3440
|
-
if (!permissionGranted) {
|
|
3441
|
-
const result2 = {
|
|
3442
|
-
status: "failed",
|
|
3443
|
-
tool_call_id: invocation.toolCallId,
|
|
3444
|
-
error: {
|
|
3445
|
-
code: "permission_denied",
|
|
3446
|
-
message: `Permission denied for ${invocation.toolName}`,
|
|
3447
|
-
retryable: false
|
|
3448
|
-
}
|
|
3449
|
-
};
|
|
3450
|
-
await this.notify(this.mapper.buildResultUpdate(result2, reference));
|
|
3451
|
-
this.cleanupReference(reference, result2);
|
|
3452
|
-
return result2;
|
|
3453
|
-
}
|
|
3454
|
-
const result = await this.runtime.executeToolCall(
|
|
3455
|
-
{
|
|
3456
|
-
tool_call_id: invocation.toolCallId,
|
|
3457
|
-
tool_name: invocation.toolName,
|
|
3458
|
-
args: invocation.args
|
|
3459
|
-
},
|
|
3460
|
-
{
|
|
3461
|
-
appId: context.appId,
|
|
3462
|
-
userId: context.userId,
|
|
3463
|
-
workspaceId: context.workspaceId,
|
|
3464
|
-
provider: context.provider,
|
|
3465
|
-
namespace: context.namespace
|
|
3466
|
-
}
|
|
3467
|
-
);
|
|
3468
|
-
const normalizedResult = {
|
|
3469
|
-
...result,
|
|
3470
|
-
tool_call_id: result.tool_call_id || invocation.toolCallId
|
|
3471
|
-
};
|
|
3472
|
-
if (normalizedResult.task_id) {
|
|
3473
|
-
this.taskReferences.set(normalizedResult.task_id, reference);
|
|
3474
|
-
}
|
|
3475
|
-
await this.notify(this.mapper.buildResultUpdate(normalizedResult, reference));
|
|
3476
|
-
this.telemetry.emit({
|
|
3477
|
-
phase: "execute",
|
|
3478
|
-
result: normalizedResult.status === "failed" ? "failed" : "ok",
|
|
3479
|
-
sessionId: reference.sessionId,
|
|
3480
|
-
toolCallId: reference.toolCallId,
|
|
3481
|
-
toolName: invocation.toolName,
|
|
3482
|
-
taskId: normalizedResult.task_id,
|
|
3483
|
-
attemptId: normalizedResult.attempt_id,
|
|
3484
|
-
status: normalizedResult.status === "queued" ? "in_progress" : normalizedResult.status === "completed" ? "completed" : normalizedResult.status === "failed" ? "failed" : void 0,
|
|
3485
|
-
error: normalizedResult.error?.message
|
|
3486
|
-
});
|
|
3487
|
-
this.cleanupReference(reference, normalizedResult);
|
|
3488
|
-
return normalizedResult;
|
|
3489
|
-
}
|
|
3490
|
-
ingestContractCallback(payload, context) {
|
|
3491
|
-
const initialTaskId = resolveTaskId(payload);
|
|
3492
|
-
const initialToolCallId = resolveToolCallId2(payload);
|
|
3493
|
-
if (initialTaskId) {
|
|
3494
|
-
const existingByTask = this.taskReferences.get(initialTaskId);
|
|
3495
|
-
const existingByCall = initialToolCallId ? this.toolCallReferences.get(initialToolCallId) : void 0;
|
|
3496
|
-
const reference = existingByTask || existingByCall || {
|
|
3497
|
-
sessionId: context.sessionId,
|
|
3498
|
-
toolCallId: initialToolCallId || initialTaskId,
|
|
3499
|
-
appId: "unknown",
|
|
3500
|
-
userId: "unknown",
|
|
3501
|
-
title: initialToolCallId || initialTaskId,
|
|
3502
|
-
kind: "other"
|
|
3503
|
-
};
|
|
3504
|
-
this.taskReferences.set(initialTaskId, reference);
|
|
3505
|
-
this.toolCallReferences.set(reference.toolCallId, reference);
|
|
3506
|
-
}
|
|
3507
|
-
const event = this.runtime.ingestExecutionEvent(payload);
|
|
3508
|
-
this.telemetry.emit({
|
|
3509
|
-
phase: "ingest",
|
|
3510
|
-
result: event ? "ok" : "skipped",
|
|
3511
|
-
sessionId: context.sessionId,
|
|
3512
|
-
toolCallId: event?.tool_call_id,
|
|
3513
|
-
taskId: event?.task_id,
|
|
3514
|
-
attemptId: event?.attempt_id,
|
|
3515
|
-
eventId: event?.event_id,
|
|
3516
|
-
eventType: event?.event_type
|
|
3517
|
-
});
|
|
3518
|
-
return event;
|
|
3519
|
-
}
|
|
3520
|
-
async handleRuntimeEvent(event) {
|
|
3521
|
-
const reference = this.resolveReferenceForEvent(event);
|
|
3522
|
-
if (!reference) {
|
|
3523
|
-
this.telemetry.emit({
|
|
3524
|
-
phase: "runtime_event",
|
|
3525
|
-
result: "skipped",
|
|
3526
|
-
toolCallId: event.tool_call_id,
|
|
3527
|
-
taskId: event.task_id,
|
|
3528
|
-
attemptId: event.attempt_id,
|
|
3529
|
-
eventId: event.event_id,
|
|
3530
|
-
eventType: event.event_type
|
|
3531
|
-
});
|
|
3532
|
-
return;
|
|
3533
|
-
}
|
|
3534
|
-
this.taskReferences.set(event.task_id, reference);
|
|
3535
|
-
const update = this.mapper.buildLifecycleUpdate(event, reference);
|
|
3536
|
-
if (!update) {
|
|
3537
|
-
this.telemetry.emit({
|
|
3538
|
-
phase: "runtime_event",
|
|
3539
|
-
result: "skipped",
|
|
3540
|
-
sessionId: reference.sessionId,
|
|
3541
|
-
toolCallId: reference.toolCallId,
|
|
3542
|
-
taskId: event.task_id,
|
|
3543
|
-
attemptId: event.attempt_id,
|
|
3544
|
-
eventId: event.event_id,
|
|
3545
|
-
eventType: event.event_type
|
|
3546
|
-
});
|
|
3547
|
-
return;
|
|
3548
|
-
}
|
|
3549
|
-
await this.notify(update);
|
|
3550
|
-
this.telemetry.emit({
|
|
3551
|
-
phase: "runtime_event",
|
|
3552
|
-
result: "ok",
|
|
3553
|
-
sessionId: reference.sessionId,
|
|
3554
|
-
toolCallId: reference.toolCallId,
|
|
3555
|
-
taskId: event.task_id,
|
|
3556
|
-
attemptId: event.attempt_id,
|
|
3557
|
-
eventId: event.event_id,
|
|
3558
|
-
eventType: event.event_type,
|
|
3559
|
-
status: update.update.status
|
|
3560
|
-
});
|
|
3561
|
-
if (isTerminalStatus(update.update.status)) {
|
|
3562
|
-
this.taskReferences.delete(event.task_id);
|
|
3563
|
-
this.toolCallReferences.delete(reference.toolCallId);
|
|
3564
|
-
}
|
|
3565
|
-
}
|
|
3566
|
-
resolveReferenceForEvent(event) {
|
|
3567
|
-
const byTask = this.taskReferences.get(event.task_id);
|
|
3568
|
-
if (byTask) return byTask;
|
|
3569
|
-
const byToolCall = this.toolCallReferences.get(event.tool_call_id);
|
|
3570
|
-
if (byToolCall) return byToolCall;
|
|
3571
|
-
return void 0;
|
|
3572
|
-
}
|
|
3573
|
-
rememberReference(reference) {
|
|
3574
|
-
this.toolCallReferences.set(reference.toolCallId, reference);
|
|
3575
|
-
}
|
|
3576
|
-
cleanupReference(reference, result) {
|
|
3577
|
-
const status = result.status === "queued" ? "in_progress" : result.status === "completed" ? "completed" : result.status === "failed" ? "failed" : "failed";
|
|
3578
|
-
if (!isTerminalStatus(status)) return;
|
|
3579
|
-
this.toolCallReferences.delete(reference.toolCallId);
|
|
3580
|
-
if (result.task_id) {
|
|
3581
|
-
this.taskReferences.delete(result.task_id);
|
|
3582
|
-
}
|
|
3583
|
-
}
|
|
3584
|
-
async notify(update) {
|
|
3585
|
-
try {
|
|
3586
|
-
await this.notifier.notify("session/update", update);
|
|
3587
|
-
this.telemetry.emit({
|
|
3588
|
-
phase: "notify",
|
|
3589
|
-
result: "ok",
|
|
3590
|
-
sessionId: update.sessionId,
|
|
3591
|
-
toolCallId: update.update.toolCallId,
|
|
3592
|
-
status: update.update.status,
|
|
3593
|
-
metadata: {
|
|
3594
|
-
type: update.update.type
|
|
3595
|
-
}
|
|
3596
|
-
});
|
|
3597
|
-
} catch (error) {
|
|
3598
|
-
const reference = this.toolCallReferences.get(update.update.toolCallId);
|
|
3599
|
-
this.telemetry.emit({
|
|
3600
|
-
phase: "notify",
|
|
3601
|
-
result: "failed",
|
|
3602
|
-
sessionId: update.sessionId,
|
|
3603
|
-
toolCallId: update.update.toolCallId,
|
|
3604
|
-
status: update.update.status,
|
|
3605
|
-
error: error instanceof Error ? error.message : "Unknown notify error"
|
|
3606
|
-
});
|
|
3607
|
-
if (!reference) return;
|
|
3608
|
-
const failureUpdate = this.mapper.buildNotifyFailureUpdate(reference, error);
|
|
3609
|
-
try {
|
|
3610
|
-
await this.notifier.notify("session/update", failureUpdate);
|
|
3611
|
-
} catch {
|
|
3612
|
-
}
|
|
3613
|
-
}
|
|
3614
|
-
}
|
|
3615
|
-
};
|
|
9
|
+
} from "./chunk-ORAAYW4C.mjs";
|
|
3616
10
|
|
|
3617
11
|
// src/index.ts
|
|
3618
12
|
var _RealtimeXSDK = class _RealtimeXSDK {
|
|
3619
13
|
constructor(config = {}) {
|
|
3620
14
|
const envAppId = this.getEnvVar("RTX_APP_ID");
|
|
3621
|
-
const envAppName = this.getEnvVar("RTX_APP_NAME");
|
|
3622
15
|
const envApiKey = this.getEnvVar("RTX_API_KEY");
|
|
3623
16
|
this.appId = config.realtimex?.appId || envAppId || "";
|
|
3624
|
-
this.appName = config.realtimex?.appName || envAppName;
|
|
3625
17
|
this.apiKey = config.realtimex?.apiKey || envApiKey;
|
|
3626
|
-
this.permissions = config.permissions || [];
|
|
3627
18
|
this.realtimexUrl = config.realtimex?.url || _RealtimeXSDK.DEFAULT_REALTIMEX_URL;
|
|
3628
|
-
this.
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
if (config.contract) {
|
|
3634
|
-
this.task.configureContract(config.contract);
|
|
3635
|
-
}
|
|
3636
|
-
this.port = new PortModule(config.defaultPort);
|
|
3637
|
-
this.llm = new LLMModule(this.realtimexUrl, this.appId, this.appName, this.apiKey);
|
|
3638
|
-
this.tts = new TTSModule(this.realtimexUrl, this.appId, this.appName, this.apiKey);
|
|
3639
|
-
this.stt = new STTModule(this.realtimexUrl, this.appId, this.appName, this.apiKey);
|
|
3640
|
-
this.agent = new AgentModule(this.httpClient);
|
|
3641
|
-
this.acpAgent = new AcpAgentModule(this.httpClient);
|
|
3642
|
-
this.mcp = new MCPModule(this.realtimexUrl, this.appId, this.appName, this.apiKey);
|
|
3643
|
-
this.contract = new ContractModule(this.realtimexUrl, this.appName, this.appId, this.apiKey);
|
|
3644
|
-
this.contractRuntime = new ContractRuntime({
|
|
3645
|
-
baseUrl: this.realtimexUrl,
|
|
3646
|
-
appId: this.appId || void 0,
|
|
3647
|
-
appName: this.appName,
|
|
3648
|
-
apiKey: this.apiKey,
|
|
3649
|
-
permissions: this.permissions
|
|
3650
|
-
});
|
|
3651
|
-
this.database = new DatabaseModule(this.realtimexUrl, this.appId, this.apiKey);
|
|
3652
|
-
this.auth = new AuthModule(this.realtimexUrl, this.appId, this.apiKey);
|
|
3653
|
-
this.credentials = new CredentialsModule(this.httpClient);
|
|
3654
|
-
this.v1 = this.apiKey || this.appId ? new V1ApiNamespace(this.realtimexUrl, this.apiKey ?? "", this.appId || void 0) : void 0;
|
|
3655
|
-
this.desktopRuntimeSessions = this.v1?.desktopRuntimeSessions;
|
|
3656
|
-
this.desktopBrowser = this.v1?.desktopBrowser;
|
|
3657
|
-
if (this.permissions.length > 0 && this.appId && !this.apiKey) {
|
|
3658
|
-
this.register().catch((err) => {
|
|
3659
|
-
console.error("[RealtimeX SDK] Auto-registration failed:", err.message);
|
|
3660
|
-
});
|
|
3661
|
-
}
|
|
3662
|
-
}
|
|
3663
|
-
/**
|
|
3664
|
-
* Register app with RealtimeX hub and request declared permissions upfront.
|
|
3665
|
-
* This is called automatically if permissions are provided in constructor.
|
|
3666
|
-
*/
|
|
3667
|
-
async register(permissions) {
|
|
3668
|
-
const perms = permissions || this.permissions;
|
|
3669
|
-
if (perms.length === 0) return;
|
|
3670
|
-
try {
|
|
3671
|
-
const response = await fetch(`${this.realtimexUrl.replace(/\/$/, "")}/sdk/register`, {
|
|
3672
|
-
method: "POST",
|
|
3673
|
-
headers: { "Content-Type": "application/json" },
|
|
3674
|
-
body: JSON.stringify({
|
|
3675
|
-
app_id: this.appId,
|
|
3676
|
-
app_name: this.appName,
|
|
3677
|
-
permissions: perms
|
|
3678
|
-
})
|
|
3679
|
-
});
|
|
3680
|
-
const data = await response.json();
|
|
3681
|
-
if (!response.ok) {
|
|
3682
|
-
throw new Error(data.error || "Registration failed");
|
|
3683
|
-
}
|
|
3684
|
-
console.log(`[RealtimeX SDK] App registered successfully (${data.message})`);
|
|
3685
|
-
} catch (error) {
|
|
3686
|
-
throw new Error(`Failed to register app: ${error.message}`);
|
|
3687
|
-
}
|
|
19
|
+
this.v1 = new V1ApiNamespace(
|
|
20
|
+
this.realtimexUrl,
|
|
21
|
+
this.apiKey ?? "",
|
|
22
|
+
this.appId || void 0
|
|
23
|
+
);
|
|
3688
24
|
}
|
|
3689
|
-
/**
|
|
3690
|
-
* Get environment variable (works in Node.js and browser)
|
|
3691
|
-
*/
|
|
3692
25
|
getEnvVar(name) {
|
|
3693
26
|
if (typeof process !== "undefined" && process.env) {
|
|
3694
27
|
return process.env[name];
|
|
@@ -3698,125 +31,16 @@ var _RealtimeXSDK = class _RealtimeXSDK {
|
|
|
3698
31
|
}
|
|
3699
32
|
return void 0;
|
|
3700
33
|
}
|
|
3701
|
-
/**
|
|
3702
|
-
* Ping RealtimeX server to verify connection and authentication.
|
|
3703
|
-
* Works in both development (API Key) and production (App ID) modes.
|
|
3704
|
-
*/
|
|
3705
|
-
async ping() {
|
|
3706
|
-
try {
|
|
3707
|
-
const headers = { "Content-Type": "application/json" };
|
|
3708
|
-
if (this.apiKey) {
|
|
3709
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
3710
|
-
}
|
|
3711
|
-
if (this.appId) {
|
|
3712
|
-
headers["x-app-id"] = this.appId;
|
|
3713
|
-
}
|
|
3714
|
-
const response = await fetch(`${this.realtimexUrl.replace(/\/$/, "")}/sdk/ping`, {
|
|
3715
|
-
method: "GET",
|
|
3716
|
-
headers
|
|
3717
|
-
});
|
|
3718
|
-
const data = await response.json();
|
|
3719
|
-
if (!response.ok) {
|
|
3720
|
-
throw new Error(data.error || "Ping failed");
|
|
3721
|
-
}
|
|
3722
|
-
return data;
|
|
3723
|
-
} catch (error) {
|
|
3724
|
-
throw new Error(`Connection failed: ${error.message}`);
|
|
3725
|
-
}
|
|
3726
|
-
}
|
|
3727
|
-
/**
|
|
3728
|
-
* Get the absolute path to the data directory for this app.
|
|
3729
|
-
* Path: ~/.realtimex.ai/Resources/local-apps/{appId}
|
|
3730
|
-
*/
|
|
3731
|
-
async getAppDataDir() {
|
|
3732
|
-
try {
|
|
3733
|
-
const headers = { "Content-Type": "application/json" };
|
|
3734
|
-
if (this.apiKey) {
|
|
3735
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
3736
|
-
}
|
|
3737
|
-
if (this.appId) {
|
|
3738
|
-
headers["x-app-id"] = this.appId;
|
|
3739
|
-
}
|
|
3740
|
-
const response = await fetch(`${this.realtimexUrl.replace(/\/$/, "")}/sdk/local-apps/data-dir`, {
|
|
3741
|
-
method: "GET",
|
|
3742
|
-
headers
|
|
3743
|
-
});
|
|
3744
|
-
const data = await response.json();
|
|
3745
|
-
if (!response.ok) {
|
|
3746
|
-
throw new Error(data.error || "Failed to get data directory");
|
|
3747
|
-
}
|
|
3748
|
-
return data.dataDir;
|
|
3749
|
-
} catch (error) {
|
|
3750
|
-
throw new Error(`Failed to get app data directory: ${error.message}`);
|
|
3751
|
-
}
|
|
3752
|
-
}
|
|
3753
34
|
};
|
|
3754
35
|
_RealtimeXSDK.DEFAULT_REALTIMEX_URL = "http://localhost:3001";
|
|
3755
36
|
var RealtimeXSDK = _RealtimeXSDK;
|
|
3756
37
|
export {
|
|
3757
|
-
ACPContractAdapter,
|
|
3758
|
-
ACPEventMapper,
|
|
3759
|
-
ACPPermissionBridge,
|
|
3760
|
-
ACPTelemetry,
|
|
3761
|
-
AcpAgentModule,
|
|
3762
|
-
ActivitiesModule,
|
|
3763
|
-
AgentModule,
|
|
3764
|
-
ApiModule,
|
|
3765
|
-
AuthModule,
|
|
3766
38
|
AuthenticationError,
|
|
3767
|
-
CONTRACT_ATTEMPT_PREFIX,
|
|
3768
|
-
CONTRACT_EVENT_ID_HEADER,
|
|
3769
|
-
CONTRACT_SIGNATURE_ALGORITHM,
|
|
3770
|
-
CONTRACT_SIGNATURE_HEADER,
|
|
3771
|
-
ClaudeToolAdapter,
|
|
3772
|
-
CodexToolAdapter,
|
|
3773
|
-
ContractCache,
|
|
3774
|
-
ContractClient,
|
|
3775
|
-
ContractError,
|
|
3776
|
-
ContractHttpClient,
|
|
3777
|
-
ContractModule,
|
|
3778
|
-
ContractRuntime,
|
|
3779
|
-
ContractValidationError,
|
|
3780
|
-
DatabaseModule,
|
|
3781
39
|
DeveloperApiClient,
|
|
3782
40
|
DeveloperApiError,
|
|
3783
|
-
GeminiToolAdapter,
|
|
3784
|
-
LLMModule,
|
|
3785
|
-
LLMPermissionError,
|
|
3786
|
-
LLMProviderError,
|
|
3787
|
-
LOCAL_APP_CONTRACT_VERSION,
|
|
3788
|
-
MCPModule,
|
|
3789
41
|
NotFoundError,
|
|
3790
|
-
PermissionDeniedError,
|
|
3791
|
-
PermissionRequiredError,
|
|
3792
|
-
PortModule,
|
|
3793
42
|
RealtimeXSDK,
|
|
3794
|
-
RetryPolicy,
|
|
3795
|
-
RuntimeTransportError,
|
|
3796
|
-
STTModule,
|
|
3797
|
-
ScopeDeniedError,
|
|
3798
|
-
ScopeGuard,
|
|
3799
43
|
ServerError,
|
|
3800
|
-
StaticAuthProvider,
|
|
3801
|
-
TTSModule,
|
|
3802
|
-
TaskModule,
|
|
3803
|
-
ToolNotFoundError,
|
|
3804
|
-
ToolProjector,
|
|
3805
|
-
ToolValidationError,
|
|
3806
44
|
V1ApiNamespace,
|
|
3807
|
-
ValidationError
|
|
3808
|
-
VectorStore,
|
|
3809
|
-
WebhookModule,
|
|
3810
|
-
buildContractIdempotencyKey,
|
|
3811
|
-
buildContractSignatureMessage,
|
|
3812
|
-
canonicalEventToLegacyAction,
|
|
3813
|
-
createContractEventId,
|
|
3814
|
-
hashContractPayload,
|
|
3815
|
-
normalizeAttemptId,
|
|
3816
|
-
normalizeContractEvent,
|
|
3817
|
-
normalizeLocalAppContractV1,
|
|
3818
|
-
normalizeSchema,
|
|
3819
|
-
parseAttemptRunId,
|
|
3820
|
-
signContractEvent,
|
|
3821
|
-
toStableToolName
|
|
45
|
+
ValidationError
|
|
3822
46
|
};
|