Node.js SDK
@weharness/tracing sends your agent’s traces to WeHarness. It is plain OpenTelemetry with the GenAI semantic conventions, sent over OTLP/HTTP (protobuf) with your ingest key.
| Package | @weharness/tracing on npm |
| Version | 0.1.2 (changelog) |
| Node.js | 18 or later |
| Module type | ES module (import) |
| Licence | Apache-2.0 |
| Depends on | @opentelemetry/api 1.9.1, @opentelemetry/sdk-trace-base and @opentelemetry/sdk-trace-node 2.12.0, @opentelemetry/resources 2.12.0, @opentelemetry/exporter-trace-otlp-proto 0.223.0 |
npm install @weharness/tracingExample
Section titled “Example”import { init, agentRun, modelCall, toolCall, shutdown } from "@weharness/tracing";
init({ service: "refund-agent" }); // the key comes from WEHARNESS_INGEST_KEY
await agentRun("refunds", async () => { await modelCall("gemini-3.8-flash", { provider: "gcp.gemini" }, async (call) => { // call your model call.usage({ inputTokens: 120, outputTokens: 40 }); }); await toolCall("lookup_order", async () => { /* run the tool */ });});await shutdown(); // send what is queued before the process exitsinit()
Section titled “init()”init({ key, service, version, revision, endpoint, captureContent = false } = {}) // returns the NodeTracerProviderStarts sending traces with a tracer provider of its own.
| Option | Default | What it does |
|---|---|---|
key |
WEHARNESS_INGEST_KEY |
The ingest key. Without one, init() throws. |
service |
OTEL_SERVICE_NAME, else unknown_service |
The resource’s service.name |
version |
— | The resource’s service.version |
revision |
WEHARNESS_REVISION or a CI variable |
The resource’s vcs.ref.head.revision. See Environment variables. |
endpoint |
WEHARNESS_OTLP_ENDPOINT, else https://api.weharness.io/otlp/v1/traces |
Where spans are sent |
captureContent |
false |
Send prompts, outputs and tool payloads too. See Content capture. |
When no tracer provider is registered yet, init() registers its own as the global provider, with async context, so nested spans share their trace. When your application registered one first, init() warns and its provider serves only WeHarness’s helpers: use spanProcessor() instead to send your own spans too.
spanProcessor()
Section titled “spanProcessor()”spanProcessor({ key, endpoint, captureContent = false } = {}) // returns a BatchSpanProcessorThe processor that sends spans to WeHarness. Add it to your own tracer provider when you already use OpenTelemetry, instead of calling init():
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";import { spanProcessor } from "@weharness/tracing";
const provider = new NodeTracerProvider({ spanProcessors: [spanProcessor()] });provider.register();Spans go in background batches (up to 512 spans, every 2 seconds, at most 2,048 queued), with a 10-second request timeout. A slow or unreachable WeHarness never blocks your agent.
agentRun()
Section titled “agentRun()”await agentRun(name, async (run) => { ... }, attributes = {})One run of your agent: the root of a trace, named invoke_agent <name>, with gen_ai.operation.name=invoke_agent and gen_ai.agent.name. Returns what your function returns.
modelCall()
Section titled “modelCall()”await modelCall(model, { provider, operation = "chat", ...attributes }, async (call) => { ... })A call to a model, named <operation> <model>, of kind client, with gen_ai.operation.name, gen_ai.request.model and, when given, gen_ai.provider.name.
toolCall()
Section titled “toolCall()”await toolCall(name, async (call) => { ... }, attributes = {})A tool your agent calls, named execute_tool <name>, with gen_ai.operation.name=execute_tool and gen_ai.tool.name. It also records where your code called it: code.file.path (relative to the working directory), code.line.number and code.function.name. A caller outside the working directory, or in node_modules, gives its function name only. This is what Find in code opens.
The span helper
Section titled “The span helper”Each function receives a helper:
| Member | What it does |
|---|---|
usage({ inputTokens, outputTokens }) |
Sets gen_ai.usage.input_tokens and gen_ai.usage.output_tokens |
set(key, value) |
Sets any attribute on the span |
span |
The OpenTelemetry span itself |
When your function throws, the error is recorded on the span, the span is marked as an error, and the error is thrown again. The span always ends.
shutdown()
Section titled “shutdown()”await shutdown()Sends what is still queued. Await it before your process exits.
Content capture
Section titled “Content capture”By default the SDK removes content before spans leave your process: prompts, model outputs, system instructions, tool arguments and results, request and response bodies, database statements, message bodies and stack traces. The exported CONTENT array lists the attribute names (and prefixes) it removes; it is the same list WeHarness’s gateway uses.
To send content, pass captureContent: true to init() or spanProcessor(), and make the ingest key with Keep prompts and outputs on. If the key does not keep content, the gateway removes it anyway. See Content capture.
The filter works on attribute names. It is not a secret detector: do not put secrets or personal data in span names, error messages or your own attributes. Never use the SDK, or an ingest key, in code that runs in a browser.
Other exports
Section titled “Other exports”caller(stack?) returns the code.* attributes for the place that called it. toolCall() uses it; you can add the same attributes to your own spans.