@perkos/perkos-a2a 0.12.59 → 0.12.60
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/README.md +6 -0
- package/dist/bridge-agent.js +8 -0
- package/dist/bridge-agent.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +146 -274
- package/dist/index.js.map +4 -4
- package/dist/voice-capability.d.ts +19 -0
- package/dist/voice-capability.d.ts.map +1 -0
- package/dist/voice-capability.js +78 -0
- package/dist/voice-capability.js.map +1 -0
- package/dist/voice-capability.test.d.ts +2 -0
- package/dist/voice-capability.test.d.ts.map +1 -0
- package/dist/voice-capability.test.js +32 -0
- package/dist/voice-capability.test.js.map +1 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { A2APluginConfig } from "./types.js";
|
|
2
|
+
export declare const PERKOS_VOICE_PROBE_MARKER: "PERKOS_VOICE_PROBE";
|
|
3
|
+
export declare const PERKOS_VOICE_ENROLL_MARKER: "PERKOS_VOICE_ENROLL";
|
|
4
|
+
type VoiceModule = {
|
|
5
|
+
handleA2AVoiceMarker: (message: string, options: Record<string, unknown>) => Promise<string | null>;
|
|
6
|
+
configureA2AVoiceEnrollment: (enrollment: unknown) => Promise<{
|
|
7
|
+
ready: boolean;
|
|
8
|
+
actionCode?: string;
|
|
9
|
+
}>;
|
|
10
|
+
};
|
|
11
|
+
type VoiceModuleLoader = () => Promise<VoiceModule>;
|
|
12
|
+
/**
|
|
13
|
+
* Optional bridge: exact Voice markers are consumed before the model sees
|
|
14
|
+
* them. A2A supplies identity/transport only; the separately installed Voice
|
|
15
|
+
* package owns support detection, credential storage and runtime activation.
|
|
16
|
+
*/
|
|
17
|
+
export declare function tryHandleInstalledVoiceMarker(message: string, config: A2APluginConfig, loadVoice?: VoiceModuleLoader): Promise<string | null>;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=voice-capability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voice-capability.d.ts","sourceRoot":"","sources":["../src/voice-capability.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAe,MAAM,YAAY,CAAC;AAE/D,eAAO,MAAM,yBAAyB,EAAG,oBAA6B,CAAC;AACvE,eAAO,MAAM,0BAA0B,EAAG,qBAA8B,CAAC;AAEzE,KAAK,WAAW,GAAG;IACjB,oBAAoB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACpG,2BAA2B,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxG,CAAC;AAEF,KAAK,iBAAiB,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;AAsCpD;;;;GAIG;AACH,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,eAAe,EACvB,SAAS,GAAE,iBAAiC,GAC3C,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiBxB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export const PERKOS_VOICE_PROBE_MARKER = "PERKOS_VOICE_PROBE";
|
|
2
|
+
export const PERKOS_VOICE_ENROLL_MARKER = "PERKOS_VOICE_ENROLL";
|
|
3
|
+
function runtime(runtimeKind) {
|
|
4
|
+
if (runtimeKind === "hermes" || runtimeKind === "hermes-api")
|
|
5
|
+
return "hermes";
|
|
6
|
+
if (runtimeKind === "openclaw")
|
|
7
|
+
return "openclaw";
|
|
8
|
+
if (runtimeKind === "zeroclaw")
|
|
9
|
+
return "zeroclaw";
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
function controlPlane(config) {
|
|
13
|
+
const heartbeat = config.platform?.heartbeatUrl;
|
|
14
|
+
const relayApiKey = config.relay?.apiKey || config.chat?.apiKey;
|
|
15
|
+
if (!heartbeat || !relayApiKey)
|
|
16
|
+
return null;
|
|
17
|
+
let url;
|
|
18
|
+
try {
|
|
19
|
+
url = new URL(heartbeat);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const match = url.pathname.match(/^(.*)\/agents\/([^/]+)\/heartbeat\/?$/u);
|
|
25
|
+
const agentId = config.platform?.agentId || (match ? decodeURIComponent(match[2]) : "");
|
|
26
|
+
if (!match || !agentId)
|
|
27
|
+
return null;
|
|
28
|
+
return { apiBaseUrl: `${url.origin}${match[1]}`, agentId, relayApiKey };
|
|
29
|
+
}
|
|
30
|
+
async function reportPluginMissing(message, config) {
|
|
31
|
+
if (message !== PERKOS_VOICE_PROBE_MARKER)
|
|
32
|
+
return "PERKOS_VOICE_FAILED:voice_plugin_missing";
|
|
33
|
+
const target = controlPlane(config);
|
|
34
|
+
if (!target)
|
|
35
|
+
return "PERKOS_VOICE_FAILED:a2a_identity_unavailable";
|
|
36
|
+
const response = await fetch(`${target.apiBaseUrl}/agents/${encodeURIComponent(target.agentId)}/voice-credential/capability-report`, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: { authorization: `Bearer ${target.relayApiKey}`, "content-type": "application/json" },
|
|
39
|
+
body: JSON.stringify({ state: "unsupported", reasonCode: "voice_plugin_missing" }),
|
|
40
|
+
}).catch(() => null);
|
|
41
|
+
return response?.ok ? "PERKOS_VOICE_UNSUPPORTED:voice_plugin_missing" : "PERKOS_VOICE_FAILED:control_plane_unavailable";
|
|
42
|
+
}
|
|
43
|
+
async function defaultLoader() {
|
|
44
|
+
const specifier = "@perkos/perkos-voice/a2a";
|
|
45
|
+
return import(specifier);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Optional bridge: exact Voice markers are consumed before the model sees
|
|
49
|
+
* them. A2A supplies identity/transport only; the separately installed Voice
|
|
50
|
+
* package owns support detection, credential storage and runtime activation.
|
|
51
|
+
*/
|
|
52
|
+
export async function tryHandleInstalledVoiceMarker(message, config, loadVoice = defaultLoader) {
|
|
53
|
+
const marker = message.trim();
|
|
54
|
+
if (marker !== PERKOS_VOICE_PROBE_MARKER && marker !== PERKOS_VOICE_ENROLL_MARKER)
|
|
55
|
+
return null;
|
|
56
|
+
const target = controlPlane(config);
|
|
57
|
+
if (!target)
|
|
58
|
+
return "PERKOS_VOICE_FAILED:a2a_identity_unavailable";
|
|
59
|
+
const runtimeKind = runtime(config.runtime?.kind);
|
|
60
|
+
if (!runtimeKind)
|
|
61
|
+
return reportPluginMissing(marker, config);
|
|
62
|
+
let voice;
|
|
63
|
+
try {
|
|
64
|
+
voice = await loadVoice();
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return reportPluginMissing(marker, config);
|
|
68
|
+
}
|
|
69
|
+
if (typeof voice.handleA2AVoiceMarker !== "function" || typeof voice.configureA2AVoiceEnrollment !== "function") {
|
|
70
|
+
return reportPluginMissing(marker, config);
|
|
71
|
+
}
|
|
72
|
+
return voice.handleA2AVoiceMarker(marker, {
|
|
73
|
+
...target,
|
|
74
|
+
runtime: runtimeKind,
|
|
75
|
+
configure: voice.configureA2AVoiceEnrollment,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=voice-capability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voice-capability.js","sourceRoot":"","sources":["../src/voice-capability.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voice-capability.test.d.ts","sourceRoot":"","sources":["../src/voice-capability.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { tryHandleInstalledVoiceMarker } from "./voice-capability.js";
|
|
3
|
+
const config = {
|
|
4
|
+
agentName: "Athena", port: 5060, skills: [], peers: {},
|
|
5
|
+
runtime: { kind: "hermes-api" },
|
|
6
|
+
relay: { url: "wss://transport.perkos.xyz/a2a", apiKey: "rk_private", enabled: true },
|
|
7
|
+
platform: { agentId: "athena-id", heartbeatUrl: "https://dev.api.perkos.xyz/agents/athena-id/heartbeat" },
|
|
8
|
+
};
|
|
9
|
+
describe("optional Voice capability bridge", () => {
|
|
10
|
+
it("delegates exact Voice markers with the existing scoped identity", async () => {
|
|
11
|
+
let options;
|
|
12
|
+
const result = await tryHandleInstalledVoiceMarker("PERKOS_VOICE_ENROLL", config, async () => ({
|
|
13
|
+
handleA2AVoiceMarker: async (_message, value) => { options = value; return "PERKOS_VOICE_READY"; },
|
|
14
|
+
configureA2AVoiceEnrollment: async () => ({ ready: true }),
|
|
15
|
+
}));
|
|
16
|
+
expect(result).toBe("PERKOS_VOICE_READY");
|
|
17
|
+
expect(options?.apiBaseUrl).toBe("https://dev.api.perkos.xyz");
|
|
18
|
+
expect(options?.agentId).toBe("athena-id");
|
|
19
|
+
expect(options?.relayApiKey).toBe("rk_private");
|
|
20
|
+
expect(options?.runtime).toBe("hermes");
|
|
21
|
+
});
|
|
22
|
+
it("ignores natural language and keeps Voice parallel", async () => {
|
|
23
|
+
let loaded = false;
|
|
24
|
+
const result = await tryHandleInstalledVoiceMarker("please enable calls", config, async () => {
|
|
25
|
+
loaded = true;
|
|
26
|
+
throw new Error("not used");
|
|
27
|
+
});
|
|
28
|
+
expect(result).toBeNull();
|
|
29
|
+
expect(loaded).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=voice-capability.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voice-capability.test.js","sourceRoot":"","sources":["../src/voice-capability.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,6BAA6B,EAAE,MAAM,uBAAuB,CAAC;AAGtE,MAAM,MAAM,GAAoB;IAC9B,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE;IACtD,OAAO,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE;IAC/B,KAAK,EAAE,EAAE,GAAG,EAAE,gCAAgC,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE;IACrF,QAAQ,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,uDAAuD,EAAE;CAC1G,CAAC;AAEF,QAAQ,CAAC,kCAAkC,EAAE,GAAG,EAAE;IAClD,EAAE,CAAC,iEAAiE,EAAE,KAAK,IAAI,EAAE;QAC/E,IAAI,OAA4C,CAAC;QACjD,MAAM,MAAM,GAAG,MAAM,6BAA6B,CAAC,qBAAqB,EAAE,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;YAC7F,oBAAoB,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,GAAG,OAAO,GAAG,KAAK,CAAC,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC;YAClG,2BAA2B,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;SAC3D,CAAC,CAAC,CAAC;QACJ,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAC1C,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;QAC/D,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC3C,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAChD,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACjE,IAAI,MAAM,GAAG,KAAK,CAAC;QACnB,MAAM,MAAM,GAAG,MAAM,6BAA6B,CAAC,qBAAqB,EAAE,MAAM,EAAE,KAAK,IAAI,EAAE;YAC3F,MAAM,GAAG,IAAI,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC1B,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;AACH,CAAC,CAAC,CAAC"}
|
package/openclaw.plugin.json
CHANGED