@100xprompt/brain 0.1.0 → 0.1.1
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 +66 -3
- package/package.json +1 -1
- package/src/cli.ts +28 -3
- package/src/dashboard/cli.ts +148 -0
- package/src/dashboard/conversions.ts +170 -0
- package/src/dashboard/report.ts +261 -0
- package/src/dashboard/send.ts +213 -0
- package/src/dashboard/state.ts +95 -0
- package/src/gateway/common.ts +232 -0
- package/src/gateway/connect.ts +164 -162
- package/src/gateway/connectors/aider.ts +356 -0
- package/src/gateway/connectors/amazon-q-cli.ts +6 -0
- package/src/gateway/connectors/amazon-q-ide.ts +6 -0
- package/src/gateway/connectors/antigravity.ts +6 -0
- package/src/gateway/connectors/auggie.ts +6 -0
- package/src/gateway/connectors/cline-sdk.ts +85 -0
- package/src/gateway/connectors/cline.ts +34 -0
- package/src/gateway/connectors/codebuff.ts +6 -0
- package/src/gateway/connectors/continue.ts +579 -0
- package/src/gateway/connectors/copilot-cli.ts +138 -0
- package/src/gateway/connectors/crush.ts +313 -0
- package/src/gateway/connectors/cursor-cli.ts +6 -0
- package/src/gateway/connectors/cursor.ts +6 -0
- package/src/gateway/connectors/devin.ts +6 -0
- package/src/gateway/connectors/factory-droid.ts +308 -0
- package/src/gateway/connectors/goose.ts +79 -0
- package/src/gateway/connectors/grok-cli.ts +82 -0
- package/src/gateway/connectors/hermes.ts +230 -0
- package/src/gateway/connectors/iflow-cli.ts +50 -0
- package/src/gateway/connectors/index.ts +86 -0
- package/src/gateway/connectors/kilo-cli.ts +434 -0
- package/src/gateway/connectors/kilo-code.ts +32 -0
- package/src/gateway/connectors/kimi-code.ts +241 -0
- package/src/gateway/connectors/kiro-cli.ts +6 -0
- package/src/gateway/connectors/kiro-ide.ts +6 -0
- package/src/gateway/connectors/letta-code.ts +170 -0
- package/src/gateway/connectors/mastracode.ts +91 -0
- package/src/gateway/connectors/mistral-vibe.ts +495 -0
- package/src/gateway/connectors/muse.ts +27 -0
- package/src/gateway/connectors/omp.ts +279 -0
- package/src/gateway/connectors/openhands.ts +296 -0
- package/src/gateway/connectors/pearai.ts +52 -0
- package/src/gateway/connectors/pi.ts +357 -0
- package/src/gateway/connectors/qoder-cli.ts +6 -0
- package/src/gateway/connectors/qwen-code.ts +411 -0
- package/src/gateway/connectors/roo.ts +43 -0
- package/src/gateway/connectors/tabnine-cli.ts +6 -0
- package/src/gateway/connectors/void.ts +6 -0
- package/src/gateway/connectors/vscode-copilot.ts +286 -0
- package/src/gateway/connectors/windsurf.ts +6 -0
- package/src/gateway/connectors/zed.ts +29 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
// Gateway connector for Aider (aider-chat; checked against aider 0.86.2 with litellm 1.81.10, the copy in
|
|
2
|
+
// ~/.cache/brain-wire/aider/venv).
|
|
3
|
+
//
|
|
4
|
+
// How Aider is pointed at the gateway: a model entry of its own, made Aider's default, without touching the person's
|
|
5
|
+
// OpenAI settings (openai-api-base / openai-api-key / OPENAI_API_KEY keep serving their own OpenAI models).
|
|
6
|
+
// ~/.aider.model.settings.yml an entry `name: openai/<model>` (marked with a comment as brain's) whose extra_params
|
|
7
|
+
// carry api_base <route>/v1, api_key and extra_headers { x-100x-context-edit: on }. Aider passes extra_params
|
|
8
|
+
// straight to litellm.completion, so the gateway is called as OpenAI Chat Completions (<route>/v1/chat/completions,
|
|
9
|
+
// Authorization: Bearer <key>). The entry also gives Aider the edit settings it uses for Gemini Flash.
|
|
10
|
+
// ~/.aider.model.metadata.json the same model's context window (else Aider assumes an unknown model: a 1k-token chat
|
|
11
|
+
// history budget, a small repo map and a warning on every start). JSON5, read by Aider with json5.loads.
|
|
12
|
+
// ~/.aider.conf.yml `model: openai/<model>` (the default model) and `show-model-warnings: false` (Aider would warn
|
|
13
|
+
// at every start that OPENAI_API_KEY is not set, which the gateway model does not use). Both put back on disconnect.
|
|
14
|
+
// The key is copied (holdsKey, 0600): Aider has no key-file or key-command option. Its conf file (configargparse YAML)
|
|
15
|
+
// and settings file (yaml.safe_load) do no interpolation, and its .env files (python-dotenv) only expand ${VAR}. litellm
|
|
16
|
+
// can read a key file (api_key "os.environ/oidc/file/<path>", litellm/secret_managers/main.py get_secret), but it does
|
|
17
|
+
// not strip the trailing newline the brain's key file ends with, and the request then fails (tried: "Connection error").
|
|
18
|
+
//
|
|
19
|
+
// Evidence (site-packages of the copy above):
|
|
20
|
+
// aider/main.py:464-497 config files: ./.aider.conf.yml, <git root>/.aider.conf.yml, ~/.aider.conf.yml (the nearest wins).
|
|
21
|
+
// aider/main.py:305-358 .aider.model.settings.yml searched in ~, the git root and the cwd (register_models).
|
|
22
|
+
// aider/main.py:390-412 .aider.model.metadata.json likewise (register_litellm_models); aider/models.py:1097-1118 json5.
|
|
23
|
+
// aider/main.py:500-504 .env files (~/.env, git root, cwd) are loaded, then the arguments parsed again (AIDER_* win).
|
|
24
|
+
// aider/models.py:1070-1094 each settings entry replaces any earlier entry of the same name (later files/entries win).
|
|
25
|
+
// aider/models.py:116-138 ModelSettings fields (extra_params among them); unknown fields are an error.
|
|
26
|
+
// aider/models.py:970-1022 send_completion(): kwargs.update(self.extra_params) (996), then litellm.completion(**kwargs) (1021).
|
|
27
|
+
// aider/models.py:697-765, 1149-1185 the start-up check that warns when OPENAI_API_KEY is missing for openai/ models.
|
|
28
|
+
// aider/main.py:890-902 that check runs only with --show-model-warnings (the default).
|
|
29
|
+
// Docs: https://aider.chat/docs/config/adv-model-settings.html (extra_params, extra_headers, metadata file),
|
|
30
|
+
// https://aider.chat/docs/llms/openai-compat.html (openai/<model> against an OpenAI-compatible API).
|
|
31
|
+
import { join } from "node:path"
|
|
32
|
+
import { type Codec, type Connector, EDIT_HEADER, type Json, readKey, routeUrl, yamlTopLevel } from "../common.ts"
|
|
33
|
+
|
|
34
|
+
/** The one key of brain's own entry in the settings list and in the metadata object. */
|
|
35
|
+
export const AIDER_ENTRY = "brain-gateway"
|
|
36
|
+
/** Marks brain's entry in each file (a YAML comment, a JSON5 comment), so it is told apart from the person's own. */
|
|
37
|
+
export const AIDER_MARK = "100xprompt gateway, added by brain gateway connect --tool aider"
|
|
38
|
+
/** What Aider is told about the gateway's model: the Gemini Flash models' window and output limit. */
|
|
39
|
+
const CONTEXT = 1_048_576
|
|
40
|
+
const OUTPUT = 65_536
|
|
41
|
+
|
|
42
|
+
const esc = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
43
|
+
|
|
44
|
+
/** A value in YAML flow style, written as JSON (which PyYAML and Bun both read): strings are always quoted. */
|
|
45
|
+
const flow = (v: unknown): string =>
|
|
46
|
+
Array.isArray(v)
|
|
47
|
+
? `[${v.map(flow).join(", ")}]`
|
|
48
|
+
: v && typeof v === "object"
|
|
49
|
+
? `{${Object.entries(v)
|
|
50
|
+
.filter(([, x]) => x !== undefined)
|
|
51
|
+
.map(([k, x]) => `${JSON.stringify(k)}: ${flow(x)}`)
|
|
52
|
+
.join(", ")}}`
|
|
53
|
+
: JSON.stringify(v)
|
|
54
|
+
|
|
55
|
+
// ---- ~/.aider.model.settings.yml: a YAML list; brain's entry is one key ---------------------------------------------
|
|
56
|
+
|
|
57
|
+
/** Where brain's entry is: [start, end) is its text without the last newline; `next` is past that newline. */
|
|
58
|
+
function findEntry(text: string): { start: number; end: number; next: number } | undefined {
|
|
59
|
+
const m = new RegExp(`^- .*# ${esc(AIDER_MARK)}[ \\t]*$`, "m").exec(text)
|
|
60
|
+
if (!m) return undefined
|
|
61
|
+
const start = m.index
|
|
62
|
+
let end = start + m[0].length
|
|
63
|
+
// Its fields: the indented lines that follow (brain writes no blank or comment lines inside it).
|
|
64
|
+
while (text[end] === "\n") {
|
|
65
|
+
const nl = text.indexOf("\n", end + 1)
|
|
66
|
+
const lineEnd = nl < 0 ? text.length : nl
|
|
67
|
+
const line = text.slice(end + 1, lineEnd)
|
|
68
|
+
if (!/^[ \t]+[^\s#]/.test(line)) break
|
|
69
|
+
end = lineEnd
|
|
70
|
+
}
|
|
71
|
+
return { start, end, next: end < text.length ? end + 1 : end }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function renderEntry(v: Json): string {
|
|
75
|
+
const { name, ...rest } = v
|
|
76
|
+
const fields = Object.entries(rest)
|
|
77
|
+
.filter(([, x]) => x !== undefined)
|
|
78
|
+
.map(([k, x]) => ` ${k}: ${flow(x)}`)
|
|
79
|
+
return [`- name: ${flow(name)} # ${AIDER_MARK}`, ...fields].join("\n")
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const SAMPLE_ENTRY = { name: "openai/brain-check", extra_params: { api_base: "https://example.invalid/v1" } }
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Aider's model settings file: a top-level YAML list of entries. Brain's entry (the key AIDER_ENTRY) is found by its
|
|
86
|
+
* marker comment, replaced or removed as a whole, and added at the end (a later entry of the same name wins in Aider).
|
|
87
|
+
* Every other byte stays as it was; adding and then removing the entry gives the file back exactly.
|
|
88
|
+
*/
|
|
89
|
+
export const modelSettingsList: Codec = {
|
|
90
|
+
check(text) {
|
|
91
|
+
if (!text.trim()) return undefined
|
|
92
|
+
if (/^---\s*$/m.test(text.replace(/^---\s*\n/, "")) || /^\.\.\.\s*$/m.test(text)) return "it holds more than one YAML document; left as it is"
|
|
93
|
+
let v: unknown
|
|
94
|
+
try {
|
|
95
|
+
v = Bun.YAML.parse(text)
|
|
96
|
+
} catch {
|
|
97
|
+
return "it is not valid YAML; left as it is"
|
|
98
|
+
}
|
|
99
|
+
if (v !== null && !Array.isArray(v)) return "it is not a YAML list of model settings; left as it is"
|
|
100
|
+
// Brain adds `- name: ...` at the left margin, so the list must be written that way (not [...] or indented).
|
|
101
|
+
const first = text.split("\n").find((l) => l.trim() && !/^\s*#/.test(l) && !/^---\s*$/.test(l))
|
|
102
|
+
if (first !== undefined && !/^-(\s|$)/.test(first)) return "its list is not a block list at the left margin; left as it is"
|
|
103
|
+
// Proof that brain's entry can be added: the result reads as the same list plus that entry.
|
|
104
|
+
try {
|
|
105
|
+
const had = Array.isArray(v) ? v.length : 0
|
|
106
|
+
const ours = findEntry(text) !== undefined
|
|
107
|
+
const probe = Bun.YAML.parse(modelSettingsList.set(text, AIDER_ENTRY, SAMPLE_ENTRY)) as unknown
|
|
108
|
+
// Brain's entry already there is replaced in place; otherwise the sample must be a new last entry.
|
|
109
|
+
if (!Array.isArray(probe) || probe.length !== had + (ours ? 0 : 1) || (!ours && (probe.at(-1) as Json | undefined)?.name !== SAMPLE_ENTRY.name))
|
|
110
|
+
return "brain cannot add its entry to it safely; left as it is"
|
|
111
|
+
} catch {
|
|
112
|
+
return "brain cannot add its entry to it safely; left as it is"
|
|
113
|
+
}
|
|
114
|
+
return undefined
|
|
115
|
+
},
|
|
116
|
+
get(text, key) {
|
|
117
|
+
if (key !== AIDER_ENTRY) return undefined
|
|
118
|
+
const at = findEntry(text)
|
|
119
|
+
if (!at) return undefined
|
|
120
|
+
try {
|
|
121
|
+
const v = Bun.YAML.parse(text.slice(at.start, at.end)) as unknown
|
|
122
|
+
return Array.isArray(v) ? v[0] : v
|
|
123
|
+
} catch {
|
|
124
|
+
return text.slice(at.start, at.end)
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
set(text, key, value) {
|
|
128
|
+
if (key !== AIDER_ENTRY) return text
|
|
129
|
+
const at = findEntry(text)
|
|
130
|
+
if (value === undefined) {
|
|
131
|
+
if (!at) return text
|
|
132
|
+
// The last line of a file with no final newline: the newline brain put before it goes too.
|
|
133
|
+
if (at.next === at.end && at.start > 0) return text.slice(0, at.start - 1)
|
|
134
|
+
return text.slice(0, at.start) + text.slice(at.next)
|
|
135
|
+
}
|
|
136
|
+
const entry = renderEntry(value as Json)
|
|
137
|
+
if (at) return text.slice(0, at.start) + entry + text.slice(at.end)
|
|
138
|
+
if (!text) return `${entry}\n`
|
|
139
|
+
return text.endsWith("\n") ? `${text}${entry}\n` : `${text}\n${entry}`
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ---- ~/.aider.model.metadata.json: a JSON5 object; brain's member is one key -----------------------------------------
|
|
144
|
+
|
|
145
|
+
/** The index just past the JSON string that starts at i (a double quote). */
|
|
146
|
+
function stringEnd(t: string, i: number): number {
|
|
147
|
+
let j = i + 1
|
|
148
|
+
while (j < t.length) {
|
|
149
|
+
if (t[j] === "\\") j += 2
|
|
150
|
+
else if (t[j] === '"') return j + 1
|
|
151
|
+
else j++
|
|
152
|
+
}
|
|
153
|
+
return t.length
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The index of the next character that is not whitespace or inside a comment. */
|
|
157
|
+
function skipBlank(t: string, i: number): number {
|
|
158
|
+
while (i < t.length) {
|
|
159
|
+
if (/\s/.test(t[i]!)) i++
|
|
160
|
+
else if (t.startsWith("//", i)) {
|
|
161
|
+
const n = t.indexOf("\n", i)
|
|
162
|
+
i = n < 0 ? t.length : n + 1
|
|
163
|
+
} else if (t.startsWith("/*", i)) {
|
|
164
|
+
const n = t.indexOf("*/", i + 2)
|
|
165
|
+
if (n < 0) throw new Error("unclosed comment")
|
|
166
|
+
i = n + 2
|
|
167
|
+
} else break
|
|
168
|
+
}
|
|
169
|
+
return i
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** The text with comments and trailing commas taken out (outside strings): what JSON.parse can read. */
|
|
173
|
+
function plainJson(t: string): string {
|
|
174
|
+
let out = ""
|
|
175
|
+
let i = 0
|
|
176
|
+
while (i < t.length) {
|
|
177
|
+
const c = t[i]!
|
|
178
|
+
if (c === '"') {
|
|
179
|
+
const j = stringEnd(t, i)
|
|
180
|
+
out += t.slice(i, j)
|
|
181
|
+
i = j
|
|
182
|
+
} else if (t.startsWith("//", i) || t.startsWith("/*", i)) {
|
|
183
|
+
const j = skipBlank(t, i)
|
|
184
|
+
out += " "
|
|
185
|
+
i = j
|
|
186
|
+
} else if (c === ",") {
|
|
187
|
+
const j = skipBlank(t, i + 1)
|
|
188
|
+
if (t[j] !== "}" && t[j] !== "]") out += c
|
|
189
|
+
i++
|
|
190
|
+
} else {
|
|
191
|
+
out += c
|
|
192
|
+
i++
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return out
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** The index just past the JSON value that starts at i. */
|
|
199
|
+
function valueEnd(t: string, i: number): number {
|
|
200
|
+
if (t[i] === '"') return stringEnd(t, i)
|
|
201
|
+
if (t[i] === "{" || t[i] === "[") {
|
|
202
|
+
let depth = 0
|
|
203
|
+
let j = i
|
|
204
|
+
while (j < t.length) {
|
|
205
|
+
const c = t[j]!
|
|
206
|
+
if (c === '"') {
|
|
207
|
+
j = stringEnd(t, j)
|
|
208
|
+
continue
|
|
209
|
+
}
|
|
210
|
+
if (t.startsWith("//", j) || t.startsWith("/*", j)) {
|
|
211
|
+
j = skipBlank(t, j)
|
|
212
|
+
continue
|
|
213
|
+
}
|
|
214
|
+
if (c === "{" || c === "[") depth++
|
|
215
|
+
else if (c === "}" || c === "]") {
|
|
216
|
+
depth--
|
|
217
|
+
if (depth === 0) return j + 1
|
|
218
|
+
}
|
|
219
|
+
j++
|
|
220
|
+
}
|
|
221
|
+
return t.length
|
|
222
|
+
}
|
|
223
|
+
let j = i
|
|
224
|
+
while (j < t.length && !/[\s,}\]]/.test(t[j]!) && !t.startsWith("//", j) && !t.startsWith("/*", j)) j++
|
|
225
|
+
return j
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const TAG = `/* ${AIDER_MARK} */`
|
|
229
|
+
|
|
230
|
+
/** Brain's member: `\n /* mark *\/ "<name>": <value>,` from start to end, as brain writes it right after the opening brace. */
|
|
231
|
+
function findMember(t: string): { start: number; end: number; name: string; value: string } | undefined {
|
|
232
|
+
const at = t.indexOf(TAG)
|
|
233
|
+
if (at < 0) return undefined
|
|
234
|
+
let i = at + TAG.length
|
|
235
|
+
while (t[i] === " ") i++
|
|
236
|
+
if (t[i] !== '"') return undefined
|
|
237
|
+
const nameEnd = stringEnd(t, i)
|
|
238
|
+
let name: string
|
|
239
|
+
try {
|
|
240
|
+
name = JSON.parse(t.slice(i, nameEnd))
|
|
241
|
+
} catch {
|
|
242
|
+
return undefined
|
|
243
|
+
}
|
|
244
|
+
i = nameEnd
|
|
245
|
+
while (t[i] === " ") i++
|
|
246
|
+
if (t[i] !== ":") return undefined
|
|
247
|
+
i++
|
|
248
|
+
while (t[i] === " ") i++
|
|
249
|
+
const vEnd = valueEnd(t, i)
|
|
250
|
+
if (t[vEnd] !== ",") return undefined
|
|
251
|
+
return { start: t.slice(at - 3, at) === "\n " ? at - 3 : at, end: vEnd + 1, name, value: t.slice(i, vEnd) }
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const memberText = (name: string, info: unknown) => `\n ${TAG} ${JSON.stringify(name)}: ${JSON.stringify(info)},`
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Aider's model metadata file: a JSON5 object (Aider reads it with json5.loads). Brain's member (the key AIDER_ENTRY,
|
|
258
|
+
* value { "<model>": {...} }) is written on one line right after the opening brace, marked with a comment, and taken
|
|
259
|
+
* out again exactly; comments and the person's own members stay as they were. A file that is JSON5 beyond comments and
|
|
260
|
+
* trailing commas (unquoted keys, single quotes) is left alone.
|
|
261
|
+
*/
|
|
262
|
+
export const modelMetadataJson: Codec = {
|
|
263
|
+
check(text) {
|
|
264
|
+
if (!text.trim()) return undefined
|
|
265
|
+
try {
|
|
266
|
+
const v = JSON.parse(plainJson(text))
|
|
267
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return "it is not a JSON object; left as it is"
|
|
268
|
+
const probe = JSON.parse(plainJson(modelMetadataJson.set(text, AIDER_ENTRY, { "openai/brain-check": { mode: "chat" } })))
|
|
269
|
+
if (!probe?.["openai/brain-check"]) return "brain cannot add its entry to it safely; left as it is"
|
|
270
|
+
} catch {
|
|
271
|
+
return "it is not JSON that brain can edit safely (JSON5 beyond comments and trailing commas?); left as it is"
|
|
272
|
+
}
|
|
273
|
+
return undefined
|
|
274
|
+
},
|
|
275
|
+
get(text, key) {
|
|
276
|
+
if (key !== AIDER_ENTRY) return undefined
|
|
277
|
+
const m = findMember(text)
|
|
278
|
+
if (!m) return undefined
|
|
279
|
+
try {
|
|
280
|
+
return { [m.name]: JSON.parse(plainJson(m.value)) }
|
|
281
|
+
} catch {
|
|
282
|
+
return m.value
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
set(text, key, value) {
|
|
286
|
+
if (key !== AIDER_ENTRY) return text
|
|
287
|
+
const m = findMember(text)
|
|
288
|
+
if (value === undefined) {
|
|
289
|
+
if (!m) return text
|
|
290
|
+
// Added to an empty object, the member took the newline before the closing brace with it.
|
|
291
|
+
const extra = text[m.start - 1] === "{" && text.startsWith("\n}", m.end) ? 1 : 0
|
|
292
|
+
return text.slice(0, m.start) + text.slice(m.end + extra)
|
|
293
|
+
}
|
|
294
|
+
const [name, info] = Object.entries(value as Json)[0]!
|
|
295
|
+
const seg = memberText(name, info)
|
|
296
|
+
if (m) return text.slice(0, m.start) + seg + text.slice(m.end)
|
|
297
|
+
const base = text.trim() ? text : "{}\n"
|
|
298
|
+
const open = skipBlank(base, 0)
|
|
299
|
+
const rest = base.slice(open + 1)
|
|
300
|
+
if (/^\s*\}/.test(rest)) return `${base.slice(0, open + 1)}${seg}\n${rest.replace(/^\s*/, "")}`
|
|
301
|
+
return base.slice(0, open + 1) + seg + rest
|
|
302
|
+
},
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ---- the connector ------------------------------------------------------------------------------------------------------
|
|
306
|
+
|
|
307
|
+
export const connectors: Connector[] = [
|
|
308
|
+
{
|
|
309
|
+
id: "aider",
|
|
310
|
+
label: "Aider",
|
|
311
|
+
file: (home) => join(home, ".aider.conf.yml"),
|
|
312
|
+
// ~/.aider holds Aider's caches and analytics from its first run.
|
|
313
|
+
probe: [".aider"],
|
|
314
|
+
authHeader: "authorization",
|
|
315
|
+
edits: (home, s) => {
|
|
316
|
+
const model = `openai/${s.model}`
|
|
317
|
+
return [
|
|
318
|
+
{
|
|
319
|
+
path: join(home, ".aider.model.settings.yml"),
|
|
320
|
+
format: "custom",
|
|
321
|
+
codec: modelSettingsList,
|
|
322
|
+
holdsKey: true,
|
|
323
|
+
secretKeys: [AIDER_ENTRY],
|
|
324
|
+
values: {
|
|
325
|
+
[AIDER_ENTRY]: {
|
|
326
|
+
name: model,
|
|
327
|
+
// As Aider's own settings for Gemini Flash (aider/resources/model-settings.yml, gemini/gemini-3-flash-preview).
|
|
328
|
+
edit_format: "diff-fenced",
|
|
329
|
+
use_repo_map: true,
|
|
330
|
+
overeager: true,
|
|
331
|
+
use_temperature: false,
|
|
332
|
+
extra_params: { api_base: `${routeUrl(s)}/v1`, api_key: readKey(home), ...(s.contextEdit ? { extra_headers: { [EDIT_HEADER]: "on" } } : {}) },
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
path: join(home, ".aider.model.metadata.json"),
|
|
338
|
+
format: "custom",
|
|
339
|
+
codec: modelMetadataJson,
|
|
340
|
+
values: { [AIDER_ENTRY]: { [model]: { max_input_tokens: CONTEXT, max_output_tokens: OUTPUT, max_tokens: OUTPUT, litellm_provider: "openai", mode: "chat" } } },
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
path: join(home, ".aider.conf.yml"),
|
|
344
|
+
format: "custom",
|
|
345
|
+
codec: yamlTopLevel,
|
|
346
|
+
values: { model, "show-model-warnings": false },
|
|
347
|
+
},
|
|
348
|
+
]
|
|
349
|
+
},
|
|
350
|
+
notes: [
|
|
351
|
+
"Aider cannot read its key from a file, so the key is kept in ~/.aider.model.settings.yml (the gateway model's own entry), which is now readable by you only (0600); a new sign-in replaces it there.",
|
|
352
|
+
"The gateway's model is now Aider's default (model: in ~/.aider.conf.yml); --model, AIDER_MODEL (in your shell or a .env file) or a project's own .aider.conf.yml still take precedence. Your OpenAI settings are not touched.",
|
|
353
|
+
"Aider's start-up model warnings are off while connected (it would warn that OPENAI_API_KEY is not set, which the gateway does not use); disconnect puts that setting back.",
|
|
354
|
+
],
|
|
355
|
+
},
|
|
356
|
+
]
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Gateway connector for Amazon Q Developer CLI: none. Its endpoint settings (api.codewhisperer.service, api.q.service) only
|
|
2
|
+
// move it between AWS Q servers, which speak AWS's own streaming API (generateAssistantResponse), not OpenAI or Anthropic; it is
|
|
3
|
+
// replaced by Kiro CLI (aws/amazon-q-developer-cli crates/chat-cli/src/api_client/endpoints.rs; wire/drivers/b.ts:26).
|
|
4
|
+
import type { Connector } from "../common.ts"
|
|
5
|
+
|
|
6
|
+
export const connectors: Connector[] = []
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Gateway connector for Amazon Q Developer (IDE plugin): none. Chat runs in AWS's language server against Amazon Q (AWS
|
|
2
|
+
// sign-in, AWS event-stream API); the model menu offers only Amazon Q's own models and there is no endpoint or key setting
|
|
3
|
+
// (https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/q-in-ides-chat-models.html; wire/drivers/e.ts:306).
|
|
4
|
+
import type { Connector } from "../common.ts"
|
|
5
|
+
|
|
6
|
+
export const connectors: Connector[] = []
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Gateway connector for Antigravity: none. The IDE's agent runs in Google's `agy` language server against Google's backend
|
|
2
|
+
// with a fixed model list; neither Antigravity IDE 1.107.0 (extensions/agy/package.json settings) nor its docs offer an
|
|
3
|
+
// endpoint or key, and a settings.json "openai-compatible" attempt went unanswered (https://discuss.ai.google.dev/t/168654, 2026-06-01).
|
|
4
|
+
import type { Connector } from "../common.ts"
|
|
5
|
+
|
|
6
|
+
export const connectors: Connector[] = []
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Gateway connector for Auggie: none. It speaks only Augment's own chat-stream API (its model registry keeps provider
|
|
2
|
+
// "augment" models only, wire/drivers/b.ts:12), and ~/.augment/settings.json has `defaultModel` but no provider, endpoint or
|
|
3
|
+
// key (https://docs.augmentcode.com/cli/config, checked 2026-11-10).
|
|
4
|
+
import type { Connector } from "../common.ts"
|
|
5
|
+
|
|
6
|
+
export const connectors: Connector[] = []
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Gateway connector for Cline's CLI / SDK (`cline`, npm package cline 3.0.74 on @cline/core 0.2.0, checked 2026-11-10 in
|
|
2
|
+
// ~/.cache/brain-wire/cline-sdk). Cline keeps its providers in two plain JSON files under ~/.cline/data/settings/:
|
|
3
|
+
//
|
|
4
|
+
// providers.json { version: 1, lastUsedProvider, modes, providers: { <id>: { settings, updatedAt, tokenSource } } }
|
|
5
|
+
// settings = { provider, apiKey, model, baseUrl, headers, ... } (@cline/core dist/services/llms/
|
|
6
|
+
// provider-settings.d.ts ProviderSettingsSchema; StoredProviderSettings in dist/types/provider-settings.d.ts).
|
|
7
|
+
// Cline itself keeps this file 0600 (ProviderSettingsManager: write() with mode 384, chmod 384 on load),
|
|
8
|
+
// reads it fresh before every change (saveProviderSettings is read-modify-write) and starts a run on
|
|
9
|
+
// `lastUsedProvider` when no -P is given.
|
|
10
|
+
// models.json { version: 1, providers: { <id>: { provider: { name, baseUrl, defaultModelId, protocol, client,
|
|
11
|
+
// capabilities }, models: { <model>: { id, name, contextWindow, maxTokens, capabilities } } } } }
|
|
12
|
+
// (dist/services/providers/local-provider-registry.d.ts StoredModelsFileSchema): the custom providers
|
|
13
|
+
// Cline knows about; `cline auth -p <id>` refuses an id that is in neither the built-ins nor this file.
|
|
14
|
+
//
|
|
15
|
+
// So brain adds its own named provider, "brain-gateway" (shown as "100xprompt gateway", its model as 100xprompt Pro), an
|
|
16
|
+
// OpenAI Chat Completions client on <route>/v1 with the x-100x-context-edit header (settings.headers), and makes it the
|
|
17
|
+
// last-used provider. The person's own providers are not touched. Cline has no key-file or key-command option for a
|
|
18
|
+
// custom provider (built-ins read a fixed env var at most: `apiKeyEnv` in @cline/llms), so the key is copied into
|
|
19
|
+
// providers.json, which stays 0600.
|
|
20
|
+
//
|
|
21
|
+
// Checked for real (scripts/gateway-real/cline-sdk.ts): with these two files, `cline -y "<prompt>"` sends POST
|
|
22
|
+
// /flash/v1/chat/completions with `Authorization: Bearer <key>`, `x-100x-context-edit: on` and model gemini-3.8-flash, and
|
|
23
|
+
// never asks for /v1/models.
|
|
24
|
+
//
|
|
25
|
+
// Not covered here: CLINE_PROVIDER_SETTINGS_PATH (a providers.json somewhere else; models.json then sits next to it) is
|
|
26
|
+
// not followed; CLINE_DIR and CLINE_DATA_DIR are, when they exist. The VS Code extension (id `cline`, see cline.ts) shares
|
|
27
|
+
// ~/.cline/data but chooses its provider from its own globalState.json, so this connector does not change it.
|
|
28
|
+
import { join } from "node:path"
|
|
29
|
+
import { type Connector, dirFromEnv, EDIT_HEADER, type Env, modelName, readKey, routeUrl } from "../common.ts"
|
|
30
|
+
|
|
31
|
+
const ID = "brain-gateway"
|
|
32
|
+
/** Gemini 3.x Flash, which the gateway's models are: a 1M-token window, 64K out. */
|
|
33
|
+
const CONTEXT_WINDOW = 1_048_576
|
|
34
|
+
const MAX_OUTPUT = 65_536
|
|
35
|
+
/** Cline requires an ISO date here; a fixed one keeps edits() free of side effects (Cline sets its own when it saves). */
|
|
36
|
+
const UPDATED_AT = "2026-01-01T00:00:00.000Z"
|
|
37
|
+
|
|
38
|
+
/** ~/.cline/data, or $CLINE_DATA_DIR, or $CLINE_DIR/data (each only when it exists), as @cline/shared resolves it. */
|
|
39
|
+
export const clineDataDir = (home: string, env: Env) => dirFromEnv(env, "CLINE_DATA_DIR", join(dirFromEnv(env, "CLINE_DIR", join(home, ".cline")), "data"))
|
|
40
|
+
const settingsDir = (home: string, env: Env) => join(clineDataDir(home, env), "settings")
|
|
41
|
+
|
|
42
|
+
export const connectors: Connector[] = [
|
|
43
|
+
{
|
|
44
|
+
id: "cline-sdk",
|
|
45
|
+
label: "Cline CLI",
|
|
46
|
+
file: (home, env) => join(settingsDir(home, env), "providers.json"),
|
|
47
|
+
probe: [".cline"],
|
|
48
|
+
authHeader: "authorization",
|
|
49
|
+
edits: (home, s, env) => {
|
|
50
|
+
const baseUrl = `${routeUrl(s)}/v1`
|
|
51
|
+
return [
|
|
52
|
+
{
|
|
53
|
+
path: join(settingsDir(home, env), "models.json"),
|
|
54
|
+
format: "json",
|
|
55
|
+
values: {
|
|
56
|
+
version: 1,
|
|
57
|
+
[`providers/${ID}`]: {
|
|
58
|
+
provider: { name: "100xprompt gateway", baseUrl, defaultModelId: s.model, protocol: "openai-chat", client: "openai-compatible", capabilities: ["tools", "streaming"] },
|
|
59
|
+
models: { [s.model]: { id: s.model, name: modelName(s.model), contextWindow: CONTEXT_WINDOW, maxTokens: MAX_OUTPUT, capabilities: ["tools", "streaming", "images"] } },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
path: join(settingsDir(home, env), "providers.json"),
|
|
65
|
+
format: "json",
|
|
66
|
+
holdsKey: true,
|
|
67
|
+
secretKeys: [`providers/${ID}`],
|
|
68
|
+
values: {
|
|
69
|
+
version: 1,
|
|
70
|
+
lastUsedProvider: ID,
|
|
71
|
+
[`providers/${ID}`]: {
|
|
72
|
+
settings: { provider: ID, apiKey: readKey(home), model: s.model, baseUrl, ...(s.contextEdit ? { headers: { [EDIT_HEADER]: "on" } } : {}) },
|
|
73
|
+
updatedAt: UPDATED_AT,
|
|
74
|
+
tokenSource: "manual",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
notes: [
|
|
81
|
+
"Cline CLI now starts on the 100xprompt gateway provider (its last-used provider); `cline -P <provider>` or /model still picks another for a run.",
|
|
82
|
+
"Cline keeps provider keys in ~/.cline/data/settings/providers.json (it has no key-file option), so the gateway key is copied there; the file stays readable by you only (0600).",
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Gateway connector for Cline, the VS Code extension: NOT BUILT. Its settings are files, but not files brain can safely
|
|
2
|
+
// write and cleanly undo. (Cline's CLI / SDK is connected by cline-sdk.ts.)
|
|
3
|
+
//
|
|
4
|
+
// Checked 2026-11-10 against Cline 4.1.26 (saoudrizwan.claude-dev-4.1.26 in ~/.vscode/extensions, dist/extension.js) and
|
|
5
|
+
// the SDK it shares with the CLI (@cline/core 0.2.0 in ~/.cache/brain-wire/cline-sdk):
|
|
6
|
+
// - Where the settings are: since 4.x the extension's global state and secrets are JSON files, ~/.cline/data/
|
|
7
|
+
// globalState.json and secrets.json (0600) ($CLINE_DATA_DIR overrides): p3s() builds them as TDe file stores, and
|
|
8
|
+
// activation (bg0 -> RWu) migrates VS Code's own extension state into them. The provider a task uses comes from that
|
|
9
|
+
// state (SessionFactory: getApiConfiguration() -> actModeApiProvider / planModeApiProvider), with per-provider keys:
|
|
10
|
+
// for an OpenAI-compatible endpoint `openAiBaseUrl`, `actModeOpenAiModelId` / `planModeOpenAiModelId`,
|
|
11
|
+
// `actModeOpenAiModelInfo`, `openAiHeaders` in globalState.json and `openAiApiKey` in secrets.json.
|
|
12
|
+
// - Why brain cannot edit them safely:
|
|
13
|
+
// 1. The extension keeps both files in memory and writes them back whole. TDe reads its file once, in the constructor
|
|
14
|
+
// (`this.data = this.readFromDisk()`), and every change rewrites the file from that copy (setBatch -> writeToDisk);
|
|
15
|
+
// nothing reloads it. While VS Code (or Cursor or Windsurf, which share ~/.cline/data) runs Cline, a connect is
|
|
16
|
+
// overwritten by the next state write, and a disconnect is undone: the gateway settings, and on the next secrets
|
|
17
|
+
// write the key copy, come back from memory after brain recorded them as removed.
|
|
18
|
+
// 2. There is one OpenAI-compatible slot, not a named provider: connect would have to rewrite the person's own
|
|
19
|
+
// openAiBaseUrl / openAiApiKey / model entries and the act and plan providers.
|
|
20
|
+
// 3. A second key copy brain would not know about: the SDK's legacy migration (@cline/core migrateLegacyProviderSettings,
|
|
21
|
+
// run by the ProviderSettingsManager constructor, which both the extension and the CLI create to read
|
|
22
|
+
// providers.json) copies every provider that has a key in
|
|
23
|
+
// globalState.json / secrets.json (`openAiApiKey` -> "openai" -> "openai-compatible") into
|
|
24
|
+
// ~/.cline/data/settings/providers.json, key included, when that provider is not there yet. Disconnect would leave it.
|
|
25
|
+
// - What does reach the extension: when no provider was ever chosen in it (actModeApiProvider unset), the SessionFactory
|
|
26
|
+
// falls back to providers.json's last-used provider ("Using SDK provider fallback"), which `--tool cline-sdk` sets. Not
|
|
27
|
+
// verified with a custom provider id in the extension, so not claimed.
|
|
28
|
+
//
|
|
29
|
+
// What would make it buildable: a connector hook in the core that refuses to connect or disconnect while the app holding
|
|
30
|
+
// the files is running (VS Code, Cursor, Windsurf), and a way to also own (and remove) the providers.json copy the
|
|
31
|
+
// migration makes, e.g. by writing providers.json's "openai-compatible" entry as well so the migration skips it.
|
|
32
|
+
import type { Connector } from "../common.ts"
|
|
33
|
+
|
|
34
|
+
export const connectors: Connector[] = []
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Gateway connector for Codebuff (now the Freebuff CLI, npm `freebuff`): not built. Its /byok takes an OpenAI-compatible
|
|
2
|
+
// base URL (kept in ~/.config/freebuff/byok/connections.json), but a CLI connection's key can only be an `env:NAME` reference
|
|
3
|
+
// or an OS-keychain entry, never a file (CodebuffAI/codebuff sdk/src/byok.ts:27,1196-1300 and cli/src/utils/byok.ts:57-63, 2026-10-14).
|
|
4
|
+
import type { Connector } from "../common.ts"
|
|
5
|
+
|
|
6
|
+
export const connectors: Connector[] = []
|