oc_chat
Loading...
Searching...
No Matches
formatting.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# oc_ui/terminal/formatting.py
4
5"""
6Pure presentation helpers for terminal labels and tables.
7
8Purpose
9-------
10Format strings the user sees — byte-count → ``KB``/``MB``, token
11count → ``2.4K``/``1.20M``, model + capability → ``deepseek-v4-pro
12(thinking, high)``. These are a layer above the
13:class:`~oc_ui.presenter_base.Presenter` ABC: the presenters call
14``show_info`` / ``show_session_list`` / ``show_available_models``
15with strings produced here.
16
17Sub-agents that use :class:`LoggingPresenter` or no presenter at all
18never need these — that's why they live in ``oc_ui`` rather than
19next to the runtime objects they describe.
20
21Responsibilities
22----------------
23- :func:`fmt_size_kb` — render a byte count compactly.
24- :func:`fmt_tokens_or_size` — pick the most informative chat-volume
25 label for the menu (token count when available, file size as a
26 fallback).
27- :func:`fmt_model_caps` — format the model line with thinking /
28 reasoning capability hints in either compact (table) or verbose
29 (continue-prompt) form.
30
31Diagnostics
32-----------
33Domain: OC_UI.TERMINAL
34Levels:
35 L2 — lifecycle events
36 L3 — semantic details
37 L4 — deep tracing
38
39Contracts
40---------
41- All three formatters are pure functions: same inputs → same string
42 output.
43- :func:`fmt_size_kb` switches to ``MB`` at ``1_000_000`` bytes.
44- :func:`fmt_tokens_or_size` falls back to ``"empty"`` when neither
45 tokens nor bytes are recorded.
46- :func:`fmt_model_caps` returns the bare model name when the model
47 declares neither ``supports_thinking`` nor ``supports_reasoning``.
48
49Dependencies
50------------
51- oc_diagnostics (structured debug logging via _dbg)
52- oc_chat.core.models (lazy; ModelManager type hint for fmt_model_caps)
53- oc_chat.core.session (lazy; TokenStats type hint for fmt_tokens_or_size)
54"""
55from __future__ import annotations
56
57from typing import TYPE_CHECKING
58
59from oc_diagnostics import _dbg
60
61if TYPE_CHECKING:
62 from oc_chat.core.models import ModelManager
63 from oc_chat.core.session import TokenStats
64
65
66_dbg("OC_UI.TERMINAL", "module", "loaded", 4)
67
68
69def fmt_size_kb(n_bytes: int) -> str:
70 """Render a byte count as ``NNN KB`` (or MB for >=1 MB)."""
71 if n_bytes >= 1_000_000:
72 return f"{n_bytes / 1_000_000:.1f} MB"
73 return f"{n_bytes / 1024:.0f} KB"
74
75
76def fmt_tokens_or_size(stats: "TokenStats", size_bytes: int) -> str:
77 """Pick the most informative chat-volume label for the menu.
78
79 If usage tokens were recorded, display them. Otherwise fall back to
80 the chat log file size so the user has *something* to gauge volume by.
81 """
82 if stats.total_tokens > 0:
83 n = stats.total_tokens
84 if n >= 1_000_000:
85 return f"tokens: {n / 1_000_000:.2f}M"
86 if n >= 1_000:
87 return f"tokens: {n / 1_000:.1f}K"
88 return f"tokens: {n}"
89 if size_bytes > 0:
90 return f"size: {fmt_size_kb(size_bytes)}"
91 return "empty"
92
93
95 model: str,
96 reasoning_effort: str,
97 model_manager: "ModelManager | None",
98 *,
99 verbose: bool = False,
100) -> str:
101 """Format the model line including thinking/reasoning capability hints.
102
103 *verbose* form: ``"deepseek-v4-pro (thinking, effort: high)"`` — fine
104 for the [1] continue-prompt where we have a whole line.
105 Default compact form: ``"deepseek-v4-pro (thinking, high)"`` — fits the
106 list-table column.
107 """
108 caps = model_manager.capabilities(model) if model_manager else {}
109 if caps.get("supports_thinking"):
110 kind = "thinking"
111 elif caps.get("supports_reasoning"):
112 kind = "reasoning"
113 else:
114 return model
115 if verbose:
116 return f"{model} ({kind}, effort: {reasoning_effort})"
117 return f"{model} ({kind}, {reasoning_effort})"
str fmt_size_kb(int n_bytes)
Definition formatting.py:69
str fmt_tokens_or_size("TokenStats" stats, int size_bytes)
Definition formatting.py:76
str fmt_model_caps(str model, str reasoning_effort, "ModelManager | None" model_manager, *, bool verbose=False)