@kweaver-ai/kweaver-sdk 0.4.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 (64) hide show
  1. package/bin/kweaver.js +9 -0
  2. package/dist/api/agent-chat.d.ts +69 -0
  3. package/dist/api/agent-chat.js +379 -0
  4. package/dist/api/agent-list.d.ts +12 -0
  5. package/dist/api/agent-list.js +33 -0
  6. package/dist/api/context-loader.d.ts +115 -0
  7. package/dist/api/context-loader.js +259 -0
  8. package/dist/api/conversations.d.ts +24 -0
  9. package/dist/api/conversations.js +64 -0
  10. package/dist/api/knowledge-networks.d.ts +57 -0
  11. package/dist/api/knowledge-networks.js +158 -0
  12. package/dist/api/ontology-query.d.ts +75 -0
  13. package/dist/api/ontology-query.js +238 -0
  14. package/dist/api/semantic-search.d.ts +12 -0
  15. package/dist/api/semantic-search.js +34 -0
  16. package/dist/auth/oauth.d.ts +75 -0
  17. package/dist/auth/oauth.js +417 -0
  18. package/dist/cli.d.ts +1 -0
  19. package/dist/cli.js +79 -0
  20. package/dist/client.d.ts +95 -0
  21. package/dist/client.js +104 -0
  22. package/dist/commands/agent-chat.d.ts +12 -0
  23. package/dist/commands/agent-chat.js +193 -0
  24. package/dist/commands/agent.d.ts +28 -0
  25. package/dist/commands/agent.js +431 -0
  26. package/dist/commands/auth.d.ts +9 -0
  27. package/dist/commands/auth.js +201 -0
  28. package/dist/commands/bkn.d.ts +70 -0
  29. package/dist/commands/bkn.js +1371 -0
  30. package/dist/commands/call.d.ts +14 -0
  31. package/dist/commands/call.js +151 -0
  32. package/dist/commands/context-loader.d.ts +1 -0
  33. package/dist/commands/context-loader.js +383 -0
  34. package/dist/commands/token.d.ts +2 -0
  35. package/dist/commands/token.js +24 -0
  36. package/dist/config/store.d.ts +77 -0
  37. package/dist/config/store.js +380 -0
  38. package/dist/index.d.ts +53 -0
  39. package/dist/index.js +44 -0
  40. package/dist/kweaver.d.ts +146 -0
  41. package/dist/kweaver.js +184 -0
  42. package/dist/resources/agents.d.ts +37 -0
  43. package/dist/resources/agents.js +60 -0
  44. package/dist/resources/bkn.d.ts +45 -0
  45. package/dist/resources/bkn.js +86 -0
  46. package/dist/resources/context-loader.d.ts +15 -0
  47. package/dist/resources/context-loader.js +32 -0
  48. package/dist/resources/conversations.d.ts +11 -0
  49. package/dist/resources/conversations.js +17 -0
  50. package/dist/resources/knowledge-networks.d.ts +65 -0
  51. package/dist/resources/knowledge-networks.js +167 -0
  52. package/dist/ui/ChatApp.d.ts +16 -0
  53. package/dist/ui/ChatApp.js +248 -0
  54. package/dist/ui/MarkdownBlock.d.ts +5 -0
  55. package/dist/ui/MarkdownBlock.js +137 -0
  56. package/dist/ui/display-text.d.ts +1 -0
  57. package/dist/ui/display-text.js +1 -0
  58. package/dist/utils/browser.d.ts +1 -0
  59. package/dist/utils/browser.js +20 -0
  60. package/dist/utils/display-text.d.ts +3 -0
  61. package/dist/utils/display-text.js +46 -0
  62. package/dist/utils/http.d.ts +17 -0
  63. package/dist/utils/http.js +72 -0
  64. package/package.json +62 -0
