mini-coder 0.5.14 → 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.
Files changed (70) hide show
  1. package/README.md +25 -109
  2. package/bin/mc.ts +8 -11
  3. package/bun.lock +79 -269
  4. package/package.json +17 -22
  5. package/src/agent.ts +237 -1403
  6. package/src/args.ts +289 -0
  7. package/src/headless.ts +43 -358
  8. package/src/index.ts +29 -1016
  9. package/src/oauth.ts +117 -0
  10. package/src/prompt.ts +227 -284
  11. package/src/session.ts +55 -1306
  12. package/src/shared.ts +117 -38
  13. package/src/tool-bash.ts +110 -0
  14. package/src/tool-edit.ts +133 -0
  15. package/src/tool-task.ts +114 -0
  16. package/src/tui-components.ts +150 -0
  17. package/src/tui-conversation.ts +262 -0
  18. package/src/tui-editor.ts +29 -0
  19. package/src/tui-overlay.ts +403 -0
  20. package/src/tui.ts +236 -0
  21. package/src/types.ts +160 -0
  22. package/tsconfig.json +17 -0
  23. package/BENCHMARK.md +0 -107
  24. package/LICENSE +0 -9
  25. package/PROGRESS.md +0 -5
  26. package/assets/icon-1-minimal.svg +0 -31
  27. package/assets/icon-2-dark-terminal.svg +0 -48
  28. package/assets/icon-3-gradient-modern.svg +0 -45
  29. package/assets/icon-4-filled-bold.svg +0 -54
  30. package/assets/icon-5-community-badge.svg +0 -63
  31. package/assets/mc-claude-smart.png +0 -0
  32. package/assets/mc-gpt-smart.png +0 -0
  33. package/assets/preview-0-5-0.png +0 -0
  34. package/assets/preview.gif +0 -0
  35. package/benchmark-baseline.sh +0 -15
  36. package/benchmark-loop.sh +0 -19
  37. package/skills-lock.json +0 -15
  38. package/src/assistant-output.ts +0 -73
  39. package/src/cli.ts +0 -134
  40. package/src/delegation.ts +0 -238
  41. package/src/errors.ts +0 -15
  42. package/src/git.ts +0 -247
  43. package/src/input.ts +0 -168
  44. package/src/mcp.ts +0 -609
  45. package/src/paths.ts +0 -37
  46. package/src/session-message.ts +0 -385
  47. package/src/settings.ts +0 -449
  48. package/src/skills.ts +0 -271
  49. package/src/submit.ts +0 -376
  50. package/src/text.ts +0 -71
  51. package/src/theme.ts +0 -330
  52. package/src/tool-common.ts +0 -93
  53. package/src/tool-delegate.ts +0 -125
  54. package/src/tool-grep.ts +0 -606
  55. package/src/tool-read.ts +0 -313
  56. package/src/tool-shell.ts +0 -1051
  57. package/src/tools.ts +0 -1179
  58. package/src/ui/agent.ts +0 -320
  59. package/src/ui/commands.test.ts +0 -957
  60. package/src/ui/commands.ts +0 -848
  61. package/src/ui/conversation.test.ts +0 -585
  62. package/src/ui/conversation.ts +0 -1836
  63. package/src/ui/help.ts +0 -158
  64. package/src/ui/input.test.ts +0 -64
  65. package/src/ui/input.ts +0 -138
  66. package/src/ui/overlay.ts +0 -59
  67. package/src/ui/runtime.ts +0 -69
  68. package/src/ui/status.ts +0 -220
  69. package/src/ui.ts +0 -1190
  70. package/src/version.ts +0 -48
