import { z } from "zod"; import { apiClient } from "./client"; const idLike = z.union([z.string(), z.number()]).transform(String); /** * Research result and session payload are loose by design — the backend * returns `asdict(result)` of a Python dataclass we don't want to mirror * field-for-field. Keep core fields strict and let extras pass through. */ export const researchRunResultSchema = z .object({ job_id: idLike.optional(), project_id: idLike.optional(), project_name: z.string().optional(), goal: z.string().optional(), status: z.string().optional(), started_at: z.string().nullable().optional(), finished_at: z.string().nullable().optional(), steps_taken: z.number().optional(), pages_visited: z.number().optional(), entities_found: z.number().optional(), claims_added: z.number().optional(), error: z.string().nullable().optional(), }) .passthrough(); export type ResearchRunResult = z.infer; export const researchSessionItemSchema = z .object({ job_id: idLike, status: z.string().optional(), goal: z.string().nullable().optional(), seed_url: z.string().nullable().optional(), started_at: z.string().nullable().optional(), finished_at: z.string().nullable().optional(), steps_taken: z.number().nullable().optional(), pages_visited: z.number().nullable().optional(), entities_found: z.number().nullable().optional(), }) .passthrough(); export const researchSessionListSchema = z.array(researchSessionItemSchema); export type ResearchSessionItem = z.infer; export const researchSessionDetailSchema = researchSessionItemSchema.extend({ trace: z.array(z.record(z.string(), z.unknown())).optional(), }); export type ResearchSessionDetail = z.infer; export interface StartResearchRequest { project_name: string; source_name: string; url?: string; seed_entity_id?: number | null; goal?: string; max_depth?: number; max_steps?: number; max_branch?: number; min_relevance?: number; same_domain_only?: boolean; analyze_page_types?: string[]; extraction_mode?: "rule_only" | "llm_only" | "hybrid" | "compare"; extractor_provider?: string; extractor_model?: string | null; extractor_base_url?: string | null; fallback_to_rules?: boolean; check_robots_txt?: boolean; respect_robots_txt?: boolean | null; } export const researchApi = { startByProject: (body: StartResearchRequest) => apiClient.post("/research/run/by-project", researchRunResultSchema, body), listSessions: (projectName: string, limit = 25) => apiClient.get( `/projects/${encodeURIComponent(projectName)}/research/sessions`, researchSessionListSchema, { query: { limit } }, ), getSession: (jobId: string) => apiClient.get( `/research/sessions/${encodeURIComponent(jobId)}`, researchSessionDetailSchema, ), };