mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-17 11:37:20 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c1c6a134b | ||
|
|
e0aa270328 | ||
|
|
6ab537ad4e | ||
|
|
6d7a4e32fe | ||
|
|
eb2c236699 | ||
|
|
86a655fb5d | ||
|
|
c50e7d0606 | ||
|
|
4ec7b4289f | ||
|
|
6bebe37bfc |
+1
-1
@@ -8,7 +8,7 @@ info:
|
||||
description: "Cursor助手"
|
||||
copyright: "© 2026, Cursor助手"
|
||||
comments: "Cursor助手"
|
||||
version: "0.0.38"
|
||||
version: "0.0.40"
|
||||
|
||||
dev_mode:
|
||||
root_path: .
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.0.38</string>
|
||||
<string>0.0.39</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.0.38</string>
|
||||
<string>0.0.39</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0.0</string>
|
||||
<key>LSUIElement</key>
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.0.38</string>
|
||||
<string>0.0.39</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.0.38</string>
|
||||
<string>0.0.39</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0.0</string>
|
||||
<key>LSUIElement</key>
|
||||
|
||||
+28
-15
@@ -6,7 +6,7 @@
|
||||
name: "Cursor助手"
|
||||
arch: ${GOARCH}
|
||||
platform: "linux"
|
||||
version: "0.0.38"
|
||||
version: "0.0.39"
|
||||
section: "default"
|
||||
priority: "extra"
|
||||
maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}>
|
||||
@@ -24,24 +24,24 @@ contents:
|
||||
- src: "./build/linux/Cursor助手.desktop"
|
||||
dst: "/usr/share/applications/Cursor助手.desktop"
|
||||
|
||||
# Default dependencies for Debian 12/Ubuntu 22.04+ with WebKit 4.1
|
||||
# Default dependencies for the GTK4 + WebKitGTK 6.0 stack (Ubuntu 24.04+ / Debian 13+)
|
||||
depends:
|
||||
- libgtk-3-0
|
||||
- libwebkit2gtk-4.1-0
|
||||
- libgtk-4-1
|
||||
- libwebkitgtk-6.0-4
|
||||
|
||||
# Distribution-specific overrides for different package formats and WebKit versions
|
||||
# Distribution-specific overrides for different package formats
|
||||
overrides:
|
||||
# RPM packages for RHEL/CentOS/AlmaLinux/Rocky Linux (WebKit 4.0)
|
||||
# RPM packages for Fedora / RHEL / AlmaLinux / Rocky Linux
|
||||
rpm:
|
||||
depends:
|
||||
- gtk3
|
||||
- webkit2gtk4.1
|
||||
|
||||
# Arch Linux packages (WebKit 4.1)
|
||||
- gtk4
|
||||
- webkitgtk6.0
|
||||
|
||||
# Arch Linux packages
|
||||
archlinux:
|
||||
depends:
|
||||
- gtk3
|
||||
- webkit2gtk-4.1
|
||||
- gtk4
|
||||
- webkitgtk-6.0
|
||||
|
||||
# scripts section to ensure desktop database is updated after install
|
||||
scripts:
|
||||
@@ -50,13 +50,26 @@ scripts:
|
||||
# preremove: "./build/linux/nfpm/scripts/preremove.sh"
|
||||
# postremove: "./build/linux/nfpm/scripts/postremove.sh"
|
||||
|
||||
# If you build your app with -tags gtk3 (legacy WebKit2GTK 4.1 stack — supported through v3.0.x, removed in v3.1),
|
||||
# replace the depends/overrides above with these:
|
||||
#
|
||||
# depends:
|
||||
# - libgtk-3-0
|
||||
# - libwebkit2gtk-4.1-0
|
||||
# overrides:
|
||||
# rpm:
|
||||
# depends:
|
||||
# - gtk3
|
||||
# - webkit2gtk4.1
|
||||
# archlinux:
|
||||
# depends:
|
||||
# - gtk3
|
||||
# - webkit2gtk-4.1
|
||||
#
|
||||
# replaces:
|
||||
# - foobar
|
||||
# provides:
|
||||
# - bar
|
||||
# depends:
|
||||
# - gtk3
|
||||
# - libwebkit2gtk
|
||||
# recommends:
|
||||
# - whatever
|
||||
# suggests:
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fixed": {
|
||||
"file_version": "0.0.38"
|
||||
"file_version": "0.0.39"
|
||||
},
|
||||
"info": {
|
||||
"0000": {
|
||||
"ProductVersion": "0.0.38",
|
||||
"ProductVersion": "0.0.39",
|
||||
"CompanyName": "Cursor助手",
|
||||
"FileDescription": "Cursor助手",
|
||||
"LegalCopyright": "© 2026, Cursor助手",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
!define INFO_PRODUCTNAME "Cursor助手"
|
||||
!endif
|
||||
!ifndef INFO_PRODUCTVERSION
|
||||
!define INFO_PRODUCTVERSION "0.0.38"
|
||||
!define INFO_PRODUCTVERSION "0.0.39"
|
||||
!endif
|
||||
!ifndef INFO_COPYRIGHT
|
||||
!define INFO_COPYRIGHT "© 2026, Cursor助手"
|
||||
@@ -27,8 +27,16 @@
|
||||
!endif
|
||||
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}"
|
||||
|
||||
!ifndef WAILS_INSTALL_SCOPE
|
||||
!define WAILS_INSTALL_SCOPE "machine"
|
||||
!endif
|
||||
|
||||
!ifndef REQUEST_EXECUTION_LEVEL
|
||||
!define REQUEST_EXECUTION_LEVEL "admin"
|
||||
!if "${WAILS_INSTALL_SCOPE}" == "user"
|
||||
!define REQUEST_EXECUTION_LEVEL "user"
|
||||
!else
|
||||
!define REQUEST_EXECUTION_LEVEL "admin"
|
||||
!endif
|
||||
!endif
|
||||
|
||||
RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
|
||||
@@ -115,23 +123,40 @@ RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
|
||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||
|
||||
SetRegView 64
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
|
||||
!if "${WAILS_INSTALL_SCOPE}" == "user"
|
||||
WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}"
|
||||
WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}"
|
||||
WriteRegStr HKCU "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}"
|
||||
WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}"
|
||||
WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
|
||||
WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
|
||||
|
||||
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
|
||||
IntFmt $0 "0x%08X" $0
|
||||
WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0"
|
||||
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
|
||||
IntFmt $0 "0x%08X" $0
|
||||
WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0"
|
||||
!else
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}"
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
|
||||
WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
|
||||
|
||||
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
|
||||
IntFmt $0 "0x%08X" $0
|
||||
WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0"
|
||||
!endif
|
||||
!macroend
|
||||
|
||||
!macro wails.deleteUninstaller
|
||||
Delete "$INSTDIR\uninstall.exe"
|
||||
|
||||
SetRegView 64
|
||||
DeleteRegKey HKLM "${UNINST_KEY}"
|
||||
!if "${WAILS_INSTALL_SCOPE}" == "user"
|
||||
DeleteRegKey HKCU "${UNINST_KEY}"
|
||||
!else
|
||||
DeleteRegKey HKLM "${UNINST_KEY}"
|
||||
!endif
|
||||
!macroend
|
||||
|
||||
!macro wails.setShellContext
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<assemblyIdentity type="win32" name="com.cursor.wuxianxubei" version="0.0.38" processorArchitecture="*"/>
|
||||
<assemblyIdentity type="win32" name="com.cursor.wuxianxubei" version="0.0.39" processorArchitecture="*"/>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
createEmptyModelAdapter,
|
||||
normalizeModelAdapter,
|
||||
OPENAI_ENDPOINT_CHAT_COMPLETIONS,
|
||||
OPENAI_ENDPOINT_CUSTOM,
|
||||
OPENAI_ENDPOINT_RESPONSES,
|
||||
OPENAI_EXTRA_PARAMS_DEFAULT_JSON,
|
||||
} from "@/state/appState";
|
||||
@@ -22,6 +23,7 @@ const reasoningEffortOptions = [
|
||||
{ label: "中", value: "medium", icon: "icon-[mdi--head-lightbulb-outline]" },
|
||||
{ label: "高", value: "high", icon: "icon-[mdi--brain]" },
|
||||
{ label: "极高", value: "xhigh", icon: "icon-[mdi--head-cog-outline]" },
|
||||
{ label: "最高", value: "max", icon: "icon-[mdi--brain]" },
|
||||
];
|
||||
|
||||
const anthropicThinkingEffortOptions = [
|
||||
@@ -35,10 +37,11 @@ const anthropicThinkingEffortOptions = [
|
||||
const openAIEndpointOptions = [
|
||||
{ label: "/v1/responses", value: OPENAI_ENDPOINT_RESPONSES, icon: "icon-[mdi--api]" },
|
||||
{ label: "/v1/chat/completions", value: OPENAI_ENDPOINT_CHAT_COMPLETIONS, icon: "icon-[mdi--message-text-outline]" },
|
||||
{ label: "自定义路径", value: OPENAI_ENDPOINT_CUSTOM, icon: "icon-[mdi--pencil-outline]" },
|
||||
];
|
||||
|
||||
const fieldTips = {
|
||||
openAIExtraParams: "开启后会把 JSON 对象合并到 OpenAI 请求体。OpenAI service_tier 支持 auto、default、flex、scale、priority;priority 可用于高优先级/Fast 类场景。",
|
||||
openAIExtraParams: "开启后会把 JSON 对象覆盖到 OpenAI 请求体。同名字段以这里为准。OpenAI service_tier 支持 auto、default、flex、scale、priority;priority 可用于高优先级/Fast 类场景。",
|
||||
};
|
||||
|
||||
const props = defineProps({
|
||||
|
||||
@@ -274,18 +274,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"1b7d24b212e52c54": {
|
||||
"source": "{0} 的推理强度仅支持 low、medium、high、xhigh",
|
||||
"kind": "template",
|
||||
"placeholders": 1,
|
||||
"refs": [
|
||||
{
|
||||
"file": "src/state/appState.js",
|
||||
"line": 315,
|
||||
"column": 14
|
||||
}
|
||||
]
|
||||
},
|
||||
"1bc77f5ab979f4c1": {
|
||||
"source": "新增模型配置",
|
||||
"kind": "text",
|
||||
@@ -1175,6 +1163,23 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"8672864e90417138": {
|
||||
"source": "最高",
|
||||
"kind": "text",
|
||||
"placeholders": 0,
|
||||
"refs": [
|
||||
{
|
||||
"file": "src/components/ModelAdapterModal.vue",
|
||||
"line": 26,
|
||||
"column": 12
|
||||
},
|
||||
{
|
||||
"file": "src/views/ModelEditor.vue",
|
||||
"line": 41,
|
||||
"column": 12
|
||||
}
|
||||
]
|
||||
},
|
||||
"86df7ec743047234": {
|
||||
"source": "服务运行中",
|
||||
"kind": "text",
|
||||
@@ -2110,6 +2115,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"dbb4b5be9b5723dc": {
|
||||
"source": "{0} 的推理强度仅支持 low、medium、high、xhigh、max",
|
||||
"kind": "template",
|
||||
"placeholders": 1,
|
||||
"refs": [
|
||||
{
|
||||
"file": "src/state/appState.js",
|
||||
"line": 435,
|
||||
"column": 14
|
||||
}
|
||||
]
|
||||
},
|
||||
"dbee6e7139243362": {
|
||||
"source": "{0} 的接口地址不能为空",
|
||||
"kind": "template",
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
"15d124b200ddabed": "Maximum number of context tokens the model can accept in a single request. Leave blank to use the default.",
|
||||
"18b7312022cd1840": "Start Service",
|
||||
"1af38868896cf53d": "Routing mode only supports local or upstream",
|
||||
"1b7d24b212e52c54": "{0} reasoning effort only supports low, medium, high, and xhigh",
|
||||
"1bc77f5ab979f4c1": "Add Model Settings",
|
||||
"1e238093b79b3165": "Uses 65536 by default when left blank",
|
||||
"24343a2096988d42": "Failed to open",
|
||||
@@ -84,6 +83,7 @@
|
||||
"7e9e334aeb0bdc07": "Service operation failed",
|
||||
"7f68ebad19ba6bcd": "Check for Updates",
|
||||
"81123c56d5d880d0": "API Key",
|
||||
"8672864e90417138": "Max",
|
||||
"86df7ec743047234": "Service running",
|
||||
"87ed126f7bd1121e": "Routing Mode",
|
||||
"899add6275682210": "Uses 200000 by default when left blank",
|
||||
@@ -154,6 +154,7 @@
|
||||
"d7da2aabd35772ec": "e.g. 200000 (leave blank to use the default)",
|
||||
"da590a8fe3ce4de0": "Please select",
|
||||
"daede9881787abe7": "Notes",
|
||||
"dbb4b5be9b5723dc": "{0} reasoning effort only supports low, medium, high, xhigh, and max",
|
||||
"dbee6e7139243362": "{0} base URL cannot be empty",
|
||||
"dc82c5e8fb2ab777": "Version: v{0}",
|
||||
"de8184da1ef88d03": "Configured",
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
"15d124b200ddabed": "モデルが1回のリクエストで受け取れる最大コンテキスト Token 数。空欄の場合はデフォルト値を使用します。",
|
||||
"18b7312022cd1840": "サービスを開始",
|
||||
"1af38868896cf53d": "ルーティングモードは local または upstream のみサポートします",
|
||||
"1b7d24b212e52c54": "{0} の推論強度は low、medium、high、xhigh のみサポートします",
|
||||
"1bc77f5ab979f4c1": "モデル設定を追加",
|
||||
"1e238093b79b3165": "空欄で 65536",
|
||||
"24343a2096988d42": "開けませんでした",
|
||||
@@ -84,6 +83,7 @@
|
||||
"7e9e334aeb0bdc07": "サービス操作に失敗しました",
|
||||
"7f68ebad19ba6bcd": "アップデートを確認",
|
||||
"81123c56d5d880d0": "API キー",
|
||||
"8672864e90417138": "最大",
|
||||
"86df7ec743047234": "サービス稼働中",
|
||||
"87ed126f7bd1121e": "ルーティングモード",
|
||||
"899add6275682210": "空欄で 200000",
|
||||
@@ -154,6 +154,7 @@
|
||||
"d7da2aabd35772ec": "例: 200000(空欄でデフォルト値)",
|
||||
"da590a8fe3ce4de0": "選択してください",
|
||||
"daede9881787abe7": "メモ",
|
||||
"dbb4b5be9b5723dc": "{0} の推論強度は low、medium、high、xhigh、max のみサポートします",
|
||||
"dbee6e7139243362": "{0} のベース URL は必須です",
|
||||
"dc82c5e8fb2ab777": "バージョン: v{0}",
|
||||
"de8184da1ef88d03": "設定済み",
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
"15d124b200ddabed": "模型单次可接受的最大上下文 Token 数。留空时使用默认值。",
|
||||
"18b7312022cd1840": "启动服务",
|
||||
"1af38868896cf53d": "运行模式仅支持 local 或 upstream",
|
||||
"1b7d24b212e52c54": "{0} 的推理强度仅支持 low、medium、high、xhigh",
|
||||
"1bc77f5ab979f4c1": "新增模型配置",
|
||||
"1e238093b79b3165": "留空时默认 65536",
|
||||
"24343a2096988d42": "打开失败",
|
||||
@@ -84,6 +83,7 @@
|
||||
"7e9e334aeb0bdc07": "服务操作失败",
|
||||
"7f68ebad19ba6bcd": "检查更新",
|
||||
"81123c56d5d880d0": "访问密钥",
|
||||
"8672864e90417138": "最高",
|
||||
"86df7ec743047234": "服务运行中",
|
||||
"87ed126f7bd1121e": "运行模式",
|
||||
"899add6275682210": "留空时默认 200000",
|
||||
@@ -154,6 +154,7 @@
|
||||
"d7da2aabd35772ec": "例如:200000(留空用默认值)",
|
||||
"da590a8fe3ce4de0": "请选择",
|
||||
"daede9881787abe7": "备注",
|
||||
"dbb4b5be9b5723dc": "{0} 的推理强度仅支持 low、medium、high、xhigh、max",
|
||||
"dbee6e7139243362": "{0} 的接口地址不能为空",
|
||||
"dc82c5e8fb2ab777": "版本:v{0}",
|
||||
"de8184da1ef88d03": "已配置",
|
||||
|
||||
@@ -22,11 +22,12 @@ import {
|
||||
const APP_STATE_STORAGE_KEY = "cursor-client:runtime-state:v2";
|
||||
const GENERIC_SERVICE_ERROR = "服务错误";
|
||||
const SUPPORTED_MODEL_ADAPTER_TYPES = new Set(["openai", "anthropic"]);
|
||||
const SUPPORTED_REASONING_EFFORTS = new Set(["low", "medium", "high", "xhigh"]);
|
||||
const SUPPORTED_REASONING_EFFORTS = new Set(["low", "medium", "high", "xhigh", "max"]);
|
||||
const SUPPORTED_ANTHROPIC_THINKING_EFFORTS = new Set(["low", "medium", "high", "xhigh", "max"]);
|
||||
export const ANTHROPIC_THINKING_EFFORT_DEFAULT = "xhigh";
|
||||
export const OPENAI_ENDPOINT_RESPONSES = "/v1/responses";
|
||||
export const OPENAI_ENDPOINT_CHAT_COMPLETIONS = "/v1/chat/completions";
|
||||
export const OPENAI_ENDPOINT_CUSTOM = "/custom";
|
||||
export const OPENAI_EXTRA_PARAMS_DEFAULT_JSON = `{
|
||||
"service_tier": "priority"
|
||||
}`;
|
||||
@@ -34,7 +35,7 @@ export const EXTRA_PARAMS_DEFAULT_JSON = `{
|
||||
}`;
|
||||
export const CUSTOM_HEADERS_DEFAULT_JSON = `{
|
||||
}`;
|
||||
const SUPPORTED_OPENAI_ENDPOINTS = new Set([OPENAI_ENDPOINT_RESPONSES, OPENAI_ENDPOINT_CHAT_COMPLETIONS]);
|
||||
const SUPPORTED_OPENAI_ENDPOINTS = new Set([OPENAI_ENDPOINT_RESPONSES, OPENAI_ENDPOINT_CHAT_COMPLETIONS, OPENAI_ENDPOINT_CUSTOM]);
|
||||
const SUPPORTED_ROUTE_MODES = new Set(["local", "upstream"]);
|
||||
const PROXY_STATE_EVENT = "proxy:state";
|
||||
const USER_CONFIG_CHANGED_EVENT = "user-config:changed";
|
||||
@@ -282,6 +283,9 @@ export function createEmptyModelAdapter() {
|
||||
};
|
||||
}
|
||||
|
||||
// normalizeOpenAIEndpoint 归一化 endpoint 路径。
|
||||
// 支持三个预设值:/v1/responses、/v1/chat/completions、/custom(自定义路径)。
|
||||
// 选 /custom 时,用户需在接口地址栏填写完整请求 URL。
|
||||
function normalizeOpenAIEndpoint(value) {
|
||||
const text = asString(value).toLowerCase();
|
||||
if (!text) {
|
||||
@@ -290,6 +294,10 @@ function normalizeOpenAIEndpoint(value) {
|
||||
return SUPPORTED_OPENAI_ENDPOINTS.has(text) ? text : "";
|
||||
}
|
||||
|
||||
function isValidOpenAIEndpoint(value) {
|
||||
return normalizeOpenAIEndpoint(value) !== "";
|
||||
}
|
||||
|
||||
function validateJSONObject(value, label) {
|
||||
const text = asString(value);
|
||||
if (!text) {
|
||||
@@ -424,10 +432,10 @@ export function validateModelAdapters(source) {
|
||||
return `${prefix} 的模型标识不能为空`;
|
||||
}
|
||||
if (adapter.type === "openai" && !SUPPORTED_REASONING_EFFORTS.has(adapter.reasoningEffort)) {
|
||||
return `${prefix} 的推理强度仅支持 low、medium、high、xhigh`;
|
||||
return `${prefix} 的推理强度仅支持 low、medium、high、xhigh、max`;
|
||||
}
|
||||
if (adapter.type === "openai" && !SUPPORTED_OPENAI_ENDPOINTS.has(adapter.openAIEndpoint)) {
|
||||
return `${prefix} 的 OpenAI 端点仅支持 /v1/responses 或 /v1/chat/completions`;
|
||||
if (adapter.type === "openai" && !isValidOpenAIEndpoint(adapter.openAIEndpoint)) {
|
||||
return `${prefix} 的 OpenAI 端点仅支持 /v1/responses、/v1/chat/completions 或以 / 开头的自定义路径`;
|
||||
}
|
||||
if (adapter.type === "openai" && adapter.openAIExtraParamsEnabled) {
|
||||
const extraParamsError = validateOpenAIExtraParamsJSON(adapter.openAIExtraParamsJSON);
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
isModelAdapterTestResultStale,
|
||||
normalizeModelAdapter,
|
||||
OPENAI_ENDPOINT_CHAT_COMPLETIONS,
|
||||
OPENAI_ENDPOINT_CUSTOM,
|
||||
OPENAI_ENDPOINT_RESPONSES,
|
||||
OPENAI_EXTRA_PARAMS_DEFAULT_JSON,
|
||||
runModelAdapterTest,
|
||||
@@ -37,6 +38,7 @@ const reasoningEffortOptions = [
|
||||
{ label: "中", value: "medium", icon: "icon-[mdi--head-lightbulb-outline]" },
|
||||
{ label: "高", value: "high", icon: "icon-[mdi--brain]" },
|
||||
{ label: "极高", value: "xhigh", icon: "icon-[mdi--head-cog-outline]" },
|
||||
{ label: "最高", value: "max", icon: "icon-[mdi--brain]" },
|
||||
];
|
||||
|
||||
const anthropicThinkingEffortOptions = [
|
||||
@@ -50,6 +52,7 @@ const anthropicThinkingEffortOptions = [
|
||||
const openAIEndpointOptions = [
|
||||
{ label: "/v1/responses", value: OPENAI_ENDPOINT_RESPONSES, icon: "icon-[mdi--api]" },
|
||||
{ label: "/v1/chat/completions", value: OPENAI_ENDPOINT_CHAT_COMPLETIONS, icon: "icon-[mdi--message-text-outline]" },
|
||||
{ label: "自定义路径(请输入完整请求地址)", value: OPENAI_ENDPOINT_CUSTOM, icon: "icon-[mdi--pencil-outline]" },
|
||||
];
|
||||
|
||||
const editorIndex = ref(-1);
|
||||
@@ -128,7 +131,7 @@ const fieldTips = {
|
||||
contextWindowTokens: "模型单次可接受的最大上下文 Token 数。留空时使用默认值。",
|
||||
reasoningEffort: "推理强度仅对部分支持 reasoning_effort 的模型生效,并不是所有模型都支持。越高通常越稳,但也可能更慢。",
|
||||
maxCompletionTokens: "单次回复允许生成的最大 Token 数。留空时使用默认值。",
|
||||
openAIEndpoint: "OpenAI 兼容接口使用的协议端点。未选择时默认使用 /v1/responses。",
|
||||
openAIEndpoint: "选择接口协议端点。选“自定义路径”时,请在接口地址栏填写完整请求地址(含 /chat/completions 或 /responses 路径后缀),系统会根据末段自动判断协议形态。",
|
||||
openAIExtraParams: "开启后会把 JSON 对象覆盖到 OpenAI 请求体。同名字段以这里为准。OpenAI service_tier 支持 auto、default、flex、scale、priority。",
|
||||
customHeaders: "开启后会把 JSON 对象覆盖到最终请求头。同名请求头以这里为准,值必须是字符串。",
|
||||
anthropicExtraParams: "开启后会把 JSON 对象覆盖到 Anthropic 请求体。同名字段以这里为准。",
|
||||
|
||||
@@ -29,7 +29,7 @@ type openAIRequestBody struct {
|
||||
Tools []json.RawMessage `json:"tools,omitempty"`
|
||||
Messages []map[string]any `json:"messages"`
|
||||
Stream bool `json:"stream"`
|
||||
MaxTokens int `json:"max_tokens"`
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
StreamOptions map[string]any `json:"stream_options"`
|
||||
ReasoningEffort string `json:"reasoning_effort,omitempty"`
|
||||
PromptCacheKey string `json:"prompt_cache_key,omitempty"`
|
||||
@@ -41,7 +41,7 @@ type openAIResponsesRequestBody struct {
|
||||
Input []map[string]any `json:"input"`
|
||||
Tools []map[string]any `json:"tools,omitempty"`
|
||||
Stream bool `json:"stream"`
|
||||
MaxOutputTokens int `json:"max_output_tokens"`
|
||||
MaxOutputTokens int `json:"max_output_tokens,omitempty"`
|
||||
Reasoning *openAIResponsesReasoning `json:"reasoning,omitempty"`
|
||||
Include []string `json:"include,omitempty"`
|
||||
PromptCacheKey string `json:"prompt_cache_key,omitempty"`
|
||||
@@ -304,15 +304,67 @@ func OpenAIEndpointURL(baseURL string, endpoint string) string {
|
||||
if !strings.HasPrefix(normalizedEndpoint, "/") {
|
||||
normalizedEndpoint = "/" + normalizedEndpoint
|
||||
}
|
||||
// 规则0:自定义路径模式
|
||||
// - baseURL 已含 endpoint 后缀(/chat/completions 或 /responses)→ 直接用 base
|
||||
// - 否则追加 /chat/completions(默认协议形态,覆盖 Z.AI /v4 等场景)
|
||||
if normalizedEndpoint == modelchannel.OpenAIEndpointCustom {
|
||||
if OpenAIEndpointFromBaseURL(base) != "" {
|
||||
return base
|
||||
}
|
||||
return base + "/chat/completions"
|
||||
}
|
||||
// 规则1:baseURL 已含 endpoint 后缀 → 直接用 base
|
||||
if OpenAIEndpointFromBaseURL(base) != "" {
|
||||
return base
|
||||
}
|
||||
if strings.HasSuffix(base, "/v1") && strings.HasPrefix(normalizedEndpoint, "/v1/") {
|
||||
return base + strings.TrimPrefix(normalizedEndpoint, "/v1")
|
||||
// 规则2:baseURL 以 /vN 结尾时,剥离 endpoint 的版本前缀(/v1/、/v2/ 等)
|
||||
// 这样 base=.../v4 + endpoint=/v1/chat/completions → .../v4/chat/completions
|
||||
if _, ok := trailingVersionSegment(base); ok {
|
||||
if rest, stripped := stripEndpointVersionPrefix(normalizedEndpoint); stripped {
|
||||
return base + rest
|
||||
}
|
||||
}
|
||||
// 规则3:兜底原样拼接
|
||||
return base + normalizedEndpoint
|
||||
}
|
||||
|
||||
// trailingVersionSegment 检测 URL 末尾是否以 /vN 形式结尾(N 为数字),
|
||||
// 返回版本段(如 "v4")和是否匹配。用于通用版本段去重。
|
||||
func trailingVersionSegment(base string) (string, bool) {
|
||||
idx := strings.LastIndex(base, "/")
|
||||
if idx < 0 {
|
||||
return "", false
|
||||
}
|
||||
seg := base[idx+1:]
|
||||
if len(seg) < 2 || seg[0] != 'v' {
|
||||
return "", false
|
||||
}
|
||||
for i := 1; i < len(seg); i++ {
|
||||
if seg[i] < '0' || seg[i] > '9' {
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
return seg, true
|
||||
}
|
||||
|
||||
// stripEndpointVersionPrefix 剥离 endpoint 路径开头的版本段前缀(/vN/),
|
||||
// 返回剩余路径和是否成功剥离。
|
||||
// /v1/chat/completions → ("/chat/completions", true)
|
||||
// /chat/completions → ("", false)
|
||||
func stripEndpointVersionPrefix(endpoint string) (string, bool) {
|
||||
if len(endpoint) < 4 || endpoint[0] != '/' || endpoint[1] != 'v' {
|
||||
return "", false
|
||||
}
|
||||
i := 2
|
||||
for i < len(endpoint) && endpoint[i] >= '0' && endpoint[i] <= '9' {
|
||||
i++
|
||||
}
|
||||
if i == 2 || i >= len(endpoint) || endpoint[i] != '/' {
|
||||
return "", false
|
||||
}
|
||||
return endpoint[i:], true
|
||||
}
|
||||
|
||||
func ResolveOpenAIEndpoint(baseURL string, endpoint string) string {
|
||||
if endpointFromURL := OpenAIEndpointFromBaseURL(baseURL); endpointFromURL != "" {
|
||||
return endpointFromURL
|
||||
@@ -377,11 +429,11 @@ func (adapter *OpenAIAdapter) Stream(ctx context.Context, req StreamRequest, sin
|
||||
req.OpenAIEndpoint = endpoint
|
||||
if req.RequestKnobs != nil {
|
||||
req.RequestKnobs["openai_endpoint"] = endpoint
|
||||
if endpoint == modelchannel.OpenAIEndpointResponses {
|
||||
if modelchannel.OpenAIEndpointShape(endpoint) == "responses" {
|
||||
req.RequestKnobs["max_output_tokens"] = req.MaxTokens
|
||||
}
|
||||
}
|
||||
if endpoint == modelchannel.OpenAIEndpointResponses {
|
||||
if modelchannel.OpenAIEndpointShape(endpoint) == "responses" {
|
||||
return adapter.streamResponses(ctx, req, baseURL, apiKey, modelID, sink)
|
||||
}
|
||||
return adapter.streamChatCompletions(ctx, req, baseURL, apiKey, modelID, sink)
|
||||
@@ -403,9 +455,11 @@ func (adapter *OpenAIAdapter) streamChatCompletions(ctx context.Context, req Str
|
||||
Model: modelID,
|
||||
Messages: normalizedMessages,
|
||||
Stream: true,
|
||||
MaxTokens: req.MaxTokens,
|
||||
StreamOptions: map[string]any{"include_usage": true},
|
||||
}
|
||||
if shouldSendOpenAIMaxOutputTokens(modelID) {
|
||||
requestBody.MaxTokens = req.MaxTokens
|
||||
}
|
||||
if key := openAIPromptCacheKey(req, modelID); key != "" {
|
||||
requestBody.PromptCacheKey = key
|
||||
}
|
||||
@@ -425,14 +479,14 @@ func (adapter *OpenAIAdapter) streamChatCompletions(ctx context.Context, req Str
|
||||
recordLLMSummaryArtifact(req, buildLLMSummaryPayload(req, "openai", modelID, startedAt, time.Time{}, finishedAt, "", 0, 0, 0, 0, err))
|
||||
return err
|
||||
}
|
||||
applyOpenAIThinkingDisable(bodyMap, req, baseURL, modelID, modelchannel.OpenAIEndpointChatCompletions)
|
||||
applyOpenAIThinkingDisable(bodyMap, req, baseURL, modelID, req.OpenAIEndpoint)
|
||||
if err := ApplyOpenAIExtraParams(bodyMap, req.OpenAIExtraParamsEnabled, req.OpenAIExtraParamsJSON); err != nil {
|
||||
finishedAt = time.Now().UTC()
|
||||
recordLLMSummaryArtifact(req, buildLLMSummaryPayload(req, "openai", modelID, startedAt, time.Time{}, finishedAt, "", 0, 0, 0, 0, err))
|
||||
return err
|
||||
}
|
||||
body = bodyMap
|
||||
requestURL := OpenAIEndpointURL(baseURL, modelchannel.OpenAIEndpointChatCompletions)
|
||||
requestURL := OpenAIEndpointURL(baseURL, req.OpenAIEndpoint)
|
||||
recordLLMRequestArtifact(req, "openai", modelID, "POST", requestURL, body)
|
||||
|
||||
payload, err := json.Marshal(body)
|
||||
@@ -862,12 +916,14 @@ func (adapter *OpenAIAdapter) streamResponses(ctx context.Context, req StreamReq
|
||||
return err
|
||||
}
|
||||
requestBody := openAIResponsesRequestBody{
|
||||
Model: modelID,
|
||||
Instructions: instructions,
|
||||
Input: input,
|
||||
Stream: true,
|
||||
MaxOutputTokens: req.MaxTokens,
|
||||
Store: false,
|
||||
Model: modelID,
|
||||
Instructions: instructions,
|
||||
Input: input,
|
||||
Stream: true,
|
||||
Store: false,
|
||||
}
|
||||
if shouldSendOpenAIMaxOutputTokens(modelID) {
|
||||
requestBody.MaxOutputTokens = req.MaxTokens
|
||||
}
|
||||
if key := openAIPromptCacheKey(req, modelID); key != "" {
|
||||
requestBody.PromptCacheKey = key
|
||||
@@ -901,7 +957,7 @@ func (adapter *OpenAIAdapter) streamResponses(ctx context.Context, req StreamReq
|
||||
recordLLMSummaryArtifact(req, buildLLMSummaryPayload(req, "openai", modelID, startedAt, time.Time{}, finishedAt, "", 0, 0, 0, 0, err))
|
||||
return err
|
||||
}
|
||||
applyOpenAIThinkingDisable(bodyMap, req, baseURL, modelID, modelchannel.OpenAIEndpointResponses)
|
||||
applyOpenAIThinkingDisable(bodyMap, req, baseURL, modelID, req.OpenAIEndpoint)
|
||||
if err := ApplyOpenAIExtraParams(bodyMap, req.OpenAIExtraParamsEnabled, req.OpenAIExtraParamsJSON); err != nil {
|
||||
finishedAt = time.Now().UTC()
|
||||
recordLLMSummaryArtifact(req, buildLLMSummaryPayload(req, "openai", modelID, startedAt, time.Time{}, finishedAt, "", 0, 0, 0, 0, err))
|
||||
@@ -909,7 +965,7 @@ func (adapter *OpenAIAdapter) streamResponses(ctx context.Context, req StreamReq
|
||||
}
|
||||
body = bodyMap
|
||||
|
||||
requestURL := OpenAIEndpointURL(baseURL, modelchannel.OpenAIEndpointResponses)
|
||||
requestURL := OpenAIEndpointURL(baseURL, req.OpenAIEndpoint)
|
||||
recordLLMRequestArtifact(req, "openai", modelID, "POST", requestURL, body)
|
||||
|
||||
payload, err := json.Marshal(body)
|
||||
@@ -1792,6 +1848,10 @@ func normalizeOpenAIProviderMessages(messages []Message, thinkingEnabled bool) (
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func shouldSendOpenAIMaxOutputTokens(modelID string) bool {
|
||||
return !strings.Contains(strings.ToLower(strings.TrimSpace(modelID)), "gpt")
|
||||
}
|
||||
|
||||
func shouldIncludeOpenAIReasoningContent(message Message, thinkingEnabled bool) bool {
|
||||
if strings.TrimSpace(message.ReasoningContent) != "" {
|
||||
return true
|
||||
@@ -1819,7 +1879,7 @@ func applyOpenAIThinkingDisable(body map[string]any, req StreamRequest, baseURL
|
||||
delete(body, "reasoning_effort")
|
||||
setRequestKnob(req, "thinking_disabled_provider_param", "enable_thinking")
|
||||
case "reasoning_none":
|
||||
if endpoint == modelchannel.OpenAIEndpointResponses {
|
||||
if modelchannel.OpenAIEndpointShape(endpoint) == "responses" {
|
||||
body["reasoning"] = map[string]any{"effort": "none"}
|
||||
} else {
|
||||
body["reasoning_effort"] = "none"
|
||||
|
||||
@@ -152,9 +152,9 @@ func NormalizeModelAdapterConfigs(input []ModelAdapterConfig) ([]ModelAdapterCon
|
||||
case next.ModelID == "":
|
||||
return nil, errors.New("模型适配器 modelID 不能为空")
|
||||
case next.Type == "openai" && next.ReasoningEffort == "":
|
||||
return nil, errors.New("模型适配器 reasoningEffort 仅支持 low、medium、high、xhigh")
|
||||
return nil, errors.New("模型适配器 reasoningEffort 仅支持 low、medium、high、xhigh、max")
|
||||
case next.Type == "openai" && next.OpenAIEndpoint == "":
|
||||
return nil, errors.New("模型适配器 openAIEndpoint 仅支持 /v1/responses 或 /v1/chat/completions")
|
||||
return nil, errors.New("模型适配器 openAIEndpoint 仅支持 /v1/responses、/v1/chat/completions 或 /custom(自定义路径)")
|
||||
case next.Type == "openai" && next.OpenAIExtraParamsEnabled:
|
||||
if err := validateJSONMap(next.OpenAIExtraParamsJSON, "openAIExtraParamsJSON"); err != nil {
|
||||
return nil, err
|
||||
@@ -216,7 +216,7 @@ func normalizeReasoningEffort(value string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case "", "medium":
|
||||
return "medium"
|
||||
case "low", "high", "xhigh":
|
||||
case "low", "high", "xhigh", "max":
|
||||
return strings.ToLower(strings.TrimSpace(value))
|
||||
default:
|
||||
return ""
|
||||
|
||||
@@ -46,6 +46,8 @@ const (
|
||||
bootstrapStatsigDisableTerminalOutputUIStreaming = "disable_terminal_output_ui_streaming"
|
||||
bootstrapStatsigBrowserCanvas = "browser_canvas"
|
||||
bootstrapStatsigEnableMultitaskMode = "enable_multitask_mode"
|
||||
bootstrapStatsigDecomposeAlwaysLocalExtHostGate = "decompose_always_local_ext_host"
|
||||
bootstrapStatsigCursorExtensionsIsolationV2Gate = "cursor_extensions_isolation_v2"
|
||||
bootstrapStatsigCursorAgentWorkerExtension = "enable_cursor_agent_worker_extension"
|
||||
bootstrapStatsigExperimentName = "free_user_model_picker"
|
||||
bootstrapStatsigVariantParam = "variant"
|
||||
@@ -139,6 +141,8 @@ var bootstrapStatsigTemplate = statsigBootstrapTemplate{
|
||||
bootstrapStatsigDisableTerminalOutputUIStreaming: buildEnabledStatsigGate(bootstrapStatsigDisableTerminalOutputUIStreaming),
|
||||
bootstrapStatsigBrowserCanvas: buildEnabledStatsigGate(bootstrapStatsigBrowserCanvas),
|
||||
bootstrapStatsigEnableMultitaskMode: buildEnabledStatsigGate(bootstrapStatsigEnableMultitaskMode),
|
||||
bootstrapStatsigDecomposeAlwaysLocalExtHostGate: buildDisabledStatsigGate(bootstrapStatsigDecomposeAlwaysLocalExtHostGate),
|
||||
bootstrapStatsigCursorExtensionsIsolationV2Gate: buildDisabledStatsigGate(bootstrapStatsigCursorExtensionsIsolationV2Gate),
|
||||
bootstrapStatsigCursorAgentWorkerExtension: buildDisabledStatsigGate(bootstrapStatsigCursorAgentWorkerExtension),
|
||||
},
|
||||
DynamicConfigs: map[string]statsigDynamicConfigTemplate{
|
||||
@@ -742,7 +746,7 @@ func buildThinkingEffortVariantDisplayName(modelDisplayName string, effortValue
|
||||
|
||||
func thinkingEffortValuesForAdapter(adapterType string) []string {
|
||||
values := []string{"disabled", "low", "medium", "high", "xhigh"}
|
||||
if strings.EqualFold(strings.TrimSpace(adapterType), "anthropic") {
|
||||
if adapterType := strings.ToLower(strings.TrimSpace(adapterType)); adapterType == "openai" || adapterType == "anthropic" {
|
||||
values = append(values, "max")
|
||||
}
|
||||
return values
|
||||
@@ -769,7 +773,7 @@ func defaultThinkingEffortForAdapter(adapter legacyruntime.ModelAdapterConfig) s
|
||||
if strings.EqualFold(strings.TrimSpace(adapter.Type), "anthropic") {
|
||||
return normalizeAvailableModelThinkingEffort(adapter.AnthropicThinkingEffort, true, "xhigh")
|
||||
}
|
||||
return normalizeAvailableModelThinkingEffort(adapter.ReasoningEffort, false, "medium")
|
||||
return normalizeAvailableModelThinkingEffort(adapter.ReasoningEffort, true, "medium")
|
||||
}
|
||||
|
||||
func normalizeAvailableModelThinkingEffort(raw string, allowMax bool, fallback string) string {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package upstream
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildBootstrapStatsigConfigJSONDisablesAlwaysLocalDecompositionGate(t *testing.T) {
|
||||
payload, err := buildBootstrapStatsigConfigJSON(12345, "test-auth-id")
|
||||
if err != nil {
|
||||
t.Fatalf("build bootstrap statsig config: %v", err)
|
||||
}
|
||||
|
||||
var decoded statsigBootstrapTemplate
|
||||
if err := json.Unmarshal(payload, &decoded); err != nil {
|
||||
t.Fatalf("decode bootstrap statsig config: %v", err)
|
||||
}
|
||||
|
||||
gate, ok := decoded.FeatureGates[bootstrapStatsigDecomposeAlwaysLocalExtHostGate]
|
||||
if !ok {
|
||||
t.Fatalf("missing feature gate %q", bootstrapStatsigDecomposeAlwaysLocalExtHostGate)
|
||||
}
|
||||
if value, _ := gate["value"].(bool); value {
|
||||
t.Fatalf("expected %q to be disabled", bootstrapStatsigDecomposeAlwaysLocalExtHostGate)
|
||||
}
|
||||
if ruleID, _ := gate["rule_id"].(string); ruleID != "local_disabled" {
|
||||
t.Fatalf("unexpected rule_id: %q", ruleID)
|
||||
}
|
||||
}
|
||||
@@ -668,7 +668,7 @@ func normalizeModelAdapterTestType(value string) string {
|
||||
|
||||
func normalizeModelAdapterTestReasoning(value string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case "low", "medium", "high", "xhigh":
|
||||
case "low", "medium", "high", "xhigh", "max":
|
||||
return strings.ToLower(strings.TrimSpace(value))
|
||||
default:
|
||||
return "medium"
|
||||
|
||||
@@ -3,6 +3,7 @@ package cursor
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -23,8 +24,14 @@ const (
|
||||
cursorStateDBRelativePath = "Cursor/User/globalStorage/state.vscdb"
|
||||
cursorStateDarwinRelativePath = "Library/Application Support/Cursor/User/globalStorage/state.vscdb"
|
||||
cursorStateLinuxRelativePath = ".config/Cursor/User/globalStorage/state.vscdb"
|
||||
cursorStateStatsigBootstrapKey = "workbench.experiments.statsigBootstrap"
|
||||
)
|
||||
|
||||
var cursorStateDisabledStatsigGates = []string{
|
||||
"decompose_always_local_ext_host",
|
||||
"cursor_extensions_isolation_v2",
|
||||
}
|
||||
|
||||
// InjectCursorUserInfo synchronizes the Cursor user-level auth cache used by the
|
||||
// Settings page. It does not modify the installed Cursor app bundle.
|
||||
func InjectCursorUserInfo(email, token string) error {
|
||||
@@ -42,11 +49,12 @@ func InjectCursorUserInfo(email, token string) error {
|
||||
}
|
||||
|
||||
logger.Infof(
|
||||
"injectCursorUserInfo synced path=%s email=%s membership=%s subscription=%s",
|
||||
"injectCursorUserInfo synced path=%s email=%s membership=%s subscription=%s disabled_statsig_gates=%s",
|
||||
stateDBPath,
|
||||
values["cursorAuth/cachedEmail"],
|
||||
values["cursorAuth/stripeMembershipType"],
|
||||
values["cursorAuth/stripeSubscriptionStatus"],
|
||||
strings.Join(cursorStateDisabledStatsigGates, ","),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
@@ -111,6 +119,10 @@ func syncCursorAuthStateDB(path string, values map[string]string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := disableCursorStatsigGates(ctx, tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -118,6 +130,70 @@ func syncCursorAuthStateDB(path string, values map[string]string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func disableCursorStatsigGates(ctx context.Context, tx *sql.Tx) error {
|
||||
var raw []byte
|
||||
err := tx.QueryRowContext(ctx, "SELECT value FROM ItemTable WHERE key = ?", cursorStateStatsigBootstrapKey).Scan(&raw)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var payload map[string]any
|
||||
if err := json.Unmarshal(raw, &payload); err != nil {
|
||||
return fmt.Errorf("解析 Cursor Statsig bootstrap 失败: %w", err)
|
||||
}
|
||||
|
||||
featureGates, _ := payload["feature_gates"].(map[string]any)
|
||||
if featureGates == nil {
|
||||
featureGates = map[string]any{}
|
||||
payload["feature_gates"] = featureGates
|
||||
}
|
||||
|
||||
hashUsed, _ := payload["hash_used"].(string)
|
||||
for _, gate := range cursorStateDisabledStatsigGates {
|
||||
disableCursorStatsigGate(featureGates, gate)
|
||||
if strings.EqualFold(hashUsed, "djb2") {
|
||||
disableCursorStatsigGate(featureGates, cursorStateDJB2Hash(gate))
|
||||
}
|
||||
}
|
||||
|
||||
updated, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("编码 Cursor Statsig bootstrap 失败: %w", err)
|
||||
}
|
||||
if _, err := tx.ExecContext(ctx, "UPDATE ItemTable SET value = ? WHERE key = ?", updated, cursorStateStatsigBootstrapKey); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func disableCursorStatsigGate(featureGates map[string]any, key string) {
|
||||
gate, _ := featureGates[key].(map[string]any)
|
||||
if gate == nil {
|
||||
gate = map[string]any{
|
||||
"name": key,
|
||||
"rule_id": "local_disabled",
|
||||
"ruleID": "local_disabled",
|
||||
"group_name": "local_disabled",
|
||||
"groupName": "local_disabled",
|
||||
"id_type": "userID",
|
||||
"idType": "userID",
|
||||
}
|
||||
featureGates[key] = gate
|
||||
}
|
||||
gate["value"] = false
|
||||
}
|
||||
|
||||
func cursorStateDJB2Hash(value string) string {
|
||||
var hash uint32
|
||||
for _, b := range []byte(value) {
|
||||
hash = hash*31 + uint32(b)
|
||||
}
|
||||
return fmt.Sprintf("%d", hash)
|
||||
}
|
||||
|
||||
func resolveCursorStateDBPath() (string, error) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
|
||||
@@ -13,6 +13,7 @@ const ChannelIDHexLength = 16
|
||||
const (
|
||||
OpenAIEndpointResponses = "/v1/responses"
|
||||
OpenAIEndpointChatCompletions = "/v1/chat/completions"
|
||||
OpenAIEndpointCustom = "/custom"
|
||||
)
|
||||
|
||||
func NormalizeBaseURL(raw string) (string, error) {
|
||||
@@ -39,20 +40,37 @@ func NormalizeBaseURL(raw string) (string, error) {
|
||||
return normalized, nil
|
||||
}
|
||||
|
||||
// NormalizeOpenAIEndpoint 归一化 OpenAI endpoint 路径。
|
||||
// 支持三个预设值:/v1/responses、/v1/chat/completions、/custom(自定义路径)。
|
||||
// 选 /custom 时,用户需在接口地址栏填写完整请求 URL。
|
||||
func NormalizeOpenAIEndpoint(providerType string, endpoint string) string {
|
||||
if strings.TrimSpace(strings.ToLower(providerType)) != "openai" {
|
||||
return ""
|
||||
}
|
||||
switch strings.ToLower(strings.TrimSpace(endpoint)) {
|
||||
case "", OpenAIEndpointResponses:
|
||||
normalized := strings.TrimSpace(endpoint)
|
||||
switch normalized {
|
||||
case "":
|
||||
return OpenAIEndpointResponses
|
||||
case OpenAIEndpointChatCompletions:
|
||||
return OpenAIEndpointChatCompletions
|
||||
case OpenAIEndpointResponses, OpenAIEndpointChatCompletions, OpenAIEndpointCustom:
|
||||
return normalized
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// OpenAIEndpointShape 根据 endpoint 路径末段推断协议形态。
|
||||
// 返回 "responses"(Responses API)或 "chat/completions"(Chat Completions API)。
|
||||
// 这样 /v1/chat/completions、/v4/chat/completions、/chat/completions 都走同一协议分支。
|
||||
func OpenAIEndpointShape(endpoint string) string {
|
||||
lower := strings.ToLower(strings.TrimSpace(endpoint))
|
||||
switch {
|
||||
case strings.HasSuffix(lower, "/responses"):
|
||||
return "responses"
|
||||
default:
|
||||
return "chat/completions"
|
||||
}
|
||||
}
|
||||
|
||||
func BuildLegacyChannelID(baseURL string, modelID string, apiKey string, name string) string {
|
||||
return buildChannelID([]string{
|
||||
strings.TrimSpace(baseURL),
|
||||
|
||||
@@ -138,7 +138,7 @@ func NormalizeModelAdapterConfigs(input []ModelAdapterConfig) ([]ModelAdapterCon
|
||||
case next.ModelID == "":
|
||||
return nil, errors.New("模型适配器 modelID 不能为空")
|
||||
case next.Type == "openai" && next.ReasoningEffort == "":
|
||||
return nil, errors.New("模型适配器 reasoningEffort 仅支持 low、medium、high、xhigh")
|
||||
return nil, errors.New("模型适配器 reasoningEffort 仅支持 low、medium、high、xhigh、max")
|
||||
case next.Type == "openai" && next.OpenAIEndpoint == "":
|
||||
return nil, errors.New("模型适配器 openAIEndpoint 仅支持 /v1/responses 或 /v1/chat/completions")
|
||||
case next.Type == "openai" && next.OpenAIExtraParamsEnabled:
|
||||
@@ -202,7 +202,7 @@ func normalizeReasoningEffort(value string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case "", "medium":
|
||||
return "medium"
|
||||
case "low", "high", "xhigh":
|
||||
case "low", "high", "xhigh", "max":
|
||||
return strings.ToLower(strings.TrimSpace(value))
|
||||
default:
|
||||
return ""
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
你是一个极度务实且高效的软件工程师。你认真对待工程质量,并以直接、客观的陈述方式体现协作。你沟通高效,在不加入无关细节的前提下,清楚告知用户你正在进行的操作。
|
||||
|
||||
!IMPORTANT 如非用户明确要求,禁止使用Subagent
|
||||
|
||||
你具有极强的架构经验和模块化经验,当用户提出泛泛的需求时,**你会不断评估架构、模块、数据流向、状态机的合理性**,你会自信的引导用户重构代码。
|
||||
|
||||
你不会从代码、函数等层面交流,只输出你认为用户能看懂的数据结构、演变过程、模块关系、作用域、伪代码等,**并附带注释**。
|
||||
你只输出你认为普通用户能看懂的回复,再不炫技的情况下回复内容可能是数据结构、演变过程、模块关系、作用域、伪代码、mermaid等,**并附带注释**。
|
||||
|
||||
除非你已经确定项目是面向对象的,其他情况下你都偏好 Functional Programming. DSL化编码风格是是你喜欢的,也是用户喜欢的,也是可读的、优秀的。
|
||||
|
||||
|
||||
+4
-8
@@ -1,10 +1,6 @@
|
||||
QQ10群:1049354347
|
||||
----
|
||||
- 修复 Taking longer than expected.
|
||||
- 修复gpt5.5思考内容(加密内容展示)
|
||||
- 新增多任务能力,支持在会话中并行处理多个任务
|
||||
- 优化 Subagent 模型选择逻辑,提升分发稳定性
|
||||
- 修复 macOS 下证书信任失败导致无法正常连接的问题
|
||||
- 新增 ServerTime 接口及 Mock 实现,补齐本地模式兼容性
|
||||
- 优化 OpenAI / Anthropic 适配器的请求结构,提升模型请求稳定性
|
||||
- 优化提示词表达,精简系统前缀,提升响应质量
|
||||
- 支持 GPT 5.6
|
||||
- 修复一些bug
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user