Most functions on Stellarium have a flat price. AI models don't work that way: a call's cost depends on how many tokens go in and how many come out. Inference functions let providers charge per token, and let callers cap what they'll pay before anything runs.
Every resource function on Stellarium is one of two kinds. The original kind is a fixed function: it returns one value and, if it's paid, costs the same flat amount on every call.
An inference function is a function that runs AI inference. Stellarium tracks the input and output tokens each call uses, estimates the cost before the call runs, and charges for the tokens actually used afterwards.
Fixed function
Inference function
Declared with
nothing extra
kind: "inference"
Required parameters
none
maxTokens: int32, plus any others
exec returns
the value
{ value, usage: { inputTokens, outputTokens } }
Price
costMicroUsdc per call
input and output rates per million tokens
Estimator
none
estimateInputTokens, when paid
To a script, an inference function looks like any other function. It sees only value, and the token counts stay between the nodes and the operator.
02
Host a model.
An inference function is declared like any other function on a resource, with a few additions. The examples on this page use Claude Sonnet, from the sh.stellarium.inference.claude.Sonnet resource.
kind: "inference" marks it as an inference function.
A maxTokens: int32 parameter. The caller uses it to set the most output tokens the call may produce. The function can take any other parameters too.
A per-token payment manifest, if the function is paid. It sets inputCostMicroUsdcPerMillionTokens and outputCostMicroUsdcPerMillionTokens instead of costMicroUsdc. Either rate can be 0, for a model that charges only for input, for example.
An estimateInputTokens(args) estimator, if the function is paid. You write its body, just like exec, and it only runs on your node, so it can call a token counting API such as Anthropic's.
sonnet.ts
import Anthropic from "@anthropic-ai/sdk";
import type { StellariumResource } from "stellarium-ts";
const client = new Anthropic();
const arg = (args: { name: string; value: unknown }[], name: string) => args.find((a) => a.name === name)?.value;
export const sonnet: StellariumResource = {
path: "sh.stellarium.inference.claude",
name: "Sonnet",
description: "A collection of functions for Claude Sonnet 5.5",
functions: [
{
name: "message",
kind: "inference",
parameters: [
{ name: "prompt", type: "string", description: "The prompt to generate text from." },
{ name: "maxTokens", type: "int32", description: "The maximum number of tokens to generate." },
],
returnType: "string",
description: "Prompt the model to generate text.",
// Runs on your node before every paid call.
estimateInputTokens: async (args) => {
const { input_tokens } = await client.messages.countTokens({
model: "claude-sonnet-5-5",
messages: [{ role: "user", content: arg(args, "prompt") as string }],
});
return input_tokens;
},
exec: async (args) => {
const message = await client.messages.create({
model: "claude-sonnet-5-5",
max_tokens: arg(args, "maxTokens") as number,
messages: [{ role: "user", content: arg(args, "prompt") as string }],
});
const text = message.content.flatMap((block) => (block.type === "text" ? [block.text] : [])).join("");
return { value: text, usage: { inputTokens: message.usage.input_tokens, outputTokens: message.usage.output_tokens } };
},
paymentManifest: {
inputCostMicroUsdcPerMillionTokens: 2_500_000, // $2.50 per million input tokens
outputCostMicroUsdcPerMillionTokens: 15_000_000, // $15 per million output tokens
recipientAddress: process.env.CLAUDE_SONNET_PAYMENT_ADDRESS!,
},
},
],
};
The node checks all of this when it starts, and start throws if an inference function is missing a piece. An inference function without a paymentManifest is free: it still needs maxTokens, but no estimator. Hosting paid functions also needs OPERATOR_URL set, as described in the node guide.
03
How a call gets paid.
A call's charge, in micro-USDC, is its token counts times the advertised rates, rounded up:
The caller can't know the output length ahead of time, and the provider can't be trusted to report any number it likes. So every paid call goes through these steps:
01
The caller asks for an estimate
Before calling, the caller sends the call to the provider's POST /v1/run/resource/estimate. The provider runs its estimator and answers with inputTokens and maxAmount: the charge for those input tokens plus maxTokens output tokens.
02
The caller checks it
It recomputes maxAmount from the advertised rates, and refuses the call if it is over the node's inferenceCostLimitMicroUsdc. That way a caller never overpays.
03
The caller signs a voucher
It signs an inference voucher (kind: "inference") carrying maxAmount, both rates and maxTokens, and sends it with the call.
04
The provider verifies it
The voucher's rates must match the manifest and its maxTokens must match the call. The provider runs its estimator again and answers 402 with requiredMaxAmount if the voucher doesn't cover it. Inference never runs unpaid.
05
The provider reports usage
After the call, the provider sends the voucher and the actual token counts to the operator. Output tokens are capped at maxTokens.
06
The operator settles
It checks the voucher's signature, refuses output beyond maxTokens, and charges the reported usage at the signed rates, never more than maxAmount. A call that costs nothing isn't settled.
The caller agrees to a ceiling up front and pays only for what was used. The provider knows the call is covered before it spends anything on inference.
04
Call one from a script.
Callers pass maxTokens like any other argument. Their node handles the estimate and the voucher.
Summarize.req
request Summarize {
const sonnet: sh.stellarium.inference.claude.Sonnet
const reply = sonnet.message(prompt: "Summarize the plot of Hamlet", maxTokens: 400)
return reply.value
}
Sonnet charges $2.50 per million input tokens and $15 per million output tokens. Say the prompt is estimated at 1,000 tokens and the caller allows 400 output tokens:
Input
Output
Charge
Signed ceiling (maxAmount)
1,000 × 2.5
400 × 15
8,500 micro-USDC ($0.0085)
Actual call, 250 output tokens
1,000 × 2.5
250 × 15
6,250 micro-USDC ($0.00625)
The caller pays the second amount. Larger models or longer outputs can go over the default limit, so a node that calls them can raise its ceiling:
.env
# The most this node will sign for in one inference call, in micro-USDC.
# Defaults to COST_LIMIT_MICRO_USDC.
INFERENCE_COST_LIMIT_MICRO_USDC=50000
05
Find models on the network.
Inference functions are listed by GET /v1/resources on every node, alongside every other function. They carry kind: "inference" and their per-token payment manifest, so you can compare rates before calling anything. Browse them on the Stellarium registry, or read the full reference in the stellarium-ts README.