diff --git a/Cargo.lock b/Cargo.lock index 4b2961547..06a4843ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,6 +142,7 @@ dependencies = [ "arc-workflows", "axum", "base64", + "chrono", "clap", "dirs", "http-body-util", @@ -308,6 +309,7 @@ dependencies = [ name = "arc-types" version = "0.1.0" dependencies = [ + "chrono", "prettyplease", "schemars 0.8.22", "serde", diff --git a/apps/arc-web/app/routes/session-detail.tsx b/apps/arc-web/app/routes/session-detail.tsx index 440e86527..cf8609283 100644 --- a/apps/arc-web/app/routes/session-detail.tsx +++ b/apps/arc-web/app/routes/session-detail.tsx @@ -31,21 +31,25 @@ export async function loader({ request, params }: Route.LoaderArgs) { model: apiSession.model, created_at: apiSession.created_at, updated_at: apiSession.updated_at, - turns: apiSession.turns.map((t) => { - if (t.kind === "tool" && t.tools) { - return { - kind: "tool" as const, - 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, - })), - }; + 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 }; } - return { kind: t.kind as "user" | "assistant", content: t.content ?? "", created_at: t.created_at }; }), }; const sessionGroups = groupSessionsByDate( diff --git a/crates/arc-api/Cargo.toml b/crates/arc-api/Cargo.toml index bb0d0928e..835dc34b9 100644 --- a/crates/arc-api/Cargo.toml +++ b/crates/arc-api/Cargo.toml @@ -15,6 +15,7 @@ arc-llm = { path = "../arc-llm" } arc-util = { path = "../arc-util" } arc-db = { path = "../arc-db" } arc-types = { path = "../arc-types" } +chrono.workspace = true axum = "0.8" dirs.workspace = true sqlx.workspace = true diff --git a/crates/arc-api/src/demo/mod.rs b/crates/arc-api/src/demo/mod.rs index a0f8ee553..83f71d3ca 100644 --- a/crates/arc-api/src/demo/mod.rs +++ b/crates/arc-api/src/demo/mod.rs @@ -524,7 +524,7 @@ pub async fn send_message_stub( State(_state): State>, Path(_id): Path, ) -> Response { - (StatusCode::OK, Json(serde_json::json!({"accepted": true}))).into_response() + (StatusCode::ACCEPTED, Json(serde_json::json!({"accepted": true}))).into_response() } pub async fn session_events_stub( @@ -2366,48 +2366,61 @@ mod retros { mod sessions { use arc_types::*; + use chrono::{DateTime, Utc}; + + fn ts(s: &str) -> DateTime { + s.parse().unwrap() + } pub fn list_items() -> Vec { vec![ SessionListItem { id: "s1".into(), title: "Add rate limiting to auth endpoints".into(), - created_at: "2026-03-06T14:30:00Z".into(), + created_at: ts("2026-03-06T14:30:00Z"), + updated_at: ts("2026-03-06T15:45:00Z"), }, SessionListItem { id: "s2".into(), title: "Fix config parsing for nested values".into(), - created_at: "2026-03-06T12:30:00Z".into(), + created_at: ts("2026-03-06T12:30:00Z"), + updated_at: ts("2026-03-06T13:15:00Z"), }, SessionListItem { id: "s3".into(), title: "Migrate to React Router v7".into(), - created_at: "2026-03-05T10:00:00Z".into(), + created_at: ts("2026-03-05T10:00:00Z"), + updated_at: ts("2026-03-05T11:30:00Z"), }, SessionListItem { id: "s4".into(), title: "Add dark mode toggle".into(), - created_at: "2026-03-05T09:00:00Z".into(), + created_at: ts("2026-03-05T09:00:00Z"), + updated_at: ts("2026-03-05T09:45:00Z"), }, SessionListItem { id: "s5".into(), title: "Update OpenAPI spec for v3".into(), - created_at: "2026-03-05T08:00:00Z".into(), + created_at: ts("2026-03-05T08:00:00Z"), + updated_at: ts("2026-03-05T08:30:00Z"), }, SessionListItem { id: "s6".into(), title: "Terraform module for Redis cluster".into(), - created_at: "2026-03-03T15:00:00Z".into(), + created_at: ts("2026-03-03T15:00:00Z"), + updated_at: ts("2026-03-03T16:00:00Z"), }, SessionListItem { id: "s7".into(), title: "Add pipeline event types".into(), - created_at: "2026-03-01T11:00:00Z".into(), + created_at: ts("2026-03-01T11:00:00Z"), + updated_at: ts("2026-03-01T12:00:00Z"), }, SessionListItem { id: "s8".into(), title: "Implement webhook retry logic".into(), - created_at: "2026-02-28T09:00:00Z".into(), + created_at: ts("2026-02-28T09:00:00Z"), + updated_at: ts("2026-02-28T10:00:00Z"), }, ] } @@ -2415,50 +2428,50 @@ mod sessions { pub fn detail(id: &str) -> Option { match id { "s1" => Some(SessionDetail { - id: "s1".into(), title: "Add rate limiting to auth endpoints".into(), model: "Opus 4.6".into(), created_at: "2026-03-06T14:30:00Z".into(), updated_at: "2026-03-06T15:45:00Z".into(), + id: "s1".into(), title: "Add rate limiting to auth endpoints".into(), model: "Opus 4.6".into(), created_at: ts("2026-03-06T14:30:00Z"), updated_at: ts("2026-03-06T15:45:00Z"), turns: vec![ - 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![ + SessionTurn::UserTurn(UserTurn { kind: UserTurnKind::User, 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.".into(), created_at: ts("2026-02-28T10:00:00Z") }), + SessionTurn::AssistantTurn(AssistantTurn { kind: AssistantTurnKind::Assistant, content: "I'll implement sliding window rate limiting using Redis. Let me first look at the existing auth routes and middleware setup.".into() }), + SessionTurn::ToolTurn(ToolTurn { kind: ToolTurnKind::Tool, tools: vec![ 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![ + ] }), + SessionTurn::AssistantTurn(AssistantTurn { kind: AssistantTurnKind::Assistant, content: "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() }), + SessionTurn::ToolTurn(ToolTurn { kind: ToolTurnKind::Tool, tools: vec![ 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![] }, + ] }), + SessionTurn::AssistantTurn(AssistantTurn { kind: AssistantTurnKind::Assistant, content: "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() }), ], }), "s2" => Some(SessionDetail { - id: "s2".into(), title: "Fix config parsing for nested values".into(), model: "Sonnet 4.6".into(), created_at: "2026-03-06T12:30:00Z".into(), updated_at: "2026-03-06T13:15:00Z".into(), + id: "s2".into(), title: "Fix config parsing for nested values".into(), model: "Sonnet 4.6".into(), created_at: ts("2026-03-06T12:30:00Z"), updated_at: ts("2026-03-06T13:15:00Z"), turns: vec![ - 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![ + SessionTurn::UserTurn(UserTurn { kind: UserTurnKind::User, content: "The CLI crashes when parsing nested TOML config values like [database.connection]. Can you debug and fix this?".into(), created_at: ts("2026-02-28T10:00:00Z") }), + SessionTurn::AssistantTurn(AssistantTurn { kind: AssistantTurnKind::Assistant, content: "Let me look at the config parser to understand how nested keys are handled.".into() }), + SessionTurn::ToolTurn(ToolTurn { kind: ToolTurnKind::Tool, tools: vec![ 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 {\n const result: Record = {};\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` 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![ + ] }), + SessionTurn::AssistantTurn(AssistantTurn { kind: AssistantTurnKind::Assistant, content: "Found the issue. The parser uses a flat `Record` 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() }), + SessionTurn::ToolTurn(ToolTurn { kind: ToolTurnKind::Tool, tools: vec![ 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![] }, + ] }), + SessionTurn::AssistantTurn(AssistantTurn { kind: AssistantTurnKind::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.".into() }), ], }), "s3" => Some(SessionDetail { - id: "s3".into(), title: "Migrate to React Router v7".into(), model: "Opus 4.6".into(), created_at: "2026-03-05T10:00:00Z".into(), updated_at: "2026-03-05T11:30:00Z".into(), + id: "s3".into(), title: "Migrate to React Router v7".into(), model: "Opus 4.6".into(), created_at: ts("2026-03-05T10:00:00Z"), updated_at: ts("2026-03-05T11:30:00Z"), turns: vec![ - 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![ + SessionTurn::UserTurn(UserTurn { kind: UserTurnKind::User, content: "Help me migrate our app from React Router v6 to v7. We're using createBrowserRouter with data loaders.".into(), created_at: ts("2026-02-26T10:00:00Z") }), + SessionTurn::AssistantTurn(AssistantTurn { kind: AssistantTurnKind::Assistant, content: "I'll audit your current router setup and identify what needs to change for v7. Let me scan the codebase.".into() }), + SessionTurn::ToolTurn(ToolTurn { kind: ToolTurnKind::Tool, tools: vec![ 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![] }, + ] }), + SessionTurn::AssistantTurn(AssistantTurn { kind: AssistantTurnKind::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.".into() }), ], }), _ => None, diff --git a/crates/arc-types/Cargo.toml b/crates/arc-types/Cargo.toml index 2e31d253e..da4aa03cb 100644 --- a/crates/arc-types/Cargo.toml +++ b/crates/arc-types/Cargo.toml @@ -9,6 +9,7 @@ description = "Generated Rust types from the Arc API OpenAPI spec" doctest = false [dependencies] +chrono = { workspace = true, features = ["serde"] } serde.workspace = true serde_json.workspace = true diff --git a/openapi/arc-api.yaml b/openapi/arc-api.yaml index 74fd8e694..3900a0425 100644 --- a/openapi/arc-api.yaml +++ b/openapi/arc-api.yaml @@ -763,13 +763,7 @@ paths: summary: Retrieve Session description: Returns the full session detail including all conversation turns. parameters: - - name: id - in: path - required: true - description: Unique session identifier. - schema: - type: string - example: s1 + - $ref: "#/components/parameters/SessionId" responses: "200": description: Session detail @@ -791,13 +785,7 @@ paths: summary: Send Session Message description: Append a user message to an existing session. The server will process it and produce assistant and tool turns asynchronously via the event stream. parameters: - - name: id - in: path - required: true - description: Unique session identifier. - schema: - type: string - example: s1 + - $ref: "#/components/parameters/SessionId" requestBody: required: true content: @@ -805,19 +793,12 @@ paths: schema: $ref: "#/components/schemas/SendMessageRequest" responses: - "200": - description: Message accepted + "202": + description: Message accepted for processing content: application/json: schema: - type: object - properties: - accepted: - type: boolean - description: Whether the message was accepted for processing. - example: true - required: - - accepted + $ref: "#/components/schemas/SendMessageResponse" "404": description: Session not found content: @@ -832,13 +813,7 @@ paths: summary: Stream Session Events description: Opens a server-sent event (SSE) stream for real-time session updates. Events include new assistant turns, tool invocations, and completion signals. parameters: - - name: id - in: path - required: true - description: Unique session identifier. - schema: - type: string - example: s1 + - $ref: "#/components/parameters/SessionId" responses: "200": description: Server-sent event stream @@ -1080,6 +1055,15 @@ components: schema: type: string + SessionId: + name: id + in: path + required: true + description: Unique session identifier. + schema: + type: string + example: s1 + PageLimit: name: page[limit] in: query @@ -2091,6 +2075,7 @@ components: - id - title - created_at + - updated_at properties: id: type: string @@ -2102,35 +2087,78 @@ components: example: Add rate limiting to auth endpoints created_at: type: string - description: ISO 8601 timestamp when the session was created. + format: date-time + description: Timestamp when the session was created. example: "2026-03-06T14:30:00Z" + updated_at: + type: string + format: date-time + description: Timestamp when the session was last updated (e.g. new turn added). + example: "2026-03-06T15:45:00Z" SessionTurn: description: A single turn in a session conversation — a user message, assistant response, or tool invocation block. + discriminator: + propertyName: kind + mapping: + user: "#/components/schemas/UserTurn" + assistant: "#/components/schemas/AssistantTurn" + tool: "#/components/schemas/ToolTurn" + oneOf: + - $ref: "#/components/schemas/UserTurn" + - $ref: "#/components/schemas/AssistantTurn" + - $ref: "#/components/schemas/ToolTurn" + + UserTurn: + description: A user message turn. type: object required: - kind + - content + - created_at properties: kind: type: string - description: The type of turn. - enum: - - user - - assistant - - tool - example: user + enum: [user] content: type: string - description: Text content of the turn. Present for user and assistant turns, absent for tool turns. + description: Text content of the user message. example: Add rate limiting to the auth endpoints using a sliding window approach with Redis. created_at: type: string - description: ISO 8601 timestamp when the turn was created. Typically present for user turns. + format: date-time + description: Timestamp when the turn was created. example: "2026-02-28T10:00:00Z" + + AssistantTurn: + description: An assistant response turn. + type: object + required: + - kind + - content + properties: + kind: + type: string + enum: [assistant] + content: + type: string + description: Text content of the assistant response. + example: I'll implement sliding window rate limiting using Redis. + + ToolTurn: + description: A tool invocation turn. + type: object + required: + - kind + - tools + properties: + kind: + type: string + enum: [tool] tools: type: array - description: Tool invocations for this turn. Present only when kind is "tool". + description: Tool invocations for this turn. items: $ref: "#/components/schemas/ToolUse" @@ -2159,11 +2187,13 @@ components: example: Opus 4.6 created_at: type: string - description: ISO 8601 timestamp when the session was created. + format: date-time + description: Timestamp when the session was created. example: "2026-03-06T14:30:00Z" updated_at: type: string - description: ISO 8601 timestamp when the session was last updated (e.g. new turn added). + format: date-time + description: Timestamp when the session was last updated (e.g. new turn added). example: "2026-03-06T15:45:00Z" turns: type: array @@ -2175,9 +2205,9 @@ components: description: Request body for starting a new session. type: object required: - - prompt + - content properties: - prompt: + content: type: string description: The initial user message to start the session. example: Add rate limiting to the auth endpoints using a sliding window approach with Redis, 10 requests per minute per IP. @@ -2199,7 +2229,8 @@ components: example: s42 created_at: type: string - description: ISO 8601 timestamp when the session was created. + format: date-time + description: Timestamp when the session was created. example: "2026-03-06T16:00:00Z" SendMessageRequest: @@ -2213,6 +2244,17 @@ components: description: The user message text. example: Can you also add a bypass for internal health-check IPs? + SendMessageResponse: + description: Acknowledgement that the message was accepted for asynchronous processing. + type: object + required: + - accepted + properties: + accepted: + type: boolean + description: Whether the message was accepted for processing. + example: true + # ── Insights Schemas ──────────────────────────────────────────────── SavedQuery: diff --git a/packages/arc-api-client/src/.openapi-generator/FILES b/packages/arc-api-client/src/.openapi-generator/FILES index 79c109885..bbd7cd112 100644 --- a/packages/arc-api-client/src/.openapi-generator/FILES +++ b/packages/arc-api-client/src/.openapi-generator/FILES @@ -19,6 +19,7 @@ index.ts models/aggregate-usage.ts models/api-question-option.ts models/api-question.ts +models/assistant-turn.ts models/branch.ts models/cancel-run200-response.ts models/check-run-status.ts @@ -75,7 +76,7 @@ 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/send-message-response.ts models/session-detail.ts models/session-list-item.ts models/session-turn.ts @@ -92,11 +93,13 @@ models/steer-request.ts models/steer-run200-response.ts models/submit-answer-request.ts models/submit-answer-response.ts +models/tool-turn.ts models/tool-use.ts models/usage-by-model.ts models/usage-stage.ts models/usage-totals.ts models/user-response.ts +models/user-turn.ts models/verification-category.ts models/verification-control.ts models/verification-detail-response.ts diff --git a/packages/arc-api-client/src/api/sessions-api.ts b/packages/arc-api-client/src/api/sessions-api.ts index 16c93466c..750c5a438 100644 --- a/packages/arc-api-client/src/api/sessions-api.ts +++ b/packages/arc-api-client/src/api/sessions-api.ts @@ -32,7 +32,7 @@ import type { PaginatedSessionList } from '../models'; // @ts-ignore import type { SendMessageRequest } from '../models'; // @ts-ignore -import type { SendSessionMessage200Response } from '../models'; +import type { SendMessageResponse } from '../models'; // @ts-ignore import type { SessionDetail } from '../models'; /** @@ -314,7 +314,7 @@ export const SessionsApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async sendSessionMessage(id: string, sendMessageRequest: SendMessageRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async sendSessionMessage(id: string, sendMessageRequest: SendMessageRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.sendSessionMessage(id, sendMessageRequest, options); const localVarOperationServerIndex = configuration?.serverIndex ?? 0; const localVarOperationServerBasePath = operationServerMap['SessionsApi.sendSessionMessage']?.[localVarOperationServerIndex]?.url; @@ -381,7 +381,7 @@ export const SessionsApiFactory = function (configuration?: Configuration, baseP * @param {*} [options] Override http request option. * @throws {RequiredError} */ - sendSessionMessage(id: string, sendMessageRequest: SendMessageRequest, options?: RawAxiosRequestConfig): AxiosPromise { + sendSessionMessage(id: string, sendMessageRequest: SendMessageRequest, options?: RawAxiosRequestConfig): AxiosPromise { return localVarFp.sendSessionMessage(id, sendMessageRequest, options).then((request) => request(axios, basePath)); }, /** diff --git a/packages/arc-api-client/src/models/assistant-turn.ts b/packages/arc-api-client/src/models/assistant-turn.ts new file mode 100644 index 000000000..ba78a039e --- /dev/null +++ b/packages/arc-api-client/src/models/assistant-turn.ts @@ -0,0 +1,34 @@ +/* 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. + */ + + + +/** + * An assistant response turn. + */ +export interface AssistantTurn { + 'kind': AssistantTurnKindEnum; + /** + * Text content of the assistant response. + */ + 'content': string; +} + +export const AssistantTurnKindEnum = { + ASSISTANT: 'assistant' +} as const; + +export type AssistantTurnKindEnum = typeof AssistantTurnKindEnum[keyof typeof AssistantTurnKindEnum]; + + diff --git a/packages/arc-api-client/src/models/create-session-request.ts b/packages/arc-api-client/src/models/create-session-request.ts index 2a1028542..a879efcdb 100644 --- a/packages/arc-api-client/src/models/create-session-request.ts +++ b/packages/arc-api-client/src/models/create-session-request.ts @@ -21,7 +21,7 @@ export interface CreateSessionRequest { /** * The initial user message to start the session. */ - 'prompt': string; + 'content': string; /** * LLM model to use. If omitted, the server default is used. */ diff --git a/packages/arc-api-client/src/models/create-session-response.ts b/packages/arc-api-client/src/models/create-session-response.ts index 1d0938e67..aa4afd22e 100644 --- a/packages/arc-api-client/src/models/create-session-response.ts +++ b/packages/arc-api-client/src/models/create-session-response.ts @@ -23,7 +23,7 @@ export interface CreateSessionResponse { */ 'id': string; /** - * ISO 8601 timestamp when the session was created. + * Timestamp when the session was created. */ 'created_at': string; } diff --git a/packages/arc-api-client/src/models/index.ts b/packages/arc-api-client/src/models/index.ts index 86c20dfc7..aac00499e 100644 --- a/packages/arc-api-client/src/models/index.ts +++ b/packages/arc-api-client/src/models/index.ts @@ -1,6 +1,7 @@ export * from './aggregate-usage'; export * from './api-question'; export * from './api-question-option'; +export * from './assistant-turn'; export * from './branch'; export * from './cancel-run200-response'; export * from './check-run'; @@ -56,7 +57,7 @@ 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 './send-message-response'; export * from './session-detail'; export * from './session-list-item'; export * from './session-turn'; @@ -73,11 +74,13 @@ export * from './steer-request'; export * from './steer-run200-response'; export * from './submit-answer-request'; export * from './submit-answer-response'; +export * from './tool-turn'; export * from './tool-use'; export * from './usage-by-model'; export * from './usage-stage'; export * from './usage-totals'; export * from './user-response'; +export * from './user-turn'; export * from './verification-category'; export * from './verification-control'; export * from './verification-detail-response'; diff --git a/packages/arc-api-client/src/models/send-session-message200-response.ts b/packages/arc-api-client/src/models/send-message-response.ts similarity index 77% rename from packages/arc-api-client/src/models/send-session-message200-response.ts rename to packages/arc-api-client/src/models/send-message-response.ts index f59e634d4..3c0029a2e 100644 --- a/packages/arc-api-client/src/models/send-session-message200-response.ts +++ b/packages/arc-api-client/src/models/send-message-response.ts @@ -14,7 +14,10 @@ -export interface SendSessionMessage200Response { +/** + * Acknowledgement that the message was accepted for asynchronous processing. + */ +export interface SendMessageResponse { /** * Whether the message was accepted for processing. */ diff --git a/packages/arc-api-client/src/models/session-detail.ts b/packages/arc-api-client/src/models/session-detail.ts index 66fbdd085..b3f2187ab 100644 --- a/packages/arc-api-client/src/models/session-detail.ts +++ b/packages/arc-api-client/src/models/session-detail.ts @@ -34,11 +34,11 @@ export interface SessionDetail { */ 'model': string; /** - * ISO 8601 timestamp when the session was created. + * Timestamp when the session was created. */ 'created_at': string; /** - * ISO 8601 timestamp when the session was last updated (e.g. new turn added). + * Timestamp when the session was last updated (e.g. new turn added). */ 'updated_at': string; /** diff --git a/packages/arc-api-client/src/models/session-list-item.ts b/packages/arc-api-client/src/models/session-list-item.ts index 26dfcaa5f..18aca7551 100644 --- a/packages/arc-api-client/src/models/session-list-item.ts +++ b/packages/arc-api-client/src/models/session-list-item.ts @@ -27,8 +27,12 @@ export interface SessionListItem { */ 'title': string; /** - * ISO 8601 timestamp when the session was created. + * Timestamp when the session was created. */ 'created_at': string; + /** + * Timestamp when the session was last updated (e.g. new turn added). + */ + 'updated_at': string; } diff --git a/packages/arc-api-client/src/models/session-turn.ts b/packages/arc-api-client/src/models/session-turn.ts index 20d21f64f..621d1fbc8 100644 --- a/packages/arc-api-client/src/models/session-turn.ts +++ b/packages/arc-api-client/src/models/session-turn.ts @@ -13,38 +13,23 @@ */ +// 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 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; -} - -export const SessionTurnKindEnum = { - USER: 'user', - ASSISTANT: 'assistant', - TOOL: 'tool' -} as const; - -export type SessionTurnKindEnum = typeof SessionTurnKindEnum[keyof typeof SessionTurnKindEnum]; +export type SessionTurn = { kind: 'assistant' } & AssistantTurn | { kind: 'tool' } & ToolTurn | { kind: 'user' } & UserTurn; diff --git a/packages/arc-api-client/src/models/tool-turn.ts b/packages/arc-api-client/src/models/tool-turn.ts new file mode 100644 index 000000000..0fb31f42f --- /dev/null +++ b/packages/arc-api-client/src/models/tool-turn.ts @@ -0,0 +1,37 @@ +/* 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 { ToolUse } from './tool-use'; + +/** + * A tool invocation turn. + */ +export interface ToolTurn { + 'kind': ToolTurnKindEnum; + /** + * Tool invocations for this turn. + */ + 'tools': Array; +} + +export const ToolTurnKindEnum = { + TOOL: 'tool' +} as const; + +export type ToolTurnKindEnum = typeof ToolTurnKindEnum[keyof typeof ToolTurnKindEnum]; + + diff --git a/packages/arc-api-client/src/models/user-turn.ts b/packages/arc-api-client/src/models/user-turn.ts new file mode 100644 index 000000000..732ea2170 --- /dev/null +++ b/packages/arc-api-client/src/models/user-turn.ts @@ -0,0 +1,38 @@ +/* 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. + */ + + + +/** + * 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]; + +