From e6416e877b736337c1c7a35aeb53b5641b5b4ad0 Mon Sep 17 00:00:00 2001 From: Jonathan Karras Date: Sun, 26 Apr 2026 21:43:32 -0600 Subject: [PATCH] Adjust Synthetic quotas to 5-hour weekly and search limits --- README.md | 23 ++++- README.zh-CN.md | 23 ++++- package.json | 2 +- plugin/lib/i18n.ts | 16 +++- plugin/lib/synthetic.ts | 199 ++++++++++++++++++++++++++++++++++++++++ plugin/lib/types.ts | 1 + plugin/mystatus.ts | 29 ++++-- 7 files changed, 279 insertions(+), 14 deletions(-) create mode 100644 plugin/lib/synthetic.ts diff --git a/README.md b/README.md index 5ab5080..e0af9c7 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ An [OpenCode](https://opencode.ai) plugin to query account quota usage for multi | OpenAI | Plus / Team / Pro | `~/.local/share/opencode/auth.json` | | Zhipu AI | Coding Plan | `~/.local/share/opencode/auth.json` | | Z.ai | Coding Plan | `~/.local/share/opencode/auth.json` | +| Synthetic.new | API Key | `~/.local/share/opencode/auth.json` | | GitHub Copilot | Individual / Business | `~/.local/share/opencode/auth.json` | | Google Cloud | Antigravity | `~/.config/opencode/antigravity-accounts.json` | @@ -130,6 +131,25 @@ Account: 9c89****AQVM (Z.ai) Used: 0.5M / 10.0M Resets in: 4h +## Synthetic Account Quota + +Account: Synthetic (API) + +5-hour limit +██████████████████████████████ 99% remaining +Used: 28 / 750 +Quota resets: 48m (2026-04-27T03:34:13.000Z) + +Weekly limit +██████████████████████████████ 99% remaining +Used: $35.85 / $36.00 +Quota resets: 1h 32m (2026-04-27T04:18:36.000Z) + +Search limit +██████████████████████████████ 100% remaining +Used: 0 / 250 +Quota resets: 1h 39m (2026-04-27T04:25:14.238Z) + ## GitHub Copilot Account Quota Account: GitHub Copilot (individual) @@ -161,7 +181,7 @@ Claude 2d 9h ░░░░░░░░░░░░░░░░░░░ No additional configuration required. The plugin automatically reads credentials from: -- **OpenAI, Zhipu AI, Z.ai & GitHub Copilot**: `~/.local/share/opencode/auth.json` +- **OpenAI, Zhipu AI, Z.ai, Synthetic.new & GitHub Copilot**: `~/.local/share/opencode/auth.json` - **Google Cloud**: `~/.config/opencode/antigravity-accounts.json` ### Google Cloud Setup @@ -182,6 +202,7 @@ This plugin is safe to use: - `https://chatgpt.com/backend-api/wham/usage` - OpenAI official quota API - `https://bigmodel.cn/api/monitor/usage/quota/limit` - Zhipu AI official quota API - `https://api.z.ai/api/monitor/usage/quota/limit` - Z.ai official quota API +- `https://api.synthetic.new/v2/quotas` - Synthetic.new official quota API - `https://api.github.com/copilot_internal/user` - GitHub Copilot official API - `https://oauth2.googleapis.com/token` - Google official OAuth API - `https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels` - Google Cloud official API diff --git a/README.zh-CN.md b/README.zh-CN.md index 06bb45e..e898ab0 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -17,6 +17,7 @@ | OpenAI | Plus / Team / Pro | `~/.local/share/opencode/auth.json` | | 智谱 AI | Coding Plan | `~/.local/share/opencode/auth.json` | | Z.ai | Coding Plan | `~/.local/share/opencode/auth.json` | +| Synthetic.new | API Key | `~/.local/share/opencode/auth.json` | | GitHub Copilot | Individual / Business | `~/.local/share/opencode/auth.json` | | Google Cloud | Antigravity | `~/.config/opencode/antigravity-accounts.json` | @@ -130,6 +131,25 @@ Account: 9c89****AQVM (Z.ai) 已用: 0.5M / 10.0M 重置: 4小时后 +## Synthetic 账号额度 + +Account: Synthetic (API) + +5 小时限额 +██████████████████████████████ 剩余 99% +已用: 28 / 750 +配额重置: 48分钟 (2026-04-27T03:34:13.000Z) + +周额度 +██████████████████████████████ 剩余 99% +已用: $35.85 / $36.00 +配额重置: 1小时32分钟 (2026-04-27T04:18:36.000Z) + +搜索额度 +██████████████████████████████ 剩余 100% +已用: 0 / 250 +配额重置: 1小时39分钟 (2026-04-27T04:25:14.238Z) + ## GitHub Copilot Account Quota Account: GitHub Copilot (individual) @@ -161,7 +181,7 @@ Claude 2d 9h ░░░░░░░░░░░░░░░░░░░ 无需额外配置。插件自动从以下位置读取认证信息: -- **OpenAI、智谱 AI、Z.ai 和 GitHub Copilot**: `~/.local/share/opencode/auth.json` +- **OpenAI、智谱 AI、Z.ai、Synthetic.new 和 GitHub Copilot**: `~/.local/share/opencode/auth.json` - **Google Cloud**: `~/.config/opencode/antigravity-accounts.json` ### Google Cloud 设置 @@ -182,6 +202,7 @@ Claude 2d 9h ░░░░░░░░░░░░░░░░░░░ - `https://chatgpt.com/backend-api/wham/usage` - OpenAI 官方额度查询接口 - `https://bigmodel.cn/api/monitor/usage/quota/limit` - 智谱 AI 官方额度查询接口 - `https://api.z.ai/api/monitor/usage/quota/limit` - Z.ai 官方额度查询接口 +- `https://api.synthetic.new/v2/quotas` - Synthetic.new 官方额度查询接口 - `https://api.github.com/copilot_internal/user` - GitHub Copilot 官方 API - `https://oauth2.googleapis.com/token` - Google 官方 OAuth 接口 - `https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels` - Google Cloud 官方接口 diff --git a/package.json b/package.json index 2a5530c..58b25e3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "opencode-mystatus", "version": "1.2.4", - "description": "Check all your AI subscription quotas in one command. Supports OpenAI, Zhipu AI, Z.ai, and Google Antigravity. More platforms coming soon.", + "description": "Check all your AI subscription quotas in one command. Supports OpenAI, Zhipu AI, Z.ai, Synthetic.new, GitHub Copilot, and Google Antigravity.", "type": "module", "main": "dist/plugin/mystatus.js", "types": "dist/plugin/mystatus.d.ts", diff --git a/plugin/lib/i18n.ts b/plugin/lib/i18n.ts index 2c15d4b..8d289eb 100644 --- a/plugin/lib/i18n.ts +++ b/plugin/lib/i18n.ts @@ -71,21 +71,27 @@ const translations = { tokenExpired: "⚠️ OAuth 授权已过期,请在 OpenCode 中使用一次 OpenAI 模型以刷新授权。", noAccounts: - "未找到任何已配置的账号。\n\n支持的账号类型:\n- OpenAI (Plus/Team/Pro 订阅用户)\n- 智谱 AI (Coding Plan)\n- Z.ai (Coding Plan)\n- Google Cloud (Antigravity)", + "未找到任何已配置的账号。\n\n支持的账号类型:\n- OpenAI (Plus/Team/Pro 订阅用户)\n- 智谱 AI (Coding Plan)\n- Z.ai (Coding Plan)\n- Synthetic.new (API Key)\n- Google Cloud (Antigravity)", queryFailed: "❌ 查询失败的账号:\n", // 平台标题 openaiTitle: "## OpenAI 账号额度", zhipuTitle: "## 智谱 AI 账号额度", zaiTitle: "## Z.ai 账号额度", + syntheticTitle: "## Synthetic 账号额度", // 智谱 AI 相关 zhipuApiError: (status: number, text: string) => `智谱 API 请求失败 (${status}): ${text}`, zaiApiError: (status: number, text: string) => `Z.ai API 请求失败 (${status}): ${text}`, + syntheticApiError: (status: number, text: string) => + `Synthetic API 请求失败 (${status}): ${text}`, zhipuTokensLimit: "5 小时 Token 限额", zhipuMcpLimit: "MCP 月度配额", + syntheticFiveHourLimit: "5 小时限额", + syntheticWeeklyLimit: "周额度", + syntheticSearchLimit: "搜索额度", zhipuAccountName: "Coding Plan", zaiAccountName: "Z.ai", noQuotaData: "暂无配额数据", @@ -149,21 +155,27 @@ const translations = { tokenExpired: "⚠️ OAuth token expired. Please use an OpenAI model in OpenCode to refresh authorization.", noAccounts: - "No configured accounts found.\n\nSupported account types:\n- OpenAI (Plus/Team/Pro subscribers)\n- Zhipu AI (Coding Plan)\n- Z.ai (Coding Plan)\n- Google Cloud (Antigravity)", + "No configured accounts found.\n\nSupported account types:\n- OpenAI (Plus/Team/Pro subscribers)\n- Zhipu AI (Coding Plan)\n- Z.ai (Coding Plan)\n- Synthetic.new (API Key)\n- Google Cloud (Antigravity)", queryFailed: "❌ Failed to query accounts:\n", // 平台标题 openaiTitle: "## OpenAI Account Quota", zhipuTitle: "## Zhipu AI Account Quota", zaiTitle: "## Z.ai Account Quota", + syntheticTitle: "## Synthetic Account Quota", // 智谱 AI 相关 zhipuApiError: (status: number, text: string) => `Zhipu API request failed (${status}): ${text}`, zaiApiError: (status: number, text: string) => `Z.ai API request failed (${status}): ${text}`, + syntheticApiError: (status: number, text: string) => + `Synthetic API request failed (${status}): ${text}`, zhipuTokensLimit: "5-hour token limit", zhipuMcpLimit: "MCP monthly quota", + syntheticFiveHourLimit: "5-hour limit", + syntheticWeeklyLimit: "Weekly limit", + syntheticSearchLimit: "Search limit", zhipuAccountName: "Coding Plan", zaiAccountName: "Z.ai", noQuotaData: "No quota data available", diff --git a/plugin/lib/synthetic.ts b/plugin/lib/synthetic.ts new file mode 100644 index 0000000..dddf49b --- /dev/null +++ b/plugin/lib/synthetic.ts @@ -0,0 +1,199 @@ +/** + * Synthetic.new 额度查询模块 + * + * [输入]: API Key + * [输出]: 格式化的额度使用情况 + * [定位]: 被 mystatus.ts 调用,处理 Synthetic 账号 + * [同步]: mystatus.ts, types.ts, utils.ts, i18n.ts + */ + +import { t } from "./i18n"; +import { + type QueryResult, + type ZhipuAuthData, + HIGH_USAGE_THRESHOLD, +} from "./types"; +import { formatDuration, createProgressBar, fetchWithTimeout } from "./utils"; + +interface SyntheticQuotaResponse { + search?: { + hourly?: { + limit: number; + requests: number; + renewsAt: string; + }; + }; + weeklyTokenLimit?: { + nextRegenAt: string; + percentRemaining: number; + maxCredits: string; + remainingCredits: string; + nextRegenCredits: string; + }; + rollingFiveHourLimit?: { + remaining: number; + max: number; + nextTickAt: string; + }; +} + +interface QuotaItem { + label: string; + usedText: string; + remainPercent: number; + renewsAt: string; +} + +interface CountQuota { + limit: number; + requests: number; + renewsAt: string; +} + +const SYNTHETIC_QUOTA_QUERY_URL = "https://api.synthetic.new/v2/quotas"; + +async function fetchSyntheticUsage( + apiKey: string, +): Promise { + const response = await fetchWithTimeout(SYNTHETIC_QUOTA_QUERY_URL, { + method: "GET", + headers: { + Authorization: `Bearer ${apiKey}`, + "Content-Type": "application/json", + "User-Agent": "OpenCode-Status-Plugin/1.0", + }, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(t.syntheticApiError(response.status, errorText)); + } + + return response.json() as Promise; +} + +function isValidCountQuota( + quota: CountQuota | undefined, +): quota is CountQuota { + return !!quota && typeof quota.limit === "number" && typeof quota.requests === "number"; +} + +function calcRemainPercent(limit: number, used: number): number { + if (limit <= 0) return 0; + const remain = Math.max(0, limit - used); + const rawPercent = (remain / limit) * 100; + let percent = Math.floor(rawPercent); + + // Avoid displaying 100% when there is any usage. + if (used > 0 && remain > 0 && percent >= 100) { + percent = 99; + } + + return Math.max(0, Math.min(100, percent)); +} + +function formatSyntheticUsage(data: SyntheticQuotaResponse): string { + const lines: string[] = []; + const quotas: QuotaItem[] = []; + + if (data.rollingFiveHourLimit) { + const limit = Math.max(0, data.rollingFiveHourLimit.max); + const used = Math.max(0, limit - data.rollingFiveHourLimit.remaining); + const remainPercent = calcRemainPercent(limit, used); + + quotas.push({ + label: t.syntheticFiveHourLimit, + usedText: `${used} / ${limit}`, + remainPercent, + renewsAt: data.rollingFiveHourLimit.nextTickAt, + }); + } + + if (data.weeklyTokenLimit) { + const weekly = data.weeklyTokenLimit; + const rawPercent = Math.max(0, Math.min(100, weekly.percentRemaining)); + const hasUsage = weekly.remainingCredits !== weekly.maxCredits; + const remainPercent = + hasUsage && rawPercent >= 100 ? 99 : Math.floor(rawPercent); + + quotas.push({ + label: t.syntheticWeeklyLimit, + usedText: `${weekly.remainingCredits} / ${weekly.maxCredits}`, + remainPercent, + renewsAt: weekly.nextRegenAt, + }); + } + + if (isValidCountQuota(data.search?.hourly)) { + const limit = Math.max(0, data.search.hourly.limit); + const used = Math.max(0, data.search.hourly.requests); + const remainPercent = calcRemainPercent(limit, used); + + quotas.push({ + label: t.syntheticSearchLimit, + usedText: `${used} / ${limit}`, + remainPercent, + renewsAt: data.search.hourly.renewsAt, + }); + } + + if (quotas.length === 0) { + lines.push(`${t.account} Synthetic (API)`); + lines.push(""); + lines.push(t.noQuotaData); + return lines.join("\n"); + } + + lines.push(`${t.account} Synthetic (API)`); + lines.push(""); + + for (const [index, quota] of quotas.entries()) { + const progressBar = createProgressBar(quota.remainPercent); + + if (index > 0) lines.push(""); + lines.push(quota.label); + lines.push(`${progressBar} ${t.remaining(quota.remainPercent)}`); + lines.push(`${t.used}: ${quota.usedText}`); + + const renewAtDate = new Date(quota.renewsAt); + if (!Number.isNaN(renewAtDate.getTime())) { + const resetSeconds = Math.max( + 0, + Math.floor((renewAtDate.getTime() - Date.now()) / 1000), + ); + lines.push( + `${t.quotaResets}: ${formatDuration(resetSeconds)} (${renewAtDate.toISOString()})`, + ); + } else { + lines.push(`${t.quotaResets}: ${quota.renewsAt}`); + } + + if (quota.remainPercent <= 100 - HIGH_USAGE_THRESHOLD) { + lines.push(""); + lines.push(t.limitReached); + } + } + + return lines.join("\n"); +} + +export async function querySyntheticUsage( + authData: ZhipuAuthData | undefined, +): Promise { + if (!authData || authData.type !== "api" || !authData.key) { + return null; + } + + try { + const usage = await fetchSyntheticUsage(authData.key); + return { + success: true, + output: formatSyntheticUsage(usage), + }; + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : String(err), + }; + } +} diff --git a/plugin/lib/types.ts b/plugin/lib/types.ts index 01543f8..5fdd738 100644 --- a/plugin/lib/types.ts +++ b/plugin/lib/types.ts @@ -100,6 +100,7 @@ export interface AuthData { openai?: OpenAIAuthData; "zhipuai-coding-plan"?: ZhipuAuthData; "zai-coding-plan"?: ZhipuAuthData; + synthetic?: ZhipuAuthData; "github-copilot"?: CopilotAuthData; } diff --git a/plugin/mystatus.ts b/plugin/mystatus.ts index 00eddee..72f2a5e 100644 --- a/plugin/mystatus.ts +++ b/plugin/mystatus.ts @@ -16,6 +16,7 @@ import { t } from "./lib/i18n"; import { type AuthData, type QueryResult } from "./lib/types"; import { queryOpenAIUsage } from "./lib/openai"; import { queryZaiUsage, queryZhipuUsage } from "./lib/zhipu"; +import { querySyntheticUsage } from "./lib/synthetic"; import { queryGoogleUsage } from "./lib/google"; import { queryCopilotUsage } from "./lib/copilot"; @@ -28,7 +29,7 @@ export const MyStatusPlugin: Plugin = async () => { tool: { mystatus: tool({ description: - "Query account quota usage for all configured AI platforms. Returns remaining quota percentages, usage stats, and reset countdowns with visual progress bars. Currently supports OpenAI (ChatGPT/Codex), Zhipu AI, Z.ai, Google Antigravity, and GitHub Copilot.", + "Query account quota usage for all configured AI platforms. Returns remaining quota percentages, usage stats, and reset countdowns with visual progress bars. Currently supports OpenAI (ChatGPT/Codex), Zhipu AI, Z.ai, Synthetic.new, Google Antigravity, and GitHub Copilot.", args: {}, async execute() { // 1. 读取 auth.json @@ -46,14 +47,21 @@ export const MyStatusPlugin: Plugin = async () => { } // 2. 并行查询所有平台(Google 不依赖 authData) - const [openaiResult, zhipuResult, zaiResult, googleResult, copilotResult] = - await Promise.all([ - queryOpenAIUsage(authData.openai), - queryZhipuUsage(authData["zhipuai-coding-plan"]), - queryZaiUsage(authData["zai-coding-plan"]), - queryGoogleUsage(), - queryCopilotUsage(authData["github-copilot"]), - ]); + const [ + openaiResult, + zhipuResult, + zaiResult, + syntheticResult, + googleResult, + copilotResult, + ] = await Promise.all([ + queryOpenAIUsage(authData.openai), + queryZhipuUsage(authData["zhipuai-coding-plan"]), + queryZaiUsage(authData["zai-coding-plan"]), + querySyntheticUsage(authData.synthetic), + queryGoogleUsage(), + queryCopilotUsage(authData["github-copilot"]), + ]); // 3. 收集结果 const results: string[] = []; @@ -68,6 +76,9 @@ export const MyStatusPlugin: Plugin = async () => { // 处理 Z.ai 结果 collectResult(zaiResult, t.zaiTitle, results, errors); + // 处理 Synthetic 结果 + collectResult(syntheticResult, t.syntheticTitle, results, errors); + // 处理 Google 结果 collectResult(googleResult, t.googleTitle, results, errors);