From 3f3fdf8a85570692c684b5d993904a00a8fa4413 Mon Sep 17 00:00:00 2001 From: lakshit verma Date: Wed, 19 Aug 2026 17:45:17 +0530 Subject: [PATCH] feat(shared): agentic AI types, error codes, tool and SSE schemas Extend the closed error catalog with the AI error codes (AI_UNAVAILABLE, AI_BUDGET_EXCEEDED, approval lifecycle codes, AI_INJECTION_BLOCKED), add the AgentRun/AgentToolCall/AgentConfig/CopilotSession/TriageSuggestion DTOs, add aiEnabled to UserAdmin, and add the LLM-facing tool arg schemas plus the zod-validated SSE event schema. Owner/version stay out of tool args per the agentic spec (the runtime injects them). --- packages/shared/src/schemas.ts | 103 ++++++++++++++++++++++++++++++++ packages/shared/src/types.ts | 105 ++++++++++++++++++++++++++++++++- 2 files changed, 207 insertions(+), 1 deletion(-) diff --git a/packages/shared/src/schemas.ts b/packages/shared/src/schemas.ts index c041a68..7238005 100644 --- a/packages/shared/src/schemas.ts +++ b/packages/shared/src/schemas.ts @@ -103,6 +103,12 @@ export const updateStatusSchema = z }) .strict(); +export const updateAiSchema = z + .object({ + aiEnabled: z.boolean(), + }) + .strict(); + // ── Query params ───────────────────────────────────────────────────── export const cursorQuerySchema = z.object({ @@ -120,6 +126,95 @@ export const offsetQuerySchema = z.object({ search: z.string().trim().max(64).optional(), }); +// ── Agentic AI ───────────────────────────────────────────────────────────── + +// LLM-facing tool arg schemas. `version` and `owner` are intentionally absent: +// the runtime injects them (see agent spec §4.3). + +export const aiListWorkOrdersSchema = z + .object({ + status: workOrderStatusSchema.optional(), + priority: workOrderPrioritySchema.optional(), + search: z.string().trim().max(64).optional(), + }) + .strict(); + +export const aiGetWorkOrderSchema = z + .object({ + id: z.string().min(1), + }) + .strict(); + +export const aiCreateWorkOrderSchema = createWorkOrderSchema; + +export const aiUpdateWorkOrderSchema = z + .object({ + id: z.string().min(1), + title: z.string().trim().min(3).max(100).optional(), + description: z.string().trim().max(2000).nullable().optional(), + priority: workOrderPrioritySchema.optional(), + status: workOrderStatusSchema.optional(), + }) + .strict() + .refine((v) => v.title !== undefined || v.description !== undefined || v.priority !== undefined || v.status !== undefined, { + message: 'at least one field to update is required', + }); + +export const aiDeleteWorkOrderSchema = z + .object({ + id: z.string().min(1), + }) + .strict(); + +export const triageProposalSchema = z + .object({ + summary: z.string().trim().min(1).max(200), + suggestedPriority: workOrderPrioritySchema, + flagForDispatcher: z.boolean(), + }) + .strict(); + +export const sendCopilotMessageSchema = z + .object({ + content: z.string().trim().min(1).max(4000), + }) + .strict(); + +// ── SSE event payloads (agent spec §4.8) ────────────────────────────────── + +export const sseEventSchema = z.discriminatedUnion('event', [ + z.object({ event: z.literal('token'), content: z.string() }), + z.object({ event: z.literal('tool_call_start'), toolCallId: z.string(), tool: z.string(), args: z.unknown() }), + z.object({ + event: z.literal('tool_approval_required'), + toolCallId: z.string(), + tool: z.string(), + args: z.unknown(), + preImage: z.unknown().nullable(), + afterDiff: z.unknown(), + summary: z.string(), + expiresAt: z.string(), + }), + z.object({ event: z.literal('tool_approval_expired'), toolCallId: z.string() }), + z.object({ + event: z.literal('tool_result'), + toolCallId: z.string(), + outcome: z.string(), + result: z.unknown().optional(), + }), + z.object({ + event: z.literal('message_done'), + runId: z.string(), + content: z.string(), + inputTokens: z.number().int().nonnegative(), + outputTokens: z.number().int().nonnegative(), + }), + z.object({ event: z.literal('error'), code: z.string(), message: z.string(), requestId: z.string() }), + z.object({ event: z.literal('ping'), ts: z.string() }), +]); + +export type SseEvent = z.infer; + // ── Inferred input types ───────────────────────────────────────────── export type RegisterInput = z.infer; @@ -133,5 +228,13 @@ export type UpdateProfileInput = z.infer; export type ChangePasswordInput = z.infer; export type UpdateRoleInput = z.infer; export type UpdateStatusInput = z.infer; +export type UpdateAiInput = z.infer; export type CursorQuery = z.infer; export type OffsetQuery = z.infer; +export type AiListWorkOrdersInput = z.infer; +export type AiGetWorkOrderInput = z.infer; +export type AiCreateWorkOrderInput = z.infer; +export type AiUpdateWorkOrderInput = z.infer; +export type AiDeleteWorkOrderInput = z.infer; +export type TriageProposal = z.infer; +export type SendCopilotMessageInput = z.infer; diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index f4b43fa..255aa46 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -16,6 +16,7 @@ export interface UserPublic { export interface UserAdmin extends UserPublic { isActive: boolean; lastLoginAt: string | null; + aiEnabled: boolean; } export interface WorkOrderPublic { @@ -53,7 +54,15 @@ export type ErrorCode = | 'AUTH_GENERIC' | 'EMAIL_TAKEN' | 'REFRESH_REUSE' - | 'INTERNAL'; + | 'INTERNAL' + | 'AI_UNAVAILABLE' + | 'AI_BUDGET_EXCEEDED' + | 'AI_APPROVAL_PENDING' + | 'AI_APPROVAL_RESOLVED' + | 'AI_APPROVAL_STALE' + | 'AI_APPROVAL_EXPIRED' + | 'AI_MESSAGE_DUPLICATE' + | 'AI_INJECTION_BLOCKED'; export interface ApiErrorBody { code: ErrorCode; @@ -97,3 +106,97 @@ export const REFRESH_TOKEN_TTL_SECONDS = 604800; export const APP_ISS = 'workorders'; export const APP_AUD = 'workorders-api'; + +// ── Agentic AI ───────────────────────────────────────────────────────────── + +export type CopilotSessionStatus = 'active' | 'archived' | 'expired'; + +export interface CopilotSession { + id: string; + userId: string; + status: CopilotSessionStatus; + createdAt: string; + updatedAt: string; +} + +export type AgentRunMode = 'copilot' | 'autonomous'; + +export type AgentRunStatus = 'running' | 'complete' | 'error' | 'budget_exceeded' | 'expired' | 'aborted'; + +export interface AgentRun { + id: string; + mode: AgentRunMode; + actorId: string | null; + agentName?: string; + status: AgentRunStatus; + model: string; + inputTokens: number; + outputTokens: number; + startedAt: string; + finishedAt: string | null; + errorCode?: ErrorCode; +} + +export type AgentToolOutcome = + | 'executed' + | 'approved' + | 'rejected' + | 'expired' + | 'stale' + | 'error' + | 'blocked' + | 'aborted'; + +export type AgentApprovalStatus = 'pending' | 'approved' | 'rejected' | 'expired' | 'stale'; + +export interface AgentApproval { + status: AgentApprovalStatus; + summary: string; + expiresAt: string; + decidedBy?: string; + decidedAt?: string; +} + +export interface AgentToolCall { + id: string; + runId: string; + tool: string; + args: unknown; + outcome: AgentToolOutcome; + result?: unknown; + latencyMs: number; + createdAt: string; + stagedVersion?: number; + executedVersion?: number; + preImage?: unknown; + approval?: AgentApproval; +} + +export interface AgentToolCallPublic extends AgentToolCall { + result?: never; +} + +export type TriageMode = 'suggest' | 'auto-apply'; + +export interface AgentConfig { + name: string; + enabled: boolean; + mode: TriageMode; + allowedFields: string[]; + dailyActionCap: number; + flagThreshold: WorkOrderPriority; + workingHours: string; + updatedBy: string | null; + updatedAt: string; +} + +export interface TriageSuggestion { + id: string; + workOrderId: string; + runId: string; + summary: string; + suggestedPriority: WorkOrderPriority; + flagForDispatcher: boolean; + applied: boolean; + createdAt: string; +}