feat: Add reasoning effort control for Claude 3.7 (#2540)
* feat: Add reasoning effort control for Anthropic models with Anthropic Provider and OpenAI Provider - Add reasoning effort settings with low/medium/high options - Implement reasoning effort for Claude 3.7 Sonnet models - Update localization tips for reasoning effort - Enhance provider handling of reasoning effort parameters * fix: Extract o1-mini and o1-preview * fix: Add OpenAI o-series model to ReasoningModel * fix: Improve OpenAI o-series model detection * style: Reduce font size * fix: Add default token handling using DEFAULT_MAX_TOKENS * fix: Add beta parameter for Anthropic reasoning models
This commit is contained in:
parent
956c2f683d
commit
ae11490f87
@ -81,7 +81,7 @@
|
||||
"webdav": "4.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "^0.24.3",
|
||||
"@anthropic-ai/sdk": "^0.38.0",
|
||||
"@electron-toolkit/eslint-config-prettier": "^2.0.0",
|
||||
"@electron-toolkit/eslint-config-ts": "^1.0.1",
|
||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||
|
||||
@ -1839,6 +1839,10 @@ export function isVisionModel(model: Model): boolean {
|
||||
return VISION_REGEX.test(model.id) || model.type?.includes('vision') || false
|
||||
}
|
||||
|
||||
export function isOpenAIoSeries(model: Model): boolean {
|
||||
return ['o1', 'o1-2024-12-17'].includes(model.id) || model.id.includes('o3')
|
||||
}
|
||||
|
||||
export function isReasoningModel(model: Model): boolean {
|
||||
if (!model) {
|
||||
return false
|
||||
@ -1848,6 +1852,10 @@ export function isReasoningModel(model: Model): boolean {
|
||||
return REASONING_REGEX.test(model.name) || model.type?.includes('reasoning') || false
|
||||
}
|
||||
|
||||
if (model.id.includes('claude-3-7-sonnet') || model.id.includes('claude-3.7-sonnet') || isOpenAIoSeries(model)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return REASONING_REGEX.test(model.id) || model.type?.includes('reasoning') || false
|
||||
}
|
||||
|
||||
|
||||
@ -52,7 +52,7 @@
|
||||
"settings.reasoning_effort.low": "low",
|
||||
"settings.reasoning_effort.medium": "medium",
|
||||
"settings.reasoning_effort.off": "off",
|
||||
"settings.reasoning_effort.tip": "Only supports reasoning models",
|
||||
"settings.reasoning_effort.tip": "Only supports OpenAI o-series and Anthropic reasoning models",
|
||||
"title": "Assistants"
|
||||
},
|
||||
"auth": {
|
||||
|
||||
@ -52,7 +52,7 @@
|
||||
"settings.reasoning_effort.low": "短い",
|
||||
"settings.reasoning_effort.medium": "中程度",
|
||||
"settings.reasoning_effort.off": "オフ",
|
||||
"settings.reasoning_effort.tip": "この設定は推論モデルのみサポートしています",
|
||||
"settings.reasoning_effort.tip": "OpenAIのoシリーズとAnthropicの推論モデルのみサポートしています",
|
||||
"title": "アシスタント"
|
||||
},
|
||||
"auth": {
|
||||
|
||||
@ -52,7 +52,7 @@
|
||||
"settings.reasoning_effort.low": "Короткая",
|
||||
"settings.reasoning_effort.medium": "Средняя",
|
||||
"settings.reasoning_effort.off": "Выключено",
|
||||
"settings.reasoning_effort.tip": "Эта настройка поддерживается только моделями с рассуждением",
|
||||
"settings.reasoning_effort.tip": "Поддерживается только моделями с рассуждением OpenAI o-series и Anthropic",
|
||||
"title": "Ассистенты"
|
||||
},
|
||||
"auth": {
|
||||
|
||||
@ -52,7 +52,7 @@
|
||||
"settings.reasoning_effort.low": "短",
|
||||
"settings.reasoning_effort.medium": "中",
|
||||
"settings.reasoning_effort.off": "关",
|
||||
"settings.reasoning_effort.tip": "该设置仅支持推理模型",
|
||||
"settings.reasoning_effort.tip": "仅支持 OpenAI o-series 和 Anthropic 推理模型",
|
||||
"title": "助手"
|
||||
},
|
||||
"auth": {
|
||||
|
||||
@ -52,7 +52,7 @@
|
||||
"settings.reasoning_effort.low": "短",
|
||||
"settings.reasoning_effort.medium": "中",
|
||||
"settings.reasoning_effort.off": "關",
|
||||
"settings.reasoning_effort.tip": "該設置僅支持推理模型",
|
||||
"settings.reasoning_effort.tip": "僅支持OpenAI o系列和Anthropic推理模型",
|
||||
"title": "助手"
|
||||
},
|
||||
"auth": {
|
||||
|
||||
@ -32,7 +32,7 @@ import {
|
||||
} from '@renderer/store/settings'
|
||||
import { Assistant, AssistantSettings, ThemeMode, TranslateLanguageVarious } from '@renderer/types'
|
||||
import { modalConfirm } from '@renderer/utils'
|
||||
import { Col, InputNumber, Row, Select, Slider, Switch, Tooltip } from 'antd'
|
||||
import { Col, InputNumber, Row, Segmented, Select, Slider, Switch, Tooltip } from 'antd'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
@ -51,6 +51,7 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
const [maxTokens, setMaxTokens] = useState(assistant?.settings?.maxTokens ?? 0)
|
||||
const [fontSizeValue, setFontSizeValue] = useState(fontSize)
|
||||
const [streamOutput, setStreamOutput] = useState(assistant?.settings?.streamOutput ?? true)
|
||||
const [reasoningEffort, setReasoningEffort] = useState(assistant?.settings?.reasoning_effort)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
@ -96,9 +97,14 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
const onReasoningEffortChange = (value) => {
|
||||
updateAssistantSettings({ reasoning_effort: value })
|
||||
}
|
||||
|
||||
const onReset = () => {
|
||||
setTemperature(DEFAULT_TEMPERATURE)
|
||||
setContextCount(DEFAULT_CONTEXTCOUNT)
|
||||
setReasoningEffort(undefined)
|
||||
updateAssistant({
|
||||
...assistant,
|
||||
settings: {
|
||||
@ -109,6 +115,7 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
maxTokens: DEFAULT_MAX_TOKENS,
|
||||
streamOutput: true,
|
||||
hideMessages: false,
|
||||
reasoning_effort: undefined,
|
||||
customParameters: []
|
||||
}
|
||||
})
|
||||
@ -120,6 +127,7 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
setEnableMaxTokens(assistant?.settings?.enableMaxTokens ?? false)
|
||||
setMaxTokens(assistant?.settings?.maxTokens ?? DEFAULT_MAX_TOKENS)
|
||||
setStreamOutput(assistant?.settings?.streamOutput ?? true)
|
||||
setReasoningEffort(assistant?.settings?.reasoning_effort)
|
||||
}, [assistant])
|
||||
|
||||
return (
|
||||
@ -223,6 +231,45 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
<SettingDivider />
|
||||
<Row align="middle">
|
||||
<Label>{t('assistants.settings.reasoning_effort')}</Label>
|
||||
<Tooltip title={t('assistants.settings.reasoning_effort.tip')}>
|
||||
<QuestionIcon />
|
||||
</Tooltip>
|
||||
</Row>
|
||||
<Row align="middle" gutter={10}>
|
||||
<Col span={24}>
|
||||
<SegmentedContainer>
|
||||
<Segmented<'low' | 'medium' | 'high' | undefined>
|
||||
value={reasoningEffort}
|
||||
onChange={(value) => {
|
||||
setReasoningEffort(value)
|
||||
onReasoningEffortChange(value)
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
value: 'low',
|
||||
label: t('assistants.settings.reasoning_effort.low')
|
||||
},
|
||||
{
|
||||
value: 'medium',
|
||||
label: t('assistants.settings.reasoning_effort.medium')
|
||||
},
|
||||
{
|
||||
value: 'high',
|
||||
label: t('assistants.settings.reasoning_effort.high')
|
||||
},
|
||||
{
|
||||
value: undefined,
|
||||
label: t('assistants.settings.reasoning_effort.off')
|
||||
}
|
||||
]}
|
||||
block
|
||||
/>
|
||||
</SegmentedContainer>
|
||||
</Col>
|
||||
</Row>
|
||||
</SettingGroup>
|
||||
<SettingGroup>
|
||||
<SettingSubtitle style={{ marginTop: 0 }}>{t('settings.messages.title')}</SettingSubtitle>
|
||||
@ -485,4 +532,24 @@ export const SettingGroup = styled.div<{ theme?: ThemeMode }>`
|
||||
margin-bottom: 10px;
|
||||
`
|
||||
|
||||
// Define the styled component with hover state styling
|
||||
const SegmentedContainer = styled.div`
|
||||
.ant-segmented-item {
|
||||
font-size: 12px;
|
||||
}
|
||||
.ant-segmented-item-selected {
|
||||
background-color: var(--color-primary) !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.ant-segmented-item:hover:not(.ant-segmented-item-selected) {
|
||||
background-color: var(--color-primary-bg) !important;
|
||||
color: var(--color-primary) !important;
|
||||
}
|
||||
|
||||
.ant-segmented-thumb {
|
||||
background-color: var(--color-primary) !important;
|
||||
}
|
||||
`
|
||||
|
||||
export default SettingsTab
|
||||
|
||||
@ -154,6 +154,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
|
||||
setMaxTokens(0)
|
||||
setStreamOutput(true)
|
||||
setTopP(1)
|
||||
setReasoningEffort(undefined)
|
||||
setCustomParameters([])
|
||||
updateAssistantSettings({
|
||||
temperature: DEFAULT_TEMPERATURE,
|
||||
@ -162,6 +163,7 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
|
||||
maxTokens: 0,
|
||||
streamOutput: true,
|
||||
topP: 1,
|
||||
reasoning_effort: undefined,
|
||||
customParameters: []
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import Anthropic from '@anthropic-ai/sdk'
|
||||
import { MessageCreateParamsNonStreaming, MessageParam } from '@anthropic-ai/sdk/resources'
|
||||
import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant'
|
||||
import { isReasoningModel } from '@renderer/config/models'
|
||||
import { getStoreSetting } from '@renderer/hooks/useSettings'
|
||||
import i18n from '@renderer/i18n'
|
||||
import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/AssistantService'
|
||||
@ -18,7 +19,11 @@ export default class AnthropicProvider extends BaseProvider {
|
||||
|
||||
constructor(provider: Provider) {
|
||||
super(provider)
|
||||
this.sdk = new Anthropic({ apiKey: this.apiKey, baseURL: this.getBaseURL() })
|
||||
this.sdk = new Anthropic({
|
||||
apiKey: this.apiKey,
|
||||
baseURL: this.getBaseURL(),
|
||||
dangerouslyAllowBrowser: true
|
||||
})
|
||||
}
|
||||
|
||||
public getBaseURL(): string {
|
||||
@ -60,6 +65,47 @@ export default class AnthropicProvider extends BaseProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private getTemperature(assistant: Assistant, model: Model) {
|
||||
if (isReasoningModel(model)) return undefined
|
||||
|
||||
return assistant?.settings?.temperature
|
||||
}
|
||||
|
||||
private getTopP(assistant: Assistant, model: Model) {
|
||||
if (isReasoningModel(model)) return undefined
|
||||
|
||||
return assistant?.settings?.topP
|
||||
}
|
||||
|
||||
private getReasoningEffort(assistant: Assistant, model: Model) {
|
||||
if (isReasoningModel(model)) {
|
||||
const effort_ratio =
|
||||
assistant?.settings?.reasoning_effort === 'high'
|
||||
? 0.8
|
||||
: assistant?.settings?.reasoning_effort === 'medium'
|
||||
? 0.5
|
||||
: assistant?.settings?.reasoning_effort === 'low'
|
||||
? 0.2
|
||||
: undefined
|
||||
if (!effort_ratio)
|
||||
return {
|
||||
type: 'disabled'
|
||||
}
|
||||
|
||||
if (model.id.includes('claude-3.7-sonnet') || model.id.includes('claude-3-7-sonnet')) {
|
||||
return {
|
||||
type: 'enabled',
|
||||
budget_tokens: Math.max(
|
||||
Math.min((assistant?.settings?.maxTokens || DEFAULT_MAX_TOKENS) * effort_ratio, 32000),
|
||||
1024
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
public async completions({ messages, assistant, onChunk, onFilterMessages }: CompletionsParams) {
|
||||
const defaultModel = getDefaultModel()
|
||||
const model = assistant.model || defaultModel
|
||||
@ -84,20 +130,43 @@ export default class AnthropicProvider extends BaseProvider {
|
||||
model: model.id,
|
||||
messages: userMessages,
|
||||
max_tokens: maxTokens || DEFAULT_MAX_TOKENS,
|
||||
temperature: assistant?.settings?.temperature,
|
||||
top_p: assistant?.settings?.topP,
|
||||
temperature: this.getTemperature(assistant, model),
|
||||
top_p: this.getTopP(assistant, model),
|
||||
system: assistant.prompt,
|
||||
...this.getCustomParameters(assistant)
|
||||
}
|
||||
|
||||
if (isReasoningModel(model)) {
|
||||
;(body as any).thinking = this.getReasoningEffort(assistant, model)
|
||||
;(body as any).betas = ['output-128k-2025-02-19']
|
||||
}
|
||||
|
||||
let time_first_token_millsec = 0
|
||||
let time_first_content_millsec = 0
|
||||
const start_time_millsec = new Date().getTime()
|
||||
|
||||
if (!streamOutput) {
|
||||
const message = await this.sdk.messages.create({ ...body, stream: false })
|
||||
const time_completion_millsec = new Date().getTime() - start_time_millsec
|
||||
|
||||
let text = ''
|
||||
let reasoning_content = ''
|
||||
|
||||
if (message.content && message.content.length > 0) {
|
||||
const thinkingBlock = message.content.find((block) => block.type === 'thinking')
|
||||
const textBlock = message.content.find((block) => block.type === 'text')
|
||||
|
||||
if (thinkingBlock && 'thinking' in thinkingBlock) {
|
||||
reasoning_content = thinkingBlock.thinking
|
||||
}
|
||||
|
||||
if (textBlock && 'text' in textBlock) {
|
||||
text = textBlock.text
|
||||
}
|
||||
}
|
||||
return onChunk({
|
||||
text: message.content[0].type === 'text' ? message.content[0].text : '',
|
||||
text,
|
||||
reasoning_content,
|
||||
usage: message.usage,
|
||||
metrics: {
|
||||
completion_tokens: message.usage.output_tokens,
|
||||
@ -113,6 +182,7 @@ export default class AnthropicProvider extends BaseProvider {
|
||||
const { signal } = abortController
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let hasThinkingContent = false
|
||||
const stream = this.sdk.messages
|
||||
.stream({ ...body, stream: true }, { signal })
|
||||
.on('text', (text) => {
|
||||
@ -123,9 +193,34 @@ export default class AnthropicProvider extends BaseProvider {
|
||||
if (time_first_token_millsec == 0) {
|
||||
time_first_token_millsec = new Date().getTime() - start_time_millsec
|
||||
}
|
||||
|
||||
if (hasThinkingContent && time_first_content_millsec === 0) {
|
||||
time_first_content_millsec = new Date().getTime()
|
||||
}
|
||||
|
||||
const time_thinking_millsec = time_first_content_millsec ? time_first_content_millsec - start_time_millsec : 0
|
||||
|
||||
const time_completion_millsec = new Date().getTime() - start_time_millsec
|
||||
onChunk({
|
||||
text,
|
||||
metrics: {
|
||||
completion_tokens: undefined,
|
||||
time_completion_millsec,
|
||||
time_first_token_millsec,
|
||||
time_thinking_millsec
|
||||
}
|
||||
})
|
||||
})
|
||||
.on('thinking', (thinking) => {
|
||||
hasThinkingContent = true
|
||||
if (time_first_token_millsec == 0) {
|
||||
time_first_token_millsec = new Date().getTime() - start_time_millsec
|
||||
}
|
||||
|
||||
const time_completion_millsec = new Date().getTime() - start_time_millsec
|
||||
onChunk({
|
||||
reasoning_content: thinking,
|
||||
text: '',
|
||||
metrics: {
|
||||
completion_tokens: undefined,
|
||||
time_completion_millsec,
|
||||
@ -134,6 +229,8 @@ export default class AnthropicProvider extends BaseProvider {
|
||||
})
|
||||
})
|
||||
.on('finalMessage', (message) => {
|
||||
const time_completion_millsec = new Date().getTime() - start_time_millsec
|
||||
const time_thinking_millsec = time_first_content_millsec ? time_first_content_millsec - start_time_millsec : 0
|
||||
onChunk({
|
||||
text: '',
|
||||
usage: {
|
||||
@ -143,8 +240,9 @@ export default class AnthropicProvider extends BaseProvider {
|
||||
},
|
||||
metrics: {
|
||||
completion_tokens: message.usage.output_tokens,
|
||||
time_completion_millsec: new Date().getTime() - start_time_millsec,
|
||||
time_first_token_millsec
|
||||
time_completion_millsec,
|
||||
time_first_token_millsec,
|
||||
time_thinking_millsec
|
||||
}
|
||||
})
|
||||
resolve()
|
||||
|
||||
@ -1,4 +1,11 @@
|
||||
import { getOpenAIWebSearchParams, isReasoningModel, isSupportedModel, isVisionModel } from '@renderer/config/models'
|
||||
import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant'
|
||||
import {
|
||||
getOpenAIWebSearchParams,
|
||||
isOpenAIoSeries,
|
||||
isReasoningModel,
|
||||
isSupportedModel,
|
||||
isVisionModel
|
||||
} from '@renderer/config/models'
|
||||
import { getStoreSetting } from '@renderer/hooks/useSettings'
|
||||
import i18n from '@renderer/i18n'
|
||||
import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/AssistantService'
|
||||
@ -156,11 +163,48 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
}
|
||||
|
||||
if (isReasoningModel(model)) {
|
||||
if (model.provider === 'openrouter') {
|
||||
return {
|
||||
reasoning: {
|
||||
effort: assistant?.settings?.reasoning_effort
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isOpenAIoSeries(model)) {
|
||||
return {
|
||||
reasoning_effort: assistant?.settings?.reasoning_effort
|
||||
}
|
||||
}
|
||||
|
||||
const effort_ratio =
|
||||
assistant?.settings?.reasoning_effort === 'high'
|
||||
? 0.8
|
||||
: assistant?.settings?.reasoning_effort === 'medium'
|
||||
? 0.5
|
||||
: assistant?.settings?.reasoning_effort === 'low'
|
||||
? 0.2
|
||||
: undefined
|
||||
|
||||
if (model.id.includes('claude-3.7-sonnet') || model.id.includes('claude-3-7-sonnet')) {
|
||||
if (!effort_ratio) {
|
||||
return {
|
||||
type: 'disabled'
|
||||
}
|
||||
}
|
||||
return {
|
||||
thinking: {
|
||||
budget_tokens: Math.max(
|
||||
Math.min((assistant?.settings?.maxTokens || DEFAULT_MAX_TOKENS) * effort_ratio, 32000),
|
||||
1024
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
@ -175,7 +219,7 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
|
||||
let systemMessage = assistant.prompt ? { role: 'system', content: assistant.prompt } : undefined
|
||||
|
||||
if (['o1', 'o1-2024-12-17'].includes(model.id) || model.id.startsWith('o3')) {
|
||||
if (isOpenAIoSeries(model)) {
|
||||
systemMessage = {
|
||||
role: 'developer',
|
||||
content: `Formatting re-enabled${systemMessage ? '\n' + systemMessage.content : ''}`
|
||||
@ -212,6 +256,7 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
delta: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta & {
|
||||
reasoning_content?: string
|
||||
reasoning?: string
|
||||
thinking?: string
|
||||
}
|
||||
) => {
|
||||
if (!delta?.content) return false
|
||||
@ -226,7 +271,7 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
}
|
||||
|
||||
// 如果有reasoning_content或reasoning,说明是在思考中
|
||||
if (delta?.reasoning_content || delta?.reasoning) {
|
||||
if (delta?.reasoning_content || delta?.reasoning || delta?.thinking) {
|
||||
hasReasoningContent = true
|
||||
}
|
||||
|
||||
|
||||
13
yarn.lock
13
yarn.lock
@ -110,9 +110,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@anthropic-ai/sdk@npm:^0.24.3":
|
||||
version: 0.24.3
|
||||
resolution: "@anthropic-ai/sdk@npm:0.24.3"
|
||||
"@anthropic-ai/sdk@npm:^0.38.0":
|
||||
version: 0.38.0
|
||||
resolution: "@anthropic-ai/sdk@npm:0.38.0"
|
||||
dependencies:
|
||||
"@types/node": "npm:^18.11.18"
|
||||
"@types/node-fetch": "npm:^2.6.4"
|
||||
@ -121,8 +121,7 @@ __metadata:
|
||||
form-data-encoder: "npm:1.7.2"
|
||||
formdata-node: "npm:^4.3.2"
|
||||
node-fetch: "npm:^2.6.7"
|
||||
web-streams-polyfill: "npm:^3.2.1"
|
||||
checksum: 10c0/1c73c3df9637522da548d2cddfaf89513dac935c5cdb7c0b3db1c427c069a0de76df935bd189e477822063e9f944360e2d059827d5be4dca33bd388c61e97a30
|
||||
checksum: 10c0/accd003cbe314d32d4d36f5fd7fd743c32e2a896c9ea57190966eda20b8c46e00f542bf03ec3603d1274a7ac18e902bed4158ff5980e4e248a6d5c75e3fd891a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -2996,7 +2995,7 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "CherryStudio@workspace:."
|
||||
dependencies:
|
||||
"@anthropic-ai/sdk": "npm:^0.24.3"
|
||||
"@anthropic-ai/sdk": "npm:^0.38.0"
|
||||
"@electron-toolkit/eslint-config-prettier": "npm:^2.0.0"
|
||||
"@electron-toolkit/eslint-config-ts": "npm:^1.0.1"
|
||||
"@electron-toolkit/preload": "npm:^3.0.0"
|
||||
@ -14363,7 +14362,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"web-streams-polyfill@npm:^3.0.3, web-streams-polyfill@npm:^3.2.1":
|
||||
"web-streams-polyfill@npm:^3.0.3":
|
||||
version: 3.3.3
|
||||
resolution: "web-streams-polyfill@npm:3.3.3"
|
||||
checksum: 10c0/64e855c47f6c8330b5436147db1c75cb7e7474d924166800e8e2aab5eb6c76aac4981a84261dd2982b3e754490900b99791c80ae1407a9fa0dcff74f82ea3a7f
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user