pull/20268/head
James Long 2026-03-24 15:17:39 -04:00
parent 31c4a4fb47
commit 8d5c84d516
26 changed files with 4466 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,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,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)