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).
This commit is contained in:
lakshit verma 2026-08-19 17:45:17 +05:30
parent 96f960e0c9
commit 3f3fdf8a85
No known key found for this signature in database
2 changed files with 207 additions and 1 deletions

View file

@ -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<typeof sseEventSchema>;
// ── Inferred input types ─────────────────────────────────────────────
export type RegisterInput = z.infer<typeof registerSchema>;
@ -133,5 +228,13 @@ export type UpdateProfileInput = z.infer<typeof updateProfileSchema>;
export type ChangePasswordInput = z.infer<typeof changePasswordSchema>;
export type UpdateRoleInput = z.infer<typeof updateRoleSchema>;
export type UpdateStatusInput = z.infer<typeof updateStatusSchema>;
export type UpdateAiInput = z.infer<typeof updateAiSchema>;
export type CursorQuery = z.infer<typeof cursorQuerySchema>;
export type OffsetQuery = z.infer<typeof offsetQuerySchema>;
export type AiListWorkOrdersInput = z.infer<typeof aiListWorkOrdersSchema>;
export type AiGetWorkOrderInput = z.infer<typeof aiGetWorkOrderSchema>;
export type AiCreateWorkOrderInput = z.infer<typeof aiCreateWorkOrderSchema>;
export type AiUpdateWorkOrderInput = z.infer<typeof aiUpdateWorkOrderSchema>;
export type AiDeleteWorkOrderInput = z.infer<typeof aiDeleteWorkOrderSchema>;
export type TriageProposal = z.infer<typeof triageProposalSchema>;
export type SendCopilotMessageInput = z.infer<typeof sendCopilotMessageSchema>;

View file

@ -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;
}