Skip to content
Open
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
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,9 @@ align_start = "" # optional RFC3339 anchor for the first window; empty
[codex]
enabled = true
prompt = "ok"
# Use either "model" (single) or "models" (array) for Codex.
# If different models maintain separate rate limit windows, list all of them:
# models = ["gpt-5.4-mini", "gpt-5.3-codex-spark"]
model = "gpt-5.4-mini" # cheapest Codex model for triggering
reasoning_effort = "low" # "minimal" is rejected when web_search/image_gen tools are enabled
extra_args = [] # extra Codex CLI args; exec-only flags such as --json are ignored
Expand Down Expand Up @@ -308,8 +311,12 @@ your budget:

Claude/Codex don't expose per-model prices at runtime (Anthropic's local cost
cache is empty; Codex's model cache has no price field), so the cheapest model is
a sensible default rather than a live price lookup. Override `model` per provider
if you prefer.
a sensible default rather than a live price lookup. Override `model` (or `models`)
per provider if you prefer.

If you use multiple Codex models that have independent windows (for example
`gpt-5.3-codex-spark`), configure `models = ["gpt-5.4-mini", "gpt-5.3-codex-spark"]`
so `limitping` pings every listed model each time a window resets.

### Active-session detection (hooks)

Expand Down
7 changes: 6 additions & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ align_start = "" # 可选 RFC3339:首个窗口的相位锚点;留空 =
[codex]
enabled = true
prompt = "ok"
# 使用 "model"(单个)或 "models"(数组)。若不同模型有独立窗口,可列出多个:
# models = ["gpt-5.4-mini", "gpt-5.3-codex-spark"]
model = "gpt-5.4-mini" # 用于触发的最便宜 Codex 模型
reasoning_effort = "low" # 启用 web_search/image_gen 工具时,"minimal" 会被拒绝
extra_args = [] # 额外 Codex CLI 参数;--json 等 exec-only 参数会被忽略
Expand All @@ -284,7 +286,10 @@ align_start = ""

Claude/Codex 运行时都拿不到每个模型的价格(Anthropic 本地价格缓存是空的;Codex 的模型
缓存没有价格字段),所以这里用"最便宜模型"作为合理默认,而不是实时查价。需要的话可
按 Provider 覆盖 `model`。
按 Provider 覆盖 `model`(或用 `models` 数组)。

如果 Codex 的不同模型(如 `gpt-5.3-codex-spark`)有各自的窗口,请配置
`models = ["gpt-5.4-mini", "gpt-5.3-codex-spark"]`,这样每次重置时都会为所有模型 ping。

### 活跃会话检测(钩子)

Expand Down
41 changes: 36 additions & 5 deletions internal/cli/bg.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/spf13/cobra"

"github.com/wavever/CCLimitPing/internal/config"
"github.com/wavever/CCLimitPing/internal/provider"
)

const (
Expand Down Expand Up @@ -266,15 +267,45 @@ func runBgStatus(ctx context.Context, out io.Writer) error {
return nil
}

names := make([]string, len(providers))
for i, p := range providers {
names[i] = p.Name()
// Build watching list, expanding Codex to show the configured model(s)
// being pinged (e.g. gpt-5.3-codex-spark).
watchNames := make([]string, 0, len(providers))
for _, p := range providers {
n := p.Name()
if n == "codex" {
ms := config.CodexModels(cfg.Codex)
if len(ms) == 1 {
n = "codex (" + ms[0] + ")"
} else if len(ms) > 1 {
n = "codex (" + strings.Join(ms, ", ") + ")"
}
}
watchNames = append(watchNames, n)
}
fmt.Fprintf(out, " %s: %s%s\n", text.bgFieldWatching, strings.Join(names, ", "), dryRunNote(st.DryRun))
fmt.Fprintf(out, " %s: %s%s\n", text.bgFieldWatching, strings.Join(watchNames, ", "), dryRunNote(st.DryRun))

// Per-provider usage, the same view as `limitping status`.
// Per-provider usage (expanded per Codex model when the config lists
// multiple models so that `bg status` shows e.g. gpt-5.3-codex-spark usage).
fmt.Fprintln(out)
for _, p := range providers {
if codex, ok := p.(*provider.Codex); ok {
models := config.CodexModels(cfg.Codex)
if len(models) > 0 {
for _, m := range models {
mctx, mcancel := context.WithTimeout(ctx, bgUsageTimeout)
mu, merr := codex.ReadUsageForModel(mctx, m)
mcancel()
if merr != nil {
// fall back to a generic read
rctx, rcancel := context.WithTimeout(ctx, bgUsageTimeout)
mu, _ = p.ReadUsage(rctx)
rcancel()
}
printUsageWithLabel(out, "codex ("+m+")", mu, false)
}
continue
}
}
rctx, cancel := context.WithTimeout(ctx, bgUsageTimeout)
u, uerr := p.ReadUsage(rctx)
cancel()
Expand Down
25 changes: 24 additions & 1 deletion internal/cli/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,22 @@ func runStatus(ctx context.Context, out, progress io.Writer, text cliText, provi
entries = append(entries, newStatusJSON(u, verbose))
continue
}
// For Codex with multiple configured models, show separate usage
// blocks using model-specific data from additional_rate_limits when available.
if codex, ok := p.(*provider.Codex); ok {
ccfg, _ := config.Load()
models := config.CodexModels(ccfg.Codex)
if len(models) > 0 {
for _, m := range models {
mu, merr := codex.ReadUsageForModel(ctx, m)
if merr != nil {
mu = u
}
printUsageWithLabel(out, "codex ("+m+")", mu, verbose)
}
continue
}
}
printUsage(out, u, verbose)
}
if jsonOut {
Expand Down Expand Up @@ -154,11 +170,18 @@ func newWindowJSON(w usage.Window) *windowJSON {
}

func printUsage(out io.Writer, u *usage.Usage, verbose bool) {
printUsageWithLabel(out, u.Provider, u, verbose)
}

// printUsageWithLabel prints the usage snapshot using the provided label
// as the provider/model heading (used by bg status to show per-Codex-model
// usage when multiple models are configured for pinging).
func printUsageWithLabel(out io.Writer, label string, u *usage.Usage, verbose bool) {
plan := u.Plan
if plan != "" {
plan = " (" + plan + ")"
}
fmt.Fprintf(out, "%s%s\n", u.Provider, plan)
fmt.Fprintf(out, "%s%s\n", label, plan)
fmt.Fprintf(out, " 5h %s\n", fmtWindow(u.FiveHour))
fmt.Fprintf(out, " weekly %s\n", fmtWindow(u.Weekly))
if u.Credits != nil && (u.Credits.HasCredits || u.Credits.Unlimited) {
Expand Down
28 changes: 25 additions & 3 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,35 @@ func (d Duration) MarshalText() ([]byte, error) {

// ProviderConfig holds the per-provider knobs. ReasoningEffort applies only to
// Codex and is ignored by Claude.
//
// For Codex, you can specify a single model via "model" or multiple via "models".
// When "models" is set, all listed models are pinged (useful if different
// Codex models have separate rate-limit windows).
type ProviderConfig struct {
Enabled bool `toml:"enabled"`
Prompt string `toml:"prompt"`
ExtraArgs []string `toml:"extra_args"`
Model string `toml:"model"`
Models []string `toml:"models"`
ReasoningEffort string `toml:"reasoning_effort"`
AlignStart string `toml:"align_start"`
}

// CodexModels returns the list of models that the Codex provider should ping
// (and that should be reflected in status reports). It prefers the "models"
// array, falls back to the singular "model" field, and finally the default.
func CodexModels(cfg ProviderConfig) []string {
if len(cfg.Models) > 0 {
cp := make([]string, len(cfg.Models))
copy(cp, cfg.Models)
return cp
}
if cfg.Model != "" {
return []string{cfg.Model}
}
return []string{"gpt-5.4-mini"}
}

// Config is the full configuration.
type Config struct {
// WeeklyThreshold: skip pinging when the weekly window's utilization
Expand Down Expand Up @@ -178,9 +198,11 @@ align_start = ""
[codex]
enabled = true
prompt = "ok"
# Cheapest Codex model for triggering (see ~/.codex/models_cache.json for the
# list available to your plan). Empty = use the Codex default model.
model = "gpt-5.4-mini"
# Codex model(s) for triggering. Use "model" for one, or "models" (array) to
# ping multiple models on every window reset (e.g. ["gpt-5.4-mini", "gpt-5.3-codex-spark"]
# if they maintain separate 5h windows). See ~/.codex/models_cache.json.
# model = "gpt-5.4-mini" # cheapest Codex model for triggering
models = ["gpt-5.4-mini"]
# "low" keeps the ping cheap; "minimal" is rejected when web_search/image_gen
# tools are enabled in your Codex config.
reasoning_effort = "low"
Expand Down
Loading