@link-assistant/agent 0.24.0 → 0.24.2
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/README.md +19 -0
- package/package.json +1 -1
- package/src/cli/continuous-mode.js +17 -112
- package/src/cli/event-handler.js +103 -82
- package/src/cli/model-config.js +18 -5
- package/src/json-standard/index.ts +15 -2
package/README.md
CHANGED
|
@@ -286,6 +286,10 @@ Options:
|
|
|
286
286
|
Default: opencode/minimax-m2.5-free
|
|
287
287
|
--json-standard JSON output format standard
|
|
288
288
|
Choices: "opencode" (default), "claude" (experimental)
|
|
289
|
+
--output-format Alias for JSON output format
|
|
290
|
+
Choices: "json" (OpenCode), "stream-json" (Claude NDJSON)
|
|
291
|
+
--input-format Input format
|
|
292
|
+
Choices: "text" (default), "stream-json" (Claude JSONL)
|
|
289
293
|
--use-existing-claude-oauth Use existing Claude OAuth credentials
|
|
290
294
|
from ~/.claude/.credentials.json
|
|
291
295
|
--system-message Full override of the system message
|
|
@@ -390,6 +394,21 @@ echo "your message here" | agent
|
|
|
390
394
|
}
|
|
391
395
|
```
|
|
392
396
|
|
|
397
|
+
**Live `stream-json`:**
|
|
398
|
+
|
|
399
|
+
For bidirectional drivers, use:
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
agent --input-format stream-json --output-format stream-json
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
The live contract supports `user_prompt`, `system`, `interrupt`, and
|
|
406
|
+
`permission_response` frames on stdin. It emits replay acknowledgements for
|
|
407
|
+
consumed user frames and an explicit idle/turn-boundary event
|
|
408
|
+
(`session_idle` in OpenCode output, `idle` in Claude output). See
|
|
409
|
+
[docs/stdin-mode.md](../docs/stdin-mode.md#live-stream-json-contract) for the
|
|
410
|
+
session resume and steering semantics.
|
|
411
|
+
|
|
393
412
|
## Supported Tools
|
|
394
413
|
|
|
395
414
|
All 13 tools are **enabled by default** with **no configuration required**. See [TOOLS.md](../TOOLS.md) for complete documentation.
|
package/package.json
CHANGED
|
@@ -10,6 +10,7 @@ import { Session } from '../session/index.ts';
|
|
|
10
10
|
import { SessionPrompt } from '../session/prompt.ts';
|
|
11
11
|
import { createEventHandler, serializeOutput } from '../json-standard/index.ts';
|
|
12
12
|
import { createContinuousStdinReader } from './input-queue.js';
|
|
13
|
+
import { outputBusEvent } from './event-handler.js';
|
|
13
14
|
import { Permission } from '../permission/index.ts';
|
|
14
15
|
import { Log } from '../util/log.ts';
|
|
15
16
|
import { config } from '../config/config.ts';
|
|
@@ -400,62 +401,14 @@ export async function runContinuousServerMode(
|
|
|
400
401
|
|
|
401
402
|
// Subscribe to all bus events and output in selected format
|
|
402
403
|
unsub = Bus.subscribeAll((event) => {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
type: 'step_start',
|
|
412
|
-
timestamp: Date.now(),
|
|
413
|
-
sessionID,
|
|
414
|
-
part,
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
if (part.type === 'step-finish') {
|
|
419
|
-
eventHandler.output({
|
|
420
|
-
type: 'step_finish',
|
|
421
|
-
timestamp: Date.now(),
|
|
422
|
-
sessionID,
|
|
423
|
-
part,
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
if (part.type === 'text' && part.time?.end) {
|
|
428
|
-
eventHandler.output({
|
|
429
|
-
type: 'text',
|
|
430
|
-
timestamp: Date.now(),
|
|
431
|
-
sessionID,
|
|
432
|
-
part,
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
if (part.type === 'tool') {
|
|
437
|
-
eventHandler.output({
|
|
438
|
-
type: 'tool_use',
|
|
439
|
-
timestamp: Date.now(),
|
|
440
|
-
sessionID,
|
|
441
|
-
part,
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
if (event.type === 'session.error') {
|
|
447
|
-
const props = event.properties;
|
|
448
|
-
if (props.sessionID !== sessionID || !props.error) {
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
hasError = true;
|
|
452
|
-
eventHandler.output({
|
|
453
|
-
type: 'error',
|
|
454
|
-
timestamp: Date.now(),
|
|
455
|
-
sessionID,
|
|
456
|
-
error: props.error,
|
|
457
|
-
});
|
|
458
|
-
}
|
|
404
|
+
outputBusEvent({
|
|
405
|
+
event,
|
|
406
|
+
sessionID,
|
|
407
|
+
eventHandler,
|
|
408
|
+
onError: () => {
|
|
409
|
+
hasError = true;
|
|
410
|
+
},
|
|
411
|
+
});
|
|
459
412
|
});
|
|
460
413
|
|
|
461
414
|
// Create continuous stdin reader
|
|
@@ -661,62 +614,14 @@ export async function runContinuousDirectMode(
|
|
|
661
614
|
|
|
662
615
|
// Subscribe to all bus events and output in selected format
|
|
663
616
|
unsub = Bus.subscribeAll((event) => {
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
type: 'step_start',
|
|
673
|
-
timestamp: Date.now(),
|
|
674
|
-
sessionID,
|
|
675
|
-
part,
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
if (part.type === 'step-finish') {
|
|
680
|
-
eventHandler.output({
|
|
681
|
-
type: 'step_finish',
|
|
682
|
-
timestamp: Date.now(),
|
|
683
|
-
sessionID,
|
|
684
|
-
part,
|
|
685
|
-
});
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
if (part.type === 'text' && part.time?.end) {
|
|
689
|
-
eventHandler.output({
|
|
690
|
-
type: 'text',
|
|
691
|
-
timestamp: Date.now(),
|
|
692
|
-
sessionID,
|
|
693
|
-
part,
|
|
694
|
-
});
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
if (part.type === 'tool') {
|
|
698
|
-
eventHandler.output({
|
|
699
|
-
type: 'tool_use',
|
|
700
|
-
timestamp: Date.now(),
|
|
701
|
-
sessionID,
|
|
702
|
-
part,
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
if (event.type === 'session.error') {
|
|
708
|
-
const props = event.properties;
|
|
709
|
-
if (props.sessionID !== sessionID || !props.error) {
|
|
710
|
-
return;
|
|
711
|
-
}
|
|
712
|
-
hasError = true;
|
|
713
|
-
eventHandler.output({
|
|
714
|
-
type: 'error',
|
|
715
|
-
timestamp: Date.now(),
|
|
716
|
-
sessionID,
|
|
717
|
-
error: props.error,
|
|
718
|
-
});
|
|
719
|
-
}
|
|
617
|
+
outputBusEvent({
|
|
618
|
+
event,
|
|
619
|
+
sessionID,
|
|
620
|
+
eventHandler,
|
|
621
|
+
onError: () => {
|
|
622
|
+
hasError = true;
|
|
623
|
+
},
|
|
624
|
+
});
|
|
720
625
|
});
|
|
721
626
|
|
|
722
627
|
// Create continuous stdin reader
|
package/src/cli/event-handler.js
CHANGED
|
@@ -26,105 +26,126 @@ export function createBusEventSubscription({
|
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
const unsub = Bus.subscribeAll((event) => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (part.type === 'step-start') {
|
|
38
|
-
eventHandler.output({
|
|
39
|
-
type: 'step_start',
|
|
40
|
-
timestamp: Date.now(),
|
|
41
|
-
sessionID,
|
|
42
|
-
part,
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (part.type === 'step-finish') {
|
|
47
|
-
eventHandler.output({
|
|
48
|
-
type: 'step_finish',
|
|
49
|
-
timestamp: Date.now(),
|
|
50
|
-
sessionID,
|
|
51
|
-
part,
|
|
52
|
-
});
|
|
53
|
-
}
|
|
29
|
+
outputBusEvent({
|
|
30
|
+
event,
|
|
31
|
+
sessionID,
|
|
32
|
+
eventHandler,
|
|
33
|
+
onError,
|
|
34
|
+
onIdle: idleResolve,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
54
37
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
type: 'text',
|
|
58
|
-
timestamp: Date.now(),
|
|
59
|
-
sessionID,
|
|
60
|
-
part,
|
|
61
|
-
});
|
|
62
|
-
}
|
|
38
|
+
return { unsub, idlePromise };
|
|
39
|
+
}
|
|
63
40
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
41
|
+
export function outputBusEvent({
|
|
42
|
+
event,
|
|
43
|
+
sessionID,
|
|
44
|
+
eventHandler,
|
|
45
|
+
onError,
|
|
46
|
+
onIdle,
|
|
47
|
+
}) {
|
|
48
|
+
// Output events in selected JSON format
|
|
49
|
+
if (event.type === 'message.part.updated') {
|
|
50
|
+
const part = event.properties.part;
|
|
51
|
+
if (part.sessionID !== sessionID) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
71
54
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
}
|
|
55
|
+
// Output different event types
|
|
56
|
+
if (part.type === 'step-start') {
|
|
57
|
+
eventHandler.output({
|
|
58
|
+
type: 'step_start',
|
|
59
|
+
timestamp: Date.now(),
|
|
60
|
+
sessionID,
|
|
61
|
+
part,
|
|
62
|
+
});
|
|
82
63
|
}
|
|
83
64
|
|
|
84
|
-
|
|
85
|
-
// The consumer replies with a `permission_response` frame over stdin which
|
|
86
|
-
// is routed to Permission.respond. No TUI is involved.
|
|
87
|
-
if (event.type === 'permission.updated') {
|
|
88
|
-
const permission = event.properties;
|
|
89
|
-
if (permission.sessionID !== sessionID) {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
65
|
+
if (part.type === 'step-finish') {
|
|
92
66
|
eventHandler.output({
|
|
93
|
-
type: '
|
|
67
|
+
type: 'step_finish',
|
|
94
68
|
timestamp: Date.now(),
|
|
95
69
|
sessionID,
|
|
96
|
-
|
|
97
|
-
callID: permission.callID,
|
|
98
|
-
tool: permission.type,
|
|
99
|
-
pattern: permission.pattern,
|
|
100
|
-
title: permission.title,
|
|
101
|
-
metadata: permission.metadata,
|
|
70
|
+
part,
|
|
102
71
|
});
|
|
103
72
|
}
|
|
104
73
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
74
|
+
if (part.type === 'text' && part.time?.end) {
|
|
75
|
+
eventHandler.output({
|
|
76
|
+
type: 'text',
|
|
77
|
+
timestamp: Date.now(),
|
|
78
|
+
sessionID,
|
|
79
|
+
part,
|
|
80
|
+
});
|
|
111
81
|
}
|
|
112
82
|
|
|
113
|
-
|
|
114
|
-
if (event.type === 'session.error') {
|
|
115
|
-
const props = event.properties;
|
|
116
|
-
if (props.sessionID !== sessionID || !props.error) {
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
onError();
|
|
83
|
+
if (part.type === 'tool') {
|
|
120
84
|
eventHandler.output({
|
|
121
|
-
type: '
|
|
85
|
+
type: 'tool_use',
|
|
122
86
|
timestamp: Date.now(),
|
|
123
87
|
sessionID,
|
|
124
|
-
|
|
88
|
+
part,
|
|
125
89
|
});
|
|
90
|
+
|
|
91
|
+
// If tool failed, also output an error event
|
|
92
|
+
if (part.state?.status === 'error') {
|
|
93
|
+
eventHandler.output({
|
|
94
|
+
type: 'error',
|
|
95
|
+
timestamp: Date.now(),
|
|
96
|
+
sessionID,
|
|
97
|
+
error: part.state.error || 'Tool execution failed',
|
|
98
|
+
});
|
|
99
|
+
}
|
|
126
100
|
}
|
|
127
|
-
}
|
|
101
|
+
}
|
|
128
102
|
|
|
129
|
-
|
|
103
|
+
// Emit a JSON permission request when a tool needs approval (issue #271).
|
|
104
|
+
// The consumer replies with a `permission_response` frame over stdin which
|
|
105
|
+
// is routed to Permission.respond. No TUI is involved.
|
|
106
|
+
if (event.type === 'permission.updated') {
|
|
107
|
+
const permission = event.properties;
|
|
108
|
+
if (permission.sessionID !== sessionID) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
eventHandler.output({
|
|
112
|
+
type: 'permission_request',
|
|
113
|
+
timestamp: Date.now(),
|
|
114
|
+
sessionID,
|
|
115
|
+
permissionID: permission.id,
|
|
116
|
+
callID: permission.callID,
|
|
117
|
+
tool: permission.type,
|
|
118
|
+
pattern: permission.pattern,
|
|
119
|
+
title: permission.title,
|
|
120
|
+
metadata: permission.metadata,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Handle session idle to know when to stop
|
|
125
|
+
if (
|
|
126
|
+
event.type === 'session.idle' &&
|
|
127
|
+
event.properties.sessionID === sessionID
|
|
128
|
+
) {
|
|
129
|
+
eventHandler.output({
|
|
130
|
+
type: 'session_idle',
|
|
131
|
+
timestamp: Date.now(),
|
|
132
|
+
sessionID,
|
|
133
|
+
});
|
|
134
|
+
onIdle?.();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Handle errors
|
|
138
|
+
if (event.type === 'session.error') {
|
|
139
|
+
const props = event.properties;
|
|
140
|
+
if (props.sessionID !== sessionID || !props.error) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
onError();
|
|
144
|
+
eventHandler.output({
|
|
145
|
+
type: 'error',
|
|
146
|
+
timestamp: Date.now(),
|
|
147
|
+
sessionID,
|
|
148
|
+
error: props.error,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
130
151
|
}
|
package/src/cli/model-config.js
CHANGED
|
@@ -302,7 +302,10 @@ async function resolveCompactionModelEntry(
|
|
|
302
302
|
|
|
303
303
|
// Short name resolution
|
|
304
304
|
const { Provider } = await import('../provider/provider.ts');
|
|
305
|
-
const resolved = await Provider.
|
|
305
|
+
const resolved = await Provider.resolveShortModelName(modelArg);
|
|
306
|
+
if (!resolved) {
|
|
307
|
+
throw new Error('ProviderModelNotFoundError');
|
|
308
|
+
}
|
|
306
309
|
return {
|
|
307
310
|
providerID: resolved.providerID,
|
|
308
311
|
modelID: resolved.modelID,
|
|
@@ -336,10 +339,17 @@ async function parseCompactionModelConfig(
|
|
|
336
339
|
|
|
337
340
|
// Check for --compaction-models (cascade) first — it overrides --compaction-model
|
|
338
341
|
const cliCompactionModelsArg = getCompactionModelsFromProcessArgv();
|
|
342
|
+
const defaultCompactionModels = getDefaultCompactionModels(defaultOptions);
|
|
343
|
+
const compactionModelsSource =
|
|
344
|
+
cliCompactionModelsArg ||
|
|
345
|
+
(argv['compaction-models'] &&
|
|
346
|
+
argv['compaction-models'] !== defaultCompactionModels)
|
|
347
|
+
? 'cli'
|
|
348
|
+
: 'default';
|
|
339
349
|
const compactionModelsArg =
|
|
340
350
|
cliCompactionModelsArg ??
|
|
341
351
|
argv['compaction-models'] ??
|
|
342
|
-
|
|
352
|
+
defaultCompactionModels;
|
|
343
353
|
|
|
344
354
|
// Parse the links notation sequence into an array of model names
|
|
345
355
|
const modelNames = parseLinksNotationSequence(compactionModelsArg);
|
|
@@ -360,8 +370,11 @@ async function parseCompactionModelConfig(
|
|
|
360
370
|
useSameModel: resolved.useSameModel,
|
|
361
371
|
});
|
|
362
372
|
} catch (err) {
|
|
363
|
-
|
|
364
|
-
|
|
373
|
+
const logSkip =
|
|
374
|
+
compactionModelsSource === 'default'
|
|
375
|
+
? Log.Default.debug
|
|
376
|
+
: Log.Default.warn;
|
|
377
|
+
logSkip(() => ({
|
|
365
378
|
message: 'skipping unresolvable compaction model in cascade',
|
|
366
379
|
model: name,
|
|
367
380
|
error: err?.message,
|
|
@@ -374,7 +387,7 @@ async function parseCompactionModelConfig(
|
|
|
374
387
|
models: compactionModels.map((m) =>
|
|
375
388
|
m.useSameModel ? 'same' : `${m.providerID}/${m.modelID}`
|
|
376
389
|
),
|
|
377
|
-
source:
|
|
390
|
+
source: compactionModelsSource,
|
|
378
391
|
}));
|
|
379
392
|
|
|
380
393
|
// Use the first model as the primary compaction model (for backward compatibility)
|
|
@@ -18,7 +18,13 @@ export type JsonStandard = 'opencode' | 'claude';
|
|
|
18
18
|
* OpenCode JSON event types
|
|
19
19
|
*/
|
|
20
20
|
export interface OpenCodeEvent {
|
|
21
|
-
type:
|
|
21
|
+
type:
|
|
22
|
+
| 'step_start'
|
|
23
|
+
| 'step_finish'
|
|
24
|
+
| 'text'
|
|
25
|
+
| 'tool_use'
|
|
26
|
+
| 'error'
|
|
27
|
+
| 'session_idle';
|
|
22
28
|
timestamp: number;
|
|
23
29
|
sessionID: string;
|
|
24
30
|
part?: Record<string, unknown>;
|
|
@@ -29,7 +35,7 @@ export interface OpenCodeEvent {
|
|
|
29
35
|
* Claude JSON event types (stream-json format)
|
|
30
36
|
*/
|
|
31
37
|
export interface ClaudeEvent {
|
|
32
|
-
type: 'init' | 'message' | 'tool_use' | 'tool_result' | 'result';
|
|
38
|
+
type: 'init' | 'message' | 'tool_use' | 'tool_result' | 'result' | 'idle';
|
|
33
39
|
timestamp?: string;
|
|
34
40
|
session_id?: string;
|
|
35
41
|
role?: 'assistant' | 'user';
|
|
@@ -126,6 +132,13 @@ export function convertOpenCodeToClaude(
|
|
|
126
132
|
duration_ms: event.timestamp - startTime,
|
|
127
133
|
};
|
|
128
134
|
|
|
135
|
+
case 'session_idle':
|
|
136
|
+
return {
|
|
137
|
+
type: 'idle',
|
|
138
|
+
timestamp,
|
|
139
|
+
session_id,
|
|
140
|
+
};
|
|
141
|
+
|
|
129
142
|
case 'error':
|
|
130
143
|
return {
|
|
131
144
|
type: 'result',
|