Compare commits

...

2 Commits

Author SHA1 Message Date
James Long
544b179ee7 notes 2026-03-24 16:17:58 -04:00
James Long
8d5c84d516 Fuzzer 2026-03-24 15:17:39 -04:00
28 changed files with 4537 additions and 8 deletions

View File

@@ -26,13 +26,15 @@ export namespace Global {
}
}
await Promise.all([
fs.mkdir(Global.Path.data, { recursive: true }),
fs.mkdir(Global.Path.config, { recursive: true }),
fs.mkdir(Global.Path.state, { recursive: true }),
fs.mkdir(Global.Path.log, { recursive: true }),
fs.mkdir(Global.Path.bin, { recursive: true }),
])
try {
await Promise.all([
fs.mkdir(Global.Path.data, { recursive: true }),
fs.mkdir(Global.Path.config, { recursive: true }),
fs.mkdir(Global.Path.state, { recursive: true }),
fs.mkdir(Global.Path.log, { recursive: true }),
fs.mkdir(Global.Path.bin, { recursive: true }),
])
} catch (err) {}
const CACHE_VERSION = "21"

View File

@@ -94,7 +94,10 @@ export namespace ModelsDev {
.catch(() => undefined)
if (snapshot) return snapshot
if (Flag.OPENCODE_DISABLE_MODELS_FETCH) return {}
const json = await fetch(`${url()}/api.json`).then((x) => x.text())
const json = await fetch(`${url()}/api.json`)
.then((x) => x.text())
.catch(() => undefined)
if (!json) return {}
return JSON.parse(json)
})

View File

