Skip to content

Commit 86c86e8

Browse files
authored
Merge branch 'main' into copilot/suggest-onboarding-experience-designs
2 parents d1d65a6 + 9c0bee2 commit 86c86e8

15 files changed

+426
-236
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
title: "Add context reset endpoint to Admin API for agent-friendly state recovery"
3+
parentIssue: 1720
4+
labels:
5+
- enhancement
6+
- agent-ux
7+
assignees: []
8+
milestone:
9+
---
10+
11+
AI agents frequently make state-mutating requests while exploring an API (creating resources, updating fields, etc.). When an agent realises it has reached an inconsistent state, it currently has no way to recover short of restarting the Counterfact process. That restart destroys any session, breaks the agent's tooling loop, and wastes time.
12+
13+
A single HTTP call to reset every context back to its initial state would let agents recover instantly and retry from a known baseline, without restarting.
14+
15+
## Proposed change
16+
17+
Add a new admin API endpoint:
18+
19+
```
20+
POST /_counterfact/api/contexts/reset
21+
```
22+
23+
The endpoint should:
24+
25+
1. Remove every path-level context entry that was created or modified since startup (i.e. anything beyond the root `/` context that the `ContextRegistry` adds in its constructor).
26+
2. Reset the root `/` context to an empty object `{}`.
27+
3. Return `{ success: true, message: "All contexts reset" }`.
28+
29+
Optionally accept a JSON body `{ "path": "/pets" }` to reset only a single subtree rather than everything.
30+
31+
## Motivation
32+
33+
- Agents need fast, zero-friction feedback loops. A `POST /_counterfact/api/contexts/reset` call is trivially scriptable.
34+
- The existing `POST /_counterfact/api/contexts/:path` endpoint can **update** a context, but it cannot erase keys or remove path-level entries; a dedicated reset endpoint is required.
35+
- Human developers working in automated test loops also benefit: each test can start with a clean slate without restarting the server.
36+
37+
## Acceptance criteria
38+
39+
- [ ] `POST /_counterfact/api/contexts/reset` returns `200 { success: true }` and all contexts are restored to their initial state
40+
- [ ] After a reset, `GET /_counterfact/api/contexts` returns only the root `/` context with an empty object
41+
- [ ] `POST /_counterfact/api/contexts/reset` with body `{ "path": "/pets" }` resets only the `/pets` subtree and leaves other paths unchanged
42+
- [ ] The endpoint is protected by the same bearer-token / loopback guard as all other admin API endpoints
43+
- [ ] Unit tests cover the happy path, the scoped reset, and the auth-guard behaviour
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
title: "Serve parsed OpenAPI spec as JSON via Admin API for programmatic route discovery"
3+
parentIssue: 1720
4+
labels:
5+
- enhancement
6+
- agent-ux
7+
assignees: []
8+
milestone:
9+
---
10+
11+
Counterfact already serves the OpenAPI document at `/counterfact/openapi` as YAML so Swagger UI can display it. However, AI agents need to consume the spec programmatically — as a JSON object — without knowing the original file path or performing a YAML-to-JSON conversion themselves.
12+
13+
The Admin API should expose the fully-resolved (bundle-dereferenced) spec as JSON at a stable, well-known endpoint so agents can discover all available routes, schemas, and response codes in a single HTTP call.
14+
15+
## Proposed change
16+
17+
Add a new read-only endpoint to the Admin API:
18+
19+
```
20+
GET /_counterfact/api/openapi
21+
```
22+
23+
Response: `Content-Type: application/json`, body is the bundled OpenAPI document as a plain JSON object (identical to what `openapiMiddleware` currently produces, but serialised as JSON instead of YAML).
24+
25+
Optionally, also add:
26+
27+
```
28+
GET /_counterfact/api/openapi/paths → just the `paths` object
29+
GET /_counterfact/api/openapi/paths?route=<path> → schema for a single path item
30+
(route is URL-encoded, e.g. route=%2Fpet%2F%7BpetId%7D)
31+
```
32+
33+
The `route` query parameter avoids the routing ambiguity that would arise from embedding a slash-containing OpenAPI path into URL segments.
34+
35+
## Motivation
36+
37+
- The existing `/counterfact/openapi` endpoint is designed for Swagger UI, which accepts YAML. Agents prefer JSON and parse it without additional dependencies.
38+
- The Admin API already acts as the programmatic interface; consolidating spec access there keeps the surface area consistent and auth-guarded.
39+
- Agents building or testing against the mock server can dynamically enumerate valid paths, required parameters, and response schemas — enabling them to generate correct requests on the first attempt rather than guessing.
40+
41+
## Acceptance criteria
42+
43+
- [ ] `GET /_counterfact/api/openapi` returns `200 application/json` with the fully-resolved OpenAPI document
44+
- [ ] The returned JSON matches the structure produced by `@apidevtools/json-schema-ref-parser`'s `bundle()` call
45+
- [ ] `GET /_counterfact/api/openapi/paths` returns only the `paths` section of the spec
46+
- [ ] The endpoint is protected by the same bearer-token / loopback guard as other admin API endpoints
47+
- [ ] Returns `503` (or a clear error) when Counterfact was started without a spec (`openApiPath === "_"`)
48+
- [ ] Unit tests cover the happy path, the paths sub-endpoint, and the no-spec case
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
---
2+
title: "Add `counterfact agent-prompt` command to generate LLM system prompts"
3+
parentIssue: 1720
4+
labels:
5+
- enhancement
6+
- agent-ux
7+
- dx
8+
assignees: []
9+
milestone:
10+
---
11+
12+
AI coding agents work best when given a precise system prompt that tells them the capabilities and conventions of the tools they are using. Today there is no built-in way to describe Counterfact — its Admin API endpoints, context model, route conventions, and OpenAPI-driven type system — in a format suitable for injection into an LLM context window.
13+
14+
A `counterfact agent-prompt` CLI command would auto-generate a tailored system prompt from the running server, giving agents everything they need to generate correct route handlers and Admin API calls on the first attempt.
15+
16+
## Proposed change
17+
18+
Add a new sub-command to the CLI:
19+
20+
```bash
21+
npx counterfact agent-prompt [--url <server-url>] [--format <markdown|json>]
22+
```
23+
24+
When run, the command:
25+
26+
1. Connects to the Counterfact Admin API at `<server-url>` (default `http://localhost:3100`).
27+
2. Fetches `/_counterfact/api/openapi`, `/_counterfact/api/routes`, and `/_counterfact/api/config`.
28+
3. Generates a structured prompt containing:
29+
- A short description of Counterfact's purpose and conventions.
30+
- The list of registered routes (path + method + summary from the spec).
31+
- The Admin API surface (endpoints, request/response shapes).
32+
- The context model (how to read and write context via the Admin API).
33+
- TypeScript route handler conventions (file structure, `$.context`, response helpers).
34+
4. Writes the prompt to stdout (for piping into other tools) or to a file with `--output <path>`.
35+
36+
### Example output (abbreviated)
37+
38+
```markdown
39+
# Counterfact Mock Server — Agent Instructions
40+
41+
## Server info
42+
- Base URL: http://localhost:3100
43+
- OpenAPI spec: petstore3
44+
45+
## Available routes
46+
- GET /pet/{petId} → Find pet by ID
47+
- POST /pet → Add a new pet
48+
49+
## Admin API
50+
- GET /_counterfact/api/health → Server health
51+
- POST /_counterfact/api/contexts/reset → Reset all context state
52+
53+
54+
## Writing route handlers
55+
Route handlers live in `./api/routes/`. Each file exports GET, POST, etc.
56+
Use `$.context` to read/write shared state. Return `{ status: 200, body: … }`.
57+
```
58+
59+
## Motivation
60+
61+
- Reduces the number of LLM iterations needed to produce a working route handler from scratch.
62+
- Makes the Admin API self-describing — an agent can call `agent-prompt`, inject the output into its context window, and immediately start using the Admin API correctly.
63+
- Keeps the prompt up-to-date automatically: because it is generated from the live server, it always reflects the actual spec and config.
64+
65+
## Acceptance criteria
66+
67+
- [ ] `npx counterfact agent-prompt` prints a Markdown prompt to stdout when a Counterfact server is running locally on the default port
68+
- [ ] The prompt includes the list of registered routes with HTTP methods and summaries
69+
- [ ] The prompt includes a description of every Admin API endpoint
70+
- [ ] `--format json` outputs a machine-readable JSON object with the same information
71+
- [ ] `--output <path>` writes the prompt to a file instead of stdout
72+
- [ ] The command exits with a non-zero code and a clear error message when the server is not reachable
73+
- [ ] The command is documented in `docs/usage.md` under a new "Using with AI agents" section
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
title: "Expose request/response history via Admin API for agent introspection"
3+
parentIssue: 1720
4+
labels:
5+
- enhancement
6+
- agent-ux
7+
assignees: []
8+
milestone:
9+
---
10+
11+
AI agents need to understand what happened during a sequence of calls in order to reason about correctness and debug failures. Currently the only way to see request/response pairs is to tail the debug log, which is not machine-readable and is not available via HTTP.
12+
13+
Adding a request-history endpoint to the Admin API gives agents a reliable, structured view of recent interactions without requiring external tooling.
14+
15+
## Proposed change
16+
17+
1. Add an in-memory ring buffer (e.g. last 100 entries, configurable via a `--history-size` flag or a field in `Config`) to the Koa dispatcher. After each request is handled, append a record:
18+
19+
```ts
20+
interface ValidationError {
21+
location: "query" | "header" | "path" | "body";
22+
name?: string; // parameter name (for query/header/path)
23+
path?: string; // JSON Pointer (for body fields, e.g. "/price")
24+
message: string; // human-readable description
25+
}
26+
27+
interface HistoryEntry {
28+
id: number; // monotonically increasing
29+
timestamp: string; // ISO-8601
30+
method: string;
31+
path: string;
32+
query: Record<string, string>;
33+
requestHeaders: Record<string, string>;
34+
requestBody: unknown;
35+
statusCode: number;
36+
responseHeaders: Record<string, string>;
37+
responseBody: unknown;
38+
durationMs: number;
39+
validationErrors: ValidationError[]; // structured errors from the request-validator
40+
}
41+
```
42+
43+
2. Expose it via the Admin API:
44+
45+
```
46+
GET /_counterfact/api/requests → last N entries (newest first)
47+
GET /_counterfact/api/requests?limit=10 → last 10 entries
48+
DELETE /_counterfact/api/requests → clear the history
49+
```
50+
51+
## Motivation
52+
53+
- Agents can replay or compare request/response pairs to verify that their code is producing correct calls.
54+
- Validation errors are surfaced alongside the request that triggered them, giving agents immediate, structured feedback.
55+
- Human developers troubleshooting auto-generated routes also benefit from a time-ordered log of all interactions.
56+
57+
## Acceptance criteria
58+
59+
- [ ] After making several requests, `GET /_counterfact/api/requests` returns a JSON array of history entries in reverse-chronological order
60+
- [ ] Each entry includes method, path, status code, request body, response body, duration, and any validation errors
61+
- [ ] The `?limit=N` query parameter limits the number of returned entries
62+
- [ ] `DELETE /_counterfact/api/requests` empties the history and returns `{ success: true }`
63+
- [ ] Admin auth guard (bearer token / loopback) is applied to all history endpoints
64+
- [ ] The ring buffer is capped (default 100 entries) to avoid unbounded memory growth
65+
- [ ] Unit tests cover normal recording, the limit parameter, and the clear endpoint
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
title: "Add named scenario save/load to Admin API for rapid agent state switching"
3+
parentIssue: 1720
4+
labels:
5+
- enhancement
6+
- agent-ux
7+
assignees: []
8+
milestone:
9+
---
10+
11+
AI agents often need to test the same application code against multiple server states (e.g. "empty inventory", "one item in cart", "checkout in progress"). Currently the only way to set up a known state is to call a series of `POST /_counterfact/api/contexts/:path` requests. There is no way to save that state, give it a name, and restore it later in one operation.
12+
13+
A scenario system lets agents define and switch between named server states with a single HTTP call.
14+
15+
## Proposed change
16+
17+
### Endpoints
18+
19+
```
20+
POST /_counterfact/api/scenarios → create a scenario from current context state
21+
GET /_counterfact/api/scenarios → list all saved scenarios
22+
GET /_counterfact/api/scenarios/:name → get a specific scenario
23+
PUT /_counterfact/api/scenarios/:name → create or overwrite a named scenario
24+
POST /_counterfact/api/scenarios/:name/load → restore server state from this scenario
25+
DELETE /_counterfact/api/scenarios/:name → delete a scenario
26+
```
27+
28+
### Schema
29+
30+
```ts
31+
interface Scenario {
32+
name: string;
33+
description?: string;
34+
createdAt: string; // ISO-8601
35+
contexts: Record<string, unknown>; // path → context object snapshot
36+
}
37+
```
38+
39+
### Behaviour
40+
41+
- Scenarios are stored **in memory** by default (cleared on restart). When `--persist-scenarios` is passed, scenarios are read from `.counterfact-scenarios.json` in `basePath` at startup (if the file exists), and every create/update/delete operation is immediately written back to that file. This means a server restarted with `--persist-scenarios` automatically restores all previously saved scenarios.
42+
- `POST /_counterfact/api/scenarios/:name/load` calls the existing context reset logic, then replays every entry in the scenario's `contexts` map.
43+
44+
### Example workflow
45+
46+
```http
47+
# 1. Set up a "one pet in store" state
48+
POST /_counterfact/api/contexts/pets { "pets": [{ "id": 1, "name": "Fido" }] }
49+
50+
# 2. Save it
51+
PUT /_counterfact/api/scenarios/one-pet { "description": "Single pet in store" }
52+
53+
# 3. Later, restore it instantly
54+
POST /_counterfact/api/scenarios/one-pet/load
55+
```
56+
57+
## Motivation
58+
59+
- Agents switching between test scenarios today must issue a reset followed by N context-update calls. A single `POST /load` cuts that to one round-trip.
60+
- Named scenarios are self-describing, making it easy for a human reviewing the agent's work to understand what each test case represents.
61+
- Scenarios can be committed alongside application code (via `--persist-scenarios`) to give teams a reproducible set of server states.
62+
63+
## Acceptance criteria
64+
65+
- [ ] `PUT /_counterfact/api/scenarios/empty-cart` saves the current context state under the name `empty-cart`
66+
- [ ] `POST /_counterfact/api/scenarios/empty-cart/load` restores all contexts to the saved state
67+
- [ ] `GET /_counterfact/api/scenarios` lists all saved scenarios with their names and descriptions
68+
- [ ] `DELETE /_counterfact/api/scenarios/empty-cart` removes the scenario
69+
- [ ] All endpoints respect the bearer-token / loopback auth guard
70+
- [ ] Scenarios survive a context reset (reset only affects live contexts, not the saved scenario definitions)
71+
- [ ] Unit tests cover CRUD operations and the load/restore round-trip
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
title: "Support seeded/deterministic random responses for reproducible agent testing"
3+
parentIssue: 1720
4+
labels:
5+
- enhancement
6+
- agent-ux
7+
assignees: []
8+
milestone:
9+
---
10+
11+
When Counterfact generates random example responses (via `@faker-js/faker` or similar), the values change on every request. For human developers iterating interactively this is fine, but for AI agents it causes two problems:
12+
13+
1. **Non-deterministic test failures** – an agent writing assertions against a response can't predict the value it will receive.
14+
2. **Irreproducible debugging sessions** – when an agent records a failing scenario and tries to reproduce it, the server produces different data.
15+
16+
Allowing consumers to fix the random seed makes responses repeatable across runs without requiring every route handler to be rewritten.
17+
18+
## Proposed change
19+
20+
1. Add an optional `--seed <number>` CLI flag (and a corresponding `seed` field in `Config`, default `undefined`).
21+
2. When a seed is provided, initialise `faker` (and any other PRNG used in response generation) with that seed before the server starts.
22+
3. Expose the seed via the Admin API:
23+
24+
```
25+
GET /_counterfact/api/config/seed → { seed: 42 | null }
26+
POST /_counterfact/api/config/seed → body { "seed": 42 } sets a new seed at runtime
27+
DELETE /_counterfact/api/config/seed → removes the seed, restoring random behaviour
28+
```
29+
30+
Setting the seed at runtime resets the PRNG state, so the sequence of random values produced by subsequent requests is identical to what would be produced by starting the server with that seed.
31+
32+
The seed must be a non-negative integer (0–2³²−1). Providing a non-integer, a negative number, or a value outside this range returns `400 { "error": "seed must be a non-negative integer ≤ 4294967295" }`.
33+
34+
## Motivation
35+
36+
- Agents running regression tests need stable, assertable values.
37+
- CI pipelines benefit from deterministic snapshots they can diff against a known baseline.
38+
- Human developers can share a seed when filing a bug report, letting others reproduce the exact same generated data.
39+
40+
## Acceptance criteria
41+
42+
- [ ] `counterfact --seed 42 …` produces the same sequence of random responses across multiple server restarts
43+
- [ ] `POST /_counterfact/api/config/seed` with `{ "seed": 42 }` takes effect immediately; the next generated response uses the seeded sequence
44+
- [ ] `DELETE /_counterfact/api/config/seed` restores non-deterministic (truly random) behaviour
45+
- [ ] Without `--seed`, behaviour is unchanged (fully random as today)
46+
- [ ] The `seed` value is included in `GET /_counterfact/api/config` response
47+
- [ ] Unit tests assert that the same seed yields the same output, and that different seeds yield different outputs

0 commit comments

Comments
 (0)