@framers/agentos-ext-porcupine 0.2.0
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/LICENSE +23 -0
- package/SKILL.md +16 -0
- package/dist/PorcupineWakeWordProvider.d.ts +109 -0
- package/dist/PorcupineWakeWordProvider.d.ts.map +1 -0
- package/dist/PorcupineWakeWordProvider.js +121 -0
- package/dist/PorcupineWakeWordProvider.js.map +1 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/manifest.json +8 -0
- package/package.json +43 -0
- package/src/PorcupineWakeWordProvider.ts +178 -0
- package/src/index.ts +112 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Framers
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# porcupine — Wake-Word Extension Pack
|
|
2
|
+
|
|
3
|
+
Provides wake-word detection via the [Picovoice Porcupine](https://picovoice.ai/platform/porcupine/) engine.
|
|
4
|
+
|
|
5
|
+
## Configuration
|
|
6
|
+
|
|
7
|
+
| Option | Description |
|
|
8
|
+
|--------|-------------|
|
|
9
|
+
| `accessKey` | **Required.** Picovoice access key from [console.picovoice.ai](https://console.picovoice.ai/) |
|
|
10
|
+
| `keywords` | Array of built-in keyword names (e.g. `['porcupine', 'bumblebee']`) |
|
|
11
|
+
| `sensitivities` | Per-keyword detection sensitivity in [0, 1] (default 0.5 each) |
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
- On-device, privacy-preserving detection (no audio sent to cloud)
|
|
15
|
+
- Stateless per-frame processing
|
|
16
|
+
- Configurable sensitivity per keyword
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file PorcupineWakeWordProvider.ts
|
|
3
|
+
* @description Wake-word detection provider backed by Picovoice Porcupine.
|
|
4
|
+
*
|
|
5
|
+
* [Porcupine](https://picovoice.ai/platform/porcupine/) is an on-device
|
|
6
|
+
* wake-word engine. This provider wraps `@picovoice/porcupine-node` and
|
|
7
|
+
* exposes the `WakeWordProvider` contract expected by the AgentOS voice
|
|
8
|
+
* pipeline.
|
|
9
|
+
*
|
|
10
|
+
* The `@picovoice/porcupine-node` package is declared as a peer dependency so
|
|
11
|
+
* it is loaded only at runtime via dynamic `import()`.
|
|
12
|
+
*
|
|
13
|
+
* ### Threading model
|
|
14
|
+
* Porcupine's `process()` method is synchronous and stateless per-frame — each
|
|
15
|
+
* 512-sample frame is processed independently. The provider is therefore safe
|
|
16
|
+
* to call from any async context.
|
|
17
|
+
*
|
|
18
|
+
* @module porcupine
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* A detected wake-word event returned by {@link PorcupineWakeWordProvider.detect}.
|
|
22
|
+
*/
|
|
23
|
+
export interface WakeWordDetection {
|
|
24
|
+
/** The keyword string that was detected (e.g. `'porcupine'`). */
|
|
25
|
+
keyword: string;
|
|
26
|
+
/**
|
|
27
|
+
* Confidence score. Porcupine does not expose a per-detection confidence;
|
|
28
|
+
* this is always `1.0` to signal a positive detection.
|
|
29
|
+
*/
|
|
30
|
+
confidence: 1.0;
|
|
31
|
+
/** Stable provider identifier. */
|
|
32
|
+
providerId: 'porcupine';
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Constructor options for {@link PorcupineWakeWordProvider}.
|
|
36
|
+
*/
|
|
37
|
+
export interface PorcupineWakeWordProviderOptions {
|
|
38
|
+
/** Picovoice access key from https://console.picovoice.ai/ */
|
|
39
|
+
accessKey: string;
|
|
40
|
+
/**
|
|
41
|
+
* Built-in keyword names to detect (e.g. `['porcupine', 'bumblebee']`).
|
|
42
|
+
* @defaultValue `['porcupine']`
|
|
43
|
+
*/
|
|
44
|
+
keywords?: string[];
|
|
45
|
+
/**
|
|
46
|
+
* Detection sensitivity in [0, 1] for each keyword.
|
|
47
|
+
* Must be the same length as `keywords` when provided.
|
|
48
|
+
* @defaultValue `[0.5]` (or `0.5` per keyword)
|
|
49
|
+
*/
|
|
50
|
+
sensitivities?: number[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Picovoice Porcupine wake-word provider.
|
|
54
|
+
*
|
|
55
|
+
* Implements the `WakeWordProvider` contract expected by the AgentOS voice
|
|
56
|
+
* pipeline without taking a hard runtime dependency on the interface types.
|
|
57
|
+
*/
|
|
58
|
+
export declare class PorcupineWakeWordProvider {
|
|
59
|
+
/** Stable provider identifier used by the AgentOS extension registry. */
|
|
60
|
+
readonly id = "porcupine";
|
|
61
|
+
private readonly _accessKey;
|
|
62
|
+
private readonly _keywords;
|
|
63
|
+
private readonly _sensitivities;
|
|
64
|
+
/** Lazily initialised Porcupine instance. */
|
|
65
|
+
private _porcupine;
|
|
66
|
+
/**
|
|
67
|
+
* Create a new {@link PorcupineWakeWordProvider}.
|
|
68
|
+
*
|
|
69
|
+
* @param options - Configuration including the required Picovoice access key.
|
|
70
|
+
*/
|
|
71
|
+
constructor(options: PorcupineWakeWordProviderOptions);
|
|
72
|
+
/**
|
|
73
|
+
* Lazily initialise the Porcupine engine on first use.
|
|
74
|
+
*
|
|
75
|
+
* Dynamic import keeps the peer dep truly optional at module-load time.
|
|
76
|
+
*/
|
|
77
|
+
private _getPorcupine;
|
|
78
|
+
/**
|
|
79
|
+
* Process a single audio frame and detect any wake-word.
|
|
80
|
+
*
|
|
81
|
+
* Each frame must be exactly 512 samples of 16-bit PCM at 16 kHz (the
|
|
82
|
+
* Porcupine frame length). `sampleRate` is accepted for interface
|
|
83
|
+
* compatibility but Porcupine always operates at 16 kHz.
|
|
84
|
+
*
|
|
85
|
+
* @param frame - 16-bit PCM audio frame as an `Int16Array` (512 samples).
|
|
86
|
+
* @param sampleRate - Sample rate (informational; must be 16000 for Porcupine).
|
|
87
|
+
* @returns A {@link WakeWordDetection} when a keyword is detected, or `null`.
|
|
88
|
+
*/
|
|
89
|
+
detect(frame: Int16Array, _sampleRate: number): Promise<WakeWordDetection | null>;
|
|
90
|
+
/**
|
|
91
|
+
* No-op reset.
|
|
92
|
+
*
|
|
93
|
+
* Porcupine processes frames statelessly; there is no internal buffer to
|
|
94
|
+
* flush. This method exists for interface compatibility.
|
|
95
|
+
*/
|
|
96
|
+
reset(): void;
|
|
97
|
+
/**
|
|
98
|
+
* Release the native Porcupine engine resources.
|
|
99
|
+
*
|
|
100
|
+
* Call this when the provider is no longer needed to free memory held by
|
|
101
|
+
* the native addon.
|
|
102
|
+
*/
|
|
103
|
+
dispose(): Promise<void>;
|
|
104
|
+
/** Returns the configured keyword list. */
|
|
105
|
+
getKeywords(): string[];
|
|
106
|
+
/** Returns the configured sensitivity list. */
|
|
107
|
+
getSensitivities(): number[];
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=PorcupineWakeWordProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PorcupineWakeWordProvider.d.ts","sourceRoot":"","sources":["../src/PorcupineWakeWordProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAMH;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,UAAU,EAAE,GAAG,CAAC;IAChB,kCAAkC;IAClC,UAAU,EAAE,WAAW,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,gCAAgC;IAC/C,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAMD;;;;;GAKG;AACH,qBAAa,yBAAyB;IACpC,yEAAyE;IACzE,QAAQ,CAAC,EAAE,eAAe;IAE1B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAW;IACrC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAW;IAE1C,6CAA6C;IAE7C,OAAO,CAAC,UAAU,CAAoB;IAEtC;;;;OAIG;gBACS,OAAO,EAAE,gCAAgC;IAWrD;;;;OAIG;YAEW,aAAa;IAgB3B;;;;;;;;;;OAUG;IACG,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAgBvF;;;;;OAKG;IACH,KAAK,IAAI,IAAI;IAIb;;;;;OAKG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAW9B,2CAA2C;IAC3C,WAAW,IAAI,MAAM,EAAE;IAEvB,+CAA+C;IAC/C,gBAAgB,IAAI,MAAM,EAAE;CAC7B"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file PorcupineWakeWordProvider.ts
|
|
3
|
+
* @description Wake-word detection provider backed by Picovoice Porcupine.
|
|
4
|
+
*
|
|
5
|
+
* [Porcupine](https://picovoice.ai/platform/porcupine/) is an on-device
|
|
6
|
+
* wake-word engine. This provider wraps `@picovoice/porcupine-node` and
|
|
7
|
+
* exposes the `WakeWordProvider` contract expected by the AgentOS voice
|
|
8
|
+
* pipeline.
|
|
9
|
+
*
|
|
10
|
+
* The `@picovoice/porcupine-node` package is declared as a peer dependency so
|
|
11
|
+
* it is loaded only at runtime via dynamic `import()`.
|
|
12
|
+
*
|
|
13
|
+
* ### Threading model
|
|
14
|
+
* Porcupine's `process()` method is synchronous and stateless per-frame — each
|
|
15
|
+
* 512-sample frame is processed independently. The provider is therefore safe
|
|
16
|
+
* to call from any async context.
|
|
17
|
+
*
|
|
18
|
+
* @module porcupine
|
|
19
|
+
*/
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Provider
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
/**
|
|
24
|
+
* Picovoice Porcupine wake-word provider.
|
|
25
|
+
*
|
|
26
|
+
* Implements the `WakeWordProvider` contract expected by the AgentOS voice
|
|
27
|
+
* pipeline without taking a hard runtime dependency on the interface types.
|
|
28
|
+
*/
|
|
29
|
+
export class PorcupineWakeWordProvider {
|
|
30
|
+
/** Stable provider identifier used by the AgentOS extension registry. */
|
|
31
|
+
id = 'porcupine';
|
|
32
|
+
_accessKey;
|
|
33
|
+
_keywords;
|
|
34
|
+
_sensitivities;
|
|
35
|
+
/** Lazily initialised Porcupine instance. */
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
_porcupine = null;
|
|
38
|
+
/**
|
|
39
|
+
* Create a new {@link PorcupineWakeWordProvider}.
|
|
40
|
+
*
|
|
41
|
+
* @param options - Configuration including the required Picovoice access key.
|
|
42
|
+
*/
|
|
43
|
+
constructor(options) {
|
|
44
|
+
this._accessKey = options.accessKey;
|
|
45
|
+
this._keywords = options.keywords ?? ['porcupine'];
|
|
46
|
+
this._sensitivities =
|
|
47
|
+
options.sensitivities ?? this._keywords.map(() => 0.5);
|
|
48
|
+
}
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Private helpers
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
/**
|
|
53
|
+
* Lazily initialise the Porcupine engine on first use.
|
|
54
|
+
*
|
|
55
|
+
* Dynamic import keeps the peer dep truly optional at module-load time.
|
|
56
|
+
*/
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
|
+
async _getPorcupine() {
|
|
59
|
+
if (!this._porcupine) {
|
|
60
|
+
const { Porcupine } = await import('@picovoice/porcupine-node');
|
|
61
|
+
this._porcupine = new Porcupine(this._accessKey, this._keywords, this._sensitivities);
|
|
62
|
+
}
|
|
63
|
+
return this._porcupine;
|
|
64
|
+
}
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Public API
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
/**
|
|
69
|
+
* Process a single audio frame and detect any wake-word.
|
|
70
|
+
*
|
|
71
|
+
* Each frame must be exactly 512 samples of 16-bit PCM at 16 kHz (the
|
|
72
|
+
* Porcupine frame length). `sampleRate` is accepted for interface
|
|
73
|
+
* compatibility but Porcupine always operates at 16 kHz.
|
|
74
|
+
*
|
|
75
|
+
* @param frame - 16-bit PCM audio frame as an `Int16Array` (512 samples).
|
|
76
|
+
* @param sampleRate - Sample rate (informational; must be 16000 for Porcupine).
|
|
77
|
+
* @returns A {@link WakeWordDetection} when a keyword is detected, or `null`.
|
|
78
|
+
*/
|
|
79
|
+
async detect(frame, _sampleRate) {
|
|
80
|
+
const porcupine = await this._getPorcupine();
|
|
81
|
+
const keywordIndex = porcupine.process(frame);
|
|
82
|
+
if (keywordIndex < 0) {
|
|
83
|
+
// No detection.
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
keyword: this._keywords[keywordIndex] ?? String(keywordIndex),
|
|
88
|
+
confidence: 1.0,
|
|
89
|
+
providerId: 'porcupine',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* No-op reset.
|
|
94
|
+
*
|
|
95
|
+
* Porcupine processes frames statelessly; there is no internal buffer to
|
|
96
|
+
* flush. This method exists for interface compatibility.
|
|
97
|
+
*/
|
|
98
|
+
reset() {
|
|
99
|
+
// Intentional no-op — Porcupine is stateless per frame.
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Release the native Porcupine engine resources.
|
|
103
|
+
*
|
|
104
|
+
* Call this when the provider is no longer needed to free memory held by
|
|
105
|
+
* the native addon.
|
|
106
|
+
*/
|
|
107
|
+
async dispose() {
|
|
108
|
+
if (this._porcupine) {
|
|
109
|
+
this._porcupine.release();
|
|
110
|
+
this._porcupine = null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Accessors (for testing / diagnostics)
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
/** Returns the configured keyword list. */
|
|
117
|
+
getKeywords() { return [...this._keywords]; }
|
|
118
|
+
/** Returns the configured sensitivity list. */
|
|
119
|
+
getSensitivities() { return [...this._sensitivities]; }
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=PorcupineWakeWordProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PorcupineWakeWordProvider.js","sourceRoot":"","sources":["../src/PorcupineWakeWordProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAwCH,8EAA8E;AAC9E,WAAW;AACX,8EAA8E;AAE9E;;;;;GAKG;AACH,MAAM,OAAO,yBAAyB;IACpC,yEAAyE;IAChE,EAAE,GAAG,WAAW,CAAC;IAET,UAAU,CAAS;IACnB,SAAS,CAAW;IACpB,cAAc,CAAW;IAE1C,6CAA6C;IAC7C,8DAA8D;IACtD,UAAU,GAAe,IAAI,CAAC;IAEtC;;;;OAIG;IACH,YAAY,OAAyC;QACnD,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;QACpC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,QAAQ,IAAI,CAAC,WAAW,CAAC,CAAC;QACnD,IAAI,CAAC,cAAc;YACjB,OAAO,CAAC,aAAa,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;IAC3D,CAAC;IAED,8EAA8E;IAC9E,kBAAkB;IAClB,8EAA8E;IAE9E;;;;OAIG;IACH,8DAA8D;IACtD,KAAK,CAAC,aAAa;QACzB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,2BAA2B,CAAC,CAAC;YAChE,IAAI,CAAC,UAAU,GAAG,IAAI,SAAS,CAC7B,IAAI,CAAC,UAAU,EACf,IAAI,CAAC,SAAS,EACd,IAAI,CAAC,cAAc,CACpB,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,8EAA8E;IAC9E,aAAa;IACb,8EAA8E;IAE9E;;;;;;;;;;OAUG;IACH,KAAK,CAAC,MAAM,CAAC,KAAiB,EAAE,WAAmB;QACjD,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAC7C,MAAM,YAAY,GAAW,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAEtD,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;YACrB,gBAAgB;YAChB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,IAAI,MAAM,CAAC,YAAY,CAAC;YAC7D,UAAU,EAAE,GAAG;YACf,UAAU,EAAE,WAAW;SACxB,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACH,KAAK;QACH,wDAAwD;IAC1D,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,OAAO;QACX,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;YAC1B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACzB,CAAC;IACH,CAAC;IAED,8EAA8E;IAC9E,wCAAwC;IACxC,8EAA8E;IAE9E,2CAA2C;IAC3C,WAAW,KAAe,OAAO,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAEvD,+CAA+C;IAC/C,gBAAgB,KAAe,OAAO,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;CAClE"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file index.ts
|
|
3
|
+
* @description Pack factory for the Porcupine wake-word extension pack.
|
|
4
|
+
*
|
|
5
|
+
* Exports the main {@link createPorcupine} factory function and the
|
|
6
|
+
* {@link createExtensionPack} bridge function that conforms to the AgentOS
|
|
7
|
+
* manifest factory convention.
|
|
8
|
+
*
|
|
9
|
+
* ### Usage (direct)
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { createPorcupine } from '@framers/agentos-ext-porcupine';
|
|
12
|
+
*
|
|
13
|
+
* const wakeWord = createPorcupine({ accessKey: 'YOUR_KEY', keywords: ['porcupine'] });
|
|
14
|
+
* const detection = await wakeWord.detect(frame, 16000);
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* ### Usage (manifest-driven)
|
|
18
|
+
* ```json
|
|
19
|
+
* { "packs": [{ "module": "@framers/agentos-ext-porcupine" }] }
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @module porcupine
|
|
23
|
+
*/
|
|
24
|
+
import { PorcupineWakeWordProvider } from './PorcupineWakeWordProvider.js';
|
|
25
|
+
import type { PorcupineWakeWordProviderOptions } from './PorcupineWakeWordProvider.js';
|
|
26
|
+
/** Subset of ExtensionDescriptor required by this pack. */
|
|
27
|
+
interface ExtensionDescriptor {
|
|
28
|
+
id: string;
|
|
29
|
+
kind: string;
|
|
30
|
+
payload: unknown;
|
|
31
|
+
enableByDefault?: boolean;
|
|
32
|
+
metadata?: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
/** Subset of ExtensionPack required by this pack. */
|
|
35
|
+
interface ExtensionPack {
|
|
36
|
+
id: string;
|
|
37
|
+
descriptors: ExtensionDescriptor[];
|
|
38
|
+
}
|
|
39
|
+
/** Subset of ExtensionPackContext required by this pack. */
|
|
40
|
+
interface ExtensionPackContext {
|
|
41
|
+
getSecret?: (id: string) => string | undefined;
|
|
42
|
+
options?: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create a standalone {@link PorcupineWakeWordProvider} instance.
|
|
46
|
+
*
|
|
47
|
+
* @param options - Constructor options (access key required).
|
|
48
|
+
* @returns Configured {@link PorcupineWakeWordProvider}.
|
|
49
|
+
*/
|
|
50
|
+
export declare function createPorcupine(options: PorcupineWakeWordProviderOptions): PorcupineWakeWordProvider;
|
|
51
|
+
/**
|
|
52
|
+
* AgentOS manifest factory function.
|
|
53
|
+
*
|
|
54
|
+
* Reads the `PICOVOICE_ACCESS_KEY` secret and optional keyword configuration
|
|
55
|
+
* from the context, and returns an {@link ExtensionPack} containing a single
|
|
56
|
+
* `wake-word-provider` descriptor.
|
|
57
|
+
*
|
|
58
|
+
* @param context - Pack context supplied by the extension manager.
|
|
59
|
+
* @returns A fully configured {@link ExtensionPack}.
|
|
60
|
+
*/
|
|
61
|
+
export declare function createExtensionPack(context: ExtensionPackContext): ExtensionPack;
|
|
62
|
+
export { PorcupineWakeWordProvider } from './PorcupineWakeWordProvider.js';
|
|
63
|
+
export type { WakeWordDetection, PorcupineWakeWordProviderOptions, } from './PorcupineWakeWordProvider.js';
|
|
64
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,gCAAgC,CAAC;AAMvF,2DAA2D;AAC3D,UAAU,mBAAmB;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,qDAAqD;AACrD,UAAU,aAAa;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,mBAAmB,EAAE,CAAC;CACpC;AAED,4DAA4D;AAC5D,UAAU,oBAAoB;IAC5B,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AASD;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,gCAAgC,GACxC,yBAAyB,CAE3B;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CAoBhF;AAMD,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,YAAY,EACV,iBAAiB,EACjB,gCAAgC,GACjC,MAAM,gCAAgC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file index.ts
|
|
3
|
+
* @description Pack factory for the Porcupine wake-word extension pack.
|
|
4
|
+
*
|
|
5
|
+
* Exports the main {@link createPorcupine} factory function and the
|
|
6
|
+
* {@link createExtensionPack} bridge function that conforms to the AgentOS
|
|
7
|
+
* manifest factory convention.
|
|
8
|
+
*
|
|
9
|
+
* ### Usage (direct)
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { createPorcupine } from '@framers/agentos-ext-porcupine';
|
|
12
|
+
*
|
|
13
|
+
* const wakeWord = createPorcupine({ accessKey: 'YOUR_KEY', keywords: ['porcupine'] });
|
|
14
|
+
* const detection = await wakeWord.detect(frame, 16000);
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* ### Usage (manifest-driven)
|
|
18
|
+
* ```json
|
|
19
|
+
* { "packs": [{ "module": "@framers/agentos-ext-porcupine" }] }
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @module porcupine
|
|
23
|
+
*/
|
|
24
|
+
import { PorcupineWakeWordProvider } from './PorcupineWakeWordProvider.js';
|
|
25
|
+
/** Kind constant matching packages/agentos/src/extensions/types.ts. */
|
|
26
|
+
const EXTENSION_KIND_WAKE_WORD = 'wake-word-provider';
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Factories
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
/**
|
|
31
|
+
* Create a standalone {@link PorcupineWakeWordProvider} instance.
|
|
32
|
+
*
|
|
33
|
+
* @param options - Constructor options (access key required).
|
|
34
|
+
* @returns Configured {@link PorcupineWakeWordProvider}.
|
|
35
|
+
*/
|
|
36
|
+
export function createPorcupine(options) {
|
|
37
|
+
return new PorcupineWakeWordProvider(options);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* AgentOS manifest factory function.
|
|
41
|
+
*
|
|
42
|
+
* Reads the `PICOVOICE_ACCESS_KEY` secret and optional keyword configuration
|
|
43
|
+
* from the context, and returns an {@link ExtensionPack} containing a single
|
|
44
|
+
* `wake-word-provider` descriptor.
|
|
45
|
+
*
|
|
46
|
+
* @param context - Pack context supplied by the extension manager.
|
|
47
|
+
* @returns A fully configured {@link ExtensionPack}.
|
|
48
|
+
*/
|
|
49
|
+
export function createExtensionPack(context) {
|
|
50
|
+
const accessKey = context.getSecret?.('PICOVOICE_ACCESS_KEY') ?? '';
|
|
51
|
+
const opts = context.options ?? {};
|
|
52
|
+
const keywords = opts['keywords'];
|
|
53
|
+
const sensitivities = opts['sensitivities'];
|
|
54
|
+
const provider = new PorcupineWakeWordProvider({ accessKey, keywords, sensitivities });
|
|
55
|
+
return {
|
|
56
|
+
id: 'porcupine',
|
|
57
|
+
descriptors: [
|
|
58
|
+
{
|
|
59
|
+
id: 'porcupine',
|
|
60
|
+
kind: EXTENSION_KIND_WAKE_WORD,
|
|
61
|
+
payload: provider,
|
|
62
|
+
enableByDefault: true,
|
|
63
|
+
metadata: { providerId: 'porcupine' },
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Re-exports
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
export { PorcupineWakeWordProvider } from './PorcupineWakeWordProvider.js';
|
|
72
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AA4B3E,uEAAuE;AACvE,MAAM,wBAAwB,GAAG,oBAAoB,CAAC;AAEtD,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAyC;IAEzC,OAAO,IAAI,yBAAyB,CAAC,OAAO,CAAC,CAAC;AAChD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAA6B;IAC/D,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,sBAAsB,CAAC,IAAI,EAAE,CAAC;IACpE,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;IACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAyB,CAAC;IAC1D,MAAM,aAAa,GAAG,IAAI,CAAC,eAAe,CAAyB,CAAC;IAEpE,MAAM,QAAQ,GAAG,IAAI,yBAAyB,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC,CAAC;IAEvF,OAAO;QACL,EAAE,EAAE,WAAW;QACf,WAAW,EAAE;YACX;gBACE,EAAE,EAAE,WAAW;gBACf,IAAI,EAAE,wBAAwB;gBAC9B,OAAO,EAAE,QAAQ;gBACjB,eAAe,EAAE,IAAI;gBACrB,QAAQ,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE;aACtC;SACF;KACF,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC"}
|
package/manifest.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@framers/agentos-ext-porcupine",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Wake-word detection via Picovoice Porcupine for AgentOS voice pipeline",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src",
|
|
17
|
+
"SKILL.md",
|
|
18
|
+
"manifest.json"
|
|
19
|
+
],
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"@framers/agentos": "^0.1.0",
|
|
22
|
+
"@picovoice/porcupine-node": "^3.0.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"typescript": "^5.5.0",
|
|
26
|
+
"vitest": "^1.6.0",
|
|
27
|
+
"@framers/agentos": "0.1.94"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"author": "Frame.dev",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/framersai/agentos-extensions.git",
|
|
34
|
+
"directory": "registry/curated/voice/porcupine"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc -p tsconfig.json",
|
|
41
|
+
"test": "vitest run"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file PorcupineWakeWordProvider.ts
|
|
3
|
+
* @description Wake-word detection provider backed by Picovoice Porcupine.
|
|
4
|
+
*
|
|
5
|
+
* [Porcupine](https://picovoice.ai/platform/porcupine/) is an on-device
|
|
6
|
+
* wake-word engine. This provider wraps `@picovoice/porcupine-node` and
|
|
7
|
+
* exposes the `WakeWordProvider` contract expected by the AgentOS voice
|
|
8
|
+
* pipeline.
|
|
9
|
+
*
|
|
10
|
+
* The `@picovoice/porcupine-node` package is declared as a peer dependency so
|
|
11
|
+
* it is loaded only at runtime via dynamic `import()`.
|
|
12
|
+
*
|
|
13
|
+
* ### Threading model
|
|
14
|
+
* Porcupine's `process()` method is synchronous and stateless per-frame — each
|
|
15
|
+
* 512-sample frame is processed independently. The provider is therefore safe
|
|
16
|
+
* to call from any async context.
|
|
17
|
+
*
|
|
18
|
+
* @module porcupine
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Public types
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A detected wake-word event returned by {@link PorcupineWakeWordProvider.detect}.
|
|
27
|
+
*/
|
|
28
|
+
export interface WakeWordDetection {
|
|
29
|
+
/** The keyword string that was detected (e.g. `'porcupine'`). */
|
|
30
|
+
keyword: string;
|
|
31
|
+
/**
|
|
32
|
+
* Confidence score. Porcupine does not expose a per-detection confidence;
|
|
33
|
+
* this is always `1.0` to signal a positive detection.
|
|
34
|
+
*/
|
|
35
|
+
confidence: 1.0;
|
|
36
|
+
/** Stable provider identifier. */
|
|
37
|
+
providerId: 'porcupine';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Constructor options for {@link PorcupineWakeWordProvider}.
|
|
42
|
+
*/
|
|
43
|
+
export interface PorcupineWakeWordProviderOptions {
|
|
44
|
+
/** Picovoice access key from https://console.picovoice.ai/ */
|
|
45
|
+
accessKey: string;
|
|
46
|
+
/**
|
|
47
|
+
* Built-in keyword names to detect (e.g. `['porcupine', 'bumblebee']`).
|
|
48
|
+
* @defaultValue `['porcupine']`
|
|
49
|
+
*/
|
|
50
|
+
keywords?: string[];
|
|
51
|
+
/**
|
|
52
|
+
* Detection sensitivity in [0, 1] for each keyword.
|
|
53
|
+
* Must be the same length as `keywords` when provided.
|
|
54
|
+
* @defaultValue `[0.5]` (or `0.5` per keyword)
|
|
55
|
+
*/
|
|
56
|
+
sensitivities?: number[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Provider
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Picovoice Porcupine wake-word provider.
|
|
65
|
+
*
|
|
66
|
+
* Implements the `WakeWordProvider` contract expected by the AgentOS voice
|
|
67
|
+
* pipeline without taking a hard runtime dependency on the interface types.
|
|
68
|
+
*/
|
|
69
|
+
export class PorcupineWakeWordProvider {
|
|
70
|
+
/** Stable provider identifier used by the AgentOS extension registry. */
|
|
71
|
+
readonly id = 'porcupine';
|
|
72
|
+
|
|
73
|
+
private readonly _accessKey: string;
|
|
74
|
+
private readonly _keywords: string[];
|
|
75
|
+
private readonly _sensitivities: number[];
|
|
76
|
+
|
|
77
|
+
/** Lazily initialised Porcupine instance. */
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
|
+
private _porcupine: any | null = null;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create a new {@link PorcupineWakeWordProvider}.
|
|
83
|
+
*
|
|
84
|
+
* @param options - Configuration including the required Picovoice access key.
|
|
85
|
+
*/
|
|
86
|
+
constructor(options: PorcupineWakeWordProviderOptions) {
|
|
87
|
+
this._accessKey = options.accessKey;
|
|
88
|
+
this._keywords = options.keywords ?? ['porcupine'];
|
|
89
|
+
this._sensitivities =
|
|
90
|
+
options.sensitivities ?? this._keywords.map(() => 0.5);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Private helpers
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Lazily initialise the Porcupine engine on first use.
|
|
99
|
+
*
|
|
100
|
+
* Dynamic import keeps the peer dep truly optional at module-load time.
|
|
101
|
+
*/
|
|
102
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
103
|
+
private async _getPorcupine(): Promise<any> {
|
|
104
|
+
if (!this._porcupine) {
|
|
105
|
+
const { Porcupine } = await import('@picovoice/porcupine-node');
|
|
106
|
+
this._porcupine = new Porcupine(
|
|
107
|
+
this._accessKey,
|
|
108
|
+
this._keywords,
|
|
109
|
+
this._sensitivities,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
return this._porcupine;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Public API
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Process a single audio frame and detect any wake-word.
|
|
121
|
+
*
|
|
122
|
+
* Each frame must be exactly 512 samples of 16-bit PCM at 16 kHz (the
|
|
123
|
+
* Porcupine frame length). `sampleRate` is accepted for interface
|
|
124
|
+
* compatibility but Porcupine always operates at 16 kHz.
|
|
125
|
+
*
|
|
126
|
+
* @param frame - 16-bit PCM audio frame as an `Int16Array` (512 samples).
|
|
127
|
+
* @param sampleRate - Sample rate (informational; must be 16000 for Porcupine).
|
|
128
|
+
* @returns A {@link WakeWordDetection} when a keyword is detected, or `null`.
|
|
129
|
+
*/
|
|
130
|
+
async detect(frame: Int16Array, _sampleRate: number): Promise<WakeWordDetection | null> {
|
|
131
|
+
const porcupine = await this._getPorcupine();
|
|
132
|
+
const keywordIndex: number = porcupine.process(frame);
|
|
133
|
+
|
|
134
|
+
if (keywordIndex < 0) {
|
|
135
|
+
// No detection.
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
keyword: this._keywords[keywordIndex] ?? String(keywordIndex),
|
|
141
|
+
confidence: 1.0,
|
|
142
|
+
providerId: 'porcupine',
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* No-op reset.
|
|
148
|
+
*
|
|
149
|
+
* Porcupine processes frames statelessly; there is no internal buffer to
|
|
150
|
+
* flush. This method exists for interface compatibility.
|
|
151
|
+
*/
|
|
152
|
+
reset(): void {
|
|
153
|
+
// Intentional no-op — Porcupine is stateless per frame.
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Release the native Porcupine engine resources.
|
|
158
|
+
*
|
|
159
|
+
* Call this when the provider is no longer needed to free memory held by
|
|
160
|
+
* the native addon.
|
|
161
|
+
*/
|
|
162
|
+
async dispose(): Promise<void> {
|
|
163
|
+
if (this._porcupine) {
|
|
164
|
+
this._porcupine.release();
|
|
165
|
+
this._porcupine = null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// Accessors (for testing / diagnostics)
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
/** Returns the configured keyword list. */
|
|
174
|
+
getKeywords(): string[] { return [...this._keywords]; }
|
|
175
|
+
|
|
176
|
+
/** Returns the configured sensitivity list. */
|
|
177
|
+
getSensitivities(): number[] { return [...this._sensitivities]; }
|
|
178
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file index.ts
|
|
3
|
+
* @description Pack factory for the Porcupine wake-word extension pack.
|
|
4
|
+
*
|
|
5
|
+
* Exports the main {@link createPorcupine} factory function and the
|
|
6
|
+
* {@link createExtensionPack} bridge function that conforms to the AgentOS
|
|
7
|
+
* manifest factory convention.
|
|
8
|
+
*
|
|
9
|
+
* ### Usage (direct)
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { createPorcupine } from '@framers/agentos-ext-porcupine';
|
|
12
|
+
*
|
|
13
|
+
* const wakeWord = createPorcupine({ accessKey: 'YOUR_KEY', keywords: ['porcupine'] });
|
|
14
|
+
* const detection = await wakeWord.detect(frame, 16000);
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* ### Usage (manifest-driven)
|
|
18
|
+
* ```json
|
|
19
|
+
* { "packs": [{ "module": "@framers/agentos-ext-porcupine" }] }
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @module porcupine
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { PorcupineWakeWordProvider } from './PorcupineWakeWordProvider.js';
|
|
26
|
+
import type { PorcupineWakeWordProviderOptions } from './PorcupineWakeWordProvider.js';
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Local interface mirrors — avoids a hard runtime dep on @framers/agentos
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
/** Subset of ExtensionDescriptor required by this pack. */
|
|
33
|
+
interface ExtensionDescriptor {
|
|
34
|
+
id: string;
|
|
35
|
+
kind: string;
|
|
36
|
+
payload: unknown;
|
|
37
|
+
enableByDefault?: boolean;
|
|
38
|
+
metadata?: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Subset of ExtensionPack required by this pack. */
|
|
42
|
+
interface ExtensionPack {
|
|
43
|
+
id: string;
|
|
44
|
+
descriptors: ExtensionDescriptor[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Subset of ExtensionPackContext required by this pack. */
|
|
48
|
+
interface ExtensionPackContext {
|
|
49
|
+
getSecret?: (id: string) => string | undefined;
|
|
50
|
+
options?: Record<string, unknown>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Kind constant matching packages/agentos/src/extensions/types.ts. */
|
|
54
|
+
const EXTENSION_KIND_WAKE_WORD = 'wake-word-provider';
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Factories
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Create a standalone {@link PorcupineWakeWordProvider} instance.
|
|
62
|
+
*
|
|
63
|
+
* @param options - Constructor options (access key required).
|
|
64
|
+
* @returns Configured {@link PorcupineWakeWordProvider}.
|
|
65
|
+
*/
|
|
66
|
+
export function createPorcupine(
|
|
67
|
+
options: PorcupineWakeWordProviderOptions,
|
|
68
|
+
): PorcupineWakeWordProvider {
|
|
69
|
+
return new PorcupineWakeWordProvider(options);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* AgentOS manifest factory function.
|
|
74
|
+
*
|
|
75
|
+
* Reads the `PICOVOICE_ACCESS_KEY` secret and optional keyword configuration
|
|
76
|
+
* from the context, and returns an {@link ExtensionPack} containing a single
|
|
77
|
+
* `wake-word-provider` descriptor.
|
|
78
|
+
*
|
|
79
|
+
* @param context - Pack context supplied by the extension manager.
|
|
80
|
+
* @returns A fully configured {@link ExtensionPack}.
|
|
81
|
+
*/
|
|
82
|
+
export function createExtensionPack(context: ExtensionPackContext): ExtensionPack {
|
|
83
|
+
const accessKey = context.getSecret?.('PICOVOICE_ACCESS_KEY') ?? '';
|
|
84
|
+
const opts = context.options ?? {};
|
|
85
|
+
const keywords = opts['keywords'] as string[] | undefined;
|
|
86
|
+
const sensitivities = opts['sensitivities'] as number[] | undefined;
|
|
87
|
+
|
|
88
|
+
const provider = new PorcupineWakeWordProvider({ accessKey, keywords, sensitivities });
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
id: 'porcupine',
|
|
92
|
+
descriptors: [
|
|
93
|
+
{
|
|
94
|
+
id: 'porcupine',
|
|
95
|
+
kind: EXTENSION_KIND_WAKE_WORD,
|
|
96
|
+
payload: provider,
|
|
97
|
+
enableByDefault: true,
|
|
98
|
+
metadata: { providerId: 'porcupine' },
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Re-exports
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
export { PorcupineWakeWordProvider } from './PorcupineWakeWordProvider.js';
|
|
109
|
+
export type {
|
|
110
|
+
WakeWordDetection,
|
|
111
|
+
PorcupineWakeWordProviderOptions,
|
|
112
|
+
} from './PorcupineWakeWordProvider.js';
|