@oar-sh/oar 0.9.16 → 0.9.17
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/CHANGELOG.md +220 -0
- package/README.md +12 -11
- package/package.json +2 -1
- package/server/claude-worker/claude-attachments.mjs +7 -4
- package/server/claude-worker/claude-sdk-adapter.mjs +167 -9
- package/server/claude-worker/claude-session-process.mjs +127 -29
- package/server/claude-worker/claude-session-worker-main.mjs +266 -0
- package/server/claude-worker/claude-session-worker.mjs +2 -253
- package/server/claude-worker/claude-turn-failure.mjs +8 -0
- package/server/claude-worker/claude-turn-publisher.mjs +282 -13
- package/server/claude-worker/claude-usage-totals.mjs +108 -0
- package/server/claude-worker/sdk-message-normalizer.mjs +55 -0
- package/server/copilot-worker/copilot-sdk-adapter.mjs +18 -4
- package/server/copilot-worker/copilot-sdk-session-process.mjs +71 -5
- package/server/db-schema.mjs +63 -0
- package/server/deepseek-worker/deepseek-claude-backend.mjs +433 -0
- package/server/deepseek-worker/deepseek-session-worker.mjs +37 -0
- 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/public/app/api-client.js +53 -0
- package/server/public/app/bootstrap.js +101 -13
- package/server/public/app/context-tier-options.mjs +4 -0
- package/server/public/app/conversation-provider-indicator.mjs +7 -0
- package/server/public/app/conversation-view.js +2 -0
- package/server/public/app/deepseek-settings-ui.js +342 -0
- package/server/public/app/inbox-ui.js +14 -3
- package/server/public/app/journal-view.js +31 -3
- package/server/public/app/model-selector-options.mjs +11 -0
- package/server/public/app/model-vision-support.mjs +2 -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 +4 -1
- package/server/public/app/relay-error-ctas.mjs +8 -0
- 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 +10 -0
- package/server/public/index.html +408 -33
- package/server/repositories/conversation-group-repository.mjs +13 -6
- package/server/routes/conversation-group-routes.mjs +2 -1
- package/server/routes/deepseek-usage-import-routes.mjs +72 -0
- package/server/routes/messages-routes.mjs +192 -15
- package/server/routes/sessions-routes.mjs +364 -55
- package/server/server-runtime.mjs +123 -10
- package/server/services/about-service.mjs +1 -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-contract.mjs +100 -5
- package/server/services/plan-usage-deepseek.mjs +931 -0
- package/server/services/plan-usage-service.mjs +431 -0
- package/server/services/provider-model-selection.mjs +1 -0
- package/server/services/provider-reasoning-effort.mjs +15 -2
- package/server/services/remote-relay-dispatcher.mjs +2 -1
- package/server/services/session-worker-launch-service.mjs +233 -82
- package/server/services/worker-secret-redaction.mjs +318 -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 +2 -2
- package/shared/remote-relay-contract.mjs +1 -1
- package/shared/steer-settle-failure.mjs +1 -0
- package/shared/worker-runtime/direct-openai-image.mjs +128 -0
- package/shared/worker-runtime/worker-log-file.mjs +60 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Migration: the DeepSeek usage checkpoint keeps the run id of its web
|
|
5
|
+
* search and fetch counts apart from the run id of its token totals
|
|
6
|
+
* (services/plan-usage-service.mjs, the DeepSeek card in Check Usage).
|
|
7
|
+
* Builds on 0027.
|
|
8
|
+
*
|
|
9
|
+
* `deepseek_usage_checkpoints` gains one column:
|
|
10
|
+
* - `request_run_id`: the worker's id of the Claude Code process the
|
|
11
|
+
* `search_requests` and `fetch_requests` counts came from; '' before any
|
|
12
|
+
* report with a run id carried counts for the model. With 0027 one
|
|
13
|
+
* `run_id` served both the totals and the counts, so a report that
|
|
14
|
+
* carried only counts for a model (a sub-agent's model that searched
|
|
15
|
+
* without a token entry of its own) moved the run id of that model's token
|
|
16
|
+
* totals too: a later token report of the earlier process was then read
|
|
17
|
+
* as a new process and booked in full instead of as a stale one. Now a
|
|
18
|
+
* token entry moves `run_id` and a request entry moves `request_run_id`,
|
|
19
|
+
* and neither disturbs the other.
|
|
20
|
+
*
|
|
21
|
+
* Additive and idempotent. Safe to run on every boot.
|
|
22
|
+
*
|
|
23
|
+
* Usage: node server/migrations/0028-deepseek-usage-checkpoint-request-runs.mjs [path/to/copilot.db]
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { fileURLToPath } from 'url';
|
|
27
|
+
import path from 'path';
|
|
28
|
+
import process from 'process';
|
|
29
|
+
import Database from 'better-sqlite3';
|
|
30
|
+
|
|
31
|
+
import { DEEPSEEK_USAGE_CHECKPOINTS_TABLE } from './0024-deepseek-usage-totals.mjs';
|
|
32
|
+
import { migrateDeepSeekUsageCheckpointRuns } from './0027-deepseek-usage-checkpoint-runs.mjs';
|
|
33
|
+
|
|
34
|
+
// Added to deepseek_usage_checkpoints, with their DDL.
|
|
35
|
+
export const DEEPSEEK_USAGE_CHECKPOINT_REQUEST_RUN_COLUMNS = Object.freeze([
|
|
36
|
+
['request_run_id', "TEXT NOT NULL DEFAULT ''"],
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
function tableExists(db, name) {
|
|
40
|
+
return !!db
|
|
41
|
+
.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`)
|
|
42
|
+
.get(name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function columnNames(db, table) {
|
|
46
|
+
return new Set(db.prepare(`PRAGMA table_info(${table})`).all().map((column) => column.name));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** What keeps the service from using the column: '' when nothing does. */
|
|
50
|
+
export function deepseekUsageCheckpointRequestRunsProblem(db) {
|
|
51
|
+
if (!tableExists(db, DEEPSEEK_USAGE_CHECKPOINTS_TABLE)) return `no ${DEEPSEEK_USAGE_CHECKPOINTS_TABLE} table`;
|
|
52
|
+
const present = columnNames(db, DEEPSEEK_USAGE_CHECKPOINTS_TABLE);
|
|
53
|
+
const missing = DEEPSEEK_USAGE_CHECKPOINT_REQUEST_RUN_COLUMNS.map(([name]) => name).filter((name) => !present.has(name));
|
|
54
|
+
if (missing.length) return `${DEEPSEEK_USAGE_CHECKPOINTS_TABLE} lacks ${missing.join(', ')}`;
|
|
55
|
+
return '';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function migrateDeepSeekUsageCheckpointRequestRuns(db) {
|
|
59
|
+
// The column goes next to 0027's: a database that skipped 0027 gets them first.
|
|
60
|
+
migrateDeepSeekUsageCheckpointRuns(db);
|
|
61
|
+
let columnsAdded = 0;
|
|
62
|
+
const present = columnNames(db, DEEPSEEK_USAGE_CHECKPOINTS_TABLE);
|
|
63
|
+
for (const [name, ddl] of DEEPSEEK_USAGE_CHECKPOINT_REQUEST_RUN_COLUMNS) {
|
|
64
|
+
if (present.has(name)) continue;
|
|
65
|
+
db.exec(`ALTER TABLE ${DEEPSEEK_USAGE_CHECKPOINTS_TABLE} ADD COLUMN ${name} ${ddl}`);
|
|
66
|
+
columnsAdded += 1;
|
|
67
|
+
}
|
|
68
|
+
return { applied: columnsAdded > 0, columnsAdded };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function defaultDbPath() {
|
|
72
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
73
|
+
return path.resolve(here, '..', 'data', 'copilot.db');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function migrate(dbPath, options = {}) {
|
|
77
|
+
const db = new Database(dbPath, options);
|
|
78
|
+
try {
|
|
79
|
+
return migrateDeepSeekUsageCheckpointRequestRuns(db);
|
|
80
|
+
} finally {
|
|
81
|
+
db.close();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
86
|
+
if (isMain) {
|
|
87
|
+
const dbPath = process.argv[2] ? path.resolve(process.argv[2]) : defaultDbPath();
|
|
88
|
+
try {
|
|
89
|
+
const result = migrate(dbPath);
|
|
90
|
+
console.log(`Migration 0028 on ${dbPath}`);
|
|
91
|
+
console.log(result.applied
|
|
92
|
+
? ` deepseek usage checkpoint request runs: columns added ${result.columnsAdded}`
|
|
93
|
+
: ' skipped: nothing to change');
|
|
94
|
+
process.exit(0);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.error(`Migration 0028 failed: ${err?.message || err}`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Migration: the DeepSeek balance reference notes when the balance last rose
|
|
5
|
+
* (services/plan-usage-service.mjs, the DeepSeek card's balance bar). Builds
|
|
6
|
+
* on 0025.
|
|
7
|
+
*
|
|
8
|
+
* `deepseek_balance_reference` gains these columns:
|
|
9
|
+
* - `rose_at`: when a read last found the balance above the read before it
|
|
10
|
+
* (a top-up or a grant); NULL while no rise was seen. `reference_at` cannot
|
|
11
|
+
* tell: it is also set by the first read of a currency. A starting balance
|
|
12
|
+
* set on the DeepSeek tab counts until the balance rises after it was set
|
|
13
|
+
* (plan-usage-deepseek.mjs).
|
|
14
|
+
* - `last_read_at`: when `last_total` was read; NULL for a row from before.
|
|
15
|
+
* - `rose_from_at`: when the balance before that rise was read; NULL when
|
|
16
|
+
* unknown. A rise the relay first sees after the starting balance was set
|
|
17
|
+
* may be a top-up from before it, unless the balance before the rise was
|
|
18
|
+
* read after it too.
|
|
19
|
+
*
|
|
20
|
+
* Additive and idempotent. Safe to run on every boot.
|
|
21
|
+
*
|
|
22
|
+
* Usage: node server/migrations/0029-deepseek-balance-rises.mjs [path/to/copilot.db]
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { fileURLToPath } from 'url';
|
|
26
|
+
import path from 'path';
|
|
27
|
+
import process from 'process';
|
|
28
|
+
import Database from 'better-sqlite3';
|
|
29
|
+
|
|
30
|
+
import { DEEPSEEK_BALANCE_REFERENCE_TABLE, migrateDeepSeekUsageRequestsDaily } from './0025-deepseek-usage-requests-daily.mjs';
|
|
31
|
+
|
|
32
|
+
// Added to deepseek_balance_reference, with their DDL.
|
|
33
|
+
export const DEEPSEEK_BALANCE_RISE_COLUMNS = Object.freeze([
|
|
34
|
+
['rose_at', 'TEXT'],
|
|
35
|
+
['last_read_at', 'TEXT'],
|
|
36
|
+
['rose_from_at', 'TEXT'],
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
function tableExists(db, name) {
|
|
40
|
+
return !!db
|
|
41
|
+
.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`)
|
|
42
|
+
.get(name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function columnNames(db, table) {
|
|
46
|
+
return new Set(db.prepare(`PRAGMA table_info(${table})`).all().map((column) => column.name));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** What keeps the service from using the column: '' when nothing does. */
|
|
50
|
+
export function deepseekBalanceRisesProblem(db) {
|
|
51
|
+
if (!tableExists(db, DEEPSEEK_BALANCE_REFERENCE_TABLE)) return `no ${DEEPSEEK_BALANCE_REFERENCE_TABLE} table`;
|
|
52
|
+
const present = columnNames(db, DEEPSEEK_BALANCE_REFERENCE_TABLE);
|
|
53
|
+
const missing = DEEPSEEK_BALANCE_RISE_COLUMNS.map(([name]) => name).filter((name) => !present.has(name));
|
|
54
|
+
if (missing.length) return `${DEEPSEEK_BALANCE_REFERENCE_TABLE} lacks ${missing.join(', ')}`;
|
|
55
|
+
return '';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function migrateDeepSeekBalanceRises(db) {
|
|
59
|
+
// The column goes on 0025's table: a database that skipped 0025 gets it first.
|
|
60
|
+
migrateDeepSeekUsageRequestsDaily(db);
|
|
61
|
+
let columnsAdded = 0;
|
|
62
|
+
const present = columnNames(db, DEEPSEEK_BALANCE_REFERENCE_TABLE);
|
|
63
|
+
for (const [name, ddl] of DEEPSEEK_BALANCE_RISE_COLUMNS) {
|
|
64
|
+
if (present.has(name)) continue;
|
|
65
|
+
db.exec(`ALTER TABLE ${DEEPSEEK_BALANCE_REFERENCE_TABLE} ADD COLUMN ${name} ${ddl}`);
|
|
66
|
+
columnsAdded += 1;
|
|
67
|
+
}
|
|
68
|
+
return { applied: columnsAdded > 0, columnsAdded };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function defaultDbPath() {
|
|
72
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
73
|
+
return path.resolve(here, '..', 'data', 'copilot.db');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function migrate(dbPath, options = {}) {
|
|
77
|
+
const db = new Database(dbPath, options);
|
|
78
|
+
try {
|
|
79
|
+
return migrateDeepSeekBalanceRises(db);
|
|
80
|
+
} finally {
|
|
81
|
+
db.close();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
86
|
+
if (isMain) {
|
|
87
|
+
const dbPath = process.argv[2] ? path.resolve(process.argv[2]) : defaultDbPath();
|
|
88
|
+
try {
|
|
89
|
+
const result = migrate(dbPath);
|
|
90
|
+
console.log(`Migration 0029 on ${dbPath}`);
|
|
91
|
+
console.log(result.applied
|
|
92
|
+
? ` deepseek balance rises: columns added ${result.columnsAdded}`
|
|
93
|
+
: ' skipped: nothing to change');
|
|
94
|
+
process.exit(0);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.error(`Migration 0029 failed: ${err?.message || err}`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Migration: the figures of DeepSeek's own usage export, imported from the
|
|
5
|
+
* platform's two CSV files (services/deepseek-usage-import.mjs). The usage
|
|
6
|
+
* card in Check Usage shows them instead of the relay's estimate for the
|
|
7
|
+
* days and models they cover.
|
|
8
|
+
*
|
|
9
|
+
* Adds one table of its own, `deepseek_usage_actuals`, one row per export
|
|
10
|
+
* day, model and API key (the key is the day, so a re-export taken in
|
|
11
|
+
* another time zone replaces the day instead of adding a second row):
|
|
12
|
+
* - `day`: the local date of `period_start`, in the export's time zone;
|
|
13
|
+
* - `period_start`, `period_end`: the first start and last end of the day's
|
|
14
|
+
* periods as the export gives them (ISO with the export's offset);
|
|
15
|
+
* - `model`, `key_fp` (the key masked as `sk-XXXXX…YYYY`), `key_name`;
|
|
16
|
+
* - `hit_tokens`, `miss_tokens`, `output_tokens`, `request_count`;
|
|
17
|
+
* - `cost`, `currency`: the period's cost, amount × price summed;
|
|
18
|
+
* - `imported_at`.
|
|
19
|
+
*
|
|
20
|
+
* The relay's own usage tables are not touched. Additive and idempotent.
|
|
21
|
+
* Safe to run on every boot.
|
|
22
|
+
*
|
|
23
|
+
* Usage: node server/migrations/0030-deepseek-usage-actuals.mjs [path/to/copilot.db]
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { fileURLToPath } from 'url';
|
|
27
|
+
import path from 'path';
|
|
28
|
+
import process from 'process';
|
|
29
|
+
import Database from 'better-sqlite3';
|
|
30
|
+
|
|
31
|
+
export const DEEPSEEK_USAGE_ACTUALS_TABLE = 'deepseek_usage_actuals';
|
|
32
|
+
|
|
33
|
+
export const DEEPSEEK_USAGE_ACTUALS_COLUMNS = Object.freeze([
|
|
34
|
+
'period_start',
|
|
35
|
+
'period_end',
|
|
36
|
+
'day',
|
|
37
|
+
'model',
|
|
38
|
+
'key_fp',
|
|
39
|
+
'key_name',
|
|
40
|
+
'hit_tokens',
|
|
41
|
+
'miss_tokens',
|
|
42
|
+
'output_tokens',
|
|
43
|
+
'request_count',
|
|
44
|
+
'cost',
|
|
45
|
+
'currency',
|
|
46
|
+
'imported_at',
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
const CREATE_SQL = `
|
|
50
|
+
CREATE TABLE IF NOT EXISTS ${DEEPSEEK_USAGE_ACTUALS_TABLE} (
|
|
51
|
+
period_start TEXT NOT NULL,
|
|
52
|
+
period_end TEXT NOT NULL,
|
|
53
|
+
day TEXT NOT NULL,
|
|
54
|
+
model TEXT NOT NULL,
|
|
55
|
+
key_fp TEXT NOT NULL,
|
|
56
|
+
key_name TEXT NOT NULL DEFAULT '',
|
|
57
|
+
hit_tokens INTEGER NOT NULL DEFAULT 0,
|
|
58
|
+
miss_tokens INTEGER NOT NULL DEFAULT 0,
|
|
59
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
60
|
+
request_count INTEGER NOT NULL DEFAULT 0,
|
|
61
|
+
cost REAL NOT NULL DEFAULT 0,
|
|
62
|
+
currency TEXT NOT NULL DEFAULT '',
|
|
63
|
+
imported_at TEXT NOT NULL,
|
|
64
|
+
PRIMARY KEY (day, model, key_fp)
|
|
65
|
+
);
|
|
66
|
+
CREATE INDEX IF NOT EXISTS idx_${DEEPSEEK_USAGE_ACTUALS_TABLE}_key_day
|
|
67
|
+
ON ${DEEPSEEK_USAGE_ACTUALS_TABLE} (key_fp, day);
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
function tableExists(db, name) {
|
|
71
|
+
return !!db
|
|
72
|
+
.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`)
|
|
73
|
+
.get(name);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function columnNames(db, table) {
|
|
77
|
+
return new Set(db.prepare(`PRAGMA table_info(${table})`).all().map((column) => column.name));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** What keeps the import from using the table: '' when nothing does. */
|
|
81
|
+
export function deepseekUsageActualsProblem(db) {
|
|
82
|
+
if (!tableExists(db, DEEPSEEK_USAGE_ACTUALS_TABLE)) return `no ${DEEPSEEK_USAGE_ACTUALS_TABLE} table`;
|
|
83
|
+
const present = columnNames(db, DEEPSEEK_USAGE_ACTUALS_TABLE);
|
|
84
|
+
const missing = DEEPSEEK_USAGE_ACTUALS_COLUMNS.filter((name) => !present.has(name));
|
|
85
|
+
if (missing.length) return `${DEEPSEEK_USAGE_ACTUALS_TABLE} lacks ${missing.join(', ')}`;
|
|
86
|
+
return '';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function migrateDeepSeekUsageActuals(db) {
|
|
90
|
+
const existed = tableExists(db, DEEPSEEK_USAGE_ACTUALS_TABLE);
|
|
91
|
+
db.exec(CREATE_SQL);
|
|
92
|
+
return { applied: !existed, tableCreated: !existed };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function defaultDbPath() {
|
|
96
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
97
|
+
return path.resolve(here, '..', 'data', 'copilot.db');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function migrate(dbPath, options = {}) {
|
|
101
|
+
const db = new Database(dbPath, options);
|
|
102
|
+
try {
|
|
103
|
+
return migrateDeepSeekUsageActuals(db);
|
|
104
|
+
} finally {
|
|
105
|
+
db.close();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
110
|
+
if (isMain) {
|
|
111
|
+
const dbPath = process.argv[2] ? path.resolve(process.argv[2]) : defaultDbPath();
|
|
112
|
+
try {
|
|
113
|
+
const result = migrate(dbPath);
|
|
114
|
+
console.log(`Migration 0030 on ${dbPath}`);
|
|
115
|
+
console.log(result.applied
|
|
116
|
+
? ' deepseek usage actuals: table created'
|
|
117
|
+
: ' skipped: nothing to change');
|
|
118
|
+
process.exit(0);
|
|
119
|
+
} catch (err) {
|
|
120
|
+
console.error(`Migration 0030 failed: ${err?.message || err}`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -530,6 +530,59 @@ 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
|
+
/**
|
|
579
|
+
* Imports DeepSeek's usage export: the texts of its amount and cost files.
|
|
580
|
+
* Answers `{ ok, summary }`; a refused export throws with the relay's reason.
|
|
581
|
+
*/
|
|
582
|
+
export async function importDeepSeekUsageExport({ amount, cost }) {
|
|
583
|
+
return settingsRequest('/api/deepseek-usage-import', { amount, cost }, 'Failed to import the DeepSeek usage export');
|
|
584
|
+
}
|
|
585
|
+
|
|
533
586
|
export async function loadWindowsAutostartSetting() {
|
|
534
587
|
return apiFetchOrThrow('/api/settings/windows-autostart');
|
|
535
588
|
}
|