@link-assistant/agent 0.0.9 → 0.0.12
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/EXAMPLES.md +36 -0
- package/MODELS.md +72 -24
- package/README.md +59 -2
- package/TOOLS.md +20 -0
- package/package.json +35 -2
- package/src/agent/agent.ts +68 -54
- package/src/auth/claude-oauth.ts +426 -0
- package/src/auth/index.ts +28 -26
- package/src/auth/plugins.ts +876 -0
- package/src/bun/index.ts +53 -43
- package/src/bus/global.ts +5 -5
- package/src/bus/index.ts +59 -53
- package/src/cli/bootstrap.js +12 -12
- package/src/cli/bootstrap.ts +6 -6
- package/src/cli/cmd/agent.ts +97 -92
- package/src/cli/cmd/auth.ts +469 -0
- package/src/cli/cmd/cmd.ts +2 -2
- package/src/cli/cmd/export.ts +41 -41
- package/src/cli/cmd/mcp.ts +144 -119
- package/src/cli/cmd/models.ts +30 -29
- package/src/cli/cmd/run.ts +269 -213
- package/src/cli/cmd/stats.ts +185 -146
- package/src/cli/error.ts +17 -13
- package/src/cli/ui.ts +39 -24
- package/src/command/index.ts +26 -26
- package/src/config/config.ts +528 -288
- package/src/config/markdown.ts +15 -15
- package/src/file/ripgrep.ts +201 -169
- package/src/file/time.ts +21 -18
- package/src/file/watcher.ts +51 -42
- package/src/file.ts +1 -1
- package/src/flag/flag.ts +26 -11
- package/src/format/formatter.ts +206 -162
- package/src/format/index.ts +61 -61
- package/src/global/index.ts +21 -21
- package/src/id/id.ts +47 -33
- package/src/index.js +346 -199
- package/src/json-standard/index.ts +67 -51
- package/src/mcp/index.ts +135 -128
- package/src/patch/index.ts +336 -267
- package/src/project/bootstrap.ts +15 -15
- package/src/project/instance.ts +43 -36
- package/src/project/project.ts +47 -47
- package/src/project/state.ts +37 -33
- package/src/provider/models-macro.ts +5 -5
- package/src/provider/models.ts +32 -32
- package/src/provider/opencode.js +19 -19
- package/src/provider/provider.ts +518 -277
- package/src/provider/transform.ts +143 -102
- package/src/server/project.ts +21 -21
- package/src/server/server.ts +111 -105
- package/src/session/agent.js +66 -60
- package/src/session/compaction.ts +136 -111
- package/src/session/index.ts +189 -156
- package/src/session/message-v2.ts +312 -268
- package/src/session/message.ts +73 -57
- package/src/session/processor.ts +180 -166
- package/src/session/prompt.ts +678 -533
- package/src/session/retry.ts +26 -23
- package/src/session/revert.ts +76 -62
- package/src/session/status.ts +26 -26
- package/src/session/summary.ts +97 -76
- package/src/session/system.ts +77 -63
- package/src/session/todo.ts +22 -16
- package/src/snapshot/index.ts +92 -76
- package/src/storage/storage.ts +157 -120
- package/src/tool/bash.ts +116 -106
- package/src/tool/batch.ts +73 -59
- package/src/tool/codesearch.ts +60 -53
- package/src/tool/edit.ts +319 -263
- package/src/tool/glob.ts +32 -28
- package/src/tool/grep.ts +72 -53
- package/src/tool/invalid.ts +7 -7
- package/src/tool/ls.ts +77 -64
- package/src/tool/multiedit.ts +30 -21
- package/src/tool/patch.ts +121 -94
- package/src/tool/read.ts +140 -122
- package/src/tool/registry.ts +38 -38
- package/src/tool/task.ts +93 -60
- package/src/tool/todo.ts +16 -16
- package/src/tool/tool.ts +45 -36
- package/src/tool/webfetch.ts +97 -74
- package/src/tool/websearch.ts +78 -64
- package/src/tool/write.ts +21 -15
- package/src/util/binary.ts +27 -19
- package/src/util/context.ts +8 -8
- package/src/util/defer.ts +7 -5
- package/src/util/error.ts +24 -19
- package/src/util/eventloop.ts +16 -10
- package/src/util/filesystem.ts +37 -33
- package/src/util/fn.ts +11 -8
- package/src/util/iife.ts +1 -1
- package/src/util/keybind.ts +44 -44
- package/src/util/lazy.ts +7 -7
- package/src/util/locale.ts +20 -16
- package/src/util/lock.ts +43 -38
- package/src/util/log.ts +95 -85
- package/src/util/queue.ts +8 -8
- package/src/util/rpc.ts +35 -23
- package/src/util/scrap.ts +4 -4
- package/src/util/signal.ts +5 -5
- package/src/util/timeout.ts +6 -6
- package/src/util/token.ts +2 -2
- package/src/util/wildcard.ts +38 -27
package/src/config/config.ts
CHANGED
|
@@ -1,130 +1,147 @@
|
|
|
1
|
-
import { Log } from
|
|
2
|
-
import path from
|
|
3
|
-
import os from
|
|
4
|
-
import z from
|
|
5
|
-
import { Filesystem } from
|
|
6
|
-
import { ModelsDev } from
|
|
7
|
-
import { mergeDeep, pipe } from
|
|
8
|
-
import { Global } from
|
|
9
|
-
import fs from
|
|
10
|
-
import { lazy } from
|
|
11
|
-
import { NamedError } from
|
|
12
|
-
import { Flag } from
|
|
13
|
-
import { Auth } from
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
import { Log } from '../util/log';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import z from 'zod';
|
|
5
|
+
import { Filesystem } from '../util/filesystem';
|
|
6
|
+
import { ModelsDev } from '../provider/models';
|
|
7
|
+
import { mergeDeep, pipe } from 'remeda';
|
|
8
|
+
import { Global } from '../global';
|
|
9
|
+
import fs from 'fs/promises';
|
|
10
|
+
import { lazy } from '../util/lazy';
|
|
11
|
+
import { NamedError } from '../util/error';
|
|
12
|
+
import { Flag } from '../flag/flag';
|
|
13
|
+
import { Auth } from '../auth';
|
|
14
|
+
import {
|
|
15
|
+
type ParseError as JsoncParseError,
|
|
16
|
+
parse as parseJsonc,
|
|
17
|
+
printParseErrorCode,
|
|
18
|
+
} from 'jsonc-parser';
|
|
19
|
+
import { Instance } from '../project/instance';
|
|
20
|
+
import { ConfigMarkdown } from './markdown';
|
|
17
21
|
|
|
18
22
|
export namespace Config {
|
|
19
|
-
const log = Log.create({ service:
|
|
23
|
+
const log = Log.create({ service: 'config' });
|
|
20
24
|
|
|
21
25
|
export const state = Instance.state(async () => {
|
|
22
|
-
const auth = await Auth.all()
|
|
23
|
-
let result = await global()
|
|
26
|
+
const auth = await Auth.all();
|
|
27
|
+
let result = await global();
|
|
24
28
|
|
|
25
29
|
// Override with custom config if provided
|
|
26
30
|
if (Flag.OPENCODE_CONFIG) {
|
|
27
|
-
result = mergeDeep(result, await loadFile(Flag.OPENCODE_CONFIG))
|
|
28
|
-
log.debug(
|
|
31
|
+
result = mergeDeep(result, await loadFile(Flag.OPENCODE_CONFIG));
|
|
32
|
+
log.debug('loaded custom config', { path: Flag.OPENCODE_CONFIG });
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
for (const file of [
|
|
32
|
-
const found = await Filesystem.findUp(
|
|
35
|
+
for (const file of ['opencode.jsonc', 'opencode.json']) {
|
|
36
|
+
const found = await Filesystem.findUp(
|
|
37
|
+
file,
|
|
38
|
+
Instance.directory,
|
|
39
|
+
Instance.worktree
|
|
40
|
+
);
|
|
33
41
|
for (const resolved of found.toReversed()) {
|
|
34
|
-
result = mergeDeep(result, await loadFile(resolved))
|
|
42
|
+
result = mergeDeep(result, await loadFile(resolved));
|
|
35
43
|
}
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
if (Flag.OPENCODE_CONFIG_CONTENT) {
|
|
39
|
-
result = mergeDeep(result, JSON.parse(Flag.OPENCODE_CONFIG_CONTENT))
|
|
40
|
-
log.debug(
|
|
47
|
+
result = mergeDeep(result, JSON.parse(Flag.OPENCODE_CONFIG_CONTENT));
|
|
48
|
+
log.debug('loaded custom config from OPENCODE_CONFIG_CONTENT');
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
for (const [key, value] of Object.entries(auth)) {
|
|
44
|
-
if (value.type ===
|
|
45
|
-
process.env[value.key] = value.token
|
|
46
|
-
const wellknown = (await fetch(`${key}/.well-known/opencode`).then(
|
|
47
|
-
|
|
52
|
+
if (value.type === 'wellknown') {
|
|
53
|
+
process.env[value.key] = value.token;
|
|
54
|
+
const wellknown = (await fetch(`${key}/.well-known/opencode`).then(
|
|
55
|
+
(x) => x.json()
|
|
56
|
+
)) as any;
|
|
57
|
+
result = mergeDeep(
|
|
58
|
+
result,
|
|
59
|
+
await load(JSON.stringify(wellknown.config ?? {}), process.cwd())
|
|
60
|
+
);
|
|
48
61
|
}
|
|
49
62
|
}
|
|
50
63
|
|
|
51
|
-
result.agent = result.agent || {}
|
|
52
|
-
result.mode = result.mode || {}
|
|
64
|
+
result.agent = result.agent || {};
|
|
65
|
+
result.mode = result.mode || {};
|
|
53
66
|
|
|
54
67
|
const directories = [
|
|
55
68
|
Global.Path.config,
|
|
56
69
|
...(await Array.fromAsync(
|
|
57
70
|
Filesystem.up({
|
|
58
|
-
targets: [
|
|
71
|
+
targets: ['.opencode'],
|
|
59
72
|
start: Instance.directory,
|
|
60
73
|
stop: Instance.worktree,
|
|
61
|
-
})
|
|
74
|
+
})
|
|
62
75
|
)),
|
|
63
|
-
]
|
|
76
|
+
];
|
|
64
77
|
|
|
65
78
|
if (Flag.OPENCODE_CONFIG_DIR) {
|
|
66
|
-
directories.push(Flag.OPENCODE_CONFIG_DIR)
|
|
67
|
-
log.debug(
|
|
79
|
+
directories.push(Flag.OPENCODE_CONFIG_DIR);
|
|
80
|
+
log.debug('loading config from OPENCODE_CONFIG_DIR', {
|
|
81
|
+
path: Flag.OPENCODE_CONFIG_DIR,
|
|
82
|
+
});
|
|
68
83
|
}
|
|
69
84
|
|
|
70
|
-
const promises: Promise<void>[] = []
|
|
85
|
+
const promises: Promise<void>[] = [];
|
|
71
86
|
for (const dir of directories) {
|
|
72
|
-
await assertValid(dir)
|
|
87
|
+
await assertValid(dir);
|
|
73
88
|
|
|
74
|
-
if (dir.endsWith(
|
|
75
|
-
for (const file of [
|
|
76
|
-
log.debug(`loading config from ${path.join(dir, file)}`)
|
|
77
|
-
result = mergeDeep(result, await loadFile(path.join(dir, file)))
|
|
89
|
+
if (dir.endsWith('.opencode') || dir === Flag.OPENCODE_CONFIG_DIR) {
|
|
90
|
+
for (const file of ['opencode.jsonc', 'opencode.json']) {
|
|
91
|
+
log.debug(`loading config from ${path.join(dir, file)}`);
|
|
92
|
+
result = mergeDeep(result, await loadFile(path.join(dir, file)));
|
|
78
93
|
// to satisy the type checker
|
|
79
|
-
result.agent ??= {}
|
|
80
|
-
result.mode ??= {}
|
|
94
|
+
result.agent ??= {};
|
|
95
|
+
result.mode ??= {};
|
|
81
96
|
}
|
|
82
97
|
}
|
|
83
98
|
|
|
84
|
-
promises.push(installDependencies(dir))
|
|
85
|
-
result.command = mergeDeep(result.command ?? {}, await loadCommand(dir))
|
|
86
|
-
result.agent = mergeDeep(result.agent, await loadAgent(dir))
|
|
87
|
-
result.agent = mergeDeep(result.agent, await loadMode(dir))
|
|
99
|
+
promises.push(installDependencies(dir));
|
|
100
|
+
result.command = mergeDeep(result.command ?? {}, await loadCommand(dir));
|
|
101
|
+
result.agent = mergeDeep(result.agent, await loadAgent(dir));
|
|
102
|
+
result.agent = mergeDeep(result.agent, await loadMode(dir));
|
|
88
103
|
}
|
|
89
|
-
await Promise.allSettled(promises)
|
|
104
|
+
await Promise.allSettled(promises);
|
|
90
105
|
|
|
91
106
|
// Migrate deprecated mode field to agent field
|
|
92
107
|
for (const [name, mode] of Object.entries(result.mode)) {
|
|
93
108
|
result.agent = mergeDeep(result.agent ?? {}, {
|
|
94
109
|
[name]: {
|
|
95
110
|
...mode,
|
|
96
|
-
mode:
|
|
111
|
+
mode: 'primary' as const,
|
|
97
112
|
},
|
|
98
|
-
})
|
|
113
|
+
});
|
|
99
114
|
}
|
|
100
115
|
|
|
101
116
|
// Permission system removed
|
|
102
117
|
// Share/autoshare removed - no sharing support
|
|
103
118
|
|
|
104
|
-
if (!result.username) result.username = os.userInfo().username
|
|
119
|
+
if (!result.username) result.username = os.userInfo().username;
|
|
105
120
|
|
|
106
|
-
if (!result.keybinds) result.keybinds = Info.shape.keybinds.parse({})
|
|
121
|
+
if (!result.keybinds) result.keybinds = Info.shape.keybinds.parse({});
|
|
107
122
|
|
|
108
123
|
return {
|
|
109
124
|
config: result,
|
|
110
125
|
directories,
|
|
111
|
-
}
|
|
112
|
-
})
|
|
126
|
+
};
|
|
127
|
+
});
|
|
113
128
|
|
|
114
|
-
const INVALID_DIRS = new Bun.Glob(
|
|
129
|
+
const INVALID_DIRS = new Bun.Glob(
|
|
130
|
+
`{${['agents', 'commands', 'tools'].join(',')}}/`
|
|
131
|
+
);
|
|
115
132
|
async function assertValid(dir: string) {
|
|
116
133
|
const invalid = await Array.fromAsync(
|
|
117
134
|
INVALID_DIRS.scan({
|
|
118
135
|
onlyFiles: false,
|
|
119
136
|
cwd: dir,
|
|
120
|
-
})
|
|
121
|
-
)
|
|
137
|
+
})
|
|
138
|
+
);
|
|
122
139
|
for (const item of invalid) {
|
|
123
140
|
throw new ConfigDirectoryTypoError({
|
|
124
141
|
path: dir,
|
|
125
142
|
dir: item,
|
|
126
143
|
suggestion: item.substring(0, item.length - 1),
|
|
127
|
-
})
|
|
144
|
+
});
|
|
128
145
|
}
|
|
129
146
|
}
|
|
130
147
|
|
|
@@ -132,47 +149,47 @@ export namespace Config {
|
|
|
132
149
|
// Dependency installation removed - no plugin support
|
|
133
150
|
}
|
|
134
151
|
|
|
135
|
-
const COMMAND_GLOB = new Bun.Glob(
|
|
152
|
+
const COMMAND_GLOB = new Bun.Glob('command/**/*.md');
|
|
136
153
|
async function loadCommand(dir: string) {
|
|
137
|
-
const result: Record<string, Command> = {}
|
|
154
|
+
const result: Record<string, Command> = {};
|
|
138
155
|
for await (const item of COMMAND_GLOB.scan({
|
|
139
156
|
absolute: true,
|
|
140
157
|
followSymlinks: true,
|
|
141
158
|
dot: true,
|
|
142
159
|
cwd: dir,
|
|
143
160
|
})) {
|
|
144
|
-
const md = await ConfigMarkdown.parse(item)
|
|
145
|
-
if (!md.data) continue
|
|
161
|
+
const md = await ConfigMarkdown.parse(item);
|
|
162
|
+
if (!md.data) continue;
|
|
146
163
|
|
|
147
164
|
const name = (() => {
|
|
148
|
-
const patterns = [
|
|
149
|
-
const pattern = patterns.find((p) => item.includes(p))
|
|
165
|
+
const patterns = ['/.opencode/command/', '/command/'];
|
|
166
|
+
const pattern = patterns.find((p) => item.includes(p));
|
|
150
167
|
|
|
151
168
|
if (pattern) {
|
|
152
|
-
const index = item.indexOf(pattern)
|
|
153
|
-
return item.slice(index + pattern.length, -3)
|
|
169
|
+
const index = item.indexOf(pattern);
|
|
170
|
+
return item.slice(index + pattern.length, -3);
|
|
154
171
|
}
|
|
155
|
-
return path.basename(item,
|
|
156
|
-
})()
|
|
172
|
+
return path.basename(item, '.md');
|
|
173
|
+
})();
|
|
157
174
|
|
|
158
175
|
const config = {
|
|
159
176
|
name,
|
|
160
177
|
...md.data,
|
|
161
178
|
template: md.content.trim(),
|
|
162
|
-
}
|
|
163
|
-
const parsed = Command.safeParse(config)
|
|
179
|
+
};
|
|
180
|
+
const parsed = Command.safeParse(config);
|
|
164
181
|
if (parsed.success) {
|
|
165
|
-
result[config.name] = parsed.data
|
|
166
|
-
continue
|
|
182
|
+
result[config.name] = parsed.data;
|
|
183
|
+
continue;
|
|
167
184
|
}
|
|
168
|
-
throw new InvalidError({ path: item }, { cause: parsed.error })
|
|
185
|
+
throw new InvalidError({ path: item }, { cause: parsed.error });
|
|
169
186
|
}
|
|
170
|
-
return result
|
|
187
|
+
return result;
|
|
171
188
|
}
|
|
172
189
|
|
|
173
|
-
const AGENT_GLOB = new Bun.Glob(
|
|
190
|
+
const AGENT_GLOB = new Bun.Glob('agent/**/*.md');
|
|
174
191
|
async function loadAgent(dir: string) {
|
|
175
|
-
const result: Record<string, Agent> = {}
|
|
192
|
+
const result: Record<string, Agent> = {};
|
|
176
193
|
|
|
177
194
|
for await (const item of AGENT_GLOB.scan({
|
|
178
195
|
absolute: true,
|
|
@@ -180,116 +197,131 @@ export namespace Config {
|
|
|
180
197
|
dot: true,
|
|
181
198
|
cwd: dir,
|
|
182
199
|
})) {
|
|
183
|
-
const md = await ConfigMarkdown.parse(item)
|
|
184
|
-
if (!md.data) continue
|
|
200
|
+
const md = await ConfigMarkdown.parse(item);
|
|
201
|
+
if (!md.data) continue;
|
|
185
202
|
|
|
186
203
|
// Extract relative path from agent folder for nested agents
|
|
187
|
-
let agentName = path.basename(item,
|
|
188
|
-
const agentFolderPath = item.includes(
|
|
189
|
-
? item.split(
|
|
190
|
-
: item.includes(
|
|
191
|
-
? item.split(
|
|
192
|
-
: agentName +
|
|
204
|
+
let agentName = path.basename(item, '.md');
|
|
205
|
+
const agentFolderPath = item.includes('/.opencode/agent/')
|
|
206
|
+
? item.split('/.opencode/agent/')[1]
|
|
207
|
+
: item.includes('/agent/')
|
|
208
|
+
? item.split('/agent/')[1]
|
|
209
|
+
: agentName + '.md';
|
|
193
210
|
|
|
194
211
|
// If agent is in a subfolder, include folder path in name
|
|
195
|
-
if (agentFolderPath.includes(
|
|
196
|
-
const relativePath = agentFolderPath.replace(
|
|
197
|
-
const pathParts = relativePath.split(
|
|
198
|
-
agentName =
|
|
212
|
+
if (agentFolderPath.includes('/')) {
|
|
213
|
+
const relativePath = agentFolderPath.replace('.md', '');
|
|
214
|
+
const pathParts = relativePath.split('/');
|
|
215
|
+
agentName =
|
|
216
|
+
pathParts.slice(0, -1).join('/') +
|
|
217
|
+
'/' +
|
|
218
|
+
pathParts[pathParts.length - 1];
|
|
199
219
|
}
|
|
200
220
|
|
|
201
221
|
const config = {
|
|
202
222
|
name: agentName,
|
|
203
223
|
...md.data,
|
|
204
224
|
prompt: md.content.trim(),
|
|
205
|
-
}
|
|
206
|
-
const parsed = Agent.safeParse(config)
|
|
225
|
+
};
|
|
226
|
+
const parsed = Agent.safeParse(config);
|
|
207
227
|
if (parsed.success) {
|
|
208
|
-
result[config.name] = parsed.data
|
|
209
|
-
continue
|
|
228
|
+
result[config.name] = parsed.data;
|
|
229
|
+
continue;
|
|
210
230
|
}
|
|
211
|
-
throw new InvalidError({ path: item }, { cause: parsed.error })
|
|
231
|
+
throw new InvalidError({ path: item }, { cause: parsed.error });
|
|
212
232
|
}
|
|
213
|
-
return result
|
|
233
|
+
return result;
|
|
214
234
|
}
|
|
215
235
|
|
|
216
|
-
const MODE_GLOB = new Bun.Glob(
|
|
236
|
+
const MODE_GLOB = new Bun.Glob('mode/*.md');
|
|
217
237
|
async function loadMode(dir: string) {
|
|
218
|
-
const result: Record<string, Agent> = {}
|
|
238
|
+
const result: Record<string, Agent> = {};
|
|
219
239
|
for await (const item of MODE_GLOB.scan({
|
|
220
240
|
absolute: true,
|
|
221
241
|
followSymlinks: true,
|
|
222
242
|
dot: true,
|
|
223
243
|
cwd: dir,
|
|
224
244
|
})) {
|
|
225
|
-
const md = await ConfigMarkdown.parse(item)
|
|
226
|
-
if (!md.data) continue
|
|
245
|
+
const md = await ConfigMarkdown.parse(item);
|
|
246
|
+
if (!md.data) continue;
|
|
227
247
|
|
|
228
248
|
const config = {
|
|
229
|
-
name: path.basename(item,
|
|
249
|
+
name: path.basename(item, '.md'),
|
|
230
250
|
...md.data,
|
|
231
251
|
prompt: md.content.trim(),
|
|
232
|
-
}
|
|
233
|
-
const parsed = Agent.safeParse(config)
|
|
252
|
+
};
|
|
253
|
+
const parsed = Agent.safeParse(config);
|
|
234
254
|
if (parsed.success) {
|
|
235
255
|
result[config.name] = {
|
|
236
256
|
...parsed.data,
|
|
237
|
-
mode:
|
|
238
|
-
}
|
|
239
|
-
continue
|
|
257
|
+
mode: 'primary' as const,
|
|
258
|
+
};
|
|
259
|
+
continue;
|
|
240
260
|
}
|
|
241
261
|
}
|
|
242
|
-
return result
|
|
262
|
+
return result;
|
|
243
263
|
}
|
|
244
264
|
|
|
245
265
|
export const McpLocal = z
|
|
246
266
|
.object({
|
|
247
|
-
type: z.literal(
|
|
248
|
-
command: z
|
|
267
|
+
type: z.literal('local').describe('Type of MCP server connection'),
|
|
268
|
+
command: z
|
|
269
|
+
.string()
|
|
270
|
+
.array()
|
|
271
|
+
.describe('Command and arguments to run the MCP server'),
|
|
249
272
|
environment: z
|
|
250
273
|
.record(z.string(), z.string())
|
|
251
274
|
.optional()
|
|
252
|
-
.describe(
|
|
253
|
-
enabled: z
|
|
275
|
+
.describe('Environment variables to set when running the MCP server'),
|
|
276
|
+
enabled: z
|
|
277
|
+
.boolean()
|
|
278
|
+
.optional()
|
|
279
|
+
.describe('Enable or disable the MCP server on startup'),
|
|
254
280
|
timeout: z
|
|
255
281
|
.number()
|
|
256
282
|
.int()
|
|
257
283
|
.positive()
|
|
258
284
|
.optional()
|
|
259
285
|
.describe(
|
|
260
|
-
|
|
286
|
+
'Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.'
|
|
261
287
|
),
|
|
262
288
|
})
|
|
263
289
|
.strict()
|
|
264
290
|
.meta({
|
|
265
|
-
ref:
|
|
266
|
-
})
|
|
291
|
+
ref: 'McpLocalConfig',
|
|
292
|
+
});
|
|
267
293
|
|
|
268
294
|
export const McpRemote = z
|
|
269
295
|
.object({
|
|
270
|
-
type: z.literal(
|
|
271
|
-
url: z.string().describe(
|
|
272
|
-
enabled: z
|
|
273
|
-
|
|
296
|
+
type: z.literal('remote').describe('Type of MCP server connection'),
|
|
297
|
+
url: z.string().describe('URL of the remote MCP server'),
|
|
298
|
+
enabled: z
|
|
299
|
+
.boolean()
|
|
300
|
+
.optional()
|
|
301
|
+
.describe('Enable or disable the MCP server on startup'),
|
|
302
|
+
headers: z
|
|
303
|
+
.record(z.string(), z.string())
|
|
304
|
+
.optional()
|
|
305
|
+
.describe('Headers to send with the request'),
|
|
274
306
|
timeout: z
|
|
275
307
|
.number()
|
|
276
308
|
.int()
|
|
277
309
|
.positive()
|
|
278
310
|
.optional()
|
|
279
311
|
.describe(
|
|
280
|
-
|
|
312
|
+
'Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.'
|
|
281
313
|
),
|
|
282
314
|
})
|
|
283
315
|
.strict()
|
|
284
316
|
.meta({
|
|
285
|
-
ref:
|
|
286
|
-
})
|
|
317
|
+
ref: 'McpRemoteConfig',
|
|
318
|
+
});
|
|
287
319
|
|
|
288
|
-
export const Mcp = z.discriminatedUnion(
|
|
289
|
-
export type Mcp = z.infer<typeof Mcp
|
|
320
|
+
export const Mcp = z.discriminatedUnion('type', [McpLocal, McpRemote]);
|
|
321
|
+
export type Mcp = z.infer<typeof Mcp>;
|
|
290
322
|
|
|
291
|
-
export const Permission = z.enum([
|
|
292
|
-
export type Permission = z.infer<typeof Permission
|
|
323
|
+
export const Permission = z.enum(['ask', 'allow', 'deny']);
|
|
324
|
+
export type Permission = z.infer<typeof Permission>;
|
|
293
325
|
|
|
294
326
|
export const Command = z.object({
|
|
295
327
|
template: z.string(),
|
|
@@ -297,8 +329,8 @@ export namespace Config {
|
|
|
297
329
|
agent: z.string().optional(),
|
|
298
330
|
model: z.string().optional(),
|
|
299
331
|
subtask: z.boolean().optional(),
|
|
300
|
-
})
|
|
301
|
-
export type Command = z.infer<typeof Command
|
|
332
|
+
});
|
|
333
|
+
export type Command = z.infer<typeof Command>;
|
|
302
334
|
|
|
303
335
|
export const Agent = z
|
|
304
336
|
.object({
|
|
@@ -308,17 +340,22 @@ export namespace Config {
|
|
|
308
340
|
prompt: z.string().optional(),
|
|
309
341
|
tools: z.record(z.string(), z.boolean()).optional(),
|
|
310
342
|
disable: z.boolean().optional(),
|
|
311
|
-
description: z
|
|
312
|
-
|
|
343
|
+
description: z
|
|
344
|
+
.string()
|
|
345
|
+
.optional()
|
|
346
|
+
.describe('Description of when to use the agent'),
|
|
347
|
+
mode: z.enum(['subagent', 'primary', 'all']).optional(),
|
|
313
348
|
color: z
|
|
314
349
|
.string()
|
|
315
|
-
.regex(/^#[0-9a-fA-F]{6}$/,
|
|
350
|
+
.regex(/^#[0-9a-fA-F]{6}$/, 'Invalid hex color format')
|
|
316
351
|
.optional()
|
|
317
|
-
.describe(
|
|
352
|
+
.describe('Hex color code for the agent (e.g., #FF5733)'),
|
|
318
353
|
permission: z
|
|
319
354
|
.object({
|
|
320
355
|
edit: Permission.optional(),
|
|
321
|
-
bash: z
|
|
356
|
+
bash: z
|
|
357
|
+
.union([Permission, z.record(z.string(), Permission)])
|
|
358
|
+
.optional(),
|
|
322
359
|
webfetch: Permission.optional(),
|
|
323
360
|
doom_loop: Permission.optional(),
|
|
324
361
|
external_directory: Permission.optional(),
|
|
@@ -327,89 +364,242 @@ export namespace Config {
|
|
|
327
364
|
})
|
|
328
365
|
.catchall(z.any())
|
|
329
366
|
.meta({
|
|
330
|
-
ref:
|
|
331
|
-
})
|
|
332
|
-
export type Agent = z.infer<typeof Agent
|
|
367
|
+
ref: 'AgentConfig',
|
|
368
|
+
});
|
|
369
|
+
export type Agent = z.infer<typeof Agent>;
|
|
333
370
|
|
|
334
371
|
export const Keybinds = z
|
|
335
372
|
.object({
|
|
336
|
-
leader: z
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
373
|
+
leader: z
|
|
374
|
+
.string()
|
|
375
|
+
.optional()
|
|
376
|
+
.default('ctrl+x')
|
|
377
|
+
.describe('Leader key for keybind combinations'),
|
|
378
|
+
app_exit: z
|
|
379
|
+
.string()
|
|
380
|
+
.optional()
|
|
381
|
+
.default('ctrl+c,ctrl+d,<leader>q')
|
|
382
|
+
.describe('Exit the application'),
|
|
383
|
+
editor_open: z
|
|
384
|
+
.string()
|
|
385
|
+
.optional()
|
|
386
|
+
.default('<leader>e')
|
|
387
|
+
.describe('Open external editor'),
|
|
388
|
+
theme_list: z
|
|
389
|
+
.string()
|
|
390
|
+
.optional()
|
|
391
|
+
.default('<leader>t')
|
|
392
|
+
.describe('List available themes'),
|
|
393
|
+
sidebar_toggle: z
|
|
394
|
+
.string()
|
|
395
|
+
.optional()
|
|
396
|
+
.default('<leader>b')
|
|
397
|
+
.describe('Toggle sidebar'),
|
|
398
|
+
status_view: z
|
|
399
|
+
.string()
|
|
400
|
+
.optional()
|
|
401
|
+
.default('<leader>s')
|
|
402
|
+
.describe('View status'),
|
|
403
|
+
session_export: z
|
|
404
|
+
.string()
|
|
405
|
+
.optional()
|
|
406
|
+
.default('<leader>x')
|
|
407
|
+
.describe('Export session to editor'),
|
|
408
|
+
session_new: z
|
|
409
|
+
.string()
|
|
410
|
+
.optional()
|
|
411
|
+
.default('<leader>n')
|
|
412
|
+
.describe('Create a new session'),
|
|
413
|
+
session_list: z
|
|
414
|
+
.string()
|
|
415
|
+
.optional()
|
|
416
|
+
.default('<leader>l')
|
|
417
|
+
.describe('List all sessions'),
|
|
418
|
+
session_timeline: z
|
|
419
|
+
.string()
|
|
420
|
+
.optional()
|
|
421
|
+
.default('<leader>g')
|
|
422
|
+
.describe('Show session timeline'),
|
|
423
|
+
session_interrupt: z
|
|
424
|
+
.string()
|
|
425
|
+
.optional()
|
|
426
|
+
.default('escape')
|
|
427
|
+
.describe('Interrupt current session'),
|
|
428
|
+
session_compact: z
|
|
429
|
+
.string()
|
|
430
|
+
.optional()
|
|
431
|
+
.default('<leader>c')
|
|
432
|
+
.describe('Compact the session'),
|
|
433
|
+
messages_page_up: z
|
|
434
|
+
.string()
|
|
435
|
+
.optional()
|
|
436
|
+
.default('pageup')
|
|
437
|
+
.describe('Scroll messages up by one page'),
|
|
438
|
+
messages_page_down: z
|
|
439
|
+
.string()
|
|
440
|
+
.optional()
|
|
441
|
+
.default('pagedown')
|
|
442
|
+
.describe('Scroll messages down by one page'),
|
|
443
|
+
messages_half_page_up: z
|
|
444
|
+
.string()
|
|
445
|
+
.optional()
|
|
446
|
+
.default('ctrl+alt+u')
|
|
447
|
+
.describe('Scroll messages up by half page'),
|
|
351
448
|
messages_half_page_down: z
|
|
352
449
|
.string()
|
|
353
450
|
.optional()
|
|
354
|
-
.default(
|
|
355
|
-
.describe(
|
|
356
|
-
messages_first: z
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
451
|
+
.default('ctrl+alt+d')
|
|
452
|
+
.describe('Scroll messages down by half page'),
|
|
453
|
+
messages_first: z
|
|
454
|
+
.string()
|
|
455
|
+
.optional()
|
|
456
|
+
.default('ctrl+g,home')
|
|
457
|
+
.describe('Navigate to first message'),
|
|
458
|
+
messages_last: z
|
|
459
|
+
.string()
|
|
460
|
+
.optional()
|
|
461
|
+
.default('ctrl+alt+g,end')
|
|
462
|
+
.describe('Navigate to last message'),
|
|
463
|
+
messages_copy: z
|
|
464
|
+
.string()
|
|
465
|
+
.optional()
|
|
466
|
+
.default('<leader>y')
|
|
467
|
+
.describe('Copy message'),
|
|
468
|
+
messages_undo: z
|
|
469
|
+
.string()
|
|
470
|
+
.optional()
|
|
471
|
+
.default('<leader>u')
|
|
472
|
+
.describe('Undo message'),
|
|
473
|
+
messages_redo: z
|
|
474
|
+
.string()
|
|
475
|
+
.optional()
|
|
476
|
+
.default('<leader>r')
|
|
477
|
+
.describe('Redo message'),
|
|
361
478
|
messages_toggle_conceal: z
|
|
362
479
|
.string()
|
|
363
480
|
.optional()
|
|
364
|
-
.default(
|
|
365
|
-
.describe(
|
|
366
|
-
model_list: z
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
481
|
+
.default('<leader>h')
|
|
482
|
+
.describe('Toggle code block concealment in messages'),
|
|
483
|
+
model_list: z
|
|
484
|
+
.string()
|
|
485
|
+
.optional()
|
|
486
|
+
.default('<leader>m')
|
|
487
|
+
.describe('List available models'),
|
|
488
|
+
model_cycle_recent: z
|
|
489
|
+
.string()
|
|
490
|
+
.optional()
|
|
491
|
+
.default('f2')
|
|
492
|
+
.describe('Next recently used model'),
|
|
493
|
+
model_cycle_recent_reverse: z
|
|
494
|
+
.string()
|
|
495
|
+
.optional()
|
|
496
|
+
.default('shift+f2')
|
|
497
|
+
.describe('Previous recently used model'),
|
|
498
|
+
command_list: z
|
|
499
|
+
.string()
|
|
500
|
+
.optional()
|
|
501
|
+
.default('ctrl+p')
|
|
502
|
+
.describe('List available commands'),
|
|
503
|
+
agent_list: z
|
|
504
|
+
.string()
|
|
505
|
+
.optional()
|
|
506
|
+
.default('<leader>a')
|
|
507
|
+
.describe('List agents'),
|
|
508
|
+
agent_cycle: z.string().optional().default('tab').describe('Next agent'),
|
|
509
|
+
agent_cycle_reverse: z
|
|
510
|
+
.string()
|
|
511
|
+
.optional()
|
|
512
|
+
.default('shift+tab')
|
|
513
|
+
.describe('Previous agent'),
|
|
514
|
+
input_clear: z
|
|
515
|
+
.string()
|
|
516
|
+
.optional()
|
|
517
|
+
.default('ctrl+c')
|
|
518
|
+
.describe('Clear input field'),
|
|
519
|
+
input_forward_delete: z
|
|
520
|
+
.string()
|
|
521
|
+
.optional()
|
|
522
|
+
.default('ctrl+d')
|
|
523
|
+
.describe('Forward delete'),
|
|
524
|
+
input_paste: z
|
|
525
|
+
.string()
|
|
526
|
+
.optional()
|
|
527
|
+
.default('ctrl+v')
|
|
528
|
+
.describe('Paste from clipboard'),
|
|
529
|
+
input_submit: z
|
|
530
|
+
.string()
|
|
531
|
+
.optional()
|
|
532
|
+
.default('return')
|
|
533
|
+
.describe('Submit input'),
|
|
534
|
+
input_newline: z
|
|
535
|
+
.string()
|
|
536
|
+
.optional()
|
|
537
|
+
.default('shift+return,ctrl+j')
|
|
538
|
+
.describe('Insert newline in input'),
|
|
539
|
+
history_previous: z
|
|
540
|
+
.string()
|
|
541
|
+
.optional()
|
|
542
|
+
.default('up')
|
|
543
|
+
.describe('Previous history item'),
|
|
544
|
+
history_next: z
|
|
545
|
+
.string()
|
|
546
|
+
.optional()
|
|
547
|
+
.default('down')
|
|
548
|
+
.describe('Next history item'),
|
|
549
|
+
session_child_cycle: z
|
|
550
|
+
.string()
|
|
551
|
+
.optional()
|
|
552
|
+
.default('ctrl+right')
|
|
553
|
+
.describe('Next child session'),
|
|
554
|
+
session_child_cycle_reverse: z
|
|
555
|
+
.string()
|
|
556
|
+
.optional()
|
|
557
|
+
.default('ctrl+left')
|
|
558
|
+
.describe('Previous child session'),
|
|
382
559
|
})
|
|
383
560
|
.strict()
|
|
384
561
|
.meta({
|
|
385
|
-
ref:
|
|
386
|
-
})
|
|
562
|
+
ref: 'KeybindsConfig',
|
|
563
|
+
});
|
|
387
564
|
|
|
388
565
|
export const TUI = z.object({
|
|
389
|
-
scroll_speed: z
|
|
566
|
+
scroll_speed: z
|
|
567
|
+
.number()
|
|
568
|
+
.min(0.001)
|
|
569
|
+
.optional()
|
|
570
|
+
.default(1)
|
|
571
|
+
.describe('TUI scroll speed'),
|
|
390
572
|
scroll_acceleration: z
|
|
391
573
|
.object({
|
|
392
|
-
enabled: z.boolean().describe(
|
|
574
|
+
enabled: z.boolean().describe('Enable scroll acceleration'),
|
|
393
575
|
})
|
|
394
576
|
.optional()
|
|
395
|
-
.describe(
|
|
396
|
-
})
|
|
577
|
+
.describe('Scroll acceleration settings'),
|
|
578
|
+
});
|
|
397
579
|
|
|
398
|
-
export const Layout = z.enum([
|
|
399
|
-
ref:
|
|
400
|
-
})
|
|
401
|
-
export type Layout = z.infer<typeof Layout
|
|
580
|
+
export const Layout = z.enum(['auto', 'stretch']).meta({
|
|
581
|
+
ref: 'LayoutConfig',
|
|
582
|
+
});
|
|
583
|
+
export type Layout = z.infer<typeof Layout>;
|
|
402
584
|
|
|
403
585
|
export const Info = z
|
|
404
586
|
.object({
|
|
405
|
-
$schema: z
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
587
|
+
$schema: z
|
|
588
|
+
.string()
|
|
589
|
+
.optional()
|
|
590
|
+
.describe('JSON schema reference for configuration validation'),
|
|
591
|
+
theme: z
|
|
592
|
+
.string()
|
|
593
|
+
.optional()
|
|
594
|
+
.describe('Theme name to use for the interface'),
|
|
595
|
+
keybinds: Keybinds.optional().describe('Custom keybind configurations'),
|
|
596
|
+
tui: TUI.optional().describe('TUI specific settings'),
|
|
409
597
|
command: z
|
|
410
598
|
.record(z.string(), Command)
|
|
411
599
|
.optional()
|
|
412
|
-
.describe(
|
|
600
|
+
.describe(
|
|
601
|
+
'Command configuration, see https://opencode.ai/docs/commands'
|
|
602
|
+
),
|
|
413
603
|
watcher: z
|
|
414
604
|
.object({
|
|
415
605
|
ignore: z.array(z.string()).optional(),
|
|
@@ -417,17 +607,32 @@ export namespace Config {
|
|
|
417
607
|
.optional(),
|
|
418
608
|
snapshot: z.boolean().optional(),
|
|
419
609
|
// share and autoshare fields removed - no sharing support
|
|
420
|
-
autoupdate: z
|
|
421
|
-
|
|
422
|
-
|
|
610
|
+
autoupdate: z
|
|
611
|
+
.boolean()
|
|
612
|
+
.optional()
|
|
613
|
+
.describe('Automatically update to the latest version'),
|
|
614
|
+
disabled_providers: z
|
|
615
|
+
.array(z.string())
|
|
616
|
+
.optional()
|
|
617
|
+
.describe('Disable providers that are loaded automatically'),
|
|
618
|
+
model: z
|
|
619
|
+
.string()
|
|
620
|
+
.describe(
|
|
621
|
+
'Model to use in the format of provider/model, eg anthropic/claude-2'
|
|
622
|
+
)
|
|
623
|
+
.optional(),
|
|
423
624
|
small_model: z
|
|
424
625
|
.string()
|
|
425
|
-
.describe(
|
|
626
|
+
.describe(
|
|
627
|
+
'Small model to use for tasks like title generation in the format of provider/model'
|
|
628
|
+
)
|
|
426
629
|
.optional(),
|
|
427
630
|
username: z
|
|
428
631
|
.string()
|
|
429
632
|
.optional()
|
|
430
|
-
.describe(
|
|
633
|
+
.describe(
|
|
634
|
+
'Custom username to display in conversations instead of system username'
|
|
635
|
+
),
|
|
431
636
|
mode: z
|
|
432
637
|
.object({
|
|
433
638
|
build: Agent.optional(),
|
|
@@ -435,7 +640,7 @@ export namespace Config {
|
|
|
435
640
|
})
|
|
436
641
|
.catchall(Agent)
|
|
437
642
|
.optional()
|
|
438
|
-
.describe(
|
|
643
|
+
.describe('@deprecated Use `agent` field instead.'),
|
|
439
644
|
agent: z
|
|
440
645
|
.object({
|
|
441
646
|
plan: Agent.optional(),
|
|
@@ -444,18 +649,25 @@ export namespace Config {
|
|
|
444
649
|
})
|
|
445
650
|
.catchall(Agent)
|
|
446
651
|
.optional()
|
|
447
|
-
.describe(
|
|
652
|
+
.describe('Agent configuration, see https://opencode.ai/docs/agent'),
|
|
448
653
|
provider: z
|
|
449
654
|
.record(
|
|
450
655
|
z.string(),
|
|
451
656
|
ModelsDev.Provider.partial()
|
|
452
657
|
.extend({
|
|
453
|
-
models: z
|
|
658
|
+
models: z
|
|
659
|
+
.record(z.string(), ModelsDev.Model.partial())
|
|
660
|
+
.optional(),
|
|
454
661
|
options: z
|
|
455
662
|
.object({
|
|
456
663
|
apiKey: z.string().optional(),
|
|
457
664
|
baseURL: z.string().optional(),
|
|
458
|
-
enterpriseUrl: z
|
|
665
|
+
enterpriseUrl: z
|
|
666
|
+
.string()
|
|
667
|
+
.optional()
|
|
668
|
+
.describe(
|
|
669
|
+
'GitHub Enterprise URL for copilot authentication'
|
|
670
|
+
),
|
|
459
671
|
timeout: z
|
|
460
672
|
.union([
|
|
461
673
|
z
|
|
@@ -463,23 +675,30 @@ export namespace Config {
|
|
|
463
675
|
.int()
|
|
464
676
|
.positive()
|
|
465
677
|
.describe(
|
|
466
|
-
|
|
678
|
+
'Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.'
|
|
679
|
+
),
|
|
680
|
+
z
|
|
681
|
+
.literal(false)
|
|
682
|
+
.describe(
|
|
683
|
+
'Disable timeout for this provider entirely.'
|
|
467
684
|
),
|
|
468
|
-
z.literal(false).describe("Disable timeout for this provider entirely."),
|
|
469
685
|
])
|
|
470
686
|
.optional()
|
|
471
687
|
.describe(
|
|
472
|
-
|
|
688
|
+
'Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.'
|
|
473
689
|
),
|
|
474
690
|
})
|
|
475
691
|
.catchall(z.any())
|
|
476
692
|
.optional(),
|
|
477
693
|
})
|
|
478
|
-
.strict()
|
|
694
|
+
.strict()
|
|
479
695
|
)
|
|
480
696
|
.optional()
|
|
481
|
-
.describe(
|
|
482
|
-
mcp: z
|
|
697
|
+
.describe('Custom provider configurations and model overrides'),
|
|
698
|
+
mcp: z
|
|
699
|
+
.record(z.string(), Mcp)
|
|
700
|
+
.optional()
|
|
701
|
+
.describe('MCP (Model Context Protocol) server configurations'),
|
|
483
702
|
formatter: z
|
|
484
703
|
.union([
|
|
485
704
|
z.literal(false),
|
|
@@ -490,16 +709,23 @@ export namespace Config {
|
|
|
490
709
|
command: z.array(z.string()).optional(),
|
|
491
710
|
environment: z.record(z.string(), z.string()).optional(),
|
|
492
711
|
extensions: z.array(z.string()).optional(),
|
|
493
|
-
})
|
|
712
|
+
})
|
|
494
713
|
),
|
|
495
714
|
])
|
|
496
715
|
.optional(),
|
|
497
|
-
instructions: z
|
|
498
|
-
|
|
716
|
+
instructions: z
|
|
717
|
+
.array(z.string())
|
|
718
|
+
.optional()
|
|
719
|
+
.describe('Additional instruction files or patterns to include'),
|
|
720
|
+
layout: Layout.optional().describe(
|
|
721
|
+
'@deprecated Always uses stretch layout.'
|
|
722
|
+
),
|
|
499
723
|
permission: z
|
|
500
724
|
.object({
|
|
501
725
|
edit: Permission.optional(),
|
|
502
|
-
bash: z
|
|
726
|
+
bash: z
|
|
727
|
+
.union([Permission, z.record(z.string(), Permission)])
|
|
728
|
+
.optional(),
|
|
503
729
|
webfetch: Permission.optional(),
|
|
504
730
|
doom_loop: Permission.optional(),
|
|
505
731
|
external_directory: Permission.optional(),
|
|
@@ -518,7 +744,7 @@ export namespace Config {
|
|
|
518
744
|
command: z.string().array(),
|
|
519
745
|
environment: z.record(z.string(), z.string()).optional(),
|
|
520
746
|
})
|
|
521
|
-
.array()
|
|
747
|
+
.array()
|
|
522
748
|
)
|
|
523
749
|
.optional(),
|
|
524
750
|
session_completed: z
|
|
@@ -530,176 +756,190 @@ export namespace Config {
|
|
|
530
756
|
.optional(),
|
|
531
757
|
})
|
|
532
758
|
.optional(),
|
|
533
|
-
chatMaxRetries: z
|
|
759
|
+
chatMaxRetries: z
|
|
760
|
+
.number()
|
|
761
|
+
.optional()
|
|
762
|
+
.describe('Number of retries for chat completions on failure'),
|
|
534
763
|
disable_paste_summary: z.boolean().optional(),
|
|
535
|
-
batch_tool: z.boolean().optional().describe(
|
|
764
|
+
batch_tool: z.boolean().optional().describe('Enable the batch tool'),
|
|
536
765
|
})
|
|
537
766
|
.optional(),
|
|
538
767
|
})
|
|
539
768
|
.strict()
|
|
540
769
|
.meta({
|
|
541
|
-
ref:
|
|
542
|
-
})
|
|
770
|
+
ref: 'Config',
|
|
771
|
+
});
|
|
543
772
|
|
|
544
|
-
export type Info = z.output<typeof Info
|
|
773
|
+
export type Info = z.output<typeof Info>;
|
|
545
774
|
|
|
546
775
|
export const global = lazy(async () => {
|
|
547
776
|
let result: Info = pipe(
|
|
548
777
|
{},
|
|
549
|
-
mergeDeep(await loadFile(path.join(Global.Path.config,
|
|
550
|
-
mergeDeep(await loadFile(path.join(Global.Path.config,
|
|
551
|
-
mergeDeep(await loadFile(path.join(Global.Path.config,
|
|
552
|
-
)
|
|
778
|
+
mergeDeep(await loadFile(path.join(Global.Path.config, 'config.json'))),
|
|
779
|
+
mergeDeep(await loadFile(path.join(Global.Path.config, 'opencode.json'))),
|
|
780
|
+
mergeDeep(await loadFile(path.join(Global.Path.config, 'opencode.jsonc')))
|
|
781
|
+
);
|
|
553
782
|
|
|
554
|
-
await import(path.join(Global.Path.config,
|
|
783
|
+
await import(path.join(Global.Path.config, 'config'), {
|
|
555
784
|
with: {
|
|
556
|
-
type:
|
|
785
|
+
type: 'toml',
|
|
557
786
|
},
|
|
558
787
|
})
|
|
559
788
|
.then(async (mod) => {
|
|
560
|
-
const { provider, model, ...rest } = mod.default
|
|
561
|
-
if (provider && model) result.model = `${provider}/${model}
|
|
562
|
-
result[
|
|
563
|
-
result = mergeDeep(result, rest)
|
|
564
|
-
await Bun.write(
|
|
565
|
-
|
|
789
|
+
const { provider, model, ...rest } = mod.default;
|
|
790
|
+
if (provider && model) result.model = `${provider}/${model}`;
|
|
791
|
+
result['$schema'] = 'https://opencode.ai/config.json';
|
|
792
|
+
result = mergeDeep(result, rest);
|
|
793
|
+
await Bun.write(
|
|
794
|
+
path.join(Global.Path.config, 'config.json'),
|
|
795
|
+
JSON.stringify(result, null, 2)
|
|
796
|
+
);
|
|
797
|
+
await fs.unlink(path.join(Global.Path.config, 'config'));
|
|
566
798
|
})
|
|
567
|
-
.catch(() => {})
|
|
799
|
+
.catch(() => {});
|
|
568
800
|
|
|
569
|
-
return result
|
|
570
|
-
})
|
|
801
|
+
return result;
|
|
802
|
+
});
|
|
571
803
|
|
|
572
804
|
async function loadFile(filepath: string): Promise<Info> {
|
|
573
|
-
log.info(
|
|
805
|
+
log.info('loading', { path: filepath });
|
|
574
806
|
let text = await Bun.file(filepath)
|
|
575
807
|
.text()
|
|
576
808
|
.catch((err) => {
|
|
577
|
-
if (err.code ===
|
|
578
|
-
throw new JsonError({ path: filepath }, { cause: err })
|
|
579
|
-
})
|
|
580
|
-
if (!text) return {}
|
|
581
|
-
return load(text, filepath)
|
|
809
|
+
if (err.code === 'ENOENT') return;
|
|
810
|
+
throw new JsonError({ path: filepath }, { cause: err });
|
|
811
|
+
});
|
|
812
|
+
if (!text) return {};
|
|
813
|
+
return load(text, filepath);
|
|
582
814
|
}
|
|
583
815
|
|
|
584
816
|
async function load(text: string, configFilepath: string) {
|
|
585
817
|
text = text.replace(/\{env:([^}]+)\}/g, (_, varName) => {
|
|
586
|
-
return process.env[varName] ||
|
|
587
|
-
})
|
|
818
|
+
return process.env[varName] || '';
|
|
819
|
+
});
|
|
588
820
|
|
|
589
|
-
const fileMatches = text.match(/\{file:[^}]+\}/g)
|
|
821
|
+
const fileMatches = text.match(/\{file:[^}]+\}/g);
|
|
590
822
|
if (fileMatches) {
|
|
591
|
-
const configDir = path.dirname(configFilepath)
|
|
592
|
-
const lines = text.split(
|
|
823
|
+
const configDir = path.dirname(configFilepath);
|
|
824
|
+
const lines = text.split('\n');
|
|
593
825
|
|
|
594
826
|
for (const match of fileMatches) {
|
|
595
|
-
const lineIndex = lines.findIndex((line) => line.includes(match))
|
|
596
|
-
if (lineIndex !== -1 && lines[lineIndex].trim().startsWith(
|
|
597
|
-
continue // Skip if line is commented
|
|
827
|
+
const lineIndex = lines.findIndex((line) => line.includes(match));
|
|
828
|
+
if (lineIndex !== -1 && lines[lineIndex].trim().startsWith('//')) {
|
|
829
|
+
continue; // Skip if line is commented
|
|
598
830
|
}
|
|
599
|
-
let filePath = match.replace(/^\{file:/,
|
|
600
|
-
if (filePath.startsWith(
|
|
601
|
-
filePath = path.join(os.homedir(), filePath.slice(2))
|
|
831
|
+
let filePath = match.replace(/^\{file:/, '').replace(/\}$/, '');
|
|
832
|
+
if (filePath.startsWith('~/')) {
|
|
833
|
+
filePath = path.join(os.homedir(), filePath.slice(2));
|
|
602
834
|
}
|
|
603
|
-
const resolvedPath = path.isAbsolute(filePath)
|
|
835
|
+
const resolvedPath = path.isAbsolute(filePath)
|
|
836
|
+
? filePath
|
|
837
|
+
: path.resolve(configDir, filePath);
|
|
604
838
|
const fileContent = (
|
|
605
839
|
await Bun.file(resolvedPath)
|
|
606
840
|
.text()
|
|
607
841
|
.catch((error) => {
|
|
608
|
-
const errMsg = `bad file reference: "${match}"
|
|
609
|
-
if (error.code ===
|
|
842
|
+
const errMsg = `bad file reference: "${match}"`;
|
|
843
|
+
if (error.code === 'ENOENT') {
|
|
610
844
|
throw new InvalidError(
|
|
611
845
|
{
|
|
612
846
|
path: configFilepath,
|
|
613
847
|
message: errMsg + ` ${resolvedPath} does not exist`,
|
|
614
848
|
},
|
|
615
|
-
{ cause: error }
|
|
616
|
-
)
|
|
849
|
+
{ cause: error }
|
|
850
|
+
);
|
|
617
851
|
}
|
|
618
|
-
throw new InvalidError(
|
|
852
|
+
throw new InvalidError(
|
|
853
|
+
{ path: configFilepath, message: errMsg },
|
|
854
|
+
{ cause: error }
|
|
855
|
+
);
|
|
619
856
|
})
|
|
620
|
-
).trim()
|
|
857
|
+
).trim();
|
|
621
858
|
// escape newlines/quotes, strip outer quotes
|
|
622
|
-
text = text.replace(match, JSON.stringify(fileContent).slice(1, -1))
|
|
859
|
+
text = text.replace(match, JSON.stringify(fileContent).slice(1, -1));
|
|
623
860
|
}
|
|
624
861
|
}
|
|
625
862
|
|
|
626
|
-
const errors: JsoncParseError[] = []
|
|
627
|
-
const data = parseJsonc(text, errors, { allowTrailingComma: true })
|
|
863
|
+
const errors: JsoncParseError[] = [];
|
|
864
|
+
const data = parseJsonc(text, errors, { allowTrailingComma: true });
|
|
628
865
|
if (errors.length) {
|
|
629
|
-
const lines = text.split(
|
|
866
|
+
const lines = text.split('\n');
|
|
630
867
|
const errorDetails = errors
|
|
631
868
|
.map((e) => {
|
|
632
|
-
const beforeOffset = text.substring(0, e.offset).split(
|
|
633
|
-
const line = beforeOffset.length
|
|
634
|
-
const column = beforeOffset[beforeOffset.length - 1].length + 1
|
|
635
|
-
const problemLine = lines[line - 1]
|
|
869
|
+
const beforeOffset = text.substring(0, e.offset).split('\n');
|
|
870
|
+
const line = beforeOffset.length;
|
|
871
|
+
const column = beforeOffset[beforeOffset.length - 1].length + 1;
|
|
872
|
+
const problemLine = lines[line - 1];
|
|
636
873
|
|
|
637
|
-
const error = `${printParseErrorCode(e.error)} at line ${line}, column ${column}
|
|
638
|
-
if (!problemLine) return error
|
|
874
|
+
const error = `${printParseErrorCode(e.error)} at line ${line}, column ${column}`;
|
|
875
|
+
if (!problemLine) return error;
|
|
639
876
|
|
|
640
|
-
return `${error}\n Line ${line}: ${problemLine}\n${
|
|
877
|
+
return `${error}\n Line ${line}: ${problemLine}\n${''.padStart(column + 9)}^`;
|
|
641
878
|
})
|
|
642
|
-
.join(
|
|
879
|
+
.join('\n');
|
|
643
880
|
|
|
644
881
|
throw new JsonError({
|
|
645
882
|
path: configFilepath,
|
|
646
883
|
message: `\n--- JSONC Input ---\n${text}\n--- Errors ---\n${errorDetails}\n--- End ---`,
|
|
647
|
-
})
|
|
884
|
+
});
|
|
648
885
|
}
|
|
649
886
|
|
|
650
|
-
const parsed = Info.safeParse(data)
|
|
887
|
+
const parsed = Info.safeParse(data);
|
|
651
888
|
if (parsed.success) {
|
|
652
889
|
if (!parsed.data.$schema) {
|
|
653
|
-
parsed.data.$schema =
|
|
654
|
-
await Bun.write(configFilepath, JSON.stringify(parsed.data, null, 2))
|
|
890
|
+
parsed.data.$schema = 'https://opencode.ai/config.json';
|
|
891
|
+
await Bun.write(configFilepath, JSON.stringify(parsed.data, null, 2));
|
|
655
892
|
}
|
|
656
|
-
const data = parsed.data
|
|
657
|
-
return data
|
|
893
|
+
const data = parsed.data;
|
|
894
|
+
return data;
|
|
658
895
|
}
|
|
659
896
|
|
|
660
897
|
throw new InvalidError({
|
|
661
898
|
path: configFilepath,
|
|
662
899
|
issues: parsed.error.issues,
|
|
663
|
-
})
|
|
900
|
+
});
|
|
664
901
|
}
|
|
665
902
|
export const JsonError = NamedError.create(
|
|
666
|
-
|
|
903
|
+
'ConfigJsonError',
|
|
667
904
|
z.object({
|
|
668
905
|
path: z.string(),
|
|
669
906
|
message: z.string().optional(),
|
|
670
|
-
})
|
|
671
|
-
)
|
|
907
|
+
})
|
|
908
|
+
);
|
|
672
909
|
|
|
673
910
|
export const ConfigDirectoryTypoError = NamedError.create(
|
|
674
|
-
|
|
911
|
+
'ConfigDirectoryTypoError',
|
|
675
912
|
z.object({
|
|
676
913
|
path: z.string(),
|
|
677
914
|
dir: z.string(),
|
|
678
915
|
suggestion: z.string(),
|
|
679
|
-
})
|
|
680
|
-
)
|
|
916
|
+
})
|
|
917
|
+
);
|
|
681
918
|
|
|
682
919
|
export const InvalidError = NamedError.create(
|
|
683
|
-
|
|
920
|
+
'ConfigInvalidError',
|
|
684
921
|
z.object({
|
|
685
922
|
path: z.string(),
|
|
686
923
|
issues: z.custom<z.core.$ZodIssue[]>().optional(),
|
|
687
924
|
message: z.string().optional(),
|
|
688
|
-
})
|
|
689
|
-
)
|
|
925
|
+
})
|
|
926
|
+
);
|
|
690
927
|
|
|
691
928
|
export async function get() {
|
|
692
|
-
return state().then((x) => x.config)
|
|
929
|
+
return state().then((x) => x.config);
|
|
693
930
|
}
|
|
694
931
|
|
|
695
932
|
export async function update(config: Info) {
|
|
696
|
-
const filepath = path.join(Instance.directory,
|
|
697
|
-
const existing = await loadFile(filepath)
|
|
698
|
-
await Bun.write(
|
|
699
|
-
|
|
933
|
+
const filepath = path.join(Instance.directory, 'config.json');
|
|
934
|
+
const existing = await loadFile(filepath);
|
|
935
|
+
await Bun.write(
|
|
936
|
+
filepath,
|
|
937
|
+
JSON.stringify(mergeDeep(existing, config), null, 2)
|
|
938
|
+
);
|
|
939
|
+
await Instance.dispose();
|
|
700
940
|
}
|
|
701
941
|
|
|
702
942
|
export async function directories() {
|
|
703
|
-
return state().then((x) => x.directories)
|
|
943
|
+
return state().then((x) => x.directories);
|
|
704
944
|
}
|
|
705
945
|
}
|