Skip to content

Commit d4ae2d8

Browse files
authored
Merge pull request #71 from link-assistant/issue-70-1e5f73ed6b3c
[WIP] Make sure it is possible to override default model using the config
2 parents 23ad400 + c3cb3a8 commit d4ae2d8

8 files changed

Lines changed: 254 additions & 47 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@link-assistant/agent': minor
3+
---
4+
5+
Add support for link-assistant-agent branding and improve default model selection
6+
7+
**New Features:**
8+
9+
- Default model is now `opencode/grok-code` (Open Code Zen / Grok Code Fast 1)
10+
- Support for `LINK_ASSISTANT_AGENT_*` environment variable prefix (backwards compatible with `OPENCODE_*`)
11+
- Support for `.link-assistant-agent` config directory (backwards compatible with `.opencode`)
12+
- Global config paths now use `link-assistant-agent` name
13+
14+
**Breaking Changes:**
15+
16+
- Global config directory changed from `~/.config/opencode/` to `~/.config/link-assistant-agent/`
17+
- Users should manually migrate global configs if needed
18+
19+
**Migration:**
20+
21+
- Environment variables: New `LINK_ASSISTANT_AGENT_*` prefix available; old `OPENCODE_*` still works
22+
- Project config: Both `.opencode/` and `.link-assistant-agent/` directories supported
23+
- Model override: Use `"model": "provider/model-id"` in config to override default

EXAMPLES.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ echo '{"message":"search web","tools":[{"name":"websearch","params":{"query":"Re
211211
**opencode (requires OPENCODE_EXPERIMENTAL_EXA=true):**
212212

213213
```bash
214-
echo '{"message":"search web","tools":[{"name":"websearch","params":{"query":"TypeScript latest features"}}]}' | OPENCODE_EXPERIMENTAL_EXA=true opencode run --format json --model opencode/grok-code
214+
echo '{"message":"search web","tools":[{"name":"websearch","params":{"query":"TypeScript latest features"}}]}' | opencode run --format json --model opencode/grok-code
215215
```
216216

217217
### codesearch Tool
@@ -229,7 +229,7 @@ echo '{"message":"search code","tools":[{"name":"codesearch","params":{"query":"
229229
**opencode (requires OPENCODE_EXPERIMENTAL_EXA=true):**
230230

231231
```bash
232-
echo '{"message":"search code","tools":[{"name":"codesearch","params":{"query":"React hooks implementation"}}]}' | OPENCODE_EXPERIMENTAL_EXA=true opencode run --format json --model opencode/grok-code
232+
echo '{"message":"search code","tools":[{"name":"codesearch","params":{"query":"React hooks implementation"}}]}' | opencode run --format json --model opencode/grok-code
233233
```
234234

235235
## Execution Tools
@@ -248,8 +248,8 @@ echo '{"message":"run batch","tools":[{"name":"batch","params":{"tool_calls":[{"
248248

249249
```bash
250250
# Create config file first
251-
mkdir -p .opencode
252-
echo '{"experimental":{"batch_tool":true}}' > .opencode/config.json
251+
mkdir -p .link-assistant-agent
252+
echo '{"experimental":{"batch_tool":true}}' > .link-assistant-agent/opencode.json
253253

254254
# Then run
255255
echo '{"message":"run batch","tools":[{"name":"batch","params":{"tool_calls":[{"tool":"bash","parameters":{"command":"echo hello"}},{"tool":"bash","parameters":{"command":"echo world"}}]}}]}' | opencode run --format json --model opencode/grok-code

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ agent mcp add playwright npx @playwright/mcp@latest
314314
agent mcp list
315315
```
316316

