From 582c11b7a87532aa8f09e89512e8804662e4ff8d Mon Sep 17 00:00:00 2001 From: Jordan Ritter Date: Wed, 22 Apr 2026 13:00:56 -0700 Subject: [PATCH 1/8] docs: rewrite flag-driven npx invocations to use llmock bin on vidaimock page npx @copilotkit/aimock resolves to the config-driven aimock bin which rejects -f/-p. Route flag-style invocations through the llmock bin using npx -p @copilotkit/aimock llmock ... so the copy-paste examples actually work. Config-driven and convert-subcommand invocations stay on the aimock bin unchanged. --- docs/migrate-from-vidaimock/index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/migrate-from-vidaimock/index.html b/docs/migrate-from-vidaimock/index.html index 1ea3debb..acdd2a2f 100644 --- a/docs/migrate-from-vidaimock/index.html +++ b/docs/migrate-from-vidaimock/index.html @@ -78,7 +78,7 @@

The quick switch

aimock (equivalent) shell -
npx @copilotkit/aimock -p 4010 -f ./fixtures
+
npx -p @copilotkit/aimock llmock -p 4010 -f ./fixtures
@@ -286,8 +286,8 @@

CLI / Docker quick start

Install & run shell
-
# Run the mock server
-npx @copilotkit/aimock -p 4010 -f ./fixtures
+              
# Run the mock server (flag-driven llmock bin)
+npx -p @copilotkit/aimock llmock -p 4010 -f ./fixtures
 
 # Point your app at it
 export OPENAI_BASE_URL=http://localhost:4010/v1

From 868561c404fd708a71adce246847aebbd418b288 Mon Sep 17 00:00:00 2001
From: Jordan Ritter 
Date: Wed, 22 Apr 2026 13:01:03 -0700
Subject: [PATCH 2/8] docs: add fail-fast to Kotlin mokksy health check,
 standardize /health path

The Kotlin fixture's repeat(30) loop silently swallowed every exception
and fell through with no throw, letting tests proceed against a dead
server. Capture the last exception and throw IllegalStateException after
the loop exhausts.

Also standardize on /health (the simpler canonical path) instead of
mixing /__aimock/health (used earlier on the page) with /health (used
later in the TestContainers example) on the same page, and route the
example npx invocation through the llmock bin.
---
 docs/migrate-from-mokksy/index.html | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/docs/migrate-from-mokksy/index.html b/docs/migrate-from-mokksy/index.html
index 3f044759..c22fd96b 100644
--- a/docs/migrate-from-mokksy/index.html
+++ b/docs/migrate-from-mokksy/index.html
@@ -153,16 +153,19 @@ 

The quick switch

"-p", "4010:4010", "-v", "./fixtures:/fixtures", "ghcr.io/copilotkit/aimock", "-f", "/fixtures", "-h", "0.0.0.0") .start().waitFor() - // Wait for server to be ready + // Wait for server to be ready — fail loudly if it never comes up + var lastError: Exception? = null repeat(30) { try { - java.net.URL("http://localhost:4010/__aimock/health") + java.net.URL("http://localhost:4010/health") .readText() return - } catch (_: Exception) { + } catch (e: Exception) { + lastError = e Thread.sleep(200) } } + throw IllegalStateException("aimock did not become healthy after 30 attempts", lastError) } @AfterAll @@ -403,7 +406,7 @@

CLI / Docker quick start

CLI sh
-
npx @copilotkit/aimock -p 4010 -f ./fixtures
+
npx -p @copilotkit/aimock llmock -p 4010 -f ./fixtures
From 4f7043c8f6c5ea352acd64af0d6fd583f7986458 Mon Sep 17 00:00:00 2001 From: Jordan Ritter Date: Wed, 22 Apr 2026 13:01:10 -0700 Subject: [PATCH 3/8] docs: fix invalid JSON in chaos-testing journal and piyook fixture examples The chaos-testing journal-entry sample had a bare "..." value that isn't valid JSON (keys-without-values); replace with a real "...": "elided for brevity" placeholder. The piyook migration example wrapped // line comments inside a code block tagged as json; JSON doesn't support line comments, so users pasting into a .json file hit parser errors. Move the auto-generation note out of the code block and into adjacent prose. Also route the example npx invocations through the llmock bin. --- docs/chaos-testing/index.html | 4 ++-- docs/migrate-from-piyook/index.html | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/chaos-testing/index.html b/docs/chaos-testing/index.html index cdbbbde7..3a51a323 100644 --- a/docs/chaos-testing/index.html +++ b/docs/chaos-testing/index.html @@ -217,7 +217,7 @@

CLI Flags

CLI chaos flags shell
-
$ npx @copilotkit/aimock --fixtures ./fixtures \
+              
$ npx -p @copilotkit/aimock llmock --fixtures ./fixtures \
   --chaos-drop 0.1 \
   --chaos-malformed 0.05 \
   --chaos-disconnect 0.02
@@ -254,7 +254,7 @@

Journal Tracking

"path": "/v1/chat/completions", "response": { "status": 500, - "fixture": { "..." }, + "fixture": { "...": "elided for brevity" }, "chaosAction": "drop" } }
diff --git a/docs/migrate-from-piyook/index.html b/docs/migrate-from-piyook/index.html index 00fc6aea..67e31b1c 100644 --- a/docs/migrate-from-piyook/index.html +++ b/docs/migrate-from-piyook/index.html @@ -191,14 +191,14 @@

Fixture format comparison

{
   "match": { "userMessage": "hello" },
   "response": { "content": "Hello there" }
-}
-
-// aimock auto-generates:
-//   - id, object, created, model
-//   - choices[].index, finish_reason
-//   - usage (prompt_tokens, completion_tokens)
-//   - SSE streaming chunks (when stream: true)
+}
+

+ aimock auto-generates id, object, created, + model, choices[].index, finish_reason, + usage (prompt / completion tokens), and SSE streaming chunks when + stream: true. +

@@ -391,8 +391,8 @@

CLI / Docker quick start

Install & run sh
-
# Run the mock server
-npx @copilotkit/aimock -p 4010 -f ./fixtures
+              
# Run the mock server (flag-driven llmock bin)
+npx -p @copilotkit/aimock llmock -p 4010 -f ./fixtures
 
 # With a full config file
 npx @copilotkit/aimock --config aimock.json --port 4010

From a9245f4634dc0efb0f868c70d2798f1f98989273 Mon Sep 17 00:00:00 2001
From: Jordan Ritter 
Date: Wed, 22 Apr 2026 13:01:19 -0700
Subject: [PATCH 4/8] docs: correct MSW v2 WebSocket claim and add missing msw
 import

MSW v2 (2024) added WebSocket interception via ws.link(), so the blanket
'MSW cannot intercept WebSocket' claim is stale. Reframe the feature
comparison to position aimock's WebSocket support as built-in provider
handshakes (OpenAI Realtime, Responses WS, Gemini Live) vs. MSW's
manual ws.link frames.

The side-by-side test-setup.ts example used http.get(...) and
HttpResponse without importing them from 'msw', so a copy-paste would
hit ReferenceError. Add the import line.

Also route the --record invocation through the llmock bin.
---
 docs/migrate-from-msw/index.html | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/docs/migrate-from-msw/index.html b/docs/migrate-from-msw/index.html
index e04cf47e..886d2e54 100644
--- a/docs/migrate-from-msw/index.html
+++ b/docs/migrate-from-msw/index.html
@@ -250,14 +250,21 @@ 

Built-in SSE for 8 providers

🔌

WebSocket APIs

-

OpenAI Realtime, Responses WS, Gemini Live. MSW cannot intercept WebSocket.

+