@@ -30,6 +30,7 @@ import { createOpenAI } from "@ai-sdk/openai"
import { createOpenAICompatible } from "@ai-sdk/openai-compatible"
import { createOpenRouter, type LanguageModelV2 } from "@openrouter/ai-sdk-provider"
import { createOpenaiCompatible as createGitHubCopilotOpenAICompatible } from "./sdk/copilot"
import { createMock } from "./sdk/mock"
import { createXai } from "@ai-sdk/xai"
import { createMistral } from "@ai-sdk/mistral"
import { createGroq } from "@ai-sdk/groq"
@@ -132,6 +133,7 @@ export namespace Provider {
"gitlab-ai-provider": createGitLab,
// @ts-ignore (TODO: kill this code so we dont have to maintain it)
"@ai-sdk/github-copilot": createGitHubCopilotOpenAICompatible,
"@opencode/mock": createMock as any,
}
type CustomModelLoader = (sdk: any, modelID: string, options?: Record<string, any>) => Promise<any>
@@ -150,6 +152,11 @@ export namespace Provider {
}
const CUSTOM_LOADERS: Record<string, CustomLoader> = {
async mock() {
return {
autoload: true,
}
},
async anthropic() {
return {
autoload: false,
@@ -920,6 +927,42 @@ export namespace Provider {
const modelsDev = await ModelsDev.get()
const database = mapValues(modelsDev, fromModelsDevProvider)
// Register the built-in mock provider for testing
database["mock"] = {
id: "mock",
name: "Mock",
source: "custom",
env: [],
options: {},
models: {
"mock-model": {
id: "mock-model",
providerID: "mock",
name: "Mock Model",
api: {
id: "mock-model",
url: "",
npm: "@opencode/mock",
},
status: "active",
capabilities: {
temperature: false,
reasoning: false,
attachment: false,
toolcall: true,
input: { text: true, audio: false, image: false, video: false, pdf: false },
output: { text: true, audio: false, image: false, video: false, pdf: false },
interleaved: false,
},
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
limit: { context: 128000, output: 4096 },
options: {},
headers: {},
release_date: "2025-01-01",
},
},
}
const disabled = new Set(config.disabled_providers ?? [])
const enabled = config.enabled_providers ? new Set(config.enabled_providers) : null

View File

@@ -0,0 +1,208 @@
# Mock RPC
Deterministic model scripting for tests.
---
## Overview
The mock provider lets test harnesses script exactly what the model should emit. Instead of hitting a real API, the user message contains a JSON object that describes each step of the conversation. This makes test scenarios fully deterministic and reproducible.
---
## Understand the protocol
The user message text is a JSON object with a `steps` array. Each step is an array of actions that the model emits on that turn.
```json
{
"steps": [
[{ "type": "text", "content": "Hello" }],
[{ "type": "text", "content": "Goodbye" }]
]
}
```
The mock model reads the **last** user message in the prompt to find this JSON.
---
## Know how steps are selected
The model picks which step to execute by counting messages with `role: "tool"` in the prompt. This count represents how many tool-result rounds have occurred.
- **Step 0** runs on the first call (no tool results yet).
- **Step 1** runs after the first tool-result round.
- **Step N** runs after the Nth tool-result round.
If the step index is out of bounds, the model emits an empty set of actions.
---
## Use the `text` action
Emits a text block.
```json
{ "type": "text", "content": "Some response text" }
```
| Field | Type | Description |
|-----------|--------|----------------------|
| `content` | string | The text to emit. |
---
## Use the `tool_call` action
Calls a tool. The input object is passed as-is.
```json
{ "type": "tool_call", "name": "write", "input": { "filePath": "a.txt", "content": "hi" } }
```
| Field | Type | Description |
|---------|--------|---------------------------------|
| `name` | string | Name of the tool to call. |
| `input` | object | Arguments passed to the tool. |
---
## Use the `thinking` action
Emits a reasoning/thinking block.
```json
{ "type": "thinking", "content": "Let me consider the options..." }
```
| Field | Type | Description |
|-----------|--------|----------------------------|
| `content` | string | The thinking text to emit. |
---
## Use the `list_tools` action
Responds with a JSON text block listing all available tools and their schemas. Useful for test scripts that need to discover tool names. No additional fields.
```json
{ "type": "list_tools" }
```
---
## Use the `error` action
Emits an error chunk.
```json
{ "type": "error", "message": "something went wrong" }
```
| Field | Type | Description |
|-----------|--------|------------------------|
| `message` | string | The error message. |
---
## Know the finish reason
The finish reason is auto-inferred from the actions in the current step. If any action has `type: "tool_call"`, the finish reason is `"tool-calls"`. Otherwise it is `"stop"`.
Token usage is always reported as `{ inputTokens: 10, outputTokens: 20, totalTokens: 30 }`.
---
## Handle invalid JSON
If the user message is not valid JSON or doesn't have a `steps` array, the model falls back to a default text response. This keeps backward compatibility with tests that don't use the RPC protocol.
---
## Examples
### Simple text response
```json
{
"steps": [
[{ "type": "text", "content": "Hello from the mock model" }]
]
}
```
### Tool discovery
```json
{
"steps": [
[{ "type": "list_tools" }]
]
}
```
### Single tool call
```json
{
"steps": [
[{ "type": "tool_call", "name": "read", "input": { "filePath": "config.json" } }]
]
}
```
### Multi-turn tool use
Step 0 calls a tool. Step 1 runs after the tool result comes back and emits a text response.
```json
{
"steps": [
[{ "type": "tool_call", "name": "write", "input": { "filePath": "a.txt", "content": "hi" } }],
[{ "type": "text", "content": "Done writing the file." }]
]
}
```
### Thinking and text
```json
{
"steps": [
[
{ "type": "thinking", "content": "The user wants a greeting." },
{ "type": "text", "content": "Hey there!" }
]
]
}
```
### Multiple actions in one step
A single step can contain any combination of actions.
```json
{
"steps": [
[
{ "type": "text", "content": "I'll create two files." },
{ "type": "tool_call", "name": "write", "input": { "filePath": "a.txt", "content": "aaa" } },
{ "type": "tool_call", "name": "write", "input": { "filePath": "b.txt", "content": "bbb" } }
],
[
{ "type": "text", "content": "Both files created." }
]
]
}
```
### Error simulation
```json
{
"steps": [
[{ "type": "error", "message": "rate limit exceeded" }]
]
}
```

View File

@@ -0,0 +1,19 @@
I got it to the point where it can run a full mock session
Run the server with `./src/provider/sdk/mock/run`. It will run it sandboxes to make sure it doesn't interact with the outside world unexpectedly.
Then run `bun run src/provider/sdk/mock/runner/index.ts` to drive a session and get a log
There is also `bun run src/provider/sdk/mock/runner/diff.ts` which will drive two sessions at once and compare them. This is annoying right now because you have to run two servers. This would let you compare the differences between versions though
## Coverage
I also have an experiment in `serve.test.ts` which runs the server as a bun test, which gives us access to coverage info. Run it like this:
```
bun test --coverage --coverage-reporter=lcov --timeout 0 src/provider/sdk/mock/runner/serve.test.ts
```
That will give you a `lcov.info` file. Convert it to HTML with this:
genhtml coverage/lcov.info -o coverage/html && open coverage/html/index.html

View File

@@ -0,0 +1,24 @@
import type { LanguageModelV2 } from "@ai-sdk/provider"
import { MockLanguageModel } from "./model"
export { vfsPlugin } from "./plugin"
export { Filesystem as VFilesystem } from "./vfs"
export interface MockProviderSettings {
name?: string
}
export interface MockProvider {
(id: string): LanguageModelV2
languageModel(id: string): LanguageModelV2
}
export function createMock(options: MockProviderSettings = {}): MockProvider {
const name = options.name ?? "mock"
const create = (id: string) => new MockLanguageModel(id, { provider: name })
const provider = Object.assign((id: string) => create(id), { languageModel: create })
return provider
}

View File

@@ -0,0 +1,244 @@
import type {
LanguageModelV2,
LanguageModelV2CallOptions,
LanguageModelV2FunctionTool,
LanguageModelV2StreamPart,
} from "@ai-sdk/provider"
/**
* Mock Model RPC Protocol
*
* The user message text is a JSON object that scripts exactly what the mock
* model should emit. This lets test harnesses drive the model deterministically.
*
* Schema:
* ```
* {
* "steps": [
* // Step 0: executed on first call (no tool results yet)
* [
* { "type": "tool_call", "name": "write", "input": { "filePath": "a.txt", "content": "hi" } }
* ],
* // Step 1: executed after first tool-result round
* [
* { "type": "text", "content": "Done!" }
* ]
* ]
* }
* ```
*
* Supported actions:
*
* { "type": "text", "content": "string" }
* Emit a text block.
*
* { "type": "tool_call", "name": "toolName", "input": { ... } }
* Call a tool. The input object is passed as-is.
*
* { "type": "thinking", "content": "string" }
* Emit a reasoning/thinking block.
*
* { "type": "list_tools" }
* Respond with a JSON text block listing all available tools and their
* schemas. Useful for test scripts that need to discover tool names.
*
* { "type": "error", "message": "string" }
* Emit an error chunk.
*
* Finish reason is auto-inferred: "tool-calls" when any tool_call action
* exists in the step, "stop" otherwise. Override with a top-level "finish"
* field on the script object.
*
* If the user message is not valid JSON or doesn't match the schema, the
* model falls back to a default text response (backward compatible).
*/
// ── Protocol types ──────────────────────────────────────────────────────
type TextAction = { type: "text"; content: string }
type ToolCallAction = { type: "tool_call"; name: string; input: Record<string, unknown> }
type ThinkingAction = { type: "thinking"; content: string }
type ListToolsAction = { type: "list_tools" }
type ErrorAction = { type: "error"; message: string }
type Action = TextAction | ToolCallAction | ThinkingAction | ListToolsAction | ErrorAction
type Script = {
steps: Action[][]
}
// ── Helpers ─────────────────────────────────────────────────────────────
function text(options: LanguageModelV2CallOptions): string {
for (const msg of [...options.prompt].reverse()) {
if (msg.role !== "user") continue
for (const part of msg.content) {
if (part.type === "text") return part.text
}
}
return ""
}
/** Count tool-result rounds since the last user message. */
function round(options: LanguageModelV2CallOptions): number {
let count = 0
for (const msg of [...options.prompt].reverse()) {
if (msg.role === "user") break
if (msg.role === "tool") count++
}
return count
}
function parse(raw: string): Script | undefined {
try {
const json = JSON.parse(raw)
if (!json || !Array.isArray(json.steps)) return undefined
return json as Script
} catch {
return undefined
}
}
function tools(options: LanguageModelV2CallOptions): LanguageModelV2FunctionTool[] {
if (!options.tools) return []
return options.tools.filter((t): t is LanguageModelV2FunctionTool => t.type === "function")
}
function emit(actions: Action[], options: LanguageModelV2CallOptions): LanguageModelV2StreamPart[] {
const chunks: LanguageModelV2StreamPart[] = []
let tid = 0
let rid = 0
let xid = 0
for (const action of actions) {
switch (action.type) {
case "text": {
const id = `mock-text-${xid++}`
chunks.push(
{ type: "text-start", id },
{ type: "text-delta", id, delta: action.content },
{ type: "text-end", id },
)
break
}
case "tool_call": {
const id = `mock-call-${tid++}`
const input = JSON.stringify(action.input)
chunks.push(
{ type: "tool-input-start", id, toolName: action.name },
{ type: "tool-input-delta", id, delta: input },
{ type: "tool-input-end", id },
{ type: "tool-call" as const, toolCallId: id, toolName: action.name, input },
)
break
}
case "thinking": {
const id = `mock-reasoning-${rid++}`
chunks.push(
{ type: "reasoning-start", id },
{ type: "reasoning-delta", id, delta: action.content },
{ type: "reasoning-end", id },
)
break
}
case "list_tools": {
const id = `mock-text-${xid++}`
const defs = tools(options).map((t) => ({
name: t.name,
description: t.description,
input: t.inputSchema,
}))
chunks.push(
{ type: "text-start", id },
{ type: "text-delta", id, delta: JSON.stringify(defs, null, 2) },
{ type: "text-end", id },
)
break
}
case "error": {
chunks.push({ type: "error", error: new Error(action.message) })
break
}
}
}
return chunks
}
// ── Model ───────────────────────────────────────────────────────────────
export class MockLanguageModel implements LanguageModelV2 {
readonly specificationVersion = "v2" as const
readonly provider: string
readonly modelId: string
readonly supportedUrls: Record<string, RegExp[]> = {}
constructor(
id: string,
readonly options: { provider: string },
) {
this.modelId = id
this.provider = options.provider
}
async doGenerate(options: LanguageModelV2CallOptions): Promise<never> {
throw new Error("`doGenerate` not implemented")
}
async doStream(options: LanguageModelV2CallOptions) {
const raw = text(options)
const script = parse(raw)
const r = round(options)
const actions = script ? (script.steps[r] ?? []) : undefined
const chunks: LanguageModelV2StreamPart[] = [
{ type: "stream-start", warnings: [] },
{
type: "response-metadata",
id: "mock-response",
modelId: this.modelId,
timestamp: new Date(),
},
]
if (actions) {
chunks.push(...emit(actions, options))
} else {
// Fallback: plain text response (backward compatible)
chunks.push(
{ type: "text-start", id: "mock-text-0" },
{
type: "text-delta",
id: "mock-text-0",
delta: `[mock] This is a streamed mock response from model "${this.modelId}". `,
},
{
type: "text-delta",
id: "mock-text-0",
delta: "The mock provider does not call any real API.",
},
{ type: "text-end", id: "mock-text-0" },
)
}
const called = actions?.some((a) => a.type === "tool_call")
chunks.push({
type: "finish",
finishReason: called ? "tool-calls" : "stop",
usage: { inputTokens: 10, outputTokens: 20, totalTokens: 30 },
})
const stream = new ReadableStream<LanguageModelV2StreamPart>({
start(controller) {
for (const chunk of chunks) controller.enqueue(chunk)
controller.close()
},
})
return { stream }
}
}

View File

@@ -0,0 +1,18 @@
import type { BunPlugin } from "bun"
import { Filesystem } from "./vfs"
/**
* Bun plugin that intercepts all loads of `util/filesystem.ts` and replaces
* the real Filesystem namespace with the in-memory VFS implementation.
*
* Must be registered via preload before any application code runs.
*/
export const vfsPlugin: BunPlugin = {
name: "vfs",
setup(build) {
build.onLoad({ filter: /util\/filesystem\.ts$/ }, () => ({
exports: { Filesystem },
loader: "object",
}))
},
}

View File

@@ -0,0 +1,2 @@
import { vfsPlugin } from "./plugin"
Bun.plugin(vfsPlugin)

View File

@@ -0,0 +1,6 @@
#!/bin/sh
ROOT="$(dirname "$0")"
cd "$ROOT/../../../.."
sandbox-exec -f ./src/provider/sdk/mock/sandbox.sb -D HOME=$HOME bun --preload "$ROOT/preload.ts" "src/index.ts" serve

View File

@@ -0,0 +1,345 @@
/**
* Shared core for mock runners: HTTP, SSE, script generation, message handling.
*/
import path from "path"
// ── Types ───────────────────────────────────────────────────────────────
export type Tool = {
id: string
description: string
parameters: {
type: string
properties?: Record<string, { type: string; description?: string }>
required?: string[]
}
}
export type Action =
| { type: "text"; content: string }
| { type: "tool_call"; name: string; input: Record<string, unknown> }
| { type: "thinking"; content: string }
| { type: "list_tools" }
| { type: "error"; message: string }
export type Script = { steps: Action[][] }
export type Event = { type: string; properties: Record<string, any> }
export type Message = { info: Record<string, any>; parts: Record<string, any>[] }
type Listener = (event: Event) => void
export type Instance = {
name: string
base: string
sse: AbortController
}
// ── HTTP ────────────────────────────────────────────────────────────────
export async function api<T = unknown>(base: string, method: string, path: string, body?: unknown): Promise<T> {
const opts: RequestInit = {
method,
headers: { "Content-Type": "application/json" },
}
if (body !== undefined) opts.body = JSON.stringify(body)
const res = await fetch(`${base}${path}`, opts)
if (!res.ok) {
const text = await res.text().catch(() => "")
throw new Error(`${method} ${path}${res.status}: ${text}`)
}
if (res.status === 204) return undefined as T
return res.json() as T
}
// ── SSE ─────────────────────────────────────────────────────────────────
const listeners = new Map<AbortController, Listener>()
function subscribe(base: string, cb: Listener): AbortController {
const abort = new AbortController()
;(async () => {
const res = await fetch(`${base}/event`, {
headers: { Accept: "text/event-stream" },
signal: abort.signal,
})
if (!res.ok || !res.body) {
log("SSE connect failed", base, res.status)
return
}
const reader = res.body.getReader()
const decoder = new TextDecoder()
let buf = ""
while (true) {
const { done, value } = await reader.read()
if (done) break
buf += decoder.decode(value, { stream: true })
const lines = buf.split("\n")
buf = lines.pop()!
for (const line of lines) {
if (!line.startsWith("data: ")) continue
try {
cb(JSON.parse(line.slice(6)))
} catch {}
}
}
})().catch(() => {})
return abort
}
export function startSSE(base: string): AbortController {
const ctrl = subscribe(base, (evt) => {
const fn = listeners.get(ctrl)
fn?.(evt)
})
listeners.set(ctrl, () => {})
return ctrl
}
export function idle(sid: string, sse: AbortController, timeout = 60_000): Promise<void> {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
cleanup()
reject(new Error(`session ${sid} did not become idle within ${timeout}ms`))
}, timeout)
const orig = listeners.get(sse)
const handler = (evt: Event) => {
orig?.(evt)
if (evt.type !== "session.status") return
if (evt.properties.sessionID !== sid) return
if (evt.properties.status?.type === "idle") {
cleanup()
resolve()
}
}
listeners.set(sse, handler)
function cleanup() {
clearTimeout(timer)
if (orig) listeners.set(sse, orig)
}
})
}
// ── Tool discovery ──────────────────────────────────────────────────────
let cachedTools: Tool[] | undefined
export async function tools(base: string): Promise<Tool[]> {
if (cachedTools) return cachedTools
cachedTools = await api<Tool[]>(base, "GET", "/experimental/tool?provider=mock&model=mock-model")
return cachedTools
}
// ── Random generators ───────────────────────────────────────────────────
function pick<T>(arr: T[]): T {
return arr[Math.floor(Math.random() * arr.length)]
}
export function rand(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1)) + min
}
const WORDS = [
"foo",
"bar",
"baz",
"qux",
"hello",
"world",
"test",
"alpha",
"beta",
"gamma",
"delta",
"src",
"lib",
"tmp",
]
const EXTS = [".ts", ".js", ".json", ".txt", ".md"]
function word() {
return pick(WORDS)
}
function sentence() {
const n = rand(3, 12)
return Array.from({ length: n }, () => word()).join(" ")
}
function filepath() {
const depth = rand(1, 3)
const parts = Array.from({ length: depth }, () => word())
return parts.join("/") + pick(EXTS)
}
function fakeInput(tool: Tool): Record<string, unknown> {
const result: Record<string, unknown> = {}
const props = tool.parameters.properties ?? {}
for (const [key, schema] of Object.entries(props)) {
switch (schema.type) {
case "string":
if (key.toLowerCase().includes("path") || key.toLowerCase().includes("file")) {
result[key] = filepath()
} else if (key.toLowerCase().includes("pattern") || key.toLowerCase().includes("regex")) {
result[key] = word()
} else if (key.toLowerCase().includes("command") || key.toLowerCase().includes("cmd")) {
result[key] = `echo ${word()}`
} else {
result[key] = sentence()
}
break
case "number":
case "integer":
result[key] = rand(1, 100)
break
case "boolean":
result[key] = Math.random() > 0.5
break
case "object":
result[key] = {}
break
case "array":
result[key] = []
break
default:
result[key] = sentence()
}
}
return result
}
// ── Action generators ───────────────────────────────────────────────────
const SAFE_TOOLS = new Set(["read", "glob", "grep", "todowrite", "webfetch", "websearch", "codesearch"])
const WRITE_TOOLS = new Set(["write", "edit", "bash"])
function textAction(): Action {
return { type: "text", content: sentence() }
}
function thinkingAction(): Action {
return { type: "thinking", content: sentence() }
}
function errorAction(): Action {
return { type: "error", message: `mock error: ${word()}` }
}
function listToolsAction(): Action {
return { type: "list_tools" }
}
async function toolAction(base: string): Promise<Action> {
const all = await tools(base)
const safe = all.filter((t) => SAFE_TOOLS.has(t.id) || WRITE_TOOLS.has(t.id))
if (!safe.length) return textAction()
const tool = pick(safe)
return { type: "tool_call", name: tool.id, input: fakeInput(tool) }
}
// ── Script generation ───────────────────────────────────────────────────
export async function script(base: string): Promise<Script> {
const r = Math.random()
if (r < 0.4) {
const call = await toolAction(base)
return { steps: [[call], [textAction()]] }
}
if (r < 0.6) {
return { steps: [[thinkingAction(), textAction()]] }
}
if (r < 0.75) {
const n = rand(2, 4)
const calls: Action[] = []
for (let i = 0; i < n; i++) calls.push(await toolAction(base))
return { steps: [calls, [textAction()]] }
}
if (r < 0.85) {
return { steps: [[textAction()]] }
}
if (r < 0.9) {
return { steps: [[listToolsAction()]] }
}
if (r < 0.95) {
const call = await toolAction(base)
return { steps: [[thinkingAction(), call], [textAction()]] }
}
return { steps: [[errorAction()]] }
}
// ── Pre-generate all scripts for a session ──────────────────────────────
export async function generate(base: string, count: number): Promise<Script[]> {
const scripts: Script[] = []
for (let i = 0; i < count; i++) scripts.push(await script(base))
return scripts
}
// ── Messages ────────────────────────────────────────────────────────────
export async function messages(base: string, sid: string): Promise<Message[]> {
return api<Message[]>(base, "GET", `/session/${sid}/message`)
}
// ── Run a full session: send all scripts, return all messages ───────────
export async function run(inst: Instance, scripts: Script[]): Promise<Message[]> {
const info = await api<{ id: string }>(inst.base, "POST", "/session", {})
const sid = info.id
for (const s of scripts) {
const payload = JSON.stringify(s)
await api(inst.base, "POST", `/session/${sid}/prompt_async`, {
parts: [{ type: "text", text: payload }],
model: { providerID: "mock", modelID: "mock-model" },
})
await idle(sid, inst.sse)
}
return messages(inst.base, sid)
}
// ── Connect to an instance ──────────────────────────────────────────────
export async function connect(name: string, port: string): Promise<Instance> {
const base = `http://localhost:${port}`
const health = await api<{ healthy: boolean; version: string }>(base, "GET", "/global/health")
if (!health.healthy) throw new Error(`${name} not healthy`)
log(`${name}: version ${health.version} at ${base}`)
const sse = startSSE(base)
return { name, base, sse }
}
// ── Logging ─────────────────────────────────────────────────────────────
export function log(...args: unknown[]) {
const ts = new Date().toISOString().slice(11, 23)
console.log(`[${ts}]`, ...args)
}
export function summary(s: Script): string {
const actions = s.steps.flat()
const types = actions.map((a) => {
if (a.type === "tool_call") return `tool:${a.name}`
return a.type
})
return `${s.steps.length} step(s): ${types.join(", ")}`
}
export function logMessages(msgs: Message[]) {
for (const msg of msgs) {
const role = msg.info.role
const parts = msg.parts.map((p: any) => {
if (p.type === "text") return `text: ${p.text?.slice(0, 80)}${p.text?.length > 80 ? "..." : ""}`
if (p.type === "tool") return `tool:${p.tool}(${p.state?.status})`
if (p.type === "reasoning") return `reasoning: ${p.text?.slice(0, 60)}${(p.text?.length ?? 0) > 60 ? "..." : ""}`
if (p.type === "step-start") return "step-start"
if (p.type === "step-finish") return `step-finish(${p.reason})`
return p.type
})
log(` ${role} [${msg.info.id?.slice(0, 8)}] ${parts.join(" | ")}`)
}
}

