chore: remove unused verification, retros, and sessions endpoints

These endpoints had zero CLI callers and served only the web UI demo.
Verification and retros were `not_implemented` stubs in real mode;
sessions had an in-memory implementation but no CLI usage. Removing
them shrinks the API surface and eliminates ~9,000 lines of dead code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-04-06 17:52:51 -04:00
parent 4352508f55
commit 5b63e25b28
No known key found for this signature in database
78 changed files with 1961 additions and 11684 deletions

View file

@ -1,97 +0,0 @@
import { formatDurationSecs } from "../lib/format";
export type SmoothnessRating = "effortless" | "smooth" | "bumpy" | "struggled" | "failed";
type LearningCategory = "repo" | "code" | "workflow" | "tool";
export interface Learning {
category: LearningCategory;
text: string;
}
type FrictionKind = "retry" | "timeout" | "wrong_approach" | "tool_failure" | "ambiguity";
export interface FrictionPoint {
kind: FrictionKind;
description: string;
stage_id?: string;
}
type OpenItemKind = "tech_debt" | "follow_up" | "investigation" | "test_gap";
export interface OpenItem {
kind: OpenItemKind;
description: string;
}
export interface StageRetro {
stage_id: string;
stage_label: string;
status: string;
duration_ms: number;
retries: number;
cost?: number;
notes?: string;
failure_reason?: string;
files_touched: string[];
}
export interface AggregateStats {
total_duration_ms: number;
total_cost?: number;
total_retries: number;
files_touched: string[];
stages_completed: number;
stages_failed: number;
}
export interface Retro {
run_id: string;
workflow_name: string;
goal: string;
timestamp: string;
smoothness?: SmoothnessRating;
stages: StageRetro[];
stats: AggregateStats;
intent?: string;
outcome?: string;
learnings?: Learning[];
friction_points?: FrictionPoint[];
open_items?: OpenItem[];
}
export const smoothnessConfig: Record<SmoothnessRating, { label: string; bg: string; text: string; dot: string }> = {
effortless: { label: "Effortless", bg: "bg-emerald-500/15", text: "text-emerald-400", dot: "bg-emerald-400" },
smooth: { label: "Smooth", bg: "bg-mint/15", text: "text-mint", dot: "bg-mint" },
bumpy: { label: "Bumpy", bg: "bg-amber/15", text: "text-amber", dot: "bg-amber" },
struggled: { label: "Struggled", bg: "bg-orange-500/15", text: "text-orange-400", dot: "bg-orange-400" },
failed: { label: "Failed", bg: "bg-coral/15", text: "text-coral", dot: "bg-coral" },
};
export const learningCategoryConfig: Record<LearningCategory, { label: string; text: string }> = {
repo: { label: "Repo", text: "text-teal-400" },
code: { label: "Code", text: "text-sky-400" },
workflow: { label: "Workflow", text: "text-violet-400" },
tool: { label: "Tool", text: "text-amber" },
};
export const frictionKindConfig: Record<FrictionKind, { label: string; text: string }> = {
retry: { label: "Retry", text: "text-amber" },
timeout: { label: "Timeout", text: "text-coral" },
wrong_approach: { label: "Wrong Approach", text: "text-orange-400" },
tool_failure: { label: "Tool Failure", text: "text-coral" },
ambiguity: { label: "Ambiguity", text: "text-violet-400" },
};
export const openItemKindConfig: Record<OpenItemKind, { label: string; text: string }> = {
tech_debt: { label: "Tech Debt", text: "text-orange-400" },
follow_up: { label: "Follow-up", text: "text-teal-400" },
investigation: { label: "Investigation", text: "text-sky-400" },
test_gap: { label: "Test Gap", text: "text-coral" },
};
function formatDurationMs(ms: number): string {
return formatDurationSecs(Math.floor(ms / 1000));
}
export { formatDurationMs };

View file

@ -1,96 +0,0 @@
export type VerificationResult = "pass" | "fail" | "skip" | "na";
export type VerificationType = "ai" | "automated" | "analysis" | "ai-analysis";
export interface Criterion {
name: string;
description: string;
type: VerificationType | null;
status: VerificationResult;
}
export interface VerificationCategory {
name: string;
question: string;
status: VerificationResult;
criteria: Criterion[];
}
export const statusConfig = {
pass: {
label: "Pass",
color: "text-mint",
bg: "bg-mint/15",
dot: "bg-mint",
border: "border-l-mint/50",
},
fail: {
label: "Fail",
color: "text-coral",
bg: "bg-coral/15",
dot: "bg-coral",
border: "border-l-coral/50",
},
skip: {
label: "Skip",
color: "text-fg-muted",
bg: "bg-overlay",
dot: "bg-fg-muted",
border: "border-l-fg-muted/50",
},
na: {
label: "N/A",
color: "text-fg-muted",
bg: "bg-overlay",
dot: "bg-fg-muted",
border: "border-l-fg-muted/50",
},
} as const satisfies Record<
VerificationResult,
{ label: string; color: string; bg: string; dot: string; border: string }
>;
export const typeConfig = {
ai: { label: "AI", color: "text-teal-300", bg: "bg-teal-500/10" },
automated: { label: "Automated", color: "text-mint", bg: "bg-mint/10" },
analysis: { label: "Analysis", color: "text-amber", bg: "bg-amber/10" },
"ai-analysis": { label: "AI + Analysis", color: "text-teal-300", bg: "bg-teal-500/10" },
} as const satisfies Record<
VerificationType,
{ label: string; color: string; bg: string }
>;
export type VerificationMode = "active" | "evaluate" | "disabled";
export interface CriterionPerformance {
f1: number | null;
passAt1: number | null;
mode: VerificationMode;
evaluations: VerificationResult[];
}
export const modeConfig = {
active: { label: "Active", color: "text-mint", bg: "bg-mint/10" },
evaluate: { label: "Evaluate", color: "text-amber", bg: "bg-amber/10" },
disabled: { label: "Disabled", color: "text-fg-muted", bg: "bg-overlay" },
} as const satisfies Record<
VerificationMode,
{ label: string; color: string; bg: string }
>;
export function getCriteriaSummary(criteria: readonly Criterion[]) {
return {
passing: criteria.filter((c) => c.status === "pass").length,
failing: criteria.filter((c) => c.status === "fail").length,
na: criteria.filter((c) => c.status === "na").length,
total: criteria.length,
};
}
export function slugify(name: string): string {
return name
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/(^-|-$)/g, "");
}

View file

@ -11,8 +11,6 @@ import {
Bars3Icon,
BeakerIcon,
ChartBarIcon,
CheckBadgeIcon,
LightBulbIcon,
MoonIcon,
PlayIcon,
RectangleStackIcon,
@ -30,8 +28,6 @@ export async function loader() {
const navigation = [
{ name: "Workflows", href: "/workflows", icon: RectangleStackIcon },
{ name: "Runs", href: "/runs", icon: PlayIcon },
{ name: "Verification", href: "/verification/criteria", icon: CheckBadgeIcon },
{ name: "Retros", href: "/retros", icon: LightBulbIcon },
{ name: "Insights", href: "/insights", icon: ChartBarIcon },
];

View file

@ -22,46 +22,3 @@ export function timeUntil(iso: string): string {
return relativeTime(Math.floor((new Date(iso).getTime() - Date.now()) / 1000), false);
}
/**
* Return a human-readable date label for grouping (e.g. "Today", "Yesterday", "Previous 7 days").
*/
function dateLabel(iso: string): string {
const now = new Date();
const date = new Date(iso);
const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const startOfYesterday = new Date(startOfToday.getTime() - 86_400_000);
const startOf7DaysAgo = new Date(startOfToday.getTime() - 7 * 86_400_000);
if (date >= startOfToday) return "Today";
if (date >= startOfYesterday) return "Yesterday";
if (date >= startOf7DaysAgo) return "Previous 7 days";
return "Older";
}
interface SessionItem {
id: string;
title: string;
created_at: string;
}
interface SessionGroup {
label: string;
sessions: SessionItem[];
}
/**
* Group a flat list of sessions (already sorted newest-first) into date-labeled groups.
*/
export function groupSessionsByDate(sessions: SessionItem[]): SessionGroup[] {
const groups: SessionGroup[] = [];
let current: SessionGroup | undefined;
for (const s of sessions) {
const label = dateLabel(s.created_at);
if (!current || current.label !== label) {
current = { label, sessions: [] };
groups.push(current);
}
current.sessions.push(s);
}
return groups;
}

View file

@ -12,7 +12,6 @@ import * as Setup from "./routes/setup";
import * as SetupComplete from "./routes/setup-complete";
import * as AuthLogin from "./routes/auth-login";
import * as Start from "./routes/start";
import * as SessionDetail from "./routes/session-detail";
import * as Workflows from "./routes/workflows";
import * as WorkflowDetail from "./routes/workflow-detail";
import * as WorkflowDefinition from "./routes/workflow-definition";
@ -26,12 +25,6 @@ import * as RunSettings from "./routes/run-settings";
import * as RunGraph from "./routes/run-graph";
import * as RunFiles from "./routes/run-files";
import * as RunUsage from "./routes/run-usage";
import * as RunRetro from "./routes/run-retro";
import * as VerificationCriteria from "./routes/verification-criteria";
import * as VerificationCriterion from "./routes/verification-criterion";
import * as VerificationControls from "./routes/verification-controls";
import * as VerificationControl from "./routes/verification-control";
import * as Retros from "./routes/retros";
import * as Insights from "./routes/insights";
import * as InsightsEditor from "./routes/insights-editor";
import * as InsightsNew from "./routes/insights-new";
@ -97,7 +90,6 @@ export const routes: RouteObject[] = [
}),
children: [
route("start", Start),
route("sessions/:sessionId", SessionDetail),
route("workflows", Workflows),
route("workflows/:name", WorkflowDetail, {
children: [
@ -115,14 +107,8 @@ export const routes: RouteObject[] = [
route("graph", RunGraph),
route("files", RunFiles),
route("usage", RunUsage),
route("retro", RunRetro),
],
}),
route("verification/criteria", VerificationCriteria),
route("verification/criteria/:id", VerificationCriterion),
route("verification/controls", VerificationControls),
route("verification/controls/:id", VerificationControl),
route("retros", Retros),
route("insights", Insights, {
children: [
indexRoute(InsightsEditor),

View file

@ -1,159 +0,0 @@
import { useState } from "react";
import { useNavigate } from "react-router";
import { MagnifyingGlassIcon, ChevronDownIcon } from "@heroicons/react/24/outline";
import { smoothnessConfig, formatDurationMs } from "../data/retros";
import type { SmoothnessRating } from "../data/retros";
import { apiJson } from "../api";
import type { PaginatedRetroList } from "@qltysh/fabro-api-client";
interface RetroRow {
run_id: string;
workflow_name: string;
goal: string;
timestamp: string;
smoothness?: SmoothnessRating;
total_duration_ms: number;
friction_point_count: number;
}
export async function loader({ request }: any) {
const { data: apiRetros } = await apiJson<PaginatedRetroList>("/retros", { request });
const retros: RetroRow[] = apiRetros.map((r) => ({
run_id: r.run.id,
workflow_name: r.workflow.slug,
goal: r.run.title,
timestamp: r.timestamp,
smoothness: r.smoothness as SmoothnessRating | undefined,
total_duration_ms: r.stats.total_duration_ms,
friction_point_count: r.friction_point_count,
}));
return { retros };
}
export function meta({}: any) {
return [{ title: "Retros \u2014 Fabro" }];
}
const smoothnessOptions: Array<{ value: SmoothnessRating; label: string }> = [
{ value: "effortless", label: "Effortless" },
{ value: "smooth", label: "Smooth" },
{ value: "bumpy", label: "Bumpy" },
{ value: "struggled", label: "Struggled" },
{ value: "failed", label: "Failed" },
];
function SmoothnesssBadge({ smoothness }: { smoothness: SmoothnessRating | undefined }) {
if (!smoothness) {
return <span className="text-xs text-fg-muted">--</span>;
}
const config = smoothnessConfig[smoothness];
return (
<span className={`inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium ${config.bg} ${config.text}`}>
<span className={`size-1.5 rounded-full ${config.dot}`} />
{config.label}
</span>
);
}
function formatTimestamp(ts: string): string {
const date = new Date(ts);
return date.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
});
}
function truncate(text: string, maxLength: number): string {
if (text.length <= maxLength) return text;
return text.slice(0, maxLength) + "\u2026";
}
export default function Retros({ loaderData }: any) {
const { retros } = loaderData;
const navigate = useNavigate();
const [query, setQuery] = useState("");
const [smoothnessFilter, setSmoothnessFilter] = useState<SmoothnessRating | "all">("all");
if (retros.length === 0) {
return <p className="py-8 text-center text-sm text-fg-muted">No retrospectives yet.</p>;
}
const lowerQuery = query.toLowerCase();
const filtered = retros.filter(
(r) =>
(smoothnessFilter === "all" || r.smoothness === smoothnessFilter) &&
(r.goal.toLowerCase().includes(lowerQuery) ||
r.workflow_name.toLowerCase().includes(lowerQuery)),
);
return (
<div className="space-y-4">
<div className="flex gap-3">
<div className="relative flex-1">
<MagnifyingGlassIcon className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-fg-muted" />
<input
type="text"
placeholder="Search retros…"
value={query}
onChange={(e) => setQuery(e.target.value)}
className="w-full rounded-md border border-line bg-panel/80 py-2 pl-9 pr-3 text-sm text-fg-2 placeholder-fg-muted outline-none transition-colors focus:border-focus focus:ring-0"
/>
</div>
<div className="relative">
<select
value={smoothnessFilter}
onChange={(e) => setSmoothnessFilter(e.target.value as SmoothnessRating | "all")}
className="appearance-none rounded-md border border-line bg-panel/80 py-2 pl-3 pr-8 text-sm text-fg-2 outline-none transition-colors focus:border-focus focus:ring-0"
>
<option value="all">All smoothness</option>
{smoothnessOptions.map((opt) => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
<ChevronDownIcon className="pointer-events-none absolute right-2 top-1/2 size-4 -translate-y-1/2 text-fg-muted" />
</div>
</div>
<div className="rounded-md border border-line overflow-hidden">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-line bg-panel/60 text-left text-xs text-fg-muted">
<th className="px-4 py-2.5 font-medium">Workflow</th>
<th className="px-4 py-2.5 font-medium">Goal</th>
<th className="px-4 py-2.5 font-medium">Smoothness</th>
<th className="px-4 py-2.5 font-medium text-right">Duration</th>
<th className="px-4 py-2.5 font-medium text-right">Frictions</th>
<th className="px-4 py-2.5 font-medium text-right">When</th>
</tr>
</thead>
<tbody>
{filtered.map((retro) => (
<tr key={retro.run_id} className="border-b border-line last:border-b-0 transition-colors hover:bg-overlay cursor-pointer" onClick={() => navigate(`/runs/${retro.run_id}/retro`)}>
<td className="px-4 py-3 font-mono text-xs font-medium text-teal-500">
{retro.workflow_name}
</td>
<td className="px-4 py-3 text-fg-2">
{truncate(retro.goal, 60)}
</td>
<td className="px-4 py-3">
<SmoothnesssBadge smoothness={retro.smoothness} />
</td>
<td className="px-4 py-3 text-right font-mono text-xs tabular-nums text-fg-3">
{formatDurationMs(retro.total_duration_ms)}
</td>
<td className="px-4 py-3 text-right font-mono text-xs tabular-nums text-fg-3">
{retro.friction_point_count}
</td>
<td className="px-4 py-3 text-right font-mono text-xs text-fg-muted">
{formatTimestamp(retro.timestamp)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}

View file

@ -11,7 +11,6 @@ const tabs = [
{ name: "Overview", path: "", count: null },
{ name: "Stages", path: "/stages/detect-drift", count: null },
{ name: "Files Changed", path: "/files", count: null },
{ name: "Retro", path: "/retro", count: null },
{ name: "Usage", path: "/usage", count: null },
];

View file

@ -1,231 +0,0 @@
import { Link } from "react-router";
import {
smoothnessConfig,
learningCategoryConfig,
frictionKindConfig,
openItemKindConfig,
formatDurationMs,
} from "../data/retros";
import type { Retro } from "../data/retros";
import { apiJson } from "../api";
export async function loader({ request, params }: any) {
const retro = await apiJson<Retro>(`/runs/${params.id}/retro`, { request });
return { retro };
}
export function meta({ data }: any) {
const retro = data?.retro;
return [{ title: retro ? `Retro: ${retro.goal} \u2014 Fabro` : "Retro \u2014 Fabro" }];
}
function formatCost(cost: number | undefined): string {
if (cost == null) return "--";
return `$${cost.toFixed(2)}`;
}
export default function RunRetro({ loaderData }: any) {
const { retro } = loaderData;
if (!retro) {
return <p className="py-8 text-center text-sm text-fg-muted">No retrospective found for this run.</p>;
}
const smoothness = retro.smoothness ? smoothnessConfig[retro.smoothness] : null;
return (
<div className="space-y-6">
{/* Smoothness + Summary Header */}
<div className="flex items-start gap-4">
{smoothness && (
<span className={`inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-semibold ${smoothness.bg} ${smoothness.text}`}>
<span className={`size-2.5 rounded-full ${smoothness.dot}`} />
{smoothness.label}
</span>
)}
<div className="min-w-0 flex-1">
<p className="text-sm text-fg-3">{retro.goal}</p>
<p className="mt-1 font-mono text-xs text-fg-muted">
{retro.workflow_name} &middot; {new Date(retro.timestamp).toLocaleString()}
</p>
</div>
</div>
{/* Aggregate Stats */}
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
<StatCard label="Duration" value={formatDurationMs(retro.stats.total_duration_ms)} />
<StatCard label="Cost" value={formatCost(retro.stats.total_cost)} />
<StatCard label="Retries" value={String(retro.stats.total_retries)} warn={retro.stats.total_retries > 0} />
<StatCard label="Files" value={String(retro.stats.files_touched.length)} />
</div>
{/* Intent + Outcome */}
{(retro.intent ?? retro.outcome) && (
<div className="grid gap-3 sm:grid-cols-2">
{retro.intent && (
<div className="rounded-md border border-line bg-panel/60 p-4">
<h3 className="text-xs font-medium uppercase tracking-wider text-fg-muted">Intent</h3>
<p className="mt-2 text-sm leading-relaxed text-fg-3">{retro.intent}</p>
</div>
)}
{retro.outcome && (
<div className="rounded-md border border-line bg-panel/60 p-4">
<h3 className="text-xs font-medium uppercase tracking-wider text-fg-muted">Outcome</h3>
<p className="mt-2 text-sm leading-relaxed text-fg-3">{retro.outcome}</p>
</div>
)}
</div>
)}
{/* Learnings */}
{retro.learnings && retro.learnings.length > 0 && (
<div>
<h3 className="mb-3 text-xs font-medium uppercase tracking-wider text-fg-muted">Learnings</h3>
<div className="space-y-2">
{retro.learnings.map((learning, i) => {
const config = learningCategoryConfig[learning.category];
return (
<div key={learning.text} className="flex items-start gap-3 rounded-md border border-line bg-panel/60 px-4 py-3">
<span className={`mt-0.5 shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider ${config.text} bg-overlay`}>
{config.label}
</span>
<p className="text-sm leading-relaxed text-fg-3">{learning.text}</p>
</div>
);
})}
</div>
</div>
)}
{/* Friction Points */}
{retro.friction_points && retro.friction_points.length > 0 && (
<div>
<h3 className="mb-3 text-xs font-medium uppercase tracking-wider text-fg-muted">Friction Points</h3>
<div className="space-y-2">
{retro.friction_points.map((fp, i) => {
const config = frictionKindConfig[fp.kind];
return (
<div key={fp.description} className="flex items-start gap-3 rounded-md border border-line bg-panel/60 px-4 py-3">
<span className={`mt-0.5 shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider ${config.text} bg-overlay`}>
{config.label}
</span>
<div className="min-w-0 flex-1">
<p className="text-sm leading-relaxed text-fg-3">{fp.description}</p>
{fp.stage_id && (
<p className="mt-1 font-mono text-xs text-fg-muted">
Stage: {retro.stages.find((s) => s.stage_id === fp.stage_id)?.stage_label ?? fp.stage_id}
</p>
)}
</div>
</div>
);
})}
</div>
</div>
)}
{/* Open Items */}
{retro.open_items && retro.open_items.length > 0 && (
<div>
<h3 className="mb-3 text-xs font-medium uppercase tracking-wider text-fg-muted">Open Items</h3>
<div className="space-y-2">
{retro.open_items.map((item, i) => {
const config = openItemKindConfig[item.kind];
return (
<div key={item.description} className="flex items-start gap-3 rounded-md border border-line bg-panel/60 px-4 py-3">
<span className={`mt-0.5 shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider ${config.text} bg-overlay`}>
{config.label}
</span>
<p className="text-sm leading-relaxed text-fg-3">{item.description}</p>
</div>
);
})}
</div>
</div>
)}
{/* Stage Breakdown */}
<div>
<h3 className="mb-3 text-xs font-medium uppercase tracking-wider text-fg-muted">Stage Breakdown</h3>
<div className="rounded-md border border-line overflow-hidden">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-line bg-panel/60 text-left text-xs text-fg-muted">
<th className="px-4 py-2.5 font-medium">Stage</th>
<th className="px-4 py-2.5 font-medium">Status</th>
<th className="px-4 py-2.5 font-medium text-right">Duration</th>
<th className="px-4 py-2.5 font-medium text-right">Retries</th>
<th className="px-4 py-2.5 font-medium text-right">Cost</th>
<th className="px-4 py-2.5 font-medium text-right">Files</th>
</tr>
</thead>
<tbody>
{retro.stages.map((stage) => (
<tr key={stage.stage_id} className="border-b border-line last:border-b-0">
<td className="px-4 py-3">
<Link
to={`/runs/${retro.run_id}/stages/${stage.stage_id}`}
className="text-fg-2 hover:text-fg"
>
{stage.stage_label}
</Link>
{stage.notes && (
<p className="mt-1 text-xs text-fg-muted">{stage.notes}</p>
)}
{stage.failure_reason && (
<p className="mt-1 text-xs text-coral/80">{stage.failure_reason}</p>
)}
</td>
<td className="px-4 py-3">
<StageStatusBadge status={stage.status} />
</td>
<td className="px-4 py-3 text-right font-mono text-xs tabular-nums text-fg-3">
{formatDurationMs(stage.duration_ms)}
</td>
<td className="px-4 py-3 text-right font-mono text-xs tabular-nums">
<span className={stage.retries > 0 ? "text-amber" : "text-fg-3"}>
{stage.retries}
</span>
</td>
<td className="px-4 py-3 text-right font-mono text-xs tabular-nums text-fg-3">
{formatCost(stage.cost)}
</td>
<td className="px-4 py-3 text-right font-mono text-xs tabular-nums text-fg-3">
{stage.files_touched.length}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
}
function StatCard({ label, value, warn }: { label: string; value: string; warn?: boolean }) {
return (
<div className="rounded-md border border-line bg-panel/60 px-4 py-3">
<p className="text-xs font-medium uppercase tracking-wider text-fg-muted">{label}</p>
<p className={`mt-1 font-mono text-lg font-semibold tabular-nums ${warn ? "text-amber" : "text-fg"}`}>
{value}
</p>
</div>
);
}
function StageStatusBadge({ status }: { status: string }) {
const styles: Record<string, string> = {
completed: "text-mint",
running: "text-teal-500",
pending: "text-fg-muted",
failed: "text-coral",
cancelled: "text-fg-muted",
};
const colorClass = styles[status] ?? "text-fg-3";
return (
<span className={`text-xs font-medium capitalize ${colorClass}`}>
{status}
</span>
);
}

View file

@ -1,448 +0,0 @@
import { useState } from "react";
import { Link, useParams } from "react-router";
import {
ChatBubbleLeftIcon,
ClipboardDocumentIcon,
CheckIcon,
PencilSquareIcon,
UserIcon,
} from "@heroicons/react/24/outline";
import { ToolRow, ToolBlock } from "../components/tool-use";
import type { ToolUse } from "../components/tool-use";
import { timeAgo, groupSessionsByDate } from "../lib/time";
import { apiJson } from "../api";
import type { SessionDetail as ApiSessionDetail, PaginatedSessionList } from "@qltysh/fabro-api-client";
export const handle = { hideHeader: true, wide: true };
export function meta({}: any) {
return [{ title: "Session — Fabro" }];
}
export async function loader({ request, params }: any) {
const [apiSession, { data: apiSessions }] = await Promise.all([
apiJson<ApiSessionDetail>(`/sessions/${params.sessionId}`, { request }),
apiJson<PaginatedSessionList>("/sessions", { request }),
]);
const session: Session = {
id: apiSession.id,
title: apiSession.title,
model: apiSession.model.id,
created_at: apiSession.created_at,
updated_at: apiSession.updated_at,
turns: apiSession.turns.map((t): Turn => {
switch (t.kind) {
case "tool":
return {
kind: "tool",
tools: t.tools.map((tu) => ({
id: tu.id,
toolName: tu.tool_name,
input: tu.input,
result: tu.result,
isError: tu.is_error,
durationMs: tu.duration_ms,
})),
};
case "user":
return { kind: "user", content: t.content, created_at: t.created_at };
case "assistant":
return { kind: "assistant", content: t.content };
}
}),
};
const sessionGroups = groupSessionsByDate(
apiSessions.map((s) => ({ id: s.id, title: s.title, created_at: s.created_at }))
);
return { session, sessionGroups };
}
type Turn =
| { kind: "user"; content: string; created_at?: string }
| { kind: "assistant"; content: string }
| { kind: "tool"; tools: ToolUse[] };
interface Session {
id: string;
title: string;
model: string;
created_at: string;
updated_at: string;
turns: Turn[];
}
// Keep hardcoded sessions as fallback
const sessions: Record<string, Session> = {
s1: {
id: "s1",
title: "Add rate limiting to auth endpoints",
model: "Opus 4.6",
created_at: "2026-03-06T14:30:00Z",
updated_at: "2026-03-06T15:45:00Z",
turns: [
{
kind: "user",
created_at: "2026-02-28T10:00:00Z",
content: "Add rate limiting to the auth endpoints. We're getting hit with brute force attempts on /api/auth/login and /api/auth/register. Use a sliding window approach with Redis, 10 requests per minute per IP.",
},
{
kind: "assistant",
content: "I'll implement sliding window rate limiting using Redis. Let me first look at the existing auth routes and middleware setup.",
},
{
kind: "tool",
tools: [
{
id: "toolu_s1_01",
toolName: "read_file",
input: `{ "path": "src/routes/auth.ts" }`,
result: `import { Router } from "express";\nimport { loginHandler } from "../handlers/auth";\nimport { registerHandler } from "../handlers/auth";\n\nconst router = Router();\nrouter.post("/login", loginHandler);\nrouter.post("/register", registerHandler);\n\nexport default router;`,
isError: false,
durationMs: 45,
},
{
id: "toolu_s1_02",
toolName: "read_file",
input: `{ "path": "src/middleware/index.ts" }`,
result: `export { authMiddleware } from "./auth";\nexport { corsMiddleware } from "./cors";\nexport { loggingMiddleware } from "./logging";`,
isError: false,
durationMs: 38,
},
{
id: "toolu_s1_03",
toolName: "search_files",
input: `{ "pattern": "redis", "path": "src/" }`,
result: `src/lib/redis.ts:1: import Redis from "ioredis";\nsrc/lib/redis.ts:3: export const redis = new Redis(process.env.REDIS_URL);`,
isError: false,
durationMs: 210,
},
],
},
{
kind: "assistant",
content: "Good — there's already a Redis client at `src/lib/redis.ts`. I'll create a rate limiting middleware using the sliding window algorithm and apply it to the auth routes.\n\nHere's the plan:\n1. Create `src/middleware/rate-limit.ts` with sliding window logic\n2. Apply it to `/login` and `/register` routes\n3. Return `429 Too Many Requests` when the limit is exceeded",
},
{
kind: "tool",
tools: [
{
id: "toolu_s1_04",
toolName: "write_file",
input: `{ "path": "src/middleware/rate-limit.ts" }`,
result: `File written: src/middleware/rate-limit.ts (47 lines)`,
isError: false,
durationMs: 65,
},
{
id: "toolu_s1_05",
toolName: "edit_file",
input: `{ "path": "src/routes/auth.ts", "operations": [{ "type": "insert", "line": 3 }] }`,
result: `Applied 1 edit to src/routes/auth.ts`,
isError: false,
durationMs: 42,
},
{
id: "toolu_s1_06",
toolName: "edit_file",
input: `{ "path": "src/middleware/index.ts", "operations": [{ "type": "append" }] }`,
result: `Applied 1 edit to src/middleware/index.ts`,
isError: false,
durationMs: 55,
},
],
},
{
kind: "assistant",
content: "Done. I've created the rate limiter and wired it up:\n\n- **`src/middleware/rate-limit.ts`** — Sliding window rate limiter using Redis sorted sets. Each request adds a timestamped entry, expired entries are pruned, and the count is checked against the limit.\n- **`src/routes/auth.ts`** — Applied `rateLimit({ windowMs: 60_000, max: 10 })` to both `/login` and `/register`.\n- **`src/middleware/index.ts`** — Re-exported the new middleware.\n\nWhen the limit is exceeded, clients get a `429` response with a `Retry-After` header indicating seconds until the window resets.",
},
],
},
s2: {
id: "s2",
title: "Fix config parsing for nested values",
model: "Sonnet 4.6",
created_at: "2026-03-06T12:30:00Z",
updated_at: "2026-03-06T13:15:00Z",
turns: [
{
kind: "user",
created_at: "2026-02-28T10:00:00Z",
content: "The CLI crashes when parsing nested TOML config values like [database.connection]. Can you debug and fix this?",
},
{
kind: "assistant",
content: "Let me look at the config parser to understand how nested keys are handled.",
},
{
kind: "tool",
tools: [
{
id: "toolu_s2_01",
toolName: "read_file",
input: `{ "path": "src/config/parser.ts" }`,
result: `export function parseConfig(raw: string): Record<string, string> {\n const result: Record<string, string> = {};\n for (const line of raw.split("\\n")) {\n const match = line.match(/^(\\w+)\\s*=\\s*(.+)$/);\n if (match) result[match[1]] = match[2].trim();\n }\n return result;\n}`,
isError: false,
durationMs: 52,
},
],
},
{
kind: "assistant",
content: "Found the issue. The parser uses a flat `Record<string, string>` and the regex `^(\\w+)` only matches simple keys — it can't handle TOML section headers like `[database.connection]` or dotted keys like `database.host`.\n\nI'll rewrite the parser to support nested sections and dotted keys, producing a properly nested object.",
},
{
kind: "tool",
tools: [
{
id: "toolu_s2_02",
toolName: "write_file",
input: `{ "path": "src/config/parser.ts" }`,
result: `File written: src/config/parser.ts (62 lines)`,
isError: false,
durationMs: 78,
},
{
id: "toolu_s2_03",
toolName: "run_command",
input: `{ "command": "npm test -- --grep 'config parser'" }`,
result: `PASS src/config/__tests__/parser.test.ts\n config parser\n ✓ parses flat keys (2ms)\n ✓ parses section headers (1ms)\n ✓ parses dotted keys (1ms)\n ✓ handles nested sections (1ms)\n\nTest Suites: 1 passed\nTests: 4 passed`,
isError: false,
durationMs: 2150,
},
],
},
{
kind: "assistant",
content: "Fixed. The parser now:\n\n1. Tracks the current section header (e.g. `[database.connection]`)\n2. Constructs nested objects from dotted paths\n3. Handles both `key = value` within sections and bare `dotted.key = value` syntax\n\nAll 4 tests pass, including the new nested section case.",
},
],
},
s3: {
id: "s3",
title: "Migrate to React Router v7",
model: "Opus 4.6",
created_at: "2026-03-05T10:00:00Z",
updated_at: "2026-03-05T11:30:00Z",
turns: [
{ kind: "user", created_at: "2026-02-26T10:00:00Z", content: "Help me migrate our app from React Router v6 to v7. We're using createBrowserRouter with data loaders." },
{ kind: "assistant", content: "I'll audit your current router setup and identify what needs to change for v7. Let me scan the codebase." },
{
kind: "tool",
tools: [
{ id: "toolu_s3_01", toolName: "search_files", input: `{ "pattern": "createBrowserRouter" }`, result: `src/main.tsx:5: import { createBrowserRouter } from "react-router-dom";\nsrc/main.tsx:8: const router = createBrowserRouter([`, isError: false, durationMs: 180 },
{ id: "toolu_s3_02", toolName: "read_file", input: `{ "path": "package.json" }`, result: `"react-router-dom": "^6.22.0"`, isError: false, durationMs: 35 },
],
},
{ kind: "assistant", content: "You're on React Router 6.22. The migration to v7 involves:\n\n1. Replace `react-router-dom` with `react-router` (single package)\n2. Update `createBrowserRouter` import path\n3. Migrate data loaders to the new `any` type\n4. Update any `useLoaderData` calls to be type-safe\n\nLet me make these changes." },
],
},
};
// Fallback for unknown session IDs
function makeFallbackSession(id: string): Session {
return {
id,
title: "Session",
model: "Opus 4.6",
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
turns: [
{ kind: "user", created_at: "2026-02-28T10:00:00Z", content: "Hello, let's get started." },
{ kind: "assistant", content: "Sure! What would you like to work on?" },
],
};
}
interface SessionGroupType {
label: string;
sessions: { id: string; title: string; created_at: string }[];
}
const sessionGroups: SessionGroupType[] = [
{
label: "Today",
sessions: [
{ id: "s1", title: "Add rate limiting to auth endpoints", created_at: "2026-03-06T14:30:00Z" },
{ id: "s2", title: "Fix config parsing for nested values", created_at: "2026-03-06T12:30:00Z" },
],
},
{
label: "Yesterday",
sessions: [
{ id: "s3", title: "Migrate to React Router v7", created_at: "2026-03-05T10:00:00Z" },
{ id: "s4", title: "Add dark mode toggle", created_at: "2026-03-05T09:00:00Z" },
{ id: "s5", title: "Update OpenAPI spec for v3", created_at: "2026-03-05T08:00:00Z" },
],
},
{
label: "Previous 7 days",
sessions: [
{ id: "s6", title: "Terraform module for Redis cluster", created_at: "2026-03-03T15:00:00Z" },
{ id: "s7", title: "Add pipeline event types", created_at: "2026-03-01T11:00:00Z" },
{ id: "s8", title: "Implement webhook retry logic", created_at: "2026-02-28T09:00:00Z" },
],
},
];
function CopyButton({ text }: { text: string }) {
const [copied, setCopied] = useState(false);
function handleCopy() {
navigator.clipboard.writeText(text).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2000);
});
}
return (
<button
type="button"
onClick={handleCopy}
className="inline-flex items-center justify-center rounded-md border border-line bg-panel/80 p-1.5 text-fg-muted transition-colors hover:border-line-strong hover:text-fg-3"
aria-label="Copy"
>
{copied
? <CheckIcon className="size-3.5" />
: <ClipboardDocumentIcon className="size-3.5" />}
</button>
);
}
function UserBlock({ content, created_at }: { content: string; created_at?: string }) {
return (
<div className="group">
<div className="flex gap-3">
<div className="flex size-7 shrink-0 items-center justify-center rounded-full bg-panel border border-line-strong">
<UserIcon className="size-3.5 text-fg-3" />
</div>
<div className="min-w-0 flex-1 pt-0.5">
<pre className="whitespace-pre-wrap font-sans text-sm leading-relaxed text-fg-2">{content}</pre>
</div>
</div>
<div className="ml-10 mt-1 flex h-6 items-center gap-2 opacity-0 transition-opacity group-hover:opacity-100">
<CopyButton text={content} />
{created_at != null && <span className="text-[11px] text-fg-muted">{timeAgo(created_at)}</span>}
</div>
</div>
);
}
function AssistantBlock({ content, showCopy }: { content: string; showCopy: boolean }) {
return (
<div className="group">
<div className="flex gap-3">
<div className="flex size-7 shrink-0 items-center justify-center rounded-full bg-teal-500/10 border border-teal-500/20">
<ChatBubbleLeftIcon className="size-3.5 text-teal-500" />
</div>
<div className="min-w-0 flex-1 pt-0.5">
<pre className="whitespace-pre-wrap font-sans text-sm leading-relaxed text-fg-3">{content}</pre>
</div>
</div>
{showCopy && (
<div className="ml-10 mt-1 h-6 opacity-0 transition-opacity group-hover:opacity-100">
<CopyButton text={content} />
</div>
)}
</div>
);
}
function SessionSidebar({ activeId, groups }: { activeId: string; groups: SessionGroupType[] }) {
return (
<aside className="w-64 shrink-0 border-r border-line flex flex-col h-[calc(100vh-4rem)]">
<div className="p-3">
<Link
to="/start"
className="flex w-full items-center gap-2 rounded-lg border border-line bg-panel/60 px-3 py-2 text-sm text-fg-2 transition-colors hover:bg-panel hover:border-line-strong"
>
<PencilSquareIcon className="size-4 text-fg-muted" />
New session
</Link>
</div>
<nav className="flex-1 overflow-y-auto px-3 pb-4">
{groups.map((group) => (
<div key={group.label} className="mt-4 first:mt-1">
<p className="px-2 mb-1.5 text-[11px] font-medium uppercase tracking-wider text-fg-muted">
{group.label}
</p>
<ul className="space-y-0.5">
{group.sessions.map((s) => (
<li key={s.id}>
<Link
to={`/sessions/${s.id}`}
className={`flex w-full flex-col rounded-lg px-2.5 py-2 text-left transition-colors ${
activeId === s.id
? "bg-overlay text-fg-2"
: "text-fg-3 hover:bg-overlay"
}`}
>
<span className="truncate text-sm">{s.title}</span>
<span className="flex items-center gap-1.5 mt-0.5">
<span className="text-[11px] text-fg-muted">{timeAgo(s.created_at)}</span>
</span>
</Link>
</li>
))}
</ul>
</div>
))}
</nav>
</aside>
);
}
export default function SessionDetail({ loaderData }: any) {
const { session, sessionGroups: loaderGroups } = loaderData;
return (
<div className="flex -mx-4 sm:-mx-6 lg:-mx-8 -my-6">
<SessionSidebar activeId={session.id} groups={loaderGroups} />
<div className="flex-1 flex flex-col min-h-[calc(100vh-4rem)]">
<div className="border-b border-line px-6 py-3 flex items-center gap-3">
<h1 className="text-sm font-medium text-fg-2">{session.title}</h1>
<span className="text-xs text-fg-muted">{timeAgo(session.created_at)}</span>
<span className="ml-auto font-mono text-xs text-fg-muted">{session.model}</span>
</div>
<div className="flex-1 overflow-y-auto px-6 py-6">
<div className="mx-auto max-w-3xl space-y-5">
{session.turns.map((turn, i) => {
switch (turn.kind) {
case "user":
return <UserBlock key={`turn-${i}`} content={turn.content} created_at={turn.created_at} />;
case "assistant": {
const next = session.turns[i + 1];
const showCopy = next?.kind !== "tool";
return <AssistantBlock key={`turn-${i}`} content={turn.content} showCopy={showCopy} />;
}
case "tool":
return <div key={`turn-${i}`} className="pl-10"><ToolBlock tools={turn.tools} /></div>;
}
})}
</div>
</div>
<div className="border-t border-line px-6 py-4">
<div className="mx-auto max-w-3xl">
<div className="flex items-start gap-3 rounded-lg border border-line bg-panel/80 px-4 py-3 focus-within:border-focus">
<textarea
placeholder="Send a message..."
rows={1}
className="flex-1 resize-none bg-transparent text-sm text-fg-2 placeholder-fg-muted outline-none"
/>
<button
type="button"
className="flex size-8 shrink-0 items-center justify-center rounded-md bg-teal-500 text-white transition-colors hover:bg-teal-400"
>
<svg viewBox="0 0 20 20" fill="currentColor" className="size-4" aria-hidden="true">
<path d="M3.105 2.288a.75.75 0 0 0-.826.95l1.414 4.926A1.5 1.5 0 0 0 5.135 9.25h6.115a.75.75 0 0 1 0 1.5H5.135a1.5 1.5 0 0 0-1.442 1.086l-1.414 4.926a.75.75 0 0 0 .826.95l14.095-5.637a.75.75 0 0 0 0-1.395L3.105 2.289Z" />
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
);
}

View file

@ -11,18 +11,12 @@ import {
FolderIcon,
} from "@heroicons/react/16/solid";
import {
BoltIcon,
BugAntIcon,
CodeBracketIcon,
MagnifyingGlassIcon,
PencilSquareIcon,
ShieldCheckIcon,
XMarkIcon,
} from "@heroicons/react/24/outline";
import { Link } from "react-router";
import { apiJson, getAuthMe } from "../api";
import { timeAgo, groupSessionsByDate } from "../lib/time";
import type { PaginatedSessionList } from "@qltysh/fabro-api-client";
import { getAuthMe } from "../api";
export const handle = { hideHeader: true, wide: true };
@ -32,11 +26,7 @@ export function meta({}: any) {
export async function loader({ request }: any) {
const { features } = await getAuthMe();
const { data: apiSessions } = await apiJson<PaginatedSessionList>("/sessions", { request });
const sessionGroups = groupSessionsByDate(
apiSessions.map((s) => ({ id: s.id, title: s.title, created_at: s.created_at }))
);
return { sessionGroups, features };
return { features };
}
const projects = [
@ -59,45 +49,8 @@ function BranchIcon({ className }: { className?: string }) {
);
}
function SessionSidebar({ groups }: { groups: { label: string; sessions: { id: string; title: string; created_at: string }[] }[] }) {
return (
<aside className="w-64 shrink-0 border-r border-line flex flex-col h-[calc(100vh-4rem)]">
<div className="p-3">
<div className="flex w-full items-center gap-2 rounded-lg border border-teal-500/20 bg-panel/60 px-3 py-2 text-sm text-fg-2">
<PencilSquareIcon className="size-4 text-teal-500" />
New session
</div>
</div>
<nav className="flex-1 overflow-y-auto px-3 pb-4">
{groups.map((group) => (
<div key={group.label} className="mt-4 first:mt-1">
<p className="px-2 mb-1.5 text-[11px] font-medium uppercase tracking-wider text-fg-muted">
{group.label}
</p>
<ul className="space-y-0.5">
{group.sessions.map((session) => (
<li key={session.id}>
<Link
to={`/sessions/${session.id}`}
className="flex w-full flex-col rounded-lg px-2.5 py-2 text-left transition-colors text-fg-3 hover:bg-overlay"
>
<span className="truncate text-sm">{session.title}</span>
<span className="flex items-center gap-1.5 mt-0.5">
<span className="text-[11px] text-fg-muted">{timeAgo(session.created_at)}</span>
</span>
</Link>
</li>
))}
</ul>
</div>
))}
</nav>
</aside>
);
}
export default function Start({ loaderData }: any) {
const { sessionGroups, features } = loaderData;
const { features } = loaderData;
const [prompt, setPrompt] = useState("");
const [project, setProject] = useState(projects[0]);
const [branch, setBranch] = useState(branches[0]);
@ -129,8 +82,6 @@ export default function Start({ loaderData }: any) {
return (
<div className="flex -mx-4 sm:-mx-6 lg:-mx-8 -my-6">
<SessionSidebar groups={sessionGroups} />
<div className="flex-1 flex flex-col items-center pt-[12vh] px-4">
<div className="w-full max-w-2xl">
<h1 className="flex items-center justify-center gap-3 text-[2rem] font-medium tracking-tight text-fg-2 text-center mb-8">

View file

@ -1,396 +0,0 @@
import { Link, useParams } from "react-router";
import {
ChevronRightIcon,
LightBulbIcon,
ClipboardDocumentListIcon,
BookOpenIcon,
FunnelIcon,
Bars3BottomLeftIcon,
WrenchIcon,
PaintBrushIcon,
CheckBadgeIcon,
BugAntIcon,
BoltIcon,
BeakerIcon,
StarIcon,
ComputerDesktopIcon,
CubeTransparentIcon,
ArrowsRightLeftIcon,
DocumentDuplicateIcon,
SparklesIcon,
ArchiveBoxXMarkIcon,
ShieldExclamationIcon,
ServerStackIcon,
ExclamationTriangleIcon,
LockClosedIcon,
PuzzlePieceIcon,
ArrowUturnLeftIcon,
EyeIcon,
CurrencyDollarIcon,
ClipboardDocumentCheckIcon,
CpuChipIcon,
FingerPrintIcon,
HandRaisedIcon,
ScaleIcon,
MapPinIcon,
DocumentTextIcon,
ShieldCheckIcon,
WrenchScrewdriverIcon,
KeyIcon,
RocketLaunchIcon,
BuildingLibraryIcon,
CheckCircleIcon,
XCircleIcon,
MinusCircleIcon,
} from "@heroicons/react/20/solid";
import {
slugify,
typeConfig,
modeConfig,
statusConfig,
} from "../data/verifications";
import type {
VerificationType,
VerificationMode,
VerificationResult,
} from "../data/verifications";
import { apiJson } from "../api";
import { timeAgo } from "../lib/time";
import type { VerificationDetailResponse } from "@qltysh/fabro-api-client";
export const handle = { hideHeader: true };
export async function loader({ request, params }: any) {
const data = await apiJson<VerificationDetailResponse>(`/verification/controls/${params.id}`, { request });
return { data };
}
export function meta({ data }: any) {
const name = data?.data?.control?.name ?? "Verification";
return [{ title: `${name} — Verification — Fabro` }];
}
type IconComponent = React.ComponentType<{ className?: string }>;
const criterionIcons: Record<string, IconComponent> = {
"Motivation": LightBulbIcon,
"Specifications": ClipboardDocumentListIcon,
"Documentation": BookOpenIcon,
"Minimization": FunnelIcon,
"Formatting": Bars3BottomLeftIcon,
"Linting": WrenchIcon,
"Style": PaintBrushIcon,
"Completeness": CheckBadgeIcon,
"Defects": BugAntIcon,
"Performance": BoltIcon,
"Test Coverage": BeakerIcon,
"Test Quality": StarIcon,
"E2E Coverage": ComputerDesktopIcon,
"Architecture": CubeTransparentIcon,
"Interfaces": ArrowsRightLeftIcon,
"Duplication": DocumentDuplicateIcon,
"Simplicity": SparklesIcon,
"Dead Code": ArchiveBoxXMarkIcon,
"Vulnerabilities": ShieldExclamationIcon,
"IaC Scanning": ServerStackIcon,
"Dependency Alerts": ExclamationTriangleIcon,
"Security Controls": LockClosedIcon,
"Compatibility": PuzzlePieceIcon,
"Rollout / Rollback": ArrowUturnLeftIcon,
"Observability": EyeIcon,
"Cost": CurrencyDollarIcon,
"Change Control": ClipboardDocumentCheckIcon,
"AI Governance": CpuChipIcon,
"Privacy": FingerPrintIcon,
"Accessibility": HandRaisedIcon,
"Licensing": ScaleIcon,
};
const categoryIcons: Record<string, IconComponent> = {
"Traceability": MapPinIcon,
"Readability": DocumentTextIcon,
"Reliability": ShieldCheckIcon,
"Code Coverage": BeakerIcon,
"Maintainability": WrenchScrewdriverIcon,
"Security": KeyIcon,
"Deployability": RocketLaunchIcon,
"Compliance": BuildingLibraryIcon,
};
function TypeBadge({ type }: { type: VerificationType | null }) {
if (type === null) return null;
const config = typeConfig[type];
return (
<span
className={`rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider ${config.color} ${config.bg}`}
>
{config.label}
</span>
);
}
function ModeBadge({ mode }: { mode: VerificationMode }) {
const config = modeConfig[mode];
return (
<span
className={`rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider ${config.color} ${config.bg}`}
>
{config.label}
</span>
);
}
function StatCard({ label, value, warn }: { label: string; value: string; warn?: boolean }) {
return (
<div className="rounded-md border border-line bg-panel/60 px-4 py-3">
<p className="text-xs font-medium uppercase tracking-wider text-fg-muted">{label}</p>
<p className={`mt-1 font-mono text-lg font-semibold tabular-nums ${warn ? "text-amber" : "text-fg"}`}>
{value}
</p>
</div>
);
}
function EvaluationBar({ evaluations }: { evaluations: readonly VerificationResult[] }) {
if (evaluations.length === 0) {
return <p className="text-sm italic text-fg-muted">No evaluations yet</p>;
}
const passCount = evaluations.filter((e) => e === "pass").length;
const failCount = evaluations.filter((e) => e === "fail").length;
const skipCount = evaluations.filter((e) => e === "skip").length;
return (
<div>
<div className="flex items-center gap-1">
{evaluations.map((result, i) => (
<div
key={`eval-${i}`}
className={`h-6 flex-1 rounded-sm ${
result === "pass"
? "bg-mint/70"
: result === "fail"
? "bg-coral/70"
: "bg-navy-600/50"
}`}
/>
))}
</div>
<div className="mt-2 flex gap-4 text-xs text-fg-muted">
<span className="flex items-center gap-1">
<span className="inline-block size-2 rounded-sm bg-mint/70" />
{passCount} pass
</span>
<span className="flex items-center gap-1">
<span className="inline-block size-2 rounded-sm bg-coral/70" />
{failCount} fail
</span>
{skipCount > 0 && (
<span className="flex items-center gap-1">
<span className="inline-block size-2 rounded-sm bg-navy-600/50" />
{skipCount} skip
</span>
)}
</div>
</div>
);
}
function ResultIcon({ result }: { result: VerificationResult }) {
const config = statusConfig[result];
if (result === "pass") return <CheckCircleIcon className={`size-4 ${config.color}`} />;
if (result === "fail") return <XCircleIcon className={`size-4 ${config.color}`} />;
return <MinusCircleIcon className={`size-4 ${config.color}`} />;
}
export default function VerificationDetail({ loaderData }: any) {
const { data } = loaderData;
const { control: controlInfo, performance: apiPerf, control_detail: apiDetail, recent_results: apiRecentResults, siblings: apiSiblings } = data;
const criterion = {
name: controlInfo.name,
description: controlInfo.description,
type: (controlInfo.type ?? null) as VerificationType | null,
};
const categoryName = controlInfo.criterion.name;
const performance = {
f1: apiPerf.f1 ?? null,
passAt1: apiPerf.pass_at_1 ?? null,
mode: apiPerf.mode as VerificationMode,
evaluations: apiPerf.evaluations as VerificationResult[],
};
const detail = apiDetail ? {
description: apiDetail.rationale,
checks: apiDetail.checks,
passExample: apiDetail.pass_example,
failExample: apiDetail.fail_example,
} : null;
const recentResults = apiRecentResults.map((r) => ({
runId: r.run.id,
runTitle: r.run.title,
workflow: r.workflow.slug,
result: r.result as VerificationResult,
timestamp: r.timestamp,
}));
const siblings = apiSiblings.map((s) => ({
name: s.name,
slug: s.slug,
type: (s.type ?? null) as VerificationType | null,
mode: (s.mode ?? "disabled") as VerificationMode,
}));
const Icon = criterionIcons[criterion.name];
const CatIcon = categoryIcons[categoryName];
const passRate = performance.evaluations.length > 0
? (performance.evaluations.filter((e) => e === "pass").length / performance.evaluations.length * 100).toFixed(0)
: null;
return (
<div className="space-y-6">
{/* Breadcrumb */}
<nav className="flex items-center gap-1 text-sm text-fg-muted">
<Link to="/verification/criteria" className="text-fg-3 hover:text-fg">Verification</Link>
<ChevronRightIcon className="size-3" />
<span className="text-fg-3">{categoryName}</span>
<ChevronRightIcon className="size-3" />
<span>{criterion.name}</span>
</nav>
{/* Header */}
<div className="flex items-start gap-3">
{Icon && <Icon className="mt-0.5 size-6 text-fg-3" />}
<div className="min-w-0 flex-1">
<h2 className="text-xl font-semibold text-fg">{criterion.name}</h2>
<p className="mt-1 text-sm text-fg-muted">{criterion.description}</p>
<div className="mt-2 flex items-center gap-2">
<TypeBadge type={criterion.type} />
<ModeBadge mode={performance.mode} />
</div>
</div>
</div>
{/* Description block */}
{detail && (
<div className="rounded-md border border-line bg-panel/60 px-5 py-4">
<p className="text-sm leading-relaxed text-fg-2">{detail.description}</p>
</div>
)}
{/* Stat cards */}
<div className="grid grid-cols-4 gap-3">
<StatCard label="Accuracy (F1)" value={performance.f1 != null ? performance.f1.toFixed(2) : "—"} />
<StatCard label="pass@1" value={performance.passAt1 != null ? performance.passAt1.toFixed(2) : "—"} />
<StatCard label="Pass Rate" value={passRate != null ? `${passRate}%` : "—"} />
<StatCard label="Total Evals" value={String(performance.evaluations.length)} />
</div>
{/* Evaluation history */}
<div>
<h3 className="mb-3 text-sm font-semibold text-fg">Evaluation History</h3>
<EvaluationBar evaluations={performance.evaluations} />
</div>
{/* Recent runs table */}
<div>
<h3 className="mb-3 text-sm font-semibold text-fg">Recent Runs</h3>
<div className="rounded-md border border-line overflow-hidden">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-line bg-panel/60 text-left text-xs text-fg-muted">
<th className="py-2.5 pl-4 pr-3 font-medium">Run</th>
<th className="py-2.5 px-3 font-medium w-8">Result</th>
<th className="py-2.5 px-3 font-medium">Workflow</th>
<th className="py-2.5 pl-3 pr-4 font-medium text-right">Time</th>
</tr>
</thead>
<tbody>
{recentResults.map((run) => (
<tr key={run.runId} className="border-b border-line last:border-b-0 cursor-pointer transition-colors hover:bg-overlay">
<td className="py-2.5 pl-4 pr-3">
<Link to={`/runs/${run.runId}`} className="font-medium text-fg-2 hover:text-fg">
{run.runTitle}
</Link>
</td>
<td className="py-2.5 px-3">
<ResultIcon result={run.result} />
</td>
<td className="py-2.5 px-3 text-fg-muted">{run.workflow}</td>
<td className="py-2.5 pl-3 pr-4 text-right text-fg-muted">{timeAgo(run.timestamp)}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* What this checks / Examples */}
{detail && (
<div className="grid grid-cols-2 gap-4">
<div className="rounded-md border border-line bg-panel/60 px-5 py-4">
<h3 className="mb-3 text-sm font-semibold text-fg">What This Checks</h3>
<ul className="space-y-2 text-sm text-fg-2">
{detail.checks.map((check) => (
<li key={check} className="flex items-start gap-2">
<span className="mt-1.5 size-1.5 shrink-0 rounded-full bg-fg-muted" />
{check}
</li>
))}
</ul>
</div>
<div className="space-y-4">
<div className="rounded-md border border-line bg-panel/60 px-5 py-4">
<h3 className="mb-2 text-sm font-semibold text-mint">Pass Example</h3>
<p className="text-sm text-fg-2">{detail.passExample}</p>
</div>
<div className="rounded-md border border-line bg-panel/60 px-5 py-4">
<h3 className="mb-2 text-sm font-semibold text-coral">Fail Example</h3>
<p className="text-sm text-fg-2">{detail.failExample}</p>
</div>
</div>
</div>
)}
{/* Sibling controls */}
{siblings.length > 0 && (
<div>
<h3 className="mb-3 text-sm font-semibold text-fg">
{CatIcon && <CatIcon className="mr-1.5 inline size-4 text-fg-3" />}
Other {categoryName} Controls
</h3>
<div className="rounded-md border border-line overflow-hidden">
<table className="w-full text-sm">
<tbody>
{siblings.map((sibling) => {
const SibIcon = criterionIcons[sibling.name];
return (
<tr key={sibling.name} className="border-b border-line last:border-b-0 cursor-pointer transition-colors hover:bg-overlay">
<td className="w-8 py-2.5 pl-4 pr-0">
{SibIcon && <SibIcon className="size-4 text-fg-3" />}
</td>
<td className="py-2.5 pl-2 pr-3">
<Link
to={`/verification/controls/${sibling.slug}`}
className="font-medium text-fg-2 hover:text-fg"
>
{sibling.name}
</Link>
</td>
<td className="py-2.5 px-3 text-fg-muted" />
<td className="whitespace-nowrap py-2.5 px-3 text-right">
<TypeBadge type={sibling.type} />
</td>
<td className="whitespace-nowrap py-2.5 pl-3 pr-4">
<ModeBadge mode={sibling.mode} />
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
)}
</div>
);
}

View file

@ -1,180 +0,0 @@
import { useNavigate } from "react-router";
import {
LightBulbIcon,
ClipboardDocumentListIcon,
BookOpenIcon,
FunnelIcon,
Bars3BottomLeftIcon,
WrenchIcon,
PaintBrushIcon,
CheckBadgeIcon,
BugAntIcon,
BoltIcon,
BeakerIcon,
StarIcon,
ComputerDesktopIcon,
CubeTransparentIcon,
ArrowsRightLeftIcon,
DocumentDuplicateIcon,
SparklesIcon,
ArchiveBoxXMarkIcon,
ShieldExclamationIcon,
ServerStackIcon,
ExclamationTriangleIcon,
LockClosedIcon,
PuzzlePieceIcon,
ArrowUturnLeftIcon,
EyeIcon,
CurrencyDollarIcon,
ClipboardDocumentCheckIcon,
CpuChipIcon,
FingerPrintIcon,
HandRaisedIcon,
ScaleIcon,
} from "@heroicons/react/20/solid";
import {
typeConfig,
modeConfig,
} from "../data/verifications";
import type {
VerificationType,
VerificationMode,
} from "../data/verifications";
import { apiJson } from "../api";
import type { VerificationControlListItem } from "@qltysh/fabro-api-client";
export async function loader({ request }: any) {
const { data: controls } = await apiJson<{ data: VerificationControlListItem[] }>("/verification/controls", { request });
return { controls };
}
export const handle = { wide: true };
export function meta({}: any) {
return [{ title: "Controls — Verification — Fabro" }];
}
type IconComponent = React.ComponentType<{ className?: string }>;
const controlIcons: Record<string, IconComponent> = {
"Motivation": LightBulbIcon,
"Specifications": ClipboardDocumentListIcon,
"Documentation": BookOpenIcon,
"Minimization": FunnelIcon,
"Formatting": Bars3BottomLeftIcon,
"Linting": WrenchIcon,
"Style": PaintBrushIcon,
"Completeness": CheckBadgeIcon,
"Defects": BugAntIcon,
"Performance": BoltIcon,
"Test Coverage": BeakerIcon,
"Test Quality": StarIcon,
"E2E Coverage": ComputerDesktopIcon,
"Architecture": CubeTransparentIcon,
"Interfaces": ArrowsRightLeftIcon,
"Duplication": DocumentDuplicateIcon,
"Simplicity": SparklesIcon,
"Dead Code": ArchiveBoxXMarkIcon,
"Vulnerabilities": ShieldExclamationIcon,
"IaC Scanning": ServerStackIcon,
"Dependency Alerts": ExclamationTriangleIcon,
"Security Controls": LockClosedIcon,
"Compatibility": PuzzlePieceIcon,
"Rollout / Rollback": ArrowUturnLeftIcon,
"Observability": EyeIcon,
"Cost": CurrencyDollarIcon,
"Change Control": ClipboardDocumentCheckIcon,
"AI Governance": CpuChipIcon,
"Privacy": FingerPrintIcon,
"Accessibility": HandRaisedIcon,
"Licensing": ScaleIcon,
};
function TypeBadge({ type }: { type: VerificationType | null }) {
if (type === null) return null;
const config = typeConfig[type];
return (
<span
className={`rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider ${config.color} ${config.bg}`}
>
{config.label}
</span>
);
}
function ModeBadge({ mode }: { mode: VerificationMode }) {
const config = modeConfig[mode];
return (
<span
className={`rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider ${config.color} ${config.bg}`}
>
{config.label}
</span>
);
}
export default function VerificationControls({ loaderData }: any) {
const { controls } = loaderData;
const navigate = useNavigate();
return (
<div className="space-y-4">
<div className="rounded-md border border-line overflow-hidden">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-line bg-panel/60 text-left text-xs text-fg-muted">
<th className="w-8 py-2.5 pl-4 pr-0 font-medium" />
<th className="py-2.5 pl-2 pr-3 font-medium">Control</th>
<th className="py-2.5 px-3 font-medium">Description</th>
<th className="py-2.5 px-3 font-medium">Criterion</th>
<th className="py-2.5 px-3 font-medium text-right">Type</th>
<th className="py-2.5 px-3 font-medium text-right">Accuracy (F1)</th>
<th className="py-2.5 px-3 font-medium text-right">pass@1</th>
<th className="py-2.5 pl-3 pr-4 font-medium">Mode</th>
</tr>
</thead>
<tbody>
{controls.map((control) => {
const Icon = controlIcons[control.name];
const mode = (control.mode ?? "disabled") as VerificationMode;
return (
<tr
key={control.slug}
className="border-b border-line last:border-b-0 cursor-pointer transition-colors hover:bg-overlay"
onClick={() => navigate(`/verification/controls/${control.slug}`)}
>
<td className="w-8 py-2.5 pl-4 pr-0">
{Icon && <Icon className="size-4 text-fg-3" />}
</td>
<td className="whitespace-nowrap py-2.5 pl-2 pr-3 font-medium text-fg-2">
{control.name}
</td>
<td className="py-2.5 px-3 text-fg-muted">
{control.description || (
<span className="italic">Not configured</span>
)}
</td>
<td className="whitespace-nowrap py-2.5 px-3 text-xs text-fg-muted">
{control.criterion.name}
</td>
<td className="whitespace-nowrap py-2.5 px-3 text-right">
<TypeBadge type={(control.type ?? null) as VerificationType | null} />
</td>
<td className="whitespace-nowrap py-2.5 px-3 text-right font-mono text-xs tabular-nums text-fg-2">
{control.f1 != null ? control.f1.toFixed(2) : <span className="text-fg-muted"></span>}
</td>
<td className="whitespace-nowrap py-2.5 px-3 text-right font-mono text-xs tabular-nums text-fg-2">
{control.pass_at_1 != null ? control.pass_at_1.toFixed(2) : <span className="text-fg-muted"></span>}
</td>
<td className="whitespace-nowrap py-2.5 pl-3 pr-4">
<ModeBadge mode={mode} />
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
}

View file

@ -1,449 +0,0 @@
import { useState } from "react";
import { useNavigate } from "react-router";
import {
Disclosure,
DisclosureButton,
DisclosurePanel,
} from "@headlessui/react";
import {
ChevronRightIcon,
LightBulbIcon,
ClipboardDocumentListIcon,
BookOpenIcon,
FunnelIcon,
Bars3BottomLeftIcon,
WrenchIcon,
PaintBrushIcon,
CheckBadgeIcon,
BugAntIcon,
BoltIcon,
BeakerIcon,
StarIcon,
ComputerDesktopIcon,
CubeTransparentIcon,
ArrowsRightLeftIcon,
DocumentDuplicateIcon,
SparklesIcon,
ArchiveBoxXMarkIcon,
ShieldExclamationIcon,
ServerStackIcon,
ExclamationTriangleIcon,
LockClosedIcon,
PuzzlePieceIcon,
ArrowUturnLeftIcon,
EyeIcon,
CurrencyDollarIcon,
ClipboardDocumentCheckIcon,
CpuChipIcon,
FingerPrintIcon,
HandRaisedIcon,
ScaleIcon,
MapPinIcon,
DocumentTextIcon,
ShieldCheckIcon,
WrenchScrewdriverIcon,
KeyIcon,
RocketLaunchIcon,
BuildingLibraryIcon,
} from "@heroicons/react/20/solid";
import {
MagnifyingGlassIcon,
ChevronDownIcon,
} from "@heroicons/react/24/outline";
import {
typeConfig,
modeConfig,
slugify,
} from "../data/verifications";
import type {
VerificationType,
VerificationMode,
VerificationResult,
VerificationCategory,
CriterionPerformance,
} from "../data/verifications";
import { apiJson } from "../api";
import type { VerificationCriterion as ApiVerificationCriterion } from "@qltysh/fabro-api-client";
export async function loader({ request }: any) {
const { data: apiCategories } = await apiJson<{ data: ApiVerificationCriterion[] }>("/verification/criteria", { request });
const categories: VerificationCategory[] = apiCategories.map((cat) => ({
name: cat.name,
question: cat.question,
status: "pass" as const,
criteria: cat.controls.map((c) => ({
name: c.name,
description: c.description,
type: (c.type ?? null) as VerificationType | null,
status: "pass" as const,
})),
}));
// Build a performance map from API data
const criterionPerformance: Record<string, CriterionPerformance> = {};
for (const cat of apiCategories) {
for (const ctrl of cat.controls) {
criterionPerformance[ctrl.name] = {
f1: ctrl.f1 ?? null,
passAt1: ctrl.pass_at_1 ?? null,
mode: (ctrl.mode ?? "disabled") as VerificationMode,
evaluations: (ctrl.evaluations ?? []) as VerificationResult[],
};
}
}
return { categories, criterionPerformance };
}
export const handle = { wide: true };
export function meta({}: any) {
return [{ title: "Verifications — Fabro" }];
}
type IconComponent = React.ComponentType<{ className?: string }>;
function TrafficLightIcon({ className }: { className?: string }) {
return (
<svg viewBox="0 0 20 20" fill="currentColor" className={className}>
<path
fillRule="evenodd"
d="M7 3a3 3 0 0 1 6 0v14a3 3 0 0 1-6 0V3Zm3 1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm0 5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm0 5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"
clipRule="evenodd"
/>
</svg>
);
}
const criterionIcons: Record<string, IconComponent> = {
"Motivation": LightBulbIcon,
"Specifications": ClipboardDocumentListIcon,
"Documentation": BookOpenIcon,
"Minimization": FunnelIcon,
"Formatting": Bars3BottomLeftIcon,
"Linting": WrenchIcon,
"Style": PaintBrushIcon,
"Completeness": CheckBadgeIcon,
"Defects": BugAntIcon,
"Performance": BoltIcon,
"Test Coverage": BeakerIcon,
"Test Quality": StarIcon,
"E2E Coverage": ComputerDesktopIcon,
"Architecture": CubeTransparentIcon,
"Interfaces": ArrowsRightLeftIcon,
"Duplication": DocumentDuplicateIcon,
"Simplicity": SparklesIcon,
"Dead Code": ArchiveBoxXMarkIcon,
"Vulnerabilities": ShieldExclamationIcon,
"IaC Scanning": ServerStackIcon,
"Dependency Alerts": ExclamationTriangleIcon,
"Security Controls": LockClosedIcon,
"Compatibility": PuzzlePieceIcon,
"Rollout / Rollback": ArrowUturnLeftIcon,
"Observability": EyeIcon,
"Cost": CurrencyDollarIcon,
"Change Control": ClipboardDocumentCheckIcon,
"AI Governance": CpuChipIcon,
"Privacy": FingerPrintIcon,
"Accessibility": HandRaisedIcon,
"Licensing": ScaleIcon,
};
const categoryIcons: Record<string, IconComponent> = {
"Traceability": MapPinIcon,
"Readability": DocumentTextIcon,
"Reliability": ShieldCheckIcon,
"Code Coverage": TrafficLightIcon,
"Maintainability": WrenchScrewdriverIcon,
"Security": KeyIcon,
"Deployability": RocketLaunchIcon,
"Compliance": BuildingLibraryIcon,
};
function TypeBadge({ type }: { type: VerificationType | null }) {
if (type === null) return null;
const config = typeConfig[type];
return (
<span
className={`rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider ${config.color} ${config.bg}`}
>
{config.label}
</span>
);
}
type ViewMode = "grouped" | "ungrouped";
function CriterionRow({ slug, children }: { slug: string; children: React.ReactNode }) {
const navigate = useNavigate();
return (
<tr
className="border-b border-line last:border-b-0 cursor-pointer transition-colors hover:bg-overlay"
onClick={() => navigate(`/verification/controls/${slug}`)}
>
{children}
</tr>
);
}
function CategoryCard({ category, perfMap }: { category: VerificationCategory; perfMap: Record<string, CriterionPerformance> }) {
return (
<Disclosure
as="div"
className="rounded-md border border-line overflow-hidden"
>
<DisclosureButton className="group flex w-full items-center gap-3 px-4 py-3.5 text-left transition-colors hover:bg-overlay">
{(() => {
const CatIcon = categoryIcons[category.name];
return CatIcon ? <CatIcon className="size-5 shrink-0 text-fg-3" /> : null;
})()}
<div className="min-w-0 flex-1">
<div className="flex items-baseline gap-3">
<span className="shrink-0 text-sm font-semibold text-fg">
{category.name}
</span>
<span className="truncate text-xs text-fg-muted">
{category.question}
</span>
</div>
</div>
<span className="shrink-0 text-xs text-fg-muted">
<span className="font-mono tabular-nums">{category.criteria.length}</span> controls
</span>
<ChevronRightIcon className="size-4 shrink-0 text-fg-muted transition-transform duration-200 group-data-open:rotate-90" />
</DisclosureButton>
<DisclosurePanel
transition
className="origin-top transition duration-200 ease-out data-closed:-translate-y-1 data-closed:opacity-0"
>
<div className="border-t border-line">
<table className="w-full text-sm">
<tbody>
{category.criteria.map((criterion) => {
const Icon = criterionIcons[criterion.name];
const perf = perfMap[criterion.name];
return (
<CriterionRow key={criterion.name} slug={slugify(criterion.name)}>
<td className="w-8 py-2.5 pl-5 pr-0">
{Icon && <Icon className="size-4 text-fg-3" />}
</td>
<td className="whitespace-nowrap py-2.5 pl-2 pr-3 font-medium text-fg-2">
{criterion.name}
</td>
<td className="py-2.5 px-3 text-fg-muted">
{criterion.description || (
<span className="italic">Not configured</span>
)}
</td>
<td className="whitespace-nowrap py-2.5 pl-3 pr-1 text-right">
<TypeBadge type={criterion.type} />
</td>
<td className="whitespace-nowrap py-2.5 px-1">
{perf && <ModeBadge mode={perf.mode} />}
</td>
<td className="whitespace-nowrap py-2.5 pl-1 pr-4">
{perf && <EvaluationDots evaluations={perf.evaluations} />}
</td>
</CriterionRow>
);
})}
</tbody>
</table>
</div>
</DisclosurePanel>
</Disclosure>
);
}
function GroupedView({ categories, perfMap }: { categories: readonly VerificationCategory[]; perfMap: Record<string, CriterionPerformance> }) {
return (
<div className="space-y-3">
{categories.map((category) => (
<CategoryCard key={category.name} category={category} perfMap={perfMap} />
))}
</div>
);
}
function ModeBadge({ mode }: { mode: VerificationMode }) {
const config = modeConfig[mode];
return (
<span
className={`rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider ${config.color} ${config.bg}`}
>
{config.label}
</span>
);
}
function EvaluationDots({ evaluations }: { evaluations: readonly VerificationResult[] }) {
if (evaluations.length === 0) {
return <span className="text-xs italic text-fg-muted"></span>;
}
return (
<div className="flex items-center gap-0.5">
{evaluations.map((result, i) => (
<span
key={`eval-${i}`}
className={`inline-block size-2.5 rounded-sm ${
result === "pass"
? "bg-mint/70"
: result === "fail"
? "bg-coral/70"
: "bg-navy-600/50"
}`}
/>
))}
</div>
);
}
function UngroupedView({ categories, perfMap }: { categories: readonly VerificationCategory[]; perfMap: Record<string, CriterionPerformance> }) {
return (
<div className="rounded-md border border-line overflow-hidden">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-line bg-panel/60 text-left text-xs text-fg-muted">
<th className="w-8 py-2.5 pl-4 pr-0 font-medium" />
<th className="py-2.5 pl-2 pr-3 font-medium">Verification</th>
<th className="py-2.5 px-3 font-medium">Description</th>
<th className="py-2.5 px-3 font-medium">Category</th>
<th className="py-2.5 px-3 font-medium text-right">Type</th>
<th className="py-2.5 px-3 font-medium text-right">Accuracy (F1)</th>
<th className="py-2.5 px-3 font-medium text-right">pass@1</th>
<th className="py-2.5 px-3 font-medium">Mode</th>
<th className="py-2.5 pl-3 pr-4 font-medium">Evaluations</th>
</tr>
</thead>
<tbody>
{categories.flatMap((category) =>
category.criteria.map((criterion) => {
const Icon = criterionIcons[criterion.name];
const perf = perfMap[criterion.name];
return (
<CriterionRow key={`${category.name}-${criterion.name}`} slug={slugify(criterion.name)}>
<td className="w-8 py-2.5 pl-4 pr-0">
{Icon && <Icon className="size-4 text-fg-3" />}
</td>
<td className="whitespace-nowrap py-2.5 pl-2 pr-3 font-medium text-fg-2">
{criterion.name}
</td>
<td className="py-2.5 px-3 text-fg-muted">
{criterion.description || (
<span className="italic">Not configured</span>
)}
</td>
<td className="whitespace-nowrap py-2.5 px-3 text-xs text-fg-muted">
{category.name}
</td>
<td className="whitespace-nowrap py-2.5 px-3 text-right">
<TypeBadge type={criterion.type} />
</td>
<td className="whitespace-nowrap py-2.5 px-3 text-right font-mono text-xs tabular-nums text-fg-2">
{perf?.f1 != null ? perf.f1.toFixed(2) : <span className="text-fg-muted"></span>}
</td>
<td className="whitespace-nowrap py-2.5 px-3 text-right font-mono text-xs tabular-nums text-fg-2">
{perf?.passAt1 != null ? perf.passAt1.toFixed(2) : <span className="text-fg-muted"></span>}
</td>
<td className="whitespace-nowrap py-2.5 px-3">
{perf && <ModeBadge mode={perf.mode} />}
</td>
<td className="whitespace-nowrap py-2.5 pl-3 pr-4">
{perf && <EvaluationDots evaluations={perf.evaluations} />}
</td>
</CriterionRow>
);
}),
)}
</tbody>
</table>
</div>
);
}
function filterCategories(
categories: readonly VerificationCategory[],
query: string,
modeFilter: VerificationMode | "all",
perfMap: Record<string, CriterionPerformance>,
): VerificationCategory[] {
const lowerQuery = query.toLowerCase();
return categories
.map((category) => {
const filtered = category.criteria.filter((c) => {
const perf = perfMap[c.name];
const matchesMode = modeFilter === "all" || perf?.mode === modeFilter;
const matchesQuery =
lowerQuery === "" ||
c.name.toLowerCase().includes(lowerQuery) ||
c.description.toLowerCase().includes(lowerQuery) ||
category.name.toLowerCase().includes(lowerQuery);
return matchesMode && matchesQuery;
});
return { ...category, criteria: filtered };
})
.filter((category) => category.criteria.length > 0);
}
export default function Verifications({ loaderData }: any) {
const { categories: verificationCategories, criterionPerformance } = loaderData;
const [view, setView] = useState<ViewMode>("grouped");
const [query, setQuery] = useState("");
const [modeFilter, setModeFilter] = useState<VerificationMode | "all">("all");
const filtered = filterCategories(verificationCategories, query, modeFilter, criterionPerformance);
return (
<div className="space-y-4">
{/* Toolbar */}
<div className="flex gap-3">
<div className="relative flex-1">
<MagnifyingGlassIcon className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-fg-muted" />
<input
type="text"
placeholder="Search verifications…"
value={query}
onChange={(e) => setQuery(e.target.value)}
className="w-full rounded-md border border-line bg-panel/80 py-2 pl-9 pr-3 text-sm text-fg-2 placeholder-fg-muted outline-none transition-colors focus:border-focus focus:ring-0"
/>
</div>
<div className="relative">
<select
value={modeFilter}
onChange={(e) => setModeFilter(e.target.value as VerificationMode | "all")}
className="appearance-none rounded-md border border-line bg-panel/80 py-2 pl-3 pr-8 text-sm text-fg-2 outline-none transition-colors focus:border-focus focus:ring-0"
>
<option value="all">All modes</option>
<option value="active">Active</option>
<option value="evaluate">Evaluate</option>
<option value="disabled">Disabled</option>
</select>
<ChevronDownIcon className="pointer-events-none absolute right-2 top-1/2 size-4 -translate-y-1/2 text-fg-muted" />
</div>
<div className="flex items-center gap-1 rounded-md border border-line bg-panel/80 p-0.5">
<button
type="button"
onClick={() => setView("grouped")}
className={`inline-flex items-center gap-1.5 rounded px-2.5 py-1 text-xs font-medium transition-colors ${view === "grouped" ? "bg-overlay text-teal-500" : "text-fg-muted hover:text-fg-3"}`}
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="size-3.5" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" d="M2.25 7.125C2.25 6.504 2.754 6 3.375 6h6c.621 0 1.125.504 1.125 1.125v3.75c0 .621-.504 1.125-1.125 1.125h-6a1.125 1.125 0 0 1-1.125-1.125v-3.75ZM14.25 8.625c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v8.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 0 1-1.125-1.125v-8.25ZM3.75 16.125c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 0 1-1.125-1.125v-1.5Z" />
</svg>
Grouped
</button>
<button
type="button"
onClick={() => setView("ungrouped")}
className={`inline-flex items-center gap-1.5 rounded px-2.5 py-1 text-xs font-medium transition-colors ${view === "ungrouped" ? "bg-overlay text-teal-500" : "text-fg-muted hover:text-fg-3"}`}
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="size-3.5" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 12h16.5m-16.5 3.75h16.5M3.75 19.5h16.5M5.625 4.5h12.75a1.875 1.875 0 0 1 0 3.75H5.625a1.875 1.875 0 0 1 0-3.75Z" />
</svg>
List
</button>
</div>
</div>
{view === "grouped" ? <GroupedView categories={filtered} perfMap={criterionPerformance} /> : <UngroupedView categories={filtered} perfMap={criterionPerformance} />}
</div>
);
}

View file

@ -1,255 +0,0 @@
import { Link, useNavigate } from "react-router";
import {
ChevronRightIcon,
LightBulbIcon,
ClipboardDocumentListIcon,
BookOpenIcon,
FunnelIcon,
Bars3BottomLeftIcon,
WrenchIcon,
PaintBrushIcon,
CheckBadgeIcon,
BugAntIcon,
BoltIcon,
BeakerIcon,
StarIcon,
ComputerDesktopIcon,
CubeTransparentIcon,
ArrowsRightLeftIcon,
DocumentDuplicateIcon,
SparklesIcon,
ArchiveBoxXMarkIcon,
ShieldExclamationIcon,
ServerStackIcon,
ExclamationTriangleIcon,
LockClosedIcon,
PuzzlePieceIcon,
ArrowUturnLeftIcon,
EyeIcon,
CurrencyDollarIcon,
ClipboardDocumentCheckIcon,
CpuChipIcon,
FingerPrintIcon,
HandRaisedIcon,
ScaleIcon,
MapPinIcon,
DocumentTextIcon,
ShieldCheckIcon,
WrenchScrewdriverIcon,
KeyIcon,
RocketLaunchIcon,
BuildingLibraryIcon,
} from "@heroicons/react/20/solid";
import {
typeConfig,
modeConfig,
slugify,
} from "../data/verifications";
import type {
VerificationType,
VerificationMode,
VerificationResult,
} from "../data/verifications";
import { apiJson } from "../api";
import type { VerificationCriterionDetail } from "@qltysh/fabro-api-client";
export const handle = { hideHeader: true };
export async function loader({ request, params }: any) {
const data = await apiJson<VerificationCriterionDetail>(`/verification/criteria/${params.id}`, { request });
return { data };
}
export function meta({ data }: any) {
const name = data?.data?.name ?? "Criterion";
return [{ title: `${name} — Verification — Fabro` }];
}
type IconComponent = React.ComponentType<{ className?: string }>;
function TrafficLightIcon({ className }: { className?: string }) {
return (
<svg viewBox="0 0 20 20" fill="currentColor" className={className}>
<path
fillRule="evenodd"
d="M7 3a3 3 0 0 1 6 0v14a3 3 0 0 1-6 0V3Zm3 1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm0 5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm0 5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"
clipRule="evenodd"
/>
</svg>
);
}
const controlIcons: Record<string, IconComponent> = {
"Motivation": LightBulbIcon,
"Specifications": ClipboardDocumentListIcon,
"Documentation": BookOpenIcon,
"Minimization": FunnelIcon,
"Formatting": Bars3BottomLeftIcon,
"Linting": WrenchIcon,
"Style": PaintBrushIcon,
"Completeness": CheckBadgeIcon,
"Defects": BugAntIcon,
"Performance": BoltIcon,
"Test Coverage": BeakerIcon,
"Test Quality": StarIcon,
"E2E Coverage": ComputerDesktopIcon,
"Architecture": CubeTransparentIcon,
"Interfaces": ArrowsRightLeftIcon,
"Duplication": DocumentDuplicateIcon,
"Simplicity": SparklesIcon,
"Dead Code": ArchiveBoxXMarkIcon,
"Vulnerabilities": ShieldExclamationIcon,
"IaC Scanning": ServerStackIcon,
"Dependency Alerts": ExclamationTriangleIcon,
"Security Controls": LockClosedIcon,
"Compatibility": PuzzlePieceIcon,
"Rollout / Rollback": ArrowUturnLeftIcon,
"Observability": EyeIcon,
"Cost": CurrencyDollarIcon,
"Change Control": ClipboardDocumentCheckIcon,
"AI Governance": CpuChipIcon,
"Privacy": FingerPrintIcon,
"Accessibility": HandRaisedIcon,
"Licensing": ScaleIcon,
};
const categoryIcons: Record<string, IconComponent> = {
"Traceability": MapPinIcon,
"Readability": DocumentTextIcon,
"Reliability": ShieldCheckIcon,
"Code Coverage": TrafficLightIcon,
"Maintainability": WrenchScrewdriverIcon,
"Security": KeyIcon,
"Deployability": RocketLaunchIcon,
"Compliance": BuildingLibraryIcon,
};
function TypeBadge({ type }: { type: VerificationType | null }) {
if (type === null) return null;
const config = typeConfig[type];
return (
<span
className={`rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider ${config.color} ${config.bg}`}
>
{config.label}
</span>
);
}
function ModeBadge({ mode }: { mode: VerificationMode }) {
const config = modeConfig[mode];
return (
<span
className={`rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wider ${config.color} ${config.bg}`}
>
{config.label}
</span>
);
}
function EvaluationDots({ evaluations }: { evaluations: readonly VerificationResult[] }) {
if (evaluations.length === 0) {
return <span className="text-xs italic text-fg-muted"></span>;
}
return (
<div className="flex items-center gap-0.5">
{evaluations.map((result, i) => (
<span
key={`eval-${i}`}
className={`inline-block size-2.5 rounded-sm ${
result === "pass"
? "bg-mint/70"
: result === "fail"
? "bg-coral/70"
: "bg-navy-600/50"
}`}
/>
))}
</div>
);
}
export default function VerificationCriterion({ loaderData }: any) {
const { data } = loaderData;
const navigate = useNavigate();
const CatIcon = categoryIcons[data.name];
return (
<div className="space-y-6">
{/* Breadcrumb */}
<nav className="flex items-center gap-1 text-sm text-fg-muted">
<Link to="/verification/criteria" className="text-fg-3 hover:text-fg">Verification</Link>
<ChevronRightIcon className="size-3" />
<span>{data.name}</span>
</nav>
{/* Header */}
<div className="flex items-start gap-3">
{CatIcon && <CatIcon className="mt-0.5 size-6 text-fg-3" />}
<div className="min-w-0 flex-1">
<h2 className="text-xl font-semibold text-fg">{data.name}</h2>
<p className="mt-1 text-sm text-fg-muted">{data.question}</p>
</div>
</div>
{/* Controls table */}
<div className="rounded-md border border-line overflow-hidden">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-line bg-panel/60 text-left text-xs text-fg-muted">
<th className="w-8 py-2.5 pl-4 pr-0 font-medium" />
<th className="py-2.5 pl-2 pr-3 font-medium">Control</th>
<th className="py-2.5 px-3 font-medium">Description</th>
<th className="py-2.5 px-3 font-medium text-right">Type</th>
<th className="py-2.5 px-3 font-medium text-right">Accuracy (F1)</th>
<th className="py-2.5 px-3 font-medium text-right">pass@1</th>
<th className="py-2.5 px-3 font-medium">Mode</th>
<th className="py-2.5 pl-3 pr-4 font-medium">Evaluations</th>
</tr>
</thead>
<tbody>
{data.controls.map((control) => {
const Icon = controlIcons[control.name];
const mode = (control.mode ?? "disabled") as VerificationMode;
return (
<tr
key={control.slug}
className="border-b border-line last:border-b-0 cursor-pointer transition-colors hover:bg-overlay"
onClick={() => navigate(`/verification/controls/${slugify(control.name)}`)}
>
<td className="w-8 py-2.5 pl-4 pr-0">
{Icon && <Icon className="size-4 text-fg-3" />}
</td>
<td className="whitespace-nowrap py-2.5 pl-2 pr-3 font-medium text-fg-2">
{control.name}
</td>
<td className="py-2.5 px-3 text-fg-muted">
{control.description || (
<span className="italic">Not configured</span>
)}
</td>
<td className="whitespace-nowrap py-2.5 px-3 text-right">
<TypeBadge type={(control.type ?? null) as VerificationType | null} />
</td>
<td className="whitespace-nowrap py-2.5 px-3 text-right font-mono text-xs tabular-nums text-fg-2">
{control.f1 != null ? control.f1.toFixed(2) : <span className="text-fg-muted"></span>}
</td>
<td className="whitespace-nowrap py-2.5 px-3 text-right font-mono text-xs tabular-nums text-fg-2">
{control.pass_at_1 != null ? control.pass_at_1.toFixed(2) : <span className="text-fg-muted"></span>}
</td>
<td className="whitespace-nowrap py-2.5 px-3">
<ModeBadge mode={mode} />
</td>
<td className="whitespace-nowrap py-2.5 pl-3 pr-4">
<EvaluationDots evaluations={(control.evaluations ?? []) as VerificationResult[]} />
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -61,7 +61,7 @@
<script type="module" src="/assets/chunk-sadshphz.js"></script>
<script type="module" src="/assets/chunk-pmthkscp.js"></script>
<script type="module" src="/assets/chunk-v61ks9f7.js"></script>
<script type="module" src="/assets/entry-k7vnt1v8.js"></script>
<script type="module" src="/assets/entry-xtvaf517.js"></script>
<script type="module" src="/assets/chunk-n1k68xa8.js"></script>
<script type="module" src="/assets/chunk-rsph5pvm.js"></script>
<script type="module" src="/assets/chunk-9t57pdty.js"></script>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -19,6 +19,5 @@ pub mod server_config {
pub use fabro_config::server::*;
pub use fabro_types::Settings;
}
pub mod sessions;
pub mod tls;
pub mod web_auth;

View file

@ -57,8 +57,6 @@ use crate::error::ApiError;
use crate::jwt_auth::{AuthMode, AuthenticatedService};
use crate::run_manifest;
use crate::secret_store::{SecretStore, SecretStoreError};
use crate::sessions as sessions_mod;
use crate::sessions::{SessionStore, new_session_store};
use crate::static_files;
use crate::web_auth;
use fabro_interview::{Answer, Interviewer, QuestionType, WebInterviewer};
@ -249,7 +247,7 @@ pub struct AppState {
max_concurrent_runs: usize,
scheduler_notify: Notify,
global_event_tx: broadcast::Sender<EventEnvelope>,
pub sessions: SessionStore,
pub(crate) secret_store: AsyncRwLock<SecretStore>,
pub(crate) settings: Arc<RwLock<Settings>>,
pub(crate) config_path: PathBuf,
@ -404,7 +402,6 @@ fn demo_routes() -> Router<Arc<AppState>> {
.route("/runs/{id}/pause", post(demo::pause_stub))
.route("/runs/{id}/unpause", post(demo::unpause_stub))
.route("/runs/{id}/graph", get(demo::get_run_graph))
.route("/runs/{id}/retro", get(demo::get_run_retro))
.route("/runs/{id}/stages", get(demo::get_run_stages))
.route("/runs/{id}/artifacts", get(demo::list_run_artifacts_stub))
.route(
@ -435,35 +432,6 @@ fn demo_routes() -> Router<Arc<AppState>> {
.route("/workflows", get(demo::list_workflows))
.route("/workflows/{name}", get(demo::get_workflow))
.route("/workflows/{name}/runs", get(demo::list_workflow_runs))
.route(
"/verification/criteria",
get(demo::list_verification_criteria),
)
.route(
"/verification/criteria/{id}",
get(demo::get_verification_criterion),
)
.route(
"/verification/controls",
get(demo::list_verification_controls),
)
.route(
"/verification/controls/{id}",
get(demo::get_verification_control),
)
.route(
"/verification/signoffs",
get(demo::list_signoffs).post(demo::create_signoff_stub),
)
.route("/verification/signoffs/{id}", get(demo::get_signoff))
.route("/retros", get(demo::list_retros))
.route(
"/sessions",
get(demo::list_sessions).post(demo::create_session_stub),
)
.route("/sessions/{id}", get(demo::get_session))
.route("/sessions/{id}/messages", post(demo::send_message_stub))
.route("/sessions/{id}/events", get(demo::session_events_stub))
.route(
"/insights/queries",
get(demo::list_saved_queries).post(demo::save_query_stub),
@ -517,7 +485,6 @@ fn real_routes() -> Router<Arc<AppState>> {
.route("/runs/{id}/pause", post(pause_run))
.route("/runs/{id}/unpause", post(unpause_run))
.route("/runs/{id}/graph", get(get_graph))
.route("/runs/{id}/retro", get(get_retro))
.route("/runs/{id}/stages", get(not_implemented))
.route("/runs/{id}/artifacts", get(list_run_artifacts))
.route("/runs/{id}/stages/{stageId}/turns", get(not_implemented))
@ -542,26 +509,6 @@ fn real_routes() -> Router<Arc<AppState>> {
.route("/workflows", get(not_implemented))
.route("/workflows/{name}", get(not_implemented))
.route("/workflows/{name}/runs", get(not_implemented))
.route("/verification/criteria", get(not_implemented))
.route("/verification/criteria/{id}", get(not_implemented))
.route("/verification/controls", get(not_implemented))
.route("/verification/controls/{id}", get(not_implemented))
.route(
"/verification/signoffs",
get(not_implemented).post(not_implemented),
)
.route("/verification/signoffs/{id}", get(not_implemented))
.route("/retros", get(not_implemented))
.route(
"/sessions",
get(sessions_mod::list_sessions).post(sessions_mod::create_session),
)
.route("/sessions/{id}", get(sessions_mod::retrieve_session))
.route("/sessions/{id}/messages", post(sessions_mod::send_message))
.route(
"/sessions/{id}/events",
get(sessions_mod::stream_session_events),
)
.route(
"/insights/queries",
get(not_implemented).post(not_implemented),
@ -1432,7 +1379,6 @@ pub(crate) fn build_app_state_with_path(
max_concurrent_runs,
scheduler_notify: Notify::new(),
global_event_tx,
sessions: new_session_store(),
secret_store: AsyncRwLock::new(secret_store),
settings,
config_path,
@ -3554,40 +3500,6 @@ async fn create_completion(
}
}
async fn get_retro(
_auth: AuthenticatedService,
State(state): State<Arc<AppState>>,
Path(id): Path<String>,
) -> Response {
let id = match parse_run_id_path(&id) {
Ok(id) => id,
Err(response) => return response,
};
{
let runs = state.runs.lock().expect("runs lock poisoned");
if !runs.contains_key(&id) {
return ApiError::not_found("Run not found.").into_response();
}
}
match state.store.open_run_reader(&id).await {
Ok(run_store) => match run_store.state().await {
Ok(run_state) => match run_state.retro {
Some(retro) => (StatusCode::OK, Json(retro)).into_response(),
None => (StatusCode::OK, Json(serde_json::json!(null))).into_response(),
},
Err(err) => {
tracing::warn!(run_id = %id, error = %err, "Failed to load retro state from store");
(StatusCode::OK, Json(serde_json::json!(null))).into_response()
}
},
Err(err) => {
tracing::warn!(run_id = %id, error = %err, "Failed to open run store reader");
ApiError::not_found("Run not found.").into_response()
}
}
}
/// Render DOT source to a styled image via `render_dot` on a blocking thread.
pub(crate) async fn render_graph_bytes(dot_source: &str, format: GraphFormat) -> Response {
use fabro_graphviz::render::render_dot;

View file

@ -1,635 +0,0 @@
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, RwLock};
use axum::Json;
#[cfg(test)]
use axum::body::to_bytes;
use axum::extract::{Path, Query, State};
use axum::http::StatusCode;
use axum::response::sse::{Event, Sse};
use axum::response::{IntoResponse, Response};
use fabro_llm::generate::{GenerateParams, stream as llm_stream};
use fabro_llm::types::{Message as LlmMessage, StreamEvent};
use tokio::sync::broadcast;
use tokio_stream::wrappers::BroadcastStream;
use fabro_api::types::{
AssistantTurn, AssistantTurnKind, CreateSessionRequest, CreateSessionResponse, ModelReference,
PaginatedSessionList, PaginationMeta, SendMessageRequest, SendMessageResponse, SessionDetail,
SessionListItem, SessionTurn, UserTurn, UserTurnKind,
};
use crate::error::ApiError;
use crate::jwt_auth::AuthenticatedService;
use crate::server::{AppState, PaginationParams};
pub type SessionStore = Arc<RwLock<HashMap<uuid::Uuid, SessionState>>>;
pub fn new_session_store() -> SessionStore {
Arc::new(RwLock::new(HashMap::new()))
}
pub struct SessionState {
pub id: uuid::Uuid,
pub title: String,
pub model_id: String,
pub model_provider: Option<String>,
pub system_prompt: Option<String>,
pub turns: Vec<SessionTurn>,
pub created_at: chrono::DateTime<chrono::Utc>,
pub updated_at: chrono::DateTime<chrono::Utc>,
pub event_tx: broadcast::Sender<SessionEvent>,
pub generation_seq: Arc<AtomicU64>,
}
#[derive(Clone, Debug)]
pub enum SessionEvent {
TextDelta {
delta: String,
},
AssistantTurnComplete {
content: String,
created_at: chrono::DateTime<chrono::Utc>,
},
Done,
Error {
message: String,
},
}
fn generate_title(content: &str) -> String {
let trimmed = content.trim();
if trimmed.len() <= 60 {
return trimmed.to_string();
}
// Find a word boundary near 60 chars
match trimmed[..60].rfind(' ') {
Some(pos) => format!("{}", &trimmed[..pos]),
None => format!("{}", &trimmed[..60]),
}
}
fn resolve_model(model_arg: Option<String>) -> (String, Option<String>) {
let raw = model_arg.unwrap_or_else(|| {
fabro_model::Catalog::builtin()
.list(None)
.first()
.map_or_else(|| "claude-sonnet-4-5".to_string(), |m| m.id.clone())
});
match fabro_model::Catalog::builtin().get(&raw) {
Some(info) => (info.id.clone(), Some(info.provider.to_string())),
None => (raw, None),
}
}
fn turns_to_messages(turns: &[SessionTurn]) -> Vec<LlmMessage> {
turns
.iter()
.filter_map(|turn| match turn {
SessionTurn::UserTurn(t) => Some(LlmMessage::user(&t.content)),
SessionTurn::AssistantTurn(t) => Some(LlmMessage::assistant(&t.content)),
SessionTurn::ToolTurn(_) => None,
})
.collect()
}
fn spawn_generation(store: SessionStore, session_id: uuid::Uuid, dry_run: bool, seq_at_start: u64) {
tokio::spawn(async move {
use futures_util::StreamExt;
let (event_tx, model_id, model_provider, system_prompt, messages, generation_seq) = {
let store = store.read().expect("session store lock poisoned");
let Some(session) = store.get(&session_id) else {
return;
};
(
session.event_tx.clone(),
session.model_id.clone(),
session.model_provider.clone(),
session.system_prompt.clone(),
turns_to_messages(&session.turns),
Arc::clone(&session.generation_seq),
)
};
if dry_run {
let content = "This is a dry-run response.".to_string();
let now = chrono::Utc::now();
let _ = event_tx.send(SessionEvent::TextDelta {
delta: content.clone(),
});
let _ = event_tx.send(SessionEvent::AssistantTurnComplete {
content: content.clone(),
created_at: now,
});
// Append assistant turn to session
{
let mut store = store.write().expect("session store lock poisoned");
if let Some(session) = store.get_mut(&session_id) {
session
.turns
.push(SessionTurn::AssistantTurn(AssistantTurn {
kind: AssistantTurnKind::Assistant,
content,
created_at: now,
}));
session.updated_at = now;
}
}
let _ = event_tx.send(SessionEvent::Done);
return;
}
let mut params = GenerateParams::new(&model_id)
.messages(messages)
.max_tokens(4096);
if let Some(ref provider) = model_provider {
params = params.provider(provider);
}
if let Some(ref system) = system_prompt {
params = params.system(system);
}
let stream_result = match llm_stream(params).await {
Ok(s) => s,
Err(e) => {
let _ = event_tx.send(SessionEvent::Error {
message: format!("LLM error: {e}"),
});
return;
}
};
let mut stream_result = stream_result;
let mut full_text = String::new();
while let Some(event) = stream_result.next().await {
// Check if generation was superseded by a new message
if generation_seq.load(Ordering::Relaxed) != seq_at_start {
return;
}
match event {
Ok(StreamEvent::TextDelta { delta, .. }) => {
full_text.push_str(&delta);
let _ = event_tx.send(SessionEvent::TextDelta { delta });
}
Err(e) => {
let _ = event_tx.send(SessionEvent::Error {
message: format!("Stream error: {e}"),
});
return;
}
_ => {}
}
}
let now = chrono::Utc::now();
let _ = event_tx.send(SessionEvent::AssistantTurnComplete {
content: full_text.clone(),
created_at: now,
});
// Append assistant turn to session
{
let mut store = store.write().expect("session store lock poisoned");
if let Some(session) = store.get_mut(&session_id) {
session
.turns
.push(SessionTurn::AssistantTurn(AssistantTurn {
kind: AssistantTurnKind::Assistant,
content: full_text,
created_at: now,
}));
session.updated_at = now;
}
}
let _ = event_tx.send(SessionEvent::Done);
});
}
pub async fn create_session(
_auth: AuthenticatedService,
State(state): State<Arc<AppState>>,
Json(req): Json<CreateSessionRequest>,
) -> Response {
let (model_id, model_provider) = resolve_model(req.model);
let now = chrono::Utc::now();
let session_id = uuid::Uuid::new_v4();
let title = generate_title(&req.content);
let (event_tx, _) = broadcast::channel(256);
let generation_seq = Arc::new(AtomicU64::new(1));
let user_turn = SessionTurn::UserTurn(UserTurn {
kind: UserTurnKind::User,
content: req.content,
created_at: now,
});
let session = SessionState {
id: session_id,
title: title.clone(),
model_id: model_id.clone(),
model_provider: model_provider.clone(),
system_prompt: req.system,
turns: vec![user_turn],
created_at: now,
updated_at: now,
event_tx: event_tx.clone(),
generation_seq: Arc::clone(&generation_seq),
};
{
let mut store = state.sessions.write().expect("session store lock poisoned");
store.insert(session_id, session);
}
spawn_generation(Arc::clone(&state.sessions), session_id, state.dry_run(), 1);
(
StatusCode::CREATED,
Json(CreateSessionResponse {
id: session_id,
title,
model: ModelReference { id: model_id },
created_at: now,
updated_at: now,
}),
)
.into_response()
}
pub async fn retrieve_session(
_auth: AuthenticatedService,
State(state): State<Arc<AppState>>,
Path(id): Path<uuid::Uuid>,
) -> Response {
let store = state.sessions.read().expect("session store lock poisoned");
match store.get(&id) {
Some(session) => (
StatusCode::OK,
Json(SessionDetail {
id: session.id,
title: session.title.clone(),
model: ModelReference {
id: session.model_id.clone(),
},
created_at: session.created_at,
updated_at: session.updated_at,
turns: session.turns.clone(),
}),
)
.into_response(),
None => ApiError::not_found("Session not found.").into_response(),
}
}
pub async fn send_message(
_auth: AuthenticatedService,
State(state): State<Arc<AppState>>,
Path(id): Path<uuid::Uuid>,
Json(req): Json<SendMessageRequest>,
) -> Response {
let seq = {
let mut store = state.sessions.write().expect("session store lock poisoned");
match store.get_mut(&id) {
Some(session) => {
let now = chrono::Utc::now();
session.turns.push(SessionTurn::UserTurn(UserTurn {
kind: UserTurnKind::User,
content: req.content,
created_at: now,
}));
session.updated_at = now;
session.generation_seq.fetch_add(1, Ordering::Relaxed) + 1
}
None => return ApiError::not_found("Session not found.").into_response(),
}
};
spawn_generation(Arc::clone(&state.sessions), id, state.dry_run(), seq);
(
StatusCode::ACCEPTED,
Json(SendMessageResponse { accepted: true }),
)
.into_response()
}
pub async fn stream_session_events(
_auth: AuthenticatedService,
State(state): State<Arc<AppState>>,
Path(id): Path<uuid::Uuid>,
) -> Response {
use tokio_stream::StreamExt;
let rx = {
let store = state.sessions.read().expect("session store lock poisoned");
match store.get(&id) {
Some(session) => session.event_tx.subscribe(),
None => return ApiError::not_found("Session not found.").into_response(),
}
};
let stream = BroadcastStream::new(rx).filter_map(|result| match result {
Ok(event) => {
let sse: Option<Event> = match event {
SessionEvent::TextDelta { delta } => Some(
Event::default()
.event("content_delta")
.data(serde_json::json!({"delta": delta}).to_string()),
),
SessionEvent::AssistantTurnComplete {
content,
created_at,
} => Some(
Event::default().event("assistant_turn").data(
serde_json::json!({
"kind": "assistant",
"content": content,
"created_at": created_at,
})
.to_string(),
),
),
SessionEvent::Done => Some(Event::default().event("done").data("{}")),
SessionEvent::Error { message } => Some(
Event::default()
.event("error")
.data(serde_json::json!({"message": message}).to_string()),
),
};
sse.map(Ok::<_, std::convert::Infallible>)
}
Err(_) => None,
});
Sse::new(stream).into_response()
}
pub async fn list_sessions(
_auth: AuthenticatedService,
State(state): State<Arc<AppState>>,
Query(pagination): Query<PaginationParams>,
) -> Response {
let store = state.sessions.read().expect("session store lock poisoned");
let limit = pagination.limit.clamp(1, 100) as usize;
let offset = pagination.offset as usize;
let mut items: Vec<SessionListItem> = store
.values()
.map(|session| {
let last_message_preview = session
.turns
.last()
.map(|t| match t {
SessionTurn::UserTurn(u) => u.content.clone(),
SessionTurn::AssistantTurn(a) => a.content.clone(),
SessionTurn::ToolTurn(_) => String::new(),
})
.unwrap_or_default();
let preview = if last_message_preview.len() > 100 {
format!("{}", &last_message_preview[..100])
} else {
last_message_preview
};
SessionListItem {
id: session.id,
title: session.title.clone(),
model: ModelReference {
id: session.model_id.clone(),
},
last_message_preview: preview,
created_at: session.created_at,
updated_at: session.updated_at,
}
})
.collect();
// Sort by updated_at desc
items.sort_by(|a, b| b.updated_at.cmp(&a.updated_at));
let page: Vec<_> = items.into_iter().skip(offset).take(limit + 1).collect();
let has_more = page.len() > limit;
let data: Vec<_> = page.into_iter().take(limit).collect();
(
StatusCode::OK,
Json(PaginatedSessionList {
data,
meta: PaginationMeta { has_more },
}),
)
.into_response()
}
#[cfg(test)]
mod tests {
use super::*;
use axum::body::Body;
use axum::http::Request;
use tower::ServiceExt;
use crate::jwt_auth::AuthMode;
use crate::server::{build_router, create_app_state_with_options};
fn dry_run_app() -> axum::Router {
let state = create_app_state_with_options(
fabro_types::Settings {
dry_run: Some(true),
..Default::default()
},
5,
);
build_router(state, AuthMode::Disabled)
}
async fn body_json(body: Body) -> serde_json::Value {
let bytes = to_bytes(body, usize::MAX).await.unwrap();
serde_json::from_slice(&bytes).unwrap()
}
fn api(path: &str) -> String {
format!("/api/v1{path}")
}
async fn create_test_session(app: &axum::Router) -> serde_json::Value {
let req = Request::builder()
.method("POST")
.uri(api("/sessions"))
.header("content-type", "application/json")
.body(Body::from(
serde_json::to_string(&serde_json::json!({
"content": "Hello, world!"
}))
.unwrap(),
))
.unwrap();
let response = app.clone().oneshot(req).await.unwrap();
assert_eq!(response.status(), StatusCode::CREATED);
body_json(response.into_body()).await
}
#[tokio::test]
async fn create_session_returns_201() {
let app = dry_run_app();
let body = create_test_session(&app).await;
assert!(body["id"].is_string());
assert!(body["title"].is_string());
assert!(body["model"]["id"].is_string());
assert!(body["created_at"].is_string());
assert!(body["updated_at"].is_string());
}
#[tokio::test]
async fn retrieve_session_after_create() {
let app = dry_run_app();
let create_body = create_test_session(&app).await;
let session_id = create_body["id"].as_str().unwrap();
// Give generation task a moment to complete
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
let req = Request::builder()
.method("GET")
.uri(api(&format!("/sessions/{session_id}")))
.body(Body::empty())
.unwrap();
let response = app.oneshot(req).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = body_json(response.into_body()).await;
assert_eq!(body["id"].as_str().unwrap(), session_id);
assert!(body["turns"].is_array());
// Should have at least the initial user turn
assert!(!body["turns"].as_array().unwrap().is_empty());
assert_eq!(body["turns"][0]["kind"], "user");
}
#[tokio::test]
async fn retrieve_session_not_found() {
let app = dry_run_app();
let req = Request::builder()
.method("GET")
.uri(api("/sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890"))
.body(Body::empty())
.unwrap();
let response = app.oneshot(req).await.unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
#[tokio::test]
async fn send_message_returns_202() {
let app = dry_run_app();
let create_body = create_test_session(&app).await;
let session_id = create_body["id"].as_str().unwrap();
let req = Request::builder()
.method("POST")
.uri(api(&format!("/sessions/{session_id}/messages")))
.header("content-type", "application/json")
.body(Body::from(
serde_json::to_string(&serde_json::json!({
"content": "Follow up question"
}))
.unwrap(),
))
.unwrap();
let response = app.oneshot(req).await.unwrap();
assert_eq!(response.status(), StatusCode::ACCEPTED);
let body = body_json(response.into_body()).await;
assert_eq!(body["accepted"], true);
}
#[tokio::test]
async fn send_message_not_found() {
let app = dry_run_app();
let req = Request::builder()
.method("POST")
.uri(api(
"/sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/messages",
))
.header("content-type", "application/json")
.body(Body::from(
serde_json::to_string(&serde_json::json!({
"content": "Hello"
}))
.unwrap(),
))
.unwrap();
let response = app.oneshot(req).await.unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
#[tokio::test]
async fn list_sessions_empty() {
let app = dry_run_app();
let req = Request::builder()
.method("GET")
.uri(api("/sessions"))
.body(Body::empty())
.unwrap();
let response = app.oneshot(req).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = body_json(response.into_body()).await;
assert!(body["data"].as_array().unwrap().is_empty());
assert_eq!(body["meta"]["has_more"], false);
}
#[tokio::test]
async fn list_sessions_after_create() {
let app = dry_run_app();
let _create_body = create_test_session(&app).await;
let req = Request::builder()
.method("GET")
.uri(api("/sessions"))
.body(Body::empty())
.unwrap();
let response = app.oneshot(req).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = body_json(response.into_body()).await;
assert_eq!(body["data"].as_array().unwrap().len(), 1);
}
#[tokio::test]
async fn stream_events_dry_run() {
let app = dry_run_app();
let create_body = create_test_session(&app).await;
let session_id = create_body["id"].as_str().unwrap();
// Give the generation task a moment to produce events
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
let req = Request::builder()
.method("GET")
.uri(api(&format!("/sessions/{session_id}/events")))
.body(Body::empty())
.unwrap();
let response = app.oneshot(req).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(
response
.headers()
.get("content-type")
.unwrap()
.to_str()
.unwrap(),
"text/event-stream"
);
}
}

View file

@ -53,14 +53,6 @@ const ENDPOINTS: &[PaginatedEndpoint] = &[
path: "/api/v1/workflows/implement/runs",
name: "listWorkflowRuns",
},
PaginatedEndpoint {
path: "/api/v1/retros",
name: "listRetros",
},
PaginatedEndpoint {
path: "/api/v1/sessions",
name: "listSessions",
},
PaginatedEndpoint {
path: "/api/v1/insights/queries",
name: "listSavedQueries",

View file

@ -5,16 +5,13 @@ api/human-in-the-loop-api.ts
api/insights-api.ts
api/models-api.ts
api/repos-api.ts
api/retros-api.ts
api/run-internals-api.ts
api/run-outputs-api.ts
api/runs-api.ts
api/secrets-api.ts
api/sessions-api.ts
api/settings-api.ts
api/system-api.ts
api/usage-api.ts
api/verification-api.ts
api/workflows-api.ts
base.ts
common.ts
@ -30,7 +27,6 @@ models/artifact-entry.ts
models/artifact-list-response.ts
models/artifacts-settings.ts
models/assistant-stage-turn.ts
models/assistant-turn.ts
models/auth-settings.ts
models/board-column.ts
models/check-run-status.ts
@ -43,15 +39,7 @@ models/completion-response.ts
models/completion-tool-choice.ts
models/completion-tool-definition.ts
models/completion-usage.ts
models/control-detail.ts
models/control-info.ts
models/control-performance.ts
models/control-reference.ts
models/create-completion-request.ts
models/create-session-request.ts
models/create-session-response.ts
models/create-signoff-request.ts
models/criterion-reference.ts
models/daytona-settings-network-one-of.ts
models/daytona-settings-network.ts
models/daytona-settings.ts
@ -74,8 +62,6 @@ models/execute-query-response.ts
models/features.ts
models/file-checkpoint.ts
models/file-diff.ts
models/friction-kind.ts
models/friction-point.ts
models/git-author-settings.ts
models/git-hub-settings.ts
models/git-settings.ts
@ -85,8 +71,6 @@ models/hook-definition.ts
models/index.ts
models/internal-run-status.ts
models/internal-stage-status.ts
models/learning-category.ts
models/learning.ts
models/llm-settings.ts
models/local-sandbox-settings.ts
models/log-settings.ts
@ -109,22 +93,15 @@ models/model-test-result.ts
models/model.ts
models/node-state.ts
models/node-status-record.ts
models/open-item-kind.ts
models/open-item.ts
models/paginated-api-question-list.ts
models/paginated-event-list.ts
models/paginated-history-entry-list.ts
models/paginated-model-list.ts
models/paginated-retro-list.ts
models/paginated-run-file-list.ts
models/paginated-run-list.ts
models/paginated-run-stage-list.ts
models/paginated-saved-query-list.ts
models/paginated-session-list.ts
models/paginated-signoff-list.ts
models/paginated-stage-turn-list.ts
models/paginated-verification-control-list.ts
models/paginated-verification-criterion-list.ts
models/paginated-workflow-list.ts
models/pagination-meta.ts
models/preflight-check-detail.ts
@ -140,16 +117,12 @@ models/prune-runs-request.ts
models/prune-runs-response.ts
models/pull-request-settings.ts
models/question-type.ts
models/recent-control-result.ts
models/render-workflow-graph-direction.ts
models/render-workflow-graph-format.ts
models/render-workflow-graph-request.ts
models/repo-check-response-permissions.ts
models/repo-check-response.ts
models/repository-reference.ts
models/retro-detail.ts
models/retro-list-item.ts
models/retro-stats.ts
models/root-response-urls.ts
models/root-response.ts
models/run-artifact-entry.ts
@ -180,25 +153,15 @@ models/save-query-request.ts
models/saved-query.ts
models/secret-list-response.ts
models/secret-metadata.ts
models/send-message-request.ts
models/send-message-response.ts
models/server-settings-exec.ts
models/server-settings-fabro.ts
models/server-settings-server-tls.ts
models/server-settings-server.ts
models/server-settings.ts
models/session-detail.ts
models/session-list-item.ts
models/session-turn.ts
models/set-secret-request.ts
models/setup-settings.ts
models/sibling-control.ts
models/signoff-status.ts
models/signoff.ts
models/smoothness-rating.ts
models/ssh-access-request.ts
models/ssh-access-response.ts
models/stage-retro.ts
models/stage-status.ts
models/stage-turn.ts
models/start-run-request.ts
@ -212,22 +175,12 @@ models/system-stage-turn.ts
models/tls-settings.ts
models/token-usage.ts
models/tool-stage-turn.ts
models/tool-turn.ts
models/tool-use.ts
models/usage-by-model.ts
models/usage-stage-ref.ts
models/usage-stage.ts
models/usage-totals.ts
models/user-response.ts
models/user-turn.ts
models/verification-control-list-item.ts
models/verification-control.ts
models/verification-criterion-detail.ts
models/verification-criterion.ts
models/verification-detail-response.ts
models/verification-mode.ts
models/verification-result.ts
models/verification-type.ts
models/web-settings.ts
models/webhook-settings.ts
models/workflow-detail.ts

View file

@ -20,15 +20,12 @@ export * from './api/human-in-the-loop-api';
export * from './api/insights-api';
export * from './api/models-api';
export * from './api/repos-api';
export * from './api/retros-api';
export * from './api/run-internals-api';
export * from './api/run-outputs-api';
export * from './api/runs-api';
export * from './api/secrets-api';
export * from './api/sessions-api';
export * from './api/settings-api';
export * from './api/system-api';
export * from './api/usage-api';
export * from './api/verification-api';
export * from './api/workflows-api';

View file

@ -1,237 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import type { Configuration } from '../configuration';
import type { AxiosPromise, AxiosInstance, RawAxiosRequestConfig } from 'axios';
import globalAxios from 'axios';
// Some imports not used depending on template conditions
// @ts-ignore
import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction, replaceWithSerializableTypeIfNeeded } from '../common';
// @ts-ignore
import { BASE_PATH, COLLECTION_FORMATS, type RequestArgs, BaseAPI, RequiredError, operationServerMap } from '../base';
// @ts-ignore
import type { ErrorResponse } from '../models';
// @ts-ignore
import type { PaginatedRetroList } from '../models';
// @ts-ignore
import type { RetroDetail } from '../models';
// @ts-ignore
import type { SmoothnessRating } from '../models';
/**
* RetrosApi - axios parameter creator
*/
export const RetrosApiAxiosParamCreator = function (configuration?: Configuration) {
return {
/**
* Returns a paginated list of run retrospectives ordered by recency, with smoothness ratings and summary statistics.
* @summary List Retros
* @param {string} [workflow] Filter retros by workflow slug.
* @param {SmoothnessRating} [smoothness] Filter retros by smoothness rating.
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
listRetros: async (workflow?: string, smoothness?: SmoothnessRating, pageLimit?: number, pageOffset?: number, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/api/v1/retros`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication mTLS required
await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration)
// authentication BearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
if (workflow !== undefined) {
localVarQueryParameter['workflow'] = workflow;
}
if (smoothness !== undefined) {
localVarQueryParameter['smoothness'] = smoothness;
}
if (pageLimit !== undefined) {
localVarQueryParameter['page[limit]'] = pageLimit;
}
if (pageOffset !== undefined) {
localVarQueryParameter['page[offset]'] = pageOffset;
}
localVarHeaderParameter['Accept'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Returns the retrospective analysis for a completed run, or null if the retro has not been generated yet.
* @summary Retrieve Retro
* @param {string} id Unique run identifier (ULID).
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
retrieveRetro: async (id: string, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'id' is not null or undefined
assertParamExists('retrieveRetro', 'id', id)
const localVarPath = `/api/v1/runs/{id}/retro`
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication mTLS required
await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration)
// authentication BearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
localVarHeaderParameter['Accept'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
}
};
/**
* RetrosApi - functional programming interface
*/
export const RetrosApiFp = function(configuration?: Configuration) {
const localVarAxiosParamCreator = RetrosApiAxiosParamCreator(configuration)
return {
/**
* Returns a paginated list of run retrospectives ordered by recency, with smoothness ratings and summary statistics.
* @summary List Retros
* @param {string} [workflow] Filter retros by workflow slug.
* @param {SmoothnessRating} [smoothness] Filter retros by smoothness rating.
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async listRetros(workflow?: string, smoothness?: SmoothnessRating, pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<PaginatedRetroList>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.listRetros(workflow, smoothness, pageLimit, pageOffset, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['RetrosApi.listRetros']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
* Returns the retrospective analysis for a completed run, or null if the retro has not been generated yet.
* @summary Retrieve Retro
* @param {string} id Unique run identifier (ULID).
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async retrieveRetro(id: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<RetroDetail>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.retrieveRetro(id, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['RetrosApi.retrieveRetro']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
}
};
/**
* RetrosApi - factory interface
*/
export const RetrosApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
const localVarFp = RetrosApiFp(configuration)
return {
/**
* Returns a paginated list of run retrospectives ordered by recency, with smoothness ratings and summary statistics.
* @summary List Retros
* @param {string} [workflow] Filter retros by workflow slug.
* @param {SmoothnessRating} [smoothness] Filter retros by smoothness rating.
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
listRetros(workflow?: string, smoothness?: SmoothnessRating, pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig): AxiosPromise<PaginatedRetroList> {
return localVarFp.listRetros(workflow, smoothness, pageLimit, pageOffset, options).then((request) => request(axios, basePath));
},
/**
* Returns the retrospective analysis for a completed run, or null if the retro has not been generated yet.
* @summary Retrieve Retro
* @param {string} id Unique run identifier (ULID).
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
retrieveRetro(id: string, options?: RawAxiosRequestConfig): AxiosPromise<RetroDetail> {
return localVarFp.retrieveRetro(id, options).then((request) => request(axios, basePath));
},
};
};
/**
* RetrosApi - object-oriented interface
*/
export class RetrosApi extends BaseAPI {
/**
* Returns a paginated list of run retrospectives ordered by recency, with smoothness ratings and summary statistics.
* @summary List Retros
* @param {string} [workflow] Filter retros by workflow slug.
* @param {SmoothnessRating} [smoothness] Filter retros by smoothness rating.
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public listRetros(workflow?: string, smoothness?: SmoothnessRating, pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig) {
return RetrosApiFp(this.configuration).listRetros(workflow, smoothness, pageLimit, pageOffset, options).then((request) => request(this.axios, this.basePath));
}
/**
* Returns the retrospective analysis for a completed run, or null if the retro has not been generated yet.
* @summary Retrieve Retro
* @param {string} id Unique run identifier (ULID).
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public retrieveRetro(id: string, options?: RawAxiosRequestConfig) {
return RetrosApiFp(this.configuration).retrieveRetro(id, options).then((request) => request(this.axios, this.basePath));
}
}

View file

@ -1,468 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import type { Configuration } from '../configuration';
import type { AxiosPromise, AxiosInstance, RawAxiosRequestConfig } from 'axios';
import globalAxios from 'axios';
// Some imports not used depending on template conditions
// @ts-ignore
import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction, replaceWithSerializableTypeIfNeeded } from '../common';
// @ts-ignore
import { BASE_PATH, COLLECTION_FORMATS, type RequestArgs, BaseAPI, RequiredError, operationServerMap } from '../base';
// @ts-ignore
import type { CreateSessionRequest } from '../models';
// @ts-ignore
import type { CreateSessionResponse } from '../models';
// @ts-ignore
import type { ErrorResponse } from '../models';
// @ts-ignore
import type { PaginatedSessionList } from '../models';
// @ts-ignore
import type { SendMessageRequest } from '../models';
// @ts-ignore
import type { SendMessageResponse } from '../models';
// @ts-ignore
import type { SessionDetail } from '../models';
/**
* SessionsApi - axios parameter creator
*/
export const SessionsApiAxiosParamCreator = function (configuration?: Configuration) {
return {
/**
* Start a new interactive chat session. The initial user prompt is required; a model may optionally be specified.
* @summary Create Session
* @param {CreateSessionRequest} createSessionRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
createSession: async (createSessionRequest: CreateSessionRequest, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'createSessionRequest' is not null or undefined
assertParamExists('createSession', 'createSessionRequest', createSessionRequest)
const localVarPath = `/api/v1/sessions`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication mTLS required
await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration)
// authentication BearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
localVarHeaderParameter['Content-Type'] = 'application/json';
localVarHeaderParameter['Accept'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
localVarRequestOptions.data = serializeDataIfNeeded(createSessionRequest, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Returns sessions ordered by recency (newest first).
* @summary List Sessions
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
listSessions: async (pageLimit?: number, pageOffset?: number, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/api/v1/sessions`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication mTLS required
await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration)
// authentication BearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
if (pageLimit !== undefined) {
localVarQueryParameter['page[limit]'] = pageLimit;
}
if (pageOffset !== undefined) {
localVarQueryParameter['page[offset]'] = pageOffset;
}
localVarHeaderParameter['Accept'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Returns the full session detail including all conversation turns.
* @summary Retrieve Session
* @param {string} id Unique session identifier.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
retrieveSession: async (id: string, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'id' is not null or undefined
assertParamExists('retrieveSession', 'id', id)
const localVarPath = `/api/v1/sessions/{id}`
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication mTLS required
await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration)
// authentication BearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
localVarHeaderParameter['Accept'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Append a user message to an existing session. The server will process it and produce assistant and tool turns asynchronously via the event stream.
* @summary Send Session Message
* @param {string} id Unique session identifier.
* @param {SendMessageRequest} sendMessageRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
sendSessionMessage: async (id: string, sendMessageRequest: SendMessageRequest, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'id' is not null or undefined
assertParamExists('sendSessionMessage', 'id', id)
// verify required parameter 'sendMessageRequest' is not null or undefined
assertParamExists('sendSessionMessage', 'sendMessageRequest', sendMessageRequest)
const localVarPath = `/api/v1/sessions/{id}/messages`
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication mTLS required
await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration)
// authentication BearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
localVarHeaderParameter['Content-Type'] = 'application/json';
localVarHeaderParameter['Accept'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
localVarRequestOptions.data = serializeDataIfNeeded(sendMessageRequest, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Opens a server-sent event (SSE) stream for real-time session updates. Each SSE frame includes a sequential numeric `id:` field that supports resumption via the `Last-Event-ID` request header. The stream emits the following SSE event types: - `event: content_delta` data: `{\"delta\": \"...\"}` (incremental text chunk) - `event: assistant_turn` data: `AssistantTurn` JSON object - `event: tool_turn` data: `ToolTurn` JSON object - `event: done` data: `{}` (stream complete) - `event: error` data: `{\"message\": \"...\"}` (error occurred) Each SSE frame has an `id:` line (sequential integer), an `event:` line (the event type), and a `data:` line (the JSON payload).
* @summary Stream Session Events
* @param {string} id Unique session identifier.
* @param {string} [lastEventID] SSE reconnection header. When provided, the server resumes the stream after the event with this ID. IDs are 0-based sequential integers assigned to each emitted SSE frame.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
streamSessionEvents: async (id: string, lastEventID?: string, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'id' is not null or undefined
assertParamExists('streamSessionEvents', 'id', id)
const localVarPath = `/api/v1/sessions/{id}/events`
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication mTLS required
await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration)
// authentication BearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
localVarHeaderParameter['Accept'] = 'text/event-stream,application/json';
if (lastEventID != null) {
localVarHeaderParameter['Last-Event-ID'] = String(lastEventID);
}
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
}
};
/**
* SessionsApi - functional programming interface
*/
export const SessionsApiFp = function(configuration?: Configuration) {
const localVarAxiosParamCreator = SessionsApiAxiosParamCreator(configuration)
return {
/**
* Start a new interactive chat session. The initial user prompt is required; a model may optionally be specified.
* @summary Create Session
* @param {CreateSessionRequest} createSessionRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async createSession(createSessionRequest: CreateSessionRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<CreateSessionResponse>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.createSession(createSessionRequest, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['SessionsApi.createSession']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
* Returns sessions ordered by recency (newest first).
* @summary List Sessions
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async listSessions(pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<PaginatedSessionList>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.listSessions(pageLimit, pageOffset, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['SessionsApi.listSessions']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
* Returns the full session detail including all conversation turns.
* @summary Retrieve Session
* @param {string} id Unique session identifier.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async retrieveSession(id: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SessionDetail>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.retrieveSession(id, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['SessionsApi.retrieveSession']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
* Append a user message to an existing session. The server will process it and produce assistant and tool turns asynchronously via the event stream.
* @summary Send Session Message
* @param {string} id Unique session identifier.
* @param {SendMessageRequest} sendMessageRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async sendSessionMessage(id: string, sendMessageRequest: SendMessageRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SendMessageResponse>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.sendSessionMessage(id, sendMessageRequest, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['SessionsApi.sendSessionMessage']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
* Opens a server-sent event (SSE) stream for real-time session updates. Each SSE frame includes a sequential numeric `id:` field that supports resumption via the `Last-Event-ID` request header. The stream emits the following SSE event types: - `event: content_delta` data: `{\"delta\": \"...\"}` (incremental text chunk) - `event: assistant_turn` data: `AssistantTurn` JSON object - `event: tool_turn` data: `ToolTurn` JSON object - `event: done` data: `{}` (stream complete) - `event: error` data: `{\"message\": \"...\"}` (error occurred) Each SSE frame has an `id:` line (sequential integer), an `event:` line (the event type), and a `data:` line (the JSON payload).
* @summary Stream Session Events
* @param {string} id Unique session identifier.
* @param {string} [lastEventID] SSE reconnection header. When provided, the server resumes the stream after the event with this ID. IDs are 0-based sequential integers assigned to each emitted SSE frame.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async streamSessionEvents(id: string, lastEventID?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.streamSessionEvents(id, lastEventID, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['SessionsApi.streamSessionEvents']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
}
};
/**
* SessionsApi - factory interface
*/
export const SessionsApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
const localVarFp = SessionsApiFp(configuration)
return {
/**
* Start a new interactive chat session. The initial user prompt is required; a model may optionally be specified.
* @summary Create Session
* @param {CreateSessionRequest} createSessionRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
createSession(createSessionRequest: CreateSessionRequest, options?: RawAxiosRequestConfig): AxiosPromise<CreateSessionResponse> {
return localVarFp.createSession(createSessionRequest, options).then((request) => request(axios, basePath));
},
/**
* Returns sessions ordered by recency (newest first).
* @summary List Sessions
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
listSessions(pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig): AxiosPromise<PaginatedSessionList> {
return localVarFp.listSessions(pageLimit, pageOffset, options).then((request) => request(axios, basePath));
},
/**
* Returns the full session detail including all conversation turns.
* @summary Retrieve Session
* @param {string} id Unique session identifier.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
retrieveSession(id: string, options?: RawAxiosRequestConfig): AxiosPromise<SessionDetail> {
return localVarFp.retrieveSession(id, options).then((request) => request(axios, basePath));
},
/**
* Append a user message to an existing session. The server will process it and produce assistant and tool turns asynchronously via the event stream.
* @summary Send Session Message
* @param {string} id Unique session identifier.
* @param {SendMessageRequest} sendMessageRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
sendSessionMessage(id: string, sendMessageRequest: SendMessageRequest, options?: RawAxiosRequestConfig): AxiosPromise<SendMessageResponse> {
return localVarFp.sendSessionMessage(id, sendMessageRequest, options).then((request) => request(axios, basePath));
},
/**
* Opens a server-sent event (SSE) stream for real-time session updates. Each SSE frame includes a sequential numeric `id:` field that supports resumption via the `Last-Event-ID` request header. The stream emits the following SSE event types: - `event: content_delta` data: `{\"delta\": \"...\"}` (incremental text chunk) - `event: assistant_turn` data: `AssistantTurn` JSON object - `event: tool_turn` data: `ToolTurn` JSON object - `event: done` data: `{}` (stream complete) - `event: error` data: `{\"message\": \"...\"}` (error occurred) Each SSE frame has an `id:` line (sequential integer), an `event:` line (the event type), and a `data:` line (the JSON payload).
* @summary Stream Session Events
* @param {string} id Unique session identifier.
* @param {string} [lastEventID] SSE reconnection header. When provided, the server resumes the stream after the event with this ID. IDs are 0-based sequential integers assigned to each emitted SSE frame.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
streamSessionEvents(id: string, lastEventID?: string, options?: RawAxiosRequestConfig): AxiosPromise<string> {
return localVarFp.streamSessionEvents(id, lastEventID, options).then((request) => request(axios, basePath));
},
};
};
/**
* SessionsApi - object-oriented interface
*/
export class SessionsApi extends BaseAPI {
/**
* Start a new interactive chat session. The initial user prompt is required; a model may optionally be specified.
* @summary Create Session
* @param {CreateSessionRequest} createSessionRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public createSession(createSessionRequest: CreateSessionRequest, options?: RawAxiosRequestConfig) {
return SessionsApiFp(this.configuration).createSession(createSessionRequest, options).then((request) => request(this.axios, this.basePath));
}
/**
* Returns sessions ordered by recency (newest first).
* @summary List Sessions
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public listSessions(pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig) {
return SessionsApiFp(this.configuration).listSessions(pageLimit, pageOffset, options).then((request) => request(this.axios, this.basePath));
}
/**
* Returns the full session detail including all conversation turns.
* @summary Retrieve Session
* @param {string} id Unique session identifier.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public retrieveSession(id: string, options?: RawAxiosRequestConfig) {
return SessionsApiFp(this.configuration).retrieveSession(id, options).then((request) => request(this.axios, this.basePath));
}
/**
* Append a user message to an existing session. The server will process it and produce assistant and tool turns asynchronously via the event stream.
* @summary Send Session Message
* @param {string} id Unique session identifier.
* @param {SendMessageRequest} sendMessageRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public sendSessionMessage(id: string, sendMessageRequest: SendMessageRequest, options?: RawAxiosRequestConfig) {
return SessionsApiFp(this.configuration).sendSessionMessage(id, sendMessageRequest, options).then((request) => request(this.axios, this.basePath));
}
/**
* Opens a server-sent event (SSE) stream for real-time session updates. Each SSE frame includes a sequential numeric `id:` field that supports resumption via the `Last-Event-ID` request header. The stream emits the following SSE event types: - `event: content_delta` data: `{\"delta\": \"...\"}` (incremental text chunk) - `event: assistant_turn` data: `AssistantTurn` JSON object - `event: tool_turn` data: `ToolTurn` JSON object - `event: done` data: `{}` (stream complete) - `event: error` data: `{\"message\": \"...\"}` (error occurred) Each SSE frame has an `id:` line (sequential integer), an `event:` line (the event type), and a `data:` line (the JSON payload).
* @summary Stream Session Events
* @param {string} id Unique session identifier.
* @param {string} [lastEventID] SSE reconnection header. When provided, the server resumes the stream after the event with this ID. IDs are 0-based sequential integers assigned to each emitted SSE frame.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public streamSessionEvents(id: string, lastEventID?: string, options?: RawAxiosRequestConfig) {
return SessionsApiFp(this.configuration).streamSessionEvents(id, lastEventID, options).then((request) => request(this.axios, this.basePath));
}
}

View file

@ -1,647 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import type { Configuration } from '../configuration';
import type { AxiosPromise, AxiosInstance, RawAxiosRequestConfig } from 'axios';
import globalAxios from 'axios';
// Some imports not used depending on template conditions
// @ts-ignore
import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction, replaceWithSerializableTypeIfNeeded } from '../common';
// @ts-ignore
import { BASE_PATH, COLLECTION_FORMATS, type RequestArgs, BaseAPI, RequiredError, operationServerMap } from '../base';
// @ts-ignore
import type { CreateSignoffRequest } from '../models';
// @ts-ignore
import type { ErrorResponse } from '../models';
// @ts-ignore
import type { PaginatedSignoffList } from '../models';
// @ts-ignore
import type { PaginatedVerificationControlList } from '../models';
// @ts-ignore
import type { PaginatedVerificationCriterionList } from '../models';
// @ts-ignore
import type { Signoff } from '../models';
// @ts-ignore
import type { VerificationCriterionDetail } from '../models';
// @ts-ignore
import type { VerificationDetailResponse } from '../models';
/**
* VerificationApi - axios parameter creator
*/
export const VerificationApiAxiosParamCreator = function (configuration?: Configuration) {
return {
/**
* Creates a new signoff for a (control, repository, commit SHA) tuple. Multiple signoffs are allowed per tuple; the latest one wins for display purposes.
* @summary Create Signoff
* @param {CreateSignoffRequest} createSignoffRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
createSignoff: async (createSignoffRequest: CreateSignoffRequest, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'createSignoffRequest' is not null or undefined
assertParamExists('createSignoff', 'createSignoffRequest', createSignoffRequest)
const localVarPath = `/api/v1/verification/signoffs`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication mTLS required
await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration)
// authentication BearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
localVarHeaderParameter['Content-Type'] = 'application/json';
localVarHeaderParameter['Accept'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
localVarRequestOptions.data = serializeDataIfNeeded(createSignoffRequest, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Returns a paginated list of signoffs, optionally filtered by control, repository, and/or commit SHA.
* @summary List Signoffs
* @param {string} [control] Filter signoffs by control slug.
* @param {string} [repository] Filter signoffs by repository name.
* @param {string} [commitSha] Filter signoffs by commit SHA.
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
listSignoffs: async (control?: string, repository?: string, commitSha?: string, pageLimit?: number, pageOffset?: number, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/api/v1/verification/signoffs`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication mTLS required
await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration)
// authentication BearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
if (control !== undefined) {
localVarQueryParameter['control'] = control;
}
if (repository !== undefined) {
localVarQueryParameter['repository'] = repository;
}
if (commitSha !== undefined) {
localVarQueryParameter['commit_sha'] = commitSha;
}
if (pageLimit !== undefined) {
localVarQueryParameter['page[limit]'] = pageLimit;
}
if (pageOffset !== undefined) {
localVarQueryParameter['page[offset]'] = pageOffset;
}
localVarHeaderParameter['Accept'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Returns a flat paginated list of all verification controls across all criteria.
* @summary List Verification Controls
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
listVerificationControls: async (pageLimit?: number, pageOffset?: number, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/api/v1/verification/controls`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication mTLS required
await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration)
// authentication BearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
if (pageLimit !== undefined) {
localVarQueryParameter['page[limit]'] = pageLimit;
}
if (pageOffset !== undefined) {
localVarQueryParameter['page[offset]'] = pageOffset;
}
localVarHeaderParameter['Accept'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Returns paginated verification criteria with their controls and performance metrics. Each criterion contains controls; retrieve a specific control via `/api/v1/verification/controls/{id}`.
* @summary List Verification Criteria
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
listVerificationCriteria: async (pageLimit?: number, pageOffset?: number, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/api/v1/verification/criteria`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication mTLS required
await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration)
// authentication BearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
if (pageLimit !== undefined) {
localVarQueryParameter['page[limit]'] = pageLimit;
}
if (pageOffset !== undefined) {
localVarQueryParameter['page[offset]'] = pageOffset;
}
localVarHeaderParameter['Accept'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Returns a specific signoff by ID.
* @summary Retrieve Signoff
* @param {string} id Unique identifier of a signoff (ULID).
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
retrieveSignoff: async (id: string, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'id' is not null or undefined
assertParamExists('retrieveSignoff', 'id', id)
const localVarPath = `/api/v1/verification/signoffs/{id}`
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication mTLS required
await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration)
// authentication BearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
localVarHeaderParameter['Accept'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Returns detailed information about a specific verification control, including performance data, recent results, and sibling controls in the same criterion.
* @summary Retrieve Verification Control
* @param {string} id URL-safe slug identifying a verification control.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
retrieveVerificationControl: async (id: string, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'id' is not null or undefined
assertParamExists('retrieveVerificationControl', 'id', id)
const localVarPath = `/api/v1/verification/controls/{id}`
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication mTLS required
await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration)
// authentication BearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
localVarHeaderParameter['Accept'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Returns a specific verification criterion with its controls and performance metrics.
* @summary Retrieve Verification Criterion
* @param {string} id URL-safe slug identifying a verification criterion.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
retrieveVerificationCriterion: async (id: string, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'id' is not null or undefined
assertParamExists('retrieveVerificationCriterion', 'id', id)
const localVarPath = `/api/v1/verification/criteria/{id}`
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication mTLS required
await setApiKeyToObject(localVarHeaderParameter, "X-mTLS-Client-CN", configuration)
// authentication BearerAuth required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
localVarHeaderParameter['Accept'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
}
};
/**
* VerificationApi - functional programming interface
*/
export const VerificationApiFp = function(configuration?: Configuration) {
const localVarAxiosParamCreator = VerificationApiAxiosParamCreator(configuration)
return {
/**
* Creates a new signoff for a (control, repository, commit SHA) tuple. Multiple signoffs are allowed per tuple; the latest one wins for display purposes.
* @summary Create Signoff
* @param {CreateSignoffRequest} createSignoffRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async createSignoff(createSignoffRequest: CreateSignoffRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Signoff>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.createSignoff(createSignoffRequest, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['VerificationApi.createSignoff']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
* Returns a paginated list of signoffs, optionally filtered by control, repository, and/or commit SHA.
* @summary List Signoffs
* @param {string} [control] Filter signoffs by control slug.
* @param {string} [repository] Filter signoffs by repository name.
* @param {string} [commitSha] Filter signoffs by commit SHA.
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async listSignoffs(control?: string, repository?: string, commitSha?: string, pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<PaginatedSignoffList>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.listSignoffs(control, repository, commitSha, pageLimit, pageOffset, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['VerificationApi.listSignoffs']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
* Returns a flat paginated list of all verification controls across all criteria.
* @summary List Verification Controls
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async listVerificationControls(pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<PaginatedVerificationControlList>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.listVerificationControls(pageLimit, pageOffset, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['VerificationApi.listVerificationControls']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
* Returns paginated verification criteria with their controls and performance metrics. Each criterion contains controls; retrieve a specific control via `/api/v1/verification/controls/{id}`.
* @summary List Verification Criteria
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async listVerificationCriteria(pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<PaginatedVerificationCriterionList>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.listVerificationCriteria(pageLimit, pageOffset, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['VerificationApi.listVerificationCriteria']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
* Returns a specific signoff by ID.
* @summary Retrieve Signoff
* @param {string} id Unique identifier of a signoff (ULID).
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async retrieveSignoff(id: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Signoff>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.retrieveSignoff(id, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['VerificationApi.retrieveSignoff']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
* Returns detailed information about a specific verification control, including performance data, recent results, and sibling controls in the same criterion.
* @summary Retrieve Verification Control
* @param {string} id URL-safe slug identifying a verification control.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async retrieveVerificationControl(id: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<VerificationDetailResponse>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.retrieveVerificationControl(id, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['VerificationApi.retrieveVerificationControl']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
* Returns a specific verification criterion with its controls and performance metrics.
* @summary Retrieve Verification Criterion
* @param {string} id URL-safe slug identifying a verification criterion.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async retrieveVerificationCriterion(id: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<VerificationCriterionDetail>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.retrieveVerificationCriterion(id, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['VerificationApi.retrieveVerificationCriterion']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
}
};
/**
* VerificationApi - factory interface
*/
export const VerificationApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
const localVarFp = VerificationApiFp(configuration)
return {
/**
* Creates a new signoff for a (control, repository, commit SHA) tuple. Multiple signoffs are allowed per tuple; the latest one wins for display purposes.
* @summary Create Signoff
* @param {CreateSignoffRequest} createSignoffRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
createSignoff(createSignoffRequest: CreateSignoffRequest, options?: RawAxiosRequestConfig): AxiosPromise<Signoff> {
return localVarFp.createSignoff(createSignoffRequest, options).then((request) => request(axios, basePath));
},
/**
* Returns a paginated list of signoffs, optionally filtered by control, repository, and/or commit SHA.
* @summary List Signoffs
* @param {string} [control] Filter signoffs by control slug.
* @param {string} [repository] Filter signoffs by repository name.
* @param {string} [commitSha] Filter signoffs by commit SHA.
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
listSignoffs(control?: string, repository?: string, commitSha?: string, pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig): AxiosPromise<PaginatedSignoffList> {
return localVarFp.listSignoffs(control, repository, commitSha, pageLimit, pageOffset, options).then((request) => request(axios, basePath));
},
/**
* Returns a flat paginated list of all verification controls across all criteria.
* @summary List Verification Controls
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
listVerificationControls(pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig): AxiosPromise<PaginatedVerificationControlList> {
return localVarFp.listVerificationControls(pageLimit, pageOffset, options).then((request) => request(axios, basePath));
},
/**
* Returns paginated verification criteria with their controls and performance metrics. Each criterion contains controls; retrieve a specific control via `/api/v1/verification/controls/{id}`.
* @summary List Verification Criteria
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
listVerificationCriteria(pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig): AxiosPromise<PaginatedVerificationCriterionList> {
return localVarFp.listVerificationCriteria(pageLimit, pageOffset, options).then((request) => request(axios, basePath));
},
/**
* Returns a specific signoff by ID.
* @summary Retrieve Signoff
* @param {string} id Unique identifier of a signoff (ULID).
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
retrieveSignoff(id: string, options?: RawAxiosRequestConfig): AxiosPromise<Signoff> {
return localVarFp.retrieveSignoff(id, options).then((request) => request(axios, basePath));
},
/**
* Returns detailed information about a specific verification control, including performance data, recent results, and sibling controls in the same criterion.
* @summary Retrieve Verification Control
* @param {string} id URL-safe slug identifying a verification control.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
retrieveVerificationControl(id: string, options?: RawAxiosRequestConfig): AxiosPromise<VerificationDetailResponse> {
return localVarFp.retrieveVerificationControl(id, options).then((request) => request(axios, basePath));
},
/**
* Returns a specific verification criterion with its controls and performance metrics.
* @summary Retrieve Verification Criterion
* @param {string} id URL-safe slug identifying a verification criterion.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
retrieveVerificationCriterion(id: string, options?: RawAxiosRequestConfig): AxiosPromise<VerificationCriterionDetail> {
return localVarFp.retrieveVerificationCriterion(id, options).then((request) => request(axios, basePath));
},
};
};
/**
* VerificationApi - object-oriented interface
*/
export class VerificationApi extends BaseAPI {
/**
* Creates a new signoff for a (control, repository, commit SHA) tuple. Multiple signoffs are allowed per tuple; the latest one wins for display purposes.
* @summary Create Signoff
* @param {CreateSignoffRequest} createSignoffRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public createSignoff(createSignoffRequest: CreateSignoffRequest, options?: RawAxiosRequestConfig) {
return VerificationApiFp(this.configuration).createSignoff(createSignoffRequest, options).then((request) => request(this.axios, this.basePath));
}
/**
* Returns a paginated list of signoffs, optionally filtered by control, repository, and/or commit SHA.
* @summary List Signoffs
* @param {string} [control] Filter signoffs by control slug.
* @param {string} [repository] Filter signoffs by repository name.
* @param {string} [commitSha] Filter signoffs by commit SHA.
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public listSignoffs(control?: string, repository?: string, commitSha?: string, pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig) {
return VerificationApiFp(this.configuration).listSignoffs(control, repository, commitSha, pageLimit, pageOffset, options).then((request) => request(this.axios, this.basePath));
}
/**
* Returns a flat paginated list of all verification controls across all criteria.
* @summary List Verification Controls
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public listVerificationControls(pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig) {
return VerificationApiFp(this.configuration).listVerificationControls(pageLimit, pageOffset, options).then((request) => request(this.axios, this.basePath));
}
/**
* Returns paginated verification criteria with their controls and performance metrics. Each criterion contains controls; retrieve a specific control via `/api/v1/verification/controls/{id}`.
* @summary List Verification Criteria
* @param {number} [pageLimit] Maximum number of items to return per page.
* @param {number} [pageOffset] Number of items to skip before returning results.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public listVerificationCriteria(pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig) {
return VerificationApiFp(this.configuration).listVerificationCriteria(pageLimit, pageOffset, options).then((request) => request(this.axios, this.basePath));
}
/**
* Returns a specific signoff by ID.
* @summary Retrieve Signoff
* @param {string} id Unique identifier of a signoff (ULID).
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public retrieveSignoff(id: string, options?: RawAxiosRequestConfig) {
return VerificationApiFp(this.configuration).retrieveSignoff(id, options).then((request) => request(this.axios, this.basePath));
}
/**
* Returns detailed information about a specific verification control, including performance data, recent results, and sibling controls in the same criterion.
* @summary Retrieve Verification Control
* @param {string} id URL-safe slug identifying a verification control.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public retrieveVerificationControl(id: string, options?: RawAxiosRequestConfig) {
return VerificationApiFp(this.configuration).retrieveVerificationControl(id, options).then((request) => request(this.axios, this.basePath));
}
/**
* Returns a specific verification criterion with its controls and performance metrics.
* @summary Retrieve Verification Criterion
* @param {string} id URL-safe slug identifying a verification criterion.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
public retrieveVerificationCriterion(id: string, options?: RawAxiosRequestConfig) {
return VerificationApiFp(this.configuration).retrieveVerificationCriterion(id, options).then((request) => request(this.axios, this.basePath));
}
}

View file

@ -1,38 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
* An assistant response turn.
*/
export interface AssistantTurn {
'kind': AssistantTurnKindEnum;
/**
* Text content of the assistant response.
*/
'content': string;
/**
* Timestamp when the turn was created.
*/
'created_at': string;
}
export const AssistantTurnKindEnum = {
ASSISTANT: 'assistant'
} as const;
export type AssistantTurnKindEnum = typeof AssistantTurnKindEnum[keyof typeof AssistantTurnKindEnum];

View file

@ -1,38 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
* Detailed information about a verification control including checks and examples.
*/
export interface ControlDetail {
/**
* Detailed prose description of the control\'s purpose and rationale.
*/
'rationale': string;
/**
* Specific checks performed by this control.
*/
'checks': Array<string>;
/**
* Example scenario where the control passes.
*/
'pass_example': string;
/**
* Example scenario where the control fails.
*/
'fail_example': string;
}

View file

@ -1,44 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { CriterionReference } from './criterion-reference';
// May contain unused imports in some cases
// @ts-ignore
import type { VerificationType } from './verification-type';
/**
* Core metadata about a verification control.
*/
export interface ControlInfo {
/**
* Human-readable control name.
*/
'name': string;
/**
* URL-safe slug.
*/
'slug': string;
/**
* Short description of what the control verifies.
*/
'description': string;
'type'?: VerificationType;
'criterion': CriterionReference;
}

View file

@ -1,43 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { VerificationMode } from './verification-mode';
// May contain unused imports in some cases
// @ts-ignore
import type { VerificationResult } from './verification-result';
/**
* Performance metrics for a verification control.
*/
export interface ControlPerformance {
'mode': VerificationMode;
/**
* F1 score of the control\'s AI evaluator.
*/
'f1'?: number;
/**
* Pass@1 rate.
*/
'pass_at_1'?: number;
/**
* Recent evaluation results (newest first).
*/
'evaluations': Array<VerificationResult>;
}

View file

@ -1,26 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
* Reference to a verification control by slug.
*/
export interface ControlReference {
/**
* Control slug.
*/
'slug': string;
}

View file

@ -1,34 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
* Request body for starting a new session.
*/
export interface CreateSessionRequest {
/**
* The initial user message to start the session.
*/
'content': string;
/**
* LLM model to use. If omitted, the server default is used.
*/
'model'?: string;
/**
* System prompt for the session.
*/
'system'?: string;
}

View file

@ -1,42 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { ModelReference } from './model-reference';
/**
* Response returned after successfully creating a session.
*/
export interface CreateSessionResponse {
/**
* Unique identifier for the newly created session.
*/
'id': string;
/**
* Server-generated title for the session.
*/
'title': string;
'model': ModelReference;
/**
* Timestamp when the session was created.
*/
'created_at': string;
/**
* Timestamp when the session was last updated (equal to created_at at creation time).
*/
'updated_at': string;
}

View file

@ -1,52 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { SignoffStatus } from './signoff-status';
/**
* Request body to create a new signoff.
*/
export interface CreateSignoffRequest {
/**
* Control slug or ID.
*/
'control': string;
/**
* Repository name.
*/
'repository': string;
/**
* Git commit SHA.
*/
'commit_sha': string;
'status': SignoffStatus;
/**
* Optional URL with more details.
*/
'url'?: string;
/**
* Optional human-readable description.
*/
'description'?: string;
/**
* Freeform string identifying the logical origin.
*/
'source'?: string;
}

View file

@ -1,26 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
* Reference to a verification criterion by name.
*/
export interface CriterionReference {
/**
* Criterion name.
*/
'name': string;
}

View file

@ -1,32 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
* Type of friction encountered during a run.
*/
export const FrictionKind = {
RETRY: 'retry',
TIMEOUT: 'timeout',
WRONG_APPROACH: 'wrong_approach',
TOOL_FAILURE: 'tool_failure',
AMBIGUITY: 'ambiguity'
} as const;
export type FrictionKind = typeof FrictionKind[keyof typeof FrictionKind];

View file

@ -1,36 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { FrictionKind } from './friction-kind';
/**
* A point where the run encountered difficulty.
*/
export interface FrictionPoint {
'kind': FrictionKind;
/**
* Description of the friction encountered.
*/
'description': string;
/**
* Stage where the friction occurred, if applicable.
*/
'stage_id'?: string;
}

View file

@ -8,7 +8,6 @@ export * from './artifact-entry';
export * from './artifact-list-response';
export * from './artifacts-settings';
export * from './assistant-stage-turn';
export * from './assistant-turn';
export * from './auth-settings';
export * from './board-column';
export * from './check-run';
@ -21,15 +20,7 @@ export * from './completion-response';
export * from './completion-tool-choice';
export * from './completion-tool-definition';
export * from './completion-usage';
export * from './control-detail';
export * from './control-info';
export * from './control-performance';
export * from './control-reference';
export * from './create-completion-request';
export * from './create-session-request';
export * from './create-session-response';
export * from './create-signoff-request';
export * from './criterion-reference';
export * from './daytona-settings';
export * from './daytona-settings-network';
export * from './daytona-settings-network-one-of';
@ -52,8 +43,6 @@ export * from './execute-query-response-rows-inner-inner';
export * from './features';
export * from './file-checkpoint';
export * from './file-diff';
export * from './friction-kind';
export * from './friction-point';
export * from './git-author-settings';
export * from './git-hub-settings';
export * from './git-settings';
@ -62,8 +51,6 @@ export * from './history-entry';
export * from './hook-definition';
export * from './internal-run-status';
export * from './internal-stage-status';
export * from './learning';
export * from './learning-category';
export * from './llm-settings';
export * from './local-sandbox-settings';
export * from './log-settings';
@ -86,22 +73,15 @@ export * from './model-test-mode';
export * from './model-test-result';
export * from './node-state';
export * from './node-status-record';
export * from './open-item';
export * from './open-item-kind';
export * from './paginated-api-question-list';
export * from './paginated-event-list';
export * from './paginated-history-entry-list';
export * from './paginated-model-list';
export * from './paginated-retro-list';
export * from './paginated-run-file-list';
export * from './paginated-run-list';
export * from './paginated-run-stage-list';
export * from './paginated-saved-query-list';
export * from './paginated-session-list';
export * from './paginated-signoff-list';
export * from './paginated-stage-turn-list';
export * from './paginated-verification-control-list';
export * from './paginated-verification-criterion-list';
export * from './paginated-workflow-list';
export * from './pagination-meta';
export * from './preflight-check-detail';
@ -117,16 +97,12 @@ export * from './prune-runs-request';
export * from './prune-runs-response';
export * from './pull-request-settings';
export * from './question-type';
export * from './recent-control-result';
export * from './render-workflow-graph-direction';
export * from './render-workflow-graph-format';
export * from './render-workflow-graph-request';
export * from './repo-check-response';
export * from './repo-check-response-permissions';
export * from './repository-reference';
export * from './retro-detail';
export * from './retro-list-item';
export * from './retro-stats';
export * from './root-response';
export * from './root-response-urls';
export * from './run-artifact-entry';
@ -157,25 +133,15 @@ export * from './save-query-request';
export * from './saved-query';
export * from './secret-list-response';
export * from './secret-metadata';
export * from './send-message-request';
export * from './send-message-response';
export * from './server-settings';
export * from './server-settings-exec';
export * from './server-settings-fabro';
export * from './server-settings-server';
export * from './server-settings-server-tls';
export * from './session-detail';
export * from './session-list-item';
export * from './session-turn';
export * from './set-secret-request';
export * from './setup-settings';
export * from './sibling-control';
export * from './signoff';
export * from './signoff-status';
export * from './smoothness-rating';
export * from './ssh-access-request';
export * from './ssh-access-response';
export * from './stage-retro';
export * from './stage-status';
export * from './stage-turn';
export * from './start-run-request';
@ -189,22 +155,12 @@ export * from './system-stage-turn';
export * from './tls-settings';
export * from './token-usage';
export * from './tool-stage-turn';
export * from './tool-turn';
export * from './tool-use';
export * from './usage-by-model';
export * from './usage-stage';
export * from './usage-stage-ref';
export * from './usage-totals';
export * from './user-response';
export * from './user-turn';
export * from './verification-control';
export * from './verification-control-list-item';
export * from './verification-criterion';
export * from './verification-criterion-detail';
export * from './verification-detail-response';
export * from './verification-mode';
export * from './verification-result';
export * from './verification-type';
export * from './web-settings';
export * from './webhook-settings';
export * from './workflow-detail';

View file

@ -1,31 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
* Category of a learning insight.
*/
export const LearningCategory = {
REPO: 'repo',
CODE: 'code',
WORKFLOW: 'workflow',
TOOL: 'tool'
} as const;
export type LearningCategory = typeof LearningCategory[keyof typeof LearningCategory];

View file

@ -1,32 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { LearningCategory } from './learning-category';
/**
* An insight discovered during the run.
*/
export interface Learning {
'category': LearningCategory;
/**
* Description of the learning.
*/
'text': string;
}

View file

@ -1,31 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
* Type of open item identified during a run.
*/
export const OpenItemKind = {
TECH_DEBT: 'tech_debt',
FOLLOW_UP: 'follow_up',
INVESTIGATION: 'investigation',
TEST_GAP: 'test_gap'
} as const;
export type OpenItemKind = typeof OpenItemKind[keyof typeof OpenItemKind];

View file

@ -1,32 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { OpenItemKind } from './open-item-kind';
/**
* A follow-up item identified during the run.
*/
export interface OpenItem {
'kind': OpenItemKind;
/**
* Description of the open item.
*/
'description': string;
}

View file

@ -1,30 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { PaginationMeta } from './pagination-meta';
// May contain unused imports in some cases
// @ts-ignore
import type { RetroListItem } from './retro-list-item';
/**
* Paginated list of run retrospectives.
*/
export interface PaginatedRetroList {
'data': Array<RetroListItem>;
'meta': PaginationMeta;
}

View file

@ -1,30 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { PaginationMeta } from './pagination-meta';
// May contain unused imports in some cases
// @ts-ignore
import type { RunVerification } from './run-verification';
/**
* Paginated list of run verification categories.
*/
export interface PaginatedRunVerificationList {
'data': Array<RunVerification>;
'meta': PaginationMeta;
}

View file

@ -1,30 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { PaginationMeta } from './pagination-meta';
// May contain unused imports in some cases
// @ts-ignore
import type { SessionListItem } from './session-list-item';
/**
* Paginated list of sessions.
*/
export interface PaginatedSessionList {
'data': Array<SessionListItem>;
'meta': PaginationMeta;
}

View file

@ -1,30 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { PaginationMeta } from './pagination-meta';
// May contain unused imports in some cases
// @ts-ignore
import type { Signoff } from './signoff';
/**
* Paginated list of signoffs.
*/
export interface PaginatedSignoffList {
'data': Array<Signoff>;
'meta': PaginationMeta;
}

View file

@ -1,30 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { PaginationMeta } from './pagination-meta';
// May contain unused imports in some cases
// @ts-ignore
import type { VerificationControlListItem } from './verification-control-list-item';
/**
* Paginated list of verification controls.
*/
export interface PaginatedVerificationControlList {
'data': Array<VerificationControlListItem>;
'meta': PaginationMeta;
}

View file

@ -1,30 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { PaginationMeta } from './pagination-meta';
// May contain unused imports in some cases
// @ts-ignore
import type { VerificationCriterion } from './verification-criterion';
/**
* Paginated list of verification criteria.
*/
export interface PaginatedVerificationCriterionList {
'data': Array<VerificationCriterion>;
'meta': PaginationMeta;
}

View file

@ -1,40 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { RunReference } from './run-reference';
// May contain unused imports in some cases
// @ts-ignore
import type { VerificationResult } from './verification-result';
// May contain unused imports in some cases
// @ts-ignore
import type { WorkflowReference } from './workflow-reference';
/**
* Result of a recent verification control evaluation for a specific run.
*/
export interface RecentControlResult {
'run': RunReference;
'workflow': WorkflowReference;
'result': VerificationResult;
/**
* ISO 8601 timestamp of the evaluation.
*/
'timestamp': string;
}

View file

@ -1,87 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { FrictionPoint } from './friction-point';
// May contain unused imports in some cases
// @ts-ignore
import type { Learning } from './learning';
// May contain unused imports in some cases
// @ts-ignore
import type { OpenItem } from './open-item';
// May contain unused imports in some cases
// @ts-ignore
import type { RetroStats } from './retro-stats';
// May contain unused imports in some cases
// @ts-ignore
import type { SmoothnessRating } from './smoothness-rating';
// May contain unused imports in some cases
// @ts-ignore
import type { StageRetro } from './stage-retro';
/**
* Full retrospective analysis for a completed run.
*/
export interface RetroDetail {
/**
* Unique run identifier.
*/
'run_id': string;
/**
* Workflow slug that produced this run.
*/
'workflow_name': string;
/**
* The goal that was set for the run.
*/
'goal': string;
/**
* ISO 8601 timestamp when the retro was generated.
*/
'timestamp': string;
/**
* Absent when the retro has been generated from quantitative data but not yet enriched by the retro agent.
*/
'smoothness'?: SmoothnessRating;
/**
* Per-stage retrospective data.
*/
'stages': Array<StageRetro>;
'stats': RetroStats;
/**
* What the agent intended to accomplish.
*/
'intent'?: string;
/**
* What actually happened during the run.
*/
'outcome'?: string;
/**
* Insights discovered during the run.
*/
'learnings'?: Array<Learning>;
/**
* Points where the run encountered difficulty.
*/
'friction_points'?: Array<FrictionPoint>;
/**
* Follow-up items identified during the run.
*/
'open_items'?: Array<OpenItem>;
}

View file

@ -1,51 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { RetroStats } from './retro-stats';
// May contain unused imports in some cases
// @ts-ignore
import type { RunReference } from './run-reference';
// May contain unused imports in some cases
// @ts-ignore
import type { SmoothnessRating } from './smoothness-rating';
// May contain unused imports in some cases
// @ts-ignore
import type { WorkflowReference } from './workflow-reference';
/**
* Summary of a run retrospective shown in list views.
*/
export interface RetroListItem {
'run': RunReference;
'workflow': WorkflowReference;
/**
* Timestamp when the retro was generated.
*/
'timestamp': string;
/**
* Absent when the retro has been generated from quantitative data but not yet enriched by the retro agent.
*/
'smoothness'?: SmoothnessRating;
'stats': RetroStats;
/**
* Number of friction points identified in the retro.
*/
'friction_point_count': number;
}

View file

@ -1,46 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
* Summary statistics for a run retrospective.
*/
export interface RetroStats {
/**
* Total run duration in milliseconds.
*/
'total_duration_ms': number;
/**
* Total cost in USD. Absent when cost data is unavailable from the model provider.
*/
'total_cost'?: number;
/**
* Total number of retries across all stages.
*/
'total_retries': number;
/**
* List of files modified during the run.
*/
'files_touched': Array<string>;
/**
* Number of stages that completed successfully.
*/
'stages_completed': number;
/**
* Number of stages that failed.
*/
'stages_failed': number;
}

View file

@ -1,44 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { VerificationResult } from './verification-result';
// May contain unused imports in some cases
// @ts-ignore
import type { VerificationType } from './verification-type';
/**
* A verification control result within a run.
*/
export interface RunVerificationControl {
/**
* Human-readable control name.
*/
'name': string;
/**
* URL-safe slug for linking to verification detail page.
*/
'slug': string;
/**
* Short description of what the control verifies.
*/
'description': string;
'type': VerificationType;
'status': VerificationResult;
}

View file

@ -1,43 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { RunVerificationControl } from './run-verification-control';
// May contain unused imports in some cases
// @ts-ignore
import type { VerificationResult } from './verification-result';
/**
* Verification results for a category within a run.
*/
export interface RunVerification {
/**
* Category name.
*/
'name': string;
/**
* The guiding question for this verification category.
*/
'question': string;
'status': VerificationResult;
/**
* Individual control results within this category.
*/
'controls': Array<RunVerificationControl>;
}

View file

@ -1,26 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
* Request body for sending a follow-up message in an existing session.
*/
export interface SendMessageRequest {
/**
* The user message text.
*/
'content': string;
}

View file

@ -1,26 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
* Acknowledgement that the message was accepted for asynchronous processing.
*/
export interface SendMessageResponse {
/**
* Whether the message was accepted for processing.
*/
'accepted': boolean;
}

View file

@ -1,49 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { ModelReference } from './model-reference';
// May contain unused imports in some cases
// @ts-ignore
import type { SessionTurn } from './session-turn';
/**
* Full session record including metadata and the complete conversation history.
*/
export interface SessionDetail {
/**
* Unique session identifier.
*/
'id': string;
/**
* Short title summarizing the session topic.
*/
'title': string;
'model': ModelReference;
/**
* Timestamp when the session was created.
*/
'created_at': string;
/**
* Timestamp when the session was last updated (e.g. new turn added).
*/
'updated_at': string;
/**
* Ordered list of conversation turns.
*/
'turns': Array<SessionTurn>;
}

View file

@ -1,46 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { ModelReference } from './model-reference';
/**
* Summary of a session shown in list views.
*/
export interface SessionListItem {
/**
* Unique session identifier.
*/
'id': string;
/**
* Short title summarizing the session topic.
*/
'title': string;
'model': ModelReference;
/**
* Truncated snippet of the most recent turn\'s content.
*/
'last_message_preview': string;
/**
* Timestamp when the session was created.
*/
'created_at': string;
/**
* Timestamp when the session was last updated (e.g. new turn added).
*/
'updated_at': string;
}

View file

@ -1,35 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { AssistantTurn } from './assistant-turn';
// May contain unused imports in some cases
// @ts-ignore
import type { ToolTurn } from './tool-turn';
// May contain unused imports in some cases
// @ts-ignore
import type { ToolUse } from './tool-use';
// May contain unused imports in some cases
// @ts-ignore
import type { UserTurn } from './user-turn';
/**
* @type SessionTurn
* A single turn in a session conversation a user message, assistant response, or tool invocation block.
*/
export type SessionTurn = { kind: 'assistant' } & AssistantTurn | { kind: 'tool' } & ToolTurn | { kind: 'user' } & UserTurn;

View file

@ -1,40 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { VerificationMode } from './verification-mode';
// May contain unused imports in some cases
// @ts-ignore
import type { VerificationType } from './verification-type';
/**
* Summary of a sibling verification control in the same category.
*/
export interface SiblingControl {
/**
* Human-readable control name.
*/
'name': string;
/**
* URL-safe slug.
*/
'slug': string;
'type'?: VerificationType;
'mode'?: VerificationMode;
}

View file

@ -1,30 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
* Status of a signoff.
*/
export const SignoffStatus = {
PASS: 'pass',
FAIL: 'fail',
PENDING: 'pending'
} as const;
export type SignoffStatus = typeof SignoffStatus[keyof typeof SignoffStatus];

View file

@ -1,60 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { ControlReference } from './control-reference';
// May contain unused imports in some cases
// @ts-ignore
import type { RepositoryReference } from './repository-reference';
// May contain unused imports in some cases
// @ts-ignore
import type { SignoffStatus } from './signoff-status';
/**
* A stamp of approval for a (control, repository, commit SHA) tuple.
*/
export interface Signoff {
/**
* Unique identifier (ULID).
*/
'id': string;
'control': ControlReference;
'repository': RepositoryReference;
/**
* Git commit SHA this signoff applies to.
*/
'commit_sha': string;
'status': SignoffStatus;
/**
* Optional URL with more details about the signoff.
*/
'url'?: string;
/**
* Optional human-readable description.
*/
'description'?: string;
/**
* Freeform string identifying the logical origin of the signoff.
*/
'source'?: string;
/**
* When the signoff was created.
*/
'created_at': string;
}

View file

@ -1,32 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
* Qualitative assessment of how smoothly a run executed.
*/
export const SmoothnessRating = {
EFFORTLESS: 'effortless',
SMOOTH: 'smooth',
BUMPY: 'bumpy',
STRUGGLED: 'struggled',
FAILED: 'failed'
} as const;
export type SmoothnessRating = typeof SmoothnessRating[keyof typeof SmoothnessRating];

View file

@ -1,58 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
* Retrospective data for a single stage in the workflow.
*/
export interface StageRetro {
/**
* Identifier of the stage in the workflow graph.
*/
'stage_id': string;
/**
* Human-readable label for the stage.
*/
'stage_label': string;
/**
* Final status of the stage.
*/
'status': string;
/**
* Stage duration in milliseconds.
*/
'duration_ms': number;
/**
* Number of retries for this stage.
*/
'retries': number;
/**
* Cost in USD for this stage. Absent when cost data is unavailable.
*/
'cost'?: number;
/**
* Optional notes about this stage\'s execution.
*/
'notes'?: string;
/**
* Reason the stage failed, if applicable.
*/
'failure_reason'?: string;
/**
* Files modified during this stage.
*/
'files_touched': Array<string>;
}

View file

@ -1,41 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { ToolUse } from './tool-use';
/**
* A tool invocation turn.
*/
export interface ToolTurn {
'kind': ToolTurnKindEnum;
/**
* Tool invocations for this turn.
*/
'tools': Array<ToolUse>;
/**
* Timestamp when the turn was created.
*/
'created_at': string;
}
export const ToolTurnKindEnum = {
TOOL: 'tool'
} as const;
export type ToolTurnKindEnum = typeof ToolTurnKindEnum[keyof typeof ToolTurnKindEnum];

View file

@ -1,38 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
* A user message turn.
*/
export interface UserTurn {
'kind': UserTurnKindEnum;
/**
* Text content of the user message.
*/
'content': string;
/**
* Timestamp when the turn was created.
*/
'created_at': string;
}
export const UserTurnKindEnum = {
USER: 'user'
} as const;
export type UserTurnKindEnum = typeof UserTurnKindEnum[keyof typeof UserTurnKindEnum];

View file

@ -1,56 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { CriterionReference } from './criterion-reference';
// May contain unused imports in some cases
// @ts-ignore
import type { VerificationMode } from './verification-mode';
// May contain unused imports in some cases
// @ts-ignore
import type { VerificationType } from './verification-type';
/**
* A verification control in a flat list view with criterion reference.
*/
export interface VerificationControlListItem {
/**
* Human-readable control name.
*/
'name': string;
/**
* URL-safe slug for API lookups.
*/
'slug': string;
/**
* Short description of what the control verifies.
*/
'description': string;
'type': VerificationType;
'mode'?: VerificationMode;
/**
* F1 score of the control\'s AI evaluator.
*/
'f1'?: number;
/**
* Pass@1 rate.
*/
'pass_at_1'?: number;
'criterion': CriterionReference;
}

View file

@ -1,59 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { VerificationMode } from './verification-mode';
// May contain unused imports in some cases
// @ts-ignore
import type { VerificationResult } from './verification-result';
// May contain unused imports in some cases
// @ts-ignore
import type { VerificationType } from './verification-type';
/**
* A verification control within a category, with performance metrics.
*/
export interface VerificationControl {
/**
* Human-readable control name.
*/
'name': string;
/**
* URL-safe slug for API lookups.
*/
'slug': string;
/**
* Short description of what the control verifies.
*/
'description': string;
'type': VerificationType;
'mode'?: VerificationMode;
/**
* F1 score of the control\'s AI evaluator.
*/
'f1'?: number;
/**
* Pass@1 rate probability of passing on the first evaluation.
*/
'pass_at_1'?: number;
/**
* Recent evaluation results (newest first).
*/
'evaluations'?: Array<VerificationResult>;
}

View file

@ -1,37 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { VerificationControl } from './verification-control';
/**
* Detail view of a verification criterion with inline controls and performance metrics.
*/
export interface VerificationCriterionDetail {
/**
* Criterion name.
*/
'name': string;
/**
* Guiding question for the criterion.
*/
'question': string;
/**
* Verification controls in this criterion with performance metrics.
*/
'controls': Array<VerificationControl>;
}

View file

@ -1,37 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { VerificationControl } from './verification-control';
/**
* A group of related verification controls.
*/
export interface VerificationCriterion {
/**
* Criterion name.
*/
'name': string;
/**
* Guiding question for the criterion.
*/
'question': string;
/**
* Verification controls in this criterion.
*/
'controls': Array<VerificationControl>;
}

View file

@ -1,48 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { ControlDetail } from './control-detail';
// May contain unused imports in some cases
// @ts-ignore
import type { ControlInfo } from './control-info';
// May contain unused imports in some cases
// @ts-ignore
import type { ControlPerformance } from './control-performance';
// May contain unused imports in some cases
// @ts-ignore
import type { RecentControlResult } from './recent-control-result';
// May contain unused imports in some cases
// @ts-ignore
import type { SiblingControl } from './sibling-control';
/**
* Complete detail view of a verification control with performance, examples, and recent results.
*/
export interface VerificationDetailResponse {
'control': ControlInfo;
'performance': ControlPerformance;
'control_detail': ControlDetail;
/**
* Recent evaluation results across runs.
*/
'recent_results': Array<RecentControlResult>;
/**
* Other controls in the same category.
*/
'siblings': Array<SiblingControl>;
}

View file

@ -1,30 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
* Operational mode of a verification control.
*/
export const VerificationMode = {
ACTIVE: 'active',
EVALUATE: 'evaluate',
DISABLED: 'disabled'
} as const;
export type VerificationMode = typeof VerificationMode[keyof typeof VerificationMode];

View file

@ -1,31 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
* Outcome of a verification control evaluation. `skip`: evaluation was intentionally skipped (e.g., control is disabled). `na`: control does not apply to this run (e.g., Python lint on a Rust-only change).
*/
export const VerificationResult = {
PASS: 'pass',
FAIL: 'fail',
SKIP: 'skip',
NA: 'na'
} as const;
export type VerificationResult = typeof VerificationResult[keyof typeof VerificationResult];

View file

@ -1,31 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
* The evaluation method used by a verification control.
*/
export const VerificationType = {
AI: 'ai',
AUTOMATED: 'automated',
ANALYSIS: 'analysis',
AI_ANALYSIS: 'ai-analysis'
} as const;
export type VerificationType = typeof VerificationType[keyof typeof VerificationType];