+ OpenAI Realtime, Responses WS, Gemini Live — all built in. MSW v2 added + WebSocket interception via ws.link(), but you still handwrite every + frame; aimock ships the full provider handshakes. +

Record & Replay

Proxy real APIs, save as fixtures, replay forever. - npx @copilotkit/aimock --record --provider-openai https://api.openai.com + npx -p @copilotkit/aimock llmock --record --provider-openai + https://api.openai.com

@@ -311,9 +318,9 @@

What you keep (or lose)

WebSocket - ✗ - ✓ - 3 protocols + Manual (ws.link, v2+) + Built-in + 3 AI protocols Record & replay @@ -347,6 +354,7 @@

Using aimock alongside MSW

test-setup.ts ts
// test setup
 import { setupServer } from 'msw/node'
+import { http, HttpResponse } from 'msw'
 import { LLMock } from '@copilotkit/aimock'
 
 // MSW for REST APIs
@@ -372,7 +380,7 @@ 

CLI / Docker quick start

CLI sh
-
npx @copilotkit/aimock -p 4010 -f ./fixtures
+
npx -p @copilotkit/aimock llmock -p 4010 -f ./fixtures
From e50a7c16ff92d912735fb2c82ef9dcf61eb8c40b Mon Sep 17 00:00:00 2001 From: Jordan Ritter Date: Wed, 22 Apr 2026 13:01:26 -0700 Subject: [PATCH 5/8] docs: remove unsupported Weaviate claim, add probes to helm values example CHANGELOG 1.7.0 ships VectorMock with Pinecone, Qdrant, and ChromaDB compatibility. Weaviate is not referenced anywhere in src/ or packages/, so advertising it on the mock-llm migration page was a false feature claim. Drop Weaviate from the vector-DB card. The helm values.yaml example advertised /health and /ready probes in prose but shipped no livenessProbe / readinessProbe stanzas, so copy-paste produced a probe-less deployment. Wire them in with sane defaults (initialDelaySeconds + periodSeconds). Also route the example npx invocation through the llmock bin. --- docs/migrate-from-mock-llm/index.html | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/migrate-from-mock-llm/index.html b/docs/migrate-from-mock-llm/index.html index bac7c2d4..a83c210f 100644 --- a/docs/migrate-from-mock-llm/index.html +++ b/docs/migrate-from-mock-llm/index.html @@ -242,7 +242,7 @@

Zero dependencies

📦

Vector DB mocking

-

Mock Pinecone, Qdrant, Weaviate, and ChromaDB endpoints for RAG pipeline testing.

+

Mock Pinecone, Qdrant, and ChromaDB endpoints for RAG pipeline testing.

@@ -446,6 +446,20 @@

Kubernetes migration

mountPath: /app/fixtures existingClaim: "" # PVC for fixture files +livenessProbe: + httpGet: + path: /health + port: 4010 + initialDelaySeconds: 5 + periodSeconds: 10 + +readinessProbe: + httpGet: + path: /ready + port: 4010 + initialDelaySeconds: 2 + periodSeconds: 5 + resources: {} # limits: # cpu: 200m @@ -477,8 +491,8 @@

CLI / Docker quick start

Install & run sh
-
# Run the mock server
-npx @copilotkit/aimock -p 4010 -f ./fixtures
+              
# Run the mock server (flag-driven llmock bin)
+npx -p @copilotkit/aimock llmock -p 4010 -f ./fixtures
 
 # With a full config file
 npx @copilotkit/aimock --config aimock.json --port 4010

From 74cc215fbd5f2ad46ddf0b1792c81f9efdcf4414 Mon Sep 17 00:00:00 2001
From: Jordan Ritter 
Date: Wed, 22 Apr 2026 13:01:38 -0700
Subject: [PATCH 6/8] docs: rework python-mocks conftest fixtures for fail-fast
 and safe subprocess handling

The two conftest.py examples had five overlapping correctness bugs that
caused copy-paste adopters to silently proceed against dead servers or
deadlock their test runs:

- Health poll swallowed only ConnectionError; other exceptions bypassed
  the sleep and fell through. Add for/else to raise on exhaustion and
  check proc.poll() so an aimock that crashed on startup surfaces the
  failure instead of hanging.
- os.environ['OPENAI_BASE_URL' / 'OPENAI_API_KEY'] was set with no
  teardown, clobbering real credentials on the host process. Save
  originals and restore (or pop) in a finally block.
- proc.wait() had no timeout, so a hung aimock shutdown froze pytest
  indefinitely. Use proc.wait(timeout=10) with kill fallback.
- subprocess.PIPE stdout/stderr with no consumer thread deadlocks once
  the ~64KB buffer fills (the class of bug CHANGELOG 1.8.0 claimed to
  fix). Switch to DEVNULL since the examples suppress output anyway.
- The unscoped 'npx aimock' in the Popen call resolved to nothing on
  fresh machines; use the scoped llmock bin invocation.

Also standardize on /health instead of /__aimock/health.
---
 docs/migrate-from-python-mocks/index.html | 76 +++++++++++++++++------
 1 file changed, 58 insertions(+), 18 deletions(-)

diff --git a/docs/migrate-from-python-mocks/index.html b/docs/migrate-from-python-mocks/index.html
index 3ee71a42..42b68b57 100644
--- a/docs/migrate-from-python-mocks/index.html
+++ b/docs/migrate-from-python-mocks/index.html
@@ -255,21 +255,41 @@ 

aimock (after)

"ghcr.io/copilotkit/aimock:latest", "-f", "/fixtures", "-h", "0.0.0.0" ]) - # Wait for health endpoint + # Wait for health endpoint — fail loudly if aimock never comes up import requests for _ in range(30): + if proc.poll() is not None: + raise RuntimeError(f"aimock exited early with code {proc.returncode}") try: - if requests.get("http://localhost:4010/__aimock/health").ok: + if requests.get("http://localhost:4010/health").ok: break except requests.ConnectionError: - time.sleep(0.2) - + pass + time.sleep(0.2) + else: + raise RuntimeError("aimock did not become healthy after 30 attempts") + + # Save originals so we don't clobber real credentials in the test process + prev_base = os.environ.get("OPENAI_BASE_URL") + prev_key = os.environ.get("OPENAI_API_KEY") os.environ["OPENAI_BASE_URL"] = "http://localhost:4010/v1" os.environ["OPENAI_API_KEY"] = "mock-key" - yield "http://localhost:4010" - proc.terminate() - proc.wait()
+ try: + yield "http://localhost:4010" + finally: + proc.terminate() + try: + proc.wait(timeout=10) + except subprocess.TimeoutExpired: + proc.kill() + proc.wait(timeout=5) + # Restore originals (or remove if there were none) + for name, val in (("OPENAI_BASE_URL", prev_base), ("OPENAI_API_KEY", prev_key)): + if val is None: + os.environ.pop(name, None) + else: + os.environ[name] = val
@@ -435,8 +455,8 @@

CLI / Docker quick start

Install & run sh
-
# Run the mock server (requires Node.js)
-npx @copilotkit/aimock -p 4010 -f ./fixtures
+              
# Run the mock server (requires Node.js, flag-driven llmock bin)
+npx -p @copilotkit/aimock llmock -p 4010 -f ./fixtures
 
 # Point your Python app at the mock
 export OPENAI_BASE_URL=http://localhost:4010/v1
@@ -490,24 +510,44 @@ 

Alternative: npx fixture (no Docker)

