chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import type { ResolvedLLMTarget } from '@/core/llm-manager/llm-routing'
|
||||
import type { CompletionParams } from '@/core/llm-manager/types'
|
||||
import { LLMDuties, LLMProviders } from '@/core/llm-manager/types'
|
||||
import OpenRouterLLMProvider from '@/core/llm-manager/llm-providers/openrouter-llm-provider'
|
||||
|
||||
const openRouterMocks = vi.hoisted(() => {
|
||||
const languageModel = {
|
||||
doGenerate: vi.fn(),
|
||||
doStream: vi.fn()
|
||||
}
|
||||
const chat = vi.fn(() => languageModel)
|
||||
const createOpenRouter = vi.fn(() => ({
|
||||
chat
|
||||
}))
|
||||
|
||||
return {
|
||||
chat,
|
||||
createOpenRouter,
|
||||
languageModel
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@openrouter/ai-sdk-provider', () => ({
|
||||
createOpenRouter: openRouterMocks.createOpenRouter
|
||||
}))
|
||||
|
||||
vi.mock('@/config', () => ({
|
||||
CONFIG_MANAGER: {
|
||||
getProviderAPIKeyEnv: vi.fn(() => null)
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/helpers/log-helper', () => ({
|
||||
LogHelper: {
|
||||
title: vi.fn(),
|
||||
success: vi.fn(),
|
||||
info: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
warning: vi.fn(),
|
||||
error: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
interface ProviderWithPrivateCallOptions {
|
||||
buildCallOptions(
|
||||
prompt: string,
|
||||
completionParams: CompletionParams
|
||||
): Record<string, unknown>
|
||||
}
|
||||
|
||||
function createOpenRouterProvider(): ProviderWithPrivateCallOptions {
|
||||
const target: ResolvedLLMTarget = {
|
||||
provider: LLMProviders.OpenRouter,
|
||||
model: 'qwen/qwen3.7-max',
|
||||
label: 'openrouter/qwen/qwen3.7-max',
|
||||
isLocal: false,
|
||||
isEnabled: true,
|
||||
isResolved: true
|
||||
}
|
||||
|
||||
return new OpenRouterLLMProvider(target) as unknown as ProviderWithPrivateCallOptions
|
||||
}
|
||||
|
||||
function createCompletionParams(
|
||||
data: CompletionParams['data']
|
||||
): CompletionParams {
|
||||
return {
|
||||
dutyType: LLMDuties.ReAct,
|
||||
systemPrompt: 'Plan the next step.',
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
describe('AISDKRemoteLLMProvider', () => {
|
||||
beforeEach(() => {
|
||||
vi.stubEnv('LEON_OPENROUTER_API_KEY', 'test-openrouter-key')
|
||||
})
|
||||
|
||||
it('adds a JSON instruction when structured response format is enabled', () => {
|
||||
const provider = createOpenRouterProvider()
|
||||
const options = provider.buildCallOptions('Choose a tool.', createCompletionParams({
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: { type: 'string' }
|
||||
},
|
||||
required: ['type'],
|
||||
additionalProperties: false
|
||||
}))
|
||||
|
||||
const messages = options['prompt'] as Array<Record<string, unknown>>
|
||||
const systemMessage = messages[0] as Record<string, unknown>
|
||||
|
||||
expect(systemMessage['role']).toBe('system')
|
||||
expect(systemMessage['content']).toContain('JSON')
|
||||
expect(options['responseFormat']).toEqual({
|
||||
type: 'json',
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: { type: 'string' }
|
||||
},
|
||||
required: ['type'],
|
||||
additionalProperties: false
|
||||
},
|
||||
name: 'structured_output'
|
||||
})
|
||||
})
|
||||
|
||||
it('does not add the JSON instruction for plain text calls', () => {
|
||||
const provider = createOpenRouterProvider()
|
||||
const options = provider.buildCallOptions(
|
||||
'Answer normally.',
|
||||
createCompletionParams(null)
|
||||
)
|
||||
|
||||
const messages = options['prompt'] as Array<Record<string, unknown>>
|
||||
const systemMessage = messages[0] as Record<string, unknown>
|
||||
|
||||
expect(systemMessage['content']).toBe('Plan the next step.')
|
||||
expect(options['responseFormat']).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,99 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('@/helpers/log-helper', () => ({
|
||||
LogHelper: {
|
||||
title: vi.fn(),
|
||||
error: vi.fn(),
|
||||
warning: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
describe('External HTTP plugin loader', () => {
|
||||
it('loads plugins from conventional roots and lets profile plugins override global plugins', async () => {
|
||||
const { loadExternalHTTPPlugins } = await import(
|
||||
'@/core/http-server/http-plugins/loader'
|
||||
)
|
||||
const tempRoot = await fs.mkdtemp(
|
||||
path.join(os.tmpdir(), 'leon-http-plugins-')
|
||||
)
|
||||
const globalPluginsRoot = path.join(tempRoot, 'global')
|
||||
const profilePluginsRoot = path.join(tempRoot, 'profile')
|
||||
const globalPlugin = path.join(globalPluginsRoot, 'company-agent')
|
||||
const profilePlugin = path.join(profilePluginsRoot, 'company-agent')
|
||||
|
||||
await fs.mkdir(globalPlugin, { recursive: true })
|
||||
await fs.mkdir(profilePlugin, { recursive: true })
|
||||
await fs.writeFile(
|
||||
path.join(globalPlugin, 'plugin.yml'),
|
||||
[
|
||||
'id: company_agent',
|
||||
'name: Company Agent',
|
||||
'version: 1.0.0',
|
||||
'description: Global company integration',
|
||||
'entry: index.mjs'
|
||||
].join('\n')
|
||||
)
|
||||
await fs.writeFile(
|
||||
path.join(globalPlugin, 'index.mjs'),
|
||||
[
|
||||
'export default {',
|
||||
' id: "company-agent",',
|
||||
' name: "Company Agent",',
|
||||
' version: "1.0.0",',
|
||||
' description: "Global company integration",',
|
||||
' register: async () => undefined',
|
||||
'}'
|
||||
].join('\n')
|
||||
)
|
||||
await fs.writeFile(
|
||||
path.join(profilePlugin, 'plugin.yml'),
|
||||
[
|
||||
'id: company_agent',
|
||||
'name: Profile Company Agent',
|
||||
'version: 2.0.0',
|
||||
'description: Profile company integration',
|
||||
'entry: index.mjs'
|
||||
].join('\n')
|
||||
)
|
||||
await fs.writeFile(
|
||||
path.join(profilePlugin, 'index.mjs'),
|
||||
[
|
||||
'export const httpPlugin = {',
|
||||
' id: "company-agent",',
|
||||
' name: "Profile Company Agent",',
|
||||
' version: "2.0.0",',
|
||||
' description: "Profile company integration",',
|
||||
' register: async () => undefined',
|
||||
'}'
|
||||
].join('\n')
|
||||
)
|
||||
|
||||
const plugins = await loadExternalHTTPPlugins([
|
||||
{
|
||||
scope: 'global',
|
||||
directory: globalPluginsRoot
|
||||
},
|
||||
{
|
||||
scope: 'profile',
|
||||
directory: profilePluginsRoot
|
||||
}
|
||||
])
|
||||
|
||||
expect(plugins).toHaveLength(1)
|
||||
expect(plugins[0]).toMatchObject({
|
||||
source: 'profile',
|
||||
path: profilePlugin,
|
||||
definition: {
|
||||
id: 'company-agent',
|
||||
name: 'Profile Company Agent',
|
||||
version: '2.0.0',
|
||||
description: 'Profile company integration'
|
||||
}
|
||||
})
|
||||
|
||||
await fs.rm(tempRoot, { recursive: true, force: true })
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,165 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import MiniMaxLLMProvider from '@/core/llm-manager/llm-providers/minimax-llm-provider'
|
||||
import type { ResolvedLLMTarget } from '@/core/llm-manager/llm-routing'
|
||||
import type { CompletionParams } from '@/core/llm-manager/types'
|
||||
import { LLMDuties, LLMProviders } from '@/core/llm-manager/types'
|
||||
|
||||
vi.mock('@/config', () => ({
|
||||
CONFIG_MANAGER: {
|
||||
getProviderAPIKeyEnv: vi.fn(() => null)
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/helpers/log-helper', () => ({
|
||||
LogHelper: {
|
||||
title: vi.fn(),
|
||||
success: vi.fn(),
|
||||
info: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
warning: vi.fn(),
|
||||
error: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
interface ProviderWithPrivateCallOptions {
|
||||
buildCallOptions(
|
||||
prompt: string,
|
||||
completionParams: CompletionParams
|
||||
): Record<string, unknown>
|
||||
}
|
||||
|
||||
type TestProvider = MiniMaxLLMProvider & ProviderWithPrivateCallOptions
|
||||
|
||||
function createProvider(model: string): TestProvider {
|
||||
const target: ResolvedLLMTarget = {
|
||||
provider: LLMProviders.MiniMax,
|
||||
model,
|
||||
label: `minimax/${model}`,
|
||||
isLocal: false,
|
||||
isEnabled: true,
|
||||
isResolved: true
|
||||
}
|
||||
|
||||
return new MiniMaxLLMProvider(target) as TestProvider
|
||||
}
|
||||
|
||||
function createCompletionParams(
|
||||
overrides: Partial<CompletionParams> = {}
|
||||
): CompletionParams {
|
||||
return {
|
||||
dutyType: LLMDuties.ReAct,
|
||||
systemPrompt: 'Plan the next step.',
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
describe('MiniMaxLLMProvider', () => {
|
||||
beforeEach(() => {
|
||||
vi.stubEnv('LEON_MINIMAX_API_KEY', 'test-minimax-key')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
it('uses adaptive thinking and separated reasoning for MiniMax-M3', () => {
|
||||
const provider = createProvider('MiniMax-M3')
|
||||
const options = provider.buildCallOptions(
|
||||
'Answer normally.',
|
||||
createCompletionParams({ reasoningMode: 'on' })
|
||||
)
|
||||
|
||||
expect(options['providerOptions']).toEqual({
|
||||
minimax: {
|
||||
reasoning_split: true,
|
||||
thinking: { type: 'adaptive' }
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('disables MiniMax-M3 thinking when requested', () => {
|
||||
const provider = createProvider('MiniMax-M3')
|
||||
const options = provider.buildCallOptions(
|
||||
'Answer directly.',
|
||||
createCompletionParams({ reasoningMode: 'off' })
|
||||
)
|
||||
|
||||
expect(options['providerOptions']).toEqual({
|
||||
minimax: {
|
||||
reasoning_split: true,
|
||||
thinking: { type: 'disabled' }
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps MiniMax-M2.7 thinking enabled', () => {
|
||||
const provider = createProvider('MiniMax-M2.7')
|
||||
const options = provider.buildCallOptions(
|
||||
'Answer directly.',
|
||||
createCompletionParams({ reasoningMode: 'off' })
|
||||
)
|
||||
|
||||
expect(options['providerOptions']).toEqual({
|
||||
minimax: {
|
||||
reasoning_split: true
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('sends the documented request fields to the OpenAI-compatible endpoint', async () => {
|
||||
const fetchMock = vi.fn(
|
||||
async (input: string | URL | Request, init?: RequestInit) => {
|
||||
void input
|
||||
void init
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
id: 'chatcmpl-test',
|
||||
created: 0,
|
||||
model: 'MiniMax-M3',
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: 'Done.'
|
||||
},
|
||||
finish_reason: 'stop'
|
||||
}
|
||||
],
|
||||
usage: {
|
||||
prompt_tokens: 1,
|
||||
completion_tokens: 1,
|
||||
total_tokens: 2
|
||||
}
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
vi.stubGlobal('fetch', fetchMock)
|
||||
const provider = createProvider('MiniMax-M3')
|
||||
|
||||
await provider.runChatCompletion(
|
||||
'Answer directly.',
|
||||
createCompletionParams({ reasoningMode: 'off' })
|
||||
)
|
||||
|
||||
const [requestURL, requestInit] = fetchMock.mock.calls[0]!
|
||||
const requestBody = JSON.parse(String(requestInit?.body)) as Record<
|
||||
string,
|
||||
unknown
|
||||
>
|
||||
|
||||
expect(String(requestURL)).toBe(
|
||||
'https://api.minimax.io/v1/chat/completions'
|
||||
)
|
||||
expect(requestBody['thinking']).toEqual({ type: 'disabled' })
|
||||
expect(requestBody['reasoning_split']).toBe(true)
|
||||
expect(requestBody['reasoning_effort']).toBeUndefined()
|
||||
})
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user