super-dsh 0.1.10 → 0.1.11
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 +18 -2
- package/agent-agy/dist/agent.js +19 -1
- package/agent-claude/dist/adapter.js +106 -31
- package/agent-claude/dist/agent.js +177 -22
- package/agent-claude/dist/claude-client.js +84 -43
- package/agent-claude/dist/claude-launch.js +101 -0
- package/agent-claude/dist/claude-native-config.js +169 -0
- package/agent-claude/dist/claude-provider-gateway.js +340 -0
- package/agent-claude/dist/claude-providers.js +391 -0
- package/agent-claude/dist/claude-route.js +152 -0
- package/agent-claude/dist/index.js +214 -37
- package/agent-claude/dist/models.js +14 -0
- package/agent-claude/dist/sdk-runtime.js +320 -0
- package/agent-claude/package.json +6 -4
- package/agent-codex/README.md +18 -6
- package/agent-codex/dist/adapter.js +3 -2
- package/agent-codex/dist/agent.js +41 -7
- package/agent-codex/dist/app-server-client.js +7 -1
- package/agent-codex/dist/app-server-wire.js +10 -22
- package/agent-codex/dist/codex-launch.js +10 -32
- package/agent-codex/dist/codex-native-config.js +9 -3
- package/agent-codex/dist/codex-oauth.js +153 -0
- package/agent-codex/dist/codex-provider-gateway.js +36 -38
- package/agent-codex/dist/codex-providers.js +20 -7
- package/agent-codex/dist/codex-runtime.js +120 -0
- package/agent-codex/dist/index.js +18 -11
- package/agent-codex/package.json +1 -1
- package/agent-codex-sdk/dist/adapter.js +3 -2
- package/agent-codex-sdk/dist/agent.js +35 -1
- package/agent-codex-sdk/dist/codex-native-config.js +9 -3
- package/agent-codex-sdk/dist/codex-oauth.js +153 -0
- package/agent-codex-sdk/dist/codex-provider-gateway.js +36 -38
- package/agent-codex-sdk/dist/codex-providers.js +20 -7
- package/agent-codex-sdk/dist/codex-sdk-launch.js +5 -38
- package/agent-codex-sdk/dist/index.js +18 -11
- package/agent-codex-sdk/dist/sdk-runtime.js +2 -2
- package/agent-hermes/dist/agent.js +46 -2
- package/agent-hermes/dist/hermes-client.js +49 -1
- package/agent-omp/dist/agent.js +17 -4
- package/agent-omp-sdk/dist/agent.d.ts +11 -0
- package/agent-omp-sdk/dist/agent.js +17 -4
- package/agent-pi/dist/agent.js +33 -1
- package/client/index.js +150 -58
- package/package.json +2 -3
- package/superd-hub/dist/client/agent-settings.d.ts +66 -12
- package/superd-hub/dist/client/agent-settings.js +85 -14
- package/superd-hub/dist/client/runtime-settings-api.js +5 -5
- package/superd-hub/dist/world-mount.d.ts +6 -5
- package/superd-hub/dist/world-mount.js +6 -6
- package/superd-hub/lib/client/index.js +150 -58
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claude-provider-gateway.ts — the adapter-owned Remote behind the Hub's Claude
|
|
3
|
+
* settings panel (2026-10-04 multi-provider ruling).
|
|
4
|
+
*
|
|
5
|
+
* Why a Remote and not a hub route: the provider list is ADAPTER config
|
|
6
|
+
* (dev-rules §12), so the adapter owns it and the hub only forwards.
|
|
7
|
+
* `TypertRemoteService` + `@Remote` is the whole mechanism — standard (TC39)
|
|
8
|
+
* method decorators recorded on the class prototype, read back at dispatch time
|
|
9
|
+
* by the gateway's source-claim scan. No codegen, no compiler-injected metadata,
|
|
10
|
+
* which is why a plain `tsc` adapter build can ship one.
|
|
11
|
+
*
|
|
12
|
+
* Wire contract (`superd-hub/src/client/runtime-settings-api.ts` builds each
|
|
13
|
+
* envelope; the hub carrier forwards `envelope.method` verbatim):
|
|
14
|
+
*
|
|
15
|
+
* request POST /hub/api?agent=claude
|
|
16
|
+
* { type:'client-request', rpcId, method:'claudeProviders/<op>',
|
|
17
|
+
* payload: { args: { request?: … } } }
|
|
18
|
+
* response { type:'server-response', rpcId, result:{ ok:true, value:<view> } }
|
|
19
|
+
*
|
|
20
|
+
* There is no `select`: which provider a SESSION runs on is DSH's own
|
|
21
|
+
* per-session `model/selection`, chosen from the harness's model selector. This
|
|
22
|
+
* panel is a list editor — add, edit, remove — plus the reconcile with the
|
|
23
|
+
* user's `settings.json` that {@link syncFromNative} performs.
|
|
24
|
+
*
|
|
25
|
+
* The class is a delegation shell on purpose: every operation is a plain
|
|
26
|
+
* function over (worldHome, claudeHome), so the entire contract — the key
|
|
27
|
+
* privacy rule, the validation, the sync, the view projection — is pinnable
|
|
28
|
+
* under `node --test` with no Cordis Context and no real home.
|
|
29
|
+
*
|
|
30
|
+
* @module agent-claude/claude-provider-gateway
|
|
31
|
+
*/
|
|
32
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
33
|
+
var useValue = arguments.length > 2;
|
|
34
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
35
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
36
|
+
}
|
|
37
|
+
return useValue ? value : void 0;
|
|
38
|
+
};
|
|
39
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
40
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
41
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
42
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
43
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
44
|
+
var _, done = false;
|
|
45
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
46
|
+
var context = {};
|
|
47
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
48
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
49
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
50
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
51
|
+
if (kind === "accessor") {
|
|
52
|
+
if (result === void 0) continue;
|
|
53
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
54
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
55
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
56
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
57
|
+
}
|
|
58
|
+
else if (_ = accept(result)) {
|
|
59
|
+
if (kind === "field") initializers.unshift(_);
|
|
60
|
+
else descriptor[key] = _;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
64
|
+
done = true;
|
|
65
|
+
};
|
|
66
|
+
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
67
|
+
import { isProviderId, providerStorePath, readProviderStore, removeProvider, syncNativeProviders, upsertProvider, writeProviderStore, } from "./claude-providers.js";
|
|
68
|
+
import { readNativeModel, readNativeProviders, } from "./claude-native-config.js";
|
|
69
|
+
import { join } from "node:path";
|
|
70
|
+
function isRecord(value) {
|
|
71
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
72
|
+
}
|
|
73
|
+
function nonEmpty(value) {
|
|
74
|
+
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Whether an entry's key is actually available right now.
|
|
78
|
+
*
|
|
79
|
+
* Claude has no auth-manager shadowing to work around (the codex face's
|
|
80
|
+
* ChatGPT-login wrinkle has no analogue here): both kinds carry the literal
|
|
81
|
+
* secret and reach the child through its own environment
|
|
82
|
+
* (`claude-launch.ts`), so the store IS the whole truth — an empty value is the
|
|
83
|
+
* real "keyed outside Super D" state and nothing else can supply the key.
|
|
84
|
+
*/
|
|
85
|
+
function keyAvailable(entry) {
|
|
86
|
+
return entry.auth.value !== "";
|
|
87
|
+
}
|
|
88
|
+
function project(entry) {
|
|
89
|
+
return {
|
|
90
|
+
id: entry.id,
|
|
91
|
+
label: entry.label,
|
|
92
|
+
baseUrl: entry.baseUrl,
|
|
93
|
+
models: entry.models,
|
|
94
|
+
authKind: entry.auth.kind,
|
|
95
|
+
hasKey: keyAvailable(entry),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/** Turn one parsed native table into the store's import shape. */
|
|
99
|
+
function toImport(table) {
|
|
100
|
+
// The native read emits a table only when a usable endpoint exists, and it
|
|
101
|
+
// always classifies an auth shape by then; the optionals are the TYPE being
|
|
102
|
+
// honest about a hand-edited file, so the fold keeps only tables that
|
|
103
|
+
// actually carry the pair.
|
|
104
|
+
if (table.baseUrl === undefined || table.auth === undefined)
|
|
105
|
+
return undefined;
|
|
106
|
+
return {
|
|
107
|
+
baseUrl: table.baseUrl,
|
|
108
|
+
auth: table.auth,
|
|
109
|
+
models: table.models,
|
|
110
|
+
...(table.aliases === undefined ? {} : { aliases: table.aliases }),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Reconcile the canonical store against the user's `settings.json` env.
|
|
115
|
+
*
|
|
116
|
+
* This is the sync the 2026-10-04 ruling asks for: the store is canonical, the
|
|
117
|
+
* native settings env is a SOURCE, and the two are joined by endpoint URL (the
|
|
118
|
+
* only identity a hand-written settings env reliably carries — it names no
|
|
119
|
+
* provider). It runs at adapter startup and every ten minutes thereafter, and
|
|
120
|
+
* it is idempotent and silent — the store is only rewritten when something
|
|
121
|
+
* actually moved.
|
|
122
|
+
*
|
|
123
|
+
* @param worldHome - the world's DSH home.
|
|
124
|
+
* @param claudeHome - the native claude home (read only).
|
|
125
|
+
* @returns the effective store plus what the sync changed.
|
|
126
|
+
*/
|
|
127
|
+
export function syncFromNative(worldHome, claudeHome) {
|
|
128
|
+
const store = readProviderStore(worldHome);
|
|
129
|
+
const imported = readNativeProviders(claudeHome)
|
|
130
|
+
.map(toImport)
|
|
131
|
+
.filter((value) => value !== undefined);
|
|
132
|
+
const result = syncNativeProviders(store, imported);
|
|
133
|
+
if (result.added.length > 0 || result.updated.length > 0) {
|
|
134
|
+
writeProviderStore(worldHome, result.store);
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
/** The view for one (already loaded) store. */
|
|
139
|
+
function viewOf(worldHome, claudeHome, store, syncedAt, note) {
|
|
140
|
+
// A settings env declares at most one endpoint, so the FIRST table is the
|
|
141
|
+
// one in force. `readNativeModel` is read on its own because a native setup
|
|
142
|
+
// on the default Anthropic endpoint carries a model but no base URL.
|
|
143
|
+
const baseUrl = readNativeProviders(claudeHome)[0]?.baseUrl;
|
|
144
|
+
const model = readNativeModel(claudeHome);
|
|
145
|
+
const native = baseUrl === undefined && model === undefined
|
|
146
|
+
? null
|
|
147
|
+
: {
|
|
148
|
+
...(baseUrl === undefined ? {} : { baseUrl }),
|
|
149
|
+
...(model === undefined ? {} : { model }),
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
providers: store.providers.map(project),
|
|
153
|
+
native,
|
|
154
|
+
paths: {
|
|
155
|
+
store: providerStorePath(worldHome),
|
|
156
|
+
settings: join(claudeHome, "settings.json"),
|
|
157
|
+
},
|
|
158
|
+
...(syncedAt === undefined ? {} : { syncedAt }),
|
|
159
|
+
...(note === undefined ? {} : { note }),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Read the panel state, reconciling with the native `settings.json` env first.
|
|
164
|
+
*
|
|
165
|
+
* Reading never writes the native home; the only write this can perform is to
|
|
166
|
+
* the adapter's own store, and only when the sync found something new.
|
|
167
|
+
*
|
|
168
|
+
* @param worldHome - the world's DSH home.
|
|
169
|
+
* @param claudeHome - the native claude home.
|
|
170
|
+
* @param syncedAt - epoch ms to report as the last reconcile.
|
|
171
|
+
* @returns the panel's state.
|
|
172
|
+
*/
|
|
173
|
+
export function readProvidersView(worldHome, claudeHome, syncedAt) {
|
|
174
|
+
const { store, added, updated } = syncFromNative(worldHome, claudeHome);
|
|
175
|
+
const note = added.length === 0 && updated.length === 0
|
|
176
|
+
? undefined
|
|
177
|
+
: `Imported ${added.length} provider(s) from your Claude settings.json${updated.length === 0 ? "" : ` and refreshed ${updated.join(", ")}`}.`;
|
|
178
|
+
// A read IS the reconcile, so the timestamp is the read's own unless the
|
|
179
|
+
// caller has a better one — otherwise the panel would never have one to show.
|
|
180
|
+
return viewOf(worldHome, claudeHome, store, syncedAt ?? Date.now(), note);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Validate one `claudeProviders/save` request.
|
|
184
|
+
*
|
|
185
|
+
* @param request - the wire payload's `request` argument.
|
|
186
|
+
* @param existing - the entry this save replaces, so an edit that does not
|
|
187
|
+
* re-type the key keeps the stored one.
|
|
188
|
+
* @throws {TypeError} naming the offending field.
|
|
189
|
+
*/
|
|
190
|
+
export function normalizeSaveRequest(request, existing) {
|
|
191
|
+
if (!isRecord(request) || !isRecord(request["provider"])) {
|
|
192
|
+
throw new TypeError("claudeProviders/save: provider must be an object");
|
|
193
|
+
}
|
|
194
|
+
const raw = request["provider"];
|
|
195
|
+
const id = raw["id"];
|
|
196
|
+
if (!isProviderId(id)) {
|
|
197
|
+
throw new TypeError(`claudeProviders/save: id must match /^[a-z0-9][a-z0-9-]*$/ (got ${JSON.stringify(id)})`);
|
|
198
|
+
}
|
|
199
|
+
const label = nonEmpty(raw["label"]);
|
|
200
|
+
if (label === undefined)
|
|
201
|
+
throw new TypeError("claudeProviders/save: label must be a non-empty string");
|
|
202
|
+
const baseUrl = nonEmpty(raw["baseUrl"]);
|
|
203
|
+
if (baseUrl === undefined)
|
|
204
|
+
throw new TypeError("claudeProviders/save: baseUrl must be a non-empty string");
|
|
205
|
+
let parsed;
|
|
206
|
+
try {
|
|
207
|
+
parsed = new URL(baseUrl);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
throw new TypeError(`claudeProviders/save: baseUrl is not a URL (got ${JSON.stringify(baseUrl)})`);
|
|
211
|
+
}
|
|
212
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
213
|
+
throw new TypeError(`claudeProviders/save: baseUrl must be http(s) (got ${parsed.protocol})`);
|
|
214
|
+
}
|
|
215
|
+
const rawModels = raw["models"];
|
|
216
|
+
// An empty list is legal: it is the state a provider folded in from a native
|
|
217
|
+
// settings env that names no alias model arrives in, and the panel must be
|
|
218
|
+
// able to save an edit without inventing models.
|
|
219
|
+
if (!Array.isArray(rawModels)) {
|
|
220
|
+
throw new TypeError("claudeProviders/save: models must be an array");
|
|
221
|
+
}
|
|
222
|
+
const models = rawModels.map((value) => {
|
|
223
|
+
if (!isRecord(value))
|
|
224
|
+
throw new TypeError("claudeProviders/save: every entry of models must be an object");
|
|
225
|
+
const modelId = nonEmpty(value["id"]);
|
|
226
|
+
if (modelId === undefined)
|
|
227
|
+
throw new TypeError("claudeProviders/save: every entry of models needs a non-empty id");
|
|
228
|
+
const displayName = nonEmpty(value["displayName"]);
|
|
229
|
+
const contextWindow = value["contextWindow"];
|
|
230
|
+
return {
|
|
231
|
+
id: modelId,
|
|
232
|
+
...(displayName === undefined ? {} : { displayName }),
|
|
233
|
+
...(typeof contextWindow === "number" && Number.isSafeInteger(contextWindow) && contextWindow > 0
|
|
234
|
+
? { contextWindow }
|
|
235
|
+
: {}),
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
// `aliases` is deliberately NOT a save field: the DEFAULT_*_MODEL mapping is
|
|
239
|
+
// derived by the native fold (spec §2.2), the panel never edits it, and a
|
|
240
|
+
// save of a folded entry drops it — the next reconcile re-derives it from
|
|
241
|
+
// the live settings env for as long as the endpoint still matches.
|
|
242
|
+
const rawAuth = raw["auth"];
|
|
243
|
+
if (!isRecord(rawAuth))
|
|
244
|
+
throw new TypeError("claudeProviders/save: auth must be an object");
|
|
245
|
+
const kind = rawAuth["kind"];
|
|
246
|
+
if (kind !== "authToken" && kind !== "apiKey") {
|
|
247
|
+
throw new TypeError(`claudeProviders/save: auth.kind must be "authToken" or "apiKey" (got ${JSON.stringify(kind)})`);
|
|
248
|
+
}
|
|
249
|
+
const value = rawAuth["value"];
|
|
250
|
+
if (typeof value !== "string")
|
|
251
|
+
throw new TypeError("claudeProviders/save: auth.value must be a string");
|
|
252
|
+
// An empty value on an edit means "keep the stored one" — the panel is
|
|
253
|
+
// write-only for keys, so it cannot echo the existing value back. The keep
|
|
254
|
+
// is per kind: a secret of the other shape is not this provider's key.
|
|
255
|
+
const kept = value === "" && existing?.auth.kind === kind ? existing.auth.value : value;
|
|
256
|
+
const auth = { kind, value: kept };
|
|
257
|
+
return { id, label, baseUrl, auth, models, origin: "user" };
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Insert or replace one provider.
|
|
261
|
+
*
|
|
262
|
+
* Nothing native is written, and nothing is "activated": the entry sits in the
|
|
263
|
+
* list, and the next session that picks one of its models gets a child pinned
|
|
264
|
+
* to it — `claude-launch.ts` builds the provider's environment at spawn.
|
|
265
|
+
*/
|
|
266
|
+
export function saveProvider(worldHome, claudeHome, request, syncedAt) {
|
|
267
|
+
const store = readProviderStore(worldHome);
|
|
268
|
+
const id = isRecord(request) && isRecord(request["provider"]) ? request["provider"]["id"] : undefined;
|
|
269
|
+
const existing = store.providers.find((entry) => entry.id === id);
|
|
270
|
+
const entry = normalizeSaveRequest(request, existing);
|
|
271
|
+
const next = upsertProvider(store, entry);
|
|
272
|
+
writeProviderStore(worldHome, next);
|
|
273
|
+
return viewOf(worldHome, claudeHome, next, syncedAt);
|
|
274
|
+
}
|
|
275
|
+
/** Drop one provider. The native settings are left alone — removing from this list is not unconfiguring Claude Code. */
|
|
276
|
+
export function removeProviderEntry(worldHome, claudeHome, request, syncedAt) {
|
|
277
|
+
const id = isRecord(request) ? nonEmpty(request["id"]) : undefined;
|
|
278
|
+
if (id === undefined)
|
|
279
|
+
throw new TypeError("claudeProviders/remove: id must be a non-empty string");
|
|
280
|
+
const store = readProviderStore(worldHome);
|
|
281
|
+
const next = removeProvider(store, id);
|
|
282
|
+
writeProviderStore(worldHome, next);
|
|
283
|
+
return viewOf(worldHome, claudeHome, next, syncedAt);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* The adapter's provider Remote. One instance per world context.
|
|
287
|
+
*/
|
|
288
|
+
let ClaudeProvidersGateway = (() => {
|
|
289
|
+
let _classSuper = TypertRemoteService;
|
|
290
|
+
let _instanceExtraInitializers = [];
|
|
291
|
+
let _get_decorators;
|
|
292
|
+
let _save_decorators;
|
|
293
|
+
let _remove_decorators;
|
|
294
|
+
return class ClaudeProvidersGateway extends _classSuper {
|
|
295
|
+
static {
|
|
296
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
297
|
+
_get_decorators = [Remote("get")];
|
|
298
|
+
_save_decorators = [Remote("save")];
|
|
299
|
+
_remove_decorators = [Remote("remove")];
|
|
300
|
+
__esDecorate(this, null, _get_decorators, { kind: "method", name: "get", static: false, private: false, access: { has: obj => "get" in obj, get: obj => obj.get }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
301
|
+
__esDecorate(this, null, _save_decorators, { kind: "method", name: "save", static: false, private: false, access: { has: obj => "save" in obj, get: obj => obj.save }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
302
|
+
__esDecorate(this, null, _remove_decorators, { kind: "method", name: "remove", static: false, private: false, access: { has: obj => "remove" in obj, get: obj => obj.remove }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
303
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
304
|
+
}
|
|
305
|
+
worldHome = __runInitializers(this, _instanceExtraInitializers);
|
|
306
|
+
claudeHome;
|
|
307
|
+
onChange;
|
|
308
|
+
/**
|
|
309
|
+
* @param ctx - the world context this service registers on (namespace `claudeProviders`).
|
|
310
|
+
* @param worldHome - the world's DSH home holding adapter state (`<dshHome>/agents/claude`).
|
|
311
|
+
* @param claudeHome - the USER's real claude home (native settings live there), resolved lazily so construction stays zero-IO.
|
|
312
|
+
* @param onChange - notified after any mutation, so the provider's `ctx.llm` routes can be re-registered.
|
|
313
|
+
*/
|
|
314
|
+
constructor(ctx, worldHome, claudeHome, onChange = () => { }) {
|
|
315
|
+
super(ctx, "claudeProviders");
|
|
316
|
+
this.worldHome = worldHome;
|
|
317
|
+
this.claudeHome = claudeHome;
|
|
318
|
+
this.onChange = onChange;
|
|
319
|
+
}
|
|
320
|
+
/** Read the panel state, reconciling the store with the native settings env first. Never writes the native home. */
|
|
321
|
+
async get() {
|
|
322
|
+
const view = readProvidersView(this.worldHome, this.claudeHome());
|
|
323
|
+
this.onChange();
|
|
324
|
+
return view;
|
|
325
|
+
}
|
|
326
|
+
/** Add or edit one provider. */
|
|
327
|
+
async save(request) {
|
|
328
|
+
const view = saveProvider(this.worldHome, this.claudeHome(), request);
|
|
329
|
+
this.onChange();
|
|
330
|
+
return view;
|
|
331
|
+
}
|
|
332
|
+
/** Drop one provider from the list. */
|
|
333
|
+
async remove(request) {
|
|
334
|
+
const view = removeProviderEntry(this.worldHome, this.claudeHome(), request);
|
|
335
|
+
this.onChange();
|
|
336
|
+
return view;
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
})();
|
|
340
|
+
export { ClaudeProvidersGateway };
|