@clinebot/core 0.0.36 → 0.0.37

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.
Files changed (228) hide show
  1. package/dist/ClineCore.d.ts +312 -3
  2. package/dist/ClineCore.d.ts.map +1 -1
  3. package/dist/account/cline-account-service.d.ts.map +1 -1
  4. package/dist/cron/cron-event-ingress.d.ts +38 -0
  5. package/dist/cron/cron-event-ingress.d.ts.map +1 -0
  6. package/dist/cron/cron-materializer.d.ts +36 -0
  7. package/dist/cron/cron-materializer.d.ts.map +1 -0
  8. package/dist/cron/cron-reconciler.d.ts +62 -0
  9. package/dist/cron/cron-reconciler.d.ts.map +1 -0
  10. package/dist/cron/cron-report-writer.d.ts +41 -0
  11. package/dist/cron/cron-report-writer.d.ts.map +1 -0
  12. package/dist/cron/cron-runner.d.ts +43 -0
  13. package/dist/cron/cron-runner.d.ts.map +1 -0
  14. package/dist/cron/cron-schema.d.ts +3 -0
  15. package/dist/cron/cron-schema.d.ts.map +1 -0
  16. package/dist/cron/cron-service.d.ts +57 -0
  17. package/dist/cron/cron-service.d.ts.map +1 -0
  18. package/dist/cron/cron-spec-parser.d.ts +27 -0
  19. package/dist/cron/cron-spec-parser.d.ts.map +1 -0
  20. package/dist/cron/cron-watcher.d.ts +23 -0
  21. package/dist/cron/cron-watcher.d.ts.map +1 -0
  22. package/dist/cron/scheduler.d.ts +3 -1
  23. package/dist/cron/scheduler.d.ts.map +1 -1
  24. package/dist/cron/sqlite-cron-store.d.ts +230 -0
  25. package/dist/cron/sqlite-cron-store.d.ts.map +1 -0
  26. package/dist/extensions/plugin/plugin-config-loader.d.ts +7 -1
  27. package/dist/extensions/plugin/plugin-config-loader.d.ts.map +1 -1
  28. package/dist/extensions/plugin/plugin-loader.d.ts +10 -6
  29. package/dist/extensions/plugin/plugin-loader.d.ts.map +1 -1
  30. package/dist/extensions/plugin/plugin-sandbox.d.ts +7 -1
  31. package/dist/extensions/plugin/plugin-sandbox.d.ts.map +1 -1
  32. package/dist/extensions/plugin-sandbox-bootstrap.js +236 -275
  33. package/dist/extensions/tools/constants.d.ts +1 -0
  34. package/dist/extensions/tools/constants.d.ts.map +1 -1
  35. package/dist/extensions/tools/definitions.d.ts +2 -3
  36. package/dist/extensions/tools/definitions.d.ts.map +1 -1
  37. package/dist/extensions/tools/executors/editor.d.ts.map +1 -1
  38. package/dist/extensions/tools/helpers.d.ts +1 -0
  39. package/dist/extensions/tools/helpers.d.ts.map +1 -1
  40. package/dist/extensions/tools/index.d.ts +1 -2
  41. package/dist/extensions/tools/index.d.ts.map +1 -1
  42. package/dist/extensions/tools/presets.d.ts +1 -1
  43. package/dist/extensions/tools/schemas.d.ts +25 -3
  44. package/dist/extensions/tools/schemas.d.ts.map +1 -1
  45. package/dist/extensions/tools/team/delegated-agent.d.ts +2 -2
  46. package/dist/extensions/tools/team/delegated-agent.d.ts.map +1 -1
  47. package/dist/extensions/tools/team/multi-agent.d.ts +7 -3
  48. package/dist/extensions/tools/team/multi-agent.d.ts.map +1 -1
  49. package/dist/extensions/tools/team/team-tools.d.ts.map +1 -1
  50. package/dist/extensions/tools/types.d.ts +0 -5
  51. package/dist/extensions/tools/types.d.ts.map +1 -1
  52. package/dist/hooks/hook-bridge.d.ts +118 -0
  53. package/dist/hooks/hook-bridge.d.ts.map +1 -0
  54. package/dist/hooks/hook-file-hooks.d.ts +2 -1
  55. package/dist/hooks/hook-file-hooks.d.ts.map +1 -1
  56. package/dist/hooks/hook-registry.d.ts +16 -0
  57. package/dist/hooks/hook-registry.d.ts.map +1 -0
  58. package/dist/hub/browser-websocket.d.ts.map +1 -1
  59. package/dist/hub/client.d.ts +7 -1
  60. package/dist/hub/client.d.ts.map +1 -1
  61. package/dist/hub/daemon-entry.js +721 -461
  62. package/dist/hub/daemon.d.ts.map +1 -1
  63. package/dist/hub/defaults.d.ts +8 -4
  64. package/dist/hub/defaults.d.ts.map +1 -1
  65. package/dist/hub/index.js +665 -415
  66. package/dist/hub/runtime-handlers.d.ts.map +1 -1
  67. package/dist/hub/server.d.ts +18 -0
  68. package/dist/hub/server.d.ts.map +1 -1
  69. package/dist/hub/session-client.d.ts +3 -0
  70. package/dist/hub/session-client.d.ts.map +1 -1
  71. package/dist/hub/start-shared-server.d.ts.map +1 -1
  72. package/dist/hub/ui-client.d.ts +1 -0
  73. package/dist/hub/ui-client.d.ts.map +1 -1
  74. package/dist/index.d.ts +9 -7
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +756 -467
  77. package/dist/llms/cline-recommended-models.d.ts +20 -0
  78. package/dist/llms/cline-recommended-models.d.ts.map +1 -0
  79. package/dist/llms/handler-factory.d.ts +16 -0
  80. package/dist/llms/handler-factory.d.ts.map +1 -0
  81. package/dist/llms/provider-defaults.d.ts.map +1 -1
  82. package/dist/llms/provider-settings.d.ts +45 -2
  83. package/dist/llms/provider-settings.d.ts.map +1 -1
  84. package/dist/llms/runtime-registry.d.ts.map +1 -1
  85. package/dist/runtime/agent-config-adapter.d.ts +148 -0
  86. package/dist/runtime/agent-config-adapter.d.ts.map +1 -0
  87. package/dist/runtime/agent-runtime-config-builder.d.ts +96 -0
  88. package/dist/runtime/agent-runtime-config-builder.d.ts.map +1 -0
  89. package/dist/runtime/history.d.ts +6 -0
  90. package/dist/runtime/history.d.ts.map +1 -1
  91. package/dist/runtime/host.d.ts.map +1 -1
  92. package/dist/runtime/loop-detection.d.ts +59 -0
  93. package/dist/runtime/loop-detection.d.ts.map +1 -0
  94. package/dist/runtime/mistake-tracker.d.ts +69 -0
  95. package/dist/runtime/mistake-tracker.d.ts.map +1 -0
  96. package/dist/runtime/runtime-builder.d.ts.map +1 -1
  97. package/dist/runtime/runtime-event-adapter.d.ts +102 -0
  98. package/dist/runtime/runtime-event-adapter.d.ts.map +1 -0
  99. package/dist/runtime/runtime-host.d.ts +28 -3
  100. package/dist/runtime/runtime-host.d.ts.map +1 -1
  101. package/dist/runtime/session-runtime-orchestrator.d.ts +261 -0
  102. package/dist/runtime/session-runtime-orchestrator.d.ts.map +1 -0
  103. package/dist/runtime/session-runtime.d.ts +16 -3
  104. package/dist/runtime/session-runtime.d.ts.map +1 -1
  105. package/dist/runtime/user-input-builder.d.ts +24 -0
  106. package/dist/runtime/user-input-builder.d.ts.map +1 -0
  107. package/dist/services/index.js +28 -0
  108. package/dist/services/local-runtime-bootstrap.d.ts.map +1 -1
  109. package/dist/services/plugin-tools.d.ts.map +1 -1
  110. package/dist/services/providers/local-provider-registry.d.ts +197 -21
  111. package/dist/services/providers/local-provider-registry.d.ts.map +1 -1
  112. package/dist/services/providers/local-provider-service.d.ts +3 -1
  113. package/dist/services/providers/local-provider-service.d.ts.map +1 -1
  114. package/dist/services/session-data.d.ts.map +1 -1
  115. package/dist/services/session-telemetry.d.ts +7 -2
  116. package/dist/services/session-telemetry.d.ts.map +1 -1
  117. package/dist/services/storage/file-team-store.d.ts.map +1 -1
  118. package/dist/services/storage/provider-settings-legacy-migration.d.ts.map +1 -1
  119. package/dist/services/storage/provider-settings-manager.d.ts +1 -0
  120. package/dist/services/storage/provider-settings-manager.d.ts.map +1 -1
  121. package/dist/services/storage/sqlite-team-store.d.ts.map +1 -1
  122. package/dist/session/conversation-store.d.ts +30 -0
  123. package/dist/session/conversation-store.d.ts.map +1 -0
  124. package/dist/session/message-builder.d.ts +65 -0
  125. package/dist/session/message-builder.d.ts.map +1 -0
  126. package/dist/session/session-manifest.d.ts +1 -1
  127. package/dist/transports/hub.d.ts +14 -3
  128. package/dist/transports/hub.d.ts.map +1 -1
  129. package/dist/transports/local.d.ts +14 -4
  130. package/dist/transports/local.d.ts.map +1 -1
  131. package/dist/transports/remote.d.ts.map +1 -1
  132. package/dist/types/chat-schema.d.ts +5 -5
  133. package/dist/types/config.d.ts +9 -0
  134. package/dist/types/config.d.ts.map +1 -1
  135. package/dist/types/events.d.ts +7 -6
  136. package/dist/types/events.d.ts.map +1 -1
  137. package/dist/types/provider-settings.d.ts +2 -2
  138. package/dist/types/provider-settings.d.ts.map +1 -1
  139. package/dist/types/session.d.ts +5 -2
  140. package/dist/types/session.d.ts.map +1 -1
  141. package/dist/types.d.ts +4 -4
  142. package/dist/types.d.ts.map +1 -1
  143. package/package.json +4 -4
  144. package/src/ClineCore.ts +691 -6
  145. package/src/account/cline-account-service.ts +44 -6
  146. package/src/cron/cron-event-ingress.ts +357 -0
  147. package/src/cron/cron-materializer.ts +97 -0
  148. package/src/cron/cron-reconciler.ts +241 -0
  149. package/src/cron/cron-report-writer.ts +153 -0
  150. package/src/cron/cron-runner.ts +495 -0
  151. package/src/cron/cron-schema.ts +127 -0
  152. package/src/cron/cron-service.ts +163 -0
  153. package/src/cron/cron-spec-parser.ts +489 -0
  154. package/src/cron/cron-watcher.ts +102 -0
  155. package/src/cron/index.ts +10 -0
  156. package/src/cron/scheduler.ts +141 -6
  157. package/src/cron/sqlite-cron-store.ts +1286 -0
  158. package/src/extensions/plugin/plugin-config-loader.ts +21 -1
  159. package/src/extensions/plugin/plugin-loader.ts +25 -9
  160. package/src/extensions/plugin/plugin-sandbox-bootstrap.ts +151 -1
  161. package/src/extensions/plugin/plugin-sandbox.ts +131 -7
  162. package/src/extensions/tools/constants.ts +2 -0
  163. package/src/extensions/tools/definitions.ts +31 -22
  164. package/src/extensions/tools/executors/editor.ts +4 -3
  165. package/src/extensions/tools/helpers.ts +24 -0
  166. package/src/extensions/tools/index.ts +1 -2
  167. package/src/extensions/tools/presets.ts +1 -1
  168. package/src/extensions/tools/schemas.ts +13 -18
  169. package/src/extensions/tools/team/delegated-agent.ts +8 -3
  170. package/src/extensions/tools/team/multi-agent.ts +135 -19
  171. package/src/extensions/tools/team/team-tools.ts +151 -91
  172. package/src/extensions/tools/types.ts +0 -6
  173. package/src/hooks/hook-bridge.ts +489 -0
  174. package/src/hooks/hook-file-hooks.ts +58 -3
  175. package/src/hooks/hook-registry.ts +257 -0
  176. package/src/hub/browser-websocket.ts +26 -4
  177. package/src/hub/client.ts +72 -13
  178. package/src/hub/daemon-entry.ts +35 -0
  179. package/src/hub/daemon.ts +117 -14
  180. package/src/hub/defaults.ts +39 -12
  181. package/src/hub/runtime-handlers.ts +4 -3
  182. package/src/hub/server.ts +506 -77
  183. package/src/hub/session-client.ts +43 -1
  184. package/src/hub/start-shared-server.ts +3 -0
  185. package/src/hub/ui-client.ts +4 -0
  186. package/src/index.ts +46 -1
  187. package/src/llms/cline-recommended-models.ts +167 -0
  188. package/src/llms/handler-factory.ts +56 -0
  189. package/src/llms/provider-defaults.ts +17 -1
  190. package/src/llms/provider-settings.ts +48 -1
  191. package/src/llms/runtime-registry.ts +1 -0
  192. package/src/runtime/agent-config-adapter.ts +636 -0
  193. package/src/runtime/agent-runtime-config-builder.ts +205 -0
  194. package/src/runtime/error-feedback.ts +142 -0
  195. package/src/runtime/history.ts +137 -0
  196. package/src/runtime/host.ts +22 -0
  197. package/src/runtime/loop-detection.ts +162 -0
  198. package/src/runtime/mistake-tracker.ts +221 -0
  199. package/src/runtime/runtime-builder.ts +61 -5
  200. package/src/runtime/runtime-event-adapter.ts +412 -0
  201. package/src/runtime/runtime-host.ts +45 -1
  202. package/src/runtime/session-runtime-orchestrator.ts +1253 -0
  203. package/src/runtime/session-runtime.ts +16 -2
  204. package/src/runtime/user-input-builder.ts +167 -0
  205. package/src/services/local-runtime-bootstrap.ts +128 -22
  206. package/src/services/plugin-tools.ts +1 -0
  207. package/src/services/providers/local-provider-registry.ts +273 -57
  208. package/src/services/providers/local-provider-service.ts +67 -7
  209. package/src/services/session-data.ts +16 -14
  210. package/src/services/session-telemetry.ts +6 -15
  211. package/src/services/storage/file-team-store.ts +1 -5
  212. package/src/services/storage/provider-settings-legacy-migration.ts +8 -47
  213. package/src/services/storage/provider-settings-manager.ts +16 -1
  214. package/src/services/storage/sqlite-team-store.ts +1 -5
  215. package/src/session/conversation-store.ts +77 -0
  216. package/src/session/message-builder.ts +941 -0
  217. package/src/transports/hub.ts +458 -33
  218. package/src/transports/local.ts +296 -65
  219. package/src/transports/remote.ts +1 -0
  220. package/src/types/config.ts +9 -0
  221. package/src/types/events.ts +8 -6
  222. package/src/types/index.ts +3 -0
  223. package/src/types/provider-settings.ts +8 -1
  224. package/src/types/session.ts +5 -2
  225. package/src/types.ts +15 -1
  226. package/dist/cron/index.d.ts +0 -6
  227. package/dist/cron/index.d.ts.map +0 -1
  228. package/dist/services/telemetry/index.js +0 -28
