@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,262 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
McpToolCallRequest,
|
|
3
|
+
McpToolCallResult,
|
|
4
|
+
McpToolDescriptor,
|
|
5
|
+
} from "@clinebot/agents";
|
|
6
|
+
import type {
|
|
7
|
+
McpConnectionStatus,
|
|
8
|
+
McpManager,
|
|
9
|
+
McpManagerOptions,
|
|
10
|
+
McpServerClient,
|
|
11
|
+
McpServerRegistration,
|
|
12
|
+
McpServerSnapshot,
|
|
13
|
+
} from "./types";
|
|
14
|
+
|
|
15
|
+
const DEFAULT_TOOLS_CACHE_TTL_MS = 5000;
|
|
16
|
+
|
|
17
|
+
type ManagedServerState = {
|
|
18
|
+
registration: McpServerRegistration;
|
|
19
|
+
client?: McpServerClient;
|
|
20
|
+
status: McpConnectionStatus;
|
|
21
|
+
lastError?: string;
|
|
22
|
+
updatedAt: number;
|
|
23
|
+
toolCache?: readonly McpToolDescriptor[];
|
|
24
|
+
toolCacheUpdatedAt?: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function nowMs(): number {
|
|
28
|
+
return Date.now();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function cloneTools(
|
|
32
|
+
tools: readonly McpToolDescriptor[],
|
|
33
|
+
): readonly McpToolDescriptor[] {
|
|
34
|
+
return tools.map((tool) => ({
|
|
35
|
+
name: tool.name,
|
|
36
|
+
description: tool.description,
|
|
37
|
+
inputSchema: tool.inputSchema,
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class InMemoryMcpManager implements McpManager {
|
|
42
|
+
private readonly toolsCacheTtlMs: number;
|
|
43
|
+
private readonly clientFactory: McpManagerOptions["clientFactory"];
|
|
44
|
+
private readonly servers = new Map<string, ManagedServerState>();
|
|
45
|
+
private readonly operationLocks = new Map<string, Promise<void>>();
|
|
46
|
+
|
|
47
|
+
constructor(options: McpManagerOptions) {
|
|
48
|
+
this.clientFactory = options.clientFactory;
|
|
49
|
+
this.toolsCacheTtlMs =
|
|
50
|
+
options.toolsCacheTtlMs ?? DEFAULT_TOOLS_CACHE_TTL_MS;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async registerServer(registration: McpServerRegistration): Promise<void> {
|
|
54
|
+
await this.runExclusive(registration.name, async () => {
|
|
55
|
+
const existing = this.servers.get(registration.name);
|
|
56
|
+
if (!existing) {
|
|
57
|
+
this.servers.set(registration.name, {
|
|
58
|
+
registration: { ...registration },
|
|
59
|
+
status: "disconnected",
|
|
60
|
+
updatedAt: nowMs(),
|
|
61
|
+
});
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const didTransportChange =
|
|
66
|
+
JSON.stringify(existing.registration.transport) !==
|
|
67
|
+
JSON.stringify(registration.transport);
|
|
68
|
+
existing.registration = { ...registration };
|
|
69
|
+
existing.updatedAt = nowMs();
|
|
70
|
+
|
|
71
|
+
if (didTransportChange) {
|
|
72
|
+
await this.disconnectState(existing);
|
|
73
|
+
existing.client = undefined;
|
|
74
|
+
existing.toolCache = undefined;
|
|
75
|
+
existing.toolCacheUpdatedAt = undefined;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async unregisterServer(serverName: string): Promise<void> {
|
|
81
|
+
await this.runExclusive(serverName, async () => {
|
|
82
|
+
const state = this.requireServer(serverName);
|
|
83
|
+
await this.disconnectState(state);
|
|
84
|
+
this.servers.delete(serverName);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async connectServer(serverName: string): Promise<void> {
|
|
89
|
+
await this.runExclusive(serverName, async () => {
|
|
90
|
+
const state = this.requireServer(serverName);
|
|
91
|
+
await this.connectState(state);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async disconnectServer(serverName: string): Promise<void> {
|
|
96
|
+
await this.runExclusive(serverName, async () => {
|
|
97
|
+
const state = this.requireServer(serverName);
|
|
98
|
+
await this.disconnectState(state);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async setServerDisabled(
|
|
103
|
+
serverName: string,
|
|
104
|
+
disabled: boolean,
|
|
105
|
+
): Promise<void> {
|
|
106
|
+
await this.runExclusive(serverName, async () => {
|
|
107
|
+
const state = this.requireServer(serverName);
|
|
108
|
+
state.registration = {
|
|
109
|
+
...state.registration,
|
|
110
|
+
disabled,
|
|
111
|
+
};
|
|
112
|
+
state.updatedAt = nowMs();
|
|
113
|
+
if (disabled) {
|
|
114
|
+
await this.disconnectState(state);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
listServers(): readonly McpServerSnapshot[] {
|
|
120
|
+
return [...this.servers.values()]
|
|
121
|
+
.map((state) => ({
|
|
122
|
+
name: state.registration.name,
|
|
123
|
+
status: state.status,
|
|
124
|
+
disabled: state.registration.disabled === true,
|
|
125
|
+
lastError: state.lastError,
|
|
126
|
+
toolCount: state.toolCache?.length ?? 0,
|
|
127
|
+
updatedAt: state.updatedAt,
|
|
128
|
+
metadata: state.registration.metadata,
|
|
129
|
+
}))
|
|
130
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async listTools(serverName: string): Promise<readonly McpToolDescriptor[]> {
|
|
134
|
+
const state = this.requireServer(serverName);
|
|
135
|
+
const fetchedAt = state.toolCacheUpdatedAt ?? 0;
|
|
136
|
+
if (state.toolCache && nowMs() - fetchedAt <= this.toolsCacheTtlMs) {
|
|
137
|
+
return state.toolCache;
|
|
138
|
+
}
|
|
139
|
+
return this.refreshTools(serverName);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async refreshTools(
|
|
143
|
+
serverName: string,
|
|
144
|
+
): Promise<readonly McpToolDescriptor[]> {
|
|
145
|
+
return this.runExclusive(serverName, async () => {
|
|
146
|
+
const state = this.requireServer(serverName);
|
|
147
|
+
const client = await this.ensureConnectedClient(state);
|
|
148
|
+
const tools = await client.listTools();
|
|
149
|
+
const cloned = cloneTools(tools);
|
|
150
|
+
state.toolCache = cloned;
|
|
151
|
+
state.toolCacheUpdatedAt = nowMs();
|
|
152
|
+
state.updatedAt = nowMs();
|
|
153
|
+
return cloned;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async callTool(request: McpToolCallRequest): Promise<McpToolCallResult> {
|
|
158
|
+
return this.runExclusive(request.serverName, async () => {
|
|
159
|
+
const state = this.requireServer(request.serverName);
|
|
160
|
+
const client = await this.ensureConnectedClient(state);
|
|
161
|
+
state.updatedAt = nowMs();
|
|
162
|
+
return client.callTool({
|
|
163
|
+
name: request.toolName,
|
|
164
|
+
arguments: request.arguments,
|
|
165
|
+
context: request.context,
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async dispose(): Promise<void> {
|
|
171
|
+
const names = [...this.servers.keys()];
|
|
172
|
+
for (const name of names) {
|
|
173
|
+
await this.unregisterServer(name);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private async ensureConnectedClient(
|
|
178
|
+
state: ManagedServerState,
|
|
179
|
+
): Promise<McpServerClient> {
|
|
180
|
+
await this.connectState(state);
|
|
181
|
+
if (!state.client) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
`MCP server "${state.registration.name}" does not have an initialized client.`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
return state.client;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private async connectState(state: ManagedServerState): Promise<void> {
|
|
190
|
+
if (state.registration.disabled) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`MCP server "${state.registration.name}" is disabled and cannot be connected.`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
if (state.status === "connected" && state.client) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
state.status = "connecting";
|
|
199
|
+
state.updatedAt = nowMs();
|
|
200
|
+
try {
|
|
201
|
+
const client =
|
|
202
|
+
state.client ?? (await this.clientFactory(state.registration));
|
|
203
|
+
await client.connect();
|
|
204
|
+
state.client = client;
|
|
205
|
+
state.status = "connected";
|
|
206
|
+
state.lastError = undefined;
|
|
207
|
+
state.updatedAt = nowMs();
|
|
208
|
+
} catch (error) {
|
|
209
|
+
state.status = "disconnected";
|
|
210
|
+
state.lastError = error instanceof Error ? error.message : String(error);
|
|
211
|
+
state.updatedAt = nowMs();
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private async disconnectState(state: ManagedServerState): Promise<void> {
|
|
217
|
+
if (!state.client) {
|
|
218
|
+
state.status = "disconnected";
|
|
219
|
+
state.updatedAt = nowMs();
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
await state.client.disconnect();
|
|
225
|
+
} finally {
|
|
226
|
+
state.status = "disconnected";
|
|
227
|
+
state.updatedAt = nowMs();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private requireServer(serverName: string): ManagedServerState {
|
|
232
|
+
const state = this.servers.get(serverName);
|
|
233
|
+
if (!state) {
|
|
234
|
+
throw new Error(`Unknown MCP server: ${serverName}`);
|
|
235
|
+
}
|
|
236
|
+
return state;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private async runExclusive<T>(
|
|
240
|
+
serverName: string,
|
|
241
|
+
operation: () => Promise<T>,
|
|
242
|
+
): Promise<T> {
|
|
243
|
+
const previous = this.operationLocks.get(serverName) ?? Promise.resolve();
|
|
244
|
+
let releaseCurrent: (() => void) | undefined;
|
|
245
|
+
const current = new Promise<void>((resolve) => {
|
|
246
|
+
releaseCurrent = resolve;
|
|
247
|
+
});
|
|
248
|
+
const queued = previous.catch(() => undefined).then(() => current);
|
|
249
|
+
this.operationLocks.set(serverName, queued);
|
|
250
|
+
|
|
251
|
+
await previous.catch(() => undefined);
|
|
252
|
+
try {
|
|
253
|
+
return await operation();
|
|
254
|
+
} finally {
|
|
255
|
+
releaseCurrent?.();
|
|
256
|
+
const lock = this.operationLocks.get(serverName);
|
|
257
|
+
if (lock === queued) {
|
|
258
|
+
this.operationLocks.delete(serverName);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
package/src/mcp/types.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
McpToolCallRequest,
|
|
3
|
+
McpToolCallResult,
|
|
4
|
+
McpToolDescriptor,
|
|
5
|
+
McpToolProvider,
|
|
6
|
+
ToolContext,
|
|
7
|
+
} from "@clinebot/agents";
|
|
8
|
+
|
|
9
|
+
export type McpConnectionStatus = "disconnected" | "connecting" | "connected";
|
|
10
|
+
|
|
11
|
+
export interface McpStdioTransportConfig {
|
|
12
|
+
type: "stdio";
|
|
13
|
+
command: string;
|
|
14
|
+
args?: string[];
|
|
15
|
+
cwd?: string;
|
|
16
|
+
env?: Record<string, string>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface McpSseTransportConfig {
|
|
20
|
+
type: "sse";
|
|
21
|
+
url: string;
|
|
22
|
+
headers?: Record<string, string>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface McpStreamableHttpTransportConfig {
|
|
26
|
+
type: "streamableHttp";
|
|
27
|
+
url: string;
|
|
28
|
+
headers?: Record<string, string>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type McpServerTransportConfig =
|
|
32
|
+
| McpStdioTransportConfig
|
|
33
|
+
| McpSseTransportConfig
|
|
34
|
+
| McpStreamableHttpTransportConfig;
|
|
35
|
+
|
|
36
|
+
export interface McpServerRegistration {
|
|
37
|
+
name: string;
|
|
38
|
+
transport: McpServerTransportConfig;
|
|
39
|
+
disabled?: boolean;
|
|
40
|
+
metadata?: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface McpServerSnapshot {
|
|
44
|
+
name: string;
|
|
45
|
+
status: McpConnectionStatus;
|
|
46
|
+
disabled: boolean;
|
|
47
|
+
lastError?: string;
|
|
48
|
+
toolCount: number;
|
|
49
|
+
updatedAt: number;
|
|
50
|
+
metadata?: Record<string, unknown>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface McpServerClient {
|
|
54
|
+
connect(): Promise<void>;
|
|
55
|
+
disconnect(): Promise<void>;
|
|
56
|
+
listTools(): Promise<readonly McpToolDescriptor[]>;
|
|
57
|
+
callTool(request: {
|
|
58
|
+
name: string;
|
|
59
|
+
arguments?: Record<string, unknown>;
|
|
60
|
+
context?: ToolContext;
|
|
61
|
+
}): Promise<McpToolCallResult>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type McpServerClientFactory = (
|
|
65
|
+
registration: McpServerRegistration,
|
|
66
|
+
) => Promise<McpServerClient> | McpServerClient;
|
|
67
|
+
|
|
68
|
+
export interface McpManagerOptions {
|
|
69
|
+
clientFactory: McpServerClientFactory;
|
|
70
|
+
/**
|
|
71
|
+
* Cache TTL for tools/list responses.
|
|
72
|
+
* A short cache avoids repeated list requests while keeping server metadata fresh.
|
|
73
|
+
* @default 5000
|
|
74
|
+
*/
|
|
75
|
+
toolsCacheTtlMs?: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface McpManager extends McpToolProvider {
|
|
79
|
+
registerServer(registration: McpServerRegistration): Promise<void>;
|
|
80
|
+
unregisterServer(serverName: string): Promise<void>;
|
|
81
|
+
connectServer(serverName: string): Promise<void>;
|
|
82
|
+
disconnectServer(serverName: string): Promise<void>;
|
|
83
|
+
setServerDisabled(serverName: string, disabled: boolean): Promise<void>;
|
|
84
|
+
listServers(): readonly McpServerSnapshot[];
|
|
85
|
+
refreshTools(serverName: string): Promise<readonly McpToolDescriptor[]>;
|
|
86
|
+
callTool(request: McpToolCallRequest): Promise<McpToolCallResult>;
|
|
87
|
+
dispose(): Promise<void>;
|
|
88
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import * as LlmsModels from "@clinebot/llms/models";
|
|
5
|
+
import type { RpcProviderCapability, RpcProviderModel } from "@clinebot/shared";
|
|
6
|
+
import type { ProviderSettingsManager } from "../storage/provider-settings-manager";
|
|
7
|
+
|
|
8
|
+
export type StoredModelsFile = {
|
|
9
|
+
version: 1;
|
|
10
|
+
providers: Record<
|
|
11
|
+
string,
|
|
12
|
+
{
|
|
13
|
+
provider: {
|
|
14
|
+
name: string;
|
|
15
|
+
baseUrl: string;
|
|
16
|
+
defaultModelId?: string;
|
|
17
|
+
capabilities?: RpcProviderCapability[];
|
|
18
|
+
modelsSourceUrl?: string;
|
|
19
|
+
};
|
|
20
|
+
models: Record<
|
|
21
|
+
string,
|
|
22
|
+
{
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
supportsVision?: boolean;
|
|
26
|
+
supportsAttachments?: boolean;
|
|
27
|
+
supportsReasoning?: boolean;
|
|
28
|
+
}
|
|
29
|
+
>;
|
|
30
|
+
}
|
|
31
|
+
>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const LOADED_MODELS_REGISTRY_PATHS = new Set<string>();
|
|
35
|
+
|
|
36
|
+
function titleCaseFromId(id: string): string {
|
|
37
|
+
return id
|
|
38
|
+
.split(/[-_]/)
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
41
|
+
.join(" ");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function resolveModelsRegistryPath(
|
|
45
|
+
manager: ProviderSettingsManager,
|
|
46
|
+
): string {
|
|
47
|
+
return join(dirname(manager.getFilePath()), "models.json");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function emptyModelsFile(): StoredModelsFile {
|
|
51
|
+
return { version: 1, providers: {} };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function readModelsFileSync(filePath: string): StoredModelsFile {
|
|
55
|
+
if (!existsSync(filePath)) {
|
|
56
|
+
return emptyModelsFile();
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const raw = readFileSync(filePath, "utf8");
|
|
60
|
+
const parsed = JSON.parse(raw) as Partial<StoredModelsFile>;
|
|
61
|
+
if (
|
|
62
|
+
parsed &&
|
|
63
|
+
parsed.version === 1 &&
|
|
64
|
+
parsed.providers &&
|
|
65
|
+
typeof parsed.providers === "object"
|
|
66
|
+
) {
|
|
67
|
+
return { version: 1, providers: parsed.providers };
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// Invalid or missing files fall back to an empty registry.
|
|
71
|
+
}
|
|
72
|
+
return emptyModelsFile();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function readModelsFile(
|
|
76
|
+
filePath: string,
|
|
77
|
+
): Promise<StoredModelsFile> {
|
|
78
|
+
try {
|
|
79
|
+
const raw = await readFile(filePath, "utf8");
|
|
80
|
+
const parsed = JSON.parse(raw) as Partial<StoredModelsFile>;
|
|
81
|
+
if (
|
|
82
|
+
parsed &&
|
|
83
|
+
parsed.version === 1 &&
|
|
84
|
+
parsed.providers &&
|
|
85
|
+
typeof parsed.providers === "object"
|
|
86
|
+
) {
|
|
87
|
+
return { version: 1, providers: parsed.providers };
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
// Invalid or missing files fall back to an empty registry.
|
|
91
|
+
}
|
|
92
|
+
return emptyModelsFile();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function writeModelsFileSync(
|
|
96
|
+
filePath: string,
|
|
97
|
+
state: StoredModelsFile,
|
|
98
|
+
): void {
|
|
99
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
100
|
+
writeFileSync(filePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function writeModelsFile(
|
|
104
|
+
filePath: string,
|
|
105
|
+
state: StoredModelsFile,
|
|
106
|
+
): Promise<void> {
|
|
107
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
108
|
+
await writeFile(filePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function toRpcProviderModel(
|
|
112
|
+
modelId: string,
|
|
113
|
+
info: {
|
|
114
|
+
name?: string;
|
|
115
|
+
capabilities?: string[];
|
|
116
|
+
thinkingConfig?: unknown;
|
|
117
|
+
},
|
|
118
|
+
): RpcProviderModel {
|
|
119
|
+
return {
|
|
120
|
+
id: modelId,
|
|
121
|
+
name: info.name ?? modelId,
|
|
122
|
+
supportsAttachments: info.capabilities?.includes("files"),
|
|
123
|
+
supportsVision: info.capabilities?.includes("images"),
|
|
124
|
+
supportsReasoning:
|
|
125
|
+
info.capabilities?.includes("reasoning") || info.thinkingConfig != null,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function toProviderCapabilities(
|
|
130
|
+
capabilities: RpcProviderCapability[] | undefined,
|
|
131
|
+
): Array<"reasoning" | "prompt-cache" | "tools"> | undefined {
|
|
132
|
+
if (!capabilities || capabilities.length === 0) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
const next = new Set<"reasoning" | "prompt-cache" | "tools">();
|
|
136
|
+
if (capabilities.includes("reasoning")) {
|
|
137
|
+
next.add("reasoning");
|
|
138
|
+
}
|
|
139
|
+
if (capabilities.includes("prompt-cache")) {
|
|
140
|
+
next.add("prompt-cache");
|
|
141
|
+
}
|
|
142
|
+
if (capabilities.includes("tools")) {
|
|
143
|
+
next.add("tools");
|
|
144
|
+
}
|
|
145
|
+
return next.size > 0 ? [...next] : undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function toModelCapabilities(
|
|
149
|
+
capabilities: RpcProviderCapability[] | undefined,
|
|
150
|
+
): Array<
|
|
151
|
+
"streaming" | "tools" | "reasoning" | "prompt-cache" | "images" | "files"
|
|
152
|
+
> {
|
|
153
|
+
const next = new Set<
|
|
154
|
+
"streaming" | "tools" | "reasoning" | "prompt-cache" | "images" | "files"
|
|
155
|
+
>();
|
|
156
|
+
if (!capabilities || capabilities.length === 0) {
|
|
157
|
+
return [...next];
|
|
158
|
+
}
|
|
159
|
+
if (capabilities.includes("streaming")) next.add("streaming");
|
|
160
|
+
if (capabilities.includes("tools")) next.add("tools");
|
|
161
|
+
if (capabilities.includes("reasoning")) next.add("reasoning");
|
|
162
|
+
if (capabilities.includes("prompt-cache")) next.add("prompt-cache");
|
|
163
|
+
if (capabilities.includes("vision")) {
|
|
164
|
+
next.add("images");
|
|
165
|
+
next.add("files");
|
|
166
|
+
}
|
|
167
|
+
return [...next];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function registerCustomProvider(
|
|
171
|
+
providerId: string,
|
|
172
|
+
entry: StoredModelsFile["providers"][string],
|
|
173
|
+
): void {
|
|
174
|
+
const modelCapabilities = toModelCapabilities(entry.provider.capabilities);
|
|
175
|
+
const modelEntries = Object.values(entry.models)
|
|
176
|
+
.map((model) => model.id.trim())
|
|
177
|
+
.filter((modelId) => modelId.length > 0);
|
|
178
|
+
const defaultModelId =
|
|
179
|
+
entry.provider.defaultModelId?.trim() || modelEntries[0] || "default";
|
|
180
|
+
const normalizedModels = Object.fromEntries(
|
|
181
|
+
modelEntries.map((modelId) => [
|
|
182
|
+
modelId,
|
|
183
|
+
{
|
|
184
|
+
id: modelId,
|
|
185
|
+
name: entry.models[modelId]?.name ?? modelId,
|
|
186
|
+
capabilities:
|
|
187
|
+
modelCapabilities.length > 0 ? modelCapabilities : undefined,
|
|
188
|
+
status: "active" as const,
|
|
189
|
+
},
|
|
190
|
+
]),
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
LlmsModels.registerProvider({
|
|
194
|
+
provider: {
|
|
195
|
+
id: providerId,
|
|
196
|
+
name: entry.provider.name.trim() || titleCaseFromId(providerId),
|
|
197
|
+
protocol: "openai-chat",
|
|
198
|
+
baseUrl: entry.provider.baseUrl,
|
|
199
|
+
defaultModelId,
|
|
200
|
+
capabilities: toProviderCapabilities(entry.provider.capabilities),
|
|
201
|
+
},
|
|
202
|
+
models: normalizedModels,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function ensureCustomProvidersLoadedSync(
|
|
207
|
+
manager: ProviderSettingsManager,
|
|
208
|
+
): void {
|
|
209
|
+
const modelsPath = resolveModelsRegistryPath(manager);
|
|
210
|
+
if (LOADED_MODELS_REGISTRY_PATHS.has(modelsPath)) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const state = readModelsFileSync(modelsPath);
|
|
214
|
+
for (const [providerId, entry] of Object.entries(state.providers)) {
|
|
215
|
+
registerCustomProvider(providerId, entry);
|
|
216
|
+
}
|
|
217
|
+
LOADED_MODELS_REGISTRY_PATHS.add(modelsPath);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export async function ensureCustomProvidersLoaded(
|
|
221
|
+
manager: ProviderSettingsManager,
|
|
222
|
+
): Promise<void> {
|
|
223
|
+
const modelsPath = resolveModelsRegistryPath(manager);
|
|
224
|
+
if (LOADED_MODELS_REGISTRY_PATHS.has(modelsPath)) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const state = await readModelsFile(modelsPath);
|
|
228
|
+
for (const [providerId, entry] of Object.entries(state.providers)) {
|
|
229
|
+
registerCustomProvider(providerId, entry);
|
|
230
|
+
}
|
|
231
|
+
LOADED_MODELS_REGISTRY_PATHS.add(modelsPath);
|
|
232
|
+
}
|