bosun 0.36.4 → 0.36.5
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/monitor.mjs +63 -14
- package/package.json +1 -1
- package/setup-web-server.mjs +9 -9
- package/ui/modules/settings-schema.js +7 -4
- package/ui/setup.html +95 -41
- package/voice-agents-sdk.mjs +2 -2
- package/voice-relay.mjs +2 -2
package/monitor.mjs
CHANGED
|
@@ -991,7 +991,6 @@ let {
|
|
|
991
991
|
fleet: fleetConfig,
|
|
992
992
|
internalExecutor: internalExecutorConfig,
|
|
993
993
|
executorMode: configExecutorMode,
|
|
994
|
-
githubReconcile: githubReconcileConfig,
|
|
995
994
|
} = config;
|
|
996
995
|
|
|
997
996
|
const telegramWeeklyReportEnabled = parseEnvBoolean(
|
|
@@ -1023,12 +1022,6 @@ let triggerSystemConfig =
|
|
|
1023
1022
|
: { enabled: false, templates: [], defaults: { executor: "auto", model: "auto" } };
|
|
1024
1023
|
let kanbanBackend = String(kanbanConfig?.backend || "internal").toLowerCase();
|
|
1025
1024
|
let executorMode = configExecutorMode || getExecutorMode();
|
|
1026
|
-
let githubReconcile = githubReconcileConfig || {
|
|
1027
|
-
enabled: false,
|
|
1028
|
-
intervalMs: 5 * 60 * 1000,
|
|
1029
|
-
mergedLookbackHours: 72,
|
|
1030
|
-
trackingLabels: ["tracking"],
|
|
1031
|
-
};
|
|
1032
1025
|
let chdirUnsupportedInRuntime = false;
|
|
1033
1026
|
|
|
1034
1027
|
function isChdirUnsupportedError(err) {
|
|
@@ -10158,6 +10151,10 @@ function startTaskPlannerStatusLoop() {
|
|
|
10158
10151
|
}, 25_000);
|
|
10159
10152
|
}
|
|
10160
10153
|
|
|
10154
|
+
// GitHub reconciler hooks are currently optional; keep shutdown/reload calls safe.
|
|
10155
|
+
function restartGitHubReconciler() {}
|
|
10156
|
+
function stopGitHubReconciler() {}
|
|
10157
|
+
|
|
10161
10158
|
async function maybeTriggerTaskPlanner(reason, details, options = {}) {
|
|
10162
10159
|
if (internalTaskExecutor?.isPaused?.()) {
|
|
10163
10160
|
console.log("[monitor] task planner skipped: executor paused");
|
|
@@ -13056,6 +13053,61 @@ function formatOrchestratorTailForMonitorPrompt({
|
|
|
13056
13053
|
}
|
|
13057
13054
|
}
|
|
13058
13055
|
|
|
13056
|
+
const MONITOR_MONITOR_ACTIONABLE_DIGEST_MAX_AGE_MS = (() => {
|
|
13057
|
+
const raw = Number(
|
|
13058
|
+
process.env.DEVMODE_MONITOR_MONITOR_ACTIONABLE_DIGEST_MAX_AGE_MS ||
|
|
13059
|
+
15 * 60 * 1000,
|
|
13060
|
+
);
|
|
13061
|
+
if (!Number.isFinite(raw) || raw <= 0) return 15 * 60 * 1000;
|
|
13062
|
+
return Math.max(60 * 1000, Math.min(24 * 60 * 60 * 1000, Math.trunc(raw)));
|
|
13063
|
+
})();
|
|
13064
|
+
|
|
13065
|
+
function parseDigestEntryTimestampMs(entry, { nowMs = Date.now(), digestStartedAt = 0 } = {}) {
|
|
13066
|
+
const explicitTs = Number(entry?.timestamp ?? entry?.timeMs ?? entry?.ts);
|
|
13067
|
+
if (Number.isFinite(explicitTs) && explicitTs > 0) return explicitTs;
|
|
13068
|
+
|
|
13069
|
+
const timeText = String(entry?.time || "").trim();
|
|
13070
|
+
const match = timeText.match(/^(\d{2}):(\d{2}):(\d{2})$/);
|
|
13071
|
+
if (!match) return null;
|
|
13072
|
+
|
|
13073
|
+
const hour = Number(match[1]);
|
|
13074
|
+
const minute = Number(match[2]);
|
|
13075
|
+
const second = Number(match[3]);
|
|
13076
|
+
if (![hour, minute, second].every(Number.isFinite)) return null;
|
|
13077
|
+
|
|
13078
|
+
const nowDate = new Date(nowMs);
|
|
13079
|
+
let candidate = Date.UTC(
|
|
13080
|
+
nowDate.getUTCFullYear(),
|
|
13081
|
+
nowDate.getUTCMonth(),
|
|
13082
|
+
nowDate.getUTCDate(),
|
|
13083
|
+
hour,
|
|
13084
|
+
minute,
|
|
13085
|
+
second,
|
|
13086
|
+
0,
|
|
13087
|
+
);
|
|
13088
|
+
|
|
13089
|
+
// Digest times are rendered as UTC HH:MM:SS.
|
|
13090
|
+
if (candidate > nowMs + 60_000) {
|
|
13091
|
+
candidate -= 24 * 60 * 60 * 1000;
|
|
13092
|
+
}
|
|
13093
|
+
// If digest started before midnight and entry time is after midnight.
|
|
13094
|
+
if (
|
|
13095
|
+
digestStartedAt > 0 &&
|
|
13096
|
+
candidate < digestStartedAt - 60_000 &&
|
|
13097
|
+
candidate + 24 * 60 * 60 * 1000 <= nowMs + 60_000
|
|
13098
|
+
) {
|
|
13099
|
+
candidate += 24 * 60 * 60 * 1000;
|
|
13100
|
+
}
|
|
13101
|
+
return candidate;
|
|
13102
|
+
}
|
|
13103
|
+
|
|
13104
|
+
function isDigestEntryActionable(entry, { nowMs = Date.now(), digestStartedAt = 0 } = {}) {
|
|
13105
|
+
if (Number(entry?.priority || 99) > 3) return false;
|
|
13106
|
+
const timestampMs = parseDigestEntryTimestampMs(entry, { nowMs, digestStartedAt });
|
|
13107
|
+
if (!Number.isFinite(timestampMs)) return true;
|
|
13108
|
+
return nowMs - timestampMs <= MONITOR_MONITOR_ACTIONABLE_DIGEST_MAX_AGE_MS;
|
|
13109
|
+
}
|
|
13110
|
+
|
|
13059
13111
|
async function buildMonitorMonitorPrompt({ trigger, entries, text }) {
|
|
13060
13112
|
const digestSnapshot = getDigestSnapshot();
|
|
13061
13113
|
const digestEntries =
|
|
@@ -13063,8 +13115,10 @@ async function buildMonitorMonitorPrompt({ trigger, entries, text }) {
|
|
|
13063
13115
|
? entries
|
|
13064
13116
|
: digestSnapshot?.entries || [];
|
|
13065
13117
|
const latestDigestText = String(text || monitorMonitor.lastDigestText || "");
|
|
13066
|
-
const
|
|
13067
|
-
|
|
13118
|
+
const nowMs = Date.now();
|
|
13119
|
+
const digestStartedAt = Number(digestSnapshot?.startedAt || 0);
|
|
13120
|
+
const actionableEntries = digestEntries.filter((entry) =>
|
|
13121
|
+
isDigestEntryActionable(entry, { nowMs, digestStartedAt }),
|
|
13068
13122
|
);
|
|
13069
13123
|
const modeHint =
|
|
13070
13124
|
actionableEntries.length > 0 ? "reliability-fix" : "code-analysis";
|
|
@@ -14578,7 +14632,6 @@ function applyConfig(nextConfig, options = {}) {
|
|
|
14578
14632
|
if (workflowAutomationEnabled) {
|
|
14579
14633
|
ensureWorkflowAutomationEngine().catch(() => {});
|
|
14580
14634
|
}
|
|
14581
|
-
githubReconcile = nextConfig.githubReconcile || githubReconcile;
|
|
14582
14635
|
agentPrompts = nextConfig.agentPrompts;
|
|
14583
14636
|
configExecutorConfig = nextConfig.executorConfig;
|
|
14584
14637
|
executorScheduler = nextConfig.scheduler;
|
|
@@ -14698,7 +14751,6 @@ function applyConfig(nextConfig, options = {}) {
|
|
|
14698
14751
|
} else {
|
|
14699
14752
|
stopMonitorMonitorSupervisor();
|
|
14700
14753
|
}
|
|
14701
|
-
restartGitHubReconciler();
|
|
14702
14754
|
|
|
14703
14755
|
const nextArgs = scriptArgs?.join(" ") || "";
|
|
14704
14756
|
const scriptChanged = prevScriptPath !== scriptPath || prevArgs !== nextArgs;
|
|
@@ -14732,7 +14784,6 @@ process.on("SIGINT", async () => {
|
|
|
14732
14784
|
shuttingDown = true;
|
|
14733
14785
|
stopWorkspaceSyncTimers();
|
|
14734
14786
|
stopTaskPlannerStatusLoop();
|
|
14735
|
-
stopGitHubReconciler();
|
|
14736
14787
|
// Stop monitor-monitor immediately (it's safely restartable)
|
|
14737
14788
|
stopMonitorMonitorSupervisor();
|
|
14738
14789
|
if (vkLogStream) {
|
|
@@ -14786,7 +14837,6 @@ process.on("exit", () => {
|
|
|
14786
14837
|
shuttingDown = true;
|
|
14787
14838
|
stopWorkspaceSyncTimers();
|
|
14788
14839
|
stopTaskPlannerStatusLoop();
|
|
14789
|
-
stopGitHubReconciler();
|
|
14790
14840
|
stopMonitorMonitorSupervisor();
|
|
14791
14841
|
stopAgentAlertTailer();
|
|
14792
14842
|
stopAgentWorkAnalyzer();
|
|
@@ -14802,7 +14852,6 @@ process.on("SIGTERM", async () => {
|
|
|
14802
14852
|
shuttingDown = true;
|
|
14803
14853
|
stopWorkspaceSyncTimers();
|
|
14804
14854
|
stopTaskPlannerStatusLoop();
|
|
14805
|
-
stopGitHubReconciler();
|
|
14806
14855
|
// Stop monitor-monitor immediately (it's safely restartable)
|
|
14807
14856
|
stopMonitorMonitorSupervisor();
|
|
14808
14857
|
if (vkLogStream) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bosun",
|
|
3
|
-
"version": "0.36.
|
|
3
|
+
"version": "0.36.5",
|
|
4
4
|
"description": "AI-powered orchestrator supervisor — manages AI agent executors with failover, auto-restarts on failure, analyzes crashes with Codex SDK, creates PRs via Vibe-Kanban API, and sends Telegram notifications. Supports N executors with weighted distribution, multi-repo projects, and auto-setup.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache 2.0",
|
package/setup-web-server.mjs
CHANGED
|
@@ -436,8 +436,8 @@ function buildStableSetupDefaults({
|
|
|
436
436
|
workflowMaxConcurrentBranches: 8,
|
|
437
437
|
voiceEnabled: true,
|
|
438
438
|
voiceProvider: "auto",
|
|
439
|
-
voiceModel: "gpt-
|
|
440
|
-
voiceVisionModel: "gpt-4.1-
|
|
439
|
+
voiceModel: "gpt-audio-1.5",
|
|
440
|
+
voiceVisionModel: "gpt-4.1-nano",
|
|
441
441
|
voiceId: "alloy",
|
|
442
442
|
voiceTurnDetection: "server_vad",
|
|
443
443
|
voiceFallbackMode: "browser",
|
|
@@ -445,7 +445,7 @@ function buildStableSetupDefaults({
|
|
|
445
445
|
openaiRealtimeApiKey: "",
|
|
446
446
|
azureOpenaiRealtimeEndpoint: "",
|
|
447
447
|
azureOpenaiRealtimeApiKey: "",
|
|
448
|
-
azureOpenaiRealtimeDeployment: "gpt-
|
|
448
|
+
azureOpenaiRealtimeDeployment: "gpt-audio-1.5",
|
|
449
449
|
copilotEnableAllMcpTools: false,
|
|
450
450
|
// Backward-compatible fields consumed by older setup UI revisions.
|
|
451
451
|
distribution: "primary-only",
|
|
@@ -864,16 +864,16 @@ function applyNonBlockingSetupEnvDefaults(envMap, env = {}, sourceEnv = process.
|
|
|
864
864
|
env.VOICE_MODEL,
|
|
865
865
|
envMap.VOICE_MODEL,
|
|
866
866
|
sourceEnv.VOICE_MODEL,
|
|
867
|
-
) || "gpt-
|
|
868
|
-
).trim() || "gpt-
|
|
867
|
+
) || "gpt-audio-1.5",
|
|
868
|
+
).trim() || "gpt-audio-1.5";
|
|
869
869
|
envMap.VOICE_VISION_MODEL = String(
|
|
870
870
|
pickNonEmptyValue(
|
|
871
871
|
env.voiceVisionModel,
|
|
872
872
|
env.VOICE_VISION_MODEL,
|
|
873
873
|
envMap.VOICE_VISION_MODEL,
|
|
874
874
|
sourceEnv.VOICE_VISION_MODEL,
|
|
875
|
-
) || "gpt-4.1-
|
|
876
|
-
).trim() || "gpt-4.1-
|
|
875
|
+
) || "gpt-4.1-nano",
|
|
876
|
+
).trim() || "gpt-4.1-nano";
|
|
877
877
|
envMap.VOICE_ID = normalizeEnumValue(
|
|
878
878
|
pickNonEmptyValue(
|
|
879
879
|
env.voiceId,
|
|
@@ -953,8 +953,8 @@ function applyNonBlockingSetupEnvDefaults(envMap, env = {}, sourceEnv = process.
|
|
|
953
953
|
env.AZURE_OPENAI_REALTIME_DEPLOYMENT,
|
|
954
954
|
envMap.AZURE_OPENAI_REALTIME_DEPLOYMENT,
|
|
955
955
|
sourceEnv.AZURE_OPENAI_REALTIME_DEPLOYMENT,
|
|
956
|
-
) || "gpt-
|
|
957
|
-
).trim() || "gpt-
|
|
956
|
+
) || "gpt-audio-1.5",
|
|
957
|
+
).trim() || "gpt-audio-1.5";
|
|
958
958
|
|
|
959
959
|
envMap.CONTAINER_ENABLED = toBooleanEnvString(
|
|
960
960
|
pickNonEmptyValue(env.containerEnabled, envMap.CONTAINER_ENABLED, sourceEnv.CONTAINER_ENABLED),
|
|
@@ -125,12 +125,12 @@ export const SETTINGS_SCHEMA = [
|
|
|
125
125
|
// ── Voice Assistant ──────────────────────────────────────────
|
|
126
126
|
{ key: "VOICE_ENABLED", label: "Enable Voice Mode", category: "voice", type: "boolean", defaultVal: true, description: "Enable the real-time voice assistant in the chat UI." },
|
|
127
127
|
{ key: "VOICE_PROVIDER", label: "Voice Provider", category: "voice", type: "select", defaultVal: "auto", options: ["auto", "openai", "azure", "claude", "gemini", "fallback"], description: "Voice API provider. 'auto' selects based on available keys. 'fallback' uses browser speech APIs." },
|
|
128
|
-
{ key: "VOICE_MODEL", label: "Voice Model", category: "voice", type: "
|
|
129
|
-
{ key: "VOICE_VISION_MODEL", label: "Vision Model", category: "voice", type: "
|
|
128
|
+
{ key: "VOICE_MODEL", label: "Voice Model", category: "voice", type: "select", defaultVal: "gpt-audio-1.5", options: ["gpt-audio-1.5", "gpt-realtime-1.5", "gpt-4o-realtime-preview-2024-12-17", "gemini-2.5-pro", "gemini-2.5-flash", "claude-3-7-sonnet-latest", "custom"], description: "Audio model for voice sessions. Select 'custom' to enter a model slug manually." },
|
|
129
|
+
{ key: "VOICE_VISION_MODEL", label: "Vision Model", category: "voice", type: "select", defaultVal: "gpt-4.1-nano", options: ["gpt-4.1-nano", "gpt-4.1-mini", "gpt-4.1", "gemini-2.5-flash", "gemini-2.5-pro", "claude-3-7-sonnet-latest", "custom"], description: "Vision model for live screen/camera understanding. Select 'custom' to enter a model slug manually." },
|
|
130
130
|
{ key: "OPENAI_REALTIME_API_KEY", label: "OpenAI Realtime Key", category: "voice", type: "secret", sensitive: true, description: "Dedicated API key for voice. Falls back to OPENAI_API_KEY if not set." },
|
|
131
131
|
{ key: "AZURE_OPENAI_REALTIME_ENDPOINT", label: "Azure Realtime Endpoint", category: "voice", type: "string", description: "Azure OpenAI endpoint for Realtime API (e.g., https://myresource.openai.azure.com).", validate: "^$|^https?://" },
|
|
132
132
|
{ key: "AZURE_OPENAI_REALTIME_API_KEY", label: "Azure Realtime Key", category: "voice", type: "secret", sensitive: true, description: "Azure OpenAI API key for Realtime API. Falls back to AZURE_OPENAI_API_KEY if not set." },
|
|
133
|
-
{ key: "AZURE_OPENAI_REALTIME_DEPLOYMENT", label: "Azure Deployment", category: "voice", type: "
|
|
133
|
+
{ key: "AZURE_OPENAI_REALTIME_DEPLOYMENT", label: "Azure Deployment", category: "voice", type: "select", defaultVal: "gpt-audio-1.5", options: ["gpt-audio-1.5", "gpt-realtime-1.5", "gpt-4o-realtime-preview", "custom"], description: "Azure deployment name for the Realtime model. Select 'custom' to enter manually." },
|
|
134
134
|
{ key: "VOICE_ID", label: "Voice", category: "voice", type: "select", defaultVal: "alloy", options: ["alloy", "ash", "ballad", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer", "verse"], description: "Voice personality for text-to-speech output." },
|
|
135
135
|
{ key: "VOICE_TURN_DETECTION", label: "Turn Detection", category: "voice", type: "select", defaultVal: "server_vad", options: ["server_vad", "semantic_vad", "none"], description: "How the model detects when you stop speaking. 'semantic_vad' is more intelligent but higher latency." },
|
|
136
136
|
{ key: "VOICE_DELEGATE_EXECUTOR", label: "Delegate Executor", category: "voice", type: "select", defaultVal: "codex-sdk", options: ["codex-sdk", "copilot-sdk", "claude-sdk", "gemini-sdk", "opencode-sdk"], description: "Which agent executor voice tool calls delegate to for complex tasks." },
|
|
@@ -307,8 +307,11 @@ export function validateSetting(def, value) {
|
|
|
307
307
|
return { valid: false, error: "Must be true or false" };
|
|
308
308
|
return { valid: true };
|
|
309
309
|
case "select":
|
|
310
|
-
if (def.options && !def.options.includes(String(value)))
|
|
310
|
+
if (def.options && !def.options.includes(String(value))) {
|
|
311
|
+
// Allow arbitrary values when the schema includes "custom" as an option
|
|
312
|
+
if (def.options.includes("custom")) return { valid: true };
|
|
311
313
|
return { valid: false, error: `Must be one of: ${def.options.join(", ")}` };
|
|
314
|
+
}
|
|
312
315
|
return { valid: true };
|
|
313
316
|
default:
|
|
314
317
|
if (def.validate) {
|
package/ui/setup.html
CHANGED
|
@@ -705,8 +705,8 @@ function App() {
|
|
|
705
705
|
// Voice assistant
|
|
706
706
|
const [voiceEnabled, setVoiceEnabled] = useState(true);
|
|
707
707
|
const [voiceProvider, setVoiceProvider] = useState("auto");
|
|
708
|
-
const [voiceModel, setVoiceModel] = useState("gpt-
|
|
709
|
-
const [voiceVisionModel, setVoiceVisionModel] = useState("gpt-4.1-
|
|
708
|
+
const [voiceModel, setVoiceModel] = useState("gpt-audio-1.5");
|
|
709
|
+
const [voiceVisionModel, setVoiceVisionModel] = useState("gpt-4.1-nano");
|
|
710
710
|
const [voiceId, setVoiceId] = useState("alloy");
|
|
711
711
|
const [voiceTurnDetection, setVoiceTurnDetection] = useState("server_vad");
|
|
712
712
|
const [voiceFallbackMode, setVoiceFallbackMode] = useState("browser");
|
|
@@ -714,7 +714,7 @@ function App() {
|
|
|
714
714
|
const [openaiRealtimeApiKey, setOpenaiRealtimeApiKey] = useState("");
|
|
715
715
|
const [azureOpenaiRealtimeEndpoint, setAzureOpenaiRealtimeEndpoint] = useState("");
|
|
716
716
|
const [azureOpenaiRealtimeApiKey, setAzureOpenaiRealtimeApiKey] = useState("");
|
|
717
|
-
const [azureOpenaiRealtimeDeployment, setAzureOpenaiRealtimeDeployment] = useState("gpt-
|
|
717
|
+
const [azureOpenaiRealtimeDeployment, setAzureOpenaiRealtimeDeployment] = useState("gpt-audio-1.5");
|
|
718
718
|
const [voiceProviders, setVoiceProviders] = useState([
|
|
719
719
|
{
|
|
720
720
|
id: Date.now(),
|
|
@@ -727,11 +727,11 @@ function App() {
|
|
|
727
727
|
]);
|
|
728
728
|
|
|
729
729
|
const VOICE_PROVIDER_MODEL_DEFAULTS = {
|
|
730
|
-
openai: { model: "gpt-4o-realtime-preview-2024-12-17",
|
|
731
|
-
azure: { model: "gpt-
|
|
732
|
-
claude: { model: "claude-3-7-sonnet-latest", visionModel: "claude-3-7-sonnet-latest" },
|
|
733
|
-
gemini: { model: "gemini-2.5-pro", visionModel: "gemini-2.5-flash" },
|
|
734
|
-
fallback: { model: "", visionModel: "" },
|
|
730
|
+
openai: { model: "gpt-audio-1.5", visionModel: "gpt-4.1-nano", models: ["gpt-audio-1.5", "gpt-realtime-1.5", "gpt-4o-realtime-preview-2024-12-17"], visionModels: ["gpt-4.1-nano", "gpt-4.1-mini", "gpt-4.1"] },
|
|
731
|
+
azure: { model: "gpt-audio-1.5", visionModel: "gpt-4.1-nano", models: ["gpt-audio-1.5", "gpt-realtime-1.5", "gpt-4o-realtime-preview"], visionModels: ["gpt-4.1-nano", "gpt-4.1-mini", "gpt-4.1"] },
|
|
732
|
+
claude: { model: "claude-3-7-sonnet-latest", visionModel: "claude-3-7-sonnet-latest", models: ["claude-3-7-sonnet-latest", "claude-sonnet-4-20250514"], visionModels: ["claude-3-7-sonnet-latest", "claude-sonnet-4-20250514"] },
|
|
733
|
+
gemini: { model: "gemini-2.5-pro", visionModel: "gemini-2.5-flash", models: ["gemini-2.5-pro", "gemini-2.5-flash"], visionModels: ["gemini-2.5-flash", "gemini-2.5-pro"] },
|
|
734
|
+
fallback: { model: "", visionModel: "", models: [], visionModels: [] },
|
|
735
735
|
};
|
|
736
736
|
|
|
737
737
|
const getVoiceProviderModelDefaults = (provider) =>
|
|
@@ -748,7 +748,7 @@ function App() {
|
|
|
748
748
|
const normalizedAzureDeployment = String(
|
|
749
749
|
entry.azureDeployment ??
|
|
750
750
|
fallback.azureDeployment ??
|
|
751
|
-
(normalizedProvider === "azure" ? "gpt-
|
|
751
|
+
(normalizedProvider === "azure" ? "gpt-audio-1.5" : ""),
|
|
752
752
|
).trim();
|
|
753
753
|
return {
|
|
754
754
|
id: entry.id || Date.now() + Math.random(),
|
|
@@ -783,7 +783,7 @@ function App() {
|
|
|
783
783
|
visionModel: defaults_.visionModel,
|
|
784
784
|
azureDeployment:
|
|
785
785
|
nextProvider === "azure"
|
|
786
|
-
? String(target.azureDeployment || "gpt-
|
|
786
|
+
? String(target.azureDeployment || "gpt-audio-1.5")
|
|
787
787
|
: "",
|
|
788
788
|
});
|
|
789
789
|
return list;
|
|
@@ -1157,10 +1157,10 @@ function App() {
|
|
|
1157
1157
|
if (env.AZURE_OPENAI_REALTIME_API_KEY) { setAzureOpenaiRealtimeApiKey(env.AZURE_OPENAI_REALTIME_API_KEY); envLoaded = true; }
|
|
1158
1158
|
if (env.AZURE_OPENAI_REALTIME_DEPLOYMENT) { setAzureOpenaiRealtimeDeployment(env.AZURE_OPENAI_REALTIME_DEPLOYMENT); envLoaded = true; }
|
|
1159
1159
|
const resolvedVoiceProvider = String(env.VOICE_PROVIDER || existingVoice.provider || d.voiceProvider || voiceProvider || "openai").trim().toLowerCase();
|
|
1160
|
-
const resolvedVoiceModel = String(env.VOICE_MODEL || existingVoice.model || d.voiceModel || voiceModel || "gpt-
|
|
1161
|
-
const resolvedVoiceVisionModel = String(env.VOICE_VISION_MODEL || existingVoice.visionModel || d.voiceVisionModel || voiceVisionModel || "gpt-4.1-
|
|
1160
|
+
const resolvedVoiceModel = String(env.VOICE_MODEL || existingVoice.model || d.voiceModel || voiceModel || "gpt-audio-1.5").trim();
|
|
1161
|
+
const resolvedVoiceVisionModel = String(env.VOICE_VISION_MODEL || existingVoice.visionModel || d.voiceVisionModel || voiceVisionModel || "gpt-4.1-nano").trim();
|
|
1162
1162
|
const resolvedVoiceId = String(env.VOICE_ID || existingVoice.voiceId || d.voiceId || voiceId || "alloy").trim();
|
|
1163
|
-
const resolvedAzureDeployment = String(env.AZURE_OPENAI_REALTIME_DEPLOYMENT || existingVoice.azureDeployment || d.azureOpenaiRealtimeDeployment || azureOpenaiRealtimeDeployment || "gpt-
|
|
1163
|
+
const resolvedAzureDeployment = String(env.AZURE_OPENAI_REALTIME_DEPLOYMENT || existingVoice.azureDeployment || d.azureOpenaiRealtimeDeployment || azureOpenaiRealtimeDeployment || "gpt-audio-1.5").trim();
|
|
1164
1164
|
if (Array.isArray(existingVoice.providers) && existingVoice.providers.length > 0) {
|
|
1165
1165
|
setVoiceProviders(
|
|
1166
1166
|
normalizeVoiceProviders(existingVoice.providers, {
|
|
@@ -2521,23 +2521,25 @@ function App() {
|
|
|
2521
2521
|
|
|
2522
2522
|
// ── Step 7: Advanced Settings ──────────────────────────────────────────────
|
|
2523
2523
|
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
</
|
|
2536
|
-
${open && html`<div style="padding:16px">${children}</div>`}
|
|
2524
|
+
// Section is defined at module scope so its identity is stable across
|
|
2525
|
+
// re-renders. Defining it inside StepAdvanced would create a new function
|
|
2526
|
+
// reference on every state update, causing Preact to unmount/remount the
|
|
2527
|
+
// component (losing open state + input focus).
|
|
2528
|
+
function Section({ id, title, defaultOpen, children }) {
|
|
2529
|
+
const [open, setOpen] = useState(!!defaultOpen);
|
|
2530
|
+
return html`
|
|
2531
|
+
<div style="border:1px solid var(--border-primary);border-radius:var(--radius-sm);margin-bottom:10px;overflow:hidden">
|
|
2532
|
+
<div style="display:flex;align-items:center;justify-content:space-between;padding:12px 16px;cursor:pointer;background:var(--bg-input);user-select:none"
|
|
2533
|
+
onclick=${() => setOpen((v) => !v)}>
|
|
2534
|
+
<span style="font-weight:600;font-size:0.88rem">${iconText(title)}</span>
|
|
2535
|
+
<span style="color:var(--text-dim);font-size:0.8rem">${open ? "▲" : "▼"}</span>
|
|
2537
2536
|
</div>
|
|
2538
|
-
|
|
2539
|
-
|
|
2537
|
+
${open && html`<div style="padding:16px">${children}</div>`}
|
|
2538
|
+
</div>
|
|
2539
|
+
`;
|
|
2540
|
+
}
|
|
2540
2541
|
|
|
2542
|
+
const StepAdvanced = () => {
|
|
2541
2543
|
return html`
|
|
2542
2544
|
<h2>Advanced Settings</h2>
|
|
2543
2545
|
<p class="step-desc">Fine-tune execution, model profiles, and infrastructure. Defaults are sensible — only change what you need.</p>
|
|
@@ -2876,21 +2878,73 @@ function App() {
|
|
|
2876
2878
|
</div>
|
|
2877
2879
|
<div class="form-group">
|
|
2878
2880
|
<label>Model</label>
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2881
|
+
${(() => {
|
|
2882
|
+
const defaults_ = getVoiceProviderModelDefaults(providerRow.provider);
|
|
2883
|
+
const knownModels = defaults_.models || [];
|
|
2884
|
+
const isCustom = knownModels.length > 0 && !knownModels.includes(providerRow.model) && providerRow.model !== "";
|
|
2885
|
+
return html`
|
|
2886
|
+
<select value=${isCustom ? "custom" : providerRow.model}
|
|
2887
|
+
onchange=${(e) => {
|
|
2888
|
+
if (e.target.value === "custom") {
|
|
2889
|
+
updateVoiceProviderRow(idx, "model", "");
|
|
2890
|
+
} else {
|
|
2891
|
+
updateVoiceProviderRow(idx, "model", e.target.value);
|
|
2892
|
+
}
|
|
2893
|
+
}}>
|
|
2894
|
+
${knownModels.map((m) => html`<option value=${m}>${m}</option>`)}
|
|
2895
|
+
<option value="custom">custom...</option>
|
|
2896
|
+
</select>
|
|
2897
|
+
${(isCustom || (knownModels.length > 0 && !knownModels.includes(providerRow.model))) && html`
|
|
2898
|
+
<input type="text" value=${providerRow.model}
|
|
2899
|
+
oninput=${(e) => updateVoiceProviderRow(idx, "model", e.target.value)}
|
|
2900
|
+
placeholder="Enter custom model slug..."
|
|
2901
|
+
style="margin-top:4px"
|
|
2902
|
+
/>
|
|
2903
|
+
`}
|
|
2904
|
+
${knownModels.length === 0 && html`
|
|
2905
|
+
<input type="text" value=${providerRow.model}
|
|
2906
|
+
oninput=${(e) => updateVoiceProviderRow(idx, "model", e.target.value)}
|
|
2907
|
+
placeholder="Provider model"
|
|
2908
|
+
style="margin-top:4px"
|
|
2909
|
+
/>
|
|
2910
|
+
`}
|
|
2911
|
+
`;
|
|
2912
|
+
})()}
|
|
2885
2913
|
</div>
|
|
2886
2914
|
<div class="form-group">
|
|
2887
2915
|
<label>Vision Model</label>
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2916
|
+
${(() => {
|
|
2917
|
+
const defaults_ = getVoiceProviderModelDefaults(providerRow.provider);
|
|
2918
|
+
const knownVisionModels = defaults_.visionModels || [];
|
|
2919
|
+
const isCustomVision = knownVisionModels.length > 0 && !knownVisionModels.includes(providerRow.visionModel) && providerRow.visionModel !== "";
|
|
2920
|
+
return html`
|
|
2921
|
+
<select value=${isCustomVision ? "custom" : providerRow.visionModel}
|
|
2922
|
+
onchange=${(e) => {
|
|
2923
|
+
if (e.target.value === "custom") {
|
|
2924
|
+
updateVoiceProviderRow(idx, "visionModel", "");
|
|
2925
|
+
} else {
|
|
2926
|
+
updateVoiceProviderRow(idx, "visionModel", e.target.value);
|
|
2927
|
+
}
|
|
2928
|
+
}}>
|
|
2929
|
+
${knownVisionModels.map((m) => html`<option value=${m}>${m}</option>`)}
|
|
2930
|
+
<option value="custom">custom...</option>
|
|
2931
|
+
</select>
|
|
2932
|
+
${(isCustomVision || (knownVisionModels.length > 0 && !knownVisionModels.includes(providerRow.visionModel))) && html`
|
|
2933
|
+
<input type="text" value=${providerRow.visionModel}
|
|
2934
|
+
oninput=${(e) => updateVoiceProviderRow(idx, "visionModel", e.target.value)}
|
|
2935
|
+
placeholder="Enter custom vision model slug..."
|
|
2936
|
+
style="margin-top:4px"
|
|
2937
|
+
/>
|
|
2938
|
+
`}
|
|
2939
|
+
${knownVisionModels.length === 0 && html`
|
|
2940
|
+
<input type="text" value=${providerRow.visionModel}
|
|
2941
|
+
oninput=${(e) => updateVoiceProviderRow(idx, "visionModel", e.target.value)}
|
|
2942
|
+
placeholder="Provider vision model"
|
|
2943
|
+
style="margin-top:4px"
|
|
2944
|
+
/>
|
|
2945
|
+
`}
|
|
2946
|
+
`;
|
|
2947
|
+
})()}
|
|
2894
2948
|
</div>
|
|
2895
2949
|
<div class="form-group">
|
|
2896
2950
|
<label>Voice Persona</label>
|
|
@@ -2915,7 +2969,7 @@ function App() {
|
|
|
2915
2969
|
type="text"
|
|
2916
2970
|
value=${providerRow.azureDeployment || ""}
|
|
2917
2971
|
oninput=${(e) => updateVoiceProviderRow(idx, "azureDeployment", e.target.value)}
|
|
2918
|
-
placeholder="gpt-
|
|
2972
|
+
placeholder="gpt-audio-1.5"
|
|
2919
2973
|
/>
|
|
2920
2974
|
</div>
|
|
2921
2975
|
`}
|
package/voice-agents-sdk.mjs
CHANGED
|
@@ -50,7 +50,7 @@ async function getGoogleGenAI() {
|
|
|
50
50
|
|
|
51
51
|
// ── Constants ───────────────────────────────────────────────────────────────
|
|
52
52
|
|
|
53
|
-
const OPENAI_REALTIME_MODEL = "gpt-
|
|
53
|
+
const OPENAI_REALTIME_MODEL = "gpt-audio-1.5";
|
|
54
54
|
const GEMINI_LIVE_MODEL = "gemini-2.5-flash-native-audio-preview-12-2025";
|
|
55
55
|
|
|
56
56
|
const SDK_PROVIDERS = Object.freeze({
|
|
@@ -316,7 +316,7 @@ export async function connectRealtimeSession(sessionHandle, config = {}) {
|
|
|
316
316
|
}
|
|
317
317
|
const endpoint = String(config.azureEndpoint || "").trim().replace(/\/+$/, "");
|
|
318
318
|
const deployment = String(
|
|
319
|
-
config.azureDeployment || "gpt-
|
|
319
|
+
config.azureDeployment || "gpt-audio-1.5",
|
|
320
320
|
).trim();
|
|
321
321
|
connectOpts.apiKey = credential;
|
|
322
322
|
connectOpts.url = `${endpoint}/openai/realtime?api-version=2025-04-01-preview&deployment=${deployment}`;
|
package/voice-relay.mjs
CHANGED
|
@@ -22,9 +22,9 @@ let _configLoadedAt = 0; // timestamp of last config load
|
|
|
22
22
|
const CONFIG_TTL_MS = 30_000; // re-read config every 30s
|
|
23
23
|
|
|
24
24
|
const OPENAI_REALTIME_URL = "https://api.openai.com/v1/realtime";
|
|
25
|
-
const OPENAI_REALTIME_MODEL = "gpt-
|
|
25
|
+
const OPENAI_REALTIME_MODEL = "gpt-audio-1.5";
|
|
26
26
|
const OPENAI_RESPONSES_URL = "https://api.openai.com/v1/responses";
|
|
27
|
-
const OPENAI_DEFAULT_VISION_MODEL = "gpt-4.1-
|
|
27
|
+
const OPENAI_DEFAULT_VISION_MODEL = "gpt-4.1-nano";
|
|
28
28
|
|
|
29
29
|
const AZURE_API_VERSION = "2025-04-01-preview";
|
|
30
30
|
const ANTHROPIC_MESSAGES_URL = "https://api.anthropic.com/v1/messages";
|