View File

@@ -0,0 +1,130 @@
/**
* Mock Runner — Dual Instance Diff
*
* Connects to TWO running opencode servers, runs identical mock RPC scripts
* against both (all turns on A, then all turns on B), and diffs the results.
* Each session run writes the full serialized messages and a unified diff
* into a folder under ./errors/<id>/.
*
* Usage:
* bun run src/provider/sdk/mock/runner/diff.ts <port1> <port2>
*/
import path from "path"
import { connect, generate, run, log, summary, tools, rand, type Message } from "./core"
const port1 = process.argv[2]
const port2 = process.argv[3]
if (!port1 || !port2) {
console.error("Usage: bun run src/provider/sdk/mock/runner/diff.ts <port1> <port2>")
process.exit(1)
}
const ERRORS_DIR = path.join(import.meta.dir, "errors")
// ── Normalize ───────────────────────────────────────────────────────────
function normalize(msgs: Message[]): object[] {
return msgs.map((m) => ({
role: m.info.role,
parts: m.parts.map((p) => {
const { id, sessionID, messageID, ...rest } = p
if (rest.type === "tool" && rest.state) {
const { time, ...state } = rest.state
return { ...rest, state }
}
if (rest.type === "step-finish") {
const { cost, tokens, ...finish } = rest
return finish
}
if ("time" in rest) {
const { time, ...without } = rest
return without
}
return rest
}),
}))
}
// ── Write results ───────────────────────────────────────────────────────
async function writeResults(scripts: { steps: object[][] }[], a: Message[], b: Message[]): Promise<string | false> {
const na = normalize(a)
const nb = normalize(b)
const ja = JSON.stringify(na, null, 2)
const jb = JSON.stringify(nb, null, 2)
if (ja === jb) return false
const id = Math.random().toString(36).slice(2, 10)
const dir = path.join(ERRORS_DIR, id)
const fileA = path.join(dir, "normalized_a.json")
const fileB = path.join(dir, "normalized_b.json")
await Promise.all([
Bun.write(path.join(dir, "messages_a.json"), JSON.stringify(a, null, 2)),
Bun.write(path.join(dir, "messages_b.json"), JSON.stringify(b, null, 2)),
Bun.write(fileA, ja),
Bun.write(fileB, jb),
])
// generate unified diff via system `diff`
const proc = Bun.spawn(["diff", "-u", "--label", "instance_a", fileA, "--label", "instance_b", fileB], {
stdout: "pipe",
})
const patch = await new Response(proc.stdout).text()
await proc.exited
await Bun.write(path.join(dir, "diff.patch"), patch)
return dir
}
// ── Session loop ────────────────────────────────────────────────────────
async function session(a: Awaited<ReturnType<typeof connect>>, b: Awaited<ReturnType<typeof connect>>) {
const turns = rand(30, 100)
const scripts = await generate(a.base, turns)
log(`${turns} turns generated: ${scripts.map((s) => summary(s)).join(", ")}`)
log(`running ${turns} turns on A...`)
const msgsA = await run(a, scripts)
log(`A: ${msgsA.length} messages`)
log(`running ${turns} turns on B...`)
const msgsB = await run(b, scripts)
log(`B: ${msgsB.length} messages`)
const dir = await writeResults(scripts, msgsA, msgsB)
if (dir) {
log(`DIFF → ${dir}`)
} else {
log(`OK — no differences`)
}
}
// ── Main ────────────────────────────────────────────────────────────────
async function main() {
const a = await connect("A", port1)
const b = await connect("B", port2)
const t = await tools(a.base)
log(`${t.length} tools: ${t.map((x) => x.id).join(", ")}`)
while (true) {
try {
await session(a, b)
} catch (e: any) {
log(`session failed: ${e.message}`)
await Bun.sleep(2000)
}
}
}
main().catch((e) => {
console.error(e)
process.exit(1)
})

View File

@@ -0,0 +1,8 @@
# Session diff
# 4 turns
# Instance A: port 4096
# Instance B: port 4096
# A messages: 11
# B messages: 11
--- instance A
+++ instance B

View File

