@elizaos/plugin-xr 2.0.3-beta.6 → 2.0.3-beta.7
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/actions/xr-query-vision.d.ts +3 -0
- package/dist/actions/xr-query-vision.d.ts.map +1 -0
- package/dist/actions/xr-query-vision.js +39 -0
- package/dist/actions/xr-query-vision.js.map +1 -0
- package/dist/actions/xr-view-actions.d.ts +18 -0
- package/dist/actions/xr-view-actions.d.ts.map +1 -0
- package/dist/actions/xr-view-actions.js +304 -0
- package/dist/actions/xr-view-actions.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +124 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +18 -0
- package/dist/protocol.js.map +1 -0
- package/dist/providers/xr-context.d.ts +3 -0
- package/dist/providers/xr-context.d.ts.map +1 -0
- package/dist/providers/xr-context.js +34 -0
- package/dist/providers/xr-context.js.map +1 -0
- package/dist/routes/xr-connect.d.ts +3 -0
- package/dist/routes/xr-connect.d.ts.map +1 -0
- package/{src/routes/xr-connect.ts → dist/routes/xr-connect.js} +12 -15
- package/dist/routes/xr-connect.js.map +1 -0
- package/dist/routes/xr-simulator-route.d.ts +8 -0
- package/dist/routes/xr-simulator-route.d.ts.map +1 -0
- package/{src/routes/xr-simulator-route.ts → dist/routes/xr-simulator-route.js} +10 -16
- package/dist/routes/xr-simulator-route.js.map +1 -0
- package/dist/routes/xr-status.d.ts +3 -0
- package/dist/routes/xr-status.d.ts.map +1 -0
- package/{src/routes/xr-status.ts → dist/routes/xr-status.js} +13 -15
- package/dist/routes/xr-status.js.map +1 -0
- package/dist/routes/xr-view-host.d.ts +24 -0
- package/dist/routes/xr-view-host.d.ts.map +1 -0
- package/{src/routes/xr-view-host.ts → dist/routes/xr-view-host.js} +22 -59
- package/dist/routes/xr-view-host.js.map +1 -0
- package/dist/routes/xr-views.d.ts +8 -0
- package/dist/routes/xr-views.d.ts.map +1 -0
- package/dist/routes/xr-views.js +31 -0
- package/dist/routes/xr-views.js.map +1 -0
- package/dist/services/audio-pipeline.d.ts +20 -0
- package/dist/services/audio-pipeline.d.ts.map +1 -0
- package/{src/services/audio-pipeline.ts → dist/services/audio-pipeline.js} +25 -58
- package/dist/services/audio-pipeline.js.map +1 -0
- package/dist/services/vision-pipeline.d.ts +16 -0
- package/dist/services/vision-pipeline.d.ts.map +1 -0
- package/dist/services/vision-pipeline.js +39 -0
- package/dist/services/vision-pipeline.js.map +1 -0
- package/dist/services/xr-session-service.d.ts +50 -0
- package/dist/services/xr-session-service.d.ts.map +1 -0
- package/{src/services/xr-session-service.ts → dist/services/xr-session-service.js} +85 -194
- package/dist/services/xr-session-service.js.map +1 -0
- package/package.json +9 -4
- package/AGENTS.md +0 -151
- package/CLAUDE.md +0 -151
- package/simulator/bun.lock +0 -159
- package/simulator/package.json +0 -28
- package/simulator/src/emulator.ts +0 -174
- package/simulator/src/mock-agent.ts +0 -233
- package/simulator/src/node.ts +0 -9
- package/simulator/src/playwright-fixture.ts +0 -169
- package/simulator/src/types.ts +0 -51
- package/simulator/tsconfig.json +0 -13
- package/simulator/vite.config.ts +0 -25
- package/src/__tests__/audio-pipeline.test.ts +0 -129
- package/src/__tests__/protocol.test.ts +0 -53
- package/src/__tests__/routes-e2e.test.ts +0 -276
- package/src/__tests__/vision-pipeline.test.ts +0 -73
- package/src/__tests__/xr-bundle-coverage.test.ts +0 -303
- package/src/__tests__/xr-feature-parity.test.ts +0 -524
- package/src/__tests__/xr-functional-parity.test.ts +0 -522
- package/src/__tests__/xr-view-host-http.test.ts +0 -239
- package/src/__tests__/xr-view-host.test.ts +0 -174
- package/src/actions/xr-query-vision.ts +0 -64
- package/src/actions/xr-view-actions.ts +0 -386
- package/src/index.ts +0 -55
- package/src/protocol.ts +0 -126
- package/src/providers/xr-context.ts +0 -49
- package/src/routes/xr-views.ts +0 -43
- package/src/services/vision-pipeline.ts +0 -57
- package/tsconfig.build.json +0 -9
- package/tsconfig.json +0 -30
- package/vitest.config.ts +0 -21
|
@@ -1,386 +0,0 @@
|
|
|
1
|
-
import { listViews } from "@elizaos/agent/api/views-registry";
|
|
2
|
-
import type {
|
|
3
|
-
Action,
|
|
4
|
-
HandlerCallback,
|
|
5
|
-
IAgentRuntime,
|
|
6
|
-
Memory,
|
|
7
|
-
State,
|
|
8
|
-
} from "@elizaos/core";
|
|
9
|
-
import {
|
|
10
|
-
XR_SERVICE_TYPE,
|
|
11
|
-
type XRSessionService,
|
|
12
|
-
} from "../services/xr-session-service.ts";
|
|
13
|
-
|
|
14
|
-
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
15
|
-
|
|
16
|
-
function getService(runtime: IAgentRuntime): XRSessionService | null {
|
|
17
|
-
return runtime.getService<XRSessionService>(XR_SERVICE_TYPE) ?? null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function firstConnectionId(svc: XRSessionService): string | null {
|
|
21
|
-
return svc.getConnections()[0]?.id ?? null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function agentBaseUrl(): string {
|
|
25
|
-
// The API port is orchestrator-assigned (never hardcoded) — read it from the
|
|
26
|
-
// environment, not a non-existent `runtime.port` field. Falls back to 31337.
|
|
27
|
-
const port =
|
|
28
|
-
process.env.ELIZA_API_PORT?.trim() ||
|
|
29
|
-
process.env.ELIZA_PORT?.trim() ||
|
|
30
|
-
"31337";
|
|
31
|
-
return process.env.XR_AGENT_URL ?? `http://localhost:${port}`;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// ── XR_OPEN_VIEW ───────────────────────────────────────────────────────────
|
|
35
|
-
|
|
36
|
-
export const xrOpenViewAction: Action = {
|
|
37
|
-
name: "XR_OPEN_VIEW",
|
|
38
|
-
similes: ["OPEN_XR_VIEW", "SHOW_XR_PANEL", "XR_SHOW", "XR_LAUNCH"],
|
|
39
|
-
description:
|
|
40
|
-
"Opens a view panel on the connected XR headset by view id. Use XR_LIST_VIEWS first to discover available view ids.",
|
|
41
|
-
examples: [
|
|
42
|
-
[
|
|
43
|
-
{ name: "user", content: { text: "open the wallet in XR" } },
|
|
44
|
-
{
|
|
45
|
-
name: "agent",
|
|
46
|
-
content: {
|
|
47
|
-
text: "Opening wallet view on your headset.",
|
|
48
|
-
action: "XR_OPEN_VIEW",
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
],
|
|
52
|
-
[
|
|
53
|
-
{ name: "user", content: { text: "show training dashboard in XR" } },
|
|
54
|
-
{
|
|
55
|
-
name: "agent",
|
|
56
|
-
content: { text: "Launching training panel.", action: "XR_OPEN_VIEW" },
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
],
|
|
60
|
-
|
|
61
|
-
validate: async (runtime): Promise<boolean> => {
|
|
62
|
-
const svc = getService(runtime);
|
|
63
|
-
return svc?.hasActiveConnections() ?? false;
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
handler: async (
|
|
67
|
-
runtime: IAgentRuntime,
|
|
68
|
-
message: Memory,
|
|
69
|
-
_state: State | undefined,
|
|
70
|
-
options: Record<string, unknown> | undefined,
|
|
71
|
-
callback?: HandlerCallback,
|
|
72
|
-
) => {
|
|
73
|
-
const svc = getService(runtime);
|
|
74
|
-
if (!svc) {
|
|
75
|
-
const text = "No XR session service available.";
|
|
76
|
-
await callback?.({ text });
|
|
77
|
-
return { success: false, text };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const viewId =
|
|
81
|
-
(options?.viewId as string | undefined) ??
|
|
82
|
-
extractViewId(message.content.text ?? "");
|
|
83
|
-
if (!viewId) {
|
|
84
|
-
const text =
|
|
85
|
-
"Please specify which view to open. Try XR_LIST_VIEWS to see available views.";
|
|
86
|
-
await callback?.({ text });
|
|
87
|
-
return { success: false, text };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const connId = firstConnectionId(svc);
|
|
91
|
-
if (!connId) {
|
|
92
|
-
const text = "No XR device is connected.";
|
|
93
|
-
await callback?.({ text });
|
|
94
|
-
return { success: false, text };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const base = agentBaseUrl();
|
|
98
|
-
const scale = (options?.scale as number | undefined) ?? 1.0;
|
|
99
|
-
svc.openView(connId, viewId, base, { scale, followMode: "billboard" });
|
|
100
|
-
const text = `Opening ${viewId} view on your headset.`;
|
|
101
|
-
await callback?.({ text });
|
|
102
|
-
return { success: true, text };
|
|
103
|
-
},
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
// ── XR_CLOSE_VIEW ──────────────────────────────────────────────────────────
|
|
107
|
-
|
|
108
|
-
export const xrCloseViewAction: Action = {
|
|
109
|
-
name: "XR_CLOSE_VIEW",
|
|
110
|
-
similes: ["CLOSE_XR_VIEW", "HIDE_XR_PANEL", "XR_CLOSE", "XR_DISMISS"],
|
|
111
|
-
description: "Closes a specific view panel on the connected XR headset.",
|
|
112
|
-
examples: [
|
|
113
|
-
[
|
|
114
|
-
{ name: "user", content: { text: "close the wallet panel" } },
|
|
115
|
-
{
|
|
116
|
-
name: "agent",
|
|
117
|
-
content: { text: "Closing wallet panel.", action: "XR_CLOSE_VIEW" },
|
|
118
|
-
},
|
|
119
|
-
],
|
|
120
|
-
],
|
|
121
|
-
|
|
122
|
-
validate: async (runtime) => {
|
|
123
|
-
const svc = getService(runtime);
|
|
124
|
-
return svc?.hasActiveConnections() ?? false;
|
|
125
|
-
},
|
|
126
|
-
|
|
127
|
-
handler: async (runtime, message, _state, options, callback) => {
|
|
128
|
-
const svc = getService(runtime);
|
|
129
|
-
if (!svc) {
|
|
130
|
-
const text = "No XR service.";
|
|
131
|
-
await callback?.({ text });
|
|
132
|
-
return { success: false, text };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const viewId =
|
|
136
|
-
(options?.viewId as string | undefined) ??
|
|
137
|
-
extractViewId(message.content.text ?? "");
|
|
138
|
-
const connId = firstConnectionId(svc);
|
|
139
|
-
if (!connId) {
|
|
140
|
-
const text = "No XR device connected.";
|
|
141
|
-
await callback?.({ text });
|
|
142
|
-
return { success: false, text };
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
let text: string;
|
|
146
|
-
if (viewId) {
|
|
147
|
-
svc.closeView(connId, viewId);
|
|
148
|
-
text = `Closed ${viewId}.`;
|
|
149
|
-
await callback?.({ text });
|
|
150
|
-
} else {
|
|
151
|
-
const views = collectXRViews();
|
|
152
|
-
for (const view of views) {
|
|
153
|
-
svc.closeView(connId, view.id);
|
|
154
|
-
}
|
|
155
|
-
text =
|
|
156
|
-
views.length > 0 ? "Closed all XR panels." : "No XR panels to close.";
|
|
157
|
-
await callback?.({ text });
|
|
158
|
-
}
|
|
159
|
-
return { success: true, text };
|
|
160
|
-
},
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
// ── XR_SWITCH_VIEW ─────────────────────────────────────────────────────────
|
|
164
|
-
|
|
165
|
-
export const xrSwitchViewAction: Action = {
|
|
166
|
-
name: "XR_SWITCH_VIEW",
|
|
167
|
-
similes: ["SWITCH_XR_VIEW", "XR_GO_TO", "XR_NAVIGATE"],
|
|
168
|
-
description:
|
|
169
|
-
"Switches the active (foreground) view on the XR headset without closing others.",
|
|
170
|
-
examples: [
|
|
171
|
-
[
|
|
172
|
-
{ name: "user", content: { text: "switch to companion in XR" } },
|
|
173
|
-
{
|
|
174
|
-
name: "agent",
|
|
175
|
-
content: {
|
|
176
|
-
text: "Switching to companion view.",
|
|
177
|
-
action: "XR_SWITCH_VIEW",
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
],
|
|
181
|
-
],
|
|
182
|
-
|
|
183
|
-
validate: async (runtime) => {
|
|
184
|
-
const svc = getService(runtime);
|
|
185
|
-
return svc?.hasActiveConnections() ?? false;
|
|
186
|
-
},
|
|
187
|
-
|
|
188
|
-
handler: async (runtime, message, _state, options, callback) => {
|
|
189
|
-
const svc = getService(runtime);
|
|
190
|
-
if (!svc) {
|
|
191
|
-
const text = "No XR service.";
|
|
192
|
-
await callback?.({ text });
|
|
193
|
-
return { success: false, text };
|
|
194
|
-
}
|
|
195
|
-
const viewId =
|
|
196
|
-
(options?.viewId as string | undefined) ??
|
|
197
|
-
extractViewId(message.content.text ?? "");
|
|
198
|
-
const connId = firstConnectionId(svc);
|
|
199
|
-
if (!connId || !viewId) {
|
|
200
|
-
const text = "Specify a view id.";
|
|
201
|
-
await callback?.({ text });
|
|
202
|
-
return { success: false, text };
|
|
203
|
-
}
|
|
204
|
-
svc.switchView(connId, viewId);
|
|
205
|
-
const text = `Switched to ${viewId}.`;
|
|
206
|
-
await callback?.({ text });
|
|
207
|
-
return { success: true, text };
|
|
208
|
-
},
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
// ── XR_LIST_VIEWS ──────────────────────────────────────────────────────────
|
|
212
|
-
|
|
213
|
-
export const xrListViewsAction: Action = {
|
|
214
|
-
name: "XR_LIST_VIEWS",
|
|
215
|
-
similes: ["LIST_XR_VIEWS", "XR_VIEWS", "WHAT_XR_VIEWS", "SHOW_XR_LAUNCHER"],
|
|
216
|
-
description:
|
|
217
|
-
"Lists all views available on the XR device and optionally sends a launcher catalog to the headset. Use this before XR_OPEN_VIEW.",
|
|
218
|
-
examples: [
|
|
219
|
-
[
|
|
220
|
-
{ name: "user", content: { text: "what can I open in XR?" } },
|
|
221
|
-
{
|
|
222
|
-
name: "agent",
|
|
223
|
-
content: {
|
|
224
|
-
text: "Available XR views are registered by the loaded plugins.",
|
|
225
|
-
action: "XR_LIST_VIEWS",
|
|
226
|
-
},
|
|
227
|
-
},
|
|
228
|
-
],
|
|
229
|
-
],
|
|
230
|
-
|
|
231
|
-
validate: async (runtime) => {
|
|
232
|
-
const svc = getService(runtime);
|
|
233
|
-
return svc !== null;
|
|
234
|
-
},
|
|
235
|
-
|
|
236
|
-
handler: async (runtime, _message, _state, options, callback) => {
|
|
237
|
-
const svc = getService(runtime);
|
|
238
|
-
if (!svc) {
|
|
239
|
-
const text = "No XR service.";
|
|
240
|
-
await callback?.({ text });
|
|
241
|
-
return { success: false, text };
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Collect XR view declarations from all registered plugins
|
|
245
|
-
const xrViews = collectXRViews();
|
|
246
|
-
|
|
247
|
-
const connId = firstConnectionId(svc);
|
|
248
|
-
if (connId && (options?.sendCatalog as boolean | undefined) !== false) {
|
|
249
|
-
svc.sendViewsCatalog(connId, xrViews);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (xrViews.length === 0) {
|
|
253
|
-
const text = "No XR views are currently registered.";
|
|
254
|
-
await callback?.({ text });
|
|
255
|
-
return { success: true, text };
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const list = xrViews.map((v) => `• ${v.label} (id: ${v.id})`).join("\n");
|
|
259
|
-
const text = `Available XR views:\n${list}\n\nSay "open [view name]" to launch one.`;
|
|
260
|
-
await callback?.({ text });
|
|
261
|
-
return { success: true, text };
|
|
262
|
-
},
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
// ── XR_RESIZE_VIEW ─────────────────────────────────────────────────────────
|
|
266
|
-
|
|
267
|
-
export const xrResizeViewAction: Action = {
|
|
268
|
-
name: "XR_RESIZE_VIEW",
|
|
269
|
-
similes: ["RESIZE_XR_PANEL", "XR_MAKE_BIGGER", "XR_MAKE_SMALLER", "XR_SCALE"],
|
|
270
|
-
description:
|
|
271
|
-
"Resizes or repositions the active XR view panel. Accepts scale (0.5 = half, 2.0 = double) and distance.",
|
|
272
|
-
examples: [
|
|
273
|
-
[
|
|
274
|
-
{ name: "user", content: { text: "make the panel bigger" } },
|
|
275
|
-
{
|
|
276
|
-
name: "agent",
|
|
277
|
-
content: { text: "Scaling up the panel.", action: "XR_RESIZE_VIEW" },
|
|
278
|
-
},
|
|
279
|
-
],
|
|
280
|
-
[
|
|
281
|
-
{ name: "user", content: { text: "make it smaller and move closer" } },
|
|
282
|
-
{
|
|
283
|
-
name: "agent",
|
|
284
|
-
content: {
|
|
285
|
-
text: "Resizing and moving panel closer.",
|
|
286
|
-
action: "XR_RESIZE_VIEW",
|
|
287
|
-
},
|
|
288
|
-
},
|
|
289
|
-
],
|
|
290
|
-
],
|
|
291
|
-
|
|
292
|
-
validate: async (runtime) => {
|
|
293
|
-
const svc = getService(runtime);
|
|
294
|
-
return svc?.hasActiveConnections() ?? false;
|
|
295
|
-
},
|
|
296
|
-
|
|
297
|
-
handler: async (runtime, message, _state, options, callback) => {
|
|
298
|
-
const svc = getService(runtime);
|
|
299
|
-
if (!svc) {
|
|
300
|
-
const text = "No XR service.";
|
|
301
|
-
await callback?.({ text });
|
|
302
|
-
return { success: false, text };
|
|
303
|
-
}
|
|
304
|
-
const connId = firstConnectionId(svc);
|
|
305
|
-
if (!connId) {
|
|
306
|
-
const text = "No XR device connected.";
|
|
307
|
-
await callback?.({ text });
|
|
308
|
-
return { success: false, text };
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const inputText = message.content.text?.toLowerCase() ?? "";
|
|
312
|
-
let scale = (options?.scale as number | undefined) ?? 1.0;
|
|
313
|
-
let distance = (options?.distance as number | undefined) ?? 1.5;
|
|
314
|
-
|
|
315
|
-
if (
|
|
316
|
-
inputText.includes("bigger") ||
|
|
317
|
-
inputText.includes("larger") ||
|
|
318
|
-
inputText.includes("bigger")
|
|
319
|
-
)
|
|
320
|
-
scale = 1.5;
|
|
321
|
-
if (inputText.includes("smaller") || inputText.includes("tiny"))
|
|
322
|
-
scale = 0.6;
|
|
323
|
-
if (inputText.includes("closer") || inputText.includes("nearer"))
|
|
324
|
-
distance = 0.8;
|
|
325
|
-
if (
|
|
326
|
-
inputText.includes("farther") ||
|
|
327
|
-
inputText.includes("further") ||
|
|
328
|
-
inputText.includes("away")
|
|
329
|
-
)
|
|
330
|
-
distance = 2.5;
|
|
331
|
-
if (inputText.includes("fullscreen") || inputText.includes("full screen")) {
|
|
332
|
-
svc.resizeView(connId, "", { scale: 2.0, fullscreen: true });
|
|
333
|
-
const text = "Panel fullscreened.";
|
|
334
|
-
await callback?.({ text });
|
|
335
|
-
return { success: true, text };
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const viewId = (options?.viewId as string | undefined) ?? "";
|
|
339
|
-
svc.resizeView(connId, viewId, { scale, distance });
|
|
340
|
-
const text = `Panel resized to ${scale}× at ${distance}m.`;
|
|
341
|
-
await callback?.({ text });
|
|
342
|
-
return { success: true, text };
|
|
343
|
-
},
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
347
|
-
|
|
348
|
-
/** Extract a likely view id from natural language */
|
|
349
|
-
export function extractViewId(text: string): string {
|
|
350
|
-
const lower = text.toLowerCase();
|
|
351
|
-
for (const view of collectXRViews()) {
|
|
352
|
-
const terms = [view.id, view.id.replace(/-/g, " "), view.label].map(
|
|
353
|
-
(term) => term.toLowerCase(),
|
|
354
|
-
);
|
|
355
|
-
if (terms.some((term) => term && lower.includes(term))) {
|
|
356
|
-
return view.id;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
const quoted = text.match(/["']([^"']+)["']/);
|
|
360
|
-
if (quoted?.[1]) return quoted[1];
|
|
361
|
-
return "";
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
type XRViewSummary = {
|
|
365
|
-
id: string;
|
|
366
|
-
label: string;
|
|
367
|
-
icon?: string;
|
|
368
|
-
description?: string;
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
/** Collect all XR-typed views from registered plugins */
|
|
372
|
-
export function collectXRViews(): XRViewSummary[] {
|
|
373
|
-
const byId = new Map<string, XRViewSummary>();
|
|
374
|
-
for (const view of listViews({ developerMode: true, viewType: "xr" })) {
|
|
375
|
-
if (view.viewType !== "xr") continue;
|
|
376
|
-
byId.set(view.id, {
|
|
377
|
-
id: view.id,
|
|
378
|
-
label: view.label,
|
|
379
|
-
icon: view.icon,
|
|
380
|
-
description: view.description,
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
return [...byId.values()].sort(
|
|
384
|
-
(a, b) => a.label.localeCompare(b.label) || a.id.localeCompare(b.id),
|
|
385
|
-
);
|
|
386
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import type { Plugin } from "@elizaos/core";
|
|
2
|
-
import { xrQueryVisionAction } from "./actions/xr-query-vision.ts";
|
|
3
|
-
import {
|
|
4
|
-
xrCloseViewAction,
|
|
5
|
-
xrListViewsAction,
|
|
6
|
-
xrOpenViewAction,
|
|
7
|
-
xrResizeViewAction,
|
|
8
|
-
xrSwitchViewAction,
|
|
9
|
-
} from "./actions/xr-view-actions.ts";
|
|
10
|
-
import { xrContextProvider } from "./providers/xr-context.ts";
|
|
11
|
-
import { xrConnectRoute } from "./routes/xr-connect.ts";
|
|
12
|
-
import { xrSimulatorRoute } from "./routes/xr-simulator-route.ts";
|
|
13
|
-
import { xrStatusRoute } from "./routes/xr-status.ts";
|
|
14
|
-
import { xrViewHostRoute } from "./routes/xr-view-host.ts";
|
|
15
|
-
import { xrViewsRoute } from "./routes/xr-views.ts";
|
|
16
|
-
import { XRSessionService } from "./services/xr-session-service.ts";
|
|
17
|
-
|
|
18
|
-
export type * from "./protocol.ts";
|
|
19
|
-
export { AudioPipeline } from "./services/audio-pipeline.ts";
|
|
20
|
-
export { VisionPipeline } from "./services/vision-pipeline.ts";
|
|
21
|
-
export {
|
|
22
|
-
XR_SERVICE_TYPE,
|
|
23
|
-
XR_WS_PORT_DEFAULT,
|
|
24
|
-
XRSessionService,
|
|
25
|
-
} from "./services/xr-session-service.ts";
|
|
26
|
-
|
|
27
|
-
export const xrPlugin: Plugin = {
|
|
28
|
-
name: "@elizaos/plugin-xr",
|
|
29
|
-
description:
|
|
30
|
-
"Streams audio and camera video from XR headsets (Quest 3, XReal) to the agent and delivers voice responses back.",
|
|
31
|
-
|
|
32
|
-
services: [XRSessionService],
|
|
33
|
-
actions: [
|
|
34
|
-
xrQueryVisionAction,
|
|
35
|
-
xrOpenViewAction,
|
|
36
|
-
xrCloseViewAction,
|
|
37
|
-
xrSwitchViewAction,
|
|
38
|
-
xrListViewsAction,
|
|
39
|
-
xrResizeViewAction,
|
|
40
|
-
],
|
|
41
|
-
providers: [xrContextProvider],
|
|
42
|
-
routes: [
|
|
43
|
-
xrStatusRoute,
|
|
44
|
-
xrConnectRoute,
|
|
45
|
-
xrViewsRoute,
|
|
46
|
-
xrViewHostRoute,
|
|
47
|
-
xrSimulatorRoute,
|
|
48
|
-
],
|
|
49
|
-
|
|
50
|
-
config: {
|
|
51
|
-
XR_WS_PORT: 31338,
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
export default xrPlugin;
|
package/src/protocol.ts
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
// Binary frame layout:
|
|
2
|
-
// bytes 0–3 : big-endian uint32 — JSON header length
|
|
3
|
-
// bytes 4–N : UTF-8 JSON header
|
|
4
|
-
// bytes N+1… : raw binary payload (audio PCM/Opus, JPEG, etc.)
|
|
5
|
-
//
|
|
6
|
-
// Text frames are JSON control messages (no binary payload).
|
|
7
|
-
|
|
8
|
-
export type XRDeviceType = "quest3" | "xreal" | "simulator";
|
|
9
|
-
|
|
10
|
-
// ── Client → Server (text frames) ──────────────────────────────────────────
|
|
11
|
-
|
|
12
|
-
/** View panel state reported back from the XR device */
|
|
13
|
-
export interface XRViewPanelState {
|
|
14
|
-
viewId: string;
|
|
15
|
-
active: boolean;
|
|
16
|
-
width?: number;
|
|
17
|
-
height?: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export type XRClientControl =
|
|
21
|
-
| { type: "hello"; deviceType: XRDeviceType; sessionId: string }
|
|
22
|
-
| { type: "ping" }
|
|
23
|
-
| { type: "view_ready"; viewId: string }
|
|
24
|
-
| { type: "view_closed"; viewId: string }
|
|
25
|
-
| { type: "view_event"; viewId: string; event: string; payload?: unknown };
|
|
26
|
-
|
|
27
|
-
// ── Client → Server (binary frames) ────────────────────────────────────────
|
|
28
|
-
|
|
29
|
-
export interface XRAudioHeader {
|
|
30
|
-
type: "audio";
|
|
31
|
-
ts: number;
|
|
32
|
-
sampleRate: number;
|
|
33
|
-
/** "webm-opus" from MediaRecorder, "pcm-f32" from ScriptProcessor fallback */
|
|
34
|
-
encoding: "webm-opus" | "pcm-f32";
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface XRFrameHeader {
|
|
38
|
-
type: "frame";
|
|
39
|
-
ts: number;
|
|
40
|
-
width: number;
|
|
41
|
-
height: number;
|
|
42
|
-
format: "jpeg" | "webp";
|
|
43
|
-
pose?: {
|
|
44
|
-
position: { x: number; y: number; z: number };
|
|
45
|
-
orientation: { x: number; y: number; z: number; w: number };
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export type XRBinaryHeader = XRAudioHeader | XRFrameHeader;
|
|
50
|
-
|
|
51
|
-
// ── Server → Client (text frames) ──────────────────────────────────────────
|
|
52
|
-
|
|
53
|
-
/** XR panel sizing options */
|
|
54
|
-
export interface XRPanelConfig {
|
|
55
|
-
/** Panel width relative to default (0.5 = half, 2.0 = double) */
|
|
56
|
-
scale?: number;
|
|
57
|
-
/** Follow mode: billboard | fixed | follow */
|
|
58
|
-
followMode?: "billboard" | "fixed" | "follow";
|
|
59
|
-
/** Distance from camera in metres */
|
|
60
|
-
distance?: number;
|
|
61
|
-
/** Whether to show as full-overlay or floating panel */
|
|
62
|
-
fullscreen?: boolean;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export type XRServerControl =
|
|
66
|
-
| { type: "ready"; sessionId: string }
|
|
67
|
-
| { type: "transcript"; text: string; final: boolean }
|
|
68
|
-
| { type: "agent_text"; text: string }
|
|
69
|
-
| { type: "pong" }
|
|
70
|
-
// ── View commands ────────────────────────────────────────────────────────
|
|
71
|
-
/** Open (or bring to front) a view by its registered view id */
|
|
72
|
-
| {
|
|
73
|
-
type: "view_open";
|
|
74
|
-
viewId: string;
|
|
75
|
-
agentBaseUrl: string;
|
|
76
|
-
config?: XRPanelConfig;
|
|
77
|
-
}
|
|
78
|
-
/** Close a specific view panel */
|
|
79
|
-
| { type: "view_close"; viewId: string }
|
|
80
|
-
/** Switch the "active" (foreground) view */
|
|
81
|
-
| { type: "view_switch"; viewId: string }
|
|
82
|
-
/** Resize / reposition the active or named panel */
|
|
83
|
-
| { type: "view_resize"; viewId?: string; config: XRPanelConfig }
|
|
84
|
-
/** Send all available views to the device for the launcher */
|
|
85
|
-
| {
|
|
86
|
-
type: "views_catalog";
|
|
87
|
-
views: Array<{
|
|
88
|
-
id: string;
|
|
89
|
-
label: string;
|
|
90
|
-
icon?: string;
|
|
91
|
-
description?: string;
|
|
92
|
-
}>;
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
// ── Server → Client (binary frames) ────────────────────────────────────────
|
|
96
|
-
|
|
97
|
-
export interface XRTTSAudioHeader {
|
|
98
|
-
type: "tts_audio";
|
|
99
|
-
sampleRate: number;
|
|
100
|
-
channels: number;
|
|
101
|
-
/** encoding of the outbound audio */
|
|
102
|
-
encoding: "mp3" | "wav" | "pcm-f32";
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// ── Framing helpers ─────────────────────────────────────────────────────────
|
|
106
|
-
|
|
107
|
-
export function encodeBinaryFrame(
|
|
108
|
-
header: XRBinaryHeader | XRTTSAudioHeader,
|
|
109
|
-
payload: Uint8Array | Buffer,
|
|
110
|
-
): Buffer {
|
|
111
|
-
const headerJson = Buffer.from(JSON.stringify(header), "utf8");
|
|
112
|
-
const lenBuf = Buffer.allocUnsafe(4);
|
|
113
|
-
lenBuf.writeUInt32BE(headerJson.length, 0);
|
|
114
|
-
return Buffer.concat([lenBuf, headerJson, payload]);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export function decodeBinaryFrame(data: Buffer): {
|
|
118
|
-
header: XRBinaryHeader | XRTTSAudioHeader;
|
|
119
|
-
payload: Buffer;
|
|
120
|
-
} {
|
|
121
|
-
const headerLen = data.readUInt32BE(0);
|
|
122
|
-
const headerJson = data.subarray(4, 4 + headerLen).toString("utf8");
|
|
123
|
-
const header = JSON.parse(headerJson) as XRBinaryHeader | XRTTSAudioHeader;
|
|
124
|
-
const payload = data.subarray(4 + headerLen);
|
|
125
|
-
return { header, payload };
|
|
126
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
IAgentRuntime,
|
|
3
|
-
Memory,
|
|
4
|
-
Provider,
|
|
5
|
-
ProviderResult,
|
|
6
|
-
State,
|
|
7
|
-
} from "@elizaos/core";
|
|
8
|
-
import {
|
|
9
|
-
XR_SERVICE_TYPE,
|
|
10
|
-
type XRSessionService,
|
|
11
|
-
} from "../services/xr-session-service.ts";
|
|
12
|
-
|
|
13
|
-
export const xrContextProvider: Provider = {
|
|
14
|
-
name: "XR_SESSION",
|
|
15
|
-
description: "Provides context about connected XR headsets (Quest 3, XReal)",
|
|
16
|
-
|
|
17
|
-
get: async (
|
|
18
|
-
runtime: IAgentRuntime,
|
|
19
|
-
_message: Memory,
|
|
20
|
-
_state: State,
|
|
21
|
-
): Promise<ProviderResult> => {
|
|
22
|
-
const svc = runtime.getService<XRSessionService>(XR_SERVICE_TYPE);
|
|
23
|
-
if (!svc?.hasActiveConnections()) return { text: "" };
|
|
24
|
-
|
|
25
|
-
const conns = svc.getConnections();
|
|
26
|
-
const deviceList = conns
|
|
27
|
-
.map((c) => {
|
|
28
|
-
const hasFrame = svc.getVisionPipeline().hasRecentFrame(c.id);
|
|
29
|
-
return `${c.deviceType}${hasFrame ? " (camera active)" : ""}`;
|
|
30
|
-
})
|
|
31
|
-
.join(", ");
|
|
32
|
-
|
|
33
|
-
const text = [
|
|
34
|
-
`[XR devices connected: ${deviceList}]`,
|
|
35
|
-
`[Audio streaming active — the user is speaking to you via their headset microphone.]`,
|
|
36
|
-
`[Your text responses will be spoken aloud through the headset via TTS.]`,
|
|
37
|
-
`[Use XR_QUERY_VISION to describe what the user's camera sees.]`,
|
|
38
|
-
].join("\n");
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
text,
|
|
42
|
-
values: {
|
|
43
|
-
xrConnected: true,
|
|
44
|
-
xrDevices: conns.map((c) => c.deviceType),
|
|
45
|
-
xrConnectionCount: conns.length,
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
},
|
|
49
|
-
};
|
package/src/routes/xr-views.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { listViews } from "@elizaos/agent/api/views-registry";
|
|
2
|
-
import type { Route } from "@elizaos/core";
|
|
3
|
-
import {
|
|
4
|
-
XR_SERVICE_TYPE,
|
|
5
|
-
type XRSessionService,
|
|
6
|
-
} from "../services/xr-session-service.ts";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* GET /api/xr/views
|
|
10
|
-
* Returns all XR-typed views from registered plugins.
|
|
11
|
-
* Used by app-xr to populate the view launcher.
|
|
12
|
-
*/
|
|
13
|
-
export const xrViewsRoute: Route = {
|
|
14
|
-
type: "GET",
|
|
15
|
-
path: "/xr/views",
|
|
16
|
-
description: "Lists all XR-capable views from registered plugins",
|
|
17
|
-
routeHandler: async (ctx) => {
|
|
18
|
-
const views = listViews({ developerMode: true, viewType: "xr" })
|
|
19
|
-
.filter((v) => v.viewType === "xr")
|
|
20
|
-
.map((v) => ({
|
|
21
|
-
id: v.id,
|
|
22
|
-
label: v.label,
|
|
23
|
-
icon: v.icon,
|
|
24
|
-
description: v.description,
|
|
25
|
-
tags: v.tags,
|
|
26
|
-
xrOptions: v.xrOptions,
|
|
27
|
-
path: v.path,
|
|
28
|
-
pluginName: v.pluginName,
|
|
29
|
-
available: v.available,
|
|
30
|
-
}));
|
|
31
|
-
|
|
32
|
-
const connections =
|
|
33
|
-
ctx.runtime
|
|
34
|
-
.getService<XRSessionService>(XR_SERVICE_TYPE)
|
|
35
|
-
?.getConnections()
|
|
36
|
-
.map((c) => ({ id: c.id, deviceType: c.deviceType })) ?? [];
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
status: 200,
|
|
40
|
-
body: { views, connections, count: views.length },
|
|
41
|
-
};
|
|
42
|
-
},
|
|
43
|
-
};
|