Files
buzz/desktop/src/shared/api/tauri.ts
T
DuncanandWill Pfleger 2dd0706d8f refactor(desktop): extract RawManagedAgent/fromRawManagedAgent to managedAgentMapping.ts
tauri.ts exceeded the file-size ratchet after main's #3818 moved editMessage out,
shrinking the allowance from 1175 to 1161. The six permission-policy additions to
tauri.ts that were within the old headroom now exceed the tighter baseline.

Extract RawManagedAgent type and fromRawManagedAgent function into a dedicated
shared/api/managedAgentMapping.ts module (mirrors main's editMessage.ts pattern).
tauri.ts re-exports both for zero caller changes. Removes now-unused imports
(ManagedAgentBackend, PermissionPolicy, PermissionPolicySource, RawRestartDiffEntry).

Result: tauri.ts 1073 gate vs 1161 allowed (88-line margin).
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
2026-08-07 17:35:02 -04:00

1073 lines
30 KiB
TypeScript

import { invoke as tauriInvoke } from "@tauri-apps/api/core";
import {
activateRateLimit,
parseRateLimitHint,
} from "@/shared/api/relayRateLimitGate";
import {
fromRawInstallRuntimeResult,
type RawInstallRuntimeResult,
} from "@/shared/api/installTypes";
import type {
AddChannelMembersInput,
AddChannelMembersResult,
BackendProviderCandidate,
BackendProviderProbeResult,
CanvasResponse,
GetHomeFeedInput,
HomeFeedResponse,
ManagedAgent,
RelayAgent,
RelayMember,
RelayMemberRole,
PresenceLookup,
PresenceStatus,
RelayEvent,
SearchMessagesInput,
SearchMessagesResponse,
SendChannelMessageResult,
SetCanvasInput,
SetCanvasResult,
ThreadCursor,
ThreadRepliesResponse,
CreateManagedAgentInput,
AgentModelsResponse,
UpdateManagedAgentInput,
AcpAvailabilityStatus,
AcpRuntimeCatalogEntry,
AuthStatus,
CommandAvailability,
InstallRuntimeResult,
GitBashPrerequisite,
RuntimeConfigSurface,
} from "@/shared/api/types";
export * from "@/shared/api/tauriChannels";
type RawPresenceLookup = Record<string, PresenceStatus>;
type RawAddChannelMembersResult = {
added: string[];
errors: Array<{
pubkey: string;
error: string;
}>;
};
type RawFeedItem = {
id: string;
kind: number;
pubkey: string;
content: string;
created_at: number;
channel_id: string | null;
channel_name: string;
channel_type: string | null;
tags: string[][];
category: "mention" | "needs_action" | "activity" | "agent_activity";
};
type RawHomeFeedResponse = {
feed: {
mentions: RawFeedItem[];
needs_action: RawFeedItem[];
activity: RawFeedItem[];
agent_activity: RawFeedItem[];
};
meta: {
since: number;
total: number;
generated_at: number;
};
};
type RawSearchHit = {
event_id: string;
content: string;
kind: number;
pubkey: string;
channel_id: string | null;
channel_name: string | null;
created_at: number;
score: number;
};
type RawSearchResponse = {
hits: RawSearchHit[];
found: number;
};
type RawSendChannelMessageResult = {
event_id: string;
parent_event_id: string | null;
root_event_id: string | null;
depth: number;
created_at: number;
};
type RawRelayAgent = {
pubkey: string;
name: string;
agent_type: string;
channels: string[];
channel_ids: string[];
capabilities: string[];
status: RelayAgent["status"];
respond_to?: RelayAgent["respondTo"];
respond_to_allowlist?: string[];
};
import {
fromRawManagedAgent,
type RawManagedAgent,
} from "@/shared/api/managedAgentMapping";
export { fromRawManagedAgent };
export type { RawManagedAgent };
type RawCreateManagedAgentResponse = {
agent: RawManagedAgent;
private_key_nsec: string;
profile_sync_error: string | null;
spawn_error: string | null;
};
type RawManagedAgentLog = {
content: string;
log_path: string;
};
export type RawAcpRuntimeCatalogEntry = {
id: string;
label: string;
avatar_url: string;
availability: AcpAvailabilityStatus;
command: string | null;
binary_path: string | null;
default_args: string[];
mcp_command: string | null;
model_env_var?: string | null;
provider_env_var?: string | null;
thinking_env_var?: string | null;
max_tokens_env_var?: string | null;
context_limit_env_var?: string | null;
max_rounds_env_var?: string | null;
install_hint: string;
install_instructions_url: string;
can_auto_install: boolean;
/** Optional only for older E2E fixtures; the Rust catalog always supplies it. */
requires_external_cli?: boolean;
underlying_cli_path: string | null;
node_required: boolean;
/** Tagged union with snake_case status values — same shape as `AuthStatus`. */
auth_status: AuthStatus;
login_hint?: string;
source: "builtin" | "preset" | "custom";
/** Definition-level env vars for `source: custom` entries; absent for builtin/preset. */
definition_env?: Record<string, string>;
max_parallelism?: number;
};
export type {
RawInstallRuntimeResult,
RawInstallStepResult,
} from "./installTypes";
type RawGitBashPrerequisite = {
available: boolean;
path: string | null;
install_instructions_url: string;
install_hint: string;
};
type RawCommandAvailability = {
command: string;
resolved_path: string | null;
available: boolean;
};
type RawManagedAgentPrereqs = {
acp: RawCommandAvailability;
mcp: RawCommandAvailability;
};
type RawRelayMember = {
pubkey: string;
role: string;
added_by: string | null;
created_at: string;
};
type RawListRelayMembersResponse = {
members: RawRelayMember[];
};
type RawCanvasResponse = {
content: string | null;
updated_at: number | null;
author: string | null;
};
type RawSetCanvasResult = {
ok: boolean;
event_id: string;
};
/** Error normalized from a rejected Tauri invocation with its wire payload. */
export class TauriInvokeError extends Error {
readonly payload: unknown;
constructor(message: string, payload: unknown) {
super(message);
this.name = "TauriInvokeError";
this.payload = payload;
}
}
function toTauriError(error: unknown): Error {
if (error instanceof Error) {
return error;
}
if (typeof error === "string") {
return new TauriInvokeError(error, error);
}
if (
typeof error === "object" &&
error !== null &&
"message" in error &&
typeof error.message === "string"
) {
return new TauriInvokeError(error.message, error);
}
try {
return new TauriInvokeError(JSON.stringify(error), error);
} catch {
return new TauriInvokeError("Unknown Tauri error", error);
}
}
/**
* Inspect a Tauri error message and activate the shared rate-limit gate when
* the Rust relay layer emitted an HTTP 429 response (`relay rate-limited:` prefix).
*
* Extracted so it can be unit-tested without mocking the Tauri invoke bridge.
*/
export function applyTauriRateLimitIfNeeded(message: string): void {
if (message.startsWith("relay rate-limited:")) {
activateRateLimit(parseRateLimitHint(message));
}
}
export async function invokeTauri<T>(
command: string,
args?: Record<string, unknown>,
): Promise<T> {
try {
return await tauriInvoke<T>(command, args);
} catch (error) {
const err = toTauriError(error);
// Rust emits `relay rate-limited:` for HTTP 429 responses. Activate the
// shared gate so the TS relay client backs off for the same window.
applyTauriRateLimitIfNeeded(err.message);
throw err;
}
}
export function fromRawFeedItem(item: RawFeedItem) {
return {
id: item.id,
kind: item.kind,
pubkey: item.pubkey,
content: item.content,
createdAt: item.created_at,
channelId: item.channel_id,
channelName: item.channel_name,
// Canonicalize the wire `null` to undefined so FeedItem's optional
// channelType contract holds at runtime (enrichment and the DM
// notification filter both key off `=== undefined`).
channelType: item.channel_type ?? undefined,
tags: item.tags,
category: item.category,
};
}
function fromRawSearchHit(hit: RawSearchHit) {
return {
eventId: hit.event_id,
content: hit.content,
kind: hit.kind,
pubkey: hit.pubkey,
channelId: hit.channel_id,
channelName: hit.channel_name,
createdAt: hit.created_at,
score: hit.score,
};
}
export async function getPresence(pubkeys: string[]): Promise<PresenceLookup> {
const response = await invokeTauri<RawPresenceLookup>("get_presence", {
pubkeys,
});
return Object.fromEntries(
Object.entries(response).map(([pubkey, status]) => [
pubkey.toLowerCase(),
status,
]),
);
}
export function getDefaultRelayUrl(): Promise<string> {
return invokeTauri<string>("get_default_relay_url");
}
export function autoConnectDefaultRelayEnabled(): Promise<boolean> {
return invokeTauri<boolean>("auto_connect_default_relay_enabled");
}
export function isSharedIdentity(): Promise<boolean> {
return invokeTauri<boolean>("is_shared_identity");
}
export function getRelayWsUrl(): Promise<string> {
return invokeTauri<string>("get_relay_ws_url");
}
export function getRelayHttpUrl(): Promise<string> {
return invokeTauri<string>("get_relay_http_url");
}
export async function addChannelMembers(
input: AddChannelMembersInput,
): Promise<AddChannelMembersResult> {
return invokeTauri<RawAddChannelMembersResult>("add_channel_members", input);
}
export async function removeChannelMember(
channelId: string,
pubkey: string,
): Promise<void> {
await invokeTauri("remove_channel_member", { channelId, pubkey });
}
export async function changeChannelMemberRole(
channelId: string,
pubkey: string,
role: string,
): Promise<void> {
await invokeTauri("change_channel_member_role", { channelId, pubkey, role });
}
export async function joinChannel(channelId: string): Promise<void> {
await invokeTauri("join_channel", { channelId });
}
export async function leaveChannel(channelId: string): Promise<void> {
await invokeTauri("leave_channel", { channelId });
}
export async function getCanvas(channelId: string): Promise<CanvasResponse> {
const response = await invokeTauri<RawCanvasResponse>("get_canvas", {
channelId,
});
return {
content: response.content,
// Normalize absent keys to null: ensureWelcomeCanvas treats null as
// "no canvas yet", and `undefined !== null` would make every fresh
// channel look already-seeded.
updatedAt: response.updated_at ?? null,
author: response.author ?? null,
};
}
export async function setCanvas(
input: SetCanvasInput,
): Promise<SetCanvasResult> {
const response = await invokeTauri<RawSetCanvasResult>("set_canvas", {
channelId: input.channelId,
content: input.content,
});
return {
ok: response.ok,
eventId: response.event_id,
};
}
export async function getHomeFeed(
input: GetHomeFeedInput = {},
): Promise<HomeFeedResponse> {
const response = await invokeTauri<RawHomeFeedResponse>("get_feed", input);
return {
feed: {
mentions: response.feed.mentions.map(fromRawFeedItem),
needsAction: response.feed.needs_action.map(fromRawFeedItem),
activity: response.feed.activity.map(fromRawFeedItem),
agentActivity: response.feed.agent_activity.map(fromRawFeedItem),
},
meta: {
since: response.meta.since,
total: response.meta.total,
generatedAt: response.meta.generated_at,
},
};
}
export async function searchMessages(
input: SearchMessagesInput,
): Promise<SearchMessagesResponse> {
const response = await invokeTauri<RawSearchResponse>("search_messages", {
q: input.q,
limit: input.limit,
channelId: input.channelId,
authors: input.authors,
since: input.since,
until: input.until,
});
return {
hits: response.hits.map(fromRawSearchHit),
found: response.found,
};
}
export async function getEventById(eventId: string): Promise<RelayEvent> {
const eventJson = await invokeTauri<string>("get_event", { eventId });
return JSON.parse(eventJson) as RelayEvent;
}
type RawThreadCursor = {
created_at: number;
event_id: string;
};
type RawThreadRepliesResponse = {
events: RelayEvent[];
next_cursor: RawThreadCursor | null;
};
/**
* Fetch the full reply subtree under a thread root, server-side.
*
* Unlike the channel timeline (which the desktop assembles from its local
* cache), this walks `thread_metadata` on the relay, so a thread renders
* complete even when its replies fell outside the channel cold-load window —
* the descendant gap that made deep/old threads silently incomplete.
*
* Paging is forward keyset on `(createdAt, eventId)`: pass the returned
* `nextCursor` back as `cursor` for the next page. `nextCursor` is non-null only
* when a full page was returned. The returned `events` are raw nostr events
* (`RelayEvent`), chronological (oldest first). These are the *replies* under
* the root (depth >= 1); the root event itself is NOT returned (the relay query
* keys on `root_event_id`, which a root row lacks). The caller already holds
* the root — it is the open thread head.
*/
export async function getThreadReplies(
rootEventId: string,
channelId?: string | null,
options?: {
limit?: number;
depthLimit?: number;
cursor?: ThreadCursor | null;
},
): Promise<ThreadRepliesResponse> {
const response = await invokeTauri<RawThreadRepliesResponse>(
"get_thread_replies",
{
rootEventId,
channelId: channelId ?? null,
limit: options?.limit ?? null,
depthLimit: options?.depthLimit ?? null,
cursor: options?.cursor
? {
created_at: options.cursor.createdAt,
event_id: options.cursor.eventId,
}
: null,
},
);
return {
events: response.events,
nextCursor: response.next_cursor
? {
createdAt: response.next_cursor.created_at,
eventId: response.next_cursor.event_id,
}
: null,
};
}
export async function sendChannelMessage(
channelId: string,
content: string,
parentEventId?: string | null,
mediaTags?: string[][],
mentionPubkeys?: string[],
kind?: number,
emojiTags?: string[][],
mentionTags?: string[][],
linkPreviewTags?: string[][],
): Promise<SendChannelMessageResult> {
const response = await invokeTauri<RawSendChannelMessageResult>(
"send_channel_message",
{
channelId,
content,
parentEventId,
mediaTags: mediaTags ?? null,
emojiTags: emojiTags ?? null,
mentionTags: mentionTags ?? null,
linkPreviewTags,
mentionPubkeys: mentionPubkeys ?? null,
kind: kind ?? null,
},
);
return {
eventId: response.event_id,
parentEventId: response.parent_event_id,
rootEventId: response.root_event_id,
depth: response.depth,
createdAt: response.created_at,
};
}
export type BlobDescriptor = {
url: string;
sha256: string;
size: number;
type: string;
uploaded: number;
dim?: string;
blurhash?: string;
thumb?: string;
duration?: number;
image?: string;
/** Original filename captured client-side. */
filename?: string;
};
export async function uploadMedia(
filePath: string,
isTemp: boolean,
): Promise<BlobDescriptor> {
return invokeTauri<BlobDescriptor>("upload_media", {
filePath,
isTemp,
});
}
export async function pickAndUploadMedia(
progressId?: string,
): Promise<BlobDescriptor[]> {
return invokeTauri<BlobDescriptor[]>("pick_and_upload_media", { progressId });
}
export async function uploadMediaBytes(
data: number[],
filename?: string,
/** Correlation id for `media-upload-progress` events from the Rust side. */
progressId?: string,
): Promise<BlobDescriptor> {
return invokeTauri<BlobDescriptor>("upload_media_bytes", {
data,
filename,
progressId,
});
}
export { editMessage } from "@/shared/api/editMessage";
export async function deleteMessage(
channelId: string,
eventId: string,
): Promise<void> {
await invokeTauri("delete_message", { channelId, eventId });
}
export async function addReaction(
eventId: string,
emoji: string,
emojiUrl?: string,
): Promise<void> {
await invokeTauri("add_reaction", { eventId, emoji, emojiUrl });
}
export async function removeReaction(
eventId: string,
emoji: string,
): Promise<void> {
await invokeTauri("remove_reaction", { eventId, emoji });
}
export async function signRelayEvent(input: {
kind: number;
content: string;
createdAt?: number;
tags: string[][];
}): Promise<RelayEvent> {
const eventJson = await invokeTauri<string>("sign_event", input);
return JSON.parse(eventJson) as RelayEvent;
}
export async function createAuthEvent(input: {
challenge: string;
relayUrl: string;
}): Promise<RelayEvent> {
const eventJson = await invokeTauri<string>("create_auth_event", input);
return JSON.parse(eventJson) as RelayEvent;
}
function fromRawRelayAgent(agent: RawRelayAgent): RelayAgent {
return {
pubkey: agent.pubkey,
name: agent.name,
agentType: agent.agent_type,
channels: agent.channels,
channelIds: agent.channel_ids ?? [],
capabilities: agent.capabilities,
status: agent.status,
respondTo: agent.respond_to ?? null,
respondToAllowlist: agent.respond_to_allowlist ?? [],
};
}
export function fromRawAcpRuntimeCatalogEntry(
entry: RawAcpRuntimeCatalogEntry,
): AcpRuntimeCatalogEntry {
return {
id: entry.id,
label: entry.label,
avatarUrl: entry.avatar_url,
availability: entry.availability,
command: entry.command,
binaryPath: entry.binary_path,
defaultArgs: entry.default_args,
mcpCommand: entry.mcp_command,
modelEnvVar: entry.model_env_var ?? null,
providerEnvVar: entry.provider_env_var ?? null,
thinkingEnvVar: entry.thinking_env_var ?? null,
maxTokensEnvVar: entry.max_tokens_env_var ?? null,
contextLimitEnvVar: entry.context_limit_env_var ?? null,
maxRoundsEnvVar: entry.max_rounds_env_var ?? null,
installHint: entry.install_hint,
installInstructionsUrl: entry.install_instructions_url,
canAutoInstall: entry.can_auto_install,
requiresExternalCli: entry.requires_external_cli ?? false,
underlyingCliPath: entry.underlying_cli_path,
nodeRequired: entry.node_required,
authStatus: entry.auth_status,
loginHint: entry.login_hint ?? null,
source: entry.source,
definitionEnv: entry.definition_env ?? {},
...(entry.max_parallelism !== undefined && {
maxParallelism: entry.max_parallelism,
}),
};
}
function fromRawCommandAvailability(
command: RawCommandAvailability,
): CommandAvailability {
return {
command: command.command,
resolvedPath: command.resolved_path,
available: command.available,
};
}
// ── Relay Members ────────────────────────────────────────────────────────────
function fromRawRelayMember(raw: RawRelayMember): RelayMember {
return {
pubkey: raw.pubkey,
role: raw.role as RelayMemberRole,
addedBy: raw.added_by,
createdAt: raw.created_at,
};
}
export async function listRelayMembers(): Promise<RelayMember[]> {
const response =
await invokeTauri<RawListRelayMembersResponse>("list_relay_members");
return response.members.map(fromRawRelayMember);
}
export async function getMyRelayMembership(): Promise<RelayMember | null> {
try {
const raw = await invokeTauri<RawRelayMember>("get_my_relay_membership");
return fromRawRelayMember(raw);
} catch (error) {
// "relay returned 404 Not Found" = not a relay member — return null so
// the UI hides the Members tab. Re-throw real errors (network, auth, 500)
// so React Query surfaces them.
if (
error instanceof Error &&
error.message.startsWith("relay returned 404")
) {
return null;
}
throw error;
}
}
export async function addRelayMember(
targetPubkey: string,
role: string,
): Promise<void> {
await invokeTauri("add_relay_member", { targetPubkey, role });
}
export async function removeRelayMember(targetPubkey: string): Promise<void> {
await invokeTauri("remove_relay_member", { targetPubkey });
}
export async function changeRelayMemberRole(
targetPubkey: string,
newRole: string,
): Promise<void> {
await invokeTauri("change_relay_member_role", { targetPubkey, newRole });
}
export async function listRelayAgents(): Promise<RelayAgent[]> {
return (await invokeTauri<RawRelayAgent[]>("list_relay_agents")).map(
fromRawRelayAgent,
);
}
export async function listManagedAgents(): Promise<ManagedAgent[]> {
return (await invokeTauri<RawManagedAgent[]>("list_managed_agents")).map(
fromRawManagedAgent,
);
}
export async function createManagedAgent(input: CreateManagedAgentInput) {
const response = await invokeTauri<RawCreateManagedAgentResponse>(
"create_managed_agent",
{
input: {
name: input.name,
personaId: input.personaId,
teamId: input.teamId,
relayUrl: input.relayUrl,
acpCommand: input.acpCommand,
agentCommand: input.agentCommand,
harnessOverride: input.harnessOverride ?? false,
agentArgs: input.agentArgs,
mcpCommand: input.mcpCommand,
turnTimeoutSeconds: input.turnTimeoutSeconds,
idleTimeoutSeconds: input.idleTimeoutSeconds,
maxTurnDurationSeconds: input.maxTurnDurationSeconds,
parallelism: input.parallelism,
systemPrompt: input.systemPrompt,
avatarUrl: input.avatarUrl,
model: input.model,
provider: input.provider,
envVars: input.envVars ?? {},
spawnAfterCreate: input.spawnAfterCreate,
startOnAppLaunch: input.startOnAppLaunch,
backend: input.backend,
respondTo: input.respondTo,
respondToAllowlist: input.respondToAllowlist,
relayMesh: input.relayMesh,
},
},
);
return {
agent: fromRawManagedAgent(response.agent),
privateKeyNsec: response.private_key_nsec,
profileSyncError: response.profile_sync_error,
spawnError: response.spawn_error,
};
}
export async function deleteManagedAgent(
pubkey: string,
forceRemoteDelete?: boolean,
): Promise<void> {
await invokeTauri("delete_managed_agent", {
pubkey,
forceRemoteDelete: forceRemoteDelete ?? null,
});
}
export async function getManagedAgentLog(pubkey: string, lineCount?: number) {
const response = await invokeTauri<RawManagedAgentLog>(
"get_managed_agent_log",
{
pubkey,
lineCount,
},
);
return {
content: response.content,
logPath: response.log_path,
};
}
export async function discoverGitBashPrerequisite(): Promise<GitBashPrerequisite | null> {
const prerequisite = await invokeTauri<RawGitBashPrerequisite | null>(
"discover_git_bash_prerequisite",
);
return (
prerequisite && {
available: prerequisite.available,
path: prerequisite.path,
installInstructionsUrl: prerequisite.install_instructions_url,
installHint: prerequisite.install_hint,
}
);
}
export async function discoverAcpRuntimes(): Promise<AcpRuntimeCatalogEntry[]> {
return (
await invokeTauri<RawAcpRuntimeCatalogEntry[]>("discover_acp_providers")
).map(fromRawAcpRuntimeCatalogEntry);
}
/** Input shape for creating or updating a custom harness. */
export type HarnessDefinitionInput = {
id: string;
label: string;
command: string;
args?: string[];
env?: Record<string, string>;
installInstructionsUrl?: string;
installHint?: string;
};
/** Save (create or overwrite) a custom harness definition. Returns the catalog entry. */
export async function saveCustomHarness(
definition: HarnessDefinitionInput,
originalId?: string,
): Promise<AcpRuntimeCatalogEntry> {
const raw = await invokeTauri<RawAcpRuntimeCatalogEntry>(
"save_custom_harness",
{
definition: {
id: definition.id,
label: definition.label,
command: definition.command,
args: definition.args ?? [],
env: definition.env ?? {},
installInstructionsUrl: definition.installInstructionsUrl ?? "",
installHint: definition.installHint ?? "",
},
originalId: originalId ?? null,
},
);
return fromRawAcpRuntimeCatalogEntry(raw);
}
/** Delete a custom harness definition by id. No-op if already gone. */
export async function deleteCustomHarness(id: string): Promise<void> {
await invokeTauri<void>("delete_custom_harness", { id });
}
export async function installAcpRuntime(
runtimeId: string,
): Promise<InstallRuntimeResult> {
const raw = await invokeTauri<RawInstallRuntimeResult>(
"install_acp_runtime",
{ runtimeId },
);
return fromRawInstallRuntimeResult(raw);
}
export async function discoverManagedAgentPrereqs(input: {
acpCommand?: string;
mcpCommand?: string;
}) {
const response = await invokeTauri<RawManagedAgentPrereqs>(
"discover_managed_agent_prereqs",
{
input: {
acpCommand: input.acpCommand,
mcpCommand: input.mcpCommand,
},
},
);
return {
acp: fromRawCommandAvailability(response.acp),
mcp: fromRawCommandAvailability(response.mcp),
};
}
// ── Model discovery ───────────────────────────────────────────────────────────
export async function getAgentModels(pubkey: string) {
return invokeTauri<AgentModelsResponse>("get_agent_models", { pubkey });
}
export async function getAgentConfigSurface(
pubkey: string,
): Promise<RuntimeConfigSurface> {
return invokeTauri<RuntimeConfigSurface>("get_agent_config_surface", {
pubkey,
});
}
export async function putAgentSessionConfig(
pubkey: string,
payload: unknown,
): Promise<void> {
return invokeTauri<void>("put_agent_session_config", { pubkey, payload });
}
/** File-layer config for a runtime (e.g. `~/.config/goose/config.yaml`). */
export type RuntimeFileConfigSubset = {
/** Provider set in the harness config file. */
provider: string | null;
/** Model set in the harness config file. */
model: string | null;
/** Credential env key names whose values are present in the file config. */
satisfiedEnvKeys: string[];
};
/**
* Get the file-layer config for a runtime so dialogs can show "Set in goose config" instead of
* surfacing a false required-field marker. Returns `null` when unavailable or unparseable.
*/
export async function getRuntimeFileConfig(
runtimeId: string,
): Promise<RuntimeFileConfigSubset | null> {
return invokeTauri<RuntimeFileConfigSubset | null>(
"get_runtime_file_config",
{
runtimeId,
},
);
}
/**
* Return the key names of all non-empty baked build env vars. Internal (Block) builds bake
* provider credentials into the binary at compile time; this returns *key names only* (never
* values) so dialogs treat them as satisfied without exposing secrets. OSS builds return [].
*/
export async function getBakedBuildEnvKeys(): Promise<string[]> {
return invokeTauri<string[]>("get_baked_build_env_keys");
}
/**
* A single baked build env entry.
*
* The value is already masked in Rust for secret keys (keys not in the
* explicit safe-to-reveal allowlist: `BUZZ_AGENT_PROVIDER`, `BUZZ_AGENT_MODEL`,
* `DATABRICKS_HOST`, `DATABRICKS_MODEL`). Non-allowlisted keys have their
* values replaced with `••••••`. Non-secret values are shown as-is.
* Empty-value keys are filtered out.
*/
export type BakedEnvEntry = {
key: string;
/** Display value — real value or `••••••` for masked keys. */
value: string;
/** `true` when the value was replaced by the mask placeholder in Rust. */
masked: boolean;
};
/**
* Return the baked build env entries with values shown (masked where
* appropriate) for display in the Agent defaults card.
*
* Provider and model arrive as `BUZZ_AGENT_PROVIDER` / `BUZZ_AGENT_MODEL`
* keys and are included in the list alongside other baked vars.
*
* OSS builds return an empty array — the baked-env section is hidden.
*/
export async function getBakedBuildEnv(): Promise<BakedEnvEntry[]> {
return invokeTauri<BakedEnvEntry[]>("get_baked_build_env");
}
type RawUpdateManagedAgentResponse = {
agent: RawManagedAgent;
profile_sync_error: string | null;
};
export async function updateManagedAgent(
input: UpdateManagedAgentInput,
): Promise<{ agent: ManagedAgent; profileSyncError: string | null }> {
const response = await invokeTauri<RawUpdateManagedAgentResponse>(
"update_managed_agent",
{ input },
);
return {
agent: fromRawManagedAgent(response.agent),
profileSyncError: response.profile_sync_error,
};
}
// ── Backend provider discovery ────────────────────────────────────────────────
export async function discoverBackendProviders(): Promise<
BackendProviderCandidate[]
> {
return invokeTauri<BackendProviderCandidate[]>("discover_backend_providers");
}
export async function probeBackendProvider(
binaryPath: string,
): Promise<BackendProviderProbeResult> {
return invokeTauri<BackendProviderProbeResult>("probe_backend_provider", {
binaryPath,
});
}
// ── NIP-44 encrypt-to-self ───────────────────────────────────────────────────
export async function nip44EncryptToSelf(plaintext: string): Promise<string> {
return invokeTauri<string>("nip44_encrypt_to_self", { plaintext });
}
export async function nip44DecryptFromSelf(
ciphertext: string,
): Promise<string> {
return invokeTauri<string>("nip44_decrypt_from_self", { ciphertext });
}
export async function startPairing(): Promise<string> {
return invokeTauri<string>("start_pairing");
}
export async function confirmPairingSas(): Promise<void> {
await invokeTauri("confirm_pairing_sas");
}
export async function cancelPairing(): Promise<void> {
await invokeTauri("cancel_pairing");
}
export async function applyCommunity(
relayUrl: string,
nsec?: string,
token?: string,
reposDir?: string,
agentManagedProfiles?: boolean,
): Promise<void> {
await invokeTauri("apply_workspace", {
relayUrl,
nsec: nsec ?? null,
token: token ?? null,
reposDir: reposDir ?? null,
agentManagedProfiles: agentManagedProfiles ?? false,
});
}
// Validate a candidate repos dir without mutating the filesystem. Rejects
// with a human-readable reason; resolves for a valid or empty path.
export async function validateReposDir(dir: string): Promise<void> {
await invokeTauri("validate_repos_dir", { dir });
}
export const setPreventSleepActive = (active: boolean) =>
invokeTauri("set_prevent_sleep_active", { active });
export const setAgentManagedProfiles = (enabled: boolean) =>
invokeTauri("set_agent_managed_profiles", { enabled });
/** Returns true on macOS, Windows, and Linux AppImage installs.
* Returns false on Linux non-AppImage packages (e.g. .deb) where
* Tauri's updater cannot swap the binary. */
export function isAutoUpdateSupported(): Promise<boolean> {
return invokeTauri<boolean>("is_auto_update_supported");
}