Skip to content

Commit c44878f

Browse files
committed
Update editor best of n opus
1 parent 736dc61 commit c44878f

File tree

1 file changed

+111
-61
lines changed

1 file changed

+111
-61
lines changed

.agents/editor/best-of-n/editor-best-of-n.ts

Lines changed: 111 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -428,25 +428,59 @@ function* handleStepsMax({
428428
}
429429

430430
function* handleStepsOpus({
431+
agentState,
431432
params,
432433
logger,
433434
}: AgentStepContext): ReturnType<
434435
NonNullable<SecretAgentDefinition['handleSteps']>
435436
> {
436-
const DEFAULT_N = 3
437+
const MAX_N = 3
437438
const selectorAgent = 'best-of-n-selector-opus'
438439
const n = Math.min(
439440
10,
440-
Math.max(1, (params?.n as number | undefined) ?? DEFAULT_N),
441+
Math.max(1, (params?.n as number | undefined) ?? MAX_N),
441442
)
442443

443-
// Spawn implementor agents
444-
const implementorAgents = []
445-
for (let i = 0; i < n; i++) {
446-
implementorAgents.push({
447-
agent_type: 'editor-implementor-opus',
448-
})
444+
// Model selection pattern for max mode, using opus and gpt-5
445+
const MAX_MODEL_PATTERN = [
446+
'editor-implementor-opus',
447+
'editor-implementor-opus',
448+
'editor-implementor-opus',
449+
'editor-implementor-opus',
450+
'editor-implementor-opus',
451+
'editor-implementor-opus',
452+
'editor-implementor-opus',
453+
'editor-implementor-opus',
454+
'editor-implementor-opus',
455+
'editor-implementor-opus',
456+
] as const
457+
458+
// Only keep messages up to just before the last user role message (skips input prompt, instrucitons prompt).
459+
const { messageHistory: initialMessageHistory } = agentState
460+
let userMessageIndex = initialMessageHistory.length
461+
462+
while (userMessageIndex > 0) {
463+
const message = initialMessageHistory[userMessageIndex - 1]
464+
if (message.role === 'user') {
465+
userMessageIndex--
466+
} else {
467+
break
468+
}
449469
}
470+
const updatedMessageHistory = initialMessageHistory.slice(0, userMessageIndex)
471+
yield {
472+
toolName: 'set_messages',
473+
input: {
474+
messages: updatedMessageHistory,
475+
},
476+
includeToolCall: false,
477+
} satisfies ToolCall<'set_messages'>
478+
479+
// Spawn implementor agents using the model pattern
480+
const implementorAgents = MAX_MODEL_PATTERN.slice(0, n).map((agent_type) => ({
481+
agent_type,
482+
}))
483+
450484
// Spawn all implementor agents
451485
const { toolResult: implementorResults } = yield {
452486
toolName: 'spawn_agents',
@@ -457,9 +491,14 @@ function* handleStepsOpus({
457491
} satisfies ToolCall<'spawn_agents'>
458492

459493
// Extract spawn results
460-
const spawnedImplementations =
461-
extractSpawnResults<{ text: string }[]>(implementorResults)
494+
const spawnedImplementations = extractSpawnResults(
495+
implementorResults,
496+
) as any[]
462497

498+
logger.info(
499+
{ implementorResults, spawnedImplementations },
500+
'spawnedImplementations',
501+
)
463502
// Extract all the plans from the structured outputs
464503
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
465504
// Parse implementations from spawn results
@@ -468,11 +507,11 @@ function* handleStepsOpus({
468507
content:
469508
'errorMessage' in result
470509
? `Error: ${result.errorMessage}`
471-
: result[0].text,
510+
: extractLastMessageText(result) ?? '',
472511
}))
473512

474513
// Spawn selector with implementations as params
475-
const { toolResult: selectorResult } = yield {
514+
const { toolResult: selectorResult, agentState: selectorAgentState } = yield {
476515
toolName: 'spawn_agents',
477516
input: {
478517
agents: [
@@ -486,8 +525,10 @@ function* handleStepsOpus({
486525
} satisfies ToolCall<'spawn_agents'>
487526

488527
const selectorOutput = extractSpawnResults<{
489-
implementationId: string
490-
reasoning: string
528+
value: {
529+
implementationId: string
530+
reasoning: string
531+
}
491532
}>(selectorResult)[0]
492533

493534
if ('errorMessage' in selectorOutput) {
@@ -497,7 +538,7 @@ function* handleStepsOpus({
497538
} satisfies ToolCall<'set_output'>
498539
return
499540
}
500-
const { implementationId } = selectorOutput
541+
const { implementationId } = selectorOutput.value
501542
const chosenImplementation = implementations.find(
502543
(implementation) => implementation.id === implementationId,
503544
)
@@ -509,68 +550,77 @@ function* handleStepsOpus({
509550
return
510551
}
511552

512-
// Apply the chosen implementation using STEP_TEXT (only tool calls, no commentary)
513-
const toolCallsOnly = extractToolCallsOnly(
514-
typeof chosenImplementation.content === 'string'
515-
? chosenImplementation.content
516-
: '',
517-
)
553+
const numMessagesBeforeStepText = selectorAgentState.messageHistory.length
554+
518555
const { agentState: postEditsAgentState } = yield {
519556
type: 'STEP_TEXT',
520-
text: toolCallsOnly,
557+
text: chosenImplementation.content,
521558
} as StepText
522559
const { messageHistory } = postEditsAgentState
523-
const lastAssistantMessageIndex = messageHistory.findLastIndex(
524-
(message) => message.role === 'assistant',
525-
)
526-
const editToolResults = messageHistory
527-
.slice(lastAssistantMessageIndex)
528-
.filter((message) => message.role === 'tool')
529-
.flatMap((message) => message.content)
530-
.filter((output) => output.type === 'json')
531-
.map((output) => output.value)
532560

533-
// Set output with the chosen implementation and reasoning
561+
// Set output with the messages from running the step text of the chosen implementation
534562
yield {
535563
toolName: 'set_output',
536564
input: {
537-
response: chosenImplementation.content,
538-
toolResults: editToolResults,
565+
messages: messageHistory.slice(numMessagesBeforeStepText),
539566
},
540567
includeToolCall: false,
541568
} satisfies ToolCall<'set_output'>
542569

543-
function extractSpawnResults<T>(
544-
results: any[] | undefined,
545-
): (T | { errorMessage: string })[] {
546-
if (!results) return []
547-
const spawnedResults = results
548-
.filter((result) => result.type === 'json')
549-
.map((result) => result.value)
550-
.flat() as {
551-
agentType: string
552-
value: { value?: T; errorMessage?: string }
553-
}[]
554-
return spawnedResults.map(
555-
(result) =>
556-
result.value.value ?? {
557-
errorMessage:
558-
result.value.errorMessage ?? 'Error extracting spawn results',
559-
},
560-
)
570+
/**
571+
* Extracts the array of subagent results from spawn_agents tool output.
572+
*
573+
* The spawn_agents tool result structure is:
574+
* [{ type: 'json', value: [{ agentName, agentType, value: AgentOutput }] }]
575+
*
576+
* Returns an array of agent outputs, one per spawned agent.
577+
*/
578+
function extractSpawnResults<T>(results: any[] | undefined): T[] {
579+
if (!results || results.length === 0) return []
580+
581+
// Find the json result containing spawn results
582+
const jsonResult = results.find((r) => r.type === 'json')
583+
if (!jsonResult?.value) return []
584+
585+
// Get the spawned agent results array
586+
const spawnedResults = Array.isArray(jsonResult.value)
587+
? jsonResult.value
588+
: [jsonResult.value]
589+
590+
// Extract the value (AgentOutput) from each result
591+
return spawnedResults.map((result: any) => result?.value).filter(Boolean)
561592
}
562593

563-
// Extract only tool calls from text, removing any commentary
564-
function extractToolCallsOnly(text: string): string {
565-
const toolExtractionPattern =
566-
/<codebuff_tool_call>\n(.*?)\n<\/codebuff_tool_call>/gs
567-
const matches: string[] = []
594+
/**
595+
* Extracts the text content from a 'lastMessage' AgentOutput.
596+
*
597+
* For agents with outputMode: 'last_message', the output structure is:
598+
* { type: 'lastMessage', value: [{ role: 'assistant', content: [{ type: 'text', text: '...' }] }] }
599+
*
600+
* Returns the text from the last assistant message, or null if not found.
601+
*/
602+
function extractLastMessageText(agentOutput: any): string | null {
603+
if (!agentOutput) return null
568604

569-
for (const match of text.matchAll(toolExtractionPattern)) {
570-
matches.push(match[0]) // Include the full tool call with tags
605+
// Handle 'lastMessage' output mode - the value contains an array of messages
606+
if (
607+
agentOutput.type === 'lastMessage' &&
608+
Array.isArray(agentOutput.value)
609+
) {
610+
// Find the last assistant message with text content
611+
for (let i = agentOutput.value.length - 1; i >= 0; i--) {
612+
const message = agentOutput.value[i]
613+
if (message.role === 'assistant' && Array.isArray(message.content)) {
614+
// Find text content in the message
615+
for (const part of message.content) {
616+
if (part.type === 'text' && typeof part.text === 'string') {
617+
return part.text
618+
}
619+
}
620+
}
621+
}
571622
}
572-
573-
return matches.join('\n')
623+
return null
574624
}
575625
}
576626

0 commit comments

Comments
 (0)