/** * HTTP client for Ezer Mishpati legal-ai REST API. */ /** * Where the client gets its base URL. A plain string still works; a resolver is * used when the URL comes from company-scoped plugin config, which cannot be * read at worker startup (see worker.ts — `ctx.config.get` needs a company). */ export type BaseUrlSource = string | (() => string | Promise); export class LegalApi { constructor(private baseUrl: BaseUrlSource) {} private async resolveBaseUrl(): Promise { return typeof this.baseUrl === "string" ? this.baseUrl : this.baseUrl(); } private async request(path: string, init?: RequestInit): Promise { const base = await this.resolveBaseUrl(); const res = await fetch(`${base}${path}`, { ...init, headers: { "Content-Type": "application/json", ...init?.headers, }, }); if (!res.ok) { const text = await res.text(); throw new Error(`Legal API ${res.status}: ${text}`); } return res.json() as Promise; } async listCases(): Promise { return this.request("/api/cases"); } async getCase(caseNumber: string): Promise { return this.request(`/api/cases/${encodeURIComponent(caseNumber)}/details`); } async createCase(data: CaseCreateInput): Promise { return this.request("/api/cases/create", { method: "POST", body: JSON.stringify(data), }); } async updateCase( caseNumber: string, data: CaseUpdateInput, ): Promise { return this.request(`/api/cases/${encodeURIComponent(caseNumber)}`, { method: "PUT", body: JSON.stringify(data), }); } async getCaseStatus(caseNumber: string): Promise { return this.request(`/api/cases/${encodeURIComponent(caseNumber)}/status`); } async search( query: string, limit = 10, sectionType = "", ): Promise { const params = new URLSearchParams({ query, limit: String(limit) }); if (sectionType) params.set("section_type", sectionType); return this.request(`/api/search?${params}`); } async searchCase( caseNumber: string, query: string, limit = 10, ): Promise { const params = new URLSearchParams({ query, limit: String(limit) }); return this.request( `/api/cases/${encodeURIComponent(caseNumber)}/search?${params}`, ); } async getTemplate(caseNumber: string): Promise<{ template: string }> { return this.request( `/api/cases/${encodeURIComponent(caseNumber)}/template`, ); } async getProcessingStatus(): Promise { return this.request("/api/processing-status"); } async health(): Promise<{ status: string }> { return this.request("/health"); } // ── New methods for expanded workflow ── async listDocuments(caseNumber: string): Promise { const details = await this.getCase(caseNumber); return details.documents || []; } async getDocumentText(docId: string): Promise { return this.request(`/api/documents/${docId}/text`); } async findSimilarCases(caseNumber: string): Promise { return this.request( `/api/cases/${encodeURIComponent(caseNumber)}/search?query=similar&limit=5`, ); } async setOutcome( caseNumber: string, outcome: string, reasoning?: string, ): Promise<{ status: string }> { return this.request( `/api/cases/${encodeURIComponent(caseNumber)}/outcome`, { method: "POST", body: JSON.stringify({ outcome, reasoning: reasoning || "" }), }, ); } async getClaims(caseNumber: string): Promise { return this.request(`/api/cases/${encodeURIComponent(caseNumber)}/claims`); } async setDirection( caseNumber: string, directionDoc: Record, ): Promise<{ status: string }> { return this.request( `/api/cases/${encodeURIComponent(caseNumber)}/direction`, { method: "POST", body: JSON.stringify({ direction_doc: directionDoc }), }, ); } async runQA(caseNumber: string): Promise { return this.request(`/api/cases/${encodeURIComponent(caseNumber)}/qa`, { method: "POST", }); } async triggerLearning(caseNumber: string): Promise<{ status: string }> { return this.request(`/api/cases/${encodeURIComponent(caseNumber)}/learn`, { method: "POST", }); } async getStyleGuide(): Promise { // Return static style guide reference return "ראה skill-legal-decision/SKILL.md — מדריך סגנון מלא של דפנה תמיר"; } /** * Recent conclusions of prior heartbeat runs across a case's issues (#220, * "seance"). Lets a resuming agent read what earlier sessions concluded * instead of re-deriving context from scratch. */ async getPredecessorForCase( caseNumber: string, limit = 5, ): Promise { return this.request( `/api/operations/cases/${encodeURIComponent(caseNumber)}/predecessor?limit=${limit}`, ); } } // Types export interface CaseSummary { case_number: string; title: string; status: string; } export interface CaseDetails { id: string; case_number: string; title: string; status: string; appellants: string[]; respondents: string[]; subject: string; property_address: string; expected_outcome: string; documents?: DocumentInfo[]; [key: string]: unknown; } export interface CaseCreateInput { case_number: string; title: string; appellants?: string[]; respondents?: string[]; subject?: string; property_address?: string; permit_number?: string; committee_type?: string; hearing_date?: string; notes?: string; expected_outcome?: string; } export interface CaseUpdateInput { status?: string; title?: string; subject?: string; notes?: string; hearing_date?: string; decision_date?: string; tags?: string[]; expected_outcome?: string; } export interface DocumentInfo { id: string; title: string; doc_type: string; extraction_status: string; page_count?: number; } export interface WorkflowStatus { case_number: string; title: string; status: string; documents: Array<{ title: string; type: string; extraction: string; chunks: number; pages?: number; }>; total_documents: number; total_chunks: number; has_draft: boolean; draft_size_bytes: number; next_steps: string[]; } export interface SearchResult { score: number; case_number?: string; document: string; section: string; page: number; content: string; } export interface ProcessingStatus { cases: number; documents: number; pending_processing: number; chunks: number; style_corpus_entries: number; style_patterns: number; } export interface ClaimsResponse { case_number: string; claims: Record< string, Array<{ party_role: string; claim_text: string; claim_index: number }> >; total: number; } export interface QAResponse { passed: boolean; checks: Array<{ check_name: string; passed: boolean; severity: string; details: string; }>; status: string; } export interface PredecessorRun { run_id: string; status: string; started_at: string | null; finished_at: string | null; summary: string | null; error_code: string | null; session_id: string | null; agent_name: string | null; identifier: string | null; } export interface PredecessorResponse { ok: boolean; case_number: string; runs: PredecessorRun[]; }