novacode 0.2.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/LICENSE +201 -0
- package/README.md +89 -0
- package/package.json +56 -0
- package/src/agent/agent.ts +87 -0
- package/src/agent/loop.ts +218 -0
- package/src/agent/prompt.ts +50 -0
- package/src/commands/compact.ts +28 -0
- package/src/commands/index.ts +62 -0
- package/src/commands/models.ts +86 -0
- package/src/commands/providers.ts +222 -0
- package/src/commands/session.ts +40 -0
- package/src/config/providers.ts +199 -0
- package/src/config/store.ts +67 -0
- package/src/main.ts +169 -0
- package/src/onboarding/wizard.ts +58 -0
- package/src/provider/gemini.ts +254 -0
- package/src/provider/openai.ts +218 -0
- package/src/provider/registry.ts +62 -0
- package/src/provider/stream.ts +77 -0
- package/src/session/compact.ts +126 -0
- package/src/session/store.ts +206 -0
- package/src/tools/fs.ts +195 -0
- package/src/tools/git.ts +82 -0
- package/src/tools/index.ts +33 -0
- package/src/tools/search.ts +252 -0
- package/src/tools/shell.ts +89 -0
- package/src/tools/web.ts +239 -0
- package/src/tui/app.tsx +517 -0
- package/src/tui/markdown.ts +62 -0
- package/src/tui/print.ts +75 -0
- package/src/types.ts +233 -0
- package/src/util.ts +88 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import chalk from "chalk"
|
|
2
|
+
import type { Agent } from "../agent/agent.ts"
|
|
3
|
+
import { compact as runCompact } from "../session/compact.ts"
|
|
4
|
+
import type { SessionStore } from "../session/store.ts"
|
|
5
|
+
|
|
6
|
+
export async function handleCompact(
|
|
7
|
+
agent: Agent,
|
|
8
|
+
store: SessionStore,
|
|
9
|
+
sessionId: string,
|
|
10
|
+
): Promise<string> {
|
|
11
|
+
const res = await runCompact(
|
|
12
|
+
store,
|
|
13
|
+
sessionId,
|
|
14
|
+
agent.messages,
|
|
15
|
+
agent.model,
|
|
16
|
+
agent.apiKey,
|
|
17
|
+
agent.baseUrl,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
if (res.compacted) {
|
|
21
|
+
// Update agent messages
|
|
22
|
+
const msgs = store.messages(sessionId)
|
|
23
|
+
agent.setMessages(msgs)
|
|
24
|
+
return chalk.green(`✓ Context compacted (${res.msgsRemoved} messages removed)`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return chalk.yellow("Context is small enough, no compaction needed.")
|
|
28
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import chalk from "chalk"
|
|
2
|
+
import type { Agent } from "../agent/agent.ts"
|
|
3
|
+
import type { SessionStore } from "../session/store.ts"
|
|
4
|
+
import type { Cmd } from "../types.ts"
|
|
5
|
+
import { handleCompact } from "./compact.ts"
|
|
6
|
+
import { handleModels } from "./models.ts"
|
|
7
|
+
import { handleProviders } from "./providers.ts"
|
|
8
|
+
|
|
9
|
+
export const COMMANDS: Cmd[] = [
|
|
10
|
+
{ name: "models", desc: "Switch model", aliases: ["model"] },
|
|
11
|
+
{ name: "providers", desc: "Manage providers", aliases: ["prov", "config", "cfg"] },
|
|
12
|
+
{ name: "compact", desc: "Compact context" },
|
|
13
|
+
{ name: "help", desc: "Show help" },
|
|
14
|
+
{ name: "clear", desc: "Clear screen" },
|
|
15
|
+
{ name: "quit", desc: "Exit (Ctrl+D)", aliases: ["exit"] },
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
const HELP = `
|
|
19
|
+
${chalk.bold("Commands:")}
|
|
20
|
+
${COMMANDS.map((c) => ` /${c.name.padEnd(12)} ${c.desc}`).join("\n")}
|
|
21
|
+
|
|
22
|
+
${chalk.dim("Keys:")}
|
|
23
|
+
Esc Abort
|
|
24
|
+
↑ / ↓ History
|
|
25
|
+
`
|
|
26
|
+
|
|
27
|
+
export async function dispatch(
|
|
28
|
+
input: string,
|
|
29
|
+
agent: Agent,
|
|
30
|
+
store?: SessionStore,
|
|
31
|
+
sessionId?: string,
|
|
32
|
+
): Promise<string | null> {
|
|
33
|
+
const [cmd, ...rest] = input.slice(1).split(" ")
|
|
34
|
+
const args = rest.join(" ")
|
|
35
|
+
|
|
36
|
+
switch (cmd) {
|
|
37
|
+
case "models":
|
|
38
|
+
case "model":
|
|
39
|
+
return handleModels(args, agent)
|
|
40
|
+
case "providers":
|
|
41
|
+
case "prov":
|
|
42
|
+
case "config":
|
|
43
|
+
case "cfg":
|
|
44
|
+
return handleProviders(agent)
|
|
45
|
+
case "compact":
|
|
46
|
+
if (!store || !sessionId) return chalk.red("Session store not available")
|
|
47
|
+
return handleCompact(agent, store, sessionId)
|
|
48
|
+
case "help":
|
|
49
|
+
return HELP
|
|
50
|
+
case "clear":
|
|
51
|
+
console.clear()
|
|
52
|
+
return ""
|
|
53
|
+
case "quit":
|
|
54
|
+
process.exit(0)
|
|
55
|
+
return null
|
|
56
|
+
case "exit":
|
|
57
|
+
process.exit(0)
|
|
58
|
+
return null
|
|
59
|
+
default:
|
|
60
|
+
return chalk.yellow(`Unknown: /${cmd}. Type /help`)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as clack from "@clack/prompts"
|
|
2
|
+
import chalk from "chalk"
|
|
3
|
+
import type { Agent } from "../agent/agent.ts"
|
|
4
|
+
import { getProvider, MODELS } from "../config/providers.ts"
|
|
5
|
+
import { loadAuth, loadConfig, saveConfig } from "../config/store.ts"
|
|
6
|
+
|
|
7
|
+
export async function handleModels(args: string, agent: Agent): Promise<string> {
|
|
8
|
+
const config = await loadConfig()
|
|
9
|
+
const auth = await loadAuth()
|
|
10
|
+
|
|
11
|
+
if (args) return await switchDirect(args.trim(), agent)
|
|
12
|
+
|
|
13
|
+
const options: clack.Option<string>[] = []
|
|
14
|
+
for (const m of MODELS) {
|
|
15
|
+
const cur = m.id === config.model && m.provider === config.provider
|
|
16
|
+
const pDef = getProvider(m.provider)
|
|
17
|
+
if (!pDef) continue
|
|
18
|
+
|
|
19
|
+
// Ensure we have an API key for the provider
|
|
20
|
+
const hasKey = !!auth.apiKeys[m.provider]
|
|
21
|
+
if (!hasKey) continue
|
|
22
|
+
|
|
23
|
+
options.push({
|
|
24
|
+
value: `${m.provider}:${m.id}`,
|
|
25
|
+
label: `${cur ? chalk.green("●") : "○"} ${m.id.padEnd(20)} ${fmt(m.contextWindow).padEnd(8)}`,
|
|
26
|
+
hint: pDef.name,
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!options.length)
|
|
31
|
+
return chalk.yellow("No models available. Use /providers to add a provider API key.")
|
|
32
|
+
|
|
33
|
+
const pick = await clack.select({ message: "Model", options })
|
|
34
|
+
if (clack.isCancel(pick)) return ""
|
|
35
|
+
|
|
36
|
+
const [pk, mid] = (pick as string).split(":")
|
|
37
|
+
const selectedModel = MODELS.find((m) => m.provider === pk && m.id === mid)
|
|
38
|
+
const selectedProvider = getProvider(pk!)
|
|
39
|
+
|
|
40
|
+
if (!selectedModel || !selectedProvider) return chalk.red("Error: Model or provider not found")
|
|
41
|
+
|
|
42
|
+
config.provider = pk!
|
|
43
|
+
config.model = mid!
|
|
44
|
+
await saveConfig(config)
|
|
45
|
+
|
|
46
|
+
// Update agent
|
|
47
|
+
agent.updateConfig({
|
|
48
|
+
api: selectedProvider.api,
|
|
49
|
+
model: selectedModel,
|
|
50
|
+
apiKey: auth.apiKeys[pk!] ?? "",
|
|
51
|
+
baseUrl: selectedProvider.baseUrl,
|
|
52
|
+
})
|
|
53
|
+
return chalk.green(`✓ Switched to ${mid}`)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function switchDirect(id: string, agent: Agent): Promise<string> {
|
|
57
|
+
const config = await loadConfig()
|
|
58
|
+
const auth = await loadAuth()
|
|
59
|
+
|
|
60
|
+
const m = MODELS.find((m) => m.id === id)
|
|
61
|
+
if (!m) return chalk.yellow(`"${id}" not found. Use /models`)
|
|
62
|
+
|
|
63
|
+
const pk = m.provider
|
|
64
|
+
if (!auth.apiKeys[pk]) {
|
|
65
|
+
return chalk.yellow(`No API key configured for ${pk}. Use /providers`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const selectedProvider = getProvider(pk)
|
|
69
|
+
if (!selectedProvider) return chalk.red("Error: Provider not found")
|
|
70
|
+
|
|
71
|
+
config.provider = pk
|
|
72
|
+
config.model = id
|
|
73
|
+
await saveConfig(config)
|
|
74
|
+
|
|
75
|
+
// Update agent
|
|
76
|
+
agent.updateConfig({
|
|
77
|
+
api: selectedProvider.api,
|
|
78
|
+
model: m,
|
|
79
|
+
apiKey: auth.apiKeys[pk],
|
|
80
|
+
baseUrl: selectedProvider.baseUrl,
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
return chalk.green(`✓ Switched to ${id}`)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const fmt = (n: number) => (n >= 1_000_000 ? `${n / 1_000_000}M` : `${n / 1000}K`)
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import * as clack from "@clack/prompts"
|
|
2
|
+
import chalk from "chalk"
|
|
3
|
+
import type { Agent } from "../agent/agent.ts"
|
|
4
|
+
import { getProvider, MODELS, PROVIDERS } from "../config/providers.ts"
|
|
5
|
+
import { loadAuth, loadConfig, saveAuth, saveConfig } from "../config/store.ts"
|
|
6
|
+
|
|
7
|
+
export async function handleProviders(agent: Agent): Promise<string> {
|
|
8
|
+
const config = await loadConfig()
|
|
9
|
+
const auth = await loadAuth()
|
|
10
|
+
|
|
11
|
+
const configured = PROVIDERS.filter((p) => !!auth.apiKeys[p.id])
|
|
12
|
+
|
|
13
|
+
console.log(chalk.bold("\n ⚙ Configured Providers:\n"))
|
|
14
|
+
if (configured.length === 0) {
|
|
15
|
+
console.log(chalk.dim(" No providers configured. Use 'Add Provider' below.\n"))
|
|
16
|
+
} else {
|
|
17
|
+
for (const p of configured) {
|
|
18
|
+
const isDefault = p.id === config.provider
|
|
19
|
+
const active = isDefault ? chalk.green(" ●") : ""
|
|
20
|
+
const key = chalk.green("✅")
|
|
21
|
+
const currentModel = isDefault
|
|
22
|
+
? config.model
|
|
23
|
+
: (MODELS.find((m) => m.provider === p.id)?.id ?? "")
|
|
24
|
+
console.log(` ${key} ${p.name.padEnd(24)} ${currentModel}${active}`)
|
|
25
|
+
}
|
|
26
|
+
console.log("") // Spacer
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const act = await clack.select({
|
|
30
|
+
message: "Action",
|
|
31
|
+
options: [
|
|
32
|
+
{ value: "add", label: "Add Provider" },
|
|
33
|
+
{ value: "update", label: "Update API Key" },
|
|
34
|
+
{ value: "remove", label: "Remove API Key" },
|
|
35
|
+
{ value: "default", label: "Set Default Provider" },
|
|
36
|
+
{ value: "back", label: "Back" },
|
|
37
|
+
],
|
|
38
|
+
})
|
|
39
|
+
if (clack.isCancel(act) || act === "back") return ""
|
|
40
|
+
|
|
41
|
+
if (act === "add") return addProvider(agent)
|
|
42
|
+
if (act === "update") return updateKey(agent)
|
|
43
|
+
if (act === "remove") return removeKey(agent)
|
|
44
|
+
if (act === "default") return setDefault(agent)
|
|
45
|
+
return ""
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function addProvider(agent: Agent): Promise<string> {
|
|
49
|
+
const auth = await loadAuth()
|
|
50
|
+
const config = await loadConfig()
|
|
51
|
+
|
|
52
|
+
const available = PROVIDERS.filter((p) => !auth.apiKeys[p.id])
|
|
53
|
+
if (available.length === 0) {
|
|
54
|
+
return chalk.yellow("All providers already have API keys configured.")
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const pick = await clack.select({
|
|
58
|
+
message: "Add Provider",
|
|
59
|
+
options: available.map((p) => ({ value: p.id, label: p.name })),
|
|
60
|
+
})
|
|
61
|
+
if (clack.isCancel(pick)) return ""
|
|
62
|
+
|
|
63
|
+
const pDef = getProvider(pick as string)
|
|
64
|
+
if (!pDef) return chalk.red("Error: Provider not found")
|
|
65
|
+
|
|
66
|
+
const key = await clack.password({
|
|
67
|
+
message: `${pDef.name} API Key`,
|
|
68
|
+
validate: (v) => (!v || v.length < 8 ? "Enter a valid key" : undefined),
|
|
69
|
+
})
|
|
70
|
+
if (clack.isCancel(key)) return ""
|
|
71
|
+
|
|
72
|
+
auth.apiKeys[pDef.id] = key as string
|
|
73
|
+
await saveAuth(auth)
|
|
74
|
+
|
|
75
|
+
// Set as active if no provider is currently set
|
|
76
|
+
if (!config.provider) {
|
|
77
|
+
config.provider = pDef.id
|
|
78
|
+
const mDef = MODELS.find((m) => m.provider === pDef.id)
|
|
79
|
+
if (mDef) {
|
|
80
|
+
config.model = mDef.id
|
|
81
|
+
}
|
|
82
|
+
await saveConfig(config)
|
|
83
|
+
agent.updateConfig({
|
|
84
|
+
api: pDef.api,
|
|
85
|
+
model: MODELS.find((m) => m.id === config.model)!,
|
|
86
|
+
apiKey: key as string,
|
|
87
|
+
baseUrl: pDef.baseUrl,
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return chalk.green(`✓ ${pDef.name} configured`)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function updateKey(agent: Agent): Promise<string> {
|
|
95
|
+
const auth = await loadAuth()
|
|
96
|
+
|
|
97
|
+
const configured = PROVIDERS.filter((p) => !!auth.apiKeys[p.id])
|
|
98
|
+
if (configured.length === 0) {
|
|
99
|
+
return chalk.yellow("No providers configured. Use 'Add Provider' first.")
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const pick = await clack.select({
|
|
103
|
+
message: "Update API Key",
|
|
104
|
+
options: configured.map((p) => ({ value: p.id, label: p.name })),
|
|
105
|
+
})
|
|
106
|
+
if (clack.isCancel(pick)) return ""
|
|
107
|
+
|
|
108
|
+
const pDef = getProvider(pick as string)
|
|
109
|
+
if (!pDef) return chalk.red("Error: Provider not found")
|
|
110
|
+
|
|
111
|
+
const key = await clack.password({ message: `New key for ${pDef.name}` })
|
|
112
|
+
if (clack.isCancel(key)) return ""
|
|
113
|
+
|
|
114
|
+
auth.apiKeys[pDef.id] = key as string
|
|
115
|
+
await saveAuth(auth)
|
|
116
|
+
|
|
117
|
+
// If this is the active provider, update the agent's key
|
|
118
|
+
const config = await loadConfig()
|
|
119
|
+
if (config.provider === pDef.id) {
|
|
120
|
+
const currentModel = MODELS.find((m) => m.id === config.model && m.provider === config.provider)
|
|
121
|
+
if (currentModel) {
|
|
122
|
+
agent.updateConfig({
|
|
123
|
+
api: pDef.api,
|
|
124
|
+
model: currentModel,
|
|
125
|
+
apiKey: key as string,
|
|
126
|
+
baseUrl: pDef.baseUrl,
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return chalk.green("✓ Key updated")
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function removeKey(agent: Agent): Promise<string> {
|
|
135
|
+
const auth = await loadAuth()
|
|
136
|
+
const config = await loadConfig()
|
|
137
|
+
|
|
138
|
+
const configured = PROVIDERS.filter((p) => !!auth.apiKeys[p.id])
|
|
139
|
+
if (configured.length === 0) {
|
|
140
|
+
return chalk.yellow("No configured providers to remove.")
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const pick = await clack.select({
|
|
144
|
+
message: "Remove API Key",
|
|
145
|
+
options: configured.map((p) => ({ value: p.id, label: p.name })),
|
|
146
|
+
})
|
|
147
|
+
if (clack.isCancel(pick)) return ""
|
|
148
|
+
|
|
149
|
+
const pId = pick as string
|
|
150
|
+
const confirm = await clack.confirm({
|
|
151
|
+
message: `Are you sure you want to remove the API key for ${pId}?`,
|
|
152
|
+
})
|
|
153
|
+
if (clack.isCancel(confirm) || !confirm) return ""
|
|
154
|
+
|
|
155
|
+
delete auth.apiKeys[pId]
|
|
156
|
+
await saveAuth(auth)
|
|
157
|
+
|
|
158
|
+
// If removing the active provider's key
|
|
159
|
+
if (config.provider === pId) {
|
|
160
|
+
config.provider = ""
|
|
161
|
+
config.model = ""
|
|
162
|
+
// Try to find another configured provider
|
|
163
|
+
const next = Object.keys(auth.apiKeys)[0]
|
|
164
|
+
if (next) {
|
|
165
|
+
const pDef = getProvider(next)
|
|
166
|
+
const mDef = MODELS.find((m) => m.provider === next)
|
|
167
|
+
if (pDef && mDef) {
|
|
168
|
+
config.provider = next
|
|
169
|
+
config.model = mDef.id
|
|
170
|
+
agent.updateConfig({
|
|
171
|
+
api: pDef.api,
|
|
172
|
+
model: mDef,
|
|
173
|
+
apiKey: auth.apiKeys[next]!,
|
|
174
|
+
baseUrl: pDef.baseUrl,
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
await saveConfig(config)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return chalk.green(`✓ Removed API key for ${pId}`)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function setDefault(agent: Agent): Promise<string> {
|
|
185
|
+
const config = await loadConfig()
|
|
186
|
+
const auth = await loadAuth()
|
|
187
|
+
|
|
188
|
+
const pick = await clack.select({
|
|
189
|
+
message: "Default Provider",
|
|
190
|
+
options: PROVIDERS.map((p) => {
|
|
191
|
+
const hasKey = !!auth.apiKeys[p.id]
|
|
192
|
+
return {
|
|
193
|
+
value: p.id,
|
|
194
|
+
label: `${hasKey ? "✅" : "❌"} ${p.name}`,
|
|
195
|
+
}
|
|
196
|
+
}),
|
|
197
|
+
})
|
|
198
|
+
if (clack.isCancel(pick)) return ""
|
|
199
|
+
|
|
200
|
+
const pId = pick as string
|
|
201
|
+
if (!auth.apiKeys[pId]) {
|
|
202
|
+
return chalk.yellow(`No API key for ${pId}. Please set one first.`)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const pDef = getProvider(pId)
|
|
206
|
+
const mDef = MODELS.find((m) => m.provider === pId)
|
|
207
|
+
|
|
208
|
+
if (!pDef || !mDef) return chalk.red("Error: Provider or model not found")
|
|
209
|
+
|
|
210
|
+
config.provider = pId
|
|
211
|
+
config.model = mDef.id
|
|
212
|
+
await saveConfig(config)
|
|
213
|
+
|
|
214
|
+
agent.updateConfig({
|
|
215
|
+
api: pDef.api,
|
|
216
|
+
model: mDef,
|
|
217
|
+
apiKey: auth.apiKeys[pId],
|
|
218
|
+
baseUrl: pDef.baseUrl,
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
return chalk.green(`✓ Default set to ${pDef.name} (${mDef.id})`)
|
|
222
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { getSessionStore } from "../session/store.ts"
|
|
2
|
+
|
|
3
|
+
export async function handleSessionCommand(args: string[]): Promise<void> {
|
|
4
|
+
const store = getSessionStore()
|
|
5
|
+
const [subcommand, id] = args
|
|
6
|
+
|
|
7
|
+
if (subcommand === "list" || subcommand === "ls") {
|
|
8
|
+
const sessions = store.list()
|
|
9
|
+
if (sessions.length === 0) {
|
|
10
|
+
console.log("No sessions found.")
|
|
11
|
+
return
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
console.log("ID".padEnd(25), "MODEL".padEnd(20), "UPDATED")
|
|
15
|
+
console.log("-".repeat(70))
|
|
16
|
+
for (const s of sessions) {
|
|
17
|
+
const date = new Date(s.updated).toLocaleString()
|
|
18
|
+
console.log(s.id.padEnd(25), s.model.padEnd(20), date)
|
|
19
|
+
}
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (subcommand === "delete" || subcommand === "rm") {
|
|
24
|
+
if (!id) {
|
|
25
|
+
console.error("Usage: novacode session delete <id>")
|
|
26
|
+
process.exit(1)
|
|
27
|
+
}
|
|
28
|
+
const success = store.delete(id)
|
|
29
|
+
if (success) {
|
|
30
|
+
console.log(`Deleted session: ${id}`)
|
|
31
|
+
} else {
|
|
32
|
+
console.error(`Session not found: ${id}`)
|
|
33
|
+
process.exit(1)
|
|
34
|
+
}
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.error("Unknown session subcommand. Use 'list' or 'delete'.")
|
|
39
|
+
process.exit(1)
|
|
40
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import type { Model, ProviderDef } from "../types.ts"
|
|
2
|
+
|
|
3
|
+
export const PROVIDERS: ProviderDef[] = [
|
|
4
|
+
{
|
|
5
|
+
id: "glm",
|
|
6
|
+
name: "GLM (Z.AI)",
|
|
7
|
+
api: "openai",
|
|
8
|
+
baseUrl: "https://api.z.ai/api/coding/paas/v4",
|
|
9
|
+
envKey: "GLM_API_KEY",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: "gemini",
|
|
13
|
+
name: "Gemini (Google)",
|
|
14
|
+
api: "gemini",
|
|
15
|
+
baseUrl: "https://generativelanguage.googleapis.com",
|
|
16
|
+
envKey: "GEMINI_API_KEY",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "deepseek",
|
|
20
|
+
name: "DeepSeek",
|
|
21
|
+
api: "openai",
|
|
22
|
+
baseUrl: "https://api.deepseek.com",
|
|
23
|
+
envKey: "DEEPSEEK_API_KEY",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "openai",
|
|
27
|
+
name: "OpenAI",
|
|
28
|
+
api: "openai",
|
|
29
|
+
baseUrl: "https://api.openai.com/v1",
|
|
30
|
+
envKey: "OPENAI_API_KEY",
|
|
31
|
+
},
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
export const MODELS: Model[] = [
|
|
35
|
+
// GLM
|
|
36
|
+
{
|
|
37
|
+
id: "glm-5.1",
|
|
38
|
+
name: "GLM-5.1",
|
|
39
|
+
provider: "glm",
|
|
40
|
+
contextWindow: 128_000,
|
|
41
|
+
maxTokens: 4096,
|
|
42
|
+
supportsThinking: false,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "glm-5",
|
|
46
|
+
name: "GLM-5",
|
|
47
|
+
provider: "glm",
|
|
48
|
+
contextWindow: 128_000,
|
|
49
|
+
maxTokens: 4096,
|
|
50
|
+
supportsThinking: false,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "glm-5-turbo",
|
|
54
|
+
name: "GLM-5 Turbo",
|
|
55
|
+
provider: "glm",
|
|
56
|
+
contextWindow: 128_000,
|
|
57
|
+
maxTokens: 4096,
|
|
58
|
+
supportsThinking: false,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "glm-4.7",
|
|
62
|
+
name: "GLM-4.7",
|
|
63
|
+
provider: "glm",
|
|
64
|
+
contextWindow: 128_000,
|
|
65
|
+
maxTokens: 4096,
|
|
66
|
+
supportsThinking: false,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "glm-4.7-flash",
|
|
70
|
+
name: "GLM-4.7 Flash (Free)",
|
|
71
|
+
provider: "glm",
|
|
72
|
+
contextWindow: 128_000,
|
|
73
|
+
maxTokens: 4096,
|
|
74
|
+
supportsThinking: false,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: "glm-4.5-flash",
|
|
78
|
+
name: "GLM-4.5 Flash (Free)",
|
|
79
|
+
provider: "glm",
|
|
80
|
+
contextWindow: 128_000,
|
|
81
|
+
maxTokens: 4096,
|
|
82
|
+
supportsThinking: false,
|
|
83
|
+
},
|
|
84
|
+
// Gemini
|
|
85
|
+
{
|
|
86
|
+
id: "gemini-3.1-pro-preview",
|
|
87
|
+
name: "Gemini 3.1 Pro Preview",
|
|
88
|
+
provider: "gemini",
|
|
89
|
+
contextWindow: 2_000_000,
|
|
90
|
+
maxTokens: 65_536,
|
|
91
|
+
supportsThinking: true,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "gemini-3.1-pro-preview-customtools",
|
|
95
|
+
name: "Gemini 3.1 Pro (Custom Tools)",
|
|
96
|
+
provider: "gemini",
|
|
97
|
+
contextWindow: 2_000_000,
|
|
98
|
+
maxTokens: 65_536,
|
|
99
|
+
supportsThinking: true,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "gemini-3.1-flash-lite",
|
|
103
|
+
name: "Gemini 3.1 Flash-Lite",
|
|
104
|
+
provider: "gemini",
|
|
105
|
+
contextWindow: 1_000_000,
|
|
106
|
+
maxTokens: 65_536,
|
|
107
|
+
supportsThinking: true,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "gemini-3.1-flash-lite-preview",
|
|
111
|
+
name: "Gemini 3.1 Flash-Lite Preview",
|
|
112
|
+
provider: "gemini",
|
|
113
|
+
contextWindow: 1_000_000,
|
|
114
|
+
maxTokens: 65_536,
|
|
115
|
+
supportsThinking: true,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "gemini-3-flash-preview",
|
|
119
|
+
name: "Gemini 3 Flash Preview",
|
|
120
|
+
provider: "gemini",
|
|
121
|
+
contextWindow: 1_000_000,
|
|
122
|
+
maxTokens: 65_536,
|
|
123
|
+
supportsThinking: true,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: "gemini-2.5-pro",
|
|
127
|
+
name: "Gemini 2.5 Pro",
|
|
128
|
+
provider: "gemini",
|
|
129
|
+
contextWindow: 2_000_000,
|
|
130
|
+
maxTokens: 65_536,
|
|
131
|
+
supportsThinking: true,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: "gemini-2.5-flash",
|
|
135
|
+
name: "Gemini 2.5 Flash",
|
|
136
|
+
provider: "gemini",
|
|
137
|
+
contextWindow: 1_000_000,
|
|
138
|
+
maxTokens: 65_536,
|
|
139
|
+
supportsThinking: true,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: "gemini-2.5-flash-lite",
|
|
143
|
+
name: "Gemini 2.5 Flash-Lite",
|
|
144
|
+
provider: "gemini",
|
|
145
|
+
contextWindow: 1_000_000,
|
|
146
|
+
maxTokens: 65_536,
|
|
147
|
+
supportsThinking: true,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: "gemini-2.5-computer-use-preview-10-2025",
|
|
151
|
+
name: "Gemini 2.5 Computer Use",
|
|
152
|
+
provider: "gemini",
|
|
153
|
+
contextWindow: 1_000_000,
|
|
154
|
+
maxTokens: 65_536,
|
|
155
|
+
supportsThinking: true,
|
|
156
|
+
},
|
|
157
|
+
// DeepSeek
|
|
158
|
+
{
|
|
159
|
+
id: "deepseek-chat",
|
|
160
|
+
name: "DeepSeek V3",
|
|
161
|
+
provider: "deepseek",
|
|
162
|
+
contextWindow: 64_000,
|
|
163
|
+
maxTokens: 8_192,
|
|
164
|
+
supportsThinking: false,
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: "deepseek-reasoner",
|
|
168
|
+
name: "DeepSeek R1",
|
|
169
|
+
provider: "deepseek",
|
|
170
|
+
contextWindow: 64_000,
|
|
171
|
+
maxTokens: 8_192,
|
|
172
|
+
supportsThinking: true,
|
|
173
|
+
},
|
|
174
|
+
// OpenAI
|
|
175
|
+
{
|
|
176
|
+
id: "gpt-4o",
|
|
177
|
+
name: "GPT-4o",
|
|
178
|
+
provider: "openai",
|
|
179
|
+
contextWindow: 128_000,
|
|
180
|
+
maxTokens: 16_384,
|
|
181
|
+
supportsThinking: false,
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: "o4-mini",
|
|
185
|
+
name: "o4-mini",
|
|
186
|
+
provider: "openai",
|
|
187
|
+
contextWindow: 200_000,
|
|
188
|
+
maxTokens: 100_000,
|
|
189
|
+
supportsThinking: true,
|
|
190
|
+
},
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
export function getProvider(id: string): ProviderDef | undefined {
|
|
194
|
+
return PROVIDERS.find((p) => p.id === id)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function getModelsForProvider(providerId: string): Model[] {
|
|
198
|
+
return MODELS.filter((m) => m.provider === providerId)
|
|
199
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { join } from "node:path"
|
|
2
|
+
import type { NovaAuth, NovaConfig } from "../types.ts"
|
|
3
|
+
|
|
4
|
+
const NOVA_DIR = () => join(process.env.HOME ?? "~", ".novacode")
|
|
5
|
+
const CONFIG_PATH = () => join(NOVA_DIR(), "config.json")
|
|
6
|
+
const AUTH_PATH = () => join(NOVA_DIR(), "auth.json")
|
|
7
|
+
|
|
8
|
+
const defaultConfig: NovaConfig = {
|
|
9
|
+
provider: "",
|
|
10
|
+
model: "",
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const defaultAuth: NovaAuth = {
|
|
14
|
+
apiKeys: {},
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function configExists(): Promise<boolean> {
|
|
18
|
+
try {
|
|
19
|
+
await Bun.file(CONFIG_PATH()).stat()
|
|
20
|
+
return true
|
|
21
|
+
} catch {
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function loadConfig(): Promise<NovaConfig> {
|
|
27
|
+
try {
|
|
28
|
+
const raw = await Bun.file(CONFIG_PATH()).json()
|
|
29
|
+
return { ...defaultConfig, ...raw }
|
|
30
|
+
} catch {
|
|
31
|
+
return { ...defaultConfig }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function loadAuth(): Promise<NovaAuth> {
|
|
36
|
+
try {
|
|
37
|
+
const raw = await Bun.file(AUTH_PATH()).json()
|
|
38
|
+
return { ...defaultAuth, ...raw }
|
|
39
|
+
} catch {
|
|
40
|
+
return { ...defaultAuth }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function ensureDir(): Promise<void> {
|
|
45
|
+
const { mkdir } = await import("node:fs/promises")
|
|
46
|
+
await mkdir(NOVA_DIR(), { recursive: true })
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function saveConfig(config: NovaConfig): Promise<void> {
|
|
50
|
+
await ensureDir()
|
|
51
|
+
await Bun.write(CONFIG_PATH(), JSON.stringify(config, null, 2))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function saveAuth(auth: NovaAuth): Promise<void> {
|
|
55
|
+
await ensureDir()
|
|
56
|
+
await Bun.write(AUTH_PATH(), JSON.stringify(auth, null, 2))
|
|
57
|
+
try {
|
|
58
|
+
const { chmod } = await import("node:fs/promises")
|
|
59
|
+
await chmod(AUTH_PATH(), 0o600)
|
|
60
|
+
} catch {
|
|
61
|
+
// chmod may fail on some platforms, non-fatal
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getNovaDir(): string {
|
|
66
|
+
return NOVA_DIR()
|
|
67
|
+
}
|