@@ -0,0 +1,716 @@
[
{
"info": {
"role": "user",
"time": {
"created": 1774379576189
},
"summary": {
"diffs": []
},
"agent": "build",
"model": {
"providerID": "mock",
"modelID": "mock-model"
},
"id": "msg_d2143ab7d001ASSb6uqvhJ7FN7",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad"
},
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"tool_call\",\"name\":\"write\",\"input\":{}}],[{\"type\":\"text\",\"content\":\"alpha src test\"}]]}",
"id": "prt_d2143ab7d0026isp1ndlSmyxiw",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143ab7d001ASSb6uqvhJ7FN7"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379576192,
"completed": 1774379576206
},
"parentID": "msg_d2143ab7d001ASSb6uqvhJ7FN7",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "tool-calls",
"id": "msg_d2143ab80001AOMoMijnCTf0bb",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143ab89001qUzTE2B6gdv68l",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143ab80001AOMoMijnCTf0bb"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user] steps=2",
"time": {
"start": 1774379576203,
"end": 1774379576203
},
"id": "prt_d2143ab8a001pX1CoTDdUrvSdW",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143ab80001AOMoMijnCTf0bb"
},
{
"type": "tool",
"callID": "mock-call-0",
"tool": "write",
"state": {
"status": "error",
"input": {},
"error": "The write tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"content\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n },\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"filePath\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema.",
"time": {
"start": 1774379576204,
"end": 1774379576204
}
},
"id": "prt_d2143ab8b001wYWKhe82IcjnFy",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143ab80001AOMoMijnCTf0bb"
},
{
"type": "step-finish",
"reason": "tool-calls",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143ab8c001m1pij4LiODTsKg",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143ab80001AOMoMijnCTf0bb"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379576207,
"completed": 1774379576211
},
"parentID": "msg_d2143ab7d001ASSb6uqvhJ7FN7",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "stop",
"id": "msg_d2143ab8f001BgMv7OWVnzUnr9",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143ab91001F7jxegR2MOXnNN",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143ab8f001BgMv7OWVnzUnr9"
},
{
"type": "text",
"text": "[mock-debug] round=1 roles=[system,user,assistant,tool] steps=2",
"time": {
"start": 1774379576209,
"end": 1774379576209
},
"id": "prt_d2143ab910027u37aFl4EQn4VO",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143ab8f001BgMv7OWVnzUnr9"
},
{
"type": "text",
"text": "alpha src test",
"time": {
"start": 1774379576209,
"end": 1774379576209
},
"id": "prt_d2143ab91003Ci5xJpgoiTJ1Ze",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143ab8f001BgMv7OWVnzUnr9"
},
{
"type": "step-finish",
"reason": "stop",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143ab92001PzVA5i0DjhH2l0",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143ab8f001BgMv7OWVnzUnr9"
}
]
},
{
"info": {
"role": "user",
"time": {
"created": 1774379577216
},
"summary": {
"diffs": []
},
"agent": "build",
"model": {
"providerID": "mock",
"modelID": "mock-model"
},
"id": "msg_d2143af80001O4aaGWU6LeV09I",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad"
},
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"thinking\",\"content\":\"lib lib hello qux src foo tmp gamma alpha\"},{\"type\":\"text\",\"content\":\"gamma bar gamma test src beta\"}]]}",
"id": "prt_d2143af80002E2rLd5vwk4FJPG",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143af80001O4aaGWU6LeV09I"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379577217,
"completed": 1774379577222
},
"parentID": "msg_d2143af80001O4aaGWU6LeV09I",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "stop",
"id": "msg_d2143af81001gD7i0NZzdzwZ1G",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143af830010iTFlowoExs7b0",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143af81001gD7i0NZzdzwZ1G"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user,assistant,tool,assistant,user] steps=1",
"time": {
"start": 1774379577219,
"end": 1774379577219
},
"id": "prt_d2143af83002f1VULTn0TY9R2E",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143af81001gD7i0NZzdzwZ1G"
},
{
"type": "reasoning",
"text": "lib lib hello qux src foo tmp gamma alpha",
"time": {
"start": 1774379577220,
"end": 1774379577220
},
"id": "prt_d2143af84001lRiLQgXzBHeuqP",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143af81001gD7i0NZzdzwZ1G"
},
{
"type": "text",
"text": "gamma bar gamma test src beta",
"time": {
"start": 1774379577220,
"end": 1774379577220
},
"id": "prt_d2143af84002BOGhkxAIx5gVVu",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143af81001gD7i0NZzdzwZ1G"
},
{
"type": "step-finish",
"reason": "stop",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143af85001jLwqpqu4fTNje4",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143af81001gD7i0NZzdzwZ1G"
}
]
},
{
"info": {
"role": "user",
"time": {
"created": 1774379578226
},
"summary": {
"diffs": []
},
"agent": "build",
"model": {
"providerID": "mock",
"modelID": "mock-model"
},
"id": "msg_d2143b372001KvrMbCNcI3dB63",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad"
},
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"tool_call\",\"name\":\"grep\",\"input\":{}},{\"type\":\"tool_call\",\"name\":\"todowrite\",\"input\":{}}],[{\"type\":\"text\",\"content\":\"test alpha alpha\"}]]}",
"id": "prt_d2143b372002qQsCtyXB3KXtm0",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143b372001KvrMbCNcI3dB63"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379578227,
"completed": 1774379578239
},
"parentID": "msg_d2143b372001KvrMbCNcI3dB63",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "tool-calls",
"id": "msg_d2143b373001HkR7TzdIYRbdL1",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143b376001KbLK6KZc5PYF9f",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143b373001HkR7TzdIYRbdL1"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user,assistant,tool,assistant,user,assistant,user] steps=2",
"time": {
"start": 1774379578231,
"end": 1774379578231
},
"id": "prt_d2143b376002iC7BK0widOQC98",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143b373001HkR7TzdIYRbdL1"
},
{
"type": "tool",
"callID": "mock-call-0",
"tool": "grep",
"state": {
"status": "error",
"input": {},
"error": "The grep tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"pattern\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema.",
"time": {
"start": 1774379578231,
"end": 1774379578232
}
},
"id": "prt_d2143b3770014lsHALNBXJNBmI",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143b373001HkR7TzdIYRbdL1"
},
{
"type": "tool",
"callID": "mock-call-1",
"tool": "todowrite",
"state": {
"status": "error",
"input": {},
"error": "The todowrite tool was called with invalid arguments: [\n {\n \"expected\": \"array\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"todos\"\n ],\n \"message\": \"Invalid input: expected array, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema.",
"time": {
"start": 1774379578232,
"end": 1774379578235
}
},
"id": "prt_d2143b378001e5p04JzcoGJo6Y",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143b373001HkR7TzdIYRbdL1"
},
{
"type": "step-finish",
"reason": "tool-calls",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143b37b001Ng2DGBhVEPgaSM",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143b373001HkR7TzdIYRbdL1"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379578240,
"completed": 1774379578248
},
"parentID": "msg_d2143b372001KvrMbCNcI3dB63",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "stop",
"id": "msg_d2143b380001Ls2Trp5boTPmPh",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143b383001oK6kY2ziZUzQZX",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143b380001Ls2Trp5boTPmPh"
},
{
"type": "text",
"text": "[mock-debug] round=1 roles=[system,user,assistant,tool,assistant,user,assistant,user,assistant,tool] steps=2",
"time": {
"start": 1774379578244,
"end": 1774379578244
},
"id": "prt_d2143b383002VGvjz7v5MvsYbp",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143b380001Ls2Trp5boTPmPh"
},
{
"type": "text",
"text": "test alpha alpha",
"time": {
"start": 1774379578246,
"end": 1774379578246
},
"id": "prt_d2143b384001VmUMKZMYSR29dX",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143b380001Ls2Trp5boTPmPh"
},
{
"type": "step-finish",
"reason": "stop",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143b386001nhWATjMdIw9JnA",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143b380001Ls2Trp5boTPmPh"
}
]
},
{
"info": {
"role": "user",
"time": {
"created": 1774379579237
},
"summary": {
"diffs": []
},
"agent": "build",
"model": {
"providerID": "mock",
"modelID": "mock-model"
},
"id": "msg_d2143b765001TJgD7CmDuJKvX4",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad"
},
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"tool_call\",\"name\":\"glob\",\"input\":{}}],[{\"type\":\"text\",\"content\":\"test baz lib tmp beta delta qux\"}]]}",
"id": "prt_d2143b765002XDp4iZHaPM3owV",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143b765001TJgD7CmDuJKvX4"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379579313,
"completed": 1774379579321
},
"parentID": "msg_d2143b765001TJgD7CmDuJKvX4",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "tool-calls",
"id": "msg_d2143b7b1001wKt6ridUEPuyBy",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143b7b4001qSKg1nigww5QWL",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143b7b1001wKt6ridUEPuyBy"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user,assistant,tool,assistant,user,assistant,user,assistant,tool,assistant,user] steps=2",
"time": {
"start": 1774379579317,
"end": 1774379579317
},
"id": "prt_d2143b7b5001ZgrZbJWBljSF0B",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143b7b1001wKt6ridUEPuyBy"
},
{
"type": "tool",
"callID": "mock-call-0",
"tool": "glob",
"state": {
"status": "error",
"input": {},
"error": "The glob tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"pattern\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema.",
"time": {
"start": 1774379579318,
"end": 1774379579318
}
},
"id": "prt_d2143b7b5002Hy3vHvluGodElw",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143b7b1001wKt6ridUEPuyBy"
},
{
"type": "step-finish",
"reason": "tool-calls",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143b7b7001F01F2XuppoBAvg",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143b7b1001wKt6ridUEPuyBy"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379579323,
"completed": 1774379579327
},
"parentID": "msg_d2143b765001TJgD7CmDuJKvX4",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "stop",
"id": "msg_d2143b7bb001MwZ2fPznUkNWaD",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143b7bd001B5PfId3nlb7B4p",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143b7bb001MwZ2fPznUkNWaD"
},
{
"type": "text",
"text": "[mock-debug] round=1 roles=[system,user,assistant,tool,assistant,user,assistant,user,assistant,tool,assistant,user,assistant,tool] steps=2",
"time": {
"start": 1774379579325,
"end": 1774379579325
},
"id": "prt_d2143b7bd002KNEoobBD1xRxl5",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143b7bb001MwZ2fPznUkNWaD"
},
{
"type": "text",
"text": "test baz lib tmp beta delta qux",
"time": {
"start": 1774379579326,
"end": 1774379579326
},
"id": "prt_d2143b7be0010qkY9dT7EP5bSL",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143b7bb001MwZ2fPznUkNWaD"
},
{
"type": "step-finish",
"reason": "stop",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143b7be002kAMC1rvsBo7YI2",
"sessionID": "ses_2debc5893ffeaAXV63RenuYRad",
"messageID": "msg_d2143b7bb001MwZ2fPznUkNWaD"
}
]
}
]

View File

