@100xprompt/brain 0.0.0-stage → 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/LICENSE +48 -0
- package/README.md +235 -2
- package/package.json +48 -4
- package/src/adapters/aider/index.ts +8 -0
- package/src/adapters/aider/reader.ts +129 -0
- package/src/adapters/aider/records.ts +220 -0
- package/src/adapters/aider/writer.ts +269 -0
- package/src/adapters/amazon-q-cli/index.ts +8 -0
- package/src/adapters/amazon-q-cli/reader.ts +84 -0
- package/src/adapters/amazon-q-cli/records.ts +191 -0
- package/src/adapters/amazon-q-cli/writer.ts +370 -0
- package/src/adapters/amazon-q-ide/index.ts +9 -0
- package/src/adapters/amazon-q-ide/reader.ts +131 -0
- package/src/adapters/amazon-q-ide/records.ts +141 -0
- package/src/adapters/amazon-q-ide/writer.ts +325 -0
- package/src/adapters/antigravity/index.ts +9 -0
- package/src/adapters/antigravity/proto.ts +194 -0
- package/src/adapters/antigravity/reader.ts +424 -0
- package/src/adapters/antigravity/records.ts +236 -0
- package/src/adapters/antigravity/writer.ts +463 -0
- package/src/adapters/auggie/index.ts +36 -0
- package/src/adapters/claude-code/index.ts +8 -0
- package/src/adapters/claude-code/reader.ts +639 -0
- package/src/adapters/claude-code/records.ts +88 -0
- package/src/adapters/claude-code/writer.ts +329 -0
- package/src/adapters/cline/index.ts +12 -0
- package/src/adapters/cline/reader.ts +432 -0
- package/src/adapters/cline/records.ts +151 -0
- package/src/adapters/cline/vscdb.ts +56 -0
- package/src/adapters/cline/writer.ts +656 -0
- package/src/adapters/cline/xml.ts +177 -0
- package/src/adapters/cline4/index.ts +9 -0
- package/src/adapters/cline4/reader.ts +179 -0
- package/src/adapters/cline4/records.ts +92 -0
- package/src/adapters/cline4/writer.ts +361 -0
- package/src/adapters/codebuff/index.ts +8 -0
- package/src/adapters/codebuff/project.ts +207 -0
- package/src/adapters/codebuff/reader.ts +215 -0
- package/src/adapters/codebuff/records.ts +173 -0
- package/src/adapters/codebuff/writer.ts +383 -0
- package/src/adapters/codex/index.ts +8 -0
- package/src/adapters/codex/native.ts +239 -0
- package/src/adapters/codex/reader.ts +747 -0
- package/src/adapters/codex/records.ts +114 -0
- package/src/adapters/codex/writer.ts +479 -0
- package/src/adapters/continue/index.ts +9 -0
- package/src/adapters/continue/reader.ts +68 -0
- package/src/adapters/continue/records.ts +418 -0
- package/src/adapters/continue/writer.ts +162 -0
- package/src/adapters/copilot-cli/index.ts +9 -0
- package/src/adapters/copilot-cli/reader.ts +186 -0
- package/src/adapters/copilot-cli/records.ts +241 -0
- package/src/adapters/copilot-cli/writer.ts +353 -0
- package/src/adapters/crush/index.ts +8 -0
- package/src/adapters/crush/reader.ts +180 -0
- package/src/adapters/crush/records.ts +169 -0
- package/src/adapters/crush/writer.ts +333 -0
- package/src/adapters/cursor-cli/index.ts +405 -0
- package/src/adapters/cursor-ide/agentkv.ts +95 -0
- package/src/adapters/cursor-ide/index.ts +501 -0
- package/src/adapters/cursor-ide/transcripts.ts +122 -0
- package/src/adapters/cursor-ide/workspaces.ts +119 -0
- package/src/adapters/cursor-ide/writer.ts +170 -0
- package/src/adapters/devin/index.ts +8 -0
- package/src/adapters/devin/reader.ts +305 -0
- package/src/adapters/devin/records.ts +277 -0
- package/src/adapters/devin/writer.ts +523 -0
- package/src/adapters/factory-droid/index.ts +12 -0
- package/src/adapters/factory-droid/reader.ts +184 -0
- package/src/adapters/factory-droid/records.ts +79 -0
- package/src/adapters/factory-droid/writer.ts +350 -0
- package/src/adapters/gemini-cli/exchanges.ts +228 -0
- package/src/adapters/gemini-cli/index.ts +396 -0
- package/src/adapters/gemini-cli/records.ts +521 -0
- package/src/adapters/gemini-cli/writer.ts +354 -0
- package/src/adapters/goose/index.ts +8 -0
- package/src/adapters/goose/reader.ts +138 -0
- package/src/adapters/goose/records.ts +199 -0
- package/src/adapters/goose/writer.ts +340 -0
- package/src/adapters/grok-cli/image.ts +44 -0
- package/src/adapters/grok-cli/index.ts +8 -0
- package/src/adapters/grok-cli/reader.ts +302 -0
- package/src/adapters/grok-cli/records.ts +172 -0
- package/src/adapters/grok-cli/writer.ts +329 -0
- package/src/adapters/hermes/index.ts +8 -0
- package/src/adapters/hermes/reader.ts +249 -0
- package/src/adapters/hermes/records.ts +285 -0
- package/src/adapters/hermes/writer.ts +452 -0
- package/src/adapters/iflow-cli/index.ts +72 -0
- package/src/adapters/iflow-cli/records.ts +215 -0
- package/src/adapters/iflow-cli/writer.ts +204 -0
- package/src/adapters/index.ts +112 -0
- package/src/adapters/kilo-cli/index.ts +21 -0
- package/src/adapters/kilo-code/index.ts +11 -0
- package/src/adapters/kilo-code/writer.ts +92 -0
- package/src/adapters/kimi/index.ts +9 -0
- package/src/adapters/kimi/reader.ts +92 -0
- package/src/adapters/kimi/records.ts +221 -0
- package/src/adapters/kimi/writer.ts +367 -0
- package/src/adapters/kiro-cli/index.ts +8 -0
- package/src/adapters/kiro-cli/reader.ts +87 -0
- package/src/adapters/kiro-cli/records.ts +260 -0
- package/src/adapters/kiro-cli/writer.ts +305 -0
- package/src/adapters/kiro-ide/index.ts +8 -0
- package/src/adapters/kiro-ide/writer.ts +52 -0
- package/src/adapters/letta/index.ts +8 -0
- package/src/adapters/letta/reader.ts +177 -0
- package/src/adapters/letta/records.ts +119 -0
- package/src/adapters/letta/writer.ts +413 -0
- package/src/adapters/mastracode/index.ts +8 -0
- package/src/adapters/mastracode/reader.ts +323 -0
- package/src/adapters/mastracode/records.ts +336 -0
- package/src/adapters/mastracode/writer.ts +385 -0
- package/src/adapters/mistral-vibe/index.ts +9 -0
- package/src/adapters/mistral-vibe/reader.ts +62 -0
- package/src/adapters/mistral-vibe/records.ts +104 -0
- package/src/adapters/mistral-vibe/writer.ts +359 -0
- package/src/adapters/muse/index.ts +10 -0
- package/src/adapters/muse/reader.ts +332 -0
- package/src/adapters/muse/records.ts +104 -0
- package/src/adapters/muse/writer.ts +387 -0
- package/src/adapters/omp/index.ts +13 -0
- package/src/adapters/omp/reader.ts +314 -0
- package/src/adapters/omp/records.ts +153 -0
- package/src/adapters/omp/writer.ts +216 -0
- package/src/adapters/opencode/ids.ts +52 -0
- package/src/adapters/opencode/index.ts +31 -0
- package/src/adapters/opencode/note.ts +8 -0
- package/src/adapters/opencode/reader.ts +452 -0
- package/src/adapters/opencode/tools.ts +174 -0
- package/src/adapters/opencode/v2.ts +367 -0
- package/src/adapters/opencode/writer.ts +379 -0
- package/src/adapters/openhands/index.ts +8 -0
- package/src/adapters/openhands/reader.ts +222 -0
- package/src/adapters/openhands/records.ts +136 -0
- package/src/adapters/openhands/writer.ts +404 -0
- package/src/adapters/pearai/index.ts +10 -0
- package/src/adapters/pi/index.ts +28 -0
- package/src/adapters/pi/reader.ts +261 -0
- package/src/adapters/pi/records.ts +99 -0
- package/src/adapters/pi/writer.ts +362 -0
- package/src/adapters/qoder-cli/index.ts +8 -0
- package/src/adapters/qoder-cli/reader.ts +83 -0
- package/src/adapters/qoder-cli/records.ts +45 -0
- package/src/adapters/qoder-cli/writer.ts +72 -0
- package/src/adapters/qwen-code/index.ts +84 -0
- package/src/adapters/qwen-code/records.ts +194 -0
- package/src/adapters/qwen-code/writer.ts +224 -0
- package/src/adapters/tabnine-cli/index.ts +8 -0
- package/src/adapters/tabnine-cli/reader.ts +106 -0
- package/src/adapters/tabnine-cli/records.ts +33 -0
- package/src/adapters/tabnine-cli/writer.ts +156 -0
- package/src/adapters/types.ts +73 -0
- package/src/adapters/void/index.ts +9 -0
- package/src/adapters/void/reader.ts +48 -0
- package/src/adapters/void/records.ts +285 -0
- package/src/adapters/void/writer.ts +93 -0
- package/src/adapters/vscode-copilot/index.ts +9 -0
- package/src/adapters/vscode-copilot/reader.ts +212 -0
- package/src/adapters/vscode-copilot/records.ts +388 -0
- package/src/adapters/vscode-copilot/shared.ts +68 -0
- package/src/adapters/vscode-copilot/writer.ts +546 -0
- package/src/adapters/zed/index.ts +8 -0
- package/src/adapters/zed/reader.ts +198 -0
- package/src/adapters/zed/records.ts +87 -0
- package/src/adapters/zed/writer.ts +341 -0
- package/src/brief-writers.ts +71 -0
- package/src/brief.ts +238 -0
- package/src/cli.ts +351 -0
- 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/fsutil.ts +133 -0
- package/src/gateway/common.ts +232 -0
- package/src/gateway/connect.ts +847 -0
- 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
- package/src/gateway/signin.ts +323 -0
- package/src/handoff.ts +104 -0
- package/src/hooks.ts +455 -0
- package/src/memory/audit.ts +514 -0
- package/src/memory/blobs.ts +94 -0
- package/src/memory/cli.ts +117 -0
- package/src/memory/distill.ts +381 -0
- package/src/memory/externalize.ts +435 -0
- package/src/memory/lookups.ts +70 -0
- package/src/memory/mcp-install.ts +401 -0
- package/src/memory/mcp-server.ts +296 -0
- package/src/memory/prepare.ts +179 -0
- package/src/memory/preview.ts +154 -0
- package/src/memory/project.ts +35 -0
- package/src/memory/redact.ts +72 -0
- package/src/memory/relevance.ts +187 -0
- package/src/memory/repomap.ts +516 -0
- package/src/memory/store.ts +270 -0
- package/src/memory/sync.ts +166 -0
- package/src/memory/tidy.ts +193 -0
- package/src/memory/types.ts +118 -0
- package/src/org/admin-ops.ts +96 -0
- package/src/org/auth.ts +112 -0
- package/src/org/backup.ts +115 -0
- package/src/org/cli.ts +438 -0
- package/src/org/client.ts +404 -0
- package/src/org/config.ts +44 -0
- package/src/org/crypto.ts +537 -0
- package/src/org/db.ts +435 -0
- package/src/org/graph.ts +318 -0
- package/src/org/oidc.ts +435 -0
- package/src/org/policy.ts +398 -0
- package/src/org/server.ts +920 -0
- package/src/org/sso.ts +316 -0
- package/src/org/tls.ts +66 -0
- package/src/org/types.ts +193 -0
- package/src/self.ts +68 -0
- package/src/session/attachments.ts +53 -0
- package/src/session/peekcache.ts +50 -0
- package/src/session/types.ts +104 -0
- package/src/session/util.ts +130 -0
- package/src/sqlite.ts +48 -0
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
// Gateway connector for Mistral Vibe (the `vibe` CLI), checked against vibe 3.1.0 (`vibe --version`; the install in
|
|
2
|
+
// ~/.cache/brain-wire/mistral-vibe, read only) and https://docs.mistral.ai/vibe/code/cli/configuration-reference.
|
|
3
|
+
//
|
|
4
|
+
// How Vibe reaches a custom endpoint (paths under site-packages/):
|
|
5
|
+
// - <VIBE_HOME or ~/.vibe>/config.toml (vibe/core/config/layers/user.py:26, vibe/core/paths/_vibe_home.py:13) holds a
|
|
6
|
+
// `[[providers]]` list (vibe/core/config/models.py:135-162: name, api_base, api_key_env_var, api_style "openai" by
|
|
7
|
+
// default, backend "generic" by default, extra_headers), a `[[models]]` list (models.py:520-524: name = the model id
|
|
8
|
+
// sent, provider, alias, display_name = what the model picker shows) and `active_model`, an alias
|
|
9
|
+
// (vibe/core/config/vibe_schema.py:559). Providers merge with Vibe's built-in ones by name and models by alias
|
|
10
|
+
// (vibe_schema.py:585-596), so adding one entry of each leaves the person's own and Vibe's defaults in place.
|
|
11
|
+
// - api_style "openai" posts to <api_base>/chat/completions
|
|
12
|
+
// (mistralai_vibe_local_harness/vibe/adapters/generic/_openai_chat.py:47) with `Authorization: Bearer <key>`
|
|
13
|
+
// (adapters/generic/_base.py:72-76) plus the provider's extra_headers verbatim (vibe/_runtime_config.py:551-552): so
|
|
14
|
+
// the gateway's OpenAI route, with the context-edit header.
|
|
15
|
+
// - The key: a provider names an environment variable (api_key_env_var); Vibe looks in its run's credentials, the
|
|
16
|
+
// environment, then the OS keyring (vibe/utils/api_keys.py:92-117). There is no key-file or key-command option. The
|
|
17
|
+
// app server loads <VIBE_HOME>/.env into the environment at start (vibe/app_server/stdio.py:199,
|
|
18
|
+
// vibe_schema.py:159-180; a variable already set in the shell wins), and Vibe's own setup stores keys there, owner-only
|
|
19
|
+
// (vibe/setup/auth/api_key_persistence.py:33-39). So the key is copied into that .env (holdsKey, 0600).
|
|
20
|
+
//
|
|
21
|
+
// config.toml is TOML that the core's "toml" format cannot edit (lists of tables), so this file brings its own
|
|
22
|
+
// comment-keeping TOML editor (`tomlCodec`, also used by the Grok CLI connector): it changes only the entries it is given
|
|
23
|
+
// and leaves every other line, comment and blank line as it was.
|
|
24
|
+
import { join } from "node:path"
|
|
25
|
+
import { type Codec, dirFromEnv, EDIT_HEADER, type Connector, type Json, modelName, readKey, routeUrl } from "../common.ts"
|
|
26
|
+
|
|
27
|
+
// ---- a comment-keeping TOML editor ------------------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* What a codec key names in the file:
|
|
31
|
+
* key one `key = value` line in a table ([] = the top of the file, before any [table])
|
|
32
|
+
* table a whole [a.b] table, with its sub-tables
|
|
33
|
+
* entry one [[array]] element, found by the first of `fields` it has (Vibe's models: alias, else name); with `mapForm`
|
|
34
|
+
* a file that writes the list as [array.<match>] tables is edited in that form instead
|
|
35
|
+
*/
|
|
36
|
+
export type TomlTarget =
|
|
37
|
+
| { kind: "key"; table: string[]; key: string }
|
|
38
|
+
| { kind: "table"; path: string[] }
|
|
39
|
+
| { kind: "entry"; array: string; fields: string[]; match: string; mapForm?: boolean }
|
|
40
|
+
|
|
41
|
+
/** A key as written: bare where it can be, quoted otherwise (also when it starts with a digit, which Bun's parser rejects). */
|
|
42
|
+
const BARE = /^[A-Za-z_-][A-Za-z0-9_-]*$/
|
|
43
|
+
export const tomlKey = (k: string) => (BARE.test(k) ? k : JSON.stringify(k))
|
|
44
|
+
|
|
45
|
+
/** A value on one line: strings as basic strings (JSON's escapes are TOML's), tables as inline tables. */
|
|
46
|
+
export function tomlValue(v: unknown): string {
|
|
47
|
+
if (typeof v === "string") return JSON.stringify(v).replace(/\u007f/g, "\\u007f")
|
|
48
|
+
if (typeof v === "boolean" || (typeof v === "number" && Number.isFinite(v))) return String(v)
|
|
49
|
+
if (Array.isArray(v)) return `[${v.map(tomlValue).join(", ")}]`
|
|
50
|
+
if (v && typeof v === "object") {
|
|
51
|
+
const e = Object.entries(v).filter(([, x]) => x !== undefined && x !== null)
|
|
52
|
+
return e.length ? `{ ${e.map(([k, x]) => `${tomlKey(k)} = ${tomlValue(x)}`).join(", ")} }` : "{}"
|
|
53
|
+
}
|
|
54
|
+
throw new Error("a value TOML cannot hold")
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface Row {
|
|
58
|
+
kind: "blank" | "comment" | "header" | "kv" | "more"
|
|
59
|
+
path?: string[]
|
|
60
|
+
array?: boolean
|
|
61
|
+
/** kv: where the value starts (after `=` and the spaces after it). */
|
|
62
|
+
at?: number
|
|
63
|
+
}
|
|
64
|
+
interface Doc {
|
|
65
|
+
lines: string[]
|
|
66
|
+
eol: string
|
|
67
|
+
rows: Row[]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** A dotted key (bare, "basic" or 'literal' parts) starting at `at`, and where it ends. */
|
|
71
|
+
function keyPath(s: string, at: number): { path: string[]; end: number } | undefined {
|
|
72
|
+
const path: string[] = []
|
|
73
|
+
let i = at
|
|
74
|
+
for (;;) {
|
|
75
|
+
while (s[i] === " " || s[i] === "\t") i++
|
|
76
|
+
if (s[i] === '"') {
|
|
77
|
+
let j = i + 1
|
|
78
|
+
while (j < s.length && s[j] !== '"') j += s[j] === "\\" ? 2 : 1
|
|
79
|
+
if (j >= s.length) return undefined
|
|
80
|
+
try {
|
|
81
|
+
path.push(JSON.parse(s.slice(i, j + 1)))
|
|
82
|
+
} catch {
|
|
83
|
+
return undefined
|
|
84
|
+
}
|
|
85
|
+
i = j + 1
|
|
86
|
+
} else if (s[i] === "'") {
|
|
87
|
+
const j = s.indexOf("'", i + 1)
|
|
88
|
+
if (j < 0) return undefined
|
|
89
|
+
path.push(s.slice(i + 1, j))
|
|
90
|
+
i = j + 1
|
|
91
|
+
} else {
|
|
92
|
+
const m = /^[A-Za-z0-9_-]+/.exec(s.slice(i))
|
|
93
|
+
if (!m) return undefined
|
|
94
|
+
path.push(m[0])
|
|
95
|
+
i += m[0].length
|
|
96
|
+
}
|
|
97
|
+
while (s[i] === " " || s[i] === "\t") i++
|
|
98
|
+
if (s[i] !== ".") return { path, end: i }
|
|
99
|
+
i++
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Where a multi-line string that opened with `q` closes on this line (the index after it), or -1. */
|
|
104
|
+
function closeAt(line: string, from: number, q: string): number {
|
|
105
|
+
for (let j = from; j < line.length; j++) {
|
|
106
|
+
if (q === '"""' && line[j] === "\\") {
|
|
107
|
+
j++
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
if (line.startsWith(q, j)) {
|
|
111
|
+
let k = j + 3
|
|
112
|
+
for (let n = 0; n < 2 && line[k] === q[0]; n++) k++
|
|
113
|
+
return k
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return -1
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The file as lines, each classified. A line counts (header, key = value, comment, blank) only when it starts outside any
|
|
121
|
+
* multi-line string or bracket; the lines of a value that spans several are "more". undefined: something it cannot
|
|
122
|
+
* follow (an unclosed string or bracket, a line that is not TOML).
|
|
123
|
+
*/
|
|
124
|
+
function readDoc(text: string): Doc | undefined {
|
|
125
|
+
const eol = text.includes("\r\n") ? "\r\n" : "\n"
|
|
126
|
+
const lines = text === "" ? [] : text.replace(/\r?\n$/, "").split(/\r?\n/)
|
|
127
|
+
const rows: Row[] = []
|
|
128
|
+
let ml: string | undefined
|
|
129
|
+
let depth = 0
|
|
130
|
+
for (const line of lines) {
|
|
131
|
+
const top = !ml && depth === 0
|
|
132
|
+
let i = 0
|
|
133
|
+
while (i < line.length) {
|
|
134
|
+
if (ml) {
|
|
135
|
+
const j = closeAt(line, i, ml)
|
|
136
|
+
if (j < 0) break
|
|
137
|
+
ml = undefined
|
|
138
|
+
i = j
|
|
139
|
+
continue
|
|
140
|
+
}
|
|
141
|
+
const c = line[i]!
|
|
142
|
+
if (c === "#") break
|
|
143
|
+
if (line.startsWith('"""', i) || line.startsWith("'''", i)) {
|
|
144
|
+
ml = line.slice(i, i + 3)
|
|
145
|
+
i += 3
|
|
146
|
+
continue
|
|
147
|
+
}
|
|
148
|
+
if (c === '"' || c === "'") {
|
|
149
|
+
let j = i + 1
|
|
150
|
+
while (j < line.length && line[j] !== c) j += c === '"' && line[j] === "\\" ? 2 : 1
|
|
151
|
+
if (j >= line.length) return undefined
|
|
152
|
+
i = j + 1
|
|
153
|
+
continue
|
|
154
|
+
}
|
|
155
|
+
if (c === "[" || c === "{") depth++
|
|
156
|
+
else if (c === "]" || c === "}") depth--
|
|
157
|
+
if (depth < 0) return undefined
|
|
158
|
+
i++
|
|
159
|
+
}
|
|
160
|
+
const t = line.trim()
|
|
161
|
+
if (!top) rows.push({ kind: "more" })
|
|
162
|
+
else if (!t) rows.push({ kind: "blank" })
|
|
163
|
+
else if (t.startsWith("#")) rows.push({ kind: "comment" })
|
|
164
|
+
else if (t.startsWith("[")) {
|
|
165
|
+
const m = /^\s*(\[\[?)/.exec(line)!
|
|
166
|
+
const array = m[1] === "[["
|
|
167
|
+
const k = keyPath(line, m[0].length)
|
|
168
|
+
const close = array ? "]]" : "]"
|
|
169
|
+
if (!k || !line.startsWith(close, k.end)) return undefined
|
|
170
|
+
const rest = line.slice(k.end + close.length).trim()
|
|
171
|
+
if (rest && !rest.startsWith("#")) return undefined
|
|
172
|
+
rows.push({ kind: "header", path: k.path, array })
|
|
173
|
+
} else {
|
|
174
|
+
const k = keyPath(line, 0)
|
|
175
|
+
if (!k || line[k.end] !== "=") return undefined
|
|
176
|
+
let at = k.end + 1
|
|
177
|
+
while (line[at] === " " || line[at] === "\t") at++
|
|
178
|
+
rows.push({ kind: "kv", path: k.path, at })
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (ml || depth) return undefined
|
|
182
|
+
return { lines, eol, rows }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const writeDoc = (lines: string[], eol: string) => (lines.length ? `${lines.join(eol)}${eol}` : "")
|
|
186
|
+
const eq = (a: string[] | undefined, b: string[]) => !!a && a.length === b.length && a.every((x, i) => x === b[i])
|
|
187
|
+
const startsWith = (a: string[] | undefined, b: string[]) => !!a && a.length > b.length && b.every((x, i) => x === a[i])
|
|
188
|
+
const headersWhere = (d: Doc, ok: (r: Row) => boolean) => d.rows.flatMap((r, i) => (r.kind === "header" && ok(r) ? [i] : []))
|
|
189
|
+
|
|
190
|
+
/** The first header after line `i` (lines.length when none); with `within`, headers of its sub-tables do not end it. */
|
|
191
|
+
function sectionEnd(d: Doc, i: number, within?: string[]): number {
|
|
192
|
+
for (let j = i + 1; j < d.rows.length; j++) if (d.rows[j]!.kind === "header" && !(within && startsWith(d.rows[j]!.path, within))) return j
|
|
193
|
+
return d.rows.length
|
|
194
|
+
}
|
|
195
|
+
/** A section's own lines: blank lines and comments at its end belong to whatever follows. */
|
|
196
|
+
function ownEnd(d: Doc, start: number, end: number): number {
|
|
197
|
+
while (end > start + 1 && (d.rows[end - 1]!.kind === "blank" || d.rows[end - 1]!.kind === "comment")) end--
|
|
198
|
+
return end
|
|
199
|
+
}
|
|
200
|
+
/** The end of the key = value starting at line i (a value may span lines). */
|
|
201
|
+
function kvEnd(d: Doc, i: number, limit: number): number {
|
|
202
|
+
let j = i + 1
|
|
203
|
+
while (j < limit && d.rows[j]!.kind === "more") j++
|
|
204
|
+
return j
|
|
205
|
+
}
|
|
206
|
+
/** A removed block takes one blank line before it along (the one an added block was given). */
|
|
207
|
+
const withBlankBefore = (d: Doc, from: number) => (from > 0 && d.rows[from - 1]!.kind === "blank" ? from - 1 : from)
|
|
208
|
+
|
|
209
|
+
/** The ` # comment` after a value, outside any string, with the spaces before it. */
|
|
210
|
+
function splitComment(s: string): { value: string; comment: string } {
|
|
211
|
+
for (let i = 0; i < s.length; i++) {
|
|
212
|
+
const c = s[i]
|
|
213
|
+
if (c === '"' || c === "'") {
|
|
214
|
+
let j = i + 1
|
|
215
|
+
while (j < s.length && s[j] !== c) j += c === '"' && s[j] === "\\" ? 2 : 1
|
|
216
|
+
i = j
|
|
217
|
+
} else if (c === "#") {
|
|
218
|
+
let j = i
|
|
219
|
+
while (j > 0 && /[ \t]/.test(s[j - 1]!)) j--
|
|
220
|
+
return { value: s.slice(0, j), comment: s.slice(j).trimEnd() }
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return { value: s.trimEnd(), comment: "" }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
interface Splice {
|
|
227
|
+
from: number
|
|
228
|
+
to: number
|
|
229
|
+
lines: string[]
|
|
230
|
+
}
|
|
231
|
+
function apply(d: Doc, edits: Splice[]): string {
|
|
232
|
+
const lines = d.lines.slice()
|
|
233
|
+
for (const e of edits.sort((a, b) => b.from - a.from)) lines.splice(e.from, e.to - e.from, ...e.lines)
|
|
234
|
+
return writeDoc(lines, d.eol)
|
|
235
|
+
}
|
|
236
|
+
/** A new block at the end of the file, after a blank line. */
|
|
237
|
+
const appendBlock = (d: Doc, block: string[]): Splice => ({ from: d.lines.length, to: d.lines.length, lines: d.lines.length ? ["", ...block] : block })
|
|
238
|
+
const body = (v: Json) => Object.entries(v).flatMap(([k, x]) => (x === undefined || x === null ? [] : [`${tomlKey(k)} = ${tomlValue(x)}`]))
|
|
239
|
+
|
|
240
|
+
function setKeyIn(d: Doc, table: string[], key: string, v: unknown): string {
|
|
241
|
+
let start = 0
|
|
242
|
+
let end = d.rows.findIndex((r) => r.kind === "header")
|
|
243
|
+
if (end < 0) end = d.rows.length
|
|
244
|
+
const h = table.length ? headersWhere(d, (r) => !r.array && eq(r.path, table))[0] : undefined
|
|
245
|
+
if (table.length) {
|
|
246
|
+
if (h === undefined) return v === undefined ? writeDoc(d.lines, d.eol) : apply(d, [appendBlock(d, [`[${table.map(tomlKey).join(".")}]`, `${tomlKey(key)} = ${tomlValue(v)}`])])
|
|
247
|
+
start = h + 1
|
|
248
|
+
end = sectionEnd(d, h)
|
|
249
|
+
}
|
|
250
|
+
let lastKv = -1
|
|
251
|
+
for (let i = start; i < end; i++) {
|
|
252
|
+
const r = d.rows[i]!
|
|
253
|
+
if (r.kind !== "kv") continue
|
|
254
|
+
const x = kvEnd(d, i, end)
|
|
255
|
+
if (!eq(r.path, [key])) {
|
|
256
|
+
lastKv = x
|
|
257
|
+
continue
|
|
258
|
+
}
|
|
259
|
+
if (v === undefined) {
|
|
260
|
+
// A table this leaves with nothing in it goes too (with the blank line before it): it was added with the key.
|
|
261
|
+
const emptied = h !== undefined && d.rows.slice(start, end).every((row, j) => start + j >= i && start + j < x ? true : row.kind === "blank")
|
|
262
|
+
return emptied ? apply(d, [{ from: withBlankBefore(d, h!), to: x, lines: [] }]) : apply(d, [{ from: i, to: x, lines: [] }])
|
|
263
|
+
}
|
|
264
|
+
const line = d.lines[i]!
|
|
265
|
+
const prefix = line.slice(0, r.at)
|
|
266
|
+
if (x > i + 1) return apply(d, [{ from: i, to: x, lines: [`${prefix}${tomlValue(v)}`] }])
|
|
267
|
+
// One line: its spacing, trailing comment and 'literal' quoting stay, so a later restore gives the line back as it was.
|
|
268
|
+
const { value, comment } = splitComment(line.slice(r.at))
|
|
269
|
+
const literal = value.startsWith("'") && !value.startsWith("'''") && typeof v === "string" && !/['\u0000-\u001f\u007f]/.test(v)
|
|
270
|
+
return apply(d, [{ from: i, to: x, lines: [`${prefix}${literal ? `'${v}'` : tomlValue(v)}${comment}`] }])
|
|
271
|
+
}
|
|
272
|
+
if (v === undefined) return writeDoc(d.lines, d.eol)
|
|
273
|
+
const at = lastKv >= 0 ? lastKv : table.length ? start : 0
|
|
274
|
+
return apply(d, [{ from: at, to: at, lines: [`${tomlKey(key)} = ${tomlValue(v)}`] }])
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function setTable(d: Doc, path: string[], v: unknown): string {
|
|
278
|
+
const main = headersWhere(d, (r) => !r.array && eq(r.path, path))[0]
|
|
279
|
+
const subs = headersWhere(d, (r) => startsWith(r.path, path))
|
|
280
|
+
const edits: Splice[] = subs.map((h) => ({ from: withBlankBefore(d, h), to: ownEnd(d, h, sectionEnd(d, h)), lines: [] }))
|
|
281
|
+
const block = v === undefined ? [] : [`[${path.map(tomlKey).join(".")}]`, ...body(v as Json)]
|
|
282
|
+
if (main !== undefined) {
|
|
283
|
+
const to = ownEnd(d, main, sectionEnd(d, main))
|
|
284
|
+
edits.push(v === undefined ? { from: withBlankBefore(d, main), to, lines: [] } : { from: main, to, lines: block })
|
|
285
|
+
} else if (v !== undefined) edits.push(appendBlock(d, block))
|
|
286
|
+
return apply(d, edits)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function setEntry(d: Doc, parsed: Json, t: Extract<TomlTarget, { kind: "entry" }>, v: unknown): string {
|
|
290
|
+
const list = parsed[t.array]
|
|
291
|
+
if (list && typeof list === "object" && !Array.isArray(list)) return setTable(d, [t.array, t.match], v)
|
|
292
|
+
const heads = headersWhere(d, (r) => !!r.array && eq(r.path, [t.array]))
|
|
293
|
+
const idx = (Array.isArray(list) ? list : []).findIndex((e) => entryName(e, t.fields) === t.match)
|
|
294
|
+
const range = (h: number) => ({ from: h, to: ownEnd(d, h, sectionEnd(d, h, [t.array])) })
|
|
295
|
+
const block = v === undefined ? [] : [`[[${tomlKey(t.array)}]]`, ...body(v as Json)]
|
|
296
|
+
if (idx >= 0) {
|
|
297
|
+
const r = range(heads[idx]!)
|
|
298
|
+
return apply(d, [v === undefined ? { from: withBlankBefore(d, r.from), to: r.to, lines: [] } : { ...r, lines: block }])
|
|
299
|
+
}
|
|
300
|
+
if (v === undefined) return writeDoc(d.lines, d.eol)
|
|
301
|
+
// A new entry goes after the list's last one, or at the end of the file.
|
|
302
|
+
if (heads.length) {
|
|
303
|
+
const end = range(heads[heads.length - 1]!).to
|
|
304
|
+
return apply(d, [{ from: end, to: end, lines: ["", ...block] }])
|
|
305
|
+
}
|
|
306
|
+
return apply(d, [appendBlock(d, block)])
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function entryName(e: unknown, fields: string[]): string | undefined {
|
|
310
|
+
if (!e || typeof e !== "object") return undefined
|
|
311
|
+
for (const f of fields) if (typeof (e as Json)[f] === "string") return (e as Json)[f]
|
|
312
|
+
return undefined
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const parse = (text: string): Json => (text.trim() ? (Bun.TOML.parse(text) as Json) : {})
|
|
316
|
+
const walk = (o: unknown, path: string[]): unknown => path.reduce<unknown>((cur, p) => (cur && typeof cur === "object" && !Array.isArray(cur) ? (cur as Json)[p] : undefined), o)
|
|
317
|
+
|
|
318
|
+
function getTarget(parsed: Json, t: TomlTarget): unknown {
|
|
319
|
+
if (t.kind === "key") {
|
|
320
|
+
const tab = walk(parsed, t.table)
|
|
321
|
+
return tab && typeof tab === "object" && !Array.isArray(tab) ? (tab as Json)[t.key] : undefined
|
|
322
|
+
}
|
|
323
|
+
if (t.kind === "table") return walk(parsed, t.path)
|
|
324
|
+
const list = parsed[t.array]
|
|
325
|
+
if (Array.isArray(list)) return list.find((e) => entryName(e, t.fields) === t.match)
|
|
326
|
+
return t.mapForm && list && typeof list === "object" ? (list as Json)[t.match] : undefined
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** The parsed file without the target (and without a table or list the target alone made), for comparing the rest. */
|
|
330
|
+
function without(parsed: Json, t: TomlTarget): Json {
|
|
331
|
+
const o = structuredClone(parsed)
|
|
332
|
+
const drop = (path: string[], last: string) => {
|
|
333
|
+
const parent = walk(o, path) as Json | undefined
|
|
334
|
+
if (!parent || typeof parent !== "object") return
|
|
335
|
+
delete parent[last]
|
|
336
|
+
for (let n = path.length; n > 0; n--) {
|
|
337
|
+
const here = walk(o, path.slice(0, n)) as Json
|
|
338
|
+
if (here && typeof here === "object" && !Array.isArray(here) && !Object.keys(here).length) delete (walk(o, path.slice(0, n - 1)) as Json)[path[n - 1]!]
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (t.kind === "key") drop(t.table, t.key)
|
|
342
|
+
else if (t.kind === "table") drop(t.path.slice(0, -1), t.path[t.path.length - 1]!)
|
|
343
|
+
else {
|
|
344
|
+
const list = o[t.array]
|
|
345
|
+
if (Array.isArray(list)) {
|
|
346
|
+
o[t.array] = list.filter((e) => entryName(e, t.fields) !== t.match)
|
|
347
|
+
if (!o[t.array].length) delete o[t.array]
|
|
348
|
+
} else drop([t.array], t.match)
|
|
349
|
+
}
|
|
350
|
+
return o
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** JSON with keys sorted, so two parses compare by content. */
|
|
354
|
+
const canon = (v: unknown): string =>
|
|
355
|
+
Array.isArray(v) ? `[${v.map(canon).join(",")}]` : v && typeof v === "object" ? `{${Object.keys(v).sort().map((k) => `${JSON.stringify(k)}:${canon((v as Json)[k])}`).join(",")}}` : JSON.stringify(v) ?? "null"
|
|
356
|
+
|
|
357
|
+
/** Why the file's way of writing the target's tables is not one these edits can keep valid (Bun's parser lets some through). */
|
|
358
|
+
function layoutProblem(d: Doc, parsed: Json, t: TomlTarget): string | undefined {
|
|
359
|
+
const firstHeader = d.rows.findIndex((r) => r.kind === "header")
|
|
360
|
+
const pre = d.rows.slice(0, firstHeader < 0 ? d.rows.length : firstHeader)
|
|
361
|
+
const root = t.kind === "key" ? (t.table[0] ?? t.key) : t.kind === "table" ? t.path[0]! : t.array
|
|
362
|
+
const dotted = t.kind === "key" && !t.table.length ? pre.filter((r) => r.kind === "kv" && r.path![0] === root && r.path!.length > 1) : pre.filter((r) => r.kind === "kv" && r.path![0] === root)
|
|
363
|
+
if (dotted.length) return `${root} is written inline or with dotted keys`
|
|
364
|
+
if (t.kind === "key") {
|
|
365
|
+
if (!t.table.length) return pre.filter((r) => r.kind === "kv" && eq(r.path, [t.key])).length > 1 ? `${t.key} is set twice` : undefined
|
|
366
|
+
if (headersWhere(d, (r) => !r.array && eq(r.path, t.table)).length > 1) return `[${t.table.join(".")}] appears twice`
|
|
367
|
+
if (headersWhere(d, (r) => !!r.array && eq(r.path, t.table)).length) return `${t.table.join(".")} is a list of tables`
|
|
368
|
+
return undefined
|
|
369
|
+
}
|
|
370
|
+
if (t.kind === "table") {
|
|
371
|
+
const own = headersWhere(d, (r) => !r.array && eq(r.path, t.path))
|
|
372
|
+
if (own.length > 1) return `[${t.path.join(".")}] appears twice`
|
|
373
|
+
if (headersWhere(d, (r) => !!r.array && (eq(r.path, t.path) || startsWith(t.path, r.path!))).length) return `${t.path.join(".")} sits in a list of tables`
|
|
374
|
+
if (!own.length && walk(parsed, t.path) !== undefined) return `${t.path.join(".")} is written inline or with dotted keys`
|
|
375
|
+
return undefined
|
|
376
|
+
}
|
|
377
|
+
const list = parsed[t.array]
|
|
378
|
+
if (list && typeof list === "object" && !Array.isArray(list)) {
|
|
379
|
+
if (!t.mapForm) return `${t.array} is not a list`
|
|
380
|
+
if (headersWhere(d, (r) => !!r.array && eq(r.path, [t.array])).length) return `${t.array} is written two ways`
|
|
381
|
+
for (const h of headersWhere(d, (r) => !r.array && eq(r.path, [t.array]))) if (d.rows.slice(h + 1, sectionEnd(d, h)).some((r) => r.kind === "kv")) return `${t.array} entries are written with dotted keys`
|
|
382
|
+
for (const k of Object.keys(list)) if (headersWhere(d, (r) => !r.array && eq(r.path, [t.array, k])).length !== 1) return `${t.array}.${k} is not one [${t.array}.${k}] table`
|
|
383
|
+
return undefined
|
|
384
|
+
}
|
|
385
|
+
if (headersWhere(d, (r) => !r.array && eq(r.path, [t.array])).length) return `[${t.array}] is a table here, not a list`
|
|
386
|
+
if (headersWhere(d, (r) => !!r.array && eq(r.path, [t.array])).length !== (Array.isArray(list) ? list.length : 0)) return `${t.array} is not written as [[${t.array}]] tables`
|
|
387
|
+
return undefined
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* A comment-keeping TOML codec. `target` says what each key names; `samples` are the keys the connector edits, with a
|
|
392
|
+
* value like the one it writes: before anything changes, each is tried on the file (set, then removed), and a file where
|
|
393
|
+
* that would not give back the same content, or that writes those tables in a way these edits cannot keep valid, is refused.
|
|
394
|
+
*/
|
|
395
|
+
export function tomlCodec(target: (key: string) => TomlTarget, samples: Array<[string, unknown]>): Codec {
|
|
396
|
+
const codec: Codec = {
|
|
397
|
+
check(text) {
|
|
398
|
+
if (!text.trim()) return undefined
|
|
399
|
+
let parsed: Json
|
|
400
|
+
try {
|
|
401
|
+
parsed = parse(text)
|
|
402
|
+
} catch {
|
|
403
|
+
return "it is not valid TOML; left as it is"
|
|
404
|
+
}
|
|
405
|
+
const d = readDoc(text)
|
|
406
|
+
if (!d) return "brain cannot follow its layout (an unclosed string or bracket?); left as it is"
|
|
407
|
+
for (const [key, sample] of samples) {
|
|
408
|
+
const t = target(key)
|
|
409
|
+
const why = layoutProblem(d, parsed, t)
|
|
410
|
+
if (why) return `${why}, which brain does not edit; left as it is`
|
|
411
|
+
try {
|
|
412
|
+
const added = codec.set(text, key, sample)
|
|
413
|
+
const a = parse(added)
|
|
414
|
+
const removed = parse(codec.set(added, key, undefined))
|
|
415
|
+
const rest = canon(without(parsed, t))
|
|
416
|
+
if (canon(getTarget(a, t)) !== canon(sample) || canon(without(a, t)) !== rest || getTarget(removed, t) !== undefined || canon(without(removed, t)) !== rest) throw new Error("mismatch")
|
|
417
|
+
} catch {
|
|
418
|
+
return `brain could not edit ${key.replace(/\//g, ".")} there without touching the rest; left as it is`
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return undefined
|
|
422
|
+
},
|
|
423
|
+
get(text, key) {
|
|
424
|
+
return getTarget(parse(text), target(key))
|
|
425
|
+
},
|
|
426
|
+
set(text, key, value) {
|
|
427
|
+
const d = readDoc(text)
|
|
428
|
+
if (!d) throw new Error("unreadable TOML layout")
|
|
429
|
+
const t = target(key)
|
|
430
|
+
if (t.kind === "key") return setKeyIn(d, t.table, t.key, value)
|
|
431
|
+
if (t.kind === "table") return setTable(d, t.path, value)
|
|
432
|
+
return setEntry(d, parse(text), t, value)
|
|
433
|
+
},
|
|
434
|
+
}
|
|
435
|
+
return codec
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ---- Mistral Vibe -------------------------------------------------------------------------------------------------------
|
|
439
|
+
|
|
440
|
+
/** The provider and model entries brain adds (the alias is what `active_model` names; the picker shows display_name). */
|
|
441
|
+
const PROVIDER = "100xprompt-gateway"
|
|
442
|
+
const ALIAS = "100xprompt"
|
|
443
|
+
/** The variable in <VIBE_HOME>/.env holding the key, named by the provider's api_key_env_var. */
|
|
444
|
+
const KEY_VAR = "BRAIN_GATEWAY_KEY"
|
|
445
|
+
|
|
446
|
+
const vibeHome = (home: string, env: Record<string, string | undefined>) => dirFromEnv(env, "VIBE_HOME", join(home, ".vibe"))
|
|
447
|
+
|
|
448
|
+
const vibeTarget = (key: string): TomlTarget => {
|
|
449
|
+
const [head, ...rest] = key.split("/")
|
|
450
|
+
if (head === "providers" && rest.length) return { kind: "entry", array: "providers", fields: ["name"], match: rest.join("/") }
|
|
451
|
+
if (head === "models" && rest.length) return { kind: "entry", array: "models", fields: ["alias", "name"], match: rest.join("/"), mapForm: true }
|
|
452
|
+
return { kind: "key", table: [], key }
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export const vibeToml = tomlCodec(vibeTarget, [
|
|
456
|
+
["active_model", ALIAS],
|
|
457
|
+
[`providers/${PROVIDER}`, { name: PROVIDER, api_base: "https://gateway.example/flash/v1", api_key_env_var: KEY_VAR, extra_headers: { [EDIT_HEADER]: "on" } }],
|
|
458
|
+
[`models/${ALIAS}`, { name: "model-id", provider: PROVIDER, alias: ALIAS, display_name: "100xprompt Pro" }],
|
|
459
|
+
])
|
|
460
|
+
|
|
461
|
+
export const connectors: Connector[] = [
|
|
462
|
+
{
|
|
463
|
+
id: "mistral-vibe",
|
|
464
|
+
label: "Mistral Vibe",
|
|
465
|
+
file: (home, env) => join(vibeHome(home, env), "config.toml"),
|
|
466
|
+
probe: [".vibe"],
|
|
467
|
+
authHeader: "authorization",
|
|
468
|
+
edits: (home, s, env) => [
|
|
469
|
+
{
|
|
470
|
+
path: join(vibeHome(home, env), "config.toml"),
|
|
471
|
+
format: "custom",
|
|
472
|
+
codec: vibeToml,
|
|
473
|
+
values: {
|
|
474
|
+
active_model: ALIAS,
|
|
475
|
+
// Tables last: Vibe rewrites the file with tomli_w when it saves a setting, which puts them last, so the entry
|
|
476
|
+
// still reads back the same after that.
|
|
477
|
+
[`providers/${PROVIDER}`]: {
|
|
478
|
+
name: PROVIDER,
|
|
479
|
+
api_base: `${routeUrl(s)}/v1`,
|
|
480
|
+
api_key_env_var: KEY_VAR,
|
|
481
|
+
api_style: "openai",
|
|
482
|
+
backend: "generic",
|
|
483
|
+
...(s.contextEdit ? { extra_headers: { [EDIT_HEADER]: "on" } } : {}),
|
|
484
|
+
},
|
|
485
|
+
[`models/${ALIAS}`]: { name: s.model, provider: PROVIDER, alias: ALIAS, display_name: modelName(s.model) },
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
{ path: join(vibeHome(home, env), ".env"), format: "dotenv", holdsKey: true, secretKeys: [KEY_VAR], values: { [KEY_VAR]: readKey(home) } },
|
|
489
|
+
],
|
|
490
|
+
notes: [
|
|
491
|
+
"Mistral Vibe can read a key only from an environment variable, which it loads from ~/.vibe/.env at start: the key is kept there, so that file is now readable by you only (0600).",
|
|
492
|
+
"A project's own .vibe/config.toml that sets active_model still picks its model in that project; /model switches back and forth.",
|
|
493
|
+
],
|
|
494
|
+
},
|
|
495
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Gateway connector for Muse Code (Meta's terminal coding agent, `muse`): NOT BUILT, deliberately. Checked against Muse Code
|
|
2
|
+
// 0.2.1 (0.2.1-R1215.1, build b3170a534f), the version brain's session adapter and test/fixtures/native-corpus/muse were
|
|
3
|
+
// captured from. Muse ships as a Linux x86_64 binary only and is not installed here (wire/drivers/f.ts), so nothing below
|
|
4
|
+
// could be run; the evidence is Meta's docs and the one known working custom-endpoint setup.
|
|
5
|
+
//
|
|
6
|
+
// Why brain cannot point Muse at the 100xprompt gateway by editing a file:
|
|
7
|
+
// 1. Muse only talks to Meta's own API shape. Its documented settings (~/.config/muse/settings.json, schema_version 1:
|
|
8
|
+
// https://dev.meta.ai/docs/muse-code/configuration) name no provider, endpoint or key option; sign-in is a Meta
|
|
9
|
+
// account or META_API_KEY (https://dev.meta.ai/docs/muse-code). The 0.2.1 binary enables only its `meta` and `echo`
|
|
10
|
+
// providers (muse-code-openrouter README, below).
|
|
11
|
+
// 2. Its undocumented `endpoint_transport: {base_url, auth}` setting (and the `--base-url` flag) does move the `meta`
|
|
12
|
+
// provider, but before any inference Muse fetches Meta's proprietary model catalog (`/muse-code/models`), then sends
|
|
13
|
+
// Responses requests whose stream events must each carry `sequence_number`, for `meta/muse*` model ids listed in its
|
|
14
|
+
// `model_catalog`. The gateway serves none of that catalog (it answers /v1/models with 403 and has no /muse-code
|
|
15
|
+
// route) and serves gemini-* models. Source: muse-code-openrouter 0.3.2, the adapter session-migrate used to capture
|
|
16
|
+
// the fixture through a loopback (README "Why an adapter is needed"; src/muse_code_openrouter/install.py:240-253
|
|
17
|
+
// writes provider/model/endpoint_transport {base_url: http://127.0.0.1:<port>/v1, auth: "none"}/model_catalog;
|
|
18
|
+
// proxy.py:218-231 serves /muse-code/models and /responses, :200-203 adds sequence_number), and
|
|
19
|
+
// xhluca/session-migrate scripts/native-corpus/capture-muse.py (`muse exec --provider meta --base-url ...`).
|
|
20
|
+
// 3. With `auth: "none"` (the only form that setup shows working) Muse sends no key at all; how Muse would send a key to a
|
|
21
|
+
// moved endpoint is undocumented, so the gateway key could not be delivered from a file either.
|
|
22
|
+
// Reaching the gateway would take a local adapter process translating Muse's catalog and stream to the gateway (as
|
|
23
|
+
// muse-code-openrouter does for OpenRouter), which is not a config edit; brain does not run one. Muse is still supported
|
|
24
|
+
// for sessions (src/adapters/muse).
|
|
25
|
+
import type { Connector } from "../common.ts"
|
|
26
|
+
|
|
27
|
+
export const connectors: Connector[] = []
|