@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/index.js
CHANGED
|
@@ -1,125 +1,200 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
import { Server } from './server/server.ts'
|
|
4
|
-
import { Instance } from './project/instance.ts'
|
|
5
|
-
import { Log } from './util/log.ts'
|
|
6
|
-
import { Bus } from './bus/index.ts'
|
|
7
|
-
import { Session } from './session/index.ts'
|
|
8
|
-
import { SessionPrompt } from './session/prompt.ts'
|
|
9
|
-
|
|
10
|
-
import yargs from 'yargs'
|
|
11
|
-
import { hideBin } from 'yargs/helpers'
|
|
12
|
-
import {
|
|
13
|
-
|
|
3
|
+
import { Server } from './server/server.ts';
|
|
4
|
+
import { Instance } from './project/instance.ts';
|
|
5
|
+
import { Log } from './util/log.ts';
|
|
6
|
+
import { Bus } from './bus/index.ts';
|
|
7
|
+
import { Session } from './session/index.ts';
|
|
8
|
+
import { SessionPrompt } from './session/prompt.ts';
|
|
9
|
+
// EOL is reserved for future use
|
|
10
|
+
import yargs from 'yargs';
|
|
11
|
+
import { hideBin } from 'yargs/helpers';
|
|
12
|
+
import {
|
|
13
|
+
createEventHandler,
|
|
14
|
+
isValidJsonStandard,
|
|
15
|
+
} from './json-standard/index.ts';
|
|
16
|
+
import { McpCommand } from './cli/cmd/mcp.ts';
|
|
17
|
+
import { AuthCommand } from './cli/cmd/auth.ts';
|
|
18
|
+
import { Flag } from './flag/flag.ts';
|
|
14
19
|
|
|
15
20
|
// Track if any errors occurred during execution
|
|
16
|
-
let hasError = false
|
|
21
|
+
let hasError = false;
|
|
17
22
|
|
|
18
23
|
// Install global error handlers to ensure non-zero exit codes
|
|
19
24
|
process.on('uncaughtException', (error) => {
|
|
20
|
-
hasError = true
|
|
21
|
-
console.error(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
25
|
+
hasError = true;
|
|
26
|
+
console.error(
|
|
27
|
+
JSON.stringify(
|
|
28
|
+
{
|
|
29
|
+
type: 'error',
|
|
30
|
+
errorType: error.name || 'UncaughtException',
|
|
31
|
+
message: error.message,
|
|
32
|
+
stack: error.stack,
|
|
33
|
+
},
|
|
34
|
+
null,
|
|
35
|
+
2
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
process.on('unhandledRejection', (reason, _promise) => {
|
|
42
|
+
hasError = true;
|
|
43
|
+
console.error(
|
|
44
|
+
JSON.stringify(
|
|
45
|
+
{
|
|
46
|
+
type: 'error',
|
|
47
|
+
errorType: 'UnhandledRejection',
|
|
48
|
+
message: reason?.message || String(reason),
|
|
49
|
+
stack: reason?.stack,
|
|
50
|
+
},
|
|
51
|
+
null,
|
|
52
|
+
2
|
|
53
|
+
)
|
|
54
|
+
);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
function readStdin() {
|
|
42
59
|
return new Promise((resolve, reject) => {
|
|
43
|
-
let data = ''
|
|
44
|
-
const onData = chunk => {
|
|
45
|
-
data += chunk
|
|
46
|
-
}
|
|
60
|
+
let data = '';
|
|
61
|
+
const onData = (chunk) => {
|
|
62
|
+
data += chunk;
|
|
63
|
+
};
|
|
47
64
|
const onEnd = () => {
|
|
48
|
-
cleanup()
|
|
49
|
-
resolve(data)
|
|
50
|
-
}
|
|
51
|
-
const onError = err => {
|
|
52
|
-
cleanup()
|
|
53
|
-
reject(err)
|
|
54
|
-
}
|
|
65
|
+
cleanup();
|
|
66
|
+
resolve(data);
|
|
67
|
+
};
|
|
68
|
+
const onError = (err) => {
|
|
69
|
+
cleanup();
|
|
70
|
+
reject(err);
|
|
71
|
+
};
|
|
55
72
|
const cleanup = () => {
|
|
56
|
-
process.stdin.removeListener('data', onData)
|
|
57
|
-
process.stdin.removeListener('end', onEnd)
|
|
58
|
-
process.stdin.removeListener('error', onError)
|
|
59
|
-
}
|
|
60
|
-
process.stdin.on('data', onData)
|
|
61
|
-
process.stdin.on('end', onEnd)
|
|
62
|
-
process.stdin.on('error', onError)
|
|
63
|
-
})
|
|
73
|
+
process.stdin.removeListener('data', onData);
|
|
74
|
+
process.stdin.removeListener('end', onEnd);
|
|
75
|
+
process.stdin.removeListener('error', onError);
|
|
76
|
+
};
|
|
77
|
+
process.stdin.on('data', onData);
|
|
78
|
+
process.stdin.on('end', onEnd);
|
|
79
|
+
process.stdin.on('error', onError);
|
|
80
|
+
});
|
|
64
81
|
}
|
|
65
82
|
|
|
66
83
|
async function runAgentMode(argv) {
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
84
|
+
// Set verbose mode if requested via CLI flag
|
|
85
|
+
if (argv.verbose) {
|
|
86
|
+
Flag.setVerbose(true);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Parse model argument (handle model IDs with slashes like groq/qwen/qwen3-32b)
|
|
90
|
+
const modelParts = argv.model.split('/');
|
|
91
|
+
let providerID = modelParts[0] || 'opencode';
|
|
92
|
+
let modelID = modelParts.slice(1).join('/') || 'grok-code';
|
|
93
|
+
|
|
94
|
+
// Handle --use-existing-claude-oauth option
|
|
95
|
+
// This reads OAuth credentials from ~/.claude/.credentials.json (Claude Code CLI)
|
|
96
|
+
// For new authentication, use: agent auth login (select Anthropic > Claude Pro/Max)
|
|
97
|
+
if (argv['use-existing-claude-oauth']) {
|
|
98
|
+
// Import ClaudeOAuth to check for credentials from Claude Code CLI
|
|
99
|
+
const { ClaudeOAuth } = await import('./auth/claude-oauth.ts');
|
|
100
|
+
const creds = await ClaudeOAuth.getCredentials();
|
|
101
|
+
|
|
102
|
+
if (!creds?.accessToken) {
|
|
103
|
+
console.error(
|
|
104
|
+
JSON.stringify({
|
|
105
|
+
type: 'error',
|
|
106
|
+
errorType: 'AuthenticationError',
|
|
107
|
+
message:
|
|
108
|
+
'No Claude OAuth credentials found in ~/.claude/.credentials.json. Either authenticate with Claude Code CLI first, or use: agent auth login (select Anthropic > Claude Pro/Max)',
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Set environment variable for the provider to use
|
|
115
|
+
process.env.CLAUDE_CODE_OAUTH_TOKEN = creds.accessToken;
|
|
116
|
+
|
|
117
|
+
// If user specified a model, use it with claude-oauth provider
|
|
118
|
+
// If not, use claude-oauth/claude-sonnet-4-5 as default
|
|
119
|
+
if (providerID === 'opencode' && modelID === 'grok-code') {
|
|
120
|
+
providerID = 'claude-oauth';
|
|
121
|
+
modelID = 'claude-sonnet-4-5';
|
|
122
|
+
} else if (!['claude-oauth', 'anthropic'].includes(providerID)) {
|
|
123
|
+
// If user specified a different provider, warn them
|
|
124
|
+
console.error(
|
|
125
|
+
JSON.stringify({
|
|
126
|
+
type: 'warning',
|
|
127
|
+
message: `--use-existing-claude-oauth is set but model uses provider "${providerID}". Using OAuth credentials anyway.`,
|
|
128
|
+
})
|
|
129
|
+
);
|
|
130
|
+
providerID = 'claude-oauth';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
71
133
|
|
|
72
134
|
// Validate and get JSON standard
|
|
73
|
-
const jsonStandard = argv['json-standard']
|
|
135
|
+
const jsonStandard = argv['json-standard'];
|
|
74
136
|
if (!isValidJsonStandard(jsonStandard)) {
|
|
75
|
-
console.error(
|
|
76
|
-
|
|
137
|
+
console.error(
|
|
138
|
+
`Invalid JSON standard: ${jsonStandard}. Use "opencode" or "claude".`
|
|
139
|
+
);
|
|
140
|
+
process.exit(1);
|
|
77
141
|
}
|
|
78
142
|
|
|
79
143
|
// Read system message files
|
|
80
|
-
let systemMessage = argv['system-message']
|
|
81
|
-
let appendSystemMessage = argv['append-system-message']
|
|
144
|
+
let systemMessage = argv['system-message'];
|
|
145
|
+
let appendSystemMessage = argv['append-system-message'];
|
|
82
146
|
|
|
83
147
|
if (argv['system-message-file']) {
|
|
84
|
-
const resolvedPath = require('path').resolve(
|
|
85
|
-
|
|
148
|
+
const resolvedPath = require('path').resolve(
|
|
149
|
+
process.cwd(),
|
|
150
|
+
argv['system-message-file']
|
|
151
|
+
);
|
|
152
|
+
const file = Bun.file(resolvedPath);
|
|
86
153
|
if (!(await file.exists())) {
|
|
87
|
-
console.error(
|
|
88
|
-
|
|
154
|
+
console.error(
|
|
155
|
+
`System message file not found: ${argv['system-message-file']}`
|
|
156
|
+
);
|
|
157
|
+
process.exit(1);
|
|
89
158
|
}
|
|
90
|
-
systemMessage = await file.text()
|
|
159
|
+
systemMessage = await file.text();
|
|
91
160
|
}
|
|
92
161
|
|
|
93
162
|
if (argv['append-system-message-file']) {
|
|
94
|
-
const resolvedPath = require('path').resolve(
|
|
95
|
-
|
|
163
|
+
const resolvedPath = require('path').resolve(
|
|
164
|
+
process.cwd(),
|
|
165
|
+
argv['append-system-message-file']
|
|
166
|
+
);
|
|
167
|
+
const file = Bun.file(resolvedPath);
|
|
96
168
|
if (!(await file.exists())) {
|
|
97
|
-
console.error(
|
|
98
|
-
|
|
169
|
+
console.error(
|
|
170
|
+
`Append system message file not found: ${argv['append-system-message-file']}`
|
|
171
|
+
);
|
|
172
|
+
process.exit(1);
|
|
99
173
|
}
|
|
100
|
-
appendSystemMessage = await file.text()
|
|
174
|
+
appendSystemMessage = await file.text();
|
|
101
175
|
}
|
|
102
176
|
|
|
103
177
|
// Initialize logging to redirect to log file instead of stderr
|
|
104
178
|
// This prevents log messages from mixing with JSON output
|
|
179
|
+
// In verbose mode, print to stderr for debugging
|
|
105
180
|
await Log.init({
|
|
106
|
-
print:
|
|
107
|
-
level: 'INFO'
|
|
108
|
-
})
|
|
181
|
+
print: Flag.OPENCODE_VERBOSE, // Print to stderr only in verbose mode
|
|
182
|
+
level: Flag.OPENCODE_VERBOSE ? 'DEBUG' : 'INFO',
|
|
183
|
+
});
|
|
109
184
|
|
|
110
185
|
// Read input from stdin
|
|
111
|
-
const input = await readStdin()
|
|
112
|
-
const trimmedInput = input.trim()
|
|
186
|
+
const input = await readStdin();
|
|
187
|
+
const trimmedInput = input.trim();
|
|
113
188
|
|
|
114
189
|
// Try to parse as JSON, if it fails treat it as plain text message
|
|
115
|
-
let request
|
|
190
|
+
let request;
|
|
116
191
|
try {
|
|
117
|
-
request = JSON.parse(trimmedInput)
|
|
118
|
-
} catch (
|
|
192
|
+
request = JSON.parse(trimmedInput);
|
|
193
|
+
} catch (_e) {
|
|
119
194
|
// Not JSON, treat as plain text message
|
|
120
195
|
request = {
|
|
121
|
-
message: trimmedInput
|
|
122
|
-
}
|
|
196
|
+
message: trimmedInput,
|
|
197
|
+
};
|
|
123
198
|
}
|
|
124
199
|
|
|
125
200
|
// Wrap in Instance.provide for OpenCode infrastructure
|
|
@@ -128,47 +203,73 @@ async function runAgentMode(argv) {
|
|
|
128
203
|
fn: async () => {
|
|
129
204
|
if (argv.server) {
|
|
130
205
|
// SERVER MODE: Start server and communicate via HTTP
|
|
131
|
-
await runServerMode(
|
|
206
|
+
await runServerMode(
|
|
207
|
+
request,
|
|
208
|
+
providerID,
|
|
209
|
+
modelID,
|
|
210
|
+
systemMessage,
|
|
211
|
+
appendSystemMessage,
|
|
212
|
+
jsonStandard
|
|
213
|
+
);
|
|
132
214
|
} else {
|
|
133
215
|
// DIRECT MODE: Run everything in single process
|
|
134
|
-
await runDirectMode(
|
|
216
|
+
await runDirectMode(
|
|
217
|
+
request,
|
|
218
|
+
providerID,
|
|
219
|
+
modelID,
|
|
220
|
+
systemMessage,
|
|
221
|
+
appendSystemMessage,
|
|
222
|
+
jsonStandard
|
|
223
|
+
);
|
|
135
224
|
}
|
|
136
|
-
}
|
|
137
|
-
})
|
|
225
|
+
},
|
|
226
|
+
});
|
|
138
227
|
|
|
139
228
|
// Explicitly exit to ensure process terminates
|
|
140
|
-
process.exit(hasError ? 1 : 0)
|
|
229
|
+
process.exit(hasError ? 1 : 0);
|
|
141
230
|
}
|
|
142
231
|
|
|
143
|
-
async function runServerMode(
|
|
232
|
+
async function runServerMode(
|
|
233
|
+
request,
|
|
234
|
+
providerID,
|
|
235
|
+
modelID,
|
|
236
|
+
systemMessage,
|
|
237
|
+
appendSystemMessage,
|
|
238
|
+
jsonStandard
|
|
239
|
+
) {
|
|
144
240
|
// Start server like OpenCode does
|
|
145
|
-
const server = Server.listen({ port: 0, hostname:
|
|
146
|
-
let unsub = null
|
|
241
|
+
const server = Server.listen({ port: 0, hostname: '127.0.0.1' });
|
|
242
|
+
let unsub = null;
|
|
147
243
|
|
|
148
244
|
try {
|
|
149
245
|
// Create a session
|
|
150
|
-
const createRes = await fetch(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
246
|
+
const createRes = await fetch(
|
|
247
|
+
`http://${server.hostname}:${server.port}/session`,
|
|
248
|
+
{
|
|
249
|
+
method: 'POST',
|
|
250
|
+
headers: { 'Content-Type': 'application/json' },
|
|
251
|
+
body: JSON.stringify({}),
|
|
252
|
+
}
|
|
253
|
+
);
|
|
254
|
+
const session = await createRes.json();
|
|
255
|
+
const sessionID = session.id;
|
|
157
256
|
|
|
158
257
|
if (!sessionID) {
|
|
159
|
-
throw new Error(
|
|
258
|
+
throw new Error('Failed to create session');
|
|
160
259
|
}
|
|
161
260
|
|
|
162
261
|
// Create event handler for the selected JSON standard
|
|
163
|
-
const eventHandler = createEventHandler(jsonStandard, sessionID)
|
|
262
|
+
const eventHandler = createEventHandler(jsonStandard, sessionID);
|
|
164
263
|
|
|
165
264
|
// Subscribe to all bus events and output in selected format
|
|
166
265
|
const eventPromise = new Promise((resolve) => {
|
|
167
266
|
unsub = Bus.subscribeAll((event) => {
|
|
168
267
|
// Output events in selected JSON format
|
|
169
268
|
if (event.type === 'message.part.updated') {
|
|
170
|
-
const part = event.properties.part
|
|
171
|
-
if (part.sessionID !== sessionID)
|
|
269
|
+
const part = event.properties.part;
|
|
270
|
+
if (part.sessionID !== sessionID) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
172
273
|
|
|
173
274
|
// Output different event types
|
|
174
275
|
if (part.type === 'step-start') {
|
|
@@ -176,8 +277,8 @@ async function runServerMode(request, providerID, modelID, systemMessage, append
|
|
|
176
277
|
type: 'step_start',
|
|
177
278
|
timestamp: Date.now(),
|
|
178
279
|
sessionID,
|
|
179
|
-
part
|
|
180
|
-
})
|
|
280
|
+
part,
|
|
281
|
+
});
|
|
181
282
|
}
|
|
182
283
|
|
|
183
284
|
if (part.type === 'step-finish') {
|
|
@@ -185,8 +286,8 @@ async function runServerMode(request, providerID, modelID, systemMessage, append
|
|
|
185
286
|
type: 'step_finish',
|
|
186
287
|
timestamp: Date.now(),
|
|
187
288
|
sessionID,
|
|
188
|
-
part
|
|
189
|
-
})
|
|
289
|
+
part,
|
|
290
|
+
});
|
|
190
291
|
}
|
|
191
292
|
|
|
192
293
|
if (part.type === 'text' && part.time?.end) {
|
|
@@ -194,8 +295,8 @@ async function runServerMode(request, providerID, modelID, systemMessage, append
|
|
|
194
295
|
type: 'text',
|
|
195
296
|
timestamp: Date.now(),
|
|
196
297
|
sessionID,
|
|
197
|
-
part
|
|
198
|
-
})
|
|
298
|
+
part,
|
|
299
|
+
});
|
|
199
300
|
}
|
|
200
301
|
|
|
201
302
|
if (part.type === 'tool' && part.state.status === 'completed') {
|
|
@@ -203,89 +304,108 @@ async function runServerMode(request, providerID, modelID, systemMessage, append
|
|
|
203
304
|
type: 'tool_use',
|
|
204
305
|
timestamp: Date.now(),
|
|
205
306
|
sessionID,
|
|
206
|
-
part
|
|
207
|
-
})
|
|
307
|
+
part,
|
|
308
|
+
});
|
|
208
309
|
}
|
|
209
310
|
}
|
|
210
311
|
|
|
211
312
|
// Handle session idle to know when to stop
|
|
212
|
-
if (
|
|
213
|
-
|
|
313
|
+
if (
|
|
314
|
+
event.type === 'session.idle' &&
|
|
315
|
+
event.properties.sessionID === sessionID
|
|
316
|
+
) {
|
|
317
|
+
resolve();
|
|
214
318
|
}
|
|
215
319
|
|
|
216
320
|
// Handle errors
|
|
217
321
|
if (event.type === 'session.error') {
|
|
218
|
-
const props = event.properties
|
|
219
|
-
if (props.sessionID !== sessionID || !props.error)
|
|
220
|
-
|
|
322
|
+
const props = event.properties;
|
|
323
|
+
if (props.sessionID !== sessionID || !props.error) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
hasError = true;
|
|
221
327
|
eventHandler.output({
|
|
222
328
|
type: 'error',
|
|
223
329
|
timestamp: Date.now(),
|
|
224
330
|
sessionID,
|
|
225
|
-
error: props.error
|
|
226
|
-
})
|
|
331
|
+
error: props.error,
|
|
332
|
+
});
|
|
227
333
|
}
|
|
228
|
-
})
|
|
229
|
-
})
|
|
334
|
+
});
|
|
335
|
+
});
|
|
230
336
|
|
|
231
337
|
// Send message to session with specified model (default: opencode/grok-code)
|
|
232
|
-
const message = request.message ||
|
|
233
|
-
const parts = [{ type:
|
|
338
|
+
const message = request.message || 'hi';
|
|
339
|
+
const parts = [{ type: 'text', text: message }];
|
|
234
340
|
|
|
235
341
|
// Start the prompt (don't wait for response, events come via Bus)
|
|
236
|
-
fetch(
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
342
|
+
fetch(
|
|
343
|
+
`http://${server.hostname}:${server.port}/session/${sessionID}/message`,
|
|
344
|
+
{
|
|
345
|
+
method: 'POST',
|
|
346
|
+
headers: { 'Content-Type': 'application/json' },
|
|
347
|
+
body: JSON.stringify({
|
|
348
|
+
parts,
|
|
349
|
+
model: {
|
|
350
|
+
providerID,
|
|
351
|
+
modelID,
|
|
352
|
+
},
|
|
353
|
+
system: systemMessage,
|
|
354
|
+
appendSystem: appendSystemMessage,
|
|
355
|
+
}),
|
|
356
|
+
}
|
|
357
|
+
).catch((error) => {
|
|
358
|
+
hasError = true;
|
|
250
359
|
eventHandler.output({
|
|
251
360
|
type: 'error',
|
|
252
361
|
timestamp: Date.now(),
|
|
253
362
|
sessionID,
|
|
254
|
-
error: error instanceof Error ? error.message : String(error)
|
|
255
|
-
})
|
|
256
|
-
})
|
|
363
|
+
error: error instanceof Error ? error.message : String(error),
|
|
364
|
+
});
|
|
365
|
+
});
|
|
257
366
|
|
|
258
367
|
// Wait for session to become idle
|
|
259
|
-
await eventPromise
|
|
368
|
+
await eventPromise;
|
|
260
369
|
} finally {
|
|
261
370
|
// Always clean up resources
|
|
262
|
-
if (unsub)
|
|
263
|
-
|
|
264
|
-
|
|
371
|
+
if (unsub) {
|
|
372
|
+
unsub();
|
|
373
|
+
}
|
|
374
|
+
server.stop();
|
|
375
|
+
await Instance.dispose();
|
|
265
376
|
}
|
|
266
377
|
}
|
|
267
378
|
|
|
268
|
-
async function runDirectMode(
|
|
379
|
+
async function runDirectMode(
|
|
380
|
+
request,
|
|
381
|
+
providerID,
|
|
382
|
+
modelID,
|
|
383
|
+
systemMessage,
|
|
384
|
+
appendSystemMessage,
|
|
385
|
+
jsonStandard
|
|
386
|
+
) {
|
|
269
387
|
// DIRECT MODE: Run in single process without server
|
|
270
|
-
let unsub = null
|
|
388
|
+
let unsub = null;
|
|
271
389
|
|
|
272
390
|
try {
|
|
273
391
|
// Create a session directly
|
|
274
392
|
const session = await Session.createNext({
|
|
275
|
-
directory: process.cwd()
|
|
276
|
-
})
|
|
277
|
-
const sessionID = session.id
|
|
393
|
+
directory: process.cwd(),
|
|
394
|
+
});
|
|
395
|
+
const sessionID = session.id;
|
|
278
396
|
|
|
279
397
|
// Create event handler for the selected JSON standard
|
|
280
|
-
const eventHandler = createEventHandler(jsonStandard, sessionID)
|
|
398
|
+
const eventHandler = createEventHandler(jsonStandard, sessionID);
|
|
281
399
|
|
|
282
400
|
// Subscribe to all bus events and output in selected format
|
|
283
401
|
const eventPromise = new Promise((resolve) => {
|
|
284
402
|
unsub = Bus.subscribeAll((event) => {
|
|
285
403
|
// Output events in selected JSON format
|
|
286
404
|
if (event.type === 'message.part.updated') {
|
|
287
|
-
const part = event.properties.part
|
|
288
|
-
if (part.sessionID !== sessionID)
|
|
405
|
+
const part = event.properties.part;
|
|
406
|
+
if (part.sessionID !== sessionID) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
289
409
|
|
|
290
410
|
// Output different event types
|
|
291
411
|
if (part.type === 'step-start') {
|
|
@@ -293,8 +413,8 @@ async function runDirectMode(request, providerID, modelID, systemMessage, append
|
|
|
293
413
|
type: 'step_start',
|
|
294
414
|
timestamp: Date.now(),
|
|
295
415
|
sessionID,
|
|
296
|
-
part
|
|
297
|
-
})
|
|
416
|
+
part,
|
|
417
|
+
});
|
|
298
418
|
}
|
|
299
419
|
|
|
300
420
|
if (part.type === 'step-finish') {
|
|
@@ -302,8 +422,8 @@ async function runDirectMode(request, providerID, modelID, systemMessage, append
|
|
|
302
422
|
type: 'step_finish',
|
|
303
423
|
timestamp: Date.now(),
|
|
304
424
|
sessionID,
|
|
305
|
-
part
|
|
306
|
-
})
|
|
425
|
+
part,
|
|
426
|
+
});
|
|
307
427
|
}
|
|
308
428
|
|
|
309
429
|
if (part.type === 'text' && part.time?.end) {
|
|
@@ -311,8 +431,8 @@ async function runDirectMode(request, providerID, modelID, systemMessage, append
|
|
|
311
431
|
type: 'text',
|
|
312
432
|
timestamp: Date.now(),
|
|
313
433
|
sessionID,
|
|
314
|
-
part
|
|
315
|
-
})
|
|
434
|
+
part,
|
|
435
|
+
});
|
|
316
436
|
}
|
|
317
437
|
|
|
318
438
|
if (part.type === 'tool' && part.state.status === 'completed') {
|
|
@@ -320,34 +440,39 @@ async function runDirectMode(request, providerID, modelID, systemMessage, append
|
|
|
320
440
|
type: 'tool_use',
|
|
321
441
|
timestamp: Date.now(),
|
|
322
442
|
sessionID,
|
|
323
|
-
part
|
|
324
|
-
})
|
|
443
|
+
part,
|
|
444
|
+
});
|
|
325
445
|
}
|
|
326
446
|
}
|
|
327
447
|
|
|
328
448
|
// Handle session idle to know when to stop
|
|
329
|
-
if (
|
|
330
|
-
|
|
449
|
+
if (
|
|
450
|
+
event.type === 'session.idle' &&
|
|
451
|
+
event.properties.sessionID === sessionID
|
|
452
|
+
) {
|
|
453
|
+
resolve();
|
|
331
454
|
}
|
|
332
455
|
|
|
333
456
|
// Handle errors
|
|
334
457
|
if (event.type === 'session.error') {
|
|
335
|
-
const props = event.properties
|
|
336
|
-
if (props.sessionID !== sessionID || !props.error)
|
|
337
|
-
|
|
458
|
+
const props = event.properties;
|
|
459
|
+
if (props.sessionID !== sessionID || !props.error) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
hasError = true;
|
|
338
463
|
eventHandler.output({
|
|
339
464
|
type: 'error',
|
|
340
465
|
timestamp: Date.now(),
|
|
341
466
|
sessionID,
|
|
342
|
-
error: props.error
|
|
343
|
-
})
|
|
467
|
+
error: props.error,
|
|
468
|
+
});
|
|
344
469
|
}
|
|
345
|
-
})
|
|
346
|
-
})
|
|
470
|
+
});
|
|
471
|
+
});
|
|
347
472
|
|
|
348
473
|
// Send message to session directly
|
|
349
|
-
const message = request.message ||
|
|
350
|
-
const parts = [{ type:
|
|
474
|
+
const message = request.message || 'hi';
|
|
475
|
+
const parts = [{ type: 'text', text: message }];
|
|
351
476
|
|
|
352
477
|
// Start the prompt directly without HTTP
|
|
353
478
|
SessionPrompt.prompt({
|
|
@@ -355,26 +480,28 @@ async function runDirectMode(request, providerID, modelID, systemMessage, append
|
|
|
355
480
|
parts,
|
|
356
481
|
model: {
|
|
357
482
|
providerID,
|
|
358
|
-
modelID
|
|
483
|
+
modelID,
|
|
359
484
|
},
|
|
360
485
|
system: systemMessage,
|
|
361
|
-
appendSystem: appendSystemMessage
|
|
486
|
+
appendSystem: appendSystemMessage,
|
|
362
487
|
}).catch((error) => {
|
|
363
|
-
hasError = true
|
|
488
|
+
hasError = true;
|
|
364
489
|
eventHandler.output({
|
|
365
490
|
type: 'error',
|
|
366
491
|
timestamp: Date.now(),
|
|
367
492
|
sessionID,
|
|
368
|
-
error: error instanceof Error ? error.message : String(error)
|
|
369
|
-
})
|
|
370
|
-
})
|
|
493
|
+
error: error instanceof Error ? error.message : String(error),
|
|
494
|
+
});
|
|
495
|
+
});
|
|
371
496
|
|
|
372
497
|
// Wait for session to become idle
|
|
373
|
-
await eventPromise
|
|
498
|
+
await eventPromise;
|
|
374
499
|
} finally {
|
|
375
500
|
// Always clean up resources
|
|
376
|
-
if (unsub)
|
|
377
|
-
|
|
501
|
+
if (unsub) {
|
|
502
|
+
unsub();
|
|
503
|
+
}
|
|
504
|
+
await Instance.dispose();
|
|
378
505
|
}
|
|
379
506
|
}
|
|
380
507
|
|
|
@@ -386,61 +513,81 @@ async function main() {
|
|
|
386
513
|
.usage('$0 [command] [options]')
|
|
387
514
|
// MCP subcommand
|
|
388
515
|
.command(McpCommand)
|
|
516
|
+
// Auth subcommand
|
|
517
|
+
.command(AuthCommand)
|
|
389
518
|
// Default run mode (when piping stdin)
|
|
390
519
|
.option('model', {
|
|
391
520
|
type: 'string',
|
|
392
521
|
description: 'Model to use in format providerID/modelID',
|
|
393
|
-
default: 'opencode/grok-code'
|
|
522
|
+
default: 'opencode/grok-code',
|
|
394
523
|
})
|
|
395
524
|
.option('json-standard', {
|
|
396
525
|
type: 'string',
|
|
397
|
-
description:
|
|
526
|
+
description:
|
|
527
|
+
'JSON output format standard: "opencode" (default) or "claude" (experimental)',
|
|
398
528
|
default: 'opencode',
|
|
399
|
-
choices: ['opencode', 'claude']
|
|
529
|
+
choices: ['opencode', 'claude'],
|
|
400
530
|
})
|
|
401
531
|
.option('system-message', {
|
|
402
532
|
type: 'string',
|
|
403
|
-
description: 'Full override of the system message'
|
|
533
|
+
description: 'Full override of the system message',
|
|
404
534
|
})
|
|
405
535
|
.option('system-message-file', {
|
|
406
536
|
type: 'string',
|
|
407
|
-
description: 'Full override of the system message from file'
|
|
537
|
+
description: 'Full override of the system message from file',
|
|
408
538
|
})
|
|
409
539
|
.option('append-system-message', {
|
|
410
540
|
type: 'string',
|
|
411
|
-
description: 'Append to the default system message'
|
|
541
|
+
description: 'Append to the default system message',
|
|
412
542
|
})
|
|
413
543
|
.option('append-system-message-file', {
|
|
414
544
|
type: 'string',
|
|
415
|
-
description: 'Append to the default system message from file'
|
|
545
|
+
description: 'Append to the default system message from file',
|
|
416
546
|
})
|
|
417
547
|
.option('server', {
|
|
418
548
|
type: 'boolean',
|
|
419
549
|
description: 'Run in server mode (default)',
|
|
420
|
-
default: true
|
|
550
|
+
default: true,
|
|
421
551
|
})
|
|
422
|
-
.
|
|
423
|
-
|
|
552
|
+
.option('verbose', {
|
|
553
|
+
type: 'boolean',
|
|
554
|
+
description:
|
|
555
|
+
'Enable verbose mode to debug API requests (shows system prompt, token counts, etc.)',
|
|
556
|
+
default: false,
|
|
557
|
+
})
|
|
558
|
+
.option('use-existing-claude-oauth', {
|
|
559
|
+
type: 'boolean',
|
|
560
|
+
description:
|
|
561
|
+
'Use existing Claude OAuth credentials from ~/.claude/.credentials.json (from Claude Code CLI)',
|
|
562
|
+
default: false,
|
|
563
|
+
})
|
|
564
|
+
.help().argv;
|
|
424
565
|
|
|
425
566
|
// If a command was executed (like mcp), yargs handles it
|
|
426
567
|
// Otherwise, check if we should run in agent mode (stdin piped)
|
|
427
|
-
const commandExecuted = argv._ && argv._.length > 0
|
|
568
|
+
const commandExecuted = argv._ && argv._.length > 0;
|
|
428
569
|
|
|
429
570
|
if (!commandExecuted) {
|
|
430
571
|
// No command specified, run in default agent mode (stdin processing)
|
|
431
|
-
await runAgentMode(argv)
|
|
572
|
+
await runAgentMode(argv);
|
|
432
573
|
}
|
|
433
574
|
} catch (error) {
|
|
434
|
-
hasError = true
|
|
435
|
-
console.error(
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
575
|
+
hasError = true;
|
|
576
|
+
console.error(
|
|
577
|
+
JSON.stringify(
|
|
578
|
+
{
|
|
579
|
+
type: 'error',
|
|
580
|
+
timestamp: Date.now(),
|
|
581
|
+
errorType: error instanceof Error ? error.name : 'Error',
|
|
582
|
+
message: error instanceof Error ? error.message : String(error),
|
|
583
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
584
|
+
},
|
|
585
|
+
null,
|
|
586
|
+
2
|
|
587
|
+
)
|
|
588
|
+
);
|
|
589
|
+
process.exit(1);
|
|
443
590
|
}
|
|
444
591
|
}
|
|
445
592
|
|
|
446
|
-
main()
|
|
593
|
+
main();
|