t.llm / t.tool / t.span helpers record the steps.
Mode 1 — callback-scoped (the default path)
Wrap a run inglassray.trace(name, meta?, fn). The callback receives a trace handle t; the return value becomes the trace’s output, and a throw is recorded as an error (the trace still sends).
meta object takes { customer?, sessionId?, flow?, traceId?, environment? } — see Metadata for what each field does. environment is accepted for backward compatibility but ignored since 0.1.3 (the ingest key selects the project).
Mode 2 — wrap once, trace every call
For an agent with a single entry function, wrap it once and every invocation becomes a trace:Mode 3 — manual lifecycle
When a run doesn’t fit inside one callback — batch pipelines, event-driven steps — drive the handles yourself:s.setInput(x), s.setOutput(y), s.setUsage({ inputTokens, outputTokens }) (add an optional cost to override the computed estimate, see Cost), s.setError(err), s.end(). The trace sends when t.end() is called.
When the whole trace is one model call, give the root span its kind up front — glassray.startTrace("judge", meta, { kind: "llm", model: "claude-sonnet-4-6", provider: "anthropic" }) — so usage set on the root is priced against that model (0.1.7+).
Recording steps
Inside any trace, three helpers cover the step kinds Glassray understands:t.tool() calls (Promise.all) become siblings. Spans still open when the root settles are auto-closed and flagged, so a forgotten end() can’t wedge a trace.
Usage capture
Fort.llm, token usage is extracted automatically from the common response shapes — Anthropic (usage.input_tokens / output_tokens, plus cache_read_input_tokens / cache_creation_input_tokens), OpenAI Chat Completions (usage.prompt_tokens / completion_tokens, plus prompt_tokens_details.cached_tokens and completion_tokens_details.reasoning_tokens) and OpenAI Responses (usage.input_tokens / output_tokens, plus input_tokens_details.cached_tokens and output_tokens_details.reasoning_tokens). The cache and reasoning buckets are what make the computed cost right for prompt caching and thinking models, so keep the full response object as the callback’s return value. For anything else, set it explicitly:
Cost
Glassray fills in each LLM step’s cost for you: it multiplies the step’s token usage by the price of itsmodel. So you usually don’t do anything, just make sure the model you pass is the provider’s canonical model id (the exact string you send to the provider’s API). We price ~2,500 models from the public LiteLLM price list, refreshed daily.
gpt-5.6-luna, claude-opus-4-8, gemini-2.5-pro. A custom or internal alias (for example Luna56LowPriority) won’t match a price, so its cost is left blank. Either pass the canonical id, or set the cost yourself.
The estimate prices each usage bucket at its own rate — fresh input, cached input (the cheaper cache-read rate), cache writes, output, and reasoning tokens — from 0.1.6 on, so cache-heavy calls price the way the provider bills them. It needs the buckets to be present: return the provider’s full response from t.llm (or pass them to setUsage). When exact cost matters regardless, set it yourself.
Overriding the cost. When you already know the exact figure — a custom alias, a batch or priority tier, a cache-heavy call, or a gateway that returns a cost — pass cost (in USD) on setUsage. An explicit cost always wins over the computed estimate:
t.llm(...) callback the SDK also picks up a cost your provider returns on the response usage (for example OpenRouter’s usage.cost) automatically.
The
cost field on setUsage needs @glassray/tracing 0.1.4+. Passing a canonical model (auto-computed cost) works on any version.What else an llm step records
Beside usage, t.llm captures from the response the model that actually served the call (gen_ai.response.model — the dated id behind an alias), the provider’s response id, and the finish reason (end_turn, stop, max_tokens, tool_use, …). When the step’s input is the raw request object, it also records the sampling parameters (temperature, max_tokens, top_p) and the Anthropic system prompt as gen_ai.system_instructions. All of it lands as span attributes in the trace viewer; the model, finish reason and usage buckets feed cost and error detection.
Errors
A throw anywhere — in the root callback or any helper — is recorded on the corresponding span and rethrown unchanged. You never need to try/catch for Glassray’s benefit. To record an error without throwing, uses.setError(err) in manual mode.
Escape hatches
Automatic capture reads arguments and return values. When those aren’t the interesting payload, set trace or span I/O explicitly — explicit setters always win:Sessions
Group multi-turn conversations by passing the samesessionId on each turn’s trace: