pi-harness-runtime 1.1.337 → 1.1.339
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/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-sink/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/relay/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/voice-api/dist/index.d.ts +14 -0
- package/packages/voice-api/dist/index.js +12 -0
- package/packages/voice-api/dist/line.d.ts +100 -0
- package/packages/voice-api/dist/line.js +147 -0
- package/packages/voice-api/dist/provider.d.ts +23 -0
- package/packages/voice-api/dist/provider.js +154 -0
- package/packages/voice-api/dist/telegram.d.ts +74 -0
- package/packages/voice-api/dist/telegram.js +119 -0
- package/packages/voice-api/dist/transcriber.d.ts +48 -0
- package/packages/voice-api/dist/transcriber.js +118 -0
- package/packages/voice-api/dist/types.d.ts +75 -0
- package/packages/voice-api/dist/types.js +4 -0
- package/packages/voice-api/package.json +24 -0
- package/packages/voice-api/src/index.ts +15 -0
- package/packages/voice-api/src/line.ts +229 -0
- package/packages/voice-api/src/provider.ts +186 -0
- package/packages/voice-api/src/telegram.ts +191 -0
- package/packages/voice-api/src/transcriber.ts +136 -0
- package/packages/voice-api/src/types.ts +79 -0
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
- package/scripts/voice-api-example.ts +112 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whisper Provider - Abstract interface for speech-to-text
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Create OpenAI Whisper provider
|
|
6
|
+
*/
|
|
7
|
+
export function createOpenAIProvider(apiKey) {
|
|
8
|
+
return new OpenAIProvider(apiKey);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Create Replicate Whisper provider
|
|
12
|
+
*/
|
|
13
|
+
export function createReplicateProvider(apiToken) {
|
|
14
|
+
return new ReplicateProvider(apiToken);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* OpenAI Whisper Provider
|
|
18
|
+
*/
|
|
19
|
+
class OpenAIProvider {
|
|
20
|
+
name = 'openai';
|
|
21
|
+
apiKey;
|
|
22
|
+
constructor(apiKey) {
|
|
23
|
+
this.apiKey = apiKey;
|
|
24
|
+
}
|
|
25
|
+
isConfigured() {
|
|
26
|
+
return Boolean(this.apiKey);
|
|
27
|
+
}
|
|
28
|
+
async transcribe(options) {
|
|
29
|
+
const startTime = Date.now();
|
|
30
|
+
try {
|
|
31
|
+
// Dynamic import to avoid hard dependency
|
|
32
|
+
const OpenAI = (await import('openai')).default;
|
|
33
|
+
const openai = new OpenAI({ apiKey: this.apiKey });
|
|
34
|
+
let audioData;
|
|
35
|
+
if (options.audioBuffer) {
|
|
36
|
+
audioData = Buffer.from(options.audioBuffer);
|
|
37
|
+
}
|
|
38
|
+
else if (options.audioUrl) {
|
|
39
|
+
// Fetch audio from URL
|
|
40
|
+
const response = await fetch(options.audioUrl);
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
throw new Error(`Failed to fetch audio: ${response.status}`);
|
|
43
|
+
}
|
|
44
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
45
|
+
audioData = Buffer.from(arrayBuffer);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
throw new Error('No audio data provided');
|
|
49
|
+
}
|
|
50
|
+
const result = await openai.audio.transcriptions.create({
|
|
51
|
+
file: audioData,
|
|
52
|
+
model: options.model || 'whisper-1',
|
|
53
|
+
language: options.language,
|
|
54
|
+
response_format: 'verbose_json',
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
text: result.text,
|
|
58
|
+
language: result.language || options.language,
|
|
59
|
+
duration: result.duration,
|
|
60
|
+
timestamp: new Date().toISOString(),
|
|
61
|
+
provider: 'openai',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
return {
|
|
66
|
+
text: '',
|
|
67
|
+
timestamp: new Date().toISOString(),
|
|
68
|
+
provider: 'openai',
|
|
69
|
+
error: error instanceof Error ? error.message : String(error),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Replicate Whisper Provider
|
|
76
|
+
*/
|
|
77
|
+
class ReplicateProvider {
|
|
78
|
+
name = 'replicate';
|
|
79
|
+
apiToken;
|
|
80
|
+
constructor(apiToken) {
|
|
81
|
+
this.apiToken = apiToken;
|
|
82
|
+
}
|
|
83
|
+
isConfigured() {
|
|
84
|
+
return Boolean(this.apiToken);
|
|
85
|
+
}
|
|
86
|
+
async transcribe(options) {
|
|
87
|
+
const startTime = Date.now();
|
|
88
|
+
try {
|
|
89
|
+
// Get audio data
|
|
90
|
+
let audioData;
|
|
91
|
+
if (options.audioBuffer) {
|
|
92
|
+
audioData = options.audioBuffer;
|
|
93
|
+
}
|
|
94
|
+
else if (options.audioUrl) {
|
|
95
|
+
const response = await fetch(options.audioUrl);
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
throw new Error(`Failed to fetch audio: ${response.status}`);
|
|
98
|
+
}
|
|
99
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
100
|
+
audioData = new Uint8Array(arrayBuffer);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
throw new Error('No audio data provided');
|
|
104
|
+
}
|
|
105
|
+
// Convert to base64 for Replicate
|
|
106
|
+
const base64 = btoa(String.fromCharCode(...audioData));
|
|
107
|
+
const dataUrl = `data:audio/wav;base64,${base64}`;
|
|
108
|
+
// Call Replicate API
|
|
109
|
+
const response = await fetch('https://api.replicate.com/v1/predictions', {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: {
|
|
112
|
+
'Authorization': `Token ${this.apiToken}`,
|
|
113
|
+
'Content-Type': 'application/json',
|
|
114
|
+
},
|
|
115
|
+
body: JSON.stringify({
|
|
116
|
+
version: 'e39e7d2f3c2b7a7d8c9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b',
|
|
117
|
+
input: {
|
|
118
|
+
audio: dataUrl,
|
|
119
|
+
model: options.model || 'large-v3',
|
|
120
|
+
},
|
|
121
|
+
}),
|
|
122
|
+
});
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
throw new Error(`Replicate API error: ${response.status}`);
|
|
125
|
+
}
|
|
126
|
+
const prediction = await response.json();
|
|
127
|
+
// Poll for completion
|
|
128
|
+
let result = prediction;
|
|
129
|
+
while (result.status !== 'succeeded' && result.status !== 'failed') {
|
|
130
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
131
|
+
const pollResponse = await fetch(`https://api.replicate.com/v1/predictions/${result.id}`, {
|
|
132
|
+
headers: { 'Authorization': `Token ${this.apiToken}` },
|
|
133
|
+
});
|
|
134
|
+
result = await pollResponse.json();
|
|
135
|
+
}
|
|
136
|
+
if (result.status === 'failed') {
|
|
137
|
+
throw new Error(result.error || 'Transcription failed');
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
text: result.output,
|
|
141
|
+
timestamp: new Date().toISOString(),
|
|
142
|
+
provider: 'replicate',
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
return {
|
|
147
|
+
text: '',
|
|
148
|
+
timestamp: new Date().toISOString(),
|
|
149
|
+
provider: 'replicate',
|
|
150
|
+
error: error instanceof Error ? error.message : String(error),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Voice Handler
|
|
3
|
+
*
|
|
4
|
+
* Handles voice messages from Telegram bots.
|
|
5
|
+
*/
|
|
6
|
+
import type { TranscriptionResult } from './types.js';
|
|
7
|
+
import { VoiceTranscriber } from './transcriber.js';
|
|
8
|
+
/**
|
|
9
|
+
* Telegram voice message from bot update
|
|
10
|
+
*/
|
|
11
|
+
export interface TelegramVoiceUpdate {
|
|
12
|
+
update_id: number;
|
|
13
|
+
message?: {
|
|
14
|
+
message_id: number;
|
|
15
|
+
voice?: {
|
|
16
|
+
file_id: string;
|
|
17
|
+
duration: number;
|
|
18
|
+
mime_type: string;
|
|
19
|
+
file_size?: number;
|
|
20
|
+
};
|
|
21
|
+
chat: {
|
|
22
|
+
id: number;
|
|
23
|
+
type: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Telegram Voice Handler options
|
|
29
|
+
*/
|
|
30
|
+
export interface TelegramVoiceHandlerOptions {
|
|
31
|
+
/** Telegram bot token */
|
|
32
|
+
botToken: string;
|
|
33
|
+
/** Voice transcriber instance */
|
|
34
|
+
transcriber: VoiceTranscriber;
|
|
35
|
+
/** Base URL for Telegram API */
|
|
36
|
+
apiBase?: string;
|
|
37
|
+
/** Callback when voice is transcribed */
|
|
38
|
+
onTranscribed?: (chatId: string, text: string, result: TranscriptionResult) => Promise<void>;
|
|
39
|
+
/** Callback on error */
|
|
40
|
+
onError?: (chatId: string, error: Error) => void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Telegram Voice Handler
|
|
44
|
+
*
|
|
45
|
+
* Processes voice messages from Telegram and transcribes them.
|
|
46
|
+
*/
|
|
47
|
+
export declare class TelegramVoiceHandler {
|
|
48
|
+
private botToken;
|
|
49
|
+
private transcriber;
|
|
50
|
+
private apiBase;
|
|
51
|
+
private onTranscribed?;
|
|
52
|
+
private onError?;
|
|
53
|
+
constructor(options: TelegramVoiceHandlerOptions);
|
|
54
|
+
/**
|
|
55
|
+
* Handle incoming update from Telegram webhook
|
|
56
|
+
*/
|
|
57
|
+
handleUpdate(update: TelegramVoiceUpdate): Promise<{
|
|
58
|
+
success: boolean;
|
|
59
|
+
text?: string;
|
|
60
|
+
error?: string;
|
|
61
|
+
}>;
|
|
62
|
+
/**
|
|
63
|
+
* Get file path from Telegram
|
|
64
|
+
*/
|
|
65
|
+
private getFilePath;
|
|
66
|
+
/**
|
|
67
|
+
* Send text response to chat
|
|
68
|
+
*/
|
|
69
|
+
sendMessage(chatId: string, text: string): Promise<boolean>;
|
|
70
|
+
/**
|
|
71
|
+
* Create webhook handler middleware
|
|
72
|
+
*/
|
|
73
|
+
createWebhookHandler(): (request: Request) => Promise<Response>;
|
|
74
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Voice Handler
|
|
3
|
+
*
|
|
4
|
+
* Handles voice messages from Telegram bots.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Telegram Voice Handler
|
|
8
|
+
*
|
|
9
|
+
* Processes voice messages from Telegram and transcribes them.
|
|
10
|
+
*/
|
|
11
|
+
export class TelegramVoiceHandler {
|
|
12
|
+
botToken;
|
|
13
|
+
transcriber;
|
|
14
|
+
apiBase;
|
|
15
|
+
onTranscribed;
|
|
16
|
+
onError;
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.botToken = options.botToken;
|
|
19
|
+
this.transcriber = options.transcriber;
|
|
20
|
+
this.apiBase = options.apiBase || 'https://api.telegram.org';
|
|
21
|
+
this.onTranscribed = options.onTranscribed;
|
|
22
|
+
this.onError = options.onError;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Handle incoming update from Telegram webhook
|
|
26
|
+
*/
|
|
27
|
+
async handleUpdate(update) {
|
|
28
|
+
const message = update.message;
|
|
29
|
+
const voice = message?.voice;
|
|
30
|
+
if (!message || !voice) {
|
|
31
|
+
return { success: false, error: 'No voice message in update' };
|
|
32
|
+
}
|
|
33
|
+
const chatId = String(message.chat.id);
|
|
34
|
+
try {
|
|
35
|
+
// Get file path from Telegram
|
|
36
|
+
const filePath = await this.getFilePath(voice.file_id);
|
|
37
|
+
if (!filePath) {
|
|
38
|
+
return { success: false, error: 'Failed to get file path' };
|
|
39
|
+
}
|
|
40
|
+
// Construct download URL
|
|
41
|
+
const fileUrl = `${this.apiBase}/file/bot${this.botToken}/${filePath}`;
|
|
42
|
+
// Transcribe
|
|
43
|
+
const result = await this.transcriber.transcribe({ audioUrl: fileUrl });
|
|
44
|
+
if (result.error) {
|
|
45
|
+
this.onError?.(chatId, new Error(result.error));
|
|
46
|
+
return { success: false, error: result.error };
|
|
47
|
+
}
|
|
48
|
+
// Notify callback
|
|
49
|
+
await this.onTranscribed?.(chatId, result.text, result);
|
|
50
|
+
return { success: true, text: result.text };
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
54
|
+
this.onError?.(chatId, err);
|
|
55
|
+
return { success: false, error: err.message };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get file path from Telegram
|
|
60
|
+
*/
|
|
61
|
+
async getFilePath(fileId) {
|
|
62
|
+
try {
|
|
63
|
+
const response = await fetch(`${this.apiBase}/bot${this.botToken}/getFile?file_id=${fileId}`);
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const data = await response.json();
|
|
68
|
+
return data.result?.file_path || null;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Send text response to chat
|
|
76
|
+
*/
|
|
77
|
+
async sendMessage(chatId, text) {
|
|
78
|
+
try {
|
|
79
|
+
const response = await fetch(`${this.apiBase}/bot${this.botToken}/sendMessage`, {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
headers: { 'Content-Type': 'application/json' },
|
|
82
|
+
body: JSON.stringify({
|
|
83
|
+
chat_id: chatId,
|
|
84
|
+
text,
|
|
85
|
+
parse_mode: 'Markdown',
|
|
86
|
+
}),
|
|
87
|
+
});
|
|
88
|
+
return response.ok;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Create webhook handler middleware
|
|
96
|
+
*/
|
|
97
|
+
createWebhookHandler() {
|
|
98
|
+
return async (request) => {
|
|
99
|
+
try {
|
|
100
|
+
const update = await request.json();
|
|
101
|
+
const result = await this.handleUpdate(update);
|
|
102
|
+
if (result.success && result.text) {
|
|
103
|
+
// Optionally echo back the transcription
|
|
104
|
+
// await this.sendMessage(String(update.message?.chat.id), `🎤: ${result.text}`);
|
|
105
|
+
}
|
|
106
|
+
return new Response(JSON.stringify({ ok: true }), {
|
|
107
|
+
status: 200,
|
|
108
|
+
headers: { 'Content-Type': 'application/json' },
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
return new Response(JSON.stringify({ ok: false, error: String(error) }), {
|
|
113
|
+
status: 500,
|
|
114
|
+
headers: { 'Content-Type': 'application/json' },
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Voice Transcriber - Main interface for speech-to-text
|
|
3
|
+
*/
|
|
4
|
+
import type { TranscriptionResult, TranscribeOptions, VoiceApiConfig } from './types.js';
|
|
5
|
+
import { WhisperProvider } from './provider.js';
|
|
6
|
+
/**
|
|
7
|
+
* Voice Transcriber
|
|
8
|
+
*
|
|
9
|
+
* Main interface for transcribing voice messages to text.
|
|
10
|
+
* Supports multiple providers (OpenAI, Replicate).
|
|
11
|
+
*/
|
|
12
|
+
export declare class VoiceTranscriber {
|
|
13
|
+
private providers;
|
|
14
|
+
private defaultProvider;
|
|
15
|
+
constructor(config?: VoiceApiConfig);
|
|
16
|
+
/**
|
|
17
|
+
* Check if transcriber is configured
|
|
18
|
+
*/
|
|
19
|
+
isConfigured(): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Get configured providers
|
|
22
|
+
*/
|
|
23
|
+
getProviders(): string[];
|
|
24
|
+
/**
|
|
25
|
+
* Get default provider
|
|
26
|
+
*/
|
|
27
|
+
getDefaultProvider(): string | null;
|
|
28
|
+
/**
|
|
29
|
+
* Transcribe audio to text
|
|
30
|
+
*/
|
|
31
|
+
transcribe(options: TranscribeOptions): Promise<TranscriptionResult>;
|
|
32
|
+
/**
|
|
33
|
+
* Add a provider
|
|
34
|
+
*/
|
|
35
|
+
addProvider(name: string, provider: WhisperProvider): void;
|
|
36
|
+
/**
|
|
37
|
+
* Remove a provider
|
|
38
|
+
*/
|
|
39
|
+
removeProvider(name: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Set default provider
|
|
42
|
+
*/
|
|
43
|
+
setDefaultProvider(name: string): boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Create a VoiceTranscriber from environment variables
|
|
47
|
+
*/
|
|
48
|
+
export declare function createFromEnv(): VoiceTranscriber;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Voice Transcriber - Main interface for speech-to-text
|
|
3
|
+
*/
|
|
4
|
+
import { createOpenAIProvider, createReplicateProvider } from './provider.js';
|
|
5
|
+
/**
|
|
6
|
+
* Voice Transcriber
|
|
7
|
+
*
|
|
8
|
+
* Main interface for transcribing voice messages to text.
|
|
9
|
+
* Supports multiple providers (OpenAI, Replicate).
|
|
10
|
+
*/
|
|
11
|
+
export class VoiceTranscriber {
|
|
12
|
+
providers = new Map();
|
|
13
|
+
defaultProvider = '';
|
|
14
|
+
constructor(config = {}) {
|
|
15
|
+
// Initialize OpenAI provider
|
|
16
|
+
if (config.openaiApiKey) {
|
|
17
|
+
const provider = createOpenAIProvider(config.openaiApiKey);
|
|
18
|
+
this.providers.set('openai', provider);
|
|
19
|
+
this.defaultProvider = 'openai';
|
|
20
|
+
}
|
|
21
|
+
// Initialize Replicate provider
|
|
22
|
+
if (config.replicateApiToken) {
|
|
23
|
+
const provider = createReplicateProvider(config.replicateApiToken);
|
|
24
|
+
this.providers.set('replicate', provider);
|
|
25
|
+
if (!this.defaultProvider) {
|
|
26
|
+
this.defaultProvider = 'replicate';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Set default provider from config
|
|
30
|
+
if (config.provider && this.providers.has(config.provider)) {
|
|
31
|
+
this.defaultProvider = config.provider;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check if transcriber is configured
|
|
36
|
+
*/
|
|
37
|
+
isConfigured() {
|
|
38
|
+
return this.providers.size > 0;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get configured providers
|
|
42
|
+
*/
|
|
43
|
+
getProviders() {
|
|
44
|
+
return Array.from(this.providers.keys());
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get default provider
|
|
48
|
+
*/
|
|
49
|
+
getDefaultProvider() {
|
|
50
|
+
return this.defaultProvider || null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Transcribe audio to text
|
|
54
|
+
*/
|
|
55
|
+
async transcribe(options) {
|
|
56
|
+
const providerName = options.provider || this.defaultProvider;
|
|
57
|
+
if (!providerName) {
|
|
58
|
+
return {
|
|
59
|
+
text: '',
|
|
60
|
+
timestamp: new Date().toISOString(),
|
|
61
|
+
provider: 'openai',
|
|
62
|
+
error: 'No provider configured',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const provider = this.providers.get(providerName);
|
|
66
|
+
if (!provider) {
|
|
67
|
+
return {
|
|
68
|
+
text: '',
|
|
69
|
+
timestamp: new Date().toISOString(),
|
|
70
|
+
provider: providerName,
|
|
71
|
+
error: `Provider ${providerName} not configured`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return provider.transcribe(options);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Add a provider
|
|
78
|
+
*/
|
|
79
|
+
addProvider(name, provider) {
|
|
80
|
+
this.providers.set(name, provider);
|
|
81
|
+
if (!this.defaultProvider) {
|
|
82
|
+
this.defaultProvider = name;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Remove a provider
|
|
87
|
+
*/
|
|
88
|
+
removeProvider(name) {
|
|
89
|
+
const deleted = this.providers.delete(name);
|
|
90
|
+
if (this.defaultProvider === name) {
|
|
91
|
+
this.defaultProvider = this.providers.keys().next().value || '';
|
|
92
|
+
}
|
|
93
|
+
return deleted;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Set default provider
|
|
97
|
+
*/
|
|
98
|
+
setDefaultProvider(name) {
|
|
99
|
+
if (this.providers.has(name)) {
|
|
100
|
+
this.defaultProvider = name;
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Create a VoiceTranscriber from environment variables
|
|
108
|
+
*/
|
|
109
|
+
export function createFromEnv() {
|
|
110
|
+
// SAFETY: globalThis.process is always available in Node.js
|
|
111
|
+
const env = globalThis.process;
|
|
112
|
+
const envVars = env?.env ?? {};
|
|
113
|
+
return new VoiceTranscriber({
|
|
114
|
+
openaiApiKey: envVars.OPENAI_API_KEY,
|
|
115
|
+
replicateApiToken: envVars.REPLICATE_API_TOKEN,
|
|
116
|
+
provider: envVars.VOICE_PROVIDER || 'openai',
|
|
117
|
+
});
|
|
118
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Voice API Types
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Transcription result from Whisper
|
|
6
|
+
*/
|
|
7
|
+
export interface TranscriptionResult {
|
|
8
|
+
/** The transcribed text */
|
|
9
|
+
text: string;
|
|
10
|
+
/** Language detected (ISO 639-1) */
|
|
11
|
+
language?: string;
|
|
12
|
+
/** Duration in seconds */
|
|
13
|
+
duration?: number;
|
|
14
|
+
/** Confidence score (0-1) */
|
|
15
|
+
confidence?: number;
|
|
16
|
+
/** Timestamp of transcription */
|
|
17
|
+
timestamp: string;
|
|
18
|
+
/** Provider used (openai/replicate) */
|
|
19
|
+
provider: 'openai' | 'replicate';
|
|
20
|
+
/** Error message if failed */
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Voice message from messaging platforms
|
|
25
|
+
*/
|
|
26
|
+
export interface VoiceMessage {
|
|
27
|
+
/** Unique message ID */
|
|
28
|
+
id: string;
|
|
29
|
+
/** Platform source (telegram/line) */
|
|
30
|
+
platform: 'telegram' | 'line';
|
|
31
|
+
/** Chat/user ID */
|
|
32
|
+
chatId: string;
|
|
33
|
+
/** Audio file URL or path */
|
|
34
|
+
audioUrl: string;
|
|
35
|
+
/** Duration in seconds */
|
|
36
|
+
duration?: number;
|
|
37
|
+
/** MIME type */
|
|
38
|
+
mimeType: string;
|
|
39
|
+
/** File size in bytes */
|
|
40
|
+
fileSize?: number;
|
|
41
|
+
/** Raw platform-specific data */
|
|
42
|
+
raw?: unknown;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Voice API configuration
|
|
46
|
+
*/
|
|
47
|
+
export interface VoiceApiConfig {
|
|
48
|
+
/** OpenAI API key (for Whisper) */
|
|
49
|
+
openaiApiKey?: string;
|
|
50
|
+
/** Replicate API token (alternative) */
|
|
51
|
+
replicateApiToken?: string;
|
|
52
|
+
/** Default provider */
|
|
53
|
+
provider?: 'openai' | 'replicate';
|
|
54
|
+
/** Model to use */
|
|
55
|
+
model?: string;
|
|
56
|
+
/** Language hint (ISO 639-1) */
|
|
57
|
+
language?: string;
|
|
58
|
+
/** Temperature for generation */
|
|
59
|
+
temperature?: number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Transcribe options
|
|
63
|
+
*/
|
|
64
|
+
export interface TranscribeOptions {
|
|
65
|
+
/** Audio URL or file path (required if no audioBuffer) */
|
|
66
|
+
audioUrl?: string;
|
|
67
|
+
/** Audio as Buffer (Uint8Array for cross-platform) */
|
|
68
|
+
audioBuffer?: Uint8Array;
|
|
69
|
+
/** Language hint */
|
|
70
|
+
language?: string;
|
|
71
|
+
/** Model override */
|
|
72
|
+
model?: string;
|
|
73
|
+
/** Provider override */
|
|
74
|
+
provider?: 'openai' | 'replicate';
|
|
75
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pi-harness/voice-api",
|
|
3
|
+
"version": "1.1.339",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Voice-to-text API using Whisper for Telegram and LINE integration",
|
|
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
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"test": "bun test"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"openai": "^4.77.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"typescript": "^5.7.2"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Voice API - Speech-to-Text using Whisper
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - OpenAI Whisper API (primary)
|
|
6
|
+
* - Replicate Whisper (alternative)
|
|
7
|
+
*
|
|
8
|
+
* Used by Telegram and LINE voice message handlers.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { WhisperProvider } from './provider.js';
|
|
12
|
+
export { VoiceTranscriber } from './transcriber.js';
|
|
13
|
+
export { TelegramVoiceHandler } from './telegram.js';
|
|
14
|
+
export { LineVoiceHandler } from './line.js';
|
|
15
|
+
export type { TranscriptionResult, VoiceMessage } from './types.js';
|