| |
| |
| |
| |
| |
| |
| |
| |
| |
| import { postCompletionsPromptIncremental } from '../../shared/api/completionsClient'; |
| import { resolveMockTool } from './mockExecutor'; |
| import { parseToolCallFromCompletion } from './toolCallParser'; |
| import type { ToolConfig } from './toolConfig'; |
| import { |
| runMockToolPendingGap, |
| type ToolCallingPendingLine, |
| } from './toolCallingPendingUi'; |
|
|
| export const MAX_TOOL_ROUNDS = 16; |
|
|
| export function isAbortError(err: unknown): boolean { |
| return err instanceof DOMException && err.name === 'AbortError'; |
| } |
|
|
| |
| export async function fetchIncrementalSuffix( |
| model: string, |
| enableThinking: boolean, |
| toolName: string, |
| toolContent: string, |
| signal?: AbortSignal, |
| ): Promise<string> { |
| const { incremental_suffix } = await postCompletionsPromptIncremental( |
| { model, tool_content: toolContent, tool_name: toolName, enable_thinking: enableThinking }, |
| { signal }, |
| ); |
| return incremental_suffix; |
| } |
|
|
| |
| export type ToolRoundDecision = |
| | { status: 'stop' } |
| | { status: 'malformed' } |
| | { status: 'inject'; toolName: string; mockContent: string }; |
|
|
| export function decideToolRoundContinuation( |
| assistantTurnText: string, |
| toolConfig: ToolConfig, |
| ): ToolRoundDecision { |
| const parsed = parseToolCallFromCompletion(assistantTurnText); |
| if (parsed.status === 'malformed') return { status: 'malformed' }; |
| if (parsed.status === 'absent') return { status: 'stop' }; |
|
|
| const mockContent = resolveMockTool( |
| toolConfig, |
| parsed.call.name, |
| parsed.call.arguments, |
| ); |
| if (mockContent === null) return { status: 'stop' }; |
|
|
| return { |
| status: 'inject', |
| toolName: parsed.call.name, |
| mockContent, |
| }; |
| } |
|
|
| export type PrepareToolRoundContinuationOptions = { |
| assistantTurnText: string; |
| toolConfig: ToolConfig; |
| model: string; |
| enableThinking: boolean; |
| signal?: AbortSignal; |
| |
| mockToolGapUi?: ToolCallingPendingLine; |
| |
| onMockResolved?: () => void; |
| }; |
|
|
| export type ToolRoundContinuation = |
| | { status: 'stop' } |
| | { status: 'malformed' } |
| | { status: 'continue'; incrementalSuffix: string }; |
|
|
| |
| |
| |
| |
| export async function prepareToolRoundContinuation( |
| opts: PrepareToolRoundContinuationOptions, |
| ): Promise<ToolRoundContinuation> { |
| const decision = decideToolRoundContinuation(opts.assistantTurnText, opts.toolConfig); |
| if (decision.status === 'malformed') return { status: 'malformed' }; |
| if (decision.status === 'stop') return { status: 'stop' }; |
|
|
| opts.onMockResolved?.(); |
| await runMockToolPendingGap(opts.signal, opts.mockToolGapUi); |
|
|
| const incrementalSuffix = await fetchIncrementalSuffix( |
| opts.model, |
| opts.enableThinking, |
| decision.toolName, |
| decision.mockContent, |
| opts.signal, |
| ); |
| return { status: 'continue', incrementalSuffix }; |
| } |
|
|