cd /news/ai-tools/which-ai-agent-spent-the-money-on-yo… Β· home β€Ί topics β€Ί ai-tools β€Ί article
[ARTICLE Β· art-30782] src=github.com β†— pub= topic=ai-tools verified=true sentiment=↑ positive

Which AI agent spent the money on your OpenAI/Anthropic bill

Spaturzu released open-source SDKs for TypeScript and Python that enable per-agent LLM cost attribution, budget enforcement, and cross-provider fallback by wrapping existing provider clients like OpenAI and Anthropic with a single import change.

read6 min views1 publishedJun 17, 2026

Open-source client SDKs for spaturzu β€” per-agent LLM cost attribution, budget enforcement, and cross-provider fallback. Wrap your existing provider client (OpenAI, Anthropic, Bedrock, Gemini, Mistral) and every call is metered, attributed to the agent that made it, and optionally budget-capped β€” without changing how you call the model.

Already calling OpenAI, Anthropic, Bedrock, Gemini, or Mistral? Change a single import β€” from import OpenAI from "openai"

to import OpenAI from "@spaturzu/sdk/openai"

. Construction and call sites stay exactly the same, and you can attribute each call to the agent that made it:

import OpenAI from "@spaturzu/sdk/openai";

// Swap one import β€” reads SPATURZU_API_KEY + OPENAI_API_KEY from env.
const openai = new OpenAI();

// Tag any call with the agent that made it β€” one line, no closure.
await openai.withAgent("support-triage").chat.completions.create({ /* … */ });

That's the whole migration β€” no new objects, no wrappers around your call sites. Python is identical: from spaturzu.openai import OpenAI

, then client.with_agent("support-triage").chat.completions.create(...)

. The same one-import swap works for every provider β€” @spaturzu/sdk/anthropic

, /bedrock

, /google

, /mistral

(Python: spaturzu.anthropic

, and so on).

Running multi-step workflows? Group several calls under one agent with

run()

instead β€” see[What you can do]below.

SDK Package Docs
TypeScript / Node
@spaturzu/sdk

READMEPythonspaturzu

READMEOpenClaw plugin* experimental*@spaturzu/openclaw

README

⚠️ and not yet ready for production use. APIs may change without notice.@spaturzu/openclaw

is a work in progress

Both the TypeScript and Python SDKs treat the underlying provider clients as optional dependencies β€” install only the ones you actually call.

Python β€” published on PyPI as spaturzu (Python 3.10+):

pip install spaturzu                   # core
pip install "spaturzu[openai]"         # with the OpenAI integration
pip install "spaturzu[all]"            # every provider integration

TypeScript / Node β€” published on npm as @spaturzu/sdk (ESM-only):

npm install @spaturzu/sdk
npm install openai @anthropic-ai/sdk @aws-sdk/client-bedrock-runtime @google/genai @mistralai/mistralai

Once installed, every import in this README works as written. pnpm

/ yarn

accept the same package name (pnpm add @spaturzu/sdk

).

Full docs & guides:https://spaturzu.superchiu.org/docs** TypeScript / Node API:**typescript/README.md

Python API:python/README.md

OpenClaw plugin(experimental):openclaw/README.md

For AI tools / LLMs:https://spaturzu.superchiu.org/llms.txt

The examples below all build on this one-time setup. Every later snippet reuses spaturzu

/sp

, openai

, and messages

from here.

// TypeScript
import { Spaturzu } from "@spaturzu/sdk";
import OpenAI from "openai";

const spaturzu = new Spaturzu({ apiKey: process.env.SPATURZU_API_KEY });
const openai = spaturzu.wrapOpenAI(new OpenAI());

const messages = [{ role: "user", content: "Summarize the latest sales report." }];
python
import os
from spaturzu import spaturzu
from openai import OpenAI

sp = spaturzu(api_key=os.environ["SPATURZU_API_KEY"])
openai = sp.wrap_openai(OpenAI())

messages = [{"role": "user", "content": "Summarize the latest sales report."}]

Wrapping is transparent: the wrapped client has the

same methods and return typesas the original. You keep calling the provider exactly as before β€” spaturzu just meters each call in the background.

Wrap a block of work in run("name", …)

and every model call inside it is billed to that agent β€” so the dashboard shows cost per agent, not one undifferentiated total.

// TypeScript
await spaturzu.run("researcher", async () => {
  await openai.chat.completions.create({ model: "gpt-4o", messages });
});
with sp.run("researcher"):
    openai.chat.completions.create(model="gpt-4o", messages=messages)

Nested run()

calls share one run id and extend the agent path, so a multi-step workflow shows up as a tree (research β€Ί synthesize

).