@@ -2,34 +2,70 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { dirname, join } from "node:path";
4
4
  import * as LlmsModels from "@clinebot/llms";
5
- import type { ProviderCapability, ProviderModel } from "@clinebot/shared";
5
+ import {
6
+ type ModelCapability,
7
+ ModelCapabilitySchema,
8
+ type ModelInfo,
9
+ type ProviderCapability,
10
+ ProviderCapabilitySchema,
11
+ type ProviderClient,
12
+ ProviderClientSchema,
13
+ type ProviderModel,
14
+ type ProviderProtocol,
15
+ ProviderProtocolSchema,
16
+ } from "@clinebot/shared";
17
+ import { z } from "zod";
18
+ import type {
19
+ ProviderSettings,
20
+ StoredProviderSettings,
21
+ } from "../../types/provider-settings";
6
22
  import type { ProviderSettingsManager } from "../storage/provider-settings-manager";
7
23
 
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?: ProviderCapability[];
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
- };
24
+ export const StoredModelEntrySchema = z
25
+ .object({
26
+ id: z.string().optional(),
27
+ name: z.string().optional(),
28
+ capabilities: z.array(ModelCapabilitySchema).optional(),
29
+ supportsVision: z.boolean().optional(),
30
+ supportsAttachments: z.boolean().optional(),
31
+ supportsReasoning: z.boolean().optional(),
32
+ })
33
+ .passthrough();
34
+
35
+ export type StoredModelEntry = z.infer<typeof StoredModelEntrySchema>;
36
+
37
+ export const StoredProviderMetadataSchema = z
38
+ .object({
39
+ name: z.string(),
40
+ baseUrl: z.string(),
41
+ defaultModelId: z.string().optional(),
42
+ protocol: ProviderProtocolSchema.optional(),
43
+ client: ProviderClientSchema.optional(),
44
+ capabilities: z.array(ProviderCapabilitySchema).optional(),
45
+ modelsSourceUrl: z.string().optional(),
46
+ })
47
+ .passthrough();
48
+
49
+ export const StoredProviderEntrySchema = z
50
+ .object({
51
+ provider: StoredProviderMetadataSchema.optional(),
52
+ models: z.record(z.string(), StoredModelEntrySchema).optional(),
53
+ })
54
+ .passthrough();
55
+
56
+ export type StoredProviderEntry = z.infer<typeof StoredProviderEntrySchema>;
57
+
58
+ export const StoredModelsFileSchema = z.object({
59
+ version: z.literal(1),
60
+ providers: z.record(z.string(), StoredProviderEntrySchema),
61
+ });
62
+
63
+ export type StoredModelsFile = z.infer<typeof StoredModelsFileSchema>;
64
+
65
+ const StoredModelsFileEnvelopeSchema = z.object({
66
+ version: z.literal(1),
67
+ providers: z.record(z.string(), z.unknown()),
68
+ });
33
69
 
34
70
  const LOADED_MODELS_REGISTRY_PATHS = new Set<string>();
35
71
 
@@ -51,21 +87,29 @@ export function emptyModelsFile(): StoredModelsFile {
51
87
  return { version: 1, providers: {} };
52
88
  }
53
89
 
90
+ export function parseModelsFile(input: unknown): StoredModelsFile {
91
+ const result = StoredModelsFileEnvelopeSchema.safeParse(input);
92
+ if (!result.success) {
93
+ return emptyModelsFile();
94
+ }
95
+
96
+ const providers: StoredModelsFile["providers"] = {};
97
+ for (const [providerId, entry] of Object.entries(result.data.providers)) {
98
+ const provider = StoredProviderEntrySchema.safeParse(entry);
99
+ if (provider.success) {
100
+ providers[providerId] = provider.data;
101
+ }
102
+ }
103
+ return { version: 1, providers };
104
+ }
105
+
54
106
  export function readModelsFileSync(filePath: string): StoredModelsFile {
55
107
  if (!existsSync(filePath)) {
56
108
  return emptyModelsFile();
57
109
  }
58
110
  try {
59
111
  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
- }
112
+ return parseModelsFile(JSON.parse(raw) as unknown);
69
113
  } catch {
70
114
  // Invalid or missing files fall back to an empty registry.
71
115
  }
@@ -77,15 +121,7 @@ export async function readModelsFile(
77
121
  ): Promise<StoredModelsFile> {
78
122
  try {
79
123
  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
- }
124
+ return parseModelsFile(JSON.parse(raw) as unknown);
89
125
  } catch {
90
126
  // Invalid or missing files fall back to an empty registry.
91
127
  }
@@ -97,7 +133,8 @@ export function writeModelsFileSync(
97
133
  state: StoredModelsFile,
98
134
  ): void {
99
135
  mkdirSync(dirname(filePath), { recursive: true });
100
- writeFileSync(filePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
136
+ const parsed = StoredModelsFileSchema.parse(state);
137
+ writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}\n`, "utf8");
101
138
  }
102
139
 
103
140
  export async function writeModelsFile(
@@ -105,7 +142,8 @@ export async function writeModelsFile(
105
142
  state: StoredModelsFile,
106
143
  ): Promise<void> {
107
144
  await mkdir(dirname(filePath), { recursive: true });
108
- await writeFile(filePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
145
+ const parsed = StoredModelsFileSchema.parse(state);
146
+ await writeFile(filePath, `${JSON.stringify(parsed, null, 2)}\n`, "utf8");
109
147
  }
110
148
 
111
149
  export function toProviderModel(
@@ -167,6 +205,168 @@ function toModelCapabilities(
167
205
  return [...next];
168
206
  }
169
207
 
208
+ function isCompleteProviderMetadata(
209
+ provider: StoredProviderEntry["provider"],
210
+ ): provider is NonNullable<StoredProviderEntry["provider"]> {
211
+ return (
212
+ provider != null &&
213
+ typeof provider.name === "string" &&
214
+ typeof provider.baseUrl === "string"
215
+ );
216
+ }
217
+
218
+ function resolveProviderProtocol(
219
+ protocol: ProviderProtocol | undefined,
220
+ fallback: ProviderProtocol | undefined,
221
+ ): ProviderProtocol {
222
+ return protocol ?? fallback ?? "openai-chat";
223
+ }
224
+
225
+ function resolveProviderClient(
226
+ client: ProviderClient | undefined,
227
+ protocol: ProviderProtocol,
228
+ fallback: ProviderClient | undefined,
229
+ ): ProviderClient {
230
+ return (
231
+ client ??
232
+ fallback ??
233
+ (protocol === "openai-responses" ? "openai" : "openai-compatible")
234
+ );
235
+ }
236
+
237
+ function toStoredModelInfo(
238
+ modelId: string,
239
+ model: StoredModelEntry | undefined,
240
+ ): ModelInfo {
241
+ const capabilities = new Set<ModelCapability>(model?.capabilities ?? []);
242
+ if (model?.supportsVision) capabilities.add("images");
243
+ if (model?.supportsAttachments) capabilities.add("files");
244
+ if (model?.supportsReasoning) capabilities.add("reasoning");
245
+
246
+ return {
247
+ id: modelId,
248
+ name: model?.name ?? modelId,
249
+ capabilities: capabilities.size > 0 ? [...capabilities] : undefined,
250
+ };
251
+ }
252
+
253
+ function registerCustomModels(
254
+ providerId: string,
255
+ models: StoredProviderEntry["models"] | undefined,
256
+ ): void {
257
+ for (const [modelKey, model] of Object.entries(models ?? {})) {
258
+ const modelId = model.id?.trim() || modelKey.trim();
259
+ if (!modelId) {
260
+ continue;
261
+ }
262
+ LlmsModels.registerModel(
263
+ providerId,
264
+ modelId,
265
+ toStoredModelInfo(modelId, model),
266
+ );
267
+ }
268
+ }
269
+
270
+ function modelInfoWithDefaults(
271
+ modelId: string,
272
+ info: ModelInfo | undefined,
273
+ capabilities: ModelInfo["capabilities"] | undefined,
274
+ ): ModelInfo {
275
+ return {
276
+ ...(info ?? {}),
277
+ id: modelId,
278
+ name: info?.name ?? modelId,
279
+ capabilities: info?.capabilities ?? capabilities,
280
+ };
281
+ }
282
+
283
+ function getGeneratedModelsForProvider(
284
+ providerId: string,
285
+ ): Record<string, ModelInfo> {
286
+ const generated = Object.assign(
287
+ {},
288
+ ...LlmsModels.resolveProviderModelCatalogKeys(providerId).map(
289
+ (catalogKey) => LlmsModels.getGeneratedModelsForProvider(catalogKey),
290
+ ),
291
+ );
292
+ return generated as Record<string, ModelInfo>;
293
+ }
294
+
295
+ export function registerProviderSettingsProvider(
296
+ settings: ProviderSettings,
297
+ ): void {
298
+ const providerId = settings.provider.trim();
299
+ if (!providerId || LlmsModels.isBuiltInProviderId(providerId)) {
300
+ return;
301
+ }
302
+
303
+ const baseUrl = settings.baseUrl?.trim();
304
+ if (!baseUrl) {
305
+ return;
306
+ }
307
+
308
+ const existingCollection = LlmsModels.MODEL_COLLECTIONS_BY_PROVIDER_ID[
309
+ providerId
310
+ ] as LlmsModels.ModelCollection | undefined;
311
+ const generatedModels = getGeneratedModelsForProvider(providerId);
312
+ const modelCapabilities = toModelCapabilities(settings.capabilities);
313
+ const fallbackCapabilities =
314
+ modelCapabilities.length > 0 ? modelCapabilities : undefined;
315
+ const modelId = settings.model?.trim();
316
+ const models: Record<string, ModelInfo> = {
317
+ ...generatedModels,
318
+ ...(existingCollection?.models ?? {}),
319
+ };
320
+
321
+ if (modelId) {
322
+ models[modelId] = modelInfoWithDefaults(
323
+ modelId,
324
+ models[modelId],
325
+ fallbackCapabilities,
326
+ );
327
+ }
328
+
329
+ const modelIds = Object.keys(models).filter(Boolean);
330
+ const defaultModelId = modelId || modelIds[0];
331
+ if (!defaultModelId) {
332
+ return;
333
+ }
334
+ const protocol = resolveProviderProtocol(
335
+ settings.protocol,
336
+ existingCollection?.provider.protocol,
337
+ );
338
+ const client = resolveProviderClient(
339
+ settings.client,
340
+ protocol,
341
+ existingCollection?.provider.client,
342
+ );
343
+
344
+ LlmsModels.registerProvider({
345
+ provider: {
346
+ id: providerId,
347
+ name: existingCollection?.provider.name ?? titleCaseFromId(providerId),
348
+ description: existingCollection?.provider.description,
349
+ protocol,
350
+ client,
351
+ baseUrl,
352
+ defaultModelId,
353
+ capabilities:
354
+ toProviderCapabilities(settings.capabilities) ??
355
+ existingCollection?.provider.capabilities,
356
+ source: "file",
357
+ },
358
+ models,
359
+ });
360
+ }
361
+
362
+ export function registerConfiguredProvidersFromSettings(
363
+ state: StoredProviderSettings,
364
+ ): void {
365
+ for (const entry of Object.values(state.providers)) {
366
+ registerProviderSettingsProvider(entry.settings);
367
+ }
368
+ }
369
+
170
370
  /**
171
371
  * Custom Provider Registry
172
372
  *
@@ -176,20 +376,35 @@ function toModelCapabilities(
176
376
  */
177
377
  export function registerCustomProvider(
178
378
  providerId: string,
179
- entry: StoredModelsFile["providers"][string],
379
+ entry: StoredProviderEntry,
180
380
  ): void {
381
+ const storedModels = entry.models ?? {};
382
+ if (!isCompleteProviderMetadata(entry.provider)) {
383
+ registerCustomModels(providerId, storedModels);
384
+ return;
385
+ }
386
+
181
387
  const modelCapabilities = toModelCapabilities(entry.provider.capabilities);
182
- const modelEntries = Object.values(entry.models)
183
- .map((model) => model.id.trim())
184
- .filter((modelId) => modelId.length > 0);
388
+ const modelEntries = Object.entries(storedModels)
389
+ .map(([modelKey, model]) => ({
390
+ id: model.id?.trim() || modelKey.trim(),
391
+ model,
392
+ }))
393
+ .filter(({ id }) => id.length > 0);
185
394
  const defaultModelId =
186
- entry.provider.defaultModelId?.trim() || modelEntries[0] || "default";
395
+ entry.provider.defaultModelId?.trim() || modelEntries[0]?.id || "default";
396
+ const protocol = resolveProviderProtocol(entry.provider.protocol, undefined);
397
+ const client = resolveProviderClient(
398
+ entry.provider.client,
399
+ protocol,
400
+ undefined,
401
+ );
187
402
  const normalizedModels = Object.fromEntries(
188
- modelEntries.map((modelId) => [
189
- modelId,
403
+ modelEntries.map(({ id, model }) => [
404
+ id,
190
405
  {
191
- id: modelId,
192
- name: entry.models[modelId]?.name ?? modelId,
406
+ id,
407
+ name: model.name ?? id,
193
408
  capabilities:
194
409
  modelCapabilities.length > 0 ? modelCapabilities : undefined,
195
410
  status: "active" as const,
@@ -201,11 +416,12 @@ export function registerCustomProvider(
201
416
  provider: {
202
417
  id: providerId,
203
418
  name: entry.provider.name.trim() || titleCaseFromId(providerId),
204
- protocol: "openai-chat",
205
- client: "openai-compatible",
419
+ protocol,
420
+ client,
206
421
  baseUrl: entry.provider.baseUrl,
207
422
  defaultModelId,
208
423
  capabilities: toProviderCapabilities(entry.provider.capabilities),
424
+ source: "file",
209
425
  },
210
426
  models: normalizedModels,
211
427
  });
@@ -14,7 +14,9 @@ import { loginOcaOAuth } from "../../auth/oca";
14
14
  import { resolveProviderConfig } from "../../llms/provider-defaults";
15
15
  import type {
16
16
  ModelInfo,
17
+ ProviderClient,
17
18
  ProviderConfig,
19
+ ProviderProtocol,
18
20
  ProviderSettings,
19
21
  } from "../../llms/provider-settings";
20
22
  import type { ProviderSettingsManager } from "../storage/provider-settings-manager";
@@ -38,6 +40,8 @@ export interface UpdateLocalProviderRequest {
38
40
  models?: string[];
39
41
  defaultModelId?: string | null;
40
42
  modelsSourceUrl?: string | null;
43
+ protocol?: ProviderProtocol | null;
44
+ client?: ProviderClient | null;
41
45
  capabilities?: ProviderCapability[] | null;
42
46
  }
43
47
 
@@ -256,6 +260,7 @@ function removeProviderFromSettingsState(
256
260
  mutated = true;
257
261
  }
258
262
  if (mutated) manager.write(state);
263
+ LlmsModels.unregisterProvider(providerId);
259
264
  }
260
265
 
261
266
  // --- Public API ---
@@ -338,6 +343,8 @@ export async function addLocalProvider(
338
343
  headers: normalizedHeaders,
339
344
  timeout: request.timeoutMs,
340
345
  model: defaultModelId,
346
+ protocol: request.protocol,
347
+ client: request.client,
341
348
  },
342
349
  { setLastUsed: false },
343
350
  );
@@ -350,6 +357,8 @@ export async function addLocalProvider(
350
357
  name: providerName,
351
358
  baseUrl,
352
359
  defaultModelId,
360
+ protocol: request.protocol,
361
+ client: request.client,
353
362
  capabilities,
354
363
  modelsSourceUrl: sourceUrl,
355
364
  },
@@ -380,11 +389,40 @@ export async function updateLocalProvider(
380
389
 
381
390
  const modelsPath = resolveModelsRegistryPath(manager);
382
391
  const modelsState = await readModelsFile(modelsPath);
383
- const existingEntry = modelsState.providers[providerId];
392
+ let existingEntry = modelsState.providers[providerId];
384
393
  if (!existingEntry) {
385
- throw new Error(`provider "${providerId}" does not exist`);
386
- }
394
+ const existingSettings = manager.getProviderSettings(providerId);
395
+ if (!existingSettings) {
396
+ throw new Error(`provider "${providerId}" does not exist`);
397
+ }
387
398
 
399
+ const seedModelId =
400
+ uniqueTrimmed(request.models)[0] ?? existingSettings.model?.trim();
401
+ if (!seedModelId) {
402
+ throw new Error(
403
+ `provider "${providerId}" cannot be updated because no model is configured`,
404
+ );
405
+ }
406
+
407
+ // Ephemeral seed for the existing update path; final state is computed and written below.
408
+ existingEntry = {
409
+ provider: {
410
+ name: request.name?.trim() || titleCaseFromId(providerId),
411
+ baseUrl:
412
+ request.baseUrl?.trim() ?? existingSettings.baseUrl?.trim() ?? "",
413
+ defaultModelId: seedModelId,
414
+ protocol: existingSettings.protocol,
415
+ client: existingSettings.client,
416
+ capabilities: existingSettings.capabilities,
417
+ },
418
+ models: buildProviderModels([seedModelId], existingSettings.capabilities),
419
+ };
420
+ }
421
+ if (!existingEntry.provider) {
422
+ throw new Error(
423
+ `provider "${providerId}" cannot be updated because it is a model overlay (no provider metadata)`,
424
+ );
425
+ }
388
426
  const providerName =
389
427
  request.name?.trim() ?? existingEntry.provider.name.trim();
390
428
  if (!providerName) throw new Error("name is required");
@@ -399,6 +437,14 @@ export async function updateLocalProvider(
399
437
  : request.capabilities === null
400
438
  ? undefined
401
439
  : [...new Set(request.capabilities)];
440
+ const protocol =
441
+ request.protocol === undefined
442
+ ? existingEntry.provider.protocol
443
+ : (request.protocol ?? undefined);
444
+ const client =
445
+ request.client === undefined
446
+ ? existingEntry.provider.client
447
+ : (request.client ?? undefined);
402
448
 
403
449
  const explicitModels = uniqueTrimmed(request.models);
404
450
  const nextModelsSourceUrl =
@@ -408,7 +454,7 @@ export async function updateLocalProvider(
408
454
  const shouldRecomputeModels =
409
455
  request.models !== undefined ||
410
456
  (request.modelsSourceUrl !== undefined && !!nextModelsSourceUrl);
411
- const existingModelIds = Object.keys(existingEntry.models)
457
+ const existingModelIds = Object.keys(existingEntry.models ?? {})
412
458
  .map((id) => id.trim())
413
459
  .filter(Boolean);
414
460
  const modelIds = await resolveModelIds({
@@ -440,6 +486,10 @@ export async function updateLocalProvider(
440
486
  baseUrl,
441
487
  model: defaultModelId,
442
488
  };
489
+ if (protocol) nextSettings.protocol = protocol;
490
+ else delete nextSettings.protocol;
491
+ if (client) nextSettings.client = client;
492
+ else delete nextSettings.client;
443
493
  if (request.apiKey !== undefined) {
444
494
  const apiKey = request.apiKey?.trim() ?? "";
445
495
  if (apiKey) nextSettings.apiKey = apiKey;
@@ -465,6 +515,8 @@ export async function updateLocalProvider(
465
515
  name: providerName,
466
516
  baseUrl,
467
517
  defaultModelId,
518
+ protocol,
519
+ client,
468
520
  capabilities,
469
521
  modelsSourceUrl: nextModelsSourceUrl,
470
522
  },
@@ -541,6 +593,8 @@ export async function listLocalProviders(
541
593
  : undefined,
542
594
  baseUrl: persistedSettings?.baseUrl ?? info?.baseUrl,
543
595
  defaultModelId: info?.defaultModelId,
596
+ protocol: persistedSettings?.protocol ?? info?.protocol,
597
+ client: persistedSettings?.client ?? info?.client,
544
598
  authDescription: "This provider uses API keys for authentication.",
545
599
  baseUrlDescription: "The base endpoint to use for provider requests.",
546
600
  modelList,
@@ -596,6 +650,9 @@ export function saveLocalProviderSettings(
596
650
  "contextWindow",
597
651
  "timeout",
598
652
  "apiLine",
653
+ "protocol",
654
+ "client",
655
+ "routingProviderId",
599
656
  "capabilities",
600
657
  ] as const) {
601
658
  if (Object.hasOwn(request, key)) next[key] = request[key];
@@ -640,9 +697,12 @@ function toProviderApiKey(
640
697
  providerId: OAuthProviderId,
641
698
  credentials: { access: string },
642
699
  ): string {
643
- return providerId === "cline"
644
- ? `workos:${credentials.access}`
645
- : credentials.access;
700
+ if (providerId === "cline") {
701
+ return credentials.access.startsWith("workos:")
702
+ ? credentials.access
703
+ : `workos:${credentials.access}`;
704
+ }
705
+ return credentials.access;
646
706
  }
647
707
 
648
708
  export async function loginLocalProvider(
@@ -153,22 +153,24 @@ export function withLatestAssistantTurnMetadata(
153
153
  assistantIndexes.push(next.length - 1 - lastAssistantIndex);
154
154
  }
155
155
 
156
- const usage = result.usage;
157
- const lastAssistantIndexForUsage =
158
- assistantIndexes[assistantIndexes.length - 1];
156
+ const lastAssistantIndex = assistantIndexes[assistantIndexes.length - 1];
159
157
  for (const targetIndex of assistantIndexes) {
160
158
  const target = next[targetIndex];
161
- const metrics =
162
- targetIndex === lastAssistantIndexForUsage
163
- ? {
164
- ...(target.metrics ?? {}),
165
- inputTokens: usage.inputTokens,
166
- outputTokens: usage.outputTokens,
167
- cacheReadTokens: usage.cacheReadTokens ?? 0,
168
- cacheWriteTokens: usage.cacheWriteTokens ?? 0,
169
- cost: usage.totalCost,
170
- }
171
- : target.metrics;
159
+ // Use per-turn metrics already stamped on the message if available.
160
+ // Only fall back to result.usage (total run usage) for the terminal
161
+ // assistant message when no per-turn metrics are present, to avoid
162
+ // attaching session totals to intermediate turn messages.
163
+ let metrics = target.metrics;
164
+ if (!metrics && targetIndex === lastAssistantIndex) {
165
+ const usage = result.usage;
166
+ metrics = {
167
+ inputTokens: usage.inputTokens,
168
+ outputTokens: usage.outputTokens,
169
+ cacheReadTokens: usage.cacheReadTokens ?? 0,
170
+ cacheWriteTokens: usage.cacheWriteTokens ?? 0,
171
+ cost: usage.totalCost,
172
+ };
173
+ }
172
174
  next[targetIndex] = {
173
175
  ...normalizeStoredMessageModelMetadata(target, {
174
176
  id: result.model.id,
@@ -1,7 +1,6 @@
1
1
  import type { ITelemetryService } from "@clinebot/shared";
2
2
  import { resolveDocumentsExtensionPath } from "@clinebot/shared/storage";
3
3
  import { listHookConfigFiles } from "../extensions/config/hooks-config-loader";
4
- import type { SessionSource } from "../types/common";
5
4
  import type { CoreSessionConfig } from "../types/config";
6
5
  import {
7
6
  captureHookDiscovery,
@@ -14,10 +13,15 @@ import {
14
13
  } from "./telemetry/core-events";
15
14
  import type { enrichPromptWithMentions } from "./workspace";
16
15
 
16
+ /**
17
+ * Emits local-only session creation telemetry (task.created/restarted and
18
+ * hook discovery). The transport-agnostic `session.started` event is
19
+ * emitted from `ClineCore.start` so it fires for every backend (local,
20
+ * hub, remote) at the outer API boundary.
21
+ */
17
22
  export function emitSessionCreationTelemetry(
18
23
  config: CoreSessionConfig,
19
24
  sessionId: string,
20
- source: SessionSource,
21
25
  isRestart: boolean,
22
26
  workspacePath: string,
23
27
  agentIdentity?: Partial<TelemetryAgentIdentityProperties>,
@@ -36,19 +40,6 @@ export function emitSessionCreationTelemetry(
36
40
  });
37
41
  }
38
42
  captureHookDiscoveryTelemetry(config.telemetry, { workspacePath });
39
- config.telemetry?.capture({
40
- event: "session.started",
41
- properties: {
42
- sessionId,
43
- source,
44
- providerId: config.providerId,
45
- modelId: config.modelId,
46
- enableTools: config.enableTools,
47
- enableSpawnAgent: config.enableSpawnAgent,
48
- enableAgentTeams: config.enableAgentTeams,
49
- ...agentIdentity,
50
- },
51
- });
52
43
  }
53
44
 
54
45
  export function captureHookDiscoveryTelemetry(
@@ -138,16 +138,12 @@ export class FileTeamStore implements TeamStore {
138
138
 
139
139
  loadRuntime(teamName: string): TeamRuntimeLoadResult {
140
140
  const envelope = this.readEnvelope(teamName);
141
- const interruptedRunIds = this.markInProgressRunsInterrupted(
142
- teamName,
143
- "runtime_recovered",
144
- );
145
141
  return {
146
142
  state: envelope?.teamState
147
143
  ? reviveTeamRuntimeStateDates(envelope.teamState)
148
144
  : undefined,
149
145
  teammates: envelope?.teammates ?? [],
150
- interruptedRunIds,
146
+ interruptedRunIds: [],
151
147
  };
152
148
  }
153
149