output-build-workflow
Implement an Output SDK workflow from a plan document. Use when the user asks to build, implement, or code a workflow from an…
Subscribe to cost events (cost:http:request, llm:generation:metering) to forward LLM and API spend to your own observability system. Use when adding cost/spend logging, building a cost observability integration, or forwarding per-request cost data to an external system.
Claude Code
$ npx -y skills add growthxai/output --skill output-dev-cost-hooks --agent claude-code
Codex
$ npx skills add growthxai/output --skill output-dev-cost-hooks -a codex -y
Cursor
$ npx skills add growthxai/output --skill output-dev-cost-hooks -a cursor -y
Gemini CLI
$ npx skills add growthxai/output --skill output-dev-cost-hooks -a gemini-cli -y
GitHub Copilot
$ npx skills add growthxai/output --skill output-dev-cost-hooks -a github-copilot -y
OpenCode
$ npx skills add growthxai/output --skill output-dev-cost-hooks -a opencode -y
Windsurf
$ npx skills add growthxai/output --skill output-dev-cost-hooks -a windsurf -y
name: output-dev-cost-hooks description: Subscribe to cost events (cost:http:request, llm:generation:metering) to forward LLM and API spend to your own observability system. Use when adding cost/spend logging, building a cost observability integration, or forwarding per-request cost data to an external system. allowed-tools: [Read, Write, Edit, Glob]
This skill documents how to subscribe to Output's cost events so every priced LLM call and every HTTP call with attached cost (via `addRequestCost`, see `output-dev-http-client-create`) can be forwarded to your own observability system — a webhook, a structured log pipeline, a metrics backend, etc. Handler errors are caught and logged by the framework; they never affect the workflow or the request that triggered them.
This skill is about **project-wide hook registration** for cost data already emitted by the framework. To make an HTTP client emit cost in the first place, see `output-dev-http-client-create`.
// src/cost_hooks.ts
import { on } from '@outputai/core/hooks';
import type { HttpRequestCostEvent } from '@outputai/core/http';
import type { LLMGenerationMeteringEvent } from '@outputai/core/llm';
on<HttpRequestCostEvent>('cost:http:request', async event => {
// handle HTTP cost
});
on<LLMGenerationMeteringEvent>('llm:generation:metering', async event => {
// handle LLM cost
});Add the file to `outputai.hookFiles` in `package.json`, alongside any existing hook files. Paths are relative to the package root and point at the **built** `.js` output — `npm run output:worker:build` compiles `src/` to `dist/` (per `tsconfig.json`'s `rootDir`/`outDir`), so `src/cost_hooks.ts` is registered as `dist/cost_hooks.js`, not the `.ts` source itself. The worker loads these files at startup.
{
"outputai": {
"hookFiles": [
"./dist/cost_hooks.js"
]
}
}If you'd rather skip the build step, a hook file can also be plain, uncompiled JavaScript registered directly at its `src/` path (`"./src/cost_hooks.js"`) — see `https://docs.output.ai/operations/error-hooks` for that variant. The rest of this skill uses TypeScript + the built-output path, since that's what the framework's own examples (and every other file in a scaffolded project) use.
| Event | Type import | When it fires | Prefer for | |-------|-------------|----------------|-------------| | `llm:generation:metering` | `LLMGenerationMeteringEvent` from `@outputai/core/llm` | After every LLM generation (text, image, Agent, streaming) that reports usage — including failed calls that got at least partial usage | New LLM cost integrations | | `cost:llm:request` | `LLMUsageEvent` from `@outputai/core/llm` | Legacy/compatible LLM cost event, same completion path | Existing handlers only — do not use for new work | | `cost:http:request` | `HttpRequestCostEvent` from `@outputai/core/http` | Only when your code (or a client's `afterResponse` hook) calls `addRequestCost(response, total)` | Non-LLM paid API calls |
Every event carries the same envelope: `eventId` (UUID v4, stable idempotency key), `eventDate` (ms epoch), `activityInfo` and `workflowDetails` (present when emitted from within a step/evaluator), `outputActivityKind`, and `payload` (the event-specific data described above).
Use this when spend needs to reach an external observability system over HTTP. Forward the raw envelope plus payload; redact anything that might carry secrets (API keys or tokens embedded in query strings) before logging or sending the URL.
// src/cost_hooks.ts
import { on } from '@outputai/core/hooks';
import { credentials } from '@outputai/core/credentials';
import type { HttpRequestCostEvent } from '@outputai/core/http';
import type { LLMGenerationMeteringEvent } from '@outputai/core/llm';
// Use plain fetch here, not createKyClient/outputFetch. Handlers run inside
// the emitting step's async context, so a traced client would add its own
// HTTP trace event to that step's trace on every forwarded cost event.
//
// Read the credential lazily with `get`, not `require`, at module scope: a
// hook file is imported at worker startup by an unguarded `await import()`
// (there's no try/catch around it), so a `require()` that throws here takes
// the whole worker down. `get`/`require` also only see workflow-scoped
// credentials from inside an activity — at startup, outside any activity,
// they resolve the global credential set only, so keep this in a global
// (not per-workflow) credential file.
const getWebhookUrl = (): string | undefined => credentials.get('observability.webhook_url') as string | undefined;
const postEvent = async (json: Record<string, unknown>): Promise<void> => {
const webhookUrl = getWebhookUrl();
if (!webhookUrl) {
return;
}
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
await fetch(`${webhookUrl}/events`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(json),
signal: controller.signal
});
} finally {
clearTimeout(timeout);
}
};
// Strip query strings — some APIs put API keys or tokens there.
const redactUrl = (url: string): string => {
try {
const parsed = new URL(url);
return `${parsed.origin}${parsed.pathname}`;
} catch {
return '[unparseable-url]';
}
};
on<HttpRequestCostEvent>('cost:http:request', async event => {
if (!event.workflowDetails || !event.The open-source TypeScript framework for building AI workflows and agents. Designed for Claude Code — describe what you want, Claude builds it, with all the best practices already in place. One framework.
Implement an Output SDK workflow from a plan document. Use when the user asks to build, implement, or code a workflow from an…
View, edit, and set encrypted credentials in an Output.ai project. Use when adding secrets, updating API keys, verifying…
Wire encrypted credentials to environment variables using the credential: convention. Use when setting up LLM provider keys…
Initialize encrypted credentials for an Output.ai project. Use when setting up credentials for the first time, adding…
Debug Output SDK workflow issues. Use when user reports a workflow failing, erroring, hanging, producing wrong results, or asks…
Use the Agent class for multi-step tool loops, conversation history, streaming progress, and reusable LLM agents. Use when…
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
Ask which skill or flow fits your situation. A router over the skills in this repo.