@@ -0,0 +1,716 @@
[
{
"info": {
"role": "user",
"time": {
"created": 1774379582422
},
"summary": {
"diffs": []
},
"agent": "build",
"model": {
"providerID": "mock",
"modelID": "mock-model"
},
"id": "msg_d2143c3d5001KT7T04r8FyRvjS",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B"
},
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"tool_call\",\"name\":\"write\",\"input\":{}}],[{\"type\":\"text\",\"content\":\"alpha src test\"}]]}",
"id": "prt_d2143c3d6001SGxGr1RY3wObHC",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143c3d5001KT7T04r8FyRvjS"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379582423,
"completed": 1774379582445
},
"parentID": "msg_d2143c3d5001KT7T04r8FyRvjS",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "tool-calls",
"id": "msg_d2143c3d7001769e2U0WPHNSTE",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143c3e2001CObsaU2q0Y53qL",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143c3d7001769e2U0WPHNSTE"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user] steps=2",
"time": {
"start": 1774379582434,
"end": 1774379582434
},
"id": "prt_d2143c3e2002aF1YmpGhj0Mwha",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143c3d7001769e2U0WPHNSTE"
},
{
"type": "tool",
"callID": "mock-call-0",
"tool": "write",
"state": {
"status": "error",
"input": {},
"error": "The write tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"content\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n },\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"filePath\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema.",
"time": {
"start": 1774379582441,
"end": 1774379582442
}
},
"id": "prt_d2143c3e3001IEEo3tnjUPs15a",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143c3d7001769e2U0WPHNSTE"
},
{
"type": "step-finish",
"reason": "tool-calls",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143c3ea001bqur0mrFj2phlQ",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143c3d7001769e2U0WPHNSTE"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379582446,
"completed": 1774379582450
},
"parentID": "msg_d2143c3d5001KT7T04r8FyRvjS",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "stop",
"id": "msg_d2143c3ee0010AYXnrOaS99ZOQ",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143c3f0001v8F3QQ0oX4S2rg",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143c3ee0010AYXnrOaS99ZOQ"
},
{
"type": "text",
"text": "[mock-debug] round=1 roles=[system,user,assistant,tool] steps=2",
"time": {
"start": 1774379582448,
"end": 1774379582448
},
"id": "prt_d2143c3f000204hd55JZ8otMEp",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143c3ee0010AYXnrOaS99ZOQ"
},
{
"type": "text",
"text": "alpha src test",
"time": {
"start": 1774379582449,
"end": 1774379582449
},
"id": "prt_d2143c3f1001xS7OWBZ033y8l6",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143c3ee0010AYXnrOaS99ZOQ"
},
{
"type": "step-finish",
"reason": "stop",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143c3f100229kD2ReqThCFSt",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143c3ee0010AYXnrOaS99ZOQ"
}
]
},
{
"info": {
"role": "user",
"time": {
"created": 1774379583480
},
"summary": {
"diffs": []
},
"agent": "build",
"model": {
"providerID": "mock",
"modelID": "mock-model"
},
"id": "msg_d2143c7f8001C6ROv8OzFHTCZM",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B"
},
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"thinking\",\"content\":\"lib lib hello qux src foo tmp gamma alpha\"},{\"type\":\"text\",\"content\":\"gamma bar gamma test src beta\"}]]}",
"id": "prt_d2143c7f8002dYy3UaNalp5BbL",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143c7f8001C6ROv8OzFHTCZM"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379583483,
"completed": 1774379583488
},
"parentID": "msg_d2143c7f8001C6ROv8OzFHTCZM",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "stop",
"id": "msg_d2143c7fb001we6hPbzshkzrNy",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143c7fe001TeFm1JF8iNEjwl",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143c7fb001we6hPbzshkzrNy"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user,assistant,tool,assistant,user] steps=1",
"time": {
"start": 1774379583487,
"end": 1774379583487
},
"id": "prt_d2143c7fe002qY6Re5hhZKnN5C",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143c7fb001we6hPbzshkzrNy"
},
{
"type": "reasoning",
"text": "lib lib hello qux src foo tmp gamma alpha",
"time": {
"start": 1774379583487,
"end": 1774379583487
},
"id": "prt_d2143c7ff001vrJUyBL0kPYNCQ",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143c7fb001we6hPbzshkzrNy"
},
{
"type": "text",
"text": "gamma bar gamma test src beta",
"time": {
"start": 1774379583487,
"end": 1774379583487
},
"id": "prt_d2143c7ff0023EdYEmu7mV0nO5",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143c7fb001we6hPbzshkzrNy"
},
{
"type": "step-finish",
"reason": "stop",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143c7ff003uV89tAjBWj79ko",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143c7fb001we6hPbzshkzrNy"
}
]
},
{
"info": {
"role": "user",
"time": {
"created": 1774379584507
},
"summary": {
"diffs": []
},
"agent": "build",
"model": {
"providerID": "mock",
"modelID": "mock-model"
},
"id": "msg_d2143cbfb001gAL4NWFXdZgjKs",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B"
},
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"tool_call\",\"name\":\"grep\",\"input\":{}},{\"type\":\"tool_call\",\"name\":\"todowrite\",\"input\":{}}],[{\"type\":\"text\",\"content\":\"test alpha alpha\"}]]}",
"id": "prt_d2143cbfb002hFQRFJx8dUCsxJ",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143cbfb001gAL4NWFXdZgjKs"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379584508,
"completed": 1774379584515
},
"parentID": "msg_d2143cbfb001gAL4NWFXdZgjKs",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "tool-calls",
"id": "msg_d2143cbfc001JzvvQ9J0gr9qRg",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143cc00001dev0B2qo3KPN8O",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143cbfc001JzvvQ9J0gr9qRg"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user,assistant,tool,assistant,user,assistant,user] steps=2",
"time": {
"start": 1774379584512,
"end": 1774379584512
},
"id": "prt_d2143cc00002umrLwBNdirT4qx",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143cbfc001JzvvQ9J0gr9qRg"
},
{
"type": "tool",
"callID": "mock-call-0",
"tool": "grep",
"state": {
"status": "error",
"input": {},
"error": "The grep tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"pattern\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema.",
"time": {
"start": 1774379584513,
"end": 1774379584513
}
},
"id": "prt_d2143cc00003YdloL2qv1oAuo8",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143cbfc001JzvvQ9J0gr9qRg"
},
{
"type": "tool",
"callID": "mock-call-1",
"tool": "todowrite",
"state": {
"status": "error",
"input": {},
"error": "The todowrite tool was called with invalid arguments: [\n {\n \"expected\": \"array\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"todos\"\n ],\n \"message\": \"Invalid input: expected array, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema.",
"time": {
"start": 1774379584513,
"end": 1774379584514
}
},
"id": "prt_d2143cc010015Xy8V1X3KU3hRZ",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143cbfc001JzvvQ9J0gr9qRg"
},
{
"type": "step-finish",
"reason": "tool-calls",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143cc02001mQ04qR4P4mtb8l",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143cbfc001JzvvQ9J0gr9qRg"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379584515,
"completed": 1774379584519
},
"parentID": "msg_d2143cbfb001gAL4NWFXdZgjKs",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "stop",
"id": "msg_d2143cc03001sYIyyLHeLCbCeM",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143cc05001EWqrxaAjSmxgUi",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143cc03001sYIyyLHeLCbCeM"
},
{
"type": "text",
"text": "[mock-debug] round=1 roles=[system,user,assistant,tool,assistant,user,assistant,user,assistant,tool] steps=2",
"time": {
"start": 1774379584517,
"end": 1774379584517
},
"id": "prt_d2143cc05002MtOf0RA6dgYkXp",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143cc03001sYIyyLHeLCbCeM"
},
{
"type": "text",
"text": "test alpha alpha",
"time": {
"start": 1774379584518,
"end": 1774379584518
},
"id": "prt_d2143cc060016gbI2vMS7R5jU1",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143cc03001sYIyyLHeLCbCeM"
},
{
"type": "step-finish",
"reason": "stop",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143cc06002O5FGgKAePaaGm0",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143cc03001sYIyyLHeLCbCeM"
}
]
},
{
"info": {
"role": "user",
"time": {
"created": 1774379585461
},
"summary": {
"diffs": []
},
"agent": "build",
"model": {
"providerID": "mock",
"modelID": "mock-model"
},
"id": "msg_d2143cfb5001PPQoIRtGeviy3c",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B"
},
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"tool_call\",\"name\":\"glob\",\"input\":{}}],[{\"type\":\"text\",\"content\":\"test baz lib tmp beta delta qux\"}]]}",
"id": "prt_d2143cfb50027EsruWcB94okp9",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143cfb5001PPQoIRtGeviy3c"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379585463,
"completed": 1774379585475
},
"parentID": "msg_d2143cfb5001PPQoIRtGeviy3c",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "tool-calls",
"id": "msg_d2143cfb7001QuAkgD4erkOK2t",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143cfbd001bAWzjeTU0hd34Q",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143cfb7001QuAkgD4erkOK2t"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user,assistant,tool,assistant,user,assistant,user,assistant,tool,assistant,user] steps=2",
"time": {
"start": 1774379585470,
"end": 1774379585470
},
"id": "prt_d2143cfbd0021XodVrKNZennQd",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143cfb7001QuAkgD4erkOK2t"
},
{
"type": "tool",
"callID": "mock-call-0",
"tool": "glob",
"state": {
"status": "error",
"input": {},
"error": "The glob tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"pattern\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema.",
"time": {
"start": 1774379585471,
"end": 1774379585472
}
},
"id": "prt_d2143cfbf001hch9QVAuVVKeHX",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143cfb7001QuAkgD4erkOK2t"
},
{
"type": "step-finish",
"reason": "tool-calls",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143cfc1001qdMuYmXs9sKr7o",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143cfb7001QuAkgD4erkOK2t"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379585476,
"completed": 1774379585479
},
"parentID": "msg_d2143cfb5001PPQoIRtGeviy3c",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "stop",
"id": "msg_d2143cfc400106KBhqYfWCTNFn",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143cfc6001oMJV4hwqWbigpS",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143cfc400106KBhqYfWCTNFn"
},
{
"type": "text",
"text": "[mock-debug] round=1 roles=[system,user,assistant,tool,assistant,user,assistant,user,assistant,tool,assistant,user,assistant,tool] steps=2",
"time": {
"start": 1774379585478,
"end": 1774379585478
},
"id": "prt_d2143cfc6002GJauPaAdD5zXhQ",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143cfc400106KBhqYfWCTNFn"
},
{
"type": "text",
"text": "test baz lib tmp beta delta qux",
"time": {
"start": 1774379585478,
"end": 1774379585478
},
"id": "prt_d2143cfc6003glU1XL5UB35LJX",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143cfc400106KBhqYfWCTNFn"
},
{
"type": "step-finish",
"reason": "stop",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143cfc700106w94xd70qzIrD",
"sessionID": "ses_2debc400dfferXmjJLga0kv53B",
"messageID": "msg_d2143cfc400106KBhqYfWCTNFn"
}
]
}
]

View File

@@ -0,0 +1,210 @@
[
{
"role": "user",
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"tool_call\",\"name\":\"write\",\"input\":{}}],[{\"type\":\"text\",\"content\":\"alpha src test\"}]]}"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user] steps=2"
},
{
"type": "tool",
"callID": "mock-call-0",
"tool": "write",
"state": {
"status": "error",
"input": {},
"error": "The write tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"content\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n },\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"filePath\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema."
}
},
{
"type": "step-finish",
"reason": "tool-calls"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=1 roles=[system,user,assistant,tool] steps=2"
},
{
"type": "text",
"text": "alpha src test"
},
{
"type": "step-finish",
"reason": "stop"
}
]
},
{
"role": "user",
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"thinking\",\"content\":\"lib lib hello qux src foo tmp gamma alpha\"},{\"type\":\"text\",\"content\":\"gamma bar gamma test src beta\"}]]}"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user,assistant,tool,assistant,user] steps=1"
},
{
"type": "reasoning",
"text": "lib lib hello qux src foo tmp gamma alpha"
},
{
"type": "text",
"text": "gamma bar gamma test src beta"
},
{
"type": "step-finish",
"reason": "stop"
}
]
},
{
"role": "user",
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"tool_call\",\"name\":\"grep\",\"input\":{}},{\"type\":\"tool_call\",\"name\":\"todowrite\",\"input\":{}}],[{\"type\":\"text\",\"content\":\"test alpha alpha\"}]]}"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user,assistant,tool,assistant,user,assistant,user] steps=2"
},
{
"type": "tool",
"callID": "mock-call-0",
"tool": "grep",
"state": {
"status": "error",
"input": {},
"error": "The grep tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"pattern\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema."
}
},
{
"type": "tool",
"callID": "mock-call-1",
"tool": "todowrite",
"state": {
"status": "error",
"input": {},
"error": "The todowrite tool was called with invalid arguments: [\n {\n \"expected\": \"array\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"todos\"\n ],\n \"message\": \"Invalid input: expected array, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema."
}
},
{
"type": "step-finish",
"reason": "tool-calls"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=1 roles=[system,user,assistant,tool,assistant,user,assistant,user,assistant,tool] steps=2"
},
{
"type": "text",
"text": "test alpha alpha"
},
{
"type": "step-finish",
"reason": "stop"
}
]
},
{
"role": "user",
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"tool_call\",\"name\":\"glob\",\"input\":{}}],[{\"type\":\"text\",\"content\":\"test baz lib tmp beta delta qux\"}]]}"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user,assistant,tool,assistant,user,assistant,user,assistant,tool,assistant,user] steps=2"
},
{
"type": "tool",
"callID": "mock-call-0",
"tool": "glob",
"state": {
"status": "error",
"input": {},
"error": "The glob tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"pattern\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema."
}
},
{
"type": "step-finish",
"reason": "tool-calls"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=1 roles=[system,user,assistant,tool,assistant,user,assistant,user,assistant,tool,assistant,user,assistant,tool] steps=2"
},
{
"type": "text",
"text": "test baz lib tmp beta delta qux"
},
{
"type": "step-finish",
"reason": "stop"
}
]
}
]

View File

@@ -0,0 +1,210 @@
[
{
"role": "user",
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"tool_call\",\"name\":\"write\",\"input\":{}}],[{\"type\":\"text\",\"content\":\"alpha src test\"}]]}"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user] steps=2"
},
{
"type": "tool",
"callID": "mock-call-0",
"tool": "write",
"state": {
"status": "error",
"input": {},
"error": "The write tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"content\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n },\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"filePath\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema."
}
},
{
"type": "step-finish",
"reason": "tool-calls"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=1 roles=[system,user,assistant,tool] steps=2"
},
{
"type": "text",
"text": "alpha src test"
},
{
"type": "step-finish",
"reason": "stop"
}
]
},
{
"role": "user",
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"thinking\",\"content\":\"lib lib hello qux src foo tmp gamma alpha\"},{\"type\":\"text\",\"content\":\"gamma bar gamma test src beta\"}]]}"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user,assistant,tool,assistant,user] steps=1"
},
{
"type": "reasoning",
"text": "lib lib hello qux src foo tmp gamma alpha"
},
{
"type": "text",
"text": "gamma bar gamma test src beta"
},
{
"type": "step-finish",
"reason": "stop"
}
]
},
{
"role": "user",
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"tool_call\",\"name\":\"grep\",\"input\":{}},{\"type\":\"tool_call\",\"name\":\"todowrite\",\"input\":{}}],[{\"type\":\"text\",\"content\":\"test alpha alpha\"}]]}"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user,assistant,tool,assistant,user,assistant,user] steps=2"
},
{
"type": "tool",
"callID": "mock-call-0",
"tool": "grep",
"state": {
"status": "error",
"input": {},
"error": "The grep tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"pattern\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema."
}
},
{
"type": "tool",
"callID": "mock-call-1",
"tool": "todowrite",
"state": {
"status": "error",
"input": {},
"error": "The todowrite tool was called with invalid arguments: [\n {\n \"expected\": \"array\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"todos\"\n ],\n \"message\": \"Invalid input: expected array, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema."
}
},
{
"type": "step-finish",
"reason": "tool-calls"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=1 roles=[system,user,assistant,tool,assistant,user,assistant,user,assistant,tool] steps=2"
},
{
"type": "text",
"text": "test alpha alpha"
},
{
"type": "step-finish",
"reason": "stop"
}
]
},
{
"role": "user",
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"tool_call\",\"name\":\"glob\",\"input\":{}}],[{\"type\":\"text\",\"content\":\"test baz lib tmp beta delta qux\"}]]}"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user,assistant,tool,assistant,user,assistant,user,assistant,tool,assistant,user] steps=2"
},
{
"type": "tool",
"callID": "mock-call-0",
"tool": "glob",
"state": {
"status": "error",
"input": {},
"error": "The glob tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"pattern\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema."
}
},
{
"type": "step-finish",
"reason": "tool-calls"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=1 roles=[system,user,assistant,tool,assistant,user,assistant,user,assistant,tool,assistant,user,assistant,tool] steps=2"
},
{
"type": "text",
"text": "test baz lib tmp beta delta qux"
},
{
"type": "step-finish",
"reason": "stop"
}
]
}
]

View File

@@ -0,0 +1,8 @@
# Session diff
# 3 turns
# Instance A: port 4096
# Instance B: port 4096
# A messages: 7
# B messages: 7
--- instance A
+++ instance B

View File

@@ -0,0 +1,465 @@
[
{
"info": {
"role": "user",
"time": {
"created": 1774379588771
},
"summary": {
"diffs": []
},
"agent": "build",
"model": {
"providerID": "mock",
"modelID": "mock-model"
},
"id": "msg_d2143dca3001OhaTYuqHlFDvmt",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs"
},
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"tool_call\",\"name\":\"glob\",\"input\":{}},{\"type\":\"tool_call\",\"name\":\"glob\",\"input\":{}},{\"type\":\"tool_call\",\"name\":\"webfetch\",\"input\":{}}],[{\"type\":\"text\",\"content\":\"alpha tmp beta\"}]]}",
"id": "prt_d2143dca3002J7HBkBW7BRIkdw",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143dca3001OhaTYuqHlFDvmt"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379588774,
"completed": 1774379588783
},
"parentID": "msg_d2143dca3001OhaTYuqHlFDvmt",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "tool-calls",
"id": "msg_d2143dca6001VTBnHQdm9M2HhS",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143dcaa001rYll62dnvTbQQK",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143dca6001VTBnHQdm9M2HhS"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user] steps=2",
"time": {
"start": 1774379588779,
"end": 1774379588779
},
"id": "prt_d2143dcaa002Mg8ZIWIpUYImAA",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143dca6001VTBnHQdm9M2HhS"
},
{
"type": "tool",
"callID": "mock-call-0",
"tool": "glob",
"state": {
"status": "error",
"input": {},
"error": "The glob tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"pattern\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema.",
"time": {
"start": 1774379588780,
"end": 1774379588781
}
},
"id": "prt_d2143dcab001NcfFqgPswqln8M",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143dca6001VTBnHQdm9M2HhS"
},
{
"type": "tool",
"callID": "mock-call-1",
"tool": "glob",
"state": {
"status": "error",
"input": {},
"error": "The glob tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"pattern\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema.",
"time": {
"start": 1774379588781,
"end": 1774379588782
}
},
"id": "prt_d2143dcad0019Pa1Y3ND7F2prd",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143dca6001VTBnHQdm9M2HhS"
},
{
"type": "tool",
"callID": "mock-call-2",
"tool": "webfetch",
"state": {
"status": "error",
"input": {},
"error": "The webfetch tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"url\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema.",
"time": {
"start": 1774379588782,
"end": 1774379588782
}
},
"id": "prt_d2143dcae001a1vQ60oApHrNij",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143dca6001VTBnHQdm9M2HhS"
},
{
"type": "step-finish",
"reason": "tool-calls",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143dcae002CJtrhx4fVOKLka",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143dca6001VTBnHQdm9M2HhS"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379588784,
"completed": 1774379588790
},
"parentID": "msg_d2143dca3001OhaTYuqHlFDvmt",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "stop",
"id": "msg_d2143dcb0001I5jbgvOXa1kSHJ",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143dcb3001r7hqcHFemmnT5E",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143dcb0001I5jbgvOXa1kSHJ"
},
{
"type": "text",
"text": "[mock-debug] round=1 roles=[system,user,assistant,tool] steps=2",
"time": {
"start": 1774379588788,
"end": 1774379588788
},
"id": "prt_d2143dcb3002pQdd5l0U4exwVJ",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143dcb0001I5jbgvOXa1kSHJ"
},
{
"type": "text",
"text": "alpha tmp beta",
"time": {
"start": 1774379588789,
"end": 1774379588789
},
"id": "prt_d2143dcb5001tqCK4eWTbrS1Ie",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143dcb0001I5jbgvOXa1kSHJ"
},
{
"type": "step-finish",
"reason": "stop",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143dcb5002tkgLWI8CjZ18qU",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143dcb0001I5jbgvOXa1kSHJ"
}
]
},
{
"info": {
"role": "user",
"time": {
"created": 1774379589838
},
"summary": {
"diffs": []
},
"agent": "build",
"model": {
"providerID": "mock",
"modelID": "mock-model"
},
"id": "msg_d2143e0ce0010cbodqAmPw8M4Z",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs"
},
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"thinking\",\"content\":\"hello test baz delta src\"},{\"type\":\"text\",\"content\":\"tmp foo qux\"}]]}",
"id": "prt_d2143e0ce0020ix0YCGLbGPNzH",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143e0ce0010cbodqAmPw8M4Z"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379589840,
"completed": 1774379589845
},
"parentID": "msg_d2143e0ce0010cbodqAmPw8M4Z",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "stop",
"id": "msg_d2143e0d0001gal9nb1wbQtHtb",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143e0d3001Jiu1axRJoYd56C",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143e0d0001gal9nb1wbQtHtb"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user,assistant,tool,assistant,user] steps=1",
"time": {
"start": 1774379589843,
"end": 1774379589843
},
"id": "prt_d2143e0d3002BCxqSyOdFp4MXy",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143e0d0001gal9nb1wbQtHtb"
},
{
"type": "reasoning",
"text": "hello test baz delta src",
"time": {
"start": 1774379589843,
"end": 1774379589844
},
"id": "prt_d2143e0d3003zXNCmsGkF469zo",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143e0d0001gal9nb1wbQtHtb"
},
{
"type": "text",
"text": "tmp foo qux",
"time": {
"start": 1774379589844,
"end": 1774379589844
},
"id": "prt_d2143e0d4001HbowUiT0qQfsSR",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143e0d0001gal9nb1wbQtHtb"
},
{
"type": "step-finish",
"reason": "stop",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143e0d4002sRySxZsIQiNtKw",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143e0d0001gal9nb1wbQtHtb"
}
]
},
{
"info": {
"role": "user",
"time": {
"created": 1774379590872
},
"summary": {
"diffs": []
},
"agent": "build",
"model": {
"providerID": "mock",
"modelID": "mock-model"
},
"id": "msg_d2143e4d8001jq1nk8KvjwVhpg",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs"
},
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"thinking\",\"content\":\"alpha baz gamma\"},{\"type\":\"text\",\"content\":\"src src lib foo src alpha qux delta foo\"}]]}",
"id": "prt_d2143e4d80025Dn7lCBjC14lV3",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143e4d8001jq1nk8KvjwVhpg"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379590873,
"completed": 1774379590879
},
"parentID": "msg_d2143e4d8001jq1nk8KvjwVhpg",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "stop",
"id": "msg_d2143e4d9001utRznASk4YUE10",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143e4dd001g9U0gK61u6wi16",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143e4d9001utRznASk4YUE10"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user,assistant,tool,assistant,user,assistant,user] steps=1",
"time": {
"start": 1774379590878,
"end": 1774379590878
},
"id": "prt_d2143e4dd002xYtuQBc9mq01P7",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143e4d9001utRznASk4YUE10"
},
{
"type": "reasoning",
"text": "alpha baz gamma",
"time": {
"start": 1774379590878,
"end": 1774379590878
},
"id": "prt_d2143e4de001WoiHGqS23mR2y7",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143e4d9001utRznASk4YUE10"
},
{
"type": "text",
"text": "src src lib foo src alpha qux delta foo",
"time": {
"start": 1774379590878,
"end": 1774379590878
},
"id": "prt_d2143e4de0027c6KiMdB8fTdhc",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143e4d9001utRznASk4YUE10"
},
{
"type": "step-finish",
"reason": "stop",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143e4df001RdPVu5PHt1ZXZy",
"sessionID": "ses_2debc2792ffeOX6mtNg266sdQs",
"messageID": "msg_d2143e4d9001utRznASk4YUE10"
}
]
}
]

View File

