@clinebot/core 0.0.22 → 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.
Files changed (260) hide show
  1. package/dist/ClineCore.d.ts +110 -0
  2. package/dist/ClineCore.d.ts.map +1 -0
  3. package/dist/account/cline-account-service.d.ts +2 -1
  4. package/dist/account/cline-account-service.d.ts.map +1 -1
  5. package/dist/account/index.d.ts +1 -1
  6. package/dist/account/index.d.ts.map +1 -1
  7. package/dist/account/rpc.d.ts +3 -1
  8. package/dist/account/rpc.d.ts.map +1 -1
  9. package/dist/account/types.d.ts +3 -0
  10. package/dist/account/types.d.ts.map +1 -1
  11. package/dist/agents/plugin-loader.d.ts.map +1 -1
  12. package/dist/agents/plugin-sandbox-bootstrap.js +17 -17
  13. package/dist/auth/client.d.ts +1 -1
  14. package/dist/auth/client.d.ts.map +1 -1
  15. package/dist/auth/cline.d.ts +1 -1
  16. package/dist/auth/cline.d.ts.map +1 -1
  17. package/dist/auth/codex.d.ts +1 -1
  18. package/dist/auth/codex.d.ts.map +1 -1
  19. package/dist/auth/oca.d.ts +1 -1
  20. package/dist/auth/oca.d.ts.map +1 -1
  21. package/dist/auth/utils.d.ts +2 -2
  22. package/dist/auth/utils.d.ts.map +1 -1
  23. package/dist/index.d.ts +50 -5
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +949 -0
  26. package/dist/providers/local-provider-service.d.ts +4 -4
  27. package/dist/providers/local-provider-service.d.ts.map +1 -1
  28. package/dist/runtime/runtime-builder.d.ts +1 -0
  29. package/dist/runtime/runtime-builder.d.ts.map +1 -1
  30. package/dist/runtime/session-runtime.d.ts +2 -1
  31. package/dist/runtime/session-runtime.d.ts.map +1 -1
  32. package/dist/runtime/team-runtime-registry.d.ts +13 -0
  33. package/dist/runtime/team-runtime-registry.d.ts.map +1 -0
  34. package/dist/session/default-session-manager.d.ts +2 -2
  35. package/dist/session/default-session-manager.d.ts.map +1 -1
  36. package/dist/session/rpc-runtime-ensure.d.ts +53 -0
  37. package/dist/session/rpc-runtime-ensure.d.ts.map +1 -0
  38. package/dist/session/session-config-builder.d.ts +2 -3
  39. package/dist/session/session-config-builder.d.ts.map +1 -1
  40. package/dist/session/session-host.d.ts +8 -18
  41. package/dist/session/session-host.d.ts.map +1 -1
  42. package/dist/session/session-manager.d.ts +1 -1
  43. package/dist/session/session-manager.d.ts.map +1 -1
  44. package/dist/session/session-manifest.d.ts +1 -2
  45. package/dist/session/session-manifest.d.ts.map +1 -1
  46. package/dist/session/unified-session-persistence-service.d.ts +2 -2
  47. package/dist/session/unified-session-persistence-service.d.ts.map +1 -1
  48. package/dist/session/utils/helpers.d.ts +1 -1
  49. package/dist/session/utils/helpers.d.ts.map +1 -1
  50. package/dist/session/utils/types.d.ts +1 -1
  51. package/dist/session/utils/types.d.ts.map +1 -1
  52. package/dist/storage/provider-settings-legacy-migration.d.ts.map +1 -1
  53. package/dist/telemetry/OpenTelemetryProvider.d.ts.map +1 -1
  54. package/dist/telemetry/distinct-id.d.ts +2 -0
  55. package/dist/telemetry/distinct-id.d.ts.map +1 -0
  56. package/dist/telemetry/{opentelemetry.d.ts → index.d.ts} +1 -1
  57. package/dist/telemetry/index.d.ts.map +1 -0
  58. package/dist/telemetry/index.js +28 -0
  59. package/dist/tools/constants.d.ts +1 -1
  60. package/dist/tools/constants.d.ts.map +1 -1
  61. package/dist/tools/definitions.d.ts +3 -3
  62. package/dist/tools/definitions.d.ts.map +1 -1
  63. package/dist/tools/executors/apply-patch.d.ts +1 -1
  64. package/dist/tools/executors/apply-patch.d.ts.map +1 -1
  65. package/dist/tools/executors/bash.d.ts +1 -1
  66. package/dist/tools/executors/bash.d.ts.map +1 -1
  67. package/dist/tools/executors/editor.d.ts +1 -1
  68. package/dist/tools/executors/editor.d.ts.map +1 -1
  69. package/dist/tools/executors/file-read.d.ts +1 -1
  70. package/dist/tools/executors/file-read.d.ts.map +1 -1
  71. package/dist/tools/executors/index.d.ts +14 -14
  72. package/dist/tools/executors/index.d.ts.map +1 -1
  73. package/dist/tools/executors/search.d.ts +1 -1
  74. package/dist/tools/executors/search.d.ts.map +1 -1
  75. package/dist/tools/executors/web-fetch.d.ts +1 -1
  76. package/dist/tools/executors/web-fetch.d.ts.map +1 -1
  77. package/dist/tools/helpers.d.ts +1 -1
  78. package/dist/tools/helpers.d.ts.map +1 -1
  79. package/dist/tools/index.d.ts +10 -10
  80. package/dist/tools/index.d.ts.map +1 -1
  81. package/dist/tools/model-tool-routing.d.ts +1 -1
  82. package/dist/tools/model-tool-routing.d.ts.map +1 -1
  83. package/dist/tools/presets.d.ts +1 -1
  84. package/dist/tools/presets.d.ts.map +1 -1
  85. package/dist/types/common.d.ts +17 -8
  86. package/dist/types/common.d.ts.map +1 -1
  87. package/dist/types/config.d.ts +4 -3
  88. package/dist/types/config.d.ts.map +1 -1
  89. package/dist/types/provider-settings.d.ts +1 -1
  90. package/dist/types/provider-settings.d.ts.map +1 -1
  91. package/dist/types.d.ts +5 -2
  92. package/dist/types.d.ts.map +1 -1
  93. package/dist/version.d.ts +2 -0
  94. package/dist/version.d.ts.map +1 -0
  95. package/package.json +44 -38
  96. package/src/ClineCore.ts +137 -0
  97. package/src/account/cline-account-service.test.ts +101 -0
  98. package/src/account/cline-account-service.ts +300 -0
  99. package/src/account/featurebase-token.test.ts +175 -0
  100. package/src/account/index.ts +23 -0
  101. package/src/account/rpc.test.ts +63 -0
  102. package/src/account/rpc.ts +185 -0
  103. package/src/account/types.ts +102 -0
  104. package/src/agents/agent-config-loader.test.ts +236 -0
  105. package/src/agents/agent-config-loader.ts +108 -0
  106. package/src/agents/agent-config-parser.ts +198 -0
  107. package/src/agents/hooks-config-loader.test.ts +20 -0
  108. package/src/agents/hooks-config-loader.ts +118 -0
  109. package/src/agents/index.ts +85 -0
  110. package/src/agents/plugin-config-loader.test.ts +140 -0
  111. package/src/agents/plugin-config-loader.ts +97 -0
  112. package/src/agents/plugin-loader.test.ts +210 -0
  113. package/src/agents/plugin-loader.ts +175 -0
  114. package/src/agents/plugin-sandbox-bootstrap.ts +448 -0
  115. package/src/agents/plugin-sandbox.test.ts +296 -0
  116. package/src/agents/plugin-sandbox.ts +341 -0
  117. package/src/agents/unified-config-file-watcher.test.ts +196 -0
  118. package/src/agents/unified-config-file-watcher.ts +483 -0
  119. package/src/agents/user-instruction-config-loader.test.ts +158 -0
  120. package/src/agents/user-instruction-config-loader.ts +438 -0
  121. package/src/auth/client.test.ts +40 -0
  122. package/src/auth/client.ts +25 -0
  123. package/src/auth/cline.test.ts +130 -0
  124. package/src/auth/cline.ts +420 -0
  125. package/src/auth/codex.test.ts +170 -0
  126. package/src/auth/codex.ts +491 -0
  127. package/src/auth/oca.test.ts +215 -0
  128. package/src/auth/oca.ts +573 -0
  129. package/src/auth/server.ts +216 -0
  130. package/src/auth/types.ts +81 -0
  131. package/src/auth/utils.test.ts +128 -0
  132. package/src/auth/utils.ts +247 -0
  133. package/src/chat/chat-schema.ts +82 -0
  134. package/src/index.ts +479 -0
  135. package/src/input/file-indexer.d.ts +11 -0
  136. package/src/input/file-indexer.test.ts +127 -0
  137. package/src/input/file-indexer.ts +327 -0
  138. package/src/input/index.ts +7 -0
  139. package/src/input/mention-enricher.test.ts +85 -0
  140. package/src/input/mention-enricher.ts +122 -0
  141. package/src/mcp/config-loader.test.ts +238 -0
  142. package/src/mcp/config-loader.ts +219 -0
  143. package/src/mcp/index.ts +26 -0
  144. package/src/mcp/manager.test.ts +106 -0
  145. package/src/mcp/manager.ts +262 -0
  146. package/src/mcp/types.ts +88 -0
  147. package/src/providers/local-provider-registry.ts +232 -0
  148. package/src/providers/local-provider-service.test.ts +783 -0
  149. package/src/providers/local-provider-service.ts +471 -0
  150. package/src/runtime/commands.test.ts +98 -0
  151. package/src/runtime/commands.ts +83 -0
  152. package/src/runtime/hook-file-hooks.test.ts +237 -0
  153. package/src/runtime/hook-file-hooks.ts +859 -0
  154. package/src/runtime/index.ts +37 -0
  155. package/src/runtime/rules.ts +34 -0
  156. package/src/runtime/runtime-builder.team-persistence.test.ts +245 -0
  157. package/src/runtime/runtime-builder.test.ts +371 -0
  158. package/src/runtime/runtime-builder.ts +631 -0
  159. package/src/runtime/runtime-parity.test.ts +143 -0
  160. package/src/runtime/sandbox/subprocess-sandbox.ts +231 -0
  161. package/src/runtime/session-runtime.ts +49 -0
  162. package/src/runtime/skills.ts +44 -0
  163. package/src/runtime/team-runtime-registry.ts +46 -0
  164. package/src/runtime/tool-approval.ts +104 -0
  165. package/src/runtime/workflows.test.ts +119 -0
  166. package/src/runtime/workflows.ts +45 -0
  167. package/src/session/default-session-manager.e2e.test.ts +384 -0
  168. package/src/session/default-session-manager.test.ts +1931 -0
  169. package/src/session/default-session-manager.ts +1422 -0
  170. package/src/session/file-session-service.ts +280 -0
  171. package/src/session/index.ts +45 -0
  172. package/src/session/rpc-runtime-ensure.ts +521 -0
  173. package/src/session/rpc-session-service.ts +107 -0
  174. package/src/session/rpc-spawn-lease.test.ts +49 -0
  175. package/src/session/rpc-spawn-lease.ts +122 -0
  176. package/src/session/runtime-oauth-token-manager.test.ts +137 -0
  177. package/src/session/runtime-oauth-token-manager.ts +272 -0
  178. package/src/session/session-agent-events.ts +248 -0
  179. package/src/session/session-artifacts.ts +106 -0
  180. package/src/session/session-config-builder.ts +113 -0
  181. package/src/session/session-graph.ts +92 -0
  182. package/src/session/session-host.test.ts +89 -0
  183. package/src/session/session-host.ts +205 -0
  184. package/src/session/session-manager.ts +69 -0
  185. package/src/session/session-manifest.ts +29 -0
  186. package/src/session/session-service.team-persistence.test.ts +48 -0
  187. package/src/session/session-service.ts +673 -0
  188. package/src/session/session-team-coordination.ts +229 -0
  189. package/src/session/session-telemetry.ts +100 -0
  190. package/src/session/sqlite-rpc-session-backend.ts +303 -0
  191. package/src/session/unified-session-persistence-service.test.ts +85 -0
  192. package/src/session/unified-session-persistence-service.ts +994 -0
  193. package/src/session/utils/helpers.ts +139 -0
  194. package/src/session/utils/types.ts +57 -0
  195. package/src/session/utils/usage.ts +32 -0
  196. package/src/session/workspace-manager.ts +98 -0
  197. package/src/session/workspace-manifest.ts +100 -0
  198. package/src/storage/artifact-store.ts +1 -0
  199. package/src/storage/file-team-store.ts +257 -0
  200. package/src/storage/index.ts +11 -0
  201. package/src/storage/provider-settings-legacy-migration.test.ts +424 -0
  202. package/src/storage/provider-settings-legacy-migration.ts +826 -0
  203. package/src/storage/provider-settings-manager.test.ts +191 -0
  204. package/src/storage/provider-settings-manager.ts +152 -0
  205. package/src/storage/session-store.ts +1 -0
  206. package/src/storage/sqlite-session-store.ts +275 -0
  207. package/src/storage/sqlite-team-store.ts +454 -0
  208. package/src/storage/team-store.ts +40 -0
  209. package/src/team/index.ts +4 -0
  210. package/src/team/projections.ts +285 -0
  211. package/src/telemetry/ITelemetryAdapter.ts +94 -0
  212. package/src/telemetry/LoggerTelemetryAdapter.test.ts +42 -0
  213. package/src/telemetry/LoggerTelemetryAdapter.ts +114 -0
  214. package/src/telemetry/OpenTelemetryAdapter.test.ts +157 -0
  215. package/src/telemetry/OpenTelemetryAdapter.ts +348 -0
  216. package/src/telemetry/OpenTelemetryProvider.test.ts +113 -0
  217. package/src/telemetry/OpenTelemetryProvider.ts +325 -0
  218. package/src/telemetry/TelemetryService.test.ts +134 -0
  219. package/src/telemetry/TelemetryService.ts +141 -0
  220. package/src/telemetry/core-events.ts +400 -0
  221. package/src/telemetry/distinct-id.test.ts +57 -0
  222. package/src/telemetry/distinct-id.ts +58 -0
  223. package/src/telemetry/index.ts +20 -0
  224. package/src/tools/constants.ts +35 -0
  225. package/src/tools/definitions.test.ts +704 -0
  226. package/src/tools/definitions.ts +709 -0
  227. package/src/tools/executors/apply-patch-parser.ts +520 -0
  228. package/src/tools/executors/apply-patch.ts +359 -0
  229. package/src/tools/executors/bash.test.ts +87 -0
  230. package/src/tools/executors/bash.ts +207 -0
  231. package/src/tools/executors/editor.test.ts +35 -0
  232. package/src/tools/executors/editor.ts +219 -0
  233. package/src/tools/executors/file-read.test.ts +49 -0
  234. package/src/tools/executors/file-read.ts +110 -0
  235. package/src/tools/executors/index.ts +87 -0
  236. package/src/tools/executors/search.ts +278 -0
  237. package/src/tools/executors/web-fetch.ts +259 -0
  238. package/src/tools/helpers.ts +130 -0
  239. package/src/tools/index.ts +169 -0
  240. package/src/tools/model-tool-routing.test.ts +86 -0
  241. package/src/tools/model-tool-routing.ts +132 -0
  242. package/src/tools/presets.test.ts +62 -0
  243. package/src/tools/presets.ts +168 -0
  244. package/src/tools/schemas.ts +327 -0
  245. package/src/tools/types.ts +329 -0
  246. package/src/types/common.ts +26 -0
  247. package/src/types/config.ts +86 -0
  248. package/src/types/events.ts +74 -0
  249. package/src/types/index.ts +24 -0
  250. package/src/types/provider-settings.ts +43 -0
  251. package/src/types/sessions.ts +16 -0
  252. package/src/types/storage.ts +64 -0
  253. package/src/types/workspace.ts +7 -0
  254. package/src/types.ts +132 -0
  255. package/src/version.ts +3 -0
  256. package/dist/index.node.d.ts +0 -47
  257. package/dist/index.node.d.ts.map +0 -1
  258. package/dist/index.node.js +0 -948
  259. package/dist/telemetry/opentelemetry.d.ts.map +0 -1
  260. package/dist/telemetry/opentelemetry.js +0 -27
