Enrich Sessions API: add ToolUse fields, timestamps, descriptions, and flatten list endpoint

- Add id, is_error, duration_ms to ToolUse; rename args to input
- Add created_at/updated_at timestamps to session schemas; replace time/date display strings
- Add descriptions and examples to all Sessions API fields and endpoints
- Flatten List Sessions response from grouped SessionGroup[] to SessionListItem[]
- Move date grouping (Today/Yesterday/etc.) to React client via groupSessionsByDate()
- Symlink docs/api-reference/arc-api.yaml to canonical openapi/arc-api.yaml
- Update React ToolRow components with duration display and error styling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-06 08:01:55 -05:00
parent e4babddd5e
commit e569770c36
20 changed files with 397 additions and 2564 deletions

View file

@ -11,3 +11,47 @@ export function timeAgo(iso: string): string {
const days = Math.floor(hours / 24);
return `${days}d ago`;
}
/**
* 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

@ -47,9 +47,12 @@ const statusConfig: Record<StageStatus, { icon: typeof CheckCircleIcon; color: s
};
interface ToolUse {
id: string;
toolName: string;
args: string;
input: string;
result: string;
isError: boolean;
durationMs?: number;
}
type TurnType =
@ -72,17 +75,18 @@ function ToolRow({ tool }: { tool: ToolUse }) {
<ChevronRightIcon className={`size-3 shrink-0 text-fg-muted transition-transform duration-150 ${open ? "rotate-90" : ""}`} />
<WrenchScrewdriverIcon className="size-3.5 shrink-0 text-fg-muted" />
<span className="font-mono text-xs text-fg-3">{tool.toolName}</span>
<span className="truncate font-mono text-xs text-fg-muted">{tool.args}</span>
{tool.durationMs != null && <span className="text-[11px] text-fg-muted">{tool.durationMs}ms</span>}
<span className="truncate font-mono text-xs text-fg-muted">{tool.input}</span>
</button>
{open && (
<div className="space-y-px bg-overlay px-2.5 pb-2 pt-1">
<div className="rounded bg-overlay px-2.5 py-2">
<div className="mb-1 text-[10px] font-medium uppercase tracking-wider text-fg-muted">Args</div>
<pre className="whitespace-pre-wrap font-mono text-xs leading-relaxed text-fg-3">{tool.args}</pre>
<div className="mb-1 text-[10px] font-medium uppercase tracking-wider text-fg-muted">Input</div>
<pre className="whitespace-pre-wrap font-mono text-xs leading-relaxed text-fg-3">{tool.input}</pre>
</div>
<div className="rounded bg-overlay px-2.5 py-2">
<div className="mb-1 text-[10px] font-medium uppercase tracking-wider text-fg-muted">Result</div>
<pre className="whitespace-pre-wrap font-mono text-xs leading-relaxed text-fg-3">{tool.result}</pre>
<pre className={`whitespace-pre-wrap font-mono text-xs leading-relaxed ${tool.isError ? "text-coral" : "text-fg-3"}`}>{tool.result}</pre>
</div>
</div>
)}
@ -137,9 +141,12 @@ export default function RunStages({ loaderData }: Route.ComponentProps) {
return {
kind: "tool" as const,
tools: t.tools.map((tu) => ({
id: tu.id,
toolName: tu.tool_name,
args: tu.args,
input: tu.input,
result: tu.result,
isError: tu.is_error,
durationMs: tu.duration_ms,
})),
};
}

View file

@ -9,9 +9,9 @@ import {
UserIcon,
WrenchScrewdriverIcon,
} from "@heroicons/react/24/outline";
import { timeAgo } from "../lib/time";
import { timeAgo, groupSessionsByDate } from "../lib/time";
import { apiJson } from "../api-client";
import type { SessionDetail as ApiSessionDetail, PaginatedSessionGroupList } from "@qltysh/arc-api-client";
import type { SessionDetail as ApiSessionDetail, PaginatedSessionList } from "@qltysh/arc-api-client";
import type { Route } from "./+types/session-detail";
export const handle = { hideHeader: true, wide: true };
@ -21,9 +21,9 @@ export function meta({}: Route.MetaArgs) {
}
export async function loader({ request, params }: Route.LoaderArgs) {
const [apiSession, { data: apiGroups }] = await Promise.all([
const [apiSession, { data: apiSessions }] = await Promise.all([
apiJson<ApiSessionDetail>(`/sessions/${params.sessionId}`, { request }),
apiJson<PaginatedSessionGroupList>("/sessions", { request }),
apiJson<PaginatedSessionList>("/sessions", { request }),
]);
const session: Session = {
id: apiSession.id,
@ -36,30 +36,31 @@ export async function loader({ request, params }: Route.LoaderArgs) {
return {
kind: "tool" as const,
tools: t.tools.map((tu) => ({
id: tu.id,
toolName: tu.tool_name,
args: tu.args,
input: tu.input,
result: tu.result,
isError: tu.is_error,
durationMs: tu.duration_ms,
})),
};
}
return { kind: t.kind as "user" | "assistant", content: t.content ?? "", created_at: t.created_at };
}),
};
const sessionGroups = apiGroups.map((g) => ({
label: g.label,
sessions: g.sessions.map((s) => ({
id: s.id,
title: s.title,
created_at: s.created_at,
})),
}));
const sessionGroups = groupSessionsByDate(
apiSessions.map((s) => ({ id: s.id, title: s.title, created_at: s.created_at }))
);
return { session, sessionGroups };
}
interface ToolUse {
id: string;
toolName: string;
args: string;
input: string;
result: string;
isError: boolean;
durationMs?: number;
}
type Turn =
@ -98,19 +99,28 @@ const sessions: Record<string, Session> = {
kind: "tool",
tools: [
{
id: "toolu_s1_01",
toolName: "read_file",
args: `{ "path": "src/routes/auth.ts" }`,
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",
args: `{ "path": "src/middleware/index.ts" }`,
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",
args: `{ "pattern": "redis", "path": "src/" }`,
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,
},
],
},
@ -122,19 +132,28 @@ const sessions: Record<string, Session> = {
kind: "tool",
tools: [
{
id: "toolu_s1_04",
toolName: "write_file",
args: `{ "path": "src/middleware/rate-limit.ts" }`,
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",
args: `{ "path": "src/routes/auth.ts", "operations": [{ "type": "insert", "line": 3 }] }`,
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",
args: `{ "path": "src/middleware/index.ts", "operations": [{ "type": "append" }] }`,
input: `{ "path": "src/middleware/index.ts", "operations": [{ "type": "append" }] }`,
result: `Applied 1 edit to src/middleware/index.ts`,
isError: false,
durationMs: 55,
},
],
},
@ -164,9 +183,12 @@ const sessions: Record<string, Session> = {
kind: "tool",
tools: [
{
id: "toolu_s2_01",
toolName: "read_file",
args: `{ "path": "src/config/parser.ts" }`,
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,
},
],
},
@ -178,14 +200,20 @@ const sessions: Record<string, Session> = {
kind: "tool",
tools: [
{
id: "toolu_s2_02",
toolName: "write_file",
args: `{ "path": "src/config/parser.ts" }`,
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",
args: `{ "command": "npm test -- --grep 'config parser'" }`,
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,
},
],
},
@ -207,8 +235,8 @@ const sessions: Record<string, Session> = {
{
kind: "tool",
tools: [
{ toolName: "search_files", args: `{ "pattern": "createBrowserRouter" }`, result: `src/main.tsx:5: import { createBrowserRouter } from "react-router-dom";\nsrc/main.tsx:8: const router = createBrowserRouter([` },
{ toolName: "read_file", args: `{ "path": "package.json" }`, result: `"react-router-dom": "^6.22.0"` },
{ 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 `Route.LoaderArgs` type\n4. Update any `useLoaderData` calls to be type-safe\n\nLet me make these changes." },
@ -275,17 +303,18 @@ function ToolRow({ tool }: { tool: ToolUse }) {
<ChevronRightIcon className={`size-3 shrink-0 text-fg-muted transition-transform duration-150 ${open ? "rotate-90" : ""}`} />
<WrenchScrewdriverIcon className="size-3.5 shrink-0 text-fg-muted" />
<span className="font-mono text-xs text-fg-3">{tool.toolName}</span>
<span className="truncate font-mono text-xs text-fg-muted">{tool.args}</span>
{tool.durationMs != null && <span className="text-[11px] text-fg-muted">{tool.durationMs}ms</span>}
<span className="truncate font-mono text-xs text-fg-muted">{tool.input}</span>
</button>
{open && (
<div className="space-y-px bg-overlay px-2.5 pb-2 pt-1">
<div className="rounded bg-overlay px-2.5 py-2">
<div className="mb-1 text-[10px] font-medium uppercase tracking-wider text-fg-muted">Args</div>
<pre className="whitespace-pre-wrap font-mono text-xs leading-relaxed text-fg-3">{tool.args}</pre>
<div className="mb-1 text-[10px] font-medium uppercase tracking-wider text-fg-muted">Input</div>
<pre className="whitespace-pre-wrap font-mono text-xs leading-relaxed text-fg-3">{tool.input}</pre>
</div>
<div className="rounded bg-overlay px-2.5 py-2">
<div className="mb-1 text-[10px] font-medium uppercase tracking-wider text-fg-muted">Result</div>
<pre className="whitespace-pre-wrap font-mono text-xs leading-relaxed text-fg-3">{tool.result}</pre>
<pre className={`whitespace-pre-wrap font-mono text-xs leading-relaxed ${tool.isError ? "text-coral" : "text-fg-3"}`}>{tool.result}</pre>
</div>
</div>
)}

View file

@ -20,10 +20,10 @@ import {
XMarkIcon,
} from "@heroicons/react/24/outline";
import { Link } from "react-router";
import { timeAgo } from "../lib/time";
import { timeAgo, groupSessionsByDate } from "../lib/time";
import { apiJson } from "../api-client";
import { getAppConfig } from "../lib/config.server";
import type { PaginatedProjectList, PaginatedSessionGroupList } from "@qltysh/arc-api-client";
import type { PaginatedProjectList, PaginatedSessionList } from "@qltysh/arc-api-client";
import type { Route } from "./+types/start";
export const handle = { hideHeader: true, wide: true };
@ -36,17 +36,12 @@ export async function loader({ request }: Route.LoaderArgs) {
const { feature_flags } = getAppConfig();
const [{ data: apiProjects }, { data: apiSessions }] = await Promise.all([
apiJson<PaginatedProjectList>("/projects", { request }),
apiJson<PaginatedSessionGroupList>("/sessions", { request }),
apiJson<PaginatedSessionList>("/sessions", { request }),
]);
const projects = apiProjects.map((p) => ({ id: p.id, name: p.name }));
const sessionGroups = apiSessions.map((g) => ({
label: g.label,
sessions: g.sessions.map((s) => ({
id: s.id,
title: s.title,
created_at: s.created_at,
})),
}));
const sessionGroups = groupSessionsByDate(
apiSessions.map((s) => ({ id: s.id, title: s.title, created_at: s.created_at }))
);
return { projects, sessionGroups, feature_flags };
}

View file

@ -494,7 +494,7 @@ pub async fn list_sessions(
State(_state): State<Arc<AppState>>,
Query(pagination): Query<PaginationParams>,
) -> Response {
paginated_response(sessions::groups(), &pagination)
paginated_response(sessions::list_items(), &pagination)
}
pub async fn create_session_stub(
@ -1033,8 +1033,8 @@ mod runs {
StageTurn {
kind: StageTurnKind::Tool, content: None,
tools: vec![
ToolUse { tool_name: "read_file".into(), args: r#"{ "path": "environments/production/config.toml" }"#.into(), result: "[redis]\nhost = \"redis-prod.internal\"\nport = 6379".into() },
ToolUse { tool_name: "read_file".into(), args: r#"{ "path": "environments/staging/config.toml" }"#.into(), result: "[redis]\nhost = \"redis-staging.internal\"\nport = 6379".into() },
ToolUse { id: "toolu_01".into(), tool_name: "read_file".into(), input: r#"{ "path": "environments/production/config.toml" }"#.into(), result: "[redis]\nhost = \"redis-prod.internal\"\nport = 6379".into(), is_error: false, duration_ms: Some(45) },
ToolUse { id: "toolu_02".into(), tool_name: "read_file".into(), input: r#"{ "path": "environments/staging/config.toml" }"#.into(), result: "[redis]\nhost = \"redis-staging.internal\"\nport = 6379".into(), is_error: false, duration_ms: Some(38) },
],
},
StageTurn { kind: StageTurnKind::Assistant, content: Some("I've detected drift in 3 resources between production and staging:\n\n1. **redis.max_connections** — production has 200, staging has 100\n2. **redis.tls** — enabled in production, disabled in staging\n3. **iam.session_duration** — production uses 3600s, staging uses 1800s".into()), tools: vec![] },
@ -2367,62 +2367,47 @@ mod retros {
mod sessions {
use arc_types::*;
pub fn groups() -> Vec<SessionGroup> {
pub fn list_items() -> Vec<SessionListItem> {
vec![
SessionGroup {
label: "Today".into(),
sessions: vec![
SessionListItem {
id: "s1".into(),
title: "Add rate limiting to auth endpoints".into(),
created_at: "2026-03-06T14:30:00Z".into(),
},
SessionListItem {
id: "s2".into(),
title: "Fix config parsing for nested values".into(),
created_at: "2026-03-06T12:30:00Z".into(),
},
],
SessionListItem {
id: "s1".into(),
title: "Add rate limiting to auth endpoints".into(),
created_at: "2026-03-06T14:30:00Z".into(),
},
SessionGroup {
label: "Yesterday".into(),
sessions: vec![
SessionListItem {
id: "s3".into(),
title: "Migrate to React Router v7".into(),
created_at: "2026-03-05T10:00:00Z".into(),
},
SessionListItem {
id: "s4".into(),
title: "Add dark mode toggle".into(),
created_at: "2026-03-05T09:00:00Z".into(),
},
SessionListItem {
id: "s5".into(),
title: "Update OpenAPI spec for v3".into(),
created_at: "2026-03-05T08:00:00Z".into(),
},
],
SessionListItem {
id: "s2".into(),
title: "Fix config parsing for nested values".into(),
created_at: "2026-03-06T12:30:00Z".into(),
},
SessionGroup {
label: "Previous 7 days".into(),
sessions: vec![
SessionListItem {
id: "s6".into(),
title: "Terraform module for Redis cluster".into(),
created_at: "2026-03-03T15:00:00Z".into(),
},
SessionListItem {
id: "s7".into(),
title: "Add pipeline event types".into(),
created_at: "2026-03-01T11:00:00Z".into(),
},
SessionListItem {
id: "s8".into(),
title: "Implement webhook retry logic".into(),
created_at: "2026-02-28T09:00:00Z".into(),
},
],
SessionListItem {
id: "s3".into(),
title: "Migrate to React Router v7".into(),
created_at: "2026-03-05T10:00:00Z".into(),
},
SessionListItem {
id: "s4".into(),
title: "Add dark mode toggle".into(),
created_at: "2026-03-05T09:00:00Z".into(),
},
SessionListItem {
id: "s5".into(),
title: "Update OpenAPI spec for v3".into(),
created_at: "2026-03-05T08:00:00Z".into(),
},
SessionListItem {
id: "s6".into(),
title: "Terraform module for Redis cluster".into(),
created_at: "2026-03-03T15:00:00Z".into(),
},
SessionListItem {
id: "s7".into(),
title: "Add pipeline event types".into(),
created_at: "2026-03-01T11:00:00Z".into(),
},
SessionListItem {
id: "s8".into(),
title: "Implement webhook retry logic".into(),
created_at: "2026-02-28T09:00:00Z".into(),
},
]
}
@ -2435,15 +2420,15 @@ mod sessions {
SessionTurn { kind: SessionTurnKind::User, content: Some("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.".into()), created_at: Some("2026-02-28T10:00:00Z".into()), tools: vec![] },
SessionTurn { kind: SessionTurnKind::Assistant, content: Some("I'll implement sliding window rate limiting using Redis. Let me first look at the existing auth routes and middleware setup.".into()), created_at: None, tools: vec![] },
SessionTurn { kind: SessionTurnKind::Tool, content: None, created_at: None, tools: vec![
ToolUse { tool_name: "read_file".into(), args: r#"{ "path": "src/routes/auth.ts" }"#.into(), 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;".into() },
ToolUse { tool_name: "read_file".into(), args: r#"{ "path": "src/middleware/index.ts" }"#.into(), result: "export { authMiddleware } from \"./auth\";\nexport { corsMiddleware } from \"./cors\";\nexport { loggingMiddleware } from \"./logging\";".into() },
ToolUse { tool_name: "search_files".into(), args: r#"{ "pattern": "redis", "path": "src/" }"#.into(), result: "src/lib/redis.ts:1: import Redis from \"ioredis\";\nsrc/lib/redis.ts:3: export const redis = new Redis(process.env.REDIS_URL);".into() },
ToolUse { id: "toolu_03".into(), tool_name: "read_file".into(), input: r#"{ "path": "src/routes/auth.ts" }"#.into(), 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;".into(), is_error: false, duration_ms: Some(52) },
ToolUse { id: "toolu_04".into(), tool_name: "read_file".into(), input: r#"{ "path": "src/middleware/index.ts" }"#.into(), result: "export { authMiddleware } from \"./auth\";\nexport { corsMiddleware } from \"./cors\";\nexport { loggingMiddleware } from \"./logging\";".into(), is_error: false, duration_ms: Some(35) },
ToolUse { id: "toolu_05".into(), tool_name: "search_files".into(), input: r#"{ "pattern": "redis", "path": "src/" }"#.into(), result: "src/lib/redis.ts:1: import Redis from \"ioredis\";\nsrc/lib/redis.ts:3: export const redis = new Redis(process.env.REDIS_URL);".into(), is_error: false, duration_ms: Some(180) },
] },
SessionTurn { kind: SessionTurnKind::Assistant, content: Some("Good \u{2014} 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".into()), created_at: None, tools: vec![] },
SessionTurn { kind: SessionTurnKind::Tool, content: None, created_at: None, tools: vec![
ToolUse { tool_name: "write_file".into(), args: r#"{ "path": "src/middleware/rate-limit.ts" }"#.into(), result: "File written: src/middleware/rate-limit.ts (47 lines)".into() },
ToolUse { tool_name: "edit_file".into(), args: r#"{ "path": "src/routes/auth.ts", "operations": [{ "type": "insert", "line": 3 }] }"#.into(), result: "Applied 1 edit to src/routes/auth.ts".into() },
ToolUse { tool_name: "edit_file".into(), args: r#"{ "path": "src/middleware/index.ts", "operations": [{ "type": "append" }] }"#.into(), result: "Applied 1 edit to src/middleware/index.ts".into() },
ToolUse { id: "toolu_06".into(), tool_name: "write_file".into(), input: r#"{ "path": "src/middleware/rate-limit.ts" }"#.into(), result: "File written: src/middleware/rate-limit.ts (47 lines)".into(), is_error: false, duration_ms: Some(62) },
ToolUse { id: "toolu_07".into(), tool_name: "edit_file".into(), input: r#"{ "path": "src/routes/auth.ts", "operations": [{ "type": "insert", "line": 3 }] }"#.into(), result: "Applied 1 edit to src/routes/auth.ts".into(), is_error: false, duration_ms: Some(41) },
ToolUse { id: "toolu_08".into(), tool_name: "edit_file".into(), input: r#"{ "path": "src/middleware/index.ts", "operations": [{ "type": "append" }] }"#.into(), result: "Applied 1 edit to src/middleware/index.ts".into(), is_error: false, duration_ms: Some(55) },
] },
SessionTurn { kind: SessionTurnKind::Assistant, content: Some("Done. I've created the rate limiter and wired it up:\n\n- **`src/middleware/rate-limit.ts`** \u{2014} 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`** \u{2014} Applied `rateLimit({ windowMs: 60_000, max: 10 })` to both `/login` and `/register`.\n- **`src/middleware/index.ts`** \u{2014} 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.".into()), created_at: None, tools: vec![] },
],
@ -2454,12 +2439,12 @@ mod sessions {
SessionTurn { kind: SessionTurnKind::User, content: Some("The CLI crashes when parsing nested TOML config values like [database.connection]. Can you debug and fix this?".into()), created_at: Some("2026-02-28T10:00:00Z".into()), tools: vec![] },
SessionTurn { kind: SessionTurnKind::Assistant, content: Some("Let me look at the config parser to understand how nested keys are handled.".into()), created_at: None, tools: vec![] },
SessionTurn { kind: SessionTurnKind::Tool, content: None, created_at: None, tools: vec![
ToolUse { tool_name: "read_file".into(), args: r#"{ "path": "src/config/parser.ts" }"#.into(), 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}".into() },
ToolUse { id: "toolu_09".into(), tool_name: "read_file".into(), input: r#"{ "path": "src/config/parser.ts" }"#.into(), 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}".into(), is_error: false, duration_ms: Some(67) },
] },
SessionTurn { kind: SessionTurnKind::Assistant, content: Some("Found the issue. The parser uses a flat `Record<string, string>` and the regex `^(\\w+)` only matches simple keys \u{2014} 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.".into()), created_at: None, tools: vec![] },
SessionTurn { kind: SessionTurnKind::Tool, content: None, created_at: None, tools: vec![
ToolUse { tool_name: "write_file".into(), args: r#"{ "path": "src/config/parser.ts" }"#.into(), result: "File written: src/config/parser.ts (62 lines)".into() },
ToolUse { tool_name: "run_command".into(), args: r#"{ "command": "npm test -- --grep 'config parser'" }"#.into(), result: "PASS src/config/__tests__/parser.test.ts\n config parser\n \u{2713} parses flat keys (2ms)\n \u{2713} parses section headers (1ms)\n \u{2713} parses dotted keys (1ms)\n \u{2713} handles nested sections (1ms)\n\nTest Suites: 1 passed\nTests: 4 passed".into() },
ToolUse { id: "toolu_10".into(), tool_name: "write_file".into(), input: r#"{ "path": "src/config/parser.ts" }"#.into(), result: "File written: src/config/parser.ts (62 lines)".into(), is_error: false, duration_ms: Some(78) },
ToolUse { id: "toolu_11".into(), tool_name: "run_command".into(), input: r#"{ "command": "npm test -- --grep 'config parser'" }"#.into(), result: "PASS src/config/__tests__/parser.test.ts\n config parser\n \u{2713} parses flat keys (2ms)\n \u{2713} parses section headers (1ms)\n \u{2713} parses dotted keys (1ms)\n \u{2713} handles nested sections (1ms)\n\nTest Suites: 1 passed\nTests: 4 passed".into(), is_error: false, duration_ms: Some(2150) },
] },
SessionTurn { kind: SessionTurnKind::Assistant, content: Some("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.".into()), created_at: None, tools: vec![] },
],
@ -2470,8 +2455,8 @@ mod sessions {
SessionTurn { kind: SessionTurnKind::User, content: Some("Help me migrate our app from React Router v6 to v7. We're using createBrowserRouter with data loaders.".into()), created_at: Some("2026-02-26T10:00:00Z".into()), tools: vec![] },
SessionTurn { kind: SessionTurnKind::Assistant, content: Some("I'll audit your current router setup and identify what needs to change for v7. Let me scan the codebase.".into()), created_at: None, tools: vec![] },
SessionTurn { kind: SessionTurnKind::Tool, content: None, created_at: None, tools: vec![
ToolUse { tool_name: "search_files".into(), args: r#"{ "pattern": "createBrowserRouter" }"#.into(), result: "src/main.tsx:5: import { createBrowserRouter } from \"react-router-dom\";\nsrc/main.tsx:8: const router = createBrowserRouter([".into() },
ToolUse { tool_name: "read_file".into(), args: r#"{ "path": "package.json" }"#.into(), result: "\"react-router-dom\": \"^6.22.0\"".into() },
ToolUse { id: "toolu_12".into(), tool_name: "search_files".into(), input: r#"{ "pattern": "createBrowserRouter" }"#.into(), result: "src/main.tsx:5: import { createBrowserRouter } from \"react-router-dom\";\nsrc/main.tsx:8: const router = createBrowserRouter([".into(), is_error: false, duration_ms: Some(220) },
ToolUse { id: "toolu_13".into(), tool_name: "read_file".into(), input: r#"{ "path": "package.json" }"#.into(), result: "\"react-router-dom\": \"^6.22.0\"".into(), is_error: false, duration_ms: Some(30) },
] },
SessionTurn { kind: SessionTurnKind::Assistant, content: Some("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 `Route.LoaderArgs` type\n4. Update any `useLoaderData` calls to be type-safe\n\nLet me make these changes.".into()), created_at: None, tools: vec![] },
],

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
../../openapi/arc-api.yaml

View file

@ -719,17 +719,17 @@ paths:
operationId: listSessions
tags: [Sessions]
summary: List Sessions
description: Returns sessions grouped by recency (e.g. "Today", "Yesterday"). Each group contains a flat list of session summaries.
description: Returns sessions ordered by recency (newest first).
parameters:
- $ref: "#/components/parameters/PageLimit"
- $ref: "#/components/parameters/PageOffset"
responses:
"200":
description: Paginated list of session groups
description: Paginated list of sessions
content:
application/json:
schema:
$ref: "#/components/schemas/PaginatedSessionGroupList"
$ref: "#/components/schemas/PaginatedSessionList"
post:
operationId: createSession
tags: [Sessions]
@ -1120,7 +1120,7 @@ components:
meta:
$ref: "#/components/schemas/PaginationMeta"
PaginatedSessionGroupList:
PaginatedSessionList:
type: object
required:
- data
@ -1129,7 +1129,7 @@ components:
data:
type: array
items:
$ref: "#/components/schemas/SessionGroup"
$ref: "#/components/schemas/SessionListItem"
meta:
$ref: "#/components/schemas/PaginationMeta"
@ -1478,25 +1478,39 @@ components:
type: string
ToolUse:
description: A single tool invocation with its arguments and result.
description: A single tool invocation with its input, result, and execution metadata.
type: object
required:
- id
- tool_name
- args
- input
- result
- is_error
properties:
id:
type: string
description: Unique identifier for this tool invocation. Enables correlation in parallel tool use.
example: toolu_01A09q90qw90lq917835lq9
tool_name:
type: string
description: Name of the tool that was invoked.
example: read_file
args:
input:
type: string
description: JSON-encoded arguments passed to the tool.
description: JSON-encoded input passed to the tool.
example: '{ "path": "src/routes/auth.ts" }'
result:
type: string
description: Output returned by the tool.
description: Output returned by the tool. Contains the error message when is_error is true.
example: 'import { Router } from "express";'
is_error:
type: boolean
description: Whether the tool invocation failed. When true, the result field contains the error message.
example: false
duration_ms:
type: integer
description: Wall-clock execution time of the tool invocation in milliseconds.
example: 142
StageTurn:
type: object
@ -2064,22 +2078,6 @@ components:
description: ISO 8601 timestamp when the session was created.
example: "2026-03-06T14:30:00Z"
SessionGroup:
description: A group of sessions sharing a time-based label (e.g. "Today", "Yesterday").
type: object
required:
- label
- sessions
properties:
label:
type: string
description: Human-readable group heading.
example: Today
sessions:
type: array
description: Sessions belonging to this group, ordered by recency.
items:
$ref: "#/components/schemas/SessionListItem"
SessionTurn:
description: A single turn in a session conversation — a user message, assistant response, or tool invocation block.

View file

@ -49,7 +49,7 @@ models/paginated-run-list.ts
models/paginated-run-stage-list.ts
models/paginated-run-verification-list.ts
models/paginated-saved-query-list.ts
models/paginated-session-group-list.ts
models/paginated-session-list.ts
models/paginated-stage-turn-list.ts
models/paginated-verification-category-list.ts
models/paginated-workflow-list.ts
@ -75,8 +75,8 @@ models/run-verification.ts
models/save-query-request.ts
models/saved-query.ts
models/send-message-request.ts
models/send-session-message200-response.ts
models/session-detail.ts
models/session-group.ts
models/session-list-item.ts
models/session-turn.ts
models/setting-field-type.ts

View file

@ -28,20 +28,20 @@ import type { CreateSessionResponse } from '../models';
// @ts-ignore
import type { ErrorResponse } from '../models';
// @ts-ignore
import type { PaginatedSessionGroupList } from '../models';
import type { PaginatedSessionList } from '../models';
// @ts-ignore
import type { SendMessageRequest } from '../models';
// @ts-ignore
import type { SessionDetail } from '../models';
import type { SendSessionMessage200Response } from '../models';
// @ts-ignore
import type { SteerRun200Response } from '../models';
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.
@ -76,7 +76,7 @@ export const SessionsApiAxiosParamCreator = function (configuration?: Configurat
};
},
/**
*
* Returns sessions ordered by recency (newest first).
* @summary List Sessions
* @param {number} [pageLimit]
* @param {number} [pageOffset]
@ -116,9 +116,9 @@ export const SessionsApiAxiosParamCreator = function (configuration?: Configurat
};
},
/**
*
* Returns the full session detail including all conversation turns.
* @summary Retrieve Session
* @param {string} id
* @param {string} id Unique session identifier.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
@ -150,9 +150,9 @@ export const SessionsApiAxiosParamCreator = function (configuration?: Configurat
};
},
/**
*
* 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
* @param {string} id Unique session identifier.
* @param {SendMessageRequest} sendMessageRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
@ -189,9 +189,9 @@ export const SessionsApiAxiosParamCreator = function (configuration?: Configurat
};
},
/**
*
* Opens a server-sent event (SSE) stream for real-time session updates. Events include new assistant turns, tool invocations, and completion signals.
* @summary Stream Session Events
* @param {string} id
* @param {string} id Unique session identifier.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
@ -232,7 +232,7 @@ 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.
@ -245,23 +245,23 @@ export const SessionsApiFp = function(configuration?: Configuration) {
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]
* @param {number} [pageOffset]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async listSessions(pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<PaginatedSessionGroupList>> {
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
* @param {string} id Unique session identifier.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
@ -272,23 +272,23 @@ export const SessionsApiFp = function(configuration?: Configuration) {
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
* @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<SteerRun200Response>> {
async sendSessionMessage(id: string, sendMessageRequest: SendMessageRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SendSessionMessage200Response>> {
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. Events include new assistant turns, tool invocations, and completion signals.
* @summary Stream Session Events
* @param {string} id
* @param {string} id Unique session identifier.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
@ -308,7 +308,7 @@ export const SessionsApiFactory = function (configuration?: Configuration, baseP
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.
@ -318,20 +318,20 @@ export const SessionsApiFactory = function (configuration?: Configuration, baseP
return localVarFp.createSession(createSessionRequest, options).then((request) => request(axios, basePath));
},
/**
*
* Returns sessions ordered by recency (newest first).
* @summary List Sessions
* @param {number} [pageLimit]
* @param {number} [pageOffset]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
listSessions(pageLimit?: number, pageOffset?: number, options?: RawAxiosRequestConfig): AxiosPromise<PaginatedSessionGroupList> {
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
* @param {string} id Unique session identifier.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
@ -339,20 +339,20 @@ export const SessionsApiFactory = function (configuration?: Configuration, baseP
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
* @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<SteerRun200Response> {
sendSessionMessage(id: string, sendMessageRequest: SendMessageRequest, options?: RawAxiosRequestConfig): AxiosPromise<SendSessionMessage200Response> {
return localVarFp.sendSessionMessage(id, sendMessageRequest, options).then((request) => request(axios, basePath));
},
/**
*
* Opens a server-sent event (SSE) stream for real-time session updates. Events include new assistant turns, tool invocations, and completion signals.
* @summary Stream Session Events
* @param {string} id
* @param {string} id Unique session identifier.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
@ -367,7 +367,7 @@ export const SessionsApiFactory = function (configuration?: Configuration, baseP
*/
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.
@ -378,7 +378,7 @@ export class SessionsApi extends BaseAPI {
}
/**
*
* Returns sessions ordered by recency (newest first).
* @summary List Sessions
* @param {number} [pageLimit]
* @param {number} [pageOffset]
@ -390,9 +390,9 @@ export class SessionsApi extends BaseAPI {
}
/**
*
* Returns the full session detail including all conversation turns.
* @summary Retrieve Session
* @param {string} id
* @param {string} id Unique session identifier.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
@ -401,9 +401,9 @@ export class SessionsApi extends BaseAPI {
}
/**
*
* 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
* @param {string} id Unique session identifier.
* @param {SendMessageRequest} sendMessageRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
@ -413,9 +413,9 @@ export class SessionsApi extends BaseAPI {
}
/**
*
* Opens a server-sent event (SSE) stream for real-time session updates. Events include new assistant turns, tool invocations, and completion signals.
* @summary Stream Session Events
* @param {string} id
* @param {string} id Unique session identifier.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/

View file

@ -14,8 +14,17 @@
/**
* Request body for starting a new session.
*/
export interface CreateSessionRequest {
/**
* The initial user message to start the session.
*/
'prompt': string;
/**
* LLM model to use. If omitted, the server default is used.
*/
'model'?: string;
}

View file

@ -14,8 +14,17 @@
/**
* Response returned after successfully creating a session.
*/
export interface CreateSessionResponse {
/**
* Unique identifier for the newly created session.
*/
'id': string;
/**
* ISO 8601 timestamp when the session was created.
*/
'created_at': string;
}

View file

@ -30,7 +30,7 @@ export * from './paginated-run-list';
export * from './paginated-run-stage-list';
export * from './paginated-run-verification-list';
export * from './paginated-saved-query-list';
export * from './paginated-session-group-list';
export * from './paginated-session-list';
export * from './paginated-stage-turn-list';
export * from './paginated-verification-category-list';
export * from './paginated-workflow-list';
@ -56,8 +56,8 @@ export * from './run-verification-control';
export * from './save-query-request';
export * from './saved-query';
export * from './send-message-request';
export * from './send-session-message200-response';
export * from './session-detail';
export * from './session-group';
export * from './session-list-item';
export * from './session-turn';
export * from './setting-field';

View file

@ -0,0 +1,27 @@
/* tslint:disable */
/* eslint-disable */
/**
* Arc Run API
* HTTP API for managing Arc 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';
export interface PaginatedSessionList {
'data': Array<SessionListItem>;
'meta': PaginationMeta;
}

View file

@ -14,7 +14,13 @@
/**
* Request body for sending a follow-up message in an existing session.
*/
export interface SendMessageRequest {
/**
* The user message text.
*/
'content': string;
}

View file

@ -0,0 +1,23 @@
/* tslint:disable */
/* eslint-disable */
/**
* Arc Run API
* HTTP API for managing Arc 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.
*/
export interface SendSessionMessage200Response {
/**
* Whether the message was accepted for processing.
*/
'accepted': boolean;
}

View file

@ -17,12 +17,33 @@
// @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;
/**
* The LLM model used for this session.
*/
'model': string;
/**
* ISO 8601 timestamp when the session was created.
*/
'created_at': string;
/**
* ISO 8601 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

@ -17,8 +17,17 @@
// @ts-ignore
import type { SessionListItem } from './session-list-item';
/**
* A group of sessions sharing a time-based label (e.g. \"Today\", \"Yesterday\").
*/
export interface SessionGroup {
/**
* Human-readable group heading.
*/
'label': string;
/**
* Sessions belonging to this group, ordered by recency.
*/
'sessions': Array<SessionListItem>;
}

View file

@ -14,9 +14,21 @@
/**
* Summary of a session shown in list views.
*/
export interface SessionListItem {
/**
* Unique session identifier.
*/
'id': string;
/**
* Short title summarizing the session topic.
*/
'title': string;
/**
* ISO 8601 timestamp when the session was created.
*/
'created_at': string;
}

View file

@ -17,10 +17,25 @@
// @ts-ignore
import type { ToolUse } from './tool-use';
/**
* A single turn in a session conversation a user message, assistant response, or tool invocation block.
*/
export interface SessionTurn {
/**
* The type of turn.
*/
'kind': SessionTurnKindEnum;
/**
* Text content of the turn. Present for user and assistant turns, absent for tool turns.
*/
'content'?: string;
/**
* ISO 8601 timestamp when the turn was created. Typically present for user turns.
*/
'created_at'?: string;
/**
* Tool invocations for this turn. Present only when kind is \"tool\".
*/
'tools'?: Array<ToolUse>;
}

View file

@ -14,9 +14,33 @@
/**
* A single tool invocation with its input, result, and execution metadata.
*/
export interface ToolUse {
/**
* Unique identifier for this tool invocation. Enables correlation in parallel tool use.
*/
'id': string;
/**
* Name of the tool that was invoked.
*/
'tool_name': string;
'args': string;
/**
* JSON-encoded input passed to the tool.
*/
'input': string;
/**
* Output returned by the tool. Contains the error message when is_error is true.
*/
'result': string;
/**
* Whether the tool invocation failed. When true, the result field contains the error message.
*/
'is_error': boolean;
/**
* Wall-clock execution time of the tool invocation in milliseconds.
*/
'duration_ms'?: number;
}