{{ message }}
Repository navigation
Conversation
@traceai/cursor-sdk traces @cursor/sdk (Cursor's TypeScript SDK) agent
runs. It patches the static Agent.create and Agent.resume (manuallyInstrument
for ESM, the OpenTelemetry require hook for CJS); the returned agent's send()
opens one AGENT span per run ("Cursor Agent run", session.id = agent id, so
the runs of one agent form a session) and the returned Run's stream() and
wait() feed it. Agent.prompt() is traced through Agent.create/send/wait,
which is how the SDK implements it.
Child spans: one TOOL span per tool_call call_id (running -> completed or
error, ERROR on error), one CHAIN span per thinking block and one LLM span
per run of assistant text deltas (the local runtime streams both as deltas).
The run span carries model, run/request ids, status (ERROR on error and
cancel, with Cursor's message), duration and token usage (RunResult/run
usage, else the sum of usage events; cache and reasoning tokens included).
Agent.create/resume failures such as AuthenticationError are one ERROR span
with the exception and the SDK error fields.
The SDK's own agent, run and event objects are returned unchanged; the
wrapper never reads the single-consumer stream itself. Spans end on early
break (tools marked incomplete, the run closes via onDidChangeStatus),
wait()-only use, fire-and-forget (status listener plus a short grace period),
and thrown errors. Prompts, tool arguments/results, thinking and assistant
text are hidden by default, like the Codex and Pi SDK packages
(FI_HIDE_INPUTS/FI_HIDE_OUTPUTS=false or traceConfig opt in); the API key is
never read.
Tests: 37 jest cases on a fake SDK with the real 1.0.37 shapes and control
flow, a shape guard on the installed SDK, a compile-time check of the real
SDK types (contract/sdk_types.ts), a CJS auto-instrumentation check, and a
real-SDK run with an invalid API key (AuthenticationError recorded, caller
sees the identical error, key absent from spans).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…TH-8263) A bad CURSOR_API_KEY made the example exit before shutdown(), so the Cursor Agent create ERROR span could be lost. Create the agent inside the try so finally always flushes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (TH-8263) Review round for #253. Error text is content. Unless both hideInputs and hideOutputs are false, an error now exports only its class and structural fields: the exception event carries exception.type (no exception.message, no stacktrace), and the status message and cursor.error.message are a fixed "<ErrorName> (<http status or code>)", e.g. "AuthenticationError (401)". Failed runs use "RunError (<code>)"; Cursor's RunResult.error and status event text are exported only on opt-in. Before this, a bad key exported the SDK's stack (local checkout path, username) and raw messages. A stream read partly and never closed no longer holds the run span open: once the run's end is known the span closes after streamDrainTimeoutMs (default 30 s), open tools marked incomplete. forceFlush() closes every run whose end is known; shutdown() closes every open run. unpatch() now also stops agents created before it, as documented, until manuallyInstrument() or wrapAgent() is called again. Tests: privacy tests scan span events and status messages with an error carrying /Users/<name>/ (absent by default, present on opt-in); partial stream, forceFlush, shutdown and unpatch cases (44 jest). The real-SDK contract now checks no message, stack or checkout path by default, the full stack on opt-in, and a missing-key local run through the real runtime's send/stream/wait where the explicitly passed, non-global provider receives 0 SDK-internal spans (the SDK emitted 27 "context-tracer" spans to the global provider in that run). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…H-8263) README and examples/basic.mjs register the Future AGI provider with setGlobalTracerProvider: false and pass it explicitly, so the SDK's own internal OpenTelemetry spans (scope "context-tracer") never reach Future AGI. The README now says exactly what was verified (real 1.0.37 error paths and a missing-key local run; event mapping by replaying the documented events; live signed-in run pending), adds a "Not captured / limitations" section, documents what error data is exported, splits the wrong-key and missing-key troubleshooting cases, words the stream-only child spans as a limitation of this version (onStep/onDelta unused), and uses a try/finally that always flushes. Example model: composer-2.5 (any id from Cursor.models.list()). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(TH-8263) The review-round rewrite of contract/real_sdk_error.mjs dropped the real-SDK Agent.resume check that the README and the docs page cite. It is back, with the redaction assertions: resuming an unknown local agent gives the caller the identical AgentNotFoundError, one "Cursor Agent resume" ERROR span with session.id = the agent id, the fixed status message "AgentNotFoundError (agent_not_found)" and only exception.type; the raw message (it quotes the agent id), stack trace and checkout path are not exported. 6/6 contract checks pass against @cursor/sdk 1.0.37. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
….38 runs (TH-8263) A live run on @cursor/sdk 1.0.38 showed that Cursor's totalTokens is input + output + cache read + cache write and its inputTokens excludes the cache (23820 + 258 + 19172 = 43250). gen_ai.usage.input_tokens now adds the cache tokens, as the GenAI semantic conventions ask, so input + output = total; cursor.usage.* keeps Cursor's numbers. Tests replay event sequences recorded from the live runs: no request / user / system / task events (request id from RunResult.requestId), thinking blocks closed by an empty-text duration event, repeated running updates, parallel tool calls, a non-zero shell exit as a completed call, and a plan-rejected run whose RunResult.error has no code (RunError). The fixture usage now follows Cursor's total. Dev dependency @cursor/sdk 1.0.38 (the real-SDK contract passes 6/6 on it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d (TH-8263) README gains a "Live vendor verification (2026-10-20)" section (Plus plan, @cursor/sdk 1.0.38, composer-2.5, trace ids, privacy result) and drops the pending-live-run caveats. The example and README pass the model's params (Plus rejects composer-2.5 without fast=false: "Model unavailable on Plus") and the example resolves local.cwd (a relative cwd breaks the shell tool). Troubleshooting covers both, the dashboard is grok.com/dashboard, the token section explains Cursor's cache accounting, and agent.getUsage() returned 403 feature_unavailable on Plus. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nik13
marked this pull request as ready for review
October 21, 2026 22:19
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What does this PR do?
Adds
@traceai/cursor-sdk(typescript/packages/traceai_cursor_sdk), an OpenTelemetry instrumentor for Cursor's official TypeScript SDK@cursor/sdk. Eachagent.send()(and the run insideAgent.prompt()) becomes one trace:Agent.create/Agent.resume(manuallyInstrument(CursorSDK)for ESM, the OpenTelemetry require hook for CJS). The agent they return gets a tracedsend; theRunit returns gets tracedstream()/wait()and anonDidChangeStatussubscription.Agent.prompt()is traced throughAgent.create -> send -> wait, which is how 1.0.37 implements it (checked against the installed SDK).session.id(agent id, so runs of one agent form a session),gen_ai.conversation.id,cursor.runtime, run and request ids, model, status, duration, token usage (RunResult.usage/run.usage, else the sum ofusageevents; input, output, total, cache read/write, reasoning). ERROR onerror/cancelledruns and on thrown errors.Agent.create/Agent.resumefailures (for exampleAuthenticationErrorfor a wrong key) are one ERROR spanCursor Agent create/Cursor Agent resume.wait()settling, earlybreak, a partly read stream never closed (streamDrainTimeoutMs, default 30 s, once the run's end is known), fire-and-forget (status listener plus a 2 s grace period),forceFlush()(runs whose end is known) andshutdown()(every open run).SDK facts were read from cursor.com/docs/sdk/typescript and verified against the installed 1.0.37
.d.tsanddist/esm/index.js(text and thinking arrive as deltas,thinking-completedcarriesthinking_duration_ms,tool_callis emitted at start, on partial updates and at completion,usageonce per turn end), then confirmed by live signed-in runs on 1.0.38 (below).Live vendor verification (2026-10-20)
Signed-in Cursor account (Plus plan,
grok_user key),@cursor/sdk1.0.38 (npm latest),@traceai/fi-core1.0.0 from npm, this package from apnpm packtarball (the default run at a61dfcf; the content-on runs with the ed6ff22 code, before the example's cwd fix), Node 26.8, local runtime,composer-2.5+fast: false, a throwaway git repo, freshHOME,register({ setGlobalTracerProvider: false }). Four agent runs in total.th8103-livefill-th8263eec8abf8c9a94e3c86881f144403c24eread,edit(wrotenotes.txt), 1 assistant message; 23820 input + 19172 cache read / 258 output, total 43250 (exported before the token fix, so storedprompt_tokens23820)examples/basic.mjs, content on viaFI_HIDE_INPUTS=false/FI_HIDE_OUTPUTS=false, projectth8103-livefill-th8263-content581f15b8429948b7830e387361944f5ashell,read(parallel with the shell),shell, 1 assistant; storedprompt_tokens43416 = 24165 + 19251 cached (after the fix), total 43734wait()only93c316ab674e4a59aa3c9f9ce9b14543composer-2.5without params on Plus (real failure)9529e2dfaba046829124a4b4b427fcf3RunError; Cursor's text "Model unavailable on Plus ... with fixed settings" not exported by defaultfi_verify.py --project th8103-livefill-th8263 --wait 240 --min-spans 1 --expect-type tool: PASS (2 traces, 8 spans); same for the content project: PASS (2 traces, 8 spans). Only this package's span names in both projects; 0context-tracer/LocalGitExecutorspans.Privacy,
/tmp/hr3/check_ch.pyover every ClickHousespanscolumn: default project, prompt marker 0, README marker 0,/Users/paths 0, tracebacks 0, status messages onlyRunError; content project, prompt ininput(1 row), README marker in outputs (3), home paths in tool args/results (3 + 1). The API key (and its first 12 characters) 0 in both.Differences from the replay, fixed in ed6ff22 / 2a65450 with tests:
totalTokens= input + output + cache read + cache write;inputTokensexcludes the cache. The replay assumed total = input + output.gen_ai.usage.input_tokensnow adds cache read/write (GenAI semconv), so input + output = total;cursor.usage.*keeps Cursor's numbers.{ id: "composer-2.5" }fails on Plus ("Model unavailable on Plus ... fixed settings"); README, example and docs passparams: [{ id: "fast", value: "false" }]fromCursor.models.list()and troubleshoot theRunError.local.cwdbreaks the agent'sshelltool ("working directory no longer exists"); the example now resolves it.request/user/system/taskevents in a local run (request id only onRunResult.requestId, which was already read);RunResult.errorcan have no code (status messageRunError); tool idstool_<uuid>, notruncated, parallel tool calls; a non-zero shell exit is acompletedcall. All replayed in new jest cases from the recorded sequences.agent.getUsage()returned403 feature_unavailableon Plus; README/docs no longer send users there unconditionally.@cursor/sdk1.0.38 (lockfile: only the@cursor/sdk*entries and its zod peer).Matched the replay: thinking deltas closed by an empty-text event with
thinking_duration_ms;tool_callrunning (repeated for partial updates) then completed; oneusageper turn;statusRUNNING / FINISHED / ERROR.Review round (2026-10-15)
hideInputsandhideOutputsare false, an error exports onlyexception.type(noexception.message, noexception.stacktrace), the structuralcursor.error.name/code/http_status/retryable/operation, and a fixed status message /cursor.error.message<ErrorName> (<http status or code>), e.g.AuthenticationError (401); failed runsRunError (<code>).RunResult.errorand status-event text only on opt-in. Previously a wrong key exported the SDK's stack (checkout path with the username) and raw messages.@cursor/sdkbundles@opentelemetry/apiand starts its own spans (scopecontext-tracer) on the global provider: 27 in one missing-key local run. README, example and docs now useregister({ ..., setGlobalTracerProvider: false })with the provider passed explicitly; the contract asserts the exporter then gets 0 of them.unpatch()now also stops agents created earlier; README "verified" line made exact, "Not captured / limitations" section, wrong-key vs missing-key troubleshooting, onStep/onDelta noted as unused by this version.Agent.resumecontract check (dropped in the first rewrite) is back with the redaction assertions (a61dfcf).recordException(error); not changed here.Why?
TH-8263 (TH-8103 coding-agent family): trace Cursor SDK agent runs in Future AGI. Sibling packages:
@traceai/codex-sdk(TH-8262),@traceai/pi-coding-agent(TH-8264).How was it tested?
pnpm test) on a fake SDK with the real 1.0.37 shapes and control flow: every documented event, content hidden / opt-in, error text redaction scanning attributes, span events and status messages with an error carrying a home-directory path and username (absent by default, present on opt-in, still redacted when only one side is opted in), tool error, failed and cancelled runs, stream throw,send()/create()/resume()/wait()rejecting, wait-only,Agent.prompt(), early break with and without status listeners, partly read stream, fire-and-forget, late stream start, concurrent stream + wait,forceFlush()/shutdown(), sessions, patch / unpatch / disable, malformed events.HOME=$(mktemp -d) pnpm run test:contract), REAL@cursor/sdk1.0.37, no account: type check of the real SDK types (contract/sdk_types.ts); CJS auto-instrumentation; wrong-keyAgent.create/Agent.promptand unknown-agentAgent.resume(identical error to the caller; by default no message, stack, checkout path orfile://in attributes, events or status, status messageAuthenticationError (401)/AgentNotFoundError (agent_not_found); full message and stack on opt-in); a missing-key local run through the real runtime'ssend()/stream()/wait()(eventsstatus:RUNNING, status:ERROR, run span ERRORRunError, prompt and paths absent) where the explicitly passed, non-global provider receives only@traceai/cursor-sdkspans.6/6 contract checks passed against @cursor/sdk 1.0.37, and again on 1.0.38 at 2a65450.tscstrict build (CJS, ESM, ESNext). The TS workspace has no ESLint config, so the strict build is the lint.pnpm install --filter "@traceai/cursor-sdk...", build,pnpm pack,npm installof the tarball with npm's@traceai/fi-core1.0.0 and@cursor/sdk): installs and imports in a fresh app.register({ setGlobalTracerProvider: false })(below), and the live signed-in runs above.Replay E2E evidence (before the live runs; local Future AGI, project
th8103-coding-cursor-sdk, re-run 2026-10-15 at a108ae5; a61dfcf only adds a contract check)Ran: (a) a fake agent emitting the documented 1.0.37 events (streamed run, wait-only follow-up, and a failing run whose Cursor error text quotes a home-directory path) with default privacy; (b) the REAL SDK local runtime with no key (
send/stream/wait, Cursor rejects the run); (c) the REAL SDK with a wrong key (Agent.create,Agent.prompt); (d) (a) again with content opted in. All throughregister({ setGlobalTracerProvider: false })with the provider passed explicitly.fi_verify.py ... --expect-type tool: PASS.No span other than this package's ones landed in the project from the real local run (span names since the re-run:
Cursor Agent create,Cursor Agent run,Cursor assistant message,Cursor thinking,Cursor tool: ls|read|shell), although the SDK starts about 27 internalcontext-tracerspans per local run on the global provider.Privacy
Prompts, tool arguments, tool results, thinking, assistant text, task summaries and error text are hidden by default (same default as the Codex SDK and Pi SDK packages;
traceConfig: { hideInputs: false, hideOutputs: false }orFI_HIDE_INPUTS=false/FI_HIDE_OUTPUTS=falseopt in; error text needs both). The API key is never read. ClickHouse grep over the re-run's 23 spans (status_message,span_events,input,outputand every string attribute): the 6 default-config traces contain the run marker 0 times, home-directory paths 0,stacktrace0, the rawInvalid User API Keytext 0; their status messages are onlyAuthenticationError (401),RunError,RunError (internal),read failed,shell failed. The 3 opted-in traces contain the marker 14 times and the quoted paths 5 times. API key strings 0. Span events are checked at the exporter in jest and in the contract (noexception.message/exception.stacktraceby default).Not available / limitations
run.stream();wait()-only andAgent.prompt()give the run span only. This version does not use the SDK'sonStep/onDeltacallbacks (possible follow-up: chain them in the wrappedsend()).agent.getUsage()).send()(Agent.getRun(),Agent.listRuns()) are not traced.context-tracerspans are not filtered by this package; the docs keep the FI provider non-global.pnpm-lock.yamlalso carries pnpm's re-resolution churn of a few@types/nodepeer suffixes in other importers (same as the Codex SDK PR).BLOCKED-ON
Resolved on 2026-10-20 for the local runtime (live vendor verification above). Still unproven: a cloud (
bc-) agent run end to end, which needs a GitHub repo for Cloud Agents; not attempted.Checklist
devcrsr_th8263_contract_invalid_key_do_not_use)🤖 Generated with Claude Code