mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Use panel-owned diagnostic probe ids
This commit is contained in:
parent
7be9a1de24
commit
9842ce38c4
12 changed files with 111 additions and 120 deletions
|
|
@ -192,12 +192,12 @@ export class AppServerQueryCache {
|
|||
const probes = metadata.serverDiagnostics.probes;
|
||||
const next = cloneSharedServerMetadata({
|
||||
...metadata,
|
||||
availableModels: probes["model/list"].status === "ok" ? metadata.availableModels : (this.modelsSnapshot(context) ?? []),
|
||||
availableSkills: probes["skills/list"].status === "ok" ? metadata.availableSkills : (previous?.availableSkills ?? []),
|
||||
rateLimit: probes["account/rateLimits/read"].status === "ok" ? metadata.rateLimit : (previous?.rateLimit ?? null),
|
||||
availableModels: probes.models.status === "ok" ? metadata.availableModels : (this.modelsSnapshot(context) ?? []),
|
||||
availableSkills: probes.skills.status === "ok" ? metadata.availableSkills : (previous?.availableSkills ?? []),
|
||||
rateLimit: probes.rateLimits.status === "ok" ? metadata.rateLimit : (previous?.rateLimit ?? null),
|
||||
});
|
||||
this.client.setQueryData(appServerMetadataQueryKey(context), cloneSharedServerMetadata(next));
|
||||
if (probes["model/list"].status === "ok") {
|
||||
if (probes.models.status === "ok") {
|
||||
this.client.setQueryData(appServerModelsQueryKey(context), cloneModelMetadata(next.availableModels));
|
||||
}
|
||||
return cloneSharedServerMetadata(next);
|
||||
|
|
@ -315,15 +315,15 @@ export class AppServerQueryCache {
|
|||
client: AppServerClient,
|
||||
): Promise<{
|
||||
value: readonly ModelMetadata[];
|
||||
probe: SharedServerMetadata["serverDiagnostics"]["probes"]["model/list"];
|
||||
probe: SharedServerMetadata["serverDiagnostics"]["probes"]["models"];
|
||||
}> {
|
||||
try {
|
||||
const models = cloneModelMetadata(await this.client.fetchQuery(this.modelsQueryOptionsWithClient(context, client)));
|
||||
return { value: models, probe: diagnosticProbeOk("model/list", `${String(models.length)} models`, Date.now()) };
|
||||
return { value: models, probe: diagnosticProbeOk("models", `${String(models.length)} models`, Date.now()) };
|
||||
} catch (error) {
|
||||
return {
|
||||
value: this.modelsSnapshot(context) ?? [],
|
||||
probe: diagnosticProbeError("model/list", error, Date.now()),
|
||||
probe: diagnosticProbeError("models", error, Date.now()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ interface MetadataProbeResult<T, K extends keyof Diagnostics["probes"]> {
|
|||
probe: Diagnostics["probes"][K];
|
||||
}
|
||||
|
||||
export type SkillMetadataProbeResult = MetadataProbeResult<SkillMetadata[], "skills/list">;
|
||||
export type RateLimitMetadataProbeResult = MetadataProbeResult<RateLimitSnapshot | null, "account/rateLimits/read">;
|
||||
export type SkillMetadataProbeResult = MetadataProbeResult<SkillMetadata[], "skills">;
|
||||
export type RateLimitMetadataProbeResult = MetadataProbeResult<RateLimitSnapshot | null, "rateLimits">;
|
||||
|
||||
export async function readSkillMetadataProbe(
|
||||
client: AppServerRequestClient | null,
|
||||
|
|
@ -20,13 +20,13 @@ export async function readSkillMetadataProbe(
|
|||
forceReload = false,
|
||||
): Promise<SkillMetadataProbeResult> {
|
||||
if (!client) {
|
||||
return { value: [], probe: diagnosticProbeError("skills/list", new Error("Codex app-server is not connected."), Date.now()) };
|
||||
return { value: [], probe: diagnosticProbeError("skills", new Error("Codex app-server is not connected."), Date.now()) };
|
||||
}
|
||||
try {
|
||||
const catalog = await listSkillCatalog(client, vaultPath, { forceReload });
|
||||
return { value: catalog.skills, probe: diagnosticProbeOk("skills/list", `${String(catalog.totalCount)} skills`, Date.now()) };
|
||||
return { value: catalog.skills, probe: diagnosticProbeOk("skills", `${String(catalog.totalCount)} skills`, Date.now()) };
|
||||
} catch (error) {
|
||||
return { value: [], probe: diagnosticProbeError("skills/list", error, Date.now()) };
|
||||
return { value: [], probe: diagnosticProbeError("skills", error, Date.now()) };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -34,16 +34,16 @@ export async function readRateLimitMetadataProbe(client: AppServerRequestClient
|
|||
if (!client) {
|
||||
return {
|
||||
value: null,
|
||||
probe: diagnosticProbeError("account/rateLimits/read", new Error("Codex app-server is not connected."), Date.now()),
|
||||
probe: diagnosticProbeError("rateLimits", new Error("Codex app-server is not connected."), Date.now()),
|
||||
};
|
||||
}
|
||||
try {
|
||||
const response = await readAccountRateLimits(client);
|
||||
return {
|
||||
value: rateLimitSnapshotFromAccountRateLimitsResponse(response),
|
||||
probe: diagnosticProbeOk("account/rateLimits/read", accountRateLimitsSummaryFromResponse(response), Date.now()),
|
||||
probe: diagnosticProbeOk("rateLimits", accountRateLimitsSummaryFromResponse(response), Date.now()),
|
||||
};
|
||||
} catch (error) {
|
||||
return { value: null, probe: diagnosticProbeError("account/rateLimits/read", error, Date.now()) };
|
||||
return { value: null, probe: diagnosticProbeError("rateLimits", error, Date.now()) };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ function readCachedSkills(
|
|||
return {
|
||||
items: [...skills],
|
||||
error: null,
|
||||
probe: probe ?? diagnosticProbeOk("skills/list", `${String(skills.length)} skills`, checkedAt),
|
||||
probe: probe ?? diagnosticProbeOk("skills", `${String(skills.length)} skills`, checkedAt),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -86,14 +86,14 @@ async function readPlugins(
|
|||
items: plugins,
|
||||
marketplaceErrors,
|
||||
error: null,
|
||||
probe: diagnosticProbeOk("plugin/installed", `${String(plugins.length)} plugins`, checkedAt),
|
||||
probe: diagnosticProbeOk("plugins", `${String(plugins.length)} plugins`, checkedAt),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
items: null,
|
||||
marketplaceErrors: [],
|
||||
error: shortErrorMessage(error),
|
||||
probe: diagnosticProbeError("plugin/installed", error, checkedAt),
|
||||
probe: diagnosticProbeError("plugins", error, checkedAt),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -113,13 +113,13 @@ async function readMcpServers(
|
|||
return {
|
||||
items: servers,
|
||||
error: null,
|
||||
probe: diagnosticProbeOk("mcpServerStatus/list", mcpSummary(servers), checkedAt),
|
||||
probe: diagnosticProbeOk("mcpServers", mcpSummary(servers), checkedAt),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
items: null,
|
||||
error: shortErrorMessage(error),
|
||||
probe: diagnosticProbeError("mcpServerStatus/list", error, checkedAt),
|
||||
probe: diagnosticProbeError("mcpServers", error, checkedAt),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -139,10 +139,10 @@ async function readSkills(
|
|||
return {
|
||||
items: catalog.skills,
|
||||
error: null,
|
||||
probe: diagnosticProbeOk("skills/list", `${String(catalog.totalCount)} skills`, checkedAt),
|
||||
probe: diagnosticProbeOk("skills", `${String(catalog.totalCount)} skills`, checkedAt),
|
||||
};
|
||||
} catch (error) {
|
||||
return { items: null, error: shortErrorMessage(error), probe: diagnosticProbeError("skills/list", error, checkedAt) };
|
||||
return { items: null, error: shortErrorMessage(error), probe: diagnosticProbeError("skills", error, checkedAt) };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,20 +2,20 @@ import type { ServerInitialization } from "./initialization";
|
|||
import { type McpServerDiagnostic, type McpServerStatusSummary, mcpServerDiagnosticFromStatus } from "./mcp-status";
|
||||
import { cloneToolInventorySnapshot, type ToolInventorySnapshot } from "./tool-inventory";
|
||||
|
||||
const DIAGNOSTIC_PROBE_METHODS = [
|
||||
"model/list",
|
||||
"skills/list",
|
||||
"app/list",
|
||||
"plugin/installed",
|
||||
"account/rateLimits/read",
|
||||
"mcpServerStatus/list",
|
||||
] as const;
|
||||
const DIAGNOSTIC_PROBE_DEFINITIONS = {
|
||||
models: { label: "Models" },
|
||||
skills: { label: "Skills" },
|
||||
apps: { label: "Apps" },
|
||||
plugins: { label: "Plugins" },
|
||||
rateLimits: { label: "Rate limits" },
|
||||
mcpServers: { label: "MCP servers" },
|
||||
} as const;
|
||||
|
||||
export type DiagnosticProbeMethod = (typeof DIAGNOSTIC_PROBE_METHODS)[number];
|
||||
export type DiagnosticProbeId = keyof typeof DIAGNOSTIC_PROBE_DEFINITIONS;
|
||||
type DiagnosticProbeStatus = "unknown" | "ok" | "failed";
|
||||
|
||||
export interface DiagnosticProbeResult {
|
||||
readonly method: DiagnosticProbeMethod;
|
||||
readonly id: DiagnosticProbeId;
|
||||
readonly status: DiagnosticProbeStatus;
|
||||
readonly message: string | null;
|
||||
readonly summary: string | null;
|
||||
|
|
@ -23,17 +23,16 @@ export interface DiagnosticProbeResult {
|
|||
}
|
||||
|
||||
export interface Diagnostics {
|
||||
readonly probes: Readonly<Record<DiagnosticProbeMethod, DiagnosticProbeResult>>;
|
||||
readonly probes: Readonly<Record<DiagnosticProbeId, DiagnosticProbeResult>>;
|
||||
readonly mcpServers: readonly McpServerDiagnostic[];
|
||||
readonly toolInventory: ToolInventorySnapshot | null;
|
||||
}
|
||||
|
||||
export function createServerDiagnostics(): Diagnostics {
|
||||
return {
|
||||
probes: Object.fromEntries(DIAGNOSTIC_PROBE_METHODS.map((method) => [method, createDiagnosticProbeResult(method)])) as Record<
|
||||
DiagnosticProbeMethod,
|
||||
DiagnosticProbeResult
|
||||
>,
|
||||
probes: Object.fromEntries(
|
||||
Object.keys(DIAGNOSTIC_PROBE_DEFINITIONS).map((id) => [id, createDiagnosticProbeResult(id as DiagnosticProbeId)]),
|
||||
) as Record<DiagnosticProbeId, DiagnosticProbeResult>,
|
||||
mcpServers: [],
|
||||
toolInventory: null,
|
||||
};
|
||||
|
|
@ -52,7 +51,7 @@ export function diagnosticsWithProbe(diagnostics: Diagnostics, probe: Diagnostic
|
|||
...diagnostics,
|
||||
probes: {
|
||||
...diagnostics.probes,
|
||||
[probe.method]: probe,
|
||||
[probe.id]: probe,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -64,9 +63,9 @@ export function diagnosticsWithToolInventory(diagnostics: Diagnostics, toolInven
|
|||
};
|
||||
}
|
||||
|
||||
function createDiagnosticProbeResult(method: DiagnosticProbeMethod): DiagnosticProbeResult {
|
||||
function createDiagnosticProbeResult(id: DiagnosticProbeId): DiagnosticProbeResult {
|
||||
return {
|
||||
method,
|
||||
id,
|
||||
status: "unknown",
|
||||
message: null,
|
||||
summary: null,
|
||||
|
|
@ -74,9 +73,9 @@ function createDiagnosticProbeResult(method: DiagnosticProbeMethod): DiagnosticP
|
|||
};
|
||||
}
|
||||
|
||||
export function diagnosticProbeOk(method: DiagnosticProbeMethod, summary: string | null, checkedAt: number): DiagnosticProbeResult {
|
||||
export function diagnosticProbeOk(id: DiagnosticProbeId, summary: string | null, checkedAt: number): DiagnosticProbeResult {
|
||||
return {
|
||||
method,
|
||||
id,
|
||||
status: "ok",
|
||||
message: null,
|
||||
summary,
|
||||
|
|
@ -84,9 +83,9 @@ export function diagnosticProbeOk(method: DiagnosticProbeMethod, summary: string
|
|||
};
|
||||
}
|
||||
|
||||
export function diagnosticProbeError(method: DiagnosticProbeMethod, error: unknown, checkedAt: number): DiagnosticProbeResult {
|
||||
export function diagnosticProbeError(id: DiagnosticProbeId, error: unknown, checkedAt: number): DiagnosticProbeResult {
|
||||
return {
|
||||
method,
|
||||
id,
|
||||
status: "failed",
|
||||
message: shortErrorMessage(error),
|
||||
summary: null,
|
||||
|
|
@ -94,6 +93,10 @@ export function diagnosticProbeError(method: DiagnosticProbeMethod, error: unkno
|
|||
};
|
||||
}
|
||||
|
||||
export function diagnosticProbeLabel(id: DiagnosticProbeId): string {
|
||||
return DIAGNOSTIC_PROBE_DEFINITIONS[id].label;
|
||||
}
|
||||
|
||||
export function serverIdentity(initializeResponse: ServerInitialization | null): string {
|
||||
return initializeResponse?.userAgent ?? "(not connected)";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { listModelMetadata } from "../../../../app-server/services/catalog";
|
|||
import { readToolInventory } from "../../../../app-server/services/tool-inventory";
|
||||
import {
|
||||
cloneServerDiagnostics,
|
||||
type DiagnosticProbeMethod,
|
||||
type Diagnostics,
|
||||
type DiagnosticProbeId,
|
||||
type DiagnosticProbeResult,
|
||||
diagnosticProbeError,
|
||||
diagnosticProbeOk,
|
||||
diagnosticsWithProbe,
|
||||
|
|
@ -21,7 +21,7 @@ interface RefreshServerDiagnosticsOptions {
|
|||
}
|
||||
|
||||
interface DiagnosticProbeSnapshot {
|
||||
probe: Diagnostics["probes"][DiagnosticProbeMethod];
|
||||
probe: DiagnosticProbeResult;
|
||||
}
|
||||
|
||||
export interface ChatServerDiagnosticsActionsHost extends ChatServerActionsHost {
|
||||
|
|
@ -58,7 +58,7 @@ async function refreshServerDiagnostics(
|
|||
const cachedSkillsProbe =
|
||||
options.forceResourceProbes === true
|
||||
? undefined
|
||||
: (metadataSnapshot?.serverDiagnostics.probes["skills/list"] ?? state.connection.serverDiagnostics.probes["skills/list"]);
|
||||
: (metadataSnapshot?.serverDiagnostics.probes.skills ?? state.connection.serverDiagnostics.probes.skills);
|
||||
const toolInventory = readToolInventory(client, host.vaultPath, {
|
||||
threadId: activeThreadId,
|
||||
mcpDiagnostics: initialDiagnostics.mcpServers,
|
||||
|
|
@ -69,7 +69,7 @@ async function refreshServerDiagnostics(
|
|||
if (options.forceResourceProbes === true && options.appServerMetadataSnapshot !== true) {
|
||||
probes.push(
|
||||
probeDiagnostic(
|
||||
"model/list",
|
||||
"models",
|
||||
() => listModelMetadata(client),
|
||||
(models) => `${String(models.length)} models`,
|
||||
),
|
||||
|
|
@ -108,16 +108,16 @@ function currentMetadataDiagnostics(host: ChatServerDiagnosticsActionsHost): Sha
|
|||
}
|
||||
|
||||
async function probeDiagnostic<T>(
|
||||
method: DiagnosticProbeMethod,
|
||||
id: DiagnosticProbeId,
|
||||
request: () => Promise<T>,
|
||||
summarize: (response: T) => string | null,
|
||||
): Promise<DiagnosticProbeSnapshot> {
|
||||
try {
|
||||
const response = await request();
|
||||
return {
|
||||
probe: diagnosticProbeOk(method, summarize(response), Date.now()),
|
||||
probe: diagnosticProbeOk(id, summarize(response), Date.now()),
|
||||
};
|
||||
} catch (error) {
|
||||
return { probe: diagnosticProbeError(method, error, Date.now()) };
|
||||
return { probe: diagnosticProbeError(id, error, Date.now()) };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { CLIENT_VERSION } from "../../../../constants";
|
||||
import type { DiagnosticProbeResult, Diagnostics } from "../../../../domain/server/diagnostics";
|
||||
import { type DiagnosticProbeMethod, serverIdentity, serverPlatform } from "../../../../domain/server/diagnostics";
|
||||
import type { DiagnosticProbeId, DiagnosticProbeResult, Diagnostics } from "../../../../domain/server/diagnostics";
|
||||
import { diagnosticProbeLabel, serverIdentity, serverPlatform } from "../../../../domain/server/diagnostics";
|
||||
import type { ServerInitialization } from "../../../../domain/server/initialization";
|
||||
|
||||
const RUNTIME_CHECK_PROBE_METHODS: readonly DiagnosticProbeMethod[] = ["model/list", "account/rateLimits/read"];
|
||||
const RUNTIME_CHECK_PROBE_IDS: readonly DiagnosticProbeId[] = ["models", "rateLimits"];
|
||||
|
||||
export interface DiagnosticRow {
|
||||
label: string;
|
||||
|
|
@ -38,7 +38,7 @@ export function appServerDiagnosticSections(input: AppServerDiagnosticSectionsIn
|
|||
},
|
||||
{
|
||||
title: "Runtime Checks",
|
||||
rows: RUNTIME_CHECK_PROBE_METHODS.map((method) => diagnosticProbeRow(input.diagnostics.probes[method])),
|
||||
rows: RUNTIME_CHECK_PROBE_IDS.map((id) => diagnosticProbeRow(input.diagnostics.probes[id])),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ export function appServerDiagnosticSections(input: AppServerDiagnosticSectionsIn
|
|||
function diagnosticProbeRow(probe: DiagnosticProbeResult): DiagnosticRow {
|
||||
const detail = probe.message ? ` - ${probe.message}` : probe.summary ? ` (${probe.summary})` : "";
|
||||
return {
|
||||
label: probe.method,
|
||||
label: diagnosticProbeLabel(probe.id),
|
||||
value: `${probe.status}${detail}`,
|
||||
level: probe.status === "failed" ? "error" : probe.status === "unknown" ? "warning" : "normal",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { describe, expect, it } from "vitest";
|
|||
import {
|
||||
createServerDiagnostics,
|
||||
diagnosticProbeError,
|
||||
diagnosticProbeLabel,
|
||||
diagnosticProbeOk,
|
||||
serverIdentity,
|
||||
serverPlatform,
|
||||
|
|
@ -27,43 +28,37 @@ describe("app-server diagnostics", () => {
|
|||
it("creates generic capability probe defaults", () => {
|
||||
const diagnostics = createServerDiagnostics();
|
||||
|
||||
expect(Object.keys(diagnostics.probes)).toEqual([
|
||||
"model/list",
|
||||
"skills/list",
|
||||
"app/list",
|
||||
"plugin/installed",
|
||||
"account/rateLimits/read",
|
||||
"mcpServerStatus/list",
|
||||
]);
|
||||
expect(diagnostics.probes["model/list"]).toMatchObject({
|
||||
method: "model/list",
|
||||
expect(Object.keys(diagnostics.probes)).toEqual(["models", "skills", "apps", "plugins", "rateLimits", "mcpServers"]);
|
||||
expect(diagnostics.probes.models).toMatchObject({
|
||||
id: "models",
|
||||
status: "unknown",
|
||||
message: null,
|
||||
summary: null,
|
||||
checkedAt: null,
|
||||
});
|
||||
expect(diagnosticProbeLabel("models")).toBe("Models");
|
||||
});
|
||||
|
||||
it("classifies ok and failed capability probes", () => {
|
||||
expect(diagnosticProbeOk("skills/list", "3 skills", 123)).toEqual({
|
||||
method: "skills/list",
|
||||
expect(diagnosticProbeOk("skills", "3 skills", 123)).toEqual({
|
||||
id: "skills",
|
||||
status: "ok",
|
||||
message: null,
|
||||
summary: "3 skills",
|
||||
checkedAt: 123,
|
||||
});
|
||||
expect(diagnosticProbeError("plugin/installed", new Error("boom"), 456)).toMatchObject({
|
||||
method: "plugin/installed",
|
||||
expect(diagnosticProbeError("plugins", new Error("boom"), 456)).toMatchObject({
|
||||
id: "plugins",
|
||||
status: "failed",
|
||||
message: "boom",
|
||||
checkedAt: 456,
|
||||
});
|
||||
expect(diagnosticProbeError("model/list", new Error("unknown provider failure"), 792).status).toBe("failed");
|
||||
expect(diagnosticProbeError("models", new Error("unknown provider failure"), 792).status).toBe("failed");
|
||||
});
|
||||
|
||||
it("shortens error messages and tracks MCP server diagnostics", () => {
|
||||
expect(diagnosticProbeError("model/list", "a\n b\t c", 1).message).toBe("a b c");
|
||||
expect(diagnosticProbeError("model/list", "x".repeat(200), 1).message).toHaveLength(160);
|
||||
expect(diagnosticProbeError("models", "a\n b\t c", 1).message).toBe("a b c");
|
||||
expect(diagnosticProbeError("models", "x".repeat(200), 1).message).toHaveLength(160);
|
||||
|
||||
let diagnostics = upsertMcpServerDiagnostic(createServerDiagnostics(), {
|
||||
name: "github",
|
||||
|
|
|
|||
|
|
@ -39,12 +39,12 @@ describe("AppServerQueryCache", () => {
|
|||
expect(cache.appServerMetadataSnapshot(context)?.availableModels.map((model) => model.model)).toEqual(["gpt-5.6"]);
|
||||
expect(cache.appServerMetadataSnapshot(context)?.availableSkills.map((skill) => skill.name)).toEqual(["writer"]);
|
||||
expect(cache.appServerMetadataSnapshot(context)?.rateLimit?.primary?.usedPercent).toBe(42);
|
||||
expect(cache.appServerMetadataSnapshot(context)?.serverDiagnostics.probes["skills/list"].status).toBe("failed");
|
||||
expect(cache.appServerMetadataSnapshot(context)?.serverDiagnostics.probes["account/rateLimits/read"].status).toBe("failed");
|
||||
expect(cache.appServerMetadataSnapshot(context)?.serverDiagnostics.probes.skills.status).toBe("failed");
|
||||
expect(cache.appServerMetadataSnapshot(context)?.serverDiagnostics.probes.rateLimits.status).toBe("failed");
|
||||
|
||||
cache.writeAppServerMetadata(context, metadata({ availableModels: [], modelProbeStatus: "failed" }));
|
||||
expect(cache.appServerMetadataSnapshot(context)?.availableModels.map((model) => model.model)).toEqual(["gpt-5.6"]);
|
||||
expect(cache.appServerMetadataSnapshot(context)?.serverDiagnostics.probes["model/list"].status).toBe("failed");
|
||||
expect(cache.appServerMetadataSnapshot(context)?.serverDiagnostics.probes.models.status).toBe("failed");
|
||||
});
|
||||
|
||||
it("does not accept failed metadata resource payloads as cache truth", () => {
|
||||
|
|
@ -65,7 +65,7 @@ describe("AppServerQueryCache", () => {
|
|||
|
||||
const cached = cache.appServerMetadataSnapshot(context);
|
||||
expect(cached?.runtimeConfig).not.toBeNull();
|
||||
expect(cached?.serverDiagnostics.probes["model/list"].status).toBe("failed");
|
||||
expect(cached?.serverDiagnostics.probes.models.status).toBe("failed");
|
||||
expect(cached?.availableModels).toEqual([]);
|
||||
expect(cached?.availableSkills).toEqual([]);
|
||||
expect(cached?.rateLimit).toBeNull();
|
||||
|
|
@ -192,7 +192,7 @@ describe("AppServerQueryCache", () => {
|
|||
expect(metadata?.availableModels.map((model) => model.model)).toEqual(["gpt-meta"]);
|
||||
expect(metadata?.availableSkills.map((skill) => skill.name)).toEqual(["writer"]);
|
||||
expect(metadata?.rateLimit?.primary?.usedPercent).toBe(64);
|
||||
expect(metadata?.serverDiagnostics.probes["model/list"].status).toBe("ok");
|
||||
expect(metadata?.serverDiagnostics.probes.models.status).toBe("ok");
|
||||
expect(cache.modelsSnapshot(context)?.map((model) => model.model)).toEqual(["gpt-meta"]);
|
||||
});
|
||||
|
||||
|
|
@ -237,7 +237,7 @@ describe("AppServerQueryCache", () => {
|
|||
const metadataSnapshot = await cache.refreshAppServerMetadata(context);
|
||||
|
||||
expect(metadataSnapshot?.availableModels.map((model) => model.model)).toEqual(["gpt-cached"]);
|
||||
expect(metadataSnapshot?.serverDiagnostics.probes["model/list"].status).toBe("failed");
|
||||
expect(metadataSnapshot?.serverDiagnostics.probes.models.status).toBe("failed");
|
||||
expect(cache.modelsSnapshot(context)?.map((model) => model.model)).toEqual(["gpt-cached"]);
|
||||
});
|
||||
|
||||
|
|
@ -329,20 +329,20 @@ function metadata(
|
|||
diagnostics = diagnosticsWithProbe(
|
||||
diagnostics,
|
||||
overrides.modelProbeStatus === "failed"
|
||||
? diagnosticProbeError("model/list", new Error("offline"), 1)
|
||||
: diagnosticProbeOk("model/list", "1 models", 1),
|
||||
? diagnosticProbeError("models", new Error("offline"), 1)
|
||||
: diagnosticProbeOk("models", "1 models", 1),
|
||||
);
|
||||
diagnostics = diagnosticsWithProbe(
|
||||
diagnostics,
|
||||
overrides.skillsProbeStatus === "failed"
|
||||
? diagnosticProbeError("skills/list", new Error("offline"), 1)
|
||||
: diagnosticProbeOk("skills/list", "0 skills", 1),
|
||||
? diagnosticProbeError("skills", new Error("offline"), 1)
|
||||
: diagnosticProbeOk("skills", "0 skills", 1),
|
||||
);
|
||||
diagnostics = diagnosticsWithProbe(
|
||||
diagnostics,
|
||||
overrides.rateLimitProbeStatus === "failed"
|
||||
? diagnosticProbeError("account/rateLimits/read", new Error("offline"), 1)
|
||||
: diagnosticProbeOk("account/rateLimits/read", "available", 1),
|
||||
? diagnosticProbeError("rateLimits", new Error("offline"), 1)
|
||||
: diagnosticProbeOk("rateLimits", "available", 1),
|
||||
);
|
||||
return {
|
||||
runtimeConfig: overrides.runtimeConfig ?? emptyRuntimeConfigSnapshot(),
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ function model(modelId: string): ModelMetadata {
|
|||
}
|
||||
|
||||
function serverMetadata(overrides: Partial<SharedServerMetadata> = {}): SharedServerMetadata {
|
||||
const diagnostics = diagnosticsWithProbe(createServerDiagnostics(), diagnosticProbeOk("model/list", "0 models", 1));
|
||||
const diagnostics = diagnosticsWithProbe(createServerDiagnostics(), diagnosticProbeOk("models", "0 models", 1));
|
||||
return {
|
||||
runtimeConfig: null,
|
||||
availableModels: [],
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ describe("tool inventory", () => {
|
|||
const result = await readToolInventory(client, "/vault");
|
||||
|
||||
expect(client.request).not.toHaveBeenCalledWith("app/list", expect.anything());
|
||||
expect(result.probes.some((probe) => probe.method === "app/list")).toBe(false);
|
||||
expect(result.probes.some((probe) => probe.id === "apps")).toBe(false);
|
||||
});
|
||||
|
||||
it("preserves plugin order from installed plugin summaries", async () => {
|
||||
|
|
|
|||
|
|
@ -253,10 +253,10 @@ describe("chat app-server actions", () => {
|
|||
rateLimit: rateLimitFixture(),
|
||||
serverDiagnostics: diagnosticsWithProbe(
|
||||
diagnosticsWithProbe(
|
||||
diagnosticsWithProbe(createServerDiagnostics(), diagnosticProbeOk("model/list", "1 models", 1)),
|
||||
diagnosticProbeOk("skills/list", "1 skills", 1),
|
||||
diagnosticsWithProbe(createServerDiagnostics(), diagnosticProbeOk("models", "1 models", 1)),
|
||||
diagnosticProbeOk("skills", "1 skills", 1),
|
||||
),
|
||||
diagnosticProbeOk("account/rateLimits/read", "available", 1),
|
||||
diagnosticProbeOk("rateLimits", "available", 1),
|
||||
),
|
||||
});
|
||||
const refreshAppServerMetadata = vi.fn<() => Promise<SharedServerMetadata | null>>().mockResolvedValue(refreshedMetadata);
|
||||
|
|
@ -289,15 +289,15 @@ describe("chat app-server actions", () => {
|
|||
|
||||
expect(refreshAppServerMetadata).toHaveBeenCalledOnce();
|
||||
expect(listMcpServerStatus).toHaveBeenCalledWith({ detail: "toolsAndAuthOnly", limit: 100 });
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes["model/list"]).toMatchObject({
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes.models).toMatchObject({
|
||||
status: "ok",
|
||||
summary: "1 models",
|
||||
});
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes["skills/list"]).toMatchObject({
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes.skills).toMatchObject({
|
||||
status: "ok",
|
||||
summary: "1 skills",
|
||||
});
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes["account/rateLimits/read"]).toMatchObject({
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes.rateLimits).toMatchObject({
|
||||
status: "ok",
|
||||
summary: "available",
|
||||
});
|
||||
|
|
@ -325,7 +325,7 @@ describe("chat app-server actions", () => {
|
|||
const stateStore = createChatStateStore(chatStateFixture());
|
||||
const metadataCache = metadataCacheHost({
|
||||
current: serverMetadataFixture({
|
||||
serverDiagnostics: diagnosticsWithProbe(createServerDiagnostics(), diagnosticProbeOk("model/list", "cached models", 1)),
|
||||
serverDiagnostics: diagnosticsWithProbe(createServerDiagnostics(), diagnosticProbeOk("models", "cached models", 1)),
|
||||
}),
|
||||
});
|
||||
const listModels = vi.fn().mockResolvedValue({ data: [modelFixture("gpt-direct")] });
|
||||
|
|
@ -350,7 +350,7 @@ describe("chat app-server actions", () => {
|
|||
expect(listModels).not.toHaveBeenCalled();
|
||||
expect(listSkills).not.toHaveBeenCalled();
|
||||
expect(readAccountRateLimits).not.toHaveBeenCalled();
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes["model/list"]).toMatchObject({
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes.models).toMatchObject({
|
||||
status: "ok",
|
||||
summary: "cached models",
|
||||
});
|
||||
|
|
@ -380,7 +380,7 @@ describe("chat app-server actions", () => {
|
|||
expect(listModels).toHaveBeenCalledWith({ includeHidden: false, limit: 100 });
|
||||
expect(listSkills).toHaveBeenCalledWith({ cwds: ["/vault"], forceReload: false });
|
||||
expect(readAccountRateLimits).toHaveBeenCalledWith(undefined);
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes["model/list"]).toMatchObject({
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes.models).toMatchObject({
|
||||
status: "ok",
|
||||
summary: "1 models",
|
||||
});
|
||||
|
|
@ -410,7 +410,7 @@ describe("chat app-server actions", () => {
|
|||
|
||||
await refreshing;
|
||||
expect(listMcpServerStatus).toHaveBeenCalledWith({ detail: "toolsAndAuthOnly", limit: 100 });
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes["mcpServerStatus/list"].status).toBe("unknown");
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes.mcpServers.status).toBe("unknown");
|
||||
expect(stateStore.getState().connection.serverDiagnostics.mcpServers).toEqual([]);
|
||||
expect(updateAppServerMetadata).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
@ -438,7 +438,7 @@ describe("chat app-server actions", () => {
|
|||
const stateStore = createChatStateStore(state);
|
||||
const metadata = serverMetadataFixture({
|
||||
availableModels: modelMetadataFromCatalogModels([modelFixture("gpt-cached")]),
|
||||
serverDiagnostics: diagnosticsWithProbe(createServerDiagnostics(), diagnosticProbeError("model/list", new Error("offline"), 1)),
|
||||
serverDiagnostics: diagnosticsWithProbe(createServerDiagnostics(), diagnosticProbeError("models", new Error("offline"), 1)),
|
||||
});
|
||||
const controller = createChatServerMetadataActions({
|
||||
stateStore,
|
||||
|
|
@ -451,7 +451,7 @@ describe("chat app-server actions", () => {
|
|||
await controller.refreshAppServerMetadata();
|
||||
|
||||
expect(stateStore.getState().connection.availableModels.map((model) => model.model)).toEqual(["gpt-cached"]);
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes["model/list"].status).toBe("failed");
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes.models.status).toBe("failed");
|
||||
});
|
||||
|
||||
it("does not use chat state as a second model source when metadata model refresh fails", async () => {
|
||||
|
|
@ -460,7 +460,7 @@ describe("chat app-server actions", () => {
|
|||
const stateStore = createChatStateStore(state);
|
||||
const metadata = serverMetadataFixture({
|
||||
availableModels: [],
|
||||
serverDiagnostics: diagnosticsWithProbe(createServerDiagnostics(), diagnosticProbeError("model/list", new Error("offline"), 1)),
|
||||
serverDiagnostics: diagnosticsWithProbe(createServerDiagnostics(), diagnosticProbeError("models", new Error("offline"), 1)),
|
||||
});
|
||||
const controller = createChatServerMetadataActions({
|
||||
stateStore,
|
||||
|
|
@ -473,7 +473,7 @@ describe("chat app-server actions", () => {
|
|||
await controller.refreshAppServerMetadata();
|
||||
|
||||
expect(stateStore.getState().connection.availableModels).toEqual([]);
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes["model/list"].status).toBe("failed");
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes.models.status).toBe("failed");
|
||||
});
|
||||
|
||||
it("does not apply or publish refreshed skills after the client changes", async () => {
|
||||
|
|
@ -500,7 +500,7 @@ describe("chat app-server actions", () => {
|
|||
await refreshing;
|
||||
expect(listSkills).toHaveBeenCalledWith({ cwds: ["/vault"], forceReload: true });
|
||||
expect(stateStore.getState().connection.availableSkills).toEqual([]);
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes["skills/list"].status).toBe("unknown");
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes.skills.status).toBe("unknown");
|
||||
expect(updateAppServerMetadata).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
@ -523,7 +523,7 @@ describe("chat app-server actions", () => {
|
|||
|
||||
expect(listSkills).toHaveBeenCalledWith({ cwds: ["/vault"], forceReload: true });
|
||||
expect(stateStore.getState().connection.availableSkills).toEqual(previousSkills);
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes["skills/list"]).toMatchObject({ status: "failed" });
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes.skills).toMatchObject({ status: "failed" });
|
||||
});
|
||||
|
||||
it("publishes refreshed rate limits from sparse update notifications", async () => {
|
||||
|
|
@ -570,7 +570,7 @@ describe("chat app-server actions", () => {
|
|||
await controller.applyAppServerResourceEvent({ type: "rate-limits-updated", preserveExistingOnFailure: true });
|
||||
|
||||
expect(stateStore.getState().connection.rateLimit).toBe(previousRateLimit);
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes["account/rateLimits/read"]).toMatchObject({ status: "failed" });
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes.rateLimits).toMatchObject({ status: "failed" });
|
||||
});
|
||||
|
||||
it("does not apply or publish sparse rate limit refreshes after the client changes", async () => {
|
||||
|
|
@ -600,7 +600,7 @@ describe("chat app-server actions", () => {
|
|||
|
||||
await refreshing;
|
||||
expect(stateStore.getState().connection.rateLimit).toBeNull();
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes["account/rateLimits/read"].status).toBe("unknown");
|
||||
expect(stateStore.getState().connection.serverDiagnostics.probes.rateLimits.status).toBe("unknown");
|
||||
expect(updateAppServerMetadata).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -23,12 +23,9 @@ function diagnosticsWithToolInventory(inventory: ToolInventorySnapshot) {
|
|||
describe("connection diagnostics", () => {
|
||||
it("formats connection rows and runtime checks for /doctor", () => {
|
||||
let diagnostics = createServerDiagnostics();
|
||||
diagnostics = diagnosticsWithProbe(diagnostics, diagnosticProbeOk("model/list", "12 models", 1));
|
||||
diagnostics = diagnosticsWithProbe(
|
||||
diagnostics,
|
||||
diagnosticProbeError("account/rateLimits/read", new Error("rate limit request failed"), 2),
|
||||
);
|
||||
diagnostics = diagnosticsWithProbe(diagnostics, diagnosticProbeError("skills/list", new Error("unknown method skills/list"), 3));
|
||||
diagnostics = diagnosticsWithProbe(diagnostics, diagnosticProbeOk("models", "12 models", 1));
|
||||
diagnostics = diagnosticsWithProbe(diagnostics, diagnosticProbeError("rateLimits", new Error("rate limit request failed"), 2));
|
||||
diagnostics = diagnosticsWithProbe(diagnostics, diagnosticProbeError("skills", new Error("unknown method skills/list"), 3));
|
||||
diagnostics = upsertMcpServerDiagnostic(diagnostics, {
|
||||
name: "github",
|
||||
startupStatus: "failed",
|
||||
|
|
@ -59,15 +56,11 @@ describe("connection diagnostics", () => {
|
|||
const rows = sections.flatMap((section) => section.rows);
|
||||
expect(sections.map((section) => section.title)).toEqual(["Process", "Runtime Checks"]);
|
||||
expect(rows.map((row) => `${row.label}: ${row.value}`)).toEqual(
|
||||
expect.arrayContaining([
|
||||
"connection: connected",
|
||||
"model/list: ok (12 models)",
|
||||
"account/rateLimits/read: failed - rate limit request failed",
|
||||
]),
|
||||
expect.arrayContaining(["connection: connected", "Models: ok (12 models)", "Rate limits: failed - rate limit request failed"]),
|
||||
);
|
||||
expect(rows.find((row) => row.label === "account/rateLimits/read")?.level).toBe("error");
|
||||
expect(rows.find((row) => row.label === "skills/list")).toBeUndefined();
|
||||
expect(rows.find((row) => row.label === "mcpServerStatus/list")).toBeUndefined();
|
||||
expect(rows.find((row) => row.label === "Rate limits")?.level).toBe("error");
|
||||
expect(rows.find((row) => row.label === "Skills")).toBeUndefined();
|
||||
expect(rows.find((row) => row.label === "MCP servers")).toBeUndefined();
|
||||
expect(rows.find((row) => row.label === "mcp github")).toBeUndefined();
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue