# Chat integration

Chat is a synchronous or streaming Star Task capability. It does **not** use the asynchronous
`POST /v1/tasks` creation contract, even though each completed turn is recorded as a task for
billing and audit.

## Route by task type

| Runtime `task_type` | Submit endpoint | Completion contract |
|---|---|---|
| `chat` | `POST /v1/ai/chat` | Immediate JSON or SSE stream |
| Image, video, audio, music | `POST /v1/tasks` | `202` plus `poll_url` |
| Reusable AI tool | `POST /v1/tools/:tool_id/tasks` | Tool-specific task result |

Use `GET /v1/models?view=chat-picker&site_id=<SITE_ID>` for a chat-only picker. Submit the selected
mode's exact `task_model` as `model`; never derive an alias from `product_id`.

## Quote a chat turn

```http
POST https://task.starhk.uk/v1/ai/chat/quote
Authorization: Bearer <access_token>
Content-Type: application/json
```

The quote endpoint accepts the same body as the send endpoint. Its JSON response contains
top-level `cost_points`; there is no `data` wrapper.

## Send a non-streaming message

```js
const body = {
  site_id: SITE_ID,
  model: selectedMode.task_model,
  task_type: 'chat',
  input: {
    messages: [
      { role: 'system', content: 'Answer clearly and concisely.' },
      { role: 'user', content: '你好' },
    ],
  },
  options: {
    stream: false,
    web_search: false,
    thinking: false,
    max_tokens: 4096,
  },
  idempotency_key: crypto.randomUUID(),
};

const response = await fetch('https://task.starhk.uk/v1/ai/chat', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${accessToken}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(body),
});

const result = await response.json();
if (!response.ok || result.ok !== true) throw new Error(result.error?.message ?? 'Chat failed');

const reply = result.output?.choices?.[0]?.message?.content ?? result.output?.text ?? '';
```

The native non-streaming response is:

```json
{
  "ok": true,
  "task_id": "t_xxx",
  "conversation_id": "c_xxx",
  "model": "deepseek-v4-flash",
  "output": {
    "choices": [{ "message": { "role": "assistant", "content": "你好" } }]
  },
  "cost_points": 1
}
```

`output` is the provider-compatible completion object. A site backend may normalize it to a
`reply` string, but `reply` is not a native Star Task response field.

For a follow-up turn, pass the returned ID as `options.conversation_id`. Keep the relevant prior
messages in `input.messages`; do not put `messages` at the request top level.

## Stream a response

Set `options.stream=true` and send the same request to `POST /v1/ai/chat`. The response content
type is `text/event-stream`, with OpenAI-compatible `data:` frames and a final `[DONE]` marker.
Read these response headers before consuming the stream:

- `X-Conversation-Id`
- `X-Task-Id`

Accumulate `choices[0].delta.content` from each JSON frame. Do not try to parse the stream as one
JSON document and do not poll `/v1/tasks` for the assistant text.

## Upload and reference documents

The browser integrates one Star Task attachment API. It must not choose or call a RokoAPI parser;
Star Task selects the text, office, or presentation parser from the verified file type.

Read the runtime limits before rendering the file picker:

```http
GET https://task.starhk.uk/v1/chat/attachments/capabilities?site_id=<SITE_ID>
Authorization: Bearer <access_token>
```

The response contains `max_file_bytes`, `max_files_per_message`, `image_extensions`,
`document_extensions`, and `all_extensions`. Build the input `accept` value from
`all_extensions`; do not keep a separate site-specific extension list.

Current server limits are 10 MiB per file and six attachments per chat request. Supported document
types are:

- Office and PDF: `pdf`, `docx`, `xlsx`, `xls`, `pptx`.
- OpenDocument: `odt`, `ods`.
- Text and data: `txt`, `md`, `markdown`, `csv`, `tsv`, `json`, `yaml`, `yml`, `xml`, `html`,
  `htm`, `log`, `toml`, `ini`.
- Images accepted by the same chat attachment flow: `jpg`, `jpeg`, `png`, `webp`.

Text files must be valid UTF-8. Legacy `.doc` and `.ppt`, macro-enabled Office documents,
archives, encrypted documents, and executable files are not supported. PDF OCR, image text
recognition, PowerPoint visual layout, charts, and animations are not guaranteed.

Upload one file as multipart form data:

```http
POST https://task.starhk.uk/v1/chat/attachments
Authorization: Bearer <access_token>
Content-Type: multipart/form-data

file=<binary>
site_id=<SITE_ID>
conversation_id=<optional existing conversation ID>
```

Images return `ready`; documents normally return `parsing`. Poll
`GET /v1/chat/attachments/:id?site_id=<SITE_ID>` until `ready` or `failed`. A failed document can
be retried with `POST /v1/chat/attachments/:id/retry`. Delete unused private files with
`DELETE /v1/chat/attachments/:id?site_id=<SITE_ID>`.

Reference only a ready attachment in a user message:

```json
{
  "role": "user",
  "content": [
    { "type": "text", "text": "Summarize the attached document" },
    { "type": "attachment", "attachment_id": "att_xxx" }
  ]
}
```

Attachments are private to the authenticated `user_id`, `site_id`, and bound conversation. Never
store or expose a RokoAPI token in frontend code.

For one model request, Star Task currently supplies at most 80,000 parsed characters from one
document and 120,000 characters across all documents. If a document is larger, the beginning and
end are retained with an explicit omission marker so the model cannot mistake a partial document
for complete content. These values are returned by the capabilities endpoint and may change; the
browser must not use them to reject otherwise valid uploads.

## Request and response rules

- `input` must be an object and `input.messages` must be a non-empty array.
- Message roles are `system`, `user`, or `assistant`; content may be text or supported content
  parts returned by the runtime capabilities.
- Put `stream`, `web_search`, `thinking`, `conversation_id`, and `max_tokens` in `options`.
- The successful JSON response is top-level `{ ok, task_id, conversation_id, model, output,
  cost_points }`; do not assume `{ data: ... }`.
- Generate one UUID v4 per user-initiated turn and reuse it only when retrying that same turn.
- Surface structured `{ ok:false, error:{code,message,details?} }` failures to the user.

## Common integration failures

| Failure | Cause | Fix |
|---|---|---|
| Chat type rejected by `/v1/tasks` | Chat was sent to the async media endpoint | Use `/v1/ai/chat` |
| `input must be an object` | `messages` was placed at the request top level | Wrap it as `input.messages` |
| Empty displayed reply | Client expected a top-level `reply` field | Read `output.choices[0].message.content` or normalize it in the site backend |
| Endless polling | Chat was modeled as an async task | Handle JSON/SSE directly |
