Files
MosaicIQ/packages/contracts/src/rpcSchemas.ts

271 lines
11 KiB
TypeScript

import { z } from "zod";
import {
AgentSchema,
AlertSchema,
CatalystSchema,
CompanySchema,
EarningsScheduleSchema,
ExportRecordSchema,
FilingSchema,
HoldingSchema,
MemoAnnotationSchema,
MemoCitationSchema,
MemoSectionReviewSchema,
MemoSectionSchema,
ModelRowSchema,
RiskSchema,
WorkspaceSectionSchema,
type ClientSettings,
type RpcMethod,
type RpcRequestMap,
type RpcResponseMap,
type ServerSettings,
} from "./rpc.js";
const nonEmptyString = z.string().trim().min(1);
const idString = nonEmptyString;
const tickerString = z.string().trim().min(1).max(16);
const nonNegativeIndex = z.number().int().min(0);
const unknownRecord = z.record(z.unknown());
export const ClientSettingsSchema = z.object({
theme: z.enum(["light", "dark", "system"]),
density: z.enum(["comfortable", "compact", "dense"]),
sidebarWidth: z.number().int().min(160).max(520),
navCollapsed: z.record(z.boolean()),
keybindings: z.record(z.string()),
});
export const ServerSettingsSchema = z.object({
agentConfigs: z.record(z.unknown()),
dataSources: z.record(z.boolean()),
exportPipelines: z.record(z.unknown()),
});
const RiskInputSchema = RiskSchema.omit({ id: true, companyId: true });
const SettingsUpdateSchema = z.discriminatedUnion("scope", [
z.object({ scope: z.literal("client"), changes: ClientSettingsSchema.partial() }),
z.object({ scope: z.literal("server"), changes: ServerSettingsSchema.partial() }),
]);
export const RpcRequestSchemas = {
"portfolio.get": z.undefined(),
"portfolio.addHolding": z.object({ ticker: tickerString }),
"portfolio.removeHolding": z.object({ ticker: tickerString }),
"company.get": z.object({ companyId: idString }),
"company.search": z.object({ query: nonEmptyString }),
"company.setActive": z.object({ companyId: idString }),
"workspace.getSection": z.object({ companyId: idString, section: nonEmptyString }),
"workspace.listSources": z.object({ companyId: idString }),
"catalyst.list": z.object({ companyId: idString }),
"alert.list": z.object({ companyId: idString.optional(), since: z.string().optional() }),
"risk.list": z.object({ companyId: idString }),
"risk.add": z.object({ companyId: idString, risk: RiskInputSchema }),
"earnings.getSchedule": z.object({ companyId: idString }),
"filing.list": z.object({ companyId: idString, since: z.string().optional() }),
"model.get": z.object({ companyId: idString, tab: nonEmptyString }),
"model.updateCell": z.object({
companyId: idString,
tab: nonEmptyString,
row: nonNegativeIndex,
col: nonNegativeIndex,
value: z.string(),
}),
"model.runScenario": z.object({
companyId: idString,
scenario: nonEmptyString,
overrides: z.record(z.string()),
}),
"memo.get": z.object({ companyId: idString }),
"memo.updateSection": z.object({
companyId: idString,
sectionId: idString,
title: nonEmptyString.optional(),
content: nonEmptyString,
}),
"memo.addAnnotation": z.object({
companyId: idString,
sectionId: idString,
kind: z.enum(["highlight", "comment", "strike"]),
selectedText: nonEmptyString,
comment: nonEmptyString.optional(),
}),
"memo.resolveAnnotation": z.object({ companyId: idString, annotationId: idString }),
"memo.updateSectionReview": z.object({
companyId: idString,
sectionId: idString,
status: z.enum(["pending", "in_review", "approved", "changes_requested"]),
}),
"memo.acceptEdit": z.object({ companyId: idString, editId: idString }),
"memo.rejectEdit": z.object({ companyId: idString, editId: idString, reason: z.string().optional() }),
"agent.list": z.object({ companyId: idString.optional() }),
"agent.start": z.object({ agentId: idString, companyId: idString }),
"agent.pause": z.object({ agentId: idString }),
"agent.restart": z.object({ agentId: idString }),
"agent.chat": z.object({ agentId: idString, message: nonEmptyString }),
"agent.configure": z.object({ agentId: idString, config: unknownRecord }),
"agent.getTrace": z.object({ agentId: idString, runId: idString }),
"agent.runPipeline": z.object({
companyId: idString,
pipeline: z.enum(["research", "competitive", "cross-cutting"]),
}),
"validation.run": z.object({ companyId: idString, agentType: z.enum(["sv", "qa", "rt", "all"]).optional() }),
"validation.getStatus": z.object({ companyId: idString, sectionId: idString.optional() }),
"export.list": z.object({ companyId: idString.optional() }),
"export.create": z.object({ type: z.enum(["pdf", "excel", "ppt"]), companyId: idString, options: unknownRecord.optional() }),
"export.download": z.object({ exportId: idString }),
"settings.get": z.object({ scope: z.enum(["client", "server"]) }),
"settings.update": SettingsUpdateSchema,
} satisfies Record<RpcMethod, z.ZodTypeAny>;
const SettingsResponseSchema = z.union([ClientSettingsSchema, ServerSettingsSchema]);
const ValidationIssueSchema = z.object({
severity: z.string(),
message: z.string(),
suggestion: z.string().optional(),
});
const ValidationResultSchema = z.object({
passed: z.boolean(),
confidence: z.string(),
issues: z.array(ValidationIssueSchema),
notes: z.string(),
timestamp: z.string(),
});
export const RpcResponseSchemas = {
"portfolio.get": z.object({ id: z.string(), name: z.string(), holdings: z.array(HoldingSchema), activeCompanyId: z.string() }),
"portfolio.addHolding": z.object({ holding: HoldingSchema }),
"portfolio.removeHolding": z.object({ ok: z.boolean() }),
"company.get": z.object({ company: CompanySchema }),
"company.search": z.object({ results: z.array(z.object({ ticker: z.string(), name: z.string(), sector: z.string() })) }),
"company.setActive": z.object({ ok: z.boolean() }),
"workspace.getSection": z.object({ content: WorkspaceSectionSchema, validationState: z.string() }),
"workspace.listSources": z.object({ sources: z.array(z.object({ type: z.string(), title: z.string(), metadata: z.string() })) }),
"catalyst.list": z.object({ catalysts: z.array(CatalystSchema) }),
"alert.list": z.object({ alerts: z.array(AlertSchema) }),
"risk.list": z.object({ risks: z.array(RiskSchema) }),
"risk.add": z.object({ risk: RiskSchema }),
"earnings.getSchedule": z.object({ schedule: z.array(EarningsScheduleSchema) }),
"filing.list": z.object({ filings: z.array(FilingSchema) }),
"model.get": z.object({ headers: z.array(z.string()), rows: z.array(ModelRowSchema) }),
"model.updateCell": z.object({ ok: z.boolean(), affectedCells: z.array(z.string()) }),
"model.runScenario": z.object({ headers: z.array(z.string()), rows: z.array(ModelRowSchema) }),
"memo.get": z.object({
status: z.enum(["draft", "review", "final"]),
sections: z.array(MemoSectionSchema),
citations: z.array(MemoCitationSchema),
annotations: z.array(MemoAnnotationSchema),
sectionReviews: z.array(MemoSectionReviewSchema),
}),
"memo.updateSection": z.object({
section: MemoSectionSchema,
status: z.enum(["draft", "review", "final"]),
savedAt: z.string(),
}),
"memo.addAnnotation": z.object({ annotation: MemoAnnotationSchema }),
"memo.resolveAnnotation": z.object({ annotation: MemoAnnotationSchema }),
"memo.updateSectionReview": z.object({ review: MemoSectionReviewSchema }),
"memo.acceptEdit": z.object({ ok: z.boolean() }),
"memo.rejectEdit": z.object({ ok: z.boolean() }),
"agent.list": z.object({ agents: z.array(AgentSchema) }),
"agent.start": z.object({ runId: z.string() }),
"agent.pause": z.object({ ok: z.boolean() }),
"agent.restart": z.object({ runId: z.string() }),
"agent.chat": z.object({ response: z.string() }),
"agent.configure": z.object({ ok: z.boolean() }),
"agent.getTrace": z.object({ steps: z.array(z.object({ step: z.number(), label: z.string(), detail: z.string() })) }),
"agent.runPipeline": z.object({ runIds: z.array(z.string()) }),
"validation.run": z.object({
sourceVerification: ValidationResultSchema.optional(),
modelQA: ValidationResultSchema.optional(),
redTeam: ValidationResultSchema.optional(),
}),
"validation.getStatus": z.object({
validationState: z.enum(["verified", "flagged", "unverified", "failed"]),
lastValidated: z.string().optional(),
}),
"export.list": z.object({ exports: z.array(ExportRecordSchema) }),
"export.create": z.object({ exportId: z.string() }),
"export.download": z.object({ data: z.instanceof(ArrayBuffer) }),
"settings.get": z.object({ settings: SettingsResponseSchema }),
"settings.update": z.object({ ok: z.boolean() }),
} satisfies Record<RpcMethod, z.ZodTypeAny>;
export const AgentProgressEventSchema = z.object({
type: z.literal("agent.progress"),
data: z.object({ runId: z.string(), agentId: z.string(), companyId: z.string(), progress: z.number(), action: z.string(), timestamp: z.string() }),
});
export const AgentCompletedEventSchema = z.object({
type: z.literal("agent.completed"),
data: z.object({ runId: z.string(), agentId: z.string(), companyId: z.string(), output: z.unknown(), duration: z.number(), timestamp: z.string() }),
});
export const AgentFailedEventSchema = z.object({
type: z.literal("agent.failed"),
data: z.object({ runId: z.string(), agentId: z.string(), companyId: z.string(), error: z.string(), timestamp: z.string() }),
});
export const AgentStartedEventSchema = z.object({
type: z.literal("agent.started"),
data: z.object({ runId: z.string(), agentId: z.string(), companyId: z.string(), pipeline: z.string().optional(), timestamp: z.string() }),
});
export const AgentStreamingEventSchema = z.object({
type: z.literal("agent.streaming"),
data: z.object({ runId: z.string(), agentId: z.string(), companyId: z.string(), chunk: z.string(), timestamp: z.string() }),
});
export const ValidationUpdatedEventSchema = z.object({
type: z.literal("validation.updated"),
data: z.object({
companyId: z.string(),
sectionId: z.string().optional(),
validationState: z.enum(["verified", "flagged", "unverified", "failed"]),
agentId: z.string(),
notes: z.string().optional(),
timestamp: z.string(),
}),
});
export const MemoUpdatedEventSchema = z.object({
type: z.literal("memo.updated"),
data: z.object({ companyId: z.string(), sectionId: z.string(), content: z.string(), updatedAt: z.string() }),
});
export const ModelUpdatedEventSchema = z.object({
type: z.literal("model.updated"),
data: z.object({ companyId: z.string(), tab: z.string(), cell: z.object({ row: z.number(), col: z.number() }).optional(), timestamp: z.string() }),
});
export const ServerEventSchema = z.discriminatedUnion("type", [
AgentProgressEventSchema,
AgentCompletedEventSchema,
AgentFailedEventSchema,
AgentStartedEventSchema,
AgentStreamingEventSchema,
ValidationUpdatedEventSchema,
MemoUpdatedEventSchema,
ModelUpdatedEventSchema,
]);
export const ServerEventTypeSchema = z.enum([
"agent.progress",
"agent.completed",
"agent.failed",
"agent.started",
"agent.streaming",
"validation.updated",
"memo.updated",
"model.updated",
]);
export function isRpcMethod(method: unknown): method is RpcMethod {
return typeof method === "string" && method in RpcRequestSchemas;
}
export function parseRpcRequest<T extends RpcMethod>(method: T, payload: unknown): RpcRequestMap[T] {
return RpcRequestSchemas[method].parse(payload) as RpcRequestMap[T];
}
export function parseRpcResponse<T extends RpcMethod>(method: T, data: unknown): RpcResponseMap[T] {
return RpcResponseSchemas[method].parse(data) as RpcResponseMap[T];
}
export type SettingsUpdatePayload =
| { scope: "client"; changes: Partial<ClientSettings> }
| { scope: "server"; changes: Partial<ServerSettings> };