Skip to content

Commit 74f5bd3

Browse files
zhengxuyuworkspace
andauthored
fix model pricing display for unknown costs (#374)
Co-authored-by: workspace <workspace@localhost>
1 parent a489abb commit 74f5bd3

4 files changed

Lines changed: 91 additions & 8 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@1mancompany/onemancompany",
3-
"version": "0.7.72",
3+
"version": "0.7.73",
44
"description": "The AI Operating System for One-Person Companies",
55
"bin": {
66
"onemancompany": "bin/cli.js"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "onemancompany"
3-
version = "0.7.72"
3+
version = "0.7.73"
44
description = "A one-man company simulation with pixel art visualization and LangChain AI agents"
55
requires-python = ">=3.12"
66
dependencies = [

src/onemancompany/onboard.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,8 @@ def _step_welcome(console: Console) -> None:
158158

159159
def _format_price(price_str: str | None) -> str:
160160
"""Format per-token price string to $/M tokens."""
161-
if not price_str:
162-
return PRICE_FREE
161+
if price_str is None or price_str == "":
162+
return PRICE_NA
163163
try:
164164
per_token = float(price_str)
165165
per_million = per_token * 1_000_000
@@ -226,8 +226,8 @@ def _fetch_provider_models(console: Console, provider: str, api_key: str) -> lis
226226
models.append({
227227
MODEL_KEY_ID: model_id,
228228
MODEL_KEY_NAME: display_name,
229-
MODEL_KEY_PROMPT_PRICE: _format_price(pricing.get(OR_FIELD_PROMPT)) if pricing else "",
230-
MODEL_KEY_COMPLETION_PRICE: _format_price(pricing.get(OR_FIELD_COMPLETION)) if pricing else "",
229+
MODEL_KEY_PROMPT_PRICE: _format_price(pricing.get(OR_FIELD_PROMPT) if pricing else None),
230+
MODEL_KEY_COMPLETION_PRICE: _format_price(pricing.get(OR_FIELD_COMPLETION) if pricing else None),
231231
MODEL_KEY_CONTEXT: m.get(OR_FIELD_CONTEXT_LENGTH) or m.get("context_length") or 0,
232232
})
233233

@@ -294,8 +294,8 @@ def _select_model_interactive(console: Console, all_models: list[dict]) -> str:
294294
# Build choices with pricing info
295295
choices = []
296296
for m in all_models:
297-
prompt_price = _format_price(m.get(MODEL_KEY_PROMPT_PRICE))
298-
comp_price = _format_price(m.get(MODEL_KEY_COMPLETION_PRICE))
297+
prompt_price = m.get(MODEL_KEY_PROMPT_PRICE) or PRICE_NA
298+
comp_price = m.get(MODEL_KEY_COMPLETION_PRICE) or PRICE_NA
299299
label = f"{m[MODEL_KEY_ID]} [{prompt_price} / {comp_price}]"
300300
choices.append({"name": label, "value": m[MODEL_KEY_ID]})
301301

tests/unit/test_onboard.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,89 @@ def test_custom_provider_prompts_for_base_url(self):
215215
assert [call.kwargs["message"] for call in mock_text_fn.call_args_list] == ["Base URL:", "Model ID:"]
216216

217217

218+
class TestModelPricingDisplay:
219+
"""Model list pricing display must distinguish free from unknown."""
220+
221+
def test_format_price_missing_is_na_zero_is_free(self):
222+
from onemancompany.onboard import PRICE_FREE, PRICE_NA, _format_price
223+
224+
assert _format_price(None) == PRICE_NA
225+
assert _format_price("") == PRICE_NA
226+
assert _format_price("0") == PRICE_FREE
227+
228+
def test_select_model_uses_existing_price_labels_without_reformatting(self):
229+
from onemancompany.onboard import (
230+
MODEL_KEY_COMPLETION_PRICE,
231+
MODEL_KEY_CONTEXT,
232+
MODEL_KEY_ID,
233+
MODEL_KEY_NAME,
234+
MODEL_KEY_PROMPT_PRICE,
235+
PRICE_FREE,
236+
PRICE_NA,
237+
_select_model_interactive,
238+
)
239+
240+
mock_console = MagicMock()
241+
fuzzy_prompt = MagicMock()
242+
fuzzy_prompt.execute.return_value = "claude-haiku-4-5-20251001"
243+
models = [
244+
{
245+
MODEL_KEY_ID: "claude-haiku-4-5-20251001",
246+
MODEL_KEY_NAME: "Claude Haiku 4.5",
247+
MODEL_KEY_PROMPT_PRICE: "",
248+
MODEL_KEY_COMPLETION_PRICE: "",
249+
MODEL_KEY_CONTEXT: 0,
250+
},
251+
{
252+
MODEL_KEY_ID: "openrouter/free-model",
253+
MODEL_KEY_NAME: "Free Model",
254+
MODEL_KEY_PROMPT_PRICE: PRICE_FREE,
255+
MODEL_KEY_COMPLETION_PRICE: PRICE_FREE,
256+
MODEL_KEY_CONTEXT: 0,
257+
},
258+
{
259+
MODEL_KEY_ID: "priced-model",
260+
MODEL_KEY_NAME: "Priced Model",
261+
MODEL_KEY_PROMPT_PRICE: "$0.25/M",
262+
MODEL_KEY_COMPLETION_PRICE: "$1.25/M",
263+
MODEL_KEY_CONTEXT: 0,
264+
},
265+
]
266+
267+
with patch("InquirerPy.inquirer.fuzzy", return_value=fuzzy_prompt) as mock_fuzzy:
268+
selected = _select_model_interactive(mock_console, models)
269+
270+
choices = mock_fuzzy.call_args.kwargs["choices"]
271+
assert selected == "claude-haiku-4-5-20251001"
272+
assert choices[0]["name"] == f"claude-haiku-4-5-20251001 [{PRICE_NA} / {PRICE_NA}]"
273+
assert choices[1]["name"] == "openrouter/free-model [free / free]"
274+
assert choices[2]["name"] == "priced-model [$0.25/M / $1.25/M]"
275+
276+
def test_fetch_provider_models_missing_pricing_is_na(self):
277+
from onemancompany.onboard import (
278+
MODEL_KEY_COMPLETION_PRICE,
279+
MODEL_KEY_PROMPT_PRICE,
280+
PRICE_NA,
281+
_fetch_provider_models,
282+
)
283+
284+
mock_console = MagicMock()
285+
mock_console.status.return_value.__enter__.return_value = None
286+
mock_console.status.return_value.__exit__.return_value = None
287+
mock_resp = MagicMock()
288+
mock_resp.json.return_value = {
289+
"data": [
290+
{"id": "claude-haiku-4-5-20251001", "display_name": "Claude Haiku 4.5"}
291+
]
292+
}
293+
294+
with patch("httpx.get", return_value=mock_resp):
295+
models = _fetch_provider_models(mock_console, "anthropic", "sk-ant-test")
296+
297+
assert models[0][MODEL_KEY_PROMPT_PRICE] == PRICE_NA
298+
assert models[0][MODEL_KEY_COMPLETION_PRICE] == PRICE_NA
299+
300+
218301
class TestOpenclawLaunchShErrorHandling:
219302
"""launch.sh must surface errors instead of silently returning 'No output returned'."""
220303

0 commit comments

Comments
 (0)