@adecore/agents 0.39.0 → 0.40.1
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/dist/async.d.ts +5 -0
- package/dist/async.js +13 -5
- package/dist/chat/acp-protocol.js +3 -5
- package/dist/chat/chat-core.js +0 -2
- package/dist/chat/chat-process.d.ts +1 -0
- package/dist/chat/chat-process.js +1 -1
- package/dist/chat/chat-session.js +6 -8
- package/dist/chat/claude-protocol.js +1 -6
- package/dist/chat/claude-transcript.js +1 -3
- package/dist/chat/codex-protocol.js +1 -6
- package/dist/chat/projector.js +1 -3
- package/dist/chat/request-summary.js +1 -3
- package/dist/coded-error.js +1 -5
- package/dist/context/argv.js +1 -2
- package/dist/context/verb.js +1 -3
- package/dist/error-text.d.ts +1 -0
- package/dist/error-text.js +4 -0
- package/dist/fields.d.ts +3 -0
- package/dist/fields.js +12 -0
- package/dist/fs.d.ts +1 -0
- package/dist/fs.js +8 -4
- package/dist/lineage.js +11 -5
- package/dist/messages/notice-store.js +3 -4
- package/dist/providers/accounts/acp-account.js +3 -20
- package/dist/providers/accounts/service.d.ts +1 -2
- package/dist/providers/accounts/service.js +5 -9
- package/dist/providers/accounts/status.js +1 -3
- package/dist/providers/acp-agent-state.js +1 -3
- package/dist/providers/claude.js +0 -1
- package/dist/providers/codex-models.json +3 -2
- package/dist/providers/codex.js +0 -1
- package/dist/providers/detect.js +1 -1
- package/dist/providers/shell-commands.js +1 -2
- package/dist/run-process.js +1 -3
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +5 -8
- package/dist/tasks/background-limit.js +3 -2
- package/dist/tasks/task-coordinator.js +11 -17
- package/dist/testing/watch.js +0 -1
- package/dist/usage/limits/monitor.js +1 -2
- package/dist/usage/prices-snapshot.json +8 -0
- package/dist/usage/readers/claude.js +3 -10
- package/dist/usage/readers/codex.js +2 -9
- package/dist/usage/readers/grok.js +3 -10
- package/dist/usage/readers/host-log.js +3 -10
- package/dist/usage/record.d.ts +2 -0
- package/dist/usage/record.js +13 -0
- package/package.json +6 -6
- package/src/async.ts +23 -5
- package/src/chat/acp-backend.ts +121 -0
- package/src/chat/acp-protocol.ts +172 -6
- package/src/chat/attachment-store.ts +21 -0
- package/src/chat/backend.ts +0 -2
- package/src/chat/bookmark-store.ts +23 -0
- package/src/chat/chat-core.ts +208 -7
- package/src/chat/chat-log.ts +19 -0
- package/src/chat/chat-process.ts +52 -1
- package/src/chat/chat-session.ts +401 -10
- package/src/chat/chat-store.ts +50 -0
- package/src/chat/chat-title.ts +9 -0
- package/src/chat/claude-backend.ts +50 -0
- package/src/chat/claude-protocol.ts +155 -8
- package/src/chat/claude-title.ts +10 -0
- package/src/chat/claude-transcript.ts +47 -4
- package/src/chat/codex-backend.ts +131 -0
- package/src/chat/codex-protocol.ts +180 -8
- package/src/chat/codex-thread.ts +23 -0
- package/src/chat/composer-preferences.ts +6 -0
- package/src/chat/context-breakdown.ts +11 -0
- package/src/chat/delta-coalescer.ts +8 -0
- package/src/chat/external-sessions.ts +28 -0
- package/src/chat/generated-image.ts +25 -0
- package/src/chat/index.ts +5 -0
- package/src/chat/input.ts +13 -0
- package/src/chat/json-rpc-transport.ts +28 -0
- package/src/chat/limit-resume.ts +13 -0
- package/src/chat/projector.ts +160 -4
- package/src/chat/readable-text.ts +3 -0
- package/src/chat/request-summary.ts +18 -4
- package/src/chat/subagent-projection.ts +3 -0
- package/src/chat/subagent-reader.ts +76 -0
- package/src/chat/subagent-settlement.ts +24 -0
- package/src/chat/thread-search.ts +13 -0
- package/src/chat/thread.ts +31 -0
- package/src/chat/ui-queries.ts +122 -0
- package/src/chat/ui-replies.ts +34 -0
- package/src/chat/visual-store.ts +49 -0
- package/src/chat/wake-chat.ts +3 -0
- package/src/client-sinks.ts +2 -0
- package/src/coded-error.ts +1 -5
- package/src/context/argv.ts +18 -2
- package/src/context/index.ts +1 -0
- package/src/context/refusal.ts +3 -0
- package/src/context/verb.ts +32 -4
- package/src/coordination/index.ts +10 -0
- package/src/error-text.ts +7 -0
- package/src/fields.ts +14 -0
- package/src/fs.ts +20 -4
- package/src/host/agent-host.ts +20 -0
- package/src/host/environment.ts +6 -0
- package/src/host/handlers.ts +18 -0
- package/src/host/memory-port.ts +4 -0
- package/src/host/wiring.ts +10 -0
- package/src/index.ts +1 -0
- package/src/lineage.ts +24 -5
- package/src/messages/deliver-message.ts +6 -0
- package/src/messages/deliver-notice.ts +13 -0
- package/src/messages/notice-store.ts +16 -4
- package/src/modes.ts +1 -0
- package/src/outbox/outbox-worker.ts +31 -0
- package/src/outbox/outbox.ts +5 -0
- package/src/providers/accounts/accounts.ts +36 -0
- package/src/providers/accounts/acp-account.ts +22 -27
- package/src/providers/accounts/launch.ts +4 -0
- package/src/providers/accounts/service.ts +110 -12
- package/src/providers/accounts/shadow-home.ts +10 -0
- package/src/providers/accounts/status.ts +13 -4
- package/src/providers/accounts/store.ts +25 -0
- package/src/providers/accounts/variables.ts +11 -1
- package/src/providers/acp-agent-state.ts +24 -3
- package/src/providers/acp-provider.ts +5 -0
- package/src/providers/acp-usage-log.ts +2 -0
- package/src/providers/catalog.ts +17 -0
- package/src/providers/claude.ts +10 -1
- package/src/providers/codex-models.json +3 -2
- package/src/providers/codex-rules.ts +3 -0
- package/src/providers/codex.ts +2 -1
- package/src/providers/detect.ts +6 -1
- package/src/providers/provider.ts +1 -1
- package/src/providers/registry.ts +18 -2
- package/src/providers/shell-commands.ts +37 -2
- package/src/record-directory.ts +19 -0
- package/src/run-process.ts +24 -3
- package/src/serializer.ts +3 -0
- package/src/skills.ts +73 -10
- package/src/tasks/background-limit.ts +9 -2
- package/src/tasks/end-children.ts +9 -0
- package/src/tasks/give-task.ts +8 -0
- package/src/tasks/task-coordinator.ts +81 -17
- package/src/tasks/task-store.ts +28 -0
- package/src/tasks/waiting-child.ts +21 -0
- package/src/tasks/wake-parent.ts +26 -0
- package/src/tasks/wiring.ts +22 -0
- package/src/testing/accounts.ts +2 -0
- package/src/testing/fake-acp-capture.ts +16 -0
- package/src/testing/fake-acp.ts +66 -0
- package/src/testing/fake-claude.ts +177 -0
- package/src/testing/fake-cli.ts +29 -0
- package/src/testing/fake-codex.ts +122 -0
- package/src/testing/manual-clock.ts +2 -0
- package/src/testing/watch.ts +7 -1
- package/src/title-file.ts +12 -0
- package/src/usage/accounts.ts +6 -0
- package/src/usage/aggregate.ts +19 -0
- package/src/usage/exchange.ts +12 -0
- package/src/usage/index-file.ts +27 -0
- package/src/usage/limits/monitor.ts +31 -3
- package/src/usage/limits/normalize.ts +30 -0
- package/src/usage/limits/probe.ts +17 -0
- package/src/usage/prices-snapshot.json +8 -0
- package/src/usage/pricing.ts +35 -0
- package/src/usage/projects.ts +8 -0
- package/src/usage/readers/claude.ts +12 -9
- package/src/usage/readers/codex.ts +40 -8
- package/src/usage/readers/grok.ts +17 -9
- package/src/usage/readers/host-log.ts +9 -9
- package/src/usage/record.ts +22 -0
- package/src/usage/roots.ts +6 -0
- package/src/usage/scanner.ts +61 -0
- package/src/usage/usage-service.ts +14 -0
- package/src/watch-seam.ts +8 -0
- package/src/windows-command.ts +12 -0
|
@@ -34,28 +34,37 @@ export type StoredAccounts = Record<string, unknown>;
|
|
|
34
34
|
/* Every entry that does not parse is dropped on its own; a file that is not JSON at all reads as none. */
|
|
35
35
|
export async function readAccounts(path: string, providers: AccountProviders, env: Env, host: AccountsHost = DEFAULT_ACCOUNTS_HOST): Promise<StoredAccounts> {
|
|
36
36
|
let parsed: unknown;
|
|
37
|
+
|
|
37
38
|
try {
|
|
38
39
|
parsed = JSON.parse(await readFile(path, 'utf8'));
|
|
39
40
|
} catch (e) {
|
|
40
41
|
if (isNotFound(e) || e instanceof SyntaxError) {
|
|
41
42
|
return {};
|
|
42
43
|
}
|
|
44
|
+
|
|
43
45
|
throw e;
|
|
44
46
|
}
|
|
47
|
+
|
|
45
48
|
const entries = typeof parsed === 'object' && parsed !== null ? (parsed as { accounts?: unknown }).accounts : null;
|
|
49
|
+
|
|
46
50
|
if (typeof entries !== 'object' || entries === null || Array.isArray(entries)) {
|
|
47
51
|
return {};
|
|
48
52
|
}
|
|
53
|
+
|
|
49
54
|
const stored: StoredAccounts = {};
|
|
55
|
+
|
|
50
56
|
for (const [id, raw] of Object.entries(entries)) {
|
|
51
57
|
if (!ProviderAccountIdSchema.safeParse(id).success) {
|
|
52
58
|
continue;
|
|
53
59
|
}
|
|
60
|
+
|
|
54
61
|
const account = readAccount(id, raw, providers, env, host);
|
|
62
|
+
|
|
55
63
|
if (account !== null) {
|
|
56
64
|
stored[id] = account;
|
|
57
65
|
}
|
|
58
66
|
}
|
|
67
|
+
|
|
59
68
|
return stored;
|
|
60
69
|
}
|
|
61
70
|
|
|
@@ -66,9 +75,11 @@ export async function writeAccounts(path: string, stored: StoredAccounts): Promi
|
|
|
66
75
|
/* What a client reads of an entry of a kind it may not know either: the fields this version knows. */
|
|
67
76
|
function wireAccount(entry: unknown): ProviderAccount {
|
|
68
77
|
const parsed = ProviderAccountSchema.safeParse(entry);
|
|
78
|
+
|
|
69
79
|
if (!parsed.success) {
|
|
70
80
|
return { kind: String((entry as { kind?: unknown }).kind) };
|
|
71
81
|
}
|
|
82
|
+
|
|
72
83
|
return parsed.data.env === undefined ? parsed.data : { ...parsed.data, env: redactVariables(parsed.data.env) };
|
|
73
84
|
}
|
|
74
85
|
|
|
@@ -84,6 +95,7 @@ export class InvalidAccountError extends CodedError<'invalid-account'> {
|
|
|
84
95
|
|
|
85
96
|
function storedFolders(entry: unknown): Pick<ProviderAccount, 'home' | 'shadowHome'> {
|
|
86
97
|
const parsed = ProviderAccountSchema.safeParse(entry);
|
|
98
|
+
|
|
87
99
|
return parsed.success ? { home: parsed.data.home, shadowHome: parsed.data.shadowHome } : {};
|
|
88
100
|
}
|
|
89
101
|
|
|
@@ -102,18 +114,23 @@ function linkProblem(
|
|
|
102
114
|
if (isDefaultAccount(id, account) || provider.home === undefined || account.home === undefined) {
|
|
103
115
|
return null;
|
|
104
116
|
}
|
|
117
|
+
|
|
105
118
|
const was = storedFolders(before);
|
|
119
|
+
|
|
106
120
|
if (was.home === account.home && was.shadowHome === account.shadowHome) {
|
|
107
121
|
return null;
|
|
108
122
|
}
|
|
123
|
+
|
|
109
124
|
for (const folder of [account.home, account.shadowHome]) {
|
|
110
125
|
if (folder !== undefined && !isDirectory(expandHome(folder, env))) {
|
|
111
126
|
return `${folder} is not a folder on this machine`;
|
|
112
127
|
}
|
|
113
128
|
}
|
|
129
|
+
|
|
114
130
|
if (accountFolder(id, account, provider, env) === defaultFolder(provider, env)) {
|
|
115
131
|
return `${defaultFolder(provider, env)} is the folder of the default ${provider.name} account`;
|
|
116
132
|
}
|
|
133
|
+
|
|
117
134
|
return null;
|
|
118
135
|
}
|
|
119
136
|
|
|
@@ -133,23 +150,31 @@ export function acceptAccounts(
|
|
|
133
150
|
): StoredAccounts {
|
|
134
151
|
const stored: StoredAccounts = {};
|
|
135
152
|
const reserved = { host, folderVariables: folderVariablesOf(providers) };
|
|
153
|
+
|
|
136
154
|
for (const [id, account] of Object.entries(withDefaults(saved, providers.kinds))) {
|
|
137
155
|
if (!isOfferedKind(account.kind, providers.kinds)) {
|
|
138
156
|
if (isOfferedKind(id, providers.kinds)) {
|
|
139
157
|
throw new InvalidAccountError(`"${id}" is the id of the default ${id} account`);
|
|
140
158
|
}
|
|
159
|
+
|
|
141
160
|
const kept = before[id];
|
|
142
161
|
const keptKind = typeof kept === 'object' && kept !== null ? (kept as { kind?: unknown }).kind : undefined;
|
|
143
162
|
stored[id] = keptKind === account.kind ? kept : account;
|
|
163
|
+
|
|
144
164
|
continue;
|
|
145
165
|
}
|
|
166
|
+
|
|
146
167
|
const provider = providers.get(account.kind);
|
|
168
|
+
|
|
147
169
|
const problem =
|
|
148
170
|
accountProblem(id, account, provider, env, reserved, providers.kinds) ?? linkProblem(id, account, before[id], provider, env, isDirectory);
|
|
171
|
+
|
|
149
172
|
if (problem !== null) {
|
|
150
173
|
throw new InvalidAccountError(`${id}: ${problem}`);
|
|
151
174
|
}
|
|
175
|
+
|
|
152
176
|
stored[id] = account;
|
|
153
177
|
}
|
|
178
|
+
|
|
154
179
|
return stored;
|
|
155
180
|
}
|
|
@@ -15,7 +15,6 @@ export class SecretsUnavailableError extends CodedError<'secrets-unavailable'> {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
/* What keeps the sensitive values; a test keeps them in memory. */
|
|
19
18
|
export interface SecretStore {
|
|
20
19
|
/* Null for a key it has nothing under. */
|
|
21
20
|
read(key: string): Promise<string | null>;
|
|
@@ -60,18 +59,23 @@ function isPrintableAscii(value: string): boolean {
|
|
|
60
59
|
/* What is wrong with the variables of an account, or null when nothing is. A secret that is only redacted is judged by the service. */
|
|
61
60
|
export function variablesProblem(variables: readonly ProviderAccountVariable[], reserved: ReservedVariables): string | null {
|
|
62
61
|
const seen = new Set<string>();
|
|
62
|
+
|
|
63
63
|
for (const variable of variables) {
|
|
64
64
|
if (seen.has(variable.name)) {
|
|
65
65
|
return `${variable.name} is set twice`;
|
|
66
66
|
}
|
|
67
|
+
|
|
67
68
|
seen.add(variable.name);
|
|
69
|
+
|
|
68
70
|
if (isReservedName(variable.name, reserved)) {
|
|
69
71
|
return `${variable.name} is set by ${reserved.host.name} itself`;
|
|
70
72
|
}
|
|
73
|
+
|
|
71
74
|
if (variable.sensitive && !isPrintableAscii(variable.value)) {
|
|
72
75
|
return `The value of ${variable.name} can only hold printable ASCII characters`;
|
|
73
76
|
}
|
|
74
77
|
}
|
|
78
|
+
|
|
75
79
|
return null;
|
|
76
80
|
}
|
|
77
81
|
|
|
@@ -90,6 +94,7 @@ const NOT_FOUND = 44;
|
|
|
90
94
|
|
|
91
95
|
async function run(args: string[], stdin?: string): Promise<{ code: number; stdout: string; stderr: string }> {
|
|
92
96
|
const { exitCode, stdout, stderr } = await runProcess(['security', ...args], { ...(stdin === undefined ? {} : { stdin }) });
|
|
97
|
+
|
|
93
98
|
return { code: exitCode ?? 1, stdout, stderr };
|
|
94
99
|
}
|
|
95
100
|
|
|
@@ -102,24 +107,29 @@ export function keychainSecrets(service: string): SecretStore {
|
|
|
102
107
|
return {
|
|
103
108
|
read: async (key) => {
|
|
104
109
|
const { code, stdout, stderr } = await run(['find-generic-password', '-s', service, '-a', key, '-w']);
|
|
110
|
+
|
|
105
111
|
if (code === NOT_FOUND) {
|
|
106
112
|
return null;
|
|
107
113
|
}
|
|
114
|
+
|
|
108
115
|
if (code !== 0) {
|
|
109
116
|
throw new Error(`The keychain did not give ${key}: ${stderr.trim()}`);
|
|
110
117
|
}
|
|
118
|
+
|
|
111
119
|
return stdout.endsWith('\n') ? stdout.slice(0, -1) : stdout;
|
|
112
120
|
},
|
|
113
121
|
write: async (key, value) => {
|
|
114
122
|
const hex = Buffer.from(value, 'utf8').toString('hex');
|
|
115
123
|
const quoted = `"${key.replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
|
|
116
124
|
const { code, stderr } = await run(['-i'], `add-generic-password -U -s ${service} -a ${quoted} -X ${hex}\n`);
|
|
125
|
+
|
|
117
126
|
if (code !== 0) {
|
|
118
127
|
throw new Error(`The keychain did not take ${key}: ${stderr.trim()}`);
|
|
119
128
|
}
|
|
120
129
|
},
|
|
121
130
|
remove: async (key) => {
|
|
122
131
|
const { code, stderr } = await run(['delete-generic-password', '-s', service, '-a', key]);
|
|
132
|
+
|
|
123
133
|
if (code !== 0 && code !== NOT_FOUND) {
|
|
124
134
|
throw new Error(`The keychain did not remove ${key}: ${stderr.trim()}`);
|
|
125
135
|
}
|
|
@@ -32,6 +32,7 @@ function capitalized(option: ModelOptionDescriptor): ModelOptionDescriptor {
|
|
|
32
32
|
if (option.type !== 'select') {
|
|
33
33
|
return option;
|
|
34
34
|
}
|
|
35
|
+
|
|
35
36
|
return {
|
|
36
37
|
...option,
|
|
37
38
|
choices: option.choices.map((choice) => ({ ...choice, label: choice.label.charAt(0).toLocaleUpperCase('en') + choice.label.slice(1) }))
|
|
@@ -41,9 +42,11 @@ function capitalized(option: ModelOptionDescriptor): ModelOptionDescriptor {
|
|
|
41
42
|
/* The reasoning effort a session offers, as the `effort` option the composer draws for every provider. */
|
|
42
43
|
function effortOption(info: AcpSessionInfo): ModelOptionDescriptor[] {
|
|
43
44
|
const level = info.thoughtLevel;
|
|
45
|
+
|
|
44
46
|
if (level === null || !level.choices.some((choice) => choice.id === level.current)) {
|
|
45
47
|
return [];
|
|
46
48
|
}
|
|
49
|
+
|
|
47
50
|
return [
|
|
48
51
|
{
|
|
49
52
|
id: 'effort',
|
|
@@ -62,6 +65,7 @@ function effortOption(info: AcpSessionInfo): ModelOptionDescriptor[] {
|
|
|
62
65
|
/* Parses one stored part on its own, so a part a later version wrote differently costs only that part. */
|
|
63
66
|
function storedPart<T>(schema: z.ZodType<T>, value: unknown): T | null {
|
|
64
67
|
const parsed = schema.safeParse(value);
|
|
68
|
+
|
|
65
69
|
return parsed.success ? parsed.data : null;
|
|
66
70
|
}
|
|
67
71
|
|
|
@@ -99,6 +103,7 @@ export class AcpAgentState {
|
|
|
99
103
|
|
|
100
104
|
onChange(listener: () => void): () => void {
|
|
101
105
|
this.listeners.add(listener);
|
|
106
|
+
|
|
102
107
|
return () => {
|
|
103
108
|
this.listeners.delete(listener);
|
|
104
109
|
};
|
|
@@ -108,20 +113,22 @@ export class AcpAgentState {
|
|
|
108
113
|
if (!this.options.adoptModels || info.models === null) {
|
|
109
114
|
return;
|
|
110
115
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
this.settle(changed);
|
|
116
|
+
|
|
117
|
+
this.settle(this.adopt({ defaultModel: info.models.current, models: adoptableModels(info), options: effortOption(info) }));
|
|
114
118
|
}
|
|
115
119
|
|
|
116
120
|
commandsListed(commands: readonly string[]): void {
|
|
117
121
|
const names = [...new Set(commands)].sort();
|
|
122
|
+
|
|
118
123
|
if (this.commands !== null && JSON.stringify(names) === JSON.stringify(this.commands)) {
|
|
119
124
|
return;
|
|
120
125
|
}
|
|
126
|
+
|
|
121
127
|
this.commands = names;
|
|
122
128
|
const changed = this.refresh();
|
|
123
129
|
// The commands are saved even when the capabilities stay, so a restart knows them too.
|
|
124
130
|
this.save();
|
|
131
|
+
|
|
125
132
|
if (changed) {
|
|
126
133
|
this.notify();
|
|
127
134
|
}
|
|
@@ -131,25 +138,31 @@ export class AcpAgentState {
|
|
|
131
138
|
if (this.reportsContext) {
|
|
132
139
|
return;
|
|
133
140
|
}
|
|
141
|
+
|
|
134
142
|
this.reportsContext = true;
|
|
135
143
|
this.settle(this.refresh());
|
|
136
144
|
}
|
|
137
145
|
|
|
138
146
|
private restore(): void {
|
|
139
147
|
let stored: unknown;
|
|
148
|
+
|
|
140
149
|
try {
|
|
141
150
|
stored = this.options.store?.load();
|
|
142
151
|
} catch {
|
|
143
152
|
return;
|
|
144
153
|
}
|
|
154
|
+
|
|
145
155
|
if (typeof stored !== 'object' || stored === null) {
|
|
146
156
|
return;
|
|
147
157
|
}
|
|
158
|
+
|
|
148
159
|
const record = stored as Record<string, unknown>;
|
|
149
160
|
const adopted = storedPart(AdoptedSchema, record.models);
|
|
161
|
+
|
|
150
162
|
if (adopted !== null && this.options.adoptModels) {
|
|
151
163
|
this.adopt(adopted);
|
|
152
164
|
}
|
|
165
|
+
|
|
153
166
|
this.reportsContext = record.reportsContext === true;
|
|
154
167
|
this.commands = storedPart(z.array(z.string()), record.commands);
|
|
155
168
|
this.refresh();
|
|
@@ -158,6 +171,7 @@ export class AcpAgentState {
|
|
|
158
171
|
/* What the agent showed it can do, under what the host set itself. */
|
|
159
172
|
private learned(): Partial<ProviderCapabilities> {
|
|
160
173
|
const commands = this.commands ?? [];
|
|
174
|
+
|
|
161
175
|
return {
|
|
162
176
|
...(this.reportsContext ? { reportsContextWindow: true } : {}),
|
|
163
177
|
...(commands.includes('compact') ? { compaction: 'prompt' as const } : {}),
|
|
@@ -168,14 +182,19 @@ export class AcpAgentState {
|
|
|
168
182
|
/* Lays what the agent showed over the capabilities, in place; answers whether they changed. */
|
|
169
183
|
private refresh(): boolean {
|
|
170
184
|
const next: ProviderCapabilities = { ...this.options.base, ...this.learned(), ...this.options.overrides };
|
|
185
|
+
|
|
171
186
|
if (JSON.stringify(next) === JSON.stringify(this.capabilities)) {
|
|
172
187
|
return false;
|
|
173
188
|
}
|
|
189
|
+
|
|
174
190
|
const capabilities = this.capabilities as Partial<ProviderCapabilities>;
|
|
191
|
+
|
|
175
192
|
for (const key of Object.keys(capabilities) as Array<keyof ProviderCapabilities>) {
|
|
176
193
|
delete capabilities[key];
|
|
177
194
|
}
|
|
195
|
+
|
|
178
196
|
Object.assign(capabilities, next);
|
|
197
|
+
|
|
179
198
|
return true;
|
|
180
199
|
}
|
|
181
200
|
|
|
@@ -184,7 +203,9 @@ export class AcpAgentState {
|
|
|
184
203
|
if (!adopted.models.some((model) => model.slug === adopted.defaultModel)) {
|
|
185
204
|
return false;
|
|
186
205
|
}
|
|
206
|
+
|
|
187
207
|
this.adopted = adopted;
|
|
208
|
+
|
|
188
209
|
return this.options.catalog.adopt(adopted.models, adopted.defaultModel, adopted.options.map(capitalized));
|
|
189
210
|
}
|
|
190
211
|
|
|
@@ -72,15 +72,19 @@ export function createAcpProvider(spec: AcpProviderSpec): ChatProvider {
|
|
|
72
72
|
base: ACP_CAPABILITIES,
|
|
73
73
|
overrides: spec.capabilities ?? {}
|
|
74
74
|
});
|
|
75
|
+
|
|
75
76
|
const onSession = (info: AcpSessionInfo): void => {
|
|
76
77
|
state.session(info);
|
|
77
78
|
spec.onSession?.(info);
|
|
78
79
|
};
|
|
80
|
+
|
|
79
81
|
const onCommands = (commands: string[]): void => {
|
|
80
82
|
state.commandsListed(commands);
|
|
81
83
|
spec.onCommands?.(commands);
|
|
82
84
|
};
|
|
85
|
+
|
|
83
86
|
const usageLog = spec.usageLog === undefined || OWN_USAGE_FILES.has(spec.kind) ? null : spec.usageLog;
|
|
87
|
+
|
|
84
88
|
const onUsage =
|
|
85
89
|
usageLog === null
|
|
86
90
|
? spec.onUsage
|
|
@@ -88,6 +92,7 @@ export function createAcpProvider(spec: AcpProviderSpec): ChatProvider {
|
|
|
88
92
|
writeAcpUsage(usageLog, report, Date.now());
|
|
89
93
|
spec.onUsage?.(report);
|
|
90
94
|
};
|
|
95
|
+
|
|
91
96
|
return {
|
|
92
97
|
kind: spec.kind,
|
|
93
98
|
name: spec.name,
|
|
@@ -10,8 +10,10 @@ import { hostUsageLine } from '../usage/readers/host-log.ts';
|
|
|
10
10
|
*/
|
|
11
11
|
export function writeAcpUsage(folder: string, report: AcpUsageReport, at: number): void {
|
|
12
12
|
const { sessionId, cwd, model, ...tokens } = report;
|
|
13
|
+
|
|
13
14
|
try {
|
|
14
15
|
mkdirSync(folder, { recursive: true, mode: 0o700 });
|
|
16
|
+
|
|
15
17
|
appendFileSync(
|
|
16
18
|
join(folder, `${encodeURIComponent(sessionId === '' ? 'session' : sessionId)}.jsonl`),
|
|
17
19
|
hostUsageLine({ at, model, session: sessionId, cwd, ...tokens }),
|
package/src/providers/catalog.ts
CHANGED
|
@@ -35,9 +35,11 @@ export class ModelCatalog {
|
|
|
35
35
|
if (timeOf(data) < this.shippedAt) {
|
|
36
36
|
return false;
|
|
37
37
|
}
|
|
38
|
+
|
|
38
39
|
const changed = JSON.stringify(data) !== JSON.stringify(this.data);
|
|
39
40
|
this.data = data;
|
|
40
41
|
this.hostWindows = undefined;
|
|
42
|
+
|
|
41
43
|
return changed;
|
|
42
44
|
}
|
|
43
45
|
|
|
@@ -55,24 +57,31 @@ export class ModelCatalog {
|
|
|
55
57
|
if (models.length === 0 || !models.some((model) => model.slug === defaultModel)) {
|
|
56
58
|
return false;
|
|
57
59
|
}
|
|
60
|
+
|
|
58
61
|
const base = this.profileOf(this.data.defaultModel) ?? { options: [], contextWindowTokens: {} };
|
|
59
62
|
const replaced = new Set(options.map((option) => option.id));
|
|
60
63
|
this.hostWindows ??= base.contextWindowTokens;
|
|
61
64
|
const profile = { ...base, contextWindowTokens: this.hostWindows, options: [...base.options.filter((option) => !replaced.has(option.id)), ...options] };
|
|
65
|
+
|
|
62
66
|
const profileName = (window: number | undefined): string => (window === undefined ? ADOPTED_PROFILE : `${ADOPTED_PROFILE}-${window}`);
|
|
67
|
+
|
|
63
68
|
const profiles: Record<string, ModelCatalogProfile> = {};
|
|
69
|
+
|
|
64
70
|
for (const model of models) {
|
|
65
71
|
profiles[profileName(model.contextWindow)] =
|
|
66
72
|
model.contextWindow === undefined ? profile : { ...profile, contextWindowTokens: { '*': model.contextWindow } };
|
|
67
73
|
}
|
|
74
|
+
|
|
68
75
|
const data: ModelCatalogInput = {
|
|
69
76
|
...(this.data.updatedAt === undefined ? {} : { updatedAt: this.data.updatedAt }),
|
|
70
77
|
defaultModel,
|
|
71
78
|
profiles,
|
|
72
79
|
models: models.map((model) => ({ slug: model.slug, name: model.name, profile: profileName(model.contextWindow) }))
|
|
73
80
|
};
|
|
81
|
+
|
|
74
82
|
const changed = JSON.stringify(data) !== JSON.stringify(this.data);
|
|
75
83
|
this.data = data;
|
|
84
|
+
|
|
76
85
|
return changed;
|
|
77
86
|
}
|
|
78
87
|
|
|
@@ -98,6 +107,7 @@ export class ModelCatalog {
|
|
|
98
107
|
/* Accepts a slug or an alias; unknown models answer null so the caller can decide what to do. */
|
|
99
108
|
resolveModel(name: string): string | null {
|
|
100
109
|
const model = this.data.models.find((entry) => entry.slug === name || entry.aliases?.includes(name));
|
|
110
|
+
|
|
101
111
|
return model?.slug ?? null;
|
|
102
112
|
}
|
|
103
113
|
|
|
@@ -105,8 +115,10 @@ export class ModelCatalog {
|
|
|
105
115
|
normalize(selection: Partial<ModelSelection> | undefined): ModelSelection {
|
|
106
116
|
const model = (selection?.model && this.resolveModel(selection.model)) || this.defaultModel;
|
|
107
117
|
const options: ModelSelection['options'] = {};
|
|
118
|
+
|
|
108
119
|
for (const descriptor of this.profileOf(model)?.options ?? []) {
|
|
109
120
|
const given = selection?.options?.[descriptor.id];
|
|
121
|
+
|
|
110
122
|
if (descriptor.type === 'select') {
|
|
111
123
|
options[descriptor.id] =
|
|
112
124
|
typeof given === 'string' && descriptor.choices.some((choice) => choice.id === given) ? given : descriptor.defaultChoice;
|
|
@@ -114,20 +126,25 @@ export class ModelCatalog {
|
|
|
114
126
|
options[descriptor.id] = typeof given === 'boolean' ? given : descriptor.defaultValue;
|
|
115
127
|
}
|
|
116
128
|
}
|
|
129
|
+
|
|
117
130
|
return { model, options };
|
|
118
131
|
}
|
|
119
132
|
|
|
120
133
|
contextWindowFor(selection: ModelSelection): number | null {
|
|
121
134
|
const profile = this.profileOf(selection.model);
|
|
135
|
+
|
|
122
136
|
if (!profile) {
|
|
123
137
|
return null;
|
|
124
138
|
}
|
|
139
|
+
|
|
125
140
|
const chosen = selection.options.contextWindow;
|
|
141
|
+
|
|
126
142
|
return (typeof chosen === 'string' ? profile.contextWindowTokens[chosen] : undefined) ?? profile.contextWindowTokens['*'] ?? null;
|
|
127
143
|
}
|
|
128
144
|
|
|
129
145
|
private profileOf(slug: string): ModelCatalogProfile | undefined {
|
|
130
146
|
const model = this.data.models.find((entry) => entry.slug === slug);
|
|
147
|
+
|
|
131
148
|
return model ? this.data.profiles[model.profile] : undefined;
|
|
132
149
|
}
|
|
133
150
|
}
|
package/src/providers/claude.ts
CHANGED
|
@@ -35,6 +35,7 @@ export function claudeRuntimeMode(mode: string): RuntimeMode | undefined {
|
|
|
35
35
|
if (mode === 'default') {
|
|
36
36
|
return 'supervised';
|
|
37
37
|
}
|
|
38
|
+
|
|
38
39
|
return (Object.keys(PERMISSION_MODE) as RuntimeMode[]).find((key) => PERMISSION_MODE[key] === mode);
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -55,12 +56,15 @@ export function claudeArgs(launch: ClaudeLaunch): string[] {
|
|
|
55
56
|
...(launch.folders ?? []).map((folder) => `--add-dir=${folder}`),
|
|
56
57
|
...(launch.projectConfig === false ? CLAUDE_WITHOUT_PROJECT_CONFIG_ARGS : [])
|
|
57
58
|
];
|
|
59
|
+
|
|
58
60
|
const contextWindow = launch.selection.options.contextWindow;
|
|
59
61
|
args.push('--model', `${launch.selection.model}${contextWindow === '1m' ? '[1m]' : ''}`);
|
|
60
62
|
const effort = launch.selection.options.effort;
|
|
63
|
+
|
|
61
64
|
if (typeof effort === 'string' && FLAG_EFFORTS.has(effort)) {
|
|
62
65
|
args.push('--effort', effort);
|
|
63
66
|
}
|
|
67
|
+
|
|
64
68
|
/*
|
|
65
69
|
* Fast mode has no flag of its own; the CLI reads it from the settings blob, which is also the
|
|
66
70
|
* opt-in the init frame asks for (without it the frame answers `sdk_opt_in_required`). It only
|
|
@@ -72,19 +76,25 @@ export function claudeArgs(launch: ClaudeLaunch): string[] {
|
|
|
72
76
|
...(launch.selection.options.fastMode === true ? { fastMode: true } : {}),
|
|
73
77
|
...(launch.selection.options.contextWindow === '200k' ? { autoCompactEnabled: true } : {})
|
|
74
78
|
};
|
|
79
|
+
|
|
75
80
|
if (Object.keys(settings).length > 0) {
|
|
76
81
|
args.push('--settings', JSON.stringify(settings));
|
|
77
82
|
}
|
|
83
|
+
|
|
78
84
|
const permissionMode = PERMISSION_MODE[launch.runtimeMode];
|
|
85
|
+
|
|
79
86
|
if (permissionMode) {
|
|
80
87
|
args.push('--permission-mode', permissionMode);
|
|
81
88
|
}
|
|
89
|
+
|
|
82
90
|
if (permissionMode === 'bypassPermissions') {
|
|
83
91
|
args.push('--allow-dangerously-skip-permissions');
|
|
84
92
|
}
|
|
93
|
+
|
|
85
94
|
if (launch.resume) {
|
|
86
95
|
args.push('--resume', launch.resume);
|
|
87
96
|
}
|
|
97
|
+
|
|
88
98
|
return args;
|
|
89
99
|
}
|
|
90
100
|
|
|
@@ -98,7 +108,6 @@ export function claudeEnv(selection: ModelSelection): Record<string, string> {
|
|
|
98
108
|
return selection.options.contextWindow === '200k' ? { CLAUDE_CODE_AUTO_COMPACT_WINDOW: '200000' } : {};
|
|
99
109
|
}
|
|
100
110
|
|
|
101
|
-
// What the CLI can do, as far as a client has to know.
|
|
102
111
|
export const CLAUDE_CAPABILITIES: ProviderCapabilities = {
|
|
103
112
|
chat: true,
|
|
104
113
|
terminal: true,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"updatedAt": "2026-
|
|
2
|
+
"updatedAt": "2026-10-26T22:10:00Z",
|
|
3
3
|
"defaultModel": "gpt-6-astra",
|
|
4
4
|
"profiles": {
|
|
5
5
|
"ultra": {
|
|
@@ -124,10 +124,11 @@
|
|
|
124
124
|
"models": [
|
|
125
125
|
{ "slug": "gpt-6-astra", "name": "GPT-6 Astra", "profile": "ultra", "aliases": ["astra", "gpt-6"] },
|
|
126
126
|
{ "slug": "gpt-6.1-sol", "name": "GPT-6.1 Sol", "badge": "new", "profile": "max", "aliases": ["sol"] },
|
|
127
|
+
{ "slug": "gpt-6.1-terra", "name": "GPT-6.1 Terra", "badge": "new", "profile": "ultra-low", "aliases": ["terra"] },
|
|
127
128
|
{ "slug": "gpt-6-sol", "name": "GPT-6 Sol", "profile": "max" },
|
|
128
129
|
{ "slug": "gpt-6-luna", "name": "GPT-6 Luna", "badge": "new", "profile": "max", "aliases": ["luna"] },
|
|
129
130
|
{ "slug": "gpt-5.6-sol", "name": "GPT-5.6 Sol", "profile": "ultra-low", "legacy": true },
|
|
130
|
-
{ "slug": "gpt-5.6-terra", "name": "GPT-5.6 Terra", "profile": "ultra", "
|
|
131
|
+
{ "slug": "gpt-5.6-terra", "name": "GPT-5.6 Terra", "profile": "ultra", "badge": "retiring", "legacy": true },
|
|
131
132
|
{ "slug": "gpt-5.6-luna", "name": "GPT-5.6 Luna", "profile": "max", "legacy": true },
|
|
132
133
|
{ "slug": "gpt-5.5", "name": "GPT-5.5", "profile": "xhigh", "legacy": true },
|
|
133
134
|
{ "slug": "gpt-5.3-codex-spark", "name": "GPT-5.3 Codex Spark", "profile": "spark", "aliases": ["spark"], "legacy": true }
|
|
@@ -39,6 +39,7 @@ export function defaultCodexHome(env: Record<string, string | undefined> = proce
|
|
|
39
39
|
/* Idempotent: a file that already says the same is left alone. */
|
|
40
40
|
export async function installCodexRules(path: string, rules: CodexRules): Promise<CodexRulesResult> {
|
|
41
41
|
const text = codexRulesText(rules);
|
|
42
|
+
|
|
42
43
|
try {
|
|
43
44
|
if ((await readFile(path, 'utf8')) === text) {
|
|
44
45
|
return 'unchanged';
|
|
@@ -48,7 +49,9 @@ export async function installCodexRules(path: string, rules: CodexRules): Promis
|
|
|
48
49
|
throw new Error(`Cannot read ${path}: ${errorText(e)}`);
|
|
49
50
|
}
|
|
50
51
|
}
|
|
52
|
+
|
|
51
53
|
await mkdir(dirname(path), { recursive: true });
|
|
52
54
|
await writeAtomic(path, text, 0o644);
|
|
55
|
+
|
|
53
56
|
return 'written';
|
|
54
57
|
}
|
package/src/providers/codex.ts
CHANGED
|
@@ -30,8 +30,10 @@ export function codexThreadOptions(runtimeMode: RuntimeMode): CodexThreadOptions
|
|
|
30
30
|
|
|
31
31
|
export function codexRuntimeMode(approvalPolicy: string, sandbox: string): RuntimeMode | undefined {
|
|
32
32
|
const names: Record<string, string> = { readOnly: 'read-only', workspaceWrite: 'workspace-write', dangerFullAccess: 'danger-full-access' };
|
|
33
|
+
|
|
33
34
|
return (Object.keys(THREAD_OPTIONS) as RuntimeMode[]).find((mode) => {
|
|
34
35
|
const options = THREAD_OPTIONS[mode];
|
|
36
|
+
|
|
35
37
|
return options.approvalPolicy === approvalPolicy && options.sandbox === (names[sandbox] ?? sandbox);
|
|
36
38
|
});
|
|
37
39
|
}
|
|
@@ -44,7 +46,6 @@ export function codexServiceTier(selection: ModelSelection): string | null {
|
|
|
44
46
|
return selection.options.serviceTier === true ? CODEX_PRIORITY_TIER : null;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
// What the app-server can do, as far as a client has to know.
|
|
48
49
|
export const CODEX_CAPABILITIES: ProviderCapabilities = {
|
|
49
50
|
chat: true,
|
|
50
51
|
terminal: true,
|
package/src/providers/detect.ts
CHANGED
|
@@ -18,16 +18,21 @@ export async function detectCli(
|
|
|
18
18
|
): Promise<CliDetection> {
|
|
19
19
|
try {
|
|
20
20
|
let exited: (code: number | null) => void = () => undefined;
|
|
21
|
+
|
|
21
22
|
const code = new Promise<number | null>((resolve) => {
|
|
22
23
|
exited = resolve;
|
|
23
24
|
});
|
|
25
|
+
|
|
24
26
|
const proc = spawn({ command: [command, ...flags, '--version'], cwd: process.cwd(), env: definedEnv(env), onExit: (exitCode) => exited(exitCode) });
|
|
25
27
|
proc.stdin.end();
|
|
26
28
|
const output = await new Response(proc.stdout).text();
|
|
29
|
+
|
|
27
30
|
if ((await code) !== 0) {
|
|
28
31
|
return { installed: false, version: null };
|
|
29
32
|
}
|
|
30
|
-
|
|
33
|
+
|
|
34
|
+
const version = /\d+\.\d+\.\d+/.exec(output)?.[0] ?? output.trim();
|
|
35
|
+
|
|
31
36
|
return { installed: true, version };
|
|
32
37
|
} catch {
|
|
33
38
|
return { installed: false, version: null };
|
|
@@ -26,7 +26,7 @@ export interface ChatProvider {
|
|
|
26
26
|
readonly name: string;
|
|
27
27
|
readonly catalog: ModelCatalog;
|
|
28
28
|
readonly capabilities: ProviderCapabilities;
|
|
29
|
-
// The executable and its leading arguments
|
|
29
|
+
// The executable and its leading arguments.
|
|
30
30
|
readonly command: string[];
|
|
31
31
|
// What a terminal runs to continue one of this CLI's sessions; `{id}` stands for the session id.
|
|
32
32
|
readonly resumeCommand: string;
|