@link-assistant/agent 0.22.10 → 0.23.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/agent",
3
- "version": "0.22.10",
3
+ "version": "0.23.0",
4
4
  "description": "A minimal, public domain AI CLI agent compatible with OpenCode's JSON interface. Bun-only runtime.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -8,7 +8,7 @@ import { Instance } from '../project/instance.ts';
8
8
  import { Bus } from '../bus/index.ts';
9
9
  import { Session } from '../session/index.ts';
10
10
  import { SessionPrompt } from '../session/prompt.ts';
11
- import { createEventHandler } from '../json-standard/index.ts';
11
+ import { createEventHandler, serializeOutput } from '../json-standard/index.ts';
12
12
  import { createContinuousStdinReader } from './input-queue.js';
13
13
  import { Log } from '../util/log.ts';
14
14
  import { config } from '../config/config.ts';
@@ -40,6 +40,64 @@ export function getHasError() {
40
40
  // Logger for resume operations
41
41
  const log = Log.create({ service: 'resume' });
42
42
 
43
+ function getInputFormat(argv) {
44
+ return argv['input-format'] || argv.inputFormat || 'text';
45
+ }
46
+
47
+ function outputConsumedInput({
48
+ message,
49
+ jsonStandard,
50
+ sessionID,
51
+ compactJson,
52
+ }) {
53
+ const raw = message.raw || message.message || message.system || '';
54
+ if (
55
+ jsonStandard === 'claude' &&
56
+ message.kind === 'message' &&
57
+ message.format === 'stream-json'
58
+ ) {
59
+ process.stdout.write(
60
+ serializeOutput(
61
+ {
62
+ type: 'message',
63
+ timestamp: new Date().toISOString(),
64
+ session_id: sessionID,
65
+ role: 'user',
66
+ content: [{ type: 'text', text: message.message ?? '' }],
67
+ },
68
+ 'claude'
69
+ )
70
+ );
71
+ return;
72
+ }
73
+
74
+ if (jsonStandard === 'claude') {
75
+ return;
76
+ }
77
+
78
+ outputInput(
79
+ {
80
+ raw,
81
+ parsed: message.parsed || message,
82
+ format: message.format || 'text',
83
+ kind: message.kind || 'message',
84
+ },
85
+ compactJson
86
+ );
87
+ }
88
+
89
+ function outputInputParseError(error, line, compactJson) {
90
+ hasError = true;
91
+ outputError(
92
+ {
93
+ errorType: 'ValidationError',
94
+ message: error instanceof Error ? error.message : String(error),
95
+ raw: line,
96
+ },
97
+ compactJson
98
+ );
99
+ }
100
+
43
101
  /**
44
102
  * Resolve the session to use based on --resume, --continue, and --no-fork options.
45
103
  * Returns the session ID to use, handling forking as needed.
@@ -200,8 +258,12 @@ export async function runContinuousServerMode(
200
258
  ) {
201
259
  // Check both CLI flag and environment variable for compact JSON mode
202
260
  const compactJson = argv['compact-json'] === true || config.compactJson;
261
+ const inputFormat = getInputFormat(argv);
203
262
  const isInteractive = argv.interactive !== false;
204
- const autoMerge = argv['auto-merge-queued-messages'] !== false;
263
+ const autoMerge =
264
+ inputFormat === 'stream-json'
265
+ ? false
266
+ : argv['auto-merge-queued-messages'] !== false;
205
267
 
206
268
  // Start server like OpenCode does
207
269
  const server = Server.listen({ port: 0, hostname: '127.0.0.1' });
@@ -244,9 +306,23 @@ export async function runContinuousServerMode(
244
306
  // Track if we're currently processing a message
245
307
  let isProcessing = false;
246
308
  const pendingMessages = [];
309
+ let currentSystemMessage = systemMessage;
310
+ const currentAppendSystemMessage = appendSystemMessage;
247
311
 
248
312
  // Process messages from the queue
249
313
  const processMessage = async (message) => {
314
+ if (message.kind === 'interrupt') {
315
+ SessionPrompt.cancel(sessionID);
316
+ outputConsumedInput({ message, jsonStandard, sessionID, compactJson });
317
+ return;
318
+ }
319
+
320
+ if (message.kind === 'system') {
321
+ currentSystemMessage = message.system;
322
+ outputConsumedInput({ message, jsonStandard, sessionID, compactJson });
323
+ return;
324
+ }
325
+
250
326
  if (isProcessing) {
251
327
  pendingMessages.push(message);
252
328
  return;
@@ -255,16 +331,9 @@ export async function runContinuousServerMode(
255
331
  isProcessing = true;
256
332
 
257
333
  // Output input confirmation in JSON format
258
- outputInput(
259
- {
260
- raw: message.raw || message.message,
261
- parsed: message,
262
- format: message.format || 'text',
263
- },
264
- compactJson
265
- );
334
+ outputConsumedInput({ message, jsonStandard, sessionID, compactJson });
266
335
 
267
- const messageText = message.message || 'hi';
336
+ const messageText = message.message ?? 'hi';
268
337
  const parts = [{ type: 'text', text: messageText }];
269
338
 
270
339
  // Create a promise to wait for this message to complete
@@ -290,8 +359,8 @@ export async function runContinuousServerMode(
290
359
  parts,
291
360
  model: { providerID, modelID },
292
361
  compactionModel,
293
- system: systemMessage,
294
- appendSystem: appendSystemMessage,
362
+ system: currentSystemMessage,
363
+ appendSystem: currentAppendSystemMessage,
295
364
  temperature,
296
365
  }),
297
366
  }
@@ -377,11 +446,15 @@ export async function runContinuousServerMode(
377
446
 
378
447
  // Create continuous stdin reader
379
448
  stdinReader = createContinuousStdinReader({
380
- interactive: isInteractive,
449
+ interactive: inputFormat === 'stream-json' ? false : isInteractive,
381
450
  autoMerge,
451
+ inputFormat,
382
452
  onMessage: (message) => {
383
453
  processMessage(message);
384
454
  },
455
+ onError: (error, line) => {
456
+ outputInputParseError(error, line, compactJson);
457
+ },
385
458
  });
386
459
 
387
460
  // Wait for stdin to end (EOF or close)
@@ -454,8 +527,12 @@ export async function runContinuousDirectMode(
454
527
  ) {
455
528
  // Check both CLI flag and environment variable for compact JSON mode
456
529
  const compactJson = argv['compact-json'] === true || config.compactJson;
530
+ const inputFormat = getInputFormat(argv);
457
531
  const isInteractive = argv.interactive !== false;
458
- const autoMerge = argv['auto-merge-queued-messages'] !== false;
532
+ const autoMerge =
533
+ inputFormat === 'stream-json'
534
+ ? false
535
+ : argv['auto-merge-queued-messages'] !== false;
459
536
 
460
537
  let unsub = null;
461
538
  let stdinReader = null;
@@ -483,9 +560,23 @@ export async function runContinuousDirectMode(
483
560
  // Track if we're currently processing a message
484
561
  let isProcessing = false;
485
562
  const pendingMessages = [];
563
+ let currentSystemMessage = systemMessage;
564
+ const currentAppendSystemMessage = appendSystemMessage;
486
565
 
487
566
  // Process messages from the queue
488
567
  const processMessage = async (message) => {
568
+ if (message.kind === 'interrupt') {
569
+ SessionPrompt.cancel(sessionID);
570
+ outputConsumedInput({ message, jsonStandard, sessionID, compactJson });
571
+ return;
572
+ }
573
+
574
+ if (message.kind === 'system') {
575
+ currentSystemMessage = message.system;
576
+ outputConsumedInput({ message, jsonStandard, sessionID, compactJson });
577
+ return;
578
+ }
579
+
489
580
  if (isProcessing) {
490
581
  pendingMessages.push(message);
491
582
  return;
@@ -494,16 +585,9 @@ export async function runContinuousDirectMode(
494
585
  isProcessing = true;
495
586
 
496
587
  // Output input confirmation in JSON format
497
- outputInput(
498
- {
499
- raw: message.raw || message.message,
500
- parsed: message,
501
- format: message.format || 'text',
502
- },
503
- compactJson
504
- );
588
+ outputConsumedInput({ message, jsonStandard, sessionID, compactJson });
505
589
 
506
- const messageText = message.message || 'hi';
590
+ const messageText = message.message ?? 'hi';
507
591
  const parts = [{ type: 'text', text: messageText }];
508
592
 
509
593
  // Create a promise to wait for this message to complete
@@ -525,8 +609,8 @@ export async function runContinuousDirectMode(
525
609
  parts,
526
610
  model: { providerID, modelID },
527
611
  compactionModel,
528
- system: systemMessage,
529
- appendSystem: appendSystemMessage,
612
+ system: currentSystemMessage,
613
+ appendSystem: currentAppendSystemMessage,
530
614
  temperature,
531
615
  }).catch((error) => {
532
616
  hasError = true;
@@ -610,11 +694,15 @@ export async function runContinuousDirectMode(
610
694
 
611
695
  // Create continuous stdin reader
612
696
  stdinReader = createContinuousStdinReader({
613
- interactive: isInteractive,
697
+ interactive: inputFormat === 'stream-json' ? false : isInteractive,
614
698
  autoMerge,
699
+ inputFormat,
615
700
  onMessage: (message) => {
616
701
  processMessage(message);
617
702
  },
703
+ onError: (error, line) => {
704
+ outputInputParseError(error, line, compactJson);
705
+ },
618
706
  });
619
707
 
620
708
  // Wait for stdin to end (EOF or close)
@@ -5,11 +5,13 @@
5
5
  export class InputQueue {
6
6
  constructor(options = {}) {
7
7
  this.queue = [];
8
+ this.inputFormat = options.inputFormat || 'text';
8
9
  this.autoMerge = options.autoMerge !== false; // enabled by default
9
10
  this.mergeDelayMs = options.mergeDelayMs || 50; // delay to wait for more lines
10
11
  this.pendingLines = [];
11
12
  this.mergeTimer = null;
12
13
  this.onMessage = options.onMessage || (() => {});
14
+ this.onError = options.onError || (() => {});
13
15
  this.interactive = options.interactive !== false; // enabled by default
14
16
  }
15
17
 
@@ -24,6 +26,10 @@ export class InputQueue {
24
26
  return null;
25
27
  }
26
28
 
29
+ if (this.inputFormat === 'stream-json') {
30
+ return parseStreamJsonInput(trimmed);
31
+ }
32
+
27
33
  try {
28
34
  const parsed = JSON.parse(trimmed);
29
35
  // If it has a message field, use it directly
@@ -43,6 +49,11 @@ export class InputQueue {
43
49
  * @param {string} line - Input line
44
50
  */
45
51
  addLine(line) {
52
+ if (this.inputFormat === 'stream-json') {
53
+ this.queueLine(line);
54
+ return;
55
+ }
56
+
46
57
  if (!this.interactive && !line.trim().startsWith('{')) {
47
58
  // In non-interactive mode, only accept JSON
48
59
  return;
@@ -54,11 +65,7 @@ export class InputQueue {
54
65
  this.scheduleMerge();
55
66
  } else {
56
67
  // No merging, queue immediately
57
- const parsed = this.parseInput(line);
58
- if (parsed) {
59
- this.queue.push(parsed);
60
- this.notifyMessage(parsed);
61
- }
68
+ this.queueLine(line);
62
69
  }
63
70
  }
64
71
 
@@ -130,6 +137,33 @@ export class InputQueue {
130
137
  }
131
138
  }
132
139
 
140
+ /**
141
+ * Notify listener of an input parse error
142
+ * @param {Error} error - Parse error
143
+ * @param {string} line - Raw input line
144
+ */
145
+ notifyError(error, line) {
146
+ if (this.onError) {
147
+ this.onError(error, line);
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Parse and enqueue one complete input line
153
+ * @param {string} line - Raw input line
154
+ */
155
+ queueLine(line) {
156
+ try {
157
+ const parsed = this.parseInput(line);
158
+ if (parsed) {
159
+ this.queue.push(parsed);
160
+ this.notifyMessage(parsed);
161
+ }
162
+ } catch (error) {
163
+ this.notifyError(error, line);
164
+ }
165
+ }
166
+
133
167
  /**
134
168
  * Get queue size
135
169
  * @returns {number}
@@ -139,6 +173,136 @@ export class InputQueue {
139
173
  }
140
174
  }
141
175
 
176
+ function extractContentText(content) {
177
+ if (typeof content === 'string') {
178
+ return content;
179
+ }
180
+
181
+ if (Array.isArray(content)) {
182
+ const parts = [];
183
+ for (const part of content) {
184
+ if (typeof part === 'string') {
185
+ parts.push(part);
186
+ } else if (part && typeof part === 'object') {
187
+ if (typeof part.text === 'string') {
188
+ parts.push(part.text);
189
+ } else if (typeof part.content === 'string') {
190
+ parts.push(part.content);
191
+ }
192
+ }
193
+ }
194
+ return parts.length > 0 ? parts.join('\n') : null;
195
+ }
196
+
197
+ if (content && typeof content === 'object') {
198
+ if (typeof content.text === 'string') {
199
+ return content.text;
200
+ }
201
+ if ('content' in content) {
202
+ return extractContentText(content.content);
203
+ }
204
+ }
205
+
206
+ return null;
207
+ }
208
+
209
+ function extractFrameText(frame) {
210
+ if (typeof frame.message === 'string') {
211
+ return frame.message;
212
+ }
213
+
214
+ if (frame.message && typeof frame.message === 'object') {
215
+ const messageText = extractContentText(frame.message.content);
216
+ if (messageText !== null) {
217
+ return messageText;
218
+ }
219
+ if (typeof frame.message.text === 'string') {
220
+ return frame.message.text;
221
+ }
222
+ }
223
+
224
+ const contentText = extractContentText(frame.content);
225
+ if (contentText !== null) {
226
+ return contentText;
227
+ }
228
+
229
+ if (typeof frame.text === 'string') {
230
+ return frame.text;
231
+ }
232
+
233
+ return null;
234
+ }
235
+
236
+ /**
237
+ * Parse one Claude-compatible stream-json input frame.
238
+ * @param {string} input - One JSONL frame
239
+ * @returns {object} Normalized queue message
240
+ */
241
+ export function parseStreamJsonInput(input) {
242
+ let frame;
243
+ try {
244
+ frame = JSON.parse(input);
245
+ } catch (error) {
246
+ throw new Error(
247
+ `Invalid stream-json input frame: ${
248
+ error instanceof Error ? error.message : String(error)
249
+ }`
250
+ );
251
+ }
252
+
253
+ if (!frame || typeof frame !== 'object' || Array.isArray(frame)) {
254
+ throw new Error('Invalid stream-json input frame: expected JSON object');
255
+ }
256
+
257
+ const type = frame.type;
258
+
259
+ if (type === 'interrupt') {
260
+ return {
261
+ kind: 'interrupt',
262
+ raw: input,
263
+ parsed: frame,
264
+ format: 'stream-json',
265
+ inputType: type,
266
+ };
267
+ }
268
+
269
+ if (type === 'system') {
270
+ const system = extractFrameText(frame);
271
+ if (system === null) {
272
+ throw new Error(
273
+ 'Invalid stream-json system frame: expected content text'
274
+ );
275
+ }
276
+ return {
277
+ kind: 'system',
278
+ system,
279
+ raw: input,
280
+ parsed: frame,
281
+ format: 'stream-json',
282
+ inputType: type,
283
+ };
284
+ }
285
+
286
+ if (type === 'user' || type === 'user_prompt' || type === undefined) {
287
+ const message = extractFrameText(frame);
288
+ if (message === null) {
289
+ throw new Error(
290
+ 'Invalid stream-json user frame: expected message content text'
291
+ );
292
+ }
293
+ return {
294
+ kind: 'message',
295
+ message,
296
+ raw: input,
297
+ parsed: frame,
298
+ format: 'stream-json',
299
+ inputType: type || 'message',
300
+ };
301
+ }
302
+
303
+ throw new Error(`Unsupported stream-json input frame type: ${String(type)}`);
304
+ }
305
+
142
306
  /**
143
307
  * Create a continuous stdin reader that queues input lines
144
308
  * @param {object} options - Options for the reader
package/src/cli/output.ts CHANGED
@@ -232,7 +232,7 @@ export function outputInput(
232
232
  input: {
233
233
  raw: string;
234
234
  parsed?: unknown;
235
- format?: 'json' | 'text';
235
+ format?: 'json' | 'text' | 'stream-json';
236
236
  [key: string]: unknown;
237
237
  },
238
238
  compact?: boolean
@@ -29,6 +29,19 @@ export function buildRunOptions(yargs, defaultOptions = {}) {
29
29
  default: 'opencode',
30
30
  choices: ['opencode', 'claude'],
31
31
  })
32
+ .option('output-format', {
33
+ type: 'string',
34
+ description:
35
+ 'Claude-compatible output format alias: "json" (OpenCode JSON) or "stream-json" (Claude NDJSON)',
36
+ choices: ['json', 'stream-json'],
37
+ })
38
+ .option('input-format', {
39
+ type: 'string',
40
+ description:
41
+ 'Input format: "text" (default) or Claude-compatible "stream-json" JSONL frames',
42
+ default: 'text',
43
+ choices: ['text', 'stream-json'],
44
+ })
32
45
  .option('system-message', {
33
46
  type: 'string',
34
47
  description: 'Full override of the system message',
@@ -135,6 +148,13 @@ export function buildRunOptions(yargs, defaultOptions = {}) {
135
148
  argv['no-fork'] = true;
136
149
  argv.noFork = true;
137
150
  }
151
+ if (argv.outputFormat === 'stream-json') {
152
+ argv['json-standard'] = 'claude';
153
+ argv.jsonStandard = 'claude';
154
+ } else if (argv.outputFormat === 'json') {
155
+ argv['json-standard'] = 'opencode';
156
+ argv.jsonStandard = 'opencode';
157
+ }
138
158
  }, true)
139
159
  : parser;
140
160
 
package/src/index.js CHANGED
@@ -36,6 +36,7 @@ import {
36
36
  runContinuousDirectMode,
37
37
  resolveResumeSession,
38
38
  } from './cli/continuous-mode.js';
39
+ import { parseStreamJsonInput } from './cli/input-queue.js';
39
40
  import { createBusEventSubscription } from './cli/event-handler.js';
40
41
  import {
41
42
  outputStatus,
@@ -259,6 +260,19 @@ async function readSystemMessages(argv) {
259
260
  return { systemMessage, appendSystemMessage };
260
261
  }
261
262
 
263
+ function getInputFormat(argv) {
264
+ return argv['input-format'] || argv.inputFormat || 'text';
265
+ }
266
+
267
+ function getAcceptedInputFormats(inputFormat, isInteractive) {
268
+ if (inputFormat === 'stream-json') {
269
+ return ['Claude stream-json user frames'];
270
+ }
271
+ return isInteractive
272
+ ? ['JSON object with "message" field', 'Plain text']
273
+ : ['JSON object with "message" field'];
274
+ }
275
+
262
276
  async function runAgentMode(argv, request) {
263
277
  // Log version and command info in verbose mode using lazy logging
264
278
  Log.Default.info(() => ({
@@ -663,8 +677,15 @@ async function main() {
663
677
  // Check if stdin is a TTY (interactive terminal)
664
678
  if (process.stdin.isTTY) {
665
679
  // Enter interactive terminal mode with continuous listening
666
- const isInteractive = argv.interactive !== false;
667
- const autoMerge = argv['auto-merge-queued-messages'] !== false;
680
+ const inputFormat = getInputFormat(argv);
681
+ const isInteractive =
682
+ inputFormat === 'stream-json'
683
+ ? false
684
+ : argv.interactive !== false;
685
+ const autoMerge =
686
+ inputFormat === 'stream-json'
687
+ ? false
688
+ : argv['auto-merge-queued-messages'] !== false;
668
689
  const alwaysAcceptStdin = argv['always-accept-stdin'] !== false;
669
690
 
670
691
  // Exit if --no-always-accept-stdin is set (single message mode not supported in TTY)
@@ -688,10 +709,12 @@ async function main() {
688
709
  message:
689
710
  'Agent CLI in interactive terminal mode. Type your message and press Enter.',
690
711
  hint: 'Press CTRL+C to exit. Use --help for options.',
691
- acceptedFormats: isInteractive
692
- ? ['JSON object with "message" field', 'Plain text']
693
- : ['JSON object with "message" field'],
712
+ acceptedFormats: getAcceptedInputFormats(
713
+ inputFormat,
714
+ isInteractive
715
+ ),
694
716
  options: {
717
+ inputFormat,
695
718
  interactive: isInteractive,
696
719
  autoMergeQueuedMessages: autoMerge,
697
720
  alwaysAcceptStdin,
@@ -707,8 +730,13 @@ async function main() {
707
730
  }
708
731
 
709
732
  // stdin is piped - enter stdin listening mode
710
- const isInteractive = argv.interactive !== false;
711
- const autoMerge = argv['auto-merge-queued-messages'] !== false;
733
+ const inputFormat = getInputFormat(argv);
734
+ const isInteractive =
735
+ inputFormat === 'stream-json' ? false : argv.interactive !== false;
736
+ const autoMerge =
737
+ inputFormat === 'stream-json'
738
+ ? false
739
+ : argv['auto-merge-queued-messages'] !== false;
712
740
  const alwaysAcceptStdin = argv['always-accept-stdin'] !== false;
713
741
 
714
742
  outputStatus(
@@ -719,10 +747,12 @@ async function main() {
719
747
  ? 'Agent CLI in continuous listening mode. Accepts JSON and plain text input.'
720
748
  : 'Agent CLI in single-message mode. Accepts JSON and plain text input.',
721
749
  hint: 'Press CTRL+C to exit. Use --help for options.',
722
- acceptedFormats: isInteractive
723
- ? ['JSON object with "message" field', 'Plain text']
724
- : ['JSON object with "message" field'],
750
+ acceptedFormats: getAcceptedInputFormats(
751
+ inputFormat,
752
+ isInteractive
753
+ ),
725
754
  options: {
755
+ inputFormat,
726
756
  interactive: isInteractive,
727
757
  autoMergeQueuedMessages: autoMerge,
728
758
  alwaysAcceptStdin,
@@ -757,27 +787,52 @@ async function main() {
757
787
 
758
788
  // Try to parse as JSON, if it fails treat it as plain text message
759
789
  let request;
760
- try {
761
- request = JSON.parse(trimmedInput);
762
- } catch (_e) {
763
- // Not JSON
764
- if (!isInteractive) {
765
- // In non-interactive mode, only accept JSON
790
+ if (inputFormat === 'stream-json') {
791
+ try {
792
+ const firstFrame = trimmedInput
793
+ .split(/\r?\n/)
794
+ .find((line) => line.trim());
795
+ request = parseStreamJsonInput(firstFrame || '');
796
+ if (request.kind !== 'message') {
797
+ throw new Error(
798
+ `Expected stream-json user frame, received ${request.kind}`
799
+ );
800
+ }
801
+ } catch (error) {
766
802
  outputError(
767
803
  {
768
804
  errorType: 'ValidationError',
769
805
  message:
770
- 'Invalid JSON input. In non-interactive mode (--no-interactive), only JSON input is accepted.',
771
- hint: 'Use --interactive to accept plain text, or provide valid JSON: {"message": "your text"}',
806
+ error instanceof Error ? error.message : String(error),
807
+ hint: 'Provide one Claude stream-json user frame per line.',
772
808
  },
773
809
  compactJson
774
810
  );
775
811
  process.exit(1);
776
812
  }
777
- // In interactive mode, treat as plain text message
778
- request = {
779
- message: trimmedInput,
780
- };
813
+ } else {
814
+ try {
815
+ request = JSON.parse(trimmedInput);
816
+ } catch (_e) {
817
+ // Not JSON
818
+ if (!isInteractive) {
819
+ // In non-interactive mode, only accept JSON
820
+ outputError(
821
+ {
822
+ errorType: 'ValidationError',
823
+ message:
824
+ 'Invalid JSON input. In non-interactive mode (--no-interactive), only JSON input is accepted.',
825
+ hint: 'Use --interactive to accept plain text, or provide valid JSON: {"message": "your text"}',
826
+ },
827
+ compactJson
828
+ );
829
+ process.exit(1);
830
+ }
831
+ // In interactive mode, treat as plain text message
832
+ request = {
833
+ message: trimmedInput,
834
+ };
835
+ }
781
836
  }
782
837
 
783
838
  // Output input confirmation in JSON format
@@ -785,7 +840,12 @@ async function main() {
785
840
  {
786
841
  raw: trimmedInput,
787
842
  parsed: request,
788
- format: isInteractive ? 'text' : 'json',
843
+ format:
844
+ inputFormat === 'stream-json'
845
+ ? 'stream-json'
846
+ : isInteractive
847
+ ? 'text'
848
+ : 'json',
789
849
  },
790
850
  compactJson
791
851
  );