дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 03:05:40 +02:00
feat(agents): show Claude usage limits and session cost in the agent task header (#1041)
Claude Code reports the account's 5-hour and weekly usage limits (rate_limit_event) and the session cost (result.total_cost_usd); only the limit status was used. The limits now show as meters next to the context meter in the task header, with the reset time in the tooltip, and Settings › Tools › Agents › Chat turns each meter (context, work time, limits, cost) on or off. Limits are per provider and kept in memory; cost is off by default. Closes #1057 Co-authored-by: matthiasdemauroy <[email protected]>
Цей коміт міститься в:
1 джерело
4eda613c59
коміт
39fe96bd01
19 files changed
+459
-16
No files matched your search
@@ -1161,6 +1161,12 @@ export type AgentConfigVia =
|
||||
/** `session/set_model` (agents that report `models` instead of a model config option). */
|
||||
"model";
|
||||
|
||||
export type AgentCost = {
|
||||
amount: number | null,
|
||||
/** ISO 4217 code, e.g. `USD`. */
|
||||
currency: string,
|
||||
};
|
||||
|
||||
/** One item of a task's conversation. */
|
||||
export type AgentEntry = { type: "user"; text: string;
|
||||
/** Names of the files and images sent with it. */
|
||||
@@ -1206,6 +1212,8 @@ export type AgentEvent =
|
||||
{ type: "truncated"; task: string; length: number } |
|
||||
/** Entry `index` of `task` (of its subagent `subagent`) was added or replaced. */
|
||||
{ type: "entry"; task: string; subagent: string | null; index: number; entry: AgentEntry } |
|
||||
/** An agent reported the usage limits of its account (shared by every task of `provider`). */
|
||||
{ type: "limits"; provider: string; limits: AgentLimit[] } |
|
||||
/** The installed or configured agent providers changed. */
|
||||
{ type: "providers" };
|
||||
|
||||
@@ -1216,6 +1224,16 @@ export type AgentImage = {
|
||||
name: string | null,
|
||||
};
|
||||
|
||||
/** One usage-limit window of an agent's account. */
|
||||
export type AgentLimit = {
|
||||
/** The window as the agent names it: `five_hour`, `seven_day`, `seven_day_opus`… */
|
||||
window: string,
|
||||
/** Share of the window used, 0–100. */
|
||||
usedPercent: number | null,
|
||||
/** When the window resets (ms since the epoch), if the agent said. */
|
||||
resetsMs: number | null,
|
||||
};
|
||||
|
||||
export type AgentPermissionOption = {
|
||||
id: string,
|
||||
name: string,
|
||||
@@ -1297,6 +1315,18 @@ export type AgentRetry = {
|
||||
error: string | null,
|
||||
};
|
||||
|
||||
/** What the meters in an agent task's header show. */
|
||||
export type AgentSettings = {
|
||||
/** Context window use. */
|
||||
showContextUsage?: boolean,
|
||||
/** Time the agent spent on the task. */
|
||||
showWorkTime?: boolean,
|
||||
/** The account's usage-limit windows (Claude's 5-hour and weekly limits). */
|
||||
showUsageLimits?: boolean,
|
||||
/** What the session has cost so far, as the agent reports it. */
|
||||
showCost?: boolean,
|
||||
};
|
||||
|
||||
/** Where an agent comes from. */
|
||||
export type AgentSource = { kind: "builtin" } |
|
||||
/** Defined by the user in `<config>/agents/<id>.toml`. */
|
||||
@@ -1378,6 +1408,8 @@ export type AgentUsage = {
|
||||
used: number,
|
||||
/** Size of the context window. */
|
||||
size: number,
|
||||
/** What the session has cost so far (ACP `usage_update.cost`), when the agent reports it. */
|
||||
cost?: AgentCost | null,
|
||||
};
|
||||
|
||||
export type AppInfo = {
|
||||
@@ -5785,6 +5817,8 @@ export type Settings_Deserialize = {
|
||||
/** Build, Execution, Deployment › Run Targets. */
|
||||
runTargets?: RunTarget[],
|
||||
cmake?: CMakeSettings_Deserialize,
|
||||
/** Tools › Agents › Chat. */
|
||||
agents?: AgentSettings,
|
||||
};
|
||||
|
||||
export type Settings_Serialize = {
|
||||
@@ -5841,6 +5875,8 @@ export type Settings_Serialize = {
|
||||
/** Build, Execution, Deployment › Run Targets. */
|
||||
runTargets: RunTarget[],
|
||||
cmake: CMakeSettings_Serialize,
|
||||
/** Tools › Agents › Chat. */
|
||||
agents: AgentSettings,
|
||||
};
|
||||
|
||||
export type Severity = "error" | "warning" | "info" | "hint";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { Fragment, useEffect, useLayoutEffect, useMemo, useRef, useState, type RefObject } from "react";
|
||||
import clsx from "clsx";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
import type { AgentChange, AgentEntry, AgentImage, AgentRetry, AgentTask, AgentUsage } from "@/bindings";
|
||||
import type { AgentChange, AgentEntry, AgentImage, AgentLimit, AgentRetry, AgentTask, AgentUsage } from "@/bindings";
|
||||
import { executeAction } from "@/platform/actions";
|
||||
import { call, commands, onIdeEvent } from "@/platform/ipc";
|
||||
import { basename, relativeDir, useProject } from "@/platform/project";
|
||||
@@ -28,7 +28,7 @@ import { isImeKey } from "@/platform/ime";
|
||||
import { TaskStatusIcon } from "./AgentsView";
|
||||
import { Composer } from "./Composer";
|
||||
import { SubmitButton } from "./SubmitButton";
|
||||
import { canSend, emptyDraft, retryParts, usageLabel, workTime } from "./composerModel";
|
||||
import { canSend, costLabel, emptyDraft, limitLabel, retryParts, usageLabel, workTime } from "./composerModel";
|
||||
import { ConfigPickers } from "./ConfigPickers";
|
||||
import { findRanges, formatDuration, formatSentAt } from "./conversation";
|
||||
import { ImagePreview, imageSrc } from "./ImagePreview";
|
||||
@@ -287,21 +287,45 @@ function Header({
|
||||
);
|
||||
}
|
||||
|
||||
/** Context use (when the agent reports it) and the time spent on the task. */
|
||||
/**
|
||||
* Context use, usage limits and cost (when the agent reports them) and the time spent on
|
||||
* the task, each as Settings › Tools › Agents › Chat allows.
|
||||
*/
|
||||
function Meters({ task, entries }: { task: AgentTask; entries: AgentEntry[] | null }) {
|
||||
const tx = useTx();
|
||||
const show = useProject((s) => s.settings?.agents);
|
||||
const busy = isBusy(task.status);
|
||||
const now = useNow(busy ? 1000 : 60_000);
|
||||
const usage = task.usage;
|
||||
const work = entries ? workTime(entries, busy ? now : null) : 0;
|
||||
const usage = show?.showContextUsage !== false ? task.usage : null;
|
||||
const reported = useAgents((s) => s.limits.get(task.provider));
|
||||
const limits = show?.showUsageLimits !== false ? (reported ?? []) : [];
|
||||
const cost = show?.showCost === true ? task.usage?.cost : null;
|
||||
const work = show?.showWorkTime !== false && entries ? workTime(entries, busy ? now : null) : 0;
|
||||
return (
|
||||
<span className={styles.meters}>
|
||||
{usage && <Usage usage={usage} />}
|
||||
{limits.map((l) => (
|
||||
<Limit key={l.window} limit={l} now={now} />
|
||||
))}
|
||||
{cost && <span title={tx("What the session has cost so far, as the agent reports it")}>{costLabel(cost)}</span>}
|
||||
{work >= 1000 && <span title={tx("Time the agent spent on this task")}>{formatDuration(work)}</span>}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/** A usage-limit window: "5h 19%", when it resets in the tooltip. */
|
||||
function Limit({ limit, now }: { limit: AgentLimit; now: number }) {
|
||||
const label = limitLabel(limit, now);
|
||||
return (
|
||||
<span className={styles.meter} title={label.detail} aria-label={label.detail}>
|
||||
<span className={styles.usageBar} aria-hidden>
|
||||
<span style={{ width: `${label.percent}%` }} />
|
||||
</span>
|
||||
{label.short}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/** Context window use: "12%", the numbers in the tooltip. */
|
||||
function Usage({ usage }: { usage: AgentUsage }) {
|
||||
const label = usageLabel(usage);
|
||||
|
||||
@@ -17,6 +17,8 @@ import {
|
||||
toPrompt,
|
||||
retryParts,
|
||||
usageLabel,
|
||||
limitLabel,
|
||||
costLabel,
|
||||
workTime,
|
||||
} from "./composerModel";
|
||||
import {
|
||||
@@ -151,6 +153,28 @@ describe("usage meters", () => {
|
||||
expect(usageLabel({ used: 83_100, size: 1_000_000 }).detail).toBe("83.1k / 1M tokens");
|
||||
});
|
||||
|
||||
it("names usage-limit windows and says when they reset", () => {
|
||||
const hour = 3_600_000;
|
||||
expect(limitLabel({ window: "five_hour", usedPercent: 19.4, resetsMs: 2 * hour + 51 * 60_000 }, 0)).toEqual({
|
||||
short: "5h 19%",
|
||||
percent: 19,
|
||||
detail: "5-hour limit: 19% used, resets in 2h 51m",
|
||||
});
|
||||
expect(limitLabel({ window: "seven_day", usedPercent: 120, resetsMs: 5 * 24 * hour + 3 * hour }, 0).detail).toBe(
|
||||
"Weekly limit: 100% used, resets in 5d 3h",
|
||||
);
|
||||
expect(limitLabel({ window: "spend_limit", usedPercent: 7, resetsMs: null }, 0)).toEqual({
|
||||
short: "spend_limit 7%",
|
||||
percent: 7,
|
||||
detail: "spend_limit: 7% used",
|
||||
});
|
||||
});
|
||||
|
||||
it("formats the session cost", () => {
|
||||
expect(costLabel({ amount: 0.4231, currency: "USD" })).toBe("$0.42");
|
||||
expect(costLabel({ amount: 1.5, currency: "credits" })).toBe("1.50 credits");
|
||||
});
|
||||
|
||||
it("adds up the time spent on turns", () => {
|
||||
const user = (sentMs: number, doneMs: number | null): AgentEntry => ({
|
||||
type: "user",
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
import type {
|
||||
AgentCommand,
|
||||
AgentConfigOption,
|
||||
AgentCost,
|
||||
AgentEntry,
|
||||
AgentImage,
|
||||
AgentLimit,
|
||||
AgentPrompt,
|
||||
AgentRetry,
|
||||
AgentUsage,
|
||||
@@ -181,6 +183,44 @@ export function usageLabel(u: AgentUsage): { short: string; detail: string } {
|
||||
return { short: `${pct}%`, detail: tx`${tokens(u.used)} / ${tokens(u.size)} tokens` };
|
||||
}
|
||||
|
||||
/** The usage-limit windows agents report, by the name they give them; others show that name. */
|
||||
const LIMIT_WINDOWS: Record<string, { short: string; name: string }> = {
|
||||
five_hour: { short: "5h", name: "5-hour limit" },
|
||||
seven_day: { short: "7d", name: "Weekly limit" },
|
||||
seven_day_opus: { short: "7d Opus", name: "Weekly Opus limit" },
|
||||
seven_day_sonnet: { short: "7d Sonnet", name: "Weekly Sonnet limit" },
|
||||
};
|
||||
|
||||
/** "5h 19%" for a usage-limit window, and a tooltip saying when it resets. */
|
||||
export function limitLabel(l: AgentLimit, now: number): { short: string; percent: number; detail: string } {
|
||||
const known = LIMIT_WINDOWS[l.window];
|
||||
const percent = Math.min(100, Math.round(l.usedPercent ?? 0));
|
||||
const name = known ? tx(known.name) : l.window;
|
||||
const used = tx`${name}: ${percent}% used`;
|
||||
if (l.resetsMs == null) return { short: `${known?.short ?? l.window} ${percent}%`, percent, detail: used };
|
||||
const reset = untilReset(Math.max(0, l.resetsMs - now));
|
||||
return { short: `${known?.short ?? l.window} ${percent}%`, percent, detail: tx`${used}, resets in ${reset}` };
|
||||
}
|
||||
|
||||
/** `45m`, `2h 51m`, `5d 3h`. */
|
||||
function untilReset(ms: number): string {
|
||||
const m = Math.round(ms / 60_000);
|
||||
if (m < 60) return `${m}m`;
|
||||
const h = Math.floor(m / 60);
|
||||
if (h < 24) return `${h}h ${String(m % 60).padStart(2, "0")}m`;
|
||||
return `${Math.floor(h / 24)}d ${h % 24}h`;
|
||||
}
|
||||
|
||||
/** "$0.42": what the session has cost so far. */
|
||||
export function costLabel(c: AgentCost): string {
|
||||
try {
|
||||
return new Intl.NumberFormat("en-US", { style: "currency", currency: c.currency }).format(c.amount ?? 0);
|
||||
} catch {
|
||||
// Not an ISO 4217 code.
|
||||
return `${(c.amount ?? 0).toFixed(2)} ${c.currency}`;
|
||||
}
|
||||
}
|
||||
|
||||
/** `850`, `83.1k`, `1M`. */
|
||||
function tokens(n: number): string {
|
||||
if (n >= 1_000_000) return `${+(n / 1_000_000).toFixed(1)}M`;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
|
||||
import { registerActions } from "@/platform/actions";
|
||||
import { useProject } from "@/platform/project";
|
||||
import type { AgentChange, AgentEntry, AgentTask } from "@/bindings";
|
||||
import { AgentsView } from "./AgentsView";
|
||||
import { SessionEditor } from "./SessionEditor";
|
||||
@@ -227,3 +228,36 @@ describe("session state across tasks and hosts", () => {
|
||||
expect(useAgents.getState().scrollPositions.size).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("meters in the task header", () => {
|
||||
afterEach(() => {
|
||||
useProject.setState({ settings: null });
|
||||
useAgents.setState({ limits: new Map() });
|
||||
});
|
||||
|
||||
it("shows the provider's usage limits by default and each meter as Settings › Tools › Agents › Chat allows", async () => {
|
||||
const reported: AgentTask = {
|
||||
...task("A"),
|
||||
usage: { used: 24_000, size: 200_000, cost: { amount: 0.42, currency: "USD" } },
|
||||
};
|
||||
const limits = [
|
||||
{ window: "five_hour", usedPercent: 19, resetsMs: null },
|
||||
{ window: "seven_day", usedPercent: 9.4, resetsMs: null },
|
||||
];
|
||||
useAgents.setState({ tasks: new Map([["A", reported]]), limits: new Map([["codex", limits]]) });
|
||||
render(<SessionEditor input={input("A")} />);
|
||||
await screen.findByText("Answer A");
|
||||
expect(screen.getByText("12%")).toBeInTheDocument();
|
||||
expect(screen.getByText("5h 19%")).toBeInTheDocument();
|
||||
expect(screen.getByText("7d 9%")).toBeInTheDocument();
|
||||
expect(screen.queryByText("$0.42")).toBeNull();
|
||||
act(() =>
|
||||
useProject.setState({
|
||||
settings: { agents: { showContextUsage: false, showUsageLimits: false, showCost: true } },
|
||||
}),
|
||||
);
|
||||
expect(screen.queryByText("12%")).toBeNull();
|
||||
expect(screen.queryByText("5h 19%")).toBeNull();
|
||||
expect(screen.getByText("$0.42")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
AgentEntry,
|
||||
AgentEvent,
|
||||
AgentImage,
|
||||
AgentLimit,
|
||||
AgentProvider,
|
||||
AgentTask,
|
||||
Isolation,
|
||||
@@ -59,6 +60,8 @@ interface AgentsState {
|
||||
/** Scroll position and follow-output preference by conversation, shared by both hosts. */
|
||||
scrollPositions: Map<string, { top: number; stick: boolean }>;
|
||||
providers: AgentProvider[];
|
||||
/** The usage limits each agent last reported for its account, by provider. */
|
||||
limits: Map<string, AgentLimit[]>;
|
||||
draft: Draft;
|
||||
composers: Map<string, ComposerDraft>;
|
||||
/** The composer typed in last: where "Send to Agent" adds references. */
|
||||
@@ -106,6 +109,7 @@ export const useAgents = create<AgentsState>(() => ({
|
||||
entries: new Map(),
|
||||
changes: new Map(),
|
||||
providers: [],
|
||||
limits: new Map(),
|
||||
draft: { ...fallbackDraft, ...load<Partial<Draft>>(DRAFT_KEY, {}) },
|
||||
composers: new Map(),
|
||||
lastComposer: HOME,
|
||||
@@ -261,6 +265,10 @@ function onEvent(e: AgentEvent) {
|
||||
void refreshProviders();
|
||||
break;
|
||||
}
|
||||
case "limits": {
|
||||
useAgents.setState({ limits: new Map(s.limits).set(e.provider, e.limits) });
|
||||
break;
|
||||
}
|
||||
case "entry": {
|
||||
if (
|
||||
!e.subagent &&
|
||||
|
||||
@@ -132,6 +132,16 @@ describe("agent unread activity", () => {
|
||||
expect(backend.notify).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("keeps the latest usage limits per provider without marking anything unread", () => {
|
||||
const fiveHour = (usedPercent: number) => [{ window: "five_hour", usedPercent, resetsMs: null }];
|
||||
emit({ type: "limits", provider: "claude", limits: fiveHour(10) });
|
||||
emit({ type: "limits", provider: "codex", limits: fiveHour(50) });
|
||||
emit({ type: "limits", provider: "claude", limits: fiveHour(12) });
|
||||
expect(useAgents.getState().limits.get("claude")).toEqual(fiveHour(12));
|
||||
expect(useAgents.getState().limits.get("codex")).toEqual(fiveHour(50));
|
||||
expect(useAgents.getState().unread.size).toBe(0);
|
||||
});
|
||||
|
||||
it("ignores user messages, thoughts, empty replies and subagent streaming", () => {
|
||||
const entries: AgentEntry[] = [
|
||||
{ type: "user", text: "Do this", attachments: [] },
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { validateSettings } from "../model/schema";
|
||||
|
||||
describe("agent chat settings", () => {
|
||||
it("defaults every meter but the cost to on and keeps stored values", () => {
|
||||
expect(validateSettings({}).agents).toEqual({
|
||||
showContextUsage: true,
|
||||
showWorkTime: true,
|
||||
showUsageLimits: true,
|
||||
showCost: false,
|
||||
});
|
||||
const stored = validateSettings({ agents: { showUsageLimits: false, showCost: true, showWorkTime: "no" } }).agents;
|
||||
expect(stored).toEqual({ showContextUsage: true, showWorkTime: true, showUsageLimits: false, showCost: true });
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,7 @@
|
||||
import { validateCMake } from "@/platform/cmakeSettings";
|
||||
import { DEFAULT_IGNORED_NAMES } from "@/platform/ignoredNames";
|
||||
import { validateFileTypeOverrides, validateFileTypes, type FileTypeAssociations } from "@/platform/fileTypes";
|
||||
import type { LanguageServerSettings, Settings } from "@/platform/ipc";
|
||||
import type { AgentSettings, LanguageServerSettings, Settings } from "@/platform/ipc";
|
||||
import {
|
||||
DEFAULT_LANGUAGES,
|
||||
DEFAULT_PROJECT_STRUCTURE,
|
||||
@@ -92,6 +92,8 @@ export interface ValidSettings extends WorkflowSettings {
|
||||
tools: Record<string, { path: string }>;
|
||||
/** Settings › Language Servers, by server name (as the backend sends them). */
|
||||
languageServers: Record<string, LanguageServerEntry>;
|
||||
/** Tools › Agents › Chat: the meters in an agent task's header. */
|
||||
agents: Required<AgentSettings>;
|
||||
}
|
||||
|
||||
/** One `languageServers.<name>` entry; every field optional, like in settings.toml. */
|
||||
@@ -158,6 +160,7 @@ export const DEFAULT_SETTINGS: ValidSettings = {
|
||||
spelling: { ideWords: [], projectWords: [], customDictionaries: [] },
|
||||
tools: {},
|
||||
languageServers: {},
|
||||
agents: { showContextUsage: true, showWorkTime: true, showUsageLimits: true, showCost: false },
|
||||
...DEFAULT_WORKFLOW,
|
||||
};
|
||||
|
||||
@@ -201,6 +204,7 @@ export function validateSettings(raw: Settings | unknown): ValidSettings {
|
||||
const a = obj(root.appearance);
|
||||
const e = obj(root.editor);
|
||||
const f = obj(root.files);
|
||||
const agents = obj(root.agents);
|
||||
const overrides = bindingMap(root.keymapOverrides);
|
||||
const customKeymaps: Record<string, CustomKeymapSettings> = {};
|
||||
for (const [id, raw] of Object.entries(obj(root.customKeymaps))) {
|
||||
@@ -270,6 +274,12 @@ export function validateSettings(raw: Settings | unknown): ValidSettings {
|
||||
languageServers: Object.fromEntries(
|
||||
Object.entries(obj(root.languageServers)).map(([name, ls]) => [name, obj(ls) as LanguageServerEntry]),
|
||||
),
|
||||
agents: {
|
||||
showContextUsage: bool(agents.showContextUsage, d.agents.showContextUsage),
|
||||
showWorkTime: bool(agents.showWorkTime, d.agents.showWorkTime),
|
||||
showUsageLimits: bool(agents.showUsageLimits, d.agents.showUsageLimits),
|
||||
showCost: bool(agents.showCost, d.agents.showCost),
|
||||
},
|
||||
...validateWorkflow(root),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Tools pages: Terminal, Actions on Save and External Tools. Consumed by the
|
||||
* terminal feature, the editor save participants and the run feature
|
||||
* (External Tools actions).
|
||||
* Tools pages: Terminal, Actions on Save, External Tools and Agents › Chat. Consumed
|
||||
* by the terminal feature, the editor save participants, the run feature
|
||||
* (External Tools actions) and the agent task editor.
|
||||
*/
|
||||
|
||||
import { FontPicker } from "../components/FontPicker";
|
||||
@@ -623,6 +623,35 @@ export const TOOLS_PAGES: PageSpec[] = [
|
||||
{ options: [{ kind: "custom", label: "Save", component: ActionsOnSaveNote }] },
|
||||
],
|
||||
},
|
||||
{
|
||||
// Under Settings › Tools › Agents (the agents feature registers `tools.agents`).
|
||||
id: "tools.agents.chat",
|
||||
title: "Chat",
|
||||
group: TOOLS_GROUP,
|
||||
order: 0,
|
||||
keywords: ["agent", "claude", "codex", "usage", "quota", "rate limit", "cost", "tokens", "meter"],
|
||||
sections: [
|
||||
{
|
||||
title: "Shown in the Task Header",
|
||||
options: [
|
||||
{ kind: "toggle", key: "agents.showContextUsage", label: "Context window usage" },
|
||||
{ kind: "toggle", key: "agents.showWorkTime", label: "Time spent on the task" },
|
||||
{
|
||||
kind: "toggle",
|
||||
key: "agents.showUsageLimits",
|
||||
label: "Usage limits",
|
||||
hint: "The account's 5-hour and weekly limits, when the agent reports them (Claude Code)",
|
||||
},
|
||||
{
|
||||
kind: "toggle",
|
||||
key: "agents.showCost",
|
||||
label: "Session cost",
|
||||
hint: "What the session has cost so far, as the agent reports it (API pricing, also shown on subscriptions)",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "tools.externalTools",
|
||||
title: "External Tools",
|
||||
|
||||
@@ -5688,5 +5688,20 @@
|
||||
"Unresolved merge conflicts": "未解决的合并冲突",
|
||||
"Reports the <<<<<<<, ======= and >>>>>>> lines a merge leaves in a file, whatever its language and whether or not a language server is installed.": "报告合并在文件中留下的 <<<<<<<、======= 和 >>>>>>> 行,与文件语言以及是否安装了语言服务器无关。",
|
||||
"Merge/Rebase Widget": "合并/变基微件",
|
||||
"Unresolved merge conflict markers": "未解决的合并冲突标记"
|
||||
"Unresolved merge conflict markers": "未解决的合并冲突标记",
|
||||
"What the session has cost so far, as the agent reports it": "智能体报告的本会话目前花费",
|
||||
"{name}: {percent}% used": "{name}:已使用 {percent}%",
|
||||
"{used}, resets in {reset}": "{used},{reset} 后重置",
|
||||
"5-hour limit": "5 小时限额",
|
||||
"Weekly limit": "每周限额",
|
||||
"Weekly Opus limit": "每周 Opus 限额",
|
||||
"Weekly Sonnet limit": "每周 Sonnet 限额",
|
||||
"Chat": "聊天",
|
||||
"Shown in the Task Header": "在任务标题栏中显示",
|
||||
"Context window usage": "上下文窗口用量",
|
||||
"Time spent on the task": "任务耗时",
|
||||
"Usage limits": "用量限额",
|
||||
"The account's 5-hour and weekly limits, when the agent reports them (Claude Code)": "账户的 5 小时和每周限额,在智能体报告时显示(Claude Code)",
|
||||
"Session cost": "会话花费",
|
||||
"What the session has cost so far, as the agent reports it (API pricing, also shown on subscriptions)": "智能体报告的本会话目前花费(按 API 价格计算,订阅账户也会显示)"
|
||||
}
|
||||
Reference in new issue
Заблокувати користувача