diff --git a/frontend/src/app/router.tsx b/frontend/src/app/router.tsx index e3f9899..57d06ac 100644 --- a/frontend/src/app/router.tsx +++ b/frontend/src/app/router.tsx @@ -15,6 +15,13 @@ import { TicketCreatePage } from '@/features/tickets/TicketCreatePage' import { TicketDetailPage } from '@/features/tickets/TicketDetailPage' import { TicketListPage } from '@/features/tickets/TicketListPage' import { WorkbenchPage } from '@/features/workbench/WorkbenchPage' +import { ModelProviderPage } from '@/features/modelhub/ModelProviderPage' +import { SkillListPage } from '@/features/skills/SkillListPage' +import { SkillDetailPage } from '@/features/skills/SkillDetailPage' +import { AgentListPage } from '@/features/agents/AgentListPage' +import { AgentDetailPage } from '@/features/agents/AgentDetailPage' +import { AgentRunListPage } from '@/features/agents/AgentRunListPage' +import { AgentRunDetailPage } from '@/features/agents/AgentRunDetailPage' function getRoute() { return window.location.hash.replace(/^#/, '') || '/' @@ -57,6 +64,37 @@ export function Router() { return } + if (route === '/modelhub/providers') { + return + } + + if (route === '/skills') { + return + } + + const skillMatch = route.match(/^\/skills\/(\d+)$/) + if (skillMatch) { + return + } + + if (route === '/agents') { + return + } + + const agentMatch = route.match(/^\/agents\/(\d+)$/) + if (agentMatch) { + return + } + + if (route === '/agent-runs') { + return + } + + const runMatch = route.match(/^\/agent-runs\/([0-9a-f-]+)$/i) + if (runMatch) { + return + } + if (route === '/knowledge') { return } diff --git a/frontend/src/features/agents/AgentDetailPage.tsx b/frontend/src/features/agents/AgentDetailPage.tsx new file mode 100644 index 0000000..37ea9a2 --- /dev/null +++ b/frontend/src/features/agents/AgentDetailPage.tsx @@ -0,0 +1,103 @@ +import * as React from 'react' +import { ArrowDown, ArrowLeft, ArrowUp, CopyPlus, Play, Plus, Save, Send, Trash2 } from 'lucide-react' +import { toast } from 'sonner' + +import { AppEmptyState } from '@/components/app/AppEmptyState' +import { AppField } from '@/components/app/AppField' +import { AppPage } from '@/components/app/AppPage' +import { AppPageHeader } from '@/components/app/AppPageHeader' +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { FieldGroup } from '@/components/ui/field' +import { Input } from '@/components/ui/input' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { Textarea } from '@/components/ui/textarea' +import { JsonEditor, parseJsonObject } from '@/features/ai-platform/components/JsonEditor' +import { MobileEditGuard } from '@/features/ai-platform/components/MobileEditGuard' +import { SchemaInputEditor } from '@/features/ai-platform/components/SchemaInputEditor' +import { StatusBadge } from '@/features/ai-platform/status' +import { listDeployments } from '@/features/modelhub/api' +import type { ModelDeployment } from '@/features/modelhub/types' +import { getSkill, listSkills } from '@/features/skills/api' +import type { SkillVersion } from '@/features/skills/types' +import { formatDateTime } from '@/lib/format' + +import { ensureAgentDraft, getAgent, publishAgent, rollbackAgent, startAgentRun, updateAgent, updateAgentDraft } from './api' +import type { AgentDetail, AgentVersion } from './types' + +type EditableStep = { code: string; skill_version_id: string; mapping: string } +type SkillOption = SkillVersion & { definitionName: string } + +export function AgentDetailPage({ agentId }: { agentId: number }) { + const [detail, setDetail] = React.useState(null) + const [draft, setDraft] = React.useState(null) + const [deployments, setDeployments] = React.useState([]) + const [skills, setSkills] = React.useState([]) + const [meta, setMeta] = React.useState({ name: '', description: '' }) + const [modelId, setModelId] = React.useState('') + const [systemPrompt, setSystemPrompt] = React.useState('') + const [temperature, setTemperature] = React.useState('') + const [maxTokens, setMaxTokens] = React.useState('') + const [inputSchema, setInputSchema] = React.useState('{}') + const [exampleInput, setExampleInput] = React.useState('{}') + const [steps, setSteps] = React.useState([]) + const [testVersion, setTestVersion] = React.useState('draft') + const [testInput, setTestInput] = React.useState('{}') + const [error, setError] = React.useState('') + + const hydrateDraft = React.useCallback((version: AgentVersion | null) => { + setDraft(version) + if (!version) return + setModelId(String(version.model_deployment_id)); setSystemPrompt(version.system_prompt) + setTemperature(version.temperature_override === null ? '' : String(version.temperature_override)) + setMaxTokens(version.max_output_tokens_override === null ? '' : String(version.max_output_tokens_override)) + setInputSchema(JSON.stringify(version.input_schema, null, 2)); setExampleInput(JSON.stringify(version.example_input, null, 2)); setTestInput(JSON.stringify(version.example_input, null, 2)) + setSteps(version.steps.map((step) => ({ code: step.code, skill_version_id: String(step.skill_version_id), mapping: JSON.stringify(step.input_mapping, null, 2) }))) + }, []) + + const load = React.useCallback(async () => { + try { + const [item, nextDeployments, definitions] = await Promise.all([getAgent(agentId), listDeployments(), listSkills()]) + const skillDetails = await Promise.all(definitions.map((skill) => getSkill(skill.id))) + setDetail(item); setMeta({ name: item.name, description: item.description }); setDeployments(nextDeployments) + setSkills(skillDetails.flatMap((skill) => skill.versions.map((version) => ({ ...version, definitionName: skill.name })))) + hydrateDraft(item.versions.find((version) => version.status === 'draft') ?? null) + setTestVersion(item.draft_version ? 'draft' : String(item.active_version ?? '')) + setError('') + } catch (reason) { setError(reason instanceof Error ? reason.message : '加载 Agent 失败') } + }, [agentId, hydrateDraft]) + React.useEffect(() => { void load() }, [load]) + + function updateStep(index: number, patch: Partial) { setSteps((current) => current.map((step, i) => i === index ? { ...step, ...patch } : step)) } + function moveStep(index: number, direction: -1 | 1) { setSteps((current) => { const next = [...current]; const target = index + direction; if (target < 0 || target >= next.length) return current; [next[index], next[target]] = [next[target], next[index]]; return next }) } + + async function save() { + try { + await Promise.all([ + updateAgent(agentId, meta), + updateAgentDraft(agentId, { model_deployment_id: Number(modelId), system_prompt: systemPrompt, input_schema: parseJsonObject(inputSchema, 'Agent 输入 Schema'), example_input: parseJsonObject(exampleInput, '示例输入'), temperature_override: temperature === '' ? null : Number(temperature), max_output_tokens_override: maxTokens === '' ? null : Number(maxTokens), steps: steps.map((step) => ({ code: step.code, skill_version_id: Number(step.skill_version_id), input_mapping: parseJsonObject(step.mapping, `步骤 ${step.code} 输入映射`) })) }) + ]) + toast.success('Agent 草稿已保存'); await load() + } catch (reason) { toast.error(reason instanceof Error ? reason.message : '保存失败') } + } + async function createDraft() { try { await ensureAgentDraft(agentId); toast.success('已创建新草稿'); await load() } catch (reason) { toast.error(reason instanceof Error ? reason.message : '创建草稿失败') } } + async function publish() { try { await publishAgent(agentId); toast.success('Agent 版本已发布'); await load() } catch (reason) { toast.error(reason instanceof Error ? reason.message : '发布失败') } } + async function rollback(version: number) { try { await rollbackAgent(agentId, version); toast.success(`已回滚到 v${version}`); await load() } catch (reason) { toast.error(reason instanceof Error ? reason.message : '回滚失败') } } + async function run() { try { const result = await startAgentRun(agentId, { version: testVersion === 'draft' ? 'draft' : Number(testVersion), input: parseJsonObject(testInput, '测试输入') }); toast.success('Agent 运行已进入队列'); window.location.hash = `/agent-runs/${result.public_id}` } catch (reason) { toast.error(reason instanceof Error ? reason.message : '启动运行失败') } } + + if (error) return + if (!detail) return + return + 当前 v{detail.active_version} : 尚未发布} actions={<> { window.location.hash = '/agents' }}>返回{draft ? <> void save()}>保存草稿 void publish()}>发布 v{draft.version}> : void createDraft()}>创建新草稿}>} /> + 基础与模型步骤输入 Schema测试版本 + {draft ? setMeta({ ...meta, name: e.target.value })} /> setMeta({ ...meta, description: e.target.value })} />{deployments.map((item) => {item.display_name})} setTemperature(e.target.value)} /> setMaxTokens(e.target.value)} /> setSystemPrompt(e.target.value)} /> : } + {draft ? {steps.map((step, index) => {index + 1}. {step.code}输入映射后传递给 Skill moveStep(index, -1)}>上移 moveStep(index, 1)}>下移 setSteps((current) => current.filter((_, i) => i !== index))}>删除 updateStep(index, { code: e.target.value })} /> updateStep(index, { skill_version_id: value })}>{skills.map((item) => {item.definitionName} · v{item.version}({item.status}))} updateStep(index, { mapping: value })} rows={6} />)}= 10} onClick={() => setSteps((current) => [...current, { code: `step_${current.length + 1}`, skill_version_id: String(skills[0]?.id ?? ''), mapping: '{}' }])}>添加步骤 : } + {draft ? : } + 异步沙箱与发布版本测试草稿运行会标记为沙箱;正式运行只能选择已发布版本。启动后跳转到运行轨迹并自动轮询。 { setTestVersion(value); const selected = value === 'draft' ? draft : detail.versions.find((item) => String(item.version) === value); if (selected) setTestInput(JSON.stringify(selected.example_input, null, 2)) }}>{draft ? 草稿 v{draft.version}(沙箱) : null}{detail.versions.filter((item) => item.status === 'published').map((item) => 已发布 v{item.version})} String(item.version) === testVersion)?.input_schema) ?? {}} value={testInput} onChange={setTestInput} /> void run()} disabled={!testVersion}>启动异步运行 + {detail.versions.map((version) => 版本 v{version.version}{version.model_display_name} · {version.steps.length} 个步骤 · {version.published_at ? formatDateTime(version.published_at) : formatDateTime(version.updated_at)}{version.status === 'published' && detail.active_version !== version.version ? void rollback(version.version)}>回滚至此版本 : null})} + + +} diff --git a/frontend/src/features/agents/AgentListPage.tsx b/frontend/src/features/agents/AgentListPage.tsx new file mode 100644 index 0000000..0d81392 --- /dev/null +++ b/frontend/src/features/agents/AgentListPage.tsx @@ -0,0 +1,77 @@ +import * as React from 'react' +import { Plus, X } from 'lucide-react' +import { toast } from 'sonner' + +import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable' +import { AppEmptyState } from '@/components/app/AppEmptyState' +import { AppField } from '@/components/app/AppField' +import { AppPage } from '@/components/app/AppPage' +import { AppPageHeader } from '@/components/app/AppPageHeader' +import { AppSection } from '@/components/app/AppSection' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { FieldGroup } from '@/components/ui/field' +import { Input } from '@/components/ui/input' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { MobileEditGuard } from '@/features/ai-platform/components/MobileEditGuard' +import { listDeployments } from '@/features/modelhub/api' +import type { ModelDeployment } from '@/features/modelhub/types' +import { getSkill, listSkills } from '@/features/skills/api' +import type { SkillVersion } from '@/features/skills/types' +import { formatDateTime } from '@/lib/format' + +import { createAgent, listAgents } from './api' +import type { AgentDefinition } from './types' + +type StepForm = { code: string; skill_version_id: string; mapping: string } +type SkillOption = SkillVersion & { definitionName: string } + +export function AgentListPage() { + const [items, setItems] = React.useState([]) + const [deployments, setDeployments] = React.useState([]) + const [skills, setSkills] = React.useState([]) + const [form, setForm] = React.useState({ code: '', name: '', description: '', model_deployment_id: '' }) + const [steps, setSteps] = React.useState([{ code: 'step_1', skill_version_id: '', mapping: '{\n "problem": "$input.problem"\n}' }]) + const [error, setError] = React.useState('') + + const load = React.useCallback(async () => { + try { + const [nextAgents, nextDeployments, definitions] = await Promise.all([listAgents(), listDeployments(), listSkills()]) + const details = await Promise.all(definitions.map((item) => getSkill(item.id))) + const options = details.flatMap((item) => item.versions.map((version) => ({ ...version, definitionName: item.name }))) + setItems(nextAgents); setDeployments(nextDeployments); setSkills(options) + setForm((current) => ({ ...current, model_deployment_id: current.model_deployment_id || String(nextDeployments[0]?.id ?? '') })) + setSteps((current) => current.map((step) => ({ ...step, skill_version_id: step.skill_version_id || String(options[0]?.id ?? '') }))) + setError('') + } catch (reason) { setError(reason instanceof Error ? reason.message : '加载 Agent 失败') } + }, []) + React.useEffect(() => { void load() }, [load]) + + function updateStep(index: number, patch: Partial) { setSteps((current) => current.map((step, stepIndex) => stepIndex === index ? { ...step, ...patch } : step)) } + + async function handleCreate(event: React.FormEvent) { + event.preventDefault() + try { + const created = await createAgent({ ...form, model_deployment_id: Number(form.model_deployment_id), system_prompt: '你是技术支持分析助手。必须返回符合当前 Skill 输出 Schema 的 JSON。', input_schema: { type: 'object', properties: { problem: { type: 'string' }, environment: { type: 'string' } }, required: ['problem'], additionalProperties: false }, example_input: { problem: '采集任务长时间没有更新', environment: '生产环境' }, steps: steps.map((step) => ({ code: step.code, skill_version_id: Number(step.skill_version_id), input_mapping: JSON.parse(step.mapping) as object })) }) + toast.success('Agent 草稿已创建'); window.location.hash = `/agents/${created.id}` + } catch (reason) { toast.error(reason instanceof Error ? reason.message : '创建失败') } + } + + const columns: AppDataTableColumn[] = [ + { key: 'name', header: 'Agent', cell: (item) => {item.name}{item.code} }, + { key: 'description', header: '描述', cell: (item) => {item.description || '-'} }, + { key: 'version', header: '版本', cell: (item) => {item.active_version ? 发布 v{item.active_version} : 未发布}{item.draft_version ? 草稿 v{item.draft_version} : null} }, + { key: 'updated', header: '更新时间', cell: (item) => {formatDateTime(item.updated_at)} } + ] + + return + { window.location.hash = '/agent-runs' }}>查看运行记录} /> + + setForm({ ...form, code: e.target.value })} /> setForm({ ...form, name: e.target.value })} /> setForm({ ...form, description: e.target.value })} /> setForm({ ...form, model_deployment_id: value })}>{deployments.map((item) => {item.display_name})} + {steps.map((step, index) => 步骤 {index + 1}{steps.length > 1 ? setSteps((current) => current.filter((_, i) => i !== index))}>删除步骤 : null} updateStep(index, { code: e.target.value })} /> updateStep(index, { skill_version_id: value })}>{skills.map((item) => {item.definitionName} · v{item.version}({item.status}))} updateStep(index, { mapping: e.target.value })} />)} + = 10} onClick={() => setSteps((current) => [...current, { code: `step_${current.length + 1}`, skill_version_id: String(skills[0]?.id ?? ''), mapping: '{}' }])}>添加步骤创建 Agent + + {error ? : item.id} emptyTitle="暂无 Agent" emptyDescription="请先准备模型配置和 Skill,再创建 Agent。" onRowClick={(item) => { window.location.hash = `/agents/${item.id}` }} />} + +} diff --git a/frontend/src/features/agents/AgentRunDetailPage.tsx b/frontend/src/features/agents/AgentRunDetailPage.tsx new file mode 100644 index 0000000..4aa3e83 --- /dev/null +++ b/frontend/src/features/agents/AgentRunDetailPage.tsx @@ -0,0 +1,60 @@ +import * as React from 'react' +import { ArrowLeft, Ban, Braces, Clock3, Coins, RotateCw } from 'lucide-react' +import { toast } from 'sonner' + +import { AppEmptyState } from '@/components/app/AppEmptyState' +import { AppPage } from '@/components/app/AppPage' +import { AppPageHeader } from '@/components/app/AppPageHeader' +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Separator } from '@/components/ui/separator' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { StatusBadge } from '@/features/ai-platform/status' +import { formatDateTime } from '@/lib/format' +import { cn } from '@/lib/utils' + +import { cancelAgentRun, getAgentRun } from './api' +import type { AgentRunDetail } from './types' + +function JsonBlock({ value }: { value: unknown }) { + return {JSON.stringify(value, null, 2)} +} + +export function AgentRunDetailPage({ publicId }: { publicId: string }) { + const [run, setRun] = React.useState(null) + const [selectedStepId, setSelectedStepId] = React.useState(null) + const [error, setError] = React.useState('') + + const load = React.useCallback(async () => { + try { + const next = await getAgentRun(publicId); setRun(next); setSelectedStepId((current) => current ?? next.steps[0]?.id ?? null); setError('') + } catch (reason) { setError(reason instanceof Error ? reason.message : '加载运行详情失败') } + }, [publicId]) + React.useEffect(() => { void load() }, [load]) + React.useEffect(() => { + if (!run || !['queued', 'running'].includes(run.status)) return + const timer = window.setTimeout(() => void load(), 1200) + return () => window.clearTimeout(timer) + }, [load, run]) + + async function cancel() { try { setRun(await cancelAgentRun(publicId)); toast.success('取消请求已提交') } catch (reason) { toast.error(reason instanceof Error ? reason.message : '取消失败') } } + + if (error) return + if (!run) return + const selected = run.steps.find((step) => step.id === selectedStepId) ?? null + return + {run.sandbox ? 草稿沙箱 : 发布 v{run.agent_version}}} actions={<> { window.location.hash = '/agent-runs' }}>返回 void load()}>刷新{['queued', 'running'].includes(run.status) ? void cancel()}>尽力取消 : null}>} /> + {run.error_message ? {run.error_code || '运行失败'}{run.error_message} : null} + + 执行脊柱{run.steps.length}/{run.total_steps} 个步骤已创建{run.steps.map((step) => setSelectedStepId(step.id)} className={cn('grid w-full grid-cols-[28px_minmax(0,1fr)] gap-2 rounded-md p-2 text-left transition-colors hover:bg-muted', selectedStepId === step.id && 'bg-muted')}>{step.order}{step.name}{step.code}{step.order < run.steps.length ? : null})} + {selected ? `${selected.order}. ${selected.name}` : '运行输入与最终输出'}{selected ? selected.code : '尚未生成步骤记录'}输入输出调用尝试{selected?.attempts.length ? selected.attempts.map((attempt) => {attempt.attempt_type}{attempt.input_tokens + attempt.output_tokens} tokens · {attempt.duration_ms} ms{attempt.validation_error ? 结构校验错误{attempt.validation_error} : null}) : } + 运行指标{selected ? <>> : null}配置快照历史记录不受后续配置修改影响{run.final_output !== null ? 最终结构化输出 : null} + + +} + +function Metric({ icon: Icon, label, value }: { icon: typeof Clock3; label: string; value: string }) { + return {label}{value} +} diff --git a/frontend/src/features/agents/AgentRunListPage.tsx b/frontend/src/features/agents/AgentRunListPage.tsx new file mode 100644 index 0000000..2f6fe53 --- /dev/null +++ b/frontend/src/features/agents/AgentRunListPage.tsx @@ -0,0 +1,30 @@ +import * as React from 'react' +import { RefreshCw } from 'lucide-react' + +import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable' +import { AppEmptyState } from '@/components/app/AppEmptyState' +import { AppPage } from '@/components/app/AppPage' +import { AppPageHeader } from '@/components/app/AppPageHeader' +import { Button } from '@/components/ui/button' +import { Badge } from '@/components/ui/badge' +import { StatusBadge } from '@/features/ai-platform/status' +import { formatDateTime } from '@/lib/format' + +import { listAgentRuns } from './api' +import type { AgentRunSummary } from './types' + +export function AgentRunListPage() { + const [items, setItems] = React.useState([]) + const [error, setError] = React.useState('') + const load = React.useCallback(async () => { try { setItems(await listAgentRuns()); setError('') } catch (reason) { setError(reason instanceof Error ? reason.message : '加载运行记录失败') } }, []) + React.useEffect(() => { void load() }, [load]) + const columns: AppDataTableColumn[] = [ + { key: 'agent', header: 'Agent', cell: (item) => {item.agent_name}{item.public_id.slice(0, 8)}… }, + { key: 'version', header: '版本', cell: (item) => v{item.agent_version}{item.sandbox ? 沙箱 : 正式} }, + { key: 'status', header: '状态', cell: (item) => }, + { key: 'progress', header: '当前步骤', cell: (item) => {item.current_step || '-'} · {item.total_steps} 步 }, + { key: 'usage', header: 'Token / 耗时', cell: (item) => {item.input_tokens + item.output_tokens} · {item.duration_ms} ms }, + { key: 'created', header: '创建时间', cell: (item) => {formatDateTime(item.created_at)} } + ] + return { window.location.hash = '/agents' }}>Agent 管理 void load()}>刷新>} />{error ? : item.public_id} emptyTitle="暂无运行记录" emptyDescription="从 Agent 详情的测试页启动一次运行。" onRowClick={(item) => { window.location.hash = `/agent-runs/${item.public_id}` }} />} +} diff --git a/frontend/src/features/agents/api.ts b/frontend/src/features/agents/api.ts new file mode 100644 index 0000000..6efdaa6 --- /dev/null +++ b/frontend/src/features/agents/api.ts @@ -0,0 +1,15 @@ +import { apiRequest } from '@/lib/api' +import type { AgentDefinition, AgentDetail, AgentRunDetail, AgentRunSummary, AgentVersion } from './types' + +export const listAgents = () => apiRequest('/agents') +export const createAgent = (payload: object) => apiRequest('/agents', { method: 'POST', body: JSON.stringify(payload) }) +export const getAgent = (id: number) => apiRequest(`/agents/${id}`) +export const updateAgent = (id: number, payload: object) => apiRequest(`/agents/${id}`, { method: 'PATCH', body: JSON.stringify(payload) }) +export const ensureAgentDraft = (id: number) => apiRequest(`/agents/${id}/draft`, { method: 'POST' }) +export const updateAgentDraft = (id: number, payload: object) => apiRequest(`/agents/${id}/draft`, { method: 'PATCH', body: JSON.stringify(payload) }) +export const publishAgent = (id: number) => apiRequest(`/agents/${id}/publish`, { method: 'POST' }) +export const rollbackAgent = (id: number, version: number) => apiRequest(`/agents/${id}/rollback/${version}`, { method: 'POST' }) +export const startAgentRun = (id: number, payload: object) => apiRequest(`/agents/${id}/runs`, { method: 'POST', body: JSON.stringify(payload) }) +export const listAgentRuns = () => apiRequest('/agent-runs') +export const getAgentRun = (id: string) => apiRequest(`/agent-runs/${id}`) +export const cancelAgentRun = (id: string) => apiRequest(`/agent-runs/${id}/cancel`, { method: 'POST' }) diff --git a/frontend/src/features/agents/types.ts b/frontend/src/features/agents/types.ts new file mode 100644 index 0000000..41f57db --- /dev/null +++ b/frontend/src/features/agents/types.ts @@ -0,0 +1,94 @@ +export type AgentStep = { + id: number + code: string + order: number + skill_version_id: number + skill_name: string + skill_version: number + skill_status: string + input_mapping: Record +} + +export type AgentVersion = { + id: number + version: number + status: 'draft' | 'published' | 'archived' + model_deployment_id: number + model_display_name: string + system_prompt: string + input_schema: Record + example_input: Record + temperature_override: number | null + max_output_tokens_override: number | null + steps: AgentStep[] + created_at: string + updated_at: string + published_at: string | null +} + +export type AgentDefinition = { + id: number + code: string + name: string + description: string + active_version: number | null + draft_version: number | null + updated_at: string +} + +export type AgentDetail = AgentDefinition & { versions: AgentVersion[] } + +export type AgentRunAttempt = { + id: number + attempt_type: 'initial' | 'transient_retry' | 'structure_repair' + status: string + raw_output: string + validation_error: string + input_tokens: number + output_tokens: number + duration_ms: number + created_at: string +} + +export type AgentRunStep = { + id: number + code: string + name: string + order: number + status: string + input_payload: Record + output_payload: unknown + input_tokens: number + output_tokens: number + duration_ms: number + error_code: string + error_message: string + attempts: AgentRunAttempt[] +} + +export type AgentRunSummary = { + public_id: string + agent_id: number + agent_name: string + agent_version: number + status: string + sandbox: boolean + current_step: string + total_steps: number + input_tokens: number + output_tokens: number + duration_ms: number + error_code: string + error_message: string + created_at: string + started_at: string | null + finished_at: string | null +} + +export type AgentRunDetail = AgentRunSummary & { + input_payload: Record + final_output: unknown + config_snapshot: Record + cancellation_requested_at: string | null + steps: AgentRunStep[] +}
{item.name}
{item.code}
{JSON.stringify(value, null, 2)}
{item.agent_name}
{item.public_id.slice(0, 8)}…