@clinebot/core 0.0.21 → 0.0.23
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/dist/ClineCore.d.ts +110 -0
- package/dist/ClineCore.d.ts.map +1 -0
- package/dist/account/cline-account-service.d.ts +2 -1
- package/dist/account/cline-account-service.d.ts.map +1 -1
- package/dist/account/index.d.ts +1 -1
- package/dist/account/index.d.ts.map +1 -1
- package/dist/account/rpc.d.ts +3 -1
- package/dist/account/rpc.d.ts.map +1 -1
- package/dist/account/types.d.ts +3 -0
- package/dist/account/types.d.ts.map +1 -1
- package/dist/agents/plugin-loader.d.ts.map +1 -1
- package/dist/agents/plugin-sandbox-bootstrap.js +17 -17
- package/dist/auth/client.d.ts +1 -1
- package/dist/auth/client.d.ts.map +1 -1
- package/dist/auth/cline.d.ts +1 -1
- package/dist/auth/cline.d.ts.map +1 -1
- package/dist/auth/codex.d.ts +1 -1
- package/dist/auth/codex.d.ts.map +1 -1
- package/dist/auth/oca.d.ts +1 -1
- package/dist/auth/oca.d.ts.map +1 -1
- package/dist/auth/utils.d.ts +2 -2
- package/dist/auth/utils.d.ts.map +1 -1
- package/dist/index.d.ts +50 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +949 -0
- package/dist/providers/local-provider-service.d.ts +4 -4
- package/dist/providers/local-provider-service.d.ts.map +1 -1
- package/dist/runtime/runtime-builder.d.ts +1 -0
- package/dist/runtime/runtime-builder.d.ts.map +1 -1
- package/dist/runtime/session-runtime.d.ts +2 -1
- package/dist/runtime/session-runtime.d.ts.map +1 -1
- package/dist/runtime/team-runtime-registry.d.ts +13 -0
- package/dist/runtime/team-runtime-registry.d.ts.map +1 -0
- package/dist/session/default-session-manager.d.ts +2 -2
- package/dist/session/default-session-manager.d.ts.map +1 -1
- package/dist/session/rpc-runtime-ensure.d.ts +53 -0
- package/dist/session/rpc-runtime-ensure.d.ts.map +1 -0
- package/dist/session/session-config-builder.d.ts +2 -3
- package/dist/session/session-config-builder.d.ts.map +1 -1
- package/dist/session/session-host.d.ts +8 -18
- package/dist/session/session-host.d.ts.map +1 -1
- package/dist/session/session-manager.d.ts +1 -1
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manifest.d.ts +1 -2
- package/dist/session/session-manifest.d.ts.map +1 -1
- package/dist/session/unified-session-persistence-service.d.ts +2 -2
- package/dist/session/unified-session-persistence-service.d.ts.map +1 -1
- package/dist/session/utils/helpers.d.ts +1 -1
- package/dist/session/utils/helpers.d.ts.map +1 -1
- package/dist/session/utils/types.d.ts +1 -1
- package/dist/session/utils/types.d.ts.map +1 -1
- package/dist/storage/provider-settings-legacy-migration.d.ts.map +1 -1
- package/dist/telemetry/OpenTelemetryProvider.d.ts.map +1 -1
- package/dist/telemetry/distinct-id.d.ts +2 -0
- package/dist/telemetry/distinct-id.d.ts.map +1 -0
- package/dist/telemetry/{opentelemetry.d.ts → index.d.ts} +1 -1
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +28 -0
- package/dist/tools/constants.d.ts +1 -1
- package/dist/tools/constants.d.ts.map +1 -1
- package/dist/tools/definitions.d.ts +3 -3
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/executors/apply-patch.d.ts +1 -1
- package/dist/tools/executors/apply-patch.d.ts.map +1 -1
- package/dist/tools/executors/bash.d.ts +1 -1
- package/dist/tools/executors/bash.d.ts.map +1 -1
- package/dist/tools/executors/editor.d.ts +1 -1
- package/dist/tools/executors/editor.d.ts.map +1 -1
- package/dist/tools/executors/file-read.d.ts +1 -1
- package/dist/tools/executors/file-read.d.ts.map +1 -1
- package/dist/tools/executors/index.d.ts +14 -14
- package/dist/tools/executors/index.d.ts.map +1 -1
- package/dist/tools/executors/search.d.ts +1 -1
- package/dist/tools/executors/search.d.ts.map +1 -1
- package/dist/tools/executors/web-fetch.d.ts +1 -1
- package/dist/tools/executors/web-fetch.d.ts.map +1 -1
- package/dist/tools/helpers.d.ts +1 -1
- package/dist/tools/helpers.d.ts.map +1 -1
- package/dist/tools/index.d.ts +10 -10
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/model-tool-routing.d.ts +1 -1
- package/dist/tools/model-tool-routing.d.ts.map +1 -1
- package/dist/tools/presets.d.ts +1 -1
- package/dist/tools/presets.d.ts.map +1 -1
- package/dist/types/common.d.ts +17 -8
- package/dist/types/common.d.ts.map +1 -1
- package/dist/types/config.d.ts +4 -3
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/provider-settings.d.ts +1 -1
- package/dist/types/provider-settings.d.ts.map +1 -1
- package/dist/types.d.ts +5 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +44 -38
- package/src/ClineCore.ts +137 -0
- package/src/account/cline-account-service.test.ts +101 -0
- package/src/account/cline-account-service.ts +300 -0
- package/src/account/featurebase-token.test.ts +175 -0
- package/src/account/index.ts +23 -0
- package/src/account/rpc.test.ts +63 -0
- package/src/account/rpc.ts +185 -0
- package/src/account/types.ts +102 -0
- package/src/agents/agent-config-loader.test.ts +236 -0
- package/src/agents/agent-config-loader.ts +108 -0
- package/src/agents/agent-config-parser.ts +198 -0
- package/src/agents/hooks-config-loader.test.ts +20 -0
- package/src/agents/hooks-config-loader.ts +118 -0
- package/src/agents/index.ts +85 -0
- package/src/agents/plugin-config-loader.test.ts +140 -0
- package/src/agents/plugin-config-loader.ts +97 -0
- package/src/agents/plugin-loader.test.ts +210 -0
- package/src/agents/plugin-loader.ts +175 -0
- package/src/agents/plugin-sandbox-bootstrap.ts +448 -0
- package/src/agents/plugin-sandbox.test.ts +296 -0
- package/src/agents/plugin-sandbox.ts +341 -0
- package/src/agents/unified-config-file-watcher.test.ts +196 -0
- package/src/agents/unified-config-file-watcher.ts +483 -0
- package/src/agents/user-instruction-config-loader.test.ts +158 -0
- package/src/agents/user-instruction-config-loader.ts +438 -0
- package/src/auth/client.test.ts +40 -0
- package/src/auth/client.ts +25 -0
- package/src/auth/cline.test.ts +130 -0
- package/src/auth/cline.ts +420 -0
- package/src/auth/codex.test.ts +170 -0
- package/src/auth/codex.ts +491 -0
- package/src/auth/oca.test.ts +215 -0
- package/src/auth/oca.ts +573 -0
- package/src/auth/server.ts +216 -0
- package/src/auth/types.ts +81 -0
- package/src/auth/utils.test.ts +128 -0
- package/src/auth/utils.ts +247 -0
- package/src/chat/chat-schema.ts +82 -0
- package/src/index.ts +479 -0
- package/src/input/file-indexer.d.ts +11 -0
- package/src/input/file-indexer.test.ts +127 -0
- package/src/input/file-indexer.ts +327 -0
- package/src/input/index.ts +7 -0
- package/src/input/mention-enricher.test.ts +85 -0
- package/src/input/mention-enricher.ts +122 -0
- package/src/mcp/config-loader.test.ts +238 -0
- package/src/mcp/config-loader.ts +219 -0
- package/src/mcp/index.ts +26 -0
- package/src/mcp/manager.test.ts +106 -0
- package/src/mcp/manager.ts +262 -0
- package/src/mcp/types.ts +88 -0
- package/src/providers/local-provider-registry.ts +232 -0
- package/src/providers/local-provider-service.test.ts +783 -0
- package/src/providers/local-provider-service.ts +471 -0
- package/src/runtime/commands.test.ts +98 -0
- package/src/runtime/commands.ts +83 -0
- package/src/runtime/hook-file-hooks.test.ts +237 -0
- package/src/runtime/hook-file-hooks.ts +859 -0
- package/src/runtime/index.ts +37 -0
- package/src/runtime/rules.ts +34 -0
- package/src/runtime/runtime-builder.team-persistence.test.ts +245 -0
- package/src/runtime/runtime-builder.test.ts +371 -0
- package/src/runtime/runtime-builder.ts +631 -0
- package/src/runtime/runtime-parity.test.ts +143 -0
- package/src/runtime/sandbox/subprocess-sandbox.ts +231 -0
- package/src/runtime/session-runtime.ts +49 -0
- package/src/runtime/skills.ts +44 -0
- package/src/runtime/team-runtime-registry.ts +46 -0
- package/src/runtime/tool-approval.ts +104 -0
- package/src/runtime/workflows.test.ts +119 -0
- package/src/runtime/workflows.ts +45 -0
- package/src/session/default-session-manager.e2e.test.ts +384 -0
- package/src/session/default-session-manager.test.ts +1931 -0
- package/src/session/default-session-manager.ts +1422 -0
- package/src/session/file-session-service.ts +280 -0
- package/src/session/index.ts +45 -0
- package/src/session/rpc-runtime-ensure.ts +521 -0
- package/src/session/rpc-session-service.ts +107 -0
- package/src/session/rpc-spawn-lease.test.ts +49 -0
- package/src/session/rpc-spawn-lease.ts +122 -0
- package/src/session/runtime-oauth-token-manager.test.ts +137 -0
- package/src/session/runtime-oauth-token-manager.ts +272 -0
- package/src/session/session-agent-events.ts +248 -0
- package/src/session/session-artifacts.ts +106 -0
- package/src/session/session-config-builder.ts +113 -0
- package/src/session/session-graph.ts +92 -0
- package/src/session/session-host.test.ts +89 -0
- package/src/session/session-host.ts +205 -0
- package/src/session/session-manager.ts +69 -0
- package/src/session/session-manifest.ts +29 -0
- package/src/session/session-service.team-persistence.test.ts +48 -0
- package/src/session/session-service.ts +673 -0
- package/src/session/session-team-coordination.ts +229 -0
- package/src/session/session-telemetry.ts +100 -0
- package/src/session/sqlite-rpc-session-backend.ts +303 -0
- package/src/session/unified-session-persistence-service.test.ts +85 -0
- package/src/session/unified-session-persistence-service.ts +994 -0
- package/src/session/utils/helpers.ts +139 -0
- package/src/session/utils/types.ts +57 -0
- package/src/session/utils/usage.ts +32 -0
- package/src/session/workspace-manager.ts +98 -0
- package/src/session/workspace-manifest.ts +100 -0
- package/src/storage/artifact-store.ts +1 -0
- package/src/storage/file-team-store.ts +257 -0
- package/src/storage/index.ts +11 -0
- package/src/storage/provider-settings-legacy-migration.test.ts +424 -0
- package/src/storage/provider-settings-legacy-migration.ts +826 -0
- package/src/storage/provider-settings-manager.test.ts +191 -0
- package/src/storage/provider-settings-manager.ts +152 -0
- package/src/storage/session-store.ts +1 -0
- package/src/storage/sqlite-session-store.ts +275 -0
- package/src/storage/sqlite-team-store.ts +454 -0
- package/src/storage/team-store.ts +40 -0
- package/src/team/index.ts +4 -0
- package/src/team/projections.ts +285 -0
- package/src/telemetry/ITelemetryAdapter.ts +94 -0
- package/src/telemetry/LoggerTelemetryAdapter.test.ts +42 -0
- package/src/telemetry/LoggerTelemetryAdapter.ts +114 -0
- package/src/telemetry/OpenTelemetryAdapter.test.ts +157 -0
- package/src/telemetry/OpenTelemetryAdapter.ts +348 -0
- package/src/telemetry/OpenTelemetryProvider.test.ts +113 -0
- package/src/telemetry/OpenTelemetryProvider.ts +325 -0
- package/src/telemetry/TelemetryService.test.ts +134 -0
- package/src/telemetry/TelemetryService.ts +141 -0
- package/src/telemetry/core-events.ts +400 -0
- package/src/telemetry/distinct-id.test.ts +57 -0
- package/src/telemetry/distinct-id.ts +58 -0
- package/src/telemetry/index.ts +20 -0
- package/src/tools/constants.ts +35 -0
- package/src/tools/definitions.test.ts +704 -0
- package/src/tools/definitions.ts +709 -0
- package/src/tools/executors/apply-patch-parser.ts +520 -0
- package/src/tools/executors/apply-patch.ts +359 -0
- package/src/tools/executors/bash.test.ts +87 -0
- package/src/tools/executors/bash.ts +207 -0
- package/src/tools/executors/editor.test.ts +35 -0
- package/src/tools/executors/editor.ts +219 -0
- package/src/tools/executors/file-read.test.ts +49 -0
- package/src/tools/executors/file-read.ts +110 -0
- package/src/tools/executors/index.ts +87 -0
- package/src/tools/executors/search.ts +278 -0
- package/src/tools/executors/web-fetch.ts +259 -0
- package/src/tools/helpers.ts +130 -0
- package/src/tools/index.ts +169 -0
- package/src/tools/model-tool-routing.test.ts +86 -0
- package/src/tools/model-tool-routing.ts +132 -0
- package/src/tools/presets.test.ts +62 -0
- package/src/tools/presets.ts +168 -0
- package/src/tools/schemas.ts +327 -0
- package/src/tools/types.ts +329 -0
- package/src/types/common.ts +26 -0
- package/src/types/config.ts +86 -0
- package/src/types/events.ts +74 -0
- package/src/types/index.ts +24 -0
- package/src/types/provider-settings.ts +43 -0
- package/src/types/sessions.ts +16 -0
- package/src/types/storage.ts +64 -0
- package/src/types/workspace.ts +7 -0
- package/src/types.ts +132 -0
- package/src/version.ts +3 -0
- package/dist/index.node.d.ts +0 -47
- package/dist/index.node.d.ts.map +0 -1
- package/dist/index.node.js +0 -948
- package/dist/telemetry/opentelemetry.d.ts.map +0 -1
- package/dist/telemetry/opentelemetry.js +0 -27
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import * as LlmsModels from "@clinebot/llms/models";
|
|
2
|
+
import * as LlmsProviders from "@clinebot/llms/providers";
|
|
3
|
+
import type {
|
|
4
|
+
RpcAddProviderActionRequest,
|
|
5
|
+
RpcOAuthProviderId,
|
|
6
|
+
RpcProviderListItem,
|
|
7
|
+
RpcProviderModel,
|
|
8
|
+
RpcSaveProviderSettingsActionRequest,
|
|
9
|
+
} from "@clinebot/shared";
|
|
10
|
+
import { createOAuthClientCallbacks } from "../auth/client";
|
|
11
|
+
import { loginClineOAuth } from "../auth/cline";
|
|
12
|
+
import { loginOpenAICodex } from "../auth/codex";
|
|
13
|
+
import { loginOcaOAuth } from "../auth/oca";
|
|
14
|
+
import type { ProviderSettingsManager } from "../storage/provider-settings-manager";
|
|
15
|
+
import {
|
|
16
|
+
readModelsFile,
|
|
17
|
+
registerCustomProvider,
|
|
18
|
+
resolveModelsRegistryPath,
|
|
19
|
+
toRpcProviderModel,
|
|
20
|
+
writeModelsFile,
|
|
21
|
+
} from "./local-provider-registry";
|
|
22
|
+
|
|
23
|
+
export { ensureCustomProvidersLoaded } from "./local-provider-registry";
|
|
24
|
+
|
|
25
|
+
// --- Small pure helpers ---
|
|
26
|
+
|
|
27
|
+
function resolveVisibleApiKey(settings: {
|
|
28
|
+
apiKey?: string;
|
|
29
|
+
auth?: { apiKey?: string };
|
|
30
|
+
}): string | undefined {
|
|
31
|
+
return settings.apiKey ?? settings.auth?.apiKey;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function hasOAuthAccessToken(settings: {
|
|
35
|
+
auth?: { accessToken?: string };
|
|
36
|
+
}): boolean {
|
|
37
|
+
return (settings.auth?.accessToken?.trim() ?? "").length > 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function titleCaseFromId(id: string): string {
|
|
41
|
+
return id
|
|
42
|
+
.split(/[-_]/)
|
|
43
|
+
.filter(Boolean)
|
|
44
|
+
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
|
|
45
|
+
.join(" ");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createLetter(name: string): string {
|
|
49
|
+
const parts = name.split(/\s+/).filter(Boolean);
|
|
50
|
+
if (parts.length === 0) return "?";
|
|
51
|
+
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
|
|
52
|
+
return `${parts[0][0]}${parts[1][0]}`.toUpperCase();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function stableColor(id: string): string {
|
|
56
|
+
const palette = [
|
|
57
|
+
"#c4956a",
|
|
58
|
+
"#6b8aad",
|
|
59
|
+
"#e8963a",
|
|
60
|
+
"#5b9bd5",
|
|
61
|
+
"#6bbd7b",
|
|
62
|
+
"#9b7dd4",
|
|
63
|
+
"#d07f68",
|
|
64
|
+
"#57a6a1",
|
|
65
|
+
];
|
|
66
|
+
let hash = 0;
|
|
67
|
+
for (const ch of id) hash = (hash * 31 + ch.charCodeAt(0)) >>> 0;
|
|
68
|
+
return palette[hash % palette.length];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function toSortedRpcProviderModels(
|
|
72
|
+
modelMap: Record<string, LlmsProviders.ModelInfo>,
|
|
73
|
+
): RpcProviderModel[] {
|
|
74
|
+
return Object.entries(modelMap)
|
|
75
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
76
|
+
.map(([modelId, info]) => toRpcProviderModel(modelId, info));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// --- Model ID parsing ---
|
|
80
|
+
|
|
81
|
+
function parseModelIdList(input: unknown): string[] {
|
|
82
|
+
if (!Array.isArray(input)) return [];
|
|
83
|
+
return input
|
|
84
|
+
.map((item) => {
|
|
85
|
+
if (typeof item === "string") return item.trim();
|
|
86
|
+
if (item && typeof item === "object" && "id" in item) {
|
|
87
|
+
const id = (item as { id?: unknown }).id;
|
|
88
|
+
return typeof id === "string" ? id.trim() : "";
|
|
89
|
+
}
|
|
90
|
+
return "";
|
|
91
|
+
})
|
|
92
|
+
.filter((id) => id.length > 0);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function extractModelIdsFromPayload(
|
|
96
|
+
payload: unknown,
|
|
97
|
+
providerId: string,
|
|
98
|
+
): string[] {
|
|
99
|
+
const rootArray = parseModelIdList(payload);
|
|
100
|
+
if (rootArray.length > 0) return rootArray;
|
|
101
|
+
if (!payload || typeof payload !== "object") return [];
|
|
102
|
+
|
|
103
|
+
const data = payload as {
|
|
104
|
+
data?: unknown;
|
|
105
|
+
models?: unknown;
|
|
106
|
+
providers?: Record<string, unknown>;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const direct = parseModelIdList(data.data ?? data.models);
|
|
110
|
+
if (direct.length > 0) return direct;
|
|
111
|
+
|
|
112
|
+
if (
|
|
113
|
+
data.models &&
|
|
114
|
+
typeof data.models === "object" &&
|
|
115
|
+
!Array.isArray(data.models)
|
|
116
|
+
) {
|
|
117
|
+
const keys = Object.keys(data.models).filter((k) => k.trim().length > 0);
|
|
118
|
+
if (keys.length > 0) return keys;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const scoped = data.providers?.[providerId];
|
|
122
|
+
if (scoped && typeof scoped === "object") {
|
|
123
|
+
const nested = scoped as { models?: unknown };
|
|
124
|
+
const list = parseModelIdList(nested.models ?? scoped);
|
|
125
|
+
if (list.length > 0) return list;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function fetchModelIdsFromSource(
|
|
132
|
+
url: string,
|
|
133
|
+
providerId: string,
|
|
134
|
+
): Promise<string[]> {
|
|
135
|
+
const response = await fetch(url, { method: "GET" });
|
|
136
|
+
if (!response.ok)
|
|
137
|
+
throw new Error(
|
|
138
|
+
`failed to fetch models from ${url}: HTTP ${response.status}`,
|
|
139
|
+
);
|
|
140
|
+
return extractModelIdsFromPayload(
|
|
141
|
+
(await response.json()) as unknown,
|
|
142
|
+
providerId,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function resolveProviderModelMap(
|
|
147
|
+
providerId: string,
|
|
148
|
+
config?: LlmsProviders.ProviderConfig,
|
|
149
|
+
): Promise<Record<string, LlmsProviders.ModelInfo>> {
|
|
150
|
+
const registeredModels = await LlmsModels.getModelsForProvider(providerId);
|
|
151
|
+
if (!config) {
|
|
152
|
+
return registeredModels;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const resolved = await LlmsProviders.resolveProviderConfig(
|
|
156
|
+
providerId,
|
|
157
|
+
{
|
|
158
|
+
loadPrivateOnAuth: true,
|
|
159
|
+
failOnError: false,
|
|
160
|
+
},
|
|
161
|
+
config,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
return resolved?.knownModels
|
|
165
|
+
? {
|
|
166
|
+
...registeredModels,
|
|
167
|
+
...resolved.knownModels,
|
|
168
|
+
}
|
|
169
|
+
: registeredModels;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// --- Public API ---
|
|
173
|
+
|
|
174
|
+
export async function addLocalProvider(
|
|
175
|
+
manager: ProviderSettingsManager,
|
|
176
|
+
request: Omit<RpcAddProviderActionRequest, "action">,
|
|
177
|
+
): Promise<{
|
|
178
|
+
providerId: string;
|
|
179
|
+
settingsPath: string;
|
|
180
|
+
modelsPath: string;
|
|
181
|
+
modelsCount: number;
|
|
182
|
+
}> {
|
|
183
|
+
const providerId = request.providerId.trim().toLowerCase();
|
|
184
|
+
if (!providerId) throw new Error("providerId is required");
|
|
185
|
+
if (LlmsModels.hasProvider(providerId))
|
|
186
|
+
throw new Error(`provider "${providerId}" already exists`);
|
|
187
|
+
|
|
188
|
+
const providerName = request.name.trim();
|
|
189
|
+
if (!providerName) throw new Error("name is required");
|
|
190
|
+
|
|
191
|
+
const baseUrl = request.baseUrl.trim();
|
|
192
|
+
if (!baseUrl) throw new Error("baseUrl is required");
|
|
193
|
+
|
|
194
|
+
const typedModels = (request.models ?? [])
|
|
195
|
+
.map((m) => m.trim())
|
|
196
|
+
.filter(Boolean);
|
|
197
|
+
const sourceUrl = request.modelsSourceUrl?.trim();
|
|
198
|
+
const fetchedModels = sourceUrl
|
|
199
|
+
? await fetchModelIdsFromSource(sourceUrl, providerId)
|
|
200
|
+
: [];
|
|
201
|
+
const modelIds = [...new Set([...typedModels, ...fetchedModels])];
|
|
202
|
+
if (modelIds.length === 0) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
"at least one model is required (manual or via modelsSourceUrl)",
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const defaultModelId =
|
|
209
|
+
request.defaultModelId?.trim() &&
|
|
210
|
+
modelIds.includes(request.defaultModelId.trim())
|
|
211
|
+
? request.defaultModelId.trim()
|
|
212
|
+
: modelIds[0];
|
|
213
|
+
|
|
214
|
+
const capabilities = request.capabilities?.length
|
|
215
|
+
? [...new Set(request.capabilities)]
|
|
216
|
+
: undefined;
|
|
217
|
+
const headerEntries = Object.entries(request.headers ?? {}).filter(
|
|
218
|
+
([k]) => k.trim().length > 0,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
manager.saveProviderSettings(
|
|
222
|
+
{
|
|
223
|
+
provider: providerId,
|
|
224
|
+
apiKey: request.apiKey?.trim() || undefined,
|
|
225
|
+
baseUrl,
|
|
226
|
+
headers:
|
|
227
|
+
headerEntries.length > 0
|
|
228
|
+
? Object.fromEntries(headerEntries)
|
|
229
|
+
: undefined,
|
|
230
|
+
timeout: request.timeoutMs,
|
|
231
|
+
model: defaultModelId,
|
|
232
|
+
},
|
|
233
|
+
{ setLastUsed: false },
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const modelsPath = resolveModelsRegistryPath(manager);
|
|
237
|
+
const modelsState = await readModelsFile(modelsPath);
|
|
238
|
+
const supportsVision = capabilities?.includes("vision") ?? false;
|
|
239
|
+
const supportsReasoning = capabilities?.includes("reasoning") ?? false;
|
|
240
|
+
|
|
241
|
+
modelsState.providers[providerId] = {
|
|
242
|
+
provider: {
|
|
243
|
+
name: providerName,
|
|
244
|
+
baseUrl,
|
|
245
|
+
defaultModelId,
|
|
246
|
+
capabilities,
|
|
247
|
+
modelsSourceUrl: sourceUrl,
|
|
248
|
+
},
|
|
249
|
+
models: Object.fromEntries(
|
|
250
|
+
modelIds.map((id) => [
|
|
251
|
+
id,
|
|
252
|
+
{
|
|
253
|
+
id,
|
|
254
|
+
name: id,
|
|
255
|
+
supportsVision,
|
|
256
|
+
supportsAttachments: supportsVision,
|
|
257
|
+
supportsReasoning,
|
|
258
|
+
},
|
|
259
|
+
]),
|
|
260
|
+
),
|
|
261
|
+
};
|
|
262
|
+
await writeModelsFile(modelsPath, modelsState);
|
|
263
|
+
registerCustomProvider(providerId, modelsState.providers[providerId]);
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
providerId,
|
|
267
|
+
settingsPath: manager.getFilePath(),
|
|
268
|
+
modelsPath,
|
|
269
|
+
modelsCount: modelIds.length,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export async function listLocalProviders(
|
|
274
|
+
manager: ProviderSettingsManager,
|
|
275
|
+
): Promise<{ providers: RpcProviderListItem[]; settingsPath: string }> {
|
|
276
|
+
const state = manager.read();
|
|
277
|
+
const ids = LlmsModels.getProviderIds().sort((a, b) => a.localeCompare(b));
|
|
278
|
+
|
|
279
|
+
const providers = await Promise.all(
|
|
280
|
+
ids.map(async (id): Promise<RpcProviderListItem> => {
|
|
281
|
+
const [info, registeredModels] = await Promise.all([
|
|
282
|
+
LlmsModels.getProvider(id),
|
|
283
|
+
LlmsModels.getModelsForProvider(id),
|
|
284
|
+
]);
|
|
285
|
+
const modelList = toSortedRpcProviderModels(registeredModels);
|
|
286
|
+
const persistedSettings = state.providers[id]?.settings;
|
|
287
|
+
const name = info?.name ?? titleCaseFromId(id);
|
|
288
|
+
return {
|
|
289
|
+
id,
|
|
290
|
+
name,
|
|
291
|
+
models: modelList.length,
|
|
292
|
+
color: stableColor(id),
|
|
293
|
+
letter: createLetter(name),
|
|
294
|
+
enabled: Boolean(persistedSettings),
|
|
295
|
+
apiKey: persistedSettings
|
|
296
|
+
? resolveVisibleApiKey(persistedSettings)
|
|
297
|
+
: undefined,
|
|
298
|
+
oauthAccessTokenPresent: persistedSettings
|
|
299
|
+
? hasOAuthAccessToken(persistedSettings)
|
|
300
|
+
: undefined,
|
|
301
|
+
baseUrl: persistedSettings?.baseUrl ?? info?.baseUrl,
|
|
302
|
+
defaultModelId: info?.defaultModelId,
|
|
303
|
+
authDescription: "This provider uses API keys for authentication.",
|
|
304
|
+
baseUrlDescription: "The base endpoint to use for provider requests.",
|
|
305
|
+
modelList,
|
|
306
|
+
};
|
|
307
|
+
}),
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
return { providers, settingsPath: manager.getFilePath() };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export async function getLocalProviderModels(
|
|
314
|
+
providerId: string,
|
|
315
|
+
config?: LlmsProviders.ProviderConfig,
|
|
316
|
+
): Promise<{ providerId: string; models: RpcProviderModel[] }> {
|
|
317
|
+
const id = providerId.trim();
|
|
318
|
+
const modelMap = await resolveProviderModelMap(id, config);
|
|
319
|
+
const models = toSortedRpcProviderModels(modelMap);
|
|
320
|
+
return { providerId: id, models };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function saveLocalProviderSettings(
|
|
324
|
+
manager: ProviderSettingsManager,
|
|
325
|
+
request: Omit<RpcSaveProviderSettingsActionRequest, "action">,
|
|
326
|
+
): { providerId: string; enabled: boolean; settingsPath: string } {
|
|
327
|
+
const providerId = request.providerId.trim();
|
|
328
|
+
|
|
329
|
+
if (request.enabled === false) {
|
|
330
|
+
const state = manager.read();
|
|
331
|
+
delete state.providers[providerId];
|
|
332
|
+
if (state.lastUsedProvider === providerId) delete state.lastUsedProvider;
|
|
333
|
+
manager.write(state);
|
|
334
|
+
return { providerId, enabled: false, settingsPath: manager.getFilePath() };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const existing = manager.getProviderSettings(providerId);
|
|
338
|
+
const next: Record<string, unknown> = {
|
|
339
|
+
...(existing ?? {}),
|
|
340
|
+
provider: providerId,
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
// String fields that should be cleared when empty
|
|
344
|
+
for (const key of ["apiKey", "baseUrl", "model", "region"] as const) {
|
|
345
|
+
if (Object.hasOwn(request, key) && typeof request[key] === "string") {
|
|
346
|
+
const val = (request[key] as string).trim();
|
|
347
|
+
if (val.length === 0) delete next[key];
|
|
348
|
+
else next[key] = request[key];
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Scalar passthrough fields
|
|
353
|
+
for (const key of [
|
|
354
|
+
"maxTokens",
|
|
355
|
+
"contextWindow",
|
|
356
|
+
"timeout",
|
|
357
|
+
"apiLine",
|
|
358
|
+
"capabilities",
|
|
359
|
+
] as const) {
|
|
360
|
+
if (Object.hasOwn(request, key)) next[key] = request[key];
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Merged object fields
|
|
364
|
+
for (const key of [
|
|
365
|
+
"auth",
|
|
366
|
+
"headers",
|
|
367
|
+
"reasoning",
|
|
368
|
+
"aws",
|
|
369
|
+
"gcp",
|
|
370
|
+
"azure",
|
|
371
|
+
"sap",
|
|
372
|
+
"oca",
|
|
373
|
+
] as const) {
|
|
374
|
+
if (Object.hasOwn(request, key) && request[key] != null) {
|
|
375
|
+
next[key] = {
|
|
376
|
+
...(typeof next[key] === "object" && next[key] != null
|
|
377
|
+
? (next[key] as object)
|
|
378
|
+
: {}),
|
|
379
|
+
...(request[key] as object),
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
manager.saveProviderSettings(next, { setLastUsed: false });
|
|
385
|
+
return { providerId, enabled: true, settingsPath: manager.getFilePath() };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function normalizeOAuthProvider(provider: string): RpcOAuthProviderId {
|
|
389
|
+
const normalized = provider.trim().toLowerCase();
|
|
390
|
+
if (normalized === "codex" || normalized === "openai-codex")
|
|
391
|
+
return "openai-codex";
|
|
392
|
+
if (normalized === "cline" || normalized === "oca") return normalized;
|
|
393
|
+
throw new Error(
|
|
394
|
+
`provider "${provider}" does not support OAuth login (supported: cline, oca, openai-codex)`,
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function toProviderApiKey(
|
|
399
|
+
providerId: RpcOAuthProviderId,
|
|
400
|
+
credentials: { access: string },
|
|
401
|
+
): string {
|
|
402
|
+
return providerId === "cline"
|
|
403
|
+
? `workos:${credentials.access}`
|
|
404
|
+
: credentials.access;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export async function loginLocalProvider(
|
|
408
|
+
providerId: RpcOAuthProviderId,
|
|
409
|
+
existing: LlmsProviders.ProviderSettings | undefined,
|
|
410
|
+
openUrl: (url: string) => void,
|
|
411
|
+
): Promise<{
|
|
412
|
+
access: string;
|
|
413
|
+
refresh: string;
|
|
414
|
+
expires: number;
|
|
415
|
+
accountId?: string;
|
|
416
|
+
}> {
|
|
417
|
+
const callbacks = createOAuthClientCallbacks({
|
|
418
|
+
onPrompt: async (prompt) => prompt.defaultValue ?? "",
|
|
419
|
+
openUrl,
|
|
420
|
+
onOpenUrlError: ({ error }) => {
|
|
421
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
if (providerId === "cline") {
|
|
426
|
+
return loginClineOAuth({
|
|
427
|
+
apiBaseUrl: existing?.baseUrl?.trim() || "https://api.cline.bot",
|
|
428
|
+
callbacks,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
if (providerId === "oca")
|
|
432
|
+
return loginOcaOAuth({ mode: existing?.oca?.mode, callbacks });
|
|
433
|
+
return loginOpenAICodex(callbacks);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export function saveLocalProviderOAuthCredentials(
|
|
437
|
+
manager: ProviderSettingsManager,
|
|
438
|
+
providerId: RpcOAuthProviderId,
|
|
439
|
+
existing: LlmsProviders.ProviderSettings | undefined,
|
|
440
|
+
credentials: {
|
|
441
|
+
access: string;
|
|
442
|
+
refresh: string;
|
|
443
|
+
expires: number;
|
|
444
|
+
accountId?: string;
|
|
445
|
+
},
|
|
446
|
+
): LlmsProviders.ProviderSettings {
|
|
447
|
+
const auth = {
|
|
448
|
+
...(existing?.auth ?? {}),
|
|
449
|
+
accessToken: toProviderApiKey(providerId, credentials),
|
|
450
|
+
refreshToken: credentials.refresh,
|
|
451
|
+
accountId: credentials.accountId,
|
|
452
|
+
expiresAt: credentials.expires,
|
|
453
|
+
} as LlmsProviders.ProviderSettings["auth"] & { expiresAt?: number };
|
|
454
|
+
|
|
455
|
+
const merged: LlmsProviders.ProviderSettings = {
|
|
456
|
+
...(existing ?? {
|
|
457
|
+
provider: providerId as LlmsProviders.ProviderSettings["provider"],
|
|
458
|
+
}),
|
|
459
|
+
provider: providerId as LlmsProviders.ProviderSettings["provider"],
|
|
460
|
+
auth,
|
|
461
|
+
};
|
|
462
|
+
manager.saveProviderSettings(merged, { tokenSource: "oauth" });
|
|
463
|
+
return merged;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export function resolveLocalClineAuthToken(
|
|
467
|
+
settings: LlmsProviders.ProviderSettings | undefined,
|
|
468
|
+
): string | undefined {
|
|
469
|
+
const token = settings?.auth?.accessToken?.trim() || settings?.apiKey?.trim();
|
|
470
|
+
return token && token.length > 0 ? token : undefined;
|
|
471
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { createUserInstructionConfigWatcher } from "../agents";
|
|
6
|
+
import {
|
|
7
|
+
listAvailableRuntimeCommandsFromWatcher,
|
|
8
|
+
resolveRuntimeSlashCommandFromWatcher,
|
|
9
|
+
} from "./commands";
|
|
10
|
+
|
|
11
|
+
describe("runtime command registry", () => {
|
|
12
|
+
const tempRoots: string[] = [];
|
|
13
|
+
|
|
14
|
+
afterEach(async () => {
|
|
15
|
+
await Promise.all(
|
|
16
|
+
tempRoots.map((dir) => rm(dir, { recursive: true, force: true })),
|
|
17
|
+
);
|
|
18
|
+
tempRoots.length = 0;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("lists workflow and skill commands together", async () => {
|
|
22
|
+
const tempRoot = await mkdtemp(join(tmpdir(), "core-runtime-commands-"));
|
|
23
|
+
tempRoots.push(tempRoot);
|
|
24
|
+
const skillDir = join(tempRoot, "skills", "debug");
|
|
25
|
+
const workflowsDir = join(tempRoot, "workflows");
|
|
26
|
+
await mkdir(skillDir, { recursive: true });
|
|
27
|
+
await mkdir(workflowsDir, { recursive: true });
|
|
28
|
+
await writeFile(join(skillDir, "SKILL.md"), "Use the debugging skill.");
|
|
29
|
+
await writeFile(
|
|
30
|
+
join(workflowsDir, "release.md"),
|
|
31
|
+
`---
|
|
32
|
+
name: release
|
|
33
|
+
---
|
|
34
|
+
Run the release workflow.`,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const watcher = createUserInstructionConfigWatcher({
|
|
38
|
+
skills: { directories: [join(tempRoot, "skills")] },
|
|
39
|
+
rules: { directories: [] },
|
|
40
|
+
workflows: { directories: [workflowsDir] },
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
await watcher.start();
|
|
45
|
+
expect(listAvailableRuntimeCommandsFromWatcher(watcher)).toEqual([
|
|
46
|
+
{
|
|
47
|
+
id: "debug",
|
|
48
|
+
name: "debug",
|
|
49
|
+
instructions: "Use the debugging skill.",
|
|
50
|
+
kind: "skill",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "release",
|
|
54
|
+
name: "release",
|
|
55
|
+
instructions: "Run the release workflow.",
|
|
56
|
+
kind: "workflow",
|
|
57
|
+
},
|
|
58
|
+
]);
|
|
59
|
+
} finally {
|
|
60
|
+
watcher.stop();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("expands skill and workflow slash commands with workflow precedence", async () => {
|
|
65
|
+
const tempRoot = await mkdtemp(join(tmpdir(), "core-runtime-commands-"));
|
|
66
|
+
tempRoots.push(tempRoot);
|
|
67
|
+
const skillDir = join(tempRoot, "skills", "ship");
|
|
68
|
+
const workflowsDir = join(tempRoot, "workflows");
|
|
69
|
+
await mkdir(skillDir, { recursive: true });
|
|
70
|
+
await mkdir(workflowsDir, { recursive: true });
|
|
71
|
+
await writeFile(join(skillDir, "SKILL.md"), "Use the ship skill.");
|
|
72
|
+
await writeFile(
|
|
73
|
+
join(workflowsDir, "ship.md"),
|
|
74
|
+
`---
|
|
75
|
+
name: ship
|
|
76
|
+
---
|
|
77
|
+
Run the ship workflow.`,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const watcher = createUserInstructionConfigWatcher({
|
|
81
|
+
skills: { directories: [join(tempRoot, "skills")] },
|
|
82
|
+
rules: { directories: [] },
|
|
83
|
+
workflows: { directories: [workflowsDir] },
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
await watcher.start();
|
|
88
|
+
expect(resolveRuntimeSlashCommandFromWatcher("/ship", watcher)).toBe(
|
|
89
|
+
"Run the ship workflow.",
|
|
90
|
+
);
|
|
91
|
+
expect(resolveRuntimeSlashCommandFromWatcher("/ship now", watcher)).toBe(
|
|
92
|
+
"Run the ship workflow. now",
|
|
93
|
+
);
|
|
94
|
+
} finally {
|
|
95
|
+
watcher.stop();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SkillConfig,
|
|
3
|
+
UserInstructionConfigWatcher,
|
|
4
|
+
WorkflowConfig,
|
|
5
|
+
} from "../agents";
|
|
6
|
+
|
|
7
|
+
export type RuntimeCommandKind = "skill" | "workflow";
|
|
8
|
+
|
|
9
|
+
export type AvailableRuntimeCommand = {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
instructions: string;
|
|
13
|
+
kind: RuntimeCommandKind;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type CommandRecord = {
|
|
17
|
+
item: SkillConfig | WorkflowConfig;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function isCommandEnabled(command: SkillConfig | WorkflowConfig): boolean {
|
|
21
|
+
return command.disabled !== true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function listCommandsForKind(
|
|
25
|
+
watcher: UserInstructionConfigWatcher,
|
|
26
|
+
kind: RuntimeCommandKind,
|
|
27
|
+
): AvailableRuntimeCommand[] {
|
|
28
|
+
return [...watcher.getSnapshot(kind).entries()]
|
|
29
|
+
.map(([id, record]) => ({ id, record: record as CommandRecord }))
|
|
30
|
+
.filter(({ record }) => isCommandEnabled(record.item))
|
|
31
|
+
.map(({ id, record }) => ({
|
|
32
|
+
id,
|
|
33
|
+
name: record.item.name,
|
|
34
|
+
instructions: record.item.instructions,
|
|
35
|
+
kind,
|
|
36
|
+
}))
|
|
37
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function listAvailableRuntimeCommandsFromWatcher(
|
|
41
|
+
watcher: UserInstructionConfigWatcher,
|
|
42
|
+
): AvailableRuntimeCommand[] {
|
|
43
|
+
const byName = new Map<string, AvailableRuntimeCommand>();
|
|
44
|
+
for (const command of [
|
|
45
|
+
...listCommandsForKind(watcher, "workflow"),
|
|
46
|
+
...listCommandsForKind(watcher, "skill"),
|
|
47
|
+
]) {
|
|
48
|
+
if (!byName.has(command.name)) {
|
|
49
|
+
byName.set(command.name, command);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function resolveRuntimeSlashCommandFromWatcher(
|
|
56
|
+
input: string,
|
|
57
|
+
watcher: UserInstructionConfigWatcher,
|
|
58
|
+
): string {
|
|
59
|
+
if (!input.startsWith("/") || input.length < 2) {
|
|
60
|
+
return input;
|
|
61
|
+
}
|
|
62
|
+
const match = input.match(/^\/(\S+)/);
|
|
63
|
+
if (!match) {
|
|
64
|
+
return input;
|
|
65
|
+
}
|
|
66
|
+
const name = match[1];
|
|
67
|
+
if (!name) {
|
|
68
|
+
return input;
|
|
69
|
+
}
|
|
70
|
+
const commandLength = name.length + 1;
|
|
71
|
+
const remainder = input.slice(commandLength);
|
|
72
|
+
const matched = listAvailableRuntimeCommandsFromWatcher(watcher).find(
|
|
73
|
+
(command) => command.name === name,
|
|
74
|
+
);
|
|
75
|
+
return matched ? `${matched.instructions}${remainder}` : input;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function listAvailableRuntimeCommandsForKindFromWatcher(
|
|
79
|
+
watcher: UserInstructionConfigWatcher,
|
|
80
|
+
kind: RuntimeCommandKind,
|
|
81
|
+
): AvailableRuntimeCommand[] {
|
|
82
|
+
return listCommandsForKind(watcher, kind);
|
|
83
|
+
}
|