mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-11 08:19:01 +08:00
Merge branch 'feat/agents-new' of github.com:CherryHQ/cherry-studio into feat/agents-new
This commit is contained in:
commit
56580e3fac
@ -10,7 +10,9 @@
|
|||||||
|
|
||||||
import Anthropic from '@anthropic-ai/sdk'
|
import Anthropic from '@anthropic-ai/sdk'
|
||||||
import { TextBlockParam } from '@anthropic-ai/sdk/resources'
|
import { TextBlockParam } from '@anthropic-ai/sdk/resources'
|
||||||
|
import { loggerService } from '@logger'
|
||||||
import { Provider } from '@types'
|
import { Provider } from '@types'
|
||||||
|
const logger = loggerService.withContext('anthropic-sdk')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates and configures an Anthropic SDK client based on the provider configuration.
|
* Creates and configures an Anthropic SDK client based on the provider configuration.
|
||||||
@ -70,10 +72,16 @@ export function getSdkClient(provider: Provider, oauthToken?: string | null): An
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const baseURL =
|
||||||
|
provider.type === 'anthropic'
|
||||||
|
? provider.apiHost
|
||||||
|
: (provider.anthropicApiHost && provider.anthropicApiHost.trim()) || provider.apiHost
|
||||||
|
|
||||||
|
logger.debug('Anthropic API baseURL', { baseURL })
|
||||||
return new Anthropic({
|
return new Anthropic({
|
||||||
apiKey: provider.apiKey,
|
apiKey: provider.apiKey,
|
||||||
authToken: provider.apiKey,
|
authToken: provider.apiKey,
|
||||||
baseURL: provider.apiHost,
|
baseURL,
|
||||||
dangerouslyAllowBrowser: true,
|
dangerouslyAllowBrowser: true,
|
||||||
defaultHeaders: {
|
defaultHeaders: {
|
||||||
'anthropic-beta': 'output-128k-2025-02-19',
|
'anthropic-beta': 'output-128k-2025-02-19',
|
||||||
|
|||||||
@ -2,7 +2,8 @@ import { MessageCreateParams } from '@anthropic-ai/sdk/resources'
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import express, { Request, Response } from 'express'
|
import express, { Request, Response } from 'express'
|
||||||
|
|
||||||
import { messagesService } from '../services/messages'
|
import { Provider } from '../../../renderer/src/types/provider'
|
||||||
|
import { MessagesService, messagesService } from '../services/messages'
|
||||||
import { getProviderById, validateModelId } from '../utils'
|
import { getProviderById, validateModelId } from '../utils'
|
||||||
|
|
||||||
const logger = loggerService.withContext('ApiServerMessagesRoutes')
|
const logger = loggerService.withContext('ApiServerMessagesRoutes')
|
||||||
@ -33,9 +34,8 @@ async function validateRequestBody(req: Request): Promise<{ valid: boolean; erro
|
|||||||
async function handleStreamingResponse(
|
async function handleStreamingResponse(
|
||||||
res: Response,
|
res: Response,
|
||||||
request: MessageCreateParams,
|
request: MessageCreateParams,
|
||||||
provider: any,
|
provider: Provider,
|
||||||
messagesService: any,
|
messagesService: MessagesService
|
||||||
logger: any
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
res.setHeader('Content-Type', 'text/event-stream; charset=utf-8')
|
res.setHeader('Content-Type', 'text/event-stream; charset=utf-8')
|
||||||
res.setHeader('Cache-Control', 'no-cache, no-transform')
|
res.setHeader('Cache-Control', 'no-cache, no-transform')
|
||||||
@ -80,7 +80,7 @@ async function handleStreamingResponse(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleErrorResponse(res: Response, error: any, logger: any): Response {
|
function handleErrorResponse(res: Response, error: any): Response {
|
||||||
logger.error('Message processing error', { error })
|
logger.error('Message processing error', { error })
|
||||||
|
|
||||||
let statusCode = 500
|
let statusCode = 500
|
||||||
@ -133,7 +133,7 @@ function handleErrorResponse(res: Response, error: any, logger: any): Response {
|
|||||||
async function processMessageRequest(
|
async function processMessageRequest(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
provider: any,
|
provider: Provider,
|
||||||
modelId?: string
|
modelId?: string
|
||||||
): Promise<Response | void> {
|
): Promise<Response | void> {
|
||||||
try {
|
try {
|
||||||
@ -144,17 +144,6 @@ async function processMessageRequest(
|
|||||||
request.model = modelId
|
request.model = modelId
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure provider is Anthropic type
|
|
||||||
if (provider.type !== 'anthropic') {
|
|
||||||
return res.status(400).json({
|
|
||||||
type: 'error',
|
|
||||||
error: {
|
|
||||||
type: 'invalid_request_error',
|
|
||||||
message: `Invalid provider type '${provider.type}' for messages endpoint. Expected 'anthropic' provider.`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate request
|
// Validate request
|
||||||
const validation = messagesService.validateRequest(request)
|
const validation = messagesService.validateRequest(request)
|
||||||
if (!validation.isValid) {
|
if (!validation.isValid) {
|
||||||
@ -167,9 +156,14 @@ async function processMessageRequest(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.silly('Processing message request', {
|
||||||
|
request,
|
||||||
|
provider: provider.id
|
||||||
|
})
|
||||||
|
|
||||||
// Handle streaming
|
// Handle streaming
|
||||||
if (request.stream) {
|
if (request.stream) {
|
||||||
await handleStreamingResponse(res, request, provider, messagesService, logger)
|
await handleStreamingResponse(res, request, provider, messagesService)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,7 +171,7 @@ async function processMessageRequest(
|
|||||||
const response = await messagesService.processMessage(request, provider)
|
const response = await messagesService.processMessage(request, provider)
|
||||||
return res.json(response)
|
return res.json(response)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return handleErrorResponse(res, error, logger)
|
return handleErrorResponse(res, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,7 +331,7 @@ router.post('/', async (req: Request, res: Response) => {
|
|||||||
// Use shared processing function
|
// Use shared processing function
|
||||||
return await processMessageRequest(req, res, provider, modelId)
|
return await processMessageRequest(req, res, provider, modelId)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return handleErrorResponse(res, error, logger)
|
return handleErrorResponse(res, error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -492,7 +486,7 @@ providerRouter.post('/', async (req: Request, res: Response) => {
|
|||||||
// Use shared processing function (no modelId override needed)
|
// Use shared processing function (no modelId override needed)
|
||||||
return await processMessageRequest(req, res, provider)
|
return await processMessageRequest(req, res, provider)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return handleErrorResponse(res, error, logger)
|
return handleErrorResponse(res, error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -13,14 +13,24 @@ export class ModelsService {
|
|||||||
try {
|
try {
|
||||||
logger.debug('Getting available models from providers', { filter })
|
logger.debug('Getting available models from providers', { filter })
|
||||||
|
|
||||||
const models = await listAllAvailableModels()
|
let providers = await getAvailableProviders()
|
||||||
const providers = await getAvailableProviders()
|
|
||||||
|
|
||||||
|
if (filter.providerType === 'anthropic') {
|
||||||
|
providers = providers.filter(
|
||||||
|
(p) => p.type === 'anthropic' || (p.anthropicApiHost !== undefined && p.anthropicApiHost.trim() !== '')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const models = await listAllAvailableModels(providers)
|
||||||
// Use Map to deduplicate models by their full ID (provider:model_id)
|
// Use Map to deduplicate models by their full ID (provider:model_id)
|
||||||
const uniqueModels = new Map<string, ApiModel>()
|
const uniqueModels = new Map<string, ApiModel>()
|
||||||
|
|
||||||
for (const model of models) {
|
for (const model of models) {
|
||||||
const openAIModel = transformModelToOpenAI(model, providers)
|
const provider = providers.find((p) => p.id === model.provider)
|
||||||
|
if (!provider || (provider.isAnthropicModel && !provider.isAnthropicModel(model))) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const openAIModel = transformModelToOpenAI(model, provider)
|
||||||
const fullModelId = openAIModel.id // This is already in format "provider:model_id"
|
const fullModelId = openAIModel.id // This is already in format "provider:model_id"
|
||||||
|
|
||||||
// Only add if not already present (first occurrence wins)
|
// Only add if not already present (first occurrence wins)
|
||||||
@ -32,16 +42,6 @@ export class ModelsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let modelData = Array.from(uniqueModels.values())
|
let modelData = Array.from(uniqueModels.values())
|
||||||
if (filter.providerType) {
|
|
||||||
// Apply filters
|
|
||||||
const providerType = filter.providerType
|
|
||||||
modelData = modelData.filter((model) => {
|
|
||||||
// Find the provider for this model and check its type
|
|
||||||
return model.provider_type === providerType
|
|
||||||
})
|
|
||||||
logger.debug(`Filtered by provider type '${providerType}': ${modelData.length} models`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const total = modelData.length
|
const total = modelData.length
|
||||||
|
|
||||||
// Apply pagination
|
// Apply pagination
|
||||||
|
|||||||
@ -47,9 +47,11 @@ export async function getAvailableProviders(): Promise<Provider[]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listAllAvailableModels(): Promise<Model[]> {
|
export async function listAllAvailableModels(providers?: Provider[]): Promise<Model[]> {
|
||||||
try {
|
try {
|
||||||
const providers = await getAvailableProviders()
|
if (!providers) {
|
||||||
|
providers = await getAvailableProviders()
|
||||||
|
}
|
||||||
return providers.map((p: Provider) => p.models || []).flat()
|
return providers.map((p: Provider) => p.models || []).flat()
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logger.error('Failed to list available models', { error })
|
logger.error('Failed to list available models', { error })
|
||||||
@ -107,9 +109,12 @@ export interface ModelValidationError {
|
|||||||
code: string
|
code: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function validateModelId(
|
export async function validateModelId(model: string): Promise<{
|
||||||
model: string
|
valid: boolean
|
||||||
): Promise<{ valid: boolean; error?: ModelValidationError; provider?: Provider; modelId?: string }> {
|
error?: ModelValidationError
|
||||||
|
provider?: Provider
|
||||||
|
modelId?: string
|
||||||
|
}> {
|
||||||
try {
|
try {
|
||||||
if (!model || typeof model !== 'string') {
|
if (!model || typeof model !== 'string') {
|
||||||
return {
|
return {
|
||||||
@ -192,8 +197,7 @@ export async function validateModelId(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformModelToOpenAI(model: Model, providers: Provider[]): ApiModel {
|
export function transformModelToOpenAI(model: Model, provider?: Provider): ApiModel {
|
||||||
const provider = providers.find((p) => p.id === model.provider)
|
|
||||||
const providerDisplayName = provider?.name
|
const providerDisplayName = provider?.name
|
||||||
return {
|
return {
|
||||||
id: `${model.provider}:${model.id}`,
|
id: `${model.provider}:${model.id}`,
|
||||||
@ -268,7 +272,10 @@ export function validateProvider(provider: Provider): boolean {
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logger.error('Error validating provider', { error, providerId: provider?.id })
|
logger.error('Error validating provider', {
|
||||||
|
error,
|
||||||
|
providerId: provider?.id
|
||||||
|
})
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { type Client, createClient } from '@libsql/client'
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { mcpApiService } from '@main/apiServer/services/mcp'
|
import { mcpApiService } from '@main/apiServer/services/mcp'
|
||||||
import { ModelValidationError, validateModelId } from '@main/apiServer/utils'
|
import { ModelValidationError, validateModelId } from '@main/apiServer/utils'
|
||||||
import { AgentType, MCPTool, objectKeys, Provider, Tool } from '@types'
|
import { AgentType, MCPTool, objectKeys, SlashCommand, Tool } from '@types'
|
||||||
import { drizzle, type LibSQLDatabase } from 'drizzle-orm/libsql'
|
import { drizzle, type LibSQLDatabase } from 'drizzle-orm/libsql'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
@ -11,6 +11,7 @@ import { MigrationService } from './database/MigrationService'
|
|||||||
import * as schema from './database/schema'
|
import * as schema from './database/schema'
|
||||||
import { dbPath } from './drizzle.config'
|
import { dbPath } from './drizzle.config'
|
||||||
import { AgentModelField, AgentModelValidationError } from './errors'
|
import { AgentModelField, AgentModelValidationError } from './errors'
|
||||||
|
import { builtinSlashCommands } from './services/claudecode/commands'
|
||||||
import { builtinTools } from './services/claudecode/tools'
|
import { builtinTools } from './services/claudecode/tools'
|
||||||
|
|
||||||
const logger = loggerService.withContext('BaseService')
|
const logger = loggerService.withContext('BaseService')
|
||||||
@ -76,6 +77,13 @@ export abstract class BaseService {
|
|||||||
return tools
|
return tools
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async listSlashCommands(agentType: AgentType): Promise<SlashCommand[]> {
|
||||||
|
if (agentType === 'claude-code') {
|
||||||
|
return builtinSlashCommands
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
private static async performInitialization(): Promise<void> {
|
private static async performInitialization(): Promise<void> {
|
||||||
const maxRetries = 3
|
const maxRetries = 3
|
||||||
let lastError: Error
|
let lastError: Error
|
||||||
@ -298,23 +306,6 @@ export abstract class BaseService {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// different agent types may have different provider requirements
|
|
||||||
const agentTypeProviderRequirements: Record<AgentType, Provider['type']> = {
|
|
||||||
'claude-code': 'anthropic'
|
|
||||||
}
|
|
||||||
for (const [ak, pk] of Object.entries(agentTypeProviderRequirements)) {
|
|
||||||
if (agentType === ak && validation.provider.type !== pk) {
|
|
||||||
throw new AgentModelValidationError(
|
|
||||||
{ agentType, field, model: modelValue },
|
|
||||||
{
|
|
||||||
type: 'unsupported_provider_type',
|
|
||||||
message: `Provider type '${validation.provider.type}' is not supported for agent type '${agentType}'. Expected '${pk}'`,
|
|
||||||
code: 'unsupported_provider_type'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -110,6 +110,7 @@ export class SessionService extends BaseService {
|
|||||||
|
|
||||||
const session = this.deserializeJsonFields(result[0]) as GetAgentSessionResponse
|
const session = this.deserializeJsonFields(result[0]) as GetAgentSessionResponse
|
||||||
session.tools = await this.listMcpTools(session.agent_type, session.mcps)
|
session.tools = await this.listMcpTools(session.agent_type, session.mcps)
|
||||||
|
session.slash_commands = await this.listSlashCommands(session.agent_type)
|
||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
25
src/main/services/agents/services/claudecode/commands.ts
Normal file
25
src/main/services/agents/services/claudecode/commands.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { SlashCommand } from '@types'
|
||||||
|
|
||||||
|
export const builtinSlashCommands: SlashCommand[] = [
|
||||||
|
{ command: '/add-dir', description: 'Add additional working directories' },
|
||||||
|
{ command: '/agents', description: 'Manage custom AI subagents for specialized tasks' },
|
||||||
|
{ command: '/bug', description: 'Report bugs (sends conversation to Anthropic)' },
|
||||||
|
{ command: '/clear', description: 'Clear conversation history' },
|
||||||
|
{ command: '/compact', description: 'Compact conversation with optional focus instructions' },
|
||||||
|
{ command: '/config', description: 'View/modify configuration' },
|
||||||
|
{ command: '/cost', description: 'Show token usage statistics' },
|
||||||
|
{ command: '/doctor', description: 'Checks the health of your Claude Code installation' },
|
||||||
|
{ command: '/help', description: 'Get usage help' },
|
||||||
|
{ command: '/init', description: 'Initialize project with CLAUDE.md guide' },
|
||||||
|
{ command: '/login', description: 'Switch Anthropic accounts' },
|
||||||
|
{ command: '/logout', description: 'Sign out from your Anthropic account' },
|
||||||
|
{ command: '/mcp', description: 'Manage MCP server connections and OAuth authentication' },
|
||||||
|
{ command: '/memory', description: 'Edit CLAUDE.md memory files' },
|
||||||
|
{ command: '/model', description: 'Select or change the AI model' },
|
||||||
|
{ command: '/permissions', description: 'View or update permissions' },
|
||||||
|
{ command: '/pr_comments', description: 'View pull request comments' },
|
||||||
|
{ command: '/review', description: 'Request code review' },
|
||||||
|
{ command: '/status', description: 'View account and system statuses' },
|
||||||
|
{ command: '/terminal-setup', description: 'Install Shift+Enter key binding for newlines (iTerm2 and VSCode only)' },
|
||||||
|
{ command: '/vim', description: 'Enter vim mode for alternating insert and command modes' }
|
||||||
|
]
|
||||||
@ -60,7 +60,15 @@ class ClaudeCodeService implements AgentServiceInterface {
|
|||||||
})
|
})
|
||||||
return aiStream
|
return aiStream
|
||||||
}
|
}
|
||||||
if (modelInfo.provider?.type !== 'anthropic' || modelInfo.provider.apiKey === '') {
|
if (
|
||||||
|
(modelInfo.provider?.type !== 'anthropic' &&
|
||||||
|
(modelInfo.provider?.anthropicApiHost === undefined || modelInfo.provider.anthropicApiHost.trim() === '')) ||
|
||||||
|
modelInfo.provider.apiKey === ''
|
||||||
|
) {
|
||||||
|
logger.error('Anthropic provider configuration is missing', {
|
||||||
|
modelInfo
|
||||||
|
})
|
||||||
|
|
||||||
aiStream.emit('data', {
|
aiStream.emit('data', {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
error: new Error(`Invalid provider type '${modelInfo.provider?.type}'. Expected 'anthropic' provider type.`)
|
error: new Error(`Invalid provider type '${modelInfo.provider?.type}'. Expected 'anthropic' provider type.`)
|
||||||
|
|||||||
@ -79,9 +79,37 @@ function handleSpecialProviders(model: Model, provider: Provider): Provider {
|
|||||||
/**
|
/**
|
||||||
* 格式化provider的API Host
|
* 格式化provider的API Host
|
||||||
*/
|
*/
|
||||||
|
function formatAnthropicApiHost(host: string): string {
|
||||||
|
const trimmedHost = host?.trim()
|
||||||
|
|
||||||
|
if (!trimmedHost) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmedHost.endsWith('/')) {
|
||||||
|
return trimmedHost
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmedHost.endsWith('/v1')) {
|
||||||
|
return `${trimmedHost}/`
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatApiHost(trimmedHost)
|
||||||
|
}
|
||||||
|
|
||||||
function formatProviderApiHost(provider: Provider): Provider {
|
function formatProviderApiHost(provider: Provider): Provider {
|
||||||
const formatted = { ...provider }
|
const formatted = { ...provider }
|
||||||
if (formatted.type === 'gemini') {
|
if (formatted.anthropicApiHost) {
|
||||||
|
formatted.anthropicApiHost = formatAnthropicApiHost(formatted.anthropicApiHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formatted.type === 'anthropic') {
|
||||||
|
const baseHost = formatted.anthropicApiHost || formatted.apiHost
|
||||||
|
formatted.apiHost = formatAnthropicApiHost(baseHost)
|
||||||
|
if (!formatted.anthropicApiHost) {
|
||||||
|
formatted.anthropicApiHost = formatted.apiHost
|
||||||
|
}
|
||||||
|
} else if (formatted.type === 'gemini') {
|
||||||
formatted.apiHost = formatApiHost(formatted.apiHost, 'v1beta')
|
formatted.apiHost = formatApiHost(formatted.apiHost, 'v1beta')
|
||||||
} else {
|
} else {
|
||||||
formatted.apiHost = formatApiHost(formatted.apiHost)
|
formatted.apiHost = formatApiHost(formatted.apiHost)
|
||||||
|
|||||||
@ -20,7 +20,7 @@ export const ApiModelLabel: React.FC<ModelLabelProps> = ({ model, className, cla
|
|||||||
<Avatar src={model ? getModelLogo(model.id) : undefined} className={cn('h-4 w-4', classNames?.avatar)} />
|
<Avatar src={model ? getModelLogo(model.id) : undefined} className={cn('h-4 w-4', classNames?.avatar)} />
|
||||||
<span className={classNames?.modelName}>{model?.name}</span>
|
<span className={classNames?.modelName}>{model?.name}</span>
|
||||||
<span className={classNames?.divider}> | </span>
|
<span className={classNames?.divider}> | </span>
|
||||||
<span className={classNames?.providerName}>{model?.provider_name}</span>
|
<span className={classNames?.providerName}>{model?.provider}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,6 +56,7 @@ import ZhipuProviderLogo from '@renderer/assets/images/providers/zhipu.png'
|
|||||||
import {
|
import {
|
||||||
AtLeast,
|
AtLeast,
|
||||||
isSystemProvider,
|
isSystemProvider,
|
||||||
|
Model,
|
||||||
OpenAIServiceTiers,
|
OpenAIServiceTiers,
|
||||||
Provider,
|
Provider,
|
||||||
ProviderType,
|
ProviderType,
|
||||||
@ -104,6 +105,8 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
|
|||||||
type: 'openai',
|
type: 'openai',
|
||||||
apiKey: '',
|
apiKey: '',
|
||||||
apiHost: 'https://aihubmix.com',
|
apiHost: 'https://aihubmix.com',
|
||||||
|
anthropicApiHost: 'https://aihubmix.com/anthropic',
|
||||||
|
isAnthropicModel: (m: Model) => m.id.includes('claude'),
|
||||||
models: SYSTEM_MODELS.aihubmix,
|
models: SYSTEM_MODELS.aihubmix,
|
||||||
isSystem: true,
|
isSystem: true,
|
||||||
enabled: false
|
enabled: false
|
||||||
@ -124,6 +127,7 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
|
|||||||
type: 'openai',
|
type: 'openai',
|
||||||
apiKey: '',
|
apiKey: '',
|
||||||
apiHost: 'https://open.bigmodel.cn/api/paas/v4/',
|
apiHost: 'https://open.bigmodel.cn/api/paas/v4/',
|
||||||
|
anthropicApiHost: 'https://open.bigmodel.cn/api/anthropic',
|
||||||
models: SYSTEM_MODELS.zhipu,
|
models: SYSTEM_MODELS.zhipu,
|
||||||
isSystem: true,
|
isSystem: true,
|
||||||
enabled: false
|
enabled: false
|
||||||
@ -134,6 +138,7 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
|
|||||||
type: 'openai',
|
type: 'openai',
|
||||||
apiKey: '',
|
apiKey: '',
|
||||||
apiHost: 'https://api.deepseek.com',
|
apiHost: 'https://api.deepseek.com',
|
||||||
|
anthropicApiHost: 'https://api.deepseek.com/anthropic',
|
||||||
models: SYSTEM_MODELS.deepseek,
|
models: SYSTEM_MODELS.deepseek,
|
||||||
isSystem: true,
|
isSystem: true,
|
||||||
enabled: false
|
enabled: false
|
||||||
@ -379,6 +384,7 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
|
|||||||
type: 'openai',
|
type: 'openai',
|
||||||
apiKey: '',
|
apiKey: '',
|
||||||
apiHost: 'https://api.moonshot.cn',
|
apiHost: 'https://api.moonshot.cn',
|
||||||
|
anthropicApiHost: 'https://api.moonshot.cn/anthropic',
|
||||||
models: SYSTEM_MODELS.moonshot,
|
models: SYSTEM_MODELS.moonshot,
|
||||||
isSystem: true,
|
isSystem: true,
|
||||||
enabled: false
|
enabled: false
|
||||||
@ -399,6 +405,7 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
|
|||||||
type: 'openai',
|
type: 'openai',
|
||||||
apiKey: '',
|
apiKey: '',
|
||||||
apiHost: 'https://dashscope.aliyuncs.com/compatible-mode/v1/',
|
apiHost: 'https://dashscope.aliyuncs.com/compatible-mode/v1/',
|
||||||
|
anthropicApiHost: 'https://dashscope.aliyuncs.com/api/v2/apps/claude-code-proxy',
|
||||||
models: SYSTEM_MODELS.dashscope,
|
models: SYSTEM_MODELS.dashscope,
|
||||||
isSystem: true,
|
isSystem: true,
|
||||||
enabled: false
|
enabled: false
|
||||||
@ -539,6 +546,7 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
|
|||||||
type: 'openai',
|
type: 'openai',
|
||||||
apiKey: '',
|
apiKey: '',
|
||||||
apiHost: 'https://api-inference.modelscope.cn/v1/',
|
apiHost: 'https://api-inference.modelscope.cn/v1/',
|
||||||
|
anthropicApiHost: 'https://api-inference.modelscope.cn',
|
||||||
models: SYSTEM_MODELS.modelscope,
|
models: SYSTEM_MODELS.modelscope,
|
||||||
isSystem: true,
|
isSystem: true,
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|||||||
@ -3995,6 +3995,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api_host": "API Host",
|
"api_host": "API Host",
|
||||||
|
"api_host_tooltip": "Override only when your provider requires a custom OpenAI-compatible endpoint.",
|
||||||
|
"api_host_preview": "Preview: {{url}}",
|
||||||
|
"anthropic_api_host": "Anthropic API Host",
|
||||||
|
"anthropic_api_host_tooltip": "Use only when the provider offers a Claude-compatible base URL.",
|
||||||
|
"anthropic_api_host_preview": "Anthropic preview: {{url}}",
|
||||||
|
"anthropic_api_host_tip": "Only configure this when your provider exposes an Anthropic-compatible endpoint. Ending with / ignores v1, ending with # forces use of input address.",
|
||||||
"api_key": {
|
"api_key": {
|
||||||
"label": "API Key",
|
"label": "API Key",
|
||||||
"tip": "Multiple keys separated by commas or spaces"
|
"tip": "Multiple keys separated by commas or spaces"
|
||||||
|
|||||||
@ -3995,6 +3995,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api_host": "API 地址",
|
"api_host": "API 地址",
|
||||||
|
"api_host_tooltip": "仅在服务商需要自定义的 OpenAI 兼容地址时覆盖。",
|
||||||
|
"api_host_preview": "预览:{{url}}",
|
||||||
|
"anthropic_api_host": "Anthropic API 地址",
|
||||||
|
"anthropic_api_host_tooltip": "仅当服务商提供 Claude 兼容的基础地址时填写。",
|
||||||
|
"anthropic_api_host_preview": "Anthropic 预览:{{url}}",
|
||||||
|
"anthropic_api_host_tip": "仅在服务商提供兼容 Anthropic 的地址时填写。以 / 结尾会忽略自动追加的 v1,以 # 结尾则强制使用原始地址。",
|
||||||
"api_key": {
|
"api_key": {
|
||||||
"label": "API 密钥",
|
"label": "API 密钥",
|
||||||
"tip": "多个密钥使用逗号或空格分隔"
|
"tip": "多个密钥使用逗号或空格分隔"
|
||||||
|
|||||||
@ -3995,6 +3995,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api_host": "API 主機地址",
|
"api_host": "API 主機地址",
|
||||||
|
"api_host_tooltip": "僅在服務商需要自訂的 OpenAI 相容端點時才覆蓋。",
|
||||||
|
"api_host_preview": "預覽:{{url}}",
|
||||||
|
"anthropic_api_host": "Anthropic API 主機地址",
|
||||||
|
"anthropic_api_host_tooltip": "僅在服務商提供 Claude 相容的基礎網址時設定。",
|
||||||
|
"anthropic_api_host_preview": "Anthropic 預覽:{{url}}",
|
||||||
|
"anthropic_api_host_tip": "僅在服務商提供與 Anthropic 相容的網址時設定。以 / 結尾會忽略自動附加的 v1,以 # 結尾則強制使用原始地址。",
|
||||||
"api_key": {
|
"api_key": {
|
||||||
"label": "API 金鑰",
|
"label": "API 金鑰",
|
||||||
"tip": "多個金鑰使用逗號或空格分隔"
|
"tip": "多個金鑰使用逗號或空格分隔"
|
||||||
|
|||||||
@ -55,11 +55,14 @@ interface Props {
|
|||||||
providerId: string
|
providerId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ANTHROPIC_COMPATIBLE_PROVIDER_IDS = ['deepseek', 'moonshot', 'zhipu', 'dashscope', 'modelscope', 'aihubmix']
|
||||||
|
|
||||||
const ProviderSetting: FC<Props> = ({ providerId }) => {
|
const ProviderSetting: FC<Props> = ({ providerId }) => {
|
||||||
const { provider, updateProvider, models } = useProvider(providerId)
|
const { provider, updateProvider, models } = useProvider(providerId)
|
||||||
const allProviders = useAllProviders()
|
const allProviders = useAllProviders()
|
||||||
const { updateProviders } = useProviders()
|
const { updateProviders } = useProviders()
|
||||||
const [apiHost, setApiHost] = useState(provider.apiHost)
|
const [apiHost, setApiHost] = useState(provider.apiHost)
|
||||||
|
const [anthropicApiHost, setAnthropicHost] = useState<string | undefined>(provider.anthropicApiHost)
|
||||||
const [apiVersion, setApiVersion] = useState(provider.apiVersion)
|
const [apiVersion, setApiVersion] = useState(provider.apiVersion)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
@ -140,6 +143,17 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onUpdateAnthropicHost = () => {
|
||||||
|
const trimmedHost = anthropicApiHost?.trim()
|
||||||
|
|
||||||
|
if (trimmedHost) {
|
||||||
|
updateProvider({ anthropicApiHost: trimmedHost })
|
||||||
|
setAnthropicHost(trimmedHost)
|
||||||
|
} else {
|
||||||
|
updateProvider({ anthropicApiHost: undefined })
|
||||||
|
setAnthropicHost(undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
const onUpdateApiVersion = () => updateProvider({ apiVersion })
|
const onUpdateApiVersion = () => updateProvider({ apiVersion })
|
||||||
|
|
||||||
const openApiKeyList = async () => {
|
const openApiKeyList = async () => {
|
||||||
@ -245,6 +259,34 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
|||||||
setApiHost(provider.apiHost)
|
setApiHost(provider.apiHost)
|
||||||
}, [provider.apiHost, provider.id])
|
}, [provider.apiHost, provider.id])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setAnthropicHost(provider.anthropicApiHost)
|
||||||
|
}, [provider.anthropicApiHost])
|
||||||
|
|
||||||
|
const canConfigureAnthropicHost = useMemo(() => {
|
||||||
|
return provider.type !== 'anthropic' && ANTHROPIC_COMPATIBLE_PROVIDER_IDS.includes(provider.id)
|
||||||
|
}, [provider])
|
||||||
|
|
||||||
|
const anthropicHostPreview = useMemo(() => {
|
||||||
|
const rawHost = (anthropicApiHost ?? provider.anthropicApiHost)?.trim()
|
||||||
|
if (!rawHost) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/\/messages\/?$/.test(rawHost)) {
|
||||||
|
return rawHost.replace(/\/$/, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
let normalizedHost = rawHost
|
||||||
|
if (/\/v\d+(?:\/)?$/i.test(normalizedHost)) {
|
||||||
|
normalizedHost = normalizedHost.replace(/\/$/, '')
|
||||||
|
} else {
|
||||||
|
normalizedHost = formatApiHost(normalizedHost).replace(/\/$/, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${normalizedHost}/messages`
|
||||||
|
}, [anthropicApiHost, provider.anthropicApiHost])
|
||||||
|
|
||||||
const isAnthropicOAuth = () => provider.id === 'anthropic' && provider.authType === 'oauth'
|
const isAnthropicOAuth = () => provider.id === 'anthropic' && provider.authType === 'oauth'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -351,7 +393,9 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
|||||||
{!isDmxapi && !isAnthropicOAuth() && (
|
{!isDmxapi && !isAnthropicOAuth() && (
|
||||||
<>
|
<>
|
||||||
<SettingSubtitle style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
<SettingSubtitle style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||||
{t('settings.provider.api_host')}
|
<Tooltip title={t('settings.provider.api_host_tooltip')} mouseEnterDelay={0.3}>
|
||||||
|
<span>{t('settings.provider.api_host')}</span>
|
||||||
|
</Tooltip>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
onClick={() => CustomHeaderPopup.show({ provider })}
|
onClick={() => CustomHeaderPopup.show({ provider })}
|
||||||
@ -371,17 +415,53 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Space.Compact>
|
</Space.Compact>
|
||||||
|
|
||||||
{(isOpenAIProvider(provider) || isAnthropicProvider(provider)) && (
|
{(isOpenAIProvider(provider) || isAnthropicProvider(provider)) && (
|
||||||
<SettingHelpTextRow style={{ justifyContent: 'space-between' }}>
|
<SettingHelpTextRow style={{ justifyContent: 'space-between' }}>
|
||||||
<SettingHelpText
|
<SettingHelpText
|
||||||
style={{ marginLeft: 6, marginRight: '1em', whiteSpace: 'break-spaces', wordBreak: 'break-all' }}>
|
style={{ marginLeft: 6, marginRight: '1em', whiteSpace: 'break-spaces', wordBreak: 'break-all' }}>
|
||||||
{hostPreview()}
|
{t('settings.provider.api_host_preview', { url: hostPreview() })}
|
||||||
</SettingHelpText>
|
</SettingHelpText>
|
||||||
<SettingHelpText style={{ minWidth: 'fit-content' }}>
|
<SettingHelpText style={{ minWidth: 'fit-content' }}>
|
||||||
{t('settings.provider.api.url.tip')}
|
{t('settings.provider.api.url.tip')}
|
||||||
</SettingHelpText>
|
</SettingHelpText>
|
||||||
</SettingHelpTextRow>
|
</SettingHelpTextRow>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{canConfigureAnthropicHost && (
|
||||||
|
<>
|
||||||
|
<SettingSubtitle
|
||||||
|
style={{
|
||||||
|
marginTop: 5,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between'
|
||||||
|
}}>
|
||||||
|
<Tooltip title={t('settings.provider.anthropic_api_host_tooltip')} mouseEnterDelay={0.3}>
|
||||||
|
<span>{t('settings.provider.anthropic_api_host')}</span>
|
||||||
|
</Tooltip>
|
||||||
|
</SettingSubtitle>
|
||||||
|
<Space.Compact style={{ width: '100%', marginTop: 5 }}>
|
||||||
|
<Input
|
||||||
|
value={anthropicApiHost ?? ''}
|
||||||
|
placeholder={t('settings.provider.anthropic_api_host')}
|
||||||
|
onChange={(e) => setAnthropicHost(e.target.value)}
|
||||||
|
onBlur={onUpdateAnthropicHost}
|
||||||
|
/>
|
||||||
|
</Space.Compact>
|
||||||
|
<SettingHelpTextRow style={{ justifyContent: 'space-between' }}>
|
||||||
|
<SettingHelpText
|
||||||
|
style={{ marginLeft: 6, marginRight: '1em', whiteSpace: 'break-spaces', wordBreak: 'break-all' }}>
|
||||||
|
{t('settings.provider.anthropic_api_host_preview', {
|
||||||
|
url: anthropicHostPreview || '—'
|
||||||
|
})}
|
||||||
|
</SettingHelpText>
|
||||||
|
<SettingHelpText style={{ minWidth: 'fit-content', whiteSpace: 'normal' }}>
|
||||||
|
{t('settings.provider.anthropic_api_host_tip')}
|
||||||
|
</SettingHelpText>
|
||||||
|
</SettingHelpTextRow>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -65,7 +65,7 @@ const persistedReducer = persistReducer(
|
|||||||
{
|
{
|
||||||
key: 'cherry-studio',
|
key: 'cherry-studio',
|
||||||
storage,
|
storage,
|
||||||
version: 158,
|
version: 159,
|
||||||
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'],
|
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'],
|
||||||
migrate
|
migrate
|
||||||
},
|
},
|
||||||
|
|||||||
@ -51,9 +51,18 @@ const logger = loggerService.withContext('Migrate')
|
|||||||
// remove logo base64 data to reduce the size of the state
|
// remove logo base64 data to reduce the size of the state
|
||||||
function removeMiniAppIconsFromState(state: RootState) {
|
function removeMiniAppIconsFromState(state: RootState) {
|
||||||
if (state.minapps) {
|
if (state.minapps) {
|
||||||
state.minapps.enabled = state.minapps.enabled.map((app) => ({ ...app, logo: undefined }))
|
state.minapps.enabled = state.minapps.enabled.map((app) => ({
|
||||||
state.minapps.disabled = state.minapps.disabled.map((app) => ({ ...app, logo: undefined }))
|
...app,
|
||||||
state.minapps.pinned = state.minapps.pinned.map((app) => ({ ...app, logo: undefined }))
|
logo: undefined
|
||||||
|
}))
|
||||||
|
state.minapps.disabled = state.minapps.disabled.map((app) => ({
|
||||||
|
...app,
|
||||||
|
logo: undefined
|
||||||
|
}))
|
||||||
|
state.minapps.pinned = state.minapps.pinned.map((app) => ({
|
||||||
|
...app,
|
||||||
|
logo: undefined
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +105,10 @@ function updateProvider(state: RootState, id: string, provider: Partial<Provider
|
|||||||
if (state.llm.providers) {
|
if (state.llm.providers) {
|
||||||
const index = state.llm.providers.findIndex((p) => p.id === id)
|
const index = state.llm.providers.findIndex((p) => p.id === id)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
state.llm.providers[index] = { ...state.llm.providers[index], ...provider }
|
state.llm.providers[index] = {
|
||||||
|
...state.llm.providers[index],
|
||||||
|
...provider
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -544,7 +556,10 @@ const migrateConfig = {
|
|||||||
...state.llm,
|
...state.llm,
|
||||||
providers: state.llm.providers.map((provider) => {
|
providers: state.llm.providers.map((provider) => {
|
||||||
if (provider.id === 'azure-openai') {
|
if (provider.id === 'azure-openai') {
|
||||||
provider.models = provider.models.map((model) => ({ ...model, provider: 'azure-openai' }))
|
provider.models = provider.models.map((model) => ({
|
||||||
|
...model,
|
||||||
|
provider: 'azure-openai'
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
return provider
|
return provider
|
||||||
})
|
})
|
||||||
@ -600,7 +615,10 @@ const migrateConfig = {
|
|||||||
runAsyncFunction(async () => {
|
runAsyncFunction(async () => {
|
||||||
const _topic = await db.topics.get(topic.id)
|
const _topic = await db.topics.get(topic.id)
|
||||||
if (_topic) {
|
if (_topic) {
|
||||||
const messages = (_topic?.messages || []).map((message) => ({ ...message, assistantId: assistant.id }))
|
const messages = (_topic?.messages || []).map((message) => ({
|
||||||
|
...message,
|
||||||
|
assistantId: assistant.id
|
||||||
|
}))
|
||||||
db.topics.put({ ..._topic, messages }, topic.id)
|
db.topics.put({ ..._topic, messages }, topic.id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -1717,7 +1735,10 @@ const migrateConfig = {
|
|||||||
cutoffLimit: state.websearch.contentLimit
|
cutoffLimit: state.websearch.contentLimit
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
state.websearch.compressionConfig = { method: 'none', cutoffUnit: 'char' }
|
state.websearch.compressionConfig = {
|
||||||
|
method: 'none',
|
||||||
|
cutoffUnit: 'char'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore eslint-disable-next-line
|
// @ts-ignore eslint-disable-next-line
|
||||||
@ -2502,7 +2523,10 @@ const migrateConfig = {
|
|||||||
const cherryinProvider = state.llm.providers.find((provider) => provider.id === 'cherryin')
|
const cherryinProvider = state.llm.providers.find((provider) => provider.id === 'cherryin')
|
||||||
|
|
||||||
if (cherryinProvider) {
|
if (cherryinProvider) {
|
||||||
updateProvider(state, 'cherryin', { apiHost: 'https://open.cherryin.ai', models: [] })
|
updateProvider(state, 'cherryin', {
|
||||||
|
apiHost: 'https://open.cherryin.ai',
|
||||||
|
models: []
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.llm.defaultModel?.provider === 'cherryin') {
|
if (state.llm.defaultModel?.provider === 'cherryin') {
|
||||||
@ -2571,9 +2595,37 @@ const migrateConfig = {
|
|||||||
return icon === 'agents' ? 'store' : icon
|
return icon === 'agents' ? 'store' : icon
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
state.llm.providers.forEach((provider) => {
|
||||||
|
if (provider.anthropicApiHost) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (provider.id) {
|
||||||
|
case 'deepseek':
|
||||||
|
provider.anthropicApiHost = 'https://api.deepseek.com/anthropic'
|
||||||
|
break
|
||||||
|
case 'moonshot':
|
||||||
|
provider.anthropicApiHost = 'https://api.moonshot.cn/anthropic'
|
||||||
|
break
|
||||||
|
case 'zhipu':
|
||||||
|
provider.anthropicApiHost = 'https://open.bigmodel.cn/api/anthropic'
|
||||||
|
break
|
||||||
|
case 'dashscope':
|
||||||
|
provider.anthropicApiHost = 'https://dashscope.aliyuncs.com/api/v2/apps/claude-code-proxy'
|
||||||
|
break
|
||||||
|
case 'modelscope':
|
||||||
|
provider.anthropicApiHost = 'https://api-inference.modelscope.cn'
|
||||||
|
break
|
||||||
|
case 'aihubmix':
|
||||||
|
provider.anthropicApiHost = 'https://aihubmix.com/anthropic'
|
||||||
|
provider.isAnthropicModel = (m: Model) => m.id.includes('claude')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return state
|
return state
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('migrate 158 error', error as Error)
|
logger.error('migrate 159 error', error as Error)
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,9 +42,19 @@ export const ToolSchema = z.object({
|
|||||||
|
|
||||||
export type Tool = z.infer<typeof ToolSchema>
|
export type Tool = z.infer<typeof ToolSchema>
|
||||||
|
|
||||||
|
export const SlashCommandSchema = z.object({
|
||||||
|
command: z.string(), // e.g. '/status'
|
||||||
|
description: z.string().optional() // e.g. 'Show help information'
|
||||||
|
})
|
||||||
|
|
||||||
|
export type SlashCommand = z.infer<typeof SlashCommandSchema>
|
||||||
|
|
||||||
// ------------------ Agent configuration & base schema ------------------
|
// ------------------ Agent configuration & base schema ------------------
|
||||||
export const AgentConfigurationSchema = z
|
export const AgentConfigurationSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
avatar: z.string().optional(), // URL or path to avatar image
|
||||||
|
slash_commands: z.array(z.string()).optional(), // Array of slash commands to trigger the agent, this is from agent init response
|
||||||
|
|
||||||
// https://docs.claude.com/en/docs/claude-code/sdk/sdk-permissions#mode-specific-behaviors
|
// https://docs.claude.com/en/docs/claude-code/sdk/sdk-permissions#mode-specific-behaviors
|
||||||
permission_mode: PermissionModeSchema.default('default'), // Permission mode, default to 'default'
|
permission_mode: PermissionModeSchema.default('default'), // Permission mode, default to 'default'
|
||||||
max_turns: z.number().default(100) // Maximum number of interaction turns, default to 100
|
max_turns: z.number().default(100) // Maximum number of interaction turns, default to 100
|
||||||
@ -252,7 +262,8 @@ export interface UpdateSessionRequest extends Partial<AgentBase> {}
|
|||||||
|
|
||||||
export const GetAgentSessionResponseSchema = AgentSessionEntitySchema.extend({
|
export const GetAgentSessionResponseSchema = AgentSessionEntitySchema.extend({
|
||||||
tools: z.array(ToolSchema).optional(), // All tools available to the session (including built-in and custom)
|
tools: z.array(ToolSchema).optional(), // All tools available to the session (including built-in and custom)
|
||||||
messages: z.array(AgentSessionMessageEntitySchema).optional() // Messages in the session
|
messages: z.array(AgentSessionMessageEntitySchema).optional(), // Messages in the session
|
||||||
|
slash_commands: z.array(SlashCommandSchema).optional() // Array of slash commands to trigger the agent
|
||||||
})
|
})
|
||||||
|
|
||||||
export type GetAgentSessionResponse = z.infer<typeof GetAgentSessionResponseSchema>
|
export type GetAgentSessionResponse = z.infer<typeof GetAgentSessionResponseSchema>
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import type { FileMetadata } from './file'
|
|||||||
import { KnowledgeBase, KnowledgeReference } from './knowledge'
|
import { KnowledgeBase, KnowledgeReference } from './knowledge'
|
||||||
import { MCPConfigSample, McpServerType } from './mcp'
|
import { MCPConfigSample, McpServerType } from './mcp'
|
||||||
import type { Message } from './newMessage'
|
import type { Message } from './newMessage'
|
||||||
|
import type { ServiceTier } from './provider'
|
||||||
import type { BaseTool, MCPTool } from './tool'
|
import type { BaseTool, MCPTool } from './tool'
|
||||||
|
|
||||||
export * from './agent'
|
export * from './agent'
|
||||||
@ -251,6 +252,8 @@ export type Provider = {
|
|||||||
name: string
|
name: string
|
||||||
apiKey: string
|
apiKey: string
|
||||||
apiHost: string
|
apiHost: string
|
||||||
|
anthropicApiHost?: string
|
||||||
|
isAnthropicModel?: (m: Model) => boolean
|
||||||
apiVersion?: string
|
apiVersion?: string
|
||||||
models: Model[]
|
models: Model[]
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user