# Diff

Read what a chat session's branch changes and leave review comments on specific lines. Start with `GET .../preview/diff/files` for the list of changed files, then read patches for the files you need. Reading the diff needs a running sandbox, so wake it first with `GET /api/projects/{projectId}/preview?sessionId=`. The comment endpoints work whether the sandbox runs or not.

| Method | Path                                                                                                                                                | Description                                                                                                                                                                                                            |
| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| GET    | `/api/projects/{projectId}/chat-sessions/{sessionId}/preview/diff/comments`                                                                         | List the review comments left on the diff.                                                                                                                                                                             |
| POST   | `/api/projects/{projectId}/chat-sessions/{sessionId}/preview/diff/comments`                                                                         | Leave a review comment on a range of lines in the diff.                                                                                                                                                                |
| PATCH  | `/api/projects/{projectId}/chat-sessions/{sessionId}/preview/diff/comments/{commentId}`                                                             | Edit the body of your own comment, or resolve or reopen any comment. One of the two per call.                                                                                                                          |
| DELETE | `/api/projects/{projectId}/chat-sessions/{sessionId}/preview/diff/comments/{commentId}`                                                             | Delete your own comment.                                                                                                                                                                                               |
| GET    | `/api/projects/{projectId}/chat-sessions/{sessionId}/preview/diff/commits`                                                                          | List the commits the session's branch adds over its pull request's base branch, or over the target branch before a pull request is open. Needs a running sandbox; otherwise commits is empty and unavailable says why. |
| GET    | `/api/projects/{projectId}/chat-sessions/{sessionId}/preview/diff/files`                                                                            | List the files the session's branch changes against its base, with totals.                                                                                                                                             |
| GET    | `/api/projects/{projectId}/chat-sessions/{sessionId}/preview/diff/patch?path=&paths=&afterPath=&cursor=&contents=&base=&head=&hunkOffset=&hunkEnd=` | Read patches: a page (cursor), one file (path, with hunkOffset/hunkEnd), or one file with both sides (path and contents=1). path and cursor are mutually exclusive.                                                    |

## Choose what the diff shows

`GET .../preview/diff/files` and `GET .../preview/diff/patch` take a `scope` that picks the changes to compare.

| `scope` | What the diff shows |
| --- | --- |
| `last-turn` | The default. What the agent's last turn changed. |
| `uncommitted` | The changes in the working tree that nobody committed. |
| `branch` | Everything the pushed branch changes against its pull request's base branch, or against the Target Branch before a pull request is open. The branch has to be pushed. |
| `committed` | One pushed commit. Send its sha in `commit`, taken from `GET .../preview/diff/commits`. |

The CLI sends no `scope`, so `kosuke sessions diff files list` and `kosuke sessions diff patch get` show the last turn.

## List changed files

`GET .../preview/diff/files` lists every changed file with its status and line counts, without content.

```bash
curl -s "https://app.kosuke.ai/api/projects/$PROJECT_ID/chat-sessions/$SESSION_ID/preview/diff/files?scope=branch" \
  -H "Authorization: Bearer $KOSUKE_API_KEY"
```

```json
{
  "data": {
    "files": [
      { "path": "src/settings/page.tsx", "status": "modified", "additions": 24, "deletions": 3 },
      { "path": "src/theme.ts", "status": "added", "additions": 41, "deletions": 0 }
    ],
    "base": "4b1d9e07c2a8f5b3e6d1904a7c3f8e2b5d6a9c10",
    "head": "a93f27c5e8b1d4f60a7c2e9b3d5f8a1c4e7b0d26",
    "totals": { "files": 2, "additions": 65, "deletions": 3 }
  }
}
```

`status` is `added`, `modified`, `deleted` or `renamed`, and a renamed file carries `oldPath`. When the sandbox is not running, or the scope has nothing to compare yet, `files` is empty and `unavailable` says why. When the sandbox cannot read the diff, the answer is `503`. The CLI command is `kosuke sessions diff files list <projectId> <sessionId>`.

`GET .../preview/diff/commits` lists the pushed commits the branch adds over the same branch the `branch` scope compares against, each with its `sha`, `subject` and `committedAt`.

## Read patches

`GET .../preview/diff/patch` returns patches, a page of files at a time or one file at a time.

| Parameter | What it does |
| --- | --- |
| `cursor` | The next page, from `meta.pagination.nextCursor`. Leave it out for the first page. Cannot be combined with `path`. |
| `path` | One file. Cannot be combined with `cursor`. |
| `hunkOffset` | With `path`, the first hunk to return. Pass the file's `nextHunkOffset` to read on. |
| `hunkEnd` | With `path`, the hunk to stop before. |
| `contents` | `1` with `path` returns both sides of the file in `meta.fileContents`, without the patch. |
| `paths` | A JSON array of up to 50 paths to read again. Cannot be combined with `path` or `cursor`. |
| `afterPath` | With `paths`, carries on paging after this path. |
| `base`, `head` | The `base` and `head` from an earlier answer. They keep a single-file request on the same comparison. Send both or neither. |
| `scope`, `commit` | Which changes to compare, as above. A cursor stays tied to the scope it came from. |

