@clinebot/core 0.0.2 → 0.0.4
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 +7 -7
- package/dist/default-tools/definitions.d.ts +1 -1
- package/dist/default-tools/executors/index.d.ts +1 -1
- package/dist/default-tools/index.d.ts +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.node.d.ts +35 -0
- package/dist/index.node.js +469 -47
- package/dist/providers/local-provider-service.d.ts +37 -0
- package/package.json +5 -26
- package/src/default-tools/definitions.ts +1 -1
- package/src/default-tools/executors/index.ts +1 -1
- package/src/default-tools/index.ts +1 -1
- package/src/index.node.ts +223 -0
- package/src/index.ts +9 -0
- package/src/providers/local-provider-service.ts +591 -0
- package/dist/index.browser.d.ts +0 -1
- package/dist/index.browser.js +0 -220
- package/dist/server/index.d.ts +0 -49
- package/dist/server/index.js +0 -693
- package/src/index.browser.ts +0 -1
- package/src/server/index.ts +0 -321
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import type { providers as LlmsProviders } from "@clinebot/llms";
|
|
4
|
+
import { models } from "@clinebot/llms";
|
|
5
|
+
import type {
|
|
6
|
+
RpcAddProviderActionRequest,
|
|
7
|
+
RpcOAuthProviderId,
|
|
8
|
+
RpcProviderCapability,
|
|
9
|
+
RpcProviderListItem,
|
|
10
|
+
RpcProviderModel,
|
|
11
|
+
RpcSaveProviderSettingsActionRequest,
|
|
12
|
+
} from "@clinebot/shared";
|
|
13
|
+
import { createOAuthClientCallbacks } from "../auth/client";
|
|
14
|
+
import { loginClineOAuth } from "../auth/cline";
|
|
15
|
+
import { loginOpenAICodex } from "../auth/codex";
|
|
16
|
+
import { loginOcaOAuth } from "../auth/oca";
|
|
17
|
+
import type { ProviderSettingsManager } from "../storage/provider-settings-manager";
|
|
18
|
+
|
|
19
|
+
type StoredModelsFile = {
|
|
20
|
+
version: 1;
|
|
21
|
+
providers: Record<
|
|
22
|
+
string,
|
|
23
|
+
{
|
|
24
|
+
provider: {
|
|
25
|
+
name: string;
|
|
26
|
+
baseUrl: string;
|
|
27
|
+
defaultModelId?: string;
|
|
28
|
+
capabilities?: RpcProviderCapability[];
|
|
29
|
+
modelsSourceUrl?: string;
|
|
30
|
+
};
|
|
31
|
+
models: Record<
|
|
32
|
+
string,
|
|
33
|
+
{
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
supportsVision?: boolean;
|
|
37
|
+
supportsAttachments?: boolean;
|
|
38
|
+
}
|
|
39
|
+
>;
|
|
40
|
+
}
|
|
41
|
+
>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function resolveVisibleApiKey(settings: {
|
|
45
|
+
apiKey?: string;
|
|
46
|
+
auth?: {
|
|
47
|
+
apiKey?: string;
|
|
48
|
+
};
|
|
49
|
+
}): string | undefined {
|
|
50
|
+
return settings.apiKey ?? settings.auth?.apiKey;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function hasOAuthAccessToken(settings: {
|
|
54
|
+
auth?: {
|
|
55
|
+
accessToken?: string;
|
|
56
|
+
};
|
|
57
|
+
}): boolean {
|
|
58
|
+
return (settings.auth?.accessToken?.trim() ?? "").length > 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function titleCaseFromId(id: string): string {
|
|
62
|
+
return id
|
|
63
|
+
.split(/[-_]/)
|
|
64
|
+
.filter(Boolean)
|
|
65
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
66
|
+
.join(" ");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function createLetter(name: string): string {
|
|
70
|
+
const parts = name
|
|
71
|
+
.split(/\s+/)
|
|
72
|
+
.map((part) => part.trim())
|
|
73
|
+
.filter(Boolean);
|
|
74
|
+
if (parts.length === 0) {
|
|
75
|
+
return "?";
|
|
76
|
+
}
|
|
77
|
+
if (parts.length === 1) {
|
|
78
|
+
return parts[0].slice(0, 2).toUpperCase();
|
|
79
|
+
}
|
|
80
|
+
return `${parts[0][0]}${parts[1][0]}`.toUpperCase();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function stableColor(id: string): string {
|
|
84
|
+
const palette = [
|
|
85
|
+
"#c4956a",
|
|
86
|
+
"#6b8aad",
|
|
87
|
+
"#e8963a",
|
|
88
|
+
"#5b9bd5",
|
|
89
|
+
"#6bbd7b",
|
|
90
|
+
"#9b7dd4",
|
|
91
|
+
"#d07f68",
|
|
92
|
+
"#57a6a1",
|
|
93
|
+
];
|
|
94
|
+
let hash = 0;
|
|
95
|
+
for (const ch of id) {
|
|
96
|
+
hash = (hash * 31 + ch.charCodeAt(0)) >>> 0;
|
|
97
|
+
}
|
|
98
|
+
return palette[hash % palette.length];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function resolveModelsRegistryPath(manager: ProviderSettingsManager): string {
|
|
102
|
+
return join(dirname(manager.getFilePath()), "models.json");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function emptyModelsFile(): StoredModelsFile {
|
|
106
|
+
return { version: 1, providers: {} };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function readModelsFile(filePath: string): Promise<StoredModelsFile> {
|
|
110
|
+
try {
|
|
111
|
+
const raw = await readFile(filePath, "utf8");
|
|
112
|
+
const parsed = JSON.parse(raw) as Partial<StoredModelsFile>;
|
|
113
|
+
if (
|
|
114
|
+
parsed &&
|
|
115
|
+
parsed.version === 1 &&
|
|
116
|
+
parsed.providers &&
|
|
117
|
+
typeof parsed.providers === "object"
|
|
118
|
+
) {
|
|
119
|
+
return { version: 1, providers: parsed.providers };
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
// Invalid or missing files fall back to an empty registry.
|
|
123
|
+
}
|
|
124
|
+
return emptyModelsFile();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function writeModelsFile(
|
|
128
|
+
filePath: string,
|
|
129
|
+
state: StoredModelsFile,
|
|
130
|
+
): Promise<void> {
|
|
131
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
132
|
+
await writeFile(filePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function toProviderCapabilities(
|
|
136
|
+
capabilities: RpcProviderCapability[] | undefined,
|
|
137
|
+
): Array<"reasoning" | "prompt-cache" | "tools"> | undefined {
|
|
138
|
+
if (!capabilities || capabilities.length === 0) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
const next = new Set<"reasoning" | "prompt-cache" | "tools">();
|
|
142
|
+
if (capabilities.includes("reasoning")) {
|
|
143
|
+
next.add("reasoning");
|
|
144
|
+
}
|
|
145
|
+
if (capabilities.includes("prompt-cache")) {
|
|
146
|
+
next.add("prompt-cache");
|
|
147
|
+
}
|
|
148
|
+
if (capabilities.includes("tools")) {
|
|
149
|
+
next.add("tools");
|
|
150
|
+
}
|
|
151
|
+
return next.size > 0 ? [...next] : undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function toModelCapabilities(
|
|
155
|
+
capabilities: RpcProviderCapability[] | undefined,
|
|
156
|
+
): Array<
|
|
157
|
+
"streaming" | "tools" | "reasoning" | "prompt-cache" | "images" | "files"
|
|
158
|
+
> {
|
|
159
|
+
const next = new Set<
|
|
160
|
+
"streaming" | "tools" | "reasoning" | "prompt-cache" | "images" | "files"
|
|
161
|
+
>();
|
|
162
|
+
if (!capabilities || capabilities.length === 0) {
|
|
163
|
+
return [...next];
|
|
164
|
+
}
|
|
165
|
+
if (capabilities.includes("streaming")) next.add("streaming");
|
|
166
|
+
if (capabilities.includes("tools")) next.add("tools");
|
|
167
|
+
if (capabilities.includes("reasoning")) next.add("reasoning");
|
|
168
|
+
if (capabilities.includes("prompt-cache")) next.add("prompt-cache");
|
|
169
|
+
if (capabilities.includes("vision")) {
|
|
170
|
+
next.add("images");
|
|
171
|
+
next.add("files");
|
|
172
|
+
}
|
|
173
|
+
return [...next];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function registerCustomProvider(
|
|
177
|
+
providerId: string,
|
|
178
|
+
entry: StoredModelsFile["providers"][string],
|
|
179
|
+
): void {
|
|
180
|
+
const modelCapabilities = toModelCapabilities(entry.provider.capabilities);
|
|
181
|
+
const modelEntries = Object.values(entry.models)
|
|
182
|
+
.map((model) => model.id.trim())
|
|
183
|
+
.filter((modelId) => modelId.length > 0);
|
|
184
|
+
const defaultModelId =
|
|
185
|
+
entry.provider.defaultModelId?.trim() || modelEntries[0] || "default";
|
|
186
|
+
const normalizedModels = Object.fromEntries(
|
|
187
|
+
modelEntries.map((modelId) => [
|
|
188
|
+
modelId,
|
|
189
|
+
{
|
|
190
|
+
id: modelId,
|
|
191
|
+
name: entry.models[modelId]?.name ?? modelId,
|
|
192
|
+
capabilities:
|
|
193
|
+
modelCapabilities.length > 0 ? modelCapabilities : undefined,
|
|
194
|
+
status: "active" as const,
|
|
195
|
+
},
|
|
196
|
+
]),
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
models.registerProvider({
|
|
200
|
+
provider: {
|
|
201
|
+
id: providerId,
|
|
202
|
+
name: entry.provider.name.trim() || titleCaseFromId(providerId),
|
|
203
|
+
protocol: "openai-chat",
|
|
204
|
+
baseUrl: entry.provider.baseUrl,
|
|
205
|
+
defaultModelId,
|
|
206
|
+
capabilities: toProviderCapabilities(entry.provider.capabilities),
|
|
207
|
+
},
|
|
208
|
+
models: normalizedModels,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let customProvidersLoaded = false;
|
|
213
|
+
|
|
214
|
+
export async function ensureCustomProvidersLoaded(
|
|
215
|
+
manager: ProviderSettingsManager,
|
|
216
|
+
): Promise<void> {
|
|
217
|
+
if (customProvidersLoaded) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const modelsPath = resolveModelsRegistryPath(manager);
|
|
221
|
+
const state = await readModelsFile(modelsPath);
|
|
222
|
+
for (const [providerId, entry] of Object.entries(state.providers)) {
|
|
223
|
+
registerCustomProvider(providerId, entry);
|
|
224
|
+
}
|
|
225
|
+
customProvidersLoaded = true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function parseModelIdList(input: unknown): string[] {
|
|
229
|
+
if (Array.isArray(input)) {
|
|
230
|
+
return input
|
|
231
|
+
.map((item) => {
|
|
232
|
+
if (typeof item === "string") return item.trim();
|
|
233
|
+
if (item && typeof item === "object" && "id" in item) {
|
|
234
|
+
const id = (item as { id?: unknown }).id;
|
|
235
|
+
return typeof id === "string" ? id.trim() : "";
|
|
236
|
+
}
|
|
237
|
+
return "";
|
|
238
|
+
})
|
|
239
|
+
.filter((id) => id.length > 0);
|
|
240
|
+
}
|
|
241
|
+
return [];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function extractModelIdsFromPayload(
|
|
245
|
+
payload: unknown,
|
|
246
|
+
providerId: string,
|
|
247
|
+
): string[] {
|
|
248
|
+
const rootArray = parseModelIdList(payload);
|
|
249
|
+
if (rootArray.length > 0) return rootArray;
|
|
250
|
+
if (!payload || typeof payload !== "object") return [];
|
|
251
|
+
const data = payload as {
|
|
252
|
+
data?: unknown;
|
|
253
|
+
models?: unknown;
|
|
254
|
+
providers?: Record<string, unknown>;
|
|
255
|
+
};
|
|
256
|
+
const direct = parseModelIdList(data.data ?? data.models);
|
|
257
|
+
if (direct.length > 0) return direct;
|
|
258
|
+
if (
|
|
259
|
+
data.models &&
|
|
260
|
+
typeof data.models === "object" &&
|
|
261
|
+
!Array.isArray(data.models)
|
|
262
|
+
) {
|
|
263
|
+
const modelKeys = Object.keys(data.models).filter(
|
|
264
|
+
(key) => key.trim().length > 0,
|
|
265
|
+
);
|
|
266
|
+
if (modelKeys.length > 0) return modelKeys;
|
|
267
|
+
}
|
|
268
|
+
const providerScoped = data.providers?.[providerId];
|
|
269
|
+
if (providerScoped && typeof providerScoped === "object") {
|
|
270
|
+
const nested = providerScoped as { models?: unknown };
|
|
271
|
+
const nestedList = parseModelIdList(nested.models ?? providerScoped);
|
|
272
|
+
if (nestedList.length > 0) return nestedList;
|
|
273
|
+
}
|
|
274
|
+
return [];
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function fetchModelIdsFromSource(
|
|
278
|
+
url: string,
|
|
279
|
+
providerId: string,
|
|
280
|
+
): Promise<string[]> {
|
|
281
|
+
const response = await fetch(url, { method: "GET" });
|
|
282
|
+
if (!response.ok) {
|
|
283
|
+
throw new Error(
|
|
284
|
+
`failed to fetch models from ${url}: HTTP ${response.status}`,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
const payload = (await response.json()) as unknown;
|
|
288
|
+
return extractModelIdsFromPayload(payload, providerId);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export async function addLocalProvider(
|
|
292
|
+
manager: ProviderSettingsManager,
|
|
293
|
+
request: RpcAddProviderActionRequest,
|
|
294
|
+
): Promise<{
|
|
295
|
+
providerId: string;
|
|
296
|
+
settingsPath: string;
|
|
297
|
+
modelsPath: string;
|
|
298
|
+
modelsCount: number;
|
|
299
|
+
}> {
|
|
300
|
+
const providerId = request.providerId.trim().toLowerCase();
|
|
301
|
+
if (!providerId) throw new Error("providerId is required");
|
|
302
|
+
if (models.hasProvider(providerId)) {
|
|
303
|
+
throw new Error(`provider "${providerId}" already exists`);
|
|
304
|
+
}
|
|
305
|
+
const providerName = request.name.trim();
|
|
306
|
+
if (!providerName) throw new Error("name is required");
|
|
307
|
+
const baseUrl = request.baseUrl.trim();
|
|
308
|
+
if (!baseUrl) throw new Error("baseUrl is required");
|
|
309
|
+
|
|
310
|
+
const typedModels = (request.models ?? [])
|
|
311
|
+
.map((model) => model.trim())
|
|
312
|
+
.filter((model) => model.length > 0);
|
|
313
|
+
const sourceUrl = request.modelsSourceUrl?.trim();
|
|
314
|
+
const fetchedModels = sourceUrl
|
|
315
|
+
? await fetchModelIdsFromSource(sourceUrl, providerId)
|
|
316
|
+
: [];
|
|
317
|
+
const modelIds = [...new Set([...typedModels, ...fetchedModels])];
|
|
318
|
+
if (modelIds.length === 0) {
|
|
319
|
+
throw new Error(
|
|
320
|
+
"at least one model is required (manual or via modelsSourceUrl)",
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const defaultModelId =
|
|
325
|
+
request.defaultModelId?.trim() &&
|
|
326
|
+
modelIds.includes(request.defaultModelId.trim())
|
|
327
|
+
? request.defaultModelId.trim()
|
|
328
|
+
: modelIds[0];
|
|
329
|
+
const capabilities = request.capabilities?.length
|
|
330
|
+
? [...new Set(request.capabilities)]
|
|
331
|
+
: undefined;
|
|
332
|
+
const headerEntries = Object.entries(request.headers ?? {}).filter(
|
|
333
|
+
([key]) => key.trim().length > 0,
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
manager.saveProviderSettings(
|
|
337
|
+
{
|
|
338
|
+
provider: providerId,
|
|
339
|
+
apiKey: request.apiKey?.trim() ? request.apiKey : undefined,
|
|
340
|
+
baseUrl,
|
|
341
|
+
headers:
|
|
342
|
+
headerEntries.length > 0
|
|
343
|
+
? Object.fromEntries(headerEntries)
|
|
344
|
+
: undefined,
|
|
345
|
+
timeout: request.timeoutMs,
|
|
346
|
+
model: defaultModelId,
|
|
347
|
+
},
|
|
348
|
+
{ setLastUsed: false },
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
const modelsPath = resolveModelsRegistryPath(manager);
|
|
352
|
+
const modelsState = await readModelsFile(modelsPath);
|
|
353
|
+
const supportsVision = capabilities?.includes("vision") ?? false;
|
|
354
|
+
const supportsAttachments = supportsVision;
|
|
355
|
+
modelsState.providers[providerId] = {
|
|
356
|
+
provider: {
|
|
357
|
+
name: providerName,
|
|
358
|
+
baseUrl,
|
|
359
|
+
defaultModelId,
|
|
360
|
+
capabilities,
|
|
361
|
+
modelsSourceUrl: sourceUrl,
|
|
362
|
+
},
|
|
363
|
+
models: Object.fromEntries(
|
|
364
|
+
modelIds.map((modelId) => [
|
|
365
|
+
modelId,
|
|
366
|
+
{
|
|
367
|
+
id: modelId,
|
|
368
|
+
name: modelId,
|
|
369
|
+
supportsVision,
|
|
370
|
+
supportsAttachments,
|
|
371
|
+
},
|
|
372
|
+
]),
|
|
373
|
+
),
|
|
374
|
+
};
|
|
375
|
+
await writeModelsFile(modelsPath, modelsState);
|
|
376
|
+
registerCustomProvider(providerId, modelsState.providers[providerId]);
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
providerId,
|
|
380
|
+
settingsPath: manager.getFilePath(),
|
|
381
|
+
modelsPath,
|
|
382
|
+
modelsCount: modelIds.length,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export async function listLocalProviders(
|
|
387
|
+
manager: ProviderSettingsManager,
|
|
388
|
+
): Promise<{
|
|
389
|
+
providers: RpcProviderListItem[];
|
|
390
|
+
settingsPath: string;
|
|
391
|
+
}> {
|
|
392
|
+
const state = manager.read();
|
|
393
|
+
const ids = models.getProviderIds().sort((a, b) => a.localeCompare(b));
|
|
394
|
+
const providerItems = await Promise.all(
|
|
395
|
+
ids.map(async (id): Promise<RpcProviderListItem> => {
|
|
396
|
+
const info = await models.getProvider(id);
|
|
397
|
+
const persistedSettings = state.providers[id]?.settings;
|
|
398
|
+
const providerName = info?.name ?? titleCaseFromId(id);
|
|
399
|
+
return {
|
|
400
|
+
id,
|
|
401
|
+
name: providerName,
|
|
402
|
+
models: null,
|
|
403
|
+
color: stableColor(id),
|
|
404
|
+
letter: createLetter(providerName),
|
|
405
|
+
enabled: Boolean(persistedSettings),
|
|
406
|
+
apiKey: persistedSettings
|
|
407
|
+
? resolveVisibleApiKey(persistedSettings)
|
|
408
|
+
: undefined,
|
|
409
|
+
oauthAccessTokenPresent: persistedSettings
|
|
410
|
+
? hasOAuthAccessToken(persistedSettings)
|
|
411
|
+
: undefined,
|
|
412
|
+
baseUrl: persistedSettings?.baseUrl ?? info?.baseUrl,
|
|
413
|
+
defaultModelId: info?.defaultModelId,
|
|
414
|
+
authDescription: "This provider uses API keys for authentication.",
|
|
415
|
+
baseUrlDescription: "The base endpoint to use for provider requests.",
|
|
416
|
+
};
|
|
417
|
+
}),
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
providers: providerItems,
|
|
422
|
+
settingsPath: manager.getFilePath(),
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export async function getLocalProviderModels(
|
|
427
|
+
providerId: string,
|
|
428
|
+
): Promise<{ providerId: string; models: RpcProviderModel[] }> {
|
|
429
|
+
const id = providerId.trim();
|
|
430
|
+
const modelMap = await models.getModelsForProvider(id);
|
|
431
|
+
const items = Object.entries(modelMap)
|
|
432
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
433
|
+
.map(([modelId, info]) => ({
|
|
434
|
+
id: modelId,
|
|
435
|
+
name: info.name ?? modelId,
|
|
436
|
+
supportsAttachments: info.capabilities?.includes("files"),
|
|
437
|
+
supportsVision: info.capabilities?.includes("images"),
|
|
438
|
+
}));
|
|
439
|
+
return {
|
|
440
|
+
providerId: id,
|
|
441
|
+
models: items,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export function saveLocalProviderSettings(
|
|
446
|
+
manager: ProviderSettingsManager,
|
|
447
|
+
request: RpcSaveProviderSettingsActionRequest,
|
|
448
|
+
): { providerId: string; enabled: boolean; settingsPath: string } {
|
|
449
|
+
const providerId = request.providerId.trim();
|
|
450
|
+
const state = manager.read();
|
|
451
|
+
|
|
452
|
+
if (request.enabled === false) {
|
|
453
|
+
delete state.providers[providerId];
|
|
454
|
+
if (state.lastUsedProvider === providerId) {
|
|
455
|
+
delete state.lastUsedProvider;
|
|
456
|
+
}
|
|
457
|
+
manager.write(state);
|
|
458
|
+
return {
|
|
459
|
+
providerId,
|
|
460
|
+
enabled: false,
|
|
461
|
+
settingsPath: manager.getFilePath(),
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const existing = manager.getProviderSettings(providerId);
|
|
466
|
+
const nextSettings: Record<string, unknown> = {
|
|
467
|
+
...(existing ?? {}),
|
|
468
|
+
provider: providerId,
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
const hasApiKeyUpdate =
|
|
472
|
+
Object.hasOwn(request, "apiKey") && typeof request.apiKey === "string";
|
|
473
|
+
if (hasApiKeyUpdate) {
|
|
474
|
+
const apiKey = request.apiKey?.trim() ?? "";
|
|
475
|
+
if (apiKey.length === 0) {
|
|
476
|
+
delete nextSettings.apiKey;
|
|
477
|
+
} else {
|
|
478
|
+
nextSettings.apiKey = request.apiKey;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const hasBaseUrlUpdate =
|
|
483
|
+
Object.hasOwn(request, "baseUrl") && typeof request.baseUrl === "string";
|
|
484
|
+
if (hasBaseUrlUpdate) {
|
|
485
|
+
const baseUrl = request.baseUrl?.trim() ?? "";
|
|
486
|
+
if (baseUrl.length === 0) {
|
|
487
|
+
delete nextSettings.baseUrl;
|
|
488
|
+
} else {
|
|
489
|
+
nextSettings.baseUrl = request.baseUrl;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
manager.saveProviderSettings(nextSettings, { setLastUsed: false });
|
|
494
|
+
return {
|
|
495
|
+
providerId,
|
|
496
|
+
enabled: true,
|
|
497
|
+
settingsPath: manager.getFilePath(),
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export function normalizeOAuthProvider(provider: string): RpcOAuthProviderId {
|
|
502
|
+
const normalized = provider.trim().toLowerCase();
|
|
503
|
+
if (normalized === "codex" || normalized === "openai-codex") {
|
|
504
|
+
return "openai-codex";
|
|
505
|
+
}
|
|
506
|
+
if (normalized === "cline" || normalized === "oca") {
|
|
507
|
+
return normalized;
|
|
508
|
+
}
|
|
509
|
+
throw new Error(
|
|
510
|
+
`provider "${provider}" does not support OAuth login (supported: cline, oca, openai-codex)`,
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function toProviderApiKey(
|
|
515
|
+
providerId: RpcOAuthProviderId,
|
|
516
|
+
credentials: { access: string },
|
|
517
|
+
): string {
|
|
518
|
+
if (providerId === "cline") {
|
|
519
|
+
return `workos:${credentials.access}`;
|
|
520
|
+
}
|
|
521
|
+
return credentials.access;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
export async function loginLocalProvider(
|
|
525
|
+
providerId: RpcOAuthProviderId,
|
|
526
|
+
existing: LlmsProviders.ProviderSettings | undefined,
|
|
527
|
+
openUrl: (url: string) => void,
|
|
528
|
+
): Promise<{
|
|
529
|
+
access: string;
|
|
530
|
+
refresh: string;
|
|
531
|
+
expires: number;
|
|
532
|
+
accountId?: string;
|
|
533
|
+
}> {
|
|
534
|
+
const callbacks = createOAuthClientCallbacks({
|
|
535
|
+
onPrompt: async (prompt) => prompt.defaultValue ?? "",
|
|
536
|
+
openUrl,
|
|
537
|
+
onOpenUrlError: ({ error }) => {
|
|
538
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
539
|
+
},
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
if (providerId === "cline") {
|
|
543
|
+
return loginClineOAuth({
|
|
544
|
+
apiBaseUrl: existing?.baseUrl?.trim() || "https://api.cline.bot",
|
|
545
|
+
callbacks,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
if (providerId === "oca") {
|
|
549
|
+
return loginOcaOAuth({
|
|
550
|
+
mode: existing?.oca?.mode,
|
|
551
|
+
callbacks,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
return loginOpenAICodex(callbacks);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export function saveLocalProviderOAuthCredentials(
|
|
558
|
+
manager: ProviderSettingsManager,
|
|
559
|
+
providerId: RpcOAuthProviderId,
|
|
560
|
+
existing: LlmsProviders.ProviderSettings | undefined,
|
|
561
|
+
credentials: {
|
|
562
|
+
access: string;
|
|
563
|
+
refresh: string;
|
|
564
|
+
expires: number;
|
|
565
|
+
accountId?: string;
|
|
566
|
+
},
|
|
567
|
+
): LlmsProviders.ProviderSettings {
|
|
568
|
+
const auth = {
|
|
569
|
+
...(existing?.auth ?? {}),
|
|
570
|
+
accessToken: toProviderApiKey(providerId, credentials),
|
|
571
|
+
refreshToken: credentials.refresh,
|
|
572
|
+
accountId: credentials.accountId,
|
|
573
|
+
} as LlmsProviders.ProviderSettings["auth"] & { expiresAt?: number };
|
|
574
|
+
auth.expiresAt = credentials.expires;
|
|
575
|
+
const merged: LlmsProviders.ProviderSettings = {
|
|
576
|
+
...(existing ?? {
|
|
577
|
+
provider: providerId as LlmsProviders.ProviderSettings["provider"],
|
|
578
|
+
}),
|
|
579
|
+
provider: providerId as LlmsProviders.ProviderSettings["provider"],
|
|
580
|
+
auth,
|
|
581
|
+
};
|
|
582
|
+
manager.saveProviderSettings(merged, { tokenSource: "oauth" });
|
|
583
|
+
return merged;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export function resolveLocalClineAuthToken(
|
|
587
|
+
settings: LlmsProviders.ProviderSettings | undefined,
|
|
588
|
+
): string | undefined {
|
|
589
|
+
const token = settings?.auth?.accessToken?.trim() || settings?.apiKey?.trim();
|
|
590
|
+
return token && token.length > 0 ? token : undefined;
|
|
591
|
+
}
|
package/dist/index.browser.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./index";
|