@copyeval/sdk
v0.2.1
Published
Open SDK for logging LLM feature input/output to a copyeval record store.
Downloads
449
Maintainers
Readme
@copyeval/sdk
Open SDK for logging LLM feature input/output to a copyeval record store. One call persists an interaction — the prompt, the completion, and optional model, usage, cost, and free-form metadata — scoped to your project by an API key.
- Zero dependencies. Uses the platform
fetch; runs on Node 18+, browsers, and edge runtimes. - Two entry points.
log()is the honest primitive;trace()wraps a call and captures its latency and outcome for you. - Never breaks your app.
trace()swallows logging failures (routed toonError) so telemetry can't take down the call it's observing.
Install
npm install @copyeval/sdkQuick start
import { CopyevalClient } from "@copyeval/sdk";
const copyeval = new CopyevalClient({
apiKey: process.env.COPYEVAL_API_KEY, // cev_… — a project-scoped key
});
await copyeval.log({
input: { prompt: "Summarize this ticket…" },
output: "The customer can't reset their password.",
model: "claude-opus-4-8",
inputTokens: 812,
outputTokens: 24,
costUsd: 0.0043,
metadata: { feature: "ticket-summary", userId: "u_123" },
});apiKey falls back to COPYEVAL_API_KEY when omitted, so in most apps
new CopyevalClient() is enough. Requests go to the copyeval production API by
default — you never configure the base URL.
trace() — log a call automatically
Wrap the LLM call and the SDK records its input, output, latency, and terminal
status (ok / error) for you. Your result passes through unchanged; if the
call throws, the error is logged and re-thrown as-is.
const summary = await copyeval.trace(
{ input: messages, model: "claude-opus-4-8" },
() => callTheModel(messages),
);Pull usage and cost off the provider response with onResult:
const message = await copyeval.trace(
{
input: messages,
model: "claude-opus-4-8",
onResult: (res) => ({
output: res.content,
inputTokens: res.usage.input_tokens,
outputTokens: res.usage.output_tokens,
}),
},
() => anthropic.messages.create({ model: "claude-opus-4-8", messages, max_tokens: 1024 }),
);API
new CopyevalClient(options)
| Option | Type | Default | Notes |
| ----------- | --------------------------- | ------------------------------ | ------------------------------------------------------------ |
| apiKey | string | COPYEVAL_API_KEY | Project-scoped key (cev_…). Required. |
| baseUrl | string | https://api.copyeval.com | Rarely set — falls back to COPYEVAL_BASE_URL, else the production API. |
| fetch | FetchLike | global fetch | Supply one where there's no global fetch. |
| timeoutMs | number | 10000 | Per-request timeout. |
| onError | (error: unknown) => void | warns on console | Where trace() sends a logging failure. |
client.log(entry): Promise<LoggedRecord>
Persists one record and returns it. input and output are required and may be
any JSON value; every other field is optional. Throws CopyevalError on failure.
client.trace(options, fn): Promise<T>
Runs fn, logs the outcome (awaited, so the record is durable even in a
short-lived process), and returns fn's result. A logging failure goes to
onError and never surfaces to your caller.
CopyevalError
Thrown by log(). Carries status (HTTP status) and code (the server's
machine code, e.g. unauthorized, invalid_input) when available.
How it maps to the record store
The SDK is a thin, typed wrapper over one endpoint — POST {baseUrl}/records
with Authorization: Bearer cev_…. The key is project-scoped, so the record is
attached to that key's project; you never send a project id. Reads
(GET /records) are a separate, session-authenticated console path — this
SDK only writes. See @copyeval/api
for the full record contract and how to mint a key.
License
MIT
