import type { KyInstance } from 'ky'; import { z } from 'zod'; import type { AuthStore } from '../auth/store.ts'; import { createHttpClient } from '../http/client.ts'; import { type BottleneckItem, bottlenecksResponseSchema, type CreateResearchRunResponse, createResearchRunResponseSchema, type HypothesisItem, hypothesesResponseSchema, type Me, meSchema, type Pattern, type PersonaItem, type ProductDetail, type ProductListItem, personasResponseSchema, productDetailSchema, productListItemSchema, type ResearchMode, type ResearchRun, researchModeSchema, researchRunSchema, statusResponseSchema, type WorkflowStartResponse, workflowStartResponseSchema, } from './types.ts'; export interface ApiClientOptions { apiBase: string; auth: AuthStore; } const parseArray = (schema: z.ZodType, raw: unknown): T[] => z.array(schema).parse(raw); export class ApiClient { private readonly http: KyInstance; constructor(opts: ApiClientOptions) { this.http = createHttpClient({ apiBase: opts.apiBase, auth: opts.auth }); } async me(): Promise { return meSchema.parse(await this.http.get('me').json()); } async listProducts(): Promise { return parseArray(productListItemSchema, await this.http.get('products').json()); } async getProduct(id: string): Promise { return productDetailSchema.parse(await this.http.get(`products/${id}`).json()); } async listPersonas(productId: string): Promise<{ analysisStatus: string; data: PersonaItem[]; }> { const parsed = personasResponseSchema.parse( await this.http.get(`products/${productId}/personas`).json(), ); return { analysisStatus: parsed.analysis_status, data: parsed.data }; } async listHypotheses(productId: string): Promise { const parsed = hypothesesResponseSchema.parse( await this.http.get(`products/${productId}/hypotheses`).json(), ); return parsed.data; } async listBottlenecks(productId: string): Promise { const parsed = bottlenecksResponseSchema.parse( await this.http.get(`products/${productId}/bottlenecks`).json(), ); return parsed.data; } async listPatterns(productId: string): Promise { const [hypotheses, bottlenecks] = await Promise.all([ this.listHypotheses(productId), this.listBottlenecks(productId), ]); return [ ...hypotheses.map((h): Pattern => ({ ...h, kind: 'hypothesis' })), ...bottlenecks.map((b): Pattern => ({ ...b, kind: 'bottleneck' })), ]; } async startSynthesis(productId: string): Promise<{ status: string }> { return statusResponseSchema.parse(await this.http.post(`products/${productId}/context`).json()); } async startAnalysis(productId: string): Promise<{ status: string }> { return statusResponseSchema.parse(await this.http.post(`products/${productId}/enrich`).json()); } async generatePersonas(productId: string): Promise { return workflowStartResponseSchema.parse( await this.http.post(`products/${productId}/enrich`).json(), ); } async listResearchModes(): Promise { return parseArray(researchModeSchema, await this.http.get('research-modes').json()); } async listResearchRuns(productId: string): Promise { return parseArray( researchRunSchema, await this.http.get(`products/${productId}/research-runs`).json(), ); } async getResearchRun(productId: string, runId: string): Promise { return researchRunSchema.parse( await this.http.get(`products/${productId}/research-runs/${runId}`).json(), ); } async createResearchRun( productId: string, body: { kind: 'ab_test' | 'delphi' | 'playtest' | 'experiment'; title?: string; config: Record; participant_rule?: Record; }, ): Promise { return createResearchRunResponseSchema.parse( await this.http.post(`products/${productId}/research-runs`, { json: body }).json(), ); } }