mini-coder 0.5.13 → 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 (67) hide show
  1. package/README.md +25 -108
  2. package/bin/mc.ts +8 -11
  3. package/bun.lock +79 -269
  4. package/package.json +17 -22
  5. package/src/agent.ts +242 -915
  6. package/src/args.ts +289 -0
  7. package/src/headless.ts +43 -385
  8. package/src/index.ts +29 -836
  9. package/src/oauth.ts +117 -0
  10. package/src/prompt.ts +227 -276
  11. package/src/session.ts +57 -961
  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 -4
  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/cli.ts +0 -134
  39. package/src/errors.ts +0 -15
  40. package/src/git.ts +0 -247
  41. package/src/input.ts +0 -168
  42. package/src/mcp.ts +0 -609
  43. package/src/paths.ts +0 -37
  44. package/src/session-message.ts +0 -393
  45. package/src/settings.ts +0 -449
  46. package/src/skills.ts +0 -271
  47. package/src/submit.ts +0 -371
  48. package/src/text.ts +0 -71
  49. package/src/theme.ts +0 -330
  50. package/src/tool-common.ts +0 -93
  51. package/src/tool-grep.ts +0 -606
  52. package/src/tool-read.ts +0 -313
  53. package/src/tool-shell.ts +0 -1001
  54. package/src/tools.ts +0 -854
  55. package/src/ui/agent.ts +0 -317
  56. package/src/ui/commands.test.ts +0 -913
  57. package/src/ui/commands.ts +0 -834
  58. package/src/ui/conversation.test.ts +0 -585
  59. package/src/ui/conversation.ts +0 -1836
  60. package/src/ui/help.ts +0 -158
  61. package/src/ui/input.test.ts +0 -64
  62. package/src/ui/input.ts +0 -138
  63. package/src/ui/overlay.ts +0 -59
  64. package/src/ui/runtime.ts +0 -69
  65. package/src/ui/status.ts +0 -220
  66. package/src/ui.ts +0 -1190
  67. 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,392 +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 type { AppState } from "./index.ts";
10
- import {
11
- resolveRawInput,
12
- type SubmitTurnHooks,
13
- submitResolvedInput,
14
- } from "./submit.ts";
15
- import {
16
- collapseWhitespaceToNull,
17
- joinTextBlocks,
18
- truncateText,
19
- } from "./text.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
- const HEADLESS_ACTIVITY_MAX_CHARS = 160;
62
-
63
- // ---------------------------------------------------------------------------
64
- // Helpers
65
- // ---------------------------------------------------------------------------
66
-
67
- function defaultWrite(
68
- stream: HeadlessProcessStream,
69
- text: string,
70
- ): Promise<void> {
71
- return new Promise((resolve, reject) => {
72
- let settled = false;
73
-
74
- const cleanup = (): void => {
75
- stream.off("error", handleError);
76
- };
77
-
78
- const settle = (callback: () => void): void => {
79
- if (settled) {
80
- return;
81
- }
82
- settled = true;
83
- cleanup();
84
- callback();
85
- };
86
-
87
- const handleError = (error: unknown): void => {
88
- settle(() => {
89
- reject(error);
90
- });
91
- };
92
-
93
- stream.on("error", handleError);
94
- try {
95
- stream.write(text, () => {
96
- settle(resolve);
97
- });
98
- } catch (error) {
99
- settle(() => {
100
- reject(error);
101
- });
102
- }
103
- });
104
- }
105
-
106
- function isBrokenPipeError(error: unknown): boolean {
107
- return (
108
- typeof error === "object" &&
109
- error !== null &&
110
- (("code" in error && error.code === "EPIPE") ||
111
- ("message" in error &&
112
- typeof error.message === "string" &&
113
- error.message.includes("broken pipe")))
114
- );
115
- }
116
-
117
- function createSigintHandler(state: AppState): () => void {
118
- return () => {
119
- state.abortController?.abort();
120
- };
121
- }
122
-
123
- function buildCommandError(command: string): Error {
124
- return new Error(
125
- `Headless mode does not support slash commands: /${command}`,
126
- );
127
- }
128
-
129
- function resolveHeadlessContent(
130
- state: AppState,
131
- rawInput: string,
132
- ): UserMessage["content"] {
133
- const resolved = resolveRawInput(rawInput, state);
134
- switch (resolved.type) {
135
- case "empty":
136
- throw new Error("Headless input is empty.");
137
- case "error":
138
- throw new Error(resolved.message);
139
- case "command":
140
- throw buildCommandError(resolved.command);
141
- case "message":
142
- return resolved.content;
143
- }
144
- }
145
-
146
- function extractAssistantText(message: AssistantMessage | null): string {
147
- if (!message) {
148
- return "";
149
- }
150
-
151
- return message.content
152
- .filter(
153
- (
154
- block,
155
- ): block is Extract<
156
- AssistantMessage["content"][number],
157
- { type: "text" }
158
- > => {
159
- return block.type === "text";
160
- },
161
- )
162
- .map((block) => block.text)
163
- .join("");
164
- }
165
-
166
- function extractAssistantActivitySnippet(
167
- message: AssistantMessage,
168
- ): string | null {
169
- if (!message.content.some((block) => block.type === "toolCall")) {
170
- return null;
171
- }
172
-
173
- const text = collapseWhitespaceToNull(joinTextBlocks(message.content));
174
- return text ? truncateText(text, HEADLESS_ACTIVITY_MAX_CHARS) : null;
175
- }
176
-
177
- function shouldWriteHeadlessJsonEvent(event: AgentEvent): boolean {
178
- switch (event.type) {
179
- case "user_message":
180
- case "assistant_message":
181
- case "tool_result":
182
- case "done":
183
- case "error":
184
- case "aborted":
185
- return true;
186
- default:
187
- return false;
188
- }
189
- }
190
-
191
- function createHeadlessOutputController(
192
- state: AppState,
193
- stream: HeadlessProcessStream,
194
- writeImpl: (text: string) => void | Promise<void>,
195
- options?: {
196
- attachSigint?: boolean;
197
- },
198
- ): HeadlessOutputController {
199
- let brokenPipe = false;
200
- let outputError: unknown = null;
201
- let pendingWrite = Promise.resolve();
202
- const sigintHandler = createSigintHandler(state);
203
- const attachSigint = options?.attachSigint ?? true;
204
-
205
- const stopForBrokenPipe = (): void => {
206
- if (brokenPipe) {
207
- return;
208
- }
209
- brokenPipe = true;
210
- state.abortController?.abort();
211
- };
212
-
213
- const failOutput = (error: unknown): void => {
214
- if (isBrokenPipeError(error)) {
215
- stopForBrokenPipe();
216
- return;
217
- }
218
-
219
- outputError = outputError ?? error;
220
- state.abortController?.abort();
221
- };
222
-
223
- const streamErrorHandler = (error: unknown): void => {
224
- failOutput(error);
225
- };
226
-
227
- return {
228
- write(text) {
229
- if (brokenPipe || outputError) {
230
- return;
231
- }
232
-
233
- pendingWrite = pendingWrite.then(async () => {
234
- if (brokenPipe || outputError) {
235
- return;
236
- }
237
-
238
- try {
239
- await writeImpl(text);
240
- } catch (error) {
241
- failOutput(error);
242
- }
243
- });
244
- },
245
- attach() {
246
- stream.on("error", streamErrorHandler);
247
- if (attachSigint) {
248
- process.on("SIGINT", sigintHandler);
249
- }
250
- },
251
- detach() {
252
- stream.off("error", streamErrorHandler);
253
- if (attachSigint) {
254
- process.off("SIGINT", sigintHandler);
255
- }
256
- },
257
- async finalize(stopReason) {
258
- await pendingWrite;
259
- if (outputError) {
260
- throw outputError;
261
- }
262
- return brokenPipe ? "stop" : stopReason;
263
- },
264
- };
265
- }
266
-
267
- /**
268
- * Run a single headless prompt to completion and stream completed NDJSON events.
269
- *
270
- * The raw input is parsed with the same rules as interactive input. Slash
271
- * commands are rejected in headless mode. Persisted messages and terminal
272
- * events are written as one JSON object per line; streaming delta/progress
273
- * events are omitted.
274
- *
275
- * @param state - Mutable application state for the run.
276
- * @param rawInput - Exact raw prompt text supplied by the user.
277
- * @param options - Optional event-output overrides.
278
- * @returns The terminal stop reason for the agent loop.
279
- */
280
- export async function runHeadlessPrompt(
281
- state: AppState,
282
- rawInput: string,
283
- options?: HeadlessRunOptions,
284
- ): Promise<HeadlessStopReason> {
285
- const content = resolveHeadlessContent(state, rawInput);
286
- const output = createHeadlessOutputController(
287
- state,
288
- process.stdout,
289
- options?.writeLine ?? ((line) => defaultWrite(process.stdout, `${line}\n`)),
290
- );
291
- const hooks: SubmitTurnHooks = {
292
- onEvent: (event) => {
293
- if (!shouldWriteHeadlessJsonEvent(event)) {
294
- return;
295
- }
296
- output.write(JSON.stringify(event));
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),
297
19
  },
298
- };
299
-
300
- output.attach();
301
- try {
302
- const stopReason = await submitResolvedInput(
303
- rawInput,
304
- content,
305
- state,
306
- hooks,
307
- );
308
- return await output.finalize(stopReason);
309
- } finally {
310
- output.detach();
311
- }
312
- }
313
-
314
- /**
315
- * Run a single headless prompt to completion and write the final assistant text.
316
- *
317
- * The raw input is parsed with the same rules as interactive input. Slash
318
- * commands are rejected in headless mode. The final assistant text is written
319
- * to stdout, while lightweight assistant commentary snippets from tool-use
320
- * turns are written to stderr.
321
- *
322
- * @param state - Mutable application state for the run.
323
- * @param rawInput - Exact raw prompt text supplied by the user.
324
- * @param options - Optional final-text output overrides.
325
- * @returns The terminal stop reason for the agent loop.
326
- */
327
- export async function runHeadlessPromptText(
328
- state: AppState,
329
- rawInput: string,
330
- options?: HeadlessTextRunOptions,
331
- ): Promise<HeadlessStopReason> {
332
- const content = resolveHeadlessContent(state, rawInput);
333
- const finalOutput = createHeadlessOutputController(
334
- state,
335
- process.stdout,
336
- options?.writeText ?? ((text) => defaultWrite(process.stdout, text)),
337
- );
338
- const activityOutput = createHeadlessOutputController(
339
- state,
340
- process.stderr,
341
- options?.writeActivity ?? ((text) => defaultWrite(process.stderr, text)),
342
- { attachSigint: false },
343
- );
344
- let finalAssistantMessage: AssistantMessage | null = null;
345
- const hooks: SubmitTurnHooks = {
346
- onEvent: (event) => {
347
- switch (event.type) {
348
- case "assistant_message": {
349
- const activitySnippet = extractAssistantActivitySnippet(
350
- event.message,
351
- );
352
- if (activitySnippet) {
353
- activityOutput.write(`${activitySnippet}\n`);
354
- }
355
- return;
356
- }
357
- case "done":
358
- case "error":
359
- case "aborted":
360
- finalAssistantMessage = event.message;
361
- return;
362
- default:
363
- return;
364
- }
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(),
365
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,
366
37
  };
367
38
 
368
- finalOutput.attach();
369
- activityOutput.attach();
370
- try {
371
- const stopReason = await submitResolvedInput(
372
- rawInput,
373
- content,
374
- state,
375
- hooks,
376
- );
377
- const finalText = extractAssistantText(finalAssistantMessage);
378
- if (finalText.length > 0) {
379
- 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;
380
46
  }
381
- const [finalStopReason, activityStopReason] = await Promise.all([
382
- finalOutput.finalize(stopReason),
383
- activityOutput.finalize(stopReason),
384
- ]);
385
- return finalStopReason === "stop" || activityStopReason === "stop"
386
- ? "stop"
387
- : stopReason;
388
- } finally {
389
- activityOutput.detach();
390
- finalOutput.detach();
391
47
  }
48
+
49
+ leave();
392
50
  }