mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-07-28 02:15:06 +00:00
test(copilot): add JetBrains coverage; fix Windows path inference (#433 follow-up) (#456)
Some checks are pending
CI / semgrep (push) Waiting to run
Some checks are pending
CI / semgrep (push) Waiting to run
Follow-up to #433. Adds tests for the JetBrains format: discovery of a jb chat.jsonl, parsing into a call with inferred model/tools/user message, the isJetBrainsFormat routing guard (legacy user.message files must not route to the JB parser), and the id-less dedup fallback. Also fixes inferJBProjectFromContent: it split homedir() on the platform separator but the recorded tool path on a fixed '/', so project inference always fell back to the raw session id on Windows. Split both on either separator.
This commit is contained in:
parent
e7920fc6aa
commit
0edc8644aa
2 changed files with 91 additions and 3 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import { existsSync } from 'fs'
|
||||
import { readdir, readFile, stat } from 'fs/promises'
|
||||
import { basename, dirname, join, posix, sep, win32 } from 'path'
|
||||
import { basename, dirname, join, posix, win32 } from 'path'
|
||||
import { homedir } from 'os'
|
||||
|
||||
import { readSessionFile } from '../fs-utils.js'
|
||||
|
|
@ -617,7 +617,12 @@ async function discoverJetBrainsSessions(jbDir: string): Promise<SessionSource[]
|
|||
|
||||
/** Infer a project name from tool execution paths in already-loaded content. */
|
||||
function inferJBProjectFromContent(content: string): string | null {
|
||||
const homeParts = homedir().split(sep)
|
||||
// Split on either separator so the home-depth math lines up with the recorded
|
||||
// tool path on every platform (JetBrains records Windows paths with
|
||||
// backslashes, and homedir() also uses backslashes there). Using a fixed '/'
|
||||
// for the path while splitting home on the platform sep mismatched on Windows
|
||||
// and made inference always fall back to the raw session id there.
|
||||
const homeParts = homedir().split(/[/\\]/)
|
||||
const homeDepth = homeParts.length
|
||||
const lines = content.split('\n')
|
||||
const limit = Math.min(lines.length, 200)
|
||||
|
|
@ -631,7 +636,7 @@ function inferJBProjectFromContent(content: string): string | null {
|
|||
const args = e.data?.arguments
|
||||
if (typeof args === 'object' && args !== null && typeof args.path === 'string') {
|
||||
const pathVal: string = args.path
|
||||
const parts = pathVal.split('/')
|
||||
const parts = pathVal.split(/[/\\]/)
|
||||
if (parts.length > homeDepth + 1) {
|
||||
const afterHome = parts.slice(homeDepth)
|
||||
if (afterHome.length >= 2) {
|
||||
|
|
|
|||
|
|
@ -480,3 +480,86 @@ describe('copilot provider - metadata', () => {
|
|||
expect(copilot.modelDisplayName('gpt-4.1-mini-2026-01-01')).toBe('GPT-4.1 Mini')
|
||||
})
|
||||
})
|
||||
|
||||
// JetBrains (IntelliJ/DataGrip) format, added in #433. Discovery + parsing,
|
||||
// the isJetBrainsFormat routing guard, and the id-less dedup fallback.
|
||||
describe('copilot provider - JetBrains format', () => {
|
||||
beforeEach(async () => {
|
||||
tmpDir = await mkdtemp(join(tmpdir(), 'copilot-jb-'))
|
||||
})
|
||||
afterEach(async () => {
|
||||
await rm(tmpDir, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
const jbUser = (text: string) =>
|
||||
JSON.stringify({ type: 'user.message_rendered', data: { renderedMessage: text } })
|
||||
const jbTurnStart = (turnId: string) =>
|
||||
JSON.stringify({ type: 'assistant.turn_start', data: { turnId } })
|
||||
const jbToolStart = (toolName: string, toolCallId: string, path?: string) =>
|
||||
JSON.stringify({ type: 'tool.execution_start', data: { toolName, toolCallId, arguments: path ? { path } : {} } })
|
||||
const jbAssistant = (opts: { messageId?: string; text?: string; outputTokens?: number; iterationNumber?: number }) =>
|
||||
JSON.stringify({ type: 'assistant.message', data: { ...opts } })
|
||||
|
||||
async function writeJbSession(workspaceId: string, lines: string[]) {
|
||||
const dir = join(tmpDir, workspaceId)
|
||||
await mkdir(dir, { recursive: true })
|
||||
const filePath = join(dir, 'chat.jsonl')
|
||||
await writeFile(filePath, lines.join('\n') + '\n')
|
||||
return filePath
|
||||
}
|
||||
|
||||
async function parse(filePath: string, seen = new Set<string>()) {
|
||||
const provider = createCopilotProvider('/nonexistent/legacy', '/nonexistent/vscode', tmpDir)
|
||||
const source = { path: filePath, project: 'p', provider: 'copilot' }
|
||||
const calls: ParsedProviderCall[] = []
|
||||
for await (const call of provider.createSessionParser(source, seen).parse()) calls.push(call)
|
||||
return calls
|
||||
}
|
||||
|
||||
it('discovers a JetBrains chat.jsonl under the jb dir', async () => {
|
||||
const filePath = await writeJbSession('ws-abc', [jbUser('hello'), jbAssistant({ messageId: 'm1', text: 'hi', outputTokens: 10 })])
|
||||
const provider = createCopilotProvider('/nonexistent/legacy', '/nonexistent/vscode', tmpDir)
|
||||
const sources = await provider.discoverSessions()
|
||||
expect(sources.some(s => s.path === filePath && s.provider === 'copilot')).toBe(true)
|
||||
})
|
||||
|
||||
it('parses a JetBrains session into a call with the inferred model and user message', async () => {
|
||||
const filePath = await writeJbSession('ws-abc', [
|
||||
jbUser('implement the feature'),
|
||||
jbTurnStart('t1'),
|
||||
jbToolStart('read_file', 'toolu_vrtx_x'),
|
||||
jbAssistant({ messageId: 'm1', text: 'done', outputTokens: 42 }),
|
||||
])
|
||||
const calls = await parse(filePath)
|
||||
expect(calls).toHaveLength(1)
|
||||
expect(calls[0]!.provider).toBe('copilot')
|
||||
expect(calls[0]!.model).toBe('copilot-anthropic-auto') // toolu_ prefix -> Anthropic
|
||||
expect(calls[0]!.outputTokens).toBe(42)
|
||||
expect(calls[0]!.userMessage).toBe('implement the feature')
|
||||
expect(calls[0]!.tools).toEqual(['Read'])
|
||||
expect(calls[0]!.deduplicationKey.startsWith('copilot:jb:')).toBe(true)
|
||||
})
|
||||
|
||||
it('does NOT route a legacy file (first line user.message) to the JetBrains parser', async () => {
|
||||
// Regression guard: isJetBrainsFormat must not match bare user.message.
|
||||
const filePath = await writeJbSession('ws-legacy', [
|
||||
JSON.stringify({ type: 'user.message', data: { content: 'hi' } }),
|
||||
JSON.stringify({ type: 'session.model_change', data: { newModel: 'gpt-4.1' } }),
|
||||
JSON.stringify({ type: 'assistant.message', data: { messageId: 'm1', outputTokens: 5 } }),
|
||||
])
|
||||
const calls = await parse(filePath)
|
||||
// Parsed by the legacy parser -> legacy dedup key, not a jb one.
|
||||
expect(calls.every(c => !c.deduplicationKey.startsWith('copilot:jb:'))).toBe(true)
|
||||
})
|
||||
|
||||
it('does not collapse id-less assistant messages (dedup fallback)', async () => {
|
||||
const filePath = await writeJbSession('ws-noid', [
|
||||
jbUser('q1'),
|
||||
jbAssistant({ text: 'a1', outputTokens: 5 }),
|
||||
jbAssistant({ text: 'a2', outputTokens: 6 }),
|
||||
])
|
||||
const calls = await parse(filePath)
|
||||
expect(calls).toHaveLength(2)
|
||||
expect(new Set(calls.map(c => c.deduplicationKey)).size).toBe(2)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue