@ai-ecoverse/pi-audio 1.9.0 → 1.10.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/README.md +2 -0
- package/dist/durable/index.js +45 -0
- package/dist/durable/index.js.map +7 -0
- package/dist/types/durable/index.d.ts +8 -0
- package/package.json +13 -2
package/README.md
CHANGED
|
@@ -21,6 +21,8 @@ addAudioCounters(total, usage);
|
|
|
21
21
|
|
|
22
22
|
pi-audio does not apply that patch. Hosts such as slicc-agent record monkeypatches in `patches/patches.json`.
|
|
23
23
|
|
|
24
|
+
`@ai-ecoverse/pi-audio/durable` appends a `voice.truncate` entry for a `VoiceCut`. The entry has no model messages. Its `edits` replace the target assistant message with the text heard so far and keep tool calls. The stored entry stays intact, and pi-durable derives the shorter message for the next model request. `@earendil-works/pi-durable` is an optional peer (`^1.1.0`); the root package does not import it. `VoiceCut.entryId` is the decimal string of pi-durable's numeric entry id.
|
|
25
|
+
|
|
24
26
|
`@ai-ecoverse/pi-audio/openai` registers OpenAI and OpenRouter speech adapters when `registerOpenAI()` is called. Batch transcription posts multipart audio to `/audio/transcriptions`. OpenRouter (`provider` `openrouter`, or a base URL on `openrouter.ai`) posts JSON `input_audio` instead. Speech posts JSON to `/audio/speech` and reads the raw audio response. OpenAI can return pcm, mp3, wav, opus, or flac. OpenRouter returns pcm or mp3. Realtime transcription uses a WebSocket at `/realtime?intent=transcription` for `gpt-realtime-whisper` and `gpt-live-transcribe`. The catalog key is type, provider, and id, so the batch model keeps the id `gpt-4o-transcribe`.
|
|
25
27
|
|
|
26
28
|
`convertAudio` and `convertAudioSource` convert between `AudioFormat`s: PCM16, float32, G.711 μ-law and A-law, mono and stereo, sample rate, and PCM16 WAV. Opus, MP3 and FLAC pass through only when the format does not change. `encodeAudioBase64` and `decodeAudioBase64` are the wire helpers for JSON protocols. `AudioChunk.data` stays binary. The same converter runs in an AudioWorklet from `@ai-ecoverse/pi-audio/worklet` (`AUDIO_CONVERT_PROCESSOR`, `registerAudioConvertWorklet`). Adapters declare `inputFormats` and `outputFormats` and do not resample on their own.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
function entryIdOf(entryId) {
|
|
2
|
+
if (!/^\d+$/.test(entryId)) {
|
|
3
|
+
throw new Error(`Voice cut entry is not a durable id: ${entryId}`);
|
|
4
|
+
}
|
|
5
|
+
return Number(entryId);
|
|
6
|
+
}
|
|
7
|
+
function keptBlock(block, index, cut) {
|
|
8
|
+
if (block.type === "toolCall" || block.type === "thinking") return block;
|
|
9
|
+
if (index < cut.contentIndex) return block;
|
|
10
|
+
if (index > cut.contentIndex) return void 0;
|
|
11
|
+
const heard = block.text.slice(0, cut.heardUpTo);
|
|
12
|
+
if (heard.length === 0) return void 0;
|
|
13
|
+
if (heard === block.text) return block;
|
|
14
|
+
return { ...block, text: heard };
|
|
15
|
+
}
|
|
16
|
+
function cutAssistantMessage(message, cut) {
|
|
17
|
+
const content = [];
|
|
18
|
+
for (const [index, block] of message.content.entries()) {
|
|
19
|
+
const kept = keptBlock(block, index, cut);
|
|
20
|
+
if (kept === void 0) continue;
|
|
21
|
+
content.push(kept);
|
|
22
|
+
}
|
|
23
|
+
return { ...message, content };
|
|
24
|
+
}
|
|
25
|
+
async function appendVoiceTruncate(session, conversationId, cut, message, context) {
|
|
26
|
+
const replacement = cutAssistantMessage(message, cut);
|
|
27
|
+
const target = entryIdOf(cut.entryId);
|
|
28
|
+
return session.commit(
|
|
29
|
+
(tx) => tx.appendEntry(conversationId, {
|
|
30
|
+
kind: "voice.truncate",
|
|
31
|
+
data: {
|
|
32
|
+
entryId: cut.entryId,
|
|
33
|
+
contentIndex: cut.contentIndex,
|
|
34
|
+
heardUpTo: cut.heardUpTo,
|
|
35
|
+
heardMs: cut.heardMs
|
|
36
|
+
},
|
|
37
|
+
edits: [{ target, action: "replace", messages: [replacement] }]
|
|
38
|
+
}),
|
|
39
|
+
context
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
appendVoiceTruncate
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/durable/index.ts"],
|
|
4
|
+
"sourcesContent": ["import type { AssistantMessage } from '@earendil-works/pi-ai';\nimport type { ConversationId, EntryId, EntryRecord, Tx } from '@earendil-works/pi-durable';\nimport type { VoiceCut } from '../turns.ts';\n\ntype Block = AssistantMessage['content'][number];\n\ninterface TruncateSession<C> {\n commit<T>(change: (tx: Tx) => T | Promise<T>, context: C): Promise<T>;\n}\n\nfunction entryIdOf(entryId: string): EntryId {\n if (!/^\\d+$/.test(entryId)) {\n throw new Error(`Voice cut entry is not a durable id: ${entryId}`);\n }\n return Number(entryId) as EntryId;\n}\n\nfunction keptBlock(block: Block, index: number, cut: VoiceCut): Block | undefined {\n if (block.type === 'toolCall' || block.type === 'thinking') return block;\n if (index < cut.contentIndex) return block;\n if (index > cut.contentIndex) return undefined;\n const heard = block.text.slice(0, cut.heardUpTo);\n if (heard.length === 0) return undefined;\n if (heard === block.text) return block;\n return { ...block, text: heard };\n}\n\nfunction cutAssistantMessage(message: AssistantMessage, cut: VoiceCut): AssistantMessage {\n const content: Block[] = [];\n for (const [index, block] of message.content.entries()) {\n const kept = keptBlock(block, index, cut);\n if (kept === undefined) continue;\n content.push(kept);\n }\n return { ...message, content };\n}\n\nexport async function appendVoiceTruncate<C>(\n session: TruncateSession<C>,\n conversationId: ConversationId,\n cut: VoiceCut,\n message: AssistantMessage,\n context: C\n): Promise<EntryRecord> {\n const replacement = cutAssistantMessage(message, cut);\n const target = entryIdOf(cut.entryId);\n return session.commit(\n (tx) =>\n tx.appendEntry(conversationId, {\n kind: 'voice.truncate',\n data: {\n entryId: cut.entryId,\n contentIndex: cut.contentIndex,\n heardUpTo: cut.heardUpTo,\n heardMs: cut.heardMs,\n },\n edits: [{ target, action: 'replace', messages: [replacement] }],\n }),\n context\n );\n}\n"],
|
|
5
|
+
"mappings": "AAUA,SAAS,UAAU,SAA0B;AAC3C,MAAI,CAAC,QAAQ,KAAK,OAAO,GAAG;AAC1B,UAAM,IAAI,MAAM,wCAAwC,OAAO,EAAE;AAAA,EACnE;AACA,SAAO,OAAO,OAAO;AACvB;AAEA,SAAS,UAAU,OAAc,OAAe,KAAkC;AAChF,MAAI,MAAM,SAAS,cAAc,MAAM,SAAS,WAAY,QAAO;AACnE,MAAI,QAAQ,IAAI,aAAc,QAAO;AACrC,MAAI,QAAQ,IAAI,aAAc,QAAO;AACrC,QAAM,QAAQ,MAAM,KAAK,MAAM,GAAG,IAAI,SAAS;AAC/C,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,MAAI,UAAU,MAAM,KAAM,QAAO;AACjC,SAAO,EAAE,GAAG,OAAO,MAAM,MAAM;AACjC;AAEA,SAAS,oBAAoB,SAA2B,KAAiC;AACvF,QAAM,UAAmB,CAAC;AAC1B,aAAW,CAAC,OAAO,KAAK,KAAK,QAAQ,QAAQ,QAAQ,GAAG;AACtD,UAAM,OAAO,UAAU,OAAO,OAAO,GAAG;AACxC,QAAI,SAAS,OAAW;AACxB,YAAQ,KAAK,IAAI;AAAA,EACnB;AACA,SAAO,EAAE,GAAG,SAAS,QAAQ;AAC/B;AAEA,eAAsB,oBACpB,SACA,gBACA,KACA,SACA,SACsB;AACtB,QAAM,cAAc,oBAAoB,SAAS,GAAG;AACpD,QAAM,SAAS,UAAU,IAAI,OAAO;AACpC,SAAO,QAAQ;AAAA,IACb,CAAC,OACC,GAAG,YAAY,gBAAgB;AAAA,MAC7B,MAAM;AAAA,MACN,MAAM;AAAA,QACJ,SAAS,IAAI;AAAA,QACb,cAAc,IAAI;AAAA,QAClB,WAAW,IAAI;AAAA,QACf,SAAS,IAAI;AAAA,MACf;AAAA,MACA,OAAO,CAAC,EAAE,QAAQ,QAAQ,WAAW,UAAU,CAAC,WAAW,EAAE,CAAC;AAAA,IAChE,CAAC;AAAA,IACH;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AssistantMessage } from '@earendil-works/pi-ai';
|
|
2
|
+
import type { ConversationId, EntryRecord, Tx } from '@earendil-works/pi-durable';
|
|
3
|
+
import type { VoiceCut } from '../turns.ts';
|
|
4
|
+
interface TruncateSession<C> {
|
|
5
|
+
commit<T>(change: (tx: Tx) => T | Promise<T>, context: C): Promise<T>;
|
|
6
|
+
}
|
|
7
|
+
export declare function appendVoiceTruncate<C>(session: TruncateSession<C>, conversationId: ConversationId, cut: VoiceCut, message: AssistantMessage, context: C): Promise<EntryRecord>;
|
|
8
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/pi-audio",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "Voice bindings for pi: speech-to-text, text-to-speech and realtime speech for pi-coding-agent plugins and slicc-agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -45,6 +45,10 @@
|
|
|
45
45
|
"./xai": {
|
|
46
46
|
"types": "./dist/types/xai/index.d.ts",
|
|
47
47
|
"default": "./dist/xai/index.js"
|
|
48
|
+
},
|
|
49
|
+
"./durable": {
|
|
50
|
+
"types": "./dist/types/durable/index.d.ts",
|
|
51
|
+
"default": "./dist/durable/index.js"
|
|
48
52
|
}
|
|
49
53
|
},
|
|
50
54
|
"files": [
|
|
@@ -68,6 +72,7 @@
|
|
|
68
72
|
"@ai-ecoverse/slicc-shared-web": "1.21.1",
|
|
69
73
|
"@biomejs/biome": "2.5.15",
|
|
70
74
|
"@earendil-works/pi-ai": "1.1.0",
|
|
75
|
+
"@earendil-works/pi-durable": "1.1.0",
|
|
71
76
|
"@types/node": "24.19.1",
|
|
72
77
|
"esbuild": "0.28.2",
|
|
73
78
|
"lefthook": "2.2.1",
|
|
@@ -76,6 +81,12 @@
|
|
|
76
81
|
"typescript": "7.0.2"
|
|
77
82
|
},
|
|
78
83
|
"peerDependencies": {
|
|
79
|
-
"@earendil-works/pi-ai": "1.1.0"
|
|
84
|
+
"@earendil-works/pi-ai": "1.1.0",
|
|
85
|
+
"@earendil-works/pi-durable": "^1.1.0"
|
|
86
|
+
},
|
|
87
|
+
"peerDependenciesMeta": {
|
|
88
|
+
"@earendil-works/pi-durable": {
|
|
89
|
+
"optional": true
|
|
90
|
+
}
|
|
80
91
|
}
|
|
81
92
|
}
|