@cursor/july 0.1.41 → 0.1.43

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 (195) hide show
  1. package/AGENTS.md +10 -3
  2. package/dist/bin/agent-serve.js +3 -1
  3. package/dist/channels/github/api.d.ts +5 -0
  4. package/dist/channels/github/api.d.ts.map +1 -1
  5. package/dist/channels/github/api.js +18 -0
  6. package/dist/channels/github/defaults.d.ts +4 -5
  7. package/dist/channels/github/defaults.d.ts.map +1 -1
  8. package/dist/channels/github/defaults.js +27 -1
  9. package/dist/channels/github/github-channel.d.ts.map +1 -1
  10. package/dist/channels/github/github-channel.js +28 -5
  11. package/dist/channels/github/index.d.ts +1 -0
  12. package/dist/channels/github/index.d.ts.map +1 -1
  13. package/dist/channels/github/index.js +1 -0
  14. package/dist/channels/github/progress.d.ts +54 -0
  15. package/dist/channels/github/progress.d.ts.map +1 -0
  16. package/dist/channels/github/progress.js +168 -0
  17. package/dist/channels/github/state.d.ts +2 -0
  18. package/dist/channels/github/state.d.ts.map +1 -1
  19. package/dist/channels/github/state.js +1 -1
  20. package/dist/channels/github/types.d.ts +8 -4
  21. package/dist/channels/github/types.d.ts.map +1 -1
  22. package/dist/channels/slack/slack-channel.d.ts +4 -0
  23. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  24. package/dist/channels/slack/slack-channel.js +4 -0
  25. package/dist/docs/404.html +2 -2
  26. package/dist/docs/ab.html +4 -4
  27. package/dist/docs/assets/{app.mIw24pb1.js → app.DsTLo2j1.js} +1 -1
  28. package/dist/docs/assets/chunks/@localSearchIndexroot.BZx4pmZL.js +1 -0
  29. package/dist/docs/assets/chunks/{VPLocalSearchBox.hOzpPwrG.js → VPLocalSearchBox.DoVfutcV.js} +1 -1
  30. package/dist/docs/assets/chunks/{theme.BtFhUYmh.js → theme.DtuVtKkc.js} +2 -2
  31. package/dist/docs/assets/{deployment.md.BHVjVqRa.js → deployment.md.Cdp4rTE4.js} +1 -1
  32. package/dist/docs/assets/{guides_github.md.R2QlpR75.js → guides_github.md.DSg0j9qj.js} +29 -3
  33. package/dist/docs/assets/{guides_github.md.R2QlpR75.lean.js → guides_github.md.DSg0j9qj.lean.js} +1 -1
  34. package/dist/docs/assets/guides_opentelemetry.md.DmyUwC5R.js +20 -0
  35. package/dist/docs/assets/guides_opentelemetry.md.DmyUwC5R.lean.js +1 -0
  36. package/dist/docs/assets/{index.md.BKpbkCa6.js → index.md.CMc5FP7E.js} +2 -1
  37. package/dist/docs/assets/{reference_channels.md.BIabFUAI.js → reference_channels.md.MN8iDy8V.js} +1 -1
  38. package/dist/docs/assets/reference_hooks.md.4cbRRIIu.js +14 -0
  39. package/dist/docs/assets/{reference_hooks.md.BHeG-vjK.lean.js → reference_hooks.md.4cbRRIIu.lean.js} +1 -1
  40. package/dist/docs/assets/{reference_project-layout.md._XdeMahr.js → reference_project-layout.md.D3MdHM2z.js} +2 -1
  41. package/dist/docs/assets/templates_triage.md.CConZt1z.js +11 -0
  42. package/dist/docs/assets/templates_triage.md.CConZt1z.lean.js +1 -0
  43. package/dist/docs/building-with-agents.html +5 -5
  44. package/dist/docs/concepts.html +5 -5
  45. package/dist/docs/deployment.html +6 -6
  46. package/dist/docs/evals.html +4 -4
  47. package/dist/docs/example-agents/approval-buddy.html +4 -4
  48. package/dist/docs/example-agents/benny.html +4 -4
  49. package/dist/docs/example-agents/bugbot.html +4 -4
  50. package/dist/docs/example-agents/codebase-wiki.html +4 -4
  51. package/dist/docs/example-agents/codeowners-review.html +4 -4
  52. package/dist/docs/example-agents/concierge.html +4 -4
  53. package/dist/docs/example-agents/fsd.html +4 -4
  54. package/dist/docs/example-agents/index.html +5 -5
  55. package/dist/docs/example-agents/knowledge-base.html +4 -4
  56. package/dist/docs/example-agents/oncall.html +4 -4
  57. package/dist/docs/example-agents/security-reviewer.html +4 -4
  58. package/dist/docs/example-agents/slack-agent.html +4 -4
  59. package/dist/docs/example-agents/weather-agent.html +4 -4
  60. package/dist/docs/guides/agent-to-agent.html +4 -4
  61. package/dist/docs/guides/cloud-runtime.html +5 -5
  62. package/dist/docs/guides/github.html +33 -7
  63. package/dist/docs/guides/human-in-the-loop.html +4 -4
  64. package/dist/docs/guides/mcp-oauth.html +4 -4
  65. package/dist/docs/guides/opentelemetry.html +44 -0
  66. package/dist/docs/guides/slack.html +4 -4
  67. package/dist/docs/guides/webhooks.html +4 -4
  68. package/dist/docs/hashmap.json +1 -1
  69. package/dist/docs/hillclimbing.html +4 -4
  70. package/dist/docs/index.html +7 -6
  71. package/dist/docs/quickstart.html +4 -4
  72. package/dist/docs/reference/agent-config.html +4 -4
  73. package/dist/docs/reference/artifacts.html +4 -4
  74. package/dist/docs/reference/channels.html +6 -6
  75. package/dist/docs/reference/cli.html +4 -4
  76. package/dist/docs/reference/connections.html +4 -4
  77. package/dist/docs/reference/hooks.html +9 -7
  78. package/dist/docs/reference/http-api.html +4 -4
  79. package/dist/docs/reference/instructions.html +4 -4
  80. package/dist/docs/reference/playground.html +4 -4
  81. package/dist/docs/reference/project-layout.html +7 -6
  82. package/dist/docs/reference/prompt.html +4 -4
  83. package/dist/docs/reference/schedules.html +4 -4
  84. package/dist/docs/reference/sessions.html +4 -4
  85. package/dist/docs/reference/skills.html +4 -4
  86. package/dist/docs/reference/subagents.html +4 -4
  87. package/dist/docs/reference/tools.html +4 -4
  88. package/dist/docs/scaffolding-agents.html +4 -4
  89. package/dist/docs/storage.html +4 -4
  90. package/dist/docs/templates/triage.html +35 -0
  91. package/dist/docs/troubleshooting.html +4 -4
  92. package/dist/hooks.d.ts +3 -1
  93. package/dist/hooks.d.ts.map +1 -1
  94. package/dist/hooks.js +3 -1
  95. package/dist/index.d.ts +2 -0
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +1 -0
  98. package/dist/internal/cli-ax.d.ts +1 -0
  99. package/dist/internal/cli-ax.d.ts.map +1 -1
  100. package/dist/internal/cli-ax.js +18 -3
  101. package/dist/internal/discovery.d.ts.map +1 -1
  102. package/dist/internal/discovery.js +36 -4
  103. package/dist/internal/distribution.d.ts.map +1 -1
  104. package/dist/internal/distribution.js +1 -0
  105. package/dist/internal/init-project.d.ts +24 -0
  106. package/dist/internal/init-project.d.ts.map +1 -1
  107. package/dist/internal/init-project.js +126 -7
  108. package/dist/internal/otel/attrs.d.ts +7 -0
  109. package/dist/internal/otel/attrs.d.ts.map +1 -0
  110. package/dist/internal/otel/attrs.js +13 -0
  111. package/dist/internal/otel/collector.d.ts +16 -0
  112. package/dist/internal/otel/collector.d.ts.map +1 -0
  113. package/dist/internal/otel/collector.js +564 -0
  114. package/dist/internal/otel/config.d.ts +42 -0
  115. package/dist/internal/otel/config.d.ts.map +1 -0
  116. package/dist/internal/otel/config.js +236 -0
  117. package/dist/internal/otel/http.d.ts +11 -0
  118. package/dist/internal/otel/http.d.ts.map +1 -0
  119. package/dist/internal/otel/http.js +97 -0
  120. package/dist/internal/otel/runtime.d.ts +21 -0
  121. package/dist/internal/otel/runtime.d.ts.map +1 -0
  122. package/dist/internal/otel/runtime.js +188 -0
  123. package/dist/internal/server.d.ts.map +1 -1
  124. package/dist/internal/server.js +25 -1
  125. package/dist/internal/session-engine.d.ts +10 -1
  126. package/dist/internal/session-engine.d.ts.map +1 -1
  127. package/dist/internal/session-engine.js +36 -2
  128. package/dist/memory.d.ts +4 -0
  129. package/dist/memory.d.ts.map +1 -1
  130. package/dist/memory.js +4 -0
  131. package/dist/otel.d.ts +88 -0
  132. package/dist/otel.d.ts.map +1 -0
  133. package/dist/otel.js +64 -0
  134. package/dist/playground/assets/{index-B3HBneZZ.js → index-D-DIYQzw.js} +31 -31
  135. package/dist/playground/assets/index-DQTH622f.css +1 -0
  136. package/dist/playground/index.html +2 -2
  137. package/dist/types.d.ts +31 -4
  138. package/dist/types.d.ts.map +1 -1
  139. package/docs/README.md +3 -0
  140. package/docs/deployment.md +2 -1
  141. package/docs/guides/github.md +79 -6
  142. package/docs/guides/opentelemetry.md +184 -0
  143. package/docs/reference/channels.md +3 -1
  144. package/docs/reference/hooks.md +25 -5
  145. package/docs/reference/project-layout.md +2 -0
  146. package/docs/templates/triage.md +147 -0
  147. package/package.json +23 -1
  148. package/skills/create-agent/SKILL.md +2 -2
  149. package/skills/debug/SKILL.md +4 -0
  150. package/skills/framework-map/SKILL.md +2 -0
  151. package/skills/otel/SKILL.md +80 -0
  152. package/src/bin/agent-serve.ts +3 -1
  153. package/src/channels/github/api.ts +22 -0
  154. package/src/channels/github/defaults.ts +27 -6
  155. package/src/channels/github/github-channel.ts +27 -1
  156. package/src/channels/github/index.ts +1 -0
  157. package/src/channels/github/progress.ts +243 -0
  158. package/src/channels/github/state.ts +3 -0
  159. package/src/channels/github/types.ts +8 -4
  160. package/src/channels/slack/slack-channel.ts +4 -0
  161. package/src/hooks.ts +3 -1
  162. package/src/index.ts +6 -0
  163. package/src/internal/cli-ax.ts +19 -1
  164. package/src/internal/discovery.ts +59 -4
  165. package/src/internal/distribution.ts +1 -0
  166. package/src/internal/init-project.ts +150 -7
  167. package/src/internal/otel/attrs.ts +18 -0
  168. package/src/internal/otel/collector.ts +796 -0
  169. package/src/internal/otel/config.ts +307 -0
  170. package/src/internal/otel/http.ts +126 -0
  171. package/src/internal/otel/runtime.ts +271 -0
  172. package/src/internal/server.ts +38 -1
  173. package/src/internal/session-engine.ts +52 -0
  174. package/src/memory.ts +4 -0
  175. package/src/otel.ts +155 -0
  176. package/src/types.ts +29 -4
  177. package/templates/triage/README.md +48 -0
  178. package/templates/triage/agent/agent.ts +14 -0
  179. package/templates/triage/agent/channels/intake.ts +20 -0
  180. package/templates/triage/agent/channels/slack.ts +3 -0
  181. package/templates/triage/agent/hooks/memory.ts +3 -0
  182. package/templates/triage/agent/instructions.md +27 -0
  183. package/templates/triage/agent/skills/triage-rubric.md +43 -0
  184. package/templates/triage/agent/tools/record_triage.ts +23 -0
  185. package/templates/triage/evals/evals.config.ts +5 -0
  186. package/templates/triage/evals/triage.eval.ts +43 -0
  187. package/templates/triage/package.json +18 -0
  188. package/templates/triage/tsconfig.json +12 -0
  189. package/dist/docs/assets/chunks/@localSearchIndexroot.BA7V8dEJ.js +0 -1
  190. package/dist/docs/assets/reference_hooks.md.BHeG-vjK.js +0 -12
  191. package/dist/playground/assets/index-Bqk9nplh.css +0 -1
  192. /package/dist/docs/assets/{deployment.md.BHVjVqRa.lean.js → deployment.md.Cdp4rTE4.lean.js} +0 -0
  193. /package/dist/docs/assets/{index.md.BKpbkCa6.lean.js → index.md.CMc5FP7E.lean.js} +0 -0
  194. /package/dist/docs/assets/{reference_channels.md.BIabFUAI.lean.js → reference_channels.md.MN8iDy8V.lean.js} +0 -0
  195. /package/dist/docs/assets/{reference_project-layout.md._XdeMahr.lean.js → reference_project-layout.md.D3MdHM2z.lean.js} +0 -0
