@jancellor/ask 1.0.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.
@@ -0,0 +1,101 @@
1
+ import { Agent, } from '../agent/index.js';
2
+ import { ShutdownManager } from '../shutdown-manager.js';
3
+ export async function runBatch(argument, options) {
4
+ const stdin = !process.stdin.isTTY ? await readStdin() : undefined;
5
+ const message = [stdin, argument].filter(Boolean).join('\n\n');
6
+ if (!message)
7
+ throw new Error('no message provided (pass [message] or pipe stdin)');
8
+ const agent = await Agent.create(options);
9
+ // console.error(`[Session: ${agent.sessionId}]`);
10
+ const shutdownManager = new ShutdownManager(async () => {
11
+ await agent.cancelAll();
12
+ });
13
+ shutdownManager.installSignalHandlers();
14
+ // Log tool calls to stderr as they happen
15
+ agent.addListener({
16
+ onMessages(newMessages) {
17
+ for (const msg of newMessages) {
18
+ if (msg.role !== 'assistant' || !Array.isArray(msg.content))
19
+ continue;
20
+ for (const part of msg.content) {
21
+ if (part?.type === 'tool-call') {
22
+ const line = formatToolCall(part.toolName, part.input);
23
+ console.error(truncate(line, 80));
24
+ }
25
+ }
26
+ }
27
+ },
28
+ });
29
+ await agent.ask(message);
30
+ // Extract and output the final assistant response
31
+ const response = extractFinalResponse(agent.messages);
32
+ console.log(response);
33
+ }
34
+ async function readStdin() {
35
+ let data = '';
36
+ for await (const chunk of process.stdin) {
37
+ data += String(chunk);
38
+ }
39
+ return data;
40
+ }
41
+ function formatToolCall(toolName, input) {
42
+ if (toolName === 'execute') {
43
+ const command = extractCommand(input);
44
+ if (command) {
45
+ return `$ ${command}`;
46
+ }
47
+ }
48
+ // Generic format for other tools
49
+ const inputStr = formatToolInput(input);
50
+ return inputStr ? `${toolName} ${inputStr}` : toolName;
51
+ }
52
+ function extractCommand(input) {
53
+ if (typeof input === 'string')
54
+ return input;
55
+ if (input && typeof input === 'object') {
56
+ const obj = input;
57
+ if (typeof obj.command === 'string')
58
+ return obj.command;
59
+ }
60
+ return null;
61
+ }
62
+ function formatToolInput(input) {
63
+ if (input === undefined || input === null)
64
+ return '';
65
+ if (typeof input === 'string')
66
+ return input;
67
+ return JSON.stringify(input);
68
+ }
69
+ function truncate(str, maxLen) {
70
+ const hasMultipleLines = str.includes('\n');
71
+ const firstLine = str.split('\n')[0] ?? '';
72
+ // Needs truncation (either multiple lines or too long)
73
+ const needsTruncation = hasMultipleLines || firstLine.length > maxLen;
74
+ if (!needsTruncation) {
75
+ return firstLine;
76
+ }
77
+ // Always reserve 3 chars for "...", so max content is maxLen - 3
78
+ const contentMax = maxLen - 3;
79
+ const truncated = firstLine.slice(0, contentMax);
80
+ return truncated + '...';
81
+ }
82
+ function extractFinalResponse(messages) {
83
+ // Find the last non-hidden assistant message
84
+ const assistantMessages = messages.filter((m) => !m._meta?.uiHidden && m.role === 'assistant');
85
+ const lastMessage = assistantMessages.at(-1);
86
+ if (!lastMessage)
87
+ return '';
88
+ const { content } = lastMessage;
89
+ // Handle string content
90
+ if (typeof content === 'string') {
91
+ return content;
92
+ }
93
+ // Handle array content - extract text parts
94
+ if (Array.isArray(content)) {
95
+ return content
96
+ .filter((p) => p?.type === 'text')
97
+ .map((p) => p.text)
98
+ .join('');
99
+ }
100
+ return '';
101
+ }
package/dist/index.js ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { runTui } from './tui/index.js';
4
+ import { runBatch } from './batch/index.js';
5
+ async function main() {
6
+ const program = new Command()
7
+ .name('ask')
8
+ .allowExcessArguments(false)
9
+ .option('-r, --resume [id]', 'read from given (or last) session')
10
+ .option('-f, --fork [id]', 'write to given (or random) session')
11
+ .option('-i, --interactive', 'force interactive mode')
12
+ .option('-b, --batch', 'force batch mode')
13
+ .addHelpText('after', '\nUse -- before message if ambiguous (eg ask -r -- "follow up question")')
14
+ .argument('[message]');
15
+ program.parse(process.argv);
16
+ const opts = program.opts();
17
+ const message = program.args[0];
18
+ const mode = opts.interactive && opts.batch
19
+ ? 'auto'
20
+ : opts.interactive
21
+ ? 'interactive'
22
+ : opts.batch
23
+ ? 'batch'
24
+ : 'auto';
25
+ const useInteractive = mode == 'interactive' ||
26
+ (mode == 'auto' && process.stdin.isTTY && message === undefined);
27
+ useInteractive ? await runTui(opts) : await runBatch(message, opts);
28
+ }
29
+ main().catch((error) => {
30
+ console.error(String(error));
31
+ process.exit(1);
32
+ });
@@ -0,0 +1,35 @@
1
+ export class ShutdownManager {
2
+ cleanup;
3
+ shutdownPromise = null;
4
+ constructor(cleanup) {
5
+ this.cleanup = cleanup;
6
+ }
7
+ requestShutdown() {
8
+ void this.shutdown(() => {
9
+ process.exit(0);
10
+ });
11
+ }
12
+ installSignalHandlers() {
13
+ for (const signal of ['SIGHUP', 'SIGINT', 'SIGTERM']) {
14
+ process.once(signal, () => {
15
+ void this.shutdown(() => {
16
+ // resignal to get proper exit/signal values
17
+ process.kill(process.pid, signal);
18
+ });
19
+ });
20
+ }
21
+ }
22
+ shutdown(finalize) {
23
+ if (!this.shutdownPromise) {
24
+ this.shutdownPromise = (async () => {
25
+ try {
26
+ await this.cleanup();
27
+ }
28
+ finally {
29
+ finalize();
30
+ }
31
+ })();
32
+ }
33
+ return this.shutdownPromise;
34
+ }
35
+ }
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box } from 'ink';
3
+ import { Input } from './input.js';
4
+ import { Messages } from './messages.js';
5
+ import { useAgent } from './use-agent.js';
6
+ export function App({ agent, onRequestShutdown }) {
7
+ const { messages, modelId, provider, sendMessage, abort, clear } = useAgent(agent);
8
+ const handleSubmit = (message) => {
9
+ void sendMessage(message);
10
+ };
11
+ return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [_jsx(Messages, { messages: messages, model: modelId, provider: provider }), _jsx(Input, { onSubmit: handleSubmit, onAbort: abort, onClear: clear, onRequestShutdown: onRequestShutdown })] }));
12
+ }
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { ABORTED_MESSAGE, ERROR_MESSAGE } from '../agent/index.js';
4
+ import { parseMarkdown } from './markdown.js';
5
+ export function AssistantPartMessage({ text, dim }) {
6
+ const error = [ABORTED_MESSAGE, ERROR_MESSAGE].some((e) => text.startsWith(e));
7
+ const parsed = error || dim ? text : parseMarkdown(text);
8
+ const color = error ? 'red' : dim ? 'gray' : undefined;
9
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: color, dimColor: error, children: parsed }), _jsx(Text, { children: " " })] }));
10
+ }
@@ -0,0 +1,48 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { SpinnerMessage } from './spinner-message.js';
4
+ export function ExecuteToolPartMessage({ input, output }) {
5
+ const displayInput = input == null ? null : formatExecuteToolInput(input);
6
+ const parsed = output == null ? null : parseExecuteToolOutput(output);
7
+ return (_jsxs(Box, { flexDirection: "column", children: [displayInput ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: displayInput }), _jsx(Text, { children: " " })] })) : null, parsed?.stdout ? (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: parsed.stdout }), _jsx(Text, { children: " " })] })) : null, parsed?.stderr ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: "red", dimColor: true, children: parsed.stderr }), _jsx(Text, { children: " " })] })) : null, parsed?.error ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: "red", children: ["error: ", parsed.error] }), _jsx(Text, { children: " " })] })) : null, parsed?.exit ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: parsed.exit }), _jsx(Text, { children: " " })] })) : null, parsed?.signal ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: "red", dimColor: true, children: parsed.signal }), _jsx(Text, { children: " " })] })) : null, output == null ? _jsx(SpinnerMessage, {}) : null] }));
8
+ }
9
+ function parseExecuteToolOutput(output) {
10
+ if (!output || typeof output !== 'object')
11
+ return null;
12
+ const outer = output;
13
+ if (outer.type !== 'json' || !('value' in outer))
14
+ return null;
15
+ const value = outer.value;
16
+ if (!value || typeof value !== 'object')
17
+ return null;
18
+ const inner = value;
19
+ const stdout = typeof inner.stdout === 'string' && inner.stdout.trim().length > 0
20
+ ? inner.stdout.trimEnd()
21
+ : '';
22
+ const stderr = typeof inner.stderr === 'string' && inner.stderr.trim().length > 0
23
+ ? inner.stderr.trimEnd()
24
+ : '';
25
+ const error = typeof inner.error === 'string' && inner.error.trim().length > 0
26
+ ? inner.error.trim()
27
+ : '';
28
+ const exit = inner.exit ?? inner.exitCode;
29
+ const signal = inner.signal;
30
+ return {
31
+ stdout,
32
+ stderr,
33
+ error,
34
+ exit: exit !== undefined ? `exit ${exit}` : '',
35
+ signal: signal ? String(signal) : '',
36
+ };
37
+ }
38
+ function formatExecuteToolInput(input) {
39
+ if (typeof input === 'string')
40
+ return input;
41
+ if (input && typeof input === 'object') {
42
+ const obj = input;
43
+ if (typeof obj.command === 'string')
44
+ return `$ ${obj.command}`;
45
+ return JSON.stringify(input, null, 2);
46
+ }
47
+ return String(input);
48
+ }
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { SpinnerMessage } from './spinner-message.js';
4
+ export function GenericToolPartMessage({ toolName, input, output }) {
5
+ return (_jsxs(Box, { flexDirection: "column", children: [toolName ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: toolName }), _jsx(Text, { children: " " })] })) : null, input != null ? (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: formatGenericToolInput(input) }), _jsx(Text, { children: " " })] })) : null, output != null ? (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: formatGenericToolOutput(output) }), _jsx(Text, { children: " " })] })) : (_jsx(SpinnerMessage, {}))] }));
6
+ }
7
+ function formatGenericToolInput(input) {
8
+ if (typeof input === 'string')
9
+ return input;
10
+ if (input && typeof input === 'object')
11
+ return JSON.stringify(input, null, 2);
12
+ return String(input);
13
+ }
14
+ function formatGenericToolOutput(output) {
15
+ if (!output || typeof output !== 'object')
16
+ return String(output ?? '');
17
+ const o = output;
18
+ if ('type' in o && 'value' in o) {
19
+ const value = o.value;
20
+ if (typeof value === 'string')
21
+ return value;
22
+ if (value && typeof value === 'object') {
23
+ return JSON.stringify(value, null, 2);
24
+ }
25
+ return String(value);
26
+ }
27
+ return JSON.stringify(output, null, 2);
28
+ }
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render } from 'ink';
3
+ import { Agent } from '../agent/index.js';
4
+ import { ShutdownManager } from '../shutdown-manager.js';
5
+ import { App } from './app.js';
6
+ export async function runTui(options) {
7
+ const agent = await Agent.create(options);
8
+ const shutdownManager = new ShutdownManager(async () => {
9
+ try {
10
+ app.unmount();
11
+ }
12
+ finally {
13
+ await agent.cancelAll();
14
+ }
15
+ });
16
+ shutdownManager.installSignalHandlers();
17
+ const app = render(_jsx(App, { agent: agent, onRequestShutdown: () => shutdownManager.requestShutdown() }), { exitOnCtrlC: false });
18
+ }
@@ -0,0 +1,115 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import { useInputState } from './use-input-state.js';
5
+ const CLEAR_COMMAND = '/clear';
6
+ const CURSOR_BLINK_MS = 600;
7
+ export function Input({ onSubmit, onAbort, onClear, onRequestShutdown }) {
8
+ const { value, beforeCursor, atCursor, afterCursor, moveStart, moveEnd, moveLeft, moveRight, moveWordLeft, moveWordRight, deleteBackward, deleteToStart, deleteToEnd, deleteWordBackward, insertText, clear, } = useInputState();
9
+ const [showCursor, setShowCursor] = useState(true);
10
+ const [keyPulse, setKeyPulse] = useState(0);
11
+ useEffect(() => {
12
+ let intervalId;
13
+ const timeoutId = setTimeout(() => {
14
+ setShowCursor(false);
15
+ intervalId = setInterval(() => {
16
+ setShowCursor((prev) => !prev);
17
+ }, CURSOR_BLINK_MS);
18
+ }, CURSOR_BLINK_MS);
19
+ return () => {
20
+ clearTimeout(timeoutId);
21
+ if (intervalId) {
22
+ clearInterval(intervalId);
23
+ }
24
+ };
25
+ }, [keyPulse]);
26
+ useInput((input, key) => {
27
+ setShowCursor(true);
28
+ setKeyPulse((pulse) => pulse + 1);
29
+ if (key.escape) {
30
+ onAbort();
31
+ return;
32
+ }
33
+ if (key.ctrl && input === 'c') {
34
+ onRequestShutdown();
35
+ return;
36
+ }
37
+ if (key.ctrl && input === 'a') {
38
+ moveStart();
39
+ return;
40
+ }
41
+ if (key.ctrl && input === 'e') {
42
+ moveEnd();
43
+ return;
44
+ }
45
+ if (key.ctrl && input === 'u') {
46
+ deleteToStart();
47
+ return;
48
+ }
49
+ if (key.ctrl && input === 'k') {
50
+ deleteToEnd();
51
+ return;
52
+ }
53
+ if (key.ctrl && input === 'w') {
54
+ deleteWordBackward();
55
+ return;
56
+ }
57
+ if (key.home) {
58
+ moveStart();
59
+ return;
60
+ }
61
+ if (key.end) {
62
+ moveEnd();
63
+ return;
64
+ }
65
+ if (key.leftArrow) {
66
+ if (key.ctrl || key.meta) {
67
+ moveWordLeft();
68
+ }
69
+ else {
70
+ moveLeft();
71
+ }
72
+ return;
73
+ }
74
+ if (key.rightArrow) {
75
+ if (key.ctrl || key.meta) {
76
+ moveWordRight();
77
+ }
78
+ else {
79
+ moveRight();
80
+ }
81
+ return;
82
+ }
83
+ if (key.backspace || key.delete) {
84
+ deleteBackward();
85
+ return;
86
+ }
87
+ if (key.return) {
88
+ if (key.meta) {
89
+ insertText('\n');
90
+ return;
91
+ }
92
+ const message = value.trim();
93
+ if (!message) {
94
+ return;
95
+ }
96
+ clear();
97
+ if (message === CLEAR_COMMAND) {
98
+ void onClear(() => {
99
+ console.log('[New session]\n');
100
+ });
101
+ }
102
+ else {
103
+ void onSubmit(message);
104
+ }
105
+ return;
106
+ }
107
+ if (!input) {
108
+ return;
109
+ }
110
+ insertText(input);
111
+ });
112
+ const width = Math.max(10, process.stdout.columns ?? 80);
113
+ const divider = '─'.repeat(width);
114
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "gray", children: divider }), _jsxs(Text, { children: [beforeCursor, showCursor ? _jsx(Text, { inverse: true, children: atCursor }) : atCursor, afterCursor] }), _jsx(Text, { color: "gray", children: divider })] }));
115
+ }
@@ -0,0 +1,31 @@
1
+ import chalk from 'chalk';
2
+ import { marked } from 'marked';
3
+ import TerminalRenderer from 'marked-terminal';
4
+ marked.setOptions({
5
+ renderer: new TerminalRenderer({
6
+ code: chalk.hex('#D8DEE9'),
7
+ codespan: chalk.hex('#B7A7FF'),
8
+ blockquote: chalk.hex('#7D85A8').italic,
9
+ heading: chalk.hex('#69A9FF').bold,
10
+ firstHeading: chalk.hex('#8DC2FF').bold,
11
+ strong: chalk.hex('#F0F3FF').bold,
12
+ em: chalk.hex('#8E96B8').italic,
13
+ del: chalk.hex('#6A7192').strikethrough,
14
+ link: chalk.hex('#7AA8FF'),
15
+ href: chalk.hex('#7AA8FF').underline,
16
+ html: chalk.hex('#7B84A7'),
17
+ hr: chalk.hex('#4A5070'),
18
+ listitem: chalk.hex('#C5CCE1'),
19
+ table: chalk.hex('#C5CCE1'),
20
+ paragraph: chalk.reset,
21
+ emoji: true,
22
+ showSectionPrefix: true,
23
+ reflowText: false,
24
+ width: 80,
25
+ tab: 2,
26
+ }),
27
+ });
28
+ export function parseMarkdown(text) {
29
+ const rendered = marked.parse(text);
30
+ return rendered.replace(/(?:\r?\n){1,2}$/, '');
31
+ }
@@ -0,0 +1,86 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Static } from 'ink';
3
+ import { AssistantPartMessage } from './assistant-part-message.js';
4
+ import { SpinnerMessage } from './spinner-message.js';
5
+ import { ToolPartMessage } from './tool-part-message.js';
6
+ import { UserPartMessage } from './user-part-message.js';
7
+ import { Welcome } from './welcome.js';
8
+ export function Messages({ messages: rawMessages, model, provider }) {
9
+ const messages = rawMessages.filter((m) => !m._meta?.uiHidden);
10
+ const toolResults = buildToolResultsMap(messages);
11
+ const parts = messages.flatMap((message, i) => getContentParts(message.content).map((part, j) => ({
12
+ ...part,
13
+ role: message.role,
14
+ i,
15
+ j,
16
+ id: `${message.role}-${i}-${j}`,
17
+ })));
18
+ const splitIdx = parts.findIndex((p) => p.type === 'tool-call' && !toolResults.has(p.toolCallId));
19
+ const staticParts = splitIdx === -1 ? parts : parts.slice(0, splitIdx);
20
+ const activeParts = splitIdx === -1 ? [] : parts.slice(splitIdx);
21
+ const allStaticItems = [
22
+ { id: 'welcome', type: 'welcome' },
23
+ ...staticParts,
24
+ ];
25
+ const renderPart = (p) => {
26
+ if (p.role === 'assistant' && p.type === 'text') {
27
+ return _jsx(AssistantPartMessage, { text: p.text, dim: false }, p.id);
28
+ }
29
+ if (p.role === 'user') {
30
+ if (p.type === 'text') {
31
+ return _jsx(UserPartMessage, { text: p.text }, p.id);
32
+ }
33
+ }
34
+ if (p.type === 'tool-call') {
35
+ return (_jsx(ToolPartMessage, { toolName: p.toolName ?? toolResults.get(p.toolCallId)?.toolName, input: p.input, output: toolResults.get(p.toolCallId)?.output }, p.id));
36
+ }
37
+ return null;
38
+ };
39
+ const showSpinner = messages.length > 0 && messages.at(-1).role !== 'assistant';
40
+ return (_jsxs(Box, { flexGrow: 1, flexDirection: "column", children: [_jsx(Static, { items: allStaticItems, children: (item) => {
41
+ if (item.type === 'welcome')
42
+ return _jsx(Welcome, { model: model, provider: provider }, "welcome");
43
+ return renderPart(item);
44
+ } }), activeParts.map(renderPart), showSpinner && _jsx(SpinnerMessage, {})] }));
45
+ }
46
+ function getContentParts(content) {
47
+ if (typeof content === 'string')
48
+ return [{ type: 'text', text: content }];
49
+ if (!Array.isArray(content))
50
+ return [];
51
+ const parts = [];
52
+ for (const p of content) {
53
+ if (!p || typeof p !== 'object')
54
+ continue;
55
+ if (p.type === 'text' && typeof p.text === 'string') {
56
+ parts.push({ type: 'text', text: p.text });
57
+ }
58
+ else if (p.type === 'tool-call' && typeof p.toolCallId === 'string') {
59
+ parts.push({
60
+ type: 'tool-call',
61
+ toolCallId: p.toolCallId,
62
+ toolName: typeof p.toolName === 'string' ? p.toolName : null,
63
+ input: p.input,
64
+ });
65
+ }
66
+ }
67
+ return parts;
68
+ }
69
+ function buildToolResultsMap(messages) {
70
+ const map = new Map();
71
+ for (const message of messages) {
72
+ if (message.role !== 'tool' || !Array.isArray(message.content))
73
+ continue;
74
+ for (const part of message.content) {
75
+ if (!part || typeof part !== 'object')
76
+ continue;
77
+ if (part.type === 'tool-result' && 'output' in part) {
78
+ map.set(part.toolCallId, {
79
+ toolName: typeof part.toolName === 'string' ? part.toolName : null,
80
+ output: part.output,
81
+ });
82
+ }
83
+ }
84
+ }
85
+ return map;
86
+ }
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { Box, Text } from 'ink';
4
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
5
+ export function SpinnerMessage() {
6
+ const [frameIndex, setFrameIndex] = React.useState(0);
7
+ React.useEffect(() => {
8
+ const timer = setInterval(() => {
9
+ setFrameIndex((idx) => (idx + 1) % frames.length);
10
+ }, 80);
11
+ return () => clearInterval(timer);
12
+ }, []);
13
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "white", children: frames[frameIndex] }), _jsx(Text, { children: " " })] }));
14
+ }
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ExecuteToolPartMessage } from './execute-tool-part-message.js';
3
+ import { GenericToolPartMessage } from './generic-tool-part-message.js';
4
+ export function ToolPartMessage({ toolName, input, output }) {
5
+ if (toolName === 'execute') {
6
+ return _jsx(ExecuteToolPartMessage, { toolName: toolName, input: input, output: output });
7
+ }
8
+ return _jsx(GenericToolPartMessage, { toolName: toolName, input: input, output: output });
9
+ }
@@ -0,0 +1,27 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ export function useAgent(agent) {
3
+ const [messages, setMessages] = useState([]);
4
+ useEffect(() => {
5
+ setMessages([...agent.messages]);
6
+ const listener = {
7
+ onMessages: () => setMessages([...agent.messages]),
8
+ onClear: () => setMessages([]),
9
+ };
10
+ agent.addListener(listener);
11
+ return () => {
12
+ agent.removeListener(listener);
13
+ };
14
+ }, [agent]);
15
+ const sendMessage = useCallback((message) => agent.ask(message), [agent]);
16
+ const abort = useCallback(() => agent.abort(), [agent]);
17
+ const clear = useCallback((beforeClear) => agent.clear(beforeClear), [agent]);
18
+ const provider = new URL(agent.baseUrl).hostname.split('.').at(-2) ?? agent.baseUrl;
19
+ return {
20
+ messages,
21
+ modelId: agent.modelId,
22
+ provider,
23
+ sendMessage,
24
+ abort,
25
+ clear,
26
+ };
27
+ }