Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions apps/vscode-e2e/fixtures/modes.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@
}
]
}
},
{
"match": {
"userMessage": "Use the `switch_mode` tool to switch to debug mode."
},
"response": {
"toolCalls": [
{
"name": "switch_mode",
"arguments": "{\"mode_slug\":\"debug\",\"reason\":\"User requested to switch to debug mode.\"}",
"id": "call_modes_switch_002"
}
]
}
}
]
}
95 changes: 95 additions & 0 deletions apps/vscode-e2e/src/fixtures/view-state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type { ChatCompletionRequest, ChatMessage, LLMock } from "@copilotkit/aimock"

const TASKS = ["A", "B", "C"] as const
const ROUNDS = 10

const MODE_SEQUENCES: Record<(typeof TASKS)[number], string[]> = {
A: ["ask", "debug", "architect", "orchestrator", "code", "ask", "debug", "architect", "orchestrator", "code"],
B: ["debug", "architect", "orchestrator", "code", "ask", "debug", "architect", "orchestrator", "code", "ask"],
C: ["architect", "orchestrator", "code", "ask", "debug", "architect", "orchestrator", "code", "ask", "debug"],
}

const markerFor = (taskName: (typeof TASKS)[number]) => `FOLLOWUP_MODE_ISOLATION_${taskName}`
const answerFor = (taskName: (typeof TASKS)[number], round: number) => `${taskName} follow-up round ${round}`
const callIdFor = (taskName: (typeof TASKS)[number], round: number) =>
`call_followup_mode_${taskName.toLowerCase()}_${String(round).padStart(2, "0")}`

const lastToolResultContains = (req: ChatCompletionRequest, toolCallId: string, expected: string[]) => {
const messages = Array.isArray(req?.messages) ? req.messages : []
const toolMessage = messages.filter((message: ChatMessage) => message?.role === "tool").at(-1)
const content = toolMessage?.content

return (
toolMessage?.tool_call_id === toolCallId &&
typeof content === "string" &&
expected.every((text) => content.includes(text))
)
}

const followupToolCall = (taskName: (typeof TASKS)[number], round: number) => ({
name: "ask_followup_question",
arguments: JSON.stringify({
question: `Task ${taskName}: choose mode for round ${round}`,
follow_up: [
{
text: answerFor(taskName, round),
mode: MODE_SEQUENCES[taskName][round - 1],
},
],
}),
id: callIdFor(taskName, round),
})

export const getFollowupModeIsolationPlan = () =>
TASKS.map((taskName) => ({
taskName,
marker: markerFor(taskName),
rounds: MODE_SEQUENCES[taskName].map((mode, index) => ({
round: index + 1,
answer: answerFor(taskName, index + 1),
mode,
})),
}))

export function addViewStateFixtures(mock: InstanceType<typeof LLMock>) {
for (const taskName of TASKS) {
mock.addFixture({
match: {
userMessage: markerFor(taskName),
},
response: {
toolCalls: [followupToolCall(taskName, 1)],
},
})

for (let round = 1; round < ROUNDS; round++) {
mock.addFixture({
match: {
predicate: (req) =>
lastToolResultContains(req, callIdFor(taskName, round), [answerFor(taskName, round)]),
},
response: {
toolCalls: [followupToolCall(taskName, round + 1)],
},
})
}

mock.addFixture({
match: {
predicate: (req) =>
lastToolResultContains(req, callIdFor(taskName, ROUNDS), [answerFor(taskName, ROUNDS)]),
},
response: {
toolCalls: [
{
name: "attempt_completion",
arguments: JSON.stringify({
result: `Task ${taskName} completed ${ROUNDS} follow-up mode switches.`,
}),
id: `call_followup_mode_${taskName.toLowerCase()}_complete`,
},
],
},
})
}
}
33 changes: 33 additions & 0 deletions apps/vscode-e2e/src/runTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { addSearchFilesResultFixtures } from "./fixtures/search-files"
import { addSubtaskFixtures } from "./fixtures/subtasks"
import { addUseMcpToolResultFixtures } from "./fixtures/use-mcp-tool"
import { addWriteToFileResultFixtures } from "./fixtures/write-to-file"
import { toolResultContains } from "./fixtures/tool-result"
import { addViewStateFixtures } from "./fixtures/view-state"

