@khalilgharbaoui/opencode-claude-code-plugin 0.1.4 → 0.1.6
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/README.md +49 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.js +289 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,6 +89,53 @@ Variants set the underlying reasoning effort. They're regular opencode model var
|
|
|
89
89
|
|
|
90
90
|
The minimum config is just the `plugin` entry above. Everything below is optional override that goes in a `provider.claude-code` block.
|
|
91
91
|
|
|
92
|
+
### Multiple Claude Code accounts
|
|
93
|
+
|
|
94
|
+
Declare account names once and the plugin expands them into separate opencode providers:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"plugin": ["@khalilgharbaoui/opencode-claude-code-plugin"],
|
|
99
|
+
"provider": {
|
|
100
|
+
"claude-code": {
|
|
101
|
+
"options": {
|
|
102
|
+
"accounts": ["personal", "work"]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`default` is always implicit, so the config above creates:
|
|
110
|
+
|
|
111
|
+
| Provider ID | Display name | Claude config dir |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| `claude-code-default` | `Claude Code (Default)` | normal `~/.claude` |
|
|
114
|
+
| `claude-code-personal` | `Claude Code (Personal)` | `~/.claude-personal` |
|
|
115
|
+
| `claude-code-work` | `Claude Code (Work)` | `~/.claude-work` |
|
|
116
|
+
|
|
117
|
+
Non-default accounts use `CLAUDE_CONFIG_DIR` through a generated wrapper script, so auth/session state stays isolated per account. Shared capability files and folders are symlinked from `~/.claude` into each account dir when present:
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
CLAUDE.md
|
|
121
|
+
settings.json
|
|
122
|
+
skills/
|
|
123
|
+
agents/
|
|
124
|
+
commands/
|
|
125
|
+
plugins/
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Identity/session state is not shared.
|
|
129
|
+
|
|
130
|
+
Login each account once:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
CLAUDE_CONFIG_DIR="$HOME/.claude-personal" claude auth login
|
|
134
|
+
CLAUDE_CONFIG_DIR="$HOME/.claude-work" claude auth login
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The account model IDs are internally suffixed, for example `claude-sonnet-4-6@work`, so long-lived Claude subprocess sessions do not collide across accounts. The generated wrapper strips the suffix before calling `claude --model`.
|
|
138
|
+
|
|
92
139
|
### Options reference
|
|
93
140
|
|
|
94
141
|
```json
|
|
@@ -112,6 +159,7 @@ The minimum config is just the `plugin` entry above. Everything below is optiona
|
|
|
112
159
|
| Option | Type | Default | Description |
|
|
113
160
|
|---|---|---|---|
|
|
114
161
|
| `cliPath` | string | `process.env.CLAUDE_CLI_PATH ?? "claude"` | Path to the `claude` binary. |
|
|
162
|
+
| `accounts` | string[] | – | Optional account list. `default` is implicit. Expands into `Claude Code (Default)`, `Claude Code (Personal)`, etc. |
|
|
115
163
|
| `cwd` | string | `process.cwd()` | Working directory for the spawned CLI. Resolved **lazily per request**, so opencode's project switching works. |
|
|
116
164
|
| `skipPermissions` | boolean | `true` | Pass `--dangerously-skip-permissions` to `claude`. Ignored when `proxyTools` is set — the proxy handles permissions through opencode instead. |
|
|
117
165
|
| `permissionMode` | `acceptEdits` \| `auto` \| `bypassPermissions` \| `default` \| `dontAsk` \| `plan` | – | Forwarded to `claude --permission-mode`. |
|
|
@@ -221,7 +269,7 @@ To replace (rather than augment) bridged MCP with your own:
|
|
|
221
269
|
|
|
222
270
|
Each chat keeps a long-lived `claude` subprocess so the model retains its native context across turns.
|
|
223
271
|
|
|
224
|
-
- **Session key**: `(cwd, model, tool-scope, opencode-session-id)`. The opencode session id comes from the `x-session-affinity` header opencode sets on third-party provider calls. Two chats in the same project on the same model run in **separate** CLI processes — they don't race.
|
|
272
|
+
- **Session key**: `(cwd, model, tool-scope, opencode-session-id)`. The opencode session id comes from the `x-session-affinity` header opencode sets on third-party provider calls. Two chats in the same project on the same model run in **separate** CLI processes — they don't race. In account mode, model IDs are suffixed per account, so account sessions do not collide.
|
|
225
273
|
- **Same chat, multiple turns** → process reused, full Claude context retained.
|
|
226
274
|
- **New chat** → fresh process under the new session key.
|
|
227
275
|
- **Resumed chat after restart** → in-memory state is gone; a new process spawns and the conversation history is summarized and prepended.
|
package/dist/index.d.ts
CHANGED
|
@@ -83,6 +83,9 @@ interface ClaudeCodeConfig {
|
|
|
83
83
|
provider: string;
|
|
84
84
|
cliPath: string;
|
|
85
85
|
cwd?: string;
|
|
86
|
+
account?: string;
|
|
87
|
+
configDir?: string;
|
|
88
|
+
providerID?: string;
|
|
86
89
|
skipPermissions?: boolean;
|
|
87
90
|
permissionMode?: PermissionMode;
|
|
88
91
|
mcpConfig?: string | string[];
|
|
@@ -97,6 +100,10 @@ interface ClaudeCodeProviderSettings {
|
|
|
97
100
|
cliPath?: string;
|
|
98
101
|
cwd?: string;
|
|
99
102
|
name?: string;
|
|
103
|
+
providerID?: string;
|
|
104
|
+
account?: string;
|
|
105
|
+
configDir?: string;
|
|
106
|
+
accounts?: string[];
|
|
100
107
|
skipPermissions?: boolean;
|
|
101
108
|
permissionMode?: PermissionMode;
|
|
102
109
|
mcpConfig?: string | string[];
|
package/dist/index.js
CHANGED
|
@@ -2457,10 +2457,43 @@ function defineModel(opts) {
|
|
|
2457
2457
|
var haikuCost = { input: 1e-6, output: 5e-6, cacheRead: 1e-7, cacheWrite: 125e-8 };
|
|
2458
2458
|
var sonnetCost = { input: 3e-6, output: 15e-6, cacheRead: 3e-7, cacheWrite: 375e-8 };
|
|
2459
2459
|
var opusCost = { input: 15e-6, output: 75e-6, cacheRead: 15e-7, cacheWrite: 1875e-8 };
|
|
2460
|
+
function toConfigModel(model) {
|
|
2461
|
+
const inputMods = [];
|
|
2462
|
+
const outputMods = [];
|
|
2463
|
+
for (const [k, v] of Object.entries(model.capabilities.input)) {
|
|
2464
|
+
if (v) inputMods.push(k);
|
|
2465
|
+
}
|
|
2466
|
+
for (const [k, v] of Object.entries(model.capabilities.output)) {
|
|
2467
|
+
if (v) outputMods.push(k);
|
|
2468
|
+
}
|
|
2469
|
+
return {
|
|
2470
|
+
id: model.api.id,
|
|
2471
|
+
name: model.name,
|
|
2472
|
+
status: model.status,
|
|
2473
|
+
family: model.family ?? "",
|
|
2474
|
+
release_date: model.release_date,
|
|
2475
|
+
temperature: model.capabilities.temperature,
|
|
2476
|
+
reasoning: model.capabilities.reasoning,
|
|
2477
|
+
attachment: model.capabilities.attachment,
|
|
2478
|
+
tool_call: model.capabilities.toolcall,
|
|
2479
|
+
modalities: { input: inputMods, output: outputMods },
|
|
2480
|
+
interleaved: model.capabilities.interleaved,
|
|
2481
|
+
cost: {
|
|
2482
|
+
input: model.cost.input,
|
|
2483
|
+
output: model.cost.output,
|
|
2484
|
+
cache_read: model.cost.cache.read,
|
|
2485
|
+
cache_write: model.cost.cache.write
|
|
2486
|
+
},
|
|
2487
|
+
limit: model.limit,
|
|
2488
|
+
options: model.options,
|
|
2489
|
+
headers: model.headers,
|
|
2490
|
+
variants: model.variants
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2460
2493
|
var defaultModels = {
|
|
2461
2494
|
"claude-haiku-4-5": defineModel({
|
|
2462
2495
|
id: "claude-haiku-4-5",
|
|
2463
|
-
name: "Claude
|
|
2496
|
+
name: "Claude Haiku 4.5",
|
|
2464
2497
|
family: "haiku",
|
|
2465
2498
|
reasoning: false,
|
|
2466
2499
|
context: 2e5,
|
|
@@ -2470,7 +2503,7 @@ var defaultModels = {
|
|
|
2470
2503
|
}),
|
|
2471
2504
|
"claude-sonnet-4-5": defineModel({
|
|
2472
2505
|
id: "claude-sonnet-4-5",
|
|
2473
|
-
name: "Claude
|
|
2506
|
+
name: "Claude Sonnet 4.5",
|
|
2474
2507
|
family: "sonnet",
|
|
2475
2508
|
reasoning: true,
|
|
2476
2509
|
context: 1e6,
|
|
@@ -2480,7 +2513,7 @@ var defaultModels = {
|
|
|
2480
2513
|
}),
|
|
2481
2514
|
"claude-sonnet-4-6": defineModel({
|
|
2482
2515
|
id: "claude-sonnet-4-6",
|
|
2483
|
-
name: "Claude
|
|
2516
|
+
name: "Claude Sonnet 4.6",
|
|
2484
2517
|
family: "sonnet",
|
|
2485
2518
|
reasoning: true,
|
|
2486
2519
|
context: 1e6,
|
|
@@ -2490,7 +2523,7 @@ var defaultModels = {
|
|
|
2490
2523
|
}),
|
|
2491
2524
|
"claude-opus-4-5": defineModel({
|
|
2492
2525
|
id: "claude-opus-4-5",
|
|
2493
|
-
name: "Claude
|
|
2526
|
+
name: "Claude Opus 4.5",
|
|
2494
2527
|
family: "opus",
|
|
2495
2528
|
reasoning: true,
|
|
2496
2529
|
context: 1e6,
|
|
@@ -2500,7 +2533,7 @@ var defaultModels = {
|
|
|
2500
2533
|
}),
|
|
2501
2534
|
"claude-opus-4-6": defineModel({
|
|
2502
2535
|
id: "claude-opus-4-6",
|
|
2503
|
-
name: "Claude
|
|
2536
|
+
name: "Claude Opus 4.6",
|
|
2504
2537
|
family: "opus",
|
|
2505
2538
|
reasoning: true,
|
|
2506
2539
|
context: 1e6,
|
|
@@ -2510,7 +2543,7 @@ var defaultModels = {
|
|
|
2510
2543
|
}),
|
|
2511
2544
|
"claude-opus-4-7": defineModel({
|
|
2512
2545
|
id: "claude-opus-4-7",
|
|
2513
|
-
name: "Claude
|
|
2546
|
+
name: "Claude Opus 4.7",
|
|
2514
2547
|
family: "opus",
|
|
2515
2548
|
reasoning: true,
|
|
2516
2549
|
context: 1e6,
|
|
@@ -2520,16 +2553,161 @@ var defaultModels = {
|
|
|
2520
2553
|
})
|
|
2521
2554
|
};
|
|
2522
2555
|
|
|
2556
|
+
// src/accounts.ts
|
|
2557
|
+
import { chmod, lstat, mkdir, readlink, symlink, writeFile } from "fs/promises";
|
|
2558
|
+
import path3 from "path";
|
|
2559
|
+
var BASE_PROVIDER_ID = "claude-code";
|
|
2560
|
+
var DEFAULT_ACCOUNT = "default";
|
|
2561
|
+
var SHARED_CAPABILITY_ITEMS = [
|
|
2562
|
+
"CLAUDE.md",
|
|
2563
|
+
"settings.json",
|
|
2564
|
+
"skills",
|
|
2565
|
+
"agents",
|
|
2566
|
+
"commands",
|
|
2567
|
+
"plugins"
|
|
2568
|
+
];
|
|
2569
|
+
function normalizeAccountName(account) {
|
|
2570
|
+
return account.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2571
|
+
}
|
|
2572
|
+
function resolveAccounts(value) {
|
|
2573
|
+
if (!Array.isArray(value)) return null;
|
|
2574
|
+
const accounts = value.map((account) => normalizeAccountName(String(account))).filter(Boolean);
|
|
2575
|
+
return Array.from(/* @__PURE__ */ new Set([DEFAULT_ACCOUNT, ...accounts]));
|
|
2576
|
+
}
|
|
2577
|
+
function accountProviderId(account) {
|
|
2578
|
+
return `${BASE_PROVIDER_ID}-${normalizeAccountName(account)}`;
|
|
2579
|
+
}
|
|
2580
|
+
function accountDisplayName(account) {
|
|
2581
|
+
return `Claude Code (${titleizeAccount(account)})`;
|
|
2582
|
+
}
|
|
2583
|
+
function accountModelSuffix(account) {
|
|
2584
|
+
const normalized = normalizeAccountName(account);
|
|
2585
|
+
return normalized === DEFAULT_ACCOUNT ? void 0 : normalized;
|
|
2586
|
+
}
|
|
2587
|
+
function accountConfigDir(account) {
|
|
2588
|
+
const normalized = normalizeAccountName(account);
|
|
2589
|
+
if (!normalized || normalized === DEFAULT_ACCOUNT) return void 0;
|
|
2590
|
+
return `~/.claude-${normalized}`;
|
|
2591
|
+
}
|
|
2592
|
+
function expandHome(value) {
|
|
2593
|
+
const home = process.env.HOME ?? process.env.USERPROFILE;
|
|
2594
|
+
if (value === "~") return home ?? value;
|
|
2595
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
2596
|
+
return home ? path3.join(home, value.slice(2)) : value;
|
|
2597
|
+
}
|
|
2598
|
+
return value;
|
|
2599
|
+
}
|
|
2600
|
+
async function ensureAccountRuntime(account, baseCliPath) {
|
|
2601
|
+
const configDir = accountConfigDir(account);
|
|
2602
|
+
if (!configDir) return { cliPath: baseCliPath };
|
|
2603
|
+
const expandedConfigDir = expandHome(configDir);
|
|
2604
|
+
await mkdir(expandedConfigDir, { recursive: true });
|
|
2605
|
+
try {
|
|
2606
|
+
await ensureSharedCapabilities(expandedConfigDir);
|
|
2607
|
+
} catch (err) {
|
|
2608
|
+
log.warn("failed to symlink shared capabilities; continuing anyway", {
|
|
2609
|
+
account,
|
|
2610
|
+
configDir: expandedConfigDir,
|
|
2611
|
+
error: String(err)
|
|
2612
|
+
});
|
|
2613
|
+
}
|
|
2614
|
+
const cliPath = await writeAccountWrapper(
|
|
2615
|
+
normalizeAccountName(account),
|
|
2616
|
+
baseCliPath,
|
|
2617
|
+
expandedConfigDir
|
|
2618
|
+
);
|
|
2619
|
+
return { cliPath, configDir };
|
|
2620
|
+
}
|
|
2621
|
+
async function ensureSharedCapabilities(targetRoot) {
|
|
2622
|
+
const sourceRoot = expandHome("~/.claude");
|
|
2623
|
+
for (const item of SHARED_CAPABILITY_ITEMS) {
|
|
2624
|
+
await ensureSharedCapabilityItem(sourceRoot, targetRoot, item);
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
async function ensureSharedCapabilityItem(sourceRoot, targetRoot, item) {
|
|
2628
|
+
const source = path3.join(sourceRoot, item);
|
|
2629
|
+
const target = path3.join(targetRoot, item);
|
|
2630
|
+
let sourceStat;
|
|
2631
|
+
try {
|
|
2632
|
+
sourceStat = await lstat(source);
|
|
2633
|
+
} catch {
|
|
2634
|
+
return;
|
|
2635
|
+
}
|
|
2636
|
+
try {
|
|
2637
|
+
const targetStat = await lstat(target);
|
|
2638
|
+
if (targetStat.isSymbolicLink()) {
|
|
2639
|
+
const current = await readlink(target);
|
|
2640
|
+
const resolvedCurrent = path3.resolve(path3.dirname(target), current);
|
|
2641
|
+
const resolvedSource = path3.resolve(source);
|
|
2642
|
+
if (resolvedCurrent === resolvedSource) return;
|
|
2643
|
+
}
|
|
2644
|
+
log.warn("shared Claude capability already exists; leaving untouched", {
|
|
2645
|
+
item,
|
|
2646
|
+
target,
|
|
2647
|
+
source
|
|
2648
|
+
});
|
|
2649
|
+
return;
|
|
2650
|
+
} catch {
|
|
2651
|
+
}
|
|
2652
|
+
const type = sourceStat.isDirectory() ? process.platform === "win32" ? "junction" : "dir" : "file";
|
|
2653
|
+
await symlink(source, target, type);
|
|
2654
|
+
}
|
|
2655
|
+
async function writeAccountWrapper(account, baseCliPath, configDir) {
|
|
2656
|
+
const cacheRoot = path3.join(
|
|
2657
|
+
process.env.XDG_CACHE_HOME ?? expandHome("~/.cache"),
|
|
2658
|
+
"opencode-claude-code-plugin"
|
|
2659
|
+
);
|
|
2660
|
+
const wrapperPath = path3.join(cacheRoot, `claude-${account}`);
|
|
2661
|
+
const suffix = `@${account}`;
|
|
2662
|
+
await mkdir(cacheRoot, { recursive: true });
|
|
2663
|
+
const script = `#!/usr/bin/env bash
|
|
2664
|
+
set -euo pipefail
|
|
2665
|
+
|
|
2666
|
+
args=()
|
|
2667
|
+
while [[ $# -gt 0 ]]; do
|
|
2668
|
+
if [[ "$1" == "--model" && $# -ge 2 ]]; then
|
|
2669
|
+
model="$2"
|
|
2670
|
+
if [[ "$model" == *${shellDoubleQuote(suffix)} ]]; then
|
|
2671
|
+
model="\${model%${shellDoubleQuote(suffix)}}"
|
|
2672
|
+
fi
|
|
2673
|
+
args+=("$1" "$model")
|
|
2674
|
+
shift 2
|
|
2675
|
+
else
|
|
2676
|
+
args+=("$1")
|
|
2677
|
+
shift
|
|
2678
|
+
fi
|
|
2679
|
+
done
|
|
2680
|
+
|
|
2681
|
+
export CLAUDE_CONFIG_DIR=${shellSingleQuote(configDir)}
|
|
2682
|
+
exec ${shellSingleQuote(baseCliPath)} "\${args[@]}"
|
|
2683
|
+
`;
|
|
2684
|
+
await writeFile(wrapperPath, script, "utf8");
|
|
2685
|
+
await chmod(wrapperPath, 493);
|
|
2686
|
+
return wrapperPath;
|
|
2687
|
+
}
|
|
2688
|
+
function shellSingleQuote(value) {
|
|
2689
|
+
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
2690
|
+
}
|
|
2691
|
+
function shellDoubleQuote(value) {
|
|
2692
|
+
return value.replace(/[$`"\\]/g, "\\$&");
|
|
2693
|
+
}
|
|
2694
|
+
function titleizeAccount(account) {
|
|
2695
|
+
return normalizeAccountName(account).split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2523
2698
|
// src/index.ts
|
|
2524
2699
|
function createClaudeCode(settings = {}) {
|
|
2525
2700
|
const cliPath = settings.cliPath ?? process.env.CLAUDE_CLI_PATH ?? "claude";
|
|
2526
|
-
const providerName = settings.name ?? "claude-code";
|
|
2701
|
+
const providerName = settings.providerID ?? settings.name ?? "claude-code";
|
|
2527
2702
|
const proxyTools = settings.proxyTools ?? ["Bash", "Edit", "Write", "WebFetch"];
|
|
2528
2703
|
const createModel = (modelId) => {
|
|
2529
2704
|
return new ClaudeCodeLanguageModel(modelId, {
|
|
2530
2705
|
provider: providerName,
|
|
2531
2706
|
cliPath,
|
|
2532
2707
|
cwd: settings.cwd,
|
|
2708
|
+
account: settings.account,
|
|
2709
|
+
configDir: settings.configDir,
|
|
2710
|
+
providerID: settings.providerID,
|
|
2533
2711
|
skipPermissions: settings.skipPermissions ?? true,
|
|
2534
2712
|
permissionMode: settings.permissionMode,
|
|
2535
2713
|
mcpConfig: settings.mcpConfig,
|
|
@@ -2548,11 +2726,16 @@ function createClaudeCode(settings = {}) {
|
|
|
2548
2726
|
provider.languageModel = createModel;
|
|
2549
2727
|
return provider;
|
|
2550
2728
|
}
|
|
2551
|
-
var PROVIDER_ID2 =
|
|
2729
|
+
var PROVIDER_ID2 = BASE_PROVIDER_ID;
|
|
2552
2730
|
var PACKAGE_NPM = "@khalilgharbaoui/opencode-claude-code-plugin";
|
|
2553
2731
|
function pluginEntrypoint() {
|
|
2554
2732
|
return import.meta.url.startsWith("file:") ? import.meta.url : PACKAGE_NPM;
|
|
2555
2733
|
}
|
|
2734
|
+
function cleanProviderOptions(options = {}) {
|
|
2735
|
+
const result = { ...options };
|
|
2736
|
+
delete result.accounts;
|
|
2737
|
+
return result;
|
|
2738
|
+
}
|
|
2556
2739
|
function mergeDefaultVariants(models = {}) {
|
|
2557
2740
|
const result = { ...models };
|
|
2558
2741
|
for (const [id, model] of Object.entries(defaultModels)) {
|
|
@@ -2569,16 +2752,20 @@ function mergeDefaultVariants(models = {}) {
|
|
|
2569
2752
|
}
|
|
2570
2753
|
return result;
|
|
2571
2754
|
}
|
|
2572
|
-
function defaultModelsForProvider(providerModels) {
|
|
2755
|
+
function defaultModelsForProvider(providerModels, providerID = PROVIDER_ID2, modelSuffix) {
|
|
2573
2756
|
const models = Object.fromEntries(
|
|
2574
2757
|
Object.entries(defaultModels).map(([id, model]) => {
|
|
2575
|
-
const
|
|
2758
|
+
const modelId = modelSuffix ? `${id}@${modelSuffix}` : id;
|
|
2759
|
+
const existing = providerModels[id] ?? providerModels[modelId];
|
|
2576
2760
|
return [
|
|
2577
|
-
|
|
2761
|
+
modelId,
|
|
2578
2762
|
{
|
|
2579
2763
|
...model,
|
|
2764
|
+
id: modelId,
|
|
2765
|
+
providerID,
|
|
2580
2766
|
api: {
|
|
2581
2767
|
...model.api,
|
|
2768
|
+
id: modelId,
|
|
2582
2769
|
npm: existing?.api?.npm ?? model.api.npm,
|
|
2583
2770
|
url: existing?.api?.url ?? model.api.url
|
|
2584
2771
|
}
|
|
@@ -2587,29 +2774,113 @@ function defaultModelsForProvider(providerModels) {
|
|
|
2587
2774
|
})
|
|
2588
2775
|
);
|
|
2589
2776
|
for (const [id, model] of Object.entries(providerModels)) {
|
|
2590
|
-
if (!(id in models))
|
|
2777
|
+
if (!(id in models)) {
|
|
2778
|
+
models[id] = {
|
|
2779
|
+
...model,
|
|
2780
|
+
providerID
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2591
2783
|
}
|
|
2592
2784
|
return models;
|
|
2593
2785
|
}
|
|
2594
|
-
function
|
|
2786
|
+
function configModelsForProvider(providerModels, providerID, modelSuffix) {
|
|
2787
|
+
const models = {};
|
|
2788
|
+
for (const [id, model] of Object.entries(defaultModels)) {
|
|
2789
|
+
const modelId = modelSuffix ? `${id}@${modelSuffix}` : id;
|
|
2790
|
+
const existing = providerModels[id] ?? providerModels[modelId];
|
|
2791
|
+
const full = {
|
|
2792
|
+
...model,
|
|
2793
|
+
id: modelId,
|
|
2794
|
+
providerID,
|
|
2795
|
+
api: {
|
|
2796
|
+
...model.api,
|
|
2797
|
+
id: modelId,
|
|
2798
|
+
npm: existing?.api?.npm ?? model.api.npm,
|
|
2799
|
+
url: existing?.api?.url ?? model.api.url
|
|
2800
|
+
}
|
|
2801
|
+
};
|
|
2802
|
+
models[modelId] = toConfigModel(full);
|
|
2803
|
+
}
|
|
2804
|
+
for (const [id, model] of Object.entries(providerModels)) {
|
|
2805
|
+
if (!(id in models)) {
|
|
2806
|
+
models[id] = toConfigModel({ ...model, providerID });
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
return models;
|
|
2810
|
+
}
|
|
2811
|
+
async function providerConfig(existing, providerID = PROVIDER_ID2, optionDefaults = {}, displayName) {
|
|
2812
|
+
const mergedOptions = {
|
|
2813
|
+
cliPath: "claude",
|
|
2814
|
+
proxyTools: ["Bash", "Edit", "Write", "WebFetch"],
|
|
2815
|
+
...optionDefaults,
|
|
2816
|
+
...cleanProviderOptions(existing?.options),
|
|
2817
|
+
providerID
|
|
2818
|
+
};
|
|
2819
|
+
const cliPath = String(mergedOptions.cliPath ?? "claude");
|
|
2820
|
+
const account = typeof mergedOptions.account === "string" ? mergedOptions.account : void 0;
|
|
2821
|
+
const runtime = account ? await ensureAccountRuntime(account, cliPath) : { cliPath };
|
|
2595
2822
|
return {
|
|
2596
|
-
name: existing?.name,
|
|
2823
|
+
name: displayName ?? existing?.name,
|
|
2597
2824
|
npm: existing?.npm ?? pluginEntrypoint(),
|
|
2598
2825
|
options: {
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
...existing?.options ?? {}
|
|
2826
|
+
...mergedOptions,
|
|
2827
|
+
...runtime
|
|
2602
2828
|
},
|
|
2603
2829
|
models: mergeDefaultVariants(existing?.models)
|
|
2604
2830
|
};
|
|
2605
2831
|
}
|
|
2832
|
+
async function expandAccountProviders(config) {
|
|
2833
|
+
const seed = config.provider?.[PROVIDER_ID2];
|
|
2834
|
+
const accounts = resolveAccounts(seed?.options?.accounts);
|
|
2835
|
+
if (!accounts) return false;
|
|
2836
|
+
config.provider ??= {};
|
|
2837
|
+
const seedOptions = cleanProviderOptions(seed?.options);
|
|
2838
|
+
let expandedCount = 0;
|
|
2839
|
+
for (const account of accounts) {
|
|
2840
|
+
const providerID = accountProviderId(account);
|
|
2841
|
+
try {
|
|
2842
|
+
const existing = config.provider[providerID];
|
|
2843
|
+
const modelSuffix = accountModelSuffix(account);
|
|
2844
|
+
config.provider[providerID] = {
|
|
2845
|
+
...existing,
|
|
2846
|
+
...await providerConfig(
|
|
2847
|
+
existing,
|
|
2848
|
+
providerID,
|
|
2849
|
+
{
|
|
2850
|
+
...seedOptions,
|
|
2851
|
+
account
|
|
2852
|
+
},
|
|
2853
|
+
accountDisplayName(account)
|
|
2854
|
+
),
|
|
2855
|
+
models: configModelsForProvider(
|
|
2856
|
+
existing?.models ?? seed?.models ?? {},
|
|
2857
|
+
providerID,
|
|
2858
|
+
modelSuffix
|
|
2859
|
+
)
|
|
2860
|
+
};
|
|
2861
|
+
expandedCount++;
|
|
2862
|
+
} catch (err) {
|
|
2863
|
+
log.error("failed to expand account provider", {
|
|
2864
|
+
account,
|
|
2865
|
+
providerID,
|
|
2866
|
+
error: String(err)
|
|
2867
|
+
});
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
if (expandedCount > 0) {
|
|
2871
|
+
delete config.provider[PROVIDER_ID2];
|
|
2872
|
+
}
|
|
2873
|
+
return expandedCount > 0;
|
|
2874
|
+
}
|
|
2606
2875
|
var server = async () => ({
|
|
2607
2876
|
config: async (config) => {
|
|
2608
2877
|
config.provider ??= {};
|
|
2878
|
+
const expanded = await expandAccountProviders(config);
|
|
2879
|
+
if (expanded) return;
|
|
2609
2880
|
const existing = config.provider[PROVIDER_ID2];
|
|
2610
2881
|
config.provider[PROVIDER_ID2] = {
|
|
2611
2882
|
...existing,
|
|
2612
|
-
...providerConfig(existing)
|
|
2883
|
+
...await providerConfig(existing)
|
|
2613
2884
|
};
|
|
2614
2885
|
},
|
|
2615
2886
|
provider: {
|