Files
buzz/desktop/src/shared/api/tauri.ts
T

1212 lines
30 KiB
TypeScript

import { invoke as tauriInvoke } from "@tauri-apps/api/core";
import type {
AddChannelMembersInput,
AddChannelMembersResult,
BackendProviderCandidate,
BackendProviderProbeResult,
CanvasResponse,
Channel,
ChannelDetail,
ChannelMember,
ChannelType,
CreateChannelInput,
GetHomeFeedInput,
HomeFeedResponse,
Identity,
MintTokenInput,
MintTokenResponse,
MintManagedAgentTokenInput,
ManagedAgent,
ManagedAgentBackend,
RelayAgent,
RelayMember,
RelayMemberRole,
PresenceLookup,
PresenceStatus,
Profile,
RelayEvent,
SearchMessagesInput,
SearchMessagesResponse,
SendChannelMessageResult,
SetCanvasInput,
SetCanvasResult,
SetPresenceResult,
SetChannelPurposeInput,
SetChannelTopicInput,
Token,
TokenScope,
UpdateProfileInput,
UpdateChannelInput,
UserProfileSummary,
UserSearchResult,
UsersBatchResponse,
CreateManagedAgentInput,
AgentModelsResponse,
UpdateManagedAgentInput,
AcpProvider,
CommandAvailability,
OpenDmInput,
} from "@/shared/api/types";
type RawIdentity = {
pubkey: string;
display_name: string;
};
type RawProfile = {
pubkey: string;
display_name: string | null;
avatar_url: string | null;
about: string | null;
nip05_handle: string | null;
};
type RawUserProfileSummary = {
display_name: string | null;
avatar_url: string | null;
nip05_handle: string | null;
};
type RawUsersBatchResponse = {
profiles: Record<string, RawUserProfileSummary>;
missing: string[];
};
type RawUserSearchResult = {
pubkey: string;
display_name: string | null;
avatar_url: string | null;
nip05_handle: string | null;
};
type RawSearchUsersResponse = {
users: RawUserSearchResult[];
};
type RawPresenceLookup = Record<string, PresenceStatus>;
type RawSetPresenceResult = {
status: PresenceStatus;
ttl_seconds: number;
};
type RawChannel = {
id: string;
name: string;
channel_type: ChannelType;
visibility: "open" | "private";
description: string;
topic: string | null;
purpose: string | null;
member_count: number;
last_message_at: string | null;
archived_at: string | null;
participants: string[];
participant_pubkeys: string[];
is_member?: boolean;
ttl_seconds: number | null;
ttl_deadline: string | null;
};
type RawChannelDetail = RawChannel & {
created_by: string;
created_at: string;
updated_at: string;
topic_set_by: string | null;
topic_set_at: string | null;
purpose_set_by: string | null;
purpose_set_at: string | null;
topic_required: boolean;
max_members: number | null;
nip29_group_id: string | null;
};
type RawChannelMember = {
pubkey: string;
role: ChannelMember["role"];
joined_at: string;
display_name: string | null;
};
type RawChannelMembersResponse = {
members: RawChannelMember[];
next_cursor: string | null;
};
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;
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 RawToken = {
id: string;
name: string;
scopes: TokenScope[];
channel_ids: string[];
created_at: string;
expires_at: string | null;
last_used_at: string | null;
revoked_at: string | null;
};
type RawListTokensResponse = {
tokens: RawToken[];
};
type RawMintTokenResponse = {
id: string;
token: string;
name: string;
scopes: TokenScope[];
channel_ids: string[];
created_at: string;
expires_at: string | null;
};
type RawRelayAgent = {
pubkey: string;
name: string;
agent_type: string;
channels: string[];
channel_ids: string[];
capabilities: string[];
status: RelayAgent["status"];
};
export type RawManagedAgent = {
pubkey: string;
name: string;
persona_id: string | null;
relay_url: string;
acp_command: string;
agent_command: string;
agent_args: string[];
mcp_command: string;
turn_timeout_seconds: number;
idle_timeout_seconds: number | null;
max_turn_duration_seconds: number | null;
parallelism: number;
system_prompt: string | null;
model: string | null;
mcp_toolsets: string | null;
has_api_token: boolean;
status: ManagedAgent["status"];
pid: number | null;
created_at: string;
updated_at: string;
last_started_at: string | null;
last_stopped_at: string | null;
last_exit_code: number | null;
last_error: string | null;
log_path: string;
start_on_app_launch: boolean;
backend: ManagedAgentBackend;
backend_agent_id: string | null;
};
type RawCreateManagedAgentResponse = {
agent: RawManagedAgent;
private_key_nsec: string;
api_token: string | null;
profile_sync_error: string | null;
spawn_error: string | null;
};
type RawMintManagedAgentTokenResponse = {
agent: RawManagedAgent;
token: string;
};
type RawManagedAgentLog = {
content: string;
log_path: string;
};
type RawAcpProvider = {
id: string;
label: string;
command: string;
binary_path: string;
default_args: 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;
};
function toTauriError(error: unknown): Error {
if (error instanceof Error) {
return error;
}
if (typeof error === "string") {
return new Error(error);
}
if (
typeof error === "object" &&
error !== null &&
"message" in error &&
typeof error.message === "string"
) {
return new Error(error.message);
}
try {
return new Error(JSON.stringify(error));
} catch {
return new Error("Unknown Tauri error");
}
}
export async function invokeTauri<T>(
command: string,
args?: Record<string, unknown>,
): Promise<T> {
try {
return await tauriInvoke<T>(command, args);
} catch (error) {
throw toTauriError(error);
}
}
function fromRawChannel(channel: RawChannel): Channel {
return {
id: channel.id,
name: channel.name,
channelType: channel.channel_type,
visibility: channel.visibility,
description: channel.description,
topic: channel.topic,
purpose: channel.purpose,
memberCount: channel.member_count,
lastMessageAt: channel.last_message_at,
archivedAt: channel.archived_at,
participants: channel.participants,
participantPubkeys: channel.participant_pubkeys,
isMember: channel.is_member ?? true,
ttlSeconds: channel.ttl_seconds,
ttlDeadline: channel.ttl_deadline,
};
}
function fromRawChannelDetail(channel: RawChannelDetail): ChannelDetail {
return {
...fromRawChannel(channel),
createdBy: channel.created_by,
createdAt: channel.created_at,
updatedAt: channel.updated_at,
topicSetBy: channel.topic_set_by,
topicSetAt: channel.topic_set_at,
purposeSetBy: channel.purpose_set_by,
purposeSetAt: channel.purpose_set_at,
topicRequired: channel.topic_required,
maxMembers: channel.max_members,
nip29GroupId: channel.nip29_group_id,
};
}
function fromRawChannelMember(member: RawChannelMember): ChannelMember {
return {
pubkey: member.pubkey,
role: member.role,
joinedAt: member.joined_at,
displayName: member.display_name,
};
}
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,
channelType: item.channel_type,
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,
};
}
function fromRawProfile(profile: RawProfile): Profile {
return {
pubkey: profile.pubkey,
displayName: profile.display_name,
avatarUrl: profile.avatar_url,
about: profile.about,
nip05Handle: profile.nip05_handle,
};
}
function fromRawUserProfileSummary(
profile: RawUserProfileSummary,
): UserProfileSummary {
return {
displayName: profile.display_name,
avatarUrl: profile.avatar_url,
nip05Handle: profile.nip05_handle,
};
}
function fromRawUserSearchResult(user: RawUserSearchResult): UserSearchResult {
return {
pubkey: user.pubkey,
displayName: user.display_name,
avatarUrl: user.avatar_url,
nip05Handle: user.nip05_handle,
};
}
export async function getIdentity(): Promise<Identity> {
const identity = await invokeTauri<RawIdentity>("get_identity");
return {
pubkey: identity.pubkey,
displayName: identity.display_name,
};
}
export async function getNsec(): Promise<string> {
return invokeTauri<string>("get_nsec");
}
export async function importIdentity(nsec: string): Promise<Identity> {
const raw = await invokeTauri<RawIdentity>("import_identity", { nsec });
return {
pubkey: raw.pubkey,
displayName: raw.display_name,
};
}
export async function getProfile(): Promise<Profile> {
const profile = await invokeTauri<RawProfile>("get_profile");
return fromRawProfile(profile);
}
export async function updateProfile(
input: UpdateProfileInput,
): Promise<Profile> {
const profile = await invokeTauri<RawProfile>("update_profile", input);
return fromRawProfile(profile);
}
export async function getUserProfile(pubkey?: string): Promise<Profile> {
const profile = await invokeTauri<RawProfile>("get_user_profile", { pubkey });
return fromRawProfile(profile);
}
export async function getUsersBatch(
pubkeys: string[],
): Promise<UsersBatchResponse> {
const response = await invokeTauri<RawUsersBatchResponse>("get_users_batch", {
pubkeys,
});
return {
profiles: Object.fromEntries(
Object.entries(response.profiles).map(([pubkey, profile]) => [
pubkey,
fromRawUserProfileSummary(profile),
]),
),
missing: response.missing,
};
}
export async function searchUsers(
query: string,
limit = 8,
): Promise<UserSearchResult[]> {
const response = await invokeTauri<RawSearchUsersResponse>("search_users", {
query,
limit,
});
return response.users.map(fromRawUserSearchResult);
}
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 async function setPresence(
status: PresenceStatus,
): Promise<SetPresenceResult> {
const response = await invokeTauri<RawSetPresenceResult>("set_presence", {
status,
});
return {
status: response.status,
ttlSeconds: response.ttl_seconds,
};
}
export function getDefaultRelayUrl(): Promise<string> {
return invokeTauri<string>("get_default_relay_url");
}
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 getChannels(): Promise<Channel[]> {
const channels = await invokeTauri<RawChannel[]>("get_channels");
return channels.map(fromRawChannel);
}
export async function createChannel(
input: CreateChannelInput,
): Promise<Channel> {
return fromRawChannel(await invokeTauri<RawChannel>("create_channel", input));
}
export async function openDm(input: OpenDmInput): Promise<Channel> {
return fromRawChannel(await invokeTauri<RawChannel>("open_dm", input));
}
export async function hideDm(channelId: string): Promise<void> {
await invokeTauri<void>("hide_dm", { channelId });
}
export async function getChannelDetails(
channelId: string,
): Promise<ChannelDetail> {
const channel = await invokeTauri<RawChannelDetail>("get_channel_details", {
channelId,
});
return fromRawChannelDetail(channel);
}
export async function getChannelMembers(
channelId: string,
): Promise<ChannelMember[]> {
const response = await invokeTauri<RawChannelMembersResponse>(
"get_channel_members",
{
channelId,
},
);
return response.members.map(fromRawChannelMember);
}
export async function updateChannel(
input: UpdateChannelInput,
): Promise<ChannelDetail> {
const channel = await invokeTauri<RawChannelDetail>("update_channel", input);
return fromRawChannelDetail(channel);
}
export async function setChannelTopic(
input: SetChannelTopicInput,
): Promise<void> {
await invokeTauri("set_channel_topic", input);
}
export async function setChannelPurpose(
input: SetChannelPurposeInput,
): Promise<void> {
await invokeTauri("set_channel_purpose", input);
}
export async function archiveChannel(channelId: string): Promise<void> {
await invokeTauri("archive_channel", { channelId });
}
export async function unarchiveChannel(channelId: string): Promise<void> {
await invokeTauri("unarchive_channel", { channelId });
}
export async function deleteChannel(channelId: string): Promise<void> {
await invokeTauri("delete_channel", { channelId });
}
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,
updatedAt: response.updated_at,
author: response.author,
};
}
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,
});
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;
}
export async function sendChannelMessage(
channelId: string,
content: string,
parentEventId?: string | null,
mediaTags?: string[][],
mentionPubkeys?: string[],
kind?: number,
): Promise<SendChannelMessageResult> {
const response = await invokeTauri<RawSendChannelMessageResult>(
"send_channel_message",
{
channelId,
content,
parentEventId,
mediaTags: mediaTags ?? null,
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;
};
export async function uploadMedia(
filePath: string,
isTemp: boolean,
): Promise<BlobDescriptor> {
return invokeTauri<BlobDescriptor>("upload_media", {
filePath,
isTemp,
});
}
export async function pickAndUploadMedia(): Promise<BlobDescriptor | null> {
return invokeTauri<BlobDescriptor | null>("pick_and_upload_media", {});
}
export async function uploadMediaBytes(
data: number[],
): Promise<BlobDescriptor> {
return invokeTauri<BlobDescriptor>("upload_media_bytes", { data });
}
export async function editMessage(
channelId: string,
eventId: string,
content: string,
): Promise<void> {
await invokeTauri("edit_message", { channelId, eventId, content });
}
export async function deleteMessage(eventId: string): Promise<void> {
await invokeTauri("delete_message", { eventId });
}
export async function addReaction(
eventId: string,
emoji: string,
): Promise<void> {
await invokeTauri("add_reaction", { eventId, emoji });
}
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 fromRawToken(token: RawToken): Token {
return {
id: token.id,
name: token.name,
scopes: token.scopes,
channelIds: token.channel_ids,
createdAt: token.created_at,
expiresAt: token.expires_at,
lastUsedAt: token.last_used_at,
revokedAt: token.revoked_at,
};
}
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,
};
}
export function fromRawManagedAgent(agent: RawManagedAgent): ManagedAgent {
return {
pubkey: agent.pubkey,
name: agent.name,
personaId: agent.persona_id,
relayUrl: agent.relay_url,
acpCommand: agent.acp_command,
agentCommand: agent.agent_command,
agentArgs: agent.agent_args,
mcpCommand: agent.mcp_command,
turnTimeoutSeconds: agent.turn_timeout_seconds,
idleTimeoutSeconds: agent.idle_timeout_seconds,
maxTurnDurationSeconds: agent.max_turn_duration_seconds,
parallelism: agent.parallelism,
systemPrompt: agent.system_prompt,
model: agent.model,
mcpToolsets: agent.mcp_toolsets,
hasApiToken: agent.has_api_token,
status: agent.status,
pid: agent.pid,
createdAt: agent.created_at,
updatedAt: agent.updated_at,
lastStartedAt: agent.last_started_at,
lastStoppedAt: agent.last_stopped_at,
lastExitCode: agent.last_exit_code,
lastError: agent.last_error,
logPath: agent.log_path,
startOnAppLaunch: agent.start_on_app_launch,
backend: agent.backend,
backendAgentId: agent.backend_agent_id,
};
}
function fromRawAcpProvider(provider: RawAcpProvider): AcpProvider {
return {
id: provider.id,
label: provider.label,
command: provider.command,
binaryPath: provider.binary_path,
defaultArgs: provider.default_args,
};
}
function fromRawCommandAvailability(
command: RawCommandAvailability,
): CommandAvailability {
return {
command: command.command,
resolvedPath: command.resolved_path,
available: command.available,
};
}
export async function listTokens(): Promise<Token[]> {
const response = await invokeTauri<RawListTokensResponse>("list_tokens");
return response.tokens.map(fromRawToken);
}
export async function mintToken(
input: MintTokenInput,
): Promise<MintTokenResponse> {
const response = await invokeTauri<RawMintTokenResponse>("mint_token", {
name: input.name,
scopes: input.scopes,
channelIds: input.channelIds,
expiresInDays: input.expiresInDays,
});
return {
id: response.id,
token: response.token,
name: response.name,
scopes: response.scopes,
channelIds: response.channel_ids,
createdAt: response.created_at,
expiresAt: response.expires_at,
};
}
export async function revokeToken(tokenId: string): Promise<void> {
await invokeTauri("revoke_token", { tokenId });
}
export async function revokeAllTokens(): Promise<{ revokedCount: number }> {
const response = await invokeTauri<{ revoked_count: number }>(
"revoke_all_tokens",
);
return { revokedCount: response.revoked_count };
}
// ── 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,
relayUrl: input.relayUrl,
acpCommand: input.acpCommand,
agentCommand: input.agentCommand,
agentArgs: input.agentArgs,
mcpCommand: input.mcpCommand,
mcpToolsets: input.mcpToolsets,
turnTimeoutSeconds: input.turnTimeoutSeconds,
idleTimeoutSeconds: input.idleTimeoutSeconds,
maxTurnDurationSeconds: input.maxTurnDurationSeconds,
parallelism: input.parallelism,
systemPrompt: input.systemPrompt,
avatarUrl: input.avatarUrl,
model: input.model,
mintToken: input.mintToken,
tokenScopes: input.tokenScopes,
tokenName: input.tokenName,
spawnAfterCreate: input.spawnAfterCreate,
startOnAppLaunch: input.startOnAppLaunch,
backend: input.backend,
},
},
);
return {
agent: fromRawManagedAgent(response.agent),
privateKeyNsec: response.private_key_nsec,
apiToken: response.api_token,
profileSyncError: response.profile_sync_error,
spawnError: response.spawn_error,
};
}
export async function startManagedAgent(pubkey: string): Promise<ManagedAgent> {
const response = await invokeTauri<RawManagedAgent>("start_managed_agent", {
pubkey,
});
return fromRawManagedAgent(response);
}
export async function stopManagedAgent(pubkey: string): Promise<ManagedAgent> {
const response = await invokeTauri<RawManagedAgent>("stop_managed_agent", {
pubkey,
});
return fromRawManagedAgent(response);
}
export async function deleteManagedAgent(
pubkey: string,
forceRemoteDelete?: boolean,
): Promise<void> {
await invokeTauri("delete_managed_agent", {
pubkey,
forceRemoteDelete: forceRemoteDelete ?? null,
});
}
export async function mintManagedAgentToken(input: MintManagedAgentTokenInput) {
const response = await invokeTauri<RawMintManagedAgentTokenResponse>(
"mint_managed_agent_token",
{
input: {
pubkey: input.pubkey,
tokenName: input.tokenName,
scopes: input.scopes,
},
},
);
return {
agent: fromRawManagedAgent(response.agent),
token: response.token,
};
}
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 discoverAcpProviders(): Promise<AcpProvider[]> {
return (await invokeTauri<RawAcpProvider[]>("discover_acp_providers")).map(
fromRawAcpProvider,
);
}
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 });
}
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 });
}
// ── NIP-AB device pairing ───────────────────────────────────────────────────
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 applyWorkspace(
relayUrl: string,
nsec?: string,
token?: string,
): Promise<void> {
await invokeTauri("apply_workspace", {
relayUrl,
nsec: nsec ?? null,
token: token ?? null,
});
}