@oar-sh/oar 0.9.17 → 0.9.18
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/.github/extensions/web-relay/skills/tool-activity.mjs +5 -4
- package/CHANGELOG.md +192 -0
- package/README.md +13 -12
- package/package.json +1 -1
- package/server/claude-api-worker/claude-api-backend.mjs +345 -0
- package/server/claude-api-worker/claude-api-session-worker.mjs +29 -0
- package/server/claude-worker/claude-cli-pinned-env.mjs +150 -0
- package/server/claude-worker/claude-remote-relay-tool.mjs +3 -3
- package/server/claude-worker/claude-sdk-adapter.mjs +35 -9
- package/server/claude-worker/claude-session-process.mjs +166 -21
- package/server/claude-worker/claude-session-worker-main.mjs +3 -0
- package/server/claude-worker/claude-subscription-key-check.mjs +64 -0
- package/server/claude-worker/claude-turn-publisher.mjs +117 -5
- package/server/claude-worker/claude-usage-totals.mjs +53 -0
- package/server/claude-worker/sdk-message-normalizer.mjs +1 -1
- package/server/copilot-worker/copilot-remote-relay-tool.mjs +2 -2
- package/server/copilot-worker/copilot-sdk-event-normalizer.mjs +1 -1
- package/server/copilot-worker/copilot-sdk-session-process.mjs +9 -9
- package/server/cursor-worker/cursor-remote-relay-tool.mjs +3 -3
- package/server/cursor-worker/cursor-turn-runner.mjs +4 -4
- package/server/cursor-worker/sdk-message-normalizer.mjs +2 -2
- package/server/db-schema.mjs +33 -1
- package/server/deepseek-worker/deepseek-claude-backend.mjs +14 -130
- package/server/grok-worker/acp-client.mjs +1 -1
- package/server/grok-worker/grok-turn-runner.mjs +4 -4
- package/server/grok-worker/sdk-message-normalizer.mjs +1 -1
- package/server/mcp/oar-mcp-server.mjs +11 -5
- package/server/migrations/0031-queue-provider.mjs +143 -0
- package/server/migrations/0032-runtime-session-billing-switch.mjs +94 -0
- package/server/migrations/0033-claude-api-usage-checkpoints.mjs +138 -0
- package/server/migrations/0034-claude-api-usage-totals.mjs +112 -0
- package/server/public/app/activity-replay-state.mjs +4 -3
- package/server/public/app/agent-sessions-settings-ui.js +1 -1
- package/server/public/app/agent-sessions-settings.mjs +1 -1
- package/server/public/app/api-client.js +52 -0
- package/server/public/app/bootstrap.js +153 -27
- package/server/public/app/claude-api-settings-ui.js +362 -0
- package/server/public/app/claude-billing-toggle-ui.js +224 -0
- package/server/public/app/claude-billing-toggle.mjs +112 -0
- package/server/public/app/context-tier-options.mjs +6 -2
- package/server/public/app/context-usage-view.mjs +1 -1
- package/server/public/app/conversation-provider-indicator.mjs +11 -0
- package/server/public/app/conversation-view.js +29 -3
- package/server/public/app/inbox-view.mjs +1 -1
- package/server/public/app/journal-view.js +127 -10
- package/server/public/app/model-selector-options.mjs +17 -1
- package/server/public/app/new-conversation-model-choice.mjs +21 -0
- package/server/public/app/provider-readiness.mjs +5 -2
- package/server/public/app/relay-error-ctas.mjs +50 -0
- package/server/public/app/relay-schedules-ui.js +1 -1
- package/server/public/app/settings-modal.js +11 -0
- package/server/public/app/settings-tabs.js +1 -1
- package/server/public/app/socket-handlers.js +18 -1
- package/server/public/app/tool-activity-emoji.mjs +4 -3
- package/server/public/app/usage-limit-ui.js +136 -5
- package/server/public/index.html +189 -3
- package/server/relay-tools.md +4 -4
- package/server/repositories/conversation-group-repository.mjs +1 -1
- package/server/repositories/message-repository.mjs +7 -0
- package/server/repositories/session-repository.mjs +8 -1
- package/server/routes/ask-user-routes.mjs +1 -1
- package/server/routes/conversation-group-routes.mjs +1 -1
- package/server/routes/messages-routes.mjs +645 -27
- package/server/routes/relay-ask-routes.mjs +3 -3
- package/server/routes/relay-event-routes.mjs +2 -2
- package/server/routes/relay-schedule-routes.mjs +1 -1
- package/server/routes/remote-relay-routes.mjs +3 -3
- package/server/routes/sessions-routes.mjs +327 -29
- package/server/server-runtime.mjs +363 -26
- package/server/services/about-service.mjs +1 -0
- package/server/services/background-work-view.mjs +2 -1
- package/server/services/claude-api-settings-service.mjs +575 -0
- package/server/services/claude-api-usage-service.mjs +361 -0
- package/server/services/claude-billing-swap-service.mjs +274 -0
- package/server/services/claude-billing-switch-service.mjs +343 -0
- package/server/services/plan-usage-claude-api.mjs +445 -0
- package/server/services/plan-usage-service.mjs +5 -0
- package/server/services/provider-model-selection.mjs +3 -2
- package/server/services/provider-reasoning-effort.mjs +9 -0
- package/server/services/relay-ask-service.mjs +1 -1
- package/server/services/relay-event-service.mjs +3 -2
- package/server/services/relay-schedule-service.mjs +1 -1
- package/server/services/remote-relay-caller-context.mjs +7 -0
- package/server/services/remote-relay-dispatcher.mjs +83 -14
- package/server/services/remote-relay-registry-service.mjs +1 -1
- package/server/services/session-worker-launch-service.mjs +61 -2
- package/server/services/session-worker-registry-service.mjs +28 -1
- package/server/services/session-worker-websocket-service.mjs +17 -0
- package/server/services/usage-limit-pause-service.mjs +55 -4
- package/server/services/worker-secret-redaction.mjs +2 -0
- package/shared/anthropic-api.mjs +202 -0
- package/shared/provider-routing.mjs +5 -3
- package/shared/relay-asks.mjs +11 -5
- package/shared/relay-events.mjs +2 -2
- package/shared/relay-schedules.mjs +2 -2
- package/shared/remote-relay-contract.mjs +29 -9
- package/shared/remote-relay-tool-core.mjs +23 -6
- package/shared/steer-settle-failure.mjs +1 -0
- package/shared/worker-runtime/api-client.mjs +2 -2
- package/shared/worker-runtime/heartbeat.mjs +5 -0
- package/shared/worker-runtime/worker-websocket-link.mjs +6 -0
|
@@ -80,6 +80,7 @@ import {
|
|
|
80
80
|
updateCursorSettings,
|
|
81
81
|
updateGrokSettings,
|
|
82
82
|
updateDeepSeekSettings,
|
|
83
|
+
updateClaudeApiSettings,
|
|
83
84
|
loadConversation,
|
|
84
85
|
refreshConversationHistory,
|
|
85
86
|
updateConversationTitle,
|
|
@@ -181,6 +182,7 @@ import {
|
|
|
181
182
|
composerPlaceholderFor,
|
|
182
183
|
contextWindowSuffix,
|
|
183
184
|
humanizeModelLabel,
|
|
185
|
+
distinctModelLabels,
|
|
184
186
|
modelMetadataFor,
|
|
185
187
|
modelSelectorOptionsEqual,
|
|
186
188
|
splitModelVariantId,
|
|
@@ -330,6 +332,13 @@ import {
|
|
|
330
332
|
importDeepSeekUsageExport,
|
|
331
333
|
applyDeepSeekSettingsState,
|
|
332
334
|
refreshDeepSeekSettingsState,
|
|
335
|
+
saveClaudeApiSettings,
|
|
336
|
+
removeClaudeApiSettings,
|
|
337
|
+
toggleClaudeApiProvider,
|
|
338
|
+
refreshClaudeApiModels,
|
|
339
|
+
openClaudeTabFromClaudeApi,
|
|
340
|
+
applyClaudeApiSettingsState,
|
|
341
|
+
refreshClaudeApiSettingsState,
|
|
333
342
|
saveCursorSettings,
|
|
334
343
|
removeCursorSettings,
|
|
335
344
|
saveCursorAllowanceSettings,
|
|
@@ -373,6 +382,7 @@ import {
|
|
|
373
382
|
} from './action-confirmations.js';
|
|
374
383
|
import { applyPendingActionsFromStatus, initHostSuspendUi } from './host-suspend-ui.js';
|
|
375
384
|
import { applyUsageLimitFromStatus, initUsageLimitUi } from './usage-limit-ui.js';
|
|
385
|
+
import { initClaudeBillingToggle, renderClaudeBillingToggle } from './claude-billing-toggle-ui.js';
|
|
376
386
|
import {
|
|
377
387
|
startClaudeRelogin,
|
|
378
388
|
submitClaudeLoginCodeFromInput,
|
|
@@ -449,6 +459,7 @@ const PROVIDER_LABELS = {
|
|
|
449
459
|
openai: 'OpenAI',
|
|
450
460
|
'openai-byok': 'OpenAI (BYOK)',
|
|
451
461
|
claude: 'Claude SDK',
|
|
462
|
+
'claude-api': 'Claude API',
|
|
452
463
|
'claude-cloud': 'Claude Cloud',
|
|
453
464
|
cursor: 'Cursor SDK',
|
|
454
465
|
grok: 'Grok',
|
|
@@ -522,6 +533,7 @@ let modelCatalogState = {
|
|
|
522
533
|
reasoningEfforts: [],
|
|
523
534
|
modelMetadataByModel: {},
|
|
524
535
|
claudeContextTiersByModel: {},
|
|
536
|
+
claudeApiContextTiersByModel: {},
|
|
525
537
|
stale: true,
|
|
526
538
|
metadataValid: false,
|
|
527
539
|
reasoningMetadataValid: false,
|
|
@@ -1444,6 +1456,8 @@ function syncSessionLockNote({ pinnedModel = '' } = {}) {
|
|
|
1444
1456
|
} else {
|
|
1445
1457
|
delete note.dataset.provider;
|
|
1446
1458
|
}
|
|
1459
|
+
// The billing toggle sits under the note and follows the same chat.
|
|
1460
|
+
renderClaudeBillingToggle();
|
|
1447
1461
|
}
|
|
1448
1462
|
|
|
1449
1463
|
function applyModelMetadataHardFail(message = '') {
|
|
@@ -1510,6 +1524,7 @@ function normalizeModelSelectorProviderType(providerType = '') {
|
|
|
1510
1524
|
const normalized = String(providerType || '').trim().toLowerCase();
|
|
1511
1525
|
if (normalized === 'openai' || normalized === 'openai-byok') return 'openai';
|
|
1512
1526
|
if (normalized === 'claude') return 'claude';
|
|
1527
|
+
if (normalized === 'claude-api') return 'claude-api';
|
|
1513
1528
|
if (normalized === CLAUDE_CLOUD_PROVIDER) return CLAUDE_CLOUD_PROVIDER;
|
|
1514
1529
|
if (normalized === 'cursor') return 'cursor';
|
|
1515
1530
|
if (normalized === 'grok') return 'grok';
|
|
@@ -1542,6 +1557,9 @@ function modelVisibleForActiveProvider(modelId, activeProviderType, providersByM
|
|
|
1542
1557
|
const hasCursor = providers.includes('cursor');
|
|
1543
1558
|
const hasGrok = providers.includes('grok');
|
|
1544
1559
|
const hasDeepSeek = providers.includes('deepseek');
|
|
1560
|
+
// Claude API rows are the Claude provider's ids on an API key: as
|
|
1561
|
+
// exclusive as the other runtimes' rows.
|
|
1562
|
+
const hasClaudeApi = providers.includes('claude-api');
|
|
1545
1563
|
// Only present if a relay tags catalog rows for Claude Cloud; such a row is
|
|
1546
1564
|
// then as exclusive as the other runtimes' rows.
|
|
1547
1565
|
const hasClaudeCloud = providers.includes(CLAUDE_CLOUD_PROVIDER);
|
|
@@ -1551,15 +1569,17 @@ function modelVisibleForActiveProvider(modelId, activeProviderType, providersByM
|
|
|
1551
1569
|
&& provider !== 'cursor'
|
|
1552
1570
|
&& provider !== 'grok'
|
|
1553
1571
|
&& provider !== 'deepseek'
|
|
1572
|
+
&& provider !== 'claude-api'
|
|
1554
1573
|
&& provider !== CLAUDE_CLOUD_PROVIDER
|
|
1555
1574
|
));
|
|
1556
1575
|
const activeProvider = normalizeModelSelectorProviderType(activeProviderType);
|
|
1557
1576
|
if (activeProvider === 'openai') return hasOpenAIByok;
|
|
1558
1577
|
if (activeProvider === 'claude') return hasClaude;
|
|
1578
|
+
if (activeProvider === 'claude-api') return hasClaudeApi;
|
|
1559
1579
|
if (activeProvider === 'cursor') return hasCursor;
|
|
1560
1580
|
if (activeProvider === 'grok') return hasGrok;
|
|
1561
1581
|
if (activeProvider === 'deepseek') return hasDeepSeek;
|
|
1562
|
-
return !((hasOpenAIByok || hasClaude || hasCursor || hasGrok || hasDeepSeek || hasClaudeCloud) && !hasNonExclusiveProvider);
|
|
1582
|
+
return !((hasOpenAIByok || hasClaude || hasCursor || hasGrok || hasDeepSeek || hasClaudeApi || hasClaudeCloud) && !hasNonExclusiveProvider);
|
|
1563
1583
|
}
|
|
1564
1584
|
|
|
1565
1585
|
// A Claude Cloud conversation does not pick from the shared catalog: its
|
|
@@ -1595,10 +1615,16 @@ function buildModelSelectorOptions(
|
|
|
1595
1615
|
if (normalizeModelSelectorProviderType(activeProviderType) === CLAUDE_CLOUD_PROVIDER) {
|
|
1596
1616
|
return buildClaudeCloudModelSelectorOptions(models, providersByModel, metadataByModel);
|
|
1597
1617
|
}
|
|
1598
|
-
|
|
1618
|
+
// Only this provider's models are labelled: two ids that read the same
|
|
1619
|
+
// fall back to their raw ids, and an id another provider serves
|
|
1620
|
+
// ("claude-haiku-4-5" beside the key's "claude-haiku-4-5-20251001") must
|
|
1621
|
+
// not do that to a list it is not in.
|
|
1622
|
+
const visibleModels = (models.length ? models : [FALLBACK_MODEL])
|
|
1623
|
+
.filter((modelId) => modelVisibleForActiveProvider(modelId, activeProviderType, providersByModel));
|
|
1624
|
+
const normalizedOptions = buildCatalogModelOptions(visibleModels, {
|
|
1599
1625
|
autoValue: AUTO_MODEL_OPTION,
|
|
1600
1626
|
metadataByModel,
|
|
1601
|
-
annotateContextWindow: normalizeModelSelectorProviderType(activeProviderType)
|
|
1627
|
+
annotateContextWindow: !['claude', 'claude-api'].includes(normalizeModelSelectorProviderType(activeProviderType)),
|
|
1602
1628
|
});
|
|
1603
1629
|
return normalizedOptions.filter((option) => modelVisibleForActiveProvider(
|
|
1604
1630
|
option.value,
|
|
@@ -1804,6 +1830,12 @@ function updateModelCatalogState(payload) {
|
|
|
1804
1830
|
Array.isArray(tiers) ? tiers : [],
|
|
1805
1831
|
]))
|
|
1806
1832
|
: {},
|
|
1833
|
+
claudeApiContextTiersByModel: payload?.claudeApiContextTiersByModel && typeof payload.claudeApiContextTiersByModel === 'object'
|
|
1834
|
+
? Object.fromEntries(Object.entries(payload.claudeApiContextTiersByModel).map(([modelId, tiers]) => [
|
|
1835
|
+
String(modelId || '').trim().toLowerCase(),
|
|
1836
|
+
Array.isArray(tiers) ? tiers : [],
|
|
1837
|
+
]))
|
|
1838
|
+
: {},
|
|
1807
1839
|
stale: !!payload?.stale,
|
|
1808
1840
|
metadataValid: payload?.metadataValid === true,
|
|
1809
1841
|
reasoningMetadataValid: payload?.reasoningMetadataValid === true,
|
|
@@ -1935,7 +1967,9 @@ function updateContextTierSelector(modelId) {
|
|
|
1935
1967
|
modelId,
|
|
1936
1968
|
providerType: activeComposerProviderType(),
|
|
1937
1969
|
metadata: modelMetadataFor(modelId, modelCatalogState.modelMetadataByModel) || {},
|
|
1938
|
-
claudeTiers:
|
|
1970
|
+
claudeTiers: activeComposerProviderType() === 'claude-api'
|
|
1971
|
+
? modelCatalogState.claudeApiContextTiersByModel
|
|
1972
|
+
: modelCatalogState.claudeContextTiersByModel,
|
|
1939
1973
|
});
|
|
1940
1974
|
const current = select.value;
|
|
1941
1975
|
select.innerHTML = '';
|
|
@@ -1957,8 +1991,9 @@ function updateModelPricingDetails(modelId, tier) {
|
|
|
1957
1991
|
if (!details || !summary || !grid) return;
|
|
1958
1992
|
const metadata = modelCatalogState.modelMetadataByModel?.[modelId] || {};
|
|
1959
1993
|
// The catalog's prices are Copilot credits; a cloud session is billed to the
|
|
1960
|
-
// Claude account
|
|
1961
|
-
|
|
1994
|
+
// Claude account and a Claude API chat to the Console in dollars, so they
|
|
1995
|
+
// would be the wrong numbers for them.
|
|
1996
|
+
const pricing = activeComposerProviderType() === CLAUDE_CLOUD_PROVIDER || activeComposerProviderType() === 'claude-api'
|
|
1962
1997
|
? null
|
|
1963
1998
|
: metadata?.pricing?.[tier === 'long_context' ? 'longContext' : 'default'];
|
|
1964
1999
|
if (!pricing || typeof pricing !== 'object') {
|
|
@@ -2000,6 +2035,7 @@ const RELAY_MODES_BY_PROVIDER = {
|
|
|
2000
2035
|
github: ['agent', 'ask', 'plan', 'autopilot'],
|
|
2001
2036
|
openai: ['agent', 'ask', 'plan', 'autopilot'],
|
|
2002
2037
|
claude: ['agent', 'ask', 'plan', 'autopilot'],
|
|
2038
|
+
'claude-api': ['agent', 'ask', 'plan', 'autopilot'],
|
|
2003
2039
|
cursor: ['agent', 'ask', 'plan', 'autopilot'],
|
|
2004
2040
|
grok: ['agent', 'ask', 'plan', 'autopilot'],
|
|
2005
2041
|
deepseek: ['agent', 'ask', 'plan', 'autopilot'],
|
|
@@ -2066,7 +2102,7 @@ async function persistCurrentConversationPreferences() {
|
|
|
2066
2102
|
const contextTier = String(document.getElementById('context-tier-select')?.value || 'default').trim().toLowerCase();
|
|
2067
2103
|
const preferredModelWithTier = model
|
|
2068
2104
|
&& contextTier === 'long_context'
|
|
2069
|
-
&& activeComposerProviderType() === 'claude'
|
|
2105
|
+
&& (activeComposerProviderType() === 'claude' || activeComposerProviderType() === 'claude-api')
|
|
2070
2106
|
&& !CLAUDE_LONG_CONTEXT_PATTERN.test(model)
|
|
2071
2107
|
? `${model}[1m]`
|
|
2072
2108
|
: model;
|
|
@@ -2356,6 +2392,13 @@ function reportOpenAIModelDiscoveryFailure(payload) {
|
|
|
2356
2392
|
8000,
|
|
2357
2393
|
);
|
|
2358
2394
|
}
|
|
2395
|
+
const claudeApiDiscovery = payload?.claudeApiModelDiscovery;
|
|
2396
|
+
if (claudeApiDiscovery && claudeApiDiscovery.ok === false && !claudeApiDiscovery.skipped) {
|
|
2397
|
+
showTransientRelayNotice(
|
|
2398
|
+
`Claude API model discovery failed. Cached Claude API models were kept: ${claudeApiDiscovery.error || 'unknown error'}`,
|
|
2399
|
+
8000,
|
|
2400
|
+
);
|
|
2401
|
+
}
|
|
2359
2402
|
}
|
|
2360
2403
|
|
|
2361
2404
|
async function retryModelMetadataRefresh() {
|
|
@@ -2504,6 +2547,17 @@ function applyModelVariantCatalogState(payload) {
|
|
|
2504
2547
|
: [],
|
|
2505
2548
|
}
|
|
2506
2549
|
: null,
|
|
2550
|
+
claudeApiModels: payload?.claudeApiModels && typeof payload.claudeApiModels === 'object'
|
|
2551
|
+
? {
|
|
2552
|
+
defaultModel: String(payload.claudeApiModels.defaultModel || '').trim(),
|
|
2553
|
+
availableModels: Array.isArray(payload.claudeApiModels.availableModels)
|
|
2554
|
+
? payload.claudeApiModels.availableModels.map((value) => String(value || '').trim()).filter(Boolean)
|
|
2555
|
+
: [],
|
|
2556
|
+
enabledModels: Array.isArray(payload.claudeApiModels.enabledModels)
|
|
2557
|
+
? payload.claudeApiModels.enabledModels.map((value) => String(value || '').trim()).filter(Boolean)
|
|
2558
|
+
: [],
|
|
2559
|
+
}
|
|
2560
|
+
: null,
|
|
2507
2561
|
};
|
|
2508
2562
|
}
|
|
2509
2563
|
|
|
@@ -2572,6 +2626,11 @@ function renderModelVariantCatalogBody() {
|
|
|
2572
2626
|
return entryProvider === 'claude';
|
|
2573
2627
|
}
|
|
2574
2628
|
if (entryProvider === 'claude') return false;
|
|
2629
|
+
// Claude API lists the same Claude ids as the Claude SDK tab, its own rows.
|
|
2630
|
+
if (activeTab === 'claude-api') {
|
|
2631
|
+
return entryProvider === 'claude-api';
|
|
2632
|
+
}
|
|
2633
|
+
if (entryProvider === 'claude-api') return false;
|
|
2575
2634
|
if (activeTab === 'cursor') {
|
|
2576
2635
|
return entryProvider === 'cursor';
|
|
2577
2636
|
}
|
|
@@ -2596,6 +2655,7 @@ function renderModelVariantCatalogBody() {
|
|
|
2596
2655
|
&& provider !== 'cursor'
|
|
2597
2656
|
&& provider !== 'grok'
|
|
2598
2657
|
&& provider !== 'deepseek'
|
|
2658
|
+
&& provider !== 'claude-api'
|
|
2599
2659
|
&& provider !== CLAUDE_CLOUD_PROVIDER
|
|
2600
2660
|
));
|
|
2601
2661
|
}
|
|
@@ -2729,6 +2789,31 @@ function renderModelVariantCatalogBody() {
|
|
|
2729
2789
|
}
|
|
2730
2790
|
grouped.set('deepseek', providerRows);
|
|
2731
2791
|
}
|
|
2792
|
+
const claudeApiCatalog = modelVariantCatalogState.claudeApiModels;
|
|
2793
|
+
if (claudeApiCatalog?.availableModels?.length) {
|
|
2794
|
+
const claudeApiEnabledSet = new Set(claudeApiCatalog.enabledModels || []);
|
|
2795
|
+
const claudeApiDefaultModel = String(claudeApiCatalog.defaultModel || '').trim();
|
|
2796
|
+
// Named by model; two snapshots that would read the same keep their ids.
|
|
2797
|
+
const claudeApiLabels = distinctModelLabels(claudeApiCatalog.availableModels);
|
|
2798
|
+
const providerRows = [];
|
|
2799
|
+
for (const [index, claudeApiModelId] of claudeApiCatalog.availableModels.entries()) {
|
|
2800
|
+
const isDefaultModel = claudeApiModelId === claudeApiDefaultModel;
|
|
2801
|
+
providerRows.push({
|
|
2802
|
+
variantId: `${claudeApiModelId}--provider-claude-api`,
|
|
2803
|
+
baseModelId: claudeApiModelId,
|
|
2804
|
+
provider: 'claude-api',
|
|
2805
|
+
label: claudeApiLabels.get(claudeApiModelId) || claudeApiModelId,
|
|
2806
|
+
releaseStatus: null,
|
|
2807
|
+
reasoningEffort: null,
|
|
2808
|
+
// The default model always stays enabled, as for the other providers.
|
|
2809
|
+
selectable: !isDefaultModel,
|
|
2810
|
+
enabled: claudeApiEnabledSet.has(claudeApiModelId) || isDefaultModel,
|
|
2811
|
+
claudeApiModelId,
|
|
2812
|
+
sortOrder: index,
|
|
2813
|
+
});
|
|
2814
|
+
}
|
|
2815
|
+
grouped.set('claude-api', providerRows);
|
|
2816
|
+
}
|
|
2732
2817
|
const selected = new Set(modelVariantCatalogState.enabledVariantIds);
|
|
2733
2818
|
const selectedOrder = new Map(
|
|
2734
2819
|
modelVariantCatalogState.enabledVariantIds.map((variantId, index) => [variantId, index]),
|
|
@@ -2765,24 +2850,28 @@ function renderModelVariantCatalogBody() {
|
|
|
2765
2850
|
const hasCursorTab = providerOrder.some((providerKey) => providerBelongsToTab(providerKey, 'cursor'));
|
|
2766
2851
|
const hasGrokTab = providerOrder.some((providerKey) => providerBelongsToTab(providerKey, 'grok'));
|
|
2767
2852
|
const hasDeepSeekTab = providerOrder.some((providerKey) => providerBelongsToTab(providerKey, 'deepseek'));
|
|
2853
|
+
const hasClaudeApiTab = providerOrder.some((providerKey) => providerBelongsToTab(providerKey, 'claude-api'));
|
|
2768
2854
|
if (modelVariantCatalogProviderTab === 'openai' && !hasOpenAITab) modelVariantCatalogProviderTab = 'copilot';
|
|
2769
2855
|
if (modelVariantCatalogProviderTab === 'anthropic' && !hasAnthropicTab) modelVariantCatalogProviderTab = 'copilot';
|
|
2770
2856
|
if (modelVariantCatalogProviderTab === 'cursor' && !hasCursorTab) modelVariantCatalogProviderTab = 'copilot';
|
|
2771
2857
|
if (modelVariantCatalogProviderTab === 'grok' && !hasGrokTab) modelVariantCatalogProviderTab = 'copilot';
|
|
2772
2858
|
if (modelVariantCatalogProviderTab === 'deepseek' && !hasDeepSeekTab) modelVariantCatalogProviderTab = 'copilot';
|
|
2859
|
+
if (modelVariantCatalogProviderTab === 'claude-api' && !hasClaudeApiTab) modelVariantCatalogProviderTab = 'copilot';
|
|
2773
2860
|
if (modelVariantCatalogProviderTab === 'copilot' && !hasCopilotTab) {
|
|
2774
2861
|
if (hasOpenAITab) modelVariantCatalogProviderTab = 'openai';
|
|
2775
2862
|
else if (hasAnthropicTab) modelVariantCatalogProviderTab = 'anthropic';
|
|
2776
2863
|
else if (hasCursorTab) modelVariantCatalogProviderTab = 'cursor';
|
|
2777
2864
|
else if (hasGrokTab) modelVariantCatalogProviderTab = 'grok';
|
|
2778
2865
|
else if (hasDeepSeekTab) modelVariantCatalogProviderTab = 'deepseek';
|
|
2866
|
+
else if (hasClaudeApiTab) modelVariantCatalogProviderTab = 'claude-api';
|
|
2779
2867
|
}
|
|
2780
2868
|
const visibleProviderOrder = providerOrder.filter((providerKey) => providerBelongsToTab(providerKey, modelVariantCatalogProviderTab));
|
|
2781
2869
|
const providerTabButtons = `
|
|
2782
|
-
<div class="summary-tab-strip" style="display:flex;gap:8px;align-items:center;">
|
|
2870
|
+
<div class="summary-tab-strip" style="display:flex;flex-wrap:wrap;gap:8px;align-items:center;">
|
|
2783
2871
|
<button type="button" class="summary-btn model-provider-tab${modelVariantCatalogProviderTab === 'copilot' ? ' active' : ''}" data-model-provider-tab="copilot">Copilot</button>
|
|
2784
2872
|
<button type="button" class="summary-btn model-provider-tab${modelVariantCatalogProviderTab === 'openai' ? ' active' : ''}" data-model-provider-tab="openai" ${hasOpenAITab ? '' : 'disabled'}>OpenAI</button>
|
|
2785
2873
|
<button type="button" class="summary-btn model-provider-tab${modelVariantCatalogProviderTab === 'anthropic' ? ' active' : ''}" data-model-provider-tab="anthropic" ${hasAnthropicTab ? '' : 'disabled'}>Claude SDK</button>
|
|
2874
|
+
<button type="button" class="summary-btn model-provider-tab${modelVariantCatalogProviderTab === 'claude-api' ? ' active' : ''}" data-model-provider-tab="claude-api" ${hasClaudeApiTab ? '' : 'disabled'}>Claude API</button>
|
|
2786
2875
|
<button type="button" class="summary-btn model-provider-tab${modelVariantCatalogProviderTab === 'cursor' ? ' active' : ''}" data-model-provider-tab="cursor" ${hasCursorTab ? '' : 'disabled'}>Cursor SDK</button>
|
|
2787
2876
|
<button type="button" class="summary-btn model-provider-tab${modelVariantCatalogProviderTab === 'grok' ? ' active' : ''}" data-model-provider-tab="grok" ${hasGrokTab ? '' : 'disabled'}>Grok</button>
|
|
2788
2877
|
<button type="button" class="summary-btn model-provider-tab${modelVariantCatalogProviderTab === 'deepseek' ? ' active' : ''}" data-model-provider-tab="deepseek" ${hasDeepSeekTab ? '' : 'disabled'}>DeepSeek</button>
|
|
@@ -2840,7 +2929,8 @@ function renderModelVariantCatalogBody() {
|
|
|
2840
2929
|
const selectable = row.selectable !== false;
|
|
2841
2930
|
// SDK-provider rows (Claude/Cursor/Grok/DeepSeek) carry their own enabled
|
|
2842
2931
|
// flag; only Copilot variant rows read the enabledVariantIds selection.
|
|
2843
|
-
const sdkProviderModelId = row.claudeModelId || row.cursorModelId || row.grokModelId || row.deepseekModelId
|
|
2932
|
+
const sdkProviderModelId = row.claudeModelId || row.cursorModelId || row.grokModelId || row.deepseekModelId
|
|
2933
|
+
|| row.claudeApiModelId || '';
|
|
2844
2934
|
const checked = sdkProviderModelId
|
|
2845
2935
|
? row.enabled === true
|
|
2846
2936
|
: (selectable && selected.has(row.variantId));
|
|
@@ -2862,12 +2952,14 @@ function renderModelVariantCatalogBody() {
|
|
|
2862
2952
|
? ` data-cursor-model="${escHtml(row.cursorModelId)}"`
|
|
2863
2953
|
: (row.grokModelId
|
|
2864
2954
|
? ` data-grok-model="${escHtml(row.grokModelId)}"`
|
|
2865
|
-
: (row.deepseekModelId
|
|
2955
|
+
: (row.deepseekModelId
|
|
2956
|
+
? ` data-deepseek-model="${escHtml(row.deepseekModelId)}"`
|
|
2957
|
+
: (row.claudeApiModelId ? ` data-claude-api-model="${escHtml(row.claudeApiModelId)}"` : ''))));
|
|
2866
2958
|
return `
|
|
2867
2959
|
<label class="model-variant-row">
|
|
2868
2960
|
<input class="model-variant-checkbox" type="checkbox" data-selectable="${selectable ? '1' : '0'}" data-variant-id="${escHtml(row.variantId)}"${sdkModelAttr} ${checked ? 'checked' : ''} ${selectable ? '' : 'disabled'}>
|
|
2869
2961
|
<span class="model-variant-row-copy">
|
|
2870
|
-
<span class="model-variant-row-title">${escHtml(row.variantId)}${effortChip}${statusChip}${fixedChip}</span>
|
|
2962
|
+
<span class="model-variant-row-title"${row.claudeApiModelId ? ` title="${escHtml(row.claudeApiModelId)}"` : ''}>${escHtml(row.claudeApiModelId ? row.label : row.variantId)}${effortChip}${statusChip}${fixedChip}</span>
|
|
2871
2963
|
</span>
|
|
2872
2964
|
</label>
|
|
2873
2965
|
`;
|
|
@@ -2993,12 +3085,17 @@ async function saveSelectedModelsFromModal() {
|
|
|
2993
3085
|
.filter((input) => input.checked)
|
|
2994
3086
|
.map((input) => String(input.getAttribute('data-deepseek-model') || '').trim())
|
|
2995
3087
|
.filter(Boolean);
|
|
3088
|
+
const claudeApiCheckboxes = Array.from(body.querySelectorAll('.model-variant-checkbox[data-claude-api-model]'));
|
|
3089
|
+
const selectedClaudeApiModels = claudeApiCheckboxes
|
|
3090
|
+
.filter((input) => input.checked)
|
|
3091
|
+
.map((input) => String(input.getAttribute('data-claude-api-model') || '').trim())
|
|
3092
|
+
.filter(Boolean);
|
|
2996
3093
|
// Only the active provider tab's rows are in the DOM, but the PATCH
|
|
2997
3094
|
// replaces the WHOLE enabled set — so the save must only change rows the
|
|
2998
3095
|
// user could actually see. Off-screen variants keep their stored state,
|
|
2999
3096
|
// otherwise saving from e.g. the OpenAI tab silently disables every
|
|
3000
3097
|
// Copilot-only vendor group (Anthropic, Google, …).
|
|
3001
|
-
const renderedVariantCheckboxes = Array.from(body.querySelectorAll('.model-variant-checkbox[data-selectable="1"]:not([data-claude-model]):not([data-cursor-model]):not([data-grok-model]):not([data-deepseek-model])'));
|
|
3098
|
+
const renderedVariantCheckboxes = Array.from(body.querySelectorAll('.model-variant-checkbox[data-selectable="1"]:not([data-claude-model]):not([data-cursor-model]):not([data-grok-model]):not([data-deepseek-model]):not([data-claude-api-model])'));
|
|
3002
3099
|
const renderedVariantIds = new Set(renderedVariantCheckboxes
|
|
3003
3100
|
.map((input) => String(input.getAttribute('data-variant-id') || '').trim())
|
|
3004
3101
|
.filter(Boolean));
|
|
@@ -3044,7 +3141,13 @@ async function saveSelectedModelsFromModal() {
|
|
|
3044
3141
|
if (!savedDeepSeek) throw new Error('Failed to save DeepSeek model selection');
|
|
3045
3142
|
applyDeepSeekSettingsState(savedDeepSeek);
|
|
3046
3143
|
}
|
|
3047
|
-
if (
|
|
3144
|
+
if (claudeApiCheckboxes.length) {
|
|
3145
|
+
const savedClaudeApi = await updateClaudeApiSettings({ enabledModels: selectedClaudeApiModels });
|
|
3146
|
+
if (!savedClaudeApi) throw new Error('Failed to save Claude API model selection');
|
|
3147
|
+
applyClaudeApiSettingsState(savedClaudeApi);
|
|
3148
|
+
}
|
|
3149
|
+
if (claudeCheckboxes.length || cursorCheckboxes.length || grokCheckboxes.length || deepseekCheckboxes.length
|
|
3150
|
+
|| claudeApiCheckboxes.length) {
|
|
3048
3151
|
const refreshedCatalog = await loadModelVariantCatalog();
|
|
3049
3152
|
if (refreshedCatalog) applyModelVariantCatalogState(refreshedCatalog);
|
|
3050
3153
|
}
|
|
@@ -3551,13 +3654,15 @@ async function loadContextSummaryAndRender(convId) {
|
|
|
3551
3654
|
const refreshLookupId = sessionId || trimmedConvId || null;
|
|
3552
3655
|
const providerLabel = payload.providerType === 'claude'
|
|
3553
3656
|
? 'Claude'
|
|
3554
|
-
: (payload.providerType ===
|
|
3555
|
-
? 'Claude
|
|
3556
|
-
: (payload.providerType ===
|
|
3557
|
-
? '
|
|
3558
|
-
: (payload.providerType === '
|
|
3559
|
-
? '
|
|
3560
|
-
: (payload.providerType === '
|
|
3657
|
+
: (payload.providerType === 'claude-api'
|
|
3658
|
+
? 'Claude API'
|
|
3659
|
+
: (payload.providerType === CLAUDE_CLOUD_PROVIDER
|
|
3660
|
+
? 'Claude Cloud'
|
|
3661
|
+
: (payload.providerType === 'cursor'
|
|
3662
|
+
? 'Cursor'
|
|
3663
|
+
: (payload.providerType === 'grok'
|
|
3664
|
+
? 'Grok'
|
|
3665
|
+
: (payload.providerType === 'deepseek' ? 'DeepSeek' : 'Copilot')))));
|
|
3561
3666
|
const subtitle = sessionId
|
|
3562
3667
|
? `${providerLabel} session ${sessionId.slice(0, 8)}`
|
|
3563
3668
|
: (trimmedConvId ? `Conversation ${trimmedConvId.slice(0, 8)}` : 'No conversation selected');
|
|
@@ -3566,9 +3671,10 @@ async function loadContextSummaryAndRender(convId) {
|
|
|
3566
3671
|
// (older relays send neither, and the notes fall back to draft vs stored).
|
|
3567
3672
|
const sessionField = (key) => payload[key] ?? payload.contextUsage?.[key] ?? null;
|
|
3568
3673
|
const modelContextLimit = sessionField('modelContextLimit');
|
|
3569
|
-
// A DeepSeek chat runs the same Claude Code runner, so it has
|
|
3570
|
-
// compaction window to show and to steer.
|
|
3571
|
-
const runsClaudeCode = payload.providerType === 'claude' || payload.providerType === 'deepseek'
|
|
3674
|
+
// A DeepSeek or Claude API chat runs the same Claude Code runner, so it has
|
|
3675
|
+
// the same compaction window to show and to steer.
|
|
3676
|
+
const runsClaudeCode = payload.providerType === 'claude' || payload.providerType === 'deepseek'
|
|
3677
|
+
|| payload.providerType === 'claude-api';
|
|
3572
3678
|
const usageHtml = renderContextUsageHtml(payload.contextUsage, {
|
|
3573
3679
|
compactionWindow: runsClaudeCode,
|
|
3574
3680
|
modelContextLimit,
|
|
@@ -3614,15 +3720,17 @@ async function loadContextSummaryAndRender(convId) {
|
|
|
3614
3720
|
usedTokens: autoCompactControlSession.usedTokens,
|
|
3615
3721
|
})
|
|
3616
3722
|
: '';
|
|
3617
|
-
const thinkingHtml = payload.providerType === 'claude'
|
|
3723
|
+
const thinkingHtml = payload.providerType === 'claude' || payload.providerType === 'claude-api'
|
|
3618
3724
|
? renderThinkingControlHtml({
|
|
3619
3725
|
thinkingEnabled: payload.thinkingEnabled !== false,
|
|
3620
3726
|
thinkingDisplay: payload.thinkingDisplay ?? null,
|
|
3621
3727
|
})
|
|
3622
3728
|
: '';
|
|
3623
|
-
// What this folder's commits say, with the folder override: Claude
|
|
3624
|
-
// Claude Cloud chat that was started from a folder
|
|
3625
|
-
|
|
3729
|
+
// What this folder's commits say, with the folder override: Claude (on
|
|
3730
|
+
// either billing), and a Claude Cloud chat that was started from a folder
|
|
3731
|
+
// (no path, no row).
|
|
3732
|
+
const attributionHtml = payload.providerType === 'claude' || payload.providerType === 'claude-api'
|
|
3733
|
+
|| payload.providerType === 'claude-cloud'
|
|
3626
3734
|
? renderAttributionControlHtml(payload.attribution || null)
|
|
3627
3735
|
: '';
|
|
3628
3736
|
thinkingControlStored = {
|
|
@@ -4652,6 +4760,7 @@ initSocketHandlers({
|
|
|
4652
4760
|
applyGrokSettingsState,
|
|
4653
4761
|
applyCursorSettingsState,
|
|
4654
4762
|
applyDeepSeekSettingsState,
|
|
4763
|
+
applyClaudeApiSettingsState,
|
|
4655
4764
|
});
|
|
4656
4765
|
|
|
4657
4766
|
initCwdPicker({
|
|
@@ -4736,6 +4845,15 @@ async function initApp() {
|
|
|
4736
4845
|
syncWindowRetentionSlider();
|
|
4737
4846
|
initHostSuspendUi();
|
|
4738
4847
|
initUsageLimitUi();
|
|
4848
|
+
// After a switch the chat's provider changed: its model list, lock note
|
|
4849
|
+
// and list pill follow.
|
|
4850
|
+
initClaudeBillingToggle({
|
|
4851
|
+
isSharedReaderMode,
|
|
4852
|
+
onConversationProviderChanged: (conversationId) => {
|
|
4853
|
+
if (conversationId === currentConvId) syncAutoModelAvailability();
|
|
4854
|
+
renderConvList();
|
|
4855
|
+
},
|
|
4856
|
+
});
|
|
4739
4857
|
}
|
|
4740
4858
|
setupViewportTracking();
|
|
4741
4859
|
bindChatSelectionGuard();
|
|
@@ -5036,12 +5154,15 @@ async function initApp() {
|
|
|
5036
5154
|
const claudeSettings = await refreshClaudeSettingsState();
|
|
5037
5155
|
// null on a relay without DeepSeek: not offered, as for Claude Cloud.
|
|
5038
5156
|
const deepseekSettings = await refreshDeepSeekSettingsState();
|
|
5157
|
+
// The same for Claude API; its state also decides the composer's billing toggle.
|
|
5158
|
+
const claudeApiSettings = await refreshClaudeApiSettingsState();
|
|
5039
5159
|
updateProviderReadinessInputs({
|
|
5040
5160
|
openai: openAISettings || null,
|
|
5041
5161
|
cursor: cursorSettings || null,
|
|
5042
5162
|
grok: grokSettings || null,
|
|
5043
5163
|
claude: claudeSettings || null,
|
|
5044
5164
|
deepseek: deepseekSettings || null,
|
|
5165
|
+
claudeApi: claudeApiSettings || null,
|
|
5045
5166
|
});
|
|
5046
5167
|
// Not awaited: only a Claude Cloud conversation needs it (its model list),
|
|
5047
5168
|
// and the subscription below rebuilds that list when the answer arrives. A
|
|
@@ -5221,6 +5342,11 @@ window.saveDeepSeekSettings = saveDeepSeekSettings;
|
|
|
5221
5342
|
window.removeDeepSeekSettings = removeDeepSeekSettings;
|
|
5222
5343
|
window.toggleDeepSeekProvider = toggleDeepSeekProvider;
|
|
5223
5344
|
window.importDeepSeekUsageExport = importDeepSeekUsageExport;
|
|
5345
|
+
window.saveClaudeApiSettings = saveClaudeApiSettings;
|
|
5346
|
+
window.removeClaudeApiSettings = removeClaudeApiSettings;
|
|
5347
|
+
window.toggleClaudeApiProvider = toggleClaudeApiProvider;
|
|
5348
|
+
window.refreshClaudeApiModels = refreshClaudeApiModels;
|
|
5349
|
+
window.openClaudeTabFromClaudeApi = openClaudeTabFromClaudeApi;
|
|
5224
5350
|
window.toggleGrokProvider = toggleGrokProvider;
|
|
5225
5351
|
window.saveClaudeCloudSettings = saveClaudeCloudSettings;
|
|
5226
5352
|
window.toggleClaudeCloudProvider = toggleClaudeCloudProvider;
|