@@ -0,0 +1,465 @@
[
{
"info": {
"role": "user",
"time": {
"created": 1774379593974
},
"summary": {
"diffs": []
},
"agent": "build",
"model": {
"providerID": "mock",
"modelID": "mock-model"
},
"id": "msg_d2143f0f6001W06kLQkLc0Avmt",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ"
},
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"tool_call\",\"name\":\"glob\",\"input\":{}},{\"type\":\"tool_call\",\"name\":\"glob\",\"input\":{}},{\"type\":\"tool_call\",\"name\":\"webfetch\",\"input\":{}}],[{\"type\":\"text\",\"content\":\"alpha tmp beta\"}]]}",
"id": "prt_d2143f0f6002f7aHc9lHE9QNyB",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143f0f6001W06kLQkLc0Avmt"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379594061,
"completed": 1774379594073
},
"parentID": "msg_d2143f0f6001W06kLQkLc0Avmt",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "tool-calls",
"id": "msg_d2143f14d001THhEKu7ayFlx3Z",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143f154001PVSKjmtsV3DLJe",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143f14d001THhEKu7ayFlx3Z"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user] steps=2",
"time": {
"start": 1774379594069,
"end": 1774379594069
},
"id": "prt_d2143f154002GsJ9lRNwVenp7X",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143f14d001THhEKu7ayFlx3Z"
},
{
"type": "tool",
"callID": "mock-call-0",
"tool": "glob",
"state": {
"status": "error",
"input": {},
"error": "The glob tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"pattern\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema.",
"time": {
"start": 1774379594070,
"end": 1774379594070
}
},
"id": "prt_d2143f155001i3kutYi3KNE5wz",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143f14d001THhEKu7ayFlx3Z"
},
{
"type": "tool",
"callID": "mock-call-1",
"tool": "glob",
"state": {
"status": "error",
"input": {},
"error": "The glob tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"pattern\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema.",
"time": {
"start": 1774379594071,
"end": 1774379594071
}
},
"id": "prt_d2143f156001R2TvLMCOOGpph6",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143f14d001THhEKu7ayFlx3Z"
},
{
"type": "tool",
"callID": "mock-call-2",
"tool": "webfetch",
"state": {
"status": "error",
"input": {},
"error": "The webfetch tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"url\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema.",
"time": {
"start": 1774379594071,
"end": 1774379594072
}
},
"id": "prt_d2143f157001gNlwORHT1lN5PV",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143f14d001THhEKu7ayFlx3Z"
},
{
"type": "step-finish",
"reason": "tool-calls",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143f158001FRPSA8RbDrqAZw",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143f14d001THhEKu7ayFlx3Z"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379594074,
"completed": 1774379594078
},
"parentID": "msg_d2143f0f6001W06kLQkLc0Avmt",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "stop",
"id": "msg_d2143f15a001JjvWPtfcpPMziU",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143f15c0010jDTWHviMVLv1n",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143f15a001JjvWPtfcpPMziU"
},
{
"type": "text",
"text": "[mock-debug] round=1 roles=[system,user,assistant,tool] steps=2",
"time": {
"start": 1774379594076,
"end": 1774379594076
},
"id": "prt_d2143f15c002JSe7fs2ISI0UEp",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143f15a001JjvWPtfcpPMziU"
},
{
"type": "text",
"text": "alpha tmp beta",
"time": {
"start": 1774379594077,
"end": 1774379594077
},
"id": "prt_d2143f15d001O2Tn3oWjYJtf5A",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143f15a001JjvWPtfcpPMziU"
},
{
"type": "step-finish",
"reason": "stop",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143f15d002dGtu4Z2ZRqqE9I",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143f15a001JjvWPtfcpPMziU"
}
]
},
{
"info": {
"role": "user",
"time": {
"created": 1774379595311
},
"summary": {
"diffs": []
},
"agent": "build",
"model": {
"providerID": "mock",
"modelID": "mock-model"
},
"id": "msg_d2143f62f001QX3387NAQuBWSk",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ"
},
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"thinking\",\"content\":\"hello test baz delta src\"},{\"type\":\"text\",\"content\":\"tmp foo qux\"}]]}",
"id": "prt_d2143f62f002tVsYdvhHZygvUd",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143f62f001QX3387NAQuBWSk"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379595323,
"completed": 1774379595332
},
"parentID": "msg_d2143f62f001QX3387NAQuBWSk",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "stop",
"id": "msg_d2143f63b001A6xJFFicEmTzJx",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143f6410016Hh0Sm1mRmt3X5",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143f63b001A6xJFFicEmTzJx"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user,assistant,tool,assistant,user] steps=1",
"time": {
"start": 1774379595330,
"end": 1774379595330
},
"id": "prt_d2143f641002ufYzrb5QanYeYN",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143f63b001A6xJFFicEmTzJx"
},
{
"type": "reasoning",
"text": "hello test baz delta src",
"time": {
"start": 1774379595330,
"end": 1774379595330
},
"id": "prt_d2143f642001QkVKO6KVAImQYz",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143f63b001A6xJFFicEmTzJx"
},
{
"type": "text",
"text": "tmp foo qux",
"time": {
"start": 1774379595331,
"end": 1774379595331
},
"id": "prt_d2143f642002MLHz0uIaPAkeZ4",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143f63b001A6xJFFicEmTzJx"
},
{
"type": "step-finish",
"reason": "stop",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143f6430012waVXZMFXu3TC8",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143f63b001A6xJFFicEmTzJx"
}
]
},
{
"info": {
"role": "user",
"time": {
"created": 1774379596713
},
"summary": {
"diffs": []
},
"agent": "build",
"model": {
"providerID": "mock",
"modelID": "mock-model"
},
"id": "msg_d2143fba9001XAh8JaW5JvpJlv",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ"
},
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"thinking\",\"content\":\"alpha baz gamma\"},{\"type\":\"text\",\"content\":\"src src lib foo src alpha qux delta foo\"}]]}",
"id": "prt_d2143fba9002dHYnp8GsUaBqDt",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143fba9001XAh8JaW5JvpJlv"
}
]
},
{
"info": {
"role": "assistant",
"time": {
"created": 1774379596722,
"completed": 1774379596748
},
"parentID": "msg_d2143fba9001XAh8JaW5JvpJlv",
"modelID": "mock-model",
"providerID": "mock",
"mode": "build",
"agent": "build",
"path": {
"cwd": "/Users/james/projects/opencode/packages/opencode",
"root": "/"
},
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"finish": "stop",
"id": "msg_d2143fbb2001XNDThR5D7TmNly",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ"
},
"parts": [
{
"type": "step-start",
"id": "prt_d2143fbc6001W6cc2hnRvnzsik",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143fbb2001XNDThR5D7TmNly"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user,assistant,tool,assistant,user,assistant,user] steps=1",
"time": {
"start": 1774379596744,
"end": 1774379596744
},
"id": "prt_d2143fbc7001N4GUWfPV2xT08C",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143fbb2001XNDThR5D7TmNly"
},
{
"type": "reasoning",
"text": "alpha baz gamma",
"time": {
"start": 1774379596744,
"end": 1774379596745
},
"id": "prt_d2143fbc80019Ts62MVwF52nIT",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143fbb2001XNDThR5D7TmNly"
},
{
"type": "text",
"text": "src src lib foo src alpha qux delta foo",
"time": {
"start": 1774379596747,
"end": 1774379596747
},
"id": "prt_d2143fbca001ho24JWb5CDEzHB",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143fbb2001XNDThR5D7TmNly"
},
{
"type": "step-finish",
"reason": "stop",
"cost": 0,
"tokens": {
"total": 30,
"input": 10,
"output": 20,
"reasoning": 0,
"cache": {
"read": 0,
"write": 0
}
},
"id": "prt_d2143fbcb001SvHn89K0x3NFNQ",
"sessionID": "ses_2debc1358ffeJw4radOUd2jQXZ",
"messageID": "msg_d2143fbb2001XNDThR5D7TmNly"
}
]
}
]

View File

@@ -0,0 +1,143 @@
[
{
"role": "user",
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"tool_call\",\"name\":\"glob\",\"input\":{}},{\"type\":\"tool_call\",\"name\":\"glob\",\"input\":{}},{\"type\":\"tool_call\",\"name\":\"webfetch\",\"input\":{}}],[{\"type\":\"text\",\"content\":\"alpha tmp beta\"}]]}"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user] steps=2"
},
{
"type": "tool",
"callID": "mock-call-0",
"tool": "glob",
"state": {
"status": "error",
"input": {},
"error": "The glob tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"pattern\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema."
}
},
{
"type": "tool",
"callID": "mock-call-1",
"tool": "glob",
"state": {
"status": "error",
"input": {},
"error": "The glob tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"pattern\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema."
}
},
{
"type": "tool",
"callID": "mock-call-2",
"tool": "webfetch",
"state": {
"status": "error",
"input": {},
"error": "The webfetch tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"url\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema."
}
},
{
"type": "step-finish",
"reason": "tool-calls"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=1 roles=[system,user,assistant,tool] steps=2"
},
{
"type": "text",
"text": "alpha tmp beta"
},
{
"type": "step-finish",
"reason": "stop"
}
]
},
{
"role": "user",
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"thinking\",\"content\":\"hello test baz delta src\"},{\"type\":\"text\",\"content\":\"tmp foo qux\"}]]}"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user,assistant,tool,assistant,user] steps=1"
},
{
"type": "reasoning",
"text": "hello test baz delta src"
},
{
"type": "text",
"text": "tmp foo qux"
},
{
"type": "step-finish",
"reason": "stop"
}
]
},
{
"role": "user",
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"thinking\",\"content\":\"alpha baz gamma\"},{\"type\":\"text\",\"content\":\"src src lib foo src alpha qux delta foo\"}]]}"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user,assistant,tool,assistant,user,assistant,user] steps=1"
},
{
"type": "reasoning",
"text": "alpha baz gamma"
},
{
"type": "text",
"text": "src src lib foo src alpha qux delta foo"
},
{
"type": "step-finish",
"reason": "stop"
}
]
}
]

View File

@@ -0,0 +1,143 @@
[
{
"role": "user",
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"tool_call\",\"name\":\"glob\",\"input\":{}},{\"type\":\"tool_call\",\"name\":\"glob\",\"input\":{}},{\"type\":\"tool_call\",\"name\":\"webfetch\",\"input\":{}}],[{\"type\":\"text\",\"content\":\"alpha tmp beta\"}]]}"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user] steps=2"
},
{
"type": "tool",
"callID": "mock-call-0",
"tool": "glob",
"state": {
"status": "error",
"input": {},
"error": "The glob tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"pattern\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema."
}
},
{
"type": "tool",
"callID": "mock-call-1",
"tool": "glob",
"state": {
"status": "error",
"input": {},
"error": "The glob tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"pattern\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema."
}
},
{
"type": "tool",
"callID": "mock-call-2",
"tool": "webfetch",
"state": {
"status": "error",
"input": {},
"error": "The webfetch tool was called with invalid arguments: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"url\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n }\n].\nPlease rewrite the input so it satisfies the expected schema."
}
},
{
"type": "step-finish",
"reason": "tool-calls"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=1 roles=[system,user,assistant,tool] steps=2"
},
{
"type": "text",
"text": "alpha tmp beta"
},
{
"type": "step-finish",
"reason": "stop"
}
]
},
{
"role": "user",
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"thinking\",\"content\":\"hello test baz delta src\"},{\"type\":\"text\",\"content\":\"tmp foo qux\"}]]}"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user,assistant,tool,assistant,user] steps=1"
},
{
"type": "reasoning",
"text": "hello test baz delta src"
},
{
"type": "text",
"text": "tmp foo qux"
},
{
"type": "step-finish",
"reason": "stop"
}
]
},
{
"role": "user",
"parts": [
{
"type": "text",
"text": "{\"steps\":[[{\"type\":\"thinking\",\"content\":\"alpha baz gamma\"},{\"type\":\"text\",\"content\":\"src src lib foo src alpha qux delta foo\"}]]}"
}
]
},
{
"role": "assistant",
"parts": [
{
"type": "step-start"
},
{
"type": "text",
"text": "[mock-debug] round=0 roles=[system,user,assistant,tool,assistant,user,assistant,user] steps=1"
},
{
"type": "reasoning",
"text": "alpha baz gamma"
},
{
"type": "text",
"text": "src src lib foo src alpha qux delta foo"
},
{
"type": "step-finish",
"reason": "stop"
}
]
}
]

View File

@@ -0,0 +1,88 @@
/**
* Mock Runner — Single Instance (Log Mode)
*
* Connects to one running opencode server, sends mock RPC scripts,
* and logs all user/assistant messages per turn.
*
* Usage:
* bun run src/provider/sdk/mock/runner/index.ts <port>
*/
import {
connect,
generate,
run,
log,
summary,
logMessages,
tools,
rand,
api,
idle,
messages,
type Message,
} from "./core"
const port = process.argv[2] || 4096;
async function session(inst: Awaited<ReturnType<typeof connect>>) {
const info = await api<{ id: string }>(inst.base, "POST", "/session", {})
const sid = info.id
const turns = rand(30, 100)
const history: Message[] = []
log(`session ${sid}${turns} turns`)
const scripts = await generate(inst.base, turns)
for (let i = 0; i < scripts.length; i++) {
const s = scripts[i]
const payload = JSON.stringify(s)
log(` [${i + 1}/${turns}] ${summary(s)}`)
try {
await api(inst.base, "POST", `/session/${sid}/prompt_async`, {
parts: [{ type: "text", text: payload }],
model: { providerID: "mock", modelID: "mock-model" },
})
await idle(sid, inst.sse)
const all = await messages(inst.base, sid)
const known = new Set(history.map((m) => m.info.id))
const fresh = all.filter((m) => !known.has(m.info.id))
log(`${fresh.length} new message(s):`)
logMessages(fresh)
history.push(...fresh)
} catch (e: any) {
log(` error on turn ${i + 1}: ${e.message}`)
await Bun.sleep(1000)
await api(inst.base, "POST", `/session/${sid}/abort`).catch(() => {})
await Bun.sleep(500)
}
}
log(`session ${sid} — done (${history.length} messages total)`)
}
async function main() {
const inst = await connect("server", port)
const t = await tools(inst.base)
log(`${t.length} tools: ${t.map((x) => x.id).join(", ")}`)
while (true) {
try {
await session(inst)
} catch (e: any) {
log(`session failed: ${e.message}`)
await Bun.sleep(2000)
}
}
}
main().catch((e) => {
console.error(e)
process.exit(1)
})

View File

@@ -0,0 +1,52 @@
import { vfsPlugin } from "../plugin"
Bun.plugin(vfsPlugin)
/**
* Starts the mock opencode server inside bun:test so coverage instrumentation works.
*
* Usage:
* bun test --coverage --coverage-reporter=lcov --timeout 0 src/provider/sdk/mock/runner/serve.test.ts
*
* The server runs until the process is killed (Ctrl-C).
* Coverage data is flushed on exit.
*/
import { test } from "bun:test"
import { Log } from "../../../../util/log"
import { Server } from "../../../../server/server"
import { Global } from "../../../../global"
import { Filesystem } from "../../../../util/filesystem"
import { JsonMigration } from "../../../../storage/json-migration"
import { Database } from "../../../../storage/db"
import path from "path"
const PORT = 4096;
test("serve", async () => {
process.env.AGENT = "1"
process.env.OPENCODE = "1"
process.env.OPENCODE_PID = String(process.pid)
await Log.init({ print: false, dev: true, level: "DEBUG" })
const marker = path.join(Global.Path.data, "opencode.db")
if (!(await Filesystem.exists(marker))) {
console.log("Running one-time database migration...")
await JsonMigration.run(Database.Client().$client, {
progress: (event) => {
const pct = Math.floor((event.current / event.total) * 100)
if (event.current === event.total || pct % 25 === 0) {
console.log(` migration: ${pct}%`)
}
},
})
console.log("Migration complete.")
}
const server = Server.listen({ port: PORT, hostname: "127.0.0.1" })
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
// keep alive until killed
await new Promise(() => {})
await server.stop()
})

View File

@@ -0,0 +1,17 @@
(version 1)
;; Start permissive, then lock down network and filesystem
(allow default)
;; ── Network: deny all, allow localhost ──
(deny network*)
(allow network* (local ip "localhost:*"))
(allow network* (remote ip "localhost:*"))
;; ── Filesystem writes: deny all, then allow temp dirs ──
(deny file-write*)
;; ── Filesystem reads: deny sensitive dirs ──
(deny file-read*
(subpath (string-append (param "HOME") "/.local"))
(subpath (string-append (param "HOME") "/.config")))

View File

@@ -0,0 +1,243 @@
import path from "path"
import { lookup } from "mime-types"
const ROOT = '/mock'
// TODO:
//
// * Some places use the `glob` utility to scan the filesystem which
// does not go through `Filesystem`. We should mock that too
function globToRegex(pattern: string, cwd: string): RegExp {
const full = pattern.startsWith("/") ? pattern : cwd.replace(/\/$/, "") + "/" + pattern
const escaped = full
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
.replace(/\*\*/g, "<<<GLOBSTAR>>>")
.replace(/\*/g, "[^/]*")
.replace(/<<<GLOBSTAR>>>/g, ".*")
.replace(/\?/g, "[^/]")
return new RegExp("^" + escaped + "$")
}
function enoent(p: string) {
return Object.assign(new Error(`ENOENT: ${p}`), { code: "ENOENT" })
}
/**
* In-memory virtual filesystem that implements the same API as the
* real `Filesystem` namespace from `util/filesystem`.
*/
export namespace Filesystem {
const files = new Map<string, string>()
// seed
// for (const [p, content] of Object.entries(SEED)) {
// files.set(p, content)
// }
function abs(p: string) {
if (!path.isAbsolute(p)) return path.join(ROOT, p)
return p
}
function pfx(p: string) {
const resolved = abs(p)
return resolved.endsWith("/") ? resolved : resolved + "/"
}
// -- Filesystem API --
export async function exists(p: string) {
const resolved = abs(p)
if (files.has(resolved)) return true
const pre = pfx(p)
for (const key of files.keys()) {
if (key.startsWith(pre)) return true
}
return false
}
export async function isDir(p: string) {
const resolved = abs(p)
if (files.has(resolved)) return false
const pre = pfx(p)
for (const key of files.keys()) {
if (key.startsWith(pre)) return true
}
return false
}
export function stat(p: string) {
const resolved = abs(p)
const content = files.get(resolved)
if (content !== undefined) {
const sz = new TextEncoder().encode(content).byteLength
return {
isFile: () => true,
isDirectory: () => false,
isSymbolicLink: () => false,
size: sz,
mtimeMs: Date.now(),
mtime: new Date(),
}
}
// check directory
const pre = pfx(p)
for (const key of files.keys()) {
if (key.startsWith(pre)) {
return {
isFile: () => false,
isDirectory: () => true,
isSymbolicLink: () => false,
size: 0,
mtimeMs: Date.now(),
mtime: new Date(),
}
}
}
return undefined
}
export async function size(p: string) {
const content = files.get(abs(p))
if (content === undefined) return 0
return new TextEncoder().encode(content).byteLength
}
export async function readText(p: string) {
console.log('reading', p)
const content = files.get(abs(p))
if (content === undefined) throw enoent(p)
return content
}
export async function readJson<T = any>(p: string): Promise<T> {
console.log('reading', p)
const content = files.get(abs(p))
if (content === undefined) throw enoent(p)
return JSON.parse(content)
}
export async function readBytes(p: string) {
console.log('reading', p)
const content = files.get(abs(p))
if (content === undefined) throw enoent(p)
return Buffer.from(content)
}
export async function readArrayBuffer(p: string) {
const content = files.get(abs(p))
if (content === undefined) throw enoent(p)
const buf = Buffer.from(content)
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer
}
export async function write(p: string, content: string | Buffer | Uint8Array, _mode?: number) {
console.log('writing', p)
files.set(abs(p), typeof content === "string" ? content : Buffer.from(content).toString("utf-8"))
}
export async function writeJson(p: string, data: unknown, _mode?: number) {
files.set(abs(p), JSON.stringify(data, null, 2))
}
export async function writeStream(p: string, stream: ReadableStream<Uint8Array>, _mode?: number) {
const reader = stream.getReader()
const chunks: Uint8Array[] = []
while (true) {
const { done, value } = await reader.read()
if (done) break
chunks.push(value)
}
files.set(abs(p), Buffer.concat(chunks).toString("utf-8"))
}
export function mimeType(p: string) {
return lookup(p) || "application/octet-stream"
}
export function normalizePath(p: string) {
return p
}
export function resolve(p: string) {
return path.resolve(p)
}
export function windowsPath(p: string) {
return p
}
export function overlaps(a: string, b: string) {
const relA = path.relative(a, b)
const relB = path.relative(b, a)
return !relA || !relA.startsWith("..") || !relB || !relB.startsWith("..")
}
export function contains(parent: string, child: string) {
return !path.relative(parent, child).startsWith("..")
}
export async function findUp(target: string, start: string, stop?: string) {
let current = start
const result: string[] = []
while (true) {
const search = path.join(current, target)
if (await exists(search)) result.push(search)
if (stop === current) break
const parent = path.dirname(current)
if (parent === current) break
current = parent
}
return result
}
export async function* up(options: { targets: string[]; start: string; stop?: string }) {
let current = options.start
while (true) {
for (const target of options.targets) {
const search = path.join(current, target)
if (await exists(search)) yield search
}
if (options.stop === current) break
const parent = path.dirname(current)
if (parent === current) break
current = parent
}
}
export async function globUp(pattern: string, start: string, stop?: string) {
let current = start
const result: string[] = []
while (true) {
const dir = abs(current)
const regex = globToRegex(pattern, dir)
for (const key of files.keys()) {
if (regex.test(key)) result.push(key)
}
if (stop === current) break
const parent = path.dirname(current)
if (parent === current) break
current = parent
}
return result
}
// -- extra helpers for direct test manipulation --
export function _set(p: string, content: string) {
files.set(abs(p), content)
}
export function _get(p: string) {
return files.get(abs(p))
}
export function _remove(p: string) {
files.delete(abs(p))
}
export function _list() {
return [...files.keys()].sort()
}
}

View File

@@ -28,6 +28,7 @@ const log = Log.create({ service: "db" })
export namespace Database {
export const Path = iife(() => {
return ':memory:'
if (Flag.OPENCODE_DB) {
if (path.isAbsolute(Flag.OPENCODE_DB)) return Flag.OPENCODE_DB
return path.join(Global.Path.data, Flag.OPENCODE_DB)