@voicethere/agent 0.4.0 → 0.5.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/README.md +3 -1
- package/dist/agent.js +84 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/protocol.d.ts +33 -4
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +3 -2
- package/dist/protocol.js.map +1 -1
- package/dist/runtime.d.ts +18 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +94 -2
- package/dist/runtime.js.map +1 -1
- package/dist/templates/echo/agent.js +90 -2
- package/dist/templates/echo-dc/agent.js +90 -2
- package/dist/templates/game-sync/agent.js +90 -2
- package/dist/templates/registry.d.ts.map +1 -1
- package/dist/templates/registry.js +16 -0
- package/dist/templates/registry.js.map +1 -1
- package/dist/templates/voice-starter/agent.js +90 -2
- package/dist/templates/webhooks/agent.js +677 -0
- package/dist/templates/webhooks-redis/agent.js +11449 -0
- package/dist/verify/lib.d.ts +1 -1
- package/dist/verify/lib.d.ts.map +1 -1
- package/dist/verify/lib.js +2 -0
- package/dist/verify/lib.js.map +1 -1
- package/dist/verify/run-verify.d.ts.map +1 -1
- package/dist/verify/run-verify.js +2 -3
- package/dist/verify/run-verify.js.map +1 -1
- package/package.json +1 -1
- package/templates/README.md +9 -1
- package/templates/webhooks-redis.ts +135 -0
- package/templates/webhooks.ts +114 -0
package/dist/verify/lib.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ChildToParentMessage } from "../protocol.js";
|
|
|
2
2
|
export declare const VERIFY_SESSION_ID = "local-verify";
|
|
3
3
|
export declare const DEFAULT_VERIFY_BUNDLE = "dist/agent.js";
|
|
4
4
|
export declare const DEFAULT_VERIFY_ENTRY = "agent.ts";
|
|
5
|
-
export declare const VERIFY_CALLBACK_KEYS: readonly ["onSpeechEvent", "onUserSpeechFinal", "onDataChannelMessage", "onDataChannelBinary"];
|
|
5
|
+
export declare const VERIFY_CALLBACK_KEYS: readonly ["onSpeechEvent", "onUserSpeechFinal", "onDataChannelMessage", "onDataChannelBinary", "onWebhook", "onSessionStart"];
|
|
6
6
|
export type VerifyCallbackKey = (typeof VERIFY_CALLBACK_KEYS)[number];
|
|
7
7
|
export declare function parseBundleArg(argv: string[], env?: NodeJS.ProcessEnv, cwd?: string): string;
|
|
8
8
|
export declare function waitForSpeak(messages: ChildToParentMessage[], sessionId: string, timeoutMs: number): Promise<Extract<ChildToParentMessage, {
|
package/dist/verify/lib.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../../src/verify/lib.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE3D,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAChD,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AACrD,eAAO,MAAM,oBAAoB,aAAa,CAAC;AAC/C,eAAO,MAAM,oBAAoB,
|
|
1
|
+
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../../src/verify/lib.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE3D,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAChD,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AACrD,eAAO,MAAM,oBAAoB,aAAa,CAAC;AAC/C,eAAO,MAAM,oBAAoB,+HAOvB,CAAC;AACX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,GAAG,GAAE,MAAsB,GAC1B,MAAM,CAWR;AAED,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,oBAAoB,EAAE,EAChC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,oBAAoB,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC,CAuB3D;AAED,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,oBAAoB,EAAE,EAChC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,oBAAoB,EAAE;IAAE,IAAI,EAAE,mBAAmB,CAAA;CAAE,CAAC,CAAC,CAyBvE;AAED,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,MAAM,GACnB,iBAAiB,EAAE,CAqBrB;AAED,wBAAgB,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAExE"}
|
package/dist/verify/lib.js
CHANGED
|
@@ -7,6 +7,8 @@ export const VERIFY_CALLBACK_KEYS = [
|
|
|
7
7
|
"onUserSpeechFinal",
|
|
8
8
|
"onDataChannelMessage",
|
|
9
9
|
"onDataChannelBinary",
|
|
10
|
+
"onWebhook",
|
|
11
|
+
"onSessionStart",
|
|
10
12
|
];
|
|
11
13
|
export function parseBundleArg(argv, env = process.env, cwd = process.cwd()) {
|
|
12
14
|
for (let i = 0; i < argv.length; i += 1) {
|
package/dist/verify/lib.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../../src/verify/lib.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,MAAM,CAAC,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAChD,MAAM,CAAC,MAAM,qBAAqB,GAAG,eAAe,CAAC;AACrD,MAAM,CAAC,MAAM,oBAAoB,GAAG,UAAU,CAAC;AAC/C,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,eAAe;IACf,mBAAmB;IACnB,sBAAsB;IACtB,qBAAqB;
|
|
1
|
+
{"version":3,"file":"lib.js","sourceRoot":"","sources":["../../src/verify/lib.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-verify.d.ts","sourceRoot":"","sources":["../../src/verify/run-verify.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;CACpB;AAiBD,wBAAsB,cAAc,CAClC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,
|
|
1
|
+
{"version":3,"file":"run-verify.d.ts","sourceRoot":"","sources":["../../src/verify/run-verify.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;CACpB;AAiBD,wBAAsB,cAAc,CAClC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CA+F5B;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAQrE"}
|
|
@@ -18,8 +18,7 @@ export async function runAgentVerify(options = {}) {
|
|
|
18
18
|
const quiet = options.quiet;
|
|
19
19
|
const entry = options.entry ?? "agent.ts";
|
|
20
20
|
const outfile = options.outfile ?? "dist/agent.js";
|
|
21
|
-
const bundlePath = options.bundlePath ??
|
|
22
|
-
parseBundleArg([], process.env, cwd);
|
|
21
|
+
const bundlePath = options.bundlePath ?? parseBundleArg([], process.env, cwd);
|
|
23
22
|
logLine(quiet, "[@voicethere/agent verify]");
|
|
24
23
|
const nodeMajor = Number(process.versions.node.split(".")[0]);
|
|
25
24
|
if (!Number.isFinite(nodeMajor) || nodeMajor < MIN_NODE_MAJOR) {
|
|
@@ -76,7 +75,7 @@ export async function runAgentVerify(options = {}) {
|
|
|
76
75
|
logLine(quiet, "✓ Agent registration (defineAgent)");
|
|
77
76
|
const callbacks = detectVerifyCallbacks(bundleSource);
|
|
78
77
|
if (callbacks.length === 0) {
|
|
79
|
-
return failCheck(checks, "Bundle callbacks", "Expected at least one handler: onSpeechEvent, onUserSpeechFinal, onDataChannelMessage, or
|
|
78
|
+
return failCheck(checks, "Bundle callbacks", "Expected at least one handler: onSpeechEvent, onUserSpeechFinal, onDataChannelMessage, onDataChannelBinary, onWebhook, or onSessionStart");
|
|
80
79
|
}
|
|
81
80
|
checks.push({
|
|
82
81
|
name: "Bundle callbacks",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-verify.js","sourceRoot":"","sources":["../../src/verify/run-verify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EACL,qBAAqB,EACrB,0BAA0B,EAC1B,cAAc,GACf,MAAM,UAAU,CAAC;AAElB,MAAM,cAAc,GAAG,EAAE,CAAC;AAyB1B,SAAS,OAAO,CAAC,KAA0B,EAAE,OAAe;IAC1D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;IACvC,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAChB,MAA2B,EAC3B,IAAY,EACZ,MAAc;IAEd,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IACzC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,UAA8B,EAAE;IAEhC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACzC,MAAM,MAAM,GAAwB,EAAE,CAAC;IACvC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC;IAC1C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,eAAe,CAAC;IACnD,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"run-verify.js","sourceRoot":"","sources":["../../src/verify/run-verify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EACL,qBAAqB,EACrB,0BAA0B,EAC1B,cAAc,GACf,MAAM,UAAU,CAAC;AAElB,MAAM,cAAc,GAAG,EAAE,CAAC;AAyB1B,SAAS,OAAO,CAAC,KAA0B,EAAE,OAAe;IAC1D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;IACvC,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAChB,MAA2B,EAC3B,IAAY,EACZ,MAAc;IAEd,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IACzC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;AAC/C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,UAA8B,EAAE;IAEhC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACzC,MAAM,MAAM,GAAwB,EAAE,CAAC;IACvC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC;IAC1C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,eAAe,CAAC;IACnD,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,cAAc,CAAC,EAAE,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAE9E,OAAO,CAAC,KAAK,EAAE,4BAA4B,CAAC,CAAC;IAE7C,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,cAAc,EAAE,CAAC;QAC9D,OAAO,SAAS,CACd,MAAM,EACN,iBAAiB,EACjB,QAAQ,cAAc,qBAAqB,OAAO,CAAC,OAAO,EAAE,CAC7D,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,IAAI,CAAC;QACV,IAAI,EAAE,iBAAiB;QACvB,EAAE,EAAE,IAAI;QACR,MAAM,EAAE,OAAO,CAAC,OAAO;KACxB,CAAC,CAAC;IACH,OAAO,CAAC,KAAK,EAAE,aAAa,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAE/C,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACrB,IAAI,CAAC;YACH,MAAM,gBAAgB,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;YAChD,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,cAAc;gBACpB,EAAE,EAAE,IAAI;gBACR,MAAM,EAAE,GAAG,OAAO,SAAS,KAAK,EAAE;aACnC,CAAC,CAAC;YACH,OAAO,CAAC,KAAK,EAAE,WAAW,OAAO,SAAS,KAAK,EAAE,CAAC,CAAC;QACrD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACtE,OAAO,SAAS,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,cAAc;YACpB,EAAE,EAAE,IAAI;YACR,MAAM,EAAE,sBAAsB;SAC/B,CAAC,CAAC;QACH,OAAO,CAAC,KAAK,EAAE,0BAA0B,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IAChD,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QAChC,OAAO,SAAS,CACd,MAAM,EACN,gBAAgB,EAChB,qBAAqB,cAAc,EAAE,CACtC,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,IAAI,CAAC;QACV,IAAI,EAAE,gBAAgB;QACtB,EAAE,EAAE,IAAI;QACR,MAAM,EAAE,cAAc;KACvB,CAAC,CAAC;IACH,OAAO,CAAC,KAAK,EAAE,qBAAqB,cAAc,GAAG,CAAC,CAAC;IAEvD,MAAM,YAAY,GAAG,YAAY,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IAC1D,IAAI,CAAC,0BAA0B,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9C,OAAO,SAAS,CACd,MAAM,EACN,oBAAoB,EACpB,qDAAqD,CACtD,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,IAAI,CAAC;QACV,IAAI,EAAE,oBAAoB;QAC1B,EAAE,EAAE,IAAI;QACR,MAAM,EAAE,2BAA2B;KACpC,CAAC,CAAC;IACH,OAAO,CAAC,KAAK,EAAE,oCAAoC,CAAC,CAAC;IAErD,MAAM,SAAS,GAAG,qBAAqB,CAAC,YAAY,CAAC,CAAC;IACtD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,SAAS,CACd,MAAM,EACN,kBAAkB,EAClB,0IAA0I,CAC3I,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,IAAI,CAAC;QACV,IAAI,EAAE,kBAAkB;QACxB,EAAE,EAAE,IAAI;QACR,MAAM,EAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;KAC7B,CAAC,CAAC;IACH,OAAO,CAAC,KAAK,EAAE,uBAAuB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAE/D,OAAO,CAAC,KAAK,EAAE,+CAA+C,CAAC,CAAC;IAEhE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAAyB;IAC3D,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC1D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,eAAe,CAAC;IACzB,CAAC;IACD,OAAO,MAAM;SACV,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;SAC5D,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC"}
|
package/package.json
CHANGED
package/templates/README.md
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
|
|
18
18
|
| Kind | Dashboard create | Prebuilt seed bundle | Typical consumer |
|
|
19
19
|
| --- | --- | --- | --- |
|
|
20
|
-
| **product** | Yes (`echo`, `echo-dc`, `voice-starter`, `game-sync`) | Yes — `dist/templates/<id>/agent.js` | Platform project create |
|
|
20
|
+
| **product** | Yes (`echo`, `echo-dc`, `voice-starter`, `game-sync`, `webhooks`, `webhooks-redis`) | Yes — `dist/templates/<id>/agent.js` | Platform project create |
|
|
21
21
|
| **e2e** | No | No — build from sources at test time | `voicethere/e2e` smokes |
|
|
22
22
|
|
|
23
23
|
Product templates always set `seedOnCreate: true`. CI fails if a product template is missing its prebuilt bundle after `npm run build`.
|
|
@@ -59,6 +59,14 @@ Full starter bundle covering every speech event from `@node-webrtc-rust/sdk/voic
|
|
|
59
59
|
|
|
60
60
|
Authoritative multi-object sync sample for real-time games/simulations (register, simulate, binary world snapshots).
|
|
61
61
|
|
|
62
|
+
### `webhooks.ts` (`webhooks`)
|
|
63
|
+
|
|
64
|
+
Inbound HTTP webhook sample — verifies `x-agent-webhook-signature` HMAC on the **raw body** with `AGENT_WEBHOOK_SIGNING_SECRET`, then `JSON.parse` and fans out to connected sessions via DataChannel + `speak`.
|
|
65
|
+
|
|
66
|
+
### `webhooks-redis.ts` (`webhooks-redis`)
|
|
67
|
+
|
|
68
|
+
Same HMAC verify path plus an atomic Redis counter (`AGENT_REDIS_URL`) before DataChannel fan-out. Fan-out to sessions does not require Redis; Redis is for shared cross-pod state.
|
|
69
|
+
|
|
62
70
|
## E2e templates
|
|
63
71
|
|
|
64
72
|
These mirror former `e2e/fixtures/*` sources. E2E resolves entries from the package, builds into ephemeral workdirs, and uploads `dist/agent.js`.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inbound webhook + shared Redis counter — verify HMAC first, atomic Redis update, DC fan-out.
|
|
3
|
+
*
|
|
4
|
+
* Fan-out to connected sessions does not require Redis; Redis is for shared state across
|
|
5
|
+
* runner pods (every child in the process still receives the webhook IPC).
|
|
6
|
+
*
|
|
7
|
+
* Verify `x-agent-webhook-signature` on the raw body with `AGENT_WEBHOOK_SIGNING_SECRET`
|
|
8
|
+
* before `JSON.parse`. Atomic counter uses Redis Lua (read-modify-write in one round trip).
|
|
9
|
+
*
|
|
10
|
+
* Build:
|
|
11
|
+
* npx @voicethere/agent build --entry templates/webhooks-redis.ts --outfile dist/agent.js
|
|
12
|
+
*/
|
|
13
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
14
|
+
import Redis from "ioredis";
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
agentLog,
|
|
18
|
+
broadcastToClients,
|
|
19
|
+
defineAgent,
|
|
20
|
+
} from "@voicethere/agent";
|
|
21
|
+
|
|
22
|
+
const WEBHOOK_SIGNATURE_HEADER = "x-agent-webhook-signature";
|
|
23
|
+
const REDIS_COUNTER_KEY = "agent:webhook:event_count";
|
|
24
|
+
|
|
25
|
+
/** Atomic increment — safe under concurrent webhook delivery on one pod. */
|
|
26
|
+
const LUA_INCREMENT_COUNTER = `
|
|
27
|
+
local key = KEYS[1]
|
|
28
|
+
local n = redis.call('INCR', key)
|
|
29
|
+
return n
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
const connectedSessions = new Set<string>();
|
|
33
|
+
let redis: Redis | null = null;
|
|
34
|
+
|
|
35
|
+
function verifyWebhookSignature(
|
|
36
|
+
body: Buffer,
|
|
37
|
+
headers: Record<string, string>,
|
|
38
|
+
secret: string,
|
|
39
|
+
): boolean {
|
|
40
|
+
const received =
|
|
41
|
+
headers[WEBHOOK_SIGNATURE_HEADER] ??
|
|
42
|
+
headers[WEBHOOK_SIGNATURE_HEADER.toUpperCase()];
|
|
43
|
+
if (!received?.trim()) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const expected = createHmac("sha256", secret).update(body).digest("hex");
|
|
47
|
+
try {
|
|
48
|
+
const actual = Buffer.from(received.trim(), "hex");
|
|
49
|
+
const want = Buffer.from(expected, "hex");
|
|
50
|
+
return actual.length === want.length && timingSafeEqual(actual, want);
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
defineAgent({
|
|
57
|
+
async onAgentStart({ env }) {
|
|
58
|
+
const redisUrl = env.AGENT_REDIS_URL ?? process.env.AGENT_REDIS_URL;
|
|
59
|
+
if (!redisUrl?.trim()) {
|
|
60
|
+
agentLog(
|
|
61
|
+
"warn",
|
|
62
|
+
"AGENT_REDIS_URL unset — webhooks-redis uses in-memory sessions only",
|
|
63
|
+
);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
redis = new Redis(redisUrl, {
|
|
68
|
+
maxRetriesPerRequest: 3,
|
|
69
|
+
lazyConnect: true,
|
|
70
|
+
});
|
|
71
|
+
await redis.connect();
|
|
72
|
+
agentLog("info", "webhooks-redis connected to project Redis");
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
onSessionStart({ sessionId }) {
|
|
76
|
+
connectedSessions.add(sessionId);
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
onSessionEnd({ sessionId }) {
|
|
80
|
+
connectedSessions.delete(sessionId);
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
async onWebhook(ctx) {
|
|
84
|
+
const secret = process.env.AGENT_WEBHOOK_SIGNING_SECRET?.trim();
|
|
85
|
+
if (!secret) {
|
|
86
|
+
agentLog("warn", "webhook ignored: AGENT_WEBHOOK_SIGNING_SECRET unset");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Verify on raw bytes before JSON.parse.
|
|
91
|
+
if (!verifyWebhookSignature(ctx.body, ctx.headers, secret)) {
|
|
92
|
+
agentLog("warn", `webhook signature invalid eventId=${ctx.eventId}`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let payload: unknown;
|
|
97
|
+
try {
|
|
98
|
+
payload = JSON.parse(ctx.body.toString("utf8"));
|
|
99
|
+
} catch {
|
|
100
|
+
agentLog("warn", `webhook body is not JSON eventId=${ctx.eventId}`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let eventCount: number | null = null;
|
|
105
|
+
if (redis) {
|
|
106
|
+
const result = await redis.eval(LUA_INCREMENT_COUNTER, 1, REDIS_COUNTER_KEY);
|
|
107
|
+
eventCount = typeof result === "number" ? result : Number(result);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const sessionIds = [...connectedSessions];
|
|
111
|
+
if (sessionIds.length === 0) {
|
|
112
|
+
agentLog(
|
|
113
|
+
"info",
|
|
114
|
+
`webhook verified (count=${eventCount ?? "n/a"}) with no live sessions`,
|
|
115
|
+
);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
broadcastToClients(
|
|
120
|
+
{
|
|
121
|
+
type: "webhook_event",
|
|
122
|
+
eventId: ctx.eventId,
|
|
123
|
+
path: ctx.path,
|
|
124
|
+
eventCount,
|
|
125
|
+
payload,
|
|
126
|
+
},
|
|
127
|
+
sessionIds,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
agentLog(
|
|
131
|
+
"info",
|
|
132
|
+
`webhook fan-out to ${sessionIds.length} session(s) eventId=${ctx.eventId}`,
|
|
133
|
+
);
|
|
134
|
+
},
|
|
135
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inbound webhook handler — verify HMAC on raw bytes, then parse JSON and fan out.
|
|
3
|
+
*
|
|
4
|
+
* VoiceThere forwards the exact inbound body over IPC. Verify the custom
|
|
5
|
+
* `x-agent-webhook-signature` header (hex HMAC-SHA256 of the raw body) using
|
|
6
|
+
* `AGENT_WEBHOOK_SIGNING_SECRET` **before** `JSON.parse`.
|
|
7
|
+
*
|
|
8
|
+
* Build:
|
|
9
|
+
* npx @voicethere/agent build --entry templates/webhooks.ts --outfile dist/agent.js
|
|
10
|
+
*/
|
|
11
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
agentLog,
|
|
15
|
+
broadcastToClients,
|
|
16
|
+
defineAgent,
|
|
17
|
+
speak,
|
|
18
|
+
} from "@voicethere/agent";
|
|
19
|
+
|
|
20
|
+
/** Custom header carrying hex HMAC-SHA256 of the raw webhook body. */
|
|
21
|
+
const WEBHOOK_SIGNATURE_HEADER = "x-agent-webhook-signature";
|
|
22
|
+
|
|
23
|
+
const connectedSessions = new Set<string>();
|
|
24
|
+
|
|
25
|
+
function verifyWebhookSignature(
|
|
26
|
+
body: Buffer,
|
|
27
|
+
headers: Record<string, string>,
|
|
28
|
+
secret: string,
|
|
29
|
+
): boolean {
|
|
30
|
+
const received =
|
|
31
|
+
headers[WEBHOOK_SIGNATURE_HEADER] ??
|
|
32
|
+
headers[WEBHOOK_SIGNATURE_HEADER.toUpperCase()];
|
|
33
|
+
if (!received?.trim()) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const expected = createHmac("sha256", secret).update(body).digest("hex");
|
|
37
|
+
try {
|
|
38
|
+
const actual = Buffer.from(received.trim(), "hex");
|
|
39
|
+
const want = Buffer.from(expected, "hex");
|
|
40
|
+
return actual.length === want.length && timingSafeEqual(actual, want);
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
defineAgent({
|
|
47
|
+
onSessionStart({ sessionId }) {
|
|
48
|
+
connectedSessions.add(sessionId);
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
onSessionEnd({ sessionId }) {
|
|
52
|
+
connectedSessions.delete(sessionId);
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
async onWebhook(ctx) {
|
|
56
|
+
const secret = process.env.AGENT_WEBHOOK_SIGNING_SECRET?.trim();
|
|
57
|
+
if (!secret) {
|
|
58
|
+
agentLog("warn", "webhook ignored: AGENT_WEBHOOK_SIGNING_SECRET unset");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Verify on raw bytes before JSON.parse — never parse untrusted payloads first.
|
|
63
|
+
if (!verifyWebhookSignature(ctx.body, ctx.headers, secret)) {
|
|
64
|
+
agentLog("warn", `webhook signature invalid eventId=${ctx.eventId}`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let payload: unknown;
|
|
69
|
+
try {
|
|
70
|
+
payload = JSON.parse(ctx.body.toString("utf8"));
|
|
71
|
+
} catch {
|
|
72
|
+
agentLog("warn", `webhook body is not JSON eventId=${ctx.eventId}`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const record =
|
|
77
|
+
payload && typeof payload === "object"
|
|
78
|
+
? (payload as { type?: string; text?: string })
|
|
79
|
+
: null;
|
|
80
|
+
const messageType = record?.type ?? "webhook";
|
|
81
|
+
const text =
|
|
82
|
+
typeof record?.text === "string" && record.text.trim()
|
|
83
|
+
? record.text.trim()
|
|
84
|
+
: `webhook:${messageType}`;
|
|
85
|
+
|
|
86
|
+
const sessionIds = [...connectedSessions];
|
|
87
|
+
if (sessionIds.length === 0) {
|
|
88
|
+
agentLog(
|
|
89
|
+
"info",
|
|
90
|
+
`webhook verified with no live sessions eventId=${ctx.eventId}`,
|
|
91
|
+
);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
broadcastToClients(
|
|
96
|
+
{
|
|
97
|
+
type: "webhook_event",
|
|
98
|
+
eventId: ctx.eventId,
|
|
99
|
+
path: ctx.path,
|
|
100
|
+
payload,
|
|
101
|
+
},
|
|
102
|
+
sessionIds,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
for (const sessionId of sessionIds) {
|
|
106
|
+
speak(sessionId, text);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
agentLog(
|
|
110
|
+
"info",
|
|
111
|
+
`webhook delivered to ${sessionIds.length} session(s) eventId=${ctx.eventId}`,
|
|
112
|
+
);
|
|
113
|
+
},
|
|
114
|
+
});
|