@goodandready/dsh-opencode-provider 0.5.0 → 0.5.2
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/CHANGELOG.md +107 -93
- package/LICENSE +21 -21
- package/NOTICE +15 -15
- package/README.md +307 -269
- package/README.ru.md +208 -87
- package/README.zh.md +184 -87
- package/cordis.patch.yml +11 -11
- package/lib/client.js +223 -99
- package/lib/index.d.ts +31 -1
- package/lib/index.js +188 -32
- package/package.json +122 -125
package/lib/client.js
CHANGED
|
@@ -38,6 +38,10 @@ window.__ModuleLoader__.load({
|
|
|
38
38
|
accountRef: "Credential ref",
|
|
39
39
|
accountSource: "Source",
|
|
40
40
|
addAccount: "Add account",
|
|
41
|
+
importAllAuth: "Import all from auth.json",
|
|
42
|
+
checkAllAccounts: "Check all accounts",
|
|
43
|
+
importedAll: "Imported all auth.json keys; review the accounts and save.",
|
|
44
|
+
accountsImported: "Imported keys",
|
|
41
45
|
removeAccount: "Remove",
|
|
42
46
|
pinAccount: "Pin",
|
|
43
47
|
activeAccount: "Pinned account",
|
|
@@ -107,6 +111,8 @@ window.__ModuleLoader__.load({
|
|
|
107
111
|
heatmapByHour: "By hour (all days)",
|
|
108
112
|
heatmapAllModels: "All models",
|
|
109
113
|
heatmapModel: "Model",
|
|
114
|
+
heatmapByAccount: "Account",
|
|
115
|
+
heatmapAllAccounts: "All accounts",
|
|
110
116
|
analyticsHint: "Aggregated from local session metadata by last activity. A session that switched models is counted under its last model; failures are not exposed by the projection.",
|
|
111
117
|
analyticsEmpty: "No OpenCode sessions in this window.",
|
|
112
118
|
showAll: "Show all",
|
|
@@ -144,6 +150,21 @@ window.__ModuleLoader__.load({
|
|
|
144
150
|
maxOutputTokens: "Output limit (empty = model default, 0 = omit)",
|
|
145
151
|
contextWindow: "Context override",
|
|
146
152
|
showSidebarQuota: "Show the quota card in the sidebar",
|
|
153
|
+
routing: "Routing",
|
|
154
|
+
routingHint: "Recent account/model choices. No prompts or keys are recorded.",
|
|
155
|
+
routingEmpty: "No routing decisions yet.",
|
|
156
|
+
routingRule: "Model rule",
|
|
157
|
+
routingSticky: "Sticky session",
|
|
158
|
+
routingPinned: "Pinned account",
|
|
159
|
+
routingDefault: "Automatic",
|
|
160
|
+
routingRotation: "Rotation",
|
|
161
|
+
routingFallback: "Quota fallback",
|
|
162
|
+
routingFailed: "Failed",
|
|
163
|
+
quotaFallbackModel: "Quota fallback model",
|
|
164
|
+
quotaFallbackModelHint: "If Go rejects with a quota error, retry once on this free model through Console/Zen.",
|
|
165
|
+
stickySession: "Sticky account per conversation",
|
|
166
|
+
stickySessionHint: "Prefer the last successful account for the same conversation; explicit model rules and pinned accounts win.",
|
|
167
|
+
off: "Off",
|
|
147
168
|
pluginUpdate: "Plugin update",
|
|
148
169
|
currentVersion: "Current version",
|
|
149
170
|
latestVersion: "Latest version",
|
|
@@ -195,6 +216,10 @@ window.__ModuleLoader__.load({
|
|
|
195
216
|
accountRef: "凭据引用",
|
|
196
217
|
accountSource: "来源",
|
|
197
218
|
addAccount: "添加账户",
|
|
219
|
+
importAllAuth: "从 auth.json 导入全部",
|
|
220
|
+
checkAllAccounts: "检查全部账户",
|
|
221
|
+
importedAll: "已导入 auth.json 全部密钥;请检查账户并保存。",
|
|
222
|
+
accountsImported: "已导入密钥",
|
|
198
223
|
removeAccount: "移除",
|
|
199
224
|
pinAccount: "固定",
|
|
200
225
|
activeAccount: "固定账户",
|
|
@@ -264,6 +289,8 @@ window.__ModuleLoader__.load({
|
|
|
264
289
|
heatmapByHour: "按小时(全部日期)",
|
|
265
290
|
heatmapAllModels: "全部模型",
|
|
266
291
|
heatmapModel: "模型",
|
|
292
|
+
heatmapByAccount: "账户",
|
|
293
|
+
heatmapAllAccounts: "全部账户",
|
|
267
294
|
analyticsHint: "按最后活动时间聚合本地会话元数据。切换过模型的会话计入最后使用的模型;本地投影不暴露失败。",
|
|
268
295
|
analyticsEmpty: "此窗口内没有 OpenCode 会话。",
|
|
269
296
|
showAll: "显示全部",
|
|
@@ -301,6 +328,21 @@ window.__ModuleLoader__.load({
|
|
|
301
328
|
maxOutputTokens: "输出上限(空 = 模型默认,0 = 省略)",
|
|
302
329
|
contextWindow: "上下文覆盖",
|
|
303
330
|
showSidebarQuota: "在侧边栏显示配额卡片",
|
|
331
|
+
routing: "路由",
|
|
332
|
+
routingHint: "最近使用的账户/模型选择。不会记录提示词或密钥。",
|
|
333
|
+
routingEmpty: "尚无路由决策。",
|
|
334
|
+
routingRule: "模型规则",
|
|
335
|
+
routingSticky: "会话粘性",
|
|
336
|
+
routingPinned: "固定账户",
|
|
337
|
+
routingDefault: "自动",
|
|
338
|
+
routingRotation: "轮换",
|
|
339
|
+
routingFallback: "配额回退",
|
|
340
|
+
routingFailed: "失败",
|
|
341
|
+
quotaFallbackModel: "配额回退模型",
|
|
342
|
+
quotaFallbackModelHint: "Go 返回配额错误时,通过 Console/Zen 用此免费模型重试一次。",
|
|
343
|
+
stickySession: "每个会话固定账户",
|
|
344
|
+
stickySessionHint: "同一会话优先使用上次成功的账户;显式模型规则和固定账户优先。",
|
|
345
|
+
off: "关闭",
|
|
304
346
|
pluginUpdate: "插件更新",
|
|
305
347
|
currentVersion: "当前版本",
|
|
306
348
|
latestVersion: "最新版本",
|
|
@@ -386,10 +428,13 @@ window.__ModuleLoader__.load({
|
|
|
386
428
|
body: JSON.stringify({ ref })
|
|
387
429
|
});
|
|
388
430
|
}
|
|
389
|
-
function importAuth(entry) {
|
|
431
|
+
function importAuth(entry, ref) {
|
|
390
432
|
return call("/dsh-opencode/auth-import", {
|
|
391
433
|
method: "POST",
|
|
392
|
-
body: JSON.stringify({ entry }
|
|
434
|
+
body: JSON.stringify(ref === void 0 ? { entry } : {
|
|
435
|
+
entry,
|
|
436
|
+
ref
|
|
437
|
+
})
|
|
393
438
|
});
|
|
394
439
|
}
|
|
395
440
|
function fetchAnalytics(days = 30) {
|
|
@@ -429,10 +474,12 @@ window.__ModuleLoader__.load({
|
|
|
429
474
|
function RequestHeatmapView(props) {
|
|
430
475
|
const { heatmap, t } = props;
|
|
431
476
|
const [selected, setSelected] = (0, react.useState)("all");
|
|
477
|
+
const [selectedAccount, setSelectedAccount] = (0, react.useState)("all");
|
|
432
478
|
if (heatmap === void 0) return null;
|
|
433
479
|
const models = heatmap.models ?? [];
|
|
434
|
-
const
|
|
435
|
-
const
|
|
480
|
+
const accounts = heatmap.accounts ?? [];
|
|
481
|
+
const selectedModel = selectedAccount === "all" ? models.find((entry) => `${entry.provider}/${entry.model}` === selected) : void 0;
|
|
482
|
+
const view = accounts.find((entry) => entry.account === selectedAccount) ?? selectedModel ?? heatmap;
|
|
436
483
|
const selectedKey = selectedModel === void 0 ? "all" : selected;
|
|
437
484
|
const max = Math.max(0, ...view.grid.flat());
|
|
438
485
|
const maxHours = Math.max(0, ...view.hours);
|
|
@@ -452,10 +499,37 @@ window.__ModuleLoader__.load({
|
|
|
452
499
|
t("requests")
|
|
453
500
|
]
|
|
454
501
|
}),
|
|
502
|
+
accounts.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
503
|
+
className: "oc-select",
|
|
504
|
+
value: selectedAccount,
|
|
505
|
+
title: t("heatmapByAccount"),
|
|
506
|
+
onChange: (event) => {
|
|
507
|
+
setSelectedAccount(event.target.value);
|
|
508
|
+
setSelected("all");
|
|
509
|
+
},
|
|
510
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
511
|
+
value: "all",
|
|
512
|
+
children: [
|
|
513
|
+
t("heatmapAllAccounts"),
|
|
514
|
+
" (",
|
|
515
|
+
heatmap.total,
|
|
516
|
+
")"
|
|
517
|
+
]
|
|
518
|
+
}), accounts.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
519
|
+
value: entry.account,
|
|
520
|
+
children: [
|
|
521
|
+
entry.account,
|
|
522
|
+
" (",
|
|
523
|
+
entry.total,
|
|
524
|
+
")"
|
|
525
|
+
]
|
|
526
|
+
}, entry.account))]
|
|
527
|
+
}) : null,
|
|
455
528
|
models.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
456
529
|
className: "oc-select",
|
|
457
530
|
value: selectedKey,
|
|
458
531
|
title: t("heatmapModel"),
|
|
532
|
+
disabled: selectedAccount !== "all",
|
|
459
533
|
onChange: (event) => setSelected(event.target.value),
|
|
460
534
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
461
535
|
value: "all",
|
|
@@ -907,6 +981,14 @@ window.__ModuleLoader__.load({
|
|
|
907
981
|
windows?.monthly
|
|
908
982
|
].map((window) => window?.status).find((value) => value !== void 0 && value !== "ok");
|
|
909
983
|
}
|
|
984
|
+
function routingLabel(reason, t) {
|
|
985
|
+
if (reason === "rule") return t("routingRule");
|
|
986
|
+
if (reason === "sticky") return t("routingSticky");
|
|
987
|
+
if (reason === "pinned") return t("routingPinned");
|
|
988
|
+
if (reason === "rotation") return t("routingRotation");
|
|
989
|
+
if (reason === "quota-fallback") return t("routingFallback");
|
|
990
|
+
return t("routingDefault");
|
|
991
|
+
}
|
|
910
992
|
function fillClass(value) {
|
|
911
993
|
if (value >= 90) return "oc-footFill oc-footFillBad";
|
|
912
994
|
if (value >= 75) return "oc-footFill oc-footFillWarn";
|
|
@@ -1222,6 +1304,43 @@ window.__ModuleLoader__.load({
|
|
|
1222
1304
|
})
|
|
1223
1305
|
]
|
|
1224
1306
|
}),
|
|
1307
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1308
|
+
className: "oc-card",
|
|
1309
|
+
children: [
|
|
1310
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1311
|
+
className: "oc-card-title",
|
|
1312
|
+
children: t("routing")
|
|
1313
|
+
}),
|
|
1314
|
+
(state?.routing ?? []).length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1315
|
+
className: "oc-hint",
|
|
1316
|
+
children: t("routingEmpty")
|
|
1317
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1318
|
+
className: "oc-table-scroll",
|
|
1319
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
|
|
1320
|
+
className: "oc-table",
|
|
1321
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
1322
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("date") }),
|
|
1323
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("accountLabel") }),
|
|
1324
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("model") }),
|
|
1325
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: t("routing") })
|
|
1326
|
+
] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: (state?.routing ?? []).slice(0, 8).map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
|
|
1327
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: new Date(row.at).toLocaleTimeString() }),
|
|
1328
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.account }),
|
|
1329
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.model }),
|
|
1330
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [
|
|
1331
|
+
routingLabel(row.reason, t),
|
|
1332
|
+
row.fallback ? ` · ${t("routingFallback")}` : "",
|
|
1333
|
+
row.ok ? "" : ` · ${t("routingFailed")}${row.error === void 0 ? "" : ` (${row.error})`}`
|
|
1334
|
+
] })
|
|
1335
|
+
] }, `${row.at}-${row.account}-${row.model}-${row.reason}`)) })]
|
|
1336
|
+
})
|
|
1337
|
+
}),
|
|
1338
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1339
|
+
className: "oc-hint",
|
|
1340
|
+
children: t("routingHint")
|
|
1341
|
+
})
|
|
1342
|
+
]
|
|
1343
|
+
}),
|
|
1225
1344
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1226
1345
|
className: "oc-card",
|
|
1227
1346
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1311,96 +1430,6 @@ window.__ModuleLoader__.load({
|
|
|
1311
1430
|
});
|
|
1312
1431
|
}
|
|
1313
1432
|
//#endregion
|
|
1314
|
-
//#region src/client/provider-card.tsx
|
|
1315
|
-
/**
|
|
1316
|
-
* The provider card on Settings → Models. It is deliberately a narrow key
|
|
1317
|
-
* surface: the full connection, account and model controls stay on the
|
|
1318
|
-
* dedicated Settings → OpenCode page, while first-run key entry is available
|
|
1319
|
-
* right where the Models page lists this provider.
|
|
1320
|
-
*/
|
|
1321
|
-
function OpenCodeProviderCard(props) {
|
|
1322
|
-
const { t } = props;
|
|
1323
|
-
const [state, setState] = (0, react.useState)(null);
|
|
1324
|
-
const [draft, setDraft] = (0, react.useState)("");
|
|
1325
|
-
const [notice, setNotice] = (0, react.useState)(null);
|
|
1326
|
-
const [error, setError] = (0, react.useState)(null);
|
|
1327
|
-
const [saving, setSaving] = (0, react.useState)(false);
|
|
1328
|
-
const load = () => {
|
|
1329
|
-
fetchState().then((next) => {
|
|
1330
|
-
setState(next);
|
|
1331
|
-
setError(null);
|
|
1332
|
-
}).catch((caught) => setError(caught instanceof Error ? caught.message : t("failed")));
|
|
1333
|
-
};
|
|
1334
|
-
(0, react.useEffect)(() => {
|
|
1335
|
-
let cancelled = false;
|
|
1336
|
-
fetchState().then((next) => {
|
|
1337
|
-
if (cancelled) return;
|
|
1338
|
-
setState(next);
|
|
1339
|
-
setError(null);
|
|
1340
|
-
}).catch((caught) => {
|
|
1341
|
-
if (!cancelled) setError(caught instanceof Error ? caught.message : t("failed"));
|
|
1342
|
-
});
|
|
1343
|
-
return () => {
|
|
1344
|
-
cancelled = true;
|
|
1345
|
-
};
|
|
1346
|
-
}, [t]);
|
|
1347
|
-
const configured = state?.key.configured === true;
|
|
1348
|
-
const ref = state?.config.apiKeyEnv ?? "OPENCODE_API_KEY";
|
|
1349
|
-
const save = () => {
|
|
1350
|
-
if (draft.trim() === "") return;
|
|
1351
|
-
setSaving(true);
|
|
1352
|
-
setNotice(null);
|
|
1353
|
-
setError(null);
|
|
1354
|
-
saveKey(ref, draft.trim()).then(() => {
|
|
1355
|
-
setDraft("");
|
|
1356
|
-
setNotice(t("saved"));
|
|
1357
|
-
load();
|
|
1358
|
-
}).catch((caught) => setError(caught instanceof Error ? caught.message : t("failed"))).finally(() => setSaving(false));
|
|
1359
|
-
};
|
|
1360
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1361
|
-
className: "oc-card",
|
|
1362
|
-
children: [
|
|
1363
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h3", {
|
|
1364
|
-
className: "oc-card-title",
|
|
1365
|
-
children: [t("title"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1366
|
-
className: `oc-badge ${configured ? "oc-badge-ok" : "oc-badge-warn"}`,
|
|
1367
|
-
children: configured ? t("keyConfigured") : t("keyMissing")
|
|
1368
|
-
})]
|
|
1369
|
-
}),
|
|
1370
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1371
|
-
className: "oc-hint",
|
|
1372
|
-
children: t("keyHint")
|
|
1373
|
-
}),
|
|
1374
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1375
|
-
className: "oc-row",
|
|
1376
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1377
|
-
className: "oc-input",
|
|
1378
|
-
style: { flex: "1 1 220px" },
|
|
1379
|
-
type: "password",
|
|
1380
|
-
autoComplete: "off",
|
|
1381
|
-
placeholder: t("pasteKey"),
|
|
1382
|
-
value: draft,
|
|
1383
|
-
onChange: (event) => setDraft(event.target.value)
|
|
1384
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1385
|
-
type: "button",
|
|
1386
|
-
className: "oc-button oc-button-primary",
|
|
1387
|
-
disabled: saving || draft.trim() === "",
|
|
1388
|
-
onClick: save,
|
|
1389
|
-
children: saving ? t("saving") : t("saveKey")
|
|
1390
|
-
})]
|
|
1391
|
-
}),
|
|
1392
|
-
notice === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1393
|
-
className: "oc-notice",
|
|
1394
|
-
children: notice
|
|
1395
|
-
}),
|
|
1396
|
-
error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1397
|
-
className: "oc-error",
|
|
1398
|
-
children: error
|
|
1399
|
-
})
|
|
1400
|
-
]
|
|
1401
|
-
});
|
|
1402
|
-
}
|
|
1403
|
-
//#endregion
|
|
1404
1433
|
//#region src/client/settings-draft.ts
|
|
1405
1434
|
function draftOf(state) {
|
|
1406
1435
|
const allIds = state.models.map((model) => model.id);
|
|
@@ -1419,6 +1448,8 @@ window.__ModuleLoader__.load({
|
|
|
1419
1448
|
activeAccount: state.config.activeAccount,
|
|
1420
1449
|
accounts: state.config.accounts.map((account) => ({ ...account })),
|
|
1421
1450
|
modelAccountRules: state.config.modelAccountRules.map((rule) => ({ ...rule })),
|
|
1451
|
+
quotaFallbackModel: state.config.quotaFallbackModel,
|
|
1452
|
+
stickySession: state.config.stickySession,
|
|
1422
1453
|
visibleModels: state.config.visibleModels.length === 0 ? allIds : [...state.config.visibleModels],
|
|
1423
1454
|
filterModelsByPlan: state.config.filterModelsByPlan,
|
|
1424
1455
|
showAll: state.config.visibleModels.length === 0,
|
|
@@ -1454,6 +1485,8 @@ window.__ModuleLoader__.load({
|
|
|
1454
1485
|
pattern: rule.pattern.trim(),
|
|
1455
1486
|
account: rule.account.trim()
|
|
1456
1487
|
})).filter((rule) => rule.pattern !== "" && rule.account !== ""),
|
|
1488
|
+
quotaFallbackModel: draft.quotaFallbackModel.trim(),
|
|
1489
|
+
stickySession: draft.stickySession,
|
|
1457
1490
|
visibleModels: draft.showAll ? [] : draft.visibleModels,
|
|
1458
1491
|
filterModelsByPlan: draft.filterModelsByPlan,
|
|
1459
1492
|
modelTransports: Object.fromEntries(Object.entries(draft.modelTransports).filter(([, value]) => value !== ""))
|
|
@@ -1650,6 +1683,7 @@ window.__ModuleLoader__.load({
|
|
|
1650
1683
|
const [testModel, setTestModel] = (0, react.useState)("deepseek-v4.1-flash");
|
|
1651
1684
|
const [testing, setTesting] = (0, react.useState)(false);
|
|
1652
1685
|
const [testResult, setTestResult] = (0, react.useState)(null);
|
|
1686
|
+
const [importing, setImporting] = (0, react.useState)(false);
|
|
1653
1687
|
const load = (0, react.useCallback)(async () => {
|
|
1654
1688
|
setLoading(true);
|
|
1655
1689
|
try {
|
|
@@ -1700,6 +1734,29 @@ window.__ModuleLoader__.load({
|
|
|
1700
1734
|
setError(caught instanceof Error ? caught.message : t("failed"));
|
|
1701
1735
|
}
|
|
1702
1736
|
};
|
|
1737
|
+
const importAllAuth = async () => {
|
|
1738
|
+
if (state === null || draft === null) return;
|
|
1739
|
+
const refFor = (entry) => `OPENCODE_AUTH_${entry.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase()}`;
|
|
1740
|
+
const missing = state.auth.entries.filter((entry) => !draft.accounts.some((account) => account.label === entry || account.apiKeyEnv === refFor(entry)));
|
|
1741
|
+
if (missing.length === 0) {
|
|
1742
|
+
setNotice(t("importedAll"));
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
setImporting(true);
|
|
1746
|
+
setError(null);
|
|
1747
|
+
try {
|
|
1748
|
+
for (const entry of missing) await importAuth(entry, refFor(entry));
|
|
1749
|
+
update({ accounts: [...draft.accounts, ...missing.map((entry) => ({
|
|
1750
|
+
label: entry,
|
|
1751
|
+
apiKeyEnv: refFor(entry)
|
|
1752
|
+
}))] });
|
|
1753
|
+
setNotice(t("importedAll"));
|
|
1754
|
+
} catch (caught) {
|
|
1755
|
+
setError(caught instanceof Error ? caught.message : t("failed"));
|
|
1756
|
+
} finally {
|
|
1757
|
+
setImporting(false);
|
|
1758
|
+
}
|
|
1759
|
+
};
|
|
1703
1760
|
if (loading && state === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1704
1761
|
className: "oc-hint",
|
|
1705
1762
|
children: t("loading")
|
|
@@ -2216,6 +2273,23 @@ window.__ModuleLoader__.load({
|
|
|
2216
2273
|
}] }),
|
|
2217
2274
|
children: t("addAccount")
|
|
2218
2275
|
}),
|
|
2276
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2277
|
+
type: "button",
|
|
2278
|
+
className: "oc-button",
|
|
2279
|
+
disabled: importing,
|
|
2280
|
+
onClick: () => {
|
|
2281
|
+
importAllAuth();
|
|
2282
|
+
},
|
|
2283
|
+
children: importing ? t("loading") : t("importAllAuth")
|
|
2284
|
+
}),
|
|
2285
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2286
|
+
type: "button",
|
|
2287
|
+
className: "oc-button",
|
|
2288
|
+
onClick: () => {
|
|
2289
|
+
refresh();
|
|
2290
|
+
},
|
|
2291
|
+
children: t("checkAllAccounts")
|
|
2292
|
+
}),
|
|
2219
2293
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2220
2294
|
className: "oc-badge",
|
|
2221
2295
|
children: [
|
|
@@ -2296,6 +2370,61 @@ window.__ModuleLoader__.load({
|
|
|
2296
2370
|
})
|
|
2297
2371
|
]
|
|
2298
2372
|
}),
|
|
2373
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2374
|
+
className: "oc-card",
|
|
2375
|
+
children: [
|
|
2376
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
2377
|
+
className: "oc-card-title",
|
|
2378
|
+
children: t("routing")
|
|
2379
|
+
}),
|
|
2380
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2381
|
+
className: "oc-hint",
|
|
2382
|
+
children: t("routingHint")
|
|
2383
|
+
}),
|
|
2384
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2385
|
+
className: "oc-row",
|
|
2386
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2387
|
+
type: "checkbox",
|
|
2388
|
+
checked: draft.stickySession,
|
|
2389
|
+
onChange: (event) => update({ stickySession: event.target.checked })
|
|
2390
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("stickySession") })]
|
|
2391
|
+
}),
|
|
2392
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2393
|
+
className: "oc-hint",
|
|
2394
|
+
children: t("stickySessionHint")
|
|
2395
|
+
}),
|
|
2396
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2397
|
+
className: "oc-field",
|
|
2398
|
+
children: [
|
|
2399
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2400
|
+
className: "oc-label",
|
|
2401
|
+
children: t("quotaFallbackModel")
|
|
2402
|
+
}),
|
|
2403
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
2404
|
+
className: "oc-select",
|
|
2405
|
+
value: draft.quotaFallbackModel,
|
|
2406
|
+
onChange: (event) => update({ quotaFallbackModel: event.target.value }),
|
|
2407
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2408
|
+
value: "",
|
|
2409
|
+
children: t("off")
|
|
2410
|
+
}), state.models.filter((model) => model.free).map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
2411
|
+
value: model.id,
|
|
2412
|
+
children: [
|
|
2413
|
+
model.name,
|
|
2414
|
+
" (",
|
|
2415
|
+
model.id,
|
|
2416
|
+
")"
|
|
2417
|
+
]
|
|
2418
|
+
}, model.id))]
|
|
2419
|
+
}),
|
|
2420
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2421
|
+
className: "oc-hint",
|
|
2422
|
+
children: t("quotaFallbackModelHint")
|
|
2423
|
+
})
|
|
2424
|
+
]
|
|
2425
|
+
})
|
|
2426
|
+
]
|
|
2427
|
+
}),
|
|
2299
2428
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2300
2429
|
className: "oc-card",
|
|
2301
2430
|
children: [
|
|
@@ -2707,11 +2836,6 @@ window.__ModuleLoader__.load({
|
|
|
2707
2836
|
label: () => t("nav"),
|
|
2708
2837
|
locale: NS
|
|
2709
2838
|
}, OpenCodeSettingsPage));
|
|
2710
|
-
ctx.slots.inject("settings.models.provider-card", () => slots.register({
|
|
2711
|
-
name: "settings.models.provider-card",
|
|
2712
|
-
key: "llm-opencode",
|
|
2713
|
-
locale: NS
|
|
2714
|
-
}, OpenCodeProviderCard));
|
|
2715
2839
|
ctx.slots.inject("main", () => slots.register({
|
|
2716
2840
|
name: "main",
|
|
2717
2841
|
key: "opencode-panel",
|
package/lib/index.d.ts
CHANGED
|
@@ -55,6 +55,10 @@ interface Config {
|
|
|
55
55
|
accounts?: AccountConfig[];
|
|
56
56
|
/** Preferred account per model glob; other accounts remain fallbacks. */
|
|
57
57
|
modelAccountRules?: ModelAccountRule[];
|
|
58
|
+
/** Free model used once when the Go quota rejects a request; empty disables it. */
|
|
59
|
+
quotaFallbackModel?: string;
|
|
60
|
+
/** Prefer the last successful account for a conversation. */
|
|
61
|
+
stickySession?: boolean;
|
|
58
62
|
/** Pinned account id; empty means automatic. */
|
|
59
63
|
activeAccount?: string;
|
|
60
64
|
/** Show the quota card in the sidebar. */
|
|
@@ -82,6 +86,8 @@ interface ResolvedConfig {
|
|
|
82
86
|
maxOutputTokens?: number;
|
|
83
87
|
accounts: readonly AccountConfig[];
|
|
84
88
|
modelAccountRules: readonly ModelAccountRule[];
|
|
89
|
+
quotaFallbackModel: string;
|
|
90
|
+
stickySession: boolean;
|
|
85
91
|
activeAccount: string;
|
|
86
92
|
showSidebarQuota: boolean;
|
|
87
93
|
userAgent: string;
|
|
@@ -111,6 +117,17 @@ declare function createKeyPool(ctx: Context, config: () => ResolvedConfig): KeyP
|
|
|
111
117
|
declare function createKeyResolver(ctx: Context, config: () => ResolvedConfig): KeyResolver;
|
|
112
118
|
//#endregion
|
|
113
119
|
//#region src/adapter.d.ts
|
|
120
|
+
type RoutingReason = 'rule' | 'sticky' | 'pinned' | 'default' | 'rotation' | 'quota-fallback';
|
|
121
|
+
interface RoutingTraceEntry {
|
|
122
|
+
readonly at: number;
|
|
123
|
+
readonly provider: string;
|
|
124
|
+
readonly model: string;
|
|
125
|
+
readonly account: string;
|
|
126
|
+
readonly reason: RoutingReason;
|
|
127
|
+
readonly fallback: boolean;
|
|
128
|
+
readonly ok: boolean;
|
|
129
|
+
readonly error?: string;
|
|
130
|
+
}
|
|
114
131
|
interface OpenCodeAdapterDeps {
|
|
115
132
|
readonly config: () => ResolvedConfig;
|
|
116
133
|
readonly resolveKey: KeyResolver;
|
|
@@ -118,7 +135,7 @@ interface OpenCodeAdapterDeps {
|
|
|
118
135
|
readonly resolveAttachments?: () => AttachmentStore | undefined;
|
|
119
136
|
readonly fetchImpl?: typeof fetch;
|
|
120
137
|
/** Test seam; defaults to the local append-only request log. */
|
|
121
|
-
readonly recordRequest?: (provider: string, model: string, purpose?: string) => void;
|
|
138
|
+
readonly recordRequest?: (provider: string, model: string, purpose?: string, account?: string, reason?: string) => void;
|
|
122
139
|
/** Test seam; defaults to ~/.dsh/storages/dsh-opencode/cooldowns.json. */
|
|
123
140
|
readonly cooldownPath?: string;
|
|
124
141
|
/** Receives a one-time warning when the provider tool limit drops tools. */
|
|
@@ -131,6 +148,8 @@ declare class OpenCodeAdapter extends LlmAdapter {
|
|
|
131
148
|
private cooldownWrites;
|
|
132
149
|
/** key fingerprint → cooldown deadline and the rejection that caused it. */
|
|
133
150
|
private readonly cooldowns;
|
|
151
|
+
private readonly sticky;
|
|
152
|
+
private readonly routing;
|
|
134
153
|
private warnedTools;
|
|
135
154
|
constructor(deps: OpenCodeAdapterDeps);
|
|
136
155
|
providerInfo(provider: string): LlmProviderInfo;
|
|
@@ -138,9 +157,13 @@ declare class OpenCodeAdapter extends LlmAdapter {
|
|
|
138
157
|
listModels(provider: string, opts?: {
|
|
139
158
|
unfiltered?: boolean;
|
|
140
159
|
}): Promise<readonly LlmModelInfo[]>;
|
|
160
|
+
/** Recent routing decisions, newest first; no keys or prompt content. */
|
|
161
|
+
routingTrace(): readonly RoutingTraceEntry[];
|
|
141
162
|
resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo>;
|
|
142
163
|
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
143
164
|
private run;
|
|
165
|
+
private rememberSticky;
|
|
166
|
+
private pushTrace;
|
|
144
167
|
private parse;
|
|
145
168
|
private send;
|
|
146
169
|
private keyId;
|
|
@@ -292,6 +315,12 @@ interface ModelRequestHeatmap {
|
|
|
292
315
|
hours: number[];
|
|
293
316
|
grid: number[][];
|
|
294
317
|
}
|
|
318
|
+
interface AccountRequestHeatmap {
|
|
319
|
+
account: string;
|
|
320
|
+
total: number;
|
|
321
|
+
hours: number[];
|
|
322
|
+
grid: number[][];
|
|
323
|
+
}
|
|
295
324
|
interface RequestHeatmap {
|
|
296
325
|
since: number;
|
|
297
326
|
total: number;
|
|
@@ -302,6 +331,7 @@ interface RequestHeatmap {
|
|
|
302
331
|
firstAt: number | null;
|
|
303
332
|
lastAt: number | null;
|
|
304
333
|
models: ModelRequestHeatmap[];
|
|
334
|
+
accounts: AccountRequestHeatmap[];
|
|
305
335
|
}
|
|
306
336
|
//#endregion
|
|
307
337
|
//#region src/routes.d.ts
|