A page holds up to 50 files. Each file comes back as `{ path, header, windows, totalHunks, nextHunkOffset }`. Each window holds `hunkOffset`, `hunkCount` and the hunk text in `patch`. A null `nextHunkOffset` means no hunks remain after the last window. When review comments sit on a file, its windows can leave hidden hunks between them. Compare each window's `hunkOffset` and `hunkCount` to find the gaps, then read them with `hunkOffset` and `hunkEnd`.

```bash
kosuke sessions diff patch get $PROJECT_ID $SESSION_ID --path src/theme.ts
```

When the sandbox is not running, the patch endpoint answers `503`.

## Comment on the diff

`POST .../preview/diff/comments` leaves a review comment on a range of lines. Any member of the workspace can comment.

| Field | Type | What it does |
| --- | --- | --- |
| `filePath` | string, up to 1,024 characters | Required. The file, as the diff names it. |
| `side` | `old` or `new` | Required. `new` for lines of the changed file, `old` for lines as they were before. |
| `startLineNumber` | integer, 1 or more | Required. The first commented line on that side. |
| `endLineNumber` | integer | Required. The last commented line. At least `startLineNumber`. |
| `body` | string, up to 4,000 characters | Required. The comment. |
| `lineSnapshot` | string | Required. The text of the commented lines, joined with `\n`, with no newline at the end. |

`lineSnapshot` is how Sensei tells a comment has gone stale. When the lines at that range no longer match it, the app marks the comment outdated. Send an empty string when that side has no lines, such as the `new` side of a deleted file. An empty snapshot is never compared, so it is marked outdated only when its file leaves the diff or its lines are gone.

```bash
curl -s -X POST "https://app.kosuke.ai/api/projects/$PROJECT_ID/chat-sessions/$SESSION_ID/preview/diff/comments" \
  -H "Authorization: Bearer $KOSUKE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"filePath": "src/theme.ts", "side": "new", "startLineNumber": 12, "endLineNumber": 12, "body": "Read the saved theme before the first paint.", "lineSnapshot": "const theme = \"light\";"}'
```

The answer is `201` with the comment, including its `id`, `author` and `resolvedAt`. `GET .../preview/diff/comments` returns `{ "data": { "comments": [...], "sentCommentIds": [...] } }` for the session: `sentCommentIds` names the comments a message of the session already carried in `diffCommentsContext`. The CLI group is `kosuke sessions diff comments`, with `list`, `add`, `update` and `delete`.

## Edit or resolve a comment

`PATCH .../preview/diff/comments/{commentId}` takes one of two bodies per call.

| Body | What it does | Who can send it |
| --- | --- | --- |
| `{ "body": "..." }` | Replaces the comment's text. | Its author. |
| `{ "resolved": true }` or `{ "resolved": false }` | Resolves or reopens the comment. | Any member of the workspace. |

`DELETE .../preview/diff/comments/{commentId}` removes a comment. Only its author can delete it, and the answer is `204`.

## Send comments to the agent

To have the agent work through review comments, send them with your next message in `diffCommentsContext` on `POST .../jobs/chat`. Sensei leaves out resolved comments. [Send review comments to the agent](https://kosuke.ai/docs/recipes/send-review-comments-to-the-agent.md) shows the whole flow, and [Messages and turns](https://kosuke.ai/docs/endpoint-reference/messages-and-turns.md) has the field.

## Errors

| Status | Code | When |
| --- | --- | --- |
| 400 | `BAD_REQUEST` | `scope`, `commit`, `cursor`, `base`, `head` or `paths` is not valid, or the project has no branch to compare against. |
| 400 | `VALIDATION_ERROR` | A comment field is missing or too long. |
| 402 | `CREDIT_LIMIT_REACHED` | The workspace is blocked on billing. |
| 403 | `FORBIDDEN` | Editing the text of, or deleting, another member's comment. |
| 404 | `NOT_FOUND` | The session or comment does not exist, or `path` is not in the diff. |
| 503 | `SERVICE_UNAVAILABLE` | The diff could not be read from the sandbox. On the patch endpoint, also when the sandbox is not running or the scope has nothing to compare yet. |

[Responses and errors](https://kosuke.ai/docs/api/errors.md) has the full table.

## Next steps

- [Send review comments to the agent](https://kosuke.ai/docs/recipes/send-review-comments-to-the-agent.md)
- [Files and commits](https://kosuke.ai/docs/endpoint-reference/files-and-commits.md)
- [Messages and turns](https://kosuke.ai/docs/endpoint-reference/messages-and-turns.md)
