mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-19 13:33:35 +08:00
fix(desktop): apply active provider before first prompt (#844)
Materialize the active provider runtime for empty-session first prompts before the WebSocket prewarm and user message are sent. This keeps a clean install that adds a MiniMax provider from starting the first turn without the selected provider runtime. Tested: bun test src/server/__tests__/conversations.test.ts -t "MiniMax provider turn" Tested: bun test src/server/__tests__/websocket-handler.test.ts src/server/__tests__/conversations.test.ts -t "prewarm|active provider id|MiniMax provider turn|first-turn runtime synchronization" Tested: cd desktop && bun run test -- --run src/pages/EmptySession.test.tsx -t "draft runtime|active provider runtime|creates a new session" Tested: cd desktop && bun run test -- --run src/components/controls/ModelSelector.test.tsx -t "defaults blank provider-scoped runtime selections|selects provider-scoped runtime models" Tested: cd desktop && bun run lint Tested: cd desktop && bun run build Not-tested: bun run check:desktop, unrelated full-suite failures in MessageList timestamp formatting and TabBar AbortSignal cleanup blocked the gate. Confidence: medium Scope-risk: narrow
This commit is contained in:
parent
2e52133a64
commit
78d85f0c64
@ -1,8 +1,7 @@
|
|||||||
import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import { OFFICIAL_DEFAULT_MODEL_ID, OFFICIAL_MODELS } from '../../constants/modelCatalog'
|
import { OFFICIAL_MODELS } from '../../constants/modelCatalog'
|
||||||
import {
|
import {
|
||||||
OPENAI_OFFICIAL_DEFAULT_MODEL_ID,
|
|
||||||
OPENAI_OFFICIAL_MODELS,
|
OPENAI_OFFICIAL_MODELS,
|
||||||
OPENAI_OFFICIAL_PROVIDER_ID,
|
OPENAI_OFFICIAL_PROVIDER_ID,
|
||||||
} from '../../constants/openaiOfficialProvider'
|
} from '../../constants/openaiOfficialProvider'
|
||||||
@ -16,6 +15,7 @@ import type { RuntimeSelection } from '../../types/runtime'
|
|||||||
import type { EffortLevel, ModelInfo } from '../../types/settings'
|
import type { EffortLevel, ModelInfo } from '../../types/settings'
|
||||||
import { useMobileViewport } from '../../hooks/useMobileViewport'
|
import { useMobileViewport } from '../../hooks/useMobileViewport'
|
||||||
import { isDesktopRuntime } from '../../lib/desktopRuntime'
|
import { isDesktopRuntime } from '../../lib/desktopRuntime'
|
||||||
|
import { resolveDefaultRuntimeSelection } from '../../lib/runtimeSelection'
|
||||||
import { useHahaOAuthStore } from '../../stores/hahaOAuthStore'
|
import { useHahaOAuthStore } from '../../stores/hahaOAuthStore'
|
||||||
import { useHahaOpenAIOAuthStore } from '../../stores/hahaOpenAIOAuthStore'
|
import { useHahaOpenAIOAuthStore } from '../../stores/hahaOpenAIOAuthStore'
|
||||||
import { MobileBottomSheet } from '../shared/MobileBottomSheet'
|
import { MobileBottomSheet } from '../shared/MobileBottomSheet'
|
||||||
@ -144,30 +144,6 @@ function buildProviderChoices(
|
|||||||
return choices
|
return choices
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveDefaultRuntimeSelection(
|
|
||||||
activeId: string | null,
|
|
||||||
activeProviderName: string | null,
|
|
||||||
providers: SavedProvider[],
|
|
||||||
currentModelId: string | undefined,
|
|
||||||
): RuntimeSelection {
|
|
||||||
const activeProvider = activeId
|
|
||||||
? providers.find((provider) => provider.id === activeId)
|
|
||||||
: activeProviderName
|
|
||||||
? providers.find((provider) => provider.name === activeProviderName)
|
|
||||||
: undefined
|
|
||||||
const inferredProviderId = activeId ?? activeProvider?.id ?? null
|
|
||||||
const providerMainModelId = activeProvider?.models.main.trim()
|
|
||||||
|
|
||||||
return {
|
|
||||||
providerId: inferredProviderId,
|
|
||||||
modelId: providerMainModelId || currentModelId || (
|
|
||||||
inferredProviderId === OPENAI_OFFICIAL_PROVIDER_ID
|
|
||||||
? OPENAI_OFFICIAL_DEFAULT_MODEL_ID
|
|
||||||
: OFFICIAL_DEFAULT_MODEL_ID
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ModelSelector = forwardRef<ModelSelectorHandle, Props>(function ModelSelector({
|
export const ModelSelector = forwardRef<ModelSelectorHandle, Props>(function ModelSelector({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
|
|||||||
50
desktop/src/lib/runtimeSelection.ts
Normal file
50
desktop/src/lib/runtimeSelection.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { OFFICIAL_DEFAULT_MODEL_ID } from '../constants/modelCatalog'
|
||||||
|
import {
|
||||||
|
OPENAI_OFFICIAL_DEFAULT_MODEL_ID,
|
||||||
|
OPENAI_OFFICIAL_PROVIDER_ID,
|
||||||
|
} from '../constants/openaiOfficialProvider'
|
||||||
|
import type { SavedProvider } from '../types/provider'
|
||||||
|
import type { RuntimeSelection } from '../types/runtime'
|
||||||
|
|
||||||
|
export function resolveActiveProviderRuntimeSelection(
|
||||||
|
activeId: string | null,
|
||||||
|
activeProviderName: string | null,
|
||||||
|
providers: SavedProvider[],
|
||||||
|
currentModelId: string | undefined,
|
||||||
|
): RuntimeSelection | null {
|
||||||
|
const activeProvider = activeId
|
||||||
|
? providers.find((provider) => provider.id === activeId)
|
||||||
|
: activeProviderName
|
||||||
|
? providers.find((provider) => provider.name === activeProviderName)
|
||||||
|
: undefined
|
||||||
|
const inferredProviderId = activeId ?? activeProvider?.id ?? null
|
||||||
|
if (!inferredProviderId) return null
|
||||||
|
|
||||||
|
const providerMainModelId = activeProvider?.models.main.trim()
|
||||||
|
|
||||||
|
return {
|
||||||
|
providerId: inferredProviderId,
|
||||||
|
modelId: providerMainModelId || currentModelId || (
|
||||||
|
inferredProviderId === OPENAI_OFFICIAL_PROVIDER_ID
|
||||||
|
? OPENAI_OFFICIAL_DEFAULT_MODEL_ID
|
||||||
|
: OFFICIAL_DEFAULT_MODEL_ID
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveDefaultRuntimeSelection(
|
||||||
|
activeId: string | null,
|
||||||
|
activeProviderName: string | null,
|
||||||
|
providers: SavedProvider[],
|
||||||
|
currentModelId: string | undefined,
|
||||||
|
): RuntimeSelection {
|
||||||
|
return resolveActiveProviderRuntimeSelection(
|
||||||
|
activeId,
|
||||||
|
activeProviderName,
|
||||||
|
providers,
|
||||||
|
currentModelId,
|
||||||
|
) ?? {
|
||||||
|
providerId: null,
|
||||||
|
modelId: currentModelId || OFFICIAL_DEFAULT_MODEL_ID,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -131,6 +131,7 @@ vi.mock('../components/controls/ModelSelector', async () => {
|
|||||||
import { EmptySession } from './EmptySession'
|
import { EmptySession } from './EmptySession'
|
||||||
import { ApiError } from '../api/client'
|
import { ApiError } from '../api/client'
|
||||||
import { useChatStore } from '../stores/chatStore'
|
import { useChatStore } from '../stores/chatStore'
|
||||||
|
import { useProviderStore } from '../stores/providerStore'
|
||||||
import { useSessionRuntimeStore } from '../stores/sessionRuntimeStore'
|
import { useSessionRuntimeStore } from '../stores/sessionRuntimeStore'
|
||||||
import { useSessionStore } from '../stores/sessionStore'
|
import { useSessionStore } from '../stores/sessionStore'
|
||||||
import { useSettingsStore } from '../stores/settingsStore'
|
import { useSettingsStore } from '../stores/settingsStore'
|
||||||
@ -186,6 +187,7 @@ describe('EmptySession', () => {
|
|||||||
const initialRuntimeState = useSessionRuntimeStore.getInitialState()
|
const initialRuntimeState = useSessionRuntimeStore.getInitialState()
|
||||||
const initialUiState = useUIStore.getInitialState()
|
const initialUiState = useUIStore.getInitialState()
|
||||||
const initialPluginState = usePluginStore.getInitialState()
|
const initialPluginState = usePluginStore.getInitialState()
|
||||||
|
const initialProviderState = useProviderStore.getInitialState()
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
@ -199,6 +201,7 @@ describe('EmptySession', () => {
|
|||||||
useSessionRuntimeStore.setState(initialRuntimeState, true)
|
useSessionRuntimeStore.setState(initialRuntimeState, true)
|
||||||
useUIStore.setState(initialUiState, true)
|
useUIStore.setState(initialUiState, true)
|
||||||
usePluginStore.setState(initialPluginState, true)
|
usePluginStore.setState(initialPluginState, true)
|
||||||
|
useProviderStore.setState(initialProviderState, true)
|
||||||
|
|
||||||
mocks.createSession.mockResolvedValue({ sessionId: 'draft-session' })
|
mocks.createSession.mockResolvedValue({ sessionId: 'draft-session' })
|
||||||
mocks.getRepositoryContext.mockResolvedValue(okRepositoryContext())
|
mocks.getRepositoryContext.mockResolvedValue(okRepositoryContext())
|
||||||
@ -238,6 +241,7 @@ describe('EmptySession', () => {
|
|||||||
useSessionRuntimeStore.setState(initialRuntimeState, true)
|
useSessionRuntimeStore.setState(initialRuntimeState, true)
|
||||||
useUIStore.setState(initialUiState, true)
|
useUIStore.setState(initialUiState, true)
|
||||||
usePluginStore.setState(initialPluginState, true)
|
usePluginStore.setState(initialPluginState, true)
|
||||||
|
useProviderStore.setState(initialProviderState, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('uses compact composer controls on phone-sized H5 browsers', async () => {
|
it('uses compact composer controls on phone-sized H5 browsers', async () => {
|
||||||
@ -527,6 +531,66 @@ describe('EmptySession', () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('materializes the active provider runtime before the first draft message', async () => {
|
||||||
|
useProviderStore.setState({
|
||||||
|
providers: [{
|
||||||
|
id: 'provider-minimax',
|
||||||
|
presetId: 'minimax',
|
||||||
|
name: 'MiniMax',
|
||||||
|
apiKey: 'sk-minimax',
|
||||||
|
baseUrl: 'https://api.minimaxi.com/anthropic',
|
||||||
|
apiFormat: 'anthropic',
|
||||||
|
runtimeKind: 'anthropic_compatible',
|
||||||
|
models: {
|
||||||
|
main: 'MiniMax-M3[1m]',
|
||||||
|
haiku: 'MiniMax-M3[1m]',
|
||||||
|
sonnet: 'MiniMax-M3[1m]',
|
||||||
|
opus: 'MiniMax-M3[1m]',
|
||||||
|
},
|
||||||
|
toolSearchEnabled: true,
|
||||||
|
}],
|
||||||
|
activeId: 'provider-minimax',
|
||||||
|
providerOrder: ['provider-minimax', 'claude-official', 'openai-official'],
|
||||||
|
hasLoadedProviders: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
render(<EmptySession />)
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByRole('textbox'), {
|
||||||
|
target: { value: 'draft question', selectionStart: 14 },
|
||||||
|
})
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /Run/i }))
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mocks.createSession).toHaveBeenCalledWith({ permissionMode: 'default' })
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(useSessionRuntimeStore.getState().selections['draft-session']).toEqual({
|
||||||
|
providerId: 'provider-minimax',
|
||||||
|
modelId: 'MiniMax-M3[1m]',
|
||||||
|
})
|
||||||
|
expect(mocks.wsSend.mock.calls.slice(0, 3)).toEqual([
|
||||||
|
[
|
||||||
|
'draft-session',
|
||||||
|
{
|
||||||
|
type: 'set_runtime_config',
|
||||||
|
providerId: 'provider-minimax',
|
||||||
|
modelId: 'MiniMax-M3[1m]',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['draft-session', { type: 'prewarm_session' }],
|
||||||
|
[
|
||||||
|
'draft-session',
|
||||||
|
{
|
||||||
|
type: 'user_message',
|
||||||
|
content: 'draft question',
|
||||||
|
attachments: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
it('uses native desktop file paths for draft attachments', async () => {
|
it('uses native desktop file paths for draft attachments', async () => {
|
||||||
mocks.isTauriRuntime = true
|
mocks.isTauriRuntime = true
|
||||||
window.desktopHost = {
|
window.desktopHost = {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { useTranslation } from '../i18n'
|
|||||||
import { useSessionStore } from '../stores/sessionStore'
|
import { useSessionStore } from '../stores/sessionStore'
|
||||||
import { useChatStore } from '../stores/chatStore'
|
import { useChatStore } from '../stores/chatStore'
|
||||||
import { usePluginStore } from '../stores/pluginStore'
|
import { usePluginStore } from '../stores/pluginStore'
|
||||||
|
import { useProviderStore } from '../stores/providerStore'
|
||||||
import { useSessionRuntimeStore, DRAFT_RUNTIME_SELECTION_KEY } from '../stores/sessionRuntimeStore'
|
import { useSessionRuntimeStore, DRAFT_RUNTIME_SELECTION_KEY } from '../stores/sessionRuntimeStore'
|
||||||
import { useSettingsStore } from '../stores/settingsStore'
|
import { useSettingsStore } from '../stores/settingsStore'
|
||||||
import { useUIStore } from '../stores/uiStore'
|
import { useUIStore } from '../stores/uiStore'
|
||||||
@ -21,6 +22,7 @@ import { LocalSlashCommandPanel, type LocalSlashCommandName } from '../component
|
|||||||
import { useMobileViewport } from '../hooks/useMobileViewport'
|
import { useMobileViewport } from '../hooks/useMobileViewport'
|
||||||
import { isDesktopRuntime } from '../lib/desktopRuntime'
|
import { isDesktopRuntime } from '../lib/desktopRuntime'
|
||||||
import { publicAssetPath } from '../lib/publicAsset'
|
import { publicAssetPath } from '../lib/publicAsset'
|
||||||
|
import { resolveActiveProviderRuntimeSelection } from '../lib/runtimeSelection'
|
||||||
import {
|
import {
|
||||||
filesToComposerAttachments,
|
filesToComposerAttachments,
|
||||||
selectNativeFileAttachments,
|
selectNativeFileAttachments,
|
||||||
@ -116,8 +118,11 @@ export function EmptySession() {
|
|||||||
const setActiveView = useUIStore((state) => state.setActiveView)
|
const setActiveView = useUIStore((state) => state.setActiveView)
|
||||||
const addToast = useUIStore((state) => state.addToast)
|
const addToast = useUIStore((state) => state.addToast)
|
||||||
const currentModel = useSettingsStore((state) => state.currentModel)
|
const currentModel = useSettingsStore((state) => state.currentModel)
|
||||||
|
const activeProviderName = useSettingsStore((state) => state.activeProviderName)
|
||||||
const chatSendBehavior = useSettingsStore((state) => state.chatSendBehavior)
|
const chatSendBehavior = useSettingsStore((state) => state.chatSendBehavior)
|
||||||
const defaultPermissionMode = useSettingsStore((state) => state.permissionMode)
|
const defaultPermissionMode = useSettingsStore((state) => state.permissionMode)
|
||||||
|
const providers = useProviderStore((state) => state.providers)
|
||||||
|
const activeProviderId = useProviderStore((state) => state.activeId)
|
||||||
const [draftPermissionMode, setDraftPermissionMode] = useState<PermissionMode>(defaultPermissionMode)
|
const [draftPermissionMode, setDraftPermissionMode] = useState<PermissionMode>(defaultPermissionMode)
|
||||||
const lastPluginReloadSummary = usePluginStore((state) => state.lastReloadSummary)
|
const lastPluginReloadSummary = usePluginStore((state) => state.lastReloadSummary)
|
||||||
const draftRuntimeSelection = useSessionRuntimeStore((state) => state.selections[DRAFT_RUNTIME_SELECTION_KEY])
|
const draftRuntimeSelection = useSessionRuntimeStore((state) => state.selections[DRAFT_RUNTIME_SELECTION_KEY])
|
||||||
@ -315,7 +320,17 @@ export function EmptySession() {
|
|||||||
|
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
try {
|
try {
|
||||||
const explicitDraftSelection = useSessionRuntimeStore.getState().selections[DRAFT_RUNTIME_SELECTION_KEY]
|
const runtimeStore = useSessionRuntimeStore.getState()
|
||||||
|
const explicitDraftSelection = runtimeStore.selections[DRAFT_RUNTIME_SELECTION_KEY]
|
||||||
|
const defaultActiveProviderSelection = explicitDraftSelection
|
||||||
|
? null
|
||||||
|
: resolveActiveProviderRuntimeSelection(
|
||||||
|
activeProviderId,
|
||||||
|
activeProviderName,
|
||||||
|
providers,
|
||||||
|
currentModel?.id,
|
||||||
|
)
|
||||||
|
const runtimeSelection = explicitDraftSelection ?? defaultActiveProviderSelection ?? undefined
|
||||||
const sessionId = await createSession(
|
const sessionId = await createSession(
|
||||||
workDir || undefined,
|
workDir || undefined,
|
||||||
{
|
{
|
||||||
@ -325,9 +340,11 @@ export function EmptySession() {
|
|||||||
permissionMode: draftPermissionMode,
|
permissionMode: draftPermissionMode,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if (explicitDraftSelection) {
|
if (runtimeSelection) {
|
||||||
useSessionRuntimeStore.getState().setSelection(sessionId, explicitDraftSelection)
|
runtimeStore.setSelection(sessionId, runtimeSelection)
|
||||||
useSessionRuntimeStore.getState().clearSelection(DRAFT_RUNTIME_SELECTION_KEY)
|
if (explicitDraftSelection) {
|
||||||
|
runtimeStore.clearSelection(DRAFT_RUNTIME_SELECTION_KEY)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setActiveView('code')
|
setActiveView('code')
|
||||||
useTabStore.getState().openTab(sessionId, 'New Session')
|
useTabStore.getState().openTab(sessionId, 'New Session')
|
||||||
|
|||||||
@ -2192,6 +2192,111 @@ describe('WebSocket Chat Integration', () => {
|
|||||||
}
|
}
|
||||||
}, 20_000)
|
}, 20_000)
|
||||||
|
|
||||||
|
it('does not strand the first MiniMax provider turn when prewarm and user message flush together (#844)', async () => {
|
||||||
|
const providerService = new ProviderService()
|
||||||
|
const provider = await providerService.addProvider({
|
||||||
|
presetId: 'minimax',
|
||||||
|
name: 'MiniMax first-turn race',
|
||||||
|
apiKey: 'key-minimax-first-turn-race',
|
||||||
|
baseUrl: 'https://api.minimaxi.com/anthropic',
|
||||||
|
apiFormat: 'anthropic',
|
||||||
|
models: {
|
||||||
|
main: 'MiniMax-M3',
|
||||||
|
haiku: 'MiniMax-M3',
|
||||||
|
sonnet: 'MiniMax-M3',
|
||||||
|
opus: 'MiniMax-M3',
|
||||||
|
},
|
||||||
|
model1mSupport: {
|
||||||
|
main: true,
|
||||||
|
haiku: true,
|
||||||
|
sonnet: true,
|
||||||
|
opus: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await providerService.activateProvider(provider.id)
|
||||||
|
|
||||||
|
const createRes = await fetch(`${baseUrl}/api/sessions`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ workDir: process.cwd() }),
|
||||||
|
})
|
||||||
|
expect(createRes.status).toBe(201)
|
||||||
|
const { sessionId } = await createRes.json() as { sessionId: string }
|
||||||
|
|
||||||
|
const originalStartSession = conversationService.startSession.bind(conversationService)
|
||||||
|
const startCalls: Array<{
|
||||||
|
sessionId: string
|
||||||
|
options: { providerId?: string | null } | undefined
|
||||||
|
}> = []
|
||||||
|
|
||||||
|
conversationService.startSession = (async function patchedStartSession(
|
||||||
|
sid: string,
|
||||||
|
workDir: string,
|
||||||
|
sdkUrl: string,
|
||||||
|
options?: { permissionMode?: string; model?: string; effort?: string; thinking?: 'enabled' | 'adaptive' | 'disabled'; providerId?: string | null },
|
||||||
|
) {
|
||||||
|
startCalls.push({ sessionId: sid, options })
|
||||||
|
return originalStartSession(sid, workDir, sdkUrl, options)
|
||||||
|
}) as typeof conversationService.startSession
|
||||||
|
|
||||||
|
const messages: any[] = []
|
||||||
|
const ws = new WebSocket(`${wsUrl}/ws/${sessionId}`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
ws.close()
|
||||||
|
reject(new Error(`Timed out waiting for first MiniMax provider turn for session ${sessionId}`))
|
||||||
|
}, 10_000)
|
||||||
|
|
||||||
|
ws.onmessage = (event) => {
|
||||||
|
const msg = JSON.parse(event.data as string)
|
||||||
|
messages.push(msg)
|
||||||
|
|
||||||
|
if (msg.type === 'connected') {
|
||||||
|
ws.send(JSON.stringify({ type: 'prewarm_session' }))
|
||||||
|
ws.send(JSON.stringify({ type: 'user_message', content: 'first turn without provider test' }))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.type === 'error') {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
ws.close()
|
||||||
|
reject(new Error(msg.message))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.type === 'message_complete') {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
ws.close()
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.onerror = () => {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
ws.close()
|
||||||
|
reject(new Error(`WebSocket error for first MiniMax provider turn ${sessionId}`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(startCalls).toHaveLength(1)
|
||||||
|
expect(startCalls[0]).toMatchObject({
|
||||||
|
sessionId,
|
||||||
|
options: {
|
||||||
|
providerId: provider.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(messages.some((msg) => msg.type === 'content_delta')).toBe(true)
|
||||||
|
expect(messages.some((msg) => msg.type === 'message_complete')).toBe(true)
|
||||||
|
expect(messages.some((msg) => msg.type === 'error')).toBe(false)
|
||||||
|
} finally {
|
||||||
|
ws.close()
|
||||||
|
conversationService.startSession = originalStartSession
|
||||||
|
conversationService.stopSession(sessionId)
|
||||||
|
}
|
||||||
|
}, 20_000)
|
||||||
|
|
||||||
it('should pass the active provider id into default desktop sessions', async () => {
|
it('should pass the active provider id into default desktop sessions', async () => {
|
||||||
const providerService = new ProviderService()
|
const providerService = new ProviderService()
|
||||||
const provider = await providerService.addProvider({
|
const provider = await providerService.addProvider({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user