mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-10-03 18:23:51 +08:00
Merge pull request #331 from baichuan335/feat/custom-context-option
feat: 增加自定义上下文选项功能
This commit is contained in:
@@ -78,10 +78,13 @@ export function CursorModelEditor({ draft, providers, editing, modelOptions, dis
|
||||
{ value: "openai-responses", label: "OpenAI Responses", icon: openAiIcon }, { value: "openai-chat", label: "OpenAI Chat", icon: openAiIcon }, { value: "anthropic", label: "Anthropic", icon: claudeIcon },
|
||||
]} onChange={(endpointType) => setEndpointType(endpointType as ProviderType)} /></FormField>
|
||||
{(editing || draft.modelIds.length <= 1) && <FormField label={t("显示名称")} hint={t("仅用于界面展示,不会改变发送给上游的模型名称。")}><TextInput placeholder="例如:GPT-4.1" value={draft.model.display_name} onChange={(event) => setModel({ display_name: event.target.value })} /></FormField>}
|
||||
<FormField className={styles.fullWidth} label={t("模型名称")} hint={editing ? t("可以直接输入模型标识,也可以从当前上游返回的模型列表中选择。") : t("支持选择或输入多个模型;批量添加时显示名称默认使用对应模型名称。")}>{editing
|
||||
<FormField label={t("模型名称")} hint={editing ? t("可以直接输入模型标识,也可以从当前上游返回的模型列表中选择。") : t("支持选择或输入多个模型;批量添加时显示名称默认使用对应模型名称。")}>{editing
|
||||
? <Combobox value={draft.model.model_id} options={modelOptions} placeholder="例如:gpt-4.1" append={<button type="button" className={controls.secondary} disabled={discovering || !canDiscover} onClick={onDiscover}>{discovering ? t("获取中…") : t("获取模型")}</button>} onChange={(model_id) => setModel({ model_id, display_name: draft.model.display_name || model_id })} />
|
||||
: <MultiCombobox value={draft.modelIds} options={modelOptions} placeholder="例如:gpt-4.1" append={<button type="button" className={controls.secondary} disabled={discovering || !canDiscover} onClick={onDiscover}>{discovering ? t("获取中…") : t("获取模型")}</button>} onChange={setModelIds} />
|
||||
}</FormField>
|
||||
<FormField label={t("自定义上下文")} hint={t("输入 token 数后,将作为额外选项添加到 Cursor 模型的 Context 列表;只有在 Cursor 中选中该选项时才会生效。")}>
|
||||
<TextInput type="number" min={1} step={1} aria-label={t("自定义上下文 tokens")} placeholder={t("例如:272000")} value={draft.model.context_window_tokens ?? ""} onChange={(event) => setModel({ context_window_tokens: event.target.value === "" ? null : Math.trunc(Number(event.target.value)) })} />
|
||||
</FormField>
|
||||
<div className={styles.fullWidth}><Checkbox label={t("自定义请求完整地址")} checked={draft.customRequestUrl} onChange={(customRequestUrl) => onChange({ ...draft, customRequestUrl, model: { ...draft.model, request_url: customRequestUrl ? draft.model.request_url : "" } })} /></div>
|
||||
{draft.customRequestUrl && <FormField className={styles.fullWidth} label={t("请求完整地址")} hint={t("支持完整 HTTP(S) 地址或以 / 开头、与上游地址组合的相对路径。")}><TextInput placeholder="例如:https://api.example.com/v1/chat/completions" value={draft.model.request_url} onChange={(event) => setModel({ request_url: event.target.value })} /></FormField>}
|
||||
{!editing && draft.providerMode === "new" && <>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 92,
|
||||
"line": 95,
|
||||
"column": 39
|
||||
}
|
||||
]
|
||||
@@ -190,7 +190,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/CursorSettingsPage.tsx",
|
||||
"line": 256,
|
||||
"line": 257,
|
||||
"column": 89
|
||||
}
|
||||
]
|
||||
@@ -243,7 +243,7 @@
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 81,
|
||||
"column": 119
|
||||
"column": 90
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -390,7 +390,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/CursorSettingsPage.tsx",
|
||||
"line": 255,
|
||||
"line": 256,
|
||||
"column": 36
|
||||
},
|
||||
{
|
||||
@@ -511,12 +511,12 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 89,
|
||||
"line": 92,
|
||||
"column": 56
|
||||
},
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 89,
|
||||
"line": 92,
|
||||
"column": 141
|
||||
},
|
||||
{
|
||||
@@ -731,12 +731,12 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 88,
|
||||
"line": 91,
|
||||
"column": 56
|
||||
},
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 88,
|
||||
"line": 91,
|
||||
"column": 123
|
||||
},
|
||||
{
|
||||
@@ -912,7 +912,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 85,
|
||||
"line": 88,
|
||||
"column": 58
|
||||
}
|
||||
]
|
||||
@@ -1596,7 +1596,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 88,
|
||||
"line": 91,
|
||||
"column": 78
|
||||
},
|
||||
{
|
||||
@@ -1632,6 +1632,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"5cca0b7972a11f3a": {
|
||||
"source": "自定义上下文必须是大于 0 的整数",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/CursorSettingsPage.tsx",
|
||||
"line": 238,
|
||||
"column": 171
|
||||
}
|
||||
]
|
||||
},
|
||||
"5d59857bf039cac9": {
|
||||
"source": "Cursor 助手 v{version}",
|
||||
"kind": "template",
|
||||
@@ -1777,7 +1789,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "api.ts",
|
||||
"line": 289,
|
||||
"line": 290,
|
||||
"column": 43
|
||||
}
|
||||
]
|
||||
@@ -2070,7 +2082,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "api.ts",
|
||||
"line": 284,
|
||||
"line": 285,
|
||||
"column": 43
|
||||
}
|
||||
]
|
||||
@@ -2132,12 +2144,12 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 92,
|
||||
"line": 95,
|
||||
"column": 73
|
||||
},
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 92,
|
||||
"line": 95,
|
||||
"column": 122
|
||||
}
|
||||
]
|
||||
@@ -2209,7 +2221,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "api.ts",
|
||||
"line": 230,
|
||||
"line": 231,
|
||||
"column": 21
|
||||
}
|
||||
]
|
||||
@@ -2255,7 +2267,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "pages/CursorSettingsPage.tsx",
|
||||
"line": 250,
|
||||
"line": 251,
|
||||
"column": 21
|
||||
}
|
||||
]
|
||||
@@ -2318,7 +2330,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 89,
|
||||
"line": 92,
|
||||
"column": 85
|
||||
}
|
||||
]
|
||||
@@ -2443,7 +2455,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 86,
|
||||
"line": 89,
|
||||
"column": 100
|
||||
}
|
||||
]
|
||||
@@ -2479,7 +2491,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 86,
|
||||
"line": 89,
|
||||
"column": 81
|
||||
}
|
||||
]
|
||||
@@ -2559,7 +2571,7 @@
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 81,
|
||||
"column": 81
|
||||
"column": 52
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -2592,6 +2604,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"9d8f2ef4e85ea665": {
|
||||
"source": "自定义上下文 tokens",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 86,
|
||||
"column": 63
|
||||
}
|
||||
]
|
||||
},
|
||||
"9e46da6923836182": {
|
||||
"source": "如:2026-08-23 09:00、1小时前",
|
||||
"kind": "text",
|
||||
@@ -3117,12 +3141,12 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 93,
|
||||
"line": 96,
|
||||
"column": 66
|
||||
},
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 93,
|
||||
"line": 96,
|
||||
"column": 115
|
||||
}
|
||||
]
|
||||
@@ -3286,16 +3310,28 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 94,
|
||||
"line": 97,
|
||||
"column": 66
|
||||
},
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 94,
|
||||
"line": 97,
|
||||
"column": 115
|
||||
}
|
||||
]
|
||||
},
|
||||
"c6cc835023617457": {
|
||||
"source": "自定义上下文",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 85,
|
||||
"column": 25
|
||||
}
|
||||
]
|
||||
},
|
||||
"c7ea2c9bc43134bd": {
|
||||
"source": "编辑模型",
|
||||
"kind": "text",
|
||||
@@ -3386,7 +3422,7 @@
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 81,
|
||||
"column": 54
|
||||
"column": 25
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -3443,7 +3479,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 94,
|
||||
"line": 97,
|
||||
"column": 39
|
||||
}
|
||||
]
|
||||
@@ -3477,6 +3513,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"d27db596b73a0a66": {
|
||||
"source": "例如:272000",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 86,
|
||||
"column": 96
|
||||
}
|
||||
]
|
||||
},
|
||||
"d2d648bd1c94b7f9": {
|
||||
"source": "认证",
|
||||
"kind": "text",
|
||||
@@ -3555,7 +3603,7 @@
|
||||
{
|
||||
"file": "components/ProviderEditor.tsx",
|
||||
"line": 26,
|
||||
"column": 190
|
||||
"column": 180
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -3758,7 +3806,7 @@
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 93,
|
||||
"line": 96,
|
||||
"column": 39
|
||||
}
|
||||
]
|
||||
@@ -3866,6 +3914,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"e671f8c7598139ef": {
|
||||
"source": "输入 token 数后,将作为额外选项添加到 Cursor 模型的 Context 列表;只有在 Cursor 中选中该选项时才会生效。",
|
||||
"kind": "text",
|
||||
"placeholders": [],
|
||||
"refs": [
|
||||
{
|
||||
"file": "components/cursor/CursorModelEditor.tsx",
|
||||
"line": 85,
|
||||
"column": 44
|
||||
}
|
||||
]
|
||||
},
|
||||
"e6ca887f22288cde": {
|
||||
"source": "Model ID 和显示名称不能为空",
|
||||
"kind": "text",
|
||||
|
||||
@@ -110,6 +110,7 @@
|
||||
"5ae715656ffbc35d": "Merge into the request body for every model from this provider.",
|
||||
"5b17f59d33bde39e": "Error: {error}",
|
||||
"5c55a67935af8f45": "All",
|
||||
"5cca0b7972a11f3a": "Custom context must be an integer greater than 0",
|
||||
"5d59857bf039cac9": "Cursor Assistant v{version}",
|
||||
"5f8d556a9c47da3c": "Launch at login disabled",
|
||||
"5f9acfb945229062": "Are you sure you no longer want to see this ad?",
|
||||
@@ -180,6 +181,7 @@
|
||||
"9a026819dd1af5c5": "Enter a model identifier directly or select one returned by the current provider.",
|
||||
"9ac0ac940982895d": "Select provider",
|
||||
"9c41b3a9e12ac994": "Reasoning effort",
|
||||
"9d8f2ef4e85ea665": "Custom context tokens",
|
||||
"9e46da6923836182": "For example: 2026-08-23 09:00, 1 hour ago",
|
||||
"9f6fee1aba17a565": "Language",
|
||||
"9fb48101d237ff96": "Last week",
|
||||
@@ -227,6 +229,7 @@
|
||||
"c1e98892a77f7a19": "{count} per page",
|
||||
"c3760858cdb6d9f4": "Request body",
|
||||
"c62a58459251b02c": "Image generation",
|
||||
"c6cc835023617457": "Custom context",
|
||||
"c7ea2c9bc43134bd": "Edit model",
|
||||
"c8df3c14a003bfcd": "Unable to load call details",
|
||||
"c98e118e0a43f078": "Model",
|
||||
@@ -239,6 +242,7 @@
|
||||
"cfe999e50be8ef54": "Whether the model declares image-generation support.",
|
||||
"d0bfccc77315d887": "Last month",
|
||||
"d15a909c3490a7e0": "Endpoint type",
|
||||
"d27db596b73a0a66": "For example: 272000",
|
||||
"d2d648bd1c94b7f9": "Authentication",
|
||||
"d30d35c5ec4a888d": "Select or enter at least one model",
|
||||
"d34335433395cd3a": "Start Cursor BYOK automatically after signing in.",
|
||||
@@ -269,6 +273,7 @@
|
||||
"e5043c7a2b408271": "Last 10 minutes",
|
||||
"e59ae97924d62f01": "First page",
|
||||
"e5b9961a0d5242e3": "Port settings saved. Restart the app to apply them.",
|
||||
"e671f8c7598139ef": "After entering a token count, it is added as an extra option to the Cursor model Context list. It only takes effect when selected in Cursor.",
|
||||
"e6ca887f22288cde": "Model ID and display name are required",
|
||||
"e77e3d58b0dcffaa": "Duration",
|
||||
"e828bd3a0151edc2": "The local CA must be trusted by the system",
|
||||
|
||||
@@ -110,6 +110,7 @@
|
||||
"5ae715656ffbc35d": "合并到该上游所有模型的请求体。",
|
||||
"5b17f59d33bde39e": "错误:{error}",
|
||||
"5c55a67935af8f45": "全部",
|
||||
"5cca0b7972a11f3a": "自定义上下文必须是大于 0 的整数",
|
||||
"5d59857bf039cac9": "Cursor 助手 v{version}",
|
||||
"5f8d556a9c47da3c": "已关闭开机启动",
|
||||
"5f9acfb945229062": "你确认不想再看到此广告吗?",
|
||||
@@ -180,6 +181,7 @@
|
||||
"9a026819dd1af5c5": "可以直接输入模型标识,也可以从当前上游返回的模型列表中选择。",
|
||||
"9ac0ac940982895d": "选择上游",
|
||||
"9c41b3a9e12ac994": "思考强度",
|
||||
"9d8f2ef4e85ea665": "自定义上下文 tokens",
|
||||
"9e46da6923836182": "如:2026-08-23 09:00、1小时前",
|
||||
"9f6fee1aba17a565": "语言",
|
||||
"9fb48101d237ff96": "近一周",
|
||||
@@ -227,6 +229,7 @@
|
||||
"c1e98892a77f7a19": "{count} 条/页",
|
||||
"c3760858cdb6d9f4": "请求体",
|
||||
"c62a58459251b02c": "图片生成",
|
||||
"c6cc835023617457": "自定义上下文",
|
||||
"c7ea2c9bc43134bd": "编辑模型",
|
||||
"c8df3c14a003bfcd": "无法加载调用详情",
|
||||
"c98e118e0a43f078": "模型",
|
||||
@@ -239,6 +242,7 @@
|
||||
"cfe999e50be8ef54": "是否声明模型支持图片生成。",
|
||||
"d0bfccc77315d887": "近一个月",
|
||||
"d15a909c3490a7e0": "端点类型",
|
||||
"d27db596b73a0a66": "例如:272000",
|
||||
"d2d648bd1c94b7f9": "认证",
|
||||
"d30d35c5ec4a888d": "请至少选择或输入一个模型",
|
||||
"d34335433395cd3a": "登录系统后自动启动 Cursor BYOK。",
|
||||
@@ -269,6 +273,7 @@
|
||||
"e5043c7a2b408271": "近10分钟",
|
||||
"e59ae97924d62f01": "第一页",
|
||||
"e5b9961a0d5242e3": "端口设置已保存,重启软件后生效",
|
||||
"e671f8c7598139ef": "输入 token 数后,将作为额外选项添加到 Cursor 模型的 Context 列表;只有在 Cursor 中选中该选项时才会生效。",
|
||||
"e6ca887f22288cde": "Model ID 和显示名称不能为空",
|
||||
"e77e3d58b0dcffaa": "耗时",
|
||||
"e828bd3a0151edc2": "需要在系统中信任本地 CA",
|
||||
|
||||
@@ -55,7 +55,7 @@ export function CursorSettingsPage() {
|
||||
next.model = {
|
||||
model_id: model.model_id, display_name: model.display_name, enabled: model.enabled, sort_order: model.sort_order,
|
||||
endpoint_type: model.endpoint_type, request_url: model.request_url,
|
||||
context_window_tokens: null, max_output_tokens: null,
|
||||
context_window_tokens: model.context_window_tokens, max_output_tokens: null,
|
||||
reasoning_enabled: model.reasoning_enabled, reasoning_effort: null,
|
||||
supports_image_generation: model.supports_image_generation,
|
||||
};
|
||||
@@ -235,6 +235,7 @@ function cursorModelInputs(draft: CursorModelDraft, editing: boolean) {
|
||||
if (!modelIds.length) throw new Error(t("请至少选择或输入一个模型"));
|
||||
if (editing && !draft.model.display_name.trim()) throw new Error(t("Model ID 和显示名称不能为空"));
|
||||
if (draft.customRequestUrl && !draft.model.request_url.trim()) throw new Error(t("请求完整地址不能为空"));
|
||||
if (draft.model.context_window_tokens !== null && (!Number.isSafeInteger(draft.model.context_window_tokens) || draft.model.context_window_tokens <= 0)) throw new Error(t("自定义上下文必须是大于 0 的整数"));
|
||||
return modelIds.map((modelId, index) => ({
|
||||
...draft.model,
|
||||
model_id: modelId,
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::{
|
||||
proxy::{self, CursorProxy},
|
||||
CursorSessionRegistry,
|
||||
},
|
||||
model::ProviderModel,
|
||||
model::{format_token_count, parse_token_count, ProviderModel},
|
||||
Error, Result,
|
||||
};
|
||||
|
||||
@@ -205,6 +205,23 @@ const EFFORTS: [(&str, &str); 5] = [
|
||||
];
|
||||
const DEFAULT_CONTEXT: &str = "200k";
|
||||
|
||||
fn context_options(model: &ProviderModel) -> Vec<(String, String)> {
|
||||
let mut contexts = CONTEXTS
|
||||
.into_iter()
|
||||
.map(|(value, display_name)| (value.to_owned(), display_name.to_owned()))
|
||||
.collect::<Vec<_>>();
|
||||
if let Some(tokens) = model.context_window_tokens {
|
||||
let value = tokens.to_string();
|
||||
let duplicate = contexts
|
||||
.iter()
|
||||
.any(|(existing, _)| parse_token_count(existing) == Some(tokens));
|
||||
if !duplicate {
|
||||
contexts.push((value, format!("{} (Custom)", format_token_count(tokens))));
|
||||
}
|
||||
}
|
||||
contexts
|
||||
}
|
||||
|
||||
pub async fn available_models(
|
||||
State(registry): State<CursorSessionRegistry>,
|
||||
Extension(proxy): Extension<CursorProxy>,
|
||||
@@ -324,7 +341,8 @@ fn unary_payload(body: &Bytes) -> Result<(bool, &[u8])> {
|
||||
}
|
||||
|
||||
fn available_model(model: &ProviderModel, provider_name: &str) -> AvailableModel {
|
||||
let variants = model_variants(model);
|
||||
let contexts = context_options(model);
|
||||
let variants = model_variants(model, &contexts);
|
||||
let legacy_slugs = variants
|
||||
.iter()
|
||||
.filter_map(|variant| variant.legacy_slug.clone())
|
||||
@@ -348,7 +366,7 @@ fn available_model(model: &ProviderModel, provider_name: &str) -> AvailableModel
|
||||
inputbox_short_model_name: Some(model.display_name.clone()),
|
||||
supports_sandboxing: Some(true),
|
||||
supports_cmd_k: Some(false),
|
||||
parameter_definitions: model_parameters(),
|
||||
parameter_definitions: model_parameters(&contexts),
|
||||
variants,
|
||||
legacy_slugs,
|
||||
named_model_section_index: Some(1),
|
||||
@@ -365,7 +383,7 @@ fn available_model(model: &ProviderModel, provider_name: &str) -> AvailableModel
|
||||
}
|
||||
}
|
||||
|
||||
fn model_parameters() -> Vec<ModelParameterDefinition> {
|
||||
fn model_parameters(contexts: &[(String, String)]) -> Vec<ModelParameterDefinition> {
|
||||
vec![
|
||||
ModelParameterDefinition {
|
||||
id: "context".into(),
|
||||
@@ -374,11 +392,11 @@ fn model_parameters() -> Vec<ModelParameterDefinition> {
|
||||
parameter_type: Some(ModelParameterType {
|
||||
boolean_parameter: None,
|
||||
enum_parameter: Some(EnumParameter {
|
||||
values: CONTEXTS
|
||||
.into_iter()
|
||||
values: contexts
|
||||
.iter()
|
||||
.map(|(value, display_name)| EnumParameterValue {
|
||||
value: value.into(),
|
||||
display_name: Some(display_name.into()),
|
||||
value: value.clone(),
|
||||
display_name: Some(display_name.clone()),
|
||||
})
|
||||
.collect(),
|
||||
}),
|
||||
@@ -429,9 +447,9 @@ fn model_parameters() -> Vec<ModelParameterDefinition> {
|
||||
]
|
||||
}
|
||||
|
||||
fn model_variants(model: &ProviderModel) -> Vec<ModelVariant> {
|
||||
let mut variants = Vec::with_capacity(CONTEXTS.len() * EFFORTS.len() * 2);
|
||||
for (context, context_name) in CONTEXTS {
|
||||
fn model_variants(model: &ProviderModel, contexts: &[(String, String)]) -> Vec<ModelVariant> {
|
||||
let mut variants = Vec::with_capacity(contexts.len() * EFFORTS.len() * 2);
|
||||
for (context, context_name) in contexts {
|
||||
for (effort, effort_name) in EFFORTS {
|
||||
for fast in [false, true] {
|
||||
variants.push(model_variant(
|
||||
@@ -546,7 +564,7 @@ mod tests {
|
||||
request_url: String::new(),
|
||||
enabled: true,
|
||||
sort_order: 0,
|
||||
context_window_tokens: Some(200_000),
|
||||
context_window_tokens: Some(272_000),
|
||||
max_output_tokens: None,
|
||||
reasoning_enabled: false,
|
||||
reasoning_effort: None,
|
||||
@@ -591,7 +609,22 @@ mod tests {
|
||||
.iter()
|
||||
.map(|value| value.value.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(context_values, ["200k", "356k", "800k", "1m"]);
|
||||
assert_eq!(context_values, ["200k", "356k", "800k", "1m", "272000"]);
|
||||
let custom_context = context
|
||||
.parameter_type
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.enum_parameter
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.values
|
||||
.iter()
|
||||
.find(|value| value.value == "272000")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
custom_context.display_name.as_deref(),
|
||||
Some("272K (Custom)")
|
||||
);
|
||||
let effort = mapped
|
||||
.parameter_definitions
|
||||
.iter()
|
||||
@@ -607,8 +640,8 @@ mod tests {
|
||||
.values
|
||||
.iter()
|
||||
.any(|value| value.value == "max"));
|
||||
assert_eq!(mapped.variants.len(), 40);
|
||||
assert_eq!(mapped.legacy_slugs.len(), 40);
|
||||
assert_eq!(mapped.variants.len(), 50);
|
||||
assert_eq!(mapped.legacy_slugs.len(), 50);
|
||||
assert_eq!(mapped.model_picker_badges.len(), 1);
|
||||
assert_eq!(mapped.model_picker_badges[0].label, "OpenRouter");
|
||||
assert!(!mapped.model_picker_badges[0].dismiss_on_selection);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use crate::{
|
||||
cursor::proto::agent::v1 as pb,
|
||||
model::{ModelLatency, ModelSpec, ReasoningSpec, SubagentKind, SubagentModelOverride},
|
||||
model::{
|
||||
parse_token_count, ModelLatency, ModelSpec, ReasoningSpec, SubagentKind,
|
||||
SubagentModelOverride,
|
||||
},
|
||||
Error, Result,
|
||||
};
|
||||
|
||||
@@ -136,16 +139,6 @@ fn parse_bool(parameter: &pb::requested_model::ModelParameterValue) -> Result<bo
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_token_count(value: &str) -> Option<u64> {
|
||||
let value = value.trim().to_ascii_lowercase();
|
||||
let (number, multiplier) = match value.chars().last()? {
|
||||
'k' => (&value[..value.len() - 1], 1_000),
|
||||
'm' => (&value[..value.len() - 1], 1_000_000),
|
||||
_ => (value.as_str(), 1),
|
||||
};
|
||||
number.parse::<u64>().ok()?.checked_mul(multiplier)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -9,6 +9,7 @@ mod projection;
|
||||
mod provider;
|
||||
mod run;
|
||||
mod runtime_tag;
|
||||
mod token_count;
|
||||
mod tool;
|
||||
mod usage;
|
||||
|
||||
@@ -23,5 +24,6 @@ pub use projection::*;
|
||||
pub use provider::*;
|
||||
pub use run::*;
|
||||
pub use runtime_tag::*;
|
||||
pub(crate) use token_count::*;
|
||||
pub use tool::*;
|
||||
pub use usage::*;
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
pub(crate) fn parse_token_count(value: &str) -> Option<u64> {
|
||||
let value = value.trim().to_ascii_lowercase();
|
||||
let (number, multiplier) = match value.chars().last()? {
|
||||
'k' => (&value[..value.len() - 1], 1_000),
|
||||
'm' => (&value[..value.len() - 1], 1_000_000),
|
||||
_ => (value.as_str(), 1),
|
||||
};
|
||||
number.parse::<u64>().ok()?.checked_mul(multiplier)
|
||||
}
|
||||
|
||||
pub(crate) fn format_token_count(tokens: u64) -> String {
|
||||
if tokens >= 1_000_000 && tokens.is_multiple_of(1_000_000) {
|
||||
format!("{}M", tokens / 1_000_000)
|
||||
} else if tokens >= 1_000 && tokens.is_multiple_of(1_000) {
|
||||
format!("{}K", tokens / 1_000)
|
||||
} else {
|
||||
tokens.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn token_counts_parse_plain_and_abbreviated_values() {
|
||||
assert_eq!(parse_token_count("272000"), Some(272_000));
|
||||
assert_eq!(parse_token_count("272K"), Some(272_000));
|
||||
assert_eq!(parse_token_count("1m"), Some(1_000_000));
|
||||
assert_eq!(parse_token_count("invalid"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_counts_format_exact_thousands_and_millions() {
|
||||
assert_eq!(format_token_count(272_000), "272K");
|
||||
assert_eq!(format_token_count(1_000_000), "1M");
|
||||
assert_eq!(format_token_count(272_001), "272001");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user