mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-05-17 03:56:45 +00:00
Merge pull request #171 from ksp2000/feature/copilot-auto-model-buckets
refactor(copilot): use auto model buckets for transcript inference
This commit is contained in:
commit
fbb2c4e69c
3 changed files with 129 additions and 3 deletions
|
|
@ -133,6 +133,8 @@ const BUILTIN_ALIASES: Record<string, string> = {
|
|||
'cursor-auto': 'claude-sonnet-4-5',
|
||||
'cursor-agent-auto': 'claude-sonnet-4-5',
|
||||
'copilot-auto': 'claude-sonnet-4-5',
|
||||
'copilot-openai-auto': 'gpt-5.3-codex',
|
||||
'copilot-anthropic-auto': 'claude-sonnet-4-5',
|
||||
'kiro-auto': 'claude-sonnet-4-5',
|
||||
'cline-auto': 'claude-sonnet-4-5',
|
||||
'openclaw-auto': 'claude-sonnet-4-5',
|
||||
|
|
|
|||
|
|
@ -13,8 +13,16 @@ const modelDisplayNames: Record<string, string> = {
|
|||
'gpt-4.1': 'GPT-4.1',
|
||||
'gpt-4o-mini': 'GPT-4o Mini',
|
||||
'gpt-4o': 'GPT-4o',
|
||||
'gpt-5.4': 'GPT-5.4',
|
||||
'gpt-5.3-codex': 'GPT-5.3 Codex',
|
||||
'gpt-5-mini': 'GPT-5 Mini',
|
||||
'gpt-5': 'GPT-5',
|
||||
'claude-opus-4-7': 'Opus 4.7',
|
||||
'claude-opus-4-6': 'Opus 4.6',
|
||||
'claude-opus-4-5': 'Opus 4.5',
|
||||
'claude-opus-4-1': 'Opus 4.1',
|
||||
'claude-opus-4': 'Opus 4',
|
||||
'claude-sonnet-4-6': 'Sonnet 4.6',
|
||||
'claude-sonnet-4-5': 'Sonnet 4.5',
|
||||
'claude-sonnet-4': 'Sonnet 4',
|
||||
'claude-3-7-sonnet': 'Sonnet 3.7',
|
||||
|
|
@ -45,6 +53,8 @@ const toolNameMap: Record<string, string> = {
|
|||
}
|
||||
|
||||
const CHARS_PER_TOKEN = 4
|
||||
const COPILOT_OPENAI_AUTO = 'copilot-openai-auto'
|
||||
const COPILOT_ANTHROPIC_AUTO = 'copilot-anthropic-auto'
|
||||
|
||||
const modelDisplayEntries = Object.entries(modelDisplayNames).sort((a, b) => b[0].length - a[0].length)
|
||||
|
||||
|
|
@ -143,15 +153,35 @@ type TranscriptEvent =
|
|||
| { type: 'assistant.message'; timestamp?: string; data: { messageId: string; content?: string; reasoningText?: string; toolRequests?: TranscriptToolRequest[]; outputTokens?: number } }
|
||||
| { type: string; timestamp?: string; data: Record<string, unknown> }
|
||||
|
||||
const transcriptToolCallModelHints: Array<{ prefix: string; model: string }> = [
|
||||
// Anthropic tool-call ID variants observed in Copilot transcript logs.
|
||||
{ prefix: 'toolu_bdrk_', model: COPILOT_ANTHROPIC_AUTO },
|
||||
{ prefix: 'toolu_vrtx_', model: COPILOT_ANTHROPIC_AUTO },
|
||||
{ prefix: 'tooluse_', model: COPILOT_ANTHROPIC_AUTO },
|
||||
// OpenAI tool-call IDs.
|
||||
{ prefix: 'call_', model: COPILOT_OPENAI_AUTO },
|
||||
]
|
||||
|
||||
function inferModelFromToolCallIds(events: TranscriptEvent[]): string {
|
||||
const modelCounts = new Map<string, number>()
|
||||
|
||||
for (const e of events) {
|
||||
if (e.type !== 'assistant.message') continue
|
||||
const msg = e as { data: { toolRequests?: TranscriptToolRequest[] } }
|
||||
for (const t of msg.data.toolRequests ?? []) {
|
||||
if (t.toolCallId?.startsWith('toolu_bdrk_')) return 'claude-sonnet-4-5'
|
||||
if (t.toolCallId?.startsWith('call_')) return 'gpt-4.1'
|
||||
const toolCallId = t.toolCallId ?? ''
|
||||
for (const hint of transcriptToolCallModelHints) {
|
||||
if (!toolCallId.startsWith(hint.prefix)) continue
|
||||
modelCounts.set(hint.model, (modelCounts.get(hint.model) ?? 0) + 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (modelCounts.size > 0) {
|
||||
return [...modelCounts.entries()].sort((a, b) => b[1] - a[1])[0]![0]
|
||||
}
|
||||
|
||||
return 'copilot-auto'
|
||||
}
|
||||
|
||||
|
|
@ -375,6 +405,8 @@ export function createCopilotProvider(sessionStateDir?: string, workspaceStorage
|
|||
|
||||
modelDisplayName(model: string): string {
|
||||
if (model === 'copilot-auto') return 'Copilot (auto)'
|
||||
if (model === COPILOT_OPENAI_AUTO) return 'Copilot (OpenAI auto)'
|
||||
if (model === COPILOT_ANTHROPIC_AUTO) return 'Copilot (Anthropic auto)'
|
||||
for (const [key, name] of modelDisplayEntries) {
|
||||
if (model === key || model.startsWith(key + '-')) return name
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,30 @@ function assistantMessage(opts: { messageId: string; outputTokens: number; tools
|
|||
})
|
||||
}
|
||||
|
||||
function transcriptSessionStart(sessionId: string) {
|
||||
return JSON.stringify({ type: 'session.start', data: { sessionId, producer: 'copilot-agent' } })
|
||||
}
|
||||
|
||||
function transcriptUserMessage(content: string) {
|
||||
return JSON.stringify({ type: 'user.message', data: { content, attachments: [] } })
|
||||
}
|
||||
|
||||
function transcriptAssistantMessage(opts: { messageId: string; content?: string; reasoningText?: string; toolCallIds?: string[] }) {
|
||||
return JSON.stringify({
|
||||
type: 'assistant.message',
|
||||
data: {
|
||||
messageId: opts.messageId,
|
||||
content: opts.content ?? '',
|
||||
reasoningText: opts.reasoningText ?? '',
|
||||
toolRequests: (opts.toolCallIds ?? []).map((id, i) => ({
|
||||
toolCallId: id,
|
||||
name: i === 0 ? 'read_file' : 'run_in_terminal',
|
||||
type: 'function',
|
||||
})),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
describe('copilot provider - JSONL parsing', () => {
|
||||
beforeEach(async () => {
|
||||
tmpDir = await mkdtemp(join(tmpdir(), 'copilot-test-'))
|
||||
|
|
@ -155,10 +179,76 @@ describe('copilot provider - JSONL parsing', () => {
|
|||
for await (const call of copilot.createSessionParser(source, new Set()).parse()) calls.push(call)
|
||||
|
||||
expect(calls).toHaveLength(1)
|
||||
expect(calls[0]!.messageId).toBeUndefined()
|
||||
expect(calls[0]!.outputTokens).toBe(80)
|
||||
expect(calls[0]!.model).toBe('gpt-4.1')
|
||||
})
|
||||
|
||||
it('infers OpenAI auto bucket for transcript toolCallId prefix call_', async () => {
|
||||
const eventsPath = await createSessionDir('sess-tr-call', [
|
||||
transcriptSessionStart('sess-tr-call'),
|
||||
transcriptUserMessage('check model inference'),
|
||||
transcriptAssistantMessage({
|
||||
messageId: 'msg-1',
|
||||
content: 'done',
|
||||
toolCallIds: ['call_abc123'],
|
||||
}),
|
||||
])
|
||||
|
||||
const source = { path: eventsPath, project: 'test', provider: 'copilot' }
|
||||
const calls: ParsedProviderCall[] = []
|
||||
for await (const call of copilot.createSessionParser(source, new Set()).parse()) calls.push(call)
|
||||
|
||||
expect(calls).toHaveLength(1)
|
||||
expect(calls[0]!.model).toBe('copilot-openai-auto')
|
||||
})
|
||||
|
||||
it('infers Anthropic auto bucket for transcript toolCallId prefixes tooluse_/toolu_vrtx_', async () => {
|
||||
const eventsPath = await createSessionDir('sess-tr-claude', [
|
||||
transcriptSessionStart('sess-tr-claude'),
|
||||
transcriptUserMessage('check model inference'),
|
||||
transcriptAssistantMessage({
|
||||
messageId: 'msg-1',
|
||||
content: 'done',
|
||||
toolCallIds: ['tooluse_XY', 'toolu_vrtx_01ABC'],
|
||||
}),
|
||||
])
|
||||
|
||||
const source = { path: eventsPath, project: 'test', provider: 'copilot' }
|
||||
const calls: ParsedProviderCall[] = []
|
||||
for await (const call of copilot.createSessionParser(source, new Set()).parse()) calls.push(call)
|
||||
|
||||
expect(calls).toHaveLength(1)
|
||||
expect(calls[0]!.model).toBe('copilot-anthropic-auto')
|
||||
})
|
||||
|
||||
it('chooses the dominant inferred transcript model when prefixes are mixed', async () => {
|
||||
const eventsPath = await createSessionDir('sess-tr-mixed', [
|
||||
transcriptSessionStart('sess-tr-mixed'),
|
||||
transcriptUserMessage('mixed'),
|
||||
transcriptAssistantMessage({
|
||||
messageId: 'msg-1',
|
||||
content: 'one',
|
||||
toolCallIds: ['toolu_bdrk_123'],
|
||||
}),
|
||||
transcriptAssistantMessage({
|
||||
messageId: 'msg-2',
|
||||
content: 'two',
|
||||
toolCallIds: ['call_1'],
|
||||
}),
|
||||
transcriptAssistantMessage({
|
||||
messageId: 'msg-3',
|
||||
content: 'three',
|
||||
toolCallIds: ['call_2'],
|
||||
}),
|
||||
])
|
||||
|
||||
const source = { path: eventsPath, project: 'test', provider: 'copilot' }
|
||||
const calls: ParsedProviderCall[] = []
|
||||
for await (const call of copilot.createSessionParser(source, new Set()).parse()) calls.push(call)
|
||||
|
||||
expect(calls).toHaveLength(3)
|
||||
expect(calls.every(c => c.model === 'copilot-openai-auto')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('copilot provider - discoverSessions', () => {
|
||||
|
|
@ -257,6 +347,8 @@ describe('copilot provider - metadata', () => {
|
|||
expect(copilot.modelDisplayName('gpt-5-mini')).toBe('GPT-5 Mini')
|
||||
expect(copilot.modelDisplayName('o3')).toBe('o3')
|
||||
expect(copilot.modelDisplayName('o4-mini')).toBe('o4-mini')
|
||||
expect(copilot.modelDisplayName('copilot-openai-auto')).toBe('Copilot (OpenAI auto)')
|
||||
expect(copilot.modelDisplayName('copilot-anthropic-auto')).toBe('Copilot (Anthropic auto)')
|
||||
expect(copilot.modelDisplayName('unknown-model-xyz')).toBe('unknown-model-xyz')
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue