# Agent Run Input Protocol

> **NOTE**: This document defines the RunAgentInput data structure for `POST /api/v1/agent/runs`.

## Version

- **Current**: `1.0`
- **Status**: Active

---

## Overview

`POST /api/v1/agent/runs` accepts `RunAgentInput` as request body to initiate an agent execution.

---

## RunAgentInput Schema

```typescript
interface RunAgentInput {
  threadId: string;      // 必须是有效 UUID
  runId: string;         // 最大 128 字符
  parentRunId?: string;
  state?: any;
  messages: Message[];   // 最多 200 条
  tools?: Tool[];
  context?: Context[];
  forwardedProps?: any;
}
```

### Required Fields

| Field | Type | Constraints |
|-------|------|-------------|
| `threadId` | string | 必须为有效 UUID |
| `runId` | string | 最大 128 字符 |
| `messages` | array | 最多 200 条，**必须恰好包含 1 条 user message** |
| `state` | any | - |
| `tools` | array | 可选 |
| `context` | array | 可选 |
| `forwardedProps` | any | - |

---

## Message Types

### UserMessage

```typescript
interface UserMessage {
  id: string;
  role: "user";
  content: string | ContentBlock[];
  name?: string;
  encryptedValue?: string;
}

type ContentBlock = TextContentBlock | BinaryContentBlock;

interface TextContentBlock {
  type: "text";
  text: string;
}

interface BinaryContentBlock {
  type: "binary";
  mimeType: string;      // 必须为 image/* 类型
  id?: string;
  url?: string;          // 必须是有效的 signed URL
  data?: string;         // 不允许使用 data 字段
  filename?: string;
}
```

### AssistantMessage

```typescript
interface AssistantMessage {
  id: string;
  role: "assistant";
  content?: string | null;
  name?: string | null;
  toolCalls?: ToolCall[];
  encryptedValue?: string | null;
}

interface ToolCall {
  id: string;
  type: "function";
  function: {
    name: string;
    arguments: string;
  };
  encryptedValue?: string | null;
}
```

### SystemMessage

```typescript
interface SystemMessage {
  id: string;
  role: "system";
  content: string;
  name?: string | null;
  encryptedValue?: string | null;
}
```

### ToolMessage

```typescript
interface ToolMessage {
  id: string;
  role: "tool";
  content: string;
  toolCallId: string;
  error?: string | null;
  encryptedValue?: string | null;
}
```

### Other Message Types

- **DeveloperMessage**: `role: "developer"`
- **ReasoningMessage**: `role: "reasoning"`
- **ActivityMessage**: `role: "activity"` (for progress updates)

---

## Tool Schema

```typescript
interface Tool {
  name: string;
  description: string;
  parameters: object;   // JSON Schema 格式
}
```

### Example

```json
{
  "name": "get_weather",
  "description": "Get current weather for a location",
  "parameters": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "City name"
      },
      "unit": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"]
      }
    },
    "required": ["location"]
  }
}
```

### Frontend Tool Channel Semantics

- `RunAgentInput.tools` 用于前端工具通道（供前端/HITL 审批链路透传与回放）。
- 当前后端 AgentScope 执行阶段**不**使用该字段来注册或筛选后端工具。
- 后端可调用工具以 `toolkit.register_tool_function(...)` 注册结果为准（例如 `calendar_write`）。
- 因此 `tools` 可为空数组，不影响后端已注册工具的调用能力。

> 注意：请勿把后端已注册工具重复写入 `RunAgentInput.tools`，避免命名/参数描述冲突。

---

## Context Schema

```typescript
interface Context {
  description: string;
  value: string;
}
```

---

## forwardedProps.client_time Schema

`RunAgentInput.forwardedProps` 支持透传客户端时间上下文。日历相关能力必须使用以下结构：

```typescript
interface ForwardedProps {
  client_time?: {
    device_timezone: string;           // IANA 时区，例如 "America/Los_Angeles"
    client_now_iso: string;            // RFC3339 带偏移时间，例如 "2026-03-16T09:12:33-07:00"
    client_epoch_ms: number;           // Unix epoch 毫秒
  };
}
```

### 时间来源优先级（固定）

