fix(hermes): settle direct API usage with complete wire receipts

Export image-owned qualification assets from cloud builds and cover Claude/OpenAI image, question and billing journeys.

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-09 10:13:33 -05:00
1 parent 8b5d075f0c
commit e025ff2286
38 files changed
+1347 -120

No files matched your search

@@ -10,11 +10,16 @@ describe("complete Anthropic receipt pricing", () => {
const priced = priceAnthropicReceipt(receipt);
expect(priced.costUsdExact).toBe("0.155967600");
expect(priced.costStatus).toBe("estimated");
expect(priced.pricingProvenance).toMatchObject({ source: "rate_card", version: "anthropic-standard-2026-10-07" });
expect(priced.pricingProvenance).toMatchObject({ source: "rate_card", version: "anthropic-standard-2026-10-09" });
expect(priced.pricingProvenance?.evidence).toContain("one-hour upper rate");
expect(priced.usage).toBe(receipt.usage);
expect(receipt.costStatus).toBe("unpriced");
});
it.each([
["claude-haiku-4-5-20251001", "0.077983800"], ["claude-sonnet-4-6", "0.233951400"],
])("prices the exact %s API model while retaining the conservative cache-write assumption", (model, expected) => {
expect(priceAnthropicReceipt({ ...receipt, model })).toMatchObject({ costUsdExact: expected, costStatus: "estimated" });
});
it.each([
{ complete: false }, { billingType: "unknown" }, { billingType: "subscription_included" },
{ provider: "unknown" }, { biller: "aws_bedrock" }, { model: "claude-sonnet-latest" },
+13 -7
View File
@@ -1,10 +1,14 @@
import { centsToUsd, unitsToCents, usdToUnits } from "@paperclipai/shared";
import type { AdapterUsageCheckpoint } from "@paperclipai/adapter-utils";
// Direct Anthropic API list prices, verified 2026-10-07:
// Direct Anthropic API list prices, verified 2026-10-09:
// https://platform.claude.com/docs/en/about-claude/pricing
// Keep this bounded to the qualified model; aliases and other billers stay unknown.
const RATES = ["2", "0.2", "4", "10"] as const;
// Exact selected models only; other billers and unreviewed aliases stay unknown.
const RATE_CARDS: Record<string, readonly [string, string, string, string]> = {
"claude-sonnet-5": ["2", "0.2", "4", "10"],
"claude-sonnet-4-6": ["3", "0.3", "6", "15"],
"claude-haiku-4-5-20251001": ["1", "0.1", "2", "5"],
};
/** A complete token receipt can receive an explicit estimate, never an invoice
* claim. ACP aggregates cache writes without TTL; conservatively price them at
@@ -13,19 +17,21 @@ export function priceAnthropicReceipt(receipt: AdapterUsageCheckpoint): AdapterU
if (!receipt.complete || !receipt.usage || receipt.usageBasis !== "per_run"
|| receipt.provider !== "anthropic" || receipt.biller !== "anthropic"
|| !["api", "metered_api"].includes(receipt.billingType ?? "")
|| receipt.model !== "claude-sonnet-5" || receipt.usageByModel?.length
|| !Object.hasOwn(RATE_CARDS, receipt.model ?? "") || receipt.usageByModel?.length
|| receipt.costUsd != null || receipt.costUsdExact != null || receipt.cacheAdjustedCostUsd != null
|| (receipt.pricingContext?.serviceTier && !["standard", "default"].includes(receipt.pricingContext.serviceTier))) return receipt;
const usage = receipt.usage;
const rates = RATE_CARDS[receipt.model!]!;
if (usage.cacheWriteTokens === undefined || usage.cachedInputTokens === undefined) return receipt;
const counts = [usage.inputTokens - usage.cacheWriteTokens, usage.cachedInputTokens, usage.cacheWriteTokens, usage.outputTokens];
if (counts.some(count => !Number.isSafeInteger(count) || count < 0)) return receipt;
const numerator = counts.reduce((total, count, index) => total + BigInt(count) * usdToUnits(RATES[index]), 0n);
const numerator = counts.reduce((total, count, index) => total + BigInt(count) * usdToUnits(rates[index]), 0n);
return { ...receipt, costUsdExact: centsToUsd(unitsToCents((numerator + 500_000n) / 1_000_000n)), costStatus: "estimated",
pricingProvenance: {
source: "rate_card", version: "anthropic-standard-2026-10-07",
source: "rate_card", version: "anthropic-standard-2026-10-09",
evidence: "https://platform.claude.com/docs/en/about-claude/pricing; standard global API pricing assumed; cache-write TTL unavailable, one-hour upper rate used; not an invoice",
inputCentsPerMillion: "200", cachedInputCentsPerMillion: "20", cacheWriteCentsPerMillion: "400", outputCentsPerMillion: "1000", serviceTier: "standard",
inputCentsPerMillion: String(Number(rates[0]) * 100), cachedInputCentsPerMillion: String(Number(rates[1]) * 100),
cacheWriteCentsPerMillion: String(Number(rates[2]) * 100), outputCentsPerMillion: String(Number(rates[3]) * 100), serviceTier: "standard",
},
};
}
@@ -57,6 +57,8 @@ import {
} from "../../vendor/paperclip-runner/index.js";
import * as issueServiceModule from "../issues.js";
import { NativePermissionDeclinedError } from "./native-permission-decline.js";
import { priceAnthropicReceipt } from "../anthropic-pricing.js";
import { priceCodexReceipt } from "../codex-pricing.js";
import {
createNativeHarnessBackupStamp,
verifyNativeHarnessBackupStamp,
@@ -1042,6 +1044,78 @@ describe("native provider usage normalization", () => {
billing: { complete: false, amountUsd: 0.25 } });
expect(nativeUsageCostUsd(snapshot.usage, hermes, "openrouter")).toBe(0.25);
});
describe("selected-account direct API wire usage", () => {
const tokens = { inputTokens: 20, outputTokens: 5, cacheReadTokens: 3, cacheWriteTokens: 2 };
const receipt = { schema: "paperclip.usage.tokens/v1", source: "provider_wire", biller: "anthropic", model: "claude-haiku-4-5-20251001", protocol: "messages",
complete: true, requestCount: 2, reportedRequestCount: 2, tokens, pricingContext: { serviceTier: "standard", contextTier: "short" } };
const directHermes = { ...hermes, model: receipt.model } as NativeExecutionInput["provider"];
const event = (eventType: string, turnId: string, payload: object = {}, sourceSeq = 1) => ({
eventType, turnId, payload, sourceSeq, sourceInstanceId: "wire-provider",
}) as unknown as PrpEvent;
const usageEvent = (turnId: string, tokenAccounting: unknown, runDelta: object = tokens, sourceSeq = 2) =>
event("item.completed", turnId, { kind: "usage", usage: { runDelta, tokenAccounting } }, sourceSeq);
it.each(["anthropic", "openai"] as const)("settles %s tokens across replay and queued turns with an explicit estimate", biller => {
const model = biller === "anthropic" ? receipt.model : "gpt-6-luna";
const selected = { ...directHermes, model } as NativeExecutionInput["provider"];
const authority = { ...receipt, biller, model, protocol: biller === "anthropic" ? "messages" : "chat_completions" };
const accounting = createNativeTurnAccounting(selected, biller);
accounting.observe(event("turn.started", "first"));
const firstUsage = usageEvent("first", authority);
accounting.observe(firstUsage); accounting.observe(firstUsage);
expect(accounting.snapshot().complete).toBe(false);
accounting.observe(event("turn.completed", "first", {}, 3));
accounting.observe(event("turn.started", "second", {}, 4));
accounting.observe(usageEvent("second", authority, tokens, 5));
accounting.observe(event("turn.completed", "second", {}, 6));
const snapshot = accounting.snapshot();
expect(snapshot).toMatchObject({ complete: true, settlementReady: true, turnId: "second",
usage: { runDeltaComplete: true, accountingCostIncomplete: false, tokenAccounting: { complete: true, requestCount: 4, reportedRequestCount: 4,
tokens: { inputTokens: 40, outputTokens: 10, cacheReadTokens: 6, cacheWriteTokens: 4 } } } });
expect(nativeUsageCostUsd(snapshot.usage, selected, biller)).toBeUndefined();
const priced = priceAnthropicReceipt(priceCodexReceipt({ complete: true, usageBasis: "per_run", provider: biller, biller, billingType: "metered_api", model,
usage: normalizeNativeUsage(snapshot.usage), pricingContext: authority.pricingContext as { serviceTier: "standard"; contextTier: "short" } }));
expect(priced.costStatus).toBe("estimated");
expect(priced.pricingProvenance?.source).toBe("rate_card");
expect(Number(priced.costUsdExact)).toBeGreaterThan(0);
});
it.each([
undefined, { ...receipt, model: "other-model" }, { ...receipt, biller: "openai", protocol: "responses" },
{ ...receipt, source: "agent_claim" }, { ...receipt, tokens: { ...tokens, outputTokens: 6 } },
])("keeps unowned or legacy accepted-response counters incomplete (%j)", authority => {
const accounting = createNativeTurnAccounting(directHermes, "anthropic");
accounting.observe(event("turn.started", "turn"));
accounting.observe(usageEvent("turn", authority));
accounting.observe(event("turn.completed", "turn", {}, 3));
expect(accounting.snapshot()).toMatchObject({ complete: false, settlementReady: false,
usage: { runDeltaComplete: false, accountingCostIncomplete: true } });
});
it("settles owned interrupted work without pricing its known subset or a placeholder zero", () => {
const accounting = createNativeTurnAccounting(directHermes, "anthropic");
const partial = { ...receipt, complete: false, requestCount: 3, reportedRequestCount: 2 };
accounting.observe(event("turn.started", "turn"));
accounting.observe(usageEvent("turn", partial, {}));
accounting.observe(event("turn.cancelled", "turn", {}, 3));
expect(accounting.snapshot()).toMatchObject({ complete: false, settlementReady: true, turnId: "turn",
usage: { runDeltaComplete: false, accountingCostIncomplete: true, tokenAccounting: { complete: false, tokens } } });
expect(normalizeNativeUsage(accounting.snapshot().usage)).toBeUndefined();
});
it("refuses to certify an aggregate that switches protocol or overflows safe counters", () => {
for (const overflow of [false, true]) {
const model = "gpt-6-luna", selected = { ...hermes, model } as NativeExecutionInput["provider"];
const accounting = createNativeTurnAccounting(selected, "openai");
const counts = overflow ? { inputTokens: Number.MAX_SAFE_INTEGER, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 } : tokens;
const authority = { ...receipt, biller: "openai", model, protocol: "chat_completions", tokens: counts };
accounting.observe(event("turn.started", "first"));
accounting.observe(usageEvent("first", authority, counts));
accounting.observe(event("turn.completed", "first", {}, 3));
accounting.observe(event("turn.started", "second", {}, 4));
accounting.observe(usageEvent("second", { ...authority, protocol: overflow ? "chat_completions" : "responses" }, counts, 5));
accounting.observe(event("turn.completed", "second", {}, 6));
expect(accounting.snapshot()).toMatchObject({ complete: false, usage: { runDeltaComplete: false } });
expect(accounting.snapshot().usage).not.toHaveProperty("tokenAccounting");
}
});
});
it.each([
{}, { inputTokens: 1 }, { outputTokens: 1 },
{ inputTokens: 0, outputTokens: -1 }, { inputTokens: 0.5, outputTokens: 1 },
@@ -4,7 +4,7 @@ import { dotRunnerBroker } from "../dot-runner-broker.js";
import { configuredEnvironment } from "../../vendor/paperclip-runner/index.js";
import { CURSOR_DISTRIBUTION_PINS, QUALIFIED_ACPX_PROFILES, QUALIFIED_ACPX_VERSION } from "../../vendor/paperclip-runner/index.js";
import { isProviderMode } from "../../vendor/paperclip-runner/index.js";
import { readProviderUsageBilling } from "../../vendor/paperclip-runner/index.js";
import { readProviderUsageBilling, readProviderTokenAccounting, type ProviderTokenAccounting } from "../../vendor/paperclip-runner/index.js";
import { bundledRemoteProviderPackManifestPath, bundledRemoteRunnerBinary } from "../../vendor/paperclip-runner/index.js";
import { nativeRetryCancellationEligible, rethrowNativeCancellationLockConflict, assertCancellationRequest, cancellationIntentId as callerCancellationIntentId, cancellationRequestId } from "./native-cancellation-request.js";
import { readNativePlanWait } from "./native-plan-wait.js";
@@ -7958,6 +7958,7 @@ async function executePaperclipNativeSessionWithinScope(
const costUsdExact = typeof usage?.accountingCostUsdExact === "string" ? usage.accountingCostUsdExact : undefined;
await input.onUsage?.({ usage: accountingUsage, ...billing, complete, settlement, usageBasis: "per_run", costUsdExact,
model: input.execution.provider.model ?? "unknown", costUsd: accountingCost ?? null, costStatus,
...(readProviderTokenAccounting(usage?.tokenAccounting)?.complete ? { pricingContext: readProviderTokenAccounting(usage?.tokenAccounting)!.pricingContext } : {}),
...nativeHermesPriceEvidence(usage, input.execution.provider, selectedBiller) });
if (!input.onUsage) await input.db.update(heartbeatRuns).set({
costAccountingPending: true,
@@ -9487,6 +9488,7 @@ async function executePaperclipNativeSessionWithinScope(
? input.execution.provider.kind === "aws_agentcore" ? "estimated" : nativeHermesPriceEvidence(native.usage, input.execution.provider, selectedBiller) ? "reported" : undefined
: "unpriced",
usageComplete: nativeAccountingComplete,
...(readProviderTokenAccounting(native.usage?.tokenAccounting)?.complete ? { pricingContext: readProviderTokenAccounting(native.usage?.tokenAccounting)!.pricingContext } : {}),
...(nativeSettlementReady ? { settlement: { schema: "paperclip.accounting.settlement/v1" as const, providerWorkEnded: true as const, usageComplete: nativeAccountingComplete } } : {}),
...nativeHermesPriceEvidence(native.usage, input.execution.provider, selectedBiller),
usageBasis: "per_run",
@@ -9570,6 +9572,10 @@ export function createNativeTurnAccounting(provider: NativeExecutionInput["provi
let requestCount = 0, reportedRequestCount = 0;
let tokensComplete = true;
let settlementReady = hermes;
let directAccountingReady = hermes && ["anthropic", "openai"].includes(selectedBiller ?? "");
let directAccounting: ProviderTokenAccounting | null = null;
let directRequests = 0, directReported = 0;
const directTokens = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };
for (const [turnId, turn] of turns) {
const normalized = normalizeNativeUsage(turn.usage);
tokensComplete &&= normalized !== undefined;
@@ -9581,9 +9587,22 @@ export function createNativeTurnAccounting(provider: NativeExecutionInput["provi
const price = nativeUsageCostUsd(turn.usage, provider, selectedBiller);
const billing = provider.kind === "acpx" && provider.agent === "hermes"
? readProviderUsageBilling(turn.usage?.billing) : null;
settlementReady &&= turnId !== "unattributed" && turn.terminal && billing !== null
&& selectedBiller === billing.biller && turn.usage !== null && nativeUsageMeasurement(turn.usage).providerCostUsd === billing.amountUsd;
if (price === undefined) costIncomplete = true;
const direct = nativeHermesTokenAccounting(turn.usage, provider, selectedBiller);
const compatibleDirect = direct !== null && (!directAccounting || directAccounting.protocol === direct.protocol);
directAccountingReady &&= compatibleDirect;
if (direct) {
if (!compatibleDirect) { tokensComplete = false; complete = false; }
directAccounting = direct;
directRequests += direct.requestCount; directReported += direct.reportedRequestCount;
for (const key of Object.keys(directTokens) as (keyof typeof directTokens)[]) directTokens[key] += direct.tokens[key];
tokensComplete &&= direct.complete;
complete &&= direct.complete;
} else if (hermes && ["anthropic", "openai"].includes(selectedBiller ?? "")) {
tokensComplete = false; complete = false;
}
settlementReady &&= turnId !== "unattributed" && turn.terminal && ((billing !== null
&& selectedBiller === billing.biller && turn.usage !== null && nativeUsageMeasurement(turn.usage).providerCostUsd === billing.amountUsd) || direct !== null);
if (price === undefined) costIncomplete ||= !direct?.complete;
else {
cost += usdToUnits(billing?.amountUsdExact ?? price); costKnown = true;
if (billing) {
@@ -9593,6 +9612,12 @@ export function createNativeTurnAccounting(provider: NativeExecutionInput["provi
}
}
}
const aggregateSafe = [inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens,
inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens,
directRequests, directReported, ...Object.values(directTokens),
Object.values(directTokens).reduce((sum, value) => sum + value, 0)]
.every(value => Number.isSafeInteger(value) && value >= 0);
if (!aggregateSafe) { tokensComplete = false; complete = false; directAccountingReady = false; }
// A free empty attempt contributes no price evidence for later paid work.
// Keep an incomplete zero subtotal absent so complete aggregate tokens can
// still receive an estimate. Preserve positive known spend and true zero totals.
@@ -9602,13 +9627,17 @@ export function createNativeTurnAccounting(provider: NativeExecutionInput["provi
const current = currentTurn ? turns.get(currentTurn) : undefined;
const costExact = `${cost / 1_000_000_000n}.${String(cost % 1_000_000_000n).padStart(9, "0")}`;
return { usage: { runDelta, runDeltaComplete: hermes ? tokensComplete : true, accountingCostIncomplete: costIncomplete,
...(directAccountingReady && directAccounting && Number.isSafeInteger(directRequests) && Number.isSafeInteger(directReported)
? { tokenAccounting: { ...directAccounting, complete: tokensComplete, requestCount: directRequests, reportedRequestCount: directReported,
tokens: directTokens } } : {}),
...(retainCost && provider.kind === "acpx" && provider.agent === "hermes" ? { billing: {
schema: "paperclip.usage.billing/v1", source: "provider_reported", biller: "openrouter", currency: "USD",
complete: !costIncomplete, requestCount, reportedRequestCount,
amountUsd: Number(costExact), amountUsdExact: costExact,
} } : {}),
...(retainCost ? { accountingCostUsdExact: costExact } : {}) },
turnId: current && (normalizeNativeUsage(current.usage) || (settlementReady && readProviderUsageBilling(current.usage?.billing))) ? currentTurn : undefined,
turnId: current && (normalizeNativeUsage(current.usage) || (settlementReady
&& (readProviderUsageBilling(current.usage?.billing) || nativeHermesTokenAccounting(current.usage, provider, selectedBiller)))) ? currentTurn : undefined,
complete, settlementReady };
};
return {
@@ -9634,6 +9663,7 @@ export function createNativeTurnAccounting(provider: NativeExecutionInput["provi
const usage = record(payload.usage);
turn.usage = Object.hasOwn(usage, "runDelta")
? { runDelta: record(usage.runDelta), runDeltaComplete: usage.runDeltaComplete,
...(usage.tokenAccounting === undefined ? {} : { tokenAccounting: usage.tokenAccounting }),
...(usage.billing === undefined ? {} : { billing: usage.billing }) } : null;
}
if (terminal) turn.terminal = true;
@@ -9649,6 +9679,18 @@ export function createNativeTurnAccounting(provider: NativeExecutionInput["provi
};
}
/** Only this versioned, route/model-bound receipt certifies all direct API attempts. */
function nativeHermesTokenAccounting(usage: Record<string, unknown> | null, provider: NativeExecutionInput["provider"], selectedBiller?: string | null) {
if (provider.kind !== "acpx" || provider.agent !== "hermes" || !usage || usage.billing !== undefined) return null;
const receipt = readProviderTokenAccounting(usage.tokenAccounting);
if (!receipt || receipt.biller !== selectedBiller || receipt.model !== provider.model) return null;
// Incomplete receipts preserve settlement authority, but never certify a
// placeholder breakdown as complete tokens or a zero-cost estimate.
if (!receipt.complete) return receipt;
const measurement = nativeUsageMeasurement(usage);
return Object.entries(receipt.tokens).every(([key, value]) => measurement[key] === value) ? receipt : null;
}
export function nativeUsageCostUsd(
usage: Record<string, unknown> | null,
provider?: NativeExecutionInput["provider"],