317-
This will create a configuration file at `~/.config/opencode/opencode.json` (or your system's config directory) with:
317+
This will create a configuration file at `~/.config/link-assistant-agent/opencode.json` (or your system's config directory) with:
318318

319319
```json
320320
{
@@ -477,7 +477,7 @@ The package publishes source files directly (no build step required). Bun handle
477477

478478
### No Configuration Required
479479

480-
- **WebSearch/CodeSearch**: Work without `OPENCODE_EXPERIMENTAL_EXA` environment variable
480+
- **WebSearch/CodeSearch**: Work without `LINK_ASSISTANT_AGENT_EXPERIMENTAL_EXA` environment variable (legacy `OPENCODE_EXPERIMENTAL_EXA` still supported)
481481
- **Batch Tool**: Always enabled, no experimental flag needed
482482
- **All Tools**: No config files, API keys handled automatically
483483

src/config/config.ts

Lines changed: 148 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,103 @@ import { ConfigMarkdown } from './markdown';
2222
export namespace Config {
2323
const log = Log.create({ service: 'config' });
2424

25+
/**
26+
* Automatically migrate .opencode directories to .link-assistant-agent
27+
* This ensures a smooth transition for both file system configs and environment variables.
28+
* Once .link-assistant-agent exists, we stop reading from .opencode.
29+
*/
30+
async function migrateConfigDirectories() {
31+
// Find all .opencode and .link-assistant-agent directories in the project hierarchy
32+
const allDirs = await Array.fromAsync(
33+
Filesystem.up({
34+
targets: ['.link-assistant-agent', '.opencode'],
35+
start: Instance.directory,
36+
stop: Instance.worktree,
37+
})
38+
);
39+
40+
const newConfigDirs = allDirs.filter((d) =>
41+
d.endsWith('.link-assistant-agent')
42+
);
43+
const oldConfigDirs = allDirs.filter((d) => d.endsWith('.opencode'));
44+
45+
// For each old config directory, check if there's a corresponding new one
46+
for (const oldDir of oldConfigDirs) {
47+
const parentDir = path.dirname(oldDir);
48+
const newDir = path.join(parentDir, '.link-assistant-agent');
49+
50+
// Check if the new directory already exists at the same level
51+
const newDirExists = newConfigDirs.includes(newDir);
52+
53+
if (!newDirExists) {
54+
try {
55+
// Perform migration by copying the entire directory
56+
log.info(
57+
`Migrating config from ${oldDir} to ${newDir} for smooth transition`
58+
);
59+
60+
// Use fs-extra style recursive copy
61+
await copyDirectory(oldDir, newDir);
62+
63+
log.info(`Successfully migrated config to ${newDir}`);
64+
} catch (error) {
65+
log.error(`Failed to migrate config from ${oldDir}:`, error);
66+
// Don't throw - allow the app to continue with the old config
67+
}
68+
}
69+
}
70+
71+
// Also migrate global config if needed
72+
const oldGlobalPath = path.join(os.homedir(), '.config', 'opencode');
73+
const newGlobalPath = Global.Path.config;
74+
75+
try {
76+
const oldGlobalExists = await fs
77+
.stat(oldGlobalPath)
78+
.then(() => true)
79+
.catch(() => false);
80+
const newGlobalExists = await fs
81+
.stat(newGlobalPath)
82+
.then(() => true)
83+
.catch(() => false);
84+
85+
if (oldGlobalExists && !newGlobalExists) {
86+
log.info(
87+
`Migrating global config from ${oldGlobalPath} to ${newGlobalPath}`
88+
);
89+
await copyDirectory(oldGlobalPath, newGlobalPath);
90+
log.info(`Successfully migrated global config to ${newGlobalPath}`);
91+
}
92+
} catch (error) {
93+
log.error('Failed to migrate global config:', error);
94+
// Don't throw - allow the app to continue
95+
}
96+
}
97+
98+
/**
99+
* Recursively copy a directory and all its contents
100+
*/
101+
async function copyDirectory(src: string, dest: string) {
102+
// Create destination directory
103+
await fs.mkdir(dest, { recursive: true });
104+
105+
// Read all entries in source directory
106+
const entries = await fs.readdir(src, { withFileTypes: true });
107+
108+
for (const entry of entries) {
109+
const srcPath = path.join(src, entry.name);
110+
const destPath = path.join(dest, entry.name);
111+
112+
if (entry.isDirectory()) {
113+
// Recursively copy subdirectories
114+
await copyDirectory(srcPath, destPath);
115+
} else if (entry.isFile() || entry.isSymbolicLink()) {
116+
// Copy files
117+
await fs.copyFile(srcPath, destPath);
118+
}
119+
}
120+
}
121+
25122
export const state = Instance.state(async () => {
26123
const auth = await Auth.all();
27124
let result = await global();
@@ -64,20 +161,43 @@ export namespace Config {
64161
result.agent = result.agent || {};
65162
result.mode = result.mode || {};
66163

67-
const directories = [
68-
Global.Path.config,
69-
...(await Array.fromAsync(
70-
Filesystem.up({
71-
targets: ['.opencode'],
72-
start: Instance.directory,
73-
stop: Instance.worktree,
74-
})
75-
)),
76-
];
164+
// Perform automatic migration from .opencode to .link-assistant-agent if needed
165+
await migrateConfigDirectories();
166+
167+
// Find all config directories
168+
const foundDirs = await Array.fromAsync(
169+
Filesystem.up({
170+
targets: ['.link-assistant-agent', '.opencode'],
171+
start: Instance.directory,
172+
stop: Instance.worktree,
173+
})
174+
);
175+
176+
// Check if any .link-assistant-agent directory exists
177+
const hasNewConfig = foundDirs.some((d) =>
178+
d.endsWith('.link-assistant-agent')
179+
);
180+
181+
// Filter out .opencode directories if .link-assistant-agent exists
182+
const filteredDirs = foundDirs.filter((dir) => {
183+
// If .link-assistant-agent exists, exclude .opencode directories
184+
if (hasNewConfig && dir.endsWith('.opencode')) {
185+
log.debug(
186+
'Skipping .opencode directory (using .link-assistant-agent):',
187+
{
188+
path: dir,
189+
}
190+
);
191+
return false;
192+
}
193+
return true;
194+
});
195+
196+
const directories = [Global.Path.config, ...filteredDirs];
77197

78198
if (Flag.OPENCODE_CONFIG_DIR) {
79199
directories.push(Flag.OPENCODE_CONFIG_DIR);
80-
log.debug('loading config from OPENCODE_CONFIG_DIR', {
200+
log.debug('loading config from LINK_ASSISTANT_AGENT_CONFIG_DIR', {
81201
path: Flag.OPENCODE_CONFIG_DIR,
82202
});
83203
}
@@ -86,7 +206,11 @@ export namespace Config {
86206
for (const dir of directories) {
87207
await assertValid(dir);
88208

89-
if (dir.endsWith('.opencode') || dir === Flag.OPENCODE_CONFIG_DIR) {
209+
if (
210+
dir.endsWith('.link-assistant-agent') ||
211+
dir.endsWith('.opencode') ||
212+
dir === Flag.OPENCODE_CONFIG_DIR
213+
) {
90214
for (const file of ['opencode.jsonc', 'opencode.json']) {
91215
log.debug(`loading config from ${path.join(dir, file)}`);
92216
result = mergeDeep(result, await loadFile(path.join(dir, file)));
@@ -162,7 +286,11 @@ export namespace Config {
162286
if (!md.data) continue;
163287

164288
const name = (() => {
165-
const patterns = ['/.opencode/command/', '/command/'];
289+
const patterns = [
290+
'/.link-assistant-agent/command/',
291+
'/.opencode/command/',
292+
'/command/',
293+
];
166294
const pattern = patterns.find((p) => item.includes(p));
167295

168296
if (pattern) {
@@ -202,11 +330,13 @@ export namespace Config {
202330

203331
// Extract relative path from agent folder for nested agents
204332
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';
333+
const agentFolderPath = item.includes('/.link-assistant-agent/agent/')
334+
? item.split('/.link-assistant-agent/agent/')[1]
335+
: item.includes('/.opencode/agent/')
336+
? item.split('/.opencode/agent/')[1]
337+
: item.includes('/agent/')
338+
? item.split('/agent/')[1]
339+
: agentName + '.md';
210340

211341
// If agent is in a subfolder, include folder path in name
212342
if (agentFolderPath.includes('/')) {

src/flag/flag.ts

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,67 @@
11
export namespace Flag {
2-
// OPENCODE_AUTO_SHARE removed - no sharing support
3-
export const OPENCODE_CONFIG = process.env['OPENCODE_CONFIG'];
4-
export const OPENCODE_CONFIG_DIR = process.env['OPENCODE_CONFIG_DIR'];
5-
export const OPENCODE_CONFIG_CONTENT = process.env['OPENCODE_CONFIG_CONTENT'];
6-
export const OPENCODE_DISABLE_AUTOUPDATE = truthy(
2+
// Helper to check env vars with new prefix first, then fall back to old prefix for backwards compatibility
3+
function getEnv(newKey: string, oldKey: string): string | undefined {
4+
return process.env[newKey] ?? process.env[oldKey];
5+
}
6+
7+
function truthyCompat(newKey: string, oldKey: string): boolean {
8+
const value = (getEnv(newKey, oldKey) ?? '').toLowerCase();
9+
return value === 'true' || value === '1';
10+
}
11+
12+
// LINK_ASSISTANT_AGENT_AUTO_SHARE removed - no sharing support
13+
export const OPENCODE_CONFIG = getEnv(
14+
'LINK_ASSISTANT_AGENT_CONFIG',
15+
'OPENCODE_CONFIG'
16+
);
17+
export const OPENCODE_CONFIG_DIR = getEnv(
18+
'LINK_ASSISTANT_AGENT_CONFIG_DIR',
19+
'OPENCODE_CONFIG_DIR'
20+
);
21+
export const OPENCODE_CONFIG_CONTENT = getEnv(
22+
'LINK_ASSISTANT_AGENT_CONFIG_CONTENT',
23+
'OPENCODE_CONFIG_CONTENT'
24+
);
25+
export const OPENCODE_DISABLE_AUTOUPDATE = truthyCompat(
26+
'LINK_ASSISTANT_AGENT_DISABLE_AUTOUPDATE',
727
'OPENCODE_DISABLE_AUTOUPDATE'
828
);
9-
export const OPENCODE_DISABLE_PRUNE = truthy('OPENCODE_DISABLE_PRUNE');
10-
export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthy(
29+
export const OPENCODE_DISABLE_PRUNE = truthyCompat(
30+
'LINK_ASSISTANT_AGENT_DISABLE_PRUNE',
31+
'OPENCODE_DISABLE_PRUNE'
32+
);
33+
export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthyCompat(
34+
'LINK_ASSISTANT_AGENT_ENABLE_EXPERIMENTAL_MODELS',
1135
'OPENCODE_ENABLE_EXPERIMENTAL_MODELS'
1236
);
13-
export const OPENCODE_DISABLE_AUTOCOMPACT = truthy(
37+
export const OPENCODE_DISABLE_AUTOCOMPACT = truthyCompat(
38+
'LINK_ASSISTANT_AGENT_DISABLE_AUTOCOMPACT',
1439
'OPENCODE_DISABLE_AUTOCOMPACT'
1540
);
1641

1742
// Experimental
18-
export const OPENCODE_EXPERIMENTAL = truthy('OPENCODE_EXPERIMENTAL');
43+
export const OPENCODE_EXPERIMENTAL = truthyCompat(
44+
'LINK_ASSISTANT_AGENT_EXPERIMENTAL',
45+
'OPENCODE_EXPERIMENTAL'
46+
);
1947
export const OPENCODE_EXPERIMENTAL_WATCHER =
20-
OPENCODE_EXPERIMENTAL || truthy('OPENCODE_EXPERIMENTAL_WATCHER');
48+
OPENCODE_EXPERIMENTAL ||
49+
truthyCompat(
50+
'LINK_ASSISTANT_AGENT_EXPERIMENTAL_WATCHER',
51+
'OPENCODE_EXPERIMENTAL_WATCHER'
52+
);
2153

2254
// Verbose mode - enables detailed logging of API requests
23-
export let OPENCODE_VERBOSE = truthy('OPENCODE_VERBOSE');
55+
export let OPENCODE_VERBOSE = truthyCompat(
56+
'LINK_ASSISTANT_AGENT_VERBOSE',
57+
'OPENCODE_VERBOSE'
58+
);
2459

2560
// Dry run mode - simulate operations without making actual API calls or changes
26-
export let OPENCODE_DRY_RUN = truthy('OPENCODE_DRY_RUN');
61+
export let OPENCODE_DRY_RUN = truthyCompat(
62+
'LINK_ASSISTANT_AGENT_DRY_RUN',
63+
'OPENCODE_DRY_RUN'
64+
);
2765

2866
// Allow setting verbose mode programmatically (e.g., from CLI --verbose flag)
2967
export function setVerbose(value: boolean) {

src/global/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { xdgData, xdgCache, xdgConfig, xdgState } from 'xdg-basedir';
33
import path from 'path';
44
import os from 'os';
55

6-
const app = 'opencode';
6+
const app = 'link-assistant-agent';
77

88
const data = path.join(xdgData!, app);
99
const cache = path.join(xdgCache!, app);

0 commit comments

Comments
 (0)