@@ -0,0 +1,167 @@
1
+ import { listKnowledgeNetworks, getKnowledgeNetwork, createKnowledgeNetwork, updateKnowledgeNetwork, deleteKnowledgeNetwork, listObjectTypes, listRelationTypes, listActionTypes, } from "../api/knowledge-networks.js";
2
+ import { fetchTextOrThrow } from "../utils/http.js";
3
+ function is404(err) {
4
+ return !!(err && typeof err === "object" && "status" in err && err.status === 404);
5
+ }
6
+ export class KnowledgeNetworksResource {
7
+ ctx;
8
+ constructor(ctx) {
9
+ this.ctx = ctx;
10
+ }
11
+ async list(opts = {}) {
12
+ const raw = await listKnowledgeNetworks({ ...this.ctx.base(), ...opts });
13
+ const parsed = JSON.parse(raw);
14
+ const data = parsed && typeof parsed === "object" && "data" in parsed
15
+ ? parsed.data
16
+ : parsed;
17
+ return Array.isArray(data) ? data : [];
18
+ }
19
+ async get(knId, opts = {}) {
20
+ const raw = await getKnowledgeNetwork({ ...this.ctx.base(), knId, ...opts });
21
+ return JSON.parse(raw);
22
+ }
23
+ async create(opts) {
24
+ const raw = await createKnowledgeNetwork({ ...this.ctx.base(), body: JSON.stringify(opts) });
25
+ return JSON.parse(raw);
26
+ }
27
+ async update(knId, opts) {
28
+ const raw = await updateKnowledgeNetwork({ ...this.ctx.base(), knId, body: JSON.stringify(opts) });
29
+ return JSON.parse(raw);
30
+ }
31
+ async delete(knId) {
32
+ await deleteKnowledgeNetwork({ ...this.ctx.base(), knId });
33
+ }
34
+ async listObjectTypes(knId, opts = {}) {
35
+ const raw = await listObjectTypes({ ...this.ctx.base(), knId, ...opts });
36
+ const parsed = JSON.parse(raw);
37
+ const items = Array.isArray(parsed)
38
+ ? parsed
39
+ : parsed && typeof parsed === "object" && "entries" in parsed
40
+ ? parsed.entries
41
+ : [];
42
+ return items;
43
+ }
44
+ async listRelationTypes(knId, opts = {}) {
45
+ const raw = await listRelationTypes({ ...this.ctx.base(), knId, ...opts });
46
+ const parsed = JSON.parse(raw);
47
+ const items = Array.isArray(parsed)
48
+ ? parsed
49
+ : parsed && typeof parsed === "object" && "entries" in parsed
50
+ ? parsed.entries
51
+ : [];
52
+ return items;
53
+ }
54
+ async listActionTypes(knId, opts = {}) {
55
+ const raw = await listActionTypes({ ...this.ctx.base(), knId, ...opts });
56
+ const parsed = JSON.parse(raw);
57
+ const items = Array.isArray(parsed)
58
+ ? parsed
59
+ : parsed && typeof parsed === "object" && "entries" in parsed
60
+ ? parsed.entries
61
+ : [];
62
+ return items;
63
+ }
64
+ /**
65
+ * Trigger a full build (index rebuild) of a BKN.
66
+ * Call this after adding datasources or modifying object/relation types.
67
+ *
68
+ * @param bknId BKN ID to build.
69
+ * @returns A promise that resolves immediately after the build is triggered.
70
+ * Use `buildAndWait` to block until completion.
71
+ */
72
+ async build(bknId) {
73
+ const { baseUrl, accessToken, businessDomain } = this.ctx.base();
74
+ const headers = {
75
+ "content-type": "application/json",
76
+ authorization: `Bearer ${accessToken}`,
77
+ token: accessToken,
78
+ "x-business-domain": businessDomain,
79
+ };
80
+ try {
81
+ await fetchTextOrThrow(`${baseUrl}/api/agent-retrieval/in/v1/kn/full_build_ontology`, { method: "POST", headers, body: JSON.stringify({ kn_id: bknId }) });
82
+ }
83
+ catch (err) {
84
+ if (!is404(err))
85
+ throw err;
86
+ // Fallback: call ontology-manager jobs endpoint directly
87
+ try {
88
+ await fetchTextOrThrow(`${baseUrl}/api/ontology-manager/in/v1/knowledge-networks/${encodeURIComponent(bknId)}/jobs`, {
89
+ method: "POST",
90
+ headers,
91
+ body: JSON.stringify({ name: `sdk_build_${bknId.slice(0, 8)}`, job_type: "full" }),
92
+ });
93
+ }
94
+ catch (err2) {
95
+ if (is404(err2)) {
96
+ throw new Error(`No build endpoint available for BKN ${bknId}. ` +
97
+ `Both agent-retrieval and ontology-manager returned 404. ` +
98
+ `This deployment may not support index rebuilds.`);
99
+ }
100
+ throw err2;
101
+ }
102
+ }
103
+ }
104
+ /** Poll build status for a BKN. */
105
+ async buildStatus(bknId) {
106
+ const { baseUrl, accessToken, businessDomain } = this.ctx.base();
107
+ const headers = {
108
+ authorization: `Bearer ${accessToken}`,
109
+ token: accessToken,
110
+ "x-business-domain": businessDomain,
111
+ };
112
+ try {
113
+ const { body } = await fetchTextOrThrow(`${baseUrl}/api/agent-retrieval/in/v1/kn/full_ontology_building_status?kn_id=${encodeURIComponent(bknId)}`, { headers });
114
+ const data = JSON.parse(body);
115
+ return { state: data.state ?? "running", state_detail: data.state_detail };
116
+ }
117
+ catch (err) {
118
+ if (!is404(err))
119
+ throw err;
120
+ // Fallback: check ontology-manager jobs for latest status
121
+ try {
122
+ const { body } = await fetchTextOrThrow(`${baseUrl}/api/ontology-manager/in/v1/knowledge-networks/${encodeURIComponent(bknId)}/jobs?limit=1&direction=desc`, { headers });
123
+ const data = JSON.parse(body);
124
+ const jobs = Array.isArray(data)
125
+ ? data
126
+ : data && typeof data === "object" && "entries" in data
127
+ ? (data.entries ?? [])
128
+ : data && typeof data === "object" && "data" in data
129
+ ? (data.data ?? [])
130
+ : [];
131
+ if (jobs.length > 0) {
132
+ return { state: jobs[0].state ?? "running" };
133
+ }
134
+ return { state: "completed" };
135
+ }
136
+ catch (err2) {
137
+ if (is404(err2))
138
+ return { state: "completed" };
139
+ throw err2;
140
+ }
141
+ }
142
+ }
143
+ /**
144
+ * Trigger a full BKN build and wait for it to complete.
145
+ *
146
+ * @param bknId BKN ID to build.
147
+ * @param timeout Max wait time in milliseconds (default 300_000 = 5 min).
148
+ * @param interval Poll interval in milliseconds (default 2_000).
149
+ * @throws Error if the build fails or times out.
150
+ */
151
+ async buildAndWait(bknId, { timeout = 300_000, interval = 2_000 } = {}) {
152
+ await this.build(bknId);
153
+ const deadline = Date.now() + timeout;
154
+ while (true) {
155
+ await new Promise((r) => setTimeout(r, interval));
156
+ const status = await this.buildStatus(bknId);
157
+ if (status.state === "completed")
158
+ return status;
159
+ if (status.state === "failed") {
160
+ throw new Error(`BKN build failed for ${bknId}: ${status.state_detail ?? "no detail"}`);
161
+ }
162
+ if (Date.now() > deadline) {
163
+ throw new Error(`BKN build timed out after ${timeout}ms for ${bknId}`);
164
+ }
165
+ }
166
+ }
167
+ }
@@ -0,0 +1,16 @@
1
+ import React from "react";
2
+ export interface TokenPayload {
3
+ baseUrl: string;
4
+ accessToken: string;
5
+ }
6
+ export interface ChatAppProps {
7
+ getToken: () => Promise<TokenPayload>;
8
+ agentId: string;
9
+ agentKey: string;
10
+ agentVersion: string;
11
+ businessDomain: string;
12
+ verbose: boolean;
13
+ initialConversationId?: string;
14
+ stream?: boolean;
15
+ }
16
+ export declare function ChatApp(props: ChatAppProps): React.JSX.Element;
@@ -0,0 +1,248 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { useRef, useState } from "react";
3
+ import { Box, Static, Text, useApp, useInput } from "ink";
4
+ import TextInput from "ink-text-input";
5
+ import Spinner from "ink-spinner";
6
+ import { sendChatRequest, sendChatRequestStream } from "../api/agent-chat.js";
7
+ import { formatHttpError } from "../auth/oauth.js";
8
+ import { normalizeDisplayText } from "./display-text.js";
9
+ import { MarkdownBlock } from "./MarkdownBlock.js";
10
+ const MIDDLE_ANSWER_MAX_LINES = 5;
11
+ function formatProgressValue(value) {
12
+ if (typeof value === "string")
13
+ return normalizeDisplayText(value);
14
+ if (typeof value === "number" || typeof value === "boolean")
15
+ return String(value);
16
+ if (value === null || value === undefined)
17
+ return "";
18
+ try {
19
+ return normalizeDisplayText(JSON.stringify(value));
20
+ }
21
+ catch {
22
+ return String(value);
23
+ }
24
+ }
25
+ function getLinesForItem(item, idx) {
26
+ const name = item.agent_name ?? item.skill_info?.name ?? `Step ${idx + 1}`;
27
+ const status = item.status ?? "";
28
+ const lines = [`${name}${status ? ` — ${status}` : ""}`];
29
+ const isTool = item.skill_info?.type === "TOOL";
30
+ if (isTool) {
31
+ const toolName = item.skill_info?.name ?? item.agent_name;
32
+ if (toolName) {
33
+ lines.push(`tool: ${normalizeDisplayText(toolName)}`);
34
+ }
35
+ for (const arg of item.skill_info?.args ?? []) {
36
+ const argName = arg.name ?? "arg";
37
+ const argValue = formatProgressValue(arg.value);
38
+ lines.push(`${argName}: ${argValue}`);
39
+ }
40
+ }
41
+ let answerStr;
42
+ if (typeof item.answer === "string") {
43
+ answerStr = item.answer;
44
+ }
45
+ else if (item.answer && typeof item.answer === "object") {
46
+ const o = item.answer;
47
+ const d = typeof o.description === "string" ? o.description : "";
48
+ const c = typeof o.code === "string" ? o.code : "";
49
+ const s = typeof o.solution === "string" ? o.solution : "";
50
+ const link = typeof o.link === "string" ? o.link : "";
51
+ answerStr = [d, c ? `code: ${c}` : "", s ? `solution: ${s}` : "", link ? `link: ${link}` : ""]
52
+ .filter(Boolean)
53
+ .join("\n");
54
+ }
55
+ else {
56
+ answerStr = item.result ?? "";
57
+ }
58
+ if (answerStr)
59
+ lines.push(...normalizeDisplayText(answerStr).split("\n"));
60
+ return lines;
61
+ }
62
+ function getVisibleProgress(items) {
63
+ const itemLines = items.map((item, idx) => ({ item, lines: getLinesForItem(item, idx) }));
64
+ let remaining = MIDDLE_ANSWER_MAX_LINES;
65
+ const visible = [];
66
+ for (const { item, lines } of itemLines) {
67
+ if (remaining <= 0)
68
+ break;
69
+ const take = Math.min(remaining, lines.length);
70
+ visible.push({ item, lines: lines.slice(0, take) });
71
+ remaining -= take;
72
+ }
73
+ const totalLines = itemLines.reduce((s, { lines }) => s + lines.length, 0);
74
+ return { visible, hiddenLineCount: Math.max(0, totalLines - MIDDLE_ANSWER_MAX_LINES) };
75
+ }
76
+ function renderStaticItem(item, index) {
77
+ if (item.kind === "header") {
78
+ return (_jsx(Box, { children: _jsx(Text, { dimColor: true, children: "Chat \u2014 type exit, quit or q to leave" }) }, index));
79
+ }
80
+ if (item.kind === "msg") {
81
+ const { msg } = item;
82
+ if (msg.role === "user") {
83
+ return (_jsx(Box, { flexDirection: "column", marginY: 1, width: "100%", paddingRight: 1, children: _jsx(Box, { width: "100%", paddingX: 1, paddingY: 1, backgroundColor: "#2C2C2C", children: _jsx(Text, { color: "#9B9B9B", children: normalizeDisplayText(msg.content) }) }) }, index));
84
+ }
85
+ return (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsxs(_Fragment, { children: [msg.hadProgress ? (_jsx(Text, { dimColor: true, children: "/plan \u751F\u6210\u4FEE\u6539\u8BA1\u5212" })) : null, _jsx(MarkdownBlock, { content: normalizeDisplayText(msg.content) })] }) }, index));
86
+ }
87
+ return _jsx(Box, {}, index);
88
+ }
89
+ const DynamicContent = React.memo(function DynamicContent(props) {
90
+ const { toolProgress, streamingContent, status, errorMessage } = props;
91
+ return (_jsxs(Box, { flexDirection: "column", children: [toolProgress.length > 0 ? (_jsxs(Box, { marginY: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, paddingY: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, dimColor: true, children: "Tool output" }), (() => {
92
+ const { visible, hiddenLineCount } = getVisibleProgress(toolProgress);
93
+ return (_jsxs(_Fragment, { children: [visible.map(({ item, lines }, i) => {
94
+ const isTool = item.skill_info?.type === "TOOL";
95
+ const content = (_jsx(_Fragment, { children: lines.map((line, j) => (_jsx(Text, { dimColor: true, children: line }, j))) }));
96
+ if (isTool) {
97
+ return (_jsx(Box, { marginY: 0, borderStyle: "single", borderColor: "green", paddingX: 1, paddingY: 0, flexDirection: "column", children: content }, i));
98
+ }
99
+ return (_jsx(Box, { flexDirection: "column", children: content }, i));
100
+ }), hiddenLineCount > 0 ? (_jsxs(Text, { dimColor: true, italic: true, children: ["(\u5DF2\u9690\u85CF ", hiddenLineCount, " \u884C)"] })) : null] }));
101
+ })()] })) : null, toolProgress.length > 0 && (streamingContent || status === "loading") ? (_jsx(Text, { dimColor: true, children: "/plan \u751F\u6210\u4FEE\u6539\u8BA1\u5212" })) : null, streamingContent ? (_jsx(Box, { flexDirection: "column", marginY: 1, children: _jsx(MarkdownBlock, { content: normalizeDisplayText(streamingContent) }) })) : null, errorMessage ? (_jsx(Box, { marginY: 1, children: _jsx(Text, { color: "red", children: errorMessage }) })) : null] }));
102
+ });
103
+ export function ChatApp(props) {
104
+ const { getToken, agentId, agentKey, agentVersion, businessDomain, verbose, initialConversationId, stream: useStream = true, } = props;
105
+ const [messages, setMessages] = useState([]);
106
+ const [streamingContent, setStreamingContent] = useState("");
107
+ const [toolProgress, setToolProgress] = useState([]);
108
+ const [conversationId, setConversationId] = useState(initialConversationId);
109
+ const [inputValue, setInputValue] = useState("");
110
+ const [status, setStatus] = useState("idle");
111
+ const [errorMessage, setErrorMessage] = useState("");
112
+ const pendingQueriesRef = useRef([]);
113
+ const streamingBufferRef = useRef("");
114
+ const toolProgressBufferRef = useRef([]);
115
+ const streamFlushIntervalRef = useRef(null);
116
+ const STREAM_FLUSH_MS = 550;
117
+ const { exit } = useApp();
118
+ const EXIT_WORDS = ["exit", "quit", "q"];
119
+ const doSendQuery = (query, convId) => {
120
+ setMessages((prev) => [...prev, { role: "user", content: query }]);
121
+ setInputValue("");
122
+ setStreamingContent("");
123
+ setToolProgress([]);
124
+ setErrorMessage("");
125
+ setStatus("loading");
126
+ streamingBufferRef.current = "";
127
+ toolProgressBufferRef.current = [];
128
+ const onDone = (result) => {
129
+ const hadProgress = (result.progress?.length ?? 0) > 0 || toolProgressBufferRef.current.length > 0;
130
+ setStreamingContent("");
131
+ setToolProgress(result.progress !== undefined ? result.progress : toolProgressBufferRef.current);
132
+ setMessages((prev) => [
133
+ ...prev,
134
+ { role: "agent", content: normalizeDisplayText(result.text), hadProgress },
135
+ ]);
136
+ if (result.conversationId) {
137
+ setConversationId(result.conversationId);
138
+ }
139
+ setStatus("idle");
140
+ const next = pendingQueriesRef.current.shift();
141
+ if (next !== undefined) {
142
+ doSendQuery(next, result.conversationId);
143
+ }
144
+ };
145
+ const onFail = (error) => {
146
+ setErrorMessage(formatHttpError(error));
147
+ setStatus("error");
148
+ setStreamingContent("");
149
+ setToolProgress([]);
150
+ const next = pendingQueriesRef.current.shift();
151
+ if (next !== undefined) {
152
+ pendingQueriesRef.current.unshift(next);
153
+ }
154
+ };
155
+ if (!useStream) {
156
+ void getToken()
157
+ .then((token) => sendChatRequest({
158
+ baseUrl: token.baseUrl,
159
+ accessToken: token.accessToken,
160
+ agentId,
161
+ agentKey,
162
+ agentVersion,
163
+ query,
164
+ conversationId: convId,
165
+ stream: false,
166
+ verbose,
167
+ businessDomain,
168
+ }))
169
+ .then(onDone)
170
+ .catch(onFail);
171
+ return;
172
+ }
173
+ if (streamFlushIntervalRef.current) {
174
+ clearInterval(streamFlushIntervalRef.current);
175
+ streamFlushIntervalRef.current = null;
176
+ }
177
+ streamFlushIntervalRef.current = setInterval(() => {
178
+ const nextText = streamingBufferRef.current;
179
+ const nextProgress = toolProgressBufferRef.current;
180
+ const normalizedNextText = normalizeDisplayText(nextText);
181
+ setStreamingContent((prev) => (normalizedNextText === prev ? prev : normalizedNextText));
182
+ setToolProgress((prev) => prev.length !== nextProgress.length || prev.some((p, i) => p !== nextProgress[i])
183
+ ? nextProgress
184
+ : prev);
185
+ }, STREAM_FLUSH_MS);
186
+ void getToken()
187
+ .then((token) => sendChatRequestStream({
188
+ baseUrl: token.baseUrl,
189
+ accessToken: token.accessToken,
190
+ agentId,
191
+ agentKey,
192
+ agentVersion,
193
+ query,
194
+ conversationId: convId,
195
+ stream: true,
196
+ verbose,
197
+ businessDomain,
198
+ }, {
199
+ onTextDelta: (fullText) => {
200
+ streamingBufferRef.current = normalizeDisplayText(fullText);
201
+ },
202
+ onProgress: (progress) => {
203
+ toolProgressBufferRef.current = progress;
204
+ },
205
+ }))
206
+ .then((result) => {
207
+ if (streamFlushIntervalRef.current) {
208
+ clearInterval(streamFlushIntervalRef.current);
209
+ streamFlushIntervalRef.current = null;
210
+ }
211
+ streamingBufferRef.current = "";
212
+ onDone(result);
213
+ })
214
+ .catch((error) => {
215
+ if (streamFlushIntervalRef.current) {
216
+ clearInterval(streamFlushIntervalRef.current);
217
+ streamFlushIntervalRef.current = null;
218
+ }
219
+ streamingBufferRef.current = "";
220
+ onFail(error);
221
+ });
222
+ };
223
+ useInput((input, key) => {
224
+ if (key.ctrl && input === "\x03") {
225
+ exit();
226
+ }
227
+ });
228
+ const handleSubmit = (value) => {
229
+ const trimmed = value.trim();
230
+ if (!trimmed)
231
+ return;
232
+ if (EXIT_WORDS.includes(trimmed.toLowerCase())) {
233
+ exit();
234
+ return;
235
+ }
236
+ if (status === "loading") {
237
+ pendingQueriesRef.current.push(trimmed);
238
+ setInputValue("");
239
+ return;
240
+ }
241
+ doSendQuery(trimmed, conversationId);
242
+ };
243
+ const staticItems = [
244
+ { kind: "header" },
245
+ ...messages.map((msg) => ({ kind: "msg", msg })),
246
+ ];
247
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Static, { items: staticItems, style: { width: "100%" }, children: (item, index) => renderStaticItem(item, index) }), _jsx(DynamicContent, { toolProgress: toolProgress, streamingContent: streamingContent, status: status, errorMessage: errorMessage }), _jsxs(Box, { marginTop: 1, borderStyle: "single", borderColor: "gray", backgroundColor: "black", paddingX: 1, paddingY: 0, children: [_jsx(Text, { color: "gray", children: "> " }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleSubmit, placeholder: "Type a message..." })] }), status === "loading" ? (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "green", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Thinking..." })] })) : null] }));
248
+ }
@@ -0,0 +1,5 @@
1
+ import React from "react";
2
+ export interface MarkdownBlockProps {
3
+ content: string;
4
+ }
5
+ export declare function MarkdownBlock({ content }: MarkdownBlockProps): React.JSX.Element;
@@ -0,0 +1,137 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Fragment, useMemo } from "react";
3
+ import { Box, Text } from "ink";
4
+ import { marked } from "marked";
5
+ import { normalizeDisplayText } from "./display-text.js";
6
+ function hasTokens(token) {
7
+ return "tokens" in token && Array.isArray(token.tokens);
8
+ }
9
+ function isListToken(token) {
10
+ return token.type === "list" && "items" in token;
11
+ }
12
+ function isTableToken(token) {
13
+ return token.type === "table" && "header" in token && "rows" in token;
14
+ }
15
+ function tokensToPlainText(tokens) {
16
+ if (!tokens || tokens.length === 0)
17
+ return "";
18
+ return tokens
19
+ .map((token) => {
20
+ switch (token.type) {
21
+ case "text":
22
+ return hasTokens(token) ? tokensToPlainText(token.tokens) : token.text;
23
+ case "strong":
24
+ case "em":
25
+ case "del":
26
+ return tokensToPlainText(token.tokens ?? []);
27
+ case "codespan":
28
+ return `\`${token.text}\``;
29
+ case "link":
30
+ return `${tokensToPlainText(token.tokens ?? [])} (${token.href})`;
31
+ case "image":
32
+ return `[image: ${token.text || token.href}]`;
33
+ case "br":
34
+ return "\n";
35
+ case "escape":
36
+ case "html":
37
+ return token.text;
38
+ default:
39
+ if (hasTokens(token)) {
40
+ return tokensToPlainText(token.tokens);
41
+ }
42
+ if ("text" in token && typeof token.text === "string") {
43
+ return token.text;
44
+ }
45
+ return token.raw;
46
+ }
47
+ })
48
+ .join("");
49
+ }
50
+ function renderInlineToken(token, key) {
51
+ switch (token.type) {
52
+ case "text":
53
+ if (hasTokens(token)) {
54
+ return _jsx(Fragment, { children: renderInlineTokens(token.tokens, `${key}-text`) }, key);
55
+ }
56
+ return _jsx(Fragment, { children: token.text }, key);
57
+ case "strong":
58
+ return (_jsx(Text, { bold: true, children: renderInlineTokens(token.tokens ?? [], `${key}-strong`) }, key));
59
+ case "em":
60
+ return (_jsx(Text, { italic: true, children: renderInlineTokens(token.tokens ?? [], `${key}-em`) }, key));
61
+ case "del":
62
+ return (_jsx(Text, { dimColor: true, children: renderInlineTokens(token.tokens ?? [], `${key}-del`) }, key));
63
+ case "codespan":
64
+ return (_jsx(Text, { color: "cyan", children: ` \`${token.text}\` ` }, key));
65
+ case "link":
66
+ return (_jsxs(Text, { color: "blue", underline: true, children: [renderInlineTokens(token.tokens ?? [], `${key}-link`), ` (${token.href})`] }, key));
67
+ case "image":
68
+ return (_jsxs(Text, { color: "yellow", children: ["[", token.text || "image", "] ", token.href] }, key));
69
+ case "br":
70
+ return _jsx(Fragment, { children: "\n" }, key);
71
+ case "escape":
72
+ case "html":
73
+ return _jsx(Fragment, { children: token.text }, key);
74
+ default:
75
+ if (hasTokens(token)) {
76
+ return _jsx(Fragment, { children: renderInlineTokens(token.tokens, `${key}-generic`) }, key);
77
+ }
78
+ if ("text" in token && typeof token.text === "string") {
79
+ return _jsx(Fragment, { children: token.text }, key);
80
+ }
81
+ return _jsx(Fragment, { children: token.raw }, key);
82
+ }
83
+ }
84
+ function renderInlineTokens(tokens, keyPrefix) {
85
+ return tokens.map((token, index) => renderInlineToken(token, `${keyPrefix}-${index}`));
86
+ }
87
+ function renderTable(token, key) {
88
+ const rows = [
89
+ token.header.map((cell) => tokensToPlainText(cell.tokens)),
90
+ ...token.rows.map((row) => row.map((cell) => tokensToPlainText(cell.tokens))),
91
+ ];
92
+ return (_jsx(Box, { flexDirection: "column", marginBottom: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: rows.map((row, rowIndex) => (_jsx(Text, { children: row.join(" | ") }, `${key}-row-${rowIndex}`))) }, key));
93
+ }
94
+ function renderList(token, key) {
95
+ const start = typeof token.start === "number" ? token.start : 1;
96
+ return (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: token.items.map((item, index) => {
97
+ const marker = token.ordered ? `${start + index}.` : "•";
98
+ const text = tokensToPlainText(item.tokens).trim();
99
+ return (_jsx(Box, { children: _jsx(Text, { children: `${marker} ${text}` }) }, `${key}-item-${index}`));
100
+ }) }, key));
101
+ }
102
+ function renderBlock(token, key) {
103
+ switch (token.type) {
104
+ case "space":
105
+ return null;
106
+ case "heading":
107
+ return (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: token.depth <= 2 ? "cyan" : "green", children: renderInlineTokens(token.tokens ?? [], `${key}-heading`) }) }, key));
108
+ case "paragraph":
109
+ return (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: renderInlineTokens(token.tokens ?? [], `${key}-paragraph`) }) }, key));
110
+ case "text":
111
+ return (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: hasTokens(token) ? renderInlineTokens(token.tokens, `${key}-text`) : token.text }) }, key));
112
+ case "code":
113
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: [token.lang ? _jsx(Text, { dimColor: true, children: token.lang }) : null, token.text.split("\n").map((line, index) => (_jsx(Text, { color: "cyan", children: line }, `${key}-line-${index}`)))] }, key));
114
+ case "blockquote": {
115
+ const lines = tokensToPlainText(token.tokens).split("\n");
116
+ return (_jsx(Box, { flexDirection: "column", marginBottom: 1, paddingLeft: 1, children: lines.map((line, index) => (_jsx(Text, { dimColor: true, children: `> ${line}` }, `${key}-quote-${index}`))) }, key));
117
+ }
118
+ case "list":
119
+ return isListToken(token) ? renderList(token, key) : null;
120
+ case "table":
121
+ return isTableToken(token) ? renderTable(token, key) : null;
122
+ case "hr":
123
+ return (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: "----------------------------------------" }) }, key));
124
+ case "html":
125
+ return token.block ? (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: token.text }) }, key)) : null;
126
+ default:
127
+ return (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: tokensToPlainText(hasTokens(token) ? token.tokens : undefined) || token.raw }) }, key));
128
+ }
129
+ }
130
+ export function MarkdownBlock({ content }) {
131
+ const trimmed = normalizeDisplayText(content).trim();
132
+ const tokens = useMemo(() => (trimmed ? marked.lexer(trimmed) : []), [trimmed]);
133
+ if (!trimmed) {
134
+ return _jsx(Text, {});
135
+ }
136
+ return _jsx(Box, { flexDirection: "column", children: tokens.map((token, index) => renderBlock(token, `md-${index}`)) });
137
+ }
@@ -0,0 +1 @@
1
+ export { decodeHtmlEntities, normalizeDisplayText, stripHtmlComments, } from "../utils/display-text.js";
@@ -0,0 +1 @@
1
+ export { decodeHtmlEntities, normalizeDisplayText, stripHtmlComments, } from "../utils/display-text.js";
@@ -0,0 +1 @@
1
+ export declare function openBrowser(url: string): Promise<boolean>;
@@ -0,0 +1,20 @@
1
+ import { spawn } from "node:child_process";
2
+ export function openBrowser(url) {
3
+ const command = process.platform === "darwin"
4
+ ? "open"
5
+ : process.platform === "win32"
6
+ ? "cmd"
7
+ : "xdg-open";
8
+ const args = process.platform === "win32"
9
+ ? ["/c", "start", "", url]
10
+ : [url];
11
+ return new Promise((resolve) => {
12
+ const child = spawn(command, args, {
13
+ detached: true,
14
+ stdio: "ignore",
15
+ });
16
+ child.on("error", () => resolve(false));
17
+ child.unref();
18
+ resolve(true);
19
+ });
20
+ }
@@ -0,0 +1,3 @@
1
+ export declare function decodeHtmlEntities(s: string): string;
2
+ export declare function stripHtmlComments(s: string): string;
3
+ export declare function normalizeDisplayText(s: string): string;
@@ -0,0 +1,46 @@
1
+ function decodeHtmlEntitiesPass(s) {
2
+ return s
3
+ .replace(/&amp;/g, "&")
4
+ .replace(/&#(\d+);/g, (_, code) => String.fromCodePoint(Number(code)))
5
+ .replace(/&#x([0-9a-fA-F]+);/g, (_, code) => String.fromCodePoint(parseInt(code, 16)))
6
+ .replace(/&quot;/g, "\"")
7
+ .replace(/&#39;|&apos;/g, "'")
8
+ .replace(/&lt;/g, "<")
9
+ .replace(/&gt;/g, ">")
10
+ .replace(/&nbsp;/g, " ");
11
+ }
12
+ export function decodeHtmlEntities(s) {
13
+ let current = s;
14
+ for (let i = 0; i < 5; i += 1) {
15
+ const decoded = decodeHtmlEntitiesPass(current);
16
+ if (decoded === current) {
17
+ break;
18
+ }
19
+ current = decoded;
20
+ }
21
+ return current;
22
+ }
23
+ export function stripHtmlComments(s) {
24
+ let current = s.trimStart();
25
+ // Suppress leading comment-only frames during streaming, but keep text that follows
26
+ // once the leading comment block is complete.
27
+ while (current.startsWith("<!--")) {
28
+ const endIdx = current.indexOf("-->");
29
+ if (endIdx === -1) {
30
+ return "";
31
+ }
32
+ current = current.slice(endIdx + 3).trimStart();
33
+ }
34
+ return current.replace(/<!--[\s\S]*?-->/g, "").trim();
35
+ }
36
+ export function normalizeDisplayText(s) {
37
+ let current = s;
38
+ for (let i = 0; i < 5; i += 1) {
39
+ const normalized = stripHtmlComments(decodeHtmlEntities(current));
40
+ if (normalized === current) {
41
+ break;
42
+ }
43
+ current = normalized;
44
+ }
45
+ return current;
46
+ }