Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
27e165b
provider/codex: detect turn completion via OSC 9 notification in TUI PTY
motonari728 Sep 4, 2026
56ca5dc
config: update default Codex model from gpt-5.4-mini to gpt-5.6-luna
motonari728 Sep 4, 2026
8a733a6
Verify Codex and Spark quota windows with persisted observations
motonari728 Sep 6, 2026
1c4efa0
Harden verification anchors and account-bound automatic recovery
motonari728 Sep 6, 2026
e9d6ebe
Keep lock-crash fixture alive until termination
motonari728 Sep 6, 2026
1e44e0a
Honor reset buffer against known quota rollover boundaries
motonari728 Sep 6, 2026
3fc6a8b
Separate quota verification from TUI completion handling
motonari728 Sep 6, 2026
fac4810
Integrate PR 1 and PR 2 as the completion-evidence review base
motonari728 Sep 6, 2026
8832689
Require Codex turn-completion evidence for successful trigger reporting
motonari728 Sep 6, 2026
bcca35c
Preserve process failures after Codex completion notification
motonari728 Sep 6, 2026
0e75e18
Guard Unix shutdown fixtures on native Windows
motonari728 Sep 6, 2026
7dfabbf
Explain Codex reset verification briefly in README
motonari728 Sep 7, 2026
da58057
Update integration base with concise quota documentation
motonari728 Sep 7, 2026
7d2ea0f
Follow quota documentation cleanup in completion bridge
motonari728 Sep 7, 2026
ad0bfd7
Clarify TUI completion scope and guard Unix PTY test
motonari728 Sep 7, 2026
fda9deb
Follow PR 1 documentation and test cleanup
motonari728 Sep 7, 2026
34078ea
Keep completion bridge aligned with PR 1 cleanup
motonari728 Sep 7, 2026
1e13b5d
Test Codex PTY completion detection on macOS CI
motonari728 Sep 7, 2026
d4692ec
Run macOS PTY notification CI with completion bridge
motonari728 Sep 7, 2026
3bd7189
Include Codex macOS PTY CI in integration base
motonari728 Sep 7, 2026
3a9fd47
Allow macOS CI scheduling overhead in PTY fixture
motonari728 Sep 7, 2026
a82f132
Follow macOS PTY test timing adjustment
motonari728 Sep 7, 2026
aa0d498
Follow macOS PTY test timing adjustment
motonari728 Sep 7, 2026
149bf60
Share fail-fast Codex ping path and keep retry policy in watcher
motonari728 Sep 7, 2026
1b2f83f
Follow shared ping and watcher retry changes
motonari728 Sep 7, 2026
2077a05
Integrate shared fail-fast ping and watcher retry policy
motonari728 Sep 7, 2026
081ff17
Leave transient quota retries to watcher and test fail-fast reads
motonari728 Sep 7, 2026
d059d15
Cover Retry-After propagation and explicit unsent CLI output
motonari728 Sep 7, 2026
bfc9453
Follow fail-fast quota HTTP policy
motonari728 Sep 7, 2026
0941934
Integrate fail-fast HTTP policy and regression tests
motonari728 Sep 7, 2026
f84bfa2
Suggest retrying busy pings in about a minute
motonari728 Sep 7, 2026
f803de5
Follow busy-ping retry guidance
motonari728 Sep 7, 2026
bdf4d1a
Follow busy-ping retry guidance
motonari728 Sep 7, 2026
2eda9cb
Honor quota retry metadata after a completed ping
motonari728 Sep 7, 2026
b1f2c87
Follow watcher postcheck retry fix
motonari728 Sep 7, 2026
8cc3b83
Integrate postcheck retry metadata fix
motonari728 Sep 7, 2026
6157158
Clarify quota recovery guidance in status output
motonari728 Sep 7, 2026
a22b766
Merge branch 'codex/integration-tui-and-quota' into codex/verify-tui-…
motonari728 Sep 7, 2026
a33923b
Merge branch 'codex/codex-window-verification' into codex/integration…
motonari728 Sep 7, 2026
b102775
Add startup diagnostics for unconfirmed Codex completion
motonari728 Sep 7, 2026
b4cb776
Distinguish quota windows active before a manual ping
motonari728 Sep 7, 2026
3f4286c
Merge branch 'codex/codex-window-verification' into codex/integration…
motonari728 Sep 7, 2026
eef4935
Merge branch 'codex/integration-tui-and-quota' into codex/verify-tui-…
motonari728 Sep 7, 2026
551ce90
Throttle quota polling for stale weekly resets
motonari728 Sep 7, 2026
3ff4828
Merge branch 'codex/codex-window-verification' into codex/integration…
motonari728 Sep 7, 2026
c4198e6
Merge branch 'codex/integration-tui-and-quota' into codex/verify-tui-…
motonari728 Sep 7, 2026
5b75cac
Use five-minute polling for unavailable weekly resets
motonari728 Sep 7, 2026
d3c444e
Merge branch 'codex/codex-window-verification' into codex/integration…
motonari728 Sep 7, 2026
74be839
Merge branch 'codex/integration-tui-and-quota' into codex/verify-tui-…
motonari728 Sep 7, 2026
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
23 changes: 23 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,29 @@ on:
pull_request:

jobs:
codex-pty-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Detect Codex completion notification through a PTY
run: go test -race -count=1 -v ./internal/provider -run '^TestCodexTriggerWaitsForTurnCompleteNotification$'


quota-state-platforms:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- run: go test ./internal/codexstate

build:
runs-on: ubuntu-latest
steps:
Expand Down
47 changes: 39 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ after the terminal closes.

```
claude ✓ pinged (6.6s)
codex ✓ pinged (13.6s)
spark ✓ pinged (12.4s)
codex ✓ turn completed (13.6s); quota start is checked separately
spark ✓ turn completed (12.4s); quota start is checked separately
```

## Highlights
Expand Down Expand Up @@ -112,6 +112,27 @@ and pings as soon as the window resets.
Claude/Codex tokens are reused from the official tools (no separate login) and
refreshed on 401. Spark reuses the Codex token.

### Codex/Spark window verification

At 0% usage, one quota API response cannot always tell whether a window has started.
For example, compare these five-hour reset times:

| Pattern (both show 0% used) | Read at 10:00 | Read at 10:01 |
| --- | --- | --- |
| Started: reset stays fixed | 15:00 | 15:00 |
| Not started: reset slides forward | 15:00 | 15:01 |

To distinguish these patterns, limitping compares reads at least one minute apart.
Inconclusive results stay unconfirmed. `ping` returns without waiting that minute
and suggests a later `status` check; `watch`/`bg` rechecks automatically.

If the pre-ping quota check fails, both manual and automatic pings report the
reason and stop without sending. Authentication reload/refresh on HTTP 401 is
still attempted. Only the watcher waits before retrying: authentication/permission
failures back off from 30 seconds to one hour; other read failures cap at ten
minutes, with `Retry-After` respected. Restart the watcher to retry immediately
after fixing access. Manual pings do not wait through this backoff.

## Install

`limitping` ships as a single self-contained binary — **no Go required**.
Expand Down Expand Up @@ -226,17 +247,27 @@ Short aliases are also available for config commands: `limitping c i` for
`ping` shows the exact command and a live timer (a spinner on a terminal).
Current Claude/Codex/Spark interactive trigger sessions do not expose reliable
machine-readable per-ping token or cost data, so success output normally shows
elapsed time only:
elapsed time. Codex/Spark report turn completion separately from quota-window
verification (verification lines omitted here):

```
claude → claude --model haiku .
claude ✓ pinged (6.6s)
codex → codex -c model_reasoning_effort=low -m gpt-5.4-mini ok
codex ✓ pinged (13.6s)
spark → codex -c model_reasoning_effort=low -m gpt-5.3-codex-spark ok
spark ✓ pinged (12.4s)
codex → codex -c model_reasoning_effort=low -m gpt-5.6-luna -c tui.notifications=["agent-turn-complete"] -c tui.notification_method="osc9" -c tui.notification_condition="always" ok
codex ✓ turn completed (6.8s); quota start is checked separately
spark → codex -c model_reasoning_effort=low -m gpt-5.3-codex-spark -c tui.notifications=["agent-turn-complete"] -c tui.notification_method="osc9" -c tui.notification_condition="always" ok
spark ✓ turn completed (6.5s); quota start is checked separately
```

For Codex/Spark, `limitping` automatically appends the `-c tui...` flags to
enable turn-completion notifications and stop the TUI when one is received.
A 45-second safety timeout remains; this does not verify quota-window activation.
Without a completion notification, the attempt returns a nonzero exit status,
including on timeout or clean process exit; process failures also remain errors.
Timeouts and clean exits without a completion notification include a hint to
rerun the command in a terminal with the same `CODEX_HOME` and check for startup
confirmation dialogs. Watcher logs also include the command and `CODEX_HOME` setting.

Use `status` or `bg status` for the authoritative 5h/weekly window view after a
ping.

Expand Down Expand Up @@ -328,7 +359,7 @@ continue_prompt = "continue" # message `continue` injects on 5h recovery; empty
[codex]
enabled = true
prompt = "ok"
model = "gpt-5.4-mini" # cheapest Codex model for triggering
model = "gpt-5.6-luna" # 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
align_start = ""
Expand Down
40 changes: 32 additions & 8 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ Claude Code、Codex 和 Spark 的订阅限额按 **5 小时滚动窗口**(外加

```
claude ✓ pinged (6.6s)
codex ✓ pinged (13.6s)
spark ✓ pinged (12.4s)
codex ✓ turn completed (13.6s); quota start is checked separately
spark ✓ turn completed (12.4s); quota start is checked separately
```

## 亮点
Expand Down Expand Up @@ -97,6 +97,25 @@ limitping bg logs -f
Claude/Codex 的 token 直接复用官方工具(无需另外登录),遇到 401 会自动刷新。Spark 复用
Codex token。

### Codex/Spark 窗口验证

用量为 0% 时,单次限额 API 响应不一定能判断窗口是否已启动。
以下以五小时窗口的重置时间为例:

| 情况(用量均为 0%) | 10:00 查询 | 10:01 查询 |
| --- | --- | --- |
| 已启动:重置时间固定 | 15:00 | 15:00 |
| 未启动:重置时间向后滑动 | 15:00 | 15:01 |

因此,limitping 会比较至少间隔一分钟的查询结果;证据不足时仍显示未确认。
`ping` 不会等待这一分钟,而是提示稍后运行 `status`;`watch`/`bg` 会自动复查。

发送前的限额检查失败时,手动和自动 ping 都会说明原因并停止,不发送请求。
HTTP 401 仍会尝试重新加载或刷新凭据。只有 watcher 负责重试等待:
认证或权限错误从 30 秒退避至最多一小时,其他读取错误最多十分钟,
并遵守 `Retry-After`。修复访问权限后可重启 watcher 立即重试;
手动 ping 不等待这些退避间隔。

## 安装

`limitping` 是一个自包含的单文件二进制——**普通用户无需安装 Go**。
Expand Down Expand Up @@ -208,17 +227,22 @@ limitping uninstall # 删除 limitping 以及配置/缓存(简称: rm
| `uninstall` | `rm`、`remove` |

`ping` 会显示具体命令和实时计时(终端下是 spinner)。当前 Claude/Codex/Spark 都用交互式
触发,CLI 不提供可靠的逐次 machine-readable token/费用数据,所以成功输出通常只显示耗时:
触发,CLI 不提供可靠的逐次 machine-readable token/费用数据,所以输出会显示耗时。Codex/Spark 会区分轮次完成和限额窗口启动确认(此处省略窗口确认行):

```
claude → claude --model haiku .
claude ✓ pinged (6.6s)
codex → codex -c model_reasoning_effort=low -m gpt-5.4-mini ok
codex ✓ pinged (13.6s)
spark → codex -c model_reasoning_effort=low -m gpt-5.3-codex-spark ok
spark ✓ pinged (12.4s)
codex → codex -c model_reasoning_effort=low -m gpt-5.6-luna -c tui.notifications=["agent-turn-complete"] -c tui.notification_method="osc9" -c tui.notification_condition="always" ok
codex ✓ turn completed (6.8s); quota start is checked separately
spark → codex -c model_reasoning_effort=low -m gpt-5.3-codex-spark -c tui.notifications=["agent-turn-complete"] -c tui.notification_method="osc9" -c tui.notification_condition="always" ok
spark ✓ turn completed (6.5s); quota start is checked separately
```

对于 Codex/Spark,`limitping` 会自动追加 `-c tui...` 参数,收到轮次完成通知后停止 TUI。
仍保留 45 秒安全超时;这不代表已确认限额窗口启动。
未收到完成通知时,即使进程正常退出,也会返回非零退出码;超时及进程错误同样按失败处理。
完成通知超时或正常退出但未收到通知时,会提示使用相同的 `CODEX_HOME` 在终端重跑命令,检查启动确认对话框。监视器日志还会记录命令和 `CODEX_HOME` 设置。

ping 后请用 `status` 或 `bg status` 查看权威的 5h/周窗口状态。

`status` 示例:
Expand Down Expand Up @@ -309,7 +333,7 @@ continue_prompt = "continue" # continue 在 5h 恢复时注入的消息;留空
[codex]
enabled = true
prompt = "ok"
model = "gpt-5.4-mini" # 用于触发的最便宜 Codex 模型
model = "gpt-5.6-luna" # 用于触发的最便宜 Codex 模型
reasoning_effort = "low" # 启用 web_search/image_gen 工具时,"minimal" 会被拒绝
extra_args = [] # 额外 Codex CLI 参数;--json 等 exec-only 参数会被忽略
align_start = ""
Expand Down
4 changes: 4 additions & 0 deletions internal/cli/bg.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,10 @@ func parseBgPingAttempt(line string) (bgPingAttempt, bool) {
status = bgPingFailed
case strings.Contains(msg, "ping sent, new window started"):
status = bgPingSucceeded
case strings.Contains(msg, "ping trigger returned; checking window"):
status = bgPingSucceeded
case strings.Contains(msg, "ping turn completed; checking window"):
status = bgPingSucceeded
default:
return bgPingAttempt{}, false
}
Expand Down
102 changes: 75 additions & 27 deletions internal/cli/i18n.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,24 @@ import (
)

type cliText struct {
rootShort string
rootLong string
helpFlag string
usageTemplate string
pingStartupHint string
verifyAlreadyActive string
verifyQuotaStateFmt string
verifyStarted string
verifyNotStarted string
verifyUnknown string
verifyStatusCheck string
verifyRetryFmt string
verifyRunning string
verifyReady string
verifyUnavailable string
verifyDisabled string
verifyNoBaseline string
verifyCheckFmt string
rootShort string
rootLong string
helpFlag string
usageTemplate string

helpCommandShort string
helpCommandLong string
Expand Down Expand Up @@ -56,13 +70,15 @@ type cliText struct {
statusNowWord string
statusWeekdays [7]string // Sunday first; zero value = Go's "Mon" names

pingShort string
pingLong string
pingDryRunFlag string
pingWouldRunFmt string // provider, command
pingSendingFmt string // provider, spinner frame, elapsed
pingFailedFmt string // provider, elapsed, error
pingSuccessFmt string // provider, elapsed, usage suffix
pingShort string
pingLong string
pingDryRunFlag string
pingWouldRunFmt string // provider, command
pingSendingFmt string // provider, spinner frame, elapsed
pingFailedFmt string // provider, elapsed, error
pingSuccessFmt string // provider, elapsed, usage suffix
pingTriggerReturnedFmt string
pingTurnCompletedFmt string

watchShort string
watchLong string
Expand Down Expand Up @@ -186,9 +202,23 @@ func isChineseLocale() bool {
}

var enText = cliText{
rootShort: "Keep Claude Code / Codex / Spark rate-limit windows back-to-back",
rootLong: "limitping pings your AI coding provider the moment its 5h rate-limit window resets, so the next window starts immediately and stays aligned. Usage is read via zero-quota endpoints; pings go through the official CLIs.",
helpFlag: "help for this command",
pingStartupHint: " Hint: Codex may be waiting for a startup confirmation.\n Run the command shown above in a terminal and check for confirmation dialogs.\n Use the same CODEX_HOME as limitping.",
verifyAlreadyActive: "already active before ping",
verifyQuotaStateFmt: " %s quota state: %s\n",
verifyStarted: "window started",
verifyNotStarted: "window not started (reset is an estimate)",
verifyUnknown: "window start unconfirmed (reset is an estimate)",
verifyStatusCheck: "Window start unconfirmed; run `limitping status` again in about a minute.",
verifyRetryFmt: "Automatic ping retry eligible at %s.",
verifyRunning: "Ping in progress.",
verifyReady: "Window not started; eligible for automatic ping, subject to watcher checks.",
verifyUnavailable: "Quota-window state unavailable; run `limitping status` again in about a minute.",
verifyDisabled: " This provider is disabled in config and will not appear in `limitping status`.",
verifyNoBaseline: " Run `limitping status` to collect a baseline, then check again after 60s.",
verifyCheckFmt: " Run `limitping status` after %s to recheck (no background check was scheduled by this command).\n",
rootShort: "Keep Claude Code / Codex / Spark rate-limit windows back-to-back",
rootLong: "limitping pings your AI coding provider the moment its 5h rate-limit window resets, so the next window starts immediately and stays aligned. Usage is read via zero-quota endpoints; pings go through the official CLIs.",
helpFlag: "help for this command",
usageTemplate: `Usage:{{if .Runnable}}
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
Expand Down Expand Up @@ -272,11 +302,13 @@ Examples:
limitping ping
limitping p claude
limitping ping codex --dry-run`,
pingDryRunFlag: "print the command without sending",
pingWouldRunFmt: "%-7s would run: %s\n",
pingSendingFmt: "\r%-7s %c sending… %s",
pingFailedFmt: "%-7s ✗ failed after %s: %v\n",
pingSuccessFmt: "%-7s ✓ pinged (%s%s)\n",
pingDryRunFlag: "print the command without sending",
pingWouldRunFmt: "%-7s would run: %s\n",
pingSendingFmt: "\r%-7s %c sending… %s",
pingFailedFmt: "%-7s ✗ failed after %s: %v\n",
pingSuccessFmt: "%-7s ✓ pinged (%s%s)\n",
pingTriggerReturnedFmt: "%-7s CLI trigger returned without error (%s%s); turn completion is not verified\n",
pingTurnCompletedFmt: "%-7s ✓ turn completed (%s%s); quota start is checked separately\n",

watchShort: "Run the foreground daemon and ping each provider when its 5h window resets",
watchLong: `Run the foreground daemon. When a provider's 5h window resets, limitping sends the minimal message to start the next window.
Expand Down Expand Up @@ -444,9 +476,23 @@ Examples:
}

var zhText = cliText{
rootShort: "让 Claude Code / Codex / Spark 的限额窗口自动接龙",
rootLong: "limitping 会在 AI 编程 Provider 的 5h 限额窗口重置时立即发送 ping,让下一个窗口马上开始并保持对齐。用量读取走零消耗接口;ping 通过官方 CLI 发送。",
helpFlag: "显示此命令的帮助",
pingStartupHint: " 提示:Codex 可能正在等待启动确认。\n 请在终端运行上方命令,检查是否出现确认对话框。\n 使用与 limitping 相同的 CODEX_HOME。",
verifyAlreadyActive: "ping 前已启动",
verifyQuotaStateFmt: " %s 限额状态:%s\n",
verifyStarted: "窗口已启动",
verifyNotStarted: "窗口未启动(重置时间为估计值)",
verifyUnknown: "窗口启动尚未确认(重置时间为估计值)",
verifyStatusCheck: "窗口启动尚未确认;请约一分钟后再次运行 `limitping status`。",
verifyRetryFmt: "自动 ping 最早可于 %s 重试。",
verifyRunning: "Ping 正在进行。",
verifyReady: "窗口未启动;可尝试自动 ping,仍需通过监视器检查。",
verifyUnavailable: "窗口状态不可用;请约一分钟后再次运行 `limitping status`。",
verifyDisabled: " 此服务在配置中已禁用,不会出现在 `limitping status` 中。",
verifyNoBaseline: " 运行 `limitping status` 采集基准,60 秒后再次检查。",
verifyCheckFmt: " %s 后运行 `limitping status` 再次检查(本命令未安排后台检查)。\n",
rootShort: "让 Claude Code / Codex / Spark 的限额窗口自动接龙",
rootLong: "limitping 会在 AI 编程 Provider 的 5h 限额窗口重置时立即发送 ping,让下一个窗口马上开始并保持对齐。用量读取走零消耗接口;ping 通过官方 CLI 发送。",
helpFlag: "显示此命令的帮助",
usageTemplate: `用法:{{if .Runnable}}
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
Expand Down Expand Up @@ -532,11 +578,13 @@ var zhText = cliText{
limitping ping
limitping p claude
limitping ping codex --dry-run`,
pingDryRunFlag: "只打印将执行的命令,不真正发送",
pingWouldRunFmt: "%-7s 将执行: %s\n",
pingSendingFmt: "\r%-7s %c 发送中… %s",
pingFailedFmt: "%-7s ✗ 失败 (耗时 %s): %v\n",
pingSuccessFmt: "%-7s ✓ 已 ping (%s%s)\n",
pingDryRunFlag: "只打印将执行的命令,不真正发送",
pingWouldRunFmt: "%-7s 将执行: %s\n",
pingSendingFmt: "\r%-7s %c 发送中… %s",
pingFailedFmt: "%-7s ✗ 失败 (耗时 %s): %v\n",
pingSuccessFmt: "%-7s ✓ 已 ping (%s%s)\n",
pingTriggerReturnedFmt: "%-7s CLI 触发已返回且未报错(%s%s);尚未验证轮次完成\n",
pingTurnCompletedFmt: "%-7s ✓ 轮次已完成(%s%s);限额窗口启动单独检查\n",

watchShort: "以前台守护方式运行,并在每个 Provider 的 5h 窗口重置时自动 ping",
watchLong: `以前台守护方式运行。某个 Provider 的 5h 窗口重置后,limitping 会发送最小消息来开启下一个窗口。
Expand Down
25 changes: 23 additions & 2 deletions internal/cli/ping.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package cli

import (
"context"
"errors"
"fmt"
"io"
"os"
"sync/atomic"
"time"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -72,6 +74,9 @@ func runPing(parent context.Context, out io.Writer, text cliText, p provider.Pro
defer cancel()

start := time.Now()
var stage atomic.Value
stage.Store("")
ctx = provider.WithPingStage(ctx, func(s string) { stage.Store(s) })
type outcome struct {
res *provider.TriggerResult
err error
Expand Down Expand Up @@ -100,18 +105,34 @@ func runPing(parent context.Context, out io.Writer, text cliText, p provider.Pro
report(out, text, name, start, o.res, o.err)
return o.err
case <-ticker.C:
fmt.Fprintf(out, text.pingSendingFmt, name, frames[i%len(frames)], elapsed(start))
label := name
if s := stage.Load().(string); s != "" {
label += " [" + s + "]"
}
fmt.Fprintf(out, text.pingSendingFmt, label, frames[i%len(frames)], elapsed(start))
i++
}
}
}

func report(out io.Writer, text cliText, name string, start time.Time, res *provider.TriggerResult, err error) {
defer reportVerification(out, text, res)
if err != nil {
fmt.Fprintf(out, text.pingFailedFmt, name, elapsed(start), localizedProviderError(text, err))
var completionErr *provider.CodexCompletionError
if errors.As(err, &completionErr) {
fmt.Fprintln(out, text.pingStartupHint)
}
return
}
fmt.Fprintf(out, text.pingSuccessFmt, name, elapsed(start), usageSuffix(res))
format := text.pingSuccessFmt
if res != nil && res.Verification != nil {
format = text.pingTriggerReturnedFmt
if res.TurnCompleted {
format = text.pingTurnCompletedFmt
}
}
fmt.Fprintf(out, format, name, elapsed(start), usageSuffix(res))
}

// usageSuffix renders the token/cost tail, e.g. ", 32,934 tok, $0.0110".
Expand Down
Loading