chore: typecheck and build readiness
This commit is contained in:
@@ -19,6 +19,7 @@ import type {
|
||||
ListPersistedAgentsOptions,
|
||||
PersistedAgentDescriptor,
|
||||
} from "./agent-sdk-types.js";
|
||||
import { resolveAgentModel } from "./model-resolver.js";
|
||||
|
||||
export type AgentLifecycleStatus =
|
||||
| "initializing"
|
||||
@@ -31,6 +32,7 @@ export type AgentSnapshot = {
|
||||
id: string;
|
||||
provider: AgentProvider;
|
||||
cwd: string;
|
||||
model: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
lastUserMessageAt: Date | null;
|
||||
@@ -203,9 +205,14 @@ export class AgentManager {
|
||||
config: AgentSessionConfig,
|
||||
agentId?: string
|
||||
): Promise<AgentSnapshot> {
|
||||
const client = this.requireClient(config.provider);
|
||||
const session = await client.createSession(config);
|
||||
return this.registerSession(session, config, agentId ?? this.idFactory());
|
||||
const normalizedConfig = await this.normalizeConfig(config);
|
||||
const client = this.requireClient(normalizedConfig.provider);
|
||||
const session = await client.createSession(normalizedConfig);
|
||||
return this.registerSession(
|
||||
session,
|
||||
normalizedConfig,
|
||||
agentId ?? this.idFactory()
|
||||
);
|
||||
}
|
||||
|
||||
async resumeAgent(
|
||||
@@ -213,17 +220,22 @@ export class AgentManager {
|
||||
overrides?: Partial<AgentSessionConfig>,
|
||||
agentId?: string
|
||||
): Promise<AgentSnapshot> {
|
||||
const client = this.requireClient(handle.provider);
|
||||
const session = await client.resumeSession(handle, overrides);
|
||||
const metadata = (handle.metadata ?? {}) as Partial<AgentSessionConfig>;
|
||||
const mergedConfig = {
|
||||
...metadata,
|
||||
...overrides,
|
||||
provider: handle.provider,
|
||||
} as AgentSessionConfig;
|
||||
const normalizedConfig = await this.normalizeConfig(mergedConfig);
|
||||
const resumeOverrides =
|
||||
normalizedConfig.model !== mergedConfig.model
|
||||
? { ...overrides, model: normalizedConfig.model }
|
||||
: overrides;
|
||||
const client = this.requireClient(handle.provider);
|
||||
const session = await client.resumeSession(handle, resumeOverrides);
|
||||
return this.registerSession(
|
||||
session,
|
||||
mergedConfig,
|
||||
normalizedConfig,
|
||||
agentId ?? this.idFactory()
|
||||
);
|
||||
}
|
||||
@@ -608,6 +620,7 @@ export class AgentManager {
|
||||
id: agent.id,
|
||||
provider: agent.provider,
|
||||
cwd: agent.cwd,
|
||||
model: agent.config.model ?? null,
|
||||
createdAt: agent.createdAt,
|
||||
updatedAt: agent.updatedAt,
|
||||
lastUserMessageAt: agent.lastUserMessageAt,
|
||||
@@ -623,6 +636,26 @@ export class AgentManager {
|
||||
};
|
||||
}
|
||||
|
||||
private async normalizeConfig(
|
||||
config: AgentSessionConfig
|
||||
): Promise<AgentSessionConfig> {
|
||||
const normalized: AgentSessionConfig = { ...config };
|
||||
const resolvedModel = await resolveAgentModel({
|
||||
provider: normalized.provider,
|
||||
requestedModel: normalized.model,
|
||||
cwd: normalized.cwd,
|
||||
});
|
||||
|
||||
if (resolvedModel) {
|
||||
normalized.model = resolvedModel;
|
||||
} else if (typeof normalized.model === "string") {
|
||||
const trimmed = normalized.model.trim();
|
||||
normalized.model = trimmed.length > 0 ? trimmed : undefined;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private requireClient(provider: AgentProvider): AgentClient {
|
||||
const client = this.clients.get(provider);
|
||||
if (!client) {
|
||||
|
||||
@@ -12,6 +12,7 @@ function createSnapshot(overrides?: Partial<AgentSnapshot>): AgentSnapshot {
|
||||
id: "agent-test",
|
||||
provider: "claude",
|
||||
cwd: "/tmp/project",
|
||||
model: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
lastUserMessageAt: null,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
const AUDIO_EXTENSIONS: Array<{ match: (format: string) => boolean; ext: string }> = [
|
||||
{ match: (format) => format.includes("webm"), ext: "webm" },
|
||||
{ match: (format) => format.includes("ogg"), ext: "ogg" },
|
||||
{ match: (format) => format.includes("mp3"), ext: "mp3" },
|
||||
{ match: (format) => format.includes("wav"), ext: "wav" },
|
||||
{ match: (format) => format.includes("m4a") || format.includes("aac"), ext: "m4a" },
|
||||
{ match: (format) => format.includes("mp4"), ext: "mp4" },
|
||||
{ match: (format) => format.includes("flac"), ext: "flac" },
|
||||
];
|
||||
|
||||
export function inferAudioExtension(format: string | undefined): string {
|
||||
const normalized = (format || "webm").toLowerCase();
|
||||
const candidate = AUDIO_EXTENSIONS.find((entry) => entry.match(normalized));
|
||||
return candidate?.ext ?? "webm";
|
||||
}
|
||||
|
||||
export function sanitizeForFilename(segment: string | undefined, fallback: string): string {
|
||||
const value = segment && segment.length > 0 ? segment : fallback;
|
||||
return value.replace(/[^a-zA-Z0-9-_]/g, "_").slice(0, 64);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import readline from "node:readline";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
|
||||
import {
|
||||
query,
|
||||
@@ -105,7 +105,9 @@ function emptySdkMessageStream(): AsyncIterable<SDKUserMessage> {
|
||||
}
|
||||
|
||||
function resolveCodexBinary(): string {
|
||||
const repoRoot = path.resolve(fileURLToPath(new URL("../../../../..", import.meta.url)));
|
||||
const repoRoot = path.resolve(
|
||||
fileURLToPath(new URL("../../../../..", import.meta.url))
|
||||
);
|
||||
const packageRoot = path.join(repoRoot, "node_modules", "@openai", "codex-sdk");
|
||||
const vendorDir = path.join(packageRoot, "vendor");
|
||||
|
||||
@@ -144,7 +146,7 @@ type CodexModelInfo = {
|
||||
type PendingRequest = {
|
||||
resolve: (value: unknown) => void;
|
||||
reject: (error: Error) => void;
|
||||
timer: NodeJS.Timeout;
|
||||
timer: ReturnType<typeof setTimeout>;
|
||||
};
|
||||
|
||||
class CodexAppServerClient {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { resolveAgentModel } from "./model-resolver.js";
|
||||
|
||||
vi.mock("./model-catalog.js", () => ({
|
||||
fetchProviderModelCatalog: vi.fn(),
|
||||
}));
|
||||
|
||||
import { fetchProviderModelCatalog } from "./model-catalog.js";
|
||||
|
||||
const mockedFetch = vi.mocked(fetchProviderModelCatalog);
|
||||
|
||||
describe("resolveAgentModel", () => {
|
||||
beforeEach(() => {
|
||||
mockedFetch.mockReset();
|
||||
});
|
||||
|
||||
it("returns the trimmed requested model when provided", async () => {
|
||||
const result = await resolveAgentModel({
|
||||
provider: "codex",
|
||||
requestedModel: " gpt-5.1 ",
|
||||
cwd: "/tmp",
|
||||
});
|
||||
|
||||
expect(result).toBe("gpt-5.1");
|
||||
expect(mockedFetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("uses the default model from the provider catalog when no model specified", async () => {
|
||||
mockedFetch.mockResolvedValue([
|
||||
{ id: "claude-3.5-haiku", isDefault: false } as any,
|
||||
{ id: "claude-3.5-sonnet", isDefault: true } as any,
|
||||
]);
|
||||
|
||||
const result = await resolveAgentModel({ provider: "claude", cwd: "~/repo" });
|
||||
|
||||
expect(result).toBe("claude-3.5-sonnet");
|
||||
expect(mockedFetch).toHaveBeenCalledWith("claude", {
|
||||
cwd: expect.stringMatching(/repo$/),
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to the first model when none are flagged as default", async () => {
|
||||
mockedFetch.mockResolvedValue([
|
||||
{ id: "model-a", isDefault: false } as any,
|
||||
{ id: "model-b", isDefault: false } as any,
|
||||
]);
|
||||
|
||||
const result = await resolveAgentModel({ provider: "codex" });
|
||||
|
||||
expect(result).toBe("model-a");
|
||||
});
|
||||
|
||||
it("returns undefined when the catalog lookup fails", async () => {
|
||||
mockedFetch.mockRejectedValue(new Error("boom"));
|
||||
|
||||
const result = await resolveAgentModel({ provider: "codex" });
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import { fetchProviderModelCatalog } from "./model-catalog.js";
|
||||
import type { AgentProvider } from "./agent-sdk-types.js";
|
||||
import { expandTilde } from "../terminal-mcp/tmux.js";
|
||||
|
||||
type ResolveAgentModelOptions = {
|
||||
provider: AgentProvider;
|
||||
requestedModel?: string | null;
|
||||
cwd?: string;
|
||||
};
|
||||
|
||||
export async function resolveAgentModel(
|
||||
options: ResolveAgentModelOptions
|
||||
): Promise<string | undefined> {
|
||||
const trimmed = options.requestedModel?.trim();
|
||||
if (trimmed) {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
try {
|
||||
const models = await fetchProviderModelCatalog(options.provider, {
|
||||
cwd: options.cwd ? expandTilde(options.cwd) : undefined,
|
||||
});
|
||||
const preferred = models.find((model) => model.isDefault) ?? models[0];
|
||||
return preferred?.id;
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`[AgentModelResolver] Failed to resolve default model for ${options.provider}:`,
|
||||
error
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { mkdir, writeFile } from "fs/promises";
|
||||
import { join, resolve } from "path";
|
||||
import { inferAudioExtension, sanitizeForFilename } from "./audio-utils.js";
|
||||
|
||||
const debugDir = process.env.STT_DEBUG_AUDIO_DIR
|
||||
? resolve(process.env.STT_DEBUG_AUDIO_DIR)
|
||||
: null;
|
||||
let announced = false;
|
||||
|
||||
export interface DebugAudioMetadata {
|
||||
sessionId: string;
|
||||
agentId?: string;
|
||||
requestId?: string;
|
||||
label?: string;
|
||||
format: string;
|
||||
}
|
||||
|
||||
export async function maybePersistDebugAudio(
|
||||
audio: Buffer,
|
||||
metadata: DebugAudioMetadata
|
||||
): Promise<string | null> {
|
||||
if (!debugDir) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!announced) {
|
||||
console.log(`[STT][Debug] Raw audio capture enabled at ${debugDir}`);
|
||||
announced = true;
|
||||
}
|
||||
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
||||
const folder = join(debugDir, sanitizeForFilename(metadata.sessionId, "session"));
|
||||
await mkdir(folder, { recursive: true });
|
||||
|
||||
const parts = [timestamp];
|
||||
if (metadata.agentId) {
|
||||
parts.push(sanitizeForFilename(metadata.agentId, "agent"));
|
||||
}
|
||||
if (metadata.label) {
|
||||
parts.push(sanitizeForFilename(metadata.label, "source"));
|
||||
}
|
||||
if (metadata.requestId) {
|
||||
parts.push(sanitizeForFilename(metadata.requestId, "request"));
|
||||
}
|
||||
|
||||
const ext = inferAudioExtension(metadata.format);
|
||||
const filePath = join(folder, `${parts.join("_")}.${ext}`);
|
||||
await writeFile(filePath, audio);
|
||||
return filePath;
|
||||
}
|
||||
@@ -1,4 +1,17 @@
|
||||
import { transcribeAudio, type TranscriptionResult } from "./stt-openai.js";
|
||||
import { maybePersistDebugAudio } from "./stt-debug.js";
|
||||
|
||||
interface TranscriptionMetadata {
|
||||
agentId?: string;
|
||||
requestId?: string;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface SessionTranscriptionResult extends TranscriptionResult {
|
||||
debugRecordingPath?: string;
|
||||
byteLength: number;
|
||||
format: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-session STT manager
|
||||
@@ -16,12 +29,30 @@ export class STTManager {
|
||||
*/
|
||||
public async transcribe(
|
||||
audio: Buffer,
|
||||
format: string
|
||||
): Promise<TranscriptionResult> {
|
||||
format: string,
|
||||
metadata?: TranscriptionMetadata
|
||||
): Promise<SessionTranscriptionResult> {
|
||||
const context = metadata?.label ? ` (${metadata.label})` : "";
|
||||
console.log(
|
||||
`[STT-Manager ${this.sessionId}] Transcribing ${audio.length} bytes of ${format} audio`
|
||||
`[STT-Manager ${this.sessionId}] Transcribing ${audio.length} bytes of ${format} audio${context}`
|
||||
);
|
||||
|
||||
let debugRecordingPath: string | null = null;
|
||||
try {
|
||||
debugRecordingPath = await maybePersistDebugAudio(audio, {
|
||||
sessionId: this.sessionId,
|
||||
agentId: metadata?.agentId,
|
||||
requestId: metadata?.requestId,
|
||||
label: metadata?.label,
|
||||
format,
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`[STT-Manager ${this.sessionId}] Failed to persist debug audio:`,
|
||||
error
|
||||
);
|
||||
}
|
||||
|
||||
const result = await transcribeAudio(audio, format);
|
||||
|
||||
// Filter out low-confidence transcriptions (non-speech sounds)
|
||||
@@ -34,6 +65,9 @@ export class STTManager {
|
||||
return {
|
||||
...result,
|
||||
text: "",
|
||||
byteLength: audio.length,
|
||||
format,
|
||||
debugRecordingPath: debugRecordingPath ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -43,7 +77,12 @@ export class STTManager {
|
||||
}`
|
||||
);
|
||||
|
||||
return result;
|
||||
return {
|
||||
...result,
|
||||
debugRecordingPath: debugRecordingPath ?? undefined,
|
||||
byteLength: audio.length,
|
||||
format,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,10 +3,11 @@ import { writeFile, unlink } from "fs/promises";
|
||||
import { join } from "path";
|
||||
import { tmpdir } from "os";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { inferAudioExtension } from "./audio-utils.js";
|
||||
|
||||
export interface STTConfig {
|
||||
apiKey: string;
|
||||
model?: "whisper-1";
|
||||
model?: "whisper-1" | "gpt-4o-transcribe" | "gpt-4o-mini-transcribe" | (string & {});
|
||||
confidenceThreshold?: number; // Default: -3.0
|
||||
}
|
||||
|
||||
@@ -49,7 +50,7 @@ export async function transcribeAudio(
|
||||
|
||||
try {
|
||||
// Map format to file extension
|
||||
const ext = getFileExtension(format);
|
||||
const ext = inferAudioExtension(format);
|
||||
|
||||
// Write audio buffer to temporary file
|
||||
// OpenAI API requires file upload, not raw buffer
|
||||
@@ -61,11 +62,15 @@ export async function transcribeAudio(
|
||||
);
|
||||
|
||||
// Call OpenAI Whisper API
|
||||
const modelToUse = config.model ?? "whisper-1";
|
||||
const supportsLogprobs =
|
||||
modelToUse === "gpt-4o-transcribe" || modelToUse === "gpt-4o-mini-transcribe";
|
||||
|
||||
const response = await openaiClient.audio.transcriptions.create({
|
||||
file: await import("fs").then((fs) => fs.createReadStream(tempFilePath!)),
|
||||
language: "en",
|
||||
model: config.model ?? "gpt-4o-transcribe",
|
||||
include: ["logprobs"],
|
||||
model: modelToUse,
|
||||
...(supportsLogprobs ? { include: ["logprobs"] as ["logprobs"] } : {}),
|
||||
response_format: "json", // Get language and duration info
|
||||
});
|
||||
|
||||
@@ -77,7 +82,9 @@ export async function transcribeAudio(
|
||||
// Analyze logprobs if available
|
||||
let avgLogprob: number | undefined;
|
||||
let isLowConfidence = false;
|
||||
const logprobs = response.logprobs as LogprobToken[] | undefined;
|
||||
const logprobs = supportsLogprobs
|
||||
? (response.logprobs as LogprobToken[] | undefined)
|
||||
: undefined;
|
||||
|
||||
if (logprobs && logprobs.length > 0) {
|
||||
// Calculate average logprob
|
||||
@@ -117,6 +124,7 @@ export async function transcribeAudio(
|
||||
logprobs: logprobs,
|
||||
avgLogprob: avgLogprob,
|
||||
isLowConfidence: isLowConfidence,
|
||||
language: (response as { language?: string }).language,
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error("[STT] Transcription error:", error);
|
||||
@@ -132,23 +140,6 @@ export async function transcribeAudio(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getFileExtension(format: string): string {
|
||||
// Map mime types or format strings to file extensions
|
||||
const formatLower = format.toLowerCase();
|
||||
|
||||
if (formatLower.includes("webm")) return "webm";
|
||||
if (formatLower.includes("ogg")) return "ogg";
|
||||
if (formatLower.includes("mp3")) return "mp3";
|
||||
if (formatLower.includes("wav")) return "wav";
|
||||
if (formatLower.includes("m4a")) return "m4a";
|
||||
if (formatLower.includes("mp4")) return "mp4";
|
||||
if (formatLower.includes("flac")) return "flac";
|
||||
|
||||
// Default to webm (common browser format)
|
||||
return "webm";
|
||||
}
|
||||
|
||||
export function isSTTInitialized(): boolean {
|
||||
return openaiClient !== null && config !== null;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import express from "express";
|
||||
import basicAuth from "express-basic-auth";
|
||||
import { createServer as createHTTPServer } from "http";
|
||||
import { VoiceAssistantWebSocketServer } from "./websocket-server.js";
|
||||
import { initializeSTT } from "./agent/stt-openai.js";
|
||||
import { initializeSTT, type STTConfig } from "./agent/stt-openai.js";
|
||||
import { initializeTTS } from "./agent/tts-openai.js";
|
||||
import { listConversations, deleteConversation } from "./persistence.js";
|
||||
import { AgentManager } from "./agent/agent-manager.js";
|
||||
@@ -101,9 +101,12 @@ async function main() {
|
||||
? parseFloat(process.env.STT_CONFIDENCE_THRESHOLD)
|
||||
: undefined; // Will default to -3.0 in stt-openai.ts
|
||||
|
||||
const sttModel = process.env.STT_MODEL as STTConfig["model"];
|
||||
|
||||
initializeSTT({
|
||||
apiKey,
|
||||
confidenceThreshold: sttConfidenceThreshold
|
||||
confidenceThreshold: sttConfidenceThreshold,
|
||||
...(sttModel ? { model: sttModel } : {}),
|
||||
});
|
||||
|
||||
// Initialize TTS
|
||||
|
||||
@@ -228,6 +228,7 @@ export const AgentSnapshotPayloadSchema = z.object({
|
||||
id: z.string(),
|
||||
provider: AgentProviderSchema,
|
||||
cwd: z.string(),
|
||||
model: z.string().nullable(),
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
lastUserMessageAt: z.string().nullable(),
|
||||
@@ -254,6 +255,7 @@ export function serializeAgentSnapshot(
|
||||
const { createdAt, updatedAt, lastUserMessageAt, ...rest } = snapshot;
|
||||
return {
|
||||
...rest,
|
||||
model: snapshot.model,
|
||||
createdAt: createdAt.toISOString(),
|
||||
updatedAt: updatedAt.toISOString(),
|
||||
lastUserMessageAt: lastUserMessageAt ? lastUserMessageAt.toISOString() : null,
|
||||
@@ -334,6 +336,9 @@ export const SendAgentAudioSchema = z.object({
|
||||
format: z.string(),
|
||||
isLast: z.boolean(),
|
||||
requestId: z.string().optional(), // Client-provided ID for tracking transcription
|
||||
mode: z
|
||||
.enum(["transcribe_only", "auto_run"])
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const GitSetupOptionsSchema = z.object({
|
||||
@@ -531,6 +536,11 @@ export const TranscriptionResultMessageSchema = z.object({
|
||||
language: z.string().optional(),
|
||||
duration: z.number().optional(),
|
||||
requestId: z.string().optional(), // Echoed back from request for tracking
|
||||
avgLogprob: z.number().optional(),
|
||||
isLowConfidence: z.boolean().optional(),
|
||||
byteLength: z.number().optional(),
|
||||
format: z.string().optional(),
|
||||
debugRecordingPath: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ function createSnapshot(overrides?: Partial<AgentSnapshot>): AgentSnapshot {
|
||||
id: "agent-1",
|
||||
provider: "claude",
|
||||
cwd: "/tmp/project",
|
||||
model: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
lastUserMessageAt: null,
|
||||
|
||||
@@ -545,6 +545,7 @@ export class Session {
|
||||
id: record.id,
|
||||
provider,
|
||||
cwd: record.cwd,
|
||||
model: record.config?.model ?? null,
|
||||
createdAt: createdAt.toISOString(),
|
||||
updatedAt: updatedAt.toISOString(),
|
||||
lastUserMessageAt: lastUserMessageAt ? lastUserMessageAt.toISOString() : null,
|
||||
@@ -971,7 +972,8 @@ export class Session {
|
||||
private async handleSendAgentAudio(
|
||||
msg: Extract<SessionInboundMessage, { type: "send_agent_audio" }>
|
||||
): Promise<void> {
|
||||
const { agentId, audio, format, isLast, requestId } = msg;
|
||||
const { agentId, audio, format, isLast, requestId, mode } = msg;
|
||||
const shouldAutoRun = mode === "auto_run";
|
||||
|
||||
// Decode base64
|
||||
const audioBuffer = Buffer.from(audio, "base64");
|
||||
@@ -992,7 +994,11 @@ export class Session {
|
||||
|
||||
try {
|
||||
// Transcribe the audio
|
||||
const result = await this.sttManager.transcribe(audioBuffer, format);
|
||||
const result = await this.sttManager.transcribe(audioBuffer, format, {
|
||||
agentId,
|
||||
requestId,
|
||||
label: shouldAutoRun ? "dictation:auto_run" : "dictation",
|
||||
});
|
||||
|
||||
const transcriptText = result.text.trim();
|
||||
if (!transcriptText) {
|
||||
@@ -1014,9 +1020,21 @@ export class Session {
|
||||
language: result.language,
|
||||
duration: result.duration,
|
||||
requestId,
|
||||
avgLogprob: result.avgLogprob,
|
||||
isLowConfidence: result.isLowConfidence,
|
||||
byteLength: result.byteLength,
|
||||
format: result.format,
|
||||
debugRecordingPath: result.debugRecordingPath,
|
||||
},
|
||||
});
|
||||
|
||||
if (!shouldAutoRun) {
|
||||
console.log(
|
||||
`[Session ${this.clientId}] Completed transcription for agent ${agentId} (requestId: ${requestId ?? "n/a"})`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.interruptAgentIfRunning(agentId);
|
||||
} catch (error) {
|
||||
@@ -2225,7 +2243,9 @@ export class Session {
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await this.sttManager.transcribe(audio, format);
|
||||
const result = await this.sttManager.transcribe(audio, format, {
|
||||
label: this.isRealtimeMode ? "realtime" : "buffered",
|
||||
});
|
||||
|
||||
const transcriptText = result.text.trim();
|
||||
if (!transcriptText) {
|
||||
@@ -2255,6 +2275,11 @@ export class Session {
|
||||
text: result.text,
|
||||
language: result.language,
|
||||
duration: result.duration,
|
||||
avgLogprob: result.avgLogprob,
|
||||
isLowConfidence: result.isLowConfidence,
|
||||
byteLength: result.byteLength,
|
||||
format: result.format,
|
||||
debugRecordingPath: result.debugRecordingPath,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -119,13 +119,13 @@ export function validateBranchSlug(slug: string): {
|
||||
return { valid: false, error: "Branch name too long (max 100 characters)" };
|
||||
}
|
||||
|
||||
// Check for valid characters: lowercase letters, numbers, hyphens
|
||||
const validPattern = /^[a-z0-9-]+$/;
|
||||
// Check for valid characters: lowercase letters, numbers, hyphens, forward slashes
|
||||
const validPattern = /^[a-z0-9-/]+$/;
|
||||
if (!validPattern.test(slug)) {
|
||||
return {
|
||||
valid: false,
|
||||
error:
|
||||
"Branch name must contain only lowercase letters, numbers, and hyphens",
|
||||
"Branch name must contain only lowercase letters, numbers, hyphens, and forward slashes",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user