@armadra/agent 0.6.0 → 0.6.2
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/CHANGELOG.md +43 -0
- package/CHANGELOG.zh-CN.md +32 -0
- package/dist/agent/session-settings.d.ts +4 -1
- package/dist/agent/session-settings.js +7 -2
- package/dist/ai/apis/chatgpt-backend.d.ts +7 -2
- package/dist/ai/apis/chatgpt-backend.js +12 -4
- package/dist/ai/providers/discovered-cache.d.ts +59 -0
- package/dist/ai/providers/discovered-cache.js +146 -0
- package/dist/ai/providers/model-visibility.d.ts +33 -0
- package/dist/ai/providers/model-visibility.js +70 -0
- package/dist/ai/providers/registry.d.ts +2 -0
- package/dist/ai/providers/registry.js +13 -7
- package/dist/ai/types.d.ts +5 -0
- package/dist/auth/chatgpt/backend-client.d.ts +11 -2
- package/dist/auth/chatgpt/backend-client.js +38 -6
- package/dist/auth/chatgpt/cli.d.ts +4 -1
- package/dist/auth/chatgpt/cli.js +40 -6
- package/dist/auth/chatgpt/follow.d.ts +16 -0
- package/dist/auth/chatgpt/follow.js +51 -0
- package/dist/auth/chatgpt/presets.d.ts +8 -0
- package/dist/auth/chatgpt/presets.js +12 -0
- package/dist/auth/testing/fake-oauth.d.ts +4 -1
- package/dist/auth/testing/fake-oauth.js +24 -1
- package/dist/bundle/ama.cjs +1663 -799
- package/dist/cli/compose-providers.d.ts +2 -0
- package/dist/cli/compose-providers.js +9 -0
- package/dist/cli/startup-steps.js +4 -1
- package/dist/cli/subcommands/config-set.js +19 -11
- package/dist/cli/subcommands/models-discover.d.ts +4 -1
- package/dist/cli/subcommands/models-discover.js +51 -3
- package/dist/cli/subcommands/models-enable.d.ts +13 -0
- package/dist/cli/subcommands/models-enable.js +145 -0
- package/dist/cli/subcommands/models.d.ts +2 -0
- package/dist/cli/subcommands/models.js +10 -2
- package/dist/config/json-schema.js +2 -1
- package/dist/config/key-docs.js +7 -1
- package/dist/config/schema-w5.js +9 -2
- package/dist/config/schema-w6.d.ts +1 -1
- package/dist/config/schema-w6.js +2 -1
- package/dist/config/settings-registry.d.ts +3 -0
- package/dist/config/settings-registry.js +1 -0
- package/dist/config/types-w5.d.ts +7 -0
- package/dist/config/types-w5.js +2 -0
- package/dist/config/types-w6.d.ts +5 -0
- package/dist/i18n/catalog.d.ts +50 -3
- package/dist/i18n/messages/auth.d.ts +16 -2
- package/dist/i18n/messages/auth.js +34 -2
- package/dist/i18n/messages/cli-help.js +4 -0
- package/dist/i18n/messages/config-keys.d.ts +6 -0
- package/dist/i18n/messages/config-keys.js +8 -2
- package/dist/i18n/messages/config.d.ts +8 -0
- package/dist/i18n/messages/config.js +2 -0
- package/dist/i18n/messages/errors.d.ts +1 -1
- package/dist/i18n/messages/interactive-startup.d.ts +0 -4
- package/dist/i18n/messages/interactive-startup.js +0 -4
- package/dist/i18n/messages/interactive.d.ts +0 -4
- package/dist/i18n/messages/panels.d.ts +43 -0
- package/dist/i18n/messages/panels.js +43 -0
- package/dist/i18n/messages/settings.d.ts +7 -0
- package/dist/i18n/messages/settings.js +7 -0
- package/dist/i18n/messages/subcommands-config.d.ts +2 -0
- package/dist/i18n/messages/subcommands-config.js +2 -0
- package/dist/i18n/messages/subcommands-models.d.ts +34 -0
- package/dist/i18n/messages/subcommands-models.js +34 -0
- package/dist/i18n/messages/subcommands.d.ts +28 -0
- package/dist/i18n/messages/subcommands.js +3 -0
- package/dist/modes/interactive/commands.d.ts +3 -0
- package/dist/modes/interactive/commands.js +18 -11
- package/dist/modes/interactive/config-ui.js +1 -1
- package/dist/modes/interactive/interactive-mode.js +2 -0
- package/dist/modes/interactive/model-items.d.ts +45 -0
- package/dist/modes/interactive/model-items.js +134 -0
- package/dist/modes/interactive/model-picker.d.ts +57 -0
- package/dist/modes/interactive/model-picker.js +191 -0
- package/dist/modes/interactive/startup-ui.d.ts +3 -9
- package/dist/modes/interactive/startup-ui.js +8 -60
- package/dist/tui/components/select-list.d.ts +2 -0
- package/dist/tui/components/select-list.js +7 -0
- package/docs/en/providers.md +9 -3
- package/docs/en/tui.md +8 -1
- package/docs/providers.md +27 -6
- package/docs/tui.md +8 -1
- package/package.json +1 -1
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 交互模式的 `/model` 选择器:居中覆盖层,列表项见 model-items.ts。
|
|
3
|
+
*
|
|
4
|
+
* - `Tab` 在「已配置 / 全部」两个视图间切换(过滤文本保留);全部视图里没配置的供应商标「未配置 key」,
|
|
5
|
+
* 选中它的模型不切换,选择器底部提示 `ama auth set` / `ama providers add`;
|
|
6
|
+
* - `Space` 把高亮的模型加入 / 移出用户级 `models.enabled`(经 config/edit.ts 写回);只经 `provider/*`
|
|
7
|
+
* 列入的不能单独移出,提示改用 `ama models disable`;
|
|
8
|
+
* - 筛选文本含 `@` 时列出各模型的 `@渠道` 行;
|
|
9
|
+
* - 已在用清单时,从全部视图选中清单外的模型会先把它加入清单再切换;
|
|
10
|
+
* - 提示行(模型表为空的供应商)选中时只在底部显示命令。
|
|
11
|
+
*/
|
|
12
|
+
import { accessReady, addEnabled, enabledList, enabledMatch, removeEnabled, } from "../../ai/providers/model-visibility.js";
|
|
13
|
+
import { setConfigValue } from "../../config/edit.js";
|
|
14
|
+
import { msg } from "../../i18n/index.js";
|
|
15
|
+
import { Box, SelectList, matchesKey, wrapTextWithAnsi, } from "../../tui.js";
|
|
16
|
+
import { layerInputFor } from "./config-ui.js";
|
|
17
|
+
import { catalogItems, isHintValue, loadModelCatalog } from "./model-items.js";
|
|
18
|
+
/** 选择器本体(可单独测试):`done` 收到选中的模型引用或 undefined(取消)。 */
|
|
19
|
+
export class ModelPickerView {
|
|
20
|
+
host;
|
|
21
|
+
catalog;
|
|
22
|
+
options;
|
|
23
|
+
done;
|
|
24
|
+
view = "configured";
|
|
25
|
+
enabled;
|
|
26
|
+
notice;
|
|
27
|
+
channels = false;
|
|
28
|
+
list;
|
|
29
|
+
constructor(host, catalog, options, done) {
|
|
30
|
+
this.host = host;
|
|
31
|
+
this.catalog = catalog;
|
|
32
|
+
this.options = options;
|
|
33
|
+
this.done = done;
|
|
34
|
+
this.enabled = enabledList(options.enabled);
|
|
35
|
+
this.list = new SelectList([], {
|
|
36
|
+
theme: host.theme,
|
|
37
|
+
maxVisible: 12,
|
|
38
|
+
filterable: true,
|
|
39
|
+
showCount: true,
|
|
40
|
+
...(host.keybindings !== undefined ? { keybindings: host.keybindings } : {}),
|
|
41
|
+
...(options.current !== undefined ? { currentValue: options.current } : {}),
|
|
42
|
+
emptyText: msg().panels.model.emptyConfigured,
|
|
43
|
+
onSelect: (item) => this.select(item),
|
|
44
|
+
onCancel: () => this.done(undefined),
|
|
45
|
+
});
|
|
46
|
+
this.rebuild();
|
|
47
|
+
if (options.current !== undefined)
|
|
48
|
+
this.list.selectValue(options.current);
|
|
49
|
+
}
|
|
50
|
+
get focused() {
|
|
51
|
+
return this.list.focused;
|
|
52
|
+
}
|
|
53
|
+
set focused(value) {
|
|
54
|
+
this.list.focused = value;
|
|
55
|
+
}
|
|
56
|
+
get currentView() {
|
|
57
|
+
return this.view;
|
|
58
|
+
}
|
|
59
|
+
rebuild() {
|
|
60
|
+
const m = msg().panels.model;
|
|
61
|
+
this.list.setFooter(this.view === "all" ? m.footerAll : m.footerConfigured);
|
|
62
|
+
this.list.setItems(catalogItems(this.catalog, {
|
|
63
|
+
view: this.view,
|
|
64
|
+
enabled: this.enabled,
|
|
65
|
+
current: this.options.current,
|
|
66
|
+
channels: this.channels,
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
handleInput(data) {
|
|
70
|
+
if (matchesKey(data, "tab")) {
|
|
71
|
+
this.view = this.view === "all" ? "configured" : "all";
|
|
72
|
+
this.notice = undefined;
|
|
73
|
+
this.rebuild();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (data === " ") {
|
|
77
|
+
this.toggle();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this.list.handleInput(data);
|
|
81
|
+
const channels = this.list.getFilter().includes("@");
|
|
82
|
+
if (channels !== this.channels) {
|
|
83
|
+
this.channels = channels;
|
|
84
|
+
this.rebuild();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
providerOf(value) {
|
|
88
|
+
const id = value.slice(0, value.indexOf("/"));
|
|
89
|
+
return this.catalog.providers.find((p) => p.provider.id === id);
|
|
90
|
+
}
|
|
91
|
+
save(next) {
|
|
92
|
+
try {
|
|
93
|
+
this.options.saveEnabled(next);
|
|
94
|
+
this.enabled = next;
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
this.notice = {
|
|
99
|
+
text: error instanceof Error ? error.message : String(error),
|
|
100
|
+
level: "error",
|
|
101
|
+
};
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
toggle() {
|
|
106
|
+
const item = this.list.getSelected();
|
|
107
|
+
if (item === undefined || isHintValue(item.value))
|
|
108
|
+
return;
|
|
109
|
+
const m = msg().panels.model;
|
|
110
|
+
const ref = item.value;
|
|
111
|
+
const match = this.enabled === undefined ? undefined : enabledMatch(this.enabled, ref);
|
|
112
|
+
if (match?.kind === "wildcard") {
|
|
113
|
+
this.notice = { text: m.wildcard(ref, match.pattern), level: "warning" };
|
|
114
|
+
}
|
|
115
|
+
else if (match !== undefined) {
|
|
116
|
+
if (this.save(removeEnabled(this.enabled, [ref])))
|
|
117
|
+
this.notice = { text: m.removed(ref), level: "info" };
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
const first = this.enabled === undefined;
|
|
121
|
+
if (this.save(addEnabled(this.enabled, [ref])))
|
|
122
|
+
this.notice = { text: first ? m.addedFirst(ref) : m.added(ref), level: "info" };
|
|
123
|
+
}
|
|
124
|
+
this.rebuild();
|
|
125
|
+
}
|
|
126
|
+
select(item) {
|
|
127
|
+
const m = msg().panels.model;
|
|
128
|
+
const id = item.value.slice(0, item.value.indexOf("/"));
|
|
129
|
+
if (isHintValue(item.value)) {
|
|
130
|
+
this.notice = { text: item.label, level: "info" };
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const entry = this.providerOf(item.value);
|
|
134
|
+
if (item.value !== this.options.current && entry !== undefined && !accessReady(entry.access)) {
|
|
135
|
+
const text = entry.access === "needsLogin" ? m.needsLoginHint(id) : m.noKeyHint(id);
|
|
136
|
+
this.notice = { text, level: "warning" };
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (this.enabled !== undefined &&
|
|
140
|
+
item.value !== this.options.current &&
|
|
141
|
+
enabledMatch(this.enabled, item.value) === undefined &&
|
|
142
|
+
!this.save(addEnabled(this.enabled, [item.value])))
|
|
143
|
+
return;
|
|
144
|
+
this.done(item.value);
|
|
145
|
+
}
|
|
146
|
+
render(width) {
|
|
147
|
+
const lines = this.list.render(width);
|
|
148
|
+
if (this.notice === undefined)
|
|
149
|
+
return lines;
|
|
150
|
+
const theme = this.host.theme;
|
|
151
|
+
const color = this.notice.level === "info" ? "dim" : this.notice.level;
|
|
152
|
+
return [...lines, ...wrapTextWithAnsi(theme.fg(color, this.notice.text), width)];
|
|
153
|
+
}
|
|
154
|
+
invalidate() {
|
|
155
|
+
this.list.invalidate();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** 打开 `/model` 选择器;返回选中的模型引用,取消返回 undefined。 */
|
|
159
|
+
export async function openModelPicker(host, options) {
|
|
160
|
+
const catalog = await loadModelCatalog(options.providers);
|
|
161
|
+
return new Promise((resolve) => {
|
|
162
|
+
let handle;
|
|
163
|
+
const view = new ModelPickerView(host, catalog, options, (ref) => {
|
|
164
|
+
handle?.hide();
|
|
165
|
+
resolve(ref);
|
|
166
|
+
});
|
|
167
|
+
const width = Math.max(20, Math.min(host.columns() - 2, 72));
|
|
168
|
+
handle = host.showOverlay(new Box(view, { title: options.title, theme: host.theme }), {
|
|
169
|
+
anchor: "center",
|
|
170
|
+
width,
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/** 交互模式装配:清单读 `runtime.config`,写用户级 config.json 并换上新配置。 */
|
|
175
|
+
export function modelPickerFor(host, runtime, context) {
|
|
176
|
+
return (providers, current) => openModelPicker(host, {
|
|
177
|
+
title: msg().interactive.commands.modelTitle,
|
|
178
|
+
providers,
|
|
179
|
+
current,
|
|
180
|
+
enabled: runtime.config.models?.enabled,
|
|
181
|
+
saveEnabled: (next) => {
|
|
182
|
+
const result = setConfigValue({
|
|
183
|
+
...layerInputFor(runtime, context),
|
|
184
|
+
scope: "user",
|
|
185
|
+
key: "models.enabled",
|
|
186
|
+
value: next,
|
|
187
|
+
});
|
|
188
|
+
runtime.replaceConfig?.(result.snapshot.config);
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
}
|
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
* 选择列表 / 输入框的 `TUI`;答完把问答收成一行再 `stop()`——不清屏,问答留在回滚里。
|
|
6
6
|
* 取消(Esc / Ctrl+C)按各回调的约定返回 undefined(bootstrap 据此给退出码)。
|
|
7
7
|
*
|
|
8
|
-
* `modelItems
|
|
8
|
+
* `modelItems`(model-items.ts)/ `sessionItems` 同时供交互模式里的选择器(pickers.ts)使用。启动选择器只列
|
|
9
|
+
* 已配置的模型,一个都没有时列全部。
|
|
9
10
|
*/
|
|
10
|
-
import type { Model, ProviderRegistryApi } from "../../ai/types.js";
|
|
11
11
|
import type { InteractiveUi } from "../../cli/deps.js";
|
|
12
12
|
import type { SessionListItem } from "../../session/types.js";
|
|
13
13
|
import { type Keybindings, type SelectItem, type Terminal, type Theme } from "../../tui.js";
|
|
14
|
+
export { modelDescription, modelItems } from "./model-items.js";
|
|
14
15
|
export interface StartupUiOptions {
|
|
15
16
|
/** 每次问答新建的终端;缺省 `new ProcessTerminal()`。 */
|
|
16
17
|
terminal?: () => Terminal;
|
|
@@ -19,13 +20,6 @@ export interface StartupUiOptions {
|
|
|
19
20
|
/** 会话列表的相对时间基准(测试注入)。 */
|
|
20
21
|
now?: () => number;
|
|
21
22
|
}
|
|
22
|
-
/** 选择器里模型的说明:名称(与 id 不同时)、上下文、`img`(收图片)。 */
|
|
23
|
-
export declare function modelDescription(model: Model): string | undefined;
|
|
24
|
-
/**
|
|
25
|
-
* 模型按「供应商 · 渠道」分组;有 key(或本地)的供应商排前,组标题标 key 状态。多渠道供应商的模型在
|
|
26
|
-
* 每个挂载的渠道下各出现一次,非首选渠道的值带 `@渠道`。
|
|
27
|
-
*/
|
|
28
|
-
export declare function modelItems(providers: ProviderRegistryApi): Promise<SelectItem[]>;
|
|
29
23
|
/** `刚刚 / 5 分钟前 / 3 小时前 / 2 天前 / 2026-09-01`。 */
|
|
30
24
|
export declare function relativeTime(iso: string, now: number): string;
|
|
31
25
|
/** 会话:标签 = 名字或首条提示,描述 = 相对时间 · 消息数。 */
|
|
@@ -5,12 +5,15 @@
|
|
|
5
5
|
* 选择列表 / 输入框的 `TUI`;答完把问答收成一行再 `stop()`——不清屏,问答留在回滚里。
|
|
6
6
|
* 取消(Esc / Ctrl+C)按各回调的约定返回 undefined(bootstrap 据此给退出码)。
|
|
7
7
|
*
|
|
8
|
-
* `modelItems
|
|
8
|
+
* `modelItems`(model-items.ts)/ `sessionItems` 同时供交互模式里的选择器(pickers.ts)使用。启动选择器只列
|
|
9
|
+
* 已配置的模型,一个都没有时列全部。
|
|
9
10
|
*/
|
|
10
11
|
import { msg } from "../../i18n/index.js";
|
|
11
12
|
import { existsSync, statSync } from "node:fs";
|
|
12
13
|
import { resolve } from "node:path";
|
|
13
14
|
import { Container, Editor, ProcessTerminal, SelectList, TUI, Text, createTheme, defaultKeybindings, detectCapabilities, } from "../../tui.js";
|
|
15
|
+
import { loadModelCatalog, catalogItems } from "./model-items.js";
|
|
16
|
+
export { modelDescription, modelItems } from "./model-items.js";
|
|
14
17
|
function resolveOptions(options) {
|
|
15
18
|
return {
|
|
16
19
|
terminal: options.terminal ?? (() => new ProcessTerminal()),
|
|
@@ -106,64 +109,6 @@ function askText(r, prompt) {
|
|
|
106
109
|
// ---------------------------------------------------------------------------
|
|
107
110
|
// 列表项(选择器共用)
|
|
108
111
|
// ---------------------------------------------------------------------------
|
|
109
|
-
/** 选择器里模型的说明:名称(与 id 不同时)、上下文、`img`(收图片)。 */
|
|
110
|
-
export function modelDescription(model) {
|
|
111
|
-
const ctx = model.contextWindow;
|
|
112
|
-
const parts = [
|
|
113
|
-
model.name !== "" && model.name !== model.id ? model.name : undefined,
|
|
114
|
-
ctx === undefined
|
|
115
|
-
? undefined
|
|
116
|
-
: ctx >= 1_000_000
|
|
117
|
-
? `${Math.round(ctx / 100_000) / 10}M`
|
|
118
|
-
: `${Math.round(ctx / 1000)}k`,
|
|
119
|
-
model.input.includes("image") ? "img" : undefined,
|
|
120
|
-
].filter((x) => x !== undefined);
|
|
121
|
-
return parts.length > 0 ? parts.join(" · ") : undefined;
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* 模型按「供应商 · 渠道」分组;有 key(或本地)的供应商排前,组标题标 key 状态。多渠道供应商的模型在
|
|
125
|
-
* 每个挂载的渠道下各出现一次,非首选渠道的值带 `@渠道`。
|
|
126
|
-
*/
|
|
127
|
-
export async function modelItems(providers) {
|
|
128
|
-
const groups = [];
|
|
129
|
-
for (const provider of providers.list()) {
|
|
130
|
-
if (provider.models.length === 0)
|
|
131
|
-
continue;
|
|
132
|
-
let status;
|
|
133
|
-
let ready = true;
|
|
134
|
-
if (!provider.requiresApiKey)
|
|
135
|
-
status = msg().interactive.startup.ui.local;
|
|
136
|
-
else {
|
|
137
|
-
const key = await providers.resolveApiKey(provider.id).catch(() => ({ apiKey: undefined }));
|
|
138
|
-
ready = key.apiKey !== undefined;
|
|
139
|
-
status = ready ? "key ✓" : msg().interactive.startup.ui.noKey;
|
|
140
|
-
}
|
|
141
|
-
const item = (model, group, channel) => {
|
|
142
|
-
const suffix = channel !== undefined && channel !== model.channel ? `@${channel}` : "";
|
|
143
|
-
const out = {
|
|
144
|
-
value: `${provider.id}/${model.id}${suffix}`,
|
|
145
|
-
label: `${model.id}${suffix}`,
|
|
146
|
-
group,
|
|
147
|
-
};
|
|
148
|
-
const description = modelDescription(model);
|
|
149
|
-
if (description !== undefined)
|
|
150
|
-
out.description = description;
|
|
151
|
-
return out;
|
|
152
|
-
};
|
|
153
|
-
if (provider.channels === undefined) {
|
|
154
|
-
const group = `${provider.id} · ${status}`;
|
|
155
|
-
groups.push({ ready, items: provider.models.map((model) => item(model, group)) });
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
for (const channel of provider.channels) {
|
|
159
|
-
const group = `${provider.id} · ${channel.name} · ${status}`;
|
|
160
|
-
const models = provider.models.filter((m) => m.channels?.includes(channel.name));
|
|
161
|
-
if (models.length > 0)
|
|
162
|
-
groups.push({ ready, items: models.map((model) => item(model, group, channel.name)) });
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return [...groups.filter((g) => g.ready), ...groups.filter((g) => !g.ready)].flatMap((g) => g.items);
|
|
166
|
-
}
|
|
167
112
|
/** `刚刚 / 5 分钟前 / 3 小时前 / 2 天前 / 2026-09-01`。 */
|
|
168
113
|
export function relativeTime(iso, now) {
|
|
169
114
|
const at = Date.parse(iso);
|
|
@@ -247,7 +192,10 @@ export function createStartupUi(options = {}) {
|
|
|
247
192
|
return picked?.value;
|
|
248
193
|
},
|
|
249
194
|
async pickModel(providers, reason) {
|
|
250
|
-
const
|
|
195
|
+
const catalog = await loadModelCatalog(providers);
|
|
196
|
+
let items = catalogItems(catalog, { hints: false });
|
|
197
|
+
if (items.length === 0)
|
|
198
|
+
items = catalogItems(catalog, { view: "all", hints: false });
|
|
251
199
|
if (items.length === 0)
|
|
252
200
|
return undefined;
|
|
253
201
|
const picked = await askSelect(r, {
|
|
@@ -60,6 +60,8 @@ export declare class SelectList implements Component, Focusable {
|
|
|
60
60
|
private get maxVisible();
|
|
61
61
|
setItems(items: readonly SelectItem[]): void;
|
|
62
62
|
getItems(): readonly SelectItem[];
|
|
63
|
+
/** 换列表下的按键提示(选择器切视图时)。 */
|
|
64
|
+
setFooter(footer: string | undefined): void;
|
|
63
65
|
getFilter(): string;
|
|
64
66
|
setFilter(filter: string): void;
|
|
65
67
|
getSelected(): SelectItem | undefined;
|
|
@@ -52,6 +52,13 @@ export class SelectList {
|
|
|
52
52
|
getItems() {
|
|
53
53
|
return this.filtered;
|
|
54
54
|
}
|
|
55
|
+
/** 换列表下的按键提示(选择器切视图时)。 */
|
|
56
|
+
setFooter(footer) {
|
|
57
|
+
if (footer === undefined)
|
|
58
|
+
delete this.options.footer;
|
|
59
|
+
else
|
|
60
|
+
this.options.footer = footer;
|
|
61
|
+
}
|
|
55
62
|
getFilter() {
|
|
56
63
|
return this.filter;
|
|
57
64
|
}
|
package/docs/en/providers.md
CHANGED
|
@@ -170,7 +170,11 @@ ama auth logout chatgpt # siwc revokes the refresh token first, t
|
|
|
170
170
|
| Quota | only known when exceeded (429); set a weekly cap for ama under ChatGPT → Settings → Usage → App limits | response headers, `codex.rate_limits` events, `ama auth status` queries `wham/usage` |
|
|
171
171
|
| Logout | calls `revocation_endpoint`, then deletes locally | deletes locally only |
|
|
172
172
|
|
|
173
|
-
|
|
173
|
+
**Model list**: `chatgpt` has no built-in model table (`chatgpt/<slug>` accepts any slug). After a successful `ama auth login chatgpt`, ama deletes the old cache and calls the model list endpoint once (siwc `GET /v1/models`, codex `GET /models?client_version=…`; read-only, no usage consumed; failures are silent and the output suggests `ama models discover chatgpt` instead) and caches the slugs and display names available to the account, with the flavor and a timestamp, in `<dataDir>/models/discovered/chatgpt.json` — an empty list is written as well when no model comes back, so no cache from the other sign-in method is left behind; `ama models discover chatgpt` rewrites the cache and `ama auth logout chatgpt` deletes it. When the registry is assembled the cache is merged into providers whose model table is empty, with metadata filled from the models.dev snapshot (the context window, input modalities and reasoning efforts reported by the codex backend are cached too and used when models.dev has nothing), so the `/model` picker and `ama models list` show the models; a cache whose flavor differs from the current sign-in counts as stale and is not merged (the picker suggests discovering again). Slugs missing from the cache still work with `--model chatgpt/<slug>`.
|
|
174
|
+
|
|
175
|
+
**codex `client_version`**: the codex backend filters models by `client_version` (each model has a minimum client version; omitting the parameter is a 400), and ama sends a Codex CLI version (default `0.160.0`), not its own version. If codex returns no models at login or discover time, that version is most likely too old: set a newer Codex CLI version with `ama config set auth.chatgpt.codexClientVersion <version>` (user level) or the environment variable `AMA_CHATGPT_CODEX_CLIENT_VERSION` (takes precedence), then run `ama models discover chatgpt`. Inference requests carry no version.
|
|
176
|
+
|
|
177
|
+
**The channel follows the sign-in method**: the default channel of `chatgpt` is chosen at assembly time from the flavor of the auth.json entry; on top of that every request picks the channel from the flavor of the token it uses — a model reference without `@channel` automatically uses the current sign-in method's channel (endpoint, channel headers such as `originator`, and the request-body allowlist all switch), so after `ama auth logout` and signing in the other way, a running session's next request goes to the new channel without a restart; when a session is resumed, the channel recorded in it does not count as explicit either. Only an explicit `provider/model@siwc|codex` that differs from the sign-in method reports `chatgpt_flavor_mismatch` (suggesting to drop `@channel` or switch the sign-in method).
|
|
174
178
|
|
|
175
179
|
**Credentials**: a `{ "type": "oauth", … }` entry in auth.json (file mode 0600); `ama auth list` only shows `oauth · <flavor> · <plan>`. The access token is refreshed automatically when less than 5 minutes remain or a request returns 401; several ama processes (several nodes on the canvas) share one auth.json, and refreshes are serialized through `auth.json.lock` (re-read after taking the lock; if another process already refreshed, its token is used), so refresh-token rotation never knocks another process out. When refreshing fails permanently the entry is marked `needsLogin` (tokens are not deleted), requests report `auth_expired`, and you sign in again with `ama auth login chatgpt` as prompted. Raw tokens, codes and id_tokens never reach logs, sessions, events or errors. Logins of other applications (Codex CLI and others) are never read or imported.
|
|
176
180
|
|
|
@@ -182,12 +186,14 @@ The default channel of `chatgpt` is chosen at assembly time from the flavor of t
|
|
|
182
186
|
| ---------------- | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------- |
|
|
183
187
|
| `quota_exceeded` | siwc 429 `subscription_sharing_usage_limit_exceeded`; codex 429 `usage_limit_reached` / `usage_not_included` | no retry; carries the reset time and emits `quota_update` |
|
|
184
188
|
| `auth_expired` | a 401 still failing after one refresh, a permanently failed refresh, an entry marked `needsLogin` | no retry; run `ama auth login chatgpt` again |
|
|
185
|
-
| `not_eligible` | siwc 403 `subscription_sharing_user_not_eligible` | no retry, no re-login
|
|
189
|
+
| `not_eligible` | siwc 403 `subscription_sharing_user_not_eligible` | no retry, no re-login; see troubleshooting below |
|
|
186
190
|
| (as is) | 503 and similar | the session layer's existing backoff retries |
|
|
187
191
|
|
|
192
|
+
**Troubleshooting `not_eligible`**: the account cannot share its plan usage with ama. Possible causes: the plan (sharing is offered to Plus / Pro only); a workspace account (Team / Enterprise / Edu may not offer it); a region restriction or a preview that has not rolled out yet — **the most likely cause when a Pro account still gets this error**. You can sign in with `ama auth login chatgpt --flavor codex` instead. A successful siwc login only means authorization passed; whether plan usage can be shared is only confirmed on the first request.
|
|
193
|
+
|
|
188
194
|
**Usage**: subscription requests record `usage.cost = 0` with `billing: "subscription"`; `/session` lists "subscription usage" separately (requests, tokens, cache hit rate, no USD conversion) together with the latest quota; the `quota_update` event is forwarded as is over RPC and has the same name among host events.
|
|
189
195
|
|
|
190
|
-
**Overrides** (for tests or a future own client): `auth.chatgpt.clientId` / `issuer` / `originator` / `redirectPorts` (user level and profile only), environment variables `AMA_CHATGPT_CLIENT_ID`, `AMA_CHATGPT_ISSUER`, `AMA_CHATGPT_BASE_URL` (changes the address of the current flavor's channel)
|
|
196
|
+
**Overrides** (for tests or a future own client): `auth.chatgpt.clientId` / `issuer` / `originator` / `codexClientVersion` / `redirectPorts` (user level and profile only), environment variables `AMA_CHATGPT_CLIENT_ID`, `AMA_CHATGPT_ISSUER`, `AMA_CHATGPT_BASE_URL` (changes the address of the current flavor's channel), `AMA_CHATGPT_CODEX_CLIENT_VERSION`.
|
|
191
197
|
|
|
192
198
|
**Embedding hosts**: with a profile ama never starts an interactive login; when `chatgpt` is used and the login has expired the request reports `auth_expired`, and the host guides the user to run `ama auth login chatgpt --paste` in a terminal. Hosts never read, store or forward tokens; they only consume `quota_update` and `auth_expired` / `quota_exceeded`.
|
|
193
199
|
|
package/docs/en/tui.md
CHANGED
|
@@ -188,7 +188,14 @@ Design in [rewind-plan.md](../rewind-plan.md) (Chinese). Every user message that
|
|
|
188
188
|
- `/fork` (no arguments): also picks a user message, then copies a new session up to before it.
|
|
189
189
|
- `/model`, `/resume`, `/permission` and `/thinking` without arguments open pickers; `/permissions` shows the permission decision order, loaded rules and the latest 20 auto decisions (tier, result, reason).
|
|
190
190
|
- The `/permission` picker, titled permission mode: Manual / Accept edits / Plan / Auto / Bypass permissions / Allowlist only, each with a one-line explanation and number keys 1–6 on the right for direct selection; the current mode is checked `✓`, the default mode from config is marked `Default`, Auto is marked `Recommended`, with a key hint line at the bottom. `/permission auto` and `/permission Accept edits` switch directly. The far left of the status bar is the mode's display name. When auto mode needs confirmation, the approval dialog has an extra line naming the auto rule tier / classifier and the reason. Details in [permissions.md](permissions.md).
|
|
191
|
-
- Pickers have a key hint line at the bottom (`↑↓ select · Enter confirm · Esc cancel`) and a background on the selected row (≥ 256 colors; accent bold with fewer).
|
|
191
|
+
- Pickers have a key hint line at the bottom (`↑↓ select · Enter confirm · Esc cancel`) and a background on the selected row (≥ 256 colors; accent bold with fewer).
|
|
192
|
+
- The `/model` picker (also `Ctrl+L`) checks the current model with `✓`, shows an `(i/n)` count, and descriptions include context size and `img` (accepts images):
|
|
193
|
+
- **Only configured providers by default**: a key, a valid OAuth sign-in (not one that needs signing in again) or a local server; group titles are "provider · status". When the current model is not in the list it is pinned at the top (group "current").
|
|
194
|
+
- **`Tab` switches to "all"** (press again to go back; the filter text is kept): providers without configuration are listed too, marked "no key configured"; picking one of their models does not switch and the bottom line suggests `ama auth set <provider>` (custom providers: `ama providers add`). `Tab` was chosen over an "add model" item at the end of the list: it takes no list row, never shows up in filter results, and works in the middle of filtering.
|
|
195
|
+
- **`Space` adds / removes the highlighted model to / from the list** (user-level `models.enabled`, written back to config.json): once the list is set, the "configured" view shows only listed models (plus the current one); removing the last entry deletes the key. Models listed through `provider/*` cannot be removed one by one; the hint points to `ama models disable`. While a list is in use, picking an unlisted model from "all" adds it to the list and then switches. Because of this `Space` no longer splits filter terms (the filter matches one term; `provider/model` works as a filter).
|
|
196
|
+
- **Multi-channel providers get one row per model** (the preferred channel); the description says "also @channel". With an `@` in the filter text (such as `@messages`) the `model@channel` rows are listed and picking one uses that channel. `@` was chosen over expanding with `→`: it matches the `provider/model@channel` syntax and works the same way in the startup picker. `/model packy/kimi-k2.5@messages` still switches to a specific channel directly.
|
|
197
|
+
- **ChatGPT subscription**: after `ama auth login chatgpt` the models available to the account are fetched (the read-only model list endpoint, no usage consumed) and cached in `<dataDir>/models/discovered/chatgpt.json`; the picker lists them like any other model. Without the cache the chatgpt group has a row "Run ama models discover chatgpt to list its models" (see [providers.md](providers.md#chatgpt-login)).
|
|
198
|
+
- The same list from the command line: `ama models enable <provider/model[@channel]|provider/*>…`, `ama models disable …`, `ama models list --enabled` (without a list it prints what the picker shows by default).
|
|
192
199
|
- An `@image-path` in the input (quotes allowed, Tab completes the path), or a pasted / dropped image file path, is sent as an image attachment with the message; when the current model does not accept images an `@` attachment is an error and nothing is sent, while paths without `@` are ignored (see [providers.md](providers.md) "Image input").
|
|
193
200
|
- `/statusline [full|compact]`: switch the bottom info line (without arguments it toggles, same as `Ctrl+G`), this session only; line mode has no bottom info line.
|
|
194
201
|
- `/session`, `/cache`: session usage and cache stats panels (see "Cache and context" above); `/permissions` is a panel too, with allow in green, deny in red and the decision order wrapped and aligned. With sub-agent tasks `/session` gains a "Sub-agents" line (task count and states), and after using external agents an "External agents" section (runs and usage per agent, USD / tokens / requests each in its own unit, never converted).
|
package/docs/providers.md
CHANGED
|
@@ -185,8 +185,25 @@ ama auth logout chatgpt # siwc 先撤销 refresh token 再删本
|
|
|
185
185
|
| 配额 | 只在超限(429)时可知;在 ChatGPT → 设置 → Usage → App limits 给 ama 设周上限 | 响应头、`codex.rate_limits` 事件、`ama auth status` 查 `wham/usage` |
|
|
186
186
|
| 登出 | 调 `revocation_endpoint` 撤销,再删本地 | 只删本地 |
|
|
187
187
|
|
|
188
|
-
|
|
189
|
-
|
|
188
|
+
**模型列表**:`chatgpt` 没有内置模型表(`chatgpt/<slug>` 任意接受)。`ama auth login chatgpt` 成功后先删旧缓存,再调一次
|
|
189
|
+
模型列表接口(siwc `GET /v1/models`、codex `GET /models?client_version=…`,只读、不消耗额度;失败静默,改提示
|
|
190
|
+
`ama models discover chatgpt`),把账户可用的 slug 与显示名连同 flavor、时间戳缓存到
|
|
191
|
+
`<dataDir>/models/discovered/chatgpt.json`——返回 0 个也写空表,免得残留另一种登录方式的缓存;`ama models discover
|
|
192
|
+
chatgpt` 也重写这份缓存,`ama auth logout chatgpt` 删掉它。组装注册表时缓存并入模型表为空的供应商,元数据用 models.dev
|
|
193
|
+
快照补全(codex 后端另给的上下文窗口、输入模态、推理强度也存进缓存,models.dev 补不到时用它),`/model` 选择器、
|
|
194
|
+
`ama models list` 照常列出;缓存的 flavor 与当前登录不符时视为过期、不并入(选择器提示重新发现)。缓存里没有的 slug
|
|
195
|
+
仍可 `--model chatgpt/<slug>` 使用。
|
|
196
|
+
|
|
197
|
+
**codex 的 `client_version`**:codex 后端按 `client_version` 过滤模型(每个模型有最低客户端版本,不带参数报 400),
|
|
198
|
+
ama 发的是 Codex CLI 的版本号(缺省 `0.160.0`),而不是 ama 自己的版本。登录或 discover 时 codex 返回 0 个模型,多半是
|
|
199
|
+
这个版本过旧:用 `ama config set auth.chatgpt.codexClientVersion <版本>`(用户级)或环境变量
|
|
200
|
+
`AMA_CHATGPT_CODEX_CLIENT_VERSION`(优先)设成较新的 Codex CLI 版本,再 `ama models discover chatgpt`。推理请求不带版本号。
|
|
201
|
+
|
|
202
|
+
**渠道跟随登录方式**:`chatgpt` 的缺省渠道在组装时按 auth.json 条目的 flavor 决定;此外每次请求按当次 token 所属的
|
|
203
|
+
flavor 选渠道——模型引用没写 `@渠道` 时自动用当前登录方式的渠道(端点、`originator` 等渠道头、请求体白名单一起换),
|
|
204
|
+
所以运行中的会话在 `ama auth logout` 后换另一种方式登录,下一次请求就走新渠道,不用重启;恢复会话时,会话里记录的渠道
|
|
205
|
+
也不算显式。只有显式写了 `provider/model@siwc|codex` 且与登录方式不符时才报 `chatgpt_flavor_mismatch`(提示去掉
|
|
206
|
+
`@渠道` 或换登录方式)。
|
|
190
207
|
|
|
191
208
|
**凭据**:auth.json 的 `{ "type": "oauth", … }` 条目(文件 0600),`ama auth list` 只显示 `oauth · <flavor> · <计划>`。
|
|
192
209
|
access token 剩余不到 5 分钟或请求返回 401 时自动刷新;多个 ama 进程(画布上的多个节点)共享一个 auth.json,刷新经
|
|
@@ -208,15 +225,19 @@ developer 消息(前缀依然稳定)。compat `toolsInNamespace: true` 时
|
|
|
208
225
|
| ---------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------- |
|
|
209
226
|
| `quota_exceeded` | siwc 429 `subscription_sharing_usage_limit_exceeded`;codex 429 `usage_limit_reached` / `usage_not_included` | 不重试;附重置时间并发 `quota_update` |
|
|
210
227
|
| `auth_expired` | 401 刷新一次仍失败、刷新永久失败、条目 `needsLogin` | 不重试;重新 `ama auth login chatgpt` |
|
|
211
|
-
| `not_eligible` | siwc 403 `subscription_sharing_user_not_eligible` |
|
|
228
|
+
| `not_eligible` | siwc 403 `subscription_sharing_user_not_eligible` | 不重试、不重登;排查见下 |
|
|
212
229
|
| (原样) | 503 等 | 走会话层现有的退避重试 |
|
|
213
230
|
|
|
231
|
+
**`not_eligible` 排查**:账户不能把套餐额度共享给 ama。可能的原因:账户套餐(额度共享只对 Plus / Pro 开放);工作空间
|
|
232
|
+
账户(Team / Enterprise / Edu 可能未开放);地区受限或预览期尚未开放——**Pro 账户仍报此错时最可能是这一条**。可以改用
|
|
233
|
+
`ama auth login chatgpt --flavor codex`。siwc 登录成功只说明授权通过,能否共享额度要到首次请求才能确认。
|
|
234
|
+
|
|
214
235
|
**用量**:订阅请求 `usage.cost = 0` 并标 `billing: "subscription"`;`/session` 单列「订阅用量」(请求数、token、
|
|
215
236
|
缓存命中率,不折算美元)与最近一次配额;事件 `quota_update`(RPC 原样转发,宿主事件同名)。
|
|
216
237
|
|
|
217
|
-
**覆盖**(测试或将来换自有客户端用):`auth.chatgpt.clientId` / `issuer` / `originator` / `
|
|
218
|
-
|
|
219
|
-
|
|
238
|
+
**覆盖**(测试或将来换自有客户端用):`auth.chatgpt.clientId` / `issuer` / `originator` / `codexClientVersion` /
|
|
239
|
+
`redirectPorts`(只认用户级与 profile),环境变量 `AMA_CHATGPT_CLIENT_ID`、`AMA_CHATGPT_ISSUER`、`AMA_CHATGPT_BASE_URL`
|
|
240
|
+
(改当前 flavor 渠道的地址)、`AMA_CHATGPT_CODEX_CLIENT_VERSION`。
|
|
220
241
|
|
|
221
242
|
**嵌入宿主**:有 profile 时 ama 不发起交互式登录;用到 `chatgpt` 而登录失效时请求报 `auth_expired`,由宿主引导用户
|
|
222
243
|
在终端执行 `ama auth login chatgpt --paste`。宿主不读、不存、不转发 token,只消费 `quota_update` 与
|
package/docs/tui.md
CHANGED
|
@@ -182,7 +182,14 @@ Accept edits claude-opus-5-5 medium | Ctx 3.0% | proj ⎇ main 5ae9e54 (+12,
|
|
|
182
182
|
- `/fork`(无参数):同样选一条用户消息,从它之前复制出新会话。
|
|
183
183
|
- `/model`、`/resume`、`/permission`、`/thinking` 不带参数时打开选择器;`/permissions` 显示权限判定顺序、已加载规则与最近 20 条 auto 判定(层、结果、原因)。
|
|
184
184
|
- `/permission` 选择器标题「权限模式」:Manual / Accept edits / Plan / Auto / Bypass permissions / Allowlist only,每项一行说明,右侧数字 1–6 直接选;当前模式打 `✓`,配置里的缺省模式标 `Default`,Auto 标 `Recommended`,底部一行按键提示。`/permission auto`、`/permission Accept edits` 直接切换。状态栏最左是模式的显示名。auto 模式下需要确认时,审批对话框多一行「Auto 规则层 / 分类器:原因」。详见 [permissions.md](permissions.md)。
|
|
185
|
-
- 选择器底部一行按键提示(`↑↓ 选择 · Enter 确认 · Esc 取消`),选中行有底色(≥ 256
|
|
185
|
+
- 选择器底部一行按键提示(`↑↓ 选择 · Enter 确认 · Esc 取消`),选中行有底色(≥ 256 色;更少时强调色粗体)。
|
|
186
|
+
- `/model` 选择器(`Ctrl+L` 同)给当前模型打 `✓`,并带 `(i/n)` 计数,说明里有上下文与 `img`(收图片):
|
|
187
|
+
- **缺省只列已配置的供应商**:有 key、OAuth 已登录(需重新登录的不算)或本地服务;组标题是「供应商 · 状态」。当前模型不在列表里时置顶一行(组「当前」)。
|
|
188
|
+
- **`Tab` 切到「全部」**(再按切回,筛选文本保留):另列没配置的供应商,组标题标「未配置 key」;选中它们的模型不切换,底部提示 `ama auth set <供应商>`(自定义供应商用 `ama providers add`)。选 `Tab` 而不是列表底部的「添加模型」项:不占列表位置、不混进筛选结果,筛到一半也能切。
|
|
189
|
+
- **`Space` 把高亮的模型加入 / 移出清单**(用户级 `models.enabled`,写回 config.json):设置了清单后「已配置」视图只列清单内的模型(加当前模型);清单移空时删掉这个键。经 `provider/*` 列入的不能单独移出,按提示用 `ama models disable`。已在用清单时,从「全部」选中清单外的模型会先加入清单再切换。`Space` 因此不再用作筛选的分词(筛选按一个词匹配,`provider/model` 也能筛)。
|
|
190
|
+
- **多渠道供应商每个模型一行**(首选渠道),说明里写「另有 @渠道」;筛选文本里带 `@`(如 `@messages`)时列出 `模型@渠道` 行,直接选中即用该渠道。选 `@` 而不是 `→` 展开:与 `provider/model@channel` 的写法一致,启动选择器里也能用同一习惯。`/model packy/kimi-k2.5@messages` 仍可直接切到指定渠道。
|
|
191
|
+
- **ChatGPT 订阅**:`ama auth login chatgpt` 后自动拉取账户可用的模型(只读的模型列表接口,不消耗额度)缓存到 `<dataDir>/models/discovered/chatgpt.json`,选择器里按普通模型列出;没缓存时 chatgpt 组有一行「运行 ama models discover chatgpt 获取模型」(见 [providers.md](providers.md#chatgpt-登录))。
|
|
192
|
+
- 命令行同一份清单:`ama models enable <provider/model[@channel]|provider/*>…`、`ama models disable …`、`ama models list --enabled`(没设清单时列出选择器缺省显示的模型)。
|
|
186
193
|
- 输入里的 `@图片路径`(可加引号,Tab 补全路径)或粘贴 / 拖入的图片文件路径作为图片附件随消息发送;当前模型不收图片时 `@` 附件报错、不发送,未加 `@` 的路径忽略(见 [providers.md](providers.md)「图像输入」)。
|
|
187
194
|
- `/statusline [full|compact]`:切换底部信息行(无参数时在两者间切换,同 `Ctrl+G`),只影响本会话;line 模式没有底部信息行。
|
|
188
195
|
- `/session`、`/cache`:会话用量与缓存统计面板(见上文「缓存与上下文」);`/permissions` 同样是面板,allow 绿、deny 红,判定顺序折行对齐。`/session` 有子 Agent 任务时多一行「子 Agent」(任务数与各状态),用过外部 Agent 时多一段「外部 Agent」(每个 Agent 的运行次数与用量,美元 / token / 请求数按各自单位,不换算)。
|