Files
hapi/web/scripts/fixtures/cases/codex.ts
T

542 lines
22 KiB
TypeScript

import type { FixtureCase } from '../fixtureTypes'
import { T0, wireMessage } from './support'
/**
* Generic agent family: content.type === 'codex' (AGENT_MESSAGE_PAYLOAD_TYPE,
* used by Codex/Cursor/OpenCode/Pi/…). Text and reasoning arrive as cumulative
* stream snapshots keyed by data.id.
*/
export const codexCases: FixtureCase[] = [
...([true, false] as const).map((available): FixtureCase => {
const callId = 'codex-proposed-plan:root:turn:plan-item'
const data = [
{ type: 'message', message: 'Proposal preface', id: 'codex:root:turn:before:agent_message' },
{ type: 'tool-call', name: 'ExitPlanMode', callId, input: { plan: '# Shared plan\n\n1. Inspect\n2. Implement' }, id: 'codex:root:turn:plan-item:proposed_plan' },
{ type: 'tool-call-result', callId, output: null, id: 'codex:root:turn:plan-item:proposed_plan:result' },
...(!available ? [{ type: 'message', message: 'Implementation started', id: 'codex:root:next:message:agent_message' }] : [])
]
return {
name: available ? 'codex-shared-plan-available' : 'codex-shared-plan-historical',
description: 'Shared Codex proposals remain completed content without a synthetic permission. The current plan id only enables Web client actions; native continuation preserves the card.',
messages: data.map((entry, index) => wireMessage({ id: `shared-plan-${index}`, seq: index + 1, createdAt: T0 + index * 1000,
content: { role: 'agent', content: { type: 'codex', data: entry } }
})),
agentState: { codexPlanProposalId: available ? callId : null }
}
}),
...(['ExitPlanMode', 'exit_plan_mode'] as const).map((name, index): FixtureCase => {
const plan = '# 实施计划\n\n1. Inspect **input.plan**\n2. Render Markdown\n\n```swift\nlet ready = true\n```\n'
const callId = `codex-proposed-plan:plan-${index}`
const pending = index === 1
const data = [
{ type: 'tool-call', name: 'Read', callId: 'read-before', input: { file_path: '/repo/README.md' } },
{ type: 'tool-call-result', callId: 'read-before', output: 'Readme' },
{ type: 'tool-call', name, callId, input: { plan }, id: `plan-${index}` },
...(!pending ? [{ type: 'tool-call-result', callId, output: null, id: `plan-${index}:result` }] : []),
{ type: 'tool-call', name: 'Read', callId: 'read-after', input: { file_path: '/repo/package.json' } },
{ type: 'tool-call-result', callId: 'read-after', output: '{}' }
]
return {
name: pending ? 'codex-plan-proposal-pending' : 'codex-plan-proposal-completed',
description: 'Proposal Markdown lives in input.plan. Keep it standalone between ordinary tools; null output completes a proposal without inventing approval. The lowercase alias also accepts a pending permission.',
messages: data.map((entry, seq) => wireMessage({
id: `proposal-${index}-${seq}`, seq: seq + 1, createdAt: T0 + seq * 1000,
content: { role: 'agent', content: { type: 'codex', data: entry } }
})),
...(pending ? { agentState: { requests: {
'plan-approval': { tool: name, toolCallId: callId, arguments: { plan }, createdAt: T0 + 2000 }
} } } : {})
}
}),
{
name: 'codex-message-stream-snapshot',
description: 'Codex family: two message payloads sharing a stream id (data.id) are cumulative snapshots. Expects a single agent-text block keyed by that stable stream id, carrying the final snapshot text.',
messages: [
wireMessage({
id: 'msg-codex-051',
seq: 1,
createdAt: T0,
content: {
role: 'agent',
content: {
type: 'codex',
data: {
type: 'message',
id: 'item_7',
message: 'Tracking down the failing pagination'
}
}
}
}),
wireMessage({
id: 'msg-codex-052',
seq: 2,
createdAt: T0 + 900,
content: {
role: 'agent',
content: {
type: 'codex',
data: {
type: 'message',
id: 'item_7',
message: 'Tracking down the failing pagination test: the cursor math drops the epoch check when seq wraps.'
}
}
}
})
]
},
{
name: 'codex-reasoning',
description: 'Codex family: reasoning payload becomes an agent-reasoning block keyed by its stream id (data.id).',
messages: [
wireMessage({
id: 'msg-codex-061',
seq: 1,
createdAt: T0,
content: {
role: 'agent',
content: {
type: 'codex',
data: {
type: 'reasoning',
id: 'rs_0af3d219',
message: '**Weighing pagination approaches**\n\nThe epoch guard only fires when the server resets, so the client must drop its window on mismatch.'
}
}
}
})
]
},
{
name: 'codex-blank-stream-ids',
description: 'Codex family: blank (empty or whitespace-only) data.id values are not stream identities (shared/src/messages.ts trims before accepting) — each payload keeps its own row-derived block id instead of collapsing onto a shared blank identity.',
messages: [
wireMessage({
id: 'msg-codex-065',
seq: 1,
createdAt: T0,
content: {
role: 'agent',
content: {
type: 'codex',
data: {
type: 'reasoning',
id: '',
message: 'blank id one'
}
}
}
}),
wireMessage({
id: 'msg-codex-066',
seq: 2,
createdAt: T0 + 100,
content: {
role: 'agent',
content: {
type: 'codex',
data: {
type: 'reasoning',
id: ' ',
message: 'blank id two'
}
}
}
}),
wireMessage({
id: 'msg-codex-067',
seq: 3,
createdAt: T0 + 200,
content: {
role: 'agent',
content: {
type: 'codex',
data: {
type: 'message',
id: '',
message: 'blank id text one'
}
}
}
}),
wireMessage({
id: 'msg-codex-068',
seq: 4,
createdAt: T0 + 300,
content: {
role: 'agent',
content: {
type: 'codex',
data: {
type: 'message',
id: ' ',
message: 'blank id text two'
}
}
}
})
]
},
{
name: 'codex-error-event',
description: 'Codex family: an error payload becomes an agent-event block with {type: error, message} carried verbatim.',
messages: [
wireMessage({
id: 'msg-user-501',
seq: 1,
createdAt: T0,
content: {
role: 'user',
content: { type: 'text', text: 'Continue the review.' }
}
}),
wireMessage({
id: 'msg-codex-502',
seq: 2,
createdAt: T0 + 2_700,
content: {
role: 'agent',
content: {
type: 'codex',
data: {
type: 'error',
message: 'Codex backend disconnected: stream closed before the turn completed'
}
}
}
})
]
},
{
name: 'codex-context-compacted',
description: 'Codex family: context_compacted maps to an agent-event of type compact with trigger and preTokens (accepting the snake_case pre_tokens wire key).',
messages: [
wireMessage({
id: 'msg-user-511',
seq: 1,
createdAt: T0,
content: {
role: 'user',
content: { type: 'text', text: 'Carry on.' }
}
}),
wireMessage({
id: 'msg-codex-512',
seq: 2,
createdAt: T0 + 3_100,
content: {
role: 'agent',
content: {
type: 'codex',
data: {
type: 'context_compacted',
trigger: 'auto',
pre_tokens: 186211,
thread_id: 'thread-42'
}
}
}
})
]
},
{
name: 'codex-generated-image',
description: 'Codex family: generated-image becomes a generated-image block keeping {imageId, fileName, mimeType}; the wire source (inline media provenance) and sourceImageId are web rendering concerns and are dropped by the projection.',
messages: [
wireMessage({
id: 'msg-codex-521',
seq: 1,
createdAt: T0,
content: {
role: 'agent',
content: {
type: 'codex',
data: {
type: 'generated-image',
imageId: 'img-01J9ZD8KM4TQ',
sourceImageId: 'ig_58210',
fileName: 'latency-chart.png',
mimeType: 'image/png',
id: 'aed0f1b2-5fb6-4c2d-8e9d-e2f3a4b5c521',
source: {
ingress: 'tool_result',
flavor: 'codex',
toolCallId: 'call_img_58210'
}
}
}
}
})
]
},
{
name: 'codex-review-verdict',
description: 'Codex family: a message whose text is codex review JSON (findings + overall_correctness/…) parses into a codex-review block with the normalized review object (camelCase fields, code_location flattened to filePath/lineStart/lineEnd).',
messages: [
wireMessage({
id: 'msg-codex-531',
seq: 1,
createdAt: T0,
content: {
role: 'agent',
content: {
type: 'codex',
data: {
type: 'message',
id: '550e8400-e29b-41d4-a716-446655440531',
message: '{"findings":[{"title":"[P2] Remove retained sessions when sockets disconnect","body":"Retained sockets survive disconnects.","confidence_score":0.82,"priority":2,"code_location":{"absolute_file_path":"/repo/hub/src/pairing/manager.ts","line_range":{"start":1614,"end":1619}}}],"overall_correctness":"patch is incorrect","overall_explanation":"The message-sending feature retains long-lived sockets but does not fully manage their lifecycle.","overall_confidence_score":0.8}'
}
}
}
})
]
},
{
name: 'codex-thread-goal-updated',
description: 'Codex family: thread_goal_updated normalizes to a thread-goal-updated event which is SILENT in the timeline (filtered as a goal-state signal) — expected blocks contain only the user text. latestGoal is derived state outside the normative projection.',
messages: [
wireMessage({
id: 'msg-user-541',
seq: 1,
createdAt: T0,
content: {
role: 'user',
content: { type: 'text', text: '/goal Ship the pagination fix' }
}
}),
wireMessage({
id: 'msg-codex-542',
seq: 2,
createdAt: T0 + 1_300,
content: {
role: 'agent',
content: {
type: 'codex',
data: {
type: 'thread_goal_updated',
thread_id: 'thread-42',
turn_id: 'turn-7',
goal: {
threadId: 'thread-42',
objective: 'Ship the pagination fix',
status: 'active',
tokenBudget: 200000,
tokensUsed: 8016,
timeUsedSeconds: 42,
createdAt: 1755000001300,
updatedAt: 1755000001300
}
}
}
}
})
]
},
{
name: 'codex-thread-goal-cleared',
description: 'Codex family: thread_goal_cleared normalizes to a thread-goal-cleared event which is SILENT in the timeline — expected blocks contain only the user text.',
messages: [
wireMessage({
id: 'msg-user-551',
seq: 1,
createdAt: T0,
content: {
role: 'user',
content: { type: 'text', text: '/goal clear' }
}
}),
wireMessage({
id: 'msg-codex-552',
seq: 2,
createdAt: T0 + 1_100,
content: {
role: 'agent',
content: {
type: 'codex',
data: {
type: 'thread_goal_cleared',
thread_id: 'thread-42'
}
}
}
})
]
},
{
name: 'codex-tool-call-result-pair',
description: 'Codex family: a tool-call (callId + name + structured input) paired with its tool-call-result (output object) merges into one completed tool-call block; input and output are carried verbatim (command as argv array, output as {exit_code, stdout, …}).',
messages: [
wireMessage({
id: 'msg-user-561',
seq: 1,
createdAt: T0,
content: {
role: 'user',
content: { type: 'text', text: 'Run the test suite.' }
}
}),
wireMessage({
id: 'msg-codex-562',
seq: 2,
createdAt: T0 + 1_800,
content: {
role: 'agent',
content: {
type: 'codex',
data: {
type: 'tool-call',
name: 'CodexBash',
callId: 'call_9f2d81aa04',
input: {
command: ['bash', '-lc', 'bun test'],
cwd: '/repo'
},
id: 'bfe1a2c3-6ac7-4d3e-9fae-f3a4b5c6d562'
}
}
}
}),
wireMessage({
id: 'msg-codex-563',
seq: 3,
createdAt: T0 + 9_400,
content: {
role: 'agent',
content: {
type: 'codex',
data: {
type: 'tool-call-result',
callId: 'call_9f2d81aa04',
output: {
exit_code: 0,
stdout: '420 pass\n0 fail\nRan 420 tests across 37 files. [4.21s]',
duration_seconds: 4.21
},
is_error: false,
id: 'c0f2b3d4-7bd8-4e4f-8ab0-a4b5c6d7e563'
}
}
}
})
]
},
{
name: 'codex-exploration-tool-group',
description: 'Codex exploration grouping family: consecutive CodexBash calls whose command_actions are all read/listFiles/search (and command_source is not userShell) collapse into one tool-group in visibleBlocks — a family boundary distinct from the default read/search grouping.',
messages: [
wireMessage({
id: 'msg-user-571',
seq: 1,
createdAt: T0,
content: {
role: 'user',
content: { type: 'text', text: 'Where is the pagination cursor built?' }
}
}),
wireMessage({
id: 'msg-codex-572',
seq: 2,
createdAt: T0 + 1_500,
content: {
role: 'agent',
content: {
type: 'codex',
data: {
type: 'tool-call',
name: 'CodexBash',
callId: 'call_expl_read01',
input: {
command: ['bash', '-lc', 'cat package.json'],
cwd: '/repo',
command_source: 'agent',
command_actions: [
{
type: 'read',
command: 'cat package.json',
name: 'package.json',
path: '/repo/package.json'
}
]
},
id: 'd1a3c4e5-8ce9-4f5a-9bc1-b5c6d7e8f572'
}
}
}
}),
wireMessage({
id: 'msg-codex-573',
seq: 3,
createdAt: T0 + 2_400,
content: {
role: 'agent',
content: {
type: 'codex',
data: {
type: 'tool-call-result',
callId: 'call_expl_read01',
output: {
exit_code: 0,
stdout: '{\n "name": "hapi",\n "workspaces": ["cli", "hub", "web", "shared"]\n}'
},
is_error: false,
id: 'e2b4d5f6-9dfa-4a6b-8cd2-c6d7e8f9a573'
}
}
}
}),
wireMessage({
id: 'msg-codex-574',
seq: 4,
createdAt: T0 + 3_300,
content: {
role: 'agent',
content: {
type: 'codex',
data: {
type: 'tool-call',
name: 'CodexBash',
callId: 'call_expl_search01',
input: {
command: ['bash', '-lc', 'rg buildCursor web/src'],
cwd: '/repo',
command_source: 'agent',
command_actions: [
{
type: 'search',
command: 'rg buildCursor web/src',
query: 'buildCursor',
path: 'web/src'
}
]
},
id: 'f3c5e6a7-0eab-4b7c-9de3-d7e8f9a0b574'
}
}
}
}),
wireMessage({
id: 'msg-codex-575',
seq: 5,
createdAt: T0 + 4_100,
content: {
role: 'agent',
content: {
type: 'codex',
data: {
type: 'tool-call-result',
callId: 'call_expl_search01',
output: {
exit_code: 0,
stdout: 'web/src/lib/message-window-store.ts:412:function buildCursor(page) {'
},
is_error: false,
id: 'a4d6f7b8-1fbc-4c8d-8ef4-e8f9a0b1c575'
}
}
}
})
]
}
]