@link-assistant/agent 0.5.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -3
- package/src/auth/claude-oauth.ts +50 -24
- package/src/auth/plugins.ts +28 -16
- package/src/bun/index.ts +33 -27
- package/src/bus/index.ts +3 -5
- package/src/config/config.ts +39 -22
- package/src/file/ripgrep.ts +1 -1
- package/src/file/time.ts +1 -1
- package/src/file/watcher.ts +10 -5
- package/src/format/index.ts +12 -10
- package/src/index.js +30 -35
- package/src/mcp/index.ts +32 -15
- package/src/patch/index.ts +8 -4
- package/src/project/project.ts +1 -1
- package/src/project/state.ts +15 -7
- package/src/provider/models.ts +4 -5
- package/src/provider/provider.ts +29 -21
- package/src/server/server.ts +4 -5
- package/src/session/agent.js +16 -2
- package/src/session/compaction.ts +4 -6
- package/src/session/index.ts +2 -2
- package/src/session/processor.ts +3 -7
- package/src/session/prompt.ts +78 -49
- package/src/session/revert.ts +1 -1
- package/src/session/summary.ts +2 -2
- package/src/snapshot/index.ts +27 -12
- package/src/storage/storage.ts +18 -18
- package/src/util/log-lazy.ts +291 -0
- package/src/util/log.ts +205 -28
package/src/file/watcher.ts
CHANGED
|
@@ -36,7 +36,7 @@ export namespace FileWatcher {
|
|
|
36
36
|
const state = Instance.state(
|
|
37
37
|
async () => {
|
|
38
38
|
if (Instance.project.vcs !== 'git') return {};
|
|
39
|
-
log.info('init');
|
|
39
|
+
log.info(() => ({ message: 'init' }));
|
|
40
40
|
const cfg = await Config.get();
|
|
41
41
|
const backend = (() => {
|
|
42
42
|
if (process.platform === 'win32') return 'windows';
|
|
@@ -44,18 +44,23 @@ export namespace FileWatcher {
|
|
|
44
44
|
if (process.platform === 'linux') return 'inotify';
|
|
45
45
|
})();
|
|
46
46
|
if (!backend) {
|
|
47
|
-
log.error(
|
|
47
|
+
log.error(() => ({
|
|
48
|
+
message: 'watcher backend not supported',
|
|
48
49
|
platform: process.platform,
|
|
49
|
-
});
|
|
50
|
+
}));
|
|
50
51
|
return {};
|
|
51
52
|
}
|
|
52
|
-
log.info(
|
|
53
|
+
log.info(() => ({
|
|
54
|
+
message: 'watcher backend',
|
|
55
|
+
platform: process.platform,
|
|
56
|
+
backend,
|
|
57
|
+
}));
|
|
53
58
|
const sub = await watcher().subscribe(
|
|
54
59
|
Instance.directory,
|
|
55
60
|
(err, evts) => {
|
|
56
61
|
if (err) return;
|
|
57
62
|
for (const evt of evts) {
|
|
58
|
-
log.info('event', evt);
|
|
63
|
+
log.info(() => ({ message: 'event', ...evt }));
|
|
59
64
|
if (evt.type === 'create')
|
|
60
65
|
Bus.publish(Event.Updated, { file: evt.path, event: 'add' });
|
|
61
66
|
if (evt.type === 'update')
|
package/src/format/index.ts
CHANGED
|
@@ -29,7 +29,7 @@ export namespace Format {
|
|
|
29
29
|
|
|
30
30
|
const formatters: Record<string, Formatter.Info> = {};
|
|
31
31
|
if (cfg.formatter === false) {
|
|
32
|
-
log.info('all formatters are disabled');
|
|
32
|
+
log.info(() => ({ message: 'all formatters are disabled' }));
|
|
33
33
|
return {
|
|
34
34
|
enabled,
|
|
35
35
|
formatters,
|
|
@@ -77,10 +77,10 @@ export namespace Format {
|
|
|
77
77
|
const formatters = await state().then((x) => x.formatters);
|
|
78
78
|
const result = [];
|
|
79
79
|
for (const item of Object.values(formatters)) {
|
|
80
|
-
log.info('checking',
|
|
80
|
+
log.info(() => ({ message: 'checking', name: item.name, ext }));
|
|
81
81
|
if (!item.extensions.includes(ext)) continue;
|
|
82
82
|
if (!(await isEnabled(item))) continue;
|
|
83
|
-
log.info('enabled',
|
|
83
|
+
log.info(() => ({ message: 'enabled', name: item.name, ext }));
|
|
84
84
|
result.push(item);
|
|
85
85
|
}
|
|
86
86
|
return result;
|
|
@@ -101,14 +101,14 @@ export namespace Format {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
export function init() {
|
|
104
|
-
log.info('init');
|
|
104
|
+
log.info(() => ({ message: 'init' }));
|
|
105
105
|
Bus.subscribe(File.Event.Edited, async (payload) => {
|
|
106
106
|
const file = payload.properties.file;
|
|
107
|
-
log.info('formatting',
|
|
107
|
+
log.info(() => ({ message: 'formatting', file }));
|
|
108
108
|
const ext = path.extname(file);
|
|
109
109
|
|
|
110
110
|
for (const item of await getFormatter(ext)) {
|
|
111
|
-
log.info('running',
|
|
111
|
+
log.info(() => ({ message: 'running', command: item.command }));
|
|
112
112
|
try {
|
|
113
113
|
const proc = Bun.spawn({
|
|
114
114
|
cmd: item.command.map((x) => x.replace('$FILE', file)),
|
|
@@ -119,17 +119,19 @@ export namespace Format {
|
|
|
119
119
|
});
|
|
120
120
|
const exit = await proc.exited;
|
|
121
121
|
if (exit !== 0)
|
|
122
|
-
log.error(
|
|
122
|
+
log.error(() => ({
|
|
123
|
+
message: 'failed',
|
|
123
124
|
command: item.command,
|
|
124
125
|
...item.environment,
|
|
125
|
-
});
|
|
126
|
+
}));
|
|
126
127
|
} catch (error) {
|
|
127
|
-
log.error(
|
|
128
|
+
log.error(() => ({
|
|
129
|
+
message: 'failed to format file',
|
|
128
130
|
error,
|
|
129
131
|
command: item.command,
|
|
130
132
|
...item.environment,
|
|
131
133
|
file,
|
|
132
|
-
});
|
|
134
|
+
}));
|
|
133
135
|
}
|
|
134
136
|
}
|
|
135
137
|
});
|
package/src/index.js
CHANGED
|
@@ -246,22 +246,19 @@ async function readSystemMessages(argv) {
|
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
async function runAgentMode(argv, request) {
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
console.error(`Script path: ${import.meta.path}`);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Log dry-run mode if enabled
|
|
249
|
+
// Log version and command info in verbose mode using lazy logging
|
|
250
|
+
Log.Default.lazy.info(() => ({
|
|
251
|
+
message: 'Agent started',
|
|
252
|
+
version: pkg.version,
|
|
253
|
+
command: process.argv.join(' '),
|
|
254
|
+
workingDirectory: process.cwd(),
|
|
255
|
+
scriptPath: import.meta.path,
|
|
256
|
+
}));
|
|
261
257
|
if (Flag.OPENCODE_DRY_RUN) {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
258
|
+
Log.Default.lazy.info(() => ({
|
|
259
|
+
message: 'Dry run mode enabled',
|
|
260
|
+
mode: 'dry-run',
|
|
261
|
+
}));
|
|
265
262
|
}
|
|
266
263
|
|
|
267
264
|
const { providerID, modelID } = await parseModelConfig(argv);
|
|
@@ -269,9 +266,11 @@ async function runAgentMode(argv, request) {
|
|
|
269
266
|
// Validate and get JSON standard
|
|
270
267
|
const jsonStandard = argv['json-standard'];
|
|
271
268
|
if (!isValidJsonStandard(jsonStandard)) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
269
|
+
outputStatus({
|
|
270
|
+
type: 'error',
|
|
271
|
+
errorType: 'ValidationError',
|
|
272
|
+
message: `Invalid JSON standard: ${jsonStandard}. Use "opencode" or "claude".`,
|
|
273
|
+
});
|
|
275
274
|
process.exit(1);
|
|
276
275
|
}
|
|
277
276
|
|
|
@@ -317,24 +316,20 @@ async function runAgentMode(argv, request) {
|
|
|
317
316
|
* @param {object} argv - Command line arguments
|
|
318
317
|
*/
|
|
319
318
|
async function runContinuousAgentMode(argv) {
|
|
320
|
-
// Note: verbose flag and logging are now initialized in middleware
|
|
321
|
-
// See main() function for the middleware that sets up Flag and Log.init()
|
|
322
|
-
|
|
323
319
|
const compactJson = argv['compact-json'] === true;
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// Log dry-run mode if enabled
|
|
320
|
+
// Log version and command info in verbose mode using lazy logging
|
|
321
|
+
Log.Default.lazy.info(() => ({
|
|
322
|
+
message: 'Agent started (continuous mode)',
|
|
323
|
+
version: pkg.version,
|
|
324
|
+
command: process.argv.join(' '),
|
|
325
|
+
workingDirectory: process.cwd(),
|
|
326
|
+
scriptPath: import.meta.path,
|
|
327
|
+
}));
|
|
334
328
|
if (Flag.OPENCODE_DRY_RUN) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
329
|
+
Log.Default.lazy.info(() => ({
|
|
330
|
+
message: 'Dry run mode enabled',
|
|
331
|
+
mode: 'dry-run',
|
|
332
|
+
}));
|
|
338
333
|
}
|
|
339
334
|
|
|
340
335
|
const { providerID, modelID } = await parseModelConfig(argv);
|
|
@@ -935,7 +930,7 @@ async function main() {
|
|
|
935
930
|
}
|
|
936
931
|
|
|
937
932
|
// Initialize logging system
|
|
938
|
-
// - If verbose: print logs to stderr for debugging
|
|
933
|
+
// - If verbose: print logs to stderr for debugging in JSON format
|
|
939
934
|
// - Otherwise: write logs to file to keep CLI output clean
|
|
940
935
|
await Log.init({
|
|
941
936
|
print: Flag.OPENCODE_VERBOSE,
|
package/src/mcp/index.ts
CHANGED
|
@@ -81,9 +81,10 @@ export namespace MCP {
|
|
|
81
81
|
await Promise.all(
|
|
82
82
|
Object.values(state.clients).map((client) =>
|
|
83
83
|
client.close().catch((error) => {
|
|
84
|
-
log.error(
|
|
84
|
+
log.error(() => ({
|
|
85
|
+
message: 'Failed to close MCP client',
|
|
85
86
|
error,
|
|
86
|
-
});
|
|
87
|
+
}));
|
|
87
88
|
})
|
|
88
89
|
)
|
|
89
90
|
);
|
|
@@ -119,10 +120,10 @@ export namespace MCP {
|
|
|
119
120
|
|
|
120
121
|
async function create(key: string, mcp: Config.Mcp) {
|
|
121
122
|
if (mcp.enabled === false) {
|
|
122
|
-
log.info('mcp server disabled',
|
|
123
|
+
log.info(() => ({ message: 'mcp server disabled', key }));
|
|
123
124
|
return;
|
|
124
125
|
}
|
|
125
|
-
log.info('found',
|
|
126
|
+
log.info(() => ({ message: 'found', key, type: mcp.type }));
|
|
126
127
|
let mcpClient: MCPClient | undefined;
|
|
127
128
|
let status: Status | undefined = undefined;
|
|
128
129
|
|
|
@@ -152,7 +153,11 @@ export namespace MCP {
|
|
|
152
153
|
transport,
|
|
153
154
|
})
|
|
154
155
|
.then((client) => {
|
|
155
|
-
log.info(
|
|
156
|
+
log.info(() => ({
|
|
157
|
+
message: 'connected',
|
|
158
|
+
key,
|
|
159
|
+
transport: name,
|
|
160
|
+
}));
|
|
156
161
|
mcpClient = client;
|
|
157
162
|
status = { status: 'connected' };
|
|
158
163
|
return true;
|
|
@@ -160,12 +165,13 @@ export namespace MCP {
|
|
|
160
165
|
.catch((error) => {
|
|
161
166
|
lastError =
|
|
162
167
|
error instanceof Error ? error : new Error(String(error));
|
|
163
|
-
log.debug(
|
|
168
|
+
log.debug(() => ({
|
|
169
|
+
message: 'transport connection failed',
|
|
164
170
|
key,
|
|
165
171
|
transport: name,
|
|
166
172
|
url: mcp.url,
|
|
167
173
|
error: lastError.message,
|
|
168
|
-
});
|
|
174
|
+
}));
|
|
169
175
|
status = {
|
|
170
176
|
status: 'failed' as const,
|
|
171
177
|
error: lastError.message,
|
|
@@ -198,11 +204,12 @@ export namespace MCP {
|
|
|
198
204
|
};
|
|
199
205
|
})
|
|
200
206
|
.catch((error) => {
|
|
201
|
-
log.error(
|
|
207
|
+
log.error(() => ({
|
|
208
|
+
message: 'local mcp startup failed',
|
|
202
209
|
key,
|
|
203
210
|
command: mcp.command,
|
|
204
211
|
error: error instanceof Error ? error.message : String(error),
|
|
205
|
-
});
|
|
212
|
+
}));
|
|
206
213
|
status = {
|
|
207
214
|
status: 'failed' as const,
|
|
208
215
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -228,14 +235,19 @@ export namespace MCP {
|
|
|
228
235
|
mcpClient.tools(),
|
|
229
236
|
mcp.timeout ?? 5000
|
|
230
237
|
).catch((err) => {
|
|
231
|
-
log.error(
|
|
238
|
+
log.error(() => ({
|
|
239
|
+
message: 'failed to get tools from client',
|
|
240
|
+
key,
|
|
241
|
+
error: err,
|
|
242
|
+
}));
|
|
232
243
|
return undefined;
|
|
233
244
|
});
|
|
234
245
|
if (!result) {
|
|
235
246
|
await mcpClient.close().catch((error) => {
|
|
236
|
-
log.error(
|
|
247
|
+
log.error(() => ({
|
|
248
|
+
message: 'Failed to close MCP client',
|
|
237
249
|
error,
|
|
238
|
-
});
|
|
250
|
+
}));
|
|
239
251
|
});
|
|
240
252
|
status = {
|
|
241
253
|
status: 'failed',
|
|
@@ -250,10 +262,11 @@ export namespace MCP {
|
|
|
250
262
|
};
|
|
251
263
|
}
|
|
252
264
|
|
|
253
|
-
log.info(
|
|
265
|
+
log.info(() => ({
|
|
266
|
+
message: 'create() successfully created client',
|
|
254
267
|
key,
|
|
255
268
|
toolCount: Object.keys(result).length,
|
|
256
|
-
});
|
|
269
|
+
}));
|
|
257
270
|
return {
|
|
258
271
|
mcpClient,
|
|
259
272
|
status,
|
|
@@ -274,7 +287,11 @@ export namespace MCP {
|
|
|
274
287
|
const clientsSnapshot = await clients();
|
|
275
288
|
for (const [clientName, client] of Object.entries(clientsSnapshot)) {
|
|
276
289
|
const tools = await client.tools().catch((e) => {
|
|
277
|
-
log.error(
|
|
290
|
+
log.error(() => ({
|
|
291
|
+
message: 'failed to get tools',
|
|
292
|
+
clientName,
|
|
293
|
+
error: e.message,
|
|
294
|
+
}));
|
|
278
295
|
const failedStatus = {
|
|
279
296
|
status: 'failed' as const,
|
|
280
297
|
error: e instanceof Error ? e.message : String(e),
|
package/src/patch/index.ts
CHANGED
|
@@ -532,13 +532,13 @@ export namespace Patch {
|
|
|
532
532
|
|
|
533
533
|
await fs.writeFile(hunk.path, hunk.contents, 'utf-8');
|
|
534
534
|
added.push(hunk.path);
|
|
535
|
-
log.info(
|
|
535
|
+
log.info(() => ({ message: 'Added file', path: hunk.path }));
|
|
536
536
|
break;
|
|
537
537
|
|
|
538
538
|
case 'delete':
|
|
539
539
|
await fs.unlink(hunk.path);
|
|
540
540
|
deleted.push(hunk.path);
|
|
541
|
-
log.info(
|
|
541
|
+
log.info(() => ({ message: 'Deleted file', path: hunk.path }));
|
|
542
542
|
break;
|
|
543
543
|
|
|
544
544
|
case 'update':
|
|
@@ -557,12 +557,16 @@ export namespace Patch {
|
|
|
557
557
|
await fs.writeFile(hunk.move_path, fileUpdate.content, 'utf-8');
|
|
558
558
|
await fs.unlink(hunk.path);
|
|
559
559
|
modified.push(hunk.move_path);
|
|
560
|
-
log.info(
|
|
560
|
+
log.info(() => ({
|
|
561
|
+
message: 'Moved file',
|
|
562
|
+
from: hunk.path,
|
|
563
|
+
to: hunk.move_path,
|
|
564
|
+
}));
|
|
561
565
|
} else {
|
|
562
566
|
// Regular update
|
|
563
567
|
await fs.writeFile(hunk.path, fileUpdate.content, 'utf-8');
|
|
564
568
|
modified.push(hunk.path);
|
|
565
|
-
log.info(
|
|
569
|
+
log.info(() => ({ message: 'Updated file', path: hunk.path }));
|
|
566
570
|
}
|
|
567
571
|
break;
|
|
568
572
|
}
|
package/src/project/project.ts
CHANGED
|
@@ -24,7 +24,7 @@ export namespace Project {
|
|
|
24
24
|
export type Info = z.infer<typeof Info>;
|
|
25
25
|
|
|
26
26
|
export async function fromDirectory(directory: string) {
|
|
27
|
-
log.info('fromDirectory',
|
|
27
|
+
log.info(() => ({ message: 'fromDirectory', directory }));
|
|
28
28
|
const matches = Filesystem.up({ targets: ['.git'], start: directory });
|
|
29
29
|
const git = await matches.next().then((x) => x.value);
|
|
30
30
|
await matches.return();
|
package/src/project/state.ts
CHANGED
|
@@ -36,16 +36,20 @@ export namespace State {
|
|
|
36
36
|
const entries = recordsByKey.get(key);
|
|
37
37
|
if (!entries) return;
|
|
38
38
|
|
|
39
|
-
log.info(
|
|
39
|
+
log.info(() => ({
|
|
40
|
+
message: 'waiting for state disposal to complete',
|
|
41
|
+
key,
|
|
42
|
+
}));
|
|
40
43
|
|
|
41
44
|
let disposalFinished = false;
|
|
42
45
|
|
|
43
46
|
setTimeout(() => {
|
|
44
47
|
if (!disposalFinished) {
|
|
45
|
-
log.warn(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
log.warn(() => ({
|
|
49
|
+
message:
|
|
50
|
+
'state disposal is taking an unusually long time - if it does not complete in a reasonable time, please report this as a bug',
|
|
51
|
+
key,
|
|
52
|
+
}));
|
|
49
53
|
}
|
|
50
54
|
}, 10000).unref();
|
|
51
55
|
|
|
@@ -56,7 +60,11 @@ export namespace State {
|
|
|
56
60
|
const task = Promise.resolve(entry.state)
|
|
57
61
|
.then((state) => entry.dispose!(state))
|
|
58
62
|
.catch((error) => {
|
|
59
|
-
log.error(
|
|
63
|
+
log.error(() => ({
|
|
64
|
+
message: 'Error while disposing state',
|
|
65
|
+
error,
|
|
66
|
+
key,
|
|
67
|
+
}));
|
|
60
68
|
});
|
|
61
69
|
|
|
62
70
|
tasks.push(task);
|
|
@@ -64,6 +72,6 @@ export namespace State {
|
|
|
64
72
|
await Promise.all(tasks);
|
|
65
73
|
recordsByKey.delete(key);
|
|
66
74
|
disposalFinished = true;
|
|
67
|
-
log.info('state disposal completed',
|
|
75
|
+
log.info(() => ({ message: 'state disposal completed', key }));
|
|
68
76
|
}
|
|
69
77
|
}
|
package/src/provider/models.ts
CHANGED
|
@@ -78,18 +78,17 @@ export namespace ModelsDev {
|
|
|
78
78
|
|
|
79
79
|
export async function refresh() {
|
|
80
80
|
const file = Bun.file(filepath);
|
|
81
|
-
log.info('refreshing',
|
|
82
|
-
file,
|
|
83
|
-
});
|
|
81
|
+
log.info(() => ({ message: 'refreshing', file }));
|
|
84
82
|
const result = await fetch('https://models.dev/api.json', {
|
|
85
83
|
headers: {
|
|
86
84
|
'User-Agent': 'agent-cli/1.0.0',
|
|
87
85
|
},
|
|
88
86
|
signal: AbortSignal.timeout(10 * 1000),
|
|
89
87
|
}).catch((e) => {
|
|
90
|
-
log.error(
|
|
88
|
+
log.error(() => ({
|
|
89
|
+
message: 'Failed to fetch models.dev',
|
|
91
90
|
error: e,
|
|
92
|
-
});
|
|
91
|
+
}));
|
|
93
92
|
});
|
|
94
93
|
if (result && result.ok) await Bun.write(file, await result.text());
|
|
95
94
|
}
|
package/src/provider/provider.ts
CHANGED
|
@@ -37,7 +37,7 @@ export namespace Provider {
|
|
|
37
37
|
// Check if OAuth credentials are available via the auth plugin
|
|
38
38
|
const auth = await Auth.get('anthropic');
|
|
39
39
|
if (auth?.type === 'oauth') {
|
|
40
|
-
log.info('using anthropic oauth credentials');
|
|
40
|
+
log.info(() => ({ message: 'using anthropic oauth credentials' }));
|
|
41
41
|
const loaderFn = await AuthPlugins.getLoader('anthropic');
|
|
42
42
|
if (loaderFn) {
|
|
43
43
|
const result = await loaderFn(() => Auth.get('anthropic'), input);
|
|
@@ -330,7 +330,7 @@ export namespace Provider {
|
|
|
330
330
|
google: async (input) => {
|
|
331
331
|
const auth = await Auth.get('google');
|
|
332
332
|
if (auth?.type === 'oauth') {
|
|
333
|
-
log.info('using google oauth credentials');
|
|
333
|
+
log.info(() => ({ message: 'using google oauth credentials' }));
|
|
334
334
|
const loaderFn = await AuthPlugins.getLoader('google');
|
|
335
335
|
if (loaderFn) {
|
|
336
336
|
const result = await loaderFn(() => Auth.get('google'), input);
|
|
@@ -355,7 +355,9 @@ export namespace Provider {
|
|
|
355
355
|
'github-copilot': async (input) => {
|
|
356
356
|
const auth = await Auth.get('github-copilot');
|
|
357
357
|
if (auth?.type === 'oauth') {
|
|
358
|
-
log.info(
|
|
358
|
+
log.info(() => ({
|
|
359
|
+
message: 'using github copilot oauth credentials',
|
|
360
|
+
}));
|
|
359
361
|
const loaderFn = await AuthPlugins.getLoader('github-copilot');
|
|
360
362
|
if (loaderFn) {
|
|
361
363
|
const result = await loaderFn(
|
|
@@ -383,7 +385,9 @@ export namespace Provider {
|
|
|
383
385
|
'github-copilot-enterprise': async (input) => {
|
|
384
386
|
const auth = await Auth.get('github-copilot-enterprise');
|
|
385
387
|
if (auth?.type === 'oauth') {
|
|
386
|
-
log.info(
|
|
388
|
+
log.info(() => ({
|
|
389
|
+
message: 'using github copilot enterprise oauth credentials',
|
|
390
|
+
}));
|
|
387
391
|
const loaderFn = await AuthPlugins.getLoader('github-copilot');
|
|
388
392
|
if (loaderFn) {
|
|
389
393
|
const result = await loaderFn(
|
|
@@ -435,7 +439,10 @@ export namespace Provider {
|
|
|
435
439
|
return { autoload: false };
|
|
436
440
|
}
|
|
437
441
|
|
|
438
|
-
log.info(
|
|
442
|
+
log.info(() => ({
|
|
443
|
+
message: 'using claude oauth credentials',
|
|
444
|
+
source: tokenSource,
|
|
445
|
+
}));
|
|
439
446
|
|
|
440
447
|
// Create authenticated fetch with Bearer token and OAuth beta header
|
|
441
448
|
const customFetch = ClaudeOAuth.createAuthenticatedFetch(oauthToken);
|
|
@@ -537,7 +544,7 @@ export namespace Provider {
|
|
|
537
544
|
// Maps `${provider}/${key}` to the provider’s actual model ID for custom aliases.
|
|
538
545
|
const realIdByKey = new Map<string, string>();
|
|
539
546
|
|
|
540
|
-
log.info('init');
|
|
547
|
+
log.info(() => ({ message: 'init' }));
|
|
541
548
|
|
|
542
549
|
function mergeProvider(
|
|
543
550
|
id: string,
|
|
@@ -783,7 +790,7 @@ export namespace Provider {
|
|
|
783
790
|
delete providers[providerID];
|
|
784
791
|
continue;
|
|
785
792
|
}
|
|
786
|
-
log.info('found',
|
|
793
|
+
log.info(() => ({ message: 'found', providerID }));
|
|
787
794
|
}
|
|
788
795
|
|
|
789
796
|
return {
|
|
@@ -818,19 +825,21 @@ export namespace Provider {
|
|
|
818
825
|
|
|
819
826
|
let installedPath: string;
|
|
820
827
|
if (!pkg.startsWith('file://')) {
|
|
821
|
-
log.info(
|
|
828
|
+
log.info(() => ({
|
|
829
|
+
message: 'installing provider package',
|
|
822
830
|
providerID: provider.id,
|
|
823
831
|
pkg,
|
|
824
832
|
version: 'latest',
|
|
825
|
-
});
|
|
833
|
+
}));
|
|
826
834
|
installedPath = await BunProc.install(pkg, 'latest');
|
|
827
|
-
log.info(
|
|
835
|
+
log.info(() => ({
|
|
836
|
+
message: 'provider package installed successfully',
|
|
828
837
|
providerID: provider.id,
|
|
829
838
|
pkg,
|
|
830
839
|
installedPath,
|
|
831
|
-
});
|
|
840
|
+
}));
|
|
832
841
|
} else {
|
|
833
|
-
log.info('loading local provider',
|
|
842
|
+
log.info(() => ({ message: 'loading local provider', pkg }));
|
|
834
843
|
installedPath = pkg;
|
|
835
844
|
}
|
|
836
845
|
|
|
@@ -876,13 +885,14 @@ export namespace Provider {
|
|
|
876
885
|
s.sdk.set(key, loaded);
|
|
877
886
|
return loaded as SDK;
|
|
878
887
|
})().catch((e) => {
|
|
879
|
-
log.error(
|
|
888
|
+
log.error(() => ({
|
|
889
|
+
message: 'provider initialization failed',
|
|
880
890
|
providerID: provider.id,
|
|
881
891
|
pkg: model.provider?.npm ?? provider.npm ?? provider.id,
|
|
882
892
|
error: e instanceof Error ? e.message : String(e),
|
|
883
893
|
stack: e instanceof Error ? e.stack : undefined,
|
|
884
894
|
cause: e instanceof Error && e.cause ? String(e.cause) : undefined,
|
|
885
|
-
});
|
|
895
|
+
}));
|
|
886
896
|
throw new InitError({ providerID: provider.id }, { cause: e });
|
|
887
897
|
});
|
|
888
898
|
}
|
|
@@ -896,10 +906,7 @@ export namespace Provider {
|
|
|
896
906
|
const s = await state();
|
|
897
907
|
if (s.models.has(key)) return s.models.get(key)!;
|
|
898
908
|
|
|
899
|
-
log.info('getModel',
|
|
900
|
-
providerID,
|
|
901
|
-
modelID,
|
|
902
|
-
});
|
|
909
|
+
log.info(() => ({ message: 'getModel', providerID, modelID }));
|
|
903
910
|
|
|
904
911
|
const provider = s.providers[providerID];
|
|
905
912
|
if (!provider) throw new ModelNotFoundError({ providerID, modelID });
|
|
@@ -929,7 +936,7 @@ export namespace Provider {
|
|
|
929
936
|
? await provider.getModel(sdk, realID, provider.options)
|
|
930
937
|
: sdk.languageModel(realID);
|
|
931
938
|
}
|
|
932
|
-
log.info('found',
|
|
939
|
+
log.info(() => ({ message: 'found', providerID, modelID }));
|
|
933
940
|
s.models.set(key, {
|
|
934
941
|
providerID,
|
|
935
942
|
modelID,
|
|
@@ -1032,10 +1039,11 @@ export namespace Provider {
|
|
|
1032
1039
|
if (opencodeProvider) {
|
|
1033
1040
|
const [model] = sort(Object.values(opencodeProvider.info.models));
|
|
1034
1041
|
if (model) {
|
|
1035
|
-
log.info(
|
|
1042
|
+
log.info(() => ({
|
|
1043
|
+
message: 'using opencode provider as default',
|
|
1036
1044
|
provider: opencodeProvider.info.id,
|
|
1037
1045
|
model: model.id,
|
|
1038
|
-
});
|
|
1046
|
+
}));
|
|
1039
1047
|
return {
|
|
1040
1048
|
providerID: opencodeProvider.info.id,
|
|
1041
1049
|
modelID: model.id,
|
package/src/server/server.ts
CHANGED
|
@@ -55,9 +55,7 @@ export namespace Server {
|
|
|
55
55
|
export const App = lazy(() =>
|
|
56
56
|
app
|
|
57
57
|
.onError((err, c) => {
|
|
58
|
-
log.error('failed',
|
|
59
|
-
error: err,
|
|
60
|
-
});
|
|
58
|
+
log.error(() => ({ message: 'failed', error: err }));
|
|
61
59
|
if (err instanceof NamedError) {
|
|
62
60
|
let status: ContentfulStatusCode;
|
|
63
61
|
if (err instanceof Storage.NotFoundError) status = 404;
|
|
@@ -71,10 +69,11 @@ export namespace Server {
|
|
|
71
69
|
});
|
|
72
70
|
})
|
|
73
71
|
.use(async (c, next) => {
|
|
74
|
-
log.info(
|
|
72
|
+
log.info(() => ({
|
|
73
|
+
message: 'request',
|
|
75
74
|
method: c.req.method,
|
|
76
75
|
path: c.req.path,
|
|
77
|
-
});
|
|
76
|
+
}));
|
|
78
77
|
const timer = log.time('request', {
|
|
79
78
|
method: c.req.method,
|
|
80
79
|
path: c.req.path,
|
package/src/session/agent.js
CHANGED
|
@@ -96,8 +96,22 @@ export class Agent {
|
|
|
96
96
|
const errorTime = Date.now();
|
|
97
97
|
const callID = `call_${Math.floor(Math.random() * 100000000)}`;
|
|
98
98
|
|
|
99
|
-
// Log full error to stderr for debugging
|
|
100
|
-
console.error(
|
|
99
|
+
// Log full error to stderr for debugging in JSON format
|
|
100
|
+
console.error(
|
|
101
|
+
JSON.stringify({
|
|
102
|
+
log: {
|
|
103
|
+
level: 'error',
|
|
104
|
+
timestamp: new Date().toISOString(),
|
|
105
|
+
message: 'Tool execution error',
|
|
106
|
+
tool: tool.name,
|
|
107
|
+
error: {
|
|
108
|
+
name: error.name,
|
|
109
|
+
message: error.message,
|
|
110
|
+
stack: error.stack,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
})
|
|
114
|
+
);
|
|
101
115
|
|
|
102
116
|
// Emit tool_use event with error
|
|
103
117
|
this.emitEvent('tool_use', {
|
|
@@ -52,7 +52,7 @@ export namespace SessionCompaction {
|
|
|
52
52
|
// tool calls that are no longer relevant.
|
|
53
53
|
export async function prune(input: { sessionID: string }) {
|
|
54
54
|
if (Flag.OPENCODE_DISABLE_PRUNE) return;
|
|
55
|
-
log.info('pruning');
|
|
55
|
+
log.info(() => ({ message: 'pruning' }));
|
|
56
56
|
const msgs = await Session.messages({ sessionID: input.sessionID });
|
|
57
57
|
let total = 0;
|
|
58
58
|
let pruned = 0;
|
|
@@ -78,7 +78,7 @@ export namespace SessionCompaction {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
-
log.info('found',
|
|
81
|
+
log.info(() => ({ message: 'found', pruned, total }));
|
|
82
82
|
if (pruned > PRUNE_MINIMUM) {
|
|
83
83
|
for (const part of toPrune) {
|
|
84
84
|
if (part.state.status === 'completed') {
|
|
@@ -86,7 +86,7 @@ export namespace SessionCompaction {
|
|
|
86
86
|
await Session.updatePart(part);
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
-
log.info('pruned',
|
|
89
|
+
log.info(() => ({ message: 'pruned', count: toPrune.length }));
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
@@ -139,9 +139,7 @@ export namespace SessionCompaction {
|
|
|
139
139
|
const result = await processor.process(() =>
|
|
140
140
|
streamText({
|
|
141
141
|
onError(error) {
|
|
142
|
-
log.error('stream error',
|
|
143
|
-
error,
|
|
144
|
-
});
|
|
142
|
+
log.error(() => ({ message: 'stream error', error }));
|
|
145
143
|
},
|
|
146
144
|
// set to 0, we handle loop
|
|
147
145
|
maxRetries: 0,
|