Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions packages/agent-chat/src/creditDisplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,20 @@ export function formatWholeCredits(amount: number): string {
return `${sign}${grouped(String(Math.abs(n)))}`;
}

/** Local currency per some quantity → credit text at 2 decimals.

One credit is ``rate`` of that currency (0.01 in production: one fen or one cent).
A short amount stays a fraction. It is not rounded up to 1 credit.
*/
export function formatCreditsFromLocal(localAmount: number, rate = 0.01): string {
if (!Number.isFinite(localAmount) || localAmount <= 0 || !(rate > 0)) {
return formatFromMicros(0);
}
const millionths = Math.round(localAmount * 1_000_000);
const microsPerMillionth = Math.round(MICROS_PER_CREDIT / (rate * 1_000_000));
return formatFromMicros(millionths * microsPerMillionth);
}

/** Chat hops are stored in micros. Top-ups and withdrawals stay whole credits. */
export function formatLedgerAmount(amount: number, unit?: string | null): string {
if (unit === "micros") return formatFromMicros(Math.trunc(amount));
Expand Down
25 changes: 9 additions & 16 deletions packages/agent-chat/src/ranch-shell/AgentOwnerSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { copyText } from "./connectPrompt";
import type { RanchMessages } from "./i18n";
import { officialShelfAllows } from "./officialV0";
import { formatLocalPerMillion } from "../composerPrice";
import { formatCreditsFromLocal } from "../creditDisplay";
import { chatSettleQuote, DEFAULT_CHAT_FEE_RATE } from "./settleQuote";
import { btnGhost, btnPrimary, colors, inputStyle } from "./styles";
import { useModalA11y } from "./useModalA11y";
Expand Down Expand Up @@ -95,11 +96,6 @@ function skuFromRow(row: PieceSku): Omit<PieceSku, "agent_id"> {
const FALLBACK_MODEL_ID = "openai/gpt-4o-mini";
const DEFAULT_MARKUP_PERCENT = 50;

function roundUsdPerMillion(n: number): number {
// Keep enough precision for cheap models without noisy floats.
return Math.round(n * 1e6) / 1e6;
}

function sameModelId(left: string, right: string): boolean {
return left.trim().toLowerCase() === right.trim().toLowerCase();
}
Expand Down Expand Up @@ -215,14 +211,6 @@ function resolvePricingModelId(
return FALLBACK_MODEL_ID;
}

/** 1 Credit = $0.01 — $1 = 100 Credits, same as wallet recharge. */
const CREDIT_TO_USD = 0.01;

function usdToCredits(usd: number): number {
if (!Number.isFinite(usd) || usd <= 0) return 0;
return Math.max(0, Math.ceil(roundUsdPerMillion(usd) / CREDIT_TO_USD - 1e-12));
}

function uniqModelIds(...groups: Array<Array<string | null | undefined> | undefined>): string[] {
const out: string[] = [];
const seen = new Set<string>();
Expand Down Expand Up @@ -2622,10 +2610,15 @@ export function AgentOwnerSettings({
}}
>
{fillTemplate(t.myAgentsPricingCreditsLine, {
in: String(usdToCredits(inputParsed)),
out: String(usdToCredits(outputParsed)),
in: formatCreditsFromLocal(inputParsed),
out: formatCreditsFromLocal(outputParsed),
})}
<FieldHint label={t.fieldHintLabel} text={t.myAgentsPricingCreditsNote} />
<FieldHint
label={t.fieldHintLabel}
text={fillTemplate(t.myAgentsPricingCreditsNote, {
price: (selectedCatalog?.currency || "").toUpperCase() === "CNY" ? "¥1" : "$1",
})}
/>
</div>
</div>
) : null}
Expand Down
8 changes: 4 additions & 4 deletions packages/agent-chat/src/ranch-shell/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1193,8 +1193,8 @@ const en: RanchMessages = {
myAgentsPricingModelSearch: "Search",
myAgentsPricingOfficialEmpty: "No matching OpenRouter models in Host Catalog.",
myAgentsPricingOptionLine: "${name} · in ${in} · out ${out}",
myAgentsPricingCreditsLine: "in ${in} · out ${out} Credits / 1M",
myAgentsPricingCreditsNote: "100 Credits = $1, same as wallet recharge. Each hop settles in whole Credits.",
myAgentsPricingCreditsLine: "in ${in} · out ${out} credits / 1M",
myAgentsPricingCreditsNote: "100 credits = ${price}. Usage is shown to 2 decimals. Top-up stays a whole number.",
myAgentsPricingRuntimeHint:
"Runtime reported: ${model} (heartbeat model only — Host cannot verify the key).",
myAgentsPricingSelfReportNote:
Expand Down Expand Up @@ -1952,8 +1952,8 @@ const zh: RanchMessages = {
myAgentsPricingModelSearch: "搜索",
myAgentsPricingOfficialEmpty: "Host Catalog 里没有匹配的 OpenRouter 模型。",
myAgentsPricingOptionLine: "${name} · 入 ${in} · 出 ${out}",
myAgentsPricingCreditsLine: "入 ${in} · 出 ${out} Credits /百万",
myAgentsPricingCreditsNote: "与钱包充值相同:100 Credits = $1。每跳按整数 Credits 结算。",
myAgentsPricingCreditsLine: "入 ${in} · 出 ${out} 星币 /百万",
myAgentsPricingCreditsNote: "100 星币 = ${price}。用量显示两位小数。充值仍是整数星币。",
myAgentsPricingRuntimeHint: "运行环境上报:${model}(只认心跳型号,Host 看不见钥匙)。",
myAgentsPricingSelfReportNote:
"Owner 只设默认模型 + 上浮;底价由 Host 维护,智能体不能改写全网结算价。",
Expand Down
10 changes: 9 additions & 1 deletion tests/credit-display.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const js = ts.transpileModule(source, {
}).outputText;
const moduleExports = {};
new Function("exports", js)(moduleExports);
const { formatCreditBalance, formatLedgerAmount, formatWholeCredits } = moduleExports;
const { formatCreditBalance, formatCreditsFromLocal, formatLedgerAmount, formatWholeCredits } = moduleExports;

test("a balance keeps the micro remainder to two decimals", () => {
assert.equal(formatCreditBalance(10, 14000), "10.01");
Expand All @@ -30,6 +30,14 @@ test("a chat hop in micros is shown in credits", () => {
assert.equal(formatCreditBalance(1, 4999), "1.00");
});

test("a local price becomes credits at two decimals", () => {
assert.equal(formatCreditsFromLocal(1.15), "115.00");
assert.equal(formatCreditsFromLocal(2.3), "230.00");
assert.equal(formatCreditsFromLocal(0.766667), "76.67");
assert.equal(formatCreditsFromLocal(0.004), "0.40");
assert.equal(formatCreditsFromLocal(0), "0.00");
});

test("a top-up stays a whole number", () => {
assert.equal(formatLedgerAmount(100, "credits"), "100");
assert.equal(formatLedgerAmount(100), "100");
Expand Down
Loading