@oar-sh/oar 0.9.16 → 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/polling/polling-loop.mjs +14 -87
- package/.github/extensions/web-relay/skills/tool-activity.mjs +5 -4
- package/CHANGELOG.md +412 -0
- package/README.md +15 -13
- package/package.json +2 -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-attachments.mjs +7 -4
- 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 +199 -15
- package/server/claude-worker/claude-session-process.mjs +276 -33
- package/server/claude-worker/claude-session-worker-main.mjs +269 -0
- package/server/claude-worker/claude-session-worker.mjs +2 -253
- package/server/claude-worker/claude-subscription-key-check.mjs +64 -0
- package/server/claude-worker/claude-turn-failure.mjs +8 -0
- package/server/claude-worker/claude-turn-publisher.mjs +394 -13
- package/server/claude-worker/claude-usage-totals.mjs +161 -0
- package/server/claude-worker/sdk-message-normalizer.mjs +56 -1
- package/server/copilot-worker/copilot-remote-relay-tool.mjs +2 -2
- package/server/copilot-worker/copilot-sdk-adapter.mjs +18 -4
- package/server/copilot-worker/copilot-sdk-event-normalizer.mjs +1 -1
- package/server/copilot-worker/copilot-sdk-session-process.mjs +80 -14
- 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 +96 -1
- package/server/deepseek-worker/deepseek-claude-backend.mjs +317 -0
- package/server/deepseek-worker/deepseek-session-worker.mjs +37 -0
- 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/0024-deepseek-usage-totals.mjs +151 -0
- package/server/migrations/0025-deepseek-usage-requests-daily.mjs +179 -0
- package/server/migrations/0026-deepseek-usage-served-models.mjs +222 -0
- package/server/migrations/0027-deepseek-usage-checkpoint-runs.mjs +101 -0
- package/server/migrations/0028-deepseek-usage-checkpoint-request-runs.mjs +99 -0
- package/server/migrations/0029-deepseek-balance-rises.mjs +99 -0
- package/server/migrations/0030-deepseek-usage-actuals.mjs +123 -0
- 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 +105 -0
- package/server/public/app/bootstrap.js +243 -29
- 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 +10 -2
- package/server/public/app/context-usage-view.mjs +1 -1
- package/server/public/app/conversation-provider-indicator.mjs +18 -0
- package/server/public/app/conversation-view.js +31 -3
- package/server/public/app/deepseek-settings-ui.js +342 -0
- package/server/public/app/inbox-ui.js +14 -3
- package/server/public/app/inbox-view.mjs +1 -1
- package/server/public/app/journal-view.js +156 -11
- package/server/public/app/model-selector-options.mjs +28 -1
- package/server/public/app/model-vision-support.mjs +2 -0
- package/server/public/app/new-conversation-model-choice.mjs +21 -0
- package/server/public/app/no-conversation-state.mjs +1 -1
- package/server/public/app/plan-usage-view.mjs +165 -13
- package/server/public/app/provider-readiness.mjs +8 -2
- package/server/public/app/relay-error-ctas.mjs +58 -0
- package/server/public/app/relay-schedules-ui.js +1 -1
- package/server/public/app/settings-modal.js +22 -0
- package/server/public/app/settings-tabs.js +1 -1
- package/server/public/app/socket-handlers.js +28 -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 +596 -35
- package/server/relay-tools.md +4 -4
- package/server/repositories/conversation-group-repository.mjs +14 -7
- 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 +3 -2
- package/server/routes/deepseek-usage-import-routes.mjs +72 -0
- package/server/routes/messages-routes.mjs +831 -36
- 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 +670 -63
- package/server/server-runtime.mjs +476 -26
- package/server/services/about-service.mjs +2 -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/claude-session-leftovers.mjs +226 -0
- package/server/services/deepseek-balance-service.mjs +102 -0
- package/server/services/deepseek-settings-service.mjs +380 -0
- package/server/services/deepseek-usage-import.mjs +471 -0
- package/server/services/model-variant-catalog-service.mjs +1 -0
- package/server/services/plan-usage-claude-api.mjs +445 -0
- package/server/services/plan-usage-contract.mjs +100 -5
- package/server/services/plan-usage-deepseek.mjs +931 -0
- package/server/services/plan-usage-service.mjs +436 -0
- package/server/services/provider-model-selection.mjs +4 -2
- package/server/services/provider-reasoning-effort.mjs +24 -2
- 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 +84 -14
- package/server/services/remote-relay-registry-service.mjs +1 -1
- package/server/services/session-worker-launch-service.mjs +294 -84
- 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 +320 -0
- package/shared/anthropic-api.mjs +202 -0
- package/shared/context-window-fallbacks.mjs +3 -0
- package/shared/deepseek-api.mjs +137 -0
- package/shared/deepseek-models.mjs +114 -0
- package/shared/deepseek-pricing.mjs +146 -0
- package/shared/host-claude-session-env.mjs +101 -0
- package/shared/model-id.mjs +7 -0
- package/shared/preview-tool-core.mjs +1 -1
- 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 +2 -0
- package/shared/worker-runtime/api-client.mjs +2 -2
- package/shared/worker-runtime/direct-openai-image.mjs +128 -0
- package/shared/worker-runtime/heartbeat.mjs +5 -0
- package/shared/worker-runtime/worker-log-file.mjs +60 -0
- package/shared/worker-runtime/worker-websocket-link.mjs +6 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Migration: Claude API usage totals (services/plan-usage-service.mjs, the
|
|
5
|
+
* Claude API card in Check Usage and its monthly cap).
|
|
6
|
+
*
|
|
7
|
+
* `claude_api_usage_totals`: what this relay's turns on the saved Claude API
|
|
8
|
+
* key ran, per day and model; a row only ever grows. Subscription turns are
|
|
9
|
+
* never booked here. The card shows today and this month, and the cap is a
|
|
10
|
+
* calendar month in relay local time, so the day is the relay's local date.
|
|
11
|
+
*
|
|
12
|
+
* - `day`: `YYYY-MM-DD`, the relay's local date of the turn's result.
|
|
13
|
+
* - `model`: the model's id as the CLI reported it, without the `[1m]`
|
|
14
|
+
* suffix, lower case.
|
|
15
|
+
* - `hit_tokens`, `miss_tokens`, `output_tokens`: input read from the
|
|
16
|
+
* prompt cache, other input (cache writes included), and output.
|
|
17
|
+
* - `cost_usd`: Claude Code's own estimate (`modelUsage[*].costUSD`) of what
|
|
18
|
+
* the booked turns cost, for the turns it could price.
|
|
19
|
+
* - `unknown_cost_tokens`: the tokens of turns it could not price
|
|
20
|
+
* (`costBasis: unknown`), which `cost_usd` leaves out.
|
|
21
|
+
* - `turn_count`: the posts that booked something for the model.
|
|
22
|
+
* - `first_seen_at`, `last_seen_at`, `updated_at`.
|
|
23
|
+
*
|
|
24
|
+
* Additive and idempotent. Safe to run on every boot.
|
|
25
|
+
*
|
|
26
|
+
* Usage: node server/migrations/0034-claude-api-usage-totals.mjs [path/to/copilot.db]
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { fileURLToPath } from 'url';
|
|
30
|
+
import path from 'path';
|
|
31
|
+
import process from 'process';
|
|
32
|
+
import Database from 'better-sqlite3';
|
|
33
|
+
|
|
34
|
+
export const CLAUDE_API_USAGE_TOTALS_TABLE = 'claude_api_usage_totals';
|
|
35
|
+
|
|
36
|
+
export const CLAUDE_API_USAGE_TOTALS_COLUMNS = Object.freeze([
|
|
37
|
+
'day',
|
|
38
|
+
'model',
|
|
39
|
+
'hit_tokens',
|
|
40
|
+
'miss_tokens',
|
|
41
|
+
'output_tokens',
|
|
42
|
+
'cost_usd',
|
|
43
|
+
'unknown_cost_tokens',
|
|
44
|
+
'turn_count',
|
|
45
|
+
'first_seen_at',
|
|
46
|
+
'last_seen_at',
|
|
47
|
+
'updated_at',
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
function tableExists(db, name) {
|
|
51
|
+
return !!db
|
|
52
|
+
.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`)
|
|
53
|
+
.get(name);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** What keeps the service from using the table: '' when nothing does. */
|
|
57
|
+
export function claudeApiUsageTotalsProblem(db) {
|
|
58
|
+
if (!tableExists(db, CLAUDE_API_USAGE_TOTALS_TABLE)) return `no ${CLAUDE_API_USAGE_TOTALS_TABLE} table`;
|
|
59
|
+
const present = new Set(db.prepare(`PRAGMA table_info(${CLAUDE_API_USAGE_TOTALS_TABLE})`).all().map((column) => column.name));
|
|
60
|
+
const missing = CLAUDE_API_USAGE_TOTALS_COLUMNS.filter((name) => !present.has(name));
|
|
61
|
+
if (missing.length) return `${CLAUDE_API_USAGE_TOTALS_TABLE} lacks ${missing.join(', ')}`;
|
|
62
|
+
return '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function migrateClaudeApiUsageTotals(db) {
|
|
66
|
+
if (tableExists(db, CLAUDE_API_USAGE_TOTALS_TABLE)) return { applied: false, tablesAdded: 0 };
|
|
67
|
+
db.exec(`
|
|
68
|
+
CREATE TABLE ${CLAUDE_API_USAGE_TOTALS_TABLE} (
|
|
69
|
+
day TEXT NOT NULL,
|
|
70
|
+
model TEXT NOT NULL,
|
|
71
|
+
hit_tokens INTEGER NOT NULL DEFAULT 0,
|
|
72
|
+
miss_tokens INTEGER NOT NULL DEFAULT 0,
|
|
73
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
74
|
+
cost_usd REAL NOT NULL DEFAULT 0,
|
|
75
|
+
unknown_cost_tokens INTEGER NOT NULL DEFAULT 0,
|
|
76
|
+
turn_count INTEGER NOT NULL DEFAULT 0,
|
|
77
|
+
first_seen_at TEXT NOT NULL,
|
|
78
|
+
last_seen_at TEXT NOT NULL,
|
|
79
|
+
updated_at TEXT NOT NULL,
|
|
80
|
+
PRIMARY KEY (day, model)
|
|
81
|
+
)
|
|
82
|
+
`);
|
|
83
|
+
return { applied: true, tablesAdded: 1 };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function defaultDbPath() {
|
|
87
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
88
|
+
return path.resolve(here, '..', 'data', 'copilot.db');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function migrate(dbPath, options = {}) {
|
|
92
|
+
const db = new Database(dbPath, options);
|
|
93
|
+
try {
|
|
94
|
+
return migrateClaudeApiUsageTotals(db);
|
|
95
|
+
} finally {
|
|
96
|
+
db.close();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
101
|
+
if (isMain) {
|
|
102
|
+
const dbPath = process.argv[2] ? path.resolve(process.argv[2]) : defaultDbPath();
|
|
103
|
+
try {
|
|
104
|
+
const result = migrate(dbPath);
|
|
105
|
+
console.log(`Migration 0034 on ${dbPath}`);
|
|
106
|
+
console.log(result.applied ? ` claude api usage totals: tables added ${result.tablesAdded}` : ' skipped: nothing to change');
|
|
107
|
+
process.exit(0);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
console.error(`Migration 0034 failed: ${err?.message || err}`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -26,12 +26,13 @@ export function isCompactBoundaryActivityEntry(item) {
|
|
|
26
26
|
|
|
27
27
|
// The live list of one turn keeps its newest ordinary lines only, but every
|
|
28
28
|
// structured row a line of the transcript is built from: the turn's
|
|
29
|
-
// compaction entries and the session-restart
|
|
30
|
-
// of a busy turn and take their
|
|
29
|
+
// compaction entries and the session-restart notes (a compaction window, a
|
|
30
|
+
// billing switch) would otherwise scroll out of a busy turn and take their
|
|
31
|
+
// line with them until the next reload.
|
|
31
32
|
export const LIVE_ACTIVITY_CAP = 24;
|
|
32
33
|
function isKeptLiveActivityEntry(item) {
|
|
33
34
|
const kind = normalizeRelayActivityEntry(item)?.metadata?.kind;
|
|
34
|
-
return kind === 'compact_boundary' || kind === 'compact_window_respawn';
|
|
35
|
+
return kind === 'compact_boundary' || kind === 'compact_window_respawn' || kind === 'billing_switch_respawn';
|
|
35
36
|
}
|
|
36
37
|
export function capLiveActivityEntries(items, cap = LIVE_ACTIVITY_CAP) {
|
|
37
38
|
const list = Array.isArray(items) ? items : [];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Settings → Relays → Agent sessions: the switch that lets agents start and use
|
|
2
|
-
// sessions on this relay through the
|
|
2
|
+
// sessions on this relay through the relay_tool, and the slider for the
|
|
3
3
|
// longest wait of one tool call. The decisions live in
|
|
4
4
|
// agent-sessions-settings.mjs; this file only moves them into the markup in
|
|
5
5
|
// index.html. The static controls use inline handlers that bootstrap.js
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Pure helpers for Settings → Relays → Agent sessions: whether agents may
|
|
2
|
-
// start and use sessions on this relay, and the longest time one
|
|
2
|
+
// start and use sessions on this relay, and the longest time one relay_tool
|
|
3
3
|
// call may wait. DOM-free on purpose, like claude-cloud-ui.mjs: the section
|
|
4
4
|
// (agent-sessions-settings-ui.js) owns the elements, this module the decisions.
|
|
5
5
|
//
|
|
@@ -530,6 +530,90 @@ export async function updateCursorSettings({
|
|
|
530
530
|
return settingsRequest('/api/settings/cursor', payload, 'Failed to update Cursor settings');
|
|
531
531
|
}
|
|
532
532
|
|
|
533
|
+
export async function loadDeepSeekSettings() {
|
|
534
|
+
return apiFetch('/api/settings/deepseek');
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Saves what the caller names: a key (written, never read back), the switch,
|
|
539
|
+
* the default model, the enabled models, the base URL (null = DeepSeek's
|
|
540
|
+
* own), the attribution and the monthly budget (null = none); `remove`
|
|
541
|
+
* takes the key out.
|
|
542
|
+
*/
|
|
543
|
+
export async function updateDeepSeekSettings({
|
|
544
|
+
apiKey = '',
|
|
545
|
+
model = undefined,
|
|
546
|
+
enabled = undefined,
|
|
547
|
+
enabledModels = undefined,
|
|
548
|
+
baseUrl = undefined,
|
|
549
|
+
attribution = undefined,
|
|
550
|
+
monthlyBudget = undefined,
|
|
551
|
+
monthlyBudgetCurrency = undefined,
|
|
552
|
+
startingBalance = undefined,
|
|
553
|
+
startingBalanceEdited = undefined,
|
|
554
|
+
remove = false,
|
|
555
|
+
} = {}) {
|
|
556
|
+
// Only the fields named here reach the relay: a field the DeepSeek tab
|
|
557
|
+
// shows needs its line below (api-client.deepseek-settings.test.mjs).
|
|
558
|
+
const payload = remove === true ? { remove: true } : {};
|
|
559
|
+
if (!payload.remove) {
|
|
560
|
+
const key = String(apiKey || '').trim();
|
|
561
|
+
if (key) payload.apiKey = key;
|
|
562
|
+
if (typeof model === 'string' && model.trim()) payload.model = model.trim();
|
|
563
|
+
if (typeof enabled === 'boolean') payload.enabled = enabled;
|
|
564
|
+
if (Array.isArray(enabledModels)) payload.enabledModels = enabledModels;
|
|
565
|
+
if (baseUrl === null || typeof baseUrl === 'string') payload.baseUrl = baseUrl === null ? null : String(baseUrl).trim();
|
|
566
|
+
if (typeof attribution === 'string' && attribution.trim()) payload.attribution = attribution.trim();
|
|
567
|
+
if (monthlyBudget === null || (typeof monthlyBudget === 'number' && Number.isFinite(monthlyBudget))) payload.monthlyBudget = monthlyBudget;
|
|
568
|
+
// 'USD' or 'CNY'; null is the balance's currency.
|
|
569
|
+
if (monthlyBudgetCurrency === null || typeof monthlyBudgetCurrency === 'string') {
|
|
570
|
+
payload.monthlyBudgetCurrency = String(monthlyBudgetCurrency ?? '').trim() || null;
|
|
571
|
+
}
|
|
572
|
+
if (startingBalance === null || (typeof startingBalance === 'number' && Number.isFinite(startingBalance))) payload.startingBalance = startingBalance;
|
|
573
|
+
if (typeof startingBalanceEdited === 'boolean') payload.startingBalanceEdited = startingBalanceEdited;
|
|
574
|
+
}
|
|
575
|
+
return settingsRequest('/api/settings/deepseek', payload, 'Failed to update DeepSeek settings');
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
export async function loadClaudeApiSettings() {
|
|
579
|
+
return apiFetch('/api/settings/claude-api');
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Saves what the caller names on the Claude API tab: a key (written, never
|
|
584
|
+
* read back), the switch, the default model, the enabled models and the
|
|
585
|
+
* monthly cap in US dollars (null = no cap); `remove` takes the key out.
|
|
586
|
+
*/
|
|
587
|
+
export async function updateClaudeApiSettings({
|
|
588
|
+
apiKey = '',
|
|
589
|
+
model = undefined,
|
|
590
|
+
enabled = undefined,
|
|
591
|
+
enabledModels = undefined,
|
|
592
|
+
monthlyCapUsd = undefined,
|
|
593
|
+
remove = false,
|
|
594
|
+
} = {}) {
|
|
595
|
+
// Only the fields named here reach the relay: a field the Claude API tab
|
|
596
|
+
// shows needs its line below (api-client.claude-api-settings.test.mjs).
|
|
597
|
+
const payload = remove === true ? { remove: true } : {};
|
|
598
|
+
if (!payload.remove) {
|
|
599
|
+
const key = String(apiKey || '').trim();
|
|
600
|
+
if (key) payload.apiKey = key;
|
|
601
|
+
if (typeof model === 'string' && model.trim()) payload.model = model.trim();
|
|
602
|
+
if (typeof enabled === 'boolean') payload.enabled = enabled;
|
|
603
|
+
if (Array.isArray(enabledModels)) payload.enabledModels = enabledModels;
|
|
604
|
+
if (monthlyCapUsd === null || (typeof monthlyCapUsd === 'number' && Number.isFinite(monthlyCapUsd))) payload.monthlyCapUsd = monthlyCapUsd;
|
|
605
|
+
}
|
|
606
|
+
return settingsRequest('/api/settings/claude-api', payload, 'Failed to update Claude API settings');
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Imports DeepSeek's usage export: the texts of its amount and cost files.
|
|
611
|
+
* Answers `{ ok, summary }`; a refused export throws with the relay's reason.
|
|
612
|
+
*/
|
|
613
|
+
export async function importDeepSeekUsageExport({ amount, cost }) {
|
|
614
|
+
return settingsRequest('/api/deepseek-usage-import', { amount, cost }, 'Failed to import the DeepSeek usage export');
|
|
615
|
+
}
|
|
616
|
+
|
|
533
617
|
export async function loadWindowsAutostartSetting() {
|
|
534
618
|
return apiFetchOrThrow('/api/settings/windows-autostart');
|
|
535
619
|
}
|
|
@@ -1426,6 +1510,27 @@ export async function resumeUsageLimitPause(conversationId) {
|
|
|
1426
1510
|
});
|
|
1427
1511
|
}
|
|
1428
1512
|
|
|
1513
|
+
// Move a Claude chat between the subscription ("claude") and the saved API
|
|
1514
|
+
// key ("claude-api"), from its next turn. `by: "usage-limit"` is the
|
|
1515
|
+
// usage-limit note's button. `{ ok: true, ... }`, else `{ ok: false, error,
|
|
1516
|
+
// code }` with the relay's reason (the key, the model, the cap).
|
|
1517
|
+
export async function switchConversationBilling(conversationId, { to, by = 'user' } = {}) {
|
|
1518
|
+
let refusal = null;
|
|
1519
|
+
const result = await apiFetch(`/api/conversation/${encodeURIComponent(String(conversationId || ''))}/billing`, {
|
|
1520
|
+
method: 'POST',
|
|
1521
|
+
body: JSON.stringify({ to, by }),
|
|
1522
|
+
onErrorResponse: async (response) => {
|
|
1523
|
+
const payload = await response.json().catch(() => null);
|
|
1524
|
+
refusal = {
|
|
1525
|
+
ok: false,
|
|
1526
|
+
error: readableServerError(payload?.error) || `The relay answered ${response.status}.`,
|
|
1527
|
+
code: payload?.code || null,
|
|
1528
|
+
};
|
|
1529
|
+
},
|
|
1530
|
+
});
|
|
1531
|
+
return result || refusal || { ok: false, error: 'The relay could not be reached.', code: null };
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1429
1534
|
export async function cancelUsageLimitPause(conversationId) {
|
|
1430
1535
|
return apiFetch('/api/usage-limit/cancel', {
|
|
1431
1536
|
method: 'POST',
|