import { z } from 'zod' import { type ContractJsonResponse, defineRouteContract } from '@/lib/api/contracts/types' import { cleanedWorkflowStateSchema } from '@/lib/api/contracts/workflows' import { ASYNC_TOOL_CONFIRMATION_STATUS, type AsyncConfirmationStatus, } from '@/lib/copilot/async-runs/lifecycle' export const copilotApiKeySchema = z.object({ id: z.string(), displayKey: z.string(), name: z.string().nullable(), createdAt: z.string().nullable(), lastUsed: z.string().nullable(), }) export type CopilotApiKey = z.output export const deleteCopilotApiKeyQuerySchema = z.object({ id: z.string().min(1), }) export const generateCopilotApiKeyBodySchema = z.object({ name: z.string().min(1, 'Name is required').max(255, 'Name is too long'), }) export const submitCopilotFeedbackBodySchema = z.object({ chatId: z.string().uuid('Chat ID must be a valid UUID'), userQuery: z.string().min(1, 'User query is required'), agentResponse: z.string().min(1, 'Agent response is required'), isPositiveFeedback: z.boolean(), feedback: z.string().optional(), workflowYaml: z.string().optional(), }) export type SubmitCopilotFeedbackBody = z.input export const copilotCredentialsQuerySchema = z.object({}) export const copilotConfirmBodySchema = z.object({ toolCallId: z.string().min(1, 'Tool call ID is required'), status: z.enum( Object.values(ASYNC_TOOL_CONFIRMATION_STATUS) as [ AsyncConfirmationStatus, ...AsyncConfirmationStatus[], ], { error: 'Invalid notification status' } ), message: z.string().optional(), data: z.unknown().optional(), }) export type CopilotConfirmBody = z.input export const createWorkflowCopilotChatBodySchema = z.object({ workspaceId: z.string().min(1), workflowId: z.string().min(1), }) export type CreateWorkflowCopilotChatBody = z.input export const copilotTrainingExampleBodySchema = z.object({ json: z.string().min(1, 'JSON string is required'), title: z.string().min(1, 'Title is required'), tags: z.array(z.string()).optional(), metadata: z.record(z.string(), z.unknown()).optional(), }) export type CopilotTrainingExampleBody = z.input const copilotTrainingOperationSchema = z.object({ operation_type: z.string(), block_id: z.string(), params: z.record(z.string(), z.unknown()).optional(), }) export const copilotTrainingDataBodySchema = z.object({ title: z.string().min(1, 'Title is required'), prompt: z.string().min(1, 'Prompt is required'), input: z.record(z.string(), z.unknown()), output: z.record(z.string(), z.unknown()), operations: z.array(copilotTrainingOperationSchema), }) export type CopilotTrainingDataBody = z.input export const renameCopilotChatBodySchema = z.object({ chatId: z.string().min(1), title: z.string().min(1).max(200), }) export type RenameCopilotChatBody = z.input const copilotResourceTypeSchema = z.enum([ 'table', 'file', 'workflow', 'knowledgebase', 'folder', 'scheduledtask', 'log', ]) export const addCopilotChatResourceBodySchema = z.object({ chatId: z.string(), resource: z.object({ type: copilotResourceTypeSchema, id: z.string(), title: z.string(), }), }) export type AddCopilotChatResourceBody = z.input export const removeCopilotChatResourceBodySchema = z.object({ chatId: z.string(), resourceType: copilotResourceTypeSchema, resourceId: z.string(), }) export type RemoveCopilotChatResourceBody = z.input export const reorderCopilotChatResourcesBodySchema = z.object({ chatId: z.string(), resources: z.array( z.object({ type: copilotResourceTypeSchema, id: z.string(), title: z.string(), }) ), }) export type ReorderCopilotChatResourcesBody = z.input export const revertCopilotCheckpointBodySchema = z.object({ checkpointId: z.string().min(1), }) export type RevertCopilotCheckpointBody = z.input export const copilotChatAbortBodySchema = z.object({ streamId: z.string().optional(), chatId: z.string().optional(), }) export type CopilotChatAbortBody = z.input export const copilotChatGetQuerySchema = z .object({ workflowId: z.string().optional(), workspaceId: z.string().optional(), chatId: z.string().optional(), }) .passthrough() export const copilotModelsQuerySchema = z.object({}) export const createCopilotCheckpointBodySchema = z.object({ workflowId: z.string(), chatId: z.string(), messageId: z.string().optional(), workflowState: z.string(), }) export type CreateCopilotCheckpointBody = z.input export const listCopilotCheckpointsQuerySchema = z.object({ chatId: z.string({ error: 'chatId is required' }).min(1, 'chatId is required'), }) export type ListCopilotCheckpointsQuery = z.input export const copilotChatStreamQuerySchema = z.object({ streamId: z.string().optional().default(''), after: z.string().optional().default(''), batch: z .string() .optional() .transform((value) => value === 'true'), }) const storedToolCallSchema = z .object({ id: z.string().optional(), name: z.string().optional(), state: z.string().optional(), params: z.record(z.string(), z.unknown()).optional(), result: z .object({ success: z.boolean(), output: z.unknown().optional(), error: z.string().optional(), }) .optional(), display: z .object({ text: z.string().optional(), title: z.string().optional(), phaseLabel: z.string().optional(), }) .optional(), calledBy: z.string().optional(), durationMs: z.number().optional(), error: z.string().optional(), }) .nullable() const copilotContentBlockSchema = z.object({ type: z.string(), lane: z.enum(['main', 'subagent']).optional(), content: z.string().optional(), channel: z.enum(['assistant', 'thinking']).optional(), phase: z.enum(['call', 'args_delta', 'result']).optional(), kind: z.enum(['subagent', 'structured_result', 'subagent_result']).optional(), lifecycle: z.enum(['start', 'end']).optional(), status: z.enum(['complete', 'error', 'cancelled']).optional(), parentToolCallId: z.string().optional(), toolCall: storedToolCallSchema.optional(), timestamp: z.number().optional(), endedAt: z.number().optional(), }) export const copilotChatStopBodySchema = z.object({ chatId: z.string(), streamId: z.string(), content: z.string(), contentBlocks: z.array(copilotContentBlockSchema).optional(), requestId: z.string().optional(), }) export type CopilotChatStopBody = z.input export const deleteCopilotChatBodySchema = z.object({ chatId: z.string(), }) export type DeleteCopilotChatBody = z.input const copilotPersistedMessageSchema = z .object({ id: z.string(), role: z.enum(['user', 'assistant', 'system']), content: z.string(), timestamp: z.string(), toolCalls: z.array(z.any()).optional(), contentBlocks: z.array(z.any()).optional(), fileAttachments: z .array( z.object({ id: z.string(), key: z.string(), filename: z.string(), media_type: z.string(), size: z.number(), }) ) .optional(), contexts: z.array(z.any()).optional(), citations: z.array(z.any()).optional(), errorType: z.string().optional(), }) .passthrough() export const updateCopilotMessagesBodySchema = z.object({ chatId: z.string(), messages: z.array(copilotPersistedMessageSchema), planArtifact: z.string().nullable().optional(), config: z .object({ mode: z.string().optional(), model: z.string().optional(), }) .nullable() .optional(), }) export type UpdateCopilotMessagesBody = z.input export const validateCopilotApiKeyBodySchema = z.object({ userId: z.string().min(1, 'userId is required'), /** * Originating workspace. Used to enforce per-member org-workspace credit limits * at mothership/copilot request time. Required: the Go mothership always resolves * a workspace for a chat request, so a missing value must fail closed (block the * request) rather than silently skip the per-member gate. */ workspaceId: z.string().min(1), }) export type ValidateCopilotApiKeyBody = z.input export const listCopilotApiKeysContract = defineRouteContract({ method: 'GET', path: '/api/copilot/api-keys', response: { mode: 'json', schema: z.object({ keys: z.array(copilotApiKeySchema), }), }, }) export const copilotChatListItemSchema = z.object({ id: z.string(), title: z.string().nullable(), workflowId: z.string().nullable().optional(), workspaceId: z.string().nullable().optional(), activeStreamId: z.string().nullable(), updatedAt: z.string().nullable(), }) export type CopilotChatListItem = z.output export const listCopilotChatsContract = defineRouteContract({ method: 'GET', path: '/api/copilot/chats', response: { mode: 'json', schema: z.object({ success: z.literal(true), chats: z.array(copilotChatListItemSchema), }), }, }) export const generateCopilotApiKeyContract = defineRouteContract({ method: 'POST', path: '/api/copilot/api-keys/generate', body: generateCopilotApiKeyBodySchema, response: { mode: 'json', schema: z.object({ success: z.literal(true), key: z.object({ id: z.string(), apiKey: z.string(), }), }), }, }) export type GenerateCopilotApiKeyResult = ContractJsonResponse export const deleteCopilotApiKeyContract = defineRouteContract({ method: 'DELETE', path: '/api/copilot/api-keys', query: deleteCopilotApiKeyQuerySchema, response: { mode: 'json', schema: z.object({ success: z.literal(true), }), }, }) export const submitCopilotFeedbackContract = defineRouteContract({ method: 'POST', path: '/api/copilot/feedback', body: submitCopilotFeedbackBodySchema, response: { mode: 'json', schema: z.object({ success: z.literal(true), feedbackId: z.string(), message: z.string(), metadata: z.object({ requestId: z.string(), duration: z.number(), }), }), }, }) export type SubmitCopilotFeedbackResult = ContractJsonResponse const successFlagSchema = z.object({ success: z.literal(true) }) const copilotCheckpointSchema = z.object({ id: z.string(), userId: z.string(), workflowId: z.string(), chatId: z.string(), messageId: z.string().nullable().optional(), createdAt: z.string().nullable(), updatedAt: z.string().nullable(), }) const copilotChatResourceSchema = z.object({ type: copilotResourceTypeSchema, id: z.string(), title: z.string(), }) const copilotAvailableModelSchema = z.object({ id: z.string(), friendlyName: z.string(), provider: z.string(), }) const copilotChatGetChatSchema = z .object({ id: z.string(), title: z.string().nullable(), model: z.string().nullable(), messages: z.array(z.unknown()), messageCount: z.number(), planArtifact: z.unknown().nullable(), config: z.unknown().nullable(), activeStreamId: z.string().nullable().optional(), resources: z.array(z.unknown()).optional(), createdAt: z.string().nullable(), updatedAt: z.string().nullable(), streamSnapshot: z .object({ events: z.array(z.unknown()), previewSessions: z.array(z.unknown()), status: z.string(), }) .optional(), }) .passthrough() const copilotChatGetListItemSchema = z .object({ id: z.string(), title: z.string().nullable(), model: z.string().nullable(), createdAt: z.string().nullable(), updatedAt: z.string().nullable(), }) .passthrough() const copilotConnectedCredentialSchema = z.object({ id: z.string(), name: z.string(), provider: z.string(), serviceName: z.string(), lastUsed: z.string(), isDefault: z.boolean(), }) const copilotNotConnectedServiceSchema = z.object({ providerId: z.string(), name: z.string(), description: z.string(), baseProvider: z.string(), }) const copilotCredentialsResultSchema = z.object({ oauth: z.object({ connected: z.object({ credentials: z.array(copilotConnectedCredentialSchema), total: z.number(), }), notConnected: z.object({ services: z.array(copilotNotConnectedServiceSchema), total: z.number(), }), }), environment: z.object({ variableNames: z.array(z.string()), count: z.number(), personalVariables: z.array(z.string()), workspaceVariables: z.array(z.string()), conflicts: z.array(z.string()), }), }) export const copilotCredentialsContract = defineRouteContract({ method: 'GET', path: '/api/copilot/credentials', query: copilotCredentialsQuerySchema, response: { mode: 'json', schema: z.object({ success: z.literal(true), result: copilotCredentialsResultSchema, }), }, }) export const validateCopilotApiKeyContract = defineRouteContract({ method: 'POST', path: '/api/copilot/api-keys/validate', body: validateCopilotApiKeyBodySchema, response: { mode: 'empty' }, }) export const validateCopilotByokBodySchema = z.object({ workspaceId: z.string().min(1, 'workspaceId is required'), userId: z.string().min(1, 'userId is required'), }) export type ValidateCopilotByokBody = z.input /** * Server-to-server entitlement gate called by the mothership (Go) before it * uses a workspace's own provider key. Empty 200/401/403 responses signal the * outcome; the Go caller fails closed to hosted keys on anything but a 200. */ export const validateCopilotByokContract = defineRouteContract({ method: 'POST', path: '/api/copilot/byok/validate', body: validateCopilotByokBodySchema, response: { mode: 'empty' }, }) export const listCopilotByokKeysQuerySchema = z.object({ workspaceId: z.string().min(1, 'workspaceId is required'), }) export type ListCopilotByokKeysQuery = z.input export const upsertCopilotByokKeyBodySchema = z.object({ workspaceId: z.string().min(1, 'workspaceId is required'), provider: z.string().min(1, 'provider is required'), apiKey: z.string().min(1, 'apiKey is required'), }) export type UpsertCopilotByokKeyBody = z.input export const deleteCopilotByokKeyQuerySchema = z.object({ workspaceId: z.string().min(1, 'workspaceId is required'), provider: z.string().min(1, 'provider is required'), }) export type DeleteCopilotByokKeyQuery = z.input /** * Superuser-gated proxies to the copilot's `/api/admin/byok` endpoints. The * responses are owned by the copilot service and forwarded verbatim. */ export const listCopilotByokKeysContract = defineRouteContract({ method: 'GET', path: '/api/copilot/byok', query: listCopilotByokKeysQuerySchema, response: { mode: 'json', // untyped-response: forwards the copilot /api/admin/byok response unchanged; shape is owned by the copilot service schema: z.unknown(), }, }) export const upsertCopilotByokKeyContract = defineRouteContract({ method: 'POST', path: '/api/copilot/byok', body: upsertCopilotByokKeyBodySchema, response: { mode: 'json', // untyped-response: forwards the copilot /api/admin/byok response unchanged; shape is owned by the copilot service schema: z.unknown(), }, }) export const deleteCopilotByokKeyContract = defineRouteContract({ method: 'DELETE', path: '/api/copilot/byok', query: deleteCopilotByokKeyQuerySchema, response: { mode: 'json', // untyped-response: forwards the copilot /api/admin/byok response unchanged; shape is owned by the copilot service schema: z.unknown(), }, }) export const createWorkflowCopilotChatContract = defineRouteContract({ method: 'POST', path: '/api/copilot/chats', body: createWorkflowCopilotChatBodySchema, response: { mode: 'json', schema: z.object({ success: z.literal(true), id: z.string(), }), }, }) export const createCopilotCheckpointContract = defineRouteContract({ method: 'POST', path: '/api/copilot/checkpoints', body: createCopilotCheckpointBodySchema, response: { mode: 'json', schema: z.object({ success: z.literal(true), checkpoint: copilotCheckpointSchema, }), }, }) export const listCopilotCheckpointsContract = defineRouteContract({ method: 'GET', path: '/api/copilot/checkpoints', query: listCopilotCheckpointsQuerySchema, response: { mode: 'json', schema: z.object({ success: z.literal(true), checkpoints: z.array(copilotCheckpointSchema), }), }, }) export const copilotConfirmContract = defineRouteContract({ method: 'POST', path: '/api/copilot/confirm', body: copilotConfirmBodySchema, response: { mode: 'json', schema: z.object({ success: z.literal(true), message: z.string(), toolCallId: z.string(), status: z.string(), }), }, }) export const copilotModelsContract = defineRouteContract({ method: 'GET', path: '/api/copilot/models', query: copilotModelsQuerySchema, response: { mode: 'json', schema: z.object({ success: z.literal(true), models: z.array(copilotAvailableModelSchema), }), }, }) export const addCopilotChatResourceContract = defineRouteContract({ method: 'POST', path: '/api/copilot/chat/resources', body: addCopilotChatResourceBodySchema, response: { mode: 'json', schema: z.object({ success: z.literal(true), resources: z.array(copilotChatResourceSchema).optional(), }), }, }) export const reorderCopilotChatResourcesContract = defineRouteContract({ method: 'PATCH', path: '/api/copilot/chat/resources', body: reorderCopilotChatResourcesBodySchema, response: { mode: 'json', schema: z.object({ success: z.literal(true), resources: z.array(copilotChatResourceSchema), }), }, }) export const removeCopilotChatResourceContract = defineRouteContract({ method: 'DELETE', path: '/api/copilot/chat/resources', body: removeCopilotChatResourceBodySchema, response: { mode: 'json', schema: z.object({ success: z.literal(true), resources: z.array(copilotChatResourceSchema), }), }, }) /** * Forwards the agent indexer's free-form JSON response. * Shape varies by upstream version. */ export const copilotTrainingDataContract = defineRouteContract({ method: 'POST', path: '/api/copilot/training', body: copilotTrainingDataBodySchema, response: { mode: 'json', // untyped-response: forwards external agent indexer /operations/add response unchanged; shape varies by upstream version schema: z.unknown(), }, }) /** * Forwards the agent indexer's free-form JSON response. * Shape varies by upstream version. */ export const copilotTrainingExampleContract = defineRouteContract({ method: 'POST', path: '/api/copilot/training/examples', body: copilotTrainingExampleBodySchema, response: { mode: 'json', // untyped-response: forwards external agent indexer /examples/add response unchanged; shape varies by upstream version schema: z.unknown(), }, }) export const renameCopilotChatContract = defineRouteContract({ method: 'PATCH', path: '/api/copilot/chat/rename', body: renameCopilotChatBodySchema, response: { mode: 'json', schema: successFlagSchema }, }) export const revertCopilotCheckpointContract = defineRouteContract({ method: 'POST', path: '/api/copilot/checkpoints/revert', body: revertCopilotCheckpointBodySchema, response: { mode: 'json', schema: z.object({ success: z.literal(true), workflowId: z.string(), checkpointId: z.string(), revertedAt: z.string(), checkpoint: z.object({ id: z.string(), workflowState: cleanedWorkflowStateSchema, }), }), }, }) export const copilotChatAbortContract = defineRouteContract({ method: 'POST', path: '/api/copilot/chat/abort', body: copilotChatAbortBodySchema, response: { mode: 'json', schema: z.object({ aborted: z.boolean(), settled: z.boolean().optional(), // True when the stream did not settle within the grace window and the // chat stream lock was force-broken so the chat is immediately usable. forceReleased: z.boolean().optional(), }), }, }) export const copilotChatStreamContract = defineRouteContract({ method: 'GET', path: '/api/copilot/chat/stream', query: copilotChatStreamQuerySchema, response: { mode: 'stream' }, }) export const copilotChatStopContract = defineRouteContract({ method: 'POST', path: '/api/copilot/chat/stop', body: copilotChatStopBodySchema, response: { mode: 'json', schema: successFlagSchema }, }) export const copilotChatGetContract = defineRouteContract({ method: 'GET', path: '/api/copilot/chat', query: copilotChatGetQuerySchema, response: { mode: 'json', schema: z.union([ z.object({ success: z.literal(true), chat: copilotChatGetChatSchema, }), z.object({ success: z.literal(true), chats: z.array(copilotChatGetListItemSchema), }), ]), }, }) export const deleteCopilotChatContract = defineRouteContract({ method: 'DELETE', path: '/api/copilot/chat/delete', body: deleteCopilotChatBodySchema, response: { mode: 'json', schema: successFlagSchema }, }) export const updateCopilotMessagesContract = defineRouteContract({ method: 'POST', path: '/api/copilot/chat/update-messages', body: updateCopilotMessagesBodySchema, response: { mode: 'json', schema: z.object({ success: z.literal(true), messageCount: z.number(), }), }, })