Skip to content
Closed
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
1 change: 1 addition & 0 deletions packages/ai/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

### Fixed

- Fixed Kimi Code, Z.AI, and ClinePass usage polls logging a warning every poll for accounts without a metered plan ([#17057](https://github.com/can1357/oh-my-pi/pull/17057) by [@trukhinyuri](https://github.com/trukhinyuri))
Comment thread
trukhinyuri marked this conversation as resolved.
Resolved
- Fixed Xiaomi MiMo API-key login failing with `Unsupported model mimo-v2.5` after the V2.5 retirement ([#16727](https://github.com/can1357/oh-my-pi/pull/16727) by [@LiPu-jpg](https://github.com/LiPu-jpg))
- Fixed non-streaming requests reporting a missing API key when the credential resolver actually failed, hiding auth broker, OAuth and network outages behind "No API key" ([#16655](https://github.com/can1357/oh-my-pi/pull/16655) by [@adi-IL](https://github.com/adi-IL))
- Fixed auth-gateway requests failing when a client requests an unsupported thinking effort; the gateway now uses the nearest supported tier ([#16671](https://github.com/can1357/oh-my-pi/pull/16671) by [@codyw912](https://github.com/codyw912))
Expand Down
6 changes: 6 additions & 0 deletions packages/ai/src/usage/cline-pass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ async function fetchClinePassUsage(params: UsageFetchParams, ctx: UsageFetchCont
response.status,
);
}
// An account that never subscribed to ClinePass (free models and credit only)
// answers 404 "no plan history found for user": nothing to meter, not a failure.
if (response.status === 404 && /no plan history/i.test(await response.text())) {
ctx.logger?.debug("ClinePass account has no plan to meter");
return null;
}
ctx.logger?.warn("ClinePass usage fetch failed", {
status: response.status,
statusText: response.statusText,
Expand Down
5 changes: 5 additions & 0 deletions packages/ai/src/usage/kimi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,11 @@ export const kimiUsageProvider: UsageProvider = {

const parsed = parseUsagePayload(payload, nowMs);
if (!parsed || parsed.rows.length === 0) {
// `{}` is Kimi's answer for an account without a Kimi Code plan: nothing to meter.
if (isRecord(payload) && Object.keys(payload).length === 0) {
ctx.logger?.debug("Kimi account has no Kimi Code plan to meter", { provider: params.provider });
return null;
}
ctx.logger?.warn("Kimi usage response invalid", { provider: params.provider });
return null;
}
Expand Down
6 changes: 6 additions & 0 deletions packages/ai/src/usage/zai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,12 @@ async function fetchZaiUsage(params: UsageFetchParams, ctx: UsageFetchContext):

if (!payload) return null;
if (payload.success !== true) {
// An account without a GLM Coding Plan (pay-as-you-go models only) answers
// `{code: 500, msg: "当前用户不存在coding plan"}`: nothing to meter, not a failure.
if (typeof payload.msg === "string" && /不存在\s*coding\s*plan|no\s+coding\s+plan/i.test(payload.msg)) {
ctx.logger?.debug("ZAI account has no Coding Plan to meter", { code: payload.code });
return null;
}
ctx.logger?.warn("ZAI usage response invalid", { code: payload.code, message: payload.msg });
return null;
}
Expand Down
15 changes: 15 additions & 0 deletions packages/ai/test/cline-pass-usage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,19 @@ describe("ClinePass usage provider", () => {
).toBe(false);
expect(clinePassUsageProvider.validatesCredentials).toBe(true);
});

it("treats an account without a ClinePass plan as nothing to meter, not a failed fetch", async () => {
const warnings: string[] = [];
const logger = { debug() {}, warn: (message: string) => warnings.push(message) };
const noPlan = { data: null, error: "no plan history found for user", success: false };
const missing = { error: "Not Found", success: false };

expect(await clinePassUsageProvider.fetchUsage(makeParams(), { ...makeContext(noPlan, 404), logger })).toBeNull();
expect(warnings).toEqual([]);

expect(
await clinePassUsageProvider.fetchUsage(makeParams(), { ...makeContext(missing, 404), logger }),
).toBeNull();
expect(warnings).toEqual(["ClinePass usage fetch failed"]);
});
});
16 changes: 16 additions & 0 deletions packages/ai/test/kimi-usage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,20 @@ describe("kimi usage provider", () => {
expect(report!.limits).toHaveLength(1);
expect(report!.limits[0]!.label).toBe("Weekly limit");
});

it("treats an account without a Kimi Code plan as nothing to meter, not an invalid response", async () => {
const warnings: string[] = [];
const logger = { debug() {}, warn: (message: string) => warnings.push(message) };
const fetchUsage = (payload: unknown) =>
kimiUsageProvider.fetchUsage(
{ provider: "kimi-code", credential: makeCredential() },
{ ...makeCtx(payload), logger },
);

expect(await fetchUsage({})).toBeNull();
expect(warnings).toEqual([]);

expect(await fetchUsage({ usage: "garbled" })).toBeNull();
expect(warnings).toEqual(["Kimi usage response invalid"]);
});
});
17 changes: 17 additions & 0 deletions packages/ai/test/zai-usage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,21 @@ describe("zai usage provider", () => {
expect(ranked.primary?.id).toBe("zai:tokens:5h");
expect(ranked.secondary?.id).toBe("zai:tokens:1w");
});

it("treats an account without a GLM Coding Plan as nothing to meter, not an invalid response", async () => {
const warnings: string[] = [];
const logger = { debug() {}, warn: (message: string) => warnings.push(message) };
const fetchUsage = (payload: unknown) =>
zaiUsageProvider.fetchUsage!(
{ provider: "zai", credential: makeCredential(), signal: undefined },
{ ...makeCtx(payload), logger },
);

expect(await fetchUsage({ code: 500, msg: "当前用户不存在coding plan", success: false })).toBeNull();
expect(warnings).toEqual([]);

expect(await fetchUsage({ code: 500, msg: "您的coding plan已过期", success: false })).toBeNull();
expect(await fetchUsage({ code: 500, msg: "internal error", success: false })).toBeNull();
expect(warnings).toEqual(["ZAI usage response invalid", "ZAI usage response invalid"]);
});
});