mixdog 0.7.8 → 0.7.12
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/.claude-plugin/marketplace.json +5 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +40 -0
- package/README.md +198 -251
- package/bin/statusline-launcher.mjs +5 -1
- package/bin/statusline-lib.mjs +14 -6
- package/bin/statusline.mjs +14 -6
- package/hooks/lib/settings-loader.cjs +4 -3
- package/hooks/pre-tool-subagent.cjs +7 -2
- package/hooks/session-start.cjs +52 -24
- package/lib/mixdog-debug.cjs +163 -0
- package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
- package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
- package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
- package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
- package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
- package/package.json +1 -1
- package/scripts/builtin-utils-smoke.mjs +14 -8
- package/scripts/bump.mjs +80 -0
- package/scripts/doctor.mjs +8 -3
- package/scripts/mutation-io-smoke.mjs +17 -1
- package/scripts/openai-oauth-catalog-smoke.mjs +53 -0
- package/scripts/permission-eval-smoke.mjs +18 -1
- package/scripts/statusline-launcher-smoke.mjs +2 -2
- package/scripts/webhook-selfheal-smoke.mjs +1 -3
- package/server-main.mjs +57 -3
- package/setup/config-merge.mjs +0 -1
- package/setup/install.mjs +241 -51
- package/setup/mixdog-cli.mjs +30 -3
- package/setup/setup-server.mjs +21 -33
- package/setup/setup.html +46 -11
- package/setup/tui.mjs +35 -316
- package/src/agent/orchestrator/config.mjs +0 -1
- package/src/agent/orchestrator/providers/anthropic-oauth.mjs +2 -5
- package/src/agent/orchestrator/providers/anthropic.mjs +243 -86
- package/src/agent/orchestrator/providers/gemini.mjs +386 -31
- package/src/agent/orchestrator/providers/grok-oauth.mjs +2 -5
- package/src/agent/orchestrator/providers/model-catalog.mjs +146 -13
- package/src/agent/orchestrator/providers/openai-compat-stream.mjs +366 -0
- package/src/agent/orchestrator/providers/openai-compat.mjs +74 -30
- package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +2 -1
- package/src/agent/orchestrator/providers/openai-oauth.mjs +66 -13
- package/src/agent/orchestrator/providers/openai-ws.mjs +23 -0
- package/src/agent/orchestrator/session/manager.mjs +18 -4
- package/src/agent/orchestrator/stall-policy.mjs +6 -0
- package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +29 -8
- package/src/agent/orchestrator/tools/graph-manifest.json +11 -11
- package/src/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/channels/index.mjs +27 -8
- package/src/channels/lib/event-queue.mjs +24 -1
- package/src/channels/lib/hook-pipe-server.mjs +21 -8
- package/src/channels/lib/webhook.mjs +142 -20
- package/src/memory/lib/memory-cycle1.mjs +7 -3
- package/src/memory/lib/memory-recall-store.mjs +27 -10
- package/src/search/lib/backends/openai-oauth.mjs +6 -2
- package/src/search/lib/cache.mjs +55 -7
- package/src/shared/config.mjs +1 -1
- package/src/shared/llm/cost.mjs +2 -2
- package/src/shared/open-url.mjs +37 -0
- package/src/shared/seed.mjs +20 -3
- package/src/shared/user-data-guard.mjs +3 -1
- package/scripts/test-config-rmw-restore.mjs +0 -122
- package/setup/wizard.mjs +0 -696
package/setup/mixdog-cli.mjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// mixdog-cli.mjs — `mixdog` bin dispatcher: launch Claude Code with the dev
|
|
3
|
-
// plugin load flags
|
|
3
|
+
// plugin load flags; no args → install if plugin not registered else launch;
|
|
4
|
+
// `install` → always runInstall(); other args → launch passthrough.
|
|
4
5
|
|
|
5
6
|
import { spawn } from 'node:child_process';
|
|
6
7
|
import { realpathSync } from 'node:fs';
|
|
7
8
|
import { constants as osConstants } from 'node:os';
|
|
8
9
|
import { fileURLToPath } from 'node:url';
|
|
9
10
|
import { DEFAULT_MARKETPLACE, DEFAULT_PLUGIN } from '../src/shared/plugin-paths.mjs';
|
|
10
|
-
import { runInstall } from './install.mjs';
|
|
11
|
+
import { isPluginRegistered, runInstall } from './install.mjs';
|
|
11
12
|
import { resolveClaudeExecutable } from './locate-claude.mjs';
|
|
12
13
|
|
|
13
14
|
export { resolveClaudeExecutable } from './locate-claude.mjs';
|
|
@@ -21,7 +22,8 @@ export function buildClaudeLaunchArgv(passthrough = []) {
|
|
|
21
22
|
|
|
22
23
|
export async function dispatchMixdogCli(argv = process.argv.slice(2)) {
|
|
23
24
|
const [first] = argv;
|
|
24
|
-
|
|
25
|
+
|
|
26
|
+
if (first === 'install') {
|
|
25
27
|
if (process.env.MIXDOG_CLI_DRY_RUN === '1') {
|
|
26
28
|
process.stdout.write('mixdog-cli: route=setup\n');
|
|
27
29
|
return 0;
|
|
@@ -30,6 +32,31 @@ export async function dispatchMixdogCli(argv = process.argv.slice(2)) {
|
|
|
30
32
|
return 0;
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
if (argv.length === 0) {
|
|
36
|
+
const registered = isPluginRegistered();
|
|
37
|
+
if (process.env.MIXDOG_CLI_DRY_RUN === '1') {
|
|
38
|
+
process.stdout.write(
|
|
39
|
+
registered
|
|
40
|
+
? `mixdog-cli: claude ${JSON.stringify(buildClaudeLaunchArgv([]))}\n`
|
|
41
|
+
: 'mixdog-cli: route=setup+launch\n',
|
|
42
|
+
);
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
// First run (plugin not yet registered): walk through setup, then launch in
|
|
46
|
+
// the SAME invocation so `mixdog` is the one command that onboards AND opens
|
|
47
|
+
// Claude Code — no dead-end, no required second run.
|
|
48
|
+
if (!registered) await runInstall({ launchAfter: true });
|
|
49
|
+
const claudeArgs = buildClaudeLaunchArgv([]);
|
|
50
|
+
const claudePath = resolveClaudeExecutable();
|
|
51
|
+
if (!claudePath) {
|
|
52
|
+
process.stderr.write(
|
|
53
|
+
'\n✗ `claude` was not found on PATH. Install Claude Code first: https://docs.anthropic.com/en/docs/claude-code\n',
|
|
54
|
+
);
|
|
55
|
+
return 127;
|
|
56
|
+
}
|
|
57
|
+
return launchClaude(claudePath, claudeArgs);
|
|
58
|
+
}
|
|
59
|
+
|
|
33
60
|
const claudeArgs = buildClaudeLaunchArgv(argv);
|
|
34
61
|
if (process.env.MIXDOG_CLI_DRY_RUN === '1') {
|
|
35
62
|
process.stdout.write(`mixdog-cli: claude ${JSON.stringify(claudeArgs)}\n`);
|
package/setup/setup-server.mjs
CHANGED
|
@@ -91,26 +91,13 @@ function dropRuntimeModelCaches() {
|
|
|
91
91
|
}
|
|
92
92
|
dropRuntimeModelCaches();
|
|
93
93
|
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
markUserDataInitialized(DATA_DIR);
|
|
102
|
-
}
|
|
103
|
-
if (!existsSync(USER_WORKFLOW_MD_PATH) && shouldSeedMissingUserData(DATA_DIR, 'user-workflow.md')) {
|
|
104
|
-
mkdirSync(DATA_DIR, { recursive: true });
|
|
105
|
-
writeFileSync(USER_WORKFLOW_MD_PATH, DEFAULT_USER_WORKFLOW_MD);
|
|
106
|
-
markUserDataInitialized(DATA_DIR);
|
|
107
|
-
}
|
|
108
|
-
} catch {}
|
|
109
|
-
|
|
110
|
-
// Seed plugin-owned scaffolding files (memory-config.json, etc.).
|
|
111
|
-
// Idempotent — ensureDataSeeds skips existing files; fatal throw propagates
|
|
112
|
-
// anything that already exists, so the agent/index.mjs call and this one
|
|
113
|
-
// can both run without colliding.
|
|
94
|
+
// First-install seeding SSOT. ensureDataSeeds seeds mixdog-config.json AND
|
|
95
|
+
// user-workflow.json / user-workflow.md together as one fresh-install set, so
|
|
96
|
+
// the role→preset mapping always lands regardless of which boot path (this
|
|
97
|
+
// server, MCP boot, or agent index) runs first. It must NOT be split: seeding
|
|
98
|
+
// user-workflow here separately would set the init marker early and make
|
|
99
|
+
// ensureDataSeeds refuse the remaining first-time seeds. Idempotent — existing
|
|
100
|
+
// files are left untouched.
|
|
114
101
|
ensureDataSeeds(DATA_DIR);
|
|
115
102
|
|
|
116
103
|
// -- Helpers --
|
|
@@ -155,7 +142,7 @@ const SUPPORTED_PROVIDERS = [
|
|
|
155
142
|
'anthropic-oauth', 'openai-oauth', 'openai-api', 'gemini-api', 'xai-api', 'grok-oauth',
|
|
156
143
|
'tavily', 'firecrawl', 'exa',
|
|
157
144
|
];
|
|
158
|
-
const AGENT_KEY_PROVIDER_IDS = ['openai', 'anthropic', 'gemini', 'deepseek', 'xai'
|
|
145
|
+
const AGENT_KEY_PROVIDER_IDS = ['openai', 'anthropic', 'gemini', 'deepseek', 'xai'];
|
|
159
146
|
const SEARCH_KEY_PROVIDER_IDS = ['firecrawl', 'tavily', 'exa'];
|
|
160
147
|
const AGENT_PROVIDER_ENV = Object.freeze({
|
|
161
148
|
openai: 'OPENAI_API_KEY',
|
|
@@ -163,7 +150,6 @@ const AGENT_PROVIDER_ENV = Object.freeze({
|
|
|
163
150
|
gemini: 'GEMINI_API_KEY',
|
|
164
151
|
deepseek: 'DEEPSEEK_API_KEY',
|
|
165
152
|
xai: 'XAI_API_KEY',
|
|
166
|
-
nvidia: 'NVIDIA_API_KEY',
|
|
167
153
|
});
|
|
168
154
|
|
|
169
155
|
function envSecretPresent(account) {
|
|
@@ -420,11 +406,6 @@ async function validateAgentKey(provider, key) {
|
|
|
420
406
|
{ model: 'grok-3-mini-fast', messages: [{ role: 'user', content: 'hi' }], max_tokens: 1 },
|
|
421
407
|
{ 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' });
|
|
422
408
|
return 'valid';
|
|
423
|
-
case 'nvidia':
|
|
424
|
-
await httpPostJson('https://integrate.api.nvidia.com/v1/chat/completions',
|
|
425
|
-
{ model: 'meta/llama-3.3-70b-instruct', messages: [{ role: 'user', content: 'hi' }], max_tokens: 1 },
|
|
426
|
-
{ 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' });
|
|
427
|
-
return 'valid';
|
|
428
409
|
default: return 'valid';
|
|
429
410
|
}
|
|
430
411
|
} catch { return 'invalid'; }
|
|
@@ -487,7 +468,7 @@ async function detectAuth(config = {}) {
|
|
|
487
468
|
for (const [name, envKey] of [
|
|
488
469
|
['openai', 'OPENAI_API_KEY'], ['anthropic', 'ANTHROPIC_API_KEY'],
|
|
489
470
|
['gemini', 'GEMINI_API_KEY'], ['deepseek', 'DEEPSEEK_API_KEY'],
|
|
490
|
-
['xai', 'XAI_API_KEY'],
|
|
471
|
+
['xai', 'XAI_API_KEY'],
|
|
491
472
|
]) { result.envKeys[name] = !!process.env[envKey]; }
|
|
492
473
|
// GROK_API_KEY is the last-resort xAI env alias honored by getAgentApiKey('xai')
|
|
493
474
|
// (shared/config.mjs is the SSOT); mirror it here so a GROK_API_KEY-only env
|
|
@@ -497,7 +478,7 @@ async function detectAuth(config = {}) {
|
|
|
497
478
|
// the secret value never leaves the server, so the UI can show "Set" without
|
|
498
479
|
// exposing the key.
|
|
499
480
|
result.keyStored = {};
|
|
500
|
-
for (const name of ['openai', 'anthropic', 'gemini', 'deepseek', 'xai'
|
|
481
|
+
for (const name of ['openai', 'anthropic', 'gemini', 'deepseek', 'xai']) {
|
|
501
482
|
result.keyStored[name] = hasStoredSecret(SECRET_ACCOUNTS.agentApiKey(name));
|
|
502
483
|
}
|
|
503
484
|
const ollamaUrl = config?.providers?.ollama?.baseURL || 'http://localhost:11434/v1';
|
|
@@ -537,7 +518,7 @@ async function detectAuth(config = {}) {
|
|
|
537
518
|
// in sync with src/agent/orchestrator/providers/registry.mjs.
|
|
538
519
|
const _RUNTIME_PROVIDER_NAMES = [
|
|
539
520
|
'anthropic', 'anthropic-oauth', 'openai', 'openai-oauth',
|
|
540
|
-
'gemini', 'deepseek', 'xai', 'grok-oauth',
|
|
521
|
+
'gemini', 'deepseek', 'xai', 'grok-oauth',
|
|
541
522
|
'ollama', 'lmstudio',
|
|
542
523
|
];
|
|
543
524
|
|
|
@@ -618,9 +599,17 @@ function _modelFromConfiguredId(id, provider) {
|
|
|
618
599
|
}
|
|
619
600
|
|
|
620
601
|
function _familyFromModelId(id) {
|
|
621
|
-
const
|
|
602
|
+
const s = String(id || '').toLowerCase();
|
|
603
|
+
const claude = s.match(/^claude-(opus|sonnet|haiku)/i);
|
|
622
604
|
if (claude) return claude[1].toLowerCase();
|
|
623
|
-
|
|
605
|
+
if (s.includes('nano')) return 'gpt-nano';
|
|
606
|
+
if (s.includes('mini')) return 'gpt-mini';
|
|
607
|
+
if (s.includes('codex')) return 'gpt-codex';
|
|
608
|
+
if (s.startsWith('gpt-5.5')) return 'gpt-5.5';
|
|
609
|
+
if (s.startsWith('gpt-5.4')) return 'gpt-5.4';
|
|
610
|
+
if (s.startsWith('gpt-5.2')) return 'gpt-5.2';
|
|
611
|
+
if (s.startsWith('gpt-5')) return 'gpt-5';
|
|
612
|
+
const gpt = s.match(/^(gpt-\d+(?:\.\d+)?)/i);
|
|
624
613
|
if (gpt) return gpt[1].toLowerCase();
|
|
625
614
|
return undefined;
|
|
626
615
|
}
|
|
@@ -690,7 +679,6 @@ async function listProviderModels(providerId, cfg) {
|
|
|
690
679
|
openai: { url: 'https://api.openai.com/v1/models', auth: k => ({ 'Authorization': `Bearer ${k}` }) },
|
|
691
680
|
xai: { url: 'https://api.x.ai/v1/models', auth: k => ({ 'Authorization': `Bearer ${k}` }) },
|
|
692
681
|
deepseek: { url: 'https://api.deepseek.com/v1/models', auth: k => ({ 'Authorization': `Bearer ${k}` }) },
|
|
693
|
-
nvidia: { url: 'https://integrate.api.nvidia.com/v1/models', auth: k => ({ 'Authorization': `Bearer ${k}` }) },
|
|
694
682
|
};
|
|
695
683
|
const ep = KNOWN_ENDPOINTS[httpLookupId];
|
|
696
684
|
if (ep && pcfg.apiKey) {
|
package/setup/setup.html
CHANGED
|
@@ -2396,7 +2396,7 @@ function renderWebhookSection() {
|
|
|
2396
2396
|
'<a href="https://dashboard.ngrok.com/get-started/your-authtoken" target="_blank" style="color:var(--accent);text-decoration:none;font-size:11px;">Get Auth Token ↗</a>' +
|
|
2397
2397
|
'</div>';
|
|
2398
2398
|
document.getElementById('ch-webhook-authtoken').value = w.authtoken || '';
|
|
2399
|
-
document.getElementById('ch-webhook-domain').value = w.domain || '';
|
|
2399
|
+
document.getElementById('ch-webhook-domain').value = w.ngrokDomain || w.domain || '';
|
|
2400
2400
|
}
|
|
2401
2401
|
}
|
|
2402
2402
|
|
|
@@ -2507,7 +2507,7 @@ function buildChannelsData() {
|
|
|
2507
2507
|
|
|
2508
2508
|
const webhook = { respectQuiet: respectWebhook };
|
|
2509
2509
|
const webhookAuthtoken = document.getElementById('ch-webhook-authtoken');
|
|
2510
|
-
if (webhookAuthtoken) { webhook.enabled = document.getElementById('ch-webhook-enabled')?.classList.contains('on') || false; webhook.authtoken = webhookAuthtoken.value; webhook.
|
|
2510
|
+
if (webhookAuthtoken) { webhook.enabled = document.getElementById('ch-webhook-enabled')?.classList.contains('on') || false; webhook.authtoken = webhookAuthtoken.value; webhook.ngrokDomain = document.getElementById('ch-webhook-domain')?.value || undefined; webhook.port = parseInt(document.getElementById('ch-webhook-port')?.value) || undefined; webhook.batchInterval = parseInt(document.getElementById('ch-webhook-batch-interval')?.value) || undefined; }
|
|
2511
2511
|
|
|
2512
2512
|
return {
|
|
2513
2513
|
discord: { token: document.getElementById('ch-discord-token').value, applicationId: document.getElementById('ch-discord-appid').value },
|
|
@@ -2604,7 +2604,7 @@ const AG_API_PROVIDERS = [
|
|
|
2604
2604
|
{id:'gemini',name:'Gemini',env:'GEMINI_API_KEY',url:'https://aistudio.google.com/apikey'},
|
|
2605
2605
|
{id:'deepseek',name:'DeepSeek',env:'DEEPSEEK_API_KEY',url:'https://platform.deepseek.com/api_keys'},
|
|
2606
2606
|
{id:'xai',name:'xAI',env:'XAI_API_KEY',url:'https://console.x.ai'},
|
|
2607
|
-
{id:'
|
|
2607
|
+
{id:'opencode-go',name:'OpenCode Go',env:'OPENCODE_API_KEY',url:'https://opencode.ai'},
|
|
2608
2608
|
];
|
|
2609
2609
|
const AG_OAUTH_PROVIDERS = [
|
|
2610
2610
|
{id:'openai-oauth',name:'Codex',desc:'~/.codex/auth.json',login:true},
|
|
@@ -2640,9 +2640,48 @@ const AG_EFFORT_LABEL = { none: 'None', low: 'Low', medium: 'Medium', high: 'Hig
|
|
|
2640
2640
|
// Families that don't support fast mode even when the provider does.
|
|
2641
2641
|
const AG_FAMILY_NO_FAST = new Set(['haiku', 'gpt-nano', 'gpt-codex']);
|
|
2642
2642
|
const AG_FAST_PROVIDERS = new Set(['anthropic', 'anthropic-oauth', 'openai', 'openai-oauth']);
|
|
2643
|
+
const AG_OPENAI_DIRECT_FAST_MODEL_RE = [
|
|
2644
|
+
/^gpt-5\.5(?:-\d{4}|$)/,
|
|
2645
|
+
/^gpt-5\.4(?:-\d{4}|$)/,
|
|
2646
|
+
/^gpt-5\.4-mini(?:-\d{4}|$)/,
|
|
2647
|
+
];
|
|
2643
2648
|
let agModelList = [];
|
|
2644
2649
|
const AG_ACCESS_LABELS = { full: 'Read & Write', readonly: 'Read Only', mcp: 'None' };
|
|
2645
2650
|
|
|
2651
|
+
function agNormalizeFastProvider(provider) {
|
|
2652
|
+
return provider === 'openai-api' ? 'openai' : provider;
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
function agOpenAIDirectSupportsFast(modelId) {
|
|
2656
|
+
const id = String(modelId || '').trim();
|
|
2657
|
+
return AG_OPENAI_DIRECT_FAST_MODEL_RE.some(re => re.test(id));
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
function agExplicitFastSupport(model) {
|
|
2661
|
+
const hasServiceTiers = Array.isArray(model?.serviceTiers);
|
|
2662
|
+
const hasSpeedTiers = Array.isArray(model?.additionalSpeedTiers);
|
|
2663
|
+
if (!hasServiceTiers && !hasSpeedTiers) return null;
|
|
2664
|
+
const serviceTiers = hasServiceTiers ? model.serviceTiers : [];
|
|
2665
|
+
const speedTiers = hasSpeedTiers ? model.additionalSpeedTiers : [];
|
|
2666
|
+
const serviceFast = serviceTiers.some(t => {
|
|
2667
|
+
const id = typeof t === 'string' ? t : t?.id;
|
|
2668
|
+
return id === 'priority' || id === 'fast';
|
|
2669
|
+
});
|
|
2670
|
+
const speedFast = speedTiers.some(t => t === 'fast' || t === 'priority');
|
|
2671
|
+
return serviceFast || speedFast;
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
function agModelSupportsFast(provider, model) {
|
|
2675
|
+
const normalizedProvider = agNormalizeFastProvider(provider);
|
|
2676
|
+
if (!model) return false;
|
|
2677
|
+
const explicit = agExplicitFastSupport(model);
|
|
2678
|
+
if (explicit !== null) return explicit;
|
|
2679
|
+
if (normalizedProvider === 'openai') return agOpenAIDirectSupportsFast(model.id);
|
|
2680
|
+
const providerFast = AG_FAST_PROVIDERS.has(normalizedProvider);
|
|
2681
|
+
const familyNoFast = model?.family && AG_FAMILY_NO_FAST.has(model.family);
|
|
2682
|
+
return providerFast && !familyNoFast;
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2646
2685
|
async function loadAgentData() {
|
|
2647
2686
|
const r = await fetch('/agent/config').then(r => r.json()).catch(() => ({}));
|
|
2648
2687
|
agConfig = r.config || {};
|
|
@@ -2974,9 +3013,7 @@ function agUpdateEffortAndFast(provider) {
|
|
|
2974
3013
|
effortSel.innerHTML = allowed.map(v => `<option value="${v}">${AG_EFFORT_LABEL[v] || v}</option>`).join('');
|
|
2975
3014
|
}
|
|
2976
3015
|
|
|
2977
|
-
const
|
|
2978
|
-
const familyNoFast = model?.family && AG_FAMILY_NO_FAST.has(model.family);
|
|
2979
|
-
const fastAllowed = modelResolved && providerFast && !familyNoFast;
|
|
3016
|
+
const fastAllowed = modelResolved && agModelSupportsFast(provider, model);
|
|
2980
3017
|
fastRow.style.display = fastAllowed ? 'flex' : 'none';
|
|
2981
3018
|
if (!fastAllowed) document.getElementById('ag-pf-fast').classList.remove('on');
|
|
2982
3019
|
}
|
|
@@ -3479,8 +3516,8 @@ async function srRenderModelPresets() {
|
|
|
3479
3516
|
}
|
|
3480
3517
|
|
|
3481
3518
|
// Reveal/hide effort + fast based on the currently selected openai model.
|
|
3482
|
-
// Mirrors agUpdateEffortAndFast() so the rules are identical: model
|
|
3483
|
-
// drives effort options
|
|
3519
|
+
// Mirrors agUpdateEffortAndFast() so the rules are identical: provider/model
|
|
3520
|
+
// metadata drives effort options and Fast Mode availability.
|
|
3484
3521
|
function srUpdateOpenAIEffortFast() {
|
|
3485
3522
|
const body = document.getElementById('sr-model-presets-body');
|
|
3486
3523
|
if (!body) return;
|
|
@@ -3505,9 +3542,7 @@ function srUpdateOpenAIEffortFast() {
|
|
|
3505
3542
|
effortSel.style.display = '';
|
|
3506
3543
|
effortSel.innerHTML = allowed.map(v => '<option value="' + v + '"' + (v === storedEffort ? ' selected' : '') + '>' + (AG_EFFORT_LABEL[v] || v) + '</option>').join('');
|
|
3507
3544
|
}
|
|
3508
|
-
const
|
|
3509
|
-
const familyNoFast = model?.family && AG_FAMILY_NO_FAST.has(model.family);
|
|
3510
|
-
const fastAllowed = !!model && providerFast && !familyNoFast;
|
|
3545
|
+
const fastAllowed = !!model && agModelSupportsFast(normalizedProvider, model);
|
|
3511
3546
|
const fastLabel = body.querySelector('[data-fam-fast-label="openai"]');
|
|
3512
3547
|
fastEl.style.display = fastAllowed ? '' : 'none';
|
|
3513
3548
|
if (fastLabel) fastLabel.style.display = fastAllowed ? '' : 'none';
|