import { eventIterator, oc } from "@orpc/contract"; import * as z from "zod"; import { AiConsentQuerySchema, AiConsentStatusSchema } from "./ai-consent.js"; import { ATTACHMENT_MAX_BASE64_LENGTH, ATTACHMENT_MAX_COUNT } from "./attachments.js"; import { ActionApprovalRuleSchema, ActionAutoReviewSettingsSchema, AgentSecretInputSchema, AgentSecretSchema, AgentSkillCatalogEntrySchema, AgentSkillSchema, AppBootstrapSchema, ArtifactSchema, ArtifactWithContentSchema, AvatarStyleSchema, BotMcpServerSchema, BotSchema, BotSectionSchema, CapabilityInstallSchema, ComputerModeSchema, ComputerReleaseReasonSchema, ComputerStatusSchema, ComputerUpdateSchema, ConnectionCatalogItemSchema, ConnectionSchema, CreateAgentSkillInput, CreateBotInput, CreateGroupInput, CreateRoutineInput, CreateScratchpadItemInput, DelegatedTaskSchema, DeploymentSettingsSchema, ExportManifestSchema, ExternalConversationPolicySchema, GroupDetailSchema, GroupSchema, IntegrationCatalogResultSchema, McpServerConfigInput, McpServerSchema, MemoryDocumentSchema, MemoryScopeSchema, MeSchema, MessagingAgentConnectionSchema, MessagingChannelMembershipSchema, MessagingLinkedIdentitySchema, MessagingStatusSchema, ModelCatalogEntrySchema, ModelConnectInputSchema, ModelCredentialSchema, ModelOAuthBeginSchema, REPLY_QUOTE_MAX_LENGTH, ReorderBotsInput, RoutineSchema, ScratchpadItemSchema, ScratchpadItemStatusSchema, ServerUpdateCheckSchema, ServerUpdateRequestSchema, ServerUpdateRunSchema, ServerUpdateStatusSchema, SkillPlaybookSchema, SpaceMemoryConfigSchema, SpaceNavigationSchema, SpaceSchema, TaughtSkillSchema, TeachRecordingEventSchema, ThreadMessagePageSchema, ThreadSnapshotSchema, UpdateAgentSkillInput, UpdateBotInput, UpdateExternalConversationPolicyInput, UpdateGroupInput, UsageRecordSchema, VoiceCatalogEntrySchema, VoiceCredentialSchema, VoiceInfoSchema, VoiceStatusSchema, } from "./domain.js"; import { DelegatedTaskStatusSchema, ProductEventSchema } from "./events.js"; import { Id, IsoDate } from "./ids.js"; import { InboxListOutputSchema } from "./inbox.js"; import { IntegrationProviderConfigSchema, IntegrationSetupStateSchema, } from "./integration-settings.js"; import { mediaRpcShapes } from "./media.js"; import { ModelTierMappingSchema, SetModelTierInput } from "./model-tiers.js"; import { CreateProjectGoalInput, ProjectGoalSchema, UpdateProjectGoalInput, } from "./project-goals.js"; import { ProjectSessionsOutputSchema } from "./project-sessions.js"; import { MessageReactionSchema } from "./reactions.js"; import { RunsListOutputSchema } from "./runs.js"; import { SearchQueryOutputSchema } from "./search.js"; import { ForgetUserFactsInput, UpdateUserProfileInput, UserProfileStateSchema, } from "./user-profile.js"; const botId = z.object({ botId: Id }); const groupId = z.object({ groupId: Id }); const threadTarget = z .object({ botId: Id.optional(), groupId: Id.optional(), }) .superRefine((input, ctx) => { const hasBot = Boolean(input.botId); const hasGroup = Boolean(input.groupId); if (hasBot === hasGroup) { ctx.addIssue({ code: "custom", message: "Provide exactly one of botId or groupId", path: ["botId"], }); } }); const structuredMentionTarget = z.discriminatedUnion("kind", [ z.object({ kind: z.literal("bot"), id: Id }), z.object({ kind: z.literal("group"), id: Id }), z.object({ kind: z.literal("routine"), id: Id }), z.object({ kind: z.literal("connector"), id: Id }), ]); const threadSendInput = threadTarget .safeExtend({ text: z.string().optional(), artifactIds: z.array(Id).max(ATTACHMENT_MAX_COUNT).optional(), /** Bare bot ids (legacy) or typed mention chips from the composer. */ mentions: z .array(z.union([Id, structuredMentionTarget])) .max(64) .optional(), replyToMessageId: Id.optional(), replyQuote: z.string().trim().min(1).max(REPLY_QUOTE_MAX_LENGTH).optional(), clientNonce: z.string().min(1).max(200).optional(), }) .superRefine((input, ctx) => { const text = input.text?.trim() ?? ""; const artifactIds = input.artifactIds ?? []; if (!text && artifactIds.length === 0) { ctx.addIssue({ code: "custom", message: "Provide text or at least one attachment", path: ["text"], }); } if (input.replyQuote && !input.replyToMessageId) { ctx.addIssue({ code: "custom", message: "replyQuote requires replyToMessageId", path: ["replyQuote"], }); } }); function rpc(shape: { input: I; output: O }) { return oc.input(shape.input).output(shape.output); } export const appContract = { aiConsent: { status: oc.input(AiConsentQuerySchema).output(AiConsentStatusSchema), allow: oc .input( z.object({ scope: z.string(), version: z.string(), keys: z.array(z.string()).min(1).max(200), }), ) .output(AiConsentStatusSchema), revoke: oc.input(z.object({ key: z.string().nullable() })).output(AiConsentStatusSchema), }, health: oc.output(z.object({ ok: z.literal(true), version: z.string() })), me: oc.output(MeSchema), preferences: { update: oc.input(z.object({ avatarStyle: AvatarStyleSchema })).output(MeSchema), }, /** What the user's bots know about the user, across spaces. */ profile: { get: oc.output(UserProfileStateSchema), update: oc.input(UpdateUserProfileInput).output(UserProfileStateSchema), forget: oc.input(ForgetUserFactsInput).output(UserProfileStateSchema), }, spaces: { list: oc.output(SpaceNavigationSchema), create: oc .input( z.object({ name: z.string().trim().min(1).max(60), workspacePath: z.string().trim().min(1).max(500).optional(), }), ) .output(SpaceSchema), remove: oc .input(z.object({ spaceId: Id })) .output(z.object({ ok: z.literal(true), activeSpaceId: Id })), }, bootstrap: oc.input(z.object({ botId: Id.optional() })).output(AppBootstrapSchema), deployment: { get: oc.output(DeploymentSettingsSchema), update: oc .input( z.object({ signupsEnabled: z.boolean().optional(), signupAllowlist: z.array(z.string()).optional(), computerHost: z.enum(["docker", "this-mac"]).nullable().optional(), }), ) .output(DeploymentSettingsSchema), }, /** * Deployment-owner product updates. When the Compose updater sidecar is reachable, these proxy * to its `/state` `/plan` `/apply` contract. Rollback stays on the sidecar for ops only and is * not exposed here. Never git-fetch from the API process. */ updater: { status: oc.output(ServerUpdateStatusSchema), check: oc.input(ServerUpdateRequestSchema).output(ServerUpdateCheckSchema), apply: oc.input(ServerUpdateRequestSchema).output(ServerUpdateRunSchema), }, models: { list: oc.output(z.array(ModelCatalogEntrySchema)), credentials: oc.output(z.array(ModelCredentialSchema)), connect: oc.input(ModelConnectInputSchema).output(ModelCredentialSchema), probeOpenAiCompatible: oc .input( z.union([ z.object({ baseUrl: z.string(), apiKey: z.string().optional(), }), // A connected server, probed with its stored address and key. z.object({ credentialId: Id }), ]), ) .output(z.object({ models: z.array(z.string()) })), beginOAuth: oc .input( z.object({ provider: z.string(), label: z.string().optional(), modelId: z.string().optional(), }), ) .output(ModelOAuthBeginSchema), submitOAuthCode: oc .input(z.object({ loginId: z.string(), code: z.string().trim().min(1).max(8_192) })) .output(z.object({ ok: z.literal(true) })), completeOAuth: oc .input(z.object({ loginId: z.string() })) .output( z.discriminatedUnion("status", [ z.object({ status: z.literal("pending") }), z.object({ status: z.literal("ready") }), z.object({ status: z.literal("error"), error: z.string() }), ]), ), finishOAuth: oc.input(z.object({ loginId: z.string() })).output(ModelCredentialSchema), cancelOAuth: oc .input(z.object({ loginId: z.string() })) .output(z.object({ ok: z.literal(true) })), setDefault: oc .input(z.object({ provider: z.string(), modelId: z.string() })) .output(z.object({ ok: z.literal(true) })), tiers: { list: oc.output(z.array(ModelTierMappingSchema)), set: oc.input(SetModelTierInput).output(z.array(ModelTierMappingSchema)), }, }, bots: { list: oc.output(z.array(BotSchema)), listArchived: oc.output(z.array(BotSchema)), get: oc.input(botId).output(BotSchema), create: oc.input(CreateBotInput).output(BotSchema), /** The user's main bot, created with its main conversation in the home space when missing. */ ensureMain: oc.output(BotSchema), duplicate: oc.input(botId).output(BotSchema), reorder: oc.input(ReorderBotsInput).output(z.object({ ok: z.literal(true) })), update: oc.input(UpdateBotInput).output(BotSchema), /** Builtin tool names this bot's allowlist can choose from. */ builtinTools: oc.input(botId).output(z.array(z.string())), setComputer: oc.input(z.object({ botId: Id, mode: ComputerModeSchema })).output(BotSchema), archive: oc.input(botId).output(z.object({ ok: z.literal(true) })), restore: oc.input(botId).output(z.object({ ok: z.literal(true) })), remove: oc .input(z.object({ botId: Id, deleteMemories: z.boolean().default(false) })) .output(z.object({ ok: z.literal(true) })), rotateWebhookSecret: oc.input(botId).output( z.object({ secret: z.string(), path: z.string(), webhookConfigured: z.literal(true), }), ), }, groups: { create: oc.input(CreateGroupInput).output(GroupSchema), list: oc.output(z.array(GroupSchema)), listArchived: oc.output(z.array(GroupSchema)), get: oc.input(groupId).output(GroupDetailSchema), listSessions: oc.input(groupId).output(ProjectSessionsOutputSchema), duplicate: oc.input(groupId).output(GroupSchema), update: oc.input(UpdateGroupInput).output(GroupSchema), archive: oc.input(groupId).output(z.object({ ok: z.literal(true) })), restore: oc.input(groupId).output(z.object({ ok: z.literal(true) })), remove: oc.input(groupId).output(z.object({ ok: z.literal(true) })), }, botSections: { list: oc.output(z.array(BotSectionSchema)), create: oc .input(threadTarget.safeExtend({ name: z.string().trim().min(1).max(60) })) .output(BotSectionSchema), update: oc .input(z.object({ sectionId: Id, name: z.string().trim().min(1).max(60) })) .output(BotSectionSchema), }, projectGoals: { list: oc .input( z.object({ sectionId: Id, groupId: Id.optional(), seedDefaults: z.boolean().optional(), }), ) .output(z.array(ProjectGoalSchema)), create: oc.input(CreateProjectGoalInput).output(ProjectGoalSchema), update: oc.input(UpdateProjectGoalInput).output(ProjectGoalSchema), }, threads: { head: oc.input(threadTarget).output( z.object({ threadId: Id, cursor: z.number().int().min(-1), }), ), get: oc.input(threadTarget).output(ThreadSnapshotSchema), messages: oc .input( threadTarget.safeExtend({ before: z.number().int().nonnegative().optional(), includePeerRuns: z.boolean().optional(), includePeerReceipts: z.boolean().optional(), around: z .object({ messageId: Id.optional(), seq: z.number().int().nonnegative().optional(), }) .optional(), }), ) .output(ThreadMessagePageSchema), subscribe: oc .input(threadTarget.safeExtend({ cursor: z.number().int().min(-1) })) .output(eventIterator(ProductEventSchema)), send: oc.input(threadSendInput).output( z.object({ taskId: Id, runId: Id, seq: z.number().int(), runIds: z.array(Id).optional(), }), ), react: oc .input( threadTarget.safeExtend({ messageId: Id, reaction: MessageReactionSchema, clientNonce: z.string().min(1).max(200), }), ) .output(z.object({ ok: z.literal(true) })), stop: oc.input(threadTarget).output(z.object({ ok: z.literal(true) })), followUp: oc .input(threadTarget.safeExtend({ text: z.string().min(1) })) .output(z.object({ ok: z.literal(true) })), clear: oc.input(threadTarget).output(z.object({ ok: z.literal(true) })), answer: oc .input( threadTarget.safeExtend({ runId: Id, messageId: Id, answer: z.string().min(1), }), ) .output(z.object({ ok: z.literal(true) })), markRead: oc.input(threadTarget).output(z.object({ ok: z.literal(true) })), markUnread: oc.input(threadTarget).output(z.object({ ok: z.literal(true) })), }, computer: { status: oc.input(botId).output(ComputerStatusSchema), boot: oc.input(botId).output(ComputerStatusSchema), stop: oc.input(botId).output(ComputerStatusSchema), recover: oc.input(botId).output(ComputerUpdateSchema), reset: oc.input(botId).output(ComputerStatusSchema), update: oc.input(botId).output(ComputerUpdateSchema), updates: oc.output(z.array(ComputerUpdateSchema)), releaseInterrupted: oc .input(z.object({ id: Id, workersStopped: z.literal(true) })) .output(z.object({ ok: z.literal(true) })), dismissUpdate: oc.input(z.object({ id: Id })).output(z.object({ ok: z.literal(true) })), takeover: oc.input(botId).output(z.object({ leaseId: Id, expiresAt: z.string() })), release: oc .input( z.object({ botId: Id, reason: ComputerReleaseReasonSchema.optional(), }), ) .output(z.object({ ok: z.literal(true) })), input: oc .input( z.object({ botId: Id, kind: z.enum(["key", "pointer", "clipboard", "scroll"]), payload: z.record(z.string(), z.unknown()), }), ) .output(z.object({ ok: z.literal(true) })), files: oc .input(z.object({ botId: Id, path: z.string().default("/") })) .output( z.array(z.object({ path: z.string(), kind: z.enum(["file", "dir"]), size: z.number() })), ), readFile: oc .input(z.object({ botId: Id, path: z.string() })) .output(z.object({ path: z.string(), content: z.string() })), screenUrl: oc.input(botId).output(z.object({ url: z.string().nullable() })), heartbeat: oc.input(botId).output(z.object({ ok: z.literal(true) })), }, memory: { list: oc .input(z.object({ botId: Id.optional(), scope: z.enum(["bot", "user"]).optional() })) .output(z.array(MemoryDocumentSchema)), update: oc .input(z.object({ documentId: Id, content: z.string() })) .output(MemoryDocumentSchema), exportMarkdown: oc.input(z.object({ botId: Id.optional() })).output(z.string()), providerConfig: oc.output(SpaceMemoryConfigSchema.nullable()), connectProvider: oc .input( z.object({ provider: z.string().min(1), settings: z.record(z.string(), z.string()), credentials: z.record(z.string(), z.string()), defaultMemoryScope: MemoryScopeSchema.default("isolated"), }), ) .output(SpaceMemoryConfigSchema), setDefaultScope: oc .input(z.object({ defaultMemoryScope: MemoryScopeSchema })) .output(SpaceMemoryConfigSchema), disconnectProvider: oc.output(z.object({ ok: z.literal(true) })), }, routines: { list: oc.input(botId).output(z.array(RoutineSchema)), create: oc.input(CreateRoutineInput).output(RoutineSchema), update: oc .input( z .object({ routineId: Id, name: z.string().optional(), prompt: z.string().optional(), crons: z.array(z.string().min(1)).optional(), timezone: z.string().optional(), active: z.boolean().optional(), notify: z.boolean().optional(), webhookEnabled: z.boolean().optional(), githubEnabled: z.boolean().optional(), messageProvider: z .string() .min(1) .max(50) .regex(/^[a-z0-9._-]+$/i) .nullable() .optional(), /** ISO datetime to arm a never-run one-shot. */ runAt: IsoDate.optional(), }) .superRefine((value, ctx) => { if ( value.crons && value.crons.length === 0 && value.webhookEnabled === false && value.githubEnabled === false && value.messageProvider === null ) { ctx.addIssue({ code: "custom", message: "Add a schedule, webhook, GitHub, or message trigger", path: ["crons"], }); } }), ) .output(RoutineSchema), remove: oc.input(z.object({ routineId: Id })).output(z.object({ ok: z.literal(true) })), testRun: oc .input( z.object({ routineId: Id, clientNonce: z.string().min(1).max(200).optional(), }), ) .output(z.object({ runId: Id })), }, scratchpad: { list: oc .input( z.object({ botId: Id, status: ScratchpadItemStatusSchema.optional(), includeDone: z.boolean().optional(), }), ) .output(z.array(ScratchpadItemSchema)), create: oc.input(CreateScratchpadItemInput).output(ScratchpadItemSchema), update: oc .input( z.object({ itemId: Id, title: z.string().min(1).max(200).optional(), status: ScratchpadItemStatusSchema.optional(), notes: z.string().max(4_000).optional(), }), ) .output(ScratchpadItemSchema), remove: oc.input(z.object({ itemId: Id })).output(z.object({ ok: z.literal(true) })), }, skills: { list: oc.input(botId).output(z.array(TaughtSkillSchema)), get: oc.input(z.object({ skillId: Id })).output(TaughtSkillSchema), start: oc .input(z.object({ botId: Id, goal: z.string().min(1).max(4000) })) .output(TaughtSkillSchema), appendEvent: oc .input(z.object({ skillId: Id, event: TeachRecordingEventSchema })) .output(TaughtSkillSchema), snapshot: oc.input(z.object({ skillId: Id })).output(TaughtSkillSchema), stop: oc.input(z.object({ skillId: Id })).output(TaughtSkillSchema), updateDraft: oc .input( z.object({ skillId: Id, name: z.string().optional(), playbook: SkillPlaybookSchema, }), ) .output(TaughtSkillSchema), save: oc .input(z.object({ skillId: Id, name: z.string().optional() })) .output(TaughtSkillSchema), testRun: oc .input(z.object({ skillId: Id, prompt: z.string().optional() })) .output(z.object({ runId: Id })), remove: oc.input(z.object({ skillId: Id })).output(z.object({ ok: z.literal(true) })), }, /** Claude Agent Skills (SKILL.md recipes) shared across assistants (not taught/demo skills). Pi already understands this format; we persist and inject them. */ agentSkills: { list: oc.output(z.array(AgentSkillCatalogEntrySchema)), get: oc .input( z .object({ skillId: Id.optional(), name: z.string().min(1).max(80).optional() }) .superRefine((input, ctx) => { if (!input.skillId && !input.name?.trim()) { ctx.addIssue({ code: "custom", message: "Provide skillId or name", path: ["skillId"], }); } }), ) .output(AgentSkillSchema), create: oc.input(CreateAgentSkillInput).output(AgentSkillSchema), update: oc.input(UpdateAgentSkillInput).output(AgentSkillSchema), remove: oc.input(z.object({ skillId: Id })).output(z.object({ ok: z.literal(true) })), }, capabilities: { list: oc.output(z.array(CapabilityInstallSchema)), catalogSearch: oc .input( z.object({ query: z.string().trim().max(253).default(""), usePublicCatalog: z.boolean().default(false), }), ) .output( z.object({ enabled: z.boolean(), results: z.array(IntegrationCatalogResultSchema), }), ), install: oc .input( z.object({ kind: z.enum(["skill", "plugin", "mcp", "api", "graphql"]), name: z.string().min(1).max(120), source: z.string().min(1).max(2048), config: z.record(z.string(), z.unknown()).default({}), credential: z.string().max(16_384).optional(), }), ) .output(CapabilityInstallSchema), remove: oc.input(z.object({ id: Id })).output(z.object({ ok: z.literal(true) })), }, mcp: { servers: { list: oc.output(z.array(McpServerSchema)), create: oc.input(McpServerConfigInput).output(McpServerSchema), update: oc .input( z.union([ z.object({ id: Id, config: McpServerConfigInput }), z.object({ id: Id, secret: z.string().min(1).max(16384) }), ]), ) .output(McpServerSchema), remove: oc.input(z.object({ id: Id })).output(z.object({ ok: z.literal(true) })), }, assignments: { list: oc.input(botId).output(z.array(BotMcpServerSchema)), all: oc.output(z.array(BotMcpServerSchema)), approve: oc.input(z.object({ botId: Id, serverId: Id })).output(BotMcpServerSchema), replace: oc .input( z.object({ botId: Id, assignments: z.array( z.object({ serverId: Id, allowAllTools: z.boolean().default(true), allowedTools: z.array(z.string().min(1).max(200)).max(500).default([]), }), ), }), ) .output(z.array(BotMcpServerSchema)), }, oauth: { begin: oc.input(z.object({ serverId: Id, redirectUri: z.string().url() })).output( z.discriminatedUnion("status", [ z.object({ status: z.literal("authorization_required"), sessionId: Id, authorizationUrl: z.string().url(), }), z.object({ status: z.enum(["already_connected", "authorization_not_requested"]), }), ]), ), complete: oc .input(z.object({ sessionId: Id, code: z.string().min(1), state: z.string().min(1) })) .output(z.object({ ok: z.literal(true) })), disconnect: oc.input(z.object({ serverId: Id })).output(z.object({ ok: z.literal(true) })), }, }, onboarding: { /** Seed the first-run greeting into the bot's thread (focus card is separate). */ start: oc.input(z.object({ botId: Id })).output(z.object({ ok: z.literal(true) })), /** Post the focus choice card when the thread is still idle. */ promptFocus: oc.input(z.object({ botId: Id })).output(z.object({ ok: z.literal(true) })), /** Answer the focus choice; posts the app cards. Does not rename the bot. */ choose: oc .input(z.object({ botId: Id, optionId: z.string() })) .output(z.object({ ok: z.literal(true) })), /** Dismiss the unanswered focus card without choosing an option. */ dismissFocus: oc.input(z.object({ botId: Id })).output(z.object({ ok: z.literal(true) })), /** Flip an app_connect card to connected after authorization completes. */ appConnected: oc .input( z.object({ botId: Id, provider: z.string(), connectorId: z.string().default("composio") }), ) .output(z.object({ ok: z.literal(true) })), scan: oc.input(z.object({ botId: Id.optional() })).output(z.object({ scanId: z.string() })), scanStatus: oc.input(z.object({ scanId: z.string() })).output( z.object({ status: z.enum(["scanning", "connecting", "building", "briefing", "complete", "failed"]), progress: z.number(), message: z.string(), result: z.any().optional(), }), ), }, integrationSetup: { get: oc.output(IntegrationSetupStateSchema), save: oc.input(IntegrationProviderConfigSchema).output(z.object({ ok: z.literal(true) })), }, connections: { catalog: oc .input(z.object({ query: z.string().optional(), connectorId: z.string().optional() })) .output(z.array(ConnectionCatalogItemSchema)), list: oc.output(z.array(ConnectionSchema)), begin: oc .input( z.object({ connectorId: z.string().default("composio"), provider: z.string(), displayName: z.string(), }), ) .output(z.object({ connectionId: Id, authorizationUrl: z.string().nullable() })), complete: oc .input(z.object({ connectionId: Id, code: z.string().optional() })) .output(ConnectionSchema), rename: oc .input(z.object({ connectionId: Id, displayName: z.string().trim().min(1).max(80) })) .output(ConnectionSchema), revoke: oc.input(z.object({ connectionId: Id })).output(z.object({ ok: z.literal(true) })), /** Tools the connected provider exposes. Read-only; no per-tool allowlist yet. */ tools: oc.input(z.object({ connectorId: z.string(), provider: z.string() })).output( z.array( z.object({ name: z.string(), description: z.string(), }), ), ), }, /** External messaging surface: link state, group channels, agent connections. */ messaging: { status: oc.output(MessagingStatusSchema), link: { /** Issue a short-lived code the user sends to the line from a chat app. */ start: oc .input(z.object({ botId: Id })) .output(z.object({ code: z.string(), expiresAt: z.string() })), }, identities: { setBot: oc .input(z.object({ identityId: Id, botId: Id })) .output(MessagingLinkedIdentitySchema), unlink: oc.input(z.object({ identityId: Id })).output(z.object({ ok: z.literal(true) })), }, channels: { list: oc.output(z.array(MessagingChannelMembershipSchema)), // Addressed by membership, not channel: one user can have two linked // chat apps in the same group, and each answers for its own agent. respond: oc .input(z.object({ membershipId: Id, accept: z.boolean() })) .output(MessagingChannelMembershipSchema), leave: oc.input(z.object({ membershipId: Id })).output(z.object({ ok: z.literal(true) })), }, connections: { list: oc.output(z.array(MessagingAgentConnectionSchema)), respond: oc .input(z.object({ connectionId: Id, accept: z.boolean() })) .output(MessagingAgentConnectionSchema), revoke: oc.input(z.object({ connectionId: Id })).output(z.object({ ok: z.literal(true) })), }, }, approvalRules: { list: oc.output(z.array(ActionApprovalRuleSchema)), set: oc .input( z.object({ effect: z.enum(["always_allow", "require_approval"]), matchKind: z.enum(["tool", "connector", "category"]), matchValue: z.string().min(1), }), ) .output(ActionApprovalRuleSchema), remove: oc.input(z.object({ id: Id })).output(z.object({ ok: z.literal(true) })), }, autoReview: { get: oc.output(ActionAutoReviewSettingsSchema), set: oc.input(z.object({ enabled: z.boolean() })).output(ActionAutoReviewSettingsSchema), }, artifacts: { list: oc.input(botId).output(z.array(ArtifactSchema)), create: oc .input( threadTarget.and( z.object({ name: z.string().min(1).max(255), mimeType: z.string().min(1), contentBase64: z.string().min(1).max(ATTACHMENT_MAX_BASE64_LENGTH), }), ), ) .output(ArtifactSchema), get: oc.input(threadTarget.and(z.object({ artifactId: Id }))).output(ArtifactWithContentSchema), }, usage: { list: oc.output(z.array(UsageRecordSchema)), summary: oc.output( z.object({ inputTokens: z.number(), outputTokens: z.number(), runs: z.number(), }), ), }, export: { bot: oc.input(botId).output(ExportManifestSchema), }, notifications: { registerPush: oc .input(z.object({ token: z.string().min(8).max(512) })) .output(z.object({ ok: z.literal(true) })), unregisterPush: oc.output(z.object({ ok: z.literal(true) })), }, search: { query: oc.input(z.object({ q: z.string().max(200) })).output(SearchQueryOutputSchema), }, runs: { list: oc.input(z.object({ filter: z.enum(["active", "recent"]) })).output(RunsListOutputSchema), }, tasks: { list: oc .input(z.object({ threadId: Id.optional(), status: DelegatedTaskStatusSchema.optional() })) .output(z.array(DelegatedTaskSchema)), }, /** Asks, approvals, takeovers and unread threads outside the main conversation. */ inbox: { list: oc.output(InboxListOutputSchema), }, voice: { catalog: oc.output(z.array(VoiceCatalogEntrySchema)), status: oc.output(VoiceStatusSchema), credentials: oc.output(z.array(VoiceCredentialSchema)), connect: oc .input( z.object({ provider: z.string(), apiKey: z.string().min(8), voiceId: z.string().max(120).optional(), }), ) .output(VoiceCredentialSchema), disconnect: oc .input(z.object({ provider: z.string().min(1) })) .output(z.object({ ok: z.literal(true) })), setVoice: oc .input(z.object({ voiceId: z.string().min(1).max(120), provider: z.string().optional() })) .output(VoiceStatusSchema), voices: oc .input(z.object({ provider: z.string().optional() })) .output(z.array(VoiceInfoSchema)), prepare: oc .input( z.object({ text: z.string().max(20000), voiceId: z.string().max(120).optional(), botId: Id.optional(), }), ) .output(z.object({ ready: z.boolean(), utterances: z.array(z.string()) })), }, externalConversations: { updatePolicy: oc .input(UpdateExternalConversationPolicyInput) .output(ExternalConversationPolicySchema), }, agentSecrets: { list: oc.output(z.array(AgentSecretSchema)), put: oc.input(AgentSecretInputSchema).output(AgentSecretSchema), remove: oc.input(z.object({ id: Id })).output(z.object({ ok: z.literal(true) })), }, media: { capabilities: rpc(mediaRpcShapes.capabilities), sources: { list: rpc(mediaRpcShapes.sources.list), createFolder: rpc(mediaRpcShapes.sources.createFolder), registerClient: rpc(mediaRpcShapes.sources.registerClient), pushItems: rpc(mediaRpcShapes.sources.pushItems), sync: rpc(mediaRpcShapes.sources.sync), remove: rpc(mediaRpcShapes.sources.remove), }, assets: { search: rpc(mediaRpcShapes.assets.search), get: rpc(mediaRpcShapes.assets.get), setPaidOnly: rpc(mediaRpcShapes.assets.setPaidOnly), requestUpload: rpc(mediaRpcShapes.assets.requestUpload), }, uploads: { begin: rpc(mediaRpcShapes.uploads.begin), status: rpc(mediaRpcShapes.uploads.status), complete: rpc(mediaRpcShapes.uploads.complete), }, collections: { list: rpc(mediaRpcShapes.collections.list), get: rpc(mediaRpcShapes.collections.get), rename: rpc(mediaRpcShapes.collections.rename), merge: rpc(mediaRpcShapes.collections.merge), split: rpc(mediaRpcShapes.collections.split), createManual: rpc(mediaRpcShapes.collections.createManual), recluster: rpc(mediaRpcShapes.collections.recluster), }, people: { list: rpc(mediaRpcShapes.people.list), update: rpc(mediaRpcShapes.people.update), merge: rpc(mediaRpcShapes.people.merge), assignTrack: rpc(mediaRpcShapes.people.assignTrack), recordConsent: rpc(mediaRpcShapes.people.recordConsent), remove: rpc(mediaRpcShapes.people.remove), }, marks: { list: rpc(mediaRpcShapes.marks.list), create: rpc(mediaRpcShapes.marks.create), update: rpc(mediaRpcShapes.marks.update), remove: rpc(mediaRpcShapes.marks.remove), }, brandKits: { list: rpc(mediaRpcShapes.brandKits.list), upsert: rpc(mediaRpcShapes.brandKits.upsert), remove: rpc(mediaRpcShapes.brandKits.remove), templates: rpc(mediaRpcShapes.brandKits.templates), introVariants: rpc(mediaRpcShapes.brandKits.introVariants), }, profiles: { list: rpc(mediaRpcShapes.profiles.list), get: rpc(mediaRpcShapes.profiles.get), }, projects: { list: rpc(mediaRpcShapes.projects.list), get: rpc(mediaRpcShapes.projects.get), create: rpc(mediaRpcShapes.projects.create), update: rpc(mediaRpcShapes.projects.update), remove: rpc(mediaRpcShapes.projects.remove), autoPlan: rpc(mediaRpcShapes.projects.autoPlan), }, plans: { submit: rpc(mediaRpcShapes.plans.submit), }, renders: { start: rpc(mediaRpcShapes.renders.start), get: rpc(mediaRpcShapes.renders.get), cancel: rpc(mediaRpcShapes.renders.cancel), thumbnails: rpc(mediaRpcShapes.renders.thumbnails), }, marketing: { copy: rpc(mediaRpcShapes.marketing.copy), }, publications: { list: rpc(mediaRpcShapes.publications.list), create: rpc(mediaRpcShapes.publications.create), bundle: rpc(mediaRpcShapes.publications.bundle), markPublished: rpc(mediaRpcShapes.publications.markPublished), cancel: rpc(mediaRpcShapes.publications.cancel), }, metrics: { mappings: rpc(mediaRpcShapes.metrics.mappings), saveMapping: rpc(mediaRpcShapes.metrics.saveMapping), importCsv: rpc(mediaRpcShapes.metrics.importCsv), record: rpc(mediaRpcShapes.metrics.record), funnel: rpc(mediaRpcShapes.metrics.funnel), segments: rpc(mediaRpcShapes.metrics.segments), resetTeaserModel: rpc(mediaRpcShapes.metrics.resetTeaserModel), }, }, }; export type AppContract = typeof appContract;