Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
2f11c9f
sync release versions for v1.14.46
May 10, 2026
67b9c9c
Source HTTP API ID path patterns (#26623)
kitlangton May 10, 2026
235a86f
chore: generate
opencode-agent[bot] May 10, 2026
f220f02
Source workspace path pattern (#26632)
kitlangton May 10, 2026
b3526f6
chore: generate
opencode-agent[bot] May 10, 2026
fb4bab8
Remove redundant ID Zod overrides (#26633)
kitlangton May 10, 2026
3753601
Format TUI paths relative to session directory (#26648)
thdxr May 10, 2026
a9a2a59
chore: generate
opencode-agent[bot] May 10, 2026
ce3235e
sync
fwang May 10, 2026
bcbc1db
Go add hy3 preview (#26533)
MrMushrooooom May 10, 2026
d915041
chore: generate
opencode-agent[bot] May 10, 2026
3a7f617
go: add tencent icon
fwang May 10, 2026
5217e6c
chore: generate
opencode-agent[bot] May 10, 2026
85ce6a5
feat: better image handling (auto resize & max size constraints) (#26…
rekram1-node May 10, 2026
3a78fb1
chore: generate
opencode-agent[bot] May 10, 2026
c04fa9e
sync: revert
fwang May 10, 2026
472f9e6
chore: update nix node_modules hashes
opencode-agent[bot] May 10, 2026
903d818
Zen: add Ring 2.6 1T
fwang May 10, 2026
5cf9abe
feat(scout): materialize configured reference repos (#26692)
nexxeln May 10, 2026
6589a66
chore: generate
opencode-agent[bot] May 10, 2026
d28b5ad
refactor(http-recorder): Redactor + Recorder seams, README (#26636)
kitlangton May 10, 2026
4fc5383
chore: generate
opencode-agent[bot] May 10, 2026
49ee3ba
Source diff message query pattern (#26638)
kitlangton May 10, 2026
c104098
chore: generate
opencode-agent[bot] May 10, 2026
11030c6
Scope boolean query overrides
kitlangton May 10, 2026
4fb417d
feat(http-recorder): default mode to "auto" (#26719)
kitlangton May 10, 2026
1c9a2eb
chore: generate
opencode-agent[bot] May 10, 2026
a4f3ced
Start effect-style compaction tests
kitlangton May 10, 2026
3b8790e
zen: fix usage css on mobile
fwang May 10, 2026
ce061bf
Add explicit LLM stream lifecycle events (#26722)
kitlangton May 10, 2026
cc2915b
chore: generate
opencode-agent[bot] May 10, 2026
049502f
fix(server): return diagnosable body for schema rejections (#26631)
kitlangton May 10, 2026
312e5c7
chore: generate
opencode-agent[bot] May 10, 2026
fa15dbc
Migrate compaction process tests (#26723)
kitlangton May 10, 2026
2bd3d9a
refactor(http-recorder): hide cassette format behind Cassette seam (#…
kitlangton May 10, 2026
b616543
chore: generate
opencode-agent[bot] May 10, 2026
e45b6ef
refactor(http-recorder): use Schema.TaggedErrorClass for cassette err…
kitlangton May 10, 2026
a780186
chore: generate
opencode-agent[bot] May 10, 2026
9c8da69
Use Effect timeout in compaction test (#26728)
kitlangton May 10, 2026
56d818f
zen: fix reasoning token for openai compatible endpoint
fwang May 10, 2026
2ba2ee5
docs: document bun dev tmux capture (#26764)
thdxr May 10, 2026
0fffcdf
Persist session model switches outside event flag (#26765)
thdxr May 10, 2026
2a571b3
Migrate compaction overflow tests (#26731)
kitlangton May 10, 2026
4ff0d07
Migrate configurable compaction tests (#26732)
kitlangton May 10, 2026
8f5f75d
Migrate compaction LLM test (#26733)
kitlangton May 10, 2026
ca8a42c
Migrate LLM compaction tail tests (#26734)
kitlangton May 10, 2026
a658e43
Migrate compaction plugin test (#26736)
kitlangton May 10, 2026
64dde0c
Migrate compaction tool-call test (#26737)
kitlangton May 10, 2026
5654dd2
restore managed textarea keymap handling (#26771)
kommander May 10, 2026
f71fb18
Replace compaction create test fixtures (#26738)
kitlangton May 10, 2026
d8060dc
Drop compaction create facade (#26739)
kitlangton May 10, 2026
5ef72e1
Drop unused ID Zod statics (#26740)
kitlangton May 10, 2026
128d10d
Simplify compaction test helpers (#26742)
kitlangton May 11, 2026
7cea32e
Add background code migration service (#26652)
thdxr May 11, 2026
16aa670
Effectify remaining compaction process tests (#26776)
kitlangton May 11, 2026
d0412a2
chore: generate
opencode-agent[bot] May 11, 2026
426d92e
sync release versions for v1.14.47
May 11, 2026
fed716a
Clarify compaction test harness (#26777)
kitlangton May 11, 2026
77e6c0d
feat(llm): cache hint TTL, breakpoint cap, and tool placement (#26779)
kitlangton May 11, 2026
5801cce
chore: generate
opencode-agent[bot] May 11, 2026
effd967
Use SyncEvent service at event call sites (#26782)
kitlangton May 11, 2026
83cb0f6
Drop EventV2 run facade (#26783)
kitlangton May 11, 2026
4100fcb
disable image resizing
thdxr May 11, 2026
38e4540
chore: generate
opencode-agent[bot] May 11, 2026
4d8ac17
release: v1.14.48
May 11, 2026
e610b1d
refactor: kilo compat for v1.14.48
marius-kilocode Jun 8, 2026
1f5103c
merge: record upstream v1.14.48
marius-kilocode Jun 8, 2026
cf08de3
resolve merge conflicts
marius-kilocode Jun 8, 2026
5706abc
chore(cli): normalize upstream annotations
marius-kilocode Jun 8, 2026
cc03ffc
fix: harden OpenCode v1.14.48 integration
marius-kilocode Jun 8, 2026
39dbf7f
test(cli): remove redundant exit binding loop
marius-kilocode Jun 8, 2026
e7ac3bf
Merge remote-tracking branch 'origin/main' into marius-kilocode/kilo-…
marius-kilocode Jun 8, 2026
da67373
Merge remote-tracking branch 'origin/main' into marius-kilocode/kilo-…
marius-kilocode Jun 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/calm-image-events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kilocode/cli": patch
---

Preserve image attachments when Photon is unavailable, enforce attachment limits for user images, and correlate shell lifecycle events correctly.
2 changes: 1 addition & 1 deletion .opencode-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v1.14.46
v1.14.48
6 changes: 5 additions & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
},
"patchedDependencies": {
"@npmcli/agent@4.0.0": "patches/@npmcli%2Fagent@4.0.0.patch",
"@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
"mammoth@1.12.0": "patches/mammoth@1.12.0.patch"
Expand Down
214 changes: 214 additions & 0 deletions packages/http-recorder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
# @opencode-ai/http-recorder

Record and replay HTTP and WebSocket traffic for Effect's `HttpClient`. Tests
exercise real request shapes against deterministic, version-controlled
cassettes — no manual mocks, no flakes from upstream drift.

## Install

Internal package; depended on as `@opencode-ai/http-recorder` from another
workspace package.

```ts
import { HttpRecorder } from "@opencode-ai/http-recorder"
```

## Quickstart

Provide `cassetteLayer(name)` in place of (or layered over) your `HttpClient`.
By default the layer records on first run and replays on subsequent runs —
no env-var ternary at the call site, and `CI=true` forces strict replay so
missing cassettes fail loudly in CI rather than silently re-recording.

```ts
import { Effect } from "effect"
import { HttpClient, HttpClientRequest } from "effect/unstable/http"
import { HttpRecorder } from "@opencode-ai/http-recorder"

const program = Effect.gen(function* () {
const http = yield* HttpClient.HttpClient
const response = yield* http.execute(HttpClientRequest.get("https://api.example.com/users/1"))
return yield* response.json
})

// Records if the cassette is missing, replays if it exists.
// In CI (CI=true) always replays — fails loudly on missing fixtures.
Effect.runPromise(program.pipe(Effect.provide(HttpRecorder.cassetteLayer("users/get-one"))))

// Force a refresh — always hits upstream and overwrites.
Effect.runPromise(program.pipe(Effect.provide(HttpRecorder.cassetteLayer("users/get-one", { mode: "record" }))))
```

## Modes

| Mode | Behavior |
| ------------- | ----------------------------------------------------------------------------------- |
| `auto` | Default. Replay if the cassette exists; record if missing. `CI=true` forces replay. |
| `replay` | Strict — match the request to a recorded interaction; error if none. |
| `record` | Execute upstream, append the interaction, write the cassette. |
| `passthrough` | Bypass the recorder entirely — just call upstream. |

## Cassette format

A cassette is JSON at `test/fixtures/recordings/<name>.json`:

```json
{
"version": 1,
"metadata": { "name": "users/get-one", "recordedAt": "2026-05-09T..." },
"interactions": [
{
"transport": "http",
"request": { "method": "GET", "url": "...", "headers": {...}, "body": "" },
"response": { "status": 200, "headers": {...}, "body": "..." }
}
]
}
```

Cassettes are normal source files — review them, diff them, commit them.

## Request matching

By default, requests match on canonicalized method, URL, headers, and JSON
body (object keys sorted). Two dispatch strategies are available:

- **`match`** (default) — find the first recorded interaction whose request
matches the incoming request. Same request twice returns the same response.
- **`sequential`** — return interactions in the order they were recorded,
validating each one matches as the cursor advances. Use for ordered flows
where the same URL is hit multiple times with meaningful state changes
(pagination, retries, polling).

```ts
HttpRecorder.cassetteLayer("flow/poll-until-done", { dispatch: "sequential" })
```

Supply your own matcher via `match: (incoming, recorded) => boolean` for
custom equivalence (e.g. ignoring a timestamp field in the body).

## Redaction & secret safety

Cassettes get checked in, so the recorder is aggressive about not letting
secrets escape. Redaction is configured by composing a `Redactor`:

```ts
import { HttpRecorder, Redactor } from "@opencode-ai/http-recorder"

HttpRecorder.cassetteLayer("anthropic/messages", {
redactor: Redactor.defaults({
requestHeaders: { allow: ["content-type", "anthropic-version"] },
url: { transform: (url) => url.replace(/\/accounts\/[^/]+/, "/accounts/{account}") },
body: (parsed) => ({ ...(parsed as object), user_id: "{user}" }),
}),
})
```

`Redactor.defaults({ … })` composes the four built-in redactors with your
overrides. For full control, build the stack yourself:

```ts
const redactor = Redactor.compose(
Redactor.requestHeaders({ allow: ["content-type", "x-custom"] }),
Redactor.responseHeaders(),
Redactor.url({ query: ["session-id"] }),
Redactor.body((parsed) => /* … */),
)
```

What each layer does:

- **`requestHeaders` / `responseHeaders`** — strip headers to a small
allow-list (request default: `content-type`, `accept`, `openai-beta`;
response default: `content-type`). Sensitive headers within the
allow-list (`authorization`, `cookie`, API-key headers, AWS/GCP tokens,
…) are replaced with `[REDACTED]`.
- **`url`** — query parameters matching common secret names (`api_key`,
`token`, `signature`, AWS signing params, …) are replaced with
`[REDACTED]`. URL user/password are replaced. `transform` runs after
built-in redaction for path-level scrubbing.
- **`body`** — receives the parsed JSON request body and returns a redacted
version. No-op for non-JSON bodies.

After assembling the cassette, the recorder scans every string for known
secret patterns (Bearer tokens, `sk-…`, `sk-ant-…`, Google `AIza…` keys,
AWS access keys, GitHub tokens, PEM blocks) and for values matching any
environment variable named like a credential. If anything is found, the
cassette is **not written** and the request fails with `UnsafeCassetteError`
listing what was detected.

## WebSocket recording

WebSocket support records the open frame plus client/server message
streams. It uses the shared `Cassette.Service`, so HTTP and WS interactions
can live in the same cassette.

```ts
import { HttpRecorder } from "@opencode-ai/http-recorder"
import { Effect } from "effect"

const program = Effect.gen(function* () {
const cassette = yield* HttpRecorder.Cassette.Service
const executor = yield* HttpRecorder.makeWebSocketExecutor({
name: "ws/subscribe",
cassette,
live: liveExecutor,
})
// use executor.open(...)
})
```

## Inspecting cassettes programmatically

`Cassette.Service` exposes `read`, `append`, `exists`, and `list`. `read`
returns the recorded interactions for a name; the file format is hidden
behind the seam. Useful for CI checks:

```ts
import { HttpRecorder } from "@opencode-ai/http-recorder"
import { Effect } from "effect"

const audit = Effect.gen(function* () {
const cassettes = yield* HttpRecorder.Cassette.Service
const entries = yield* cassettes.list()
const issues = yield* Effect.forEach(entries, (entry) =>
cassettes
.read(entry.name)
.pipe(Effect.map((interactions) => ({ name: entry.name, findings: HttpRecorder.secretFindings(interactions) }))),
)
return issues.filter((i) => i.findings.length > 0)
})
```

`cassetteLayer` is the batteries-included entry point — it provides
`Cassette.fileSystem({ directory })` automatically. If you want to provide
your own `Cassette.Service` (e.g. an in-memory adapter for the recorder's
own unit tests), use `recordingLayer` and supply `Cassette.fileSystem` /
`Cassette.memory` yourself.

## Options reference

```ts
type RecordReplayOptions = {
mode?: "auto" | "replay" | "record" | "passthrough" // default: "auto" (CI=true forces "replay")
directory?: string // default: <cwd>/test/fixtures/recordings
metadata?: Record<string, unknown> // merged into cassette.metadata
redactor?: Redactor // default: Redactor.defaults()
dispatch?: "match" | "sequential" // default: "match"
match?: (incoming, recorded) => boolean // custom matcher
}
```

## Layout

| File | Purpose |
| -------------- | -------------------------------------------------------------------------------- |
| `effect.ts` | `cassetteLayer` / `recordingLayer` — the `HttpClient` adapter. |
| `websocket.ts` | `makeWebSocketExecutor` — WebSocket record/replay. |
| `cassette.ts` | `Cassette.Service` — reads/writes cassette files, accumulates state. |
| `recorder.ts` | Shared transport plumbing: `UnsafeCassetteError`, `appendOrFail`, `ReplayState`. |
| `redactor.ts` | Composable `Redactor` — headers, url, body redaction. |
| `redaction.ts` | Lower-level header/URL primitives + secret pattern detection. |
| `schema.ts` | Effect Schema definitions for the cassette JSON format. |
| `storage.ts` | Path resolution, JSON encode/decode, sync existence check. |
| `matching.ts` | Request matcher, canonicalization, dispatch strategies, mismatch diagnostics. |
Loading
Loading