@cursor/july 0.1.42 → 0.1.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -0
- package/dist/bin/agent-serve.js +3 -1
- package/dist/channels/github/api.d.ts +5 -0
- package/dist/channels/github/api.d.ts.map +1 -1
- package/dist/channels/github/api.js +18 -0
- package/dist/channels/github/defaults.d.ts +4 -5
- package/dist/channels/github/defaults.d.ts.map +1 -1
- package/dist/channels/github/defaults.js +27 -1
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +41 -10
- package/dist/channels/github/index.d.ts +1 -0
- package/dist/channels/github/index.d.ts.map +1 -1
- package/dist/channels/github/index.js +1 -0
- package/dist/channels/github/progress.d.ts +54 -0
- package/dist/channels/github/progress.d.ts.map +1 -0
- package/dist/channels/github/progress.js +168 -0
- package/dist/channels/github/state.d.ts +2 -0
- package/dist/channels/github/state.d.ts.map +1 -1
- package/dist/channels/github/state.js +1 -1
- package/dist/channels/github/types.d.ts +16 -4
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.d.ts +4 -0
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +4 -0
- package/dist/channels.d.ts +17 -0
- package/dist/channels.d.ts.map +1 -1
- package/dist/channels.js +31 -0
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +4 -4
- package/dist/docs/assets/{app.C4-Gwi7g.js → app.fHnNXEJ6.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.BMT0xM4I.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.DDoLm843.js → VPLocalSearchBox.Cf6ghfFY.js} +1 -1
- package/dist/docs/assets/chunks/{theme.SbjGIiM0.js → theme.B_5-VLe8.js} +2 -2
- package/dist/docs/assets/{deployment.md.BHVjVqRa.js → deployment.md.Cdp4rTE4.js} +1 -1
- package/dist/docs/assets/{guides_github.md.R2QlpR75.js → guides_github.md.DSg0j9qj.js} +29 -3
- package/dist/docs/assets/{guides_github.md.R2QlpR75.lean.js → guides_github.md.DSg0j9qj.lean.js} +1 -1
- package/dist/docs/assets/guides_opentelemetry.md.Csn7ZI25.js +27 -0
- package/dist/docs/assets/guides_opentelemetry.md.Csn7ZI25.lean.js +1 -0
- package/dist/docs/assets/index.md.COeoSkcF.js +5 -0
- package/dist/docs/assets/index.md.COeoSkcF.lean.js +1 -0
- package/dist/docs/assets/{quickstart.md.rIbfahJM.js → quickstart.md.DZxBu44y.js} +18 -49
- package/dist/docs/assets/quickstart.md.DZxBu44y.lean.js +1 -0
- package/dist/docs/assets/{reference_agent-config.md.CRmkoxd6.js → reference_agent-config.md.XRpxctsA.js} +9 -4
- package/dist/docs/assets/{reference_agent-config.md.CRmkoxd6.lean.js → reference_agent-config.md.XRpxctsA.lean.js} +1 -1
- package/dist/docs/assets/{reference_channels.md.BIabFUAI.js → reference_channels.md.MN8iDy8V.js} +1 -1
- package/dist/docs/assets/{reference_cli.md.DqEqQpGn.js → reference_cli.md.BDmjPPAm.js} +2 -2
- package/dist/docs/assets/{reference_hooks.md.B40QAG6m.js → reference_hooks.md.4cbRRIIu.js} +2 -2
- package/dist/docs/assets/{reference_hooks.md.B40QAG6m.lean.js → reference_hooks.md.4cbRRIIu.lean.js} +1 -1
- package/dist/docs/assets/{reference_project-layout.md._XdeMahr.js → reference_project-layout.md.D3MdHM2z.js} +2 -1
- package/dist/docs/assets/{reference_tools.md.lSrsTxYJ.js → reference_tools.md.kJfJAP2m.js} +1 -1
- package/dist/docs/assets/templates_triage.md.CConZt1z.js +11 -0
- package/dist/docs/assets/templates_triage.md.CConZt1z.lean.js +1 -0
- package/dist/docs/building-with-agents.html +5 -5
- package/dist/docs/concepts.html +5 -5
- package/dist/docs/deployment.html +6 -6
- package/dist/docs/evals.html +4 -4
- package/dist/docs/example-agents/approval-buddy.html +4 -4
- package/dist/docs/example-agents/benny.html +4 -4
- package/dist/docs/example-agents/bugbot.html +4 -4
- package/dist/docs/example-agents/codebase-wiki.html +4 -4
- package/dist/docs/example-agents/codeowners-review.html +4 -4
- package/dist/docs/example-agents/concierge.html +4 -4
- package/dist/docs/example-agents/fsd.html +4 -4
- package/dist/docs/example-agents/index.html +5 -5
- package/dist/docs/example-agents/knowledge-base.html +4 -4
- package/dist/docs/example-agents/oncall.html +4 -4
- package/dist/docs/example-agents/security-reviewer.html +4 -4
- package/dist/docs/example-agents/slack-agent.html +4 -4
- package/dist/docs/example-agents/weather-agent.html +4 -4
- package/dist/docs/guides/agent-to-agent.html +4 -4
- package/dist/docs/guides/cloud-runtime.html +5 -5
- package/dist/docs/guides/github.html +33 -7
- package/dist/docs/guides/human-in-the-loop.html +4 -4
- package/dist/docs/guides/mcp-oauth.html +4 -4
- package/dist/docs/guides/opentelemetry.html +51 -0
- package/dist/docs/guides/slack.html +4 -4
- package/dist/docs/guides/webhooks.html +4 -4
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +4 -4
- package/dist/docs/index.html +8 -27
- package/dist/docs/quickstart.html +24 -55
- package/dist/docs/reference/agent-config.html +12 -7
- package/dist/docs/reference/artifacts.html +4 -4
- package/dist/docs/reference/channels.html +6 -6
- package/dist/docs/reference/cli.html +7 -7
- package/dist/docs/reference/connections.html +4 -4
- package/dist/docs/reference/hooks.html +6 -6
- package/dist/docs/reference/http-api.html +4 -4
- package/dist/docs/reference/instructions.html +4 -4
- package/dist/docs/reference/playground.html +4 -4
- package/dist/docs/reference/project-layout.html +7 -6
- package/dist/docs/reference/prompt.html +4 -4
- package/dist/docs/reference/schedules.html +4 -4
- package/dist/docs/reference/sessions.html +4 -4
- package/dist/docs/reference/skills.html +4 -4
- package/dist/docs/reference/subagents.html +4 -4
- package/dist/docs/reference/tools.html +6 -6
- package/dist/docs/scaffolding-agents.html +4 -4
- package/dist/docs/storage.html +4 -4
- package/dist/docs/templates/triage.html +35 -0
- package/dist/docs/troubleshooting.html +4 -4
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/internal/cli-ax.d.ts +1 -0
- package/dist/internal/cli-ax.d.ts.map +1 -1
- package/dist/internal/cli-ax.js +20 -5
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +38 -7
- package/dist/internal/deploy-client.d.ts +8 -0
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +5 -2
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +82 -13
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +1 -0
- package/dist/internal/host-otel.d.ts +25 -0
- package/dist/internal/host-otel.d.ts.map +1 -0
- package/dist/internal/host-otel.js +162 -0
- package/dist/internal/init-project.d.ts +24 -0
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +136 -17
- package/dist/internal/otel/attrs.d.ts +7 -0
- package/dist/internal/otel/attrs.d.ts.map +1 -0
- package/dist/internal/otel/attrs.js +13 -0
- package/dist/internal/otel/collector.d.ts +21 -0
- package/dist/internal/otel/collector.d.ts.map +1 -0
- package/dist/internal/otel/collector.js +605 -0
- package/dist/internal/otel/config.d.ts +42 -0
- package/dist/internal/otel/config.d.ts.map +1 -0
- package/dist/internal/otel/config.js +236 -0
- package/dist/internal/otel/http.d.ts +11 -0
- package/dist/internal/otel/http.d.ts.map +1 -0
- package/dist/internal/otel/http.js +97 -0
- package/dist/internal/otel/runtime.d.ts +21 -0
- package/dist/internal/otel/runtime.d.ts.map +1 -0
- package/dist/internal/otel/runtime.js +188 -0
- package/dist/internal/sdk-runner.d.ts +1 -1
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +1 -1
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +26 -1
- package/dist/internal/session-engine.d.ts +15 -2
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +58 -7
- package/dist/memory.d.ts +4 -0
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +4 -0
- package/dist/otel.d.ts +88 -0
- package/dist/otel.d.ts.map +1 -0
- package/dist/otel.js +64 -0
- package/dist/playground/assets/index-CZH9EFvO.css +1 -0
- package/dist/playground/assets/{index-B3HBneZZ.js → index-Dces8BDp.js} +34 -34
- package/dist/playground/index.html +2 -2
- package/dist/types.d.ts +103 -3
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +35 -105
- package/docs/deployment.md +2 -1
- package/docs/guides/github.md +79 -6
- package/docs/guides/opentelemetry.md +214 -0
- package/docs/quickstart.md +89 -227
- package/docs/reference/agent-config.md +52 -0
- package/docs/reference/channels.md +3 -1
- package/docs/reference/cli.md +2 -2
- package/docs/reference/hooks.md +21 -4
- package/docs/reference/project-layout.md +2 -0
- package/docs/reference/tools.md +5 -4
- package/docs/templates/triage.md +147 -0
- package/package.json +24 -2
- package/skills/create-agent/SKILL.md +2 -2
- package/skills/framework-map/SKILL.md +2 -0
- package/skills/otel/SKILL.md +98 -0
- package/src/bin/agent-serve.ts +3 -1
- package/src/channels/github/api.ts +22 -0
- package/src/channels/github/defaults.ts +27 -6
- package/src/channels/github/github-channel.ts +36 -1
- package/src/channels/github/index.ts +1 -0
- package/src/channels/github/progress.ts +243 -0
- package/src/channels/github/state.ts +3 -0
- package/src/channels/github/types.ts +12 -4
- package/src/channels/slack/slack-channel.ts +4 -0
- package/src/channels.ts +38 -0
- package/src/index.ts +7 -0
- package/src/internal/cli-ax.ts +21 -3
- package/src/internal/cli-deploy.ts +58 -3
- package/src/internal/deploy-client.ts +11 -0
- package/src/internal/discovery.ts +108 -4
- package/src/internal/distribution.ts +1 -0
- package/src/internal/host-otel.ts +252 -0
- package/src/internal/init-project.ts +160 -17
- package/src/internal/otel/attrs.ts +18 -0
- package/src/internal/otel/collector.ts +881 -0
- package/src/internal/otel/config.ts +307 -0
- package/src/internal/otel/http.ts +126 -0
- package/src/internal/otel/runtime.ts +271 -0
- package/src/internal/sdk-runner.ts +6 -0
- package/src/internal/server.ts +39 -1
- package/src/internal/session-engine.ts +83 -2
- package/src/memory.ts +4 -0
- package/src/otel.ts +155 -0
- package/src/types.ts +109 -3
- package/templates/triage/README.md +48 -0
- package/templates/triage/agent/agent.ts +14 -0
- package/templates/triage/agent/channels/intake.ts +20 -0
- package/templates/triage/agent/channels/slack.ts +3 -0
- package/templates/triage/agent/hooks/memory.ts +3 -0
- package/templates/triage/agent/instructions.md +27 -0
- package/templates/triage/agent/skills/triage-rubric.md +43 -0
- package/templates/triage/agent/tools/record_triage.ts +23 -0
- package/templates/triage/evals/evals.config.ts +5 -0
- package/templates/triage/evals/triage.eval.ts +43 -0
- package/templates/triage/package.json +18 -0
- package/templates/triage/tsconfig.json +12 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.BlU3A8EB.js +0 -1
- package/dist/docs/assets/index.md.BKpbkCa6.js +0 -24
- package/dist/docs/assets/index.md.BKpbkCa6.lean.js +0 -1
- package/dist/docs/assets/quickstart.md.rIbfahJM.lean.js +0 -1
- package/dist/playground/assets/index-Bqk9nplh.css +0 -1
- /package/dist/docs/assets/{deployment.md.BHVjVqRa.lean.js → deployment.md.Cdp4rTE4.lean.js} +0 -0
- /package/dist/docs/assets/{reference_channels.md.BIabFUAI.lean.js → reference_channels.md.MN8iDy8V.lean.js} +0 -0
- /package/dist/docs/assets/{reference_cli.md.DqEqQpGn.lean.js → reference_cli.md.BDmjPPAm.lean.js} +0 -0
- /package/dist/docs/assets/{reference_project-layout.md._XdeMahr.lean.js → reference_project-layout.md.D3MdHM2z.lean.js} +0 -0
- /package/dist/docs/assets/{reference_tools.md.lSrsTxYJ.lean.js → reference_tools.md.kJfJAP2m.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
|
+
}
|