maple-agent-tracing-ag…
Trace Agno agents with Maple: installs the OpenInference Agno instrumentor with an OTLP…
Next.js / Vercel OpenTelemetry style for Maple: instrumentation.ts, @vercel/otel bootstrap, native @opentelemetry/api call sites, inline endpoint + ingest key, no raw NodeSDK replacement, @maple-dev/browser on the client.
$ npx -y skills add mapletechlabs/maple --skill maple-nextjs-style --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/maple-nextjs-styleContext preview
The summary Claude sees to decide when to auto-load this skill.
Next.js / Vercel OpenTelemetry style for Maple: instrumentation.ts, @vercel/otel bootstrap, native @opentelemetry/api call sites, inline endpoint + ingest key, no raw NodeSDK replacement, @maple-dev/browser on the client.
name: maple-nextjs-style description: "Next.js / Vercel OpenTelemetry style for Maple: instrumentation.ts, @vercel/otel bootstrap, native @opentelemetry/api call sites, inline endpoint + ingest key, no raw NodeSDK replacement, @maple-dev/browser on the client."
For Next.js apps, use the framework entrypoint, `instrumentation.ts` with `@vercel/otel`.
// instrumentation.ts
import { registerOTel } from "@vercel/otel"
const MAPLE_ENDPOINT = "https://ingest.maple.dev" // EU: https://ingest.eu.maple.dev
const MAPLE_KEY = "MAPLE_TEST" // public ingest key (maple_pk_…), or MAPLE_TEST until the user has one
export function register() {
registerOTel({
serviceName: "my-next-app",
attributes: {
"deployment.environment.name": process.env.VERCEL_ENV ?? "development",
"vcs.repository.url.full": "https://github.com/acme/my-next-app",
"vcs.ref.head.revision": process.env.VERCEL_GIT_COMMIT_SHA,
},
traceExporter: {
url: `${MAPLE_ENDPOINT}/v1/traces`,
headers: { authorization: `Bearer ${MAPLE_KEY}` },
},
})
}Do not replace this with a custom `NodeSDK` bootstrap unless the repo is not a standard Next/Vercel app or already has a custom provider to extend.
For JavaScript/TypeScript LLM providers, use provider instrumentation instead of manual child spans. For Anthropic, add OpenInference in the same bootstrap and keep call sites native. This example uses `@vercel/otel@2.x`. If the installed types are v1, use `logRecordProcessor` (singular). Current `@opentelemetry/sdk-logs` takes `new BatchLogRecordProcessor({ exporter })`; older releases took the exporter as the first argument, and the wrong form silently drops every log. Check the installed `.d.ts`.
import Anthropic from "@anthropic-ai/sdk"
import { AnthropicInstrumentation } from "@arizeai/openinference-instrumentation-anthropic"
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http"
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs"
import { registerOTel } from "@vercel/otel"
const MAPLE_ENDPOINT = "https://ingest.maple.dev"
const MAPLE_KEY = "MAPLE_TEST"
const anthropicInstrumentation = new AnthropicInstrumentation({
traceConfig: {
hideInputs: true,
hideOutputs: true,
},
})
anthropicInstrumentation.manuallyInstrument(Anthropic)
export function register() {
registerOTel({
serviceName: "my-next-app",
instrumentations: [anthropicInstrumentation],
traceExporter: {
url: `${MAPLE_ENDPOINT}/v1/traces`,
headers: { authorization: `Bearer ${MAPLE_KEY}` },
},
logRecordProcessors: [
new BatchLogRecordProcessor({
exporter: new OTLPLogExporter({
url: `${MAPLE_ENDPOINT}/v1/logs`,
headers: { authorization: `Bearer ${MAPLE_KEY}` },
}),
}),
],
})
}Use native OTel APIs where auto-instrumentation is blind.
import { metrics, SpanStatusCode, trace } from "@opentelemetry/api"
const tracer = trace.getTracer("my-next-app")
const meter = metrics.getMeter("my-next-app")
const generated = meter.createCounter("replies.generated")
export async function POST(request: Request) {
const tenantId = request.headers.get("x-tenant-id") ?? "unknown"
return tracer.startActiveSpan("reply.generate", async (span) => {
try {
span.setAttribute("tenant.id", tenantId)
generated.add(1, { "tenant.id": tenantId, outcome: "success" })
return Response.json({ ok: true })
} catch (err) {
span.recordException(err as Error)
span.setStatus({ code: SpanStatusCode.ERROR, message: (err as Error).message })
throw err
} finally {
span.end()
}
})
}For TypeScript route handlers, wrap the business operation in `tracer.startActiveSpan(...)` with `try` / `catch` / `finally` (the shape in `maple-onboarding-style`). `@vercel/otel` already gives every request a span, so span the operation that matters rather than every handler by reflex. Don't hide the pattern behind a local helper.
If a route has an LLM call and OpenInference / provider instrumentation supports that SDK, do not wrap the provider call. Leave `client.messages.create(...)` / equivalent in place and put business context on the active product span or structured log. Do not duplicate provider/model/token attributes in route-level spans, logs, or metrics when OpenInference already reports them. Maple does not price tokens; see `maple-onboarding-style` "LLM calls" for cost and conversation grouping. For Anthropic in Next.js/ESM, keep the instrumentation instance and `manuallyInstrument(Anthropic)` call at module scope so it runs once and before route code.
Match the `@vercel/otel` logs option to the installed version: `@vercel/otel@1.x` takes `logRecordProcessor` (singular), `@vercel/otel@2.x` takes `logRecordProcessors` (plural). For normal Next.js / Vercel apps, do not guard `registerOTel(...)` behind `NEXT_RUNTIME`; Next calls `instrumentation.ts` in the appropriate runtime and `@vercel/otel` handles its own runtime differences.
`console.info` is not OTLP log export. If there is no existing logger bridge, use `@opentelemetry/api-logs` for production log records. Remove pre-existing `console.*` calls that duplicate the same structured OTel log event:
import { logs, SeverityNumber } from "@opentelemetry/api-logs"
const logger = logs.getLogger("my-next-app")
logger.emit({
severityNumber: SeverityNumber.INFO,
severityText: "INFO",
body: "generated reply",
attributes: {
"tenant.id": tenantId,
"gen_ai.provider.name": "anthropic",
"gen_ai.request.model": model,
"app.gen_ai.use_case": "support.reply",
outcome: "success",
},
})The browser half of the app uses `@maple-dev/browser` from a client component rendered in the root layout. `init()` is a no-op during server rendering, so module scope is safe.
// app/maple.tsx
"use client"
import { MapleBrowser } from "@maple-dev/browser"
MapleBrowser.init({
ingestKey: "MAPLE_TEST", // public key (maple_pk_…) only, never maple_sk_
seRepo: mapletechlabs/maple
Trace Agno agents with Maple: installs the OpenInference Agno instrumentor with an OTLP…
Trace Claude Agent SDK agents (TypeScript and Python) and Claude Code CLI sessions with…
Trace Cloudflare Agents SDK agents (AIChatAgent, Agent on Durable Objects, npm `agents` /…
Trace CrewAI crews and flows with Maple: OpenInference CrewAI instrumentor plus the model-SDK…
Trace DSPy programs and ReAct agents with Maple: OpenInference DSPy instrumentor with GenAI…
Trace Genkit (TypeScript/Node.js) agents with Maple: export Genkit's OpenTelemetry spans to…