@framers/agentos-ext-endpoint-semantic 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 +74 -0
- package/dist/SemanticEndpointDetector.d.ts +186 -0
- package/dist/SemanticEndpointDetector.d.ts.map +1 -0
- package/dist/SemanticEndpointDetector.js +308 -0
- package/dist/SemanticEndpointDetector.js.map +1 -0
- package/dist/TurnCompletenessClassifier.d.ts +82 -0
- package/dist/TurnCompletenessClassifier.d.ts.map +1 -0
- package/dist/TurnCompletenessClassifier.js +130 -0
- package/dist/TurnCompletenessClassifier.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +83 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +50 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/manifest.json +8 -0
- package/package.json +42 -0
- package/src/SemanticEndpointDetector.ts +440 -0
- package/src/TurnCompletenessClassifier.ts +157 -0
- package/src/index.ts +125 -0
- package/src/types.ts +53 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file TurnCompletenessClassifier.ts
|
|
3
|
+
* @description LLM-backed binary classifier that decides whether an utterance
|
|
4
|
+
* is a complete conversational turn (COMPLETE) or likely to continue
|
|
5
|
+
* (INCOMPLETE). Results are LRU-cached to avoid redundant LLM round-trips for
|
|
6
|
+
* repeated short phrases.
|
|
7
|
+
*
|
|
8
|
+
* @module endpoint-semantic/TurnCompletenessClassifier
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* The three possible results from {@link TurnCompletenessClassifier.classify}.
|
|
12
|
+
*
|
|
13
|
+
* - `COMPLETE` — The utterance is a self-contained thought expecting a reply.
|
|
14
|
+
* - `INCOMPLETE` — The speaker is likely to continue before yielding the floor.
|
|
15
|
+
* - `TIMEOUT` — The LLM did not respond within the configured time budget;
|
|
16
|
+
* the caller should fall back to silence-timeout behaviour.
|
|
17
|
+
*/
|
|
18
|
+
export type ClassifyResult = 'COMPLETE' | 'INCOMPLETE' | 'TIMEOUT';
|
|
19
|
+
/**
|
|
20
|
+
* Classifies a transcript as {@link ClassifyResult.COMPLETE} or
|
|
21
|
+
* {@link ClassifyResult.INCOMPLETE} using an LLM prompt.
|
|
22
|
+
*
|
|
23
|
+
* ### Caching
|
|
24
|
+
* Results are stored in a fixed-capacity LRU cache keyed on the first 100
|
|
25
|
+
* characters of the transcript. When the cache is full, the oldest entry is
|
|
26
|
+
* evicted (insertion-order eviction via `Map` iteration order, which is
|
|
27
|
+
* guaranteed by the ES2015 spec).
|
|
28
|
+
*
|
|
29
|
+
* ### Prompt format
|
|
30
|
+
* The LLM is asked to respond with exactly `COMPLETE` or `INCOMPLETE` as the
|
|
31
|
+
* first word, followed by one sentence of reasoning. Only the first word is
|
|
32
|
+
* examined — anything after the first space is treated as explanatory text and
|
|
33
|
+
* discarded.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const classifier = new TurnCompletenessClassifier(
|
|
38
|
+
* async (prompt) => openai.chat.completions.create({ ... }).then(r => r.choices[0].message.content ?? ''),
|
|
39
|
+
* 400,
|
|
40
|
+
* );
|
|
41
|
+
* const result = await classifier.classify('Tell me about your', 'User: Hello\nAgent: Hi there!');
|
|
42
|
+
* // → 'INCOMPLETE' (sentence is unfinished)
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare class TurnCompletenessClassifier {
|
|
46
|
+
private readonly llmCall;
|
|
47
|
+
private readonly timeoutMs;
|
|
48
|
+
/**
|
|
49
|
+
* LRU result cache. Keys are the first 100 chars of the transcript; values
|
|
50
|
+
* are the cached classification. `Map` preserves insertion order, so
|
|
51
|
+
* eviction iterates from the oldest entry (`.keys().next().value`).
|
|
52
|
+
*/
|
|
53
|
+
private readonly cache;
|
|
54
|
+
/**
|
|
55
|
+
* Maximum number of entries to keep in {@link cache} before evicting the
|
|
56
|
+
* oldest one.
|
|
57
|
+
*/
|
|
58
|
+
private readonly maxCacheSize;
|
|
59
|
+
/**
|
|
60
|
+
* @param llmCall — Async function that sends a prompt string to an LLM and
|
|
61
|
+
* resolves with the raw response text.
|
|
62
|
+
* @param timeoutMs — Maximum milliseconds to wait for the LLM before
|
|
63
|
+
* returning `TIMEOUT`. Defaults to 500 ms.
|
|
64
|
+
*/
|
|
65
|
+
constructor(llmCall: (prompt: string) => Promise<string>, timeoutMs?: number);
|
|
66
|
+
/**
|
|
67
|
+
* Classify `transcript` as `COMPLETE`, `INCOMPLETE`, or `TIMEOUT`.
|
|
68
|
+
*
|
|
69
|
+
* The result is cached on the first 100 characters of `transcript` so that
|
|
70
|
+
* repeated short phrases (common in voice interaction) never hit the LLM
|
|
71
|
+
* twice.
|
|
72
|
+
*
|
|
73
|
+
* @param transcript — The utterance text to evaluate.
|
|
74
|
+
* @param context — Optional string containing the last 2 conversation turns,
|
|
75
|
+
* formatted as `"Speaker: text\n..."`. When omitted, the
|
|
76
|
+
* prompt uses `'N/A'`.
|
|
77
|
+
* @returns A {@link ClassifyResult} resolving to `COMPLETE`, `INCOMPLETE`,
|
|
78
|
+
* or `TIMEOUT`.
|
|
79
|
+
*/
|
|
80
|
+
classify(transcript: string, context?: string): Promise<ClassifyResult>;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=TurnCompletenessClassifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TurnCompletenessClassifier.d.ts","sourceRoot":"","sources":["../src/TurnCompletenessClassifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,YAAY,GAAG,SAAS,CAAC;AAMnE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,0BAA0B;IAqBnC,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IArB5B;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgD;IAEtE;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAO;IAEpC;;;;;OAKG;gBAEgB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,EAC5C,SAAS,GAAE,MAAY;IAO1C;;;;;;;;;;;;;OAaG;IACG,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;CA2D9E"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file TurnCompletenessClassifier.ts
|
|
3
|
+
* @description LLM-backed binary classifier that decides whether an utterance
|
|
4
|
+
* is a complete conversational turn (COMPLETE) or likely to continue
|
|
5
|
+
* (INCOMPLETE). Results are LRU-cached to avoid redundant LLM round-trips for
|
|
6
|
+
* repeated short phrases.
|
|
7
|
+
*
|
|
8
|
+
* @module endpoint-semantic/TurnCompletenessClassifier
|
|
9
|
+
*/
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Implementation
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
/**
|
|
14
|
+
* Classifies a transcript as {@link ClassifyResult.COMPLETE} or
|
|
15
|
+
* {@link ClassifyResult.INCOMPLETE} using an LLM prompt.
|
|
16
|
+
*
|
|
17
|
+
* ### Caching
|
|
18
|
+
* Results are stored in a fixed-capacity LRU cache keyed on the first 100
|
|
19
|
+
* characters of the transcript. When the cache is full, the oldest entry is
|
|
20
|
+
* evicted (insertion-order eviction via `Map` iteration order, which is
|
|
21
|
+
* guaranteed by the ES2015 spec).
|
|
22
|
+
*
|
|
23
|
+
* ### Prompt format
|
|
24
|
+
* The LLM is asked to respond with exactly `COMPLETE` or `INCOMPLETE` as the
|
|
25
|
+
* first word, followed by one sentence of reasoning. Only the first word is
|
|
26
|
+
* examined — anything after the first space is treated as explanatory text and
|
|
27
|
+
* discarded.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const classifier = new TurnCompletenessClassifier(
|
|
32
|
+
* async (prompt) => openai.chat.completions.create({ ... }).then(r => r.choices[0].message.content ?? ''),
|
|
33
|
+
* 400,
|
|
34
|
+
* );
|
|
35
|
+
* const result = await classifier.classify('Tell me about your', 'User: Hello\nAgent: Hi there!');
|
|
36
|
+
* // → 'INCOMPLETE' (sentence is unfinished)
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export class TurnCompletenessClassifier {
|
|
40
|
+
llmCall;
|
|
41
|
+
timeoutMs;
|
|
42
|
+
/**
|
|
43
|
+
* LRU result cache. Keys are the first 100 chars of the transcript; values
|
|
44
|
+
* are the cached classification. `Map` preserves insertion order, so
|
|
45
|
+
* eviction iterates from the oldest entry (`.keys().next().value`).
|
|
46
|
+
*/
|
|
47
|
+
cache = new Map();
|
|
48
|
+
/**
|
|
49
|
+
* Maximum number of entries to keep in {@link cache} before evicting the
|
|
50
|
+
* oldest one.
|
|
51
|
+
*/
|
|
52
|
+
maxCacheSize = 100;
|
|
53
|
+
/**
|
|
54
|
+
* @param llmCall — Async function that sends a prompt string to an LLM and
|
|
55
|
+
* resolves with the raw response text.
|
|
56
|
+
* @param timeoutMs — Maximum milliseconds to wait for the LLM before
|
|
57
|
+
* returning `TIMEOUT`. Defaults to 500 ms.
|
|
58
|
+
*/
|
|
59
|
+
constructor(llmCall, timeoutMs = 500) {
|
|
60
|
+
this.llmCall = llmCall;
|
|
61
|
+
this.timeoutMs = timeoutMs;
|
|
62
|
+
}
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Public API
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
/**
|
|
67
|
+
* Classify `transcript` as `COMPLETE`, `INCOMPLETE`, or `TIMEOUT`.
|
|
68
|
+
*
|
|
69
|
+
* The result is cached on the first 100 characters of `transcript` so that
|
|
70
|
+
* repeated short phrases (common in voice interaction) never hit the LLM
|
|
71
|
+
* twice.
|
|
72
|
+
*
|
|
73
|
+
* @param transcript — The utterance text to evaluate.
|
|
74
|
+
* @param context — Optional string containing the last 2 conversation turns,
|
|
75
|
+
* formatted as `"Speaker: text\n..."`. When omitted, the
|
|
76
|
+
* prompt uses `'N/A'`.
|
|
77
|
+
* @returns A {@link ClassifyResult} resolving to `COMPLETE`, `INCOMPLETE`,
|
|
78
|
+
* or `TIMEOUT`.
|
|
79
|
+
*/
|
|
80
|
+
async classify(transcript, context) {
|
|
81
|
+
// -----------------------------------------------------------------------
|
|
82
|
+
// Cache lookup
|
|
83
|
+
// -----------------------------------------------------------------------
|
|
84
|
+
const cacheKey = transcript.slice(0, 100);
|
|
85
|
+
if (this.cache.has(cacheKey)) {
|
|
86
|
+
return this.cache.get(cacheKey);
|
|
87
|
+
}
|
|
88
|
+
// -----------------------------------------------------------------------
|
|
89
|
+
// Build prompt
|
|
90
|
+
// -----------------------------------------------------------------------
|
|
91
|
+
const prompt = `Is the following utterance a complete thought that expects a response, ` +
|
|
92
|
+
`or is the speaker likely to continue?\n\n` +
|
|
93
|
+
`Context (last 2 turns):\n${context ?? 'N/A'}\n\n` +
|
|
94
|
+
`Current utterance: "${transcript}"\n\n` +
|
|
95
|
+
`Respond with exactly COMPLETE or INCOMPLETE, then one sentence of reasoning.`;
|
|
96
|
+
// -----------------------------------------------------------------------
|
|
97
|
+
// LLM call with timeout race
|
|
98
|
+
// -----------------------------------------------------------------------
|
|
99
|
+
let rawResult;
|
|
100
|
+
try {
|
|
101
|
+
rawResult = await Promise.race([
|
|
102
|
+
this.llmCall(prompt),
|
|
103
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), this.timeoutMs)),
|
|
104
|
+
]);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Either the LLM threw or the timeout won — return TIMEOUT without
|
|
108
|
+
// caching so the next call re-attempts the LLM.
|
|
109
|
+
return 'TIMEOUT';
|
|
110
|
+
}
|
|
111
|
+
// -----------------------------------------------------------------------
|
|
112
|
+
// Parse response — only the first word matters
|
|
113
|
+
// -----------------------------------------------------------------------
|
|
114
|
+
const classification = rawResult.trim().startsWith('COMPLETE')
|
|
115
|
+
? 'COMPLETE'
|
|
116
|
+
: 'INCOMPLETE';
|
|
117
|
+
// -----------------------------------------------------------------------
|
|
118
|
+
// LRU eviction + cache write
|
|
119
|
+
// -----------------------------------------------------------------------
|
|
120
|
+
if (this.cache.size >= this.maxCacheSize) {
|
|
121
|
+
// Map.keys() returns an iterator in insertion order; the first key is the
|
|
122
|
+
// oldest entry.
|
|
123
|
+
const firstKey = this.cache.keys().next().value;
|
|
124
|
+
this.cache.delete(firstKey);
|
|
125
|
+
}
|
|
126
|
+
this.cache.set(cacheKey, classification);
|
|
127
|
+
return classification;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=TurnCompletenessClassifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TurnCompletenessClassifier.js","sourceRoot":"","sources":["../src/TurnCompletenessClassifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgBH,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,OAAO,0BAA0B;IAqBlB;IACA;IArBnB;;;;OAIG;IACc,KAAK,GAAG,IAAI,GAAG,EAAqC,CAAC;IAEtE;;;OAGG;IACc,YAAY,GAAG,GAAG,CAAC;IAEpC;;;;;OAKG;IACH,YACmB,OAA4C,EAC5C,YAAoB,GAAG;QADvB,YAAO,GAAP,OAAO,CAAqC;QAC5C,cAAS,GAAT,SAAS,CAAc;IACvC,CAAC;IAEJ,8EAA8E;IAC9E,aAAa;IACb,8EAA8E;IAE9E;;;;;;;;;;;;;OAaG;IACH,KAAK,CAAC,QAAQ,CAAC,UAAkB,EAAE,OAAgB;QACjD,0EAA0E;QAC1E,eAAe;QACf,0EAA0E;QAC1E,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAE1C,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAE,CAAC;QACnC,CAAC;QAED,0EAA0E;QAC1E,eAAe;QACf,0EAA0E;QAC1E,MAAM,MAAM,GACV,yEAAyE;YACzE,2CAA2C;YAC3C,4BAA4B,OAAO,IAAI,KAAK,MAAM;YAClD,uBAAuB,UAAU,OAAO;YACxC,8EAA8E,CAAC;QAEjF,0EAA0E;QAC1E,6BAA6B;QAC7B,0EAA0E;QAC1E,IAAI,SAAiB,CAAC;QAEtB,IAAI,CAAC;YACH,SAAS,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;gBAC7B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;gBACpB,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAC/B,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAC/D;aACF,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,mEAAmE;YACnE,gDAAgD;YAChD,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,0EAA0E;QAC1E,+CAA+C;QAC/C,0EAA0E;QAC1E,MAAM,cAAc,GAA8B,SAAS,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;YACvF,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,YAAY,CAAC;QAEjB,0EAA0E;QAC1E,6BAA6B;QAC7B,0EAA0E;QAC1E,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACzC,0EAA0E;YAC1E,gBAAgB;YAChB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAe,CAAC;YAC1D,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC9B,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;QAEzC,OAAO,cAAc,CAAC;IACxB,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file index.ts
|
|
3
|
+
* @description Pack factory for the Semantic Endpoint Detector extension pack.
|
|
4
|
+
*
|
|
5
|
+
* ### Usage (direct)
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { createSemanticEndpointDetector } from '@framers/agentos-ext-endpoint-semantic';
|
|
8
|
+
*
|
|
9
|
+
* const detector = createSemanticEndpointDetector(
|
|
10
|
+
* async (prompt) => myLlm(prompt),
|
|
11
|
+
* { timeoutMs: 400, minSilenceBeforeCheckMs: 600 }
|
|
12
|
+
* );
|
|
13
|
+
* detector.on('turn_complete', (evt) => console.log('Turn done:', evt.reason));
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* ### Usage (manifest-driven)
|
|
17
|
+
* ```json
|
|
18
|
+
* { "packs": [{ "module": "@framers/agentos-ext-endpoint-semantic" }] }
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @module endpoint-semantic
|
|
22
|
+
*/
|
|
23
|
+
import { SemanticEndpointDetector } from './SemanticEndpointDetector.js';
|
|
24
|
+
import type { SemanticEndpointConfig } from './types.js';
|
|
25
|
+
/** Subset of ExtensionDescriptor required by this pack. */
|
|
26
|
+
interface ExtensionDescriptor {
|
|
27
|
+
id: string;
|
|
28
|
+
kind: string;
|
|
29
|
+
payload: unknown;
|
|
30
|
+
enableByDefault?: boolean;
|
|
31
|
+
metadata?: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
/** Subset of ExtensionPack required by this pack. */
|
|
34
|
+
interface ExtensionPack {
|
|
35
|
+
id: string;
|
|
36
|
+
descriptors: ExtensionDescriptor[];
|
|
37
|
+
}
|
|
38
|
+
/** Subset of ExtensionPackContext required by this pack. */
|
|
39
|
+
interface ExtensionPackContext {
|
|
40
|
+
getSecret?: (id: string) => string | undefined;
|
|
41
|
+
options?: Record<string, unknown>;
|
|
42
|
+
/** Runtime LLM call function supplied by the AgentOS extension manager. */
|
|
43
|
+
llmCall?: (prompt: string) => Promise<string>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Create a standalone {@link SemanticEndpointDetector} instance.
|
|
47
|
+
*
|
|
48
|
+
* Use this when composing the detector programmatically outside of the
|
|
49
|
+
* AgentOS extension system.
|
|
50
|
+
*
|
|
51
|
+
* @param llmCall — Async function that sends a prompt string to an LLM and
|
|
52
|
+
* resolves with the raw response text.
|
|
53
|
+
* @param config — Optional configuration overrides.
|
|
54
|
+
* @returns A configured {@link SemanticEndpointDetector}.
|
|
55
|
+
*/
|
|
56
|
+
export declare function createSemanticEndpointDetector(llmCall: (prompt: string) => Promise<string>, config?: SemanticEndpointConfig): SemanticEndpointDetector;
|
|
57
|
+
/**
|
|
58
|
+
* AgentOS manifest factory function.
|
|
59
|
+
*
|
|
60
|
+
* Returns an {@link ExtensionPack} containing a single `endpoint-detector`
|
|
61
|
+
* descriptor backed by {@link SemanticEndpointDetector}.
|
|
62
|
+
*
|
|
63
|
+
* The `llmCall` function must be provided via `context.llmCall`. When it is
|
|
64
|
+
* absent a no-op stub is used (the detector will always time out and fall back
|
|
65
|
+
* to silence-timeout behaviour).
|
|
66
|
+
*
|
|
67
|
+
* @param context — Pack context supplied by the extension manager.
|
|
68
|
+
* @returns A fully configured {@link ExtensionPack}.
|
|
69
|
+
*/
|
|
70
|
+
export declare function createExtensionPack(context: ExtensionPackContext): ExtensionPack;
|
|
71
|
+
export * from './types.js';
|
|
72
|
+
export { SemanticEndpointDetector } from './SemanticEndpointDetector.js';
|
|
73
|
+
export { TurnCompletenessClassifier } from './TurnCompletenessClassifier.js';
|
|
74
|
+
export type { ClassifyResult } from './TurnCompletenessClassifier.js';
|
|
75
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAMzD,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;IAClC,2EAA2E;IAC3E,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAC/C;AASD;;;;;;;;;;GAUG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,EAC5C,MAAM,CAAC,EAAE,sBAAsB,GAC9B,wBAAwB,CAE1B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CAwBhF;AAMD,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,YAAY,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file index.ts
|
|
3
|
+
* @description Pack factory for the Semantic Endpoint Detector extension pack.
|
|
4
|
+
*
|
|
5
|
+
* ### Usage (direct)
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { createSemanticEndpointDetector } from '@framers/agentos-ext-endpoint-semantic';
|
|
8
|
+
*
|
|
9
|
+
* const detector = createSemanticEndpointDetector(
|
|
10
|
+
* async (prompt) => myLlm(prompt),
|
|
11
|
+
* { timeoutMs: 400, minSilenceBeforeCheckMs: 600 }
|
|
12
|
+
* );
|
|
13
|
+
* detector.on('turn_complete', (evt) => console.log('Turn done:', evt.reason));
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* ### Usage (manifest-driven)
|
|
17
|
+
* ```json
|
|
18
|
+
* { "packs": [{ "module": "@framers/agentos-ext-endpoint-semantic" }] }
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @module endpoint-semantic
|
|
22
|
+
*/
|
|
23
|
+
import { SemanticEndpointDetector } from './SemanticEndpointDetector.js';
|
|
24
|
+
/** Kind constant matching packages/agentos/src/extensions/types.ts. */
|
|
25
|
+
const EXTENSION_KIND_ENDPOINT_DETECTOR = 'endpoint-detector';
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Factory helpers
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
/**
|
|
30
|
+
* Create a standalone {@link SemanticEndpointDetector} instance.
|
|
31
|
+
*
|
|
32
|
+
* Use this when composing the detector programmatically outside of the
|
|
33
|
+
* AgentOS extension system.
|
|
34
|
+
*
|
|
35
|
+
* @param llmCall — Async function that sends a prompt string to an LLM and
|
|
36
|
+
* resolves with the raw response text.
|
|
37
|
+
* @param config — Optional configuration overrides.
|
|
38
|
+
* @returns A configured {@link SemanticEndpointDetector}.
|
|
39
|
+
*/
|
|
40
|
+
export function createSemanticEndpointDetector(llmCall, config) {
|
|
41
|
+
return new SemanticEndpointDetector(llmCall, config);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* AgentOS manifest factory function.
|
|
45
|
+
*
|
|
46
|
+
* Returns an {@link ExtensionPack} containing a single `endpoint-detector`
|
|
47
|
+
* descriptor backed by {@link SemanticEndpointDetector}.
|
|
48
|
+
*
|
|
49
|
+
* The `llmCall` function must be provided via `context.llmCall`. When it is
|
|
50
|
+
* absent a no-op stub is used (the detector will always time out and fall back
|
|
51
|
+
* to silence-timeout behaviour).
|
|
52
|
+
*
|
|
53
|
+
* @param context — Pack context supplied by the extension manager.
|
|
54
|
+
* @returns A fully configured {@link ExtensionPack}.
|
|
55
|
+
*/
|
|
56
|
+
export function createExtensionPack(context) {
|
|
57
|
+
const llmCall = context.llmCall ??
|
|
58
|
+
// Stub: always resolves to INCOMPLETE so the silence timeout takes over.
|
|
59
|
+
(() => Promise.resolve('INCOMPLETE (no llmCall provided)'));
|
|
60
|
+
const config = {
|
|
61
|
+
...context.options,
|
|
62
|
+
};
|
|
63
|
+
const detector = new SemanticEndpointDetector(llmCall, config);
|
|
64
|
+
return {
|
|
65
|
+
id: 'endpoint-semantic',
|
|
66
|
+
descriptors: [
|
|
67
|
+
{
|
|
68
|
+
id: 'endpoint-detector-semantic',
|
|
69
|
+
kind: EXTENSION_KIND_ENDPOINT_DETECTOR,
|
|
70
|
+
payload: detector,
|
|
71
|
+
enableByDefault: true,
|
|
72
|
+
metadata: { mode: 'semantic' },
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// Re-exports
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
export * from './types.js';
|
|
81
|
+
export { SemanticEndpointDetector } from './SemanticEndpointDetector.js';
|
|
82
|
+
export { TurnCompletenessClassifier } from './TurnCompletenessClassifier.js';
|
|
83
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AA8BzE,uEAAuE;AACvE,MAAM,gCAAgC,GAAG,mBAAmB,CAAC;AAE7D,8EAA8E;AAC9E,kBAAkB;AAClB,8EAA8E;AAE9E;;;;;;;;;;GAUG;AACH,MAAM,UAAU,8BAA8B,CAC5C,OAA4C,EAC5C,MAA+B;IAE/B,OAAO,IAAI,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AACvD,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAA6B;IAC/D,MAAM,OAAO,GACX,OAAO,CAAC,OAAO;QACf,yEAAyE;QACzE,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,kCAAkC,CAAC,CAAC,CAAC;IAE9D,MAAM,MAAM,GAA2B;QACrC,GAAI,OAAO,CAAC,OAA8C;KAC3D,CAAC;IAEF,MAAM,QAAQ,GAAG,IAAI,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAE/D,OAAO;QACL,EAAE,EAAE,mBAAmB;QACvB,WAAW,EAAE;YACX;gBACE,EAAE,EAAE,4BAA4B;gBAChC,IAAI,EAAE,gCAAgC;gBACtC,OAAO,EAAE,QAAQ;gBACjB,eAAe,EAAE,IAAI;gBACrB,QAAQ,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE;aAC/B;SACF;KACF,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file types.ts
|
|
3
|
+
* @description Public configuration types for the Semantic Endpoint Detector
|
|
4
|
+
* extension pack.
|
|
5
|
+
*
|
|
6
|
+
* @module endpoint-semantic/types
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Configuration options for the {@link SemanticEndpointDetector}.
|
|
10
|
+
*
|
|
11
|
+
* All fields are optional — reasonable defaults are applied when omitted.
|
|
12
|
+
*/
|
|
13
|
+
export interface SemanticEndpointConfig {
|
|
14
|
+
/**
|
|
15
|
+
* LLM model identifier forwarded to the runtime LLM provider when performing
|
|
16
|
+
* turn-completeness classification. The exact string is passed through
|
|
17
|
+
* verbatim; its meaning is determined by whichever provider the caller
|
|
18
|
+
* supplies via the `llmCall` constructor argument.
|
|
19
|
+
*
|
|
20
|
+
* @example 'gpt-4o-mini'
|
|
21
|
+
* @example 'claude-haiku-3-5'
|
|
22
|
+
*/
|
|
23
|
+
model?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Maximum number of milliseconds to wait for the LLM to return a
|
|
26
|
+
* classification before treating the result as a {@link ClassifyResult.TIMEOUT}.
|
|
27
|
+
* On timeout the detector falls back to the normal silence timeout behaviour.
|
|
28
|
+
*
|
|
29
|
+
* @defaultValue 500
|
|
30
|
+
*/
|
|
31
|
+
timeoutMs?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Duration of silence (in ms) after a `speech_end` VAD event before the
|
|
34
|
+
* LLM classifier is invoked. Introducing a small delay avoids calling the
|
|
35
|
+
* LLM on micro-pauses that are immediately followed by resumed speech.
|
|
36
|
+
*
|
|
37
|
+
* @defaultValue 500
|
|
38
|
+
*/
|
|
39
|
+
minSilenceBeforeCheckMs?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Duration (in ms) of silence after `speech_end` before the detector emits
|
|
42
|
+
* `turn_complete` with reason `'silence_timeout'` regardless of classifier
|
|
43
|
+
* output. Acts as a hard fallback when the LLM returns `INCOMPLETE` or times
|
|
44
|
+
* out.
|
|
45
|
+
*
|
|
46
|
+
* @defaultValue 1500
|
|
47
|
+
*/
|
|
48
|
+
silenceTimeoutMs?: number;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;;;OAMG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAEjC;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
package/manifest.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@framers/agentos-ext-endpoint-semantic",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Semantic endpoint detector with LLM turn-completeness classification",
|
|
5
|
+
"kind": "endpoint-detector",
|
|
6
|
+
"extensionId": "endpoint-semantic",
|
|
7
|
+
"entryPoint": "./dist/index.js"
|
|
8
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@framers/agentos-ext-endpoint-semantic",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Semantic endpoint detection extension pack for AgentOS voice pipeline — LLM-based turn-completeness classification",
|
|
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
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"typescript": "^5.5.0",
|
|
25
|
+
"vitest": "^1.6.0",
|
|
26
|
+
"@framers/agentos": "0.1.94"
|
|
27
|
+
},
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"author": "Frame.dev",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/framersai/agentos-extensions.git",
|
|
33
|
+
"directory": "registry/curated/voice/endpoint-semantic"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -p tsconfig.json",
|
|
40
|
+
"test": "vitest run"
|
|
41
|
+
}
|
|
42
|
+
}
|