Files
orca/src/shared/structured-agent-session-create.test.ts
T

152 lines
5.7 KiB
TypeScript

import { ResumeSource } from './rpc-contract/structured-agent-session-params'
import { STRUCTURED_AGENT_SESSION_CLI_SOURCE_RUNTIME_CAPABILITY } from './structured-agent-session-surface-capabilities'
import { describe, expect, it } from 'vitest'
import { RUNTIME_CAPABILITIES } from './protocol-version'
import { AGENT_SESSION_CREATE_MESSAGE_RUNTIME_CAPABILITY } from './agent-session-create-capabilities'
import {
structuredAgentSessionCreateParams,
type StructuredAgentSessionFirstMessage,
type StructuredAgentSessionResumeSource
} from './structured-agent-session-create'
import {
structuredAgentSessionCreateFingerprint,
structuredAgentSessionPayloadFingerprint
} from './structured-agent-session-mutation'
const SESSION_ID = 'codex_11111111_2222_3333_4444_555555555555'
const RESUME = { providerSessionId: 'thread-abc' }
/** Distinct per call so two envelopes never share an operation id by accident. */
let uuidCounter = 0
function nextUuid(): string {
uuidCounter += 1
return `00000000-0000-4000-8000-${String(uuidCounter).padStart(12, '0')}`
}
function createParams(
overrides: {
resumeFrom?: StructuredAgentSessionResumeSource
tabId?: string
firstMessage?: StructuredAgentSessionFirstMessage
options?: Record<string, string>
} = {}
) {
return structuredAgentSessionCreateParams({
sessionId: SESSION_ID,
worktree: 'id:repo-1::/repo/orca',
agent: 'codex',
...overrides,
randomUuid: nextUuid,
now: 1_800_000_000_000
})
}
describe('structured agent session create params', () => {
it('fingerprints the first message identity, content and opening option picks', () => {
const firstMessage: StructuredAgentSessionFirstMessage = {
clientMessageId: 'message-1',
body: { kind: 'message', role: 'user', blocks: [{ type: 'text', text: 'opening text' }] }
}
const first = createParams({ firstMessage, options: { model: 'picked-model' } })
expect(first.firstMessage).toEqual(firstMessage)
expect(first.options).toEqual({ model: 'picked-model' })
expect(first.envelope.payloadFingerprint).toBe(
createParams({ firstMessage, options: { model: 'picked-model' } }).envelope.payloadFingerprint
)
for (const changed of [
createParams(),
createParams({
firstMessage: { ...firstMessage, clientMessageId: 'message-2' },
options: { model: 'picked-model' }
}),
createParams({
firstMessage: {
...firstMessage,
body: { ...firstMessage.body, blocks: [{ type: 'text', text: 'different text' }] }
},
options: { model: 'picked-model' }
}),
createParams({ firstMessage, options: { model: 'other-model' } })
]) {
expect(changed.envelope.payloadFingerprint).not.toBe(first.envelope.payloadFingerprint)
}
expect(RUNTIME_CAPABILITIES).toContain(AGENT_SESSION_CREATE_MESSAGE_RUNTIME_CAPABILITY)
expect(AGENT_SESSION_CREATE_MESSAGE_RUNTIME_CAPABILITY).toBe('agentSession.create.message.v1')
})
it('separates an adopting create from a blank one and from another row', () => {
const blank = createParams().envelope.payloadFingerprint
const adopted = createParams({ resumeFrom: RESUME }).envelope.payloadFingerprint
const otherRow = createParams({
resumeFrom: { providerSessionId: 'thread-other' }
}).envelope.payloadFingerprint
expect(adopted).not.toBe(blank)
expect(otherRow).not.toBe(adopted)
})
it('leaves a blank create byte-identical to the pre-resume digest', () => {
// Pin the legacy no-resume digest; new fields must not break mixed-version create fingerprints.
expect(createParams().envelope.payloadFingerprint).toBe(
structuredAgentSessionPayloadFingerprint({
method: 'agentSession.create',
sessionId: SESSION_ID,
fields: { worktree: 'id:repo-1::/repo/orca', agent: 'codex' }
})
)
expect(createParams().envelope.payloadFingerprint).toBe(
'56cb15e22414c0f62fd89d77d00d2d6a0a422f16e95edee154fb8b5bf53fbbc3'
)
})
})
describe('the tab id a create reserves', () => {
it('rides the params and the fingerprint together', () => {
const params = createParams({ tabId: 'tab-1' })
expect(params.tabId).toBe('tab-1')
expect(params.envelope.payloadFingerprint).toBe(
structuredAgentSessionCreateFingerprint({
sessionId: SESSION_ID,
worktree: 'id:repo-1::/repo/orca',
agent: 'codex',
tabId: 'tab-1'
})
)
// The declared digest covers the tab; the host still replays a retry on its attach fingerprint.
expect(params.envelope.payloadFingerprint).not.toBe(createParams().envelope.payloadFingerprint)
})
})
it('negotiates a strict pane identity and includes it in the create fingerprint without accepting paths', () => {
const source = {
providerSessionId: 'thread-abc',
cliSource: { workspaceId: 'folder', paneKey: 'tab:leaf' }
}
expect(RUNTIME_CAPABILITIES).toContain(STRUCTURED_AGENT_SESSION_CLI_SOURCE_RUNTIME_CAPABILITY)
expect(ResumeSource.parse(source)).toEqual(source)
expect(ResumeSource.safeParse({ ...source, transcriptPath: '/client/path' }).success).toBe(false)
expect(
ResumeSource.safeParse({
...source,
cliSource: { ...source.cliSource, accountHome: '/client/home' }
}).success
).toBe(false)
const first = createParams({ resumeFrom: source })
expect(first.envelope.payloadFingerprint).not.toBe(
createParams({ resumeFrom: RESUME }).envelope.payloadFingerprint
)
expect(first.envelope.payloadFingerprint).toBe(
createParams({ resumeFrom: source }).envelope.payloadFingerprint
)
expect(first.envelope.payloadFingerprint).not.toBe(
createParams({
resumeFrom: {
...source,
cliSource: { ...source.cliSource, paneKey: 'other:leaf' }
}
}).envelope.payloadFingerprint
)
})