import type { StudioAssistantMessage } from '../../domain/types' import type OpenAI from 'openai' import { createCustomOpenAIClient } from '../../../services/openai-client-factory' import { logPlotStudioSkillTrace } from '../../observability/plot-studio-skill-trace' import { readStudioRunAutonomyMetadata } from '../../runs/autonomy-policy' import { buildStudioAgentSystemPrompt } from '../studio-agent-prompt' import { buildStudioConversationMessages } from '../studio-message-history' import { createLogger } from '../../../utils/logger' import { persistProviderMessageSnapshot, summarizeAssistantMessageForDebug, summarizeConversationMessageForDebug, summarizeConversationTailForDebug, } from '../studio-provider-message' import { requestStudioChatCompletion } from '../studio-provider-request' import { buildStudioChatTools } from '../studio-tool-schema' import { normalizeStudioAssistantText } from './message-assembly' import { logStudioLoopStepResponse, logStudioLoopStepStarted } from './observability' import type { StudioChatCompletionMessage, StudioLoopRuntime, StudioLoopStepRequest, StudioLoopStepResult, StudioOpenAIToolLoopInput } from './types' const logger = createLogger('StudioLoopRequest') const DEFAULT_MAX_STEPS = 8 export async function createStudioLoopRuntime(input: StudioOpenAIToolLoopInput): Promise { const client = createCustomOpenAIClient(input.customApiConfig) const model = (input.customApiConfig.model || '').trim() if (!model) { throw new Error('Studio agent requires a provider model') } const tools = buildStudioChatTools(input.registry, input.session.agentType, input.session.studioKind) const storedMessages = await input.messageStore.listBySessionId(input.session.id) logPlotStudioSkillTrace(input.session.studioKind, 'skill.discovery.requested', { sessionId: input.session.id, runId: input.run.id, agentType: input.session.agentType, studioKind: input.session.studioKind, }) const [availableSkills, skillSummaries] = await Promise.all([ input.listSkills?.(input.session) ?? Promise.resolve([]), input.listSkillSummaries?.(input.session) ?? Promise.resolve([]) ]) logPlotStudioSkillTrace(input.session.studioKind, 'skill.discovery.completed', { sessionId: input.session.id, runId: input.run.id, discoveredSkillCount: availableSkills.length, discoveredSkillNames: availableSkills.map((skill) => skill.name), }) logPlotStudioSkillTrace(input.session.studioKind, 'skill.summary.completed', { sessionId: input.session.id, runId: input.run.id, summaryCount: skillSummaries.length, summarySkillNames: skillSummaries.map((summary) => summary.skillName), }) logPlotStudioSkillTrace(input.session.studioKind, 'skill.prompt.catalog', { sessionId: input.session.id, runId: input.run.id, discoveredSkillCount: availableSkills.length, }) logPlotStudioSkillTrace(input.session.studioKind, 'skill.prompt.state', { sessionId: input.session.id, runId: input.run.id, summaryCount: skillSummaries.length, }) return { client, model, tools, conversation: buildStudioConversationMessages({ messages: storedMessages }), systemPrompt: buildStudioAgentSystemPrompt({ session: input.session, workContext: input.workContext, availableSkills, skillSummaries }), maxSteps: input.maxSteps ?? readStudioRunAutonomyMetadata(input.run.metadata).maxSteps ?? DEFAULT_MAX_STEPS, toolChoice: input.toolChoice ?? 'auto', currentAssistantMessage: input.assistantMessage } } export function buildStudioLoopStepRequest(runtime: StudioLoopRuntime): StudioLoopStepRequest { const messages = [ { role: 'system' as const, content: runtime.systemPrompt }, ...runtime.conversation ] const lastUserOrAssistant = messages.filter(m => m.role === 'user' || m.role === 'assistant').slice(-1)[0] if (lastUserOrAssistant && lastUserOrAssistant.role === 'assistant') { const assistantMsg = lastUserOrAssistant as OpenAI.Chat.Completions.ChatCompletionAssistantMessageParam & { reasoning_content?: unknown } logger.debug('buildStudioLoopStepRequest last assistant', { hasReasoningContent: Boolean(assistantMsg.reasoning_content), reasoningContentType: typeof assistantMsg.reasoning_content, reasoningContentIsArray: Array.isArray(assistantMsg.reasoning_content), reasoningContentLength: Array.isArray(assistantMsg.reasoning_content) ? assistantMsg.reasoning_content.length : undefined, }) logger.info('buildStudioLoopStepRequest assistant replay payload', { messageCount: messages.length, requestMessageCharsApprox: JSON.stringify(messages).length, lastAssistant: summarizeConversationMessageForDebug(lastUserOrAssistant), conversationTail: summarizeConversationTailForDebug(runtime.conversation), }) } return { messages, requestMessageCharsApprox: JSON.stringify(messages).length, requestToolSchemaCharsApprox: JSON.stringify(runtime.tools).length } } export async function requestStudioLoopStep(input: { loopInput: StudioOpenAIToolLoopInput runtime: StudioLoopRuntime request: StudioLoopStepRequest step: number stepStartedAt: number }): Promise { logStudioLoopStepStarted({ loopInput: input.loopInput, conversationLength: input.runtime.conversation.length, toolCount: input.runtime.tools.length, request: input.request, step: input.step }) const completion = await requestStudioChatCompletion({ client: input.runtime.client, model: input.runtime.model, messages: input.request.messages, tools: input.runtime.tools, toolChoice: input.runtime.toolChoice, sessionId: input.loopInput.session.id, runId: input.loopInput.run.id, step: input.step + 1, assistantMessageId: input.runtime.currentAssistantMessage.id, studioKind: input.loopInput.session.studioKind, runCreatedAt: input.loopInput.run.createdAt, requestMessageCount: input.request.messages.length, requestMessageCharsApprox: input.request.requestMessageCharsApprox, requestToolSchemaCharsApprox: input.request.requestToolSchemaCharsApprox, signal: input.loopInput.abortSignal, }) const choice = completion.choices[0] const message = choice?.message const result = { completion, message, assistantText: normalizeStudioAssistantText(message?.content), toolCalls: message?.tool_calls ?? [] } logger.info('requestStudioLoopStep provider response payload', { step: input.step + 1, finishReason: choice?.finish_reason ?? null, assistantMessage: summarizeAssistantMessageForDebug(message), }) logStudioLoopStepResponse({ loopInput: input.loopInput, result, step: input.step, stepStartedAt: input.stepStartedAt }) return result } export async function persistStudioProviderSnapshot( input: StudioOpenAIToolLoopInput, assistantMessage: StudioAssistantMessage, message: StudioChatCompletionMessage | undefined ) { logger.info('persistStudioProviderSnapshot before persist', { assistantMessageId: assistantMessage.id, providerMessage: summarizeAssistantMessageForDebug(message), }) await persistProviderMessageSnapshot({ messageStore: input.messageStore, assistantMessage, providerMessage: message }) }