Preserve published rate limits on sparse refresh

This commit is contained in:
murashit 2026-06-04 12:10:27 +09:00
parent 0389f86f93
commit d06e329a78
3 changed files with 80 additions and 1 deletions

View file

@ -171,6 +171,18 @@ export class ChatAppServerController {
this.dispatch({ type: "thread/list-applied", rateLimit: rateLimit.data, appServerDiagnostics: diagnostics });
}
async refreshPublishedRateLimits(): Promise<void> {
const rateLimit = await this.loadRateLimit();
const diagnostics = cloneAppServerDiagnostics(this.state.appServerDiagnostics);
diagnostics.probes["account/rateLimits/read"] = rateLimit.probe;
if (rateLimit.probe.status === "ok") {
this.dispatch({ type: "thread/list-applied", rateLimit: rateLimit.data, appServerDiagnostics: diagnostics });
this.publishAppServerMetadataSnapshot();
return;
}
this.dispatch({ type: "thread/list-applied", appServerDiagnostics: diagnostics });
}
async loadRateLimit(): Promise<{ data: RateLimitSnapshot | null; probe: AppServerDiagnostics["probes"]["account/rateLimits/read"] }> {
const client = this.host.currentClient();
if (!client) {

View file

@ -355,7 +355,7 @@ export function createChatViewControllerAssembly(host: ChatViewControllerAssembl
void host.refreshThreads();
},
refreshRateLimits: () => {
void appServer.refreshRateLimits();
void appServer.refreshPublishedRateLimits();
},
refreshSkills: (forceReload) => void host.refreshSkills(forceReload),
publishAppServerMetadata: host.publishAppServerMetadataSnapshot,

View file

@ -133,6 +133,59 @@ describe("ChatAppServerController", () => {
});
});
it("publishes refreshed rate limits from sparse update notifications", async () => {
const state = createChatState();
const stateStore = createChatStateStore(state);
const rateLimit = rateLimitFixture({ primary: { usedPercent: 64, windowDurationMins: 300, resetsAt: null } });
const publishAppServerMetadata = vi.fn();
const client = {
readAccountRateLimits: vi.fn().mockResolvedValue({ rateLimits: rateLimit, rateLimitsByLimitId: null }),
} as unknown as AppServerClient;
const controller = new ChatAppServerController({
stateStore,
vaultPath: "/vault",
currentClient: () => client,
runtimeSnapshot: () => ({}) as never,
forceMessagesToBottom: () => undefined,
publishThreadList: () => undefined,
publishAppServerMetadata,
});
await controller.refreshPublishedRateLimits();
expect(stateStore.getState().rateLimit).toMatchObject({ primary: { usedPercent: 64 } });
expect(publishAppServerMetadata).toHaveBeenCalledWith(expect.objectContaining({ rateLimit }));
});
it("keeps the previous rate limit snapshot when sparse update refresh fails", async () => {
const state = createChatState();
const previousRateLimit = rateLimitFixture({
limitName: "Codex",
primary: { usedPercent: 42, windowDurationMins: 300, resetsAt: null },
});
state.rateLimit = previousRateLimit;
const stateStore = createChatStateStore(state);
const publishAppServerMetadata = vi.fn();
const client = {
readAccountRateLimits: vi.fn().mockRejectedValue(new Error("offline")),
} as unknown as AppServerClient;
const controller = new ChatAppServerController({
stateStore,
vaultPath: "/vault",
currentClient: () => client,
runtimeSnapshot: () => ({}) as never,
forceMessagesToBottom: () => undefined,
publishThreadList: () => undefined,
publishAppServerMetadata,
});
await controller.refreshPublishedRateLimits();
expect(stateStore.getState().rateLimit).toBe(previousRateLimit);
expect(stateStore.getState().appServerDiagnostics.probes["account/rateLimits/read"]).toMatchObject({ status: "failed" });
expect(publishAppServerMetadata).not.toHaveBeenCalled();
});
it("loads MCP status lines with cached startup diagnostics", async () => {
const state = createChatState();
state.activeThreadId = "thread-1";
@ -188,6 +241,20 @@ function threadFixture(id: string, overrides: Partial<Thread> = {}): Thread {
};
}
function rateLimitFixture(overrides: Partial<RateLimitSnapshot> = {}): RateLimitSnapshot {
return {
limitId: "codex",
limitName: "Codex",
primary: null,
secondary: null,
credits: null,
individualLimit: null,
planType: null,
rateLimitReachedType: null,
...overrides,
};
}
function mcpServerStatus(): McpServerStatus {
return {
name: "github",