diff --git a/package.json b/package.json index cd8805fb8a..4cd6a85908 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "@ant-design/v5-patch-for-react-19": "^1.0.3", "@anthropic-ai/sdk": "^0.41.0", "@anthropic-ai/vertex-sdk": "patch:@anthropic-ai/vertex-sdk@npm%3A0.11.4#~/.yarn/patches/@anthropic-ai-vertex-sdk-npm-0.11.4-c19cb41edb.patch", + "@aws-sdk/client-bedrock-runtime": "^3.840.0", "@aws-sdk/client-s3": "^3.840.0", "@cherrystudio/embedjs": "^0.1.31", "@cherrystudio/embedjs-libsql": "^0.1.31", diff --git a/src/renderer/src/aiCore/clients/ApiClientFactory.ts b/src/renderer/src/aiCore/clients/ApiClientFactory.ts index b92c6cb9f2..e708ab8c42 100644 --- a/src/renderer/src/aiCore/clients/ApiClientFactory.ts +++ b/src/renderer/src/aiCore/clients/ApiClientFactory.ts @@ -3,6 +3,7 @@ import { Provider } from '@renderer/types' import { AihubmixAPIClient } from './AihubmixAPIClient' import { AnthropicAPIClient } from './anthropic/AnthropicAPIClient' +import { AwsBedrockAPIClient } from './aws/AwsBedrockAPIClient' import { BaseApiClient } from './BaseApiClient' import { GeminiAPIClient } from './gemini/GeminiAPIClient' import { VertexAPIClient } from './gemini/VertexAPIClient' @@ -65,6 +66,9 @@ export class ApiClientFactory { case 'anthropic': instance = new AnthropicAPIClient(provider) as BaseApiClient break + case 'aws-bedrock': + instance = new AwsBedrockAPIClient(provider) as BaseApiClient + break default: logger.debug(`Using default OpenAIApiClient for provider: ${provider.id}`) instance = new OpenAIAPIClient(provider) as BaseApiClient diff --git a/src/renderer/src/aiCore/clients/aws/AwsBedrockAPIClient.ts b/src/renderer/src/aiCore/clients/aws/AwsBedrockAPIClient.ts new file mode 100644 index 0000000000..6117dffa18 --- /dev/null +++ b/src/renderer/src/aiCore/clients/aws/AwsBedrockAPIClient.ts @@ -0,0 +1,620 @@ +import { + BedrockRuntimeClient, + ConverseCommand, + ConverseStreamCommand, + InvokeModelCommand +} from '@aws-sdk/client-bedrock-runtime' +import { loggerService } from '@logger' +import { GenericChunk } from '@renderer/aiCore/middleware/schemas' +import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant' +import { + getAwsBedrockAccessKeyId, + getAwsBedrockRegion, + getAwsBedrockSecretAccessKey +} from '@renderer/hooks/useAwsBedrock' +import { estimateTextTokens } from '@renderer/services/TokenService' +import { + GenerateImageParams, + MCPCallToolResponse, + MCPTool, + MCPToolResponse, + Model, + Provider, + ToolCallResponse +} from '@renderer/types' +import { ChunkType, MCPToolCreatedChunk, TextDeltaChunk } from '@renderer/types/chunk' +import { Message } from '@renderer/types/newMessage' +import { + AwsBedrockSdkInstance, + AwsBedrockSdkMessageParam, + AwsBedrockSdkParams, + AwsBedrockSdkRawChunk, + AwsBedrockSdkRawOutput, + AwsBedrockSdkTool, + AwsBedrockSdkToolCall, + SdkModel +} from '@renderer/types/sdk' +import { convertBase64ImageToAwsBedrockFormat } from '@renderer/utils/aws-bedrock-utils' +import { + awsBedrockToolUseToMcpTool, + isEnabledToolUse, + mcpToolCallResponseToAwsBedrockMessage, + mcpToolsToAwsBedrockTools +} from '@renderer/utils/mcp-tools' +import { findImageBlocks } from '@renderer/utils/messageUtils/find' + +import { BaseApiClient } from '../BaseApiClient' +import { RequestTransformer, ResponseChunkTransformer } from '../types' + +const logger = loggerService.withContext('AwsBedrockAPIClient') + +export class AwsBedrockAPIClient extends BaseApiClient< + AwsBedrockSdkInstance, + AwsBedrockSdkParams, + AwsBedrockSdkRawOutput, + AwsBedrockSdkRawChunk, + AwsBedrockSdkMessageParam, + AwsBedrockSdkToolCall, + AwsBedrockSdkTool +> { + constructor(provider: Provider) { + super(provider) + } + + async getSdkInstance(): Promise { + if (this.sdkInstance) { + return this.sdkInstance + } + + const region = getAwsBedrockRegion() + const accessKeyId = getAwsBedrockAccessKeyId() + const secretAccessKey = getAwsBedrockSecretAccessKey() + + if (!region) { + throw new Error('AWS region is required. Please configure AWS-Region in extra headers.') + } + + if (!accessKeyId || !secretAccessKey) { + throw new Error('AWS credentials are required. Please configure AWS-Access-Key-ID and AWS-Secret-Access-Key.') + } + + const client = new BedrockRuntimeClient({ + region, + credentials: { + accessKeyId, + secretAccessKey + } + }) + + this.sdkInstance = { client, region } + return this.sdkInstance + } + + override async createCompletions(payload: AwsBedrockSdkParams): Promise { + const sdk = await this.getSdkInstance() + + // 转换消息格式到AWS SDK原生格式 + const awsMessages = payload.messages.map((msg) => ({ + role: msg.role, + content: msg.content.map((content) => { + if (content.text) { + return { text: content.text } + } + if (content.image) { + return { + image: { + format: content.image.format, + source: content.image.source + } + } + } + if (content.toolResult) { + return { + toolResult: { + toolUseId: content.toolResult.toolUseId, + content: content.toolResult.content, + status: content.toolResult.status + } + } + } + if (content.toolUse) { + return { + toolUse: { + toolUseId: content.toolUse.toolUseId, + name: content.toolUse.name, + input: content.toolUse.input + } + } + } + // 返回符合AWS SDK ContentBlock类型的对象 + return { text: 'Unknown content type' } + }) + })) + + const commonParams = { + modelId: payload.modelId, + messages: awsMessages as any, + system: payload.system ? [{ text: payload.system }] : undefined, + inferenceConfig: { + maxTokens: payload.maxTokens || DEFAULT_MAX_TOKENS, + temperature: payload.temperature || 0.7, + topP: payload.topP || 1 + }, + toolConfig: + payload.tools && payload.tools.length > 0 + ? { + tools: payload.tools + } + : undefined + } + + try { + if (payload.stream) { + const command = new ConverseStreamCommand(commonParams) + const response = await sdk.client.send(command) + // 直接返回AWS Bedrock流式响应的异步迭代器 + return this.createStreamIterator(response) + } else { + const command = new ConverseCommand(commonParams) + const response = await sdk.client.send(command) + return { output: response } + } + } catch (error) { + logger.error('Failed to create completions with AWS Bedrock:', error as Error) + throw error + } + } + + private async *createStreamIterator(response: any): AsyncIterable { + try { + if (response.stream) { + for await (const chunk of response.stream) { + logger.debug('AWS Bedrock chunk received:', chunk) + + // AWS Bedrock的流式响应格式转换为标准格式 + if (chunk.contentBlockDelta?.delta?.text) { + yield { + contentBlockDelta: { + delta: { text: chunk.contentBlockDelta.delta.text } + } + } + } + + if (chunk.messageStart) { + yield { messageStart: chunk.messageStart } + } + + if (chunk.messageStop) { + yield { messageStop: chunk.messageStop } + } + + if (chunk.metadata) { + yield { metadata: chunk.metadata } + } + } + } + } catch (error) { + logger.error('Error in AWS Bedrock stream iterator:', error as Error) + throw error + } + } + + // @ts-ignore sdk未提供 + // eslint-disable-next-line @typescript-eslint/no-unused-vars + override async generateImage(_generateImageParams: GenerateImageParams): Promise { + return [] + } + + override async getEmbeddingDimensions(model?: Model): Promise { + if (!model) { + throw new Error('Model is required for AWS Bedrock embedding dimensions.') + } + + const sdk = await this.getSdkInstance() + + // AWS Bedrock 支持的嵌入模型及其维度 + const embeddingModels: Record = { + 'cohere.embed-english-v3': 1024, + 'cohere.embed-multilingual-v3': 1024, + // Amazon Titan embeddings + 'amazon.titan-embed-text-v1': 1536, + 'amazon.titan-embed-text-v2:0': 1024 + // 可以根据需要添加更多模型 + } + + // 如果是已知的嵌入模型,直接返回维度 + if (embeddingModels[model.id]) { + return embeddingModels[model.id] + } + + // 对于未知模型,尝试实际调用API获取维度 + try { + let requestBody: any + + if (model.id.startsWith('cohere.embed')) { + // Cohere Embed API 格式 + requestBody = { + texts: ['test'], + input_type: 'search_document', + embedding_types: ['float'] + } + } else if (model.id.startsWith('amazon.titan-embed')) { + // Amazon Titan Embed API 格式 + requestBody = { + inputText: 'test' + } + } else { + // 通用格式,大多数嵌入模型都支持 + requestBody = { + inputText: 'test' + } + } + + const command = new InvokeModelCommand({ + modelId: model.id, + body: JSON.stringify(requestBody), + contentType: 'application/json', + accept: 'application/json' + }) + + const response = await sdk.client.send(command) + const responseBody = JSON.parse(new TextDecoder().decode(response.body)) + + // 解析响应获取嵌入维度 + if (responseBody.embeddings && responseBody.embeddings.length > 0) { + // Cohere 格式 + if (responseBody.embeddings[0].values) { + return responseBody.embeddings[0].values.length + } + // 其他可能的格式 + if (Array.isArray(responseBody.embeddings[0])) { + return responseBody.embeddings[0].length + } + } + + if (responseBody.embedding && Array.isArray(responseBody.embedding)) { + // Amazon Titan 格式 + return responseBody.embedding.length + } + + // 如果无法解析,则抛出错误 + throw new Error(`Unable to determine embedding dimensions for model ${model.id}`) + } catch (error) { + logger.error('Failed to get embedding dimensions from AWS Bedrock:', error as Error) + + // 根据模型名称推测维度 + if (model.id.includes('titan')) { + return 1536 // Amazon Titan 默认维度 + } + if (model.id.includes('cohere')) { + return 1024 // Cohere 默认维度 + } + + throw new Error(`Unable to determine embedding dimensions for model ${model.id}: ${(error as Error).message}`) + } + } + + // @ts-ignore sdk未提供 + override async listModels(): Promise { + return [] + } + + public async convertMessageToSdkParam(message: Message): Promise { + const content = await this.getMessageContent(message) + const parts: Array<{ + text?: string + image?: { + format: 'png' | 'jpeg' | 'gif' | 'webp' + source: { + bytes?: Uint8Array + s3Location?: { + uri: string + bucketOwner?: string + } + } + } + }> = [] + + // 添加文本内容 - 只在有非空内容时添加 + if (content && content.trim()) { + parts.push({ text: content }) + } + + // 处理图片内容 + const imageBlocks = findImageBlocks(message) + for (const imageBlock of imageBlocks) { + if (imageBlock.file) { + try { + const image = await window.api.file.base64Image(imageBlock.file.id + imageBlock.file.ext) + const mimeType = image.mime || 'image/png' + const base64Data = image.base64 + + const awsImage = convertBase64ImageToAwsBedrockFormat(base64Data, mimeType) + if (awsImage) { + parts.push({ image: awsImage }) + } else { + // 不支持的格式,转换为文本描述 + parts.push({ text: `[Image: ${mimeType}]` }) + } + } catch (error) { + logger.error('Error processing image:', error as Error) + parts.push({ text: '[Image processing failed]' }) + } + } else if (imageBlock.url && imageBlock.url.startsWith('data:')) { + try { + // 处理base64图片URL + const matches = imageBlock.url.match(/^data:(.+);base64,(.*)$/) + if (matches && matches.length === 3) { + const mimeType = matches[1] + const base64Data = matches[2] + + const awsImage = convertBase64ImageToAwsBedrockFormat(base64Data, mimeType) + if (awsImage) { + parts.push({ image: awsImage }) + } else { + parts.push({ text: `[Image: ${mimeType}]` }) + } + } + } catch (error) { + logger.error('Error processing base64 image:', error as Error) + parts.push({ text: '[Image processing failed]' }) + } + } + } + + // 如果没有任何内容,添加默认文本而不是空文本 + if (parts.length === 0) { + parts.push({ text: 'No content provided' }) + } + + return { + role: message.role === 'system' ? 'user' : message.role, + content: parts + } + } + + getRequestTransformer(): RequestTransformer { + return { + transform: async ( + coreRequest, + assistant, + model, + isRecursiveCall, + recursiveSdkMessages + ): Promise<{ + payload: AwsBedrockSdkParams + messages: AwsBedrockSdkMessageParam[] + metadata: Record + }> => { + const { messages, mcpTools, maxTokens, streamOutput } = coreRequest + // 1. 处理系统消息 + const systemPrompt = assistant.prompt + // 2. 设置工具 + const { tools } = this.setupToolsConfig({ + mcpTools: mcpTools, + model, + enableToolUse: isEnabledToolUse(assistant) + }) + + // 3. 处理消息 + const sdkMessages: AwsBedrockSdkMessageParam[] = [] + if (typeof messages === 'string') { + sdkMessages.push({ role: 'user', content: [{ text: messages }] }) + } else { + for (const message of messages) { + sdkMessages.push(await this.convertMessageToSdkParam(message)) + } + } + + const payload: AwsBedrockSdkParams = { + modelId: model.id, + messages: + isRecursiveCall && recursiveSdkMessages && recursiveSdkMessages.length > 0 + ? recursiveSdkMessages + : sdkMessages, + system: systemPrompt, + maxTokens: maxTokens || DEFAULT_MAX_TOKENS, + temperature: this.getTemperature(assistant, model), + topP: this.getTopP(assistant, model), + stream: streamOutput !== false, + tools: tools.length > 0 ? tools : undefined + } + + const timeout = this.getTimeout(model) + return { payload, messages: sdkMessages, metadata: { timeout } } + } + } + } + + getResponseChunkTransformer(): ResponseChunkTransformer { + return () => { + let hasStartedText = false + let accumulatedJson = '' + const toolCalls: Record = {} + + return { + async transform(rawChunk: AwsBedrockSdkRawChunk, controller: TransformStreamDefaultController) { + logger.silly('Processing AWS Bedrock chunk:', rawChunk) + + // 处理消息开始事件 + if (rawChunk.messageStart) { + controller.enqueue({ + type: ChunkType.TEXT_START + }) + hasStartedText = true + logger.debug('Message started') + } + + // 处理内容块开始事件 - 参考 Anthropic 的 content_block_start 处理 + if (rawChunk.contentBlockStart?.start?.toolUse) { + const toolUse = rawChunk.contentBlockStart.start.toolUse + const blockIndex = rawChunk.contentBlockStart.contentBlockIndex || 0 + toolCalls[blockIndex] = { + id: toolUse.toolUseId, // 设置 id 字段与 toolUseId 相同 + name: toolUse.name, + toolUseId: toolUse.toolUseId, + input: {} + } + logger.debug('Tool use started:', toolUse) + } + + // 处理内容块增量事件 - 参考 Anthropic 的 content_block_delta 处理 + if (rawChunk.contentBlockDelta?.delta?.toolUse?.input) { + const inputDelta = rawChunk.contentBlockDelta.delta.toolUse.input + accumulatedJson += inputDelta + } + + // 处理文本增量 + if (rawChunk.contentBlockDelta?.delta?.text) { + if (!hasStartedText) { + controller.enqueue({ + type: ChunkType.TEXT_START + }) + hasStartedText = true + } + + controller.enqueue({ + type: ChunkType.TEXT_DELTA, + text: rawChunk.contentBlockDelta.delta.text + } as TextDeltaChunk) + } + + // 处理内容块停止事件 - 参考 Anthropic 的 content_block_stop 处理 + if (rawChunk.contentBlockStop) { + const blockIndex = rawChunk.contentBlockStop.contentBlockIndex || 0 + const toolCall = toolCalls[blockIndex] + if (toolCall && accumulatedJson) { + try { + toolCall.input = JSON.parse(accumulatedJson) + controller.enqueue({ + type: ChunkType.MCP_TOOL_CREATED, + tool_calls: [toolCall] + } as MCPToolCreatedChunk) + accumulatedJson = '' + } catch (error) { + logger.error('Error parsing tool call input:', error as Error) + } + } + } + + // 处理消息结束事件 + if (rawChunk.messageStop) { + // 从metadata中提取usage信息 + const usage = rawChunk.metadata?.usage || {} + + controller.enqueue({ + type: ChunkType.LLM_RESPONSE_COMPLETE, + response: { + usage: { + prompt_tokens: usage.inputTokens || 0, + completion_tokens: usage.outputTokens || 0, + total_tokens: (usage.inputTokens || 0) + (usage.outputTokens || 0) + } + } + }) + } + } + } + } + } + + public convertMcpToolsToSdkTools(mcpTools: MCPTool[]): AwsBedrockSdkTool[] { + return mcpToolsToAwsBedrockTools(mcpTools) + } + + convertSdkToolCallToMcp(toolCall: AwsBedrockSdkToolCall, mcpTools: MCPTool[]): MCPTool | undefined { + return awsBedrockToolUseToMcpTool(mcpTools, toolCall) + } + + convertSdkToolCallToMcpToolResponse(toolCall: AwsBedrockSdkToolCall, mcpTool: MCPTool): ToolCallResponse { + return { + id: toolCall.id, + tool: mcpTool, + arguments: toolCall.input || {}, + status: 'pending', + toolCallId: toolCall.id + } + } + + override buildSdkMessages( + currentReqMessages: AwsBedrockSdkMessageParam[], + output: AwsBedrockSdkRawOutput | string | undefined, + toolResults: AwsBedrockSdkMessageParam[] + ): AwsBedrockSdkMessageParam[] { + const messages: AwsBedrockSdkMessageParam[] = [...currentReqMessages] + + if (typeof output === 'string') { + messages.push({ + role: 'assistant', + content: [{ text: output }] + }) + } + + if (toolResults.length > 0) { + messages.push(...toolResults) + } + + return messages + } + + override estimateMessageTokens(message: AwsBedrockSdkMessageParam): number { + if (typeof message.content === 'string') { + return estimateTextTokens(message.content) + } + const content = message.content + if (Array.isArray(content)) { + return content.reduce((total, item) => { + if (item.text) { + return total + estimateTextTokens(item.text) + } + return total + }, 0) + } + return 0 + } + + public convertMcpToolResponseToSdkMessageParam( + mcpToolResponse: MCPToolResponse, + resp: MCPCallToolResponse, + model: Model + ): AwsBedrockSdkMessageParam | undefined { + if ('toolUseId' in mcpToolResponse && mcpToolResponse.toolUseId) { + // 使用专用的转换函数处理 toolUseId 情况 + return mcpToolCallResponseToAwsBedrockMessage(mcpToolResponse, resp, model) + } else if ('toolCallId' in mcpToolResponse && mcpToolResponse.toolCallId) { + return { + role: 'user', + content: [ + { + toolResult: { + toolUseId: mcpToolResponse.toolCallId, + content: resp.content + .map((item) => { + if (item.type === 'text') { + // 确保文本不为空,如果为空则提供默认文本 + return { text: item.text && item.text.trim() ? item.text : 'No text content' } + } + if (item.type === 'image' && item.data) { + const awsImage = convertBase64ImageToAwsBedrockFormat(item.data, item.mimeType) + if (awsImage) { + return { image: awsImage } + } else { + // 如果转换失败,返回描述性文本 + return { text: `[Image: ${item.mimeType || 'unknown format'}]` } + } + } + return { text: JSON.stringify(item) } + }) + .filter((content) => content !== null) + } + } + ] + } + } + return undefined + } + + extractMessagesFromSdkPayload(sdkPayload: AwsBedrockSdkParams): AwsBedrockSdkMessageParam[] { + return sdkPayload.messages || [] + } +} diff --git a/src/renderer/src/aiCore/middleware/core/StreamAdapterMiddleware.ts b/src/renderer/src/aiCore/middleware/core/StreamAdapterMiddleware.ts index 893f891c06..8bb5266319 100644 --- a/src/renderer/src/aiCore/middleware/core/StreamAdapterMiddleware.ts +++ b/src/renderer/src/aiCore/middleware/core/StreamAdapterMiddleware.ts @@ -45,7 +45,7 @@ export const StreamAdapterMiddleware: CompletionsMiddleware = } else if (result.rawOutput) { // 非流式输出,强行变为可读流 const whatwgReadableStream: ReadableStream = createSingleChunkReadableStream( - result.rawOutput + result.rawOutput as SdkRawChunk ) return { ...result, diff --git a/src/renderer/src/assets/images/providers/aws-bedrock.png b/src/renderer/src/assets/images/providers/aws-bedrock.png new file mode 100644 index 0000000000..ffe184d76c Binary files /dev/null and b/src/renderer/src/assets/images/providers/aws-bedrock.png differ diff --git a/src/renderer/src/config/models.ts b/src/renderer/src/config/models.ts index 8a8b293373..f43049f806 100644 --- a/src/renderer/src/config/models.ts +++ b/src/renderer/src/config/models.ts @@ -2345,7 +2345,8 @@ export const SYSTEM_MODELS: Record = { group: 'google' } ], - 'new-api': [] + 'new-api': [], + 'aws-bedrock': [] } export const TEXT_TO_IMAGES_MODELS = [ diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts index d5aeeef118..76095fe22b 100644 --- a/src/renderer/src/config/providers.ts +++ b/src/renderer/src/config/providers.ts @@ -5,6 +5,7 @@ import Ai302ProviderLogo from '@renderer/assets/images/providers/302ai.webp' import AiHubMixProviderLogo from '@renderer/assets/images/providers/aihubmix.webp' import AlayaNewProviderLogo from '@renderer/assets/images/providers/alayanew.webp' import AnthropicProviderLogo from '@renderer/assets/images/providers/anthropic.png' +import AwsProviderLogo from '@renderer/assets/images/providers/aws-bedrock.png' import BaichuanProviderLogo from '@renderer/assets/images/providers/baichuan.png' import BaiduCloudProviderLogo from '@renderer/assets/images/providers/baidu-cloud.svg' import BailianProviderLogo from '@renderer/assets/images/providers/bailian.png' @@ -106,7 +107,8 @@ const PROVIDER_LOGO_MAP = { cephalon: CephalonProviderLogo, lanyun: LanyunProviderLogo, vertexai: VertexAIProviderLogo, - 'new-api': NewAPIProviderLogo + 'new-api': NewAPIProviderLogo, + 'aws-bedrock': AwsProviderLogo } as const export function getProviderLogo(providerId: string) { @@ -689,5 +691,16 @@ export const PROVIDER_CONFIG = { official: 'https://docs.newapi.pro/', docs: 'https://docs.newapi.pro' } + }, + 'aws-bedrock': { + api: { + url: '' + }, + websites: { + official: 'https://aws.amazon.com/bedrock/', + apiKey: 'https://docs.aws.amazon.com/bedrock/latest/userguide/security-iam.html', + docs: 'https://docs.aws.amazon.com/bedrock/', + models: 'https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html' + } } } diff --git a/src/renderer/src/hooks/useAwsBedrock.ts b/src/renderer/src/hooks/useAwsBedrock.ts new file mode 100644 index 0000000000..e84608a3bb --- /dev/null +++ b/src/renderer/src/hooks/useAwsBedrock.ts @@ -0,0 +1,31 @@ +import store, { useAppSelector } from '@renderer/store' +import { setAwsBedrockAccessKeyId, setAwsBedrockRegion, setAwsBedrockSecretAccessKey } from '@renderer/store/llm' +import { useDispatch } from 'react-redux' + +export function useAwsBedrockSettings() { + const settings = useAppSelector((state) => state.llm.settings.awsBedrock) + const dispatch = useDispatch() + + return { + ...settings, + setAccessKeyId: (accessKeyId: string) => dispatch(setAwsBedrockAccessKeyId(accessKeyId)), + setSecretAccessKey: (secretAccessKey: string) => dispatch(setAwsBedrockSecretAccessKey(secretAccessKey)), + setRegion: (region: string) => dispatch(setAwsBedrockRegion(region)) + } +} + +export function getAwsBedrockSettings() { + return store.getState().llm.settings.awsBedrock +} + +export function getAwsBedrockAccessKeyId() { + return store.getState().llm.settings.awsBedrock.accessKeyId +} + +export function getAwsBedrockSecretAccessKey() { + return store.getState().llm.settings.awsBedrock.secretAccessKey +} + +export function getAwsBedrockRegion() { + return store.getState().llm.settings.awsBedrock.region +} diff --git a/src/renderer/src/i18n/label.ts b/src/renderer/src/i18n/label.ts index bb1cadb766..4915ef5eba 100644 --- a/src/renderer/src/i18n/label.ts +++ b/src/renderer/src/i18n/label.ts @@ -13,6 +13,7 @@ const providerKeyMap = { aihubmix: 'provider.aihubmix', alayanew: 'provider.alayanew', anthropic: 'provider.anthropic', + 'aws-bedrock': 'provider.aws-bedrock', 'azure-openai': 'provider.azure-openai', baichuan: 'provider.baichuan', 'baidu-cloud': 'provider.baidu-cloud', diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 0fbfa9fcd8..dcb2ee1fb1 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1594,6 +1594,7 @@ "aihubmix": "AiHubMix", "alayanew": "Alaya NeW", "anthropic": "Anthropic", + "aws-bedrock": "AWS Bedrock", "azure-openai": "Azure OpenAI", "baichuan": "Baichuan", "baidu-cloud": "Baidu Cloud", @@ -3035,6 +3036,16 @@ "tip": "Multiple keys separated by commas or spaces" }, "api_version": "API Version", + "aws-bedrock": { + "access_key_id": "AWS Access Key ID", + "access_key_id_help": "Your AWS Access Key ID for accessing AWS Bedrock services", + "description": "AWS Bedrock is Amazon's fully managed foundation model service that supports various advanced large language models", + "region": "AWS Region", + "region_help": "Your AWS service region, e.g., us-east-1", + "secret_access_key": "AWS Secret Access Key", + "secret_access_key_help": "Your AWS Secret Access Key, please keep it secure", + "title": "AWS Bedrock Configuration" + }, "azure": { "apiversion": { "tip": "The API version of Azure OpenAI, if you want to use Response API, please enter the preview version" diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index a5918fabde..617f719254 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -1594,6 +1594,7 @@ "aihubmix": "AiHubMix", "alayanew": "Alaya NeW", "anthropic": "Anthropic", + "aws-bedrock": "AWS Bedrock", "azure-openai": "Azure OpenAI", "baichuan": "百川", "baidu-cloud": "Baidu Cloud", @@ -3035,6 +3036,16 @@ "tip": "複数のキーはカンマまたはスペースで区切ります" }, "api_version": "APIバージョン", + "aws-bedrock": { + "access_key_id": "AWS アクセスキー ID", + "access_key_id_help": "あなたの AWS アクセスキー ID は、AWS Bedrock サービスへのアクセスに使用されます", + "description": "AWS Bedrock は、Amazon が提供する完全に管理されたベースモデルサービスで、さまざまな最先端の大言語モデルをサポートしています", + "region": "AWS リージョン", + "region_help": "あなたの AWS サービスリージョン、例:us-east-1", + "secret_access_key": "AWS アクセスキー", + "secret_access_key_help": "あなたの AWS アクセスキー、安全に保管してください", + "title": "AWS Bedrock 設定" + }, "azure": { "apiversion": { "tip": "Azure OpenAIのAPIバージョン。Response APIを使用する場合は、previewバージョンを入力してください" diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index ea2368d3db..102d605026 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -1594,6 +1594,7 @@ "aihubmix": "AiHubMix", "alayanew": "Alaya NeW", "anthropic": "Anthropic", + "aws-bedrock": "AWS Bedrock", "azure-openai": "Azure OpenAI", "baichuan": "Baichuan", "baidu-cloud": "Baidu Cloud", @@ -3035,6 +3036,16 @@ "tip": "Несколько ключей, разделенных запятыми или пробелами" }, "api_version": "Версия API", + "aws-bedrock": { + "access_key_id": "AWS Ключ доступа ID", + "access_key_id_help": "Ваш AWS Ключ доступа ID для доступа к AWS Bedrock", + "description": "AWS Bedrock — это полное управляемое сервисное предложение для моделей, поддерживающее различные современные модели языка", + "region": "AWS регион", + "region_help": "Ваш регион AWS, например us-east-1", + "secret_access_key": "AWS Ключ доступа", + "secret_access_key_help": "Ваш AWS Ключ доступа, пожалуйста, храните его в безопасности", + "title": "AWS Bedrock Конфигурация" + }, "azure": { "apiversion": { "tip": "Версия API Azure OpenAI. Если вы хотите использовать Response API, введите версию preview" diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 46ed5b2df5..7fe3afba64 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1594,6 +1594,7 @@ "aihubmix": "AiHubMix", "alayanew": "Alaya NeW", "anthropic": "Anthropic", + "aws-bedrock": "AWS Bedrock", "azure-openai": "Azure OpenAI", "baichuan": "百川", "baidu-cloud": "百度云千帆", @@ -3035,6 +3036,16 @@ "tip": "多个密钥使用逗号或空格分隔" }, "api_version": "API 版本", + "aws-bedrock": { + "access_key_id": "AWS 访问密钥 ID", + "access_key_id_help": "您的 AWS 访问密钥 ID,用于访问 AWS Bedrock 服务", + "description": "AWS Bedrock 是亚马逊提供的全托管基础模型服务,支持多种先进的大语言模型", + "region": "AWS 区域", + "region_help": "您的 AWS 服务区域,例如 us-east-1", + "secret_access_key": "AWS 访问密钥", + "secret_access_key_help": "您的 AWS 访问密钥,请妥善保管", + "title": "AWS Bedrock 配置" + }, "azure": { "apiversion": { "tip": "Azure OpenAI 的 API 版本,如果想要使用 Response API,请输入 preview 版本" diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 11c63797a6..8b006fd5c6 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1594,6 +1594,7 @@ "aihubmix": "AiHubMix", "alayanew": "Alaya NeW", "anthropic": "Anthropic", + "aws-bedrock": "AWS Bedrock", "azure-openai": "Azure OpenAI", "baichuan": "百川", "baidu-cloud": "百度雲千帆", @@ -3035,6 +3036,16 @@ "tip": "多個金鑰使用逗號或空格分隔" }, "api_version": "API 版本", + "aws-bedrock": { + "access_key_id": "AWS 存取密鑰 ID", + "access_key_id_help": "您的 AWS 存取密鑰 ID,用於存取 AWS Bedrock 服務", + "description": "AWS Bedrock 是亞馬遜提供的全托管基础模型服務,支持多種先進的大語言模型", + "region": "AWS 區域", + "region_help": "您的 AWS 服務區域,例如 us-east-1", + "secret_access_key": "AWS 存取密鑰", + "secret_access_key_help": "您的 AWS 存取密鑰,請妥善保管", + "title": "AWS Bedrock 設定" + }, "azure": { "apiversion": { "tip": "Azure OpenAI 的 API 版本,如果想要使用 Response API,請輸入 preview 版本" diff --git a/src/renderer/src/pages/settings/ProviderSettings/AwsBedrockSettings.tsx b/src/renderer/src/pages/settings/ProviderSettings/AwsBedrockSettings.tsx new file mode 100644 index 0000000000..d358198345 --- /dev/null +++ b/src/renderer/src/pages/settings/ProviderSettings/AwsBedrockSettings.tsx @@ -0,0 +1,74 @@ +import { HStack } from '@renderer/components/Layout' +import { PROVIDER_CONFIG } from '@renderer/config/providers' +import { useAwsBedrockSettings } from '@renderer/hooks/useAwsBedrock' +import { Alert, Input } from 'antd' +import { FC, useState } from 'react' +import { useTranslation } from 'react-i18next' + +import { SettingHelpLink, SettingHelpText, SettingHelpTextRow, SettingSubtitle } from '..' + +const AwsBedrockSettings: FC = () => { + const { t } = useTranslation() + const { accessKeyId, secretAccessKey, region, setAccessKeyId, setSecretAccessKey, setRegion } = + useAwsBedrockSettings() + + const providerConfig = PROVIDER_CONFIG['aws-bedrock'] + const apiKeyWebsite = providerConfig?.websites?.apiKey + + const [localAccessKeyId, setLocalAccessKeyId] = useState(accessKeyId) + const [localSecretAccessKey, setLocalSecretAccessKey] = useState(secretAccessKey) + const [localRegion, setLocalRegion] = useState(region) + + return ( + <> + {t('settings.provider.aws-bedrock.title')} + + + {t('settings.provider.aws-bedrock.access_key_id')} + setLocalAccessKeyId(e.target.value)} + onBlur={() => setAccessKeyId(localAccessKeyId)} + style={{ marginTop: 5 }} + /> + + {t('settings.provider.aws-bedrock.access_key_id_help')} + + + {t('settings.provider.aws-bedrock.secret_access_key')} + setLocalSecretAccessKey(e.target.value)} + onBlur={() => setSecretAccessKey(localSecretAccessKey)} + style={{ marginTop: 5 }} + spellCheck={false} + /> + {apiKeyWebsite && ( + + + + {t('settings.provider.get_api_key')} + + + {t('settings.provider.aws-bedrock.secret_access_key_help')} + + )} + + {t('settings.provider.aws-bedrock.region')} + setLocalRegion(e.target.value)} + onBlur={() => setRegion(localRegion)} + style={{ marginTop: 5 }} + /> + + {t('settings.provider.aws-bedrock.region_help')} + + + ) +} + +export default AwsBedrockSettings diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx index 4fa0c4d2ad..93f2a87604 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx @@ -29,6 +29,7 @@ import { SettingSubtitle, SettingTitle } from '..' +import AwsBedrockSettings from './AwsBedrockSettings' import CustomHeaderPopup from './CustomHeaderPopup' import DMXAPISettings from './DMXAPISettings' import GithubCopilotSettings from './GithubCopilotSettings' @@ -259,7 +260,7 @@ const ProviderSetting: FC = ({ providerId }) => { {isProviderSupportAuth(provider) && } {provider.id === 'openai' && } {isDmxapi && } - {provider.id !== 'vertexai' && ( + {provider.id !== 'vertexai' && provider.id !== 'aws-bedrock' && ( <> = ({ providerId }) => { {provider.id === 'lmstudio' && } {provider.id === 'gpustack' && } {provider.id === 'copilot' && } + {provider.id === 'aws-bedrock' && } {provider.id === 'vertexai' && } diff --git a/src/renderer/src/store/llm.ts b/src/renderer/src/store/llm.ts index b5876bcb80..b54c8c67d7 100644 --- a/src/renderer/src/store/llm.ts +++ b/src/renderer/src/store/llm.ts @@ -22,6 +22,11 @@ type LlmSettings = { projectId: string location: string } + awsBedrock: { + accessKeyId: string + secretAccessKey: string + region: string + } } export interface LlmState { @@ -537,6 +542,16 @@ export const INITIAL_PROVIDERS: Provider[] = [ models: SYSTEM_MODELS.voyageai, isSystem: true, enabled: false + }, + { + id: 'aws-bedrock', + name: 'AWS Bedrock', + type: 'aws-bedrock', + apiKey: '', + apiHost: '', + models: SYSTEM_MODELS['aws-bedrock'], + isSystem: true, + enabled: false } ] @@ -563,6 +578,11 @@ export const initialState: LlmState = { }, projectId: '', location: '' + }, + awsBedrock: { + accessKeyId: '', + secretAccessKey: '', + region: '' } } } @@ -687,6 +707,15 @@ const llmSlice = createSlice({ setVertexAIServiceAccountClientEmail: (state, action: PayloadAction) => { state.settings.vertexai.serviceAccount.clientEmail = action.payload }, + setAwsBedrockAccessKeyId: (state, action: PayloadAction) => { + state.settings.awsBedrock.accessKeyId = action.payload + }, + setAwsBedrockSecretAccessKey: (state, action: PayloadAction) => { + state.settings.awsBedrock.secretAccessKey = action.payload + }, + setAwsBedrockRegion: (state, action: PayloadAction) => { + state.settings.awsBedrock.region = action.payload + }, updateModel: ( state, action: PayloadAction<{ @@ -723,6 +752,9 @@ export const { setVertexAILocation, setVertexAIServiceAccountPrivateKey, setVertexAIServiceAccountClientEmail, + setAwsBedrockAccessKeyId, + setAwsBedrockSecretAccessKey, + setAwsBedrockRegion, updateModel } = llmSlice.actions diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index b8ad2a1249..d6bf1ea62f 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -1907,6 +1907,13 @@ const migrateConfig = { updateModelTextDelta(state.assistants.defaultAssistant.defaultModel) } + addProvider(state, 'aws-bedrock') + + // 初始化 awsBedrock 设置 + if (!state.llm.settings.awsBedrock) { + state.llm.settings.awsBedrock = llmInitialState.settings.awsBedrock + } + return state } catch (error) { logger.error('migrate 124 error', error as Error) diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 70a55c867a..3dfb8a7858 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -187,6 +187,7 @@ export type ProviderType = | 'azure-openai' | 'vertexai' | 'mistral' + | 'aws-bedrock' export type ModelType = 'text' | 'vision' | 'embedding' | 'reasoning' | 'function_calling' | 'web_search' | 'rerank' diff --git a/src/renderer/src/types/sdk.ts b/src/renderer/src/types/sdk.ts index ed56240398..0ed9b8da04 100644 --- a/src/renderer/src/types/sdk.ts +++ b/src/renderer/src/types/sdk.ts @@ -9,6 +9,7 @@ import { } from '@anthropic-ai/sdk/resources' import { MessageStream } from '@anthropic-ai/sdk/resources/messages/messages' import AnthropicVertex from '@anthropic-ai/vertex-sdk' +import type { BedrockRuntimeClient } from '@aws-sdk/client-bedrock-runtime' import { Content, CreateChatParameters, @@ -24,21 +25,43 @@ import { Stream } from 'openai/streaming' import { EndpointType } from './index' -export type SdkInstance = OpenAI | AzureOpenAI | Anthropic | AnthropicVertex | GoogleGenAI -export type SdkParams = OpenAISdkParams | OpenAIResponseSdkParams | AnthropicSdkParams | GeminiSdkParams -export type SdkRawChunk = OpenAISdkRawChunk | OpenAIResponseSdkRawChunk | AnthropicSdkRawChunk | GeminiSdkRawChunk -export type SdkRawOutput = OpenAISdkRawOutput | OpenAIResponseSdkRawOutput | AnthropicSdkRawOutput | GeminiSdkRawOutput +export type SdkInstance = OpenAI | AzureOpenAI | Anthropic | AnthropicVertex | GoogleGenAI | AwsBedrockSdkInstance +export type SdkParams = + | OpenAISdkParams + | OpenAIResponseSdkParams + | AnthropicSdkParams + | GeminiSdkParams + | AwsBedrockSdkParams +export type SdkRawChunk = + | OpenAISdkRawChunk + | OpenAIResponseSdkRawChunk + | AnthropicSdkRawChunk + | GeminiSdkRawChunk + | AwsBedrockSdkRawChunk +export type SdkRawOutput = + | OpenAISdkRawOutput + | OpenAIResponseSdkRawOutput + | AnthropicSdkRawOutput + | GeminiSdkRawOutput + | AwsBedrockSdkRawOutput export type SdkMessageParam = | OpenAISdkMessageParam | OpenAIResponseSdkMessageParam | AnthropicSdkMessageParam | GeminiSdkMessageParam + | AwsBedrockSdkMessageParam export type SdkToolCall = | OpenAI.Chat.Completions.ChatCompletionMessageToolCall | ToolUseBlock | FunctionCall | OpenAIResponseSdkToolCall -export type SdkTool = OpenAI.Chat.Completions.ChatCompletionTool | ToolUnion | Tool | OpenAIResponseSdkTool + | AwsBedrockSdkToolCall +export type SdkTool = + | OpenAI.Chat.Completions.ChatCompletionTool + | ToolUnion + | Tool + | OpenAIResponseSdkTool + | AwsBedrockSdkTool export type SdkModel = OpenAI.Models.Model | Anthropic.ModelInfo | GeminiModel | NewApiModel export type RequestOptions = Anthropic.RequestOptions | OpenAI.RequestOptions | GeminiOptions @@ -117,3 +140,119 @@ export type GeminiOptions = { export interface NewApiModel extends OpenAI.Models.Model { supported_endpoint_types?: EndpointType[] } + +/** + * AWS Bedrock + */ +export interface AwsBedrockSdkInstance { + client: BedrockRuntimeClient + region: string +} + +export interface AwsBedrockSdkParams { + modelId: string + messages: AwsBedrockSdkMessageParam[] + system?: string + maxTokens?: number + temperature?: number + topP?: number + stream?: boolean + tools?: AwsBedrockSdkTool[] +} + +export interface AwsBedrockSdkMessageParam { + role: 'user' | 'assistant' + content: Array<{ + text?: string + image?: { + format: 'png' | 'jpeg' | 'gif' | 'webp' + source: { + bytes?: Uint8Array + s3Location?: { + uri: string + bucketOwner?: string + } + } + } + toolResult?: { + toolUseId: string + content: Array<{ + json?: any + text?: string + image?: { + format: 'png' | 'jpeg' | 'gif' | 'webp' + source: { + bytes?: Uint8Array + s3Location?: { + uri: string + bucketOwner?: string + } + } + } + document?: any + video?: any + }> + status?: 'success' | 'error' + } + toolUse?: { + toolUseId: string + name: string + input: any + } + }> +} + +export interface AwsBedrockSdkRawChunk { + contentBlockStart?: { + start?: { + toolUse?: { + toolUseId: string + name: string + } + } + contentBlockIndex?: number + } + contentBlockDelta?: { + delta?: { + text?: string + toolUse?: { + input?: string + } + } + contentBlockIndex?: number + } + contentBlockStop?: { + contentBlockIndex?: number + } + messageStart?: any + messageStop?: any + metadata?: any +} + +export type AwsBedrockSdkRawOutput = { output: any } | AsyncIterable + +export interface AwsBedrockSdkTool { + toolSpec: { + name: string + description?: string + inputSchema: { + json: { + type: string + properties?: { + [key: string]: { + type: string + description?: string + } + } + required?: string[] + } + } + } +} + +export interface AwsBedrockSdkToolCall { + id: string + name: string + input: any + toolUseId: string +} diff --git a/src/renderer/src/utils/__tests__/aws-bedrock-utils.test.ts b/src/renderer/src/utils/__tests__/aws-bedrock-utils.test.ts new file mode 100644 index 0000000000..f1d1417005 --- /dev/null +++ b/src/renderer/src/utils/__tests__/aws-bedrock-utils.test.ts @@ -0,0 +1,226 @@ +import { describe, expect, it } from 'vitest' + +import { + type AwsBedrockImage, + type AwsBedrockImageFormat, + base64ToUint8Array, + convertBase64ImageToAwsBedrockFormat, + extractImageFormatFromMimeType, + isAwsBedrockSupportedImageFormat +} from '../aws-bedrock-utils' + +describe('utils/aws-bedrock-utils', () => { + describe('extractImageFormatFromMimeType', () => { + it('should extract png format from mime type', () => { + expect(extractImageFormatFromMimeType('image/png')).toBe('png') + }) + + it('should extract jpeg format from mime type', () => { + expect(extractImageFormatFromMimeType('image/jpeg')).toBe('jpeg') + }) + + it('should extract gif format from mime type', () => { + expect(extractImageFormatFromMimeType('image/gif')).toBe('gif') + }) + + it('should extract webp format from mime type', () => { + expect(extractImageFormatFromMimeType('image/webp')).toBe('webp') + }) + + it('should return null for unsupported mime type', () => { + expect(extractImageFormatFromMimeType('image/bmp')).toBe(null) + expect(extractImageFormatFromMimeType('image/svg+xml')).toBe(null) + expect(extractImageFormatFromMimeType('image/tiff')).toBe(null) + }) + + it('should return null for invalid mime type format', () => { + expect(extractImageFormatFromMimeType('invalid')).toBe(null) + expect(extractImageFormatFromMimeType('text/plain')).toBe(null) + expect(extractImageFormatFromMimeType('application/json')).toBe(null) + }) + + it('should return null for undefined or empty input', () => { + expect(extractImageFormatFromMimeType(undefined)).toBe(null) + expect(extractImageFormatFromMimeType('')).toBe(null) + }) + + it('should handle mime type with additional parameters', () => { + expect(extractImageFormatFromMimeType('image/png; charset=utf-8')).toBe(null) + expect(extractImageFormatFromMimeType('image/jpeg; quality=95')).toBe(null) + }) + }) + + describe('base64ToUint8Array', () => { + it('should convert valid base64 string to Uint8Array', () => { + // "hello" in base64 is "aGVsbG8=" + const base64 = 'aGVsbG8=' + const result = base64ToUint8Array(base64) + + expect(result).toBeInstanceOf(Uint8Array) + expect(result.length).toBe(5) + expect(Array.from(result)).toEqual([104, 101, 108, 108, 111]) // ASCII values for "hello" + }) + + it('should convert empty base64 string to empty Uint8Array', () => { + const result = base64ToUint8Array('') + expect(result).toBeInstanceOf(Uint8Array) + expect(result.length).toBe(0) + }) + + it('should handle base64 with padding', () => { + const base64 = 'YQ==' // "a" in base64 + const result = base64ToUint8Array(base64) + + expect(result).toBeInstanceOf(Uint8Array) + expect(result.length).toBe(1) + expect(result[0]).toBe(97) // ASCII value for "a" + }) + + it('should handle base64 without padding', () => { + const base64 = 'YWI' // "ab" in base64 without padding + const result = base64ToUint8Array(base64) + + expect(result).toBeInstanceOf(Uint8Array) + expect(result.length).toBe(2) + expect(Array.from(result)).toEqual([97, 98]) // ASCII values for "ab" + }) + + it('should throw error for invalid base64 string', () => { + expect(() => base64ToUint8Array('invalid!@#$%^&*()')).toThrow('Failed to decode base64 data') + expect(() => base64ToUint8Array('hello world!')).toThrow('Failed to decode base64 data') + }) + + it('should handle binary data correctly', () => { + // Binary data that represents a simple image header + const binaryData = new Uint8Array([137, 80, 78, 71]) // PNG header + const base64 = btoa(String.fromCharCode(...binaryData)) + const result = base64ToUint8Array(base64) + + expect(result).toBeInstanceOf(Uint8Array) + expect(Array.from(result)).toEqual([137, 80, 78, 71]) + }) + }) + + describe('convertBase64ImageToAwsBedrockFormat', () => { + const validBase64 = 'aGVsbG8=' // "hello" in base64 + + it('should convert base64 image with valid mime type', () => { + const result = convertBase64ImageToAwsBedrockFormat(validBase64, 'image/png') + + expect(result).not.toBe(null) + expect(result?.format).toBe('png') + expect(result?.source.bytes).toBeInstanceOf(Uint8Array) + expect(result?.source.bytes.length).toBe(5) + }) + + it('should use fallback format when mime type is not provided', () => { + const result = convertBase64ImageToAwsBedrockFormat(validBase64) + + expect(result).not.toBe(null) + expect(result?.format).toBe('png') // default fallback + expect(result?.source.bytes).toBeInstanceOf(Uint8Array) + }) + + it('should use custom fallback format', () => { + const result = convertBase64ImageToAwsBedrockFormat(validBase64, undefined, 'jpeg') + + expect(result).not.toBe(null) + expect(result?.format).toBe('jpeg') + expect(result?.source.bytes).toBeInstanceOf(Uint8Array) + }) + + it('should extract format from mime type when provided', () => { + const result = convertBase64ImageToAwsBedrockFormat(validBase64, 'image/webp', 'png') + + expect(result).not.toBe(null) + expect(result?.format).toBe('webp') // extracted from mime type, not fallback + }) + + it('should use fallback format for unsupported mime type', () => { + const result = convertBase64ImageToAwsBedrockFormat(validBase64, 'image/bmp') + + expect(result).not.toBe(null) + expect(result?.format).toBe('png') // uses fallback format + }) + + it('should return null for invalid base64 data', () => { + const result = convertBase64ImageToAwsBedrockFormat('invalid!@#$%^&*()', 'image/png') + + expect(result).toBe(null) + }) + + it('should return null for invalid fallback format', () => { + // @ts-ignore - testing invalid fallback format + const result = convertBase64ImageToAwsBedrockFormat(validBase64, undefined, 'bmp') + + expect(result).toBe(null) + }) + + it('should handle all supported formats', () => { + const formats: AwsBedrockImageFormat[] = ['png', 'jpeg', 'gif', 'webp'] + + formats.forEach((format) => { + const result = convertBase64ImageToAwsBedrockFormat(validBase64, `image/${format}`) + expect(result).not.toBe(null) + expect(result?.format).toBe(format) + }) + }) + + it('should return proper AwsBedrockImage structure', () => { + const result = convertBase64ImageToAwsBedrockFormat(validBase64, 'image/png') + + expect(result).toEqual({ + format: 'png', + source: { + bytes: expect.any(Uint8Array) + } + } as AwsBedrockImage) + }) + + it('should handle empty base64 string', () => { + const result = convertBase64ImageToAwsBedrockFormat('', 'image/png') + + expect(result).not.toBe(null) + expect(result?.format).toBe('png') + expect(result?.source.bytes).toBeInstanceOf(Uint8Array) + expect(result?.source.bytes.length).toBe(0) + }) + }) + + describe('isAwsBedrockSupportedImageFormat', () => { + it('should return true for supported formats', () => { + expect(isAwsBedrockSupportedImageFormat('image/png')).toBe(true) + expect(isAwsBedrockSupportedImageFormat('image/jpeg')).toBe(true) + expect(isAwsBedrockSupportedImageFormat('image/gif')).toBe(true) + expect(isAwsBedrockSupportedImageFormat('image/webp')).toBe(true) + }) + + it('should return false for unsupported formats', () => { + expect(isAwsBedrockSupportedImageFormat('image/bmp')).toBe(false) + expect(isAwsBedrockSupportedImageFormat('image/svg+xml')).toBe(false) + expect(isAwsBedrockSupportedImageFormat('image/tiff')).toBe(false) + }) + + it('should return false for non-image mime types', () => { + expect(isAwsBedrockSupportedImageFormat('text/plain')).toBe(false) + expect(isAwsBedrockSupportedImageFormat('application/json')).toBe(false) + expect(isAwsBedrockSupportedImageFormat('video/mp4')).toBe(false) + }) + + it('should return false for invalid mime types', () => { + expect(isAwsBedrockSupportedImageFormat('invalid')).toBe(false) + expect(isAwsBedrockSupportedImageFormat('image/')).toBe(false) + expect(isAwsBedrockSupportedImageFormat('/bmp')).toBe(false) + }) + + it('should return false for undefined or empty input', () => { + expect(isAwsBedrockSupportedImageFormat(undefined)).toBe(false) + expect(isAwsBedrockSupportedImageFormat('')).toBe(false) + }) + + it('should return false for mime types with additional parameters', () => { + expect(isAwsBedrockSupportedImageFormat('image/png; charset=utf-8')).toBe(false) + expect(isAwsBedrockSupportedImageFormat('image/jpeg; quality=95')).toBe(false) + }) + }) +}) diff --git a/src/renderer/src/utils/aws-bedrock-utils.ts b/src/renderer/src/utils/aws-bedrock-utils.ts new file mode 100644 index 0000000000..ec44ecb45e --- /dev/null +++ b/src/renderer/src/utils/aws-bedrock-utils.ts @@ -0,0 +1,98 @@ +/** + * AWS Bedrock 相关工具函数 + */ + +/** + * 支持的图片格式类型 + */ +export type AwsBedrockImageFormat = 'png' | 'jpeg' | 'gif' | 'webp' + +/** + * AWS Bedrock 图片对象格式 + */ +export interface AwsBedrockImage { + format: AwsBedrockImageFormat + source: { + bytes: Uint8Array + } +} + +/** + * 从 MIME 类型中提取图片格式 + * @param mimeType MIME 类型,如 'image/png' + * @returns 图片格式或 null(如果不支持) + */ +export function extractImageFormatFromMimeType(mimeType?: string): AwsBedrockImageFormat | null { + if (!mimeType) return null + + const format = mimeType.split('/')[1] as AwsBedrockImageFormat + + if (['png', 'jpeg', 'gif', 'webp'].includes(format)) { + return format + } + + return null +} + +/** + * 将 base64 字符串转换为 Uint8Array + * @param base64Data base64 编码的字符串 + * @returns Uint8Array + * @throws Error 如果 base64 解码失败 + */ +export function base64ToUint8Array(base64Data: string): Uint8Array { + try { + // 在浏览器环境中正确处理base64转换为Uint8Array + const binaryString = atob(base64Data) + const bytes = new Uint8Array(binaryString.length) + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i) + } + return bytes + } catch (error) { + throw new Error(`Failed to decode base64 data: ${error instanceof Error ? error.message : 'Unknown error'}`) + } +} + +/** + * 将 base64 图片数据转换为 AWS Bedrock 格式 + * @param data base64 编码的图片数据 + * @param mimeType 图片的 MIME 类型 + * @param fallbackFormat 当无法从 mimeType 中提取格式时的默认格式 + * @returns AWS Bedrock 格式的图片对象,如果格式不支持则返回 null + */ +export function convertBase64ImageToAwsBedrockFormat( + data: string, + mimeType?: string, + fallbackFormat: AwsBedrockImageFormat = 'png' +): AwsBedrockImage | null { + const format = extractImageFormatFromMimeType(mimeType) || fallbackFormat + + // 验证格式是否支持 + if (!['png', 'jpeg', 'gif', 'webp'].includes(format)) { + return null + } + + try { + const bytes = base64ToUint8Array(data) + + return { + format, + source: { + bytes + } + } + } catch (error) { + // 如果转换失败,返回 null + return null + } +} + +/** + * 检查给定的 MIME 类型是否为 AWS Bedrock 支持的图片格式 + * @param mimeType MIME 类型 + * @returns 是否支持 + */ +export function isAwsBedrockSupportedImageFormat(mimeType?: string): boolean { + return extractImageFormatFromMimeType(mimeType) !== null +} diff --git a/src/renderer/src/utils/mcp-tools.ts b/src/renderer/src/utils/mcp-tools.ts index e575917f7a..e2bf4e3f09 100644 --- a/src/renderer/src/utils/mcp-tools.ts +++ b/src/renderer/src/utils/mcp-tools.ts @@ -17,6 +17,7 @@ import { } from '@renderer/types' import type { MCPToolCompleteChunk, MCPToolInProgressChunk, MCPToolPendingChunk } from '@renderer/types/chunk' import { ChunkType } from '@renderer/types/chunk' +import { AwsBedrockSdkMessageParam, AwsBedrockSdkTool, AwsBedrockSdkToolCall } from '@renderer/types/sdk' import { isArray, isObject, pull, transform } from 'lodash' import { nanoid } from 'nanoid' import OpenAI from 'openai' @@ -27,6 +28,8 @@ import { ChatCompletionTool } from 'openai/resources' +import { convertBase64ImageToAwsBedrockFormat } from './aws-bedrock-utils' + const logger = loggerService.withContext('Utils:MCPTools') const MCP_AUTO_INSTALL_SERVER_NAME = '@cherry/mcp-auto-install' @@ -533,7 +536,7 @@ export function parseToolUse(content: string, mcpTools: MCPTool[], startIdx: num parsedArgs = toolArgs } // Logger.log(`Parsed arguments for tool "${toolName}":`, parsedArgs) - const mcpTool = mcpTools.find((tool) => tool.id === toolName) + const mcpTool = mcpTools.find((tool) => tool.id === toolName || tool.name === toolName) if (!mcpTool) { logger.error(`Tool "${toolName}" not found in MCP tools`) window.message.error(i18n.t('settings.mcp.errors.toolNotFound', { name: toolName })) @@ -835,6 +838,163 @@ export function mcpToolCallResponseToGeminiMessage( return message } +export function mcpToolsToAwsBedrockTools(mcpTools: MCPTool[]): Array { + return mcpTools.map((tool) => ({ + toolSpec: { + name: tool.id, + description: tool.description, + inputSchema: { + json: { + type: 'object', + properties: tool.inputSchema?.properties + ? Object.fromEntries( + Object.entries(tool.inputSchema.properties).map(([key, value]) => [ + key, + { + type: + typeof value === 'object' && value !== null && 'type' in value ? (value as any).type : 'string', + description: + typeof value === 'object' && value !== null && 'description' in value + ? (value as any).description + : undefined + } + ]) + ) + : {}, + required: tool.inputSchema?.required || [] + } + } + } + })) +} + +export function awsBedrockToolUseToMcpTool( + mcpTools: MCPTool[] | undefined, + toolCall: AwsBedrockSdkToolCall +): MCPTool | undefined { + if (!toolCall) return undefined + if (!mcpTools) return undefined + const tool = mcpTools.find((tool) => tool.id === toolCall.name || tool.name === toolCall.name) + if (!tool) { + return undefined + } + return tool +} + +export function mcpToolCallResponseToAwsBedrockMessage( + mcpToolResponse: MCPToolResponse, + resp: MCPCallToolResponse, + model: Model +): AwsBedrockSdkMessageParam { + const message: AwsBedrockSdkMessageParam = { + role: 'user', + content: [] + } + + const toolUseId = + 'toolUseId' in mcpToolResponse && mcpToolResponse.toolUseId + ? mcpToolResponse.toolUseId + : 'toolCallId' in mcpToolResponse && mcpToolResponse.toolCallId + ? mcpToolResponse.toolCallId + : 'unknown-tool-id' + + if (resp.isError) { + message.content = [ + { + toolResult: { + toolUseId: toolUseId, + content: [ + { + text: `Error: ${JSON.stringify(resp.content)}` + } + ], + status: 'error' + } + } + ] + } else { + const toolResultContent: Array<{ + json?: any + text?: string + image?: { + format: 'png' | 'jpeg' | 'gif' | 'webp' + source: { + bytes?: Uint8Array + s3Location?: { + uri: string + bucketOwner?: string + } + } + } + }> = [] + + if (isVisionModel(model)) { + for (const item of resp.content) { + switch (item.type) { + case 'text': + toolResultContent.push({ + text: item.text || 'no content' + }) + break + case 'image': + if (item.data && item.mimeType) { + const awsImage = convertBase64ImageToAwsBedrockFormat(item.data, item.mimeType) + if (awsImage) { + toolResultContent.push({ image: awsImage }) + } else { + toolResultContent.push({ + text: `[Image received: ${item.mimeType}, size: ${item.data?.length || 0} bytes]` + }) + } + } else { + toolResultContent.push({ + text: '[Image received but no data available]' + }) + } + break + default: + toolResultContent.push({ + text: `Unsupported content type: ${item.type}` + }) + break + } + } + } else { + // 对于非视觉模型,将所有内容合并为文本 + const textContent = resp.content + .map((item) => { + if (item.type === 'text') { + return item.text + } else { + // 对于非文本内容,尝试转换为JSON格式 + try { + return JSON.stringify(item) + } catch { + return `[${item.type} content]` + } + } + }) + .join('\n') + + toolResultContent.push({ + text: textContent || 'Tool execution completed with no output' + }) + } + + message.content = [ + { + toolResult: { + toolUseId: toolUseId, + content: toolResultContent, + status: 'success' + } + } + ] + } + + return message +} + export function isEnabledToolUse(assistant: Assistant) { if (assistant.model) { if (isFunctionCallingModel(assistant.model)) { diff --git a/yarn.lock b/yarn.lock index 7af86162d9..78695c5ad9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -339,6 +339,63 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-bedrock-runtime@npm:^3.840.0": + version: 3.848.0 + resolution: "@aws-sdk/client-bedrock-runtime@npm:3.848.0" + dependencies: + "@aws-crypto/sha256-browser": "npm:5.2.0" + "@aws-crypto/sha256-js": "npm:5.2.0" + "@aws-sdk/core": "npm:3.846.0" + "@aws-sdk/credential-provider-node": "npm:3.848.0" + "@aws-sdk/eventstream-handler-node": "npm:3.840.0" + "@aws-sdk/middleware-eventstream": "npm:3.840.0" + "@aws-sdk/middleware-host-header": "npm:3.840.0" + "@aws-sdk/middleware-logger": "npm:3.840.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.840.0" + "@aws-sdk/middleware-user-agent": "npm:3.848.0" + "@aws-sdk/middleware-websocket": "npm:3.844.0" + "@aws-sdk/region-config-resolver": "npm:3.840.0" + "@aws-sdk/token-providers": "npm:3.848.0" + "@aws-sdk/types": "npm:3.840.0" + "@aws-sdk/util-endpoints": "npm:3.848.0" + "@aws-sdk/util-user-agent-browser": "npm:3.840.0" + "@aws-sdk/util-user-agent-node": "npm:3.848.0" + "@smithy/config-resolver": "npm:^4.1.4" + "@smithy/core": "npm:^3.7.0" + "@smithy/eventstream-serde-browser": "npm:^4.0.4" + "@smithy/eventstream-serde-config-resolver": "npm:^4.1.2" + "@smithy/eventstream-serde-node": "npm:^4.0.4" + "@smithy/fetch-http-handler": "npm:^5.1.0" + "@smithy/hash-node": "npm:^4.0.4" + "@smithy/invalid-dependency": "npm:^4.0.4" + "@smithy/middleware-content-length": "npm:^4.0.4" + "@smithy/middleware-endpoint": "npm:^4.1.15" + "@smithy/middleware-retry": "npm:^4.1.16" + "@smithy/middleware-serde": "npm:^4.0.8" + "@smithy/middleware-stack": "npm:^4.0.4" + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/node-http-handler": "npm:^4.1.0" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/smithy-client": "npm:^4.4.7" + "@smithy/types": "npm:^4.3.1" + "@smithy/url-parser": "npm:^4.0.4" + "@smithy/util-base64": "npm:^4.0.0" + "@smithy/util-body-length-browser": "npm:^4.0.0" + "@smithy/util-body-length-node": "npm:^4.0.0" + "@smithy/util-defaults-mode-browser": "npm:^4.0.23" + "@smithy/util-defaults-mode-node": "npm:^4.0.23" + "@smithy/util-endpoints": "npm:^3.0.6" + "@smithy/util-middleware": "npm:^4.0.4" + "@smithy/util-retry": "npm:^4.0.6" + "@smithy/util-stream": "npm:^4.2.3" + "@smithy/util-utf8": "npm:^4.0.0" + "@types/uuid": "npm:^9.0.1" + tslib: "npm:^2.6.2" + uuid: "npm:^9.0.1" + checksum: 10c0/6aae8b8a8970f55605c89aa15d7efc189120cb54b64a9f57348ac4085b422d4d839fa5ed8b0d2d8ca4efc4a5326bf6988f61ded8f2b1cde79455c3bd4705ece6 + languageName: node + linkType: hard + "@aws-sdk/client-s3@npm:^3.840.0": version: 3.840.0 resolution: "@aws-sdk/client-s3@npm:3.840.0" @@ -451,6 +508,52 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-sso@npm:3.848.0": + version: 3.848.0 + resolution: "@aws-sdk/client-sso@npm:3.848.0" + dependencies: + "@aws-crypto/sha256-browser": "npm:5.2.0" + "@aws-crypto/sha256-js": "npm:5.2.0" + "@aws-sdk/core": "npm:3.846.0" + "@aws-sdk/middleware-host-header": "npm:3.840.0" + "@aws-sdk/middleware-logger": "npm:3.840.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.840.0" + "@aws-sdk/middleware-user-agent": "npm:3.848.0" + "@aws-sdk/region-config-resolver": "npm:3.840.0" + "@aws-sdk/types": "npm:3.840.0" + "@aws-sdk/util-endpoints": "npm:3.848.0" + "@aws-sdk/util-user-agent-browser": "npm:3.840.0" + "@aws-sdk/util-user-agent-node": "npm:3.848.0" + "@smithy/config-resolver": "npm:^4.1.4" + "@smithy/core": "npm:^3.7.0" + "@smithy/fetch-http-handler": "npm:^5.1.0" + "@smithy/hash-node": "npm:^4.0.4" + "@smithy/invalid-dependency": "npm:^4.0.4" + "@smithy/middleware-content-length": "npm:^4.0.4" + "@smithy/middleware-endpoint": "npm:^4.1.15" + "@smithy/middleware-retry": "npm:^4.1.16" + "@smithy/middleware-serde": "npm:^4.0.8" + "@smithy/middleware-stack": "npm:^4.0.4" + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/node-http-handler": "npm:^4.1.0" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/smithy-client": "npm:^4.4.7" + "@smithy/types": "npm:^4.3.1" + "@smithy/url-parser": "npm:^4.0.4" + "@smithy/util-base64": "npm:^4.0.0" + "@smithy/util-body-length-browser": "npm:^4.0.0" + "@smithy/util-body-length-node": "npm:^4.0.0" + "@smithy/util-defaults-mode-browser": "npm:^4.0.23" + "@smithy/util-defaults-mode-node": "npm:^4.0.23" + "@smithy/util-endpoints": "npm:^3.0.6" + "@smithy/util-middleware": "npm:^4.0.4" + "@smithy/util-retry": "npm:^4.0.6" + "@smithy/util-utf8": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/758d98cec61ee94f90e476584955409800368346ce9cafaad9d2012579655ddd7500ec31e6e4f409d4d14365ed44379b248a47b2d5a7c4dfde6658d17efea25a + languageName: node + linkType: hard + "@aws-sdk/core@npm:3.840.0": version: 3.840.0 resolution: "@aws-sdk/core@npm:3.840.0" @@ -474,6 +577,29 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/core@npm:3.846.0": + version: 3.846.0 + resolution: "@aws-sdk/core@npm:3.846.0" + dependencies: + "@aws-sdk/types": "npm:3.840.0" + "@aws-sdk/xml-builder": "npm:3.821.0" + "@smithy/core": "npm:^3.7.0" + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/signature-v4": "npm:^5.1.2" + "@smithy/smithy-client": "npm:^4.4.7" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-base64": "npm:^4.0.0" + "@smithy/util-body-length-browser": "npm:^4.0.0" + "@smithy/util-middleware": "npm:^4.0.4" + "@smithy/util-utf8": "npm:^4.0.0" + fast-xml-parser: "npm:5.2.5" + tslib: "npm:^2.6.2" + checksum: 10c0/b23115868854939ec4d2eefcedd0fe6a2dbaa8bca83e4b757c21e5c8a153c99b61ea4b645e763257b2031717dfcc9c92264f83aa4f9d0071c806895eea6722fa + languageName: node + linkType: hard + "@aws-sdk/credential-provider-env@npm:3.840.0": version: 3.840.0 resolution: "@aws-sdk/credential-provider-env@npm:3.840.0" @@ -487,6 +613,19 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-env@npm:3.846.0": + version: 3.846.0 + resolution: "@aws-sdk/credential-provider-env@npm:3.846.0" + dependencies: + "@aws-sdk/core": "npm:3.846.0" + "@aws-sdk/types": "npm:3.840.0" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/21640b6eec50de4fa3a7e2ac1c4505c0cf27f2f7540781d2892b2aa281f28d7c4214bd385e11cdbfd5e3309cd12219c05d26adf7cad4c881c995a20b8bc4dbcd + languageName: node + linkType: hard + "@aws-sdk/credential-provider-http@npm:3.840.0": version: 3.840.0 resolution: "@aws-sdk/credential-provider-http@npm:3.840.0" @@ -505,6 +644,24 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-http@npm:3.846.0": + version: 3.846.0 + resolution: "@aws-sdk/credential-provider-http@npm:3.846.0" + dependencies: + "@aws-sdk/core": "npm:3.846.0" + "@aws-sdk/types": "npm:3.840.0" + "@smithy/fetch-http-handler": "npm:^5.1.0" + "@smithy/node-http-handler": "npm:^4.1.0" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/smithy-client": "npm:^4.4.7" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-stream": "npm:^4.2.3" + tslib: "npm:^2.6.2" + checksum: 10c0/5fbc05c5b0e622ce473dda41d5402982508e63496d36cb22ee6039caf563bb5d1c5633ced6901fe8c134090818400b865202c619288979132ba635f09aa98a97 + languageName: node + linkType: hard + "@aws-sdk/credential-provider-ini@npm:3.840.0": version: 3.840.0 resolution: "@aws-sdk/credential-provider-ini@npm:3.840.0" @@ -526,6 +683,27 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-ini@npm:3.848.0": + version: 3.848.0 + resolution: "@aws-sdk/credential-provider-ini@npm:3.848.0" + dependencies: + "@aws-sdk/core": "npm:3.846.0" + "@aws-sdk/credential-provider-env": "npm:3.846.0" + "@aws-sdk/credential-provider-http": "npm:3.846.0" + "@aws-sdk/credential-provider-process": "npm:3.846.0" + "@aws-sdk/credential-provider-sso": "npm:3.848.0" + "@aws-sdk/credential-provider-web-identity": "npm:3.848.0" + "@aws-sdk/nested-clients": "npm:3.848.0" + "@aws-sdk/types": "npm:3.840.0" + "@smithy/credential-provider-imds": "npm:^4.0.6" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/shared-ini-file-loader": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/af3f7aa9816618a4be600f4feeeb737cf5bd11db4f3f7e96cc30e45e93386a2e3ab4a2f9c40b2eb738b4d4e66dbe0db5086062846a8a75dfa2fd42acfb349b33 + languageName: node + linkType: hard + "@aws-sdk/credential-provider-node@npm:3.840.0": version: 3.840.0 resolution: "@aws-sdk/credential-provider-node@npm:3.840.0" @@ -546,6 +724,26 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-node@npm:3.848.0": + version: 3.848.0 + resolution: "@aws-sdk/credential-provider-node@npm:3.848.0" + dependencies: + "@aws-sdk/credential-provider-env": "npm:3.846.0" + "@aws-sdk/credential-provider-http": "npm:3.846.0" + "@aws-sdk/credential-provider-ini": "npm:3.848.0" + "@aws-sdk/credential-provider-process": "npm:3.846.0" + "@aws-sdk/credential-provider-sso": "npm:3.848.0" + "@aws-sdk/credential-provider-web-identity": "npm:3.848.0" + "@aws-sdk/types": "npm:3.840.0" + "@smithy/credential-provider-imds": "npm:^4.0.6" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/shared-ini-file-loader": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/9887a7a32dfc687c4cfb9aacf9fbc9468916dc6022802a1ddfccc6d948202e6cf6f2d15c3e526806714edd365490a828c18ec67de977a66d83b37ab75d170d56 + languageName: node + linkType: hard + "@aws-sdk/credential-provider-process@npm:3.840.0": version: 3.840.0 resolution: "@aws-sdk/credential-provider-process@npm:3.840.0" @@ -560,6 +758,20 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-process@npm:3.846.0": + version: 3.846.0 + resolution: "@aws-sdk/credential-provider-process@npm:3.846.0" + dependencies: + "@aws-sdk/core": "npm:3.846.0" + "@aws-sdk/types": "npm:3.840.0" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/shared-ini-file-loader": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/3be6d4547cabd1fa71aa0acacc64f7996f6154aff01e7e5aa6f1cece3d89399c4f500b74db8f0173cf0c9c89275d8803970cb815d45c769808d339bdfae186fe + languageName: node + linkType: hard + "@aws-sdk/credential-provider-sso@npm:3.840.0": version: 3.840.0 resolution: "@aws-sdk/credential-provider-sso@npm:3.840.0" @@ -576,6 +788,22 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-sso@npm:3.848.0": + version: 3.848.0 + resolution: "@aws-sdk/credential-provider-sso@npm:3.848.0" + dependencies: + "@aws-sdk/client-sso": "npm:3.848.0" + "@aws-sdk/core": "npm:3.846.0" + "@aws-sdk/token-providers": "npm:3.848.0" + "@aws-sdk/types": "npm:3.840.0" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/shared-ini-file-loader": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/3ac50af20ff6646388175581cafab03b590eb5fccd1743ef45eeab3b3bb843a681e6c9e88d06c031a2886f77f649ab1a5df18cf7fb088dc8b34a7b225614ebaf + languageName: node + linkType: hard + "@aws-sdk/credential-provider-web-identity@npm:3.840.0": version: 3.840.0 resolution: "@aws-sdk/credential-provider-web-identity@npm:3.840.0" @@ -590,6 +818,32 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-web-identity@npm:3.848.0": + version: 3.848.0 + resolution: "@aws-sdk/credential-provider-web-identity@npm:3.848.0" + dependencies: + "@aws-sdk/core": "npm:3.846.0" + "@aws-sdk/nested-clients": "npm:3.848.0" + "@aws-sdk/types": "npm:3.840.0" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/bd1729dc05426d86c4feb4093b6c57eb2f11a8c10d6bd9a9b81d795bd4de1fa03f9c92c85ca35e6121c4814ba6a3416fa6bb7b3bf8171735de28999a1a239aa6 + languageName: node + linkType: hard + +"@aws-sdk/eventstream-handler-node@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/eventstream-handler-node@npm:3.840.0" + dependencies: + "@aws-sdk/types": "npm:3.840.0" + "@smithy/eventstream-codec": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/a95bc35719ed519d692d50983195ae1648edfa8c5da6750edf861e6f179daac75ab75b1235225efceae913eeebf438efb467409785aca989852adbb32637c255 + languageName: node + linkType: hard + "@aws-sdk/middleware-bucket-endpoint@npm:3.840.0": version: 3.840.0 resolution: "@aws-sdk/middleware-bucket-endpoint@npm:3.840.0" @@ -605,6 +859,18 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-eventstream@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/middleware-eventstream@npm:3.840.0" + dependencies: + "@aws-sdk/types": "npm:3.840.0" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/887e92906c5b026f01d292d4ebc58483da5676a19a63f34c33fc7d6e4ca00b2df9c4336d0afa141a2f231fb6e01c45851facdef1f531589629e7da7bcbbee02a + languageName: node + linkType: hard + "@aws-sdk/middleware-expect-continue@npm:3.840.0": version: 3.840.0 resolution: "@aws-sdk/middleware-expect-continue@npm:3.840.0" @@ -732,6 +998,39 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-user-agent@npm:3.848.0": + version: 3.848.0 + resolution: "@aws-sdk/middleware-user-agent@npm:3.848.0" + dependencies: + "@aws-sdk/core": "npm:3.846.0" + "@aws-sdk/types": "npm:3.840.0" + "@aws-sdk/util-endpoints": "npm:3.848.0" + "@smithy/core": "npm:^3.7.0" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/2ec977bd69711022a162e287584c04c66a6481ecc331ed8fe13b6fd334a9d2c3ebe13709933dd5b224915cf7fa6e196870077e428c853b772a4b841162e71752 + languageName: node + linkType: hard + +"@aws-sdk/middleware-websocket@npm:3.844.0": + version: 3.844.0 + resolution: "@aws-sdk/middleware-websocket@npm:3.844.0" + dependencies: + "@aws-sdk/types": "npm:3.840.0" + "@aws-sdk/util-format-url": "npm:3.840.0" + "@smithy/eventstream-codec": "npm:^4.0.4" + "@smithy/eventstream-serde-browser": "npm:^4.0.4" + "@smithy/fetch-http-handler": "npm:^5.1.0" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/signature-v4": "npm:^5.1.2" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-hex-encoding": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/ff626f40f2d7369cc96ac139548bfb51b04056097ac5ad06ef20776973030e50990e7b8d218955ab1ef4caf90cc55b6a7bc34e9b01ae04a7e29dd6e6d60223be + languageName: node + linkType: hard + "@aws-sdk/nested-clients@npm:3.840.0": version: 3.840.0 resolution: "@aws-sdk/nested-clients@npm:3.840.0" @@ -778,6 +1077,52 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/nested-clients@npm:3.848.0": + version: 3.848.0 + resolution: "@aws-sdk/nested-clients@npm:3.848.0" + dependencies: + "@aws-crypto/sha256-browser": "npm:5.2.0" + "@aws-crypto/sha256-js": "npm:5.2.0" + "@aws-sdk/core": "npm:3.846.0" + "@aws-sdk/middleware-host-header": "npm:3.840.0" + "@aws-sdk/middleware-logger": "npm:3.840.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.840.0" + "@aws-sdk/middleware-user-agent": "npm:3.848.0" + "@aws-sdk/region-config-resolver": "npm:3.840.0" + "@aws-sdk/types": "npm:3.840.0" + "@aws-sdk/util-endpoints": "npm:3.848.0" + "@aws-sdk/util-user-agent-browser": "npm:3.840.0" + "@aws-sdk/util-user-agent-node": "npm:3.848.0" + "@smithy/config-resolver": "npm:^4.1.4" + "@smithy/core": "npm:^3.7.0" + "@smithy/fetch-http-handler": "npm:^5.1.0" + "@smithy/hash-node": "npm:^4.0.4" + "@smithy/invalid-dependency": "npm:^4.0.4" + "@smithy/middleware-content-length": "npm:^4.0.4" + "@smithy/middleware-endpoint": "npm:^4.1.15" + "@smithy/middleware-retry": "npm:^4.1.16" + "@smithy/middleware-serde": "npm:^4.0.8" + "@smithy/middleware-stack": "npm:^4.0.4" + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/node-http-handler": "npm:^4.1.0" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/smithy-client": "npm:^4.4.7" + "@smithy/types": "npm:^4.3.1" + "@smithy/url-parser": "npm:^4.0.4" + "@smithy/util-base64": "npm:^4.0.0" + "@smithy/util-body-length-browser": "npm:^4.0.0" + "@smithy/util-body-length-node": "npm:^4.0.0" + "@smithy/util-defaults-mode-browser": "npm:^4.0.23" + "@smithy/util-defaults-mode-node": "npm:^4.0.23" + "@smithy/util-endpoints": "npm:^3.0.6" + "@smithy/util-middleware": "npm:^4.0.4" + "@smithy/util-retry": "npm:^4.0.6" + "@smithy/util-utf8": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/77057a60ce0f86bee16e1daa5214385720aa433f1ff097350b41a85dab2da2ac0a6f196f17b94d51631448adeed9dabfd8b984976771d9cfd4bb27a449f26bc6 + languageName: node + linkType: hard + "@aws-sdk/region-config-resolver@npm:3.840.0": version: 3.840.0 resolution: "@aws-sdk/region-config-resolver@npm:3.840.0" @@ -821,6 +1166,21 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/token-providers@npm:3.848.0": + version: 3.848.0 + resolution: "@aws-sdk/token-providers@npm:3.848.0" + dependencies: + "@aws-sdk/core": "npm:3.846.0" + "@aws-sdk/nested-clients": "npm:3.848.0" + "@aws-sdk/types": "npm:3.840.0" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/shared-ini-file-loader": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/c37329f6f3f41c32464d4ca512baa0aa1cd8694964af4391eebb14e7a4980316041579745bc35930caf973aa5595326da95f652b26ebb8f167cea078fb893d10 + languageName: node + linkType: hard + "@aws-sdk/types@npm:3.840.0, @aws-sdk/types@npm:^3.222.0": version: 3.840.0 resolution: "@aws-sdk/types@npm:3.840.0" @@ -852,6 +1212,31 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/util-endpoints@npm:3.848.0": + version: 3.848.0 + resolution: "@aws-sdk/util-endpoints@npm:3.848.0" + dependencies: + "@aws-sdk/types": "npm:3.840.0" + "@smithy/types": "npm:^4.3.1" + "@smithy/url-parser": "npm:^4.0.4" + "@smithy/util-endpoints": "npm:^3.0.6" + tslib: "npm:^2.6.2" + checksum: 10c0/84567b4152ea823274855cdab4acdde1ca60b4ba0be265408da13ad59b9f5ec2f16578402ca0430748b57b57f3a457466517bf434d0e9cec79abf855a0468b49 + languageName: node + linkType: hard + +"@aws-sdk/util-format-url@npm:3.840.0": + version: 3.840.0 + resolution: "@aws-sdk/util-format-url@npm:3.840.0" + dependencies: + "@aws-sdk/types": "npm:3.840.0" + "@smithy/querystring-builder": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/9f1d55e00bc10523d786e9a7c4b387ceb38170a870a1c5c8772bd3cd7d0ab1f352ca1c49a52cbf751acee65091ae9e58f079e6ee94bbe104b8989bff26f40a63 + languageName: node + linkType: hard + "@aws-sdk/util-locate-window@npm:^3.0.0": version: 3.804.0 resolution: "@aws-sdk/util-locate-window@npm:3.804.0" @@ -891,6 +1276,24 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/util-user-agent-node@npm:3.848.0": + version: 3.848.0 + resolution: "@aws-sdk/util-user-agent-node@npm:3.848.0" + dependencies: + "@aws-sdk/middleware-user-agent": "npm:3.848.0" + "@aws-sdk/types": "npm:3.840.0" + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + peerDependencies: + aws-crt: ">=1.0.0" + peerDependenciesMeta: + aws-crt: + optional: true + checksum: 10c0/165308d1323ed0f56f4366e235674a73606c9d32a47c1572541c4befc6ce5ecca2d2334981f0d77791def22dad0a722773b1540f60f2d329710f2ade361801a6 + languageName: node + linkType: hard + "@aws-sdk/xml-builder@npm:3.821.0": version: 3.821.0 resolution: "@aws-sdk/xml-builder@npm:3.821.0" @@ -4789,6 +5192,23 @@ __metadata: languageName: node linkType: hard +"@smithy/core@npm:^3.7.0, @smithy/core@npm:^3.7.1": + version: 3.7.1 + resolution: "@smithy/core@npm:3.7.1" + dependencies: + "@smithy/middleware-serde": "npm:^4.0.8" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-base64": "npm:^4.0.0" + "@smithy/util-body-length-browser": "npm:^4.0.0" + "@smithy/util-middleware": "npm:^4.0.4" + "@smithy/util-stream": "npm:^4.2.3" + "@smithy/util-utf8": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/3828f48b776a50ee58896fd8fdcd2ae28e2142114118b5ee78892c6e40f74c63f7dbb39199a324f9858d87ca3362e72563e47ddd81c38895da070c9503325405 + languageName: node + linkType: hard + "@smithy/credential-provider-imds@npm:^4.0.6": version: 4.0.6 resolution: "@smithy/credential-provider-imds@npm:4.0.6" @@ -4870,6 +5290,19 @@ __metadata: languageName: node linkType: hard +"@smithy/fetch-http-handler@npm:^5.1.0": + version: 5.1.0 + resolution: "@smithy/fetch-http-handler@npm:5.1.0" + dependencies: + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/querystring-builder": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-base64": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/9bd54f40f00f35a4eee3c359e5942fc5c6ea1c43d7c708e5dd2cd74e8291c55fc6f1ce043d66eea7c1ca687dda682899058967c5b92df75ab56e44a773bb8679 + languageName: node + linkType: hard + "@smithy/hash-blob-browser@npm:^4.0.4": version: 4.0.4 resolution: "@smithy/hash-blob-browser@npm:4.0.4" @@ -4971,6 +5404,22 @@ __metadata: languageName: node linkType: hard +"@smithy/middleware-endpoint@npm:^4.1.15, @smithy/middleware-endpoint@npm:^4.1.16": + version: 4.1.16 + resolution: "@smithy/middleware-endpoint@npm:4.1.16" + dependencies: + "@smithy/core": "npm:^3.7.1" + "@smithy/middleware-serde": "npm:^4.0.8" + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/shared-ini-file-loader": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + "@smithy/url-parser": "npm:^4.0.4" + "@smithy/util-middleware": "npm:^4.0.4" + tslib: "npm:^2.6.2" + checksum: 10c0/9f19d65ec1ed88e6a7a214821087286304199bbc613b157cca9dd7eab12f3ab6554fb38b9681759c75285210b21b4cc1527add1eafd46f9f5bfb8ca5679eebeb + languageName: node + linkType: hard + "@smithy/middleware-retry@npm:^4.1.14": version: 4.1.14 resolution: "@smithy/middleware-retry@npm:4.1.14" @@ -4988,6 +5437,23 @@ __metadata: languageName: node linkType: hard +"@smithy/middleware-retry@npm:^4.1.16": + version: 4.1.17 + resolution: "@smithy/middleware-retry@npm:4.1.17" + dependencies: + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/service-error-classification": "npm:^4.0.6" + "@smithy/smithy-client": "npm:^4.4.8" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-middleware": "npm:^4.0.4" + "@smithy/util-retry": "npm:^4.0.6" + tslib: "npm:^2.6.2" + uuid: "npm:^9.0.1" + checksum: 10c0/d8b8ce6180a1b9bef099c95a0f8bfcd232f12fc662a65f7ac2d65839009678af33665284c29b8abdb92de47f20f40ec95307a5f1d74623a3374158d800598b43 + languageName: node + linkType: hard + "@smithy/middleware-serde@npm:^4.0.8": version: 4.0.8 resolution: "@smithy/middleware-serde@npm:4.0.8" @@ -5034,6 +5500,19 @@ __metadata: languageName: node linkType: hard +"@smithy/node-http-handler@npm:^4.1.0": + version: 4.1.0 + resolution: "@smithy/node-http-handler@npm:4.1.0" + dependencies: + "@smithy/abort-controller": "npm:^4.0.4" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/querystring-builder": "npm:^4.0.4" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/6212b86b62dc44d0d8eb3949428b2ddbb5d064e722979fc5384ec52367b8246b19619732822514e0be9d6455b8c2c41d29f46a74bf43548cc2713ea7552c07a8 + languageName: node + linkType: hard + "@smithy/property-provider@npm:^4.0.4": version: 4.0.4 resolution: "@smithy/property-provider@npm:4.0.4" @@ -5125,6 +5604,21 @@ __metadata: languageName: node linkType: hard +"@smithy/smithy-client@npm:^4.4.7, @smithy/smithy-client@npm:^4.4.8": + version: 4.4.8 + resolution: "@smithy/smithy-client@npm:4.4.8" + dependencies: + "@smithy/core": "npm:^3.7.1" + "@smithy/middleware-endpoint": "npm:^4.1.16" + "@smithy/middleware-stack": "npm:^4.0.4" + "@smithy/protocol-http": "npm:^5.1.2" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-stream": "npm:^4.2.3" + tslib: "npm:^2.6.2" + checksum: 10c0/2e7a0138dcf8afed63e998254f75d90fdb8da34f96cd09f84c7736eb5118f2b539b1ccb1dce697fdd7df7653d9c34b663731b22bfd1e0cb5dbdd8f797a01dfd9 + languageName: node + linkType: hard + "@smithy/types@npm:^4.3.1": version: 4.3.1 resolution: "@smithy/types@npm:4.3.1" @@ -5216,6 +5710,19 @@ __metadata: languageName: node linkType: hard +"@smithy/util-defaults-mode-browser@npm:^4.0.23": + version: 4.0.24 + resolution: "@smithy/util-defaults-mode-browser@npm:4.0.24" + dependencies: + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/smithy-client": "npm:^4.4.8" + "@smithy/types": "npm:^4.3.1" + bowser: "npm:^2.11.0" + tslib: "npm:^2.6.2" + checksum: 10c0/f0738ae262dd79c17cfa060a26cfd84de6b51d7a238f3d48bc960f2e9888e68af719b825243c99ec65828edda52883bd70361cedd7224f290981d71963edbc07 + languageName: node + linkType: hard + "@smithy/util-defaults-mode-node@npm:^4.0.21": version: 4.0.21 resolution: "@smithy/util-defaults-mode-node@npm:4.0.21" @@ -5231,6 +5738,21 @@ __metadata: languageName: node linkType: hard +"@smithy/util-defaults-mode-node@npm:^4.0.23": + version: 4.0.24 + resolution: "@smithy/util-defaults-mode-node@npm:4.0.24" + dependencies: + "@smithy/config-resolver": "npm:^4.1.4" + "@smithy/credential-provider-imds": "npm:^4.0.6" + "@smithy/node-config-provider": "npm:^4.1.3" + "@smithy/property-provider": "npm:^4.0.4" + "@smithy/smithy-client": "npm:^4.4.8" + "@smithy/types": "npm:^4.3.1" + tslib: "npm:^2.6.2" + checksum: 10c0/4ca648d7d660bf62c096d2a4b7639e0178898def45aa5e9d0b5ddd4cd6f49478155465145a44c1634e8e3149b7e6f79a19f91f93e584d765118504bac81225c0 + languageName: node + linkType: hard + "@smithy/util-endpoints@npm:^3.0.6": version: 3.0.6 resolution: "@smithy/util-endpoints@npm:3.0.6" @@ -5288,6 +5810,22 @@ __metadata: languageName: node linkType: hard +"@smithy/util-stream@npm:^4.2.3": + version: 4.2.3 + resolution: "@smithy/util-stream@npm:4.2.3" + dependencies: + "@smithy/fetch-http-handler": "npm:^5.1.0" + "@smithy/node-http-handler": "npm:^4.1.0" + "@smithy/types": "npm:^4.3.1" + "@smithy/util-base64": "npm:^4.0.0" + "@smithy/util-buffer-from": "npm:^4.0.0" + "@smithy/util-hex-encoding": "npm:^4.0.0" + "@smithy/util-utf8": "npm:^4.0.0" + tslib: "npm:^2.6.2" + checksum: 10c0/3321f944a36c7a9a8ef17f5c58b29ef06107c9bc682d7932f2ea9e1b6f839174d07d053e81285bad8b29c11848e799795e6c016648a6e3a8636d8acfe24183ef + languageName: node + linkType: hard + "@smithy/util-uri-escape@npm:^4.0.0": version: 4.0.0 resolution: "@smithy/util-uri-escape@npm:4.0.0" @@ -7209,6 +7747,7 @@ __metadata: "@ant-design/v5-patch-for-react-19": "npm:^1.0.3" "@anthropic-ai/sdk": "npm:^0.41.0" "@anthropic-ai/vertex-sdk": "patch:@anthropic-ai/vertex-sdk@npm%3A0.11.4#~/.yarn/patches/@anthropic-ai-vertex-sdk-npm-0.11.4-c19cb41edb.patch" + "@aws-sdk/client-bedrock-runtime": "npm:^3.840.0" "@aws-sdk/client-s3": "npm:^3.840.0" "@cherrystudio/embedjs": "npm:^0.1.31" "@cherrystudio/embedjs-libsql": "npm:^0.1.31" @@ -11414,6 +11953,17 @@ __metadata: languageName: node linkType: hard +"fast-xml-parser@npm:5.2.5": + version: 5.2.5 + resolution: "fast-xml-parser@npm:5.2.5" + dependencies: + strnum: "npm:^2.1.0" + bin: + fxparser: src/cli/cli.js + checksum: 10c0/d1057d2e790c327ccfc42b872b91786a4912a152d44f9507bf053f800102dfb07ece3da0a86b33ff6a0caa5a5cad86da3326744f6ae5efb0c6c571d754fe48cd + languageName: node + linkType: hard + "fast-xml-parser@npm:^4.5.0, fast-xml-parser@npm:^4.5.1": version: 4.5.3 resolution: "fast-xml-parser@npm:4.5.3" @@ -19456,6 +20006,13 @@ __metadata: languageName: node linkType: hard +"strnum@npm:^2.1.0": + version: 2.1.1 + resolution: "strnum@npm:2.1.1" + checksum: 10c0/1f9bd1f9b4c68333f25c2b1f498ea529189f060cd50aa59f1876139c994d817056de3ce57c12c970f80568d75df2289725e218bd9e3cdf73cd1a876c9c102733 + languageName: node + linkType: hard + "strtok3@npm:^6.2.4": version: 6.3.0 resolution: "strtok3@npm:6.3.0"