@pytest.fixture(scope="session") def aimock_server(): proc = subprocess.Popen( - ["npx", "aimock", "-p", "4010", "-f", "./fixtures"], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT + ["npx", "-p", "@copilotkit/aimock", "llmock", "-p", "4010", "-f", "./fixtures"], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ) - # Wait for health endpoint + # Wait for health endpoint — fail loudly if aimock never comes up import requests for _ in range(30): + if proc.poll() is not None: + raise RuntimeError(f"aimock exited early with code {proc.returncode}") try: - if requests.get("http://localhost:4010/__aimock/health").ok: + if requests.get("http://localhost:4010/health").ok: break except requests.ConnectionError: - time.sleep(0.2) - + pass + time.sleep(0.2) + else: + raise RuntimeError("aimock did not become healthy after 30 attempts") + + # Save originals so we don't clobber real credentials in the test process + prev_base = os.environ.get("OPENAI_BASE_URL") + prev_key = os.environ.get("OPENAI_API_KEY") os.environ["OPENAI_BASE_URL"] = "http://localhost:4010/v1" os.environ["OPENAI_API_KEY"] = "mock-key" - yield "http://localhost:4010" - proc.terminate() - proc.wait()
+ try: + yield "http://localhost:4010" + finally: + proc.terminate() + try: + proc.wait(timeout=10) + except subprocess.TimeoutExpired: + proc.kill() + proc.wait(timeout=5) + # Restore originals (or remove if there were none) + for name, val in (("OPENAI_BASE_URL", prev_base), ("OPENAI_API_KEY", prev_key)): + if val is None: + os.environ.pop(name, None) + else: + os.environ[name] = val
From b243e0d03f6c839dc452c1a474916d74a6d39f05 Mon Sep 17 00:00:00 2001 From: Jordan Ritter Date: Wed, 22 Apr 2026 13:01:44 -0700 Subject: [PATCH 7/8] docs: correct Python on_message signature in openai-responses migration page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit aimock.on_message's actual signature is on_message(pattern: str, response: dict, **opts), per packages/aimock-pytest/src/aimock_pytest/_server.py:138. The migration guide used on_message(pattern, content='...') — which raises TypeError: unexpected keyword argument 'content' — in both the side-by-side snippet and the API-mapping table. Replace with the dict-positional form on_message('pattern', {'content': '...'}). --- docs/migrate-from-openai-responses/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/migrate-from-openai-responses/index.html b/docs/migrate-from-openai-responses/index.html index 1e54d1b8..c9abebe5 100644 --- a/docs/migrate-from-openai-responses/index.html +++ b/docs/migrate-from-openai-responses/index.html @@ -183,7 +183,7 @@

Before / After

from openai import OpenAI
 
 def test_chat(aimock):
