u1s1-cli 1.17.0 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -50
- package/dist/agents/claude.d.ts +4 -1
- package/dist/agents/claude.js +6 -3
- package/dist/agents/codex.d.ts +4 -1
- package/dist/agents/codex.js +6 -3
- package/dist/agents/custom-models.d.ts +61 -0
- package/dist/agents/custom-models.js +175 -0
- package/dist/agents/launch.d.ts +3 -0
- package/dist/agents/launch.js +17 -5
- package/dist/agents/models-list.d.ts +65 -0
- package/dist/agents/models-list.js +297 -0
- package/dist/api.d.ts +2 -0
- package/dist/chat-protocol/anthropic.d.ts +29 -0
- package/dist/chat-protocol/anthropic.js +484 -0
- package/dist/chat-protocol/json.d.ts +20 -0
- package/dist/chat-protocol/json.js +22 -0
- package/dist/chat-protocol/responses.d.ts +97 -0
- package/dist/chat-protocol/responses.js +1076 -0
- package/dist/chat-protocol/sse.d.ts +19 -0
- package/dist/chat-protocol/sse.js +67 -0
- package/dist/chat-protocol/thinking.d.ts +22 -0
- package/dist/chat-protocol/thinking.js +58 -0
- package/dist/config-command.js +5 -1
- package/dist/config.d.ts +22 -0
- package/dist/config.js +51 -0
- package/dist/custom-endpoint-proxy.d.ts +92 -0
- package/dist/custom-endpoint-proxy.js +461 -0
- package/dist/device-auth.d.ts +13 -2
- package/dist/device-auth.js +36 -2
- package/dist/index.js +9 -1
- package/dist/launcher.js +8 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,72 +1,47 @@
|
|
|
1
|
-
# u1s1 —
|
|
1
|
+
# u1s1 — an AI coding buddy in your terminal
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Describe what you need in plain words; u1s1 reads your files, edits code and runs commands. No model setup, no API keys to manage: sign in with your u1s1.dev account and start.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
不需要提前装任何东西,复制下面一行到终端粘贴运行:
|
|
7
|
+
**One-line installer (recommended, bundles its own Node.js)**
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
macOS / Linux
|
|
11
10
|
```bash
|
|
12
|
-
curl -fsSL https://
|
|
11
|
+
curl -fsSL https://u1s1.dev/releases/install.sh | bash
|
|
13
12
|
```
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
Windows (PowerShell)
|
|
16
15
|
```powershell
|
|
17
|
-
|
|
16
|
+
irm https://u1s1.dev/releases/install.ps1 | iex
|
|
18
17
|
```
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
### 或者,手动安装(需要 Node.js 20+)
|
|
19
|
+
**Or with npm (Node.js 22.19+)**
|
|
23
20
|
```bash
|
|
24
21
|
npm i -g u1s1-cli
|
|
25
22
|
```
|
|
26
23
|
|
|
27
|
-
|
|
24
|
+
This package connects to the international site, u1s1.dev. If this computer was already set up for another site, u1s1 keeps that setup; to switch, run `u1s1 login --site global`.
|
|
25
|
+
|
|
26
|
+
## Get started
|
|
28
27
|
```bash
|
|
29
|
-
#
|
|
28
|
+
# First run walks you through signing in at u1s1.dev
|
|
30
29
|
u1s1
|
|
31
30
|
|
|
32
|
-
#
|
|
33
|
-
cd
|
|
31
|
+
# Then, inside your project
|
|
32
|
+
cd my-project
|
|
34
33
|
u1s1
|
|
35
34
|
```
|
|
36
35
|
|
|
37
|
-
##
|
|
36
|
+
## Common commands
|
|
38
37
|
|
|
39
|
-
|
|
|
38
|
+
| Command | What it does |
|
|
40
39
|
|---|---|
|
|
41
|
-
| `u1s1` |
|
|
42
|
-
| `u1s1 -p "
|
|
43
|
-
| `u1s1 usage` |
|
|
44
|
-
| `u1s1 model` |
|
|
45
|
-
| `u1s1 update` |
|
|
46
|
-
| `u1s1
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
## 长会话自动压缩
|
|
50
|
-
|
|
51
|
-
对话越长,每一轮上传的历史越多,回复会变慢、也更容易出错。u1s1 会在上下文接近 **200K token** 时自动压缩(把早期对话浓缩成摘要,最近 2 万 token 原文保留),压缩时界面有提示;`/context` 可以随时看当前占用、预算和触发点,`/compact` 可以手动压缩。
|
|
52
|
-
|
|
53
|
-
这只改变压缩时机,不改变模型能力。想调整或关闭,编辑 `~/.u1s1/agent/settings.json`:
|
|
54
|
-
|
|
55
|
-
```json
|
|
56
|
-
{ "compaction": { "maxContextTokens": 200000 } }
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
`0` 表示不限(按模型自身窗口,如 DeepSeek Flash 的 1M)。改完重启 u1s1 生效。
|
|
60
|
-
|
|
61
|
-
## 在终端粘贴图片
|
|
62
|
-
|
|
63
|
-
默认模型 DeepSeek Flash 支持视觉输入,复制截图或图片后,在 u1s1 输入框按 `Ctrl+V` 即可附加图片。macOS 这里也使用 `Ctrl+V`;`Cmd+V` 由 Terminal/iTerm 接管,只适合粘贴文本,无法把剪贴板图片交给 CLI。
|
|
64
|
-
|
|
65
|
-
## 有一说一
|
|
66
|
-
|
|
67
|
-
- **背后模型**:默认 DeepSeek Flash(1M 上下文,每天免费 $2);难题可随时 `u1s1 model grok` 切 Grok 4.6。对话里 `/model` 也会记住,下次启动还是这个。
|
|
68
|
-
- **怎么收费**:DeepSeek Flash 每天有 $2 免费用量,北京时间 0 点恢复且不累计。免费用完或切换其他模型后,按 API 实际成本从永久余额扣,不加价;新用户另送一次性 $10 加量。
|
|
69
|
-
- **额度不够**:邀请朋友,你俩各得 $1 永久加量 → [u1s1app.com/dashboard](https://u1s1app.com/dashboard)
|
|
70
|
-
- **内核**:基于开源的 [pi](https://pi.dev) coding agent,会话管理、斜杠命令、皮肤等能力全都有。
|
|
71
|
-
|
|
72
|
-
官网:[u1s1app.com](https://u1s1app.com)
|
|
40
|
+
| `u1s1` | Interactive mode |
|
|
41
|
+
| `u1s1 -p "fix the typos in README"` | One-shot mode: do the task, then exit |
|
|
42
|
+
| `u1s1 usage` | See how much credit you've used |
|
|
43
|
+
| `u1s1 model` | Show or change the default model |
|
|
44
|
+
| `u1s1 update` | Update u1s1 to the latest version |
|
|
45
|
+
| `u1s1 login` / `u1s1 logout` | Sign in / sign out |
|
|
46
|
+
|
|
47
|
+
Website: [u1s1.dev](https://u1s1.dev)
|
package/dist/agents/claude.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type McpLaunchEnv } from "../mcp-server.js";
|
|
2
2
|
import { type AgentConfig } from "./agent-config.js";
|
|
3
|
+
import { type AgentCustomModel } from "./custom-models.js";
|
|
3
4
|
import { type RuntimePaths } from "./runtime.js";
|
|
4
5
|
export { agentsRoot, CLAUDE_TERMS_URL, cliInstallHooks, consentLines, notOpenText, overMaxTestedText, printRuntimeFailure, runtimeErrorText, spawnAgent, } from "./launch.js";
|
|
5
6
|
export declare const CLAUDE_GATEWAY_AGENT: "claude-code";
|
|
@@ -72,7 +73,9 @@ export declare function sanitizeClaudeSettings(settings: Record<string, unknown>
|
|
|
72
73
|
* model 用调用方选好的(用户在 u1s1 config 里定的、仍在阵容里的模型),没给时保留网关的,
|
|
73
74
|
* 网关也没有就用 default_model。
|
|
74
75
|
*/
|
|
75
|
-
export declare function claudeSettingsFromConfig(config: AgentConfig, model?: string | null
|
|
76
|
+
export declare function claudeSettingsFromConfig(config: AgentConfig, model?: string | null,
|
|
77
|
+
/** 自定义端点模型:接在 /model 列表后面(custom-models.ts withCustomClaudeModels) */
|
|
78
|
+
customModels?: readonly AgentCustomModel[]): Record<string, unknown>;
|
|
76
79
|
/** 调用方的 --settings 有问题(缺值、读不了、不是 JSON 对象);runClaude 报出来后以非 0 退出。 */
|
|
77
80
|
export declare class ClaudeSettingsArgError extends Error {
|
|
78
81
|
name: string;
|
package/dist/agents/claude.js
CHANGED
|
@@ -22,6 +22,7 @@ import { join, resolve } from "node:path";
|
|
|
22
22
|
import { writePrivateFile } from "../config.js";
|
|
23
23
|
import { lang, tr } from "../i18n.js";
|
|
24
24
|
import { MCP_ENV_NAMES, MCP_SERVER_NAME, MCP_TRACE_ENV, mcpInjectionDisabled, mcpLaunchEnv, mcpServerCommand, } from "../mcp-server.js";
|
|
25
|
+
import { withCustomClaudeModels } from "./custom-models.js";
|
|
25
26
|
import { AGENT_PROFILES, agentsRoot, prepareAgentLaunch, runPreparedAgent, } from "./launch.js";
|
|
26
27
|
import { runtimePaths } from "./runtime.js";
|
|
27
28
|
import { stripSecretEnv } from "../secret-env.js";
|
|
@@ -198,13 +199,15 @@ export function sanitizeClaudeSettings(settings) {
|
|
|
198
199
|
* model 用调用方选好的(用户在 u1s1 config 里定的、仍在阵容里的模型),没给时保留网关的,
|
|
199
200
|
* 网关也没有就用 default_model。
|
|
200
201
|
*/
|
|
201
|
-
export function claudeSettingsFromConfig(config, model
|
|
202
|
+
export function claudeSettingsFromConfig(config, model,
|
|
203
|
+
/** 自定义端点模型:接在 /model 列表后面(custom-models.ts withCustomClaudeModels) */
|
|
204
|
+
customModels = []) {
|
|
202
205
|
const out = sanitizeClaudeSettings(config.settings ?? {});
|
|
203
206
|
if (model)
|
|
204
207
|
out["model"] = model;
|
|
205
208
|
else if (out["model"] === undefined && config.defaultModel)
|
|
206
209
|
out["model"] = config.defaultModel;
|
|
207
|
-
return out;
|
|
210
|
+
return withCustomClaudeModels(out, customModels, out["model"]);
|
|
208
211
|
}
|
|
209
212
|
// ---------------------------------------------------------------- 调用方的 --settings
|
|
210
213
|
/** 调用方的 --settings 有问题(缺值、读不了、不是 JSON 对象);runClaude 报出来后以非 0 退出。 */
|
|
@@ -385,7 +388,7 @@ export async function runClaude(userArgs) {
|
|
|
385
388
|
const mcp = mcpInjectionDisabled()
|
|
386
389
|
? undefined
|
|
387
390
|
: mcpLaunchEnv({ proxyBaseUrl: prepared.proxy.baseUrl, localToken: prepared.proxy.localKey, features: prepared.features, lang: lang() });
|
|
388
|
-
const settings = claudeSettingsFromConfig(prepared.config, prepared.model);
|
|
391
|
+
const settings = claudeSettingsFromConfig(prepared.config, prepared.model, prepared.customModels);
|
|
389
392
|
cleanStaleSettingsFiles(paths.root);
|
|
390
393
|
const settingsFile = callerSettings.length ? processSettingsFile(paths) : paths.settingsFile;
|
|
391
394
|
writeClaudeSettings(settingsFile, finalClaudeSettings(settings, callerSettings, !!mcp));
|
package/dist/agents/codex.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type McpLaunchEnv } from "../mcp-server.js";
|
|
2
2
|
import type { AgentConfig } from "./agent-config.js";
|
|
3
|
+
import { type AgentCustomModel } from "./custom-models.js";
|
|
3
4
|
import { type RuntimePaths } from "./runtime.js";
|
|
4
5
|
export declare const CODEX_GATEWAY_AGENT: "codex";
|
|
5
6
|
/** config.toml 里我们的 provider id(不能用 openai、ollama 等内置保留名)。 */
|
|
@@ -98,7 +99,9 @@ export declare function modelCatalogFromConfig(config: AgentConfig): Record<stri
|
|
|
98
99
|
*/
|
|
99
100
|
export declare function writeCodexConfig(paths: CodexPaths, config: AgentConfig, now?: () => number, mcp?: CodexMcpServer | null,
|
|
100
101
|
/** 这次启动用的模型(launch.ts 按 u1s1 config 选好);undefined = 网关默认 */
|
|
101
|
-
model?: string | null
|
|
102
|
+
model?: string | null,
|
|
103
|
+
/** 自定义端点模型:接在网关目录后面(custom-models.ts withCustomCodexCatalog) */
|
|
104
|
+
customModels?: readonly AgentCustomModel[]): string | undefined;
|
|
102
105
|
/** [mcp_servers.u1s1] 的内容:同一个 node、同一份 CLI;只列要转发的变量名。 */
|
|
103
106
|
export declare function codexMcpServer(command?: {
|
|
104
107
|
command: string;
|
package/dist/agents/codex.js
CHANGED
|
@@ -25,6 +25,7 @@ import { writePrivateFile } from "../config.js";
|
|
|
25
25
|
import { lang, tr } from "../i18n.js";
|
|
26
26
|
import { MCP_ENV_NAMES, MCP_SERVER_NAME, MCP_TRACE_ENV, mcpInjectionDisabled, mcpLaunchEnv, mcpServerCommand, } from "../mcp-server.js";
|
|
27
27
|
import { stripSecretEnv } from "../secret-env.js";
|
|
28
|
+
import { withCustomCodexCatalog } from "./custom-models.js";
|
|
28
29
|
import { AGENT_PROFILES, agentsRoot, prepareAgentLaunch, runPreparedAgent } from "./launch.js";
|
|
29
30
|
import { runtimePaths } from "./runtime.js";
|
|
30
31
|
export const CODEX_GATEWAY_AGENT = "codex";
|
|
@@ -334,9 +335,11 @@ export function modelCatalogFromConfig(config) {
|
|
|
334
335
|
*/
|
|
335
336
|
export function writeCodexConfig(paths, config, now = Date.now, mcp = null,
|
|
336
337
|
/** 这次启动用的模型(launch.ts 按 u1s1 config 选好);undefined = 网关默认 */
|
|
337
|
-
model = config.defaultModel
|
|
338
|
+
model = config.defaultModel,
|
|
339
|
+
/** 自定义端点模型:接在网关目录后面(custom-models.ts withCustomCodexCatalog) */
|
|
340
|
+
customModels = []) {
|
|
338
341
|
mkdirSync(paths.configDir, { recursive: true, mode: 0o700 });
|
|
339
|
-
const catalog = modelCatalogFromConfig(config);
|
|
342
|
+
const catalog = withCustomCodexCatalog(modelCatalogFromConfig(config), customModels);
|
|
340
343
|
if (catalog)
|
|
341
344
|
writePrivateFile(paths.catalogFile, JSON.stringify(catalog) + "\n");
|
|
342
345
|
else
|
|
@@ -382,7 +385,7 @@ export async function runCodex(userArgs) {
|
|
|
382
385
|
const mcp = mcpInjectionDisabled()
|
|
383
386
|
? undefined
|
|
384
387
|
: mcpLaunchEnv({ proxyBaseUrl: prepared.proxy.baseUrl, localToken: prepared.proxy.localKey, features: prepared.features, lang: lang() });
|
|
385
|
-
const backup = writeCodexConfig(paths, prepared.config, Date.now, mcp ? codexMcpServer() : null, prepared.model);
|
|
388
|
+
const backup = writeCodexConfig(paths, prepared.config, Date.now, mcp ? codexMcpServer() : null, prepared.model, prepared.customModels);
|
|
386
389
|
if (backup) {
|
|
387
390
|
console.error(tr(` ${paths.configToml} 里有 u1s1 没法逐项改写的写法,已整份重写;原文件备份在 ${backup}。`, ` ${paths.configToml} had entries u1s1 couldn't update in place, so it was rewritten; the original is saved as ${backup}.`));
|
|
388
391
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 自定义端点的模型给 Claude Code / Codex 用(规划 docs/custom-endpoints-all-clients-plan-2026-11-13.md 的 A2)。
|
|
3
|
+
*
|
|
4
|
+
* 客户端里的模型 id 是 `<端点 id>/<模型 id>`(端点 id = ep + 32 位 hex,模型 id 原样,可以再带 `/`),
|
|
5
|
+
* 与 Pi 的 `provider/model` 写法一致;本机签名代理按这个 id 认出自定义模型,在本机转成 Chat Completions
|
|
6
|
+
* 直连端点(custom-endpoint-proxy.ts),不经网关、不计费。网关模型的 id 里没有这种前缀,照旧走网关。
|
|
7
|
+
*
|
|
8
|
+
* 只收 api = openai-completions 的端点(本地路由只会转 Chat Completions)。
|
|
9
|
+
*/
|
|
10
|
+
import { type CustomEndpoint, type ModelDef } from "../config.js";
|
|
11
|
+
/** 本地路由认的 id:`ep<32 hex>/<模型 id>`。 */
|
|
12
|
+
export declare const CUSTOM_MODEL_ID_RE: RegExp;
|
|
13
|
+
export declare const CUSTOM_ENDPOINT_API = "openai-completions";
|
|
14
|
+
export interface AgentCustomModel {
|
|
15
|
+
/** 客户端里用的 id(--model / -m / /model 里的值) */
|
|
16
|
+
id: string;
|
|
17
|
+
endpoint: CustomEndpoint;
|
|
18
|
+
model: ModelDef;
|
|
19
|
+
}
|
|
20
|
+
export declare function customModelId(endpointId: string, modelId: string): string;
|
|
21
|
+
/** 端点能不能给 Claude Code / Codex 用:只认 Chat Completions 协议。 */
|
|
22
|
+
export declare function routableEndpoint(ep: Pick<CustomEndpoint, "id" | "api">): boolean;
|
|
23
|
+
/** 某客户端(Claude Code / Codex)可用的自定义模型,端点顺序、端点内模型顺序。A1 的模型列表也用它。 */
|
|
24
|
+
export declare function agentCustomModels(endpoints: readonly CustomEndpoint[]): AgentCustomModel[];
|
|
25
|
+
export type CustomModelLookup =
|
|
26
|
+
/** 不是自定义模型的写法:交给网关 */
|
|
27
|
+
{
|
|
28
|
+
kind: "gateway";
|
|
29
|
+
}
|
|
30
|
+
/** 自定义模型的写法,但端点或模型已不在(删了、改了名):本机直接报错,不发给网关 */
|
|
31
|
+
| {
|
|
32
|
+
kind: "missing";
|
|
33
|
+
endpointId: string;
|
|
34
|
+
modelId: string;
|
|
35
|
+
} | {
|
|
36
|
+
kind: "custom";
|
|
37
|
+
found: AgentCustomModel;
|
|
38
|
+
};
|
|
39
|
+
export declare function lookupCustomModel(endpoints: readonly CustomEndpoint[], requested: unknown): CustomModelLookup;
|
|
40
|
+
/**
|
|
41
|
+
* 把自定义模型接在 /model 列表(modelPicker.options)后面。Claude Code 不认识这些 id,窗口只有一个全局的
|
|
42
|
+
* CLAUDE_CODE_MAX_CONTEXT_TOKENS:启动模型是自定义模型时设成它的窗口,否则不动(不让一个小窗口的本地模型
|
|
43
|
+
* 拖低网关模型的压缩点;会话中途从网关模型换到更小窗口的自定义模型时可能超窗,由端点报错)。
|
|
44
|
+
*/
|
|
45
|
+
export declare function withCustomClaudeModels(settings: Record<string, unknown>, customs: readonly AgentCustomModel[], launchModel: string | null | undefined): Record<string, unknown>;
|
|
46
|
+
/**
|
|
47
|
+
* Codex /model 里给自定义模型列的思考档位(本地路由按 chatReasoningFields 转成 reasoning_effort):
|
|
48
|
+
* 带思考契约的按契约(关不掉的不列 none);只标了推理的给 none/low/medium/high,默认 medium;非推理不列。
|
|
49
|
+
*/
|
|
50
|
+
export declare function codexCustomReasoning(model: Pick<ModelDef, "reasoning" | "thinking">): {
|
|
51
|
+
levels: string[];
|
|
52
|
+
defaultLevel: string | null;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* 自定义模型的 model_catalog 条目。指令模板等 Codex 必填但与模型无关的字段取网关目录的第一条
|
|
56
|
+
* (网关目录每条都带同一份模板);目录为空时没有模板可用,返回 undefined(Codex 对目录外的模型用
|
|
57
|
+
* 兜底元数据,照样能用)。
|
|
58
|
+
*/
|
|
59
|
+
export declare function codexCatalogEntry(c: AgentCustomModel, template: Record<string, unknown>, priority: number): Record<string, unknown>;
|
|
60
|
+
/** 网关的 model_catalog + 自定义模型(排在后面)。没有网关目录或目录为空时原样返回。 */
|
|
61
|
+
export declare function withCustomCodexCatalog(catalog: Record<string, unknown> | undefined, customs: readonly AgentCustomModel[]): Record<string, unknown> | undefined;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 自定义端点的模型给 Claude Code / Codex 用(规划 docs/custom-endpoints-all-clients-plan-2026-11-13.md 的 A2)。
|
|
3
|
+
*
|
|
4
|
+
* 客户端里的模型 id 是 `<端点 id>/<模型 id>`(端点 id = ep + 32 位 hex,模型 id 原样,可以再带 `/`),
|
|
5
|
+
* 与 Pi 的 `provider/model` 写法一致;本机签名代理按这个 id 认出自定义模型,在本机转成 Chat Completions
|
|
6
|
+
* 直连端点(custom-endpoint-proxy.ts),不经网关、不计费。网关模型的 id 里没有这种前缀,照旧走网关。
|
|
7
|
+
*
|
|
8
|
+
* 只收 api = openai-completions 的端点(本地路由只会转 Chat Completions)。
|
|
9
|
+
*/
|
|
10
|
+
import { ENDPOINT_ID_RE } from "../config.js";
|
|
11
|
+
/** 本地路由认的 id:`ep<32 hex>/<模型 id>`。 */
|
|
12
|
+
export const CUSTOM_MODEL_ID_RE = /^(ep[0-9a-f]{32})\/(.+)$/;
|
|
13
|
+
export const CUSTOM_ENDPOINT_API = "openai-completions";
|
|
14
|
+
export function customModelId(endpointId, modelId) {
|
|
15
|
+
return `${endpointId}/${modelId}`;
|
|
16
|
+
}
|
|
17
|
+
/** 端点能不能给 Claude Code / Codex 用:只认 Chat Completions 协议。 */
|
|
18
|
+
export function routableEndpoint(ep) {
|
|
19
|
+
return ENDPOINT_ID_RE.test(ep.id) && ep.api === CUSTOM_ENDPOINT_API;
|
|
20
|
+
}
|
|
21
|
+
/** 某客户端(Claude Code / Codex)可用的自定义模型,端点顺序、端点内模型顺序。A1 的模型列表也用它。 */
|
|
22
|
+
export function agentCustomModels(endpoints) {
|
|
23
|
+
const out = [];
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
for (const endpoint of endpoints) {
|
|
26
|
+
if (!routableEndpoint(endpoint))
|
|
27
|
+
continue;
|
|
28
|
+
for (const model of endpoint.models) {
|
|
29
|
+
const id = customModelId(endpoint.id, model.id);
|
|
30
|
+
if (!model.id || seen.has(id))
|
|
31
|
+
continue;
|
|
32
|
+
seen.add(id);
|
|
33
|
+
out.push({ id, endpoint, model });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
export function lookupCustomModel(endpoints, requested) {
|
|
39
|
+
if (typeof requested !== "string")
|
|
40
|
+
return { kind: "gateway" };
|
|
41
|
+
const m = CUSTOM_MODEL_ID_RE.exec(requested);
|
|
42
|
+
if (!m)
|
|
43
|
+
return { kind: "gateway" };
|
|
44
|
+
const [, endpointId, modelId] = m;
|
|
45
|
+
const endpoint = endpoints.find((ep) => ep.id === endpointId && routableEndpoint(ep));
|
|
46
|
+
const model = endpoint?.models.find((x) => x.id === modelId);
|
|
47
|
+
if (!endpoint || !model)
|
|
48
|
+
return { kind: "missing", endpointId, modelId };
|
|
49
|
+
return { kind: "custom", found: { id: requested, endpoint, model } };
|
|
50
|
+
}
|
|
51
|
+
// ---------------------------------------------------------------- Claude Code
|
|
52
|
+
function contextLabel(tokens) {
|
|
53
|
+
if (tokens >= 1_000_000)
|
|
54
|
+
return `${Math.round(tokens / 100_000) / 10}M context`;
|
|
55
|
+
return `${Math.round(tokens / 1000)}K context`;
|
|
56
|
+
}
|
|
57
|
+
function customDescription(c) {
|
|
58
|
+
const parts = [];
|
|
59
|
+
if (c.model.contextWindow > 0)
|
|
60
|
+
parts.push(contextLabel(c.model.contextWindow));
|
|
61
|
+
parts.push(`custom endpoint ${c.endpoint.name}`);
|
|
62
|
+
return parts.join(" - ");
|
|
63
|
+
}
|
|
64
|
+
function isPlainObject(value) {
|
|
65
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 把自定义模型接在 /model 列表(modelPicker.options)后面。Claude Code 不认识这些 id,窗口只有一个全局的
|
|
69
|
+
* CLAUDE_CODE_MAX_CONTEXT_TOKENS:启动模型是自定义模型时设成它的窗口,否则不动(不让一个小窗口的本地模型
|
|
70
|
+
* 拖低网关模型的压缩点;会话中途从网关模型换到更小窗口的自定义模型时可能超窗,由端点报错)。
|
|
71
|
+
*/
|
|
72
|
+
export function withCustomClaudeModels(settings, customs, launchModel) {
|
|
73
|
+
if (customs.length === 0)
|
|
74
|
+
return settings;
|
|
75
|
+
const out = { ...settings };
|
|
76
|
+
const picker = isPlainObject(out["modelPicker"]) ? { ...out["modelPicker"] } : {};
|
|
77
|
+
const options = Array.isArray(picker["options"]) ? [...picker["options"]] : [];
|
|
78
|
+
const listed = new Set(options.map((o) => (isPlainObject(o) ? o["model"] : undefined)));
|
|
79
|
+
for (const c of customs) {
|
|
80
|
+
if (listed.has(c.id))
|
|
81
|
+
continue;
|
|
82
|
+
options.push({ model: c.id, label: c.model.name || c.model.id, description: customDescription(c) });
|
|
83
|
+
}
|
|
84
|
+
picker["options"] = options;
|
|
85
|
+
out["modelPicker"] = picker;
|
|
86
|
+
const launched = customs.find((c) => c.id === launchModel);
|
|
87
|
+
if (launched && launched.model.contextWindow > 0) {
|
|
88
|
+
const env = isPlainObject(out["env"]) ? { ...out["env"] } : {};
|
|
89
|
+
env["CLAUDE_CODE_MAX_CONTEXT_TOKENS"] = String(launched.model.contextWindow);
|
|
90
|
+
out["env"] = env;
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
// ---------------------------------------------------------------- Codex
|
|
95
|
+
const CODEX_EFFORT_TEXT = {
|
|
96
|
+
none: "No reasoning",
|
|
97
|
+
minimal: "Minimal reasoning",
|
|
98
|
+
low: "Fast responses with lighter reasoning",
|
|
99
|
+
medium: "Balanced speed and reasoning depth",
|
|
100
|
+
high: "Deeper reasoning for complex problems",
|
|
101
|
+
xhigh: "Extra deep reasoning",
|
|
102
|
+
};
|
|
103
|
+
/** u1s1 思考档位 → Codex 的 reasoning effort;max 在 Responses 里没有对应档,不列。 */
|
|
104
|
+
const CODEX_EFFORT = {
|
|
105
|
+
off: "none",
|
|
106
|
+
minimal: "minimal",
|
|
107
|
+
low: "low",
|
|
108
|
+
medium: "medium",
|
|
109
|
+
high: "high",
|
|
110
|
+
xhigh: "xhigh",
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Codex /model 里给自定义模型列的思考档位(本地路由按 chatReasoningFields 转成 reasoning_effort):
|
|
114
|
+
* 带思考契约的按契约(关不掉的不列 none);只标了推理的给 none/low/medium/high,默认 medium;非推理不列。
|
|
115
|
+
*/
|
|
116
|
+
export function codexCustomReasoning(model) {
|
|
117
|
+
if (!model.reasoning)
|
|
118
|
+
return { levels: [], defaultLevel: null };
|
|
119
|
+
const caps = model.thinking;
|
|
120
|
+
if (caps && caps.levels.length) {
|
|
121
|
+
const levels = caps.levels
|
|
122
|
+
.filter((level) => level !== "off" || caps.canDisable)
|
|
123
|
+
.map((level) => CODEX_EFFORT[level])
|
|
124
|
+
.filter((effort) => typeof effort === "string");
|
|
125
|
+
const preferred = CODEX_EFFORT[caps.defaultLevel];
|
|
126
|
+
const defaultLevel = preferred && levels.includes(preferred) ? preferred : levels.filter((e) => e !== "none").at(-1) ?? null;
|
|
127
|
+
return { levels, defaultLevel };
|
|
128
|
+
}
|
|
129
|
+
return { levels: ["none", "low", "medium", "high"], defaultLevel: "medium" };
|
|
130
|
+
}
|
|
131
|
+
/** 与网关 codex-config.ts 一致:模型行没设压缩预算时按 200K 压。 */
|
|
132
|
+
const CODEX_DEFAULT_COMPACT_TOKENS = 200_000;
|
|
133
|
+
/**
|
|
134
|
+
* 自定义模型的 model_catalog 条目。指令模板等 Codex 必填但与模型无关的字段取网关目录的第一条
|
|
135
|
+
* (网关目录每条都带同一份模板);目录为空时没有模板可用,返回 undefined(Codex 对目录外的模型用
|
|
136
|
+
* 兜底元数据,照样能用)。
|
|
137
|
+
*/
|
|
138
|
+
export function codexCatalogEntry(c, template, priority) {
|
|
139
|
+
const reasoning = codexCustomReasoning(c.model);
|
|
140
|
+
const levels = reasoning.levels;
|
|
141
|
+
const window = c.model.contextWindow > 0 ? c.model.contextWindow : undefined;
|
|
142
|
+
const entry = {
|
|
143
|
+
...template,
|
|
144
|
+
slug: c.id,
|
|
145
|
+
display_name: c.model.name || c.model.id,
|
|
146
|
+
description: customDescription(c),
|
|
147
|
+
supported_reasoning_levels: levels.map((effort) => ({ effort, description: CODEX_EFFORT_TEXT[effort] ?? effort })),
|
|
148
|
+
priority,
|
|
149
|
+
input_modalities: c.model.vision ? ["text", "image"] : ["text"],
|
|
150
|
+
};
|
|
151
|
+
delete entry["default_reasoning_level"];
|
|
152
|
+
if (reasoning.defaultLevel)
|
|
153
|
+
entry["default_reasoning_level"] = reasoning.defaultLevel;
|
|
154
|
+
if (window) {
|
|
155
|
+
entry["context_window"] = window;
|
|
156
|
+
entry["max_context_window"] = window;
|
|
157
|
+
// 压缩点:200K 与窗口的 90% 取小(本地模型窗口常比 200K 小)
|
|
158
|
+
entry["auto_compact_token_limit"] = Math.min(CODEX_DEFAULT_COMPACT_TOKENS, Math.floor(window * 0.9));
|
|
159
|
+
}
|
|
160
|
+
return entry;
|
|
161
|
+
}
|
|
162
|
+
/** 网关的 model_catalog + 自定义模型(排在后面)。没有网关目录或目录为空时原样返回。 */
|
|
163
|
+
export function withCustomCodexCatalog(catalog, customs) {
|
|
164
|
+
if (!catalog || customs.length === 0)
|
|
165
|
+
return catalog;
|
|
166
|
+
const models = Array.isArray(catalog["models"]) ? catalog["models"] : [];
|
|
167
|
+
const template = models.find(isPlainObject);
|
|
168
|
+
if (!template)
|
|
169
|
+
return catalog;
|
|
170
|
+
const slugs = new Set(models.map((m) => (isPlainObject(m) ? m["slug"] : undefined)));
|
|
171
|
+
const extra = customs
|
|
172
|
+
.filter((c) => !slugs.has(c.id))
|
|
173
|
+
.map((c, i) => codexCatalogEntry(c, template, models.length + i));
|
|
174
|
+
return { ...catalog, models: [...models, ...extra] };
|
|
175
|
+
}
|
package/dist/agents/launch.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { spawn } from "node:child_process";
|
|
|
9
9
|
import { type ApiFeatures } from "../api.js";
|
|
10
10
|
import { type SiteId } from "../site.js";
|
|
11
11
|
import { type AgentConfig, type GatewayAgentName } from "./agent-config.js";
|
|
12
|
+
import { type AgentCustomModel } from "./custom-models.js";
|
|
12
13
|
import { RuntimePlanError, type DownloadInfo, type EnsuredRuntime, type InstallHooks, type ManagedAgent, type RuntimePaths, type SkippedRuntime } from "./runtime.js";
|
|
13
14
|
export declare function agentsRoot(): string;
|
|
14
15
|
export interface AgentProfile {
|
|
@@ -76,6 +77,8 @@ export interface PreparedLaunch {
|
|
|
76
77
|
site: SiteId;
|
|
77
78
|
/** /v1/models 的能力开关(搜索、云端渲染、生图);没拉到时为 undefined。MCP 工具服务按它决定提供哪些工具 */
|
|
78
79
|
features: ApiFeatures | undefined;
|
|
80
|
+
/** 账号里的自定义端点模型(id = ep…/<模型>):接在客户端模型阵容后面,请求由签名代理在本机直连端点 */
|
|
81
|
+
customModels: AgentCustomModel[];
|
|
79
82
|
}
|
|
80
83
|
/**
|
|
81
84
|
* 子进程启动前的公共部分。返回 number = 启动前就失败的退出码(已向用户说明)。
|
package/dist/agents/launch.js
CHANGED
|
@@ -11,12 +11,13 @@ import { constants as osConstants } from "node:os";
|
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { createInterface } from "node:readline/promises";
|
|
13
13
|
import { Agent, fetch as undiciFetch } from "undici";
|
|
14
|
-
import { AccessDeniedError, AuthError, fetchModels } from "../api.js";
|
|
15
|
-
import { u1s1Dir } from "../config.js";
|
|
14
|
+
import { AccessDeniedError, AuthError, fetchModels, loadCustomEndpoints } from "../api.js";
|
|
15
|
+
import { CUSTOM_ENDPOINTS, u1s1Dir } from "../config.js";
|
|
16
16
|
import { ensureSigningProxy } from "../device-auth.js";
|
|
17
17
|
import { tr } from "../i18n.js";
|
|
18
18
|
import { siteLinks, siteOf } from "../site.js";
|
|
19
19
|
import { loadAgentConfig } from "./agent-config.js";
|
|
20
|
+
import { agentCustomModels } from "./custom-models.js";
|
|
20
21
|
import { readAgentPrefs, resolveLaunchModel } from "./prefs.js";
|
|
21
22
|
import { dailyUpdateCheck, ensureAgentRuntime, fetchLatestVersion, npmRegistryFor, RuntimeInstallError, RuntimePlanError, runtimeSpec, } from "./runtime.js";
|
|
22
23
|
export function agentsRoot() {
|
|
@@ -319,8 +320,16 @@ export async function prepareAgentLaunch(profile, paths) {
|
|
|
319
320
|
return 1;
|
|
320
321
|
}
|
|
321
322
|
const config = result.config;
|
|
323
|
+
// 自定义端点:拉不到时用上次的缓存(与启动 Pi 同一做法)
|
|
324
|
+
await loadCustomEndpoints(cfg).catch(() => undefined);
|
|
325
|
+
const customModels = agentCustomModels(CUSTOM_ENDPOINTS);
|
|
322
326
|
const prefs = readAgentPrefs(profile.agent);
|
|
323
|
-
|
|
327
|
+
// 用户在 u1s1 config 里选的模型可以是自定义模型
|
|
328
|
+
const lineup = {
|
|
329
|
+
...config,
|
|
330
|
+
models: [...config.models, ...customModels.map((c) => ({ id: c.id, name: c.model.name, contextWindow: c.model.contextWindow }))],
|
|
331
|
+
};
|
|
332
|
+
const model = resolveLaunchModel(profile.agent, lineup, {
|
|
324
333
|
prefs,
|
|
325
334
|
notify: (missing, fallback) => console.error(modelMissingText(profile.agent, missing, fallback)),
|
|
326
335
|
});
|
|
@@ -343,8 +352,11 @@ export async function prepareAgentLaunch(profile, paths) {
|
|
|
343
352
|
}
|
|
344
353
|
printRuntimeNotes(runtime, profile.agent);
|
|
345
354
|
// 子进程用的代理:带上客户端名与版本,网关据此核对请求形状
|
|
346
|
-
|
|
347
|
-
|
|
355
|
+
// 自定义模型的请求在这个代理里本机直连端点(custom-endpoint-proxy.ts),不经网关
|
|
356
|
+
const proxy = await ensureSigningProxy(cfg, "terminal", attestation, { name: profile.gatewayAgent, version: runtime.version }, {
|
|
357
|
+
endpoints: [...CUSTOM_ENDPOINTS],
|
|
358
|
+
});
|
|
359
|
+
return { config, model, autoUpdate: prefs.autoUpdate, runtime, proxy, site: site.id, features: models?.features, customModels };
|
|
348
360
|
}
|
|
349
361
|
/** 用户在 u1s1 config 里选的模型不在当前阵容里(只提示一次,见 prefs.ts)。 */
|
|
350
362
|
export function modelMissingText(agent, missing, fallback) {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { ApiEndpoint, ApiModel } from "../api.js";
|
|
2
|
+
import { type CustomEndpoint, type ModelDef, type ModelRef } from "../config.js";
|
|
3
|
+
import { type AgentConfig, type GatewayAgentName } from "./agent-config.js";
|
|
4
|
+
export type ModelsClient = "pi" | "claude" | "codex";
|
|
5
|
+
export interface ModelEntry {
|
|
6
|
+
/** 该客户端 --model 要的值 */
|
|
7
|
+
id: string;
|
|
8
|
+
/** 显示名;拿不到时等于 id */
|
|
9
|
+
name: string;
|
|
10
|
+
source: "u1s1" | "custom";
|
|
11
|
+
/** source = custom 时:端点名(后台填的) */
|
|
12
|
+
endpoint?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ClientModels {
|
|
15
|
+
default: string | null;
|
|
16
|
+
/** 网关是否开放这个客户端(Pi 恒开放,不带这个字段) */
|
|
17
|
+
enabled?: boolean;
|
|
18
|
+
models: ModelEntry[];
|
|
19
|
+
}
|
|
20
|
+
export interface ModelsListing {
|
|
21
|
+
loggedIn: boolean;
|
|
22
|
+
clients: Partial<Record<ModelsClient, ClientModels>>;
|
|
23
|
+
}
|
|
24
|
+
/** Pi 的 --model 写法:provider/model(和 `u1s1 pi --list-models` 两列拼起来一致)。 */
|
|
25
|
+
export declare function piModelId(provider: string, modelId: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* 自定义端点的模型 → 列表条目。idFor 决定该客户端认的 id:Pi 是 provider/model;
|
|
28
|
+
* Claude Code / Codex 接上本地路由后(A2)按各自的写法传进来。
|
|
29
|
+
*/
|
|
30
|
+
export declare function customModelEntries(endpoints: readonly CustomEndpoint[], idFor: (endpoint: CustomEndpoint, model: ModelDef) => string): ModelEntry[];
|
|
31
|
+
export declare function piClientModels(official: readonly ModelDef[], endpoints: readonly CustomEndpoint[], preferred: ModelRef): ClientModels;
|
|
32
|
+
/**
|
|
33
|
+
* Claude Code / Codex:网关阵容(agent-config 缓存)+ 自定义端点的模型(只收本地路由转得了的
|
|
34
|
+
* openai-completions 端点,id = ep…/<模型>,见 custom-models.ts)。default 是启动时真正会用的模型:
|
|
35
|
+
* 用户在 u1s1 config 里选的(网关模型或自定义模型)还在就是它,否则网关默认——与 launch.ts 同一判断。
|
|
36
|
+
*/
|
|
37
|
+
export declare function gatewayClientModels(config: AgentConfig, preferredModel: string | undefined, endpoints?: readonly CustomEndpoint[]): ClientModels;
|
|
38
|
+
/**
|
|
39
|
+
* 上次启动 Pi 时写进 <agentDir>/models.json 的 u1s1 provider。那里的 baseUrl 通常是本机签名代理,
|
|
40
|
+
* 认不出站点;只排除明确指向别的站点的条目(换站后第一次启动 Pi 之前,离线时可能列出上一站的名字)。
|
|
41
|
+
* 那里的 name 拼了「 · 提示」(见 toProviderModels),这里去掉,只留显示名。
|
|
42
|
+
*/
|
|
43
|
+
export declare function officialModelsFromProviderFile(path: string, baseUrl: string): ModelDef[] | undefined;
|
|
44
|
+
/** endpoints.json 只取列表要用的字段,形状不对的条目跳过。 */
|
|
45
|
+
export declare function endpointsFromCache(raw: unknown): CustomEndpoint[];
|
|
46
|
+
export declare function cachedAgentConfig(cacheFile: string, site: string, agent: GatewayAgentName): AgentConfig | undefined;
|
|
47
|
+
export interface LiveCatalog {
|
|
48
|
+
models?: ApiModel[];
|
|
49
|
+
endpoints?: ApiEndpoint[];
|
|
50
|
+
}
|
|
51
|
+
export declare const LIVE_REFRESH_TIMEOUT_MS = 2500;
|
|
52
|
+
/**
|
|
53
|
+
* 并行拉 /v1/models 与 /v1/endpoints,整体不超过 timeoutMs;到点一起中止,
|
|
54
|
+
* 任何失败(断网、401、5xx、格式不对)都只是让对应字段缺席,调用方退回缓存。
|
|
55
|
+
*/
|
|
56
|
+
export declare function fetchLiveCatalog(get: (path: "/models" | "/endpoints", signal: AbortSignal) => Promise<Response>, timeoutMs?: number): Promise<LiveCatalog>;
|
|
57
|
+
export interface ModelsCommandDeps {
|
|
58
|
+
offline?: boolean;
|
|
59
|
+
/** 测试注入;默认经 authorizedFetch 请求当前站点 */
|
|
60
|
+
fetchLive?: () => Promise<LiveCatalog>;
|
|
61
|
+
}
|
|
62
|
+
export declare function buildModelsListing(deps?: ModelsCommandDeps): Promise<ModelsListing>;
|
|
63
|
+
/** 人看的表。 */
|
|
64
|
+
export declare function formatModelsTable(listing: ModelsListing): string[];
|
|
65
|
+
export declare function modelsCommand(args: readonly string[], deps?: ModelsCommandDeps): Promise<number>;
|