package/src/args.ts ADDED
@@ -0,0 +1,289 @@
1
+ import { mkdir } from "node:fs/promises";
2
+ import {
3
+ type Api,
4
+ getModels,
5
+ getProviders,
6
+ type Model,
7
+ type ThinkingLevel,
8
+ } from "@mariozechner/pi-ai";
9
+ import { Value } from "typebox/value";
10
+ import { getAvailableProviders, isOAuthProvider, loginOAuth } from "./oauth";
11
+ import { DATA_DIR, SETTINGS_PATH } from "./shared.ts";
12
+ import {
13
+ type CliOptions,
14
+ CliOptionsSchema,
15
+ type Settings,
16
+ SettingsSchema,
17
+ } from "./types.ts";
18
+
19
+ const DEFAULT_PROVIDER = "openai-codex";
20
+ const DEFAULT_MODEL_ID = "gpt-5.5";
21
+ const DEFAULT_EFFORT: ThinkingLevel = "xhigh";
22
+
23
+ function formatValidationError(
24
+ label: string,
25
+ error: ReturnType<typeof Value.Errors>[number] | undefined,
26
+ ) {
27
+ if (!error) {
28
+ return `Invalid ${label}`;
29
+ }
30
+
31
+ const path = error.instancePath || "/";
32
+ return `Invalid ${label}: ${path} ${error.message}`;
33
+ }
34
+
35
+ function parseSettings(value: unknown, label: string): Settings {
36
+ if (Value.Check(SettingsSchema, value)) {
37
+ return value;
38
+ }
39
+
40
+ throw new Error(
41
+ formatValidationError(label, Value.Errors(SettingsSchema, value)[0]),
42
+ );
43
+ }
44
+
45
+ function parseCliOptions(value: unknown): CliOptions {
46
+ if (Value.Check(CliOptionsSchema, value)) {
47
+ return value;
48
+ }
49
+
50
+ throw new Error(
51
+ formatValidationError(
52
+ "CLI options",
53
+ Value.Errors(CliOptionsSchema, value)[0],
54
+ ),
55
+ );
56
+ }
57
+
58
+ async function getSettings(): Promise<Settings | undefined> {
59
+ const file = Bun.file(SETTINGS_PATH);
60
+
61
+ if (!(await file.exists())) {
62
+ return;
63
+ }
64
+
65
+ const jsonText = await file.text();
66
+ const settings = JSON.parse(jsonText) as unknown;
67
+
68
+ return parseSettings(settings, "settings");
69
+ }
70
+
71
+ export async function saveSettings(s: Settings) {
72
+ await mkdir(DATA_DIR, { recursive: true });
73
+ const file = Bun.file(SETTINGS_PATH);
74
+ await Bun.write(file, JSON.stringify(s, null, 4));
75
+ }
76
+
77
+ function requireValue(argv: string[], index: number, flag: string) {
78
+ const value = argv[index + 1];
79
+
80
+ if (!value || value.startsWith("-")) {
81
+ throw new Error(`${flag} requires a value`);
82
+ }
83
+
84
+ return value;
85
+ }
86
+
87
+ function findModelConfig(
88
+ modelId: string,
89
+ provider: string,
90
+ customProviders?: Model<Api>[],
91
+ ) {
92
+ const knownProviders = getProviders() as string[];
93
+ if (knownProviders.includes(provider)) {
94
+ const models = getModels(provider as any);
95
+ if (models.length === 0) throw new Error("Provider has no models");
96
+ return models.find((m) => m.id === modelId);
97
+ }
98
+ return customProviders?.find(
99
+ (m) => m.provider === provider && m.id === modelId,
100
+ );
101
+ }
102
+
103
+ function getFirstModelConfig(
104
+ provider: string,
105
+ customProviders?: Model<Api>[],
106
+ ): Model<Api> {
107
+ const knownProviders = getProviders() as string[];
108
+ if (knownProviders.includes(provider)) {
109
+ const models = getModels(provider as any);
110
+ if (models.length > 0) return models[0];
111
+ }
112
+ const custom = customProviders?.find((m) => m.provider === provider);
113
+ if (custom) return custom;
114
+ throw new Error("Provider has no models");
115
+ }
116
+
117
+ function getFallbackModel(
118
+ provider: string,
119
+ explicitModel: boolean,
120
+ providerChanged: boolean,
121
+ customProviders?: Model<Api>[],
122
+ ): Model<Api> {
123
+ if (explicitModel) {
124
+ throw new Error("Model not found");
125
+ }
126
+
127
+ if (!providerChanged) {
128
+ throw new Error("Model not found");
129
+ }
130
+
131
+ return getFirstModelConfig(provider, customProviders);
132
+ }
133
+
134
+ export async function handleArgv(argv: string[]): Promise<CliOptions> {
135
+ const settings = await getSettings();
136
+ const customProviders = settings?.customProviders ?? [];
137
+ const builtInProviders = await getAvailableProviders();
138
+ const availableProviders = [
139
+ ...new Set([
140
+ ...builtInProviders,
141
+ ...customProviders.map((cp) => cp.provider),
142
+ ]),
143
+ ];
144
+ const defaultProvider = availableProviders[0] ?? DEFAULT_PROVIDER;
145
+ const settingsProvider = settings?.provider ?? defaultProvider;
146
+ const settingsModelId =
147
+ settings?.model ??
148
+ (settingsProvider === DEFAULT_PROVIDER
149
+ ? DEFAULT_MODEL_ID
150
+ : getFirstModelConfig(settingsProvider, customProviders).id);
151
+ const settingsEffort = settings?.effort ?? DEFAULT_EFFORT;
152
+ let provider: string = settingsProvider;
153
+ let modelId = settingsModelId;
154
+ let effort: string = settingsEffort;
155
+ let prompt: string | undefined;
156
+ let providerChanged = false;
157
+ let explicitProvider = false;
158
+ let explicitModel = false;
159
+
160
+ for (let i = 0; i < argv.length; i++) {
161
+ const flag = argv[i];
162
+
163
+ // Settings
164
+ if (flag === "--provider") {
165
+ provider = parseSettings(
166
+ {
167
+ provider: requireValue(argv, i, flag),
168
+ model: modelId,
169
+ effort,
170
+ },
171
+ "CLI settings",
172
+ ).provider;
173
+ providerChanged = provider !== settingsProvider;
174
+ explicitProvider = true;
175
+ i++;
176
+ continue;
177
+ }
178
+
179
+ if (flag === "--model") {
180
+ modelId = requireValue(argv, i, flag);
181
+ explicitModel = true;
182
+ i++;
183
+ continue;
184
+ }
185
+
186
+ if (flag === "--effort") {
187
+ effort = parseSettings(
188
+ {
189
+ provider,
190
+ model: modelId,
191
+ effort: requireValue(argv, i, flag),
192
+ },
193
+ "CLI settings",
194
+ ).effort;
195
+ i++;
196
+ continue;
197
+ }
198
+
199
+ // headless api
200
+ if (flag === "--prompt" || flag === "-p") {
201
+ prompt = requireValue(argv, i, flag);
202
+ i++;
203
+ continue;
204
+ }
205
+
206
+ if (flag === "--login" || flag === "-l") {
207
+ await loginOAuth(requireValue(argv, i, flag));
208
+ // Rebuild available providers after login
209
+ const refreshedProviders = await getAvailableProviders();
210
+ availableProviders.splice(
211
+ 0,
212
+ availableProviders.length,
213
+ ...new Set([
214
+ ...refreshedProviders,
215
+ ...customProviders.map((cp) => cp.provider),
216
+ ]),
217
+ );
218
+ i++;
219
+ }
220
+ }
221
+
222
+ let cliSettings = parseSettings(
223
+ {
224
+ provider,
225
+ model: modelId,
226
+ effort,
227
+ },
228
+ "CLI settings",
229
+ );
230
+ const providerAvailable = availableProviders.includes(cliSettings.provider);
231
+
232
+ if (explicitProvider) {
233
+ if (!providerAvailable && !isOAuthProvider(cliSettings.provider)) {
234
+ throw new Error(
235
+ `Provider "${cliSettings.provider}" is not logged in and no API key was found`,
236
+ );
237
+ }
238
+ } else if (!providerAvailable && availableProviders.length) {
239
+ const fallbackProvider = availableProviders[0];
240
+ provider = fallbackProvider;
241
+ providerChanged = provider !== settingsProvider;
242
+ if (!explicitModel) {
243
+ modelId = getFirstModelConfig(fallbackProvider, customProviders).id;
244
+ }
245
+
246
+ cliSettings = parseSettings(
247
+ {
248
+ provider,
249
+ model: modelId,
250
+ effort,
251
+ },
252
+ "CLI settings",
253
+ );
254
+ }
255
+
256
+ const selectedModel = findModelConfig(
257
+ cliSettings.model,
258
+ cliSettings.provider,
259
+ customProviders,
260
+ );
261
+ const model =
262
+ selectedModel ??
263
+ getFallbackModel(
264
+ cliSettings.provider,
265
+ explicitModel,
266
+ providerChanged,
267
+ customProviders,
268
+ );
269
+
270
+ const options = parseCliOptions({
271
+ provider: cliSettings.provider,
272
+ model,
273
+ effort: cliSettings.effort,
274
+ prompt,
275
+ customProviders,
276
+ });
277
+ const nextSettings = parseSettings(
278
+ {
279
+ provider: options.provider,
280
+ model: options.model.id,
281
+ effort: options.effort,
282
+ customProviders,
283
+ },
284
+ "settings",
285
+ );
286
+
287
+ await saveSettings(nextSettings);
288
+ return options;
289
+ }
package/src/headless.ts CHANGED
@@ -1,365 +1,50 @@
1
- /**
2
- * Headless one-shot execution.
3
- *
4
- * @module
5
- */
6
-
7
- import type { AssistantMessage, UserMessage } from "@mariozechner/pi-ai";
8
- import type { AgentEvent } from "./agent.ts";
9
- import {
10
- extractAssistantActivitySnippet,
11
- extractAssistantErrorText,
12
- extractAssistantText,
13
- } from "./assistant-output.ts";
14
- import type { AppState } from "./index.ts";
15
- import {
16
- resolveRawInput,
17
- type SubmitTurnHooks,
18
- submitResolvedInput,
19
- } from "./submit.ts";
20
-
21
- // ---------------------------------------------------------------------------
22
- // Types
23
- // ---------------------------------------------------------------------------
24
-
25
- type HeadlessStopReason = "stop" | "length" | "error" | "aborted";
26
-
27
- /** Options for a headless NDJSON run. */
28
- export interface HeadlessRunOptions {
29
- /** Optional line writer for completed NDJSON event output. */
30
- writeLine?: (line: string) => void | Promise<void>;
31
- }
32
-
33
- /** Options for a headless final-text run. */
34
- export interface HeadlessTextRunOptions {
35
- /** Optional writer for lightweight assistant-activity snippets. */
36
- writeActivity?: (text: string) => void | Promise<void>;
37
- /** Optional writer for the final assistant text output. */
38
- writeText?: (text: string) => void | Promise<void>;
39
- }
40
-
41
- interface HeadlessOutputController {
42
- /** Queue text for one output stream with broken-pipe handling. */
43
- write(text: string): void;
44
- /** Attach error handlers for the active run. */
45
- attach(): void;
46
- /** Remove error handlers after the run. */
47
- detach(): void;
48
- /** Wait for queued writes and resolve the final stop reason. */
49
- finalize(stopReason: HeadlessStopReason): Promise<HeadlessStopReason>;
50
- }
51
-
52
- interface HeadlessProcessStream {
53
- /** Register an output-stream error handler. */
54
- on(event: "error", listener: (error: unknown) => void): void;
55
- /** Remove an output-stream error handler. */
56
- off(event: "error", listener: (error: unknown) => void): void;
57
- /** Write a text chunk to the stream. */
58
- write(text: string, callback?: () => void): boolean;
59
- }
60
-
61
- // ---------------------------------------------------------------------------
62
- // Helpers
63
- // ---------------------------------------------------------------------------
64
-
65
- function defaultWrite(
66
- stream: HeadlessProcessStream,
67
- text: string,
68
- ): Promise<void> {
69
- return new Promise((resolve, reject) => {
70
- let settled = false;
71
-
72
- const cleanup = (): void => {
73
- stream.off("error", handleError);
74
- };
75
-
76
- const settle = (callback: () => void): void => {
77
- if (settled) {
78
- return;
79
- }
80
- settled = true;
81
- cleanup();
82
- callback();
83
- };
84
-
85
- const handleError = (error: unknown): void => {
86
- settle(() => {
87
- reject(error);
88
- });
89
- };
90
-
91
- stream.on("error", handleError);
92
- try {
93
- stream.write(text, () => {
94
- settle(resolve);
95
- });
96
- } catch (error) {
97
- settle(() => {
98
- reject(error);
99
- });
100
- }
101
- });
102
- }
103
-
104
- function isBrokenPipeError(error: unknown): boolean {
105
- return (
106
- typeof error === "object" &&
107
- error !== null &&
108
- (("code" in error && error.code === "EPIPE") ||
109
- ("message" in error &&
110
- typeof error.message === "string" &&
111
- error.message.includes("broken pipe")))
112
- );
113
- }
114
-
115
- function createSigintHandler(state: AppState): () => void {
116
- return () => {
117
- state.abortController?.abort();
118
- };
119
- }
120
-
121
- function buildCommandError(command: string): Error {
122
- return new Error(
123
- `Headless mode does not support slash commands: /${command}`,
124
- );
125
- }
126
-
127
- function resolveHeadlessContent(
128
- state: AppState,
129
- rawInput: string,
130
- ): UserMessage["content"] {
131
- const resolved = resolveRawInput(rawInput, state);
132
- switch (resolved.type) {
133
- case "empty":
134
- throw new Error("Headless input is empty.");
135
- case "error":
136
- throw new Error(resolved.message);
137
- case "command":
138
- throw buildCommandError(resolved.command);
139
- case "message":
140
- return resolved.content;
141
- }
142
- }
143
-
144
- function shouldWriteHeadlessJsonEvent(event: AgentEvent): boolean {
145
- switch (event.type) {
146
- case "user_message":
147
- case "assistant_message":
148
- case "tool_result":
149
- case "done":
150
- case "error":
151
- case "aborted":
152
- return true;
153
- default:
154
- return false;
155
- }
156
- }
157
-
158
- function createHeadlessOutputController(
159
- state: AppState,
160
- stream: HeadlessProcessStream,
161
- writeImpl: (text: string) => void | Promise<void>,
162
- options?: {
163
- attachSigint?: boolean;
164
- },
165
- ): HeadlessOutputController {
166
- let brokenPipe = false;
167
- let outputError: unknown = null;
168
- let pendingWrite = Promise.resolve();
169
- const sigintHandler = createSigintHandler(state);
170
- const attachSigint = options?.attachSigint ?? true;
171
-
172
- const stopForBrokenPipe = (): void => {
173
- if (brokenPipe) {
174
- return;
175
- }
176
- brokenPipe = true;
177
- state.abortController?.abort();
178
- };
179
-
180
- const failOutput = (error: unknown): void => {
181
- if (isBrokenPipeError(error)) {
182
- stopForBrokenPipe();
183
- return;
184
- }
185
-
186
- outputError = outputError ?? error;
187
- state.abortController?.abort();
188
- };
189
-
190
- const streamErrorHandler = (error: unknown): void => {
191
- failOutput(error);
192
- };
193
-
194
- return {
195
- write(text) {
196
- if (brokenPipe || outputError) {
197
- return;
198
- }
199
-
200
- pendingWrite = pendingWrite.then(async () => {
201
- if (brokenPipe || outputError) {
202
- return;
203
- }
204
-
205
- try {
206
- await writeImpl(text);
207
- } catch (error) {
208
- failOutput(error);
209
- }
210
- });
211
- },
212
- attach() {
213
- stream.on("error", streamErrorHandler);
214
- if (attachSigint) {
215
- process.on("SIGINT", sigintHandler);
216
- }
1
+ import type { Message } from "@mariozechner/pi-ai";
2
+ import { streamAgent } from "./agent";
3
+ import { buildSystemPrompt, injectEnvReminder, MAIN_PROMPT } from "./prompt";
4
+ import { bash, runBashTool } from "./tool-bash";
5
+ import { edit, runEditTool } from "./tool-edit";
6
+ import { runTaskTool, task } from "./tool-task";
7
+ import type { AgentContex, CliOptions, ToolAndRunner } from "./types";
8
+
9
+ export async function streamHeadless(
10
+ options: CliOptions,
11
+ leave: (s?: string) => void,
12
+ ) {
13
+ const tools: ToolAndRunner[] = [
14
+ { tool: bash, runner: runBashTool },
15
+ { tool: edit, runner: runEditTool },
16
+ {
17
+ tool: task,
18
+ runner: (args) => runTaskTool(options, args),
217
19
  },
218
- detach() {
219
- stream.off("error", streamErrorHandler);
220
- if (attachSigint) {
221
- process.off("SIGINT", sigintHandler);
222
- }
223
- },
224
- async finalize(stopReason) {
225
- await pendingWrite;
226
- if (outputError) {
227
- throw outputError;
228
- }
229
- return brokenPipe ? "stop" : stopReason;
230
- },
231
- };
232
- }
233
-
234
- /**
235
- * Run a single headless prompt to completion and stream completed NDJSON events.
236
- *
237
- * The raw input is parsed with the same rules as interactive input. Slash
238
- * commands are rejected in headless mode. Persisted messages and terminal
239
- * events are written as one JSON object per line; streaming delta/progress
240
- * events are omitted.
241
- *
242
- * @param state - Mutable application state for the run.
243
- * @param rawInput - Exact raw prompt text supplied by the user.
244
- * @param options - Optional event-output overrides.
245
- * @returns The terminal stop reason for the agent loop.
246
- */
247
- export async function runHeadlessPrompt(
248
- state: AppState,
249
- rawInput: string,
250
- options?: HeadlessRunOptions,
251
- ): Promise<HeadlessStopReason> {
252
- const content = resolveHeadlessContent(state, rawInput);
253
- const output = createHeadlessOutputController(
254
- state,
255
- process.stdout,
256
- options?.writeLine ?? ((line) => defaultWrite(process.stdout, `${line}\n`)),
257
- );
258
- const hooks: SubmitTurnHooks = {
259
- onEvent: (event) => {
260
- if (!shouldWriteHeadlessJsonEvent(event)) {
261
- return;
262
- }
263
- output.write(JSON.stringify(event));
20
+ ];
21
+ const envReminder = await injectEnvReminder();
22
+ const messages: Message[] = [
23
+ {
24
+ role: "user",
25
+ content: `${envReminder}\n\n${options.prompt || ""}`,
26
+ timestamp: Date.now(),
264
27
  },
28
+ ];
29
+ console.log(JSON.stringify(messages[0]));
30
+
31
+ const systemPrompt = await buildSystemPrompt(MAIN_PROMPT);
32
+ const ctx: AgentContex = {
33
+ systemPrompt,
34
+ tools,
35
+ messages,
36
+ options,
265
37
  };
266
38
 
267
- output.attach();
268
- try {
269
- const stopReason = await submitResolvedInput(
270
- rawInput,
271
- content,
272
- state,
273
- hooks,
274
- );
275
- return await output.finalize(stopReason);
276
- } finally {
277
- output.detach();
278
- }
279
- }
280
-
281
- /**
282
- * Run a single headless prompt to completion and write the final assistant text.
283
- *
284
- * The raw input is parsed with the same rules as interactive input. Slash
285
- * commands are rejected in headless mode. The final assistant text is written
286
- * to stdout, while lightweight assistant commentary snippets from tool-use
287
- * turns and terminal assistant error messages are written to stderr.
288
- *
289
- * @param state - Mutable application state for the run.
290
- * @param rawInput - Exact raw prompt text supplied by the user.
291
- * @param options - Optional final-text output overrides.
292
- * @returns The terminal stop reason for the agent loop.
293
- */
294
- export async function runHeadlessPromptText(
295
- state: AppState,
296
- rawInput: string,
297
- options?: HeadlessTextRunOptions,
298
- ): Promise<HeadlessStopReason> {
299
- const content = resolveHeadlessContent(state, rawInput);
300
- const finalOutput = createHeadlessOutputController(
301
- state,
302
- process.stdout,
303
- options?.writeText ?? ((text) => defaultWrite(process.stdout, text)),
304
- );
305
- const activityOutput = createHeadlessOutputController(
306
- state,
307
- process.stderr,
308
- options?.writeActivity ?? ((text) => defaultWrite(process.stderr, text)),
309
- { attachSigint: false },
310
- );
311
- let finalAssistantMessage: AssistantMessage | null = null;
312
- const hooks: SubmitTurnHooks = {
313
- onEvent: (event) => {
314
- switch (event.type) {
315
- case "assistant_message": {
316
- const activitySnippet = extractAssistantActivitySnippet(
317
- event.message,
318
- );
319
- if (activitySnippet) {
320
- activityOutput.write(`${activitySnippet}\n`);
321
- }
322
- return;
323
- }
324
- case "done":
325
- case "error":
326
- case "aborted":
327
- finalAssistantMessage = event.message;
328
- return;
329
- default:
330
- return;
331
- }
332
- },
333
- };
334
-
335
- finalOutput.attach();
336
- activityOutput.attach();
337
- try {
338
- const stopReason = await submitResolvedInput(
339
- rawInput,
340
- content,
341
- state,
342
- hooks,
343
- );
344
- const finalText = extractAssistantText(finalAssistantMessage);
345
- if (finalText.length > 0) {
346
- finalOutput.write(finalText);
39
+ const agent = streamAgent(ctx);
40
+ for await (const ev of agent) {
41
+ switch (ev.type) {
42
+ case "message_end":
43
+ case "tool_message_end":
44
+ console.log(JSON.stringify(ev.message));
45
+ break;
347
46
  }
348
-
349
- const terminalErrorText = extractAssistantErrorText(finalAssistantMessage);
350
- if (terminalErrorText) {
351
- activityOutput.write(`${terminalErrorText}\n`);
352
- }
353
-
354
- const [finalStopReason, activityStopReason] = await Promise.all([
355
- finalOutput.finalize(stopReason),
356
- activityOutput.finalize(stopReason),
357
- ]);
358
- return finalStopReason === "stop" || activityStopReason === "stop"
359
- ? "stop"
360
- : stopReason;
361
- } finally {
362
- activityOutput.detach();
363
- finalOutput.detach();
364
47
  }
48
+
49
+ leave();
365
50
  }