-    aimock.on_message("hi", content="Hello!")
+    aimock.on_message("hi", {"content": "Hello!"})
     client = OpenAI(base_url=aimock.url + "/v1", api_key="test")
     result = client.chat.completions.create(
         model="gpt-4o", messages=[{"role": "user", "content": "hi"}]
@@ -266,7 +266,7 @@ 

Feature mapping

openai_mock.chat.completions.create.response = {...} - aimock.on_message("pattern", content="...") + aimock.on_message("pattern", {"content": "..."}) Partial envelope (choices required, other fields auto-filled) From 7ff383a9f8e469f3c1aba6ecbe9f3f03b8b31ab1 Mon Sep 17 00:00:00 2001 From: Jordan Ritter Date: Wed, 22 Apr 2026 13:01:57 -0700 Subject: [PATCH 8/8] docs: correct Go + Rust SDK examples, harden Docker teardown + enableRecording cleanup Language SDK examples in the 'any language, one server' block used nonexistent builder APIs: - Go: openai.NewClient(option.WithBaseURL(...)) is not exposed by the mainstream github.com/sashabaranov/go-openai crate. Rewrite using openai.DefaultConfig + NewClientWithConfig. - Rust: Client::new().with_base_url(...) is not on async-openai. Use OpenAIConfig::new().with_api_base(...) + Client::with_config per the async-openai crate (https://docs.rs/async-openai). The GitHub Actions aimock example used 'docker run -d --name aimock' with no --rm and the stop step lacked 'if: always()', so the container name collided on retry and docker stop errors masked real test failures. Add --rm, switch the cleanup to 'docker rm -f aimock', and gate it with if: always(). The enableRecording programmatic example lacked mock.stop() cleanup, so a test failure inside the recorded block leaked the port. Wrap the enable/disable pair in try/finally and always call await mock.stop(). Also route the example flag-driven npx invocations through the llmock bin. --- docs/record-replay/index.html | 59 +++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/docs/record-replay/index.html b/docs/record-replay/index.html index 4d4a21ce..a3166d29 100644 --- a/docs/record-replay/index.html +++ b/docs/record-replay/index.html @@ -89,7 +89,7 @@

Proxy-Only Mode

Proxy-only mode shell
-
$ npx @copilotkit/aimock -f ./fixtures \
+              
$ npx -p @copilotkit/aimock llmock -f ./fixtures \
   --proxy-only \
   --provider-openai https://api.openai.com
@@ -140,7 +140,7 @@

Quick Start

CLI usage shell
-
$ npx @copilotkit/aimock -f ./fixtures \
+              
$ npx -p @copilotkit/aimock llmock -f ./fixtures \
   --record \
   --provider-openai https://api.openai.com \
   --provider-anthropic https://api.anthropic.com
@@ -302,20 +302,25 @@

Programmatic API

const mock = new LLMock(); await mock.start(); -// Enable recording — unmatched requests are proxied AND saved as fixtures -mock.enableRecording({ - providers: { - openai: "https://api.openai.com", - anthropic: "https://api.anthropic.com", - }, - fixturePath: "./fixtures/recorded", -}); - -// Make requests — unmatched ones are proxied and recorded -// ... - -// Disable recording — recorded fixtures persist on disk -mock.disableRecording();
+try { + // Enable recording — unmatched requests are proxied AND saved as fixtures + mock.enableRecording({ + providers: { + openai: "https://api.openai.com", + anthropic: "https://api.anthropic.com", + }, + fixturePath: "./fixtures/recorded", + }); + + // Make requests — unmatched ones are proxied and recorded + // ... + + // Disable recording — recorded fixtures persist on disk + mock.disableRecording(); +} finally { + // Always release the port, even if a test above threw + await mock.stop(); +}

@@ -478,10 +483,10 @@

Local Development Workflow

Record then replay shell
# First run: record real API responses
-$ npx @copilotkit/aimock --record --provider-openai https://api.openai.com -f ./fixtures
+$ npx -p @copilotkit/aimock llmock --record --provider-openai https://api.openai.com -f ./fixtures
 
 # Subsequent runs: replay from recorded fixtures
-$ npx @copilotkit/aimock -f ./fixtures
+$ npx -p @copilotkit/aimock llmock -f ./fixtures
@@ -516,7 +521,7 @@

CI Pipeline Workflow

- name: Start aimock
   run: |
-    docker run -d --name aimock \
+    docker run -d --rm --name aimock \
       -v ./fixtures:/fixtures \
       -p 4010:4010 \
       ghcr.io/copilotkit/aimock \
@@ -528,7 +533,8 @@ 

CI Pipeline Workflow

run: pnpm test - name: Stop aimock - run: docker stop aimock
+ if: always() + run: docker rm -f aimock

Request Transform

@@ -686,11 +692,16 @@

Cross-Language Testing

import openai client = openai.OpenAI(base_url="http://localhost:4010/v1", api_key="mock") -# Go -client := openai.NewClient(option.WithBaseURL("http://localhost:4010/v1")) +# Go — github.com/sashabaranov/go-openai +config := openai.DefaultConfig("mock") +config.BaseURL = "http://localhost:4010/v1" +client := openai.NewClientWithConfig(config) -# Rust -let client = Client::new().with_base_url("http://localhost:4010/v1");
+# Rust — async-openai +let config = OpenAIConfig::new() + .with_api_base("http://localhost:4010/v1") + .with_api_key("mock"); +let client = Client::with_config(config);