@@ -0,0 +1,307 @@
1
+ /**
2
+ * Merge authored `defineOtel`, `serve({ otel })`, and standard OTel env.
3
+ */
4
+
5
+ import type {
6
+ OtelConfig,
7
+ OtelDefinition,
8
+ OtelExporterConfig,
9
+ OtelLogOptions,
10
+ OtelMetricOptions,
11
+ OtelSignal,
12
+ OtelTraceOptions,
13
+ } from "../../otel.js";
14
+
15
+ export interface ResolvedOtelConfig {
16
+ enabled: boolean;
17
+ serviceName: string;
18
+ serviceVersion: string | undefined;
19
+ resourceAttributes: Record<string, string>;
20
+ exporters: OtelExporterConfig[];
21
+ traces: false | OtelTraceOptions;
22
+ metrics: false | OtelMetricOptions;
23
+ logs: false | OtelLogOptions;
24
+ }
25
+
26
+ /** True when any standard OTLP endpoint env var is set. */
27
+ export function hasOtelEnv(env: NodeJS.ProcessEnv = process.env): boolean {
28
+ return (
29
+ nonEmpty(env.OTEL_EXPORTER_OTLP_ENDPOINT) ||
30
+ nonEmpty(env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT) ||
31
+ nonEmpty(env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT) ||
32
+ nonEmpty(env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT)
33
+ );
34
+ }
35
+
36
+ /**
37
+ * Process-global authored config. OTel SDK is one per serve process — if
38
+ * several mounts ship `agent/otel.ts`, they must be identical.
39
+ */
40
+ export function pickAuthoredOtel(
41
+ mounts: ReadonlyArray<{ slug: string; name: string; otel?: OtelDefinition }>
42
+ ): OtelDefinition | undefined {
43
+ const found = mounts.filter(
44
+ (m): m is typeof m & { otel: OtelDefinition } => m.otel !== undefined
45
+ );
46
+ if (found.length === 0) {
47
+ return undefined;
48
+ }
49
+ const first = found[0]!;
50
+ const key = stableConfigKey(first.otel);
51
+ for (const other of found.slice(1)) {
52
+ if (stableConfigKey(other.otel) !== key) {
53
+ const a = first.slug === "" ? first.name : first.slug;
54
+ const b = other.slug === "" ? other.name : other.slug;
55
+ throw new Error(
56
+ `Conflicting agent/otel.ts on mounts "${a}" and "${b}": OTel is process-global. Use identical defineOtel configs, or configure once via serve({ otel }) / OTEL_EXPORTER_OTLP_*.`
57
+ );
58
+ }
59
+ }
60
+ return first.otel;
61
+ }
62
+
63
+ /**
64
+ * Resolve effective config.
65
+ *
66
+ * Precedence: `serve({ otel: false })` disables; otherwise merge
67
+ * `serve({ otel })` over `defineOtel` over env defaults.
68
+ */
69
+ export function resolveOtelConfig(input: {
70
+ authored?: OtelConfig;
71
+ serve?: OtelConfig | false;
72
+ env?: NodeJS.ProcessEnv;
73
+ }): ResolvedOtelConfig {
74
+ const env = input.env ?? process.env;
75
+ if (input.serve === false) {
76
+ return disabled();
77
+ }
78
+
79
+ const authored = input.authored ?? {};
80
+ const serve = input.serve === undefined ? {} : input.serve;
81
+ const merged: OtelConfig = {
82
+ serviceName: serve.serviceName ?? authored.serviceName,
83
+ serviceVersion: serve.serviceVersion ?? authored.serviceVersion,
84
+ resourceAttributes: {
85
+ ...authored.resourceAttributes,
86
+ ...serve.resourceAttributes,
87
+ },
88
+ exporters: serve.exporters ?? authored.exporters,
89
+ traces: serve.traces ?? authored.traces,
90
+ metrics: serve.metrics ?? authored.metrics,
91
+ logs: serve.logs ?? authored.logs,
92
+ };
93
+
94
+ const envExporters = exportersFromEnv(env);
95
+ const exporters =
96
+ merged.exporters !== undefined && merged.exporters.length > 0
97
+ ? merged.exporters.map((e) => withEnvHeaders(e, env))
98
+ : envExporters;
99
+
100
+ const optedIn =
101
+ input.authored !== undefined ||
102
+ input.serve !== undefined ||
103
+ envExporters.length > 0;
104
+ if (!optedIn || exporters.length === 0) {
105
+ return disabled();
106
+ }
107
+
108
+ const traces = resolveSignal(merged.traces);
109
+ const metrics = resolveSignal(merged.metrics);
110
+ const logs = resolveLogs(merged.logs, env);
111
+
112
+ return {
113
+ enabled: traces !== false || metrics !== false || logs !== false,
114
+ serviceName: merged.serviceName ?? env.OTEL_SERVICE_NAME ?? "cursor",
115
+ serviceVersion: merged.serviceVersion,
116
+ resourceAttributes: merged.resourceAttributes ?? {},
117
+ exporters,
118
+ traces,
119
+ metrics,
120
+ logs,
121
+ };
122
+ }
123
+
124
+ export function parseOtelHeaders(
125
+ raw: string | undefined
126
+ ): Record<string, string> {
127
+ if (raw === undefined || raw.length === 0) {
128
+ return {};
129
+ }
130
+ const out: Record<string, string> = {};
131
+ for (const part of raw.split(",")) {
132
+ const idx = part.indexOf("=");
133
+ if (idx <= 0) {
134
+ continue;
135
+ }
136
+ const key = part.slice(0, idx).trim();
137
+ const value = part.slice(idx + 1).trim();
138
+ if (key.length > 0) {
139
+ out[key] = value;
140
+ }
141
+ }
142
+ return out;
143
+ }
144
+
145
+ /** Collapse `…/v1/{traces,metrics,logs}` to the collector base. */
146
+ export function stripSignalPath(url: string): string {
147
+ return url.replace(/\/v1\/(traces|metrics|logs)\/?$/i, "");
148
+ }
149
+
150
+ /** Exporters that apply to one signal (honors per-exporter `signals`). */
151
+ export function exportersForSignal(
152
+ exporters: readonly OtelExporterConfig[],
153
+ signal: OtelSignal
154
+ ): OtelExporterConfig[] {
155
+ return exporters.filter(
156
+ (exporter) =>
157
+ exporter.signals === undefined || exporter.signals.includes(signal)
158
+ );
159
+ }
160
+
161
+ function disabled(): ResolvedOtelConfig {
162
+ return {
163
+ enabled: false,
164
+ serviceName: "cursor",
165
+ serviceVersion: undefined,
166
+ resourceAttributes: {},
167
+ exporters: [],
168
+ traces: false,
169
+ metrics: false,
170
+ logs: false,
171
+ };
172
+ }
173
+
174
+ /**
175
+ * Build exporters from env. A shared `OTEL_EXPORTER_OTLP_ENDPOINT` fans out
176
+ * to every signal. Distinct `*_TRACES_ENDPOINT` / `*_METRICS_ENDPOINT` /
177
+ * `*_LOGS_ENDPOINT` values each become their own exporter scoped to that
178
+ * signal (OTel env spec).
179
+ */
180
+ function exportersFromEnv(env: NodeJS.ProcessEnv): OtelExporterConfig[] {
181
+ const base = env.OTEL_EXPORTER_OTLP_ENDPOINT?.trim();
182
+ const traces = env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT?.trim();
183
+ const metrics = env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT?.trim();
184
+ const logs = env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT?.trim();
185
+ const protocol = protocolFromEnv(env.OTEL_EXPORTER_OTLP_PROTOCOL);
186
+ const headers = parseOtelHeaders(env.OTEL_EXPORTER_OTLP_HEADERS);
187
+
188
+ if (nonEmpty(base)) {
189
+ return [{ url: stripSignalPath(base!), headers, protocol }];
190
+ }
191
+
192
+ const out: OtelExporterConfig[] = [];
193
+ pushSignalExporter(out, traces, "traces", headers, protocol);
194
+ pushSignalExporter(out, metrics, "metrics", headers, protocol);
195
+ pushSignalExporter(out, logs, "logs", headers, protocol);
196
+ return out;
197
+ }
198
+
199
+ function pushSignalExporter(
200
+ out: OtelExporterConfig[],
201
+ raw: string | undefined,
202
+ signal: OtelSignal,
203
+ headers: Record<string, string>,
204
+ protocol: "http/json" | "http/protobuf"
205
+ ): void {
206
+ if (!nonEmpty(raw)) {
207
+ return;
208
+ }
209
+ out.push({
210
+ url: stripSignalPath(raw!),
211
+ headers,
212
+ protocol,
213
+ signals: [signal],
214
+ });
215
+ }
216
+
217
+ function withEnvHeaders(
218
+ exporter: OtelExporterConfig,
219
+ env: NodeJS.ProcessEnv
220
+ ): OtelExporterConfig {
221
+ const envHeaders = parseOtelHeaders(env.OTEL_EXPORTER_OTLP_HEADERS);
222
+ return {
223
+ url: stripSignalPath(exporter.url),
224
+ headers: { ...envHeaders, ...exporter.headers },
225
+ protocol:
226
+ exporter.protocol ?? protocolFromEnv(env.OTEL_EXPORTER_OTLP_PROTOCOL),
227
+ signals: exporter.signals,
228
+ };
229
+ }
230
+
231
+ function protocolFromEnv(
232
+ raw: string | undefined
233
+ ): "http/json" | "http/protobuf" {
234
+ const v = raw?.trim().toLowerCase();
235
+ if (v === "http/json" || v === "json") {
236
+ return "http/json";
237
+ }
238
+ // Default protobuf — matches Cursor enterprise OTLP/HTTP. grpc unsupported.
239
+ return "http/protobuf";
240
+ }
241
+
242
+ function resolveSignal<T extends object>(
243
+ value: boolean | T | undefined
244
+ ): false | T | Record<string, never> {
245
+ if (value === false) {
246
+ return false;
247
+ }
248
+ if (value === true || value === undefined) {
249
+ return {};
250
+ }
251
+ return value;
252
+ }
253
+
254
+ function resolveLogs(
255
+ value: OtelConfig["logs"],
256
+ env: NodeJS.ProcessEnv
257
+ ): false | OtelLogOptions {
258
+ if (value === false) {
259
+ return false;
260
+ }
261
+ const envPrompts = env.OTEL_LOG_USER_PROMPTS === "1";
262
+ const envTools = env.OTEL_LOG_TOOL_CONTENT === "1";
263
+ if (value === true) {
264
+ return { userPrompts: envPrompts, toolContent: envTools };
265
+ }
266
+ if (value === undefined) {
267
+ // Match OTel: unset / empty / `none` means logs off. Any other
268
+ // `OTEL_LOGS_EXPORTER` value (e.g. `otlp`) opts logs on.
269
+ if (!envPrompts && !envTools && !logsExporterOptIn(env)) {
270
+ return false;
271
+ }
272
+ return { userPrompts: envPrompts, toolContent: envTools };
273
+ }
274
+ return {
275
+ userPrompts: value.userPrompts ?? envPrompts,
276
+ toolContent: value.toolContent ?? envTools,
277
+ };
278
+ }
279
+
280
+ /** `OTEL_LOGS_EXPORTER=none` (or unset) must not enable log export. */
281
+ function logsExporterOptIn(env: NodeJS.ProcessEnv): boolean {
282
+ const raw = env.OTEL_LOGS_EXPORTER?.trim().toLowerCase();
283
+ return raw !== undefined && raw.length > 0 && raw !== "none";
284
+ }
285
+
286
+ function stableConfigKey(def: OtelDefinition): string {
287
+ const { __agentServe: _brand, ...rest } = def;
288
+ return stable(rest);
289
+ }
290
+
291
+ function stable(value: unknown): string {
292
+ if (value === null || typeof value !== "object") {
293
+ return JSON.stringify(value);
294
+ }
295
+ if (Array.isArray(value)) {
296
+ return `[${value.map(stable).join(",")}]`;
297
+ }
298
+ const obj = value as Record<string, unknown>;
299
+ return `{${Object.keys(obj)
300
+ .sort()
301
+ .map((key) => `${JSON.stringify(key)}:${stable(obj[key])}`)
302
+ .join(",")}}`;
303
+ }
304
+
305
+ function nonEmpty(value: string | undefined): boolean {
306
+ return value !== undefined && value.trim().length > 0;
307
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * HTTP SERVER spans + W3C `traceparent` extraction for inbound requests.
3
+ */
4
+
5
+ import type { IncomingMessage, ServerResponse } from "node:http";
6
+ import {
7
+ type Context,
8
+ context,
9
+ propagation,
10
+ SpanKind,
11
+ SpanStatusCode,
12
+ trace,
13
+ } from "@opentelemetry/api";
14
+ import { W3CTraceContextPropagator } from "@opentelemetry/core";
15
+ import type { OtelRuntime } from "./runtime.js";
16
+
17
+ let propagatorInstalled = false;
18
+
19
+ function ensurePropagator(): void {
20
+ if (propagatorInstalled) {
21
+ return;
22
+ }
23
+ propagation.setGlobalPropagator(new W3CTraceContextPropagator());
24
+ propagatorInstalled = true;
25
+ }
26
+
27
+ interface HttpSpanHandle {
28
+ context: Context;
29
+ end(statusCode: number, error?: unknown): void;
30
+ }
31
+
32
+ /**
33
+ * Instrument one inbound Node request: SERVER span, W3C extract, auto-end
34
+ * on response finish/close, run `fn` under the span context.
35
+ */
36
+ export function instrumentHttp<T>(
37
+ runtime: OtelRuntime | undefined,
38
+ req: IncomingMessage,
39
+ res: ServerResponse,
40
+ fn: () => Promise<T>
41
+ ): Promise<T> {
42
+ const handle = startHttpSpan(runtime, req);
43
+ if (handle === undefined) {
44
+ return fn();
45
+ }
46
+ res.once("finish", () => {
47
+ handle.end(res.statusCode);
48
+ });
49
+ res.once("close", () => {
50
+ if (!res.writableFinished) {
51
+ handle.end(res.statusCode || 499);
52
+ }
53
+ });
54
+ return context.with(handle.context, fn);
55
+ }
56
+
57
+ function startHttpSpan(
58
+ runtime: OtelRuntime | undefined,
59
+ req: IncomingMessage
60
+ ): HttpSpanHandle | undefined {
61
+ if (runtime === undefined || runtime.config.traces === false) {
62
+ return undefined;
63
+ }
64
+ ensurePropagator();
65
+ const headers: Record<string, string> = {};
66
+ for (const [key, value] of Object.entries(req.headers)) {
67
+ if (typeof value === "string") {
68
+ headers[key] = value;
69
+ } else if (Array.isArray(value) && value[0] !== undefined) {
70
+ headers[key] = value[0];
71
+ }
72
+ }
73
+ const parent = propagation.extract(context.active(), headers);
74
+ const method = req.method ?? "GET";
75
+ const path = (req.url ?? "/").split("?")[0] ?? "/";
76
+ const started = Date.now();
77
+ const span = runtime.tracer.startSpan(
78
+ "agent_sdk.http",
79
+ {
80
+ kind: SpanKind.SERVER,
81
+ attributes: {
82
+ "http.request.method": method,
83
+ "url.path": path,
84
+ "network.peer.address": req.socket.remoteAddress ?? "",
85
+ },
86
+ },
87
+ parent
88
+ );
89
+ const ctx = trace.setSpan(parent, span);
90
+ let ended = false;
91
+ return {
92
+ context: ctx,
93
+ end(statusCode, error) {
94
+ if (ended) {
95
+ return;
96
+ }
97
+ ended = true;
98
+ span.setAttribute("http.response.status_code", statusCode);
99
+ if (error !== undefined) {
100
+ span.recordException(
101
+ error instanceof Error ? error : new Error(String(error))
102
+ );
103
+ span.setStatus({
104
+ code: SpanStatusCode.ERROR,
105
+ message: error instanceof Error ? error.message : String(error),
106
+ });
107
+ } else if (statusCode >= 500) {
108
+ span.setStatus({ code: SpanStatusCode.ERROR });
109
+ } else {
110
+ span.setStatus({ code: SpanStatusCode.OK });
111
+ }
112
+ span.end();
113
+ runtime.httpDuration?.record(Date.now() - started, {
114
+ "http.request.method": method,
115
+ "http.response.status_code": statusCode,
116
+ "url.path": routeClass(path),
117
+ });
118
+ },
119
+ };
120
+ }
121
+
122
+ function routeClass(path: string): string {
123
+ return path
124
+ .replace(/\/sessions\/[0-9a-f-]{8,}/gi, "/sessions/:id")
125
+ .replace(/\/[0-9a-f]{8,}(?:-[0-9a-f]{4,})+/gi, "/:id");
126
+ }
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Process-level OTel SDK (OTLP HTTP exporters for traces / metrics / logs).
3
+ *
4
+ * Refuses to install a second global Tracer/Meter/Logger provider when one
5
+ * already owns the process (CLI, extension host, etc.). Spans still use
6
+ * `trace.getTracer()` so they nest under an existing parent when present.
7
+ */
8
+
9
+ import type { Histogram, Meter, Tracer } from "@opentelemetry/api";
10
+ import { DiagLogLevel, diag, metrics, trace } from "@opentelemetry/api";
11
+ import type { Logger as OtelLogger } from "@opentelemetry/api-logs";
12
+ import { logs } from "@opentelemetry/api-logs";
13
+ import { OTLPLogExporter as OTLPLogExporterHttp } from "@opentelemetry/exporter-logs-otlp-http";
14
+ import { OTLPLogExporter as OTLPLogExporterProto } from "@opentelemetry/exporter-logs-otlp-proto";
15
+ import { OTLPMetricExporter as OTLPMetricExporterHttp } from "@opentelemetry/exporter-metrics-otlp-http";
16
+ import { OTLPMetricExporter as OTLPMetricExporterProto } from "@opentelemetry/exporter-metrics-otlp-proto";
17
+ import { OTLPTraceExporter as OTLPTraceExporterHttp } from "@opentelemetry/exporter-trace-otlp-http";
18
+ import { OTLPTraceExporter as OTLPTraceExporterProto } from "@opentelemetry/exporter-trace-otlp-proto";
19
+ import {
20
+ type Resource,
21
+ resourceFromAttributes,
22
+ } from "@opentelemetry/resources";
23
+ import {
24
+ BatchLogRecordProcessor,
25
+ LoggerProvider,
26
+ } from "@opentelemetry/sdk-logs";
27
+ import {
28
+ AggregationTemporality,
29
+ MeterProvider,
30
+ PeriodicExportingMetricReader,
31
+ } from "@opentelemetry/sdk-metrics";
32
+ import {
33
+ BatchSpanProcessor,
34
+ NodeTracerProvider,
35
+ ParentBasedSampler,
36
+ TraceIdRatioBasedSampler,
37
+ } from "@opentelemetry/sdk-trace-node";
38
+ import {
39
+ ATTR_SERVICE_NAME,
40
+ ATTR_SERVICE_VERSION,
41
+ } from "@opentelemetry/semantic-conventions";
42
+ import type { OtelExporterConfig, OtelSignal } from "../../otel.js";
43
+ import { exportersForSignal, type ResolvedOtelConfig } from "./config.js";
44
+
45
+ const INSTRUMENTATION = "agent_sdk";
46
+
47
+ export interface OtelRuntime {
48
+ config: ResolvedOtelConfig;
49
+ tracer: Tracer;
50
+ meter: Meter | undefined;
51
+ logger: OtelLogger | undefined;
52
+ httpDuration: Histogram | undefined;
53
+ shutdown: () => Promise<void>;
54
+ }
55
+
56
+ export function createOtelRuntime(
57
+ config: ResolvedOtelConfig,
58
+ log: (line: string) => void
59
+ ): OtelRuntime | undefined {
60
+ if (!config.enabled) {
61
+ return undefined;
62
+ }
63
+ const proto = process.env.OTEL_EXPORTER_OTLP_PROTOCOL?.trim().toLowerCase();
64
+ if (proto === "grpc") {
65
+ log(
66
+ "[agent-sdk:otel] OTEL_EXPORTER_OTLP_PROTOCOL=grpc is unsupported; using http/protobuf"
67
+ );
68
+ }
69
+ const sdk = startOtelSdk(config, log);
70
+ const meter =
71
+ config.metrics === false
72
+ ? undefined
73
+ : metrics.getMeterProvider().getMeter(INSTRUMENTATION);
74
+ return {
75
+ config,
76
+ tracer: trace.getTracer(INSTRUMENTATION),
77
+ meter,
78
+ logger: config.logs === false ? undefined : logs.getLogger(INSTRUMENTATION),
79
+ httpDuration:
80
+ meter === undefined
81
+ ? undefined
82
+ : meter.createHistogram("agent_sdk.http.duration", {
83
+ description: "HTTP request duration",
84
+ unit: "ms",
85
+ }),
86
+ shutdown: sdk.shutdown,
87
+ };
88
+ }
89
+
90
+ export async function shutdownOtelRuntime(
91
+ runtime: OtelRuntime | undefined
92
+ ): Promise<void> {
93
+ await runtime?.shutdown();
94
+ }
95
+
96
+ function startOtelSdk(
97
+ config: ResolvedOtelConfig,
98
+ logger: (line: string) => void
99
+ ): {
100
+ shutdown: () => Promise<void>;
101
+ } {
102
+ diag.setLogger(
103
+ {
104
+ verbose: (m) => logger(`[agent-sdk:otel] ${m}`),
105
+ debug: (m) => logger(`[agent-sdk:otel] ${m}`),
106
+ info: (m) => logger(`[agent-sdk:otel] ${m}`),
107
+ warn: (m) => logger(`[agent-sdk:otel] warn: ${m}`),
108
+ error: (m) => logger(`[agent-sdk:otel] error: ${m}`),
109
+ },
110
+ DiagLogLevel.ERROR
111
+ );
112
+
113
+ const resource = buildResource(config);
114
+ let tracerProvider: NodeTracerProvider | undefined;
115
+ let meterProvider: MeterProvider | undefined;
116
+ let loggerProvider: LoggerProvider | undefined;
117
+
118
+ const traceOpts = config.traces;
119
+ if (traceOpts !== false) {
120
+ const exporters = exportersForSignal(config.exporters, "traces");
121
+ if (exporters.length > 0) {
122
+ if (hasRealTracerProvider()) {
123
+ logger(
124
+ "[agent-sdk:otel] TracerProvider already registered; reusing global (Agent SDK OTLP exporters not attached)"
125
+ );
126
+ } else {
127
+ tracerProvider = new NodeTracerProvider({
128
+ resource,
129
+ spanProcessors: exporters.map(
130
+ (exporter) =>
131
+ new BatchSpanProcessor(
132
+ createTraceExporter(exporter, signalUrl(exporter.url, "traces"))
133
+ )
134
+ ),
135
+ sampler: new ParentBasedSampler({
136
+ root: new TraceIdRatioBasedSampler(traceOpts.sampleRatio ?? 1),
137
+ }),
138
+ });
139
+ tracerProvider.register();
140
+ }
141
+ }
142
+ }
143
+
144
+ const metricOpts = config.metrics;
145
+ if (metricOpts !== false) {
146
+ const exporters = exportersForSignal(config.exporters, "metrics");
147
+ if (exporters.length > 0) {
148
+ const exportIntervalMillis = metricOpts.exportIntervalMillis ?? 10_000;
149
+ const provider = new MeterProvider({
150
+ resource,
151
+ readers: exporters.map(
152
+ (exporter) =>
153
+ new PeriodicExportingMetricReader({
154
+ exporter: createMetricExporter(
155
+ exporter,
156
+ signalUrl(exporter.url, "metrics")
157
+ ),
158
+ exportIntervalMillis,
159
+ })
160
+ ),
161
+ });
162
+ if (metrics.setGlobalMeterProvider(provider)) {
163
+ meterProvider = provider;
164
+ } else {
165
+ logger(
166
+ "[agent-sdk:otel] MeterProvider already registered; reusing global (Agent SDK OTLP exporters not attached)"
167
+ );
168
+ void provider.shutdown();
169
+ }
170
+ }
171
+ }
172
+
173
+ if (config.logs !== false) {
174
+ const exporters = exportersForSignal(config.exporters, "logs");
175
+ if (exporters.length > 0) {
176
+ const provider = new LoggerProvider({
177
+ resource,
178
+ processors: exporters.map(
179
+ (exporter) =>
180
+ new BatchLogRecordProcessor(
181
+ createLogExporter(exporter, signalUrl(exporter.url, "logs"))
182
+ )
183
+ ),
184
+ });
185
+ // api-logs returns the active provider (ours on success, existing otherwise).
186
+ if (logs.setGlobalLoggerProvider(provider) === provider) {
187
+ loggerProvider = provider;
188
+ } else {
189
+ logger(
190
+ "[agent-sdk:otel] LoggerProvider already registered; reusing global (Agent SDK OTLP exporters not attached)"
191
+ );
192
+ void provider.shutdown();
193
+ }
194
+ }
195
+ }
196
+
197
+ logger(
198
+ `[agent-sdk] otel enabled service=${config.serviceName} exporters=${config.exporters.length} traces=${config.traces !== false} metrics=${config.metrics !== false} logs=${config.logs !== false}`
199
+ );
200
+
201
+ return {
202
+ shutdown: async () => {
203
+ await Promise.allSettled([
204
+ tracerProvider?.shutdown(),
205
+ meterProvider?.shutdown(),
206
+ loggerProvider?.shutdown(),
207
+ ]);
208
+ },
209
+ };
210
+ }
211
+
212
+ function buildResource(config: ResolvedOtelConfig): Resource {
213
+ // Align with Cursor enterprise wire resource attrs where they overlap so
214
+ // Agent SDK traces join org metrics/logs in the same collector.
215
+ const resourceAttrs: Record<string, string> = {
216
+ [ATTR_SERVICE_NAME]: config.serviceName,
217
+ "telemetry.sdk.language": "nodejs",
218
+ "cursor.surface": "unspecified",
219
+ "cursor.entrypoint": "sdk_ts",
220
+ "agent_sdk.framework": "@cursor/july",
221
+ ...config.resourceAttributes,
222
+ };
223
+ if (config.serviceVersion !== undefined) {
224
+ resourceAttrs[ATTR_SERVICE_VERSION] = config.serviceVersion;
225
+ }
226
+ return resourceFromAttributes(resourceAttrs);
227
+ }
228
+
229
+ function signalUrl(base: string, signal: OtelSignal): string {
230
+ return `${base.replace(/\/$/, "")}/v1/${signal}`;
231
+ }
232
+
233
+ /** True when a non-noop TracerProvider already owns the process. */
234
+ function hasRealTracerProvider(): boolean {
235
+ const provider = trace.getTracerProvider() as {
236
+ getDelegate?: () => { constructor: { name: string } };
237
+ };
238
+ const delegate = provider.getDelegate?.();
239
+ return (
240
+ delegate !== undefined && delegate.constructor.name !== "NoopTracerProvider"
241
+ );
242
+ }
243
+
244
+ function isProtobuf(exporter: OtelExporterConfig): boolean {
245
+ return exporter.protocol !== "http/json";
246
+ }
247
+
248
+ function createTraceExporter(exporter: OtelExporterConfig, url: string) {
249
+ const opts = { url, headers: exporter.headers };
250
+ return isProtobuf(exporter)
251
+ ? new OTLPTraceExporterProto(opts)
252
+ : new OTLPTraceExporterHttp(opts);
253
+ }
254
+
255
+ function createMetricExporter(exporter: OtelExporterConfig, url: string) {
256
+ const opts = {
257
+ url,
258
+ headers: exporter.headers,
259
+ temporalityPreference: AggregationTemporality.DELTA,
260
+ };
261
+ return isProtobuf(exporter)
262
+ ? new OTLPMetricExporterProto(opts)
263
+ : new OTLPMetricExporterHttp(opts);
264
+ }
265
+
266
+ function createLogExporter(exporter: OtelExporterConfig, url: string) {
267
+ const opts = { url, headers: exporter.headers };
268
+ return isProtobuf(exporter)
269
+ ? new OTLPLogExporterProto(opts)
270
+ : new OTLPLogExporterHttp(opts);
271
+ }