feat(claude-kiro): 为空工具列表添加占位工具避免API错误

refactor(usage-api): 提取支持的提供商列表为共享常量
fix(usage-service): 移除重置时间的本地化格式化避免显示问题
style(usage-manager): 为不支持用量显示的提供商优化UI展示
chore: 更新README中的文档链接指向docs目录
This commit is contained in:
hex2077 2026-02-03 18:55:22 +08:00
parent 97f6b99492
commit c632f184c6
12 changed files with 92 additions and 31 deletions

View file

@ -18,7 +18,7 @@
[![GitHub stars](https://img.shields.io/github/stars/justlovemaki/AIClient-2-API.svg?style=flat&label=Star)](https://github.com/justlovemaki/AIClient-2-API/stargazers)
[![GitHub issues](https://img.shields.io/github/issues/justlovemaki/AIClient-2-API.svg)](https://github.com/justlovemaki/AIClient-2-API/issues)
[**🔧 OpenClaw 設定**](./OPENCLAW_CONFIG_GUIDE-JA.md) | [中文](./README-ZH.md) | [English](./README.md) | [**👉 日本語**](./README-JA.md) | [**📚 完全ドキュメント**](https://aiproxy.justlikemaki.vip/ja/)
[**🔧 OpenClaw 設定**](./docs/OPENCLAW_CONFIG_GUIDE-JA.md) | [中文](./README-ZH.md) | [English](./README.md) | [**👉 日本語**](./README-JA.md) | [**📚 完全ドキュメント**](https://aiproxy.justlikemaki.vip/ja/)
</div>

View file

@ -18,7 +18,7 @@
[![GitHub stars](https://img.shields.io/github/stars/justlovemaki/AIClient-2-API.svg?style=flat&label=Star)](https://github.com/justlovemaki/AIClient-2-API/stargazers)
[![GitHub issues](https://img.shields.io/github/issues/justlovemaki/AIClient-2-API.svg)](https://github.com/justlovemaki/AIClient-2-API/issues)
[**🔧 OpenClaw 配置**](./OPENCLAW_CONFIG_GUIDE-ZH.md) | [**👉 中文**](./README-ZH.md) | [English](./README.md) | [日本語](./README-JA.md) | [**📚 完整文档**](https://aiproxy.justlikemaki.vip/zh/)
[**🔧 OpenClaw 配置**](./docs/OPENCLAW_CONFIG_GUIDE-ZH.md) | [**👉 中文**](./README-ZH.md) | [English](./README.md) | [日本語](./README-JA.md) | [**📚 完整文档**](https://aiproxy.justlikemaki.vip/zh/)
</div>

View file

@ -18,7 +18,7 @@
[![GitHub stars](https://img.shields.io/github/stars/justlovemaki/AIClient-2-API.svg?style=flat&label=Star)](https://github.com/justlovemaki/AIClient-2-API/stargazers)
[![GitHub issues](https://img.shields.io/github/issues/justlovemaki/AIClient-2-API.svg)](https://github.com/justlovemaki/AIClient-2-API/issues)
[**🔧 OpenClaw Config**](./OPENCLAW_CONFIG_GUIDE.md) | [中文](./README-ZH.md) | [**👉 English**](./README.md) | [日本語](./README-JA.md) | [**📚 Documentation**](https://aiproxy.justlikemaki.vip/en/)
[**🔧 OpenClaw Config**](./docs/OPENCLAW_CONFIG_GUIDE.md) | [中文](./README-ZH.md) | [**👉 English**](./README.md) | [日本語](./README-JA.md) | [**📚 Documentation**](https://aiproxy.justlikemaki.vip/en/)
</div>

View file

@ -210,4 +210,4 @@ openclaw chat --model aiclient2api/gemini-3-flash-preview "あなたの質問"
---
詳細については、[AIClient-2-API ドキュメント](./README-JA.md) を参照してください
詳細については、[AIClient-2-API ドキュメント](../README-JA.md) を参照してください

View file

@ -210,4 +210,4 @@ openclaw chat --model aiclient2api/gemini-3-flash-preview "your question"
---
For more information, see [AIClient-2-API Documentation](./README.md)
For more information, see [AIClient-2-API Documentation](../README.md)

View file

@ -870,8 +870,21 @@ async saveCredentialsToFile(filePath, newData) {
});
if (filteredTools.length === 0) {
// 所有工具都被过滤掉了,不添加 tools 上下文
logger.info('[Kiro] All tools were filtered out');
// 所有工具都被过滤掉了,添加一个占位工具
logger.info('[Kiro] All tools were filtered out, adding placeholder tool');
const placeholderTool = {
toolSpecification: {
name: "no_tool_available",
description: "This is a placeholder tool when no other tools are available. It does nothing.",
inputSchema: {
json: {
type: "object",
properties: {}
}
}
}
};
toolsContext = { tools: [placeholderTool] };
} else {
const MAX_DESCRIPTION_LENGTH = 9216;
@ -903,6 +916,22 @@ async saveCredentialsToFile(filePath, newData) {
toolsContext = { tools: kiroTools };
}
} else {
// tools 为空或长度为 0 时,自动添加一个占位工具
logger.info('[Kiro] No tools provided, adding placeholder tool');
const placeholderTool = {
toolSpecification: {
name: "no_tool_available",
description: "This is a placeholder tool when no other tools are available. It does nothing.",
inputSchema: {
json: {
type: "object",
properties: {}
}
}
}
};
toolsContext = { tools: [placeholderTool] };
}
const history = [];

View file

@ -5,7 +5,7 @@
import { getProviderPoolManager } from './service-manager.js';
import { serviceInstances } from '../providers/adapter.js';
import { MODEL_PROVIDER, formatToLocal } from '../utils/common.js';
import { MODEL_PROVIDER } from '../utils/common.js';
/**
* 用量查询服务类
@ -396,8 +396,7 @@ export function formatGeminiUsage(usageData) {
outputTokenLimit: modelInfo.outputTokenLimit || 0,
remaining: remainingPercent,
remainingPercent: Math.round(remainingPercent * 100), // 剩余百分比
resetTime: (modelInfo.resetTimeRaw || modelInfo.resetTime) ?
formatToLocal(modelInfo.resetTimeRaw || modelInfo.resetTime) : '--',
resetTime: modelInfo.resetTime || '--',
resetTimeRaw: modelInfo.resetTimeRaw || modelInfo.resetTime || null
};
@ -464,7 +463,7 @@ export function formatAntigravityUsage(usageData) {
// 优先使用模型自己的重置时间,如果没有则使用全局重置时间
const resetTimeRaw = modelInfo.resetTimeRaw || (usageData.quotaInfo ? usageData.quotaInfo.quotaResetTime : null);
const resetTimeFormatted = resetTimeRaw ? formatToLocal(resetTimeRaw) : (modelInfo.resetTime || '--');
const resetTimeFormatted = modelInfo.resetTime || '--';
const item = {
resourceType: 'MODEL_USAGE',
@ -592,8 +591,7 @@ export function formatCodexUsage(usageData) {
modelName: modelName,
remaining: remainingPercent,
remainingPercent: Math.round(remainingPercent * 100), // 剩余百分比
resetTime: (modelInfo.resetTimeRaw || modelInfo.resetTime) ?
formatToLocal(modelInfo.resetTimeRaw || modelInfo.resetTime) : '--',
resetTime: modelInfo.resetTime || '--',
resetTimeRaw: modelInfo.resetTimeRaw || modelInfo.resetTime || null,
// 注入 raw 窗口信息以便前端使用

View file

@ -5,6 +5,8 @@ import { formatKiroUsage, formatGeminiUsage, formatAntigravityUsage, formatCodex
import { readUsageCache, writeUsageCache, readProviderUsageCache, updateProviderUsageCache } from './usage-cache.js';
import path from 'path';
const supportedProviders = ['claude-kiro-oauth', 'gemini-cli-oauth', 'gemini-antigravity', 'openai-codex-oauth'];
/**
* 获取所有支持用量查询的提供商的用量信息
* @param {Object} currentConfig - 当前配置
@ -17,9 +19,6 @@ async function getAllProvidersUsage(currentConfig, providerPoolManager) {
providers: {}
};
// 支持用量查询的提供商列表
const supportedProviders = ['claude-kiro-oauth', 'gemini-cli-oauth', 'gemini-antigravity', 'openai-codex-oauth'];
// 并发获取所有提供商的用量数据
const usagePromises = supportedProviders.map(async (providerType) => {
try {
@ -225,7 +224,6 @@ function getProviderDisplayName(provider, providerType) {
*/
export async function handleGetSupportedProviders(req, res) {
try {
const supportedProviders = ['claude-kiro-oauth', 'gemini-cli-oauth', 'gemini-antigravity', 'openai-codex-oauth'];
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(supportedProviders));
return true;

View file

@ -4,6 +4,23 @@ import { showToast } from './utils.js';
import { getAuthHeaders } from './auth.js';
import { t, getCurrentLanguage } from './i18n.js';
/**
* 不支持显示用量数据的提供商列表
* 这些提供商只显示模型名称和重置时间不显示用量数字和进度条
*/
const PROVIDERS_WITHOUT_USAGE_DISPLAY = [
'gemini-antigravity'
];
/**
* 检查提供商是否支持显示用量
* @param {string} providerType - 提供商类型
* @returns {boolean} 是否支持显示用量
*/
function shouldShowUsage(providerType) {
return !PROVIDERS_WITHOUT_USAGE_DISPLAY.includes(providerType);
}
/**
* 初始化用量管理功能
*/
@ -404,6 +421,9 @@ function createInstanceUsageCard(instance, providerType) {
const providerDisplayName = getProviderDisplayName(providerType);
const providerIcon = getProviderIcon(providerType);
// 检查是否应该显示用量信息
const showUsage = shouldShowUsage(providerType);
// 计算总用量(用于折叠摘要显示)
const totalUsage = instance.usage ? calculateTotalUsage(instance.usage.usageBreakdown) : { hasData: false, percent: 0 };
const progressClass = totalUsage.percent >= 90 ? 'danger' : (totalUsage.percent >= 70 ? 'warning' : 'normal');
@ -429,6 +449,7 @@ function createInstanceUsageCard(instance, providerType) {
<span class="collapsed-name" title="${displayName}">${displayName}</span>
${statusIcon}
</div>
${showUsage ? `
<div class="collapsed-summary-row collapsed-summary-usage-row">
${totalUsage.hasData ? `
<div class="collapsed-progress-bar ${progressClass}">
@ -438,6 +459,7 @@ function createInstanceUsageCard(instance, providerType) {
<span class="collapsed-usage-text">${displayUsageText}</span>
` : (instance.error ? `<span class="collapsed-error" data-i18n="common.error">${t('common.error')}</span>` : '')}
</div>
` : ''}
`;
// 点击折叠摘要切换展开状态
@ -504,7 +526,7 @@ function createInstanceUsageCard(instance, providerType) {
</div>
`;
} else if (instance.usage) {
content.appendChild(renderUsageDetails(instance.usage));
content.appendChild(renderUsageDetails(instance.usage, providerType));
}
expandedContent.appendChild(content);
@ -516,17 +538,21 @@ function createInstanceUsageCard(instance, providerType) {
/**
* 渲染用量详情 - 显示总用量用量明细和到期时间
* @param {Object} usage - 用量数据
* @param {string} providerType - 提供商类型
* @returns {HTMLElement} 详情元素
*/
function renderUsageDetails(usage) {
function renderUsageDetails(usage, providerType) {
const container = document.createElement('div');
container.className = 'usage-details';
// 检查是否应该显示用量信息
const showUsage = shouldShowUsage(providerType);
// 计算总用量
const totalUsage = calculateTotalUsage(usage.usageBreakdown);
// 总用量进度条
if (totalUsage.hasData) {
// 总用量进度条(不支持显示用量的提供商不显示)
if (totalUsage.hasData && showUsage) {
const totalSection = document.createElement('div');
totalSection.className = 'usage-section total-usage';
@ -543,11 +569,14 @@ function renderUsageDetails(usage) {
`;
} else {
const resetTimeEntry = usage.usageBreakdown.find(b => b.resetTime && b.resetTime !== '--');
resetTimeHTML = resetTimeEntry ? `
<div class="total-reset-info" data-i18n="usage.card.resetAt" data-i18n-params='{"time":"${resetTimeEntry.resetTime}"}'>
<i class="fas fa-history"></i> ${t('usage.card.resetAt', { time: resetTimeEntry.resetTime })}
</div>
` : '';
if (resetTimeEntry) {
const formattedResetTime = formatDate(resetTimeEntry.resetTime);
resetTimeHTML = `
<div class="total-reset-info" data-i18n="usage.card.resetAt" data-i18n-params='{"time":"${formattedResetTime}"}'>
<i class="fas fa-history"></i> ${t('usage.card.resetAt', { time: formattedResetTime })}
</div>
`;
}
}
const displayValue = totalUsage.isCodex
@ -582,7 +611,7 @@ function renderUsageDetails(usage) {
let breakdownHTML = '';
for (const breakdown of usage.usageBreakdown) {
breakdownHTML += createUsageBreakdownHTML(breakdown);
breakdownHTML += createUsageBreakdownHTML(breakdown, providerType);
}
breakdownSection.innerHTML = breakdownHTML;
@ -595,14 +624,18 @@ function renderUsageDetails(usage) {
/**
* 创建用量明细 HTML紧凑版
* @param {Object} breakdown - 用量明细数据
* @param {string} providerType - 提供商类型
* @returns {string} HTML 字符串
*/
function createUsageBreakdownHTML(breakdown) {
function createUsageBreakdownHTML(breakdown, providerType) {
// 特殊处理 Codex
if (breakdown.rateLimit && breakdown.rateLimit.primary_window) {
return createCodexUsageBreakdownHTML(breakdown);
}
// 检查是否应该显示用量信息
const showUsage = shouldShowUsage(providerType);
const usagePercent = breakdown.usageLimit > 0
? Math.min(100, (breakdown.currentUsage / breakdown.usageLimit) * 100)
: 0;
@ -613,21 +646,24 @@ function createUsageBreakdownHTML(breakdown) {
<div class="breakdown-item-compact">
<div class="breakdown-header-compact">
<span class="breakdown-name">${breakdown.displayName || breakdown.resourceType}</span>
<span class="breakdown-usage">${formatNumber(breakdown.currentUsage)} / ${formatNumber(breakdown.usageLimit)}</span>
${showUsage ? `<span class="breakdown-usage">${formatNumber(breakdown.currentUsage)} / ${formatNumber(breakdown.usageLimit)}</span>` : ''}
</div>
${showUsage ? `
<div class="progress-bar-small ${progressClass}">
<div class="progress-fill" style="width: ${usagePercent}%"></div>
</div>
` : ''}
`;
// 如果有重置时间,则显示
if (breakdown.resetTime && breakdown.resetTime !== '--') {
const resetText = t('usage.card.resetAt', { time: breakdown.resetTime });
const formattedResetTime = formatDate(breakdown.resetTime);
const resetText = t('usage.card.resetAt', { time: formattedResetTime });
html += `
<div class="extra-usage-info reset-time">
<span class="extra-label">
<i class="fas fa-history"></i>
<span data-i18n="usage.card.resetAt" data-i18n-params='${JSON.stringify({ time: breakdown.resetTime })}'>${resetText}</span>
<span data-i18n="usage.card.resetAt" data-i18n-params='${JSON.stringify({ time: formattedResetTime })}'>${resetText}</span>
</span>
</div>
`;