Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline (Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/* packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
816 lines
28 KiB
TypeScript
816 lines
28 KiB
TypeScript
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,
|
|
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 { ProductEventSchema } from "./events.js";
|
|
import { Id, IsoDate } from "./ids.js";
|
|
import {
|
|
IntegrationProviderConfigSchema,
|
|
IntegrationSetupStateSchema,
|
|
} from "./integration-settings.js";
|
|
import { MessageReactionSchema } from "./reactions.js";
|
|
import {
|
|
CreateProjectGoalInput,
|
|
ProjectGoalSchema,
|
|
UpdateProjectGoalInput,
|
|
} from "./project-goals.js";
|
|
import { ProjectSessionsOutputSchema } from "./project-sessions.js";
|
|
import { RunsListOutputSchema } from "./runs.js";
|
|
import { SearchQueryOutputSchema } from "./search.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"],
|
|
});
|
|
}
|
|
});
|
|
|
|
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),
|
|
},
|
|
spaces: {
|
|
list: oc.output(SpaceNavigationSchema),
|
|
create: oc.input(z.object({ name: z.string().trim().min(1).max(60) })).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.object({
|
|
baseUrl: z.string(),
|
|
apiKey: z.string().optional(),
|
|
}),
|
|
)
|
|
.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) })),
|
|
},
|
|
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),
|
|
duplicate: oc.input(botId).output(BotSchema),
|
|
reorder: oc.input(ReorderBotsInput).output(z.object({ ok: z.literal(true) })),
|
|
update: oc.input(UpdateBotInput).output(BotSchema),
|
|
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) })),
|
|
},
|
|
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),
|
|
},
|
|
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) })),
|
|
},
|
|
};
|
|
|
|
export type AppContract = typeof appContract;
|