نسخهٔ وب مستقل — پلتفرم نیروی کار هوش مصنوعی

مخزن اختصاصی برنامهٔ وب؛ محتوای پوشهٔ web در ریشهٔ مخزن قرار گرفت.
تاریخچهٔ پروژهٔ ربات (agy-bot) در این مخزن وجود ندارد و هیچ وابستگی runtime به آن نیست.
This commit is contained in:
mjalmoudy94
2026-10-13 00:39:28 +03:30
commit 717e53a7ce
185 changed files with 39910 additions and 0 deletions
+533
View File
@@ -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}` : "";
}