后端在运行时按以下顺序解析事件时区：

1. `event_timezone`（工具调用显式传参）
2. `forwardedProps.client_time.device_timezone`
3. `users.profile.settings.timezone`
4. `UTC`

### 约束

- `device_timezone` 必须是有效 IANA 时区。
- `client_now_iso` 必须是 RFC3339 且包含时区偏移。
- `client_epoch_ms` 必须是整数毫秒时间戳。
- 业务代码不得使用服务器本地时区作为事件语义时区。

### 说明

- `forwardedProps` 是透传字段，不改变 AG-UI 主体协议结构。
- 当 `forwardedProps.client_time` 缺失或非法时，运行时回退到 `users.profile.settings.timezone`。
- 日历写入必须在最终工具调用中带上 `event_timezone`，不得依赖工具默认值。

---

## Validation Rules

Backend 实现了以下验证规则：

| Rule | Error Message |
|------|---------------|
| payload ≤ 256KB | `RunAgentInput payload exceeds size limit` |
| threadId 必须是 UUID | `threadId must be a valid UUID` |
| runId 最大 128 字符 | `runId exceeds length limit` |
| messages ≤ 200 条 | `RunAgentInput.messages exceeds limit` |
| user text ≤ 10,000 字符 | `RunAgentInput user message text exceeds limit` |
| **恰好 1 条 user message** | `RunAgentInput.messages must contain exactly one user message` |
| user message 必须在第一条 | `RunAgentInput.messages[0].role must be user` |
| binary 必须是 image/* | `binary content requires image mimeType` |
| binary 必须有 url | `binary content requires url` |
| binary 不允许使用 data | `binary content data is not allowed` |
| 单条消息最多 3 张附件 | `Too many attachments` |

### forwardedProps.client_time Validation

建议在后端校验层返回以下错误（按业务实现映射到 4xx）：

| Rule | Error Message |
|------|---------------|
| `device_timezone` 非 IANA 时区 | `invalid client_time.device_timezone` |
| `client_now_iso` 无法解析或缺少时区 | `invalid client_time.client_now_iso` |
| `client_epoch_ms` 非整数毫秒值 | `invalid client_time.client_epoch_ms` |

---

## Request Example

### 纯文本请求

```json
{
  "threadId": "550e8400-e29b-41d4-a716-446655440000",
  "runId": "run-001",
  "state": {},
  "messages": [
    {
      "id": "msg-001",
      "role": "user",
      "content": "帮我查一下北京今天的天气"
    }
  ],
  "tools": [],
  "context": [],
  "forwardedProps": {}
}
```

### 多模态请求 (带图片)

```json
{
  "threadId": "550e8400-e29b-41d4-a716-446655440000",
  "runId": "run-002",
  "state": {},
  "messages": [
    {
      "id": "msg-001",
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "这张图片里的内容是什么?"
        },
        {
          "type": "binary",
          "mimeType": "image/png",
          "url": "https://storage.example.com/agent-inputs/user-123/image.png?signature=xxx"
        }
      ]
    }
  ],
  "tools": [],
  "context": [],
  "forwardedProps": {}
}
```

### 带 Tools 的请求

```json
{
  "threadId": "550e8400-e29b-41d4-a716-446655440000",
  "runId": "run-003",
  "state": {},
  "messages": [
    {
      "id": "msg-001",
      "role": "user",
      "content": "北京天气怎么样?"
    }
  ],
  "tools": [
    {
      "name": "get_weather",
      "description": "获取指定城市的天气信息",
      "parameters": {
        "type": "object",
        "properties": {
          "city": {
            "type": "string",
            "description": "城市名称"
          }
        },
        "required": ["city"]
      }
    }
  ],
  "context": [],
  "forwardedProps": {}
}
```

### 带 forwardedProps.client_time 的请求

```json
{
  "threadId": "550e8400-e29b-41d4-a716-446655440000",
  "runId": "run-004",
  "state": {},
  "messages": [
    {
      "id": "msg-001",
      "role": "user",
      "content": "帮我明天早上9点创建一个日历"
    }
  ],
  "tools": [],
  "context": [],
  "forwardedProps": {
    "client_time": {
      "device_timezone": "America/Los_Angeles",
      "client_now_iso": "2026-03-16T09:12:33-07:00",
      "client_epoch_ms": 1773658353000
    }
  }
}
```

---

## Response

成功响应返回 `TaskAcceptedResponse`：

```typescript
interface TaskAcceptedResponse {
  taskId: string;
  threadId: string;
  runId: string;
  created: boolean; // 是否新建会话
}
```

---

## History API

`GET /api/v1/agent/history` 返回对话历史快照。

### Request

| Parameter | Type | Description |
|-----------|------|-------------|
| `threadId` | string (query) | 可选，指定会话 ID，不指定则返回最新会话 |
| `before` | string (query) | 可选，日期格式 `YYYY-MM-DD`，返回该日期之前的快照 |

### Response

返回 `HistorySnapshotResponse`：

```typescript
interface HistorySnapshotResponse {
  scope: "history_day";
  threadId: string | null;
  day: string | null;       // ISO date format "YYYY-MM-DD"
  hasMore: boolean;
  messages: HistoryMessage[];
}
```

### HistoryMessage

根据消息 role 不同，返回字段有所差异：

```typescript
// role = "user"
interface HistoryMessageUser {
  id: string;
  seq: number;
  role: "user";
  content: string;
  attachments: HistoryAttachment[];
  timestamp: string;        // ISO-8601 timestamp
}

interface HistoryAttachment {
  mimeType: string;
  url: string;
}

// role = "assistant"
interface HistoryMessageAssistant {
  id: string;
  seq: number;
  role: "assistant";
  content: string;
  ui_schema: UiSchemaRenderer | null; // 由 worker_agent_output.ui_hints 编译
  timestamp: string;
}

type HistoryMessage = HistoryMessageUser | HistoryMessageAssistant;
```

### UiSchemaRenderer

编译后的 UI 渲染结构，详见 [UiSchema Protocol](../ui/ui-schema.md)：

```typescript
interface UiSchemaRenderer {
  version: "2.0";
  locale: string;
  status: "info" | "success" | "warning" | "error" | "pending";
  theme: "default" | "light" | "dark";
  meta?: {
    requestId?: string;
    toolId?: string;
    traceId?: string;
    userId?: string;
  };
  root: UiLayoutNode;
}
```

### Example

```json
{
  "scope": "history_day",
  "threadId": "550e8400-e29b-41d4-a716-446655440000",
  "day": "2026-03-15",
  "hasMore": true,
  "messages": [
    {
      "id": "msg-001",
      "seq": 1,
      "role": "user",
      "content": "帮我创建一个日程",
      "attachments": [
        {
          "mimeType": "image/png",
          "url": "https://project.supabase.co/storage/v1/object/sign/agent-inputs/..."
        }
      ],
      "timestamp": "2026-03-15T10:00:00Z"
    },
    {
      "id": "msg-002",
      "seq": 2,
      "role": "assistant",
      "content": "好的，我来帮您创建日程。",
      "ui_schema": {
        "version": "2.0",
        "locale": "zh-CN",
        "status": "success",
        "theme": "default",
        "root": {
          "type": "stack",
          "appearance": "card",
          "children": [
            {"type": "text", "content": "日程已创建", "role": "title"},
            {"type": "badge", "label": "SUCCESS", "status": "success"},
            {"type": "text", "content": "您的会议日程创建成功", "role": "body"}
          ]
        }
      },
      "timestamp": "2026-03-15T10:00:05Z"
    }
  ]
}
```

---

## Compatibility Notes

- `UserMessage.content` 支持 string 或 array 格式，前端优先使用 array 格式以支持多模态
- binary content 的 url 必须是有效的 signed URL，由 `/api/v1/agent/attachments` 端点生成
- backend 验证通过后，会将 binary url 转换为内部存储路径
- `tools` 是前端工具通道字段；当前后端运行时不基于该字段构造后端工具 prompt
- `RunAgentInput` 同时接受 camelCase 与 snake_case 别名输入（推荐统一使用 camelCase）
- 日历能力依赖 `forwardedProps.client_time` 透传设备时间上下文；缺失时回退用户 profile 时区
- tool 消息在存储层用于运行时上下文续接，不在 `/history` 对外返回
