|
1 | 1 | import { ToolCalls } from '../../state/chat/reducer' |
2 | | -import { LLMResponse } from './types' |
| 2 | +import { LLMResponse, LLMResponseV2, CompletedToolCalls } from './types' |
3 | 3 | import { PlanActionsParams } from '.' |
4 | 4 | import { getLLMResponse } from '../../app/api' |
5 | 5 | import { getApp } from '../app' |
6 | 6 | import { getState } from '../../state/store' |
7 | 7 | import { set, unset } from 'lodash' |
8 | 8 | import { processAllMetadata } from '../metadataProcessor' |
9 | 9 | import { getParsedIframeInfo } from '../origin' |
| 10 | +import axios from 'axios' |
| 11 | +import { configs } from '../../constants' |
10 | 12 |
|
11 | 13 |
|
12 | 14 | export async function planActionsRemote({ |
@@ -195,7 +197,7 @@ export const convertToMarkdown = async(appState, imgs): Promise<string[]> => { |
195 | 197 | } |
196 | 198 |
|
197 | 199 | const systemMessage = ` |
198 | | - You are an incredible data scientist, and proficient at using jupyter notebooks. |
| 200 | + You are an incredible data scientist, and proficient at using jupyter notebooks. |
199 | 201 | The user gives you a jupyter state and you must convert it into a markdown document. |
200 | 202 | Just give a report as a markdown document based on the notebook |
201 | 203 | Don't print any actual code |
@@ -225,3 +227,126 @@ export const convertToMarkdown = async(appState, imgs): Promise<string[]> => { |
225 | 227 | return content |
226 | 228 | } |
227 | 229 |
|
| 230 | +// V2 API planner |
| 231 | +export async function planActionsRemoteV2({ |
| 232 | + signal, |
| 233 | + conversationID, |
| 234 | + meta, |
| 235 | + user_message |
| 236 | +}: Pick<PlanActionsParams, 'signal' | 'conversationID' | 'meta'> & { user_message: string }): Promise<LLMResponseV2> { |
| 237 | + const state = getState() |
| 238 | + const thread = state.chat.activeThread |
| 239 | + const activeThread = state.chat.threads[thread] |
| 240 | + const messageHistory = activeThread.messages |
| 241 | + |
| 242 | + // Find the last user message to get tasks_id |
| 243 | + const lastUserMessageIdx = messageHistory.findLastIndex((message) => message.role === 'user') |
| 244 | + if (lastUserMessageIdx === -1) { |
| 245 | + throw new Error('No user message found in thread') |
| 246 | + } |
| 247 | + |
| 248 | + const lastUserMessage = messageHistory[lastUserMessageIdx] |
| 249 | + const tasks_id = lastUserMessage.tasks_id || null |
| 250 | + |
| 251 | + // Extract completed tool calls from the last 'pending' planner response only |
| 252 | + // Find the last assistant message with actions from pending source |
| 253 | + const completed_tool_calls: Array<{tool_call_id: string, content: string, role: 'tool'}> = [] |
| 254 | + let lastPlanIdx = -1 |
| 255 | + for (let i = messageHistory.length - 1; i >= lastUserMessageIdx; i--) { |
| 256 | + const msg = messageHistory[i] |
| 257 | + if (msg.role === 'assistant' && msg.content.type === 'ACTIONS') { |
| 258 | + lastPlanIdx = i |
| 259 | + break |
| 260 | + } |
| 261 | + } |
| 262 | + |
| 263 | + // If we found a plan, only extract if it's from pending source (client executed) |
| 264 | + if (lastPlanIdx !== -1) { |
| 265 | + const planMessage = messageHistory[lastPlanIdx] |
| 266 | + if (planMessage.role === 'assistant' && planMessage.content.type === 'ACTIONS' && |
| 267 | + planMessage.content.source === 'pending') { |
| 268 | + // Get all tool messages that belong to this plan and are finished |
| 269 | + for (const toolMessageIdx of planMessage.content.actionMessageIDs) { |
| 270 | + const toolMessage = messageHistory[toolMessageIdx] |
| 271 | + if (toolMessage?.role === 'tool' && toolMessage.action.finished) { |
| 272 | + let content = '' |
| 273 | + if (toolMessage.content.type === 'DEFAULT') { |
| 274 | + content = toolMessage.content.text |
| 275 | + } else if (toolMessage.content.type === 'BLANK') { |
| 276 | + content = toolMessage.content.content || '' |
| 277 | + } |
| 278 | + |
| 279 | + completed_tool_calls.push({ |
| 280 | + tool_call_id: toolMessage.action.id, |
| 281 | + content, |
| 282 | + role: 'tool' |
| 283 | + }) |
| 284 | + } |
| 285 | + } |
| 286 | + } |
| 287 | + } |
| 288 | + |
| 289 | + // Build request payload |
| 290 | + // Only send user_message on first call (when no completed_tool_calls) |
| 291 | + const payload: any = { |
| 292 | + conversationID, |
| 293 | + tasks_id, |
| 294 | + user_message: completed_tool_calls.length > 0 ? '' : user_message, |
| 295 | + completed_tool_calls, |
| 296 | + meta |
| 297 | + } |
| 298 | + |
| 299 | + // Add metadata hashes for analyst mode (when both drMode and analystMode are enabled) |
| 300 | + if (state.settings.drMode && state.settings.analystMode) { |
| 301 | + try { |
| 302 | + const dbId = getApp().useStore().getState().toolContext?.dbId || undefined |
| 303 | + const parsedInfo = getParsedIframeInfo() |
| 304 | + const { cardsHash, dbSchemaHash, fieldsHash, selectedDbId } = await processAllMetadata(false, dbId) |
| 305 | + |
| 306 | + payload.cardsHash = cardsHash |
| 307 | + payload.dbSchemaHash = dbSchemaHash |
| 308 | + payload.fieldsHash = fieldsHash |
| 309 | + payload.selectedDbId = `${selectedDbId}` |
| 310 | + payload.r = parsedInfo.r |
| 311 | + console.log('[minusx] Added metadata hashes to v2 request for analyst mode') |
| 312 | + } catch (error) { |
| 313 | + console.warn('[minusx] Failed to fetch metadata for analyst mode:', error) |
| 314 | + } |
| 315 | + } |
| 316 | + |
| 317 | + // Add selected asset_slug if available and team memory is enabled |
| 318 | + const selectedAssetSlug = state.settings.selectedAssetId |
| 319 | + const useTeamMemory = state.settings.useTeamMemory |
| 320 | + if (selectedAssetSlug && useTeamMemory) { |
| 321 | + payload.asset_slug = selectedAssetSlug |
| 322 | + console.log('[minusx] Added asset_slug to v2 request for enhanced context:', selectedAssetSlug) |
| 323 | + } |
| 324 | + |
| 325 | + // Make API call |
| 326 | + const response = await axios.post( |
| 327 | + `${configs.BASE_SERVER_URL}/deepresearch/v2/chat_planner`, |
| 328 | + payload, |
| 329 | + { |
| 330 | + headers: { |
| 331 | + 'Content-Type': 'application/json', |
| 332 | + }, |
| 333 | + signal |
| 334 | + } |
| 335 | + ) |
| 336 | + |
| 337 | + signal.throwIfAborted() |
| 338 | + |
| 339 | + const jsonResponse = response.data |
| 340 | + if (jsonResponse.error) { |
| 341 | + throw new Error(jsonResponse.error) |
| 342 | + } |
| 343 | + |
| 344 | + return { |
| 345 | + pending_tool_calls: jsonResponse.pending_tool_calls || [], |
| 346 | + completed_tool_calls: jsonResponse.completed_tool_calls || [], |
| 347 | + tasks_id: jsonResponse.tasks_id, |
| 348 | + credits: jsonResponse.credits, |
| 349 | + error: jsonResponse.error |
| 350 | + } |
| 351 | +} |
| 352 | + |
0 commit comments