novacode 0.5.5 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -10
- package/dist/app-CbJSUNmf.mjs +22 -0
- package/dist/app-CbJSUNmf.mjs.map +1 -0
- package/dist/main.mjs +51 -61
- package/dist/main.mjs.map +1 -1
- package/package.json +3 -5
- package/dist/app-QfQR2FN9.mjs +0 -21
- package/dist/app-QfQR2FN9.mjs.map +0 -1
- package/src/agent/agent.ts +0 -87
- package/src/agent/loop.ts +0 -237
- package/src/agent/prompt.ts +0 -50
- package/src/commands/compact.ts +0 -28
- package/src/commands/index.ts +0 -86
- package/src/commands/models.ts +0 -85
- package/src/commands/providers.ts +0 -213
- package/src/commands/session.ts +0 -40
- package/src/config/providers.ts +0 -207
- package/src/config/store.ts +0 -66
- package/src/main.ts +0 -175
- package/src/onboarding/wizard.ts +0 -54
- package/src/provider/gemini.ts +0 -261
- package/src/provider/openai.ts +0 -215
- package/src/provider/stream.ts +0 -138
- package/src/session/compact.ts +0 -126
- package/src/session/store.ts +0 -229
- package/src/tools/fs.ts +0 -189
- package/src/tools/git.ts +0 -99
- package/src/tools/index.ts +0 -33
- package/src/tools/search.ts +0 -274
- package/src/tools/shell.ts +0 -90
- package/src/tools/web.ts +0 -239
- package/src/tui/app.tsx +0 -364
- package/src/tui/components/liveArea.tsx +0 -73
- package/src/tui/components/message.tsx +0 -113
- package/src/tui/components/statusBar.tsx +0 -58
- package/src/tui/markdown.ts +0 -62
- package/src/tui/prompts.tsx +0 -205
- package/src/types.ts +0 -248
- package/src/update.ts +0 -89
- package/src/util.ts +0 -61
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { Box, Text } from "ink"
|
|
2
|
-
import type { Msg } from "../../types.ts"
|
|
3
|
-
import { formatToolArgs } from "../../util.ts"
|
|
4
|
-
import { formatMarkdown } from "../markdown.ts"
|
|
5
|
-
|
|
6
|
-
const TOOL_STYLE: Record<string, string> = {
|
|
7
|
-
read: "blue",
|
|
8
|
-
write: "magenta",
|
|
9
|
-
edit: "yellow",
|
|
10
|
-
bash: "cyan",
|
|
11
|
-
glob: "green",
|
|
12
|
-
find: "green",
|
|
13
|
-
grep: "green",
|
|
14
|
-
tree: "green",
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function hasMeaningfulContent(msg: Msg): boolean {
|
|
18
|
-
if (msg.role === "user") return true
|
|
19
|
-
if (msg.role === "tool_result") return true
|
|
20
|
-
if (msg.role === "assistant") {
|
|
21
|
-
if (msg.model === "system") return true
|
|
22
|
-
return msg.content.some((c) => {
|
|
23
|
-
if (c.type === "thinking") return c.text.trim().length > 0
|
|
24
|
-
if (c.type === "text") return c.text.trim().length > 0
|
|
25
|
-
return false
|
|
26
|
-
})
|
|
27
|
-
}
|
|
28
|
-
return false
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function Message({ msg, isFirst }: { msg: Msg; isFirst: boolean }) {
|
|
32
|
-
if (msg.role === "user") {
|
|
33
|
-
return (
|
|
34
|
-
<Box marginTop={isFirst ? 0 : 1} flexDirection="row">
|
|
35
|
-
<Box flexShrink={0} marginRight={1}>
|
|
36
|
-
<Text bold color="green">
|
|
37
|
-
{">"}
|
|
38
|
-
</Text>
|
|
39
|
-
</Box>
|
|
40
|
-
<Box flexGrow={1} flexShrink={1}>
|
|
41
|
-
<Text>
|
|
42
|
-
{typeof msg.content === "string"
|
|
43
|
-
? msg.content
|
|
44
|
-
: msg.content.map((c) => (c.type === "text" ? c.text : "")).join("")}
|
|
45
|
-
</Text>
|
|
46
|
-
</Box>
|
|
47
|
-
</Box>
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (msg.role === "assistant") {
|
|
52
|
-
if (msg.model === "system") {
|
|
53
|
-
return (
|
|
54
|
-
<Box flexDirection="column" marginTop={0}>
|
|
55
|
-
{msg.content.map((c, i) =>
|
|
56
|
-
// biome-ignore lint/suspicious/noArrayIndexKey: stable turn content
|
|
57
|
-
c.type === "text" ? <Text key={i}>{formatMarkdown(c.text)}</Text> : null,
|
|
58
|
-
)}
|
|
59
|
-
</Box>
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const hasVisibleContent = msg.content.some((c) => c.type === "text" || c.type === "thinking")
|
|
64
|
-
if (!hasVisibleContent) return null
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<Box flexDirection="column" marginTop={0}>
|
|
68
|
-
{msg.content.map((c, i) => {
|
|
69
|
-
if (c.type === "thinking") {
|
|
70
|
-
return (
|
|
71
|
-
// biome-ignore lint/suspicious/noArrayIndexKey: stable turn content
|
|
72
|
-
<Text key={i} dimColor italic>
|
|
73
|
-
{c.text}
|
|
74
|
-
</Text>
|
|
75
|
-
)
|
|
76
|
-
}
|
|
77
|
-
if (c.type === "text") {
|
|
78
|
-
// biome-ignore lint/suspicious/noArrayIndexKey: stable turn content
|
|
79
|
-
return <Text key={i}>{formatMarkdown(c.text)}</Text>
|
|
80
|
-
}
|
|
81
|
-
return null
|
|
82
|
-
})}
|
|
83
|
-
</Box>
|
|
84
|
-
)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (msg.role === "tool_result") {
|
|
88
|
-
const args = msg.args ? formatToolArgs(msg.args, true) : ""
|
|
89
|
-
|
|
90
|
-
const resText = msg.content
|
|
91
|
-
.map((c) => (c.type === "text" ? c.text : ""))
|
|
92
|
-
.join("")
|
|
93
|
-
.trim()
|
|
94
|
-
|
|
95
|
-
const isRead = msg.tool === "read"
|
|
96
|
-
const lineCount = isRead ? resText.split("\n").length : 0
|
|
97
|
-
const color = TOOL_STYLE[msg.tool] || "white"
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<Box flexDirection="row">
|
|
101
|
-
<Text color={msg.isError ? "red" : "green"}>{msg.isError ? "✗" : "✓"} </Text>
|
|
102
|
-
<Text color={color} bold>
|
|
103
|
-
{msg.tool}
|
|
104
|
-
</Text>
|
|
105
|
-
{args && <Text> {args}</Text>}
|
|
106
|
-
{isRead && !msg.isError && <Text dimColor> ({lineCount} lines)</Text>}
|
|
107
|
-
{msg.isError && resText && <Text color="red"> {resText.slice(0, 80)}</Text>}
|
|
108
|
-
</Box>
|
|
109
|
-
)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return null
|
|
113
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { Box, Text } from "ink"
|
|
2
|
-
import type { Model } from "../../types.ts"
|
|
3
|
-
|
|
4
|
-
function fmtK(n: number): string {
|
|
5
|
-
const k = n / 1000
|
|
6
|
-
return k >= 100 ? `${Math.round(k)}K` : `${k.toFixed(1)}K`
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function formatTokenUsage(used: number, contextWindow: number): string {
|
|
10
|
-
if (used === 0) return `0/${fmtK(contextWindow)}`
|
|
11
|
-
const pct = Math.round((used / contextWindow) * 100)
|
|
12
|
-
return `${fmtK(used)}/${fmtK(contextWindow)} (${pct}%)`
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function StatusBar({
|
|
16
|
-
model,
|
|
17
|
-
usage,
|
|
18
|
-
busy,
|
|
19
|
-
suggestions,
|
|
20
|
-
selCmdIdx,
|
|
21
|
-
}: {
|
|
22
|
-
model: Model
|
|
23
|
-
usage: { in: number; out: number }
|
|
24
|
-
busy: boolean
|
|
25
|
-
suggestions: Array<{ name: string; desc: string }>
|
|
26
|
-
selCmdIdx: number
|
|
27
|
-
}) {
|
|
28
|
-
return (
|
|
29
|
-
<Box justifyContent="space-between">
|
|
30
|
-
<Box>
|
|
31
|
-
{suggestions.length > 0 ? (
|
|
32
|
-
<Box flexDirection="column" marginLeft={2}>
|
|
33
|
-
{suggestions.map((s, i) => (
|
|
34
|
-
<Box key={s.name}>
|
|
35
|
-
<Text
|
|
36
|
-
color={i === selCmdIdx ? "black" : "yellow"}
|
|
37
|
-
backgroundColor={i === selCmdIdx ? "yellow" : undefined}
|
|
38
|
-
>
|
|
39
|
-
/{s.name.padEnd(10)}
|
|
40
|
-
</Text>
|
|
41
|
-
<Text dimColor> {s.desc}</Text>
|
|
42
|
-
</Box>
|
|
43
|
-
))}
|
|
44
|
-
</Box>
|
|
45
|
-
) : (
|
|
46
|
-
<Text dimColor>Enter to send · /help for commands</Text>
|
|
47
|
-
)}
|
|
48
|
-
</Box>
|
|
49
|
-
|
|
50
|
-
<Box>
|
|
51
|
-
<Text dimColor>{formatTokenUsage(usage.in, model.contextWindow)}</Text>
|
|
52
|
-
<Text dimColor> │ </Text>
|
|
53
|
-
<Text dimColor>{model.id}</Text>
|
|
54
|
-
{busy && <Text dimColor> │ Esc to stop</Text>}
|
|
55
|
-
</Box>
|
|
56
|
-
</Box>
|
|
57
|
-
)
|
|
58
|
-
}
|
package/src/tui/markdown.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk"
|
|
2
|
-
|
|
3
|
-
export class MarkdownRenderer {
|
|
4
|
-
#inCodeBlock = false
|
|
5
|
-
#codeBlockLang = ""
|
|
6
|
-
|
|
7
|
-
renderLine(line: string): string {
|
|
8
|
-
if (line.startsWith("```")) {
|
|
9
|
-
if (this.#inCodeBlock) {
|
|
10
|
-
this.#inCodeBlock = false
|
|
11
|
-
return chalk.dim(`└${"─".repeat(50)}`)
|
|
12
|
-
}
|
|
13
|
-
this.#inCodeBlock = true
|
|
14
|
-
this.#codeBlockLang = line.slice(3).trim()
|
|
15
|
-
return chalk.dim(
|
|
16
|
-
"┌" +
|
|
17
|
-
"─".repeat(10) +
|
|
18
|
-
` [Code: ${this.#codeBlockLang || "text"}] ` +
|
|
19
|
-
"─".repeat(40 - (this.#codeBlockLang?.length || 4)),
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (this.#inCodeBlock) {
|
|
24
|
-
return chalk.cyan(`│ ${line}`)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (line.startsWith("#")) {
|
|
28
|
-
const match = line.match(/^(#{1,6})\s+(.*)$/)
|
|
29
|
-
if (match?.[1] && match[2]) {
|
|
30
|
-
const level = match[1].length
|
|
31
|
-
const content = match[2]
|
|
32
|
-
if (level === 1) return chalk.bold.magenta.underline(content)
|
|
33
|
-
if (level === 2) return chalk.bold.blue(content)
|
|
34
|
-
return chalk.bold.cyan(content)
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
let formatted = line
|
|
39
|
-
if (formatted.startsWith("- ") || formatted.startsWith("* ")) {
|
|
40
|
-
formatted = ` ${chalk.yellow("•")} ${formatted.slice(2)}`
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
formatted = formatted.replace(/`([^`]+)`/g, (_, code) => chalk.yellow(code))
|
|
44
|
-
formatted = formatted.replace(/\*\*([^*]+)\*\*/g, (_, bold) => chalk.bold(bold))
|
|
45
|
-
formatted = formatted.replace(/__([^_]+)__/g, (_, bold) => chalk.bold(bold))
|
|
46
|
-
formatted = formatted.replace(/\*([^*]+)\*/g, (_, italic) => chalk.italic(italic))
|
|
47
|
-
formatted = formatted.replace(/_([^_]+)_/g, (_, italic) => chalk.italic(italic))
|
|
48
|
-
formatted = formatted.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, text, url) => {
|
|
49
|
-
return `${chalk.blue(text)} ${chalk.dim(`(${url})`)}`
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
return formatted
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function formatMarkdown(text: string): string {
|
|
57
|
-
const renderer = new MarkdownRenderer()
|
|
58
|
-
return text
|
|
59
|
-
.split("\n")
|
|
60
|
-
.map((line) => renderer.renderLine(line))
|
|
61
|
-
.join("\n")
|
|
62
|
-
}
|
package/src/tui/prompts.tsx
DELETED
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import { Box, render, Text, useInput } from "ink"
|
|
2
|
-
import { useState } from "react"
|
|
3
|
-
|
|
4
|
-
interface SelectOption {
|
|
5
|
-
value: string
|
|
6
|
-
label: string
|
|
7
|
-
hint?: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function SelectPrompt({
|
|
11
|
-
message,
|
|
12
|
-
options,
|
|
13
|
-
header,
|
|
14
|
-
onSelect,
|
|
15
|
-
}: {
|
|
16
|
-
message: string
|
|
17
|
-
options: SelectOption[]
|
|
18
|
-
header?: string
|
|
19
|
-
onSelect: (value: string | null) => void
|
|
20
|
-
}) {
|
|
21
|
-
const [idx, setIdx] = useState(0)
|
|
22
|
-
|
|
23
|
-
useInput((_, key) => {
|
|
24
|
-
if (key.escape) {
|
|
25
|
-
onSelect(null)
|
|
26
|
-
return
|
|
27
|
-
}
|
|
28
|
-
if (key.upArrow) {
|
|
29
|
-
setIdx((i) => (i - 1 + options.length) % options.length)
|
|
30
|
-
return
|
|
31
|
-
}
|
|
32
|
-
if (key.downArrow) {
|
|
33
|
-
setIdx((i) => (i + 1) % options.length)
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
if (key.return) {
|
|
37
|
-
onSelect(options[idx]?.value ?? null)
|
|
38
|
-
}
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<Box flexDirection="column" paddingX={1}>
|
|
43
|
-
{header && (
|
|
44
|
-
<Box marginBottom={1}>
|
|
45
|
-
<Text>{header}</Text>
|
|
46
|
-
</Box>
|
|
47
|
-
)}
|
|
48
|
-
<Box marginBottom={1}>
|
|
49
|
-
<Text bold>{message}</Text>
|
|
50
|
-
</Box>
|
|
51
|
-
{options.map((opt, i) => (
|
|
52
|
-
<Box key={opt.value}>
|
|
53
|
-
<Text color={i === idx ? "green" : undefined}>
|
|
54
|
-
{i === idx ? "❯ " : " "}
|
|
55
|
-
{opt.label}
|
|
56
|
-
</Text>
|
|
57
|
-
{opt.hint && i === idx && <Text dimColor> {opt.hint}</Text>}
|
|
58
|
-
</Box>
|
|
59
|
-
))}
|
|
60
|
-
<Box marginTop={1}>
|
|
61
|
-
<Text dimColor>↑↓ navigate · Enter select · Esc cancel</Text>
|
|
62
|
-
</Box>
|
|
63
|
-
</Box>
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function PasswordPrompt({
|
|
68
|
-
message,
|
|
69
|
-
validate,
|
|
70
|
-
onSubmit,
|
|
71
|
-
}: {
|
|
72
|
-
message: string
|
|
73
|
-
validate?: (v: string) => string | undefined
|
|
74
|
-
onSubmit: (value: string | null) => void
|
|
75
|
-
}) {
|
|
76
|
-
const [value, setValue] = useState("")
|
|
77
|
-
const [error, setError] = useState("")
|
|
78
|
-
|
|
79
|
-
useInput((ch, key) => {
|
|
80
|
-
if (key.escape) {
|
|
81
|
-
onSubmit(null)
|
|
82
|
-
return
|
|
83
|
-
}
|
|
84
|
-
if (key.return) {
|
|
85
|
-
const err = validate?.(value)
|
|
86
|
-
if (err) {
|
|
87
|
-
setError(err)
|
|
88
|
-
return
|
|
89
|
-
}
|
|
90
|
-
onSubmit(value)
|
|
91
|
-
return
|
|
92
|
-
}
|
|
93
|
-
if (key.backspace || key.delete) {
|
|
94
|
-
setValue((v) => v.slice(0, -1))
|
|
95
|
-
setError("")
|
|
96
|
-
return
|
|
97
|
-
}
|
|
98
|
-
if (ch) {
|
|
99
|
-
setValue((v) => v + ch)
|
|
100
|
-
setError("")
|
|
101
|
-
}
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
return (
|
|
105
|
-
<Box flexDirection="column" paddingX={1}>
|
|
106
|
-
<Box marginBottom={1}>
|
|
107
|
-
<Text bold>{message}</Text>
|
|
108
|
-
</Box>
|
|
109
|
-
<Box>
|
|
110
|
-
<Text color="green">│ </Text>
|
|
111
|
-
<Text dimColor>{"*".repeat(value.length)}</Text>
|
|
112
|
-
<Text color="green">│</Text>
|
|
113
|
-
</Box>
|
|
114
|
-
{error && (
|
|
115
|
-
<Box>
|
|
116
|
-
<Text color="red">{error}</Text>
|
|
117
|
-
</Box>
|
|
118
|
-
)}
|
|
119
|
-
<Box marginTop={1}>
|
|
120
|
-
<Text dimColor>Enter submit · Esc cancel</Text>
|
|
121
|
-
</Box>
|
|
122
|
-
</Box>
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export function ConfirmPrompt({
|
|
127
|
-
message,
|
|
128
|
-
onConfirm,
|
|
129
|
-
}: {
|
|
130
|
-
message: string
|
|
131
|
-
onConfirm: (value: boolean | null) => void
|
|
132
|
-
}) {
|
|
133
|
-
const [yes, setYes] = useState(true)
|
|
134
|
-
|
|
135
|
-
useInput((_, key) => {
|
|
136
|
-
if (key.escape) {
|
|
137
|
-
onConfirm(null)
|
|
138
|
-
return
|
|
139
|
-
}
|
|
140
|
-
if (key.leftArrow || key.rightArrow || key.tab) {
|
|
141
|
-
setYes((y) => !y)
|
|
142
|
-
return
|
|
143
|
-
}
|
|
144
|
-
if (key.return) {
|
|
145
|
-
onConfirm(yes)
|
|
146
|
-
}
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
return (
|
|
150
|
-
<Box flexDirection="column" paddingX={1}>
|
|
151
|
-
<Box marginBottom={1}>
|
|
152
|
-
<Text bold>{message}</Text>
|
|
153
|
-
</Box>
|
|
154
|
-
<Box>
|
|
155
|
-
<Text color={yes ? "green" : undefined}>{yes ? "❯ " : " "}Yes</Text>
|
|
156
|
-
</Box>
|
|
157
|
-
<Box>
|
|
158
|
-
<Text color={!yes ? "red" : undefined}>{!yes ? "❯ " : " "}No</Text>
|
|
159
|
-
</Box>
|
|
160
|
-
<Box marginTop={1}>
|
|
161
|
-
<Text dimColor>←→ toggle · Enter confirm · Esc cancel</Text>
|
|
162
|
-
</Box>
|
|
163
|
-
</Box>
|
|
164
|
-
)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Standalone wrappers for use outside the main TUI (e.g. onboarding)
|
|
168
|
-
|
|
169
|
-
export function standaloneSelect(
|
|
170
|
-
message: string,
|
|
171
|
-
options: SelectOption[],
|
|
172
|
-
header?: string,
|
|
173
|
-
): Promise<string | null> {
|
|
174
|
-
return new Promise((resolve) => {
|
|
175
|
-
const { unmount } = render(
|
|
176
|
-
<SelectPrompt
|
|
177
|
-
message={message}
|
|
178
|
-
options={options}
|
|
179
|
-
header={header}
|
|
180
|
-
onSelect={(v) => {
|
|
181
|
-
unmount()
|
|
182
|
-
resolve(v)
|
|
183
|
-
}}
|
|
184
|
-
/>,
|
|
185
|
-
)
|
|
186
|
-
})
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export function standalonePassword(
|
|
190
|
-
message: string,
|
|
191
|
-
validate?: (v: string) => string | undefined,
|
|
192
|
-
): Promise<string | null> {
|
|
193
|
-
return new Promise((resolve) => {
|
|
194
|
-
const { unmount } = render(
|
|
195
|
-
<PasswordPrompt
|
|
196
|
-
message={message}
|
|
197
|
-
validate={validate}
|
|
198
|
-
onSubmit={(v) => {
|
|
199
|
-
unmount()
|
|
200
|
-
resolve(v)
|
|
201
|
-
}}
|
|
202
|
-
/>,
|
|
203
|
-
)
|
|
204
|
-
})
|
|
205
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared type definitions for the entire project.
|
|
3
|
-
* Includes messaging, tools, providers, and agent loop events.
|
|
4
|
-
*/
|
|
5
|
-
/** Content Parts */
|
|
6
|
-
|
|
7
|
-
export interface TextPart {
|
|
8
|
-
type: "text"
|
|
9
|
-
text: string
|
|
10
|
-
signature?: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface ImagePart {
|
|
14
|
-
type: "image"
|
|
15
|
-
data: string // base64
|
|
16
|
-
mime: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface ThinkPart {
|
|
20
|
-
type: "thinking"
|
|
21
|
-
text: string
|
|
22
|
-
signature?: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface ToolCallPart {
|
|
26
|
-
type: "tool_call"
|
|
27
|
-
id: string
|
|
28
|
-
name: string
|
|
29
|
-
args: Record<string, unknown>
|
|
30
|
-
signature?: string
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export type ContentPart = TextPart | ImagePart | ThinkPart | ToolCallPart
|
|
34
|
-
|
|
35
|
-
/** Messages */
|
|
36
|
-
|
|
37
|
-
export interface UserMsg {
|
|
38
|
-
role: "user"
|
|
39
|
-
content: string | ContentPart[]
|
|
40
|
-
ts: number
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface AssistantMsg {
|
|
44
|
-
role: "assistant"
|
|
45
|
-
content: ContentPart[]
|
|
46
|
-
model: string
|
|
47
|
-
provider: string
|
|
48
|
-
usage: Usage
|
|
49
|
-
stop: StopReason
|
|
50
|
-
error?: string
|
|
51
|
-
ts: number
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface ToolResultMsg {
|
|
55
|
-
role: "tool_result"
|
|
56
|
-
callId: string
|
|
57
|
-
tool: string
|
|
58
|
-
args?: Record<string, unknown>
|
|
59
|
-
content: ContentPart[]
|
|
60
|
-
isError: boolean
|
|
61
|
-
ts: number
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export type Msg = UserMsg | AssistantMsg | ToolResultMsg
|
|
65
|
-
export type StopReason = "stop" | "length" | "tool_use" | "error" | "aborted"
|
|
66
|
-
|
|
67
|
-
/** Usage */
|
|
68
|
-
|
|
69
|
-
export interface Usage {
|
|
70
|
-
in: number
|
|
71
|
-
out: number
|
|
72
|
-
cacheRead?: number
|
|
73
|
-
cacheWrite?: number
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** Provider */
|
|
77
|
-
|
|
78
|
-
export type ApiFormat = "openai" | "gemini"
|
|
79
|
-
|
|
80
|
-
export interface ProviderDef {
|
|
81
|
-
id: string
|
|
82
|
-
name: string
|
|
83
|
-
api: ApiFormat
|
|
84
|
-
baseUrl: string
|
|
85
|
-
envKey: string // env var name for API key
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export interface Model {
|
|
89
|
-
id: string
|
|
90
|
-
name: string
|
|
91
|
-
provider: string
|
|
92
|
-
contextWindow: number
|
|
93
|
-
maxTokens: number
|
|
94
|
-
supportsThinking: boolean
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** Tools */
|
|
98
|
-
|
|
99
|
-
export interface ToolDef {
|
|
100
|
-
name: string
|
|
101
|
-
description: string
|
|
102
|
-
parameters: ToolParamDef
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export interface ToolParamDef {
|
|
106
|
-
type: "object"
|
|
107
|
-
properties: Record<string, ToolPropDef>
|
|
108
|
-
required?: string[]
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export interface ToolPropDef {
|
|
112
|
-
type: string
|
|
113
|
-
description?: string
|
|
114
|
-
enum?: string[]
|
|
115
|
-
items?: ToolPropDef
|
|
116
|
-
properties?: Record<string, ToolPropDef>
|
|
117
|
-
required?: string[]
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export interface ToolResult {
|
|
121
|
-
content: ContentPart[]
|
|
122
|
-
isError: boolean
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export type ToolExecuteFn = (
|
|
126
|
-
args: Record<string, unknown>,
|
|
127
|
-
signal?: AbortSignal,
|
|
128
|
-
) => Promise<ToolResult>
|
|
129
|
-
|
|
130
|
-
export interface Tool {
|
|
131
|
-
def: ToolDef
|
|
132
|
-
execute: ToolExecuteFn
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/** Agent Events */
|
|
136
|
-
|
|
137
|
-
export type AgentEvent =
|
|
138
|
-
| { type: "start" }
|
|
139
|
-
| { type: "turn" }
|
|
140
|
-
| { type: "text_delta"; text: string }
|
|
141
|
-
| { type: "thinking_delta"; text: string }
|
|
142
|
-
| { type: "tool_call"; call: ToolCallPart }
|
|
143
|
-
| { type: "assistant_msg"; msg: AssistantMsg }
|
|
144
|
-
| { type: "tool_result"; callId: string; result: ToolResultMsg; args?: Record<string, unknown> }
|
|
145
|
-
| { type: "turn_end"; msg: AssistantMsg; results: ToolResultMsg[] }
|
|
146
|
-
| { type: "usage"; usage: Usage }
|
|
147
|
-
|
|
148
|
-
/** Config */
|
|
149
|
-
|
|
150
|
-
export interface NovaConfig {
|
|
151
|
-
provider: string
|
|
152
|
-
model: string
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export interface NovaAuth {
|
|
156
|
-
apiKeys: Record<string, string> // provider -> key
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/** Session */
|
|
160
|
-
|
|
161
|
-
export interface Session {
|
|
162
|
-
id: string
|
|
163
|
-
cwd: string
|
|
164
|
-
model: string
|
|
165
|
-
provider: string
|
|
166
|
-
title: string | null
|
|
167
|
-
created: number
|
|
168
|
-
updated: number
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/** Loop & Provider Types */
|
|
172
|
-
|
|
173
|
-
export interface LoopCtx {
|
|
174
|
-
system: string
|
|
175
|
-
messages: Msg[]
|
|
176
|
-
tools: Tool[]
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export interface LoopOpts {
|
|
180
|
-
api: ApiFormat
|
|
181
|
-
model: Model
|
|
182
|
-
apiKey: string
|
|
183
|
-
baseUrl: string
|
|
184
|
-
maxTurns?: number
|
|
185
|
-
// Intercept tool calls before they execute
|
|
186
|
-
beforeTool?: (
|
|
187
|
-
call: ToolCallPart,
|
|
188
|
-
args: Record<string, unknown>,
|
|
189
|
-
ctx: LoopCtx,
|
|
190
|
-
) => Promise<{ block?: boolean; reason?: string } | undefined>
|
|
191
|
-
// Run logic after a tool completes
|
|
192
|
-
afterTool?: (call: ToolCallPart, result: ToolResultMsg, ctx: LoopCtx) => Promise<void>
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export interface StreamOpts {
|
|
196
|
-
api: ApiFormat
|
|
197
|
-
model: Model
|
|
198
|
-
apiKey: string
|
|
199
|
-
baseUrl: string
|
|
200
|
-
system: string
|
|
201
|
-
messages: Msg[]
|
|
202
|
-
tools: ToolDef[]
|
|
203
|
-
signal?: AbortSignal
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
export interface IEventStream<T, R> {
|
|
207
|
-
[Symbol.asyncIterator](): AsyncGenerator<T>
|
|
208
|
-
result: R | undefined
|
|
209
|
-
isDone: boolean
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export type StreamFn = (opts: StreamOpts) => IEventStream<StreamEvent, AssistantResult>
|
|
213
|
-
|
|
214
|
-
export interface StreamEvent {
|
|
215
|
-
type: "text_delta" | "thinking_delta" | "tool_call" | "usage"
|
|
216
|
-
text?: string
|
|
217
|
-
call?: ToolCallPart
|
|
218
|
-
usage?: Usage
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
export interface AssistantResult {
|
|
222
|
-
content: ContentPart[]
|
|
223
|
-
usage: Usage
|
|
224
|
-
stop: StopReason
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/** Prompts — used by interactive commands within the TUI */
|
|
228
|
-
|
|
229
|
-
export interface Prompts {
|
|
230
|
-
select(config: {
|
|
231
|
-
message: string
|
|
232
|
-
header?: string
|
|
233
|
-
options: Array<{ value: string; label: string; hint?: string }>
|
|
234
|
-
}): Promise<string | null>
|
|
235
|
-
password(config: {
|
|
236
|
-
message: string
|
|
237
|
-
validate?: (v: string) => string | undefined
|
|
238
|
-
}): Promise<string | null>
|
|
239
|
-
confirm(config: { message: string }): Promise<boolean | null>
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/** Commands */
|
|
243
|
-
|
|
244
|
-
export interface Cmd {
|
|
245
|
-
name: string
|
|
246
|
-
desc: string
|
|
247
|
-
aliases?: string[]
|
|
248
|
-
}
|