// TypeScript
await spaturzu.run("research", async () => {
  await openai.chat.completions.create({ model: "gpt-4o", messages }); // path: research

  await spaturzu.run("synthesize", async () => {
    await openai.chat.completions.create({ model: "gpt-4o", messages }); // path: research β€Ί synthesize
  });
});
with sp.run("research"):
    openai.chat.completions.create(model="gpt-4o", messages=messages)        # path: research
    with sp.run("synthesize"):
        openai.chat.completions.create(model="gpt-4o", messages=messages)    # path: research β€Ί synthesize

Set tags globally on the client, or per-frame on a run()

. Frame tags merge with the global ones (inner wins on conflict), so you can break spend down by any dimension you like.

// TypeScript
const spaturzu = new Spaturzu({
  apiKey: process.env.SPATURZU_API_KEY,
  tags: { env: "prod", team: "growth" },        // on every call
});

await spaturzu.run("billing-agent", { tags: { customer: "acme" } }, async () => {
  await openai.chat.completions.create({ model: "gpt-4o", messages });
});
sp = spaturzu(
    api_key=os.environ["SPATURZU_API_KEY"],
    tags={"env": "prod", "team": "growth"},       # on every call
)

with sp.run("billing-agent", tags={"customer": "acme"}):
    openai.chat.completions.create(model="gpt-4o", messages=messages)

When an agent's budget is exhausted, the wrapped call raises BudgetExceededError

before it reaches the provider β€” so a refused call costs nothing. (Use onBreach: "warn"

/ "on_breach": "warn"

to log and proceed instead of throwing.)

// TypeScript
import { BudgetExceededError } from "@spaturzu/sdk";

const capped = spaturzu.wrapOpenAI(new OpenAI(), {
  budget: { hardCap: true, onBreach: "throw" },
});

try {
  await capped.chat.completions.create({ model: "gpt-4o", messages });
} catch (err) {
  if (err instanceof BudgetExceededError) {
    // budget hit β€” the request never left your process, so no tokens were spent
  }
}
python
from spaturzu import BudgetExceededError

capped = sp.wrap_openai(OpenAI(), budget={"hard_cap": True, "on_breach": "throw"})

try:
    capped.chat.completions.create(model="gpt-4o", messages=messages)
except BudgetExceededError:
    pass  # call never reached OpenAI β€” no spend

Give a wrap a fallback chain. On a retryable error (429 / 5xx / connection), spaturzu transparently retries the next provider β€” and translates the response back to your primary provider's shape, so your code is unchanged.

// TypeScript
import Anthropic from "@anthropic-ai/sdk";

const resilient = spaturzu.wrapOpenAI(new OpenAI(), {
  fallback: [
    { provider: "anthropic", client: new Anthropic(), model: "claude-3-5-haiku-20241022" },
  ],
});

// If OpenAI is down, this is served by Anthropic β€” still returns an OpenAI-shaped response.
const r = await resilient.chat.completions.create({ model: "gpt-4o", messages });
python
from anthropic import Anthropic

resilient = sp.wrap_openai(OpenAI(), fallback=[
    {"provider": "anthropic", "client": Anthropic(), "model": "claude-3-5-haiku-20241022"},
])

r = resilient.chat.completions.create(model="gpt-4o", messages=messages)

All 20 directional provider pairs are supported. v1 fallback is non-streaming, text-only (no tools /

response_format

).

The same five providers, the same shape, in both languages. Streaming and sync/async calls are metered automatically β€” no extra configuration.

Provider TypeScript Python
OpenAI (+ OpenAI-compatible) wrapOpenAI
wrap_openai
Anthropic wrapAnthropic
wrap_anthropic
Amazon Bedrock wrapBedrock
wrap_bedrock
Google Gemini wrapGemini
wrap_gemini
Mistral wrapMistral
wrap_mistral

Short-lived processes(CLIs, serverless): callspaturzu.flush()

/sp.flush()

before exit so queued metering rows are sent.

For the complete API β€” every option, streaming details, and per-provider notes β€” see the TypeScript and Python READMEs, or the full docs at ** https://spaturzu.superchiu.org/docs**.

sdks/
β”œβ”€β”€ typescript/   @spaturzu/sdk        β€” Node/TS SDK (5 providers, 20 fallback pairs)
β”œβ”€β”€ python/       spaturzu             β€” Python SDK (parity with the TS surface)
└── openclaw/     @spaturzu/openclaw   β€” OpenClaw metering/budget plugin (WIP)

TypeScript packages (managed as a pnpm workspace):

pnpm install
pnpm build       # build all packages
pnpm test        # run all test suites
pnpm typecheck

Python SDK:

cd python
python3 -m venv .venv
.venv/bin/pip install -e ".[dev,all]"
.venv/bin/pytest

MIT Β© Superchiu Ltd

spaturzu is a product of Superchiu Ltd.

── more in #ai-tools 4 stories Β· sorted by recency
mcp360.ai Β· Β· #ai-tools
MCP360
── more on @spaturzu 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/which-ai-agent-spent…] indexed:0 read:6min 2026-06-17 Β· β€”