a7005e5d2f
* chore: revert agent subscription usage in settings and chat tooltip
Reverts #1697 (55c8b461b). The subscription usage plugin now covers this,
so the built-in Settings > Agents section and the context-doughnut tooltip
rows are no longer needed.
Conflicts resolved to preserve later work on the same files:
- token-usage-display.tsx/.test.tsx: kept the pinnable tooltip (#1869),
the rebuilt context-window layout (#1741), and the ACP/API source row
(#1752); removed only SessionUsageRows and its imports.
- settings/agents/page.tsx: #1741 moved <AgentUsageSection /> above the
pre-existing <Separator />, so the revert missed the JSX; removed it
and kept the separator.
- agent-usage-section.tsx/.test.tsx: deleted (later changes were cosmetic
tweaks to the section itself).
Office-side subscription surfaces (dashboard quota card, agent detail
QuotaCard, internal/agent/usage clients) predate #1697 and are untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(usage): keep #1697 client fixes when reverting its UI surface
#1697 bundled two things: the Settings > Agents section plus doughnut
tooltip rows, and a set of genuine bug fixes to the underlying provider
usage clients. Reverting it wholesale threw away the second group, which
is still live code behind the office dashboard quota card and the agent
detail QuotaCard.
Restores the client layer to its post-#1697 state (billing.go, cache.go,
client_claude.go, client_codex.go, fileutil.go, types.go and the two
client tests), fixing the defects CodeRabbit flagged on #1940:
- billing type is recomputed per call again instead of being frozen for
the process lifetime by sync.OnceValue, so login/logout is observed
without a backend restart
- usage cache regains fetch coalescing and short negative caching, so a
provider outage no longer means one outbound call per request
- Claude usage parses float utilization and the richer limits[] array
instead of failing to decode
- Claude token refresh patches only claudeAiOauth in a decoded map and
writes atomically via temp+rename, preserving unknown keys in the
credentials file the real CLI shares
- Codex reads tokens.access_token rather than a nonexistent flat token
field, and its doc comment matches the ~/.codex/auth.json path
The removed UI/API surface stays reverted: GET /api/v1/agents/usage, the
host usage lister, the settings section, and the tooltip rows.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
660 lines
19 KiB
TypeScript
660 lines
19 KiB
TypeScript
import { fetchJson, fetchJsonWithRetry, type ApiRequestOptions } from "../client";
|
|
import { getBackendConfig } from "@/lib/config";
|
|
import type {
|
|
Agent,
|
|
ListAgentsResponse,
|
|
ListAgentDiscoveryResponse,
|
|
ListAvailableAgentsResponse,
|
|
AgentProfileMcpConfig,
|
|
ListExecutorsResponse,
|
|
ListExecutorProfilesResponse,
|
|
ExecutorProfile,
|
|
ProfileEnvVar,
|
|
NotificationProvidersResponse,
|
|
NotificationProvider,
|
|
EditorsResponse,
|
|
EditorOption,
|
|
CustomPrompt,
|
|
PromptsResponse,
|
|
UserSettingsResponse,
|
|
UserSettingsUpdatePayload,
|
|
DynamicModelsResponse,
|
|
} from "@/lib/types/http";
|
|
import type {
|
|
SystemMetricsGlobalSettings,
|
|
SystemMetricsSettingsResponse,
|
|
} from "@/lib/types/system";
|
|
|
|
// User settings
|
|
export async function fetchUserSettings(options?: ApiRequestOptions) {
|
|
return fetchJsonWithRetry<UserSettingsResponse>("/api/v1/user/settings", options);
|
|
}
|
|
|
|
export async function updateUserSettings(
|
|
payload: UserSettingsUpdatePayload,
|
|
options?: ApiRequestOptions,
|
|
) {
|
|
return fetchJson<UserSettingsResponse>("/api/v1/user/settings", {
|
|
...options,
|
|
init: { method: "PATCH", body: JSON.stringify(payload), ...(options?.init ?? {}) },
|
|
});
|
|
}
|
|
|
|
export async function fetchSystemMetricsSettings(options?: ApiRequestOptions) {
|
|
return fetchJsonWithRetry<SystemMetricsSettingsResponse>(
|
|
"/api/v1/system/metrics/settings",
|
|
options,
|
|
);
|
|
}
|
|
|
|
export async function updateSystemMetricsSettings(
|
|
payload: SystemMetricsGlobalSettings,
|
|
options?: ApiRequestOptions,
|
|
) {
|
|
return fetchJson<SystemMetricsSettingsResponse>("/api/v1/system/metrics/settings", {
|
|
...options,
|
|
init: { ...(options?.init ?? {}), method: "PATCH", body: JSON.stringify(payload) },
|
|
});
|
|
}
|
|
|
|
// Executors
|
|
export async function listExecutors(options?: ApiRequestOptions): Promise<ListExecutorsResponse> {
|
|
return fetchJson<ListExecutorsResponse>("/api/v1/executors", options);
|
|
}
|
|
|
|
export async function fetchExecutor(
|
|
executorId: string,
|
|
options?: ApiRequestOptions,
|
|
): Promise<{ id: string; name: string; type: string; config?: Record<string, string> }> {
|
|
return fetchJson<{ id: string; name: string; type: string; config?: Record<string, string> }>(
|
|
`/api/v1/executors/${executorId}`,
|
|
options,
|
|
);
|
|
}
|
|
|
|
export async function createExecutor(
|
|
payload: {
|
|
name: string;
|
|
type: string;
|
|
config?: Record<string, string>;
|
|
},
|
|
options?: ApiRequestOptions,
|
|
): Promise<{ id: string; name: string; type: string; config?: Record<string, string> }> {
|
|
return fetchJson<{ id: string; name: string; type: string; config?: Record<string, string> }>(
|
|
"/api/v1/executors",
|
|
{
|
|
...options,
|
|
init: { method: "POST", body: JSON.stringify(payload), ...(options?.init ?? {}) },
|
|
},
|
|
);
|
|
}
|
|
|
|
export async function updateExecutor(
|
|
executorId: string,
|
|
payload: { name?: string; config?: Record<string, string> },
|
|
options?: ApiRequestOptions,
|
|
): Promise<void> {
|
|
await fetchJson<void>(`/api/v1/executors/${executorId}`, {
|
|
...options,
|
|
init: { method: "PATCH", body: JSON.stringify(payload), ...(options?.init ?? {}) },
|
|
});
|
|
}
|
|
|
|
// Executor profiles
|
|
export async function listExecutorProfiles(
|
|
executorId: string,
|
|
options?: ApiRequestOptions,
|
|
): Promise<ListExecutorProfilesResponse> {
|
|
return fetchJson<ListExecutorProfilesResponse>(
|
|
`/api/v1/executors/${executorId}/profiles`,
|
|
options,
|
|
);
|
|
}
|
|
|
|
export async function listAllExecutorProfiles(
|
|
options?: ApiRequestOptions,
|
|
): Promise<ListExecutorProfilesResponse> {
|
|
return fetchJson<ListExecutorProfilesResponse>("/api/v1/executor-profiles", options);
|
|
}
|
|
|
|
export type ScriptPlaceholder = {
|
|
key: string;
|
|
description: string;
|
|
example: string;
|
|
executor_types: string[];
|
|
};
|
|
|
|
export async function listScriptPlaceholders(
|
|
options?: ApiRequestOptions,
|
|
): Promise<{ placeholders: ScriptPlaceholder[] }> {
|
|
return fetchJson<{ placeholders: ScriptPlaceholder[] }>("/api/v1/script-placeholders", options);
|
|
}
|
|
|
|
export async function fetchDefaultScripts(
|
|
executorType: string,
|
|
options?: ApiRequestOptions,
|
|
): Promise<{ prepare_script: string; cleanup_script: string }> {
|
|
return fetchJson<{ prepare_script: string; cleanup_script: string }>(
|
|
`/api/v1/executor-profiles/default-script?type=${encodeURIComponent(executorType)}`,
|
|
options,
|
|
);
|
|
}
|
|
|
|
export async function createExecutorProfile(
|
|
executorId: string,
|
|
payload: {
|
|
name: string;
|
|
mcp_policy?: string;
|
|
config?: Record<string, string>;
|
|
prepare_script?: string;
|
|
cleanup_script?: string;
|
|
env_vars?: ProfileEnvVar[];
|
|
},
|
|
options?: ApiRequestOptions,
|
|
): Promise<ExecutorProfile> {
|
|
return fetchJson<ExecutorProfile>(`/api/v1/executors/${executorId}/profiles`, {
|
|
...options,
|
|
init: { method: "POST", body: JSON.stringify(payload), ...(options?.init ?? {}) },
|
|
});
|
|
}
|
|
|
|
export async function updateExecutorProfile(
|
|
executorId: string,
|
|
profileId: string,
|
|
payload: {
|
|
name?: string;
|
|
mcp_policy?: string;
|
|
config?: Record<string, string>;
|
|
prepare_script?: string;
|
|
cleanup_script?: string;
|
|
env_vars?: ProfileEnvVar[];
|
|
},
|
|
options?: ApiRequestOptions,
|
|
): Promise<ExecutorProfile> {
|
|
return fetchJson<ExecutorProfile>(`/api/v1/executors/${executorId}/profiles/${profileId}`, {
|
|
...options,
|
|
init: { method: "PATCH", body: JSON.stringify(payload), ...(options?.init ?? {}) },
|
|
});
|
|
}
|
|
|
|
export async function deleteExecutorProfile(
|
|
executorId: string,
|
|
profileId: string,
|
|
options?: ApiRequestOptions,
|
|
): Promise<{ success: boolean }> {
|
|
return fetchJson<{ success: boolean }>(`/api/v1/executors/${executorId}/profiles/${profileId}`, {
|
|
...options,
|
|
init: { method: "DELETE", ...(options?.init ?? {}) },
|
|
});
|
|
}
|
|
|
|
// Agents
|
|
import { normalizeAgentProfile } from "@/lib/api/domains/agent-profile-normalize";
|
|
|
|
function normalizeAgentResponse(agent: Agent): Agent {
|
|
return {
|
|
...agent,
|
|
profiles: (agent.profiles ?? []).map((profile) => normalizeAgentProfile(profile)),
|
|
};
|
|
}
|
|
|
|
export async function listAgents(options?: ApiRequestOptions): Promise<ListAgentsResponse> {
|
|
const res = await fetchJson<ListAgentsResponse>("/api/v1/agents", options);
|
|
return { ...res, agents: (res.agents ?? []).map(normalizeAgentResponse) };
|
|
}
|
|
|
|
export async function listAgentDiscovery(
|
|
options?: ApiRequestOptions,
|
|
): Promise<ListAgentDiscoveryResponse> {
|
|
return fetchJson<ListAgentDiscoveryResponse>("/api/v1/agents/discovery", options);
|
|
}
|
|
|
|
export async function listAvailableAgents(
|
|
options?: ApiRequestOptions,
|
|
): Promise<ListAvailableAgentsResponse> {
|
|
return fetchJson<ListAvailableAgentsResponse>("/api/v1/agents/available", options);
|
|
}
|
|
|
|
export async function getAgentProfileMcpConfig(
|
|
profileId: string,
|
|
options?: ApiRequestOptions,
|
|
): Promise<AgentProfileMcpConfig> {
|
|
return fetchJson<AgentProfileMcpConfig>(
|
|
`/api/v1/agent-profiles/${profileId}/mcp-config`,
|
|
options,
|
|
);
|
|
}
|
|
|
|
export type CommandPreviewRequest = {
|
|
model: string;
|
|
permission_settings: Record<string, boolean>;
|
|
cli_passthrough: boolean;
|
|
};
|
|
|
|
export type CommandPreviewResponse = {
|
|
supported: boolean;
|
|
command: string[];
|
|
command_string: string;
|
|
};
|
|
|
|
export async function previewAgentCommand(
|
|
agentName: string,
|
|
payload: CommandPreviewRequest,
|
|
options?: ApiRequestOptions,
|
|
): Promise<CommandPreviewResponse> {
|
|
return fetchJson<CommandPreviewResponse>(`/api/v1/agent-command-preview/${agentName}`, {
|
|
...options,
|
|
init: { method: "POST", body: JSON.stringify(payload), ...(options?.init ?? {}) },
|
|
});
|
|
}
|
|
|
|
export type InstallJobStatus = "queued" | "running" | "succeeded" | "failed";
|
|
|
|
export type InstallJob = {
|
|
job_id: string;
|
|
agent_name: string;
|
|
status: InstallJobStatus;
|
|
output?: string;
|
|
error?: string;
|
|
exit_code?: number;
|
|
started_at: string;
|
|
finished_at?: string;
|
|
};
|
|
|
|
/** Enqueue an install. Returns the job snapshot (status=queued or running). */
|
|
export async function installAgent(
|
|
agentName: string,
|
|
options?: ApiRequestOptions,
|
|
): Promise<InstallJob> {
|
|
return fetchJson<InstallJob>(`/api/v1/agent-install/${agentName}`, {
|
|
...options,
|
|
init: { method: "POST", ...(options?.init ?? {}) },
|
|
});
|
|
}
|
|
|
|
export async function listInstallJobs(
|
|
options?: ApiRequestOptions,
|
|
): Promise<{ jobs: InstallJob[] }> {
|
|
return fetchJson<{ jobs: InstallJob[] }>("/api/v1/agent-install/jobs", options);
|
|
}
|
|
|
|
export async function getInstallJob(
|
|
jobId: string,
|
|
options?: ApiRequestOptions,
|
|
): Promise<InstallJob> {
|
|
return fetchJson<InstallJob>(`/api/v1/agent-install/jobs/${jobId}`, options);
|
|
}
|
|
|
|
export type AgentLoginSession = {
|
|
session_id: string;
|
|
agent_id: string;
|
|
cmd: string[];
|
|
running: boolean;
|
|
started_at: string;
|
|
finished_at?: string;
|
|
exit_code?: number;
|
|
};
|
|
|
|
export async function startAgentLogin(
|
|
agentName: string,
|
|
size: { cols: number; rows: number },
|
|
options?: ApiRequestOptions,
|
|
): Promise<AgentLoginSession> {
|
|
return fetchJson<AgentLoginSession>(`/api/v1/agent-login/agents/${agentName}/start`, {
|
|
...options,
|
|
init: {
|
|
method: "POST",
|
|
body: JSON.stringify(size),
|
|
...(options?.init ?? {}),
|
|
},
|
|
});
|
|
}
|
|
|
|
export async function stopAgentLogin(sessionID: string): Promise<void> {
|
|
await fetchJson<{ ok: boolean }>(`/api/v1/agent-login/sessions/${sessionID}/stop`, {
|
|
init: { method: "POST" },
|
|
});
|
|
}
|
|
|
|
export async function resizeAgentLogin(
|
|
sessionID: string,
|
|
size: { cols: number; rows: number },
|
|
): Promise<void> {
|
|
await fetchJson<{ ok: boolean }>(`/api/v1/agent-login/sessions/${sessionID}/resize`, {
|
|
init: { method: "POST", body: JSON.stringify(size) },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Build the bi-directional WS URL for streaming a login session.
|
|
* Derives the host from the backend config (NOT window.location) so dev mode
|
|
* — where the browser is on :37429 and the API is on :38429 — routes to the
|
|
* Go backend, not the web dev server.
|
|
*/
|
|
export function agentLoginStreamUrl(sessionID: string): string {
|
|
const { apiBaseUrl } = getBackendConfig();
|
|
const url = new URL(apiBaseUrl);
|
|
const proto = url.protocol === "https:" ? "wss:" : "ws:";
|
|
return `${proto}//${url.host}/api/v1/agent-login/sessions/${sessionID}/stream`;
|
|
}
|
|
|
|
/**
|
|
* Start a plain host shell PTY (spawns $SHELL, or bash/sh fallback). Reuses
|
|
* the same session manager as agent-login, so stop/resize/stream all use the
|
|
* same session-ID-based endpoints.
|
|
*/
|
|
export async function startHostShell(
|
|
size: { cols: number; rows: number },
|
|
options?: ApiRequestOptions,
|
|
): Promise<AgentLoginSession> {
|
|
return fetchJson<AgentLoginSession>("/api/v1/host-shell/start", {
|
|
...options,
|
|
init: {
|
|
method: "POST",
|
|
body: JSON.stringify(size),
|
|
...(options?.init ?? {}),
|
|
},
|
|
});
|
|
}
|
|
|
|
export async function createCustomTUIAgent(
|
|
payload: { display_name: string; model?: string; command: string; description?: string },
|
|
options?: ApiRequestOptions,
|
|
): Promise<Agent> {
|
|
const res = await fetchJson<Agent>("/api/v1/agents/tui", {
|
|
...options,
|
|
init: { method: "POST", body: JSON.stringify(payload), ...(options?.init ?? {}) },
|
|
});
|
|
return normalizeAgentResponse(res);
|
|
}
|
|
|
|
export async function fetchDynamicModels(
|
|
agentName: string,
|
|
options?: ApiRequestOptions & { refresh?: boolean },
|
|
): Promise<DynamicModelsResponse> {
|
|
const refresh = options?.refresh ?? false;
|
|
const url = `/api/v1/agent-models/${agentName}${refresh ? "?refresh=true" : ""}`;
|
|
return fetchJson<DynamicModelsResponse>(url, options);
|
|
}
|
|
|
|
// Editors
|
|
export async function listEditors(options?: ApiRequestOptions) {
|
|
return fetchJson<EditorsResponse>("/api/v1/editors", options);
|
|
}
|
|
|
|
export async function createEditor(
|
|
payload: {
|
|
name: string;
|
|
kind: string;
|
|
config?: Record<string, unknown>;
|
|
enabled?: boolean;
|
|
},
|
|
options?: ApiRequestOptions,
|
|
) {
|
|
return fetchJson<EditorOption>("/api/v1/editors", {
|
|
...options,
|
|
init: { method: "POST", body: JSON.stringify(payload), ...(options?.init ?? {}) },
|
|
});
|
|
}
|
|
|
|
export async function updateEditor(
|
|
editorId: string,
|
|
payload: {
|
|
name?: string;
|
|
kind?: string;
|
|
config?: Record<string, unknown>;
|
|
enabled?: boolean;
|
|
},
|
|
options?: ApiRequestOptions,
|
|
) {
|
|
return fetchJson<EditorOption>(`/api/v1/editors/${editorId}`, {
|
|
...options,
|
|
init: { method: "PATCH", body: JSON.stringify(payload), ...(options?.init ?? {}) },
|
|
});
|
|
}
|
|
|
|
export async function deleteEditor(editorId: string, options?: ApiRequestOptions) {
|
|
return fetchJson<{ success: boolean }>(`/api/v1/editors/${editorId}`, {
|
|
...options,
|
|
init: { method: "DELETE", ...(options?.init ?? {}) },
|
|
});
|
|
}
|
|
|
|
// Prompts
|
|
export async function listPrompts(options?: ApiRequestOptions) {
|
|
return fetchJson<PromptsResponse>("/api/v1/prompts", options);
|
|
}
|
|
|
|
export async function createPrompt(
|
|
payload: { name: string; content: string },
|
|
options?: ApiRequestOptions,
|
|
) {
|
|
return fetchJson<CustomPrompt>("/api/v1/prompts", {
|
|
...options,
|
|
init: { method: "POST", body: JSON.stringify(payload), ...(options?.init ?? {}) },
|
|
});
|
|
}
|
|
|
|
export async function updatePrompt(
|
|
promptId: string,
|
|
payload: { name?: string; content?: string },
|
|
options?: ApiRequestOptions,
|
|
) {
|
|
return fetchJson<CustomPrompt>(`/api/v1/prompts/${promptId}`, {
|
|
...options,
|
|
init: { method: "PATCH", body: JSON.stringify(payload), ...(options?.init ?? {}) },
|
|
});
|
|
}
|
|
|
|
export async function deletePrompt(promptId: string, options?: ApiRequestOptions) {
|
|
return fetchJson<{ success: boolean }>(`/api/v1/prompts/${promptId}`, {
|
|
...options,
|
|
init: { method: "DELETE", ...(options?.init ?? {}) },
|
|
});
|
|
}
|
|
|
|
// Notification providers
|
|
export async function listNotificationProviders(options?: ApiRequestOptions) {
|
|
return fetchJson<NotificationProvidersResponse>("/api/v1/notification-providers", options);
|
|
}
|
|
|
|
export async function createNotificationProvider(
|
|
payload: {
|
|
name: string;
|
|
type: NotificationProvider["type"];
|
|
config?: NotificationProvider["config"];
|
|
enabled?: boolean;
|
|
events?: string[];
|
|
},
|
|
options?: ApiRequestOptions,
|
|
) {
|
|
return fetchJson<NotificationProvider>("/api/v1/notification-providers", {
|
|
...options,
|
|
init: { method: "POST", body: JSON.stringify(payload), ...(options?.init ?? {}) },
|
|
});
|
|
}
|
|
|
|
export async function updateNotificationProvider(
|
|
providerId: string,
|
|
payload: Partial<{
|
|
name: string;
|
|
type: NotificationProvider["type"];
|
|
config: NotificationProvider["config"];
|
|
enabled: boolean;
|
|
events: string[];
|
|
}>,
|
|
options?: ApiRequestOptions,
|
|
) {
|
|
return fetchJson<NotificationProvider>(`/api/v1/notification-providers/${providerId}`, {
|
|
...options,
|
|
init: { method: "PATCH", body: JSON.stringify(payload), ...(options?.init ?? {}) },
|
|
});
|
|
}
|
|
|
|
export async function testNotificationProvider(providerId: string, options?: ApiRequestOptions) {
|
|
return fetchJson<void>(`/api/v1/notification-providers/${providerId}/test`, {
|
|
...options,
|
|
init: { method: "POST", ...(options?.init ?? {}) },
|
|
});
|
|
}
|
|
|
|
export async function deleteNotificationProvider(providerId: string, options?: ApiRequestOptions) {
|
|
return fetchJson<void>(`/api/v1/notification-providers/${providerId}`, {
|
|
...options,
|
|
init: { method: "DELETE", ...(options?.init ?? {}) },
|
|
});
|
|
}
|
|
|
|
// Docker management
|
|
|
|
export type DockerContainer = {
|
|
id: string;
|
|
name: string;
|
|
image: string;
|
|
state: string;
|
|
status: string;
|
|
started_at?: string;
|
|
labels?: Record<string, string>;
|
|
};
|
|
|
|
export function buildDockerImageUrl(payload: {
|
|
dockerfile: string;
|
|
tag: string;
|
|
build_args?: Record<string, string>;
|
|
}): string {
|
|
// Returns the URL for SSE streaming. Caller should use EventSource or fetch with streaming.
|
|
const params = new URLSearchParams();
|
|
params.set("dockerfile", payload.dockerfile);
|
|
params.set("tag", payload.tag);
|
|
if (payload.build_args) {
|
|
params.set("build_args", JSON.stringify(payload.build_args));
|
|
}
|
|
return `/api/v1/docker/build?${params.toString()}`;
|
|
}
|
|
|
|
export async function buildDockerImage(
|
|
payload: {
|
|
dockerfile: string;
|
|
tag: string;
|
|
build_args?: Record<string, string>;
|
|
},
|
|
options?: ApiRequestOptions,
|
|
): Promise<Response> {
|
|
const baseUrl = options?.baseUrl ?? getBackendConfig().apiBaseUrl;
|
|
return fetch(`${baseUrl}/api/v1/docker/build`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(payload),
|
|
...(options?.init ?? {}),
|
|
});
|
|
}
|
|
|
|
export async function listDockerContainers(
|
|
filter?: { image?: string; labels?: Record<string, string> },
|
|
options?: ApiRequestOptions,
|
|
): Promise<{ containers: DockerContainer[] }> {
|
|
const params = new URLSearchParams();
|
|
if (filter?.image) params.set("image", filter.image);
|
|
if (filter?.labels) {
|
|
const labelPairs = Object.entries(filter.labels).map(([k, v]) => `${k}=${v}`);
|
|
params.set("labels", labelPairs.join(","));
|
|
}
|
|
const qs = params.toString();
|
|
return fetchJson<{ containers: DockerContainer[] }>(
|
|
`/api/v1/docker/containers${qs ? `?${qs}` : ""}`,
|
|
options,
|
|
);
|
|
}
|
|
|
|
export async function stopDockerContainer(id: string, options?: ApiRequestOptions): Promise<void> {
|
|
await fetchJson<{ success: boolean }>(`/api/v1/docker/containers/${id}/stop`, {
|
|
...options,
|
|
init: { method: "POST", ...(options?.init ?? {}) },
|
|
});
|
|
}
|
|
|
|
export async function removeDockerContainer(
|
|
id: string,
|
|
options?: ApiRequestOptions,
|
|
): Promise<void> {
|
|
await fetchJson<{ success: boolean }>(`/api/v1/docker/containers/${id}`, {
|
|
...options,
|
|
init: { method: "DELETE", ...(options?.init ?? {}) },
|
|
});
|
|
}
|
|
|
|
// Remote auth methods (for remote executors like Sprites)
|
|
|
|
export type RemoteAuthMethod = {
|
|
method_id: string;
|
|
type: "env" | "files";
|
|
env_var?: string;
|
|
setup_hint?: string;
|
|
source_files?: string[];
|
|
target_rel_dir?: string;
|
|
label?: string;
|
|
has_local_files?: boolean;
|
|
};
|
|
|
|
export type RemoteAuthSpec = {
|
|
id: string;
|
|
display_name: string;
|
|
methods: RemoteAuthMethod[];
|
|
};
|
|
|
|
export type ListRemoteCredentialsResponse = {
|
|
auth_specs: RemoteAuthSpec[];
|
|
};
|
|
|
|
export async function listRemoteCredentials(
|
|
options?: ApiRequestOptions,
|
|
): Promise<ListRemoteCredentialsResponse> {
|
|
return fetchJson<ListRemoteCredentialsResponse>("/api/v1/remote-credentials", options);
|
|
}
|
|
|
|
export type LocalGitIdentityResponse = {
|
|
user_name: string;
|
|
user_email: string;
|
|
detected: boolean;
|
|
};
|
|
|
|
export async function fetchLocalGitIdentity(
|
|
options?: ApiRequestOptions,
|
|
): Promise<LocalGitIdentityResponse> {
|
|
return fetchJson<LocalGitIdentityResponse>("/api/v1/git/identity", options);
|
|
}
|
|
|
|
// Sprites network policies
|
|
|
|
export type NetworkPolicyRule = {
|
|
domain: string;
|
|
action: "allow" | "deny";
|
|
include?: string;
|
|
};
|
|
|
|
export type NetworkPolicy = {
|
|
rules: NetworkPolicyRule[];
|
|
};
|
|
|
|
export async function getSpritesNetworkPolicies(
|
|
secretId: string,
|
|
spriteName: string,
|
|
options?: ApiRequestOptions,
|
|
): Promise<NetworkPolicy> {
|
|
const params = new URLSearchParams({ secret_id: secretId, sprite_name: spriteName });
|
|
return fetchJson<NetworkPolicy>(`/api/v1/sprites/network-policies?${params.toString()}`, options);
|
|
}
|
|
|
|
export async function updateSpritesNetworkPolicies(
|
|
secretId: string,
|
|
spriteName: string,
|
|
policy: NetworkPolicy,
|
|
options?: ApiRequestOptions,
|
|
): Promise<void> {
|
|
const params = new URLSearchParams({ secret_id: secretId, sprite_name: spriteName });
|
|
await fetchJson<void>(`/api/v1/sprites/network-policies?${params.toString()}`, {
|
|
...options,
|
|
init: { method: "PUT", body: JSON.stringify(policy), ...(options?.init ?? {}) },
|
|
});
|
|
}
|