1252 lines
29 KiB
TypeScript
1252 lines
29 KiB
TypeScript
/**
|
|
* Shared API contracts.
|
|
*
|
|
* These types mirror the FastAPI response schemas. They are intentionally
|
|
* hand-written and small: the backend is the source of truth, and a drift here
|
|
* surfaces immediately as a TypeScript error at the call site.
|
|
*/
|
|
|
|
export type Role = "owner" | "admin" | "manager" | "member" | "viewer";
|
|
|
|
export type MembershipStatus = "active" | "invited" | "suspended";
|
|
|
|
/**
|
|
* Platform-wide role — a different axis from the in-organization `Role`.
|
|
*
|
|
* - `owner` — the single owner of the whole system. Exactly one exists; only
|
|
* they may enter the admin console, create or demote managers, and move the
|
|
* owner seat.
|
|
* - `manager` — an account the owner creates. May build its own organizations
|
|
* and join other managers' organizations by invitation.
|
|
* - `user` — a plain account. Cannot create organizations; works only inside
|
|
* organizations it was invited to.
|
|
*/
|
|
export type PlatformRole = "owner" | "manager" | "user";
|
|
|
|
/** Roles that may create an organization. */
|
|
export function canCreateOrganization(role: PlatformRole | undefined | null): boolean {
|
|
return role === "owner" || role === "manager";
|
|
}
|
|
|
|
export const PLATFORM_ROLE_LABELS: Record<PlatformRole, string> = {
|
|
owner: "مالک سامانه",
|
|
manager: "مدیر",
|
|
user: "کاربر",
|
|
};
|
|
|
|
/**
|
|
* Provider kinds the AI Control Plane can talk to. agy (Google Antigravity)
|
|
* is the only one — the platform no longer has a provider picker.
|
|
*/
|
|
export type ProviderType = "agy";
|
|
|
|
/** agy reasoning efforts, cheapest first. Mirrors the backend's list. */
|
|
export type ReasoningEffort = "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
|
|
export const REASONING_EFFORTS: ReasoningEffort[] = [
|
|
"minimal",
|
|
"low",
|
|
"medium",
|
|
"high",
|
|
"xhigh",
|
|
];
|
|
|
|
const EFFORT_LABELS: Record<ReasoningEffort, string> = {
|
|
minimal: "حداقل",
|
|
low: "کم",
|
|
medium: "متوسط",
|
|
high: "زیاد",
|
|
xhigh: "بسیار زیاد",
|
|
};
|
|
|
|
/**
|
|
* Reasoning efforts a model may be configured with, or an empty list when the
|
|
* model cannot think — the backend rejects a thinking config everywhere else.
|
|
* Same marker rule as the agy adapter: thinking-capable ids contain one of
|
|
* `thinking` / `gemini-3` / `gemini-2.5-pro` / `gemini-2.5-flash`, image
|
|
* and claude models are excluded from explicit effort selection.
|
|
*/
|
|
export function effortsForModel(modelId: string): ReasoningEffort[] {
|
|
const lower = modelId.toLowerCase();
|
|
if (lower.includes("image")) return [];
|
|
if (lower.includes("claude")) return [];
|
|
if (lower.includes("gpt")) return ["medium"];
|
|
if (lower.includes("gemini-3.1-pro")) return ["low", "high"];
|
|
const canThink =
|
|
lower.includes("thinking") ||
|
|
lower.includes("gemini-3") ||
|
|
lower.includes("gemini-2.5-pro") ||
|
|
lower.includes("gemini-2.5-flash");
|
|
return canThink ? [...REASONING_EFFORTS] : [];
|
|
}
|
|
|
|
export function effortLabel(effort: string): string {
|
|
return EFFORT_LABELS[effort as ReasoningEffort] ?? effort;
|
|
}
|
|
|
|
/** A live model reported by an agy account's catalogue. */
|
|
export interface AgyAccountModel {
|
|
id: string;
|
|
display_name: string;
|
|
supports_thinking: boolean;
|
|
supported_efforts: ReasoningEffort[];
|
|
default_effort?: ReasoningEffort | null;
|
|
supports_images?: boolean;
|
|
}
|
|
|
|
/** Rotation-pool lifecycle of one agy account. */
|
|
export type AgyAccountStatus = "active" | "cooldown" | "error";
|
|
|
|
/**
|
|
* One agy account (Google identity). Deliberately credential-free: the console
|
|
* manages accounts, never tokens.
|
|
*/
|
|
export interface AgyAccount {
|
|
id: string;
|
|
provider_id: string;
|
|
name: string;
|
|
email: string;
|
|
project_id: string;
|
|
status: AgyAccountStatus;
|
|
cooldown_until: string | null;
|
|
is_active: boolean;
|
|
last_error: string;
|
|
last_used_at: string | null;
|
|
created_at: string;
|
|
}
|
|
|
|
/** What a model can do. Routing picks models by required capability. */
|
|
export type Capability = "text" | "vision" | "tools" | "reasoning" | "long_context" | "code";
|
|
|
|
/** Routing intents. Customers never see these names — only the platform owner does. */
|
|
export type RoutingMode = "fast" | "cheap" | "balanced" | "reasoning" | "vision" | "coding";
|
|
|
|
export type TaskStatus =
|
|
| "new"
|
|
| "triaged"
|
|
| "waiting_approval"
|
|
| "running"
|
|
| "done"
|
|
| "failed"
|
|
| "cancelled";
|
|
|
|
export type RunStatus =
|
|
| "queued"
|
|
| "planning"
|
|
| "waiting_approval"
|
|
| "running"
|
|
| "tool_calling"
|
|
| "sandbox_running"
|
|
| "completed"
|
|
| "failed"
|
|
| "cancelled"
|
|
| "timed_out";
|
|
|
|
export type RoleKey =
|
|
| "manager"
|
|
| "planner"
|
|
| "developer"
|
|
| "reviewer"
|
|
| "tester"
|
|
| "researcher"
|
|
| "content_creator"
|
|
| "support";
|
|
|
|
export type EmployeeStatus = "active" | "paused" | "archived";
|
|
|
|
export type PreviewDomainStatus = "active" | "disabled" | "failed";
|
|
|
|
export type RateUnit =
|
|
| "model_input_tokens"
|
|
| "model_output_tokens"
|
|
| "sandbox_cpu_second"
|
|
| "sandbox_memory_gb_second"
|
|
| "storage_gb_day"
|
|
| "tool_call"
|
|
| "premium_employee_run";
|
|
|
|
export interface ApiErrorBody {
|
|
error: {
|
|
code: string;
|
|
message: string;
|
|
details?: unknown;
|
|
/**
|
|
* Correlates this response with the row the API wrote to its error console
|
|
* (and with the `X-Request-ID` response header). Surfacing it lets a user
|
|
* quote one string that points at exactly one server-side record.
|
|
*/
|
|
request_id?: string;
|
|
};
|
|
}
|
|
|
|
export interface User {
|
|
id: string;
|
|
email: string;
|
|
name: string;
|
|
avatar_url?: string | null;
|
|
/** Platform-wide role. Exactly one account in the system is `owner`. */
|
|
platform_role: PlatformRole;
|
|
/** True for the manager tier (may create organizations). */
|
|
is_manager: boolean;
|
|
is_platform_owner: boolean;
|
|
}
|
|
|
|
export interface Organization {
|
|
id: string;
|
|
name: string;
|
|
slug: string;
|
|
owner_user_id: string;
|
|
}
|
|
|
|
export interface OrganizationWithRole extends Organization {
|
|
role: Role;
|
|
}
|
|
|
|
export interface Member {
|
|
id: string;
|
|
user_id: string;
|
|
email: string;
|
|
name: string;
|
|
role: Role;
|
|
status: MembershipStatus;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface Invite {
|
|
id: string;
|
|
email: string;
|
|
role: Role;
|
|
expires_at: string;
|
|
invite_token: string;
|
|
invite_url: string;
|
|
}
|
|
|
|
/**
|
|
* What the accept-invite screen shows before the invitee commits.
|
|
*
|
|
* Two kinds share one endpoint: a `platform` invite mints a new manager account
|
|
* (v2 D12) and has no organization yet, while an `organization` invite joins an
|
|
* existing one. The screen shows a different title for each — "ساخت حساب مدیر"
|
|
* versus joining a team — so the caller has to be able to tell them apart.
|
|
*/
|
|
export interface InvitePreview {
|
|
kind: "platform" | "organization";
|
|
organization_id: string;
|
|
organization_name: string;
|
|
email: string;
|
|
role: Role;
|
|
expires_at: string;
|
|
/** True when the signed-in user is already a member of that organization. */
|
|
already_member: boolean;
|
|
/**
|
|
* True when an account exists for the invited email. The invitee then logs
|
|
* in first; setting a password anonymously is only for brand-new accounts.
|
|
*/
|
|
has_account: boolean;
|
|
/** For a platform invite: the role the redeemed account will receive. */
|
|
platform_role: PlatformRole;
|
|
}
|
|
|
|
export interface AcceptInviteResult {
|
|
kind: "platform" | "organization";
|
|
organization_id: string;
|
|
organization_name: string;
|
|
role: Role;
|
|
}
|
|
|
|
export interface TokenResponse {
|
|
access_token: string;
|
|
token_type: string;
|
|
user: User;
|
|
}
|
|
|
|
export interface MeResponse {
|
|
user: User;
|
|
organizations: OrganizationWithRole[];
|
|
/**
|
|
* True for a manager who belongs to no organization yet — they arrived on the
|
|
* platform owner's invitation link and have built nothing, so the only useful
|
|
* thing the UI can do is open the creation wizard. The owner is excluded.
|
|
*/
|
|
needs_onboarding: boolean;
|
|
}
|
|
|
|
/** An invitation the platform owner issued, as it appears in the console. */
|
|
export interface PlatformInvite {
|
|
id: string;
|
|
email: string | null;
|
|
platform_role: string;
|
|
note: string;
|
|
expires_at: string;
|
|
created_at: string;
|
|
created_by_email: string;
|
|
}
|
|
|
|
export interface IssuedPlatformInvite {
|
|
invite: PlatformInvite;
|
|
/** The raw URL. Shown exactly once, at creation. */
|
|
invite_url: string;
|
|
}
|
|
|
|
export interface Project {
|
|
id: string;
|
|
organization_id: string;
|
|
name: string;
|
|
slug: string;
|
|
description: string;
|
|
default_branch: string;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface RepoStatus {
|
|
exists: boolean;
|
|
private: boolean;
|
|
default_branch: string;
|
|
branches: string[];
|
|
open_pull_requests: number;
|
|
}
|
|
|
|
export interface Repository {
|
|
id: string;
|
|
provider: string;
|
|
owner: string;
|
|
repo_name: string;
|
|
clone_url: string;
|
|
web_url: string;
|
|
default_branch: string;
|
|
private: boolean;
|
|
}
|
|
|
|
export interface AdminModel {
|
|
id: string;
|
|
provider_id: string;
|
|
internal_name: string;
|
|
display_name_for_admin: string;
|
|
capabilities: string[];
|
|
input_price_per_1m: number;
|
|
output_price_per_1m: number;
|
|
/** agy reasoning effort ("" = provider default). Owner-only metadata. */
|
|
reasoning_effort: string;
|
|
is_active: boolean;
|
|
}
|
|
|
|
export interface AdminRoutingPolicy {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
mode: string;
|
|
is_default: boolean;
|
|
is_active: boolean;
|
|
}
|
|
|
|
export interface AdminRateCard {
|
|
id: string;
|
|
name: string;
|
|
unit: RateUnit;
|
|
customer_price: number;
|
|
internal_cost_estimate: number;
|
|
is_active: boolean;
|
|
}
|
|
|
|
export interface AdminOrganization {
|
|
id: string;
|
|
name: string;
|
|
slug: string;
|
|
member_count: number;
|
|
created_at: string;
|
|
}
|
|
|
|
/** Result of a routing dry-run: which model a mode would pick, and why. */
|
|
export interface ModelChoice {
|
|
mode: string;
|
|
model_id: string;
|
|
internal_name: string;
|
|
capabilities: string[];
|
|
estimated_input_price_per_1m: number;
|
|
estimated_output_price_per_1m: number;
|
|
}
|
|
|
|
export interface AdminUsageRow {
|
|
organization_id: string;
|
|
quantity: number;
|
|
total_price: number;
|
|
}
|
|
|
|
/* Account administration (platform owner only) */
|
|
|
|
/** One row in the owner's account list. */
|
|
export interface AdminUser {
|
|
id: string;
|
|
email: string;
|
|
name: string;
|
|
platform_role: PlatformRole;
|
|
is_manager: boolean;
|
|
is_platform_owner: boolean;
|
|
is_active: boolean;
|
|
created_at: string;
|
|
/** How many organizations this account owns — what moves if it is demoted. */
|
|
owned_organizations: number;
|
|
}
|
|
|
|
/**
|
|
* A created account (or a password reset). `temp_password` is shown exactly
|
|
* once and never retrievable again.
|
|
*/
|
|
export interface AdminUserCreated {
|
|
user: AdminUser;
|
|
temp_password: string;
|
|
}
|
|
|
|
export interface CreateUserRequest {
|
|
email: string;
|
|
name?: string;
|
|
/** `owner` is refused: the seat moves only through transfer-ownership. */
|
|
platform_role: "manager" | "user";
|
|
}
|
|
|
|
export interface UpdateUserRequest {
|
|
platform_role?: "manager" | "user";
|
|
is_active?: boolean;
|
|
}
|
|
|
|
export interface OrganizationTransfer {
|
|
organization_id: string;
|
|
organization_name: string;
|
|
}
|
|
|
|
export interface DeleteUserResult {
|
|
deleted_user_id: string;
|
|
/** Organizations that moved to the platform owner. */
|
|
transferred_organizations: OrganizationTransfer[];
|
|
}
|
|
|
|
export interface PreviewDomain {
|
|
id: string;
|
|
host: string;
|
|
port: number;
|
|
status: PreviewDomainStatus;
|
|
protected: boolean;
|
|
url: string;
|
|
}
|
|
|
|
export interface Employee {
|
|
id: string;
|
|
organization_id: string;
|
|
project_id?: string | null;
|
|
template_id?: string | null;
|
|
name: string;
|
|
role_key: RoleKey;
|
|
status: EmployeeStatus;
|
|
routing_policy_id?: string | null;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface Task {
|
|
id: string;
|
|
organization_id: string;
|
|
project_id?: string | null;
|
|
thread_id?: string | null;
|
|
title: string;
|
|
description: string;
|
|
status: TaskStatus;
|
|
assigned_employee_id?: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface Run {
|
|
id: string;
|
|
task_id: string;
|
|
employee_id?: string | null;
|
|
status: RunStatus;
|
|
error?: string | null;
|
|
started_at?: string | null;
|
|
finished_at?: string | null;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface Thread {
|
|
id: string;
|
|
organization_id: string;
|
|
project_id?: string | null;
|
|
title: string;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface Message {
|
|
id: string;
|
|
thread_id: string;
|
|
role: "user" | "assistant" | "system";
|
|
content: string;
|
|
employee_id?: string | null;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface ThreadDetail extends Thread {
|
|
messages: Message[];
|
|
}
|
|
|
|
export interface RunStep {
|
|
id: string;
|
|
index: number;
|
|
kind: string;
|
|
title: string;
|
|
payload_json: Record<string, unknown>;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface ToolCall {
|
|
id: string;
|
|
tool_key: string;
|
|
status: string;
|
|
requires_approval: boolean;
|
|
arguments_json: Record<string, unknown>;
|
|
result_json: Record<string, unknown>;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface ArtifactDetail extends Artifact {
|
|
content_ref: string;
|
|
run_id?: string | null;
|
|
}
|
|
|
|
export interface RunDetail extends Run {
|
|
steps: RunStep[];
|
|
tool_calls: ToolCall[];
|
|
artifacts: ArtifactDetail[];
|
|
}
|
|
|
|
export interface TaskDetail extends Task {
|
|
runs: Run[];
|
|
}
|
|
|
|
export interface AgentTemplate {
|
|
id: string;
|
|
name: string;
|
|
role_key: RoleKey;
|
|
description: string;
|
|
default_tools_json: string[];
|
|
}
|
|
|
|
/** A routing policy as a customer may see it — no mode, no model hints. */
|
|
export interface RoutingChoice {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
}
|
|
|
|
export interface PromptVersion {
|
|
id: string;
|
|
version: number;
|
|
content: string;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface ToolPermission {
|
|
tool_key: string;
|
|
allowed: boolean;
|
|
}
|
|
|
|
/** One entry in the tool catalog an employee can be granted. */
|
|
export interface ToolSpec {
|
|
key: string;
|
|
label: string;
|
|
description: string;
|
|
requires_approval: boolean;
|
|
destructive: boolean;
|
|
category: string;
|
|
}
|
|
|
|
export interface EmployeeDetail extends Employee {
|
|
prompt_versions: PromptVersion[];
|
|
tool_permissions: ToolPermission[];
|
|
open_task_count: number;
|
|
}
|
|
|
|
export interface PostMessageResponse {
|
|
user_message: Message;
|
|
assistant_message: Message;
|
|
}
|
|
|
|
export interface Artifact {
|
|
id: string;
|
|
name: string;
|
|
kind: string;
|
|
size_bytes: number;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface BillingSummary {
|
|
balance: number;
|
|
currency: string;
|
|
low_balance: boolean;
|
|
low_balance_threshold: number;
|
|
total_spend: number;
|
|
by_unit: { unit: RateUnit; quantity: number; total_price: number }[];
|
|
by_employee: Record<string, number>;
|
|
by_project: Record<string, number>;
|
|
}
|
|
|
|
export interface UsageEvent {
|
|
id: string;
|
|
unit: RateUnit;
|
|
quantity: number;
|
|
unit_price: number;
|
|
total_price: number;
|
|
project_id?: string | null;
|
|
employee_id?: string | null;
|
|
run_id?: string | null;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface Invoice {
|
|
id: string;
|
|
amount: number;
|
|
currency: string;
|
|
status: string;
|
|
period_start?: string | null;
|
|
period_end?: string | null;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface Payment {
|
|
id: string;
|
|
amount: number;
|
|
currency: string;
|
|
status: string;
|
|
provider: string;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface Wallet {
|
|
balance: number;
|
|
currency: string;
|
|
low_balance: boolean;
|
|
}
|
|
|
|
export interface AuditEvent {
|
|
id: string;
|
|
action: string;
|
|
actor_user_id?: string | null;
|
|
target_type: string;
|
|
target_id: string;
|
|
created_at: string;
|
|
}
|
|
|
|
// --- sandbox ----------------------------------------------------------------
|
|
|
|
export type NetworkPolicy = "deny_all" | "allowlist" | "open";
|
|
|
|
export interface SandboxTemplate {
|
|
id: string;
|
|
name: string;
|
|
provider: string;
|
|
image: string;
|
|
description: string;
|
|
default_cpu: number;
|
|
default_memory_mb: number;
|
|
default_disk_mb: number;
|
|
}
|
|
|
|
export interface SandboxWorkspace {
|
|
id: string;
|
|
project_id: string;
|
|
template_id: string;
|
|
provider: string;
|
|
status: string;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface SandboxSession {
|
|
id: string;
|
|
workspace_id: string;
|
|
status: string;
|
|
started_at: string;
|
|
last_active_at: string;
|
|
}
|
|
|
|
export interface SandboxCommandRun {
|
|
id: string;
|
|
command: string;
|
|
exit_code: number | null;
|
|
stdout: string;
|
|
stderr: string;
|
|
duration_ms: number;
|
|
truncated: boolean;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface SandboxFileRead {
|
|
path: string;
|
|
content: string;
|
|
}
|
|
|
|
export interface SandboxSnapshot {
|
|
id: string;
|
|
name: string;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface SandboxPreviewUrl {
|
|
port: number;
|
|
url: string;
|
|
}
|
|
|
|
/**
|
|
* Effective limits are resolved server-side (project -> organization ->
|
|
* platform) and clamped. The UI only ever displays them.
|
|
*/
|
|
export interface SandboxResourceLimit {
|
|
cpu: number;
|
|
memory_mb: number;
|
|
disk_mb: number;
|
|
timeout_seconds: number;
|
|
max_output_bytes: number;
|
|
network_policy: NetworkPolicy;
|
|
}
|
|
|
|
export interface HealthResponse {
|
|
status: string;
|
|
app: string;
|
|
env: string;
|
|
}
|
|
|
|
export interface ReadinessResponse {
|
|
status: string;
|
|
checks: {
|
|
database: string;
|
|
model_provider: string;
|
|
adapters: Record<string, string>;
|
|
};
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Error console (platform owner) */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
export type ErrorSeverity = "warning" | "error" | "critical";
|
|
export type ErrorSource = "http" | "worker" | "startup";
|
|
export type ErrorStatusFilter = "open" | "resolved";
|
|
|
|
/**
|
|
* One persisted failure. Deliberately wide: a record that lacks the request
|
|
* path or the exception class forces whoever is debugging to reproduce the bug
|
|
* just to learn what it was.
|
|
*/
|
|
export interface ErrorEvent {
|
|
id: string;
|
|
kind: string;
|
|
message: string;
|
|
stack: string;
|
|
severity: ErrorSeverity;
|
|
source: ErrorSource;
|
|
error_type: string;
|
|
fingerprint: string;
|
|
status_code: number | null;
|
|
method: string;
|
|
path: string;
|
|
organization_id: string | null;
|
|
user_id: string | null;
|
|
run_id: string | null;
|
|
request_id: string;
|
|
ip: string;
|
|
user_agent: string;
|
|
metadata_json: Record<string, unknown>;
|
|
resolved_at: string | null;
|
|
resolved_note: string;
|
|
created_at: string;
|
|
}
|
|
|
|
/** Every occurrence of one distinct bug, collapsed into a single row. */
|
|
export interface ErrorGroup {
|
|
fingerprint: string;
|
|
occurrences: number;
|
|
open_count: number;
|
|
first_seen: string;
|
|
last_seen: string;
|
|
kind: string;
|
|
error_type: string;
|
|
severity: ErrorSeverity;
|
|
source: ErrorSource;
|
|
path: string;
|
|
sample_message: string;
|
|
}
|
|
|
|
export interface ErrorSummary {
|
|
open_by_severity: Record<string, number>;
|
|
total_open: number;
|
|
}
|
|
|
|
export interface ErrorListResponse {
|
|
items: ErrorEvent[];
|
|
total: number;
|
|
limit: number;
|
|
offset: number;
|
|
}
|
|
|
|
export interface ResolveErrorResponse {
|
|
fingerprint: string;
|
|
resolved: number;
|
|
}
|
|
|
|
export interface ErrorQuery {
|
|
status?: ErrorStatusFilter;
|
|
severity?: ErrorSeverity;
|
|
source?: ErrorSource;
|
|
organization_id?: string;
|
|
search?: string;
|
|
limit?: number;
|
|
offset?: number;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Realtime (WebSocket)
|
|
//
|
|
// The live feed is the same product surface as the REST API: one envelope
|
|
// shape, mirrored key-for-key from ``app/application/ports/realtime.py``.
|
|
// A page that switches on ``WsEventType`` never needs to know which part of
|
|
// the backend produced the event.
|
|
//
|
|
// Channel naming is what a page subscribes to — ``org:{id}`` for the tenant
|
|
// feed, ``task:{id}`` for one task, and so on. The server authorizes every
|
|
// subscribe against the caller's memberships, so a channel the user may not
|
|
// see simply comes back rejected.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export type WsEventType =
|
|
| "run.step"
|
|
| "run.status"
|
|
| "task.status"
|
|
| "task.created"
|
|
| "chat.pending"
|
|
| "message.created"
|
|
| "approval.pending"
|
|
| "artifact.created"
|
|
| "sandbox.command"
|
|
| "sandbox.session"
|
|
| "sandbox.reaped"
|
|
| "usage.recorded"
|
|
| "billing.wallet"
|
|
| "error.recorded"
|
|
| "audit.recorded"
|
|
| "worker.status"
|
|
// v2 delivery: CI/CD runs, releases and production deploys (org channel).
|
|
| "pipeline.queued"
|
|
| "pipeline.step"
|
|
| "pipeline.log"
|
|
| "pipeline.status"
|
|
| "release.live"
|
|
| "deploy.status";
|
|
|
|
/** One pushed event, as the browser receives it. */
|
|
export interface WsEvent<T = Record<string, unknown>> {
|
|
id: string;
|
|
type: WsEventType;
|
|
data: T;
|
|
organization_id: string | null;
|
|
/** ISO-8601 UTC; render with ``toLocaleString`` like every other timestamp. */
|
|
at: string;
|
|
}
|
|
|
|
/** Every frame the server sends. Control frames and events share ``type``. */
|
|
export type WsServerFrame =
|
|
| { type: "hello"; user_id: string | null; server_time: string }
|
|
| { type: "subscribed"; accepted: string[]; rejected: string[] }
|
|
| { type: "unsubscribed"; channels: string[] }
|
|
| { type: "pong" }
|
|
| { type: "error"; message: string }
|
|
| { type: "event"; event: WsEvent }
|
|
/** Reply to a client ``rpc`` frame: the HTTP status and parsed JSON body of the
|
|
* internal dispatch, correlating on ``id``. */
|
|
| { type: "rpc_result"; id: string; status: number; body: unknown };
|
|
|
|
/** Every frame the client may send. */
|
|
export type WsClientFrame =
|
|
| { action: "subscribe"; channels: string[] }
|
|
| { action: "unsubscribe"; channels: string[] }
|
|
| { action: "ping" }
|
|
/** A request/response call into the server. ``id`` correlates the
|
|
* ``rpc_result``; ``path`` is the full mounted path including any query
|
|
* string. Every application call travels over this frame. */
|
|
| {
|
|
action: "rpc";
|
|
id: string;
|
|
method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
|
|
path: string;
|
|
body: unknown;
|
|
};
|
|
|
|
/** Live run-step payload — what the task timeline renders as it arrives. */
|
|
export interface WsRunStepData {
|
|
run_id: string;
|
|
task_id: string;
|
|
step: {
|
|
id: string;
|
|
index: number;
|
|
kind: string;
|
|
title: string;
|
|
payload: Record<string, unknown>;
|
|
};
|
|
}
|
|
|
|
/** Live sandbox command payload — the terminal panel's stream. */
|
|
export interface WsSandboxCommandData {
|
|
session_id: string;
|
|
run_id: string;
|
|
command: string;
|
|
status: string;
|
|
exit_code: number | null;
|
|
stdout: string;
|
|
stderr: string;
|
|
duration_ms: number;
|
|
truncated: boolean;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* v2 delivery (CI/CD, demo runtime, production deploy) */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
export type PipelineRunStatus =
|
|
| "queued"
|
|
| "running"
|
|
| "succeeded"
|
|
| "failed"
|
|
| "cancelled"
|
|
| "superseded";
|
|
|
|
export type PipelineStepStatus = "pending" | "running" | "succeeded" | "failed" | "skipped";
|
|
|
|
/** The eight steps of a run, in the order the server executes them. */
|
|
export const PIPELINE_STEP_NAMES = [
|
|
"checkout",
|
|
"setup",
|
|
"build",
|
|
"test",
|
|
"package",
|
|
"deploy_demo",
|
|
"healthcheck",
|
|
"route",
|
|
] as const;
|
|
|
|
export interface PipelineStep {
|
|
id: string;
|
|
index: number;
|
|
name: string;
|
|
status: PipelineStepStatus;
|
|
exit_code: number | null;
|
|
log_tail: string;
|
|
duration_ms: number;
|
|
}
|
|
|
|
export interface PipelineRun {
|
|
id: string;
|
|
project_id: string;
|
|
branch: string;
|
|
sha: string;
|
|
trigger: "engine" | "poll" | "manual";
|
|
status: PipelineRunStatus;
|
|
error_summary: string;
|
|
started_at: string | null;
|
|
finished_at: string | null;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface PipelineRunDetail extends PipelineRun {
|
|
steps: PipelineStep[];
|
|
}
|
|
|
|
export interface Release {
|
|
id: string;
|
|
project_id: string;
|
|
run_id: string;
|
|
sha: string;
|
|
is_demo_live: boolean;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface AppRuntime {
|
|
project_id: string;
|
|
demo_host: string;
|
|
demo_url: string;
|
|
host_port: number | null;
|
|
status: "provisioning" | "live" | "degraded" | "stopped";
|
|
current_release_id: string | null;
|
|
updated_at: string;
|
|
}
|
|
|
|
export type ProductionDeployStatus =
|
|
| "queued"
|
|
| "running"
|
|
| "succeeded"
|
|
| "failed"
|
|
| "rolled_back";
|
|
|
|
/** The production target as a *user* sees it: never a secret, only a hint. */
|
|
export interface ProductionTarget {
|
|
kind: "ssh" | "sftp" | "ftp" | "ftps";
|
|
host: string;
|
|
port: number;
|
|
username: string;
|
|
auth_type: "password" | "key";
|
|
remote_path: string;
|
|
has_secret: boolean;
|
|
host_fingerprint: string | null;
|
|
last_tested_at: string | null;
|
|
last_test_ok: boolean | null;
|
|
last_test_message: string | null;
|
|
}
|
|
|
|
export interface ProductionDeploy {
|
|
id: string;
|
|
project_id: string;
|
|
kind: string;
|
|
host: string;
|
|
status: ProductionDeployStatus;
|
|
message: string;
|
|
log_tail: string;
|
|
release_id: string | null;
|
|
rolled_back_from: string | null;
|
|
started_at: string | null;
|
|
finished_at: string | null;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface ProductionOverview {
|
|
target: ProductionTarget | null;
|
|
deploys: ProductionDeploy[];
|
|
}
|
|
|
|
/** Owner console: one managed preview route. */
|
|
export interface DomainRoute {
|
|
id: string;
|
|
host: string;
|
|
port: number;
|
|
status: string;
|
|
protected: boolean;
|
|
url: string;
|
|
organization_id: string;
|
|
project_id: string;
|
|
project_name: string;
|
|
created_at: string | null;
|
|
}
|
|
|
|
export interface SuggestedRoute {
|
|
label: string;
|
|
host: string;
|
|
port: number;
|
|
description: string;
|
|
enabled: boolean;
|
|
}
|
|
|
|
export interface DomainSyncResult {
|
|
ok: boolean;
|
|
message: string;
|
|
routes: number;
|
|
changed: boolean;
|
|
}
|
|
|
|
/* -- live event payloads (channel `org:<id>`) ------------------------------ */
|
|
|
|
export interface WsPipelineLogData {
|
|
run_id: string;
|
|
step: string;
|
|
lines: string[];
|
|
}
|
|
|
|
export interface WsPipelineStepData {
|
|
run_id: string;
|
|
step: string;
|
|
index: number;
|
|
status: PipelineStepStatus;
|
|
}
|
|
|
|
export interface WsPipelineStatusData {
|
|
run_id: string;
|
|
project_id: string;
|
|
status: PipelineRunStatus;
|
|
error?: string;
|
|
}
|
|
|
|
export interface WsPipelineQueuedData {
|
|
run_id: string;
|
|
project_id: string;
|
|
sha: string;
|
|
trigger: string;
|
|
superseded: number;
|
|
}
|
|
|
|
export interface WsReleaseLiveData {
|
|
project_id: string;
|
|
run_id?: string;
|
|
release_id?: string;
|
|
sha?: string;
|
|
demo_host?: string;
|
|
demo_url?: string;
|
|
rolled_back?: boolean;
|
|
status?: string;
|
|
}
|
|
|
|
export interface WsDeployStatusData {
|
|
project_id: string;
|
|
deploy_id: string;
|
|
kind: string;
|
|
status: ProductionDeployStatus;
|
|
message: string;
|
|
}
|
|
|
|
/* -- org wizard (v2 A1-2) ------------------------------------------------- */
|
|
|
|
export interface CatalogPayload {
|
|
app_types: string[];
|
|
app_type_labels: Record<string, string>;
|
|
git_sources: string[];
|
|
languages: CatalogLanguage[];
|
|
}
|
|
|
|
export interface CatalogLanguage {
|
|
key: string;
|
|
display: string;
|
|
frameworks: CatalogFramework[];
|
|
}
|
|
|
|
export interface CatalogFramework {
|
|
key: string;
|
|
display: string;
|
|
app_type: string;
|
|
defaults: CatalogDefaults;
|
|
}
|
|
|
|
export interface CatalogDefaults {
|
|
runtime_version: string;
|
|
package_manager: string;
|
|
setup_command: string;
|
|
build_command: string | null;
|
|
test_command: string | null;
|
|
start_command: string;
|
|
app_port: number;
|
|
healthcheck_path: string;
|
|
docker_image: string;
|
|
}
|
|
|
|
export interface WizardCompleteResult {
|
|
organization: { id: string; name: string; slug: string };
|
|
project: { id: string; name: string; slug: string };
|
|
deploy_target_id: string | null;
|
|
}
|
|
|
|
export interface AppProfile {
|
|
project_id: string;
|
|
app_type: string | null;
|
|
language: string | null;
|
|
framework: string | null;
|
|
runtime_version: string | null;
|
|
package_manager: string | null;
|
|
setup_command: string | null;
|
|
build_command: string | null;
|
|
test_command: string | null;
|
|
start_command: string | null;
|
|
app_port: number | null;
|
|
healthcheck_path: string;
|
|
git_source: string;
|
|
external_repo_url: string | null;
|
|
default_branch: string;
|
|
wizard_completed: boolean;
|
|
}
|
|
|
|
export interface DeployTargetSummary {
|
|
id: string;
|
|
kind: string;
|
|
host: string;
|
|
port: number;
|
|
username: string;
|
|
auth_type: string;
|
|
remote_path: string;
|
|
has_secret: boolean;
|
|
last_test_ok: boolean | null;
|
|
last_test_message: string | null;
|
|
}
|
|
|
|
/* -- org wizard request bodies (v2 A1-2) -------------------------------- */
|
|
|
|
/** Persisted wizard draft. Secrets are never stored here — only in the final submit. */
|
|
export interface SetupDraft {
|
|
step: number;
|
|
data: Record<string, unknown>;
|
|
}
|
|
|
|
export interface CheckGitBody {
|
|
url: string;
|
|
auth_type?: string;
|
|
username?: string | null;
|
|
secret?: string | null;
|
|
}
|
|
|
|
export interface CheckDeployBody {
|
|
kind: string;
|
|
host: string;
|
|
port?: number;
|
|
username: string;
|
|
auth_type?: string;
|
|
secret?: string | null;
|
|
remote_path?: string;
|
|
}
|
|
|
|
/** Final wizard submission. Mirrors the backend `WizardCompleteRequest`. */
|
|
export interface WizardCompleteBody {
|
|
org_name: string;
|
|
slug: string;
|
|
app_name: string;
|
|
app_type: string;
|
|
language: string;
|
|
framework: string;
|
|
runtime_version?: string | null;
|
|
package_manager?: string | null;
|
|
setup_command?: string | null;
|
|
build_command?: string | null;
|
|
test_command?: string | null;
|
|
start_command?: string | null;
|
|
app_port?: number | null;
|
|
healthcheck_path?: string;
|
|
git_source?: string;
|
|
external_repo_url?: string | null;
|
|
external_git_auth_type?: string;
|
|
external_git_username?: string | null;
|
|
external_git_secret?: string | null;
|
|
deploy_target_kind?: string | null;
|
|
deploy_target_host?: string | null;
|
|
deploy_target_port?: number;
|
|
deploy_target_username?: string | null;
|
|
deploy_target_auth_type?: string;
|
|
deploy_target_secret?: string | null;
|
|
deploy_target_remote_path?: string;
|
|
}
|
|
|
|
export type DeployTargetPutBody = CheckDeployBody;
|
|
|
|
export interface GitCredentialsPutBody {
|
|
auth_type?: string;
|
|
username?: string | null;
|
|
secret?: string | null;
|
|
}
|
|
|
|
/** Updatable app-profile fields (org owner/admin only). */
|
|
export type AppProfileUpdate = Partial<
|
|
Pick<
|
|
AppProfile,
|
|
| "app_type"
|
|
| "language"
|
|
| "framework"
|
|
| "runtime_version"
|
|
| "package_manager"
|
|
| "setup_command"
|
|
| "build_command"
|
|
| "test_command"
|
|
| "start_command"
|
|
| "app_port"
|
|
| "healthcheck_path"
|
|
| "git_source"
|
|
| "external_repo_url"
|
|
| "default_branch"
|
|
>
|
|
>;
|