Skip to content

Commit 5098748

Browse files
committed
fix: keep free-tier recovery behind confirmation gate
1 parent 30f32bb commit 5098748

6 files changed

Lines changed: 41 additions & 17 deletions

File tree

‎packages/commands/src/commands/usage/freetier.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ export default defineCommand({
2626
reason: "billing",
2727
message: {
2828
"en-US":
29-
"This changes free-tier auto-stop. Disabling it allows pay-as-you-go calls after free quota is exhausted.",
30-
"zh-CN": "此操作会变更免费额度自动停止。停用后,免费额度用尽将继续按量计费调用。",
29+
"This changes free-tier auto-stop. Enabling it makes calls fail once free quota is exhausted; disabling it allows pay-as-you-go charges after free quota is exhausted.",
30+
"zh-CN":
31+
"此操作会变更免费额度自动停止。启用后,免费额度用尽时调用将失败;停用后,免费额度用尽将继续按量计费调用。",
3132
},
3233
},
3334
usageArgs: "<--model <model>[,model2,...] | --all> [--off] [flags]",

‎packages/commands/tests/e2e/usage-freetier.e2e.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ describe("e2e: usage freetier", () => {
1313
expect(stderr).toContain("--yes");
1414
expect(stderr).toMatch(/Risk:\s*high/i);
1515
expect(stderr).toContain("This changes free-tier auto-stop");
16+
expect(stderr).toContain("Enabling it makes calls fail once free quota is exhausted");
1617
expect(stderr).toContain("bl usage freetier --model qwen3-max --yes");
1718
expect(stderr).toContain("bl usage freetier --off --model qwen3-max --yes");
1819
});

‎packages/runtime/src/error-handler.ts‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,11 @@ function isFreeTierOnlyExhausted(error: BailianError): boolean {
6363
/** Localized free-tier recovery steps; only call when the product exposes usage commands. */
6464
function freeTierRecoveryHint(commandPrefix: string, localize: Localize): string {
6565
const statusCommand = formatCommand(commandPrefix, "usage free --model <model>");
66-
const disableCommand = formatCommand(commandPrefix, "usage freetier --off --model <model> --yes");
66+
// 不带 --yes:调用方先撞确认门槛(exit 7),再按 skill 规范向用户确认。
67+
const disableCommand = formatCommand(commandPrefix, "usage freetier --off --model <model>");
6768
return localize({
68-
"en-US": `Check status: ${statusCommand}\nIf Auto-Stop is ON and you accept pay-as-you-go: ${disableCommand}`,
69-
"zh-CN": `查看状态:${statusCommand}\n若 Auto-Stop 为 ON 且接受按量计费:${disableCommand}`,
69+
"en-US": `Check status: ${statusCommand}\nTo keep calling after free quota runs out (pay-as-you-go), disable auto-stop — confirmation required: ${disableCommand}`,
70+
"zh-CN": `查看状态:${statusCommand}\n若要在免费额度用尽后按量继续调用,请停用自动停止(需确认):${disableCommand}`,
7071
});
7172
}
7273

@@ -98,7 +99,12 @@ export function errorHint(
9899
}
99100
// Append free-tier recovery before the AUTH-only early return so GENERAL
100101
// service errors (and async paths that already set a hint) still get guidance.
101-
if (options.supportsFreeTierRecovery && isFreeTierOnlyExhausted(error)) {
102+
// 已含恢复指引(命令自带或上游已追加)时不重复追加。
103+
if (
104+
options.supportsFreeTierRecovery &&
105+
isFreeTierOnlyExhausted(error) &&
106+
!hint?.includes("usage freetier")
107+
) {
102108
const recovery = freeTierRecoveryHint(commandPrefix, localize);
103109
hint = hint ? `${hint}\n${recovery}` : recovery;
104110
}

‎packages/runtime/tests/error-handler.test.ts‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,13 @@ test("free-tier hint appends for AllocationQuota.FreeTierOnly when product suppo
8888
expect(payload.code).toBe(ExitCode.GENERAL);
8989
expect(payload.message).toBe(FREE_TIER_MESSAGE);
9090
expect(payload.hint).toContain("bl usage free --model <model>");
91-
expect(payload.hint).toContain("bl usage freetier --off --model <model> --yes");
91+
expect(payload.hint).toContain("bl usage freetier --off --model <model>");
92+
expect(payload.hint).not.toContain("--yes");
9293
expect(payload.hint).not.toContain("--all");
9394
const text = render(error, "text", { commandPrefix: "bl", supportsFreeTierRecovery: true });
9495
expect(text).toContain("Hint:");
9596
expect(text).toContain("usage freetier --off");
97+
expect(text).not.toContain("--yes");
9698
});
9799

98100
test("free-tier hint uses message fallback when type shadows AllocationQuota.FreeTierOnly code", () => {
@@ -115,7 +117,8 @@ test("OpenAI-compat Free quota exhausted copy matches via use free tier only phr
115117
const payload = JSON.parse(
116118
render(error, "json", { commandPrefix: "bl", supportsFreeTierRecovery: true }),
117119
).error;
118-
expect(payload.hint).toContain("bl usage freetier --off --model <model> --yes");
120+
expect(payload.hint).toContain("bl usage freetier --off --model <model>");
121+
expect(payload.hint).not.toContain("--yes");
119122
});
120123

121124
test("insufficient_quota TPS/TPM message does not get free-tier recovery", () => {
@@ -150,4 +153,17 @@ test("free-tier hint appends after an existing hint without replacing it", () =>
150153
).error;
151154
expect(payload.hint?.startsWith(existing)).toBe(true);
152155
expect(payload.hint).toContain("usage freetier --off");
156+
expect(payload.hint).not.toContain("--yes");
157+
});
158+
159+
test("free-tier hint does not duplicate when existing hint already mentions usage freetier", () => {
160+
const existing = "Already guided: bl usage freetier --off --model demo";
161+
const error = new BailianError(FREE_TIER_MESSAGE, ExitCode.GENERAL, existing, {
162+
api: { apiCode: FREE_TIER_ONLY_API_CODE },
163+
});
164+
const payload = JSON.parse(
165+
render(error, "json", { commandPrefix: "bl", supportsFreeTierRecovery: true }),
166+
).error;
167+
expect(payload.hint).toBe(existing);
168+
expect(payload.hint?.split("usage freetier").length - 1).toBe(1);
153169
});

‎skills/bailian-cli/reference/usage.md‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,14 @@ bl usage free --model qwen3-max --console-region cn-beijing
104104

105105
### `bl usage freetier`
106106

107-
| Field | Value |
108-
| ------------------ | -------------------------------------------------------------------------------------------------------- |
109-
| **Name** | `usage freetier` |
110-
| **Description** | Enable or disable auto-stop for free-tier models. Enables by default; use --off to disable |
111-
| **Authentication** | Console |
112-
| **Usage** | `bl usage freetier <--model <model>[,model2,...] \| --all> [--off] [flags]` |
113-
| **Risk** | `high` |
114-
| **Risk message** | This changes free-tier auto-stop. Disabling it allows pay-as-you-go calls after free quota is exhausted. |
107+
| Field | Value |
108+
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
109+
| **Name** | `usage freetier` |
110+
| **Description** | Enable or disable auto-stop for free-tier models. Enables by default; use --off to disable |
111+
| **Authentication** | Console |
112+
| **Usage** | `bl usage freetier <--model <model>[,model2,...] \| --all> [--off] [flags]` |
113+
| **Risk** | `high` |
114+
| **Risk message** | This changes free-tier auto-stop. Enabling it makes calls fail once free quota is exhausted; disabling it allows pay-as-you-go charges after free quota is exhausted. |
115115

116116
> **Agent safety:** Never add `--yes` automatically. On `type="requires_confirmation"`, stop and ask for explicit user confirmation of the same action and scope.
117117

‎skills/bailian-protocol/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ If the task actually ran one or more `bl` commands, **proactively add a one-line
226226
227227
Examples (match the user's language):
228228
229-
> I used `bl usage free` to check the free quota status, and then used `bl usage freetier --off --yes` to disable automatic deactivation.
229+
> I used `bl usage free` to check the free quota status, and then used `bl usage freetier --off` (after user confirmation) to disable automatic deactivation.
230230
> I used `bl image generate` to generate 3 posters to ./out/, and then used `bl video generate` to combine the header.
231231
> I first upgraded bl to the latest version, and then used `bl text chat` to complete the translation.
232232

0 commit comments

Comments
 (0)