| 61 | } |
| 62 | |
| 63 | export const createMessageStream = async ({ |
| 64 | chatId, |
| 65 | messages, |
| 66 | metadata, |
| 67 | selectedRepos, |
| 68 | prisma, |
| 69 | disabledMcpServerIds, |
| 70 | model, |
| 71 | modelName, |
| 72 | modelProviderOptions, |
| 73 | modelTemperature, |
| 74 | onFinish, |
| 75 | onError, |
| 76 | userId, |
| 77 | orgId, |
| 78 | }: CreateMessageStreamResponseProps) => { |
| 79 | // Defense-in-depth: Ask Sourcebot is a paid feature. Every caller is |
| 80 | // expected to gate on the `ask` entitlement before reaching here (see |
| 81 | // checkAskEntitlement); this assertion backstops that contract so a future |
| 82 | // ungated caller cannot execute the agent on a non-entitled deployment. |
| 83 | if (!(await hasEntitlement('ask'))) { |
| 84 | throw new Error('Ask Sourcebot is not available in the current plan.'); |
| 85 | } |
| 86 | |
| 87 | const latestMessage = messages[messages.length - 1]; |
| 88 | const sources = latestMessage.parts |
| 89 | .filter((part) => part.type === 'data-source') |
| 90 | .map((part) => part.data); |
| 91 | |
| 92 | const traceId = randomUUID(); |
| 93 | |
| 94 | // Extract user messages and assistant answers. |
| 95 | // We will use this as the context we carry between messages. |
| 96 | // Server requests always receive persisted messages between client streams, so evaluate them in the ready state. |
| 97 | const incomingTurnProgress = getTurnProgressState({ messages, status: 'ready' }); |
| 98 | let messageHistory: ModelMessage[] = |
| 99 | messages.map((message, index): ModelMessage | undefined => { |
| 100 | if (message.role === 'user') { |
| 101 | return { |
| 102 | role: 'user', |
| 103 | content: message.parts[0].type === 'text' ? message.parts[0].text : '', |
| 104 | }; |
| 105 | } |
| 106 | |
| 107 | if (message.role === 'assistant') { |
| 108 | const isLatestIncompleteAssistantMessage = |
| 109 | index === messages.length - 1 && |
| 110 | incomingTurnProgress.isTurnInProgress; |
| 111 | const answerPart = getAnswerPartFromAssistantMessage(message, isLatestIncompleteAssistantMessage); |
| 112 | if (answerPart) { |
| 113 | return { |
| 114 | role: 'assistant', |
| 115 | content: [answerPart] |
| 116 | } |
| 117 | } |
| 118 | } |
| 119 | }).filter(message => message !== undefined); |
| 120 | |