نسخهٔ وب مستقل — پلتفرم نیروی کار هوش مصنوعی
مخزن اختصاصی برنامهٔ وب؛ محتوای پوشهٔ web در ریشهٔ مخزن قرار گرفت. تاریخچهٔ پروژهٔ ربات (agy-bot) در این مخزن وجود ندارد و هیچ وابستگی runtime به آن نیست.
This commit is contained in:
@@ -0,0 +1,533 @@
|
||||
/**
|
||||
* Typed API client.
|
||||
*
|
||||
* One place knows the base URL, the bearer token and the error envelope. Every
|
||||
* call returns parsed JSON or throws `ApiError`, so components never have to
|
||||
* inspect status codes.
|
||||
*/
|
||||
|
||||
import type { ApiErrorBody } from "@agy/contracts";
|
||||
|
||||
export const API_BASE_URL =
|
||||
process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8000";
|
||||
|
||||
export class ApiError extends Error {
|
||||
readonly status: number;
|
||||
readonly code: string;
|
||||
readonly details?: unknown;
|
||||
/**
|
||||
* The server's correlation id for this failure. It is also the
|
||||
* `X-Request-ID` response header, and it identifies one row in the platform
|
||||
* error console — so a bug report that includes it is immediately actionable.
|
||||
*/
|
||||
readonly requestId?: string;
|
||||
|
||||
constructor(
|
||||
status: number,
|
||||
code: string,
|
||||
message: string,
|
||||
details?: unknown,
|
||||
requestId?: string,
|
||||
) {
|
||||
super(message);
|
||||
this.name = "ApiError";
|
||||
this.status = status;
|
||||
this.code = code;
|
||||
this.details = details;
|
||||
this.requestId = requestId;
|
||||
}
|
||||
}
|
||||
|
||||
const TOKEN_STORAGE_KEY = "agy.access_token";
|
||||
|
||||
export function getStoredToken(): string | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
try {
|
||||
return window.localStorage.getItem(TOKEN_STORAGE_KEY);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function setStoredToken(token: string | null): void {
|
||||
if (typeof window === "undefined") return;
|
||||
try {
|
||||
if (token) window.localStorage.setItem(TOKEN_STORAGE_KEY, token);
|
||||
else window.localStorage.removeItem(TOKEN_STORAGE_KEY);
|
||||
} catch {
|
||||
/* storage disabled — the session simply will not survive a reload */
|
||||
}
|
||||
}
|
||||
|
||||
export interface RequestOptions {
|
||||
method?: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
|
||||
body?: unknown;
|
||||
token?: string | null;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export async function apiRequest<T>(path: string, options: RequestOptions = {}): Promise<T> {
|
||||
const { method = "GET", body, token, signal } = options;
|
||||
const headers: Record<string, string> = { Accept: "application/json" };
|
||||
if (body !== undefined) headers["Content-Type"] = "application/json";
|
||||
const authToken = token ?? getStoredToken();
|
||||
if (authToken) headers.Authorization = `Bearer ${authToken}`;
|
||||
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(`${API_BASE_URL}${path}`, {
|
||||
method,
|
||||
headers,
|
||||
body: body === undefined ? undefined : JSON.stringify(body),
|
||||
signal,
|
||||
cache: "no-store",
|
||||
});
|
||||
} catch (error) {
|
||||
throw new ApiError(0, "network_error", "ارتباط با سرور برقرار نشد.", error);
|
||||
}
|
||||
|
||||
if (response.status === 204) return undefined as T;
|
||||
|
||||
const text = await response.text();
|
||||
const payload = text ? (JSON.parse(text) as unknown) : null;
|
||||
|
||||
if (!response.ok) {
|
||||
const envelope = payload as ApiErrorBody | null;
|
||||
const code = envelope?.error?.code ?? "http_error";
|
||||
const message = envelope?.error?.message ?? `خطای سرور (${response.status})`;
|
||||
// Fall back to the header: a failure raised outside the error handlers
|
||||
// (a CORS rejection, a proxy error) still carries the id.
|
||||
const requestId =
|
||||
envelope?.error?.request_id ?? response.headers.get("X-Request-ID") ?? undefined;
|
||||
throw new ApiError(response.status, code, message, envelope?.error?.details, requestId);
|
||||
}
|
||||
|
||||
return payload as T;
|
||||
}
|
||||
|
||||
export const api = {
|
||||
health: () => apiRequest<{ status: string }>("/health"),
|
||||
readiness: () => apiRequest<import("@agy/contracts").ReadinessResponse>("/health/ready"),
|
||||
signup: (body: { email: string; password: string; name?: string }) =>
|
||||
apiRequest<import("@agy/contracts").TokenResponse>("/api/auth/signup", {
|
||||
method: "POST",
|
||||
body,
|
||||
}),
|
||||
login: (body: { email: string; password: string }) =>
|
||||
apiRequest<import("@agy/contracts").TokenResponse>("/api/auth/login", {
|
||||
method: "POST",
|
||||
body,
|
||||
}),
|
||||
logout: () => apiRequest<void>("/api/auth/logout", { method: "POST" }),
|
||||
me: () => apiRequest<import("@agy/contracts").MeResponse>("/api/auth/me"),
|
||||
listOrganizations: () =>
|
||||
apiRequest<import("@agy/contracts").OrganizationWithRole[]>("/api/organizations"),
|
||||
createOrganization: (name: string) =>
|
||||
apiRequest<import("@agy/contracts").OrganizationWithRole>("/api/organizations", {
|
||||
method: "POST",
|
||||
body: { name },
|
||||
}),
|
||||
listMembers: (orgId: string) =>
|
||||
apiRequest<import("@agy/contracts").Member[]>(`/api/organizations/${orgId}/members`),
|
||||
createInvite: (orgId: string, body: { email: string; role: import("@agy/contracts").Role }) =>
|
||||
apiRequest<import("@agy/contracts").Invite>(`/api/organizations/${orgId}/invites`, {
|
||||
method: "POST",
|
||||
body,
|
||||
}),
|
||||
updateMember: (
|
||||
orgId: string,
|
||||
memberId: string,
|
||||
body: {
|
||||
role?: import("@agy/contracts").Role;
|
||||
status?: import("@agy/contracts").MembershipStatus;
|
||||
},
|
||||
) =>
|
||||
apiRequest<import("@agy/contracts").Member>(
|
||||
`/api/organizations/${orgId}/members/${memberId}`,
|
||||
{ method: "PATCH", body },
|
||||
),
|
||||
removeMember: (orgId: string, memberId: string) =>
|
||||
apiRequest<void>(`/api/organizations/${orgId}/members/${memberId}`, {
|
||||
method: "DELETE",
|
||||
}),
|
||||
listAudit: (orgId: string) =>
|
||||
apiRequest<import("@agy/contracts").AuditEvent[]>(`/api/organizations/${orgId}/audit`),
|
||||
|
||||
// --- projects -------------------------------------------------------------
|
||||
listProjects: (orgId: string) =>
|
||||
apiRequest<import("@agy/contracts").Project[]>(`/api/organizations/${orgId}/projects`),
|
||||
createProject: (orgId: string, body: { name: string; description?: string }) =>
|
||||
apiRequest<import("@agy/contracts").Project>(`/api/organizations/${orgId}/projects`, {
|
||||
method: "POST",
|
||||
body,
|
||||
}),
|
||||
getProject: (orgId: string, projectId: string) =>
|
||||
apiRequest<import("@agy/contracts").Project>(
|
||||
`/api/organizations/${orgId}/projects/${projectId}`,
|
||||
),
|
||||
|
||||
// --- repository -----------------------------------------------------------
|
||||
ensureRepo: (orgId: string, projectId: string) =>
|
||||
apiRequest<import("@agy/contracts").Repository>(
|
||||
`/api/organizations/${orgId}/projects/${projectId}/repo/ensure`,
|
||||
{ method: "POST" },
|
||||
),
|
||||
getRepo: (orgId: string, projectId: string) =>
|
||||
apiRequest<import("@agy/contracts").Repository>(
|
||||
`/api/organizations/${orgId}/projects/${projectId}/repo`,
|
||||
),
|
||||
getRepoStatus: (orgId: string, projectId: string) =>
|
||||
apiRequest<import("@agy/contracts").RepoStatus>(
|
||||
`/api/organizations/${orgId}/projects/${projectId}/repo/status`,
|
||||
),
|
||||
|
||||
// --- preview domains ------------------------------------------------------
|
||||
listPreviewDomains: (orgId: string, projectId: string) =>
|
||||
apiRequest<import("@agy/contracts").PreviewDomain[]>(
|
||||
`/api/organizations/${orgId}/projects/${projectId}/preview/domains`,
|
||||
),
|
||||
createPreviewDomain: (
|
||||
orgId: string,
|
||||
projectId: string,
|
||||
body: { subdomain: string; port?: number },
|
||||
) =>
|
||||
apiRequest<import("@agy/contracts").PreviewDomain>(
|
||||
`/api/organizations/${orgId}/projects/${projectId}/preview/domains`,
|
||||
{ method: "POST", body },
|
||||
),
|
||||
deletePreviewDomain: (orgId: string, projectId: string, domainId: string) =>
|
||||
apiRequest<void>(
|
||||
`/api/organizations/${orgId}/projects/${projectId}/preview/domains/${domainId}`,
|
||||
{ method: "DELETE" },
|
||||
),
|
||||
|
||||
// --- workforce ------------------------------------------------------------
|
||||
listAgentTemplates: (orgId: string) =>
|
||||
apiRequest<import("@agy/contracts").AgentTemplate[]>(
|
||||
`/api/organizations/${orgId}/agent-templates`,
|
||||
),
|
||||
listRoutingChoices: (orgId: string) =>
|
||||
apiRequest<import("@agy/contracts").RoutingChoice[]>(
|
||||
`/api/organizations/${orgId}/routing-choices`,
|
||||
),
|
||||
listEmployees: (orgId: string) =>
|
||||
apiRequest<import("@agy/contracts").Employee[]>(`/api/organizations/${orgId}/employees`),
|
||||
createEmployee: (
|
||||
orgId: string,
|
||||
body: {
|
||||
name: string;
|
||||
role_key: import("@agy/contracts").RoleKey;
|
||||
template_id?: string | null;
|
||||
project_id?: string | null;
|
||||
routing_policy_id?: string | null;
|
||||
},
|
||||
) =>
|
||||
apiRequest<import("@agy/contracts").Employee>(`/api/organizations/${orgId}/employees`, {
|
||||
method: "POST",
|
||||
body,
|
||||
}),
|
||||
getEmployee: (orgId: string, employeeId: string) =>
|
||||
apiRequest<import("@agy/contracts").EmployeeDetail>(
|
||||
`/api/organizations/${orgId}/employees/${employeeId}`,
|
||||
),
|
||||
updateEmployee: (
|
||||
orgId: string,
|
||||
employeeId: string,
|
||||
body: {
|
||||
name?: string;
|
||||
status?: import("@agy/contracts").EmployeeStatus;
|
||||
project_id?: string | null;
|
||||
routing_policy_id?: string | null;
|
||||
},
|
||||
) =>
|
||||
apiRequest<import("@agy/contracts").Employee>(
|
||||
`/api/organizations/${orgId}/employees/${employeeId}`,
|
||||
{ method: "PATCH", body },
|
||||
),
|
||||
addPromptVersion: (orgId: string, employeeId: string, content: string) =>
|
||||
apiRequest<import("@agy/contracts").PromptVersion>(
|
||||
`/api/organizations/${orgId}/employees/${employeeId}/prompt-versions`,
|
||||
{ method: "POST", body: { content } },
|
||||
),
|
||||
|
||||
// --- manager chat ---------------------------------------------------------
|
||||
listThreads: (orgId: string) =>
|
||||
apiRequest<import("@agy/contracts").Thread[]>(`/api/organizations/${orgId}/threads`),
|
||||
createThread: (orgId: string, body: { title?: string; project_id?: string | null }) =>
|
||||
apiRequest<import("@agy/contracts").Thread>(`/api/organizations/${orgId}/threads`, {
|
||||
method: "POST",
|
||||
body,
|
||||
}),
|
||||
getThread: (orgId: string, threadId: string) =>
|
||||
apiRequest<import("@agy/contracts").ThreadDetail>(
|
||||
`/api/organizations/${orgId}/threads/${threadId}`,
|
||||
),
|
||||
postMessage: (orgId: string, threadId: string, body: { content: string; employee_id?: string | null }) =>
|
||||
apiRequest<import("@agy/contracts").PostMessageResponse>(
|
||||
`/api/organizations/${orgId}/threads/${threadId}/messages`,
|
||||
{ method: "POST", body },
|
||||
),
|
||||
|
||||
// --- tasks & runs ---------------------------------------------------------
|
||||
listTasks: (orgId: string, status?: string) =>
|
||||
apiRequest<import("@agy/contracts").Task[]>(
|
||||
`/api/organizations/${orgId}/tasks${status ? `?status=${status}` : ""}`,
|
||||
),
|
||||
createTask: (
|
||||
orgId: string,
|
||||
body: {
|
||||
title: string;
|
||||
description?: string;
|
||||
project_id?: string | null;
|
||||
thread_id?: string | null;
|
||||
assigned_employee_id?: string | null;
|
||||
},
|
||||
) =>
|
||||
apiRequest<import("@agy/contracts").Task>(`/api/organizations/${orgId}/tasks`, {
|
||||
method: "POST",
|
||||
body,
|
||||
}),
|
||||
getTask: (orgId: string, taskId: string) =>
|
||||
apiRequest<import("@agy/contracts").TaskDetail>(
|
||||
`/api/organizations/${orgId}/tasks/${taskId}`,
|
||||
),
|
||||
startRun: (orgId: string, taskId: string, employeeId?: string | null) =>
|
||||
apiRequest<import("@agy/contracts").RunDetail>(
|
||||
`/api/organizations/${orgId}/tasks/${taskId}/runs`,
|
||||
{ method: "POST", body: { employee_id: employeeId ?? null } },
|
||||
),
|
||||
getRun: (orgId: string, runId: string) =>
|
||||
apiRequest<import("@agy/contracts").RunDetail>(`/api/organizations/${orgId}/runs/${runId}`),
|
||||
cancelRun: (orgId: string, runId: string) =>
|
||||
apiRequest<import("@agy/contracts").RunDetail>(
|
||||
`/api/organizations/${orgId}/runs/${runId}/cancel`,
|
||||
{ method: "POST" },
|
||||
),
|
||||
listPendingApprovals: (orgId: string) =>
|
||||
apiRequest<import("@agy/contracts").ToolCall[]>(
|
||||
`/api/organizations/${orgId}/approvals/pending`,
|
||||
),
|
||||
|
||||
// --- billing --------------------------------------------------------------
|
||||
billingSummary: (orgId: string) =>
|
||||
apiRequest<import("@agy/contracts").BillingSummary>(
|
||||
`/api/organizations/${orgId}/billing/summary`,
|
||||
),
|
||||
billingUsageEvents: (orgId: string, limit = 100) =>
|
||||
apiRequest<import("@agy/contracts").UsageEvent[]>(
|
||||
`/api/organizations/${orgId}/billing/usage-events?limit=${limit}`,
|
||||
),
|
||||
billingInvoices: (orgId: string) =>
|
||||
apiRequest<import("@agy/contracts").Invoice[]>(
|
||||
`/api/organizations/${orgId}/billing/invoices`,
|
||||
),
|
||||
billingPayments: (orgId: string) =>
|
||||
apiRequest<import("@agy/contracts").Payment[]>(
|
||||
`/api/organizations/${orgId}/billing/payments`,
|
||||
),
|
||||
billingAddCredit: (orgId: string, amount: number) =>
|
||||
apiRequest<import("@agy/contracts").Wallet>(
|
||||
`/api/organizations/${orgId}/billing/add-credit`,
|
||||
{ method: "POST", body: { amount } },
|
||||
),
|
||||
billingCheckout: (orgId: string, amount: number) =>
|
||||
apiRequest<{ payment_id: string; checkout_url: string; amount: number }>(
|
||||
`/api/organizations/${orgId}/billing/checkout`,
|
||||
{ method: "POST", body: { amount } },
|
||||
),
|
||||
|
||||
// --- tools & approvals ----------------------------------------------------
|
||||
listTools: (orgId: string) =>
|
||||
apiRequest<import("@agy/contracts").ToolSpec[]>(`/api/organizations/${orgId}/tools`),
|
||||
setToolPermission: (orgId: string, employeeId: string, toolKey: string, allowed: boolean) =>
|
||||
apiRequest<import("@agy/contracts").ToolPermission>(
|
||||
`/api/organizations/${orgId}/employees/${employeeId}/tools/${encodeURIComponent(toolKey)}`,
|
||||
{ method: "PUT", body: { allowed } },
|
||||
),
|
||||
decideApproval: (orgId: string, toolCallId: string, approve: boolean, note = "") =>
|
||||
apiRequest<import("@agy/contracts").ToolCall>(
|
||||
`/api/organizations/${orgId}/approvals/${toolCallId}/decide`,
|
||||
{ method: "POST", body: { approve, note } },
|
||||
),
|
||||
|
||||
// --- sandbox --------------------------------------------------------------
|
||||
// Every call here is metered into the usage ledger server-side. The client
|
||||
// never sends a timeout or an output cap: limits come from policy only.
|
||||
sandboxTemplates: (orgId: string) =>
|
||||
apiRequest<import("@agy/contracts").SandboxTemplate[]>(
|
||||
`/api/organizations/${orgId}/sandbox/templates`,
|
||||
),
|
||||
sandboxLimits: (orgId: string, projectId?: string) =>
|
||||
apiRequest<import("@agy/contracts").SandboxResourceLimit>(
|
||||
`/api/organizations/${orgId}/sandbox/limits${
|
||||
projectId ? `?project_id=${encodeURIComponent(projectId)}` : ""
|
||||
}`,
|
||||
),
|
||||
sandboxWorkspaces: (orgId: string, projectId: string) =>
|
||||
apiRequest<import("@agy/contracts").SandboxWorkspace[]>(
|
||||
`/api/organizations/${orgId}/sandbox/workspaces?project_id=${encodeURIComponent(projectId)}`,
|
||||
),
|
||||
sandboxCreateWorkspace: (orgId: string, body: { project_id: string; template_id: string }) =>
|
||||
apiRequest<import("@agy/contracts").SandboxWorkspace>(
|
||||
`/api/organizations/${orgId}/sandbox/workspaces`,
|
||||
{ method: "POST", body },
|
||||
),
|
||||
sandboxDestroyWorkspace: (orgId: string, workspaceId: string) =>
|
||||
apiRequest<import("@agy/contracts").SandboxWorkspace>(
|
||||
`/api/organizations/${orgId}/sandbox/workspaces/${workspaceId}`,
|
||||
{ method: "DELETE" },
|
||||
),
|
||||
sandboxStartSession: (orgId: string, workspaceId: string) =>
|
||||
apiRequest<import("@agy/contracts").SandboxSession>(
|
||||
`/api/organizations/${orgId}/sandbox/sessions`,
|
||||
{ method: "POST", body: { workspace_id: workspaceId } },
|
||||
),
|
||||
sandboxStopSession: (orgId: string, sessionId: string) =>
|
||||
apiRequest<import("@agy/contracts").SandboxSession>(
|
||||
`/api/organizations/${orgId}/sandbox/sessions/${sessionId}/stop`,
|
||||
{ method: "POST" },
|
||||
),
|
||||
sandboxRunCommand: (orgId: string, sessionId: string, command: string) =>
|
||||
apiRequest<import("@agy/contracts").SandboxCommandRun>(
|
||||
`/api/organizations/${orgId}/sandbox/sessions/${sessionId}/commands`,
|
||||
{ method: "POST", body: { command } },
|
||||
),
|
||||
sandboxCommands: (orgId: string, sessionId: string) =>
|
||||
apiRequest<import("@agy/contracts").SandboxCommandRun[]>(
|
||||
`/api/organizations/${orgId}/sandbox/sessions/${sessionId}/commands`,
|
||||
),
|
||||
sandboxReadFile: (orgId: string, sessionId: string, path: string) =>
|
||||
apiRequest<import("@agy/contracts").SandboxFileRead>(
|
||||
`/api/organizations/${orgId}/sandbox/sessions/${sessionId}/files/read`,
|
||||
{ method: "POST", body: { path } },
|
||||
),
|
||||
sandboxWriteFile: (orgId: string, sessionId: string, path: string, content: string) =>
|
||||
apiRequest<void>(
|
||||
`/api/organizations/${orgId}/sandbox/sessions/${sessionId}/files/write`,
|
||||
{ method: "POST", body: { path, content } },
|
||||
),
|
||||
sandboxSnapshot: (orgId: string, sessionId: string, name: string) =>
|
||||
apiRequest<import("@agy/contracts").SandboxSnapshot>(
|
||||
`/api/organizations/${orgId}/sandbox/sessions/${sessionId}/snapshots`,
|
||||
{ method: "POST", body: { name } },
|
||||
),
|
||||
sandboxPreviewUrl: (orgId: string, sessionId: string, port: number) =>
|
||||
apiRequest<import("@agy/contracts").SandboxPreviewUrl>(
|
||||
`/api/organizations/${orgId}/sandbox/sessions/${sessionId}/preview-url?port=${port}`,
|
||||
{ method: "POST" },
|
||||
),
|
||||
|
||||
// --- platform owner -------------------------------------------------------
|
||||
adminProviders: () =>
|
||||
apiRequest<import("@agy/contracts").AdminProvider[]>("/api/admin/providers"),
|
||||
adminCreateProvider: (body: {
|
||||
name: string;
|
||||
type: import("@agy/contracts").ProviderType;
|
||||
base_url?: string;
|
||||
}) =>
|
||||
apiRequest<import("@agy/contracts").AdminProvider>("/api/admin/providers", {
|
||||
method: "POST",
|
||||
body,
|
||||
}),
|
||||
adminCredentials: (providerId: string) =>
|
||||
apiRequest<import("@agy/contracts").AdminCredential[]>(
|
||||
`/api/admin/providers/${providerId}/credentials`,
|
||||
),
|
||||
adminCreateCredential: (providerId: string, body: { name: string; api_key: string }) =>
|
||||
apiRequest<import("@agy/contracts").AdminCredential>(
|
||||
`/api/admin/providers/${providerId}/credentials`,
|
||||
{ method: "POST", body },
|
||||
),
|
||||
adminModels: () => apiRequest<import("@agy/contracts").AdminModel[]>("/api/admin/models"),
|
||||
adminCreateModel: (body: {
|
||||
provider_id: string;
|
||||
internal_name: string;
|
||||
display_name_for_admin: string;
|
||||
capabilities: import("@agy/contracts").Capability[];
|
||||
input_price_per_1m?: number;
|
||||
output_price_per_1m?: number;
|
||||
}) =>
|
||||
apiRequest<import("@agy/contracts").AdminModel>("/api/admin/models", {
|
||||
method: "POST",
|
||||
body,
|
||||
}),
|
||||
adminRoutingPolicies: () =>
|
||||
apiRequest<import("@agy/contracts").AdminRoutingPolicy[]>("/api/admin/routing-policies"),
|
||||
adminCreateRoutingPolicy: (body: {
|
||||
name: string;
|
||||
mode: import("@agy/contracts").RoutingMode;
|
||||
description?: string;
|
||||
is_default?: boolean;
|
||||
}) =>
|
||||
apiRequest<import("@agy/contracts").AdminRoutingPolicy>("/api/admin/routing-policies", {
|
||||
method: "POST",
|
||||
body,
|
||||
}),
|
||||
adminRateCards: () =>
|
||||
apiRequest<import("@agy/contracts").AdminRateCard[]>("/api/admin/rate-cards"),
|
||||
adminUpsertRateCard: (body: {
|
||||
unit: import("@agy/contracts").RateUnit;
|
||||
name?: string;
|
||||
customer_price: number;
|
||||
internal_cost_estimate?: number;
|
||||
is_active?: boolean;
|
||||
}) =>
|
||||
apiRequest<import("@agy/contracts").AdminRateCard>("/api/admin/rate-cards", {
|
||||
method: "PUT",
|
||||
body,
|
||||
}),
|
||||
adminOrganizations: () =>
|
||||
apiRequest<import("@agy/contracts").AdminOrganization[]>("/api/admin/organizations"),
|
||||
adminUsage: () => apiRequest<import("@agy/contracts").AdminUsageRow[]>("/api/admin/usage"),
|
||||
adminValidateModelChoice: (mode: import("@agy/contracts").RoutingMode) =>
|
||||
apiRequest<import("@agy/contracts").ModelChoice>(
|
||||
`/api/admin/validate-model-choice?mode=${encodeURIComponent(mode)}`,
|
||||
{ method: "POST" },
|
||||
),
|
||||
|
||||
// --- error console --------------------------------------------------------
|
||||
// The console exists so a failure that already happened can be diagnosed
|
||||
// without reproducing it. Grouped by fingerprint, so a bug that fired a
|
||||
// thousand times is one line.
|
||||
adminErrorSummary: () =>
|
||||
apiRequest<import("@agy/contracts").ErrorSummary>("/api/admin/errors/summary"),
|
||||
adminErrorGroups: (query: import("@agy/contracts").ErrorQuery = {}) =>
|
||||
apiRequest<import("@agy/contracts").ErrorGroup[]>(
|
||||
`/api/admin/errors/groups${errorQueryString(query)}`,
|
||||
),
|
||||
adminErrors: (query: import("@agy/contracts").ErrorQuery = {}) =>
|
||||
apiRequest<import("@agy/contracts").ErrorListResponse>(
|
||||
`/api/admin/errors${errorQueryString(query)}`,
|
||||
),
|
||||
adminError: (errorId: string) =>
|
||||
apiRequest<import("@agy/contracts").ErrorEvent>(
|
||||
`/api/admin/errors/${encodeURIComponent(errorId)}`,
|
||||
),
|
||||
adminResolveError: (errorId: string, note = "") =>
|
||||
apiRequest<import("@agy/contracts").ResolveErrorResponse>(
|
||||
`/api/admin/errors/${encodeURIComponent(errorId)}/resolve`,
|
||||
{ method: "POST", body: { note } },
|
||||
),
|
||||
adminResolveErrorGroup: (fingerprint: string, note = "") =>
|
||||
apiRequest<import("@agy/contracts").ResolveErrorResponse>(
|
||||
`/api/admin/errors/groups/${encodeURIComponent(fingerprint)}/resolve`,
|
||||
{ method: "POST", body: { note } },
|
||||
),
|
||||
adminReopenErrorGroup: (fingerprint: string) =>
|
||||
apiRequest<import("@agy/contracts").ResolveErrorResponse>(
|
||||
`/api/admin/errors/groups/${encodeURIComponent(fingerprint)}/reopen`,
|
||||
{ method: "POST" },
|
||||
),
|
||||
};
|
||||
|
||||
/** Serialise the console's filters, dropping anything unset. */
|
||||
function errorQueryString(query: import("@agy/contracts").ErrorQuery): string {
|
||||
const params = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(query)) {
|
||||
if (value !== undefined && value !== null && value !== "") {
|
||||
params.set(key, String(value));
|
||||
}
|
||||
}
|
||||
const encoded = params.toString();
|
||||
return encoded ? `?${encoded}` : "";
|
||||
}
|
||||
Reference in New Issue
Block a user