@@ -0,0 +1,325 @@
1
+ import type {
2
+ BasicLogger,
3
+ ITelemetryService,
4
+ OpenTelemetryClientConfig,
5
+ TelemetryMetadata,
6
+ } from "@clinebot/shared";
7
+ import { metrics } from "@opentelemetry/api";
8
+ import { logs } from "@opentelemetry/api-logs";
9
+ import { OTLPLogExporter as OTLPLogExporterHttp } from "@opentelemetry/exporter-logs-otlp-http";
10
+ import { OTLPMetricExporter as OTLPMetricExporterHttp } from "@opentelemetry/exporter-metrics-otlp-http";
11
+ import { Resource } from "@opentelemetry/resources";
12
+ import {
13
+ BatchLogRecordProcessor,
14
+ ConsoleLogRecordExporter,
15
+ LoggerProvider,
16
+ type LogRecordExporter,
17
+ } from "@opentelemetry/sdk-logs";
18
+ import {
19
+ ConsoleMetricExporter,
20
+ MeterProvider,
21
+ type MetricReader,
22
+ PeriodicExportingMetricReader,
23
+ } from "@opentelemetry/sdk-metrics";
24
+ import {
25
+ ATTR_SERVICE_NAME,
26
+ ATTR_SERVICE_VERSION,
27
+ } from "@opentelemetry/semantic-conventions";
28
+ import { resolveCoreDistinctId } from "./distinct-id";
29
+ import {
30
+ OpenTelemetryAdapter,
31
+ type OpenTelemetryAdapterOptions,
32
+ } from "./OpenTelemetryAdapter";
33
+ import { TelemetryService } from "./TelemetryService";
34
+
35
+ type OpenTelemetryExporterKind = "console" | "otlp";
36
+ type OpenTelemetryProtocol = "http/json";
37
+
38
+ export interface OpenTelemetryProviderOptions
39
+ extends Omit<
40
+ OpenTelemetryClientConfig,
41
+ "enabled" | "logsExporter" | "metricsExporter"
42
+ > {
43
+ serviceName?: string;
44
+ serviceVersion?: string;
45
+ enabled?: boolean;
46
+ logsExporter?: string | OpenTelemetryExporterKind[];
47
+ metricsExporter?: string | OpenTelemetryExporterKind[];
48
+ metricExportIntervalMs?: number;
49
+ logMaxQueueSize?: number;
50
+ logBatchSize?: number;
51
+ logBatchTimeoutMs?: number;
52
+ }
53
+
54
+ export interface CreateOpenTelemetryTelemetryServiceOptions
55
+ extends OpenTelemetryProviderOptions,
56
+ Pick<
57
+ OpenTelemetryAdapterOptions,
58
+ "name" | "distinctId" | "commonProperties"
59
+ > {
60
+ metadata: TelemetryMetadata;
61
+ logger?: BasicLogger;
62
+ }
63
+
64
+ export class OpenTelemetryProvider {
65
+ readonly meterProvider: MeterProvider | null;
66
+ readonly loggerProvider: LoggerProvider | null;
67
+ private readonly options: OpenTelemetryProviderOptions;
68
+
69
+ constructor(options: OpenTelemetryProviderOptions = {}) {
70
+ this.options = options;
71
+ const resource = new Resource({
72
+ [ATTR_SERVICE_NAME]: options.serviceName ?? "cline",
73
+ ...(options.serviceVersion
74
+ ? { [ATTR_SERVICE_VERSION]: options.serviceVersion }
75
+ : {}),
76
+ });
77
+
78
+ this.meterProvider = this.createMeterProvider(resource);
79
+ this.loggerProvider = this.createLoggerProvider(resource);
80
+
81
+ if (this.meterProvider) {
82
+ metrics.setGlobalMeterProvider(this.meterProvider);
83
+ }
84
+ if (this.loggerProvider) {
85
+ logs.setGlobalLoggerProvider(this.loggerProvider);
86
+ }
87
+ }
88
+
89
+ createAdapter(
90
+ options: Omit<
91
+ OpenTelemetryAdapterOptions,
92
+ "meterProvider" | "loggerProvider"
93
+ >,
94
+ ): OpenTelemetryAdapter {
95
+ return new OpenTelemetryAdapter({
96
+ ...options,
97
+ meterProvider: this.meterProvider,
98
+ loggerProvider: this.loggerProvider,
99
+ });
100
+ }
101
+
102
+ createTelemetryService(
103
+ options: Omit<
104
+ CreateOpenTelemetryTelemetryServiceOptions,
105
+ keyof OpenTelemetryProviderOptions
106
+ >,
107
+ ): ITelemetryService {
108
+ const adapter = this.createAdapter({
109
+ name: options.name,
110
+ enabled: this.options.enabled,
111
+ metadata: options.metadata,
112
+ });
113
+ return new TelemetryService({
114
+ adapters: [adapter],
115
+ distinctId: resolveCoreDistinctId(options.distinctId),
116
+ commonProperties: options.commonProperties,
117
+ logger: options.logger,
118
+ });
119
+ }
120
+
121
+ async forceFlush(): Promise<void> {
122
+ await Promise.all([
123
+ this.meterProvider?.forceFlush?.(),
124
+ this.loggerProvider?.forceFlush?.(),
125
+ ]);
126
+ }
127
+
128
+ async dispose(): Promise<void> {
129
+ await Promise.all([
130
+ this.meterProvider?.shutdown?.(),
131
+ this.loggerProvider?.shutdown?.(),
132
+ ]);
133
+ }
134
+
135
+ private createMeterProvider(resource: Resource): MeterProvider | null {
136
+ const exporters = normalizeExporters(this.options.metricsExporter);
137
+ if (exporters.length === 0) {
138
+ return null;
139
+ }
140
+
141
+ const interval = Math.max(
142
+ 1_000,
143
+ this.options.metricExportIntervalMs ??
144
+ this.options.metricExportInterval ??
145
+ 60_000,
146
+ );
147
+ const timeout = Math.min(30_000, Math.floor(interval * 0.8));
148
+ const readers = exporters
149
+ .map((exporter) =>
150
+ createMetricReader(exporter, {
151
+ endpoint: this.options.otlpEndpoint,
152
+ headers: this.options.otlpHeaders,
153
+ insecure: this.options.otlpInsecure ?? false,
154
+ protocol: "http/json",
155
+ interval,
156
+ timeout,
157
+ }),
158
+ )
159
+ .filter((reader): reader is MetricReader => reader !== null);
160
+
161
+ if (readers.length === 0) {
162
+ return null;
163
+ }
164
+
165
+ return new MeterProvider({
166
+ resource,
167
+ readers,
168
+ });
169
+ }
170
+
171
+ private createLoggerProvider(resource: Resource): LoggerProvider | null {
172
+ const exporters = normalizeExporters(this.options.logsExporter);
173
+ if (exporters.length === 0) {
174
+ return null;
175
+ }
176
+
177
+ const provider = new LoggerProvider({ resource });
178
+ for (const exporter of exporters) {
179
+ const logExporter = createLogExporter(exporter, {
180
+ endpoint: this.options.otlpEndpoint,
181
+ headers: this.options.otlpHeaders,
182
+ insecure: this.options.otlpInsecure ?? false,
183
+ protocol: "http/json",
184
+ });
185
+ if (!logExporter) {
186
+ continue;
187
+ }
188
+ provider.addLogRecordProcessor(
189
+ new BatchLogRecordProcessor(logExporter, {
190
+ maxQueueSize: this.options.logMaxQueueSize ?? 2048,
191
+ maxExportBatchSize: this.options.logBatchSize ?? 512,
192
+ scheduledDelayMillis:
193
+ this.options.logBatchTimeoutMs ??
194
+ this.options.logBatchTimeout ??
195
+ 5000,
196
+ }),
197
+ );
198
+ }
199
+ return provider;
200
+ }
201
+ }
202
+
203
+ export function createOpenTelemetryTelemetryService(
204
+ options: CreateOpenTelemetryTelemetryServiceOptions,
205
+ ): { provider: OpenTelemetryProvider; telemetry: ITelemetryService } {
206
+ const provider = new OpenTelemetryProvider(options);
207
+ const telemetry = provider.createTelemetryService(options);
208
+ telemetry.captureRequired("telemetry.provider_created", {
209
+ provider: "opentelemetry",
210
+ enabled: options.enabled ?? true,
211
+ logsExporter: Array.isArray(options.logsExporter)
212
+ ? options.logsExporter.join(",")
213
+ : options.logsExporter,
214
+ metricsExporter: Array.isArray(options.metricsExporter)
215
+ ? options.metricsExporter.join(",")
216
+ : options.metricsExporter,
217
+ otlpProtocol: options.otlpProtocol,
218
+ hasOtlpEndpoint: Boolean(options.otlpEndpoint),
219
+ serviceName: options.serviceName,
220
+ serviceVersion: options.serviceVersion,
221
+ });
222
+ return {
223
+ provider,
224
+ telemetry,
225
+ };
226
+ }
227
+
228
+ export function createConfiguredTelemetryService(
229
+ options: CreateOpenTelemetryTelemetryServiceOptions,
230
+ ): {
231
+ provider?: OpenTelemetryProvider;
232
+ telemetry: ITelemetryService;
233
+ } {
234
+ if (options.enabled !== true) {
235
+ return {
236
+ telemetry: new TelemetryService({
237
+ distinctId: resolveCoreDistinctId(options.distinctId),
238
+ }),
239
+ };
240
+ }
241
+
242
+ return createOpenTelemetryTelemetryService(options);
243
+ }
244
+
245
+ function normalizeExporters(
246
+ exporters: OpenTelemetryProviderOptions["logsExporter"],
247
+ ): OpenTelemetryExporterKind[] {
248
+ if (!exporters) {
249
+ return [];
250
+ }
251
+ const values = Array.isArray(exporters) ? exporters : exporters.split(",");
252
+ return values
253
+ .map((value) => value.trim())
254
+ .filter(
255
+ (value): value is OpenTelemetryExporterKind =>
256
+ value === "console" || value === "otlp",
257
+ );
258
+ }
259
+
260
+ function createLogExporter(
261
+ exporter: OpenTelemetryExporterKind,
262
+ options: {
263
+ endpoint?: string;
264
+ headers?: Record<string, string>;
265
+ insecure: boolean;
266
+ protocol: OpenTelemetryProtocol;
267
+ },
268
+ ): LogRecordExporter | null {
269
+ if (exporter === "console") {
270
+ return new ConsoleLogRecordExporter();
271
+ }
272
+ if (!options.endpoint) {
273
+ return null;
274
+ }
275
+
276
+ const endpoint = ensurePathSuffix(options.endpoint, "/v1/logs");
277
+ return new OTLPLogExporterHttp({
278
+ url: endpoint,
279
+ headers: options.headers,
280
+ });
281
+ }
282
+
283
+ function createMetricReader(
284
+ exporter: OpenTelemetryExporterKind,
285
+ options: {
286
+ endpoint?: string;
287
+ headers?: Record<string, string>;
288
+ insecure: boolean;
289
+ protocol: OpenTelemetryProtocol;
290
+ interval: number;
291
+ timeout: number;
292
+ },
293
+ ): MetricReader | null {
294
+ if (exporter === "console") {
295
+ return new PeriodicExportingMetricReader({
296
+ exporter: new ConsoleMetricExporter(),
297
+ exportIntervalMillis: options.interval,
298
+ exportTimeoutMillis: options.timeout,
299
+ });
300
+ }
301
+ if (!options.endpoint) {
302
+ return null;
303
+ }
304
+
305
+ const endpoint = ensurePathSuffix(options.endpoint, "/v1/metrics");
306
+ return new PeriodicExportingMetricReader({
307
+ exporter: new OTLPMetricExporterHttp({
308
+ url: endpoint,
309
+ headers: options.headers,
310
+ }),
311
+ exportIntervalMillis: options.interval,
312
+ exportTimeoutMillis: options.timeout,
313
+ });
314
+ }
315
+
316
+ function ensurePathSuffix(endpoint: string, suffix: string): string {
317
+ const url = new URL(endpoint);
318
+ const normalized = url.pathname.endsWith("/")
319
+ ? url.pathname.slice(0, -1)
320
+ : url.pathname;
321
+ url.pathname = normalized.endsWith(suffix)
322
+ ? normalized
323
+ : `${normalized}${suffix}`;
324
+ return url.toString();
325
+ }
@@ -0,0 +1,134 @@
1
+ import type { BasicLogger } from "@clinebot/shared";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import type { ITelemetryAdapter } from "./ITelemetryAdapter";
4
+ import { TelemetryService } from "./TelemetryService";
5
+
6
+ describe("TelemetryService", () => {
7
+ it("merges metadata and forwards calls to adapters", async () => {
8
+ const { adapter, emit, recordCounter } = createAdapter();
9
+ const service = new TelemetryService({
10
+ adapters: [adapter],
11
+ metadata: {
12
+ extension_version: "1.2.3",
13
+ cline_type: "cli",
14
+ },
15
+ distinctId: "distinct-1",
16
+ commonProperties: {
17
+ organization_id: "org-1",
18
+ },
19
+ });
20
+
21
+ service.capture({
22
+ event: "session.started",
23
+ properties: { sessionId: "session-1" },
24
+ });
25
+ service.recordCounter("cline.session.starts.total", 1, {
26
+ sessionId: "session-1",
27
+ });
28
+ await service.flush();
29
+ await service.dispose();
30
+
31
+ expect(emit).toHaveBeenCalledWith(
32
+ "session.started",
33
+ expect.objectContaining({
34
+ sessionId: "session-1",
35
+ organization_id: "org-1",
36
+ extension_version: "1.2.3",
37
+ cline_type: "cli",
38
+ distinct_id: "distinct-1",
39
+ }),
40
+ );
41
+ expect(recordCounter).toHaveBeenCalledWith(
42
+ "cline.session.starts.total",
43
+ 1,
44
+ expect.objectContaining({
45
+ sessionId: "session-1",
46
+ distinct_id: "distinct-1",
47
+ }),
48
+ undefined,
49
+ false,
50
+ );
51
+ });
52
+
53
+ it("mirrors telemetry events into the logger when provided", () => {
54
+ const logger: BasicLogger = {
55
+ debug: vi.fn(),
56
+ info: vi.fn(),
57
+ warn: vi.fn(),
58
+ error: vi.fn(),
59
+ };
60
+ const service = new TelemetryService({
61
+ logger,
62
+ metadata: {
63
+ extension_version: "1.2.3",
64
+ cline_type: "cli",
65
+ },
66
+ distinctId: "distinct-1",
67
+ });
68
+
69
+ service.capture({
70
+ event: "session.started",
71
+ properties: { sessionId: "session-1" },
72
+ });
73
+ service.captureRequired("user.opt_out", { reason: "manual" });
74
+ service.recordCounter("cline.session.starts.total", 1, {
75
+ sessionId: "session-1",
76
+ });
77
+
78
+ expect(logger.info).toHaveBeenCalledWith(
79
+ "telemetry.event",
80
+ expect.objectContaining({
81
+ adapter: "LoggerTelemetryAdapter",
82
+ event: "session.started",
83
+ properties: expect.objectContaining({
84
+ sessionId: "session-1",
85
+ extension_version: "1.2.3",
86
+ distinct_id: "distinct-1",
87
+ }),
88
+ }),
89
+ );
90
+ expect(logger.warn).toHaveBeenCalledWith(
91
+ "telemetry.required_event",
92
+ expect.objectContaining({
93
+ adapter: "LoggerTelemetryAdapter",
94
+ event: "user.opt_out",
95
+ properties: expect.objectContaining({
96
+ reason: "manual",
97
+ extension_version: "1.2.3",
98
+ }),
99
+ }),
100
+ );
101
+ expect(logger.debug).toHaveBeenCalledWith(
102
+ "telemetry.metric",
103
+ expect.objectContaining({
104
+ adapter: "LoggerTelemetryAdapter",
105
+ instrument: "counter",
106
+ name: "cline.session.starts.total",
107
+ }),
108
+ );
109
+ });
110
+ });
111
+
112
+ function createAdapter(): {
113
+ adapter: ITelemetryAdapter;
114
+ emit: ReturnType<typeof vi.fn>;
115
+ recordCounter: ReturnType<typeof vi.fn>;
116
+ } {
117
+ const emit = vi.fn();
118
+ const recordCounter = vi.fn();
119
+ return {
120
+ adapter: {
121
+ name: "test",
122
+ emit,
123
+ emitRequired: vi.fn(),
124
+ recordCounter,
125
+ recordHistogram: vi.fn(),
126
+ recordGauge: vi.fn(),
127
+ isEnabled: vi.fn(() => true),
128
+ flush: vi.fn().mockResolvedValue(undefined),
129
+ dispose: vi.fn().mockResolvedValue(undefined),
130
+ },
131
+ emit,
132
+ recordCounter,
133
+ };
134
+ }
@@ -0,0 +1,141 @@
1
+ import type {
2
+ BasicLogger,
3
+ ITelemetryService,
4
+ TelemetryMetadata,
5
+ TelemetryProperties,
6
+ } from "@clinebot/shared";
7
+ import type { ITelemetryAdapter } from "./ITelemetryAdapter";
8
+ import { LoggerTelemetryAdapter } from "./LoggerTelemetryAdapter";
9
+
10
+ export interface TelemetryServiceOptions {
11
+ adapters?: ITelemetryAdapter[];
12
+ metadata?: Partial<TelemetryMetadata>;
13
+ distinctId?: string;
14
+ commonProperties?: TelemetryProperties;
15
+ logger?: BasicLogger;
16
+ }
17
+
18
+ export class TelemetryService implements ITelemetryService {
19
+ private adapters: ITelemetryAdapter[];
20
+ private metadata: Partial<TelemetryMetadata>;
21
+ private distinctId?: string;
22
+ private commonProperties: TelemetryProperties;
23
+
24
+ constructor(options: TelemetryServiceOptions = {}) {
25
+ this.adapters = [...(options.adapters ?? [])];
26
+ if (options.logger) {
27
+ this.adapters.push(
28
+ new LoggerTelemetryAdapter({ logger: options.logger }),
29
+ );
30
+ }
31
+ this.metadata = { ...(options.metadata ?? {}) };
32
+ this.distinctId = options.distinctId;
33
+ this.commonProperties = { ...(options.commonProperties ?? {}) };
34
+ }
35
+
36
+ addAdapter(adapter: ITelemetryAdapter): void {
37
+ this.adapters.push(adapter);
38
+ }
39
+
40
+ setDistinctId(distinctId?: string): void {
41
+ this.distinctId = distinctId;
42
+ }
43
+
44
+ setMetadata(metadata: Partial<TelemetryMetadata>): void {
45
+ this.metadata = { ...metadata };
46
+ }
47
+
48
+ updateMetadata(metadata: Partial<TelemetryMetadata>): void {
49
+ this.metadata = {
50
+ ...this.metadata,
51
+ ...metadata,
52
+ };
53
+ }
54
+
55
+ setCommonProperties(properties: TelemetryProperties): void {
56
+ this.commonProperties = { ...properties };
57
+ }
58
+
59
+ updateCommonProperties(properties: TelemetryProperties): void {
60
+ this.commonProperties = {
61
+ ...this.commonProperties,
62
+ ...properties,
63
+ };
64
+ }
65
+
66
+ isEnabled(): boolean {
67
+ return this.adapters.some((adapter) => adapter.isEnabled());
68
+ }
69
+
70
+ capture(input: { event: string; properties?: TelemetryProperties }): void {
71
+ const properties = this.buildAttributes(input.properties);
72
+ for (const adapter of this.adapters) {
73
+ adapter.emit(input.event, properties);
74
+ }
75
+ }
76
+
77
+ captureRequired(event: string, properties?: TelemetryProperties): void {
78
+ const merged = this.buildAttributes(properties);
79
+ for (const adapter of this.adapters) {
80
+ adapter.emitRequired(event, merged);
81
+ }
82
+ }
83
+
84
+ recordCounter(
85
+ name: string,
86
+ value: number,
87
+ attributes?: TelemetryProperties,
88
+ description?: string,
89
+ required = false,
90
+ ): void {
91
+ const merged = this.buildAttributes(attributes);
92
+ for (const adapter of this.adapters) {
93
+ adapter.recordCounter(name, value, merged, description, required);
94
+ }
95
+ }
96
+
97
+ recordHistogram(
98
+ name: string,
99
+ value: number,
100
+ attributes?: TelemetryProperties,
101
+ description?: string,
102
+ required = false,
103
+ ): void {
104
+ const merged = this.buildAttributes(attributes);
105
+ for (const adapter of this.adapters) {
106
+ adapter.recordHistogram(name, value, merged, description, required);
107
+ }
108
+ }
109
+
110
+ recordGauge(
111
+ name: string,
112
+ value: number | null,
113
+ attributes?: TelemetryProperties,
114
+ description?: string,
115
+ required = false,
116
+ ): void {
117
+ const merged = this.buildAttributes(attributes);
118
+ for (const adapter of this.adapters) {
119
+ adapter.recordGauge(name, value, merged, description, required);
120
+ }
121
+ }
122
+
123
+ async flush(): Promise<void> {
124
+ await Promise.all(this.adapters.map((adapter) => adapter.flush()));
125
+ }
126
+
127
+ async dispose(): Promise<void> {
128
+ await Promise.all(this.adapters.map((adapter) => adapter.dispose()));
129
+ }
130
+
131
+ private buildAttributes(
132
+ properties?: TelemetryProperties,
133
+ ): TelemetryProperties {
134
+ return {
135
+ ...this.commonProperties,
136
+ ...properties,
137
+ ...this.metadata,
138
+ ...(this.distinctId ? { distinct_id: this.distinctId } : {}),
139
+ };
140
+ }
141
+ }