pi-harness-runtime 1.1.341 → 1.1.342
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 +118 -0
- package/index.ts +58 -0
- 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/line.d.ts +2 -1
- package/packages/voice-api/dist/line.js +9 -6
- package/packages/voice-api/dist/transcriber.d.ts +12 -41
- package/packages/voice-api/dist/transcriber.js +88 -108
- package/packages/voice-api/package.json +1 -1
- package/packages/voice-api/src/line.ts +11 -8
- package/packages/voice-api/src/transcriber.ts +98 -122
- 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
|
@@ -1,48 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*/
|
|
4
|
-
import type { TranscriptionResult, TranscribeOptions, VoiceApiConfig } from './types.js';
|
|
5
|
-
import { WhisperProvider } from './provider.js';
|
|
6
|
-
/**
|
|
7
|
-
* Voice Transcriber
|
|
2
|
+
* OpenAI Whisper API Transcription
|
|
8
3
|
*
|
|
9
|
-
*
|
|
10
|
-
* Supports multiple providers (OpenAI, Replicate).
|
|
4
|
+
* Transcribes audio using OpenAI's Whisper API.
|
|
11
5
|
*/
|
|
6
|
+
import type { TranscriptionResult } from './types.js';
|
|
7
|
+
export interface TranscriberOptions {
|
|
8
|
+
openaiApiKey: string;
|
|
9
|
+
model?: string;
|
|
10
|
+
}
|
|
12
11
|
export declare class VoiceTranscriber {
|
|
13
|
-
private
|
|
14
|
-
private
|
|
15
|
-
constructor(
|
|
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;
|
|
12
|
+
private apiKey;
|
|
13
|
+
private model;
|
|
14
|
+
constructor(options: TranscriberOptions);
|
|
28
15
|
/**
|
|
29
|
-
* Transcribe audio
|
|
16
|
+
* Transcribe audio buffer using Whisper API
|
|
30
17
|
*/
|
|
31
|
-
transcribe(
|
|
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;
|
|
18
|
+
transcribe(audioBuffer: Uint8Array): Promise<TranscriptionResult>;
|
|
44
19
|
}
|
|
45
|
-
/**
|
|
46
|
-
* Create a VoiceTranscriber from environment variables
|
|
47
|
-
*/
|
|
48
|
-
export declare function createFromEnv(): VoiceTranscriber;
|
|
@@ -1,118 +1,98 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*/
|
|
4
|
-
import { createOpenAIProvider, createReplicateProvider } from './provider.js';
|
|
5
|
-
/**
|
|
6
|
-
* Voice Transcriber
|
|
2
|
+
* OpenAI Whisper API Transcription
|
|
7
3
|
*
|
|
8
|
-
*
|
|
9
|
-
* Supports multiple providers (OpenAI, Replicate).
|
|
4
|
+
* Transcribes audio using OpenAI's Whisper API.
|
|
10
5
|
*/
|
|
6
|
+
import * as https from "https";
|
|
11
7
|
export class VoiceTranscriber {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
constructor(
|
|
15
|
-
|
|
16
|
-
|
|
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;
|
|
8
|
+
apiKey;
|
|
9
|
+
model;
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.apiKey = options.openaiApiKey;
|
|
12
|
+
this.model = options.model || "whisper-1";
|
|
51
13
|
}
|
|
52
14
|
/**
|
|
53
|
-
* Transcribe audio
|
|
15
|
+
* Transcribe audio buffer using Whisper API
|
|
54
16
|
*/
|
|
55
|
-
async transcribe(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
17
|
+
async transcribe(audioBuffer) {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
const boundary = "----FormBoundary" + Math.random().toString(36).substring(2);
|
|
20
|
+
// Build multipart form data
|
|
21
|
+
const body = Buffer.concat([
|
|
22
|
+
Buffer.from(`--${boundary}\r\n`),
|
|
23
|
+
Buffer.from(`Content-Disposition: form-data; name="file"; filename="audio.m4a"\r\n`),
|
|
24
|
+
Buffer.from(`Content-Type: audio/x-m4a\r\n\r\n`),
|
|
25
|
+
Buffer.from(audioBuffer),
|
|
26
|
+
Buffer.from(`\r\n--${boundary}\r\n`),
|
|
27
|
+
Buffer.from(`Content-Disposition: form-data; name="model"\r\n\r\n`),
|
|
28
|
+
Buffer.from(`${this.model}\r\n`),
|
|
29
|
+
Buffer.from(`--${boundary}--\r\n`),
|
|
30
|
+
]);
|
|
31
|
+
const options = {
|
|
32
|
+
hostname: "api.openai.com",
|
|
33
|
+
path: "/v1/audio/transcriptions",
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: {
|
|
36
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
37
|
+
"Content-Type": `multipart/form-data; boundary=${boundary}`,
|
|
38
|
+
"Content-Length": body.length.toString(),
|
|
39
|
+
}
|
|
72
40
|
};
|
|
73
|
-
|
|
74
|
-
|
|
41
|
+
const req = https.request(options, (res) => {
|
|
42
|
+
let data = "";
|
|
43
|
+
res.on("data", (chunk) => data += chunk);
|
|
44
|
+
res.on("end", () => {
|
|
45
|
+
if (res.statusCode !== 200) {
|
|
46
|
+
try {
|
|
47
|
+
const error = JSON.parse(data);
|
|
48
|
+
resolve({
|
|
49
|
+
text: "",
|
|
50
|
+
timestamp: new Date().toISOString(),
|
|
51
|
+
provider: 'openai',
|
|
52
|
+
error: error.error?.message || data
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
resolve({
|
|
57
|
+
text: "",
|
|
58
|
+
timestamp: new Date().toISOString(),
|
|
59
|
+
provider: 'openai',
|
|
60
|
+
error: data
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const result = JSON.parse(data);
|
|
67
|
+
resolve({
|
|
68
|
+
text: result.text || "",
|
|
69
|
+
language: result.language,
|
|
70
|
+
duration: result.duration,
|
|
71
|
+
confidence: result.confidence,
|
|
72
|
+
timestamp: new Date().toISOString(),
|
|
73
|
+
provider: 'openai',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
resolve({
|
|
78
|
+
text: "",
|
|
79
|
+
timestamp: new Date().toISOString(),
|
|
80
|
+
provider: 'openai',
|
|
81
|
+
error: String(e)
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
req.on("error", (err) => {
|
|
87
|
+
resolve({
|
|
88
|
+
text: "",
|
|
89
|
+
timestamp: new Date().toISOString(),
|
|
90
|
+
provider: 'openai',
|
|
91
|
+
error: String(err)
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
req.write(body);
|
|
95
|
+
req.end();
|
|
96
|
+
});
|
|
75
97
|
}
|
|
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
98
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Handles voice messages from LINE Messaging API.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type {
|
|
7
|
+
import type { TranscriptionResult } from './types.js';
|
|
8
8
|
import { VoiceTranscriber } from './transcriber.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -38,7 +38,7 @@ export interface LineWebhookBody {
|
|
|
38
38
|
/**
|
|
39
39
|
* LINE Messaging API response
|
|
40
40
|
*/
|
|
41
|
-
interface
|
|
41
|
+
interface _LineMessageContent {
|
|
42
42
|
displayName?: string;
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -70,6 +70,7 @@ export class LineVoiceHandler {
|
|
|
70
70
|
private channelSecret?: string;
|
|
71
71
|
private transcriber: VoiceTranscriber;
|
|
72
72
|
private apiBase: string;
|
|
73
|
+
private dataApiBase: string;
|
|
73
74
|
private onTranscribed?: (userId: string, text: string, result: TranscriptionResult) => Promise<void>;
|
|
74
75
|
private onError?: (userId: string, error: Error) => void;
|
|
75
76
|
|
|
@@ -78,6 +79,7 @@ export class LineVoiceHandler {
|
|
|
78
79
|
this.channelSecret = options.channelSecret;
|
|
79
80
|
this.transcriber = options.transcriber;
|
|
80
81
|
this.apiBase = options.apiBase || 'https://api.line.me';
|
|
82
|
+
this.dataApiBase = 'https://api-data.line.me'; // Content API is different
|
|
81
83
|
this.onTranscribed = options.onTranscribed;
|
|
82
84
|
this.onError = options.onError;
|
|
83
85
|
}
|
|
@@ -85,7 +87,7 @@ export class LineVoiceHandler {
|
|
|
85
87
|
/**
|
|
86
88
|
* Verify LINE webhook signature
|
|
87
89
|
*/
|
|
88
|
-
verifySignature(
|
|
90
|
+
verifySignature(_body: string, _signature: string): boolean {
|
|
89
91
|
if (!this.channelSecret) return true; // Skip verification if no secret
|
|
90
92
|
|
|
91
93
|
// Note: In production, implement proper HMAC verification
|
|
@@ -116,9 +118,7 @@ export class LineVoiceHandler {
|
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
// Transcribe
|
|
119
|
-
const result = await this.transcriber.transcribe(
|
|
120
|
-
audioBuffer: audioContent,
|
|
121
|
-
});
|
|
121
|
+
const result = await this.transcriber.transcribe(audioContent);
|
|
122
122
|
|
|
123
123
|
if (result.error) {
|
|
124
124
|
this.onError?.(userId, new Error(result.error));
|
|
@@ -145,8 +145,9 @@ export class LineVoiceHandler {
|
|
|
145
145
|
*/
|
|
146
146
|
private async getAudioContent(messageId: string): Promise<Uint8Array | null> {
|
|
147
147
|
try {
|
|
148
|
+
// Use data API for content retrieval
|
|
148
149
|
const response = await fetch(
|
|
149
|
-
`${this.
|
|
150
|
+
`${this.dataApiBase}/v2/bot/message/${messageId}/content`,
|
|
150
151
|
{
|
|
151
152
|
headers: {
|
|
152
153
|
'Authorization': `Bearer ${this.channelAccessToken}`,
|
|
@@ -155,12 +156,14 @@ export class LineVoiceHandler {
|
|
|
155
156
|
);
|
|
156
157
|
|
|
157
158
|
if (!response.ok) {
|
|
159
|
+
console.error(`LINE API error: ${response.status} ${response.statusText}`);
|
|
158
160
|
return null;
|
|
159
161
|
}
|
|
160
162
|
|
|
161
163
|
const arrayBuffer = await response.arrayBuffer();
|
|
162
164
|
return new Uint8Array(arrayBuffer);
|
|
163
|
-
} catch {
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error(`Failed to get audio content: ${error}`);
|
|
164
167
|
return null;
|
|
165
168
|
}
|
|
166
169
|
}
|
|
@@ -1,136 +1,112 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { TranscriptionResult, TranscribeOptions, VoiceApiConfig } from './types.js';
|
|
6
|
-
import { WhisperProvider, createOpenAIProvider, createReplicateProvider } from './provider.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Voice Transcriber
|
|
2
|
+
* OpenAI Whisper API Transcription
|
|
10
3
|
*
|
|
11
|
-
*
|
|
12
|
-
* Supports multiple providers (OpenAI, Replicate).
|
|
4
|
+
* Transcribes audio using OpenAI's Whisper API.
|
|
13
5
|
*/
|
|
14
|
-
export class VoiceTranscriber {
|
|
15
|
-
private providers: Map<string, WhisperProvider> = new Map();
|
|
16
|
-
private defaultProvider: string = '';
|
|
17
6
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (config.openaiApiKey) {
|
|
21
|
-
const provider = createOpenAIProvider(config.openaiApiKey);
|
|
22
|
-
this.providers.set('openai', provider);
|
|
23
|
-
this.defaultProvider = 'openai';
|
|
24
|
-
}
|
|
7
|
+
import type { TranscriptionResult } from './types.js';
|
|
8
|
+
import * as https from "https";
|
|
25
9
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (!this.defaultProvider) {
|
|
31
|
-
this.defaultProvider = 'replicate';
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Set default provider from config
|
|
36
|
-
if (config.provider && this.providers.has(config.provider)) {
|
|
37
|
-
this.defaultProvider = config.provider;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Check if transcriber is configured
|
|
43
|
-
*/
|
|
44
|
-
isConfigured(): boolean {
|
|
45
|
-
return this.providers.size > 0;
|
|
46
|
-
}
|
|
10
|
+
export interface TranscriberOptions {
|
|
11
|
+
openaiApiKey: string;
|
|
12
|
+
model?: string;
|
|
13
|
+
}
|
|
47
14
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
getProviders(): string[] {
|
|
52
|
-
return Array.from(this.providers.keys());
|
|
53
|
-
}
|
|
15
|
+
export class VoiceTranscriber {
|
|
16
|
+
private apiKey: string;
|
|
17
|
+
private model: string;
|
|
54
18
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
getDefaultProvider(): string | null {
|
|
59
|
-
return this.defaultProvider || null;
|
|
19
|
+
constructor(options: TranscriberOptions) {
|
|
20
|
+
this.apiKey = options.openaiApiKey;
|
|
21
|
+
this.model = options.model || "whisper-1";
|
|
60
22
|
}
|
|
61
23
|
|
|
62
24
|
/**
|
|
63
|
-
* Transcribe audio
|
|
25
|
+
* Transcribe audio buffer using Whisper API
|
|
64
26
|
*/
|
|
65
|
-
async transcribe(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
27
|
+
async transcribe(audioBuffer: Uint8Array): Promise<TranscriptionResult> {
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
const boundary = "----FormBoundary" + Math.random().toString(36).substring(2);
|
|
30
|
+
|
|
31
|
+
// Build multipart form data
|
|
32
|
+
const body = Buffer.concat([
|
|
33
|
+
Buffer.from(`--${boundary}\r\n`),
|
|
34
|
+
Buffer.from(`Content-Disposition: form-data; name="file"; filename="audio.m4a"\r\n`),
|
|
35
|
+
Buffer.from(`Content-Type: audio/x-m4a\r\n\r\n`),
|
|
36
|
+
Buffer.from(audioBuffer),
|
|
37
|
+
Buffer.from(`\r\n--${boundary}\r\n`),
|
|
38
|
+
Buffer.from(`Content-Disposition: form-data; name="model"\r\n\r\n`),
|
|
39
|
+
Buffer.from(`${this.model}\r\n`),
|
|
40
|
+
Buffer.from(`--${boundary}--\r\n`),
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const options = {
|
|
44
|
+
hostname: "api.openai.com",
|
|
45
|
+
path: "/v1/audio/transcriptions",
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: {
|
|
48
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
49
|
+
"Content-Type": `multipart/form-data; boundary=${boundary}`,
|
|
50
|
+
"Content-Length": body.length.toString(),
|
|
51
|
+
}
|
|
74
52
|
};
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
53
|
+
|
|
54
|
+
const req = https.request(options, (res) => {
|
|
55
|
+
let data = "";
|
|
56
|
+
res.on("data", (chunk) => data += chunk);
|
|
57
|
+
res.on("end", () => {
|
|
58
|
+
if (res.statusCode !== 200) {
|
|
59
|
+
try {
|
|
60
|
+
const error = JSON.parse(data);
|
|
61
|
+
resolve({
|
|
62
|
+
text: "",
|
|
63
|
+
timestamp: new Date().toISOString(),
|
|
64
|
+
provider: 'openai' as const,
|
|
65
|
+
error: error.error?.message || data
|
|
66
|
+
});
|
|
67
|
+
} catch {
|
|
68
|
+
resolve({
|
|
69
|
+
text: "",
|
|
70
|
+
timestamp: new Date().toISOString(),
|
|
71
|
+
provider: 'openai' as const,
|
|
72
|
+
error: data
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const result = JSON.parse(data);
|
|
80
|
+
resolve({
|
|
81
|
+
text: result.text || "",
|
|
82
|
+
language: result.language,
|
|
83
|
+
duration: result.duration,
|
|
84
|
+
confidence: result.confidence,
|
|
85
|
+
timestamp: new Date().toISOString(),
|
|
86
|
+
provider: 'openai' as const,
|
|
87
|
+
});
|
|
88
|
+
} catch (e) {
|
|
89
|
+
resolve({
|
|
90
|
+
text: "",
|
|
91
|
+
timestamp: new Date().toISOString(),
|
|
92
|
+
provider: 'openai' as const,
|
|
93
|
+
error: String(e)
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
req.on("error", (err) => {
|
|
100
|
+
resolve({
|
|
101
|
+
text: "",
|
|
102
|
+
timestamp: new Date().toISOString(),
|
|
103
|
+
provider: 'openai' as const,
|
|
104
|
+
error: String(err)
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
req.write(body);
|
|
109
|
+
req.end();
|
|
110
|
+
});
|
|
120
111
|
}
|
|
121
112
|
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Create a VoiceTranscriber from environment variables
|
|
125
|
-
*/
|
|
126
|
-
export function createFromEnv(): VoiceTranscriber {
|
|
127
|
-
// SAFETY: globalThis.process is always available in Node.js
|
|
128
|
-
const env = (globalThis as unknown as { process: { env: Record<string, string> } }).process;
|
|
129
|
-
const envVars = env?.env ?? {};
|
|
130
|
-
|
|
131
|
-
return new VoiceTranscriber({
|
|
132
|
-
openaiApiKey: envVars.OPENAI_API_KEY,
|
|
133
|
-
replicateApiToken: envVars.REPLICATE_API_TOKEN,
|
|
134
|
-
provider: (envVars.VOICE_PROVIDER as 'openai' | 'replicate') || 'openai',
|
|
135
|
-
});
|
|
136
|
-
}
|