Skip to content

feat(sdk): emit X-DevHelm-Surface telemetry headers#13

Merged
caballeto merged 1 commit into
mainfrom
feat/surface-telemetry
May 1, 2026
Merged

feat(sdk): emit X-DevHelm-Surface telemetry headers#13
caballeto merged 1 commit into
mainfrom
feat/surface-telemetry

Conversation

@caballeto
Copy link
Copy Markdown
Member

Summary

Reports the SDK's identity to the DevHelm API on every authenticated request so the API can attribute usage to the right devtool. Wire contract documented at https://devhelm.io/telemetry; the matching API-side handler shipped in devhelmhq/mono#332.

Companion PR for the Python SDK: devhelmhq/sdk-python#19.

What lands on the wire (defaults)

Header Value
`X-DevHelm-Surface` `sdk-js`
`X-DevHelm-Surface-Version` `package.json` `version` (resolved via `createRequire` at module load)
`X-DevHelm-Sdk-Name` `sdk-js`

The version is read from `package.json` at module load with `createRequire` so the constant tracks the published version automatically — no release-script edit needed to keep them in lockstep.

Wrapper override

```ts
new Devhelm({
token: ...,
surface: 'mcp',
surfaceVersion: '0.5.0',
surfaceMetadata: { 'Mcp-Client': 'cursor' },
})
```

The SDK identity (`X-DevHelm-Sdk-Name: sdk-js`) is preserved alongside the wrapper surface so the API can distinguish wrapper-on-top-of-v0.5-SDK from wrapper-on-top-of-v0.6-SDK when debugging client-version skew.

Opt-out

`DEVHELM_TELEMETRY=0` drops every `X-DevHelm-Surface*` header at the client level. Single env var, not per-call. Auth and tenant headers are unaffected.

Zero callsite changes

Surface is set once when `new Devhelm(...)` is constructed. Every existing tool/test using the SDK keeps working unchanged.

Tests

  • 3 new `http.test.ts` tests using a stub `globalThis.fetch` to assert the outbound `Headers` object (defaults, wrapper override, env opt-out)
  • Full suite (1306) green
  • ESLint + `tsc` clean

Version bumped to `0.6.0` (additive feature; matches `sdk-python` release cadence).

Test plan

  • `npm run lint` — clean
  • `npm run typecheck` — clean
  • `npm run build` — clean
  • `npm test` — 1306 / 1306 green
  • Post-release: smoke a JS-SDK call against stage; verify the `org_surface_usage` row + `sdk_first_authenticated_request` PostHog event

Made with Cursor

Adds optional surface identification to every authenticated API call so
the API can attribute usage to the right devtool. Wire contract docs at
https://devhelm.io/telemetry; matching API-side handler in mono#332.

Headers emitted by default (every request):
- X-DevHelm-Surface: sdk-js
- X-DevHelm-Surface-Version: package.json version (resolved at module load
  via createRequire so the constant tracks pkg without a release-script
  edit)
- X-DevHelm-Sdk-Name: sdk-js

Wrappers can override `surface` / `surfaceVersion` / `surfaceMetadata` at
construction time so their traffic is attributed correctly:

    new Devhelm({
      token: ...,
      surface: 'mcp',
      surfaceVersion: '0.5.0',
      surfaceMetadata: { 'Mcp-Client': 'cursor' },
    })

The SDK identity (X-DevHelm-Sdk-Name) is preserved alongside the wrapper
surface so the API can still distinguish wrapper-on-top-of-v0.5-SDK from
wrapper-on-top-of-v0.6-SDK when debugging client-version skew.

Opt-out is a single env var, not per-call: DEVHELM_TELEMETRY=0 drops every
X-DevHelm-Surface* header at the client level. Auth + tenant headers are
unaffected.

Zero callsite changes — the surface is set once when the Devhelm client
is constructed; every existing tool/test using `new Devhelm(...)` keeps
working unchanged.

Tests: 3 new tests in http.test.ts using a stub global fetch to assert
the outbound Headers, covering defaults, wrapper override, and env opt-out.
Full suite (1306) green; eslint + tsc clean.

Bumped to 0.6.0 (additive feature; matches sdk-python release cadence).

Co-authored-by: Cursor <cursoragent@cursor.com>
@caballeto caballeto merged commit 179aacf into main May 1, 2026
3 checks passed
@caballeto caballeto deleted the feat/surface-telemetry branch May 1, 2026 17:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant