@@ -0,0 +1,97 @@
|
||||
const assert = require("node:assert/strict");
|
||||
const { EventEmitter } = require("node:events");
|
||||
const test = require("node:test");
|
||||
const {
|
||||
HardwareAgentClient,
|
||||
buildSessionUpdate,
|
||||
websocketSignature,
|
||||
} = require("./hardware-agent-client.cjs");
|
||||
|
||||
test("WebSocket signatures use the documented canonical field order", () => {
|
||||
const connection = websocketSignature({ authType: 1, deviceName: "device-1", randomNumber: 42, productKey: "product-1", timestamp: 1700000000, instanceID: "instance-1" }, "device-secret");
|
||||
assert.equal(connection, "6OFE1DUZzRgNy0o5IzZSmqKw3SUP4s3GxK2YHe8phfo=");
|
||||
});
|
||||
|
||||
test("LLM session update uses the official Config/AgentConfig schema", () => {
|
||||
const update = buildSessionUpdate({ dialog_id: "conversation-1", system_prompt: "你是陪伴助手", persona_tone: "亲切", authorized_context: "只可使用已授权背景" }, "event-1");
|
||||
assert.equal(update.type, "session.update");
|
||||
assert.equal(update.session.object, "realtime.session");
|
||||
assert.equal(update.session.input_audio_format, "pcm16");
|
||||
assert.equal(update.session.output_audio_format, "pcm16");
|
||||
assert.equal(update.session.config.ASRConfig.TurnDetectionMode, 0);
|
||||
assert.match(update.session.config.LLMConfig.SystemMessages[0], /只可使用已授权背景/);
|
||||
assert.match(update.session.config.LLMConfig.SystemMessages[0], /你是陪伴助手/);
|
||||
assert.match(update.session.config.LLMConfig.SystemMessages[0], /亲切/);
|
||||
assert.equal(update.session.agent_config.EnableConversationStateCallback, false);
|
||||
assert.equal("system_prompt" in update.session, false);
|
||||
});
|
||||
|
||||
test("S2S session update maps persona and dialog id to provider dialog config", () => {
|
||||
const update = buildSessionUpdate({ dialog_id: "conversation-2", system_prompt: "你是陪伴助手", persona_tone: "亲切自然", authorized_context: "只使用授权信息" }, "event-2", "s2s");
|
||||
const dialog = update.session.config.S2SConfig.ProviderParams.dialog;
|
||||
assert.equal(dialog.dialog_id, "conversation-2");
|
||||
assert.equal(dialog.speaking_style, "亲切自然");
|
||||
assert.match(dialog.system_role, /只使用授权信息/);
|
||||
assert.match(dialog.system_role, /你是陪伴助手/);
|
||||
assert.equal("LLMConfig" in update.session.config, false);
|
||||
});
|
||||
|
||||
class FakeWebSocket extends EventEmitter {
|
||||
static OPEN = 1;
|
||||
constructor(url, options) {
|
||||
super();
|
||||
this.url = url;
|
||||
this.options = options;
|
||||
this.readyState = 0;
|
||||
this.sent = [];
|
||||
FakeWebSocket.last = this;
|
||||
setImmediate(() => {
|
||||
this.readyState = FakeWebSocket.OPEN;
|
||||
this.emit("open");
|
||||
this.emit("message", Buffer.from(JSON.stringify({ type: "session.created", event_id: "created-event", session: { id: "provider-session-1" } })));
|
||||
});
|
||||
}
|
||||
send(body) {
|
||||
const value = JSON.parse(body);
|
||||
this.sent.push(value);
|
||||
if (value.type === "session.update") {
|
||||
setImmediate(() => this.emit("message", Buffer.from(JSON.stringify({ type: "session.updated", event_id: "updated-event", session: { id: "provider-session-1" } }))));
|
||||
}
|
||||
}
|
||||
ping() {}
|
||||
close(code, reason) {
|
||||
this.readyState = 3;
|
||||
this.emit("close", code, Buffer.from(reason));
|
||||
}
|
||||
}
|
||||
|
||||
test("hardware client waits for dynamic config acknowledgement before becoming ready", async () => {
|
||||
const events = [];
|
||||
const client = new HardwareAgentClient({ WebSocketImpl: FakeWebSocket, gatewayURL: "ws://127.0.0.1:19090/v1/realtime", emit: (event) => events.push(event) });
|
||||
const connected = await client.connect({
|
||||
instanceID: "instance-1", productKey: "product-1", deviceName: "device-1",
|
||||
deviceSecret: "device-secret-with-at-least-32-characters", hardwareID: "windows-test", botID: "bot-1",
|
||||
sessionConfig: { dialog_id: "conversation-1", authorized_context: "authorized" },
|
||||
});
|
||||
assert.equal(connected.providerConnectionRef, "provider-session-1");
|
||||
assert.match(FakeWebSocket.last.url, /bot=bot-1/);
|
||||
assert.equal(FakeWebSocket.last.options.headers["X-Product-Key"], "product-1");
|
||||
assert.equal(FakeWebSocket.last.sent[0].type, "session.update");
|
||||
client.sendAudio(Buffer.from([1, 2, 3, 4]));
|
||||
client.sendText("你好");
|
||||
assert.equal(FakeWebSocket.last.sent[1].type, "input_audio_buffer.append");
|
||||
assert.equal(FakeWebSocket.last.sent[2].type, "conversation.item.create");
|
||||
assert.ok(events.some((event) => event.type === "session.created"));
|
||||
client.close();
|
||||
});
|
||||
|
||||
test("hardware client sends session.update immediately when the WebSocket opens", async () => {
|
||||
const client = new HardwareAgentClient({ WebSocketImpl: FakeWebSocket, gatewayURL: "ws://127.0.0.1:19090/v1/realtime" });
|
||||
await client.connect({
|
||||
instanceID: "instance-1", productKey: "product-1", deviceName: "device-1",
|
||||
deviceSecret: "device-secret-with-at-least-32-characters", hardwareID: "windows-test", botID: "bot-1",
|
||||
sessionConfig: { dialog_id: "conversation-1", authorized_context: "authorized" },
|
||||
});
|
||||
assert.equal(FakeWebSocket.last.sent.filter((event) => event.type === "session.update").length, 1);
|
||||
client.close();
|
||||
});
|
||||
Reference in New Issue
Block a user