function getCliFlagValue(flag: string) {
return process.argv.find((arg, index) => process.argv[index - 1] === flag)
Expand Down Expand Up @@ -129,6 +131,37 @@ async function main() {
addUseMcpToolResultFixtures(mock)
addWriteToFileResultFixtures(mock)
addDeepSeekV4Fixtures(mock)
addViewStateFixtures(mock)

mock.addFixture({
match: {
predicate: (req) => toolResultContains(req, "call_modes_switch_001", []),
},
response: {
toolCalls: [
{
name: "attempt_completion",
arguments: JSON.stringify({ result: "Switched to ❓ Ask mode as requested." }),
id: "call_modes_post_switch_001",
},
],
},
})

mock.addFixture({
match: {
predicate: (req) => toolResultContains(req, "call_modes_switch_002", []),
},
response: {
toolCalls: [
{
name: "attempt_completion",
arguments: JSON.stringify({ result: "Switched to 🪲 Debug mode as requested." }),
id: "call_modes_post_switch_002",
},
],
},
})
Comment on lines +136 to +164

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Determine aimock fixture match precedence (programmatic vs. registration order, specificity).
fd -t f 'package.json' -d 4 apps/vscode-e2e | xargs -I{} rg -n 'aimock' {}
rg -n --iglob '*aimock*' -g '!**/node_modules/**' -l . 2>/dev/null | head -20
rg -nP --type=ts -C4 'addFixture|findFixture|matchFixture' -g '!apps/vscode-e2e/src/runTest.ts' | head -60

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 204


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== runTest outline =="
ast-grep outline apps/vscode-e2e/src/runTest.ts --view expanded 2>/dev/null || true

echo "== relevant runTest sections =="
sed -n '1,230p' apps/vscode-e2e/src/runTest.ts | cat -n

echo "== aimock usages =="
rg -n --iglob '*aimock*' -g '!**/node_modules/**' -l . 2>/dev/null || true

echo "== package references =="
grep -R '"`@copilotkit/aimock`"' . -g 'package.json' 2>/dev/null || true

echo "== lockfile/fetch package metadata =="
fd -t f 'pnpm-lock.yaml|package-lock.json|yarn.lock|package.json' apps/vscode-e2e -d 3 -x sh -c 'echo "--- $1"; sed -n "1,80p" "$1"' sh {}

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 10721


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== npm metadata for `@copilotkit/aimock` 1.35.0 =="
npm view `@copilotkit/aimock`@1.35.0 version deps dist.tarball name --json

echo "== fetch package contents and relevant source candidates =="
tmpdir="$(mktemp -d)"
tarball="$(npm view `@copilotkit/aimock`@1.35.0 dist.tarball)"
curl -fsSL "$tarball" -o "$tmpdir/pkg.tgz"
tar -tzf "$tmpdir/pkg.tgz" | rg 'package/(dist|cjs|mjs|src|lib).*|package/(test|tests).*|package/README|package/package.json' | head -200

mkdir "$tmpdir/pkg"
tar -xzf "$tmpdir/pkg.tgz" -C "$tmpdir/pkg"

echo "== source search for fixture matching methods =="
rg -n 'addFixture|loadFixture|findFixture|matchFixture|specificity|match' "$tmpdir/pkg/package" \
  -g '*.js' -g '*.ts' -g '*.mjs' -g '*.cjs' -g '*.d.ts' -C 3 | head -240

echo "== inspect matching implementation files =="
python3 - <<'PY'
import os, subprocess
pkgdir = os.environ["TMPDIR"]+"/pkg/package" if "TMPDIR" in os.environ else None
if pkgdir is None:
    print("pkgdir not set")
PY
ls "$tmpdir/pkg/package"

echo "== inspect likely fixture matching files =="
for f in $(rg -l 'function .*match|addFixture|loadFixture' "$tmpdir/pkg/package" || true); do
  echo "--- $f"
  wc -l "$f"
  sed -n '1,260p' "$f" | cat -n
done

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail
tmpdir="$(mktemp -d)"
export tmpdir
tarball="$(npm view `@copilotkit/aimock`@1.35.0 dist.tarball)"
mkdir -p "$tmpdir/pkg"
tar -xzf "$tarball" -C "$tmpdir/pkg"
pkg="$tmpdir/pkg/package"

echo "== fixture loader and match diagnostic source =="
for f in $(rg -l 'matchFixtureDiagnostic|loadFixturesFromDir|loadFixtureFile|fixture-loader|fixtures' "$pkg/src" "$pkg/dist" -g '*.ts' -g '*.js' || true); do
  echo "--- $f"
  wc -l "$f"
  rg -n -C 6 'matchFixtureDiagnostic|loadFixturesFromDir|loadFixtureFile|addFixture|lastUserMessage|userMessage|predicate|model' "$f" || true
done

echo "== deterministic match precedence probe from source text =="
python3 - <<'PY'
from pathlib import Path
import re

pkg = Path("/tmp") / Path(__file__).parent.name / "pkg/package" if False else None
# We don't import repo sources; parse distribution source snippets from prior result is not local.
PY

echo "== search for fixture-array ordering in runtime =="
rg -n 'find.*fixture|fixture.*find|filter|sort|reverse|push|splice|matchFixture|userMessage|predicate|model' "$pkg/dist" "$pkg/src" -g '*.js' -g '*.ts' -C 2

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 667


🌐 Web query:

@copilotkit/aimock LLMock addFixture fixture matching order predicate userMessage model documentation

💡 Result:

In @copilotkit/aimock (formerly @copilotkit/llmock), the LLMock fixture matching system prioritizes the order in which fixtures are added [1][2]. Fixtures are evaluated in the order they appear in the internal list [1]. Key matching mechanisms include: Fixture Matching Order and Addition - Precedence: When multiple fixtures might match a request, the one added earliest to the server typically takes precedence if they are in the same registry [1]. - Programmatic Control: You can use mockServer.prependFixture to insert a fixture at the beginning of the list (index 0) to ensure it is evaluated before previously registered file-based or programmatic fixtures [1]. - Appending: Standard addFixture or shorthand methods (like.onMessage) generally append to the list [2]. Matching Criteria (Match Object) You can define a fixture using a match object, which supports several properties [2]: - userMessage: Matches based on the user's input (typically as a substring) [1][2]. - model: Restricts the fixture to a specific model identifier [2]. - predicate: A function that receives the request and returns a boolean [2]. This is the most flexible way to match, allowing you to check message roles (e.g., tool results), headers, or other request metadata [1][2]. Because predicates cannot be serialized, they must be registered programmatically rather than via JSON files [2]. Summary of Methods: - Shorthand methods like mock.onMessage(userMessage, response) or mock.on(matchObject, response) simplify registration [2]. - For complex logic, use mock.addFixture({ match: { predicate:... }, response:... }) [2]. - Use mock.prependFixture if you need a catch-all or high-priority override (e.g., handling tool-result messages) to trigger before standard fixtures [1]. Documentation Note: The class name remains LLMock for backward compatibility following the package rename from @copilotkit/llmock to @copilotkit/aimock [3][4]. Refer to the official aimock documentation for the most current API details [5][6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== find fixtures related to modes/config/files =="
git ls-files 'apps/vscode-e2e/fixtures/*' | sed -n '1,120p'

echo "== search local fixture content for environment_details/model regex/tool ids =="
rg -n -C 3 'environment_details|openai/gpt-4-1|call_modes_switch|tools|message' apps/vscode-e2e/fixtures apps/vscode-e2e/src || true

echo "== determine recorded fixture order by parsing repo files =="
python3 - <<'PY'
from pathlib import Path
base = Path("apps/vscode-e2e/fixtures")
print("files:", [str(p) for p in sorted(base.glob("*")) if p.is_file()])
for p in sorted(base.glob("*")):
    if not p.is_file():
        continue
    text = p.read_text(errors="replace")
    hits = []
    for i,line in enumerate(text.splitlines(),1):
        if any(s in line for s in ["environment_details", "openai/gpt-4", "call_modes_switch", "gpt-4.1", "model"]):
            hits.append((i,line.strip()))
    if hits:
        print(f"--- {p}")
        for i,line in hits:
            print(f"{i}: {line}")
PY

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 50378


Remove the broad /^<environment_details>/ fixture now that both switch flows match by tool call ID.

mock.addFixture() registers in evaluation order, so the new tool-result predicates added before this regex fixture already cover the ask turn for openai/gpt-4.1. Keep only the call_modes_switch_002 request scoped by toolResultContains(req, "call_modes_switch_002", []); delete the broad regex fixture that reuses the Ask response ID.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/vscode-e2e/src/runTest.ts` around lines 136 - 164, In the fixture setup
near the mode-switch responses, remove the broad /^<environment_details>/
fixture and its reused Ask response ID. Retain the toolResultContains fixture
for "call_modes_switch_002" so the Debug flow remains scoped by its tool call
ID.


// The modes test (switch_mode → ask) triggers a second API call whose last
// user message starts with <environment_details> directly — no <user_message>
Expand Down
Loading
Loading