@100xprompt/brain 0.1.1 → 0.1.3
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 +3 -0
- package/package.json +1 -1
- package/src/dashboard/report.ts +16 -2
- package/src/gateway/common.ts +56 -4
- package/src/gateway/connect.ts +10 -5
- package/src/gateway/connectors/aider.ts +3 -5
- package/src/gateway/connectors/cline-sdk.ts +2 -5
- package/src/gateway/connectors/continue.ts +5 -6
- package/src/gateway/connectors/crush.ts +2 -2
- package/src/gateway/connectors/factory-droid.ts +6 -1
- package/src/gateway/connectors/goose.ts +4 -4
- package/src/gateway/connectors/hermes.ts +6 -2
- package/src/gateway/connectors/kimi-code.ts +2 -2
- package/src/gateway/connectors/letta-code.ts +4 -7
- package/src/gateway/connectors/openhands.ts +9 -4
- package/src/gateway/connectors/qwen-code.ts +16 -2
- package/src/gateway/connectors/vscode-copilot.ts +4 -6
package/README.md
CHANGED
|
@@ -96,6 +96,9 @@ brain gateway logout
|
|
|
96
96
|
- Connecting changes which model the tool uses: it runs on 100xprompt Pro by default (`--model flash` for 100xprompt
|
|
97
97
|
Flash), billed to your 100xprompt account. Context editing is on by default; `--no-context-edit` turns it off and is
|
|
98
98
|
remembered for later connects until `--context-edit`.
|
|
99
|
+
- `--model auto` connects the tool to 100xprompt Auto (the gateway's `/auto` route): each kind of work (everyday asks,
|
|
100
|
+
code, reasoning) moves to a cheaper model only when your company's own test of its logs (in the Savings app) proved it
|
|
101
|
+
no worse, and stays on 100xprompt Pro until then. `--model pro` or `--model flash` switches back.
|
|
99
102
|
- Before changing anything, `connect` checks that the gateway accepts your key the way that tool sends it (a free check
|
|
100
103
|
that generates nothing). If your account's quota is used up, it still connects and tells you, in the gateway's words.
|
|
101
104
|
- Only the settings brain owns are written; the value each had before is recorded and put back by `disconnect`. The key
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@100xprompt/brain",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "One working session, any AI coding tool: move sessions between Claude Code, Codex, Cursor, Gemini CLI, OpenCode and more, keep what projects learned, spend fewer tokens. Runs on Bun. By 100xprompt.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nipurn Agarwal",
|
package/src/dashboard/report.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { join } from "node:path"
|
|
|
8
8
|
import type { Reader } from "../adapters/types.ts"
|
|
9
9
|
import { toolLabel } from "../brief.ts"
|
|
10
10
|
import { DEFAULT_MODEL, DEFAULT_ROUTE, type Env, type GatewaySettings, modelName, statePath } from "../gateway/common.ts"
|
|
11
|
-
import { CONNECTORS, hasKey, installedHere } from "../gateway/connect.ts"
|
|
11
|
+
import { CONNECTORS, cliAuthPath, hasKey, installedHere } from "../gateway/connect.ts"
|
|
12
12
|
import { HOOK_TOOLS, hookInstalled } from "../hooks.ts"
|
|
13
13
|
import { MCP_TOOLS, statusMcp } from "../memory/mcp-install.ts"
|
|
14
14
|
import { chittaCandidates, memoryDbPath, openMemory } from "../memory/store.ts"
|
|
@@ -182,6 +182,20 @@ export async function harnesses(home: string, counts: Record<string, ToolCount>
|
|
|
182
182
|
return false
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* The 100xprompt CLI is the gateway's own client: there is nothing for brain to connect, and it is on the gateway
|
|
187
|
+
* whenever it is signed in (its auth.json holds a key; only its presence is looked at). Signed out, it can be
|
|
188
|
+
* (`100xprompt auth login`). Any other tool: undefined, decided by brain's connectors.
|
|
189
|
+
*/
|
|
190
|
+
const ownClient = (id: string, h: string, e: Env): Harness["gateway"] | undefined => {
|
|
191
|
+
if (id !== "100xprompt") return undefined
|
|
192
|
+
try {
|
|
193
|
+
const k = JSON.parse(readFileSync(cliAuthPath(h, e), "utf8"))?.["100xprompt"]?.key
|
|
194
|
+
return typeof k === "string" && k.trim() ? "connected" : "available"
|
|
195
|
+
} catch {
|
|
196
|
+
return "available"
|
|
197
|
+
}
|
|
198
|
+
}
|
|
185
199
|
const known = new Map<string, { label: string; present: boolean }>()
|
|
186
200
|
const add = (id: string, label: string, present: boolean) => {
|
|
187
201
|
const k = known.get(id)
|
|
@@ -207,7 +221,7 @@ export async function harnesses(home: string, counts: Record<string, ToolCount>
|
|
|
207
221
|
label: clip(k.label, 80),
|
|
208
222
|
sessions: counts?.[id] ? counts[id]!.sessions : null,
|
|
209
223
|
lastSessionAt: counts?.[id]?.lastSessionAt ?? null,
|
|
210
|
-
gateway: connected[id] ? "connected" : connectorIds.has(id) ? "available" : "unsupported",
|
|
224
|
+
gateway: connected[id] ? "connected" : (ownClient(id, home, env) ?? (connectorIds.has(id) ? "available" : "unsupported")),
|
|
211
225
|
mcp: mcpIds.has(id) ? mcpInstalled.get(id) === true : null,
|
|
212
226
|
hooks: HOOK_TOOLS.includes(id) ? hookOn(id) : null,
|
|
213
227
|
}))
|
package/src/gateway/common.ts
CHANGED
|
@@ -12,15 +12,66 @@ export const DEFAULT_ROUTE = "flash"
|
|
|
12
12
|
export const DEFAULT_MODEL = "gemini-3.8-flash"
|
|
13
13
|
export const EDIT_HEADER = "x-100x-context-edit"
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* `--model auto`: the gateway's auto-routing (the router's docs/routing-contract.md, "The /auto route"). Its route serves
|
|
17
|
+
* only this model id, and this id only on its route: per kind of work (everyday asks, code, reasoning) the gateway serves
|
|
18
|
+
* a cheaper model where the company's own test in the Savings app proved it no worse, and 100xprompt Pro everywhere else.
|
|
19
|
+
*/
|
|
20
|
+
export const AUTO_ROUTE = "auto"
|
|
21
|
+
export const AUTO_MODEL = "100xprompt-auto"
|
|
22
|
+
|
|
15
23
|
/**
|
|
16
24
|
* What 100xprompt calls the models brain may connect a tool to, as the 100xprompt CLI shows them: tools are given the
|
|
17
25
|
* gateway's model id (that is what the gateway serves), and everything brain prints uses the product name.
|
|
18
26
|
*/
|
|
19
|
-
export const MODEL_NAMES: Record<string, string> = {
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
export const MODEL_NAMES: Record<string, string> = {
|
|
28
|
+
"gemini-3.8-flash": "100xprompt Pro",
|
|
29
|
+
"gemini-3.7-flash": "100xprompt Flash",
|
|
30
|
+
"gemini-3.6-flash": "100xprompt Flash",
|
|
31
|
+
"gemini-3.5-flash": "100xprompt Flash",
|
|
32
|
+
[AUTO_MODEL]: "100xprompt Auto",
|
|
33
|
+
}
|
|
34
|
+
/** `--model pro` / `--model flash` / `--model auto`: the product names, turned into the gateway's model ids. */
|
|
35
|
+
export const MODEL_ALIASES: Record<string, string> = { pro: "gemini-3.8-flash", flash: "gemini-3.7-flash", auto: AUTO_MODEL }
|
|
22
36
|
export const modelName = (id: string) => MODEL_NAMES[id] ?? id
|
|
23
37
|
export const modelId = (given: string) => MODEL_ALIASES[given.trim().toLowerCase()] ?? given.trim()
|
|
38
|
+
/**
|
|
39
|
+
* Whether a connect uses auto-routing. No tool knows this id: a connector whose tool sizes its context window (or picks
|
|
40
|
+
* its capabilities) from the model id it is given tells it, for auto, what every model auto may serve takes (limitsFor).
|
|
41
|
+
*/
|
|
42
|
+
export const isAuto = (s: Pick<GatewaySettings, "model">) => s.model === AUTO_MODEL
|
|
43
|
+
/** What the Gemini Flash models behind 100xprompt Pro and Flash take: 1,048,576 tokens in, 65,536 out. */
|
|
44
|
+
export const GEMINI_LIMITS = { context: 1_048_576, output: 65_536 } as const
|
|
45
|
+
/**
|
|
46
|
+
* What every model auto-routing may serve takes (the contract's table: the Gemini Flash models and DeepSeek V4 Flash): the
|
|
47
|
+
* smaller window, DeepSeek V4 Flash's 1,000,000 tokens, and Gemini's 65,536 out (DeepSeek allows more). Both take text and
|
|
48
|
+
* images; audio, video and PDF only Gemini, so a tool told what it may send gets images only.
|
|
49
|
+
*/
|
|
50
|
+
export const AUTO_LIMITS = { context: 1_000_000, output: 65_536 } as const
|
|
51
|
+
/** The context window and output limit a connector tells its tool, for the model it connects. */
|
|
52
|
+
export const limitsFor = (s: Pick<GatewaySettings, "model">) => (isAuto(s) ? AUTO_LIMITS : GEMINI_LIMITS)
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The route and model a connect uses, from the flags given (undefined: not given) and the settings stored by the last
|
|
56
|
+
* connect. The auto model and the auto route go together: `--model auto` picks the auto route (a different --route with
|
|
57
|
+
* it is refused), `--route auto` picks the auto model, and another model or route after an auto connect goes back to the
|
|
58
|
+
* default route (flash) or model (100xprompt Pro).
|
|
59
|
+
*/
|
|
60
|
+
export function routeAndModel(given: { route?: string; model?: string }, stored: { route?: string; model?: string } = {}): { route: string; model: string } | { why: string } {
|
|
61
|
+
const model = given.model !== undefined ? modelId(given.model) : undefined
|
|
62
|
+
const route = given.route
|
|
63
|
+
if (model === AUTO_MODEL) {
|
|
64
|
+
if (route !== undefined && route !== AUTO_ROUTE) return { why: `--model auto uses the gateway's ${AUTO_ROUTE} route, so it cannot be combined with --route ${route}; leave out --route` }
|
|
65
|
+
return { route: AUTO_ROUTE, model }
|
|
66
|
+
}
|
|
67
|
+
if (route === AUTO_ROUTE) {
|
|
68
|
+
if (model !== undefined) return { why: `the ${AUTO_ROUTE} route chooses the model itself, so it cannot be combined with --model ${given.model!.trim()}; use --model auto` }
|
|
69
|
+
return { route: AUTO_ROUTE, model: AUTO_MODEL }
|
|
70
|
+
}
|
|
71
|
+
const wasAuto = stored.model === AUTO_MODEL || stored.route === AUTO_ROUTE
|
|
72
|
+
if (route === undefined && model === undefined) return wasAuto ? { route: AUTO_ROUTE, model: AUTO_MODEL } : { route: stored.route ?? DEFAULT_ROUTE, model: stored.model ?? DEFAULT_MODEL }
|
|
73
|
+
return { route: route ?? (wasAuto ? DEFAULT_ROUTE : (stored.route ?? DEFAULT_ROUTE)), model: model ?? (wasAuto ? DEFAULT_MODEL : (stored.model ?? DEFAULT_MODEL)) }
|
|
74
|
+
}
|
|
24
75
|
|
|
25
76
|
export interface GatewaySettings {
|
|
26
77
|
url: string
|
|
@@ -46,7 +97,8 @@ export function readKey(home: string): string {
|
|
|
46
97
|
}
|
|
47
98
|
|
|
48
99
|
/**
|
|
49
|
-
* The gateway route's base address, e.g. https://proxy.100xprompt.com/flash
|
|
100
|
+
* The gateway route's base address, e.g. https://proxy.100xprompt.com/flash (or /auto, which takes the same paths). Under
|
|
101
|
+
* it the gateway serves, for every model:
|
|
50
102
|
* /v1/chat/completions OpenAI Chat Completions (Authorization: Bearer <key>)
|
|
51
103
|
* /v1/responses OpenAI Responses (Authorization: Bearer <key>)
|
|
52
104
|
* /v1/messages Anthropic Messages (x-api-key: <key>, or Authorization: Bearer)
|
package/src/gateway/connect.ts
CHANGED
|
@@ -34,9 +34,9 @@ import {
|
|
|
34
34
|
type Json,
|
|
35
35
|
keyPath,
|
|
36
36
|
MODEL_NAMES,
|
|
37
|
-
modelId,
|
|
38
37
|
modelName,
|
|
39
38
|
readKey,
|
|
39
|
+
routeAndModel,
|
|
40
40
|
routeUrl,
|
|
41
41
|
setAt,
|
|
42
42
|
shellQuote,
|
|
@@ -46,6 +46,8 @@ import { EXTRA_CONNECTORS } from "./connectors/index.ts"
|
|
|
46
46
|
import { askHidden, browserSignIn, type SignInDeps } from "./signin.ts"
|
|
47
47
|
|
|
48
48
|
export {
|
|
49
|
+
AUTO_MODEL,
|
|
50
|
+
AUTO_ROUTE,
|
|
49
51
|
DEFAULT_MODEL,
|
|
50
52
|
DEFAULT_ROUTE,
|
|
51
53
|
DEFAULT_URL,
|
|
@@ -593,15 +595,18 @@ export async function connect(
|
|
|
593
595
|
if (!c) return { ok: false, lines: [`unknown tool "${toolId}"; \`brain gateway tools\` lists the ones that can be connected`] }
|
|
594
596
|
if (!hasKey(home) && !o.dryRun) return { ok: false, lines: ["not signed in to the gateway yet: run `brain gateway login`"] }
|
|
595
597
|
const st = loadState(home)
|
|
598
|
+
// --model auto goes with the auto route (and back to flash after it): common.ts routeAndModel.
|
|
599
|
+
const rm = routeAndModel({ route: o.settings?.route, model: o.settings?.model }, st.settings ?? {})
|
|
600
|
+
if ("why" in rm) return { ok: false, lines: [rm.why] }
|
|
596
601
|
const s: GatewaySettings = {
|
|
597
602
|
url: o.settings?.url ?? st.settings?.url ?? DEFAULT_URL,
|
|
598
|
-
route:
|
|
599
|
-
model:
|
|
603
|
+
route: rm.route,
|
|
604
|
+
model: rm.model,
|
|
600
605
|
contextEdit: o.settings?.contextEdit ?? st.settings?.contextEdit ?? true,
|
|
601
606
|
}
|
|
602
607
|
if (!/^https?:\/\/[^\s]+$/.test(s.url) || (/^http:/.test(s.url) && !/^http:\/\/(127\.0\.0\.1|localhost)(:|\/|$)/.test(s.url))) return { ok: false, lines: ["--url must be https:// (http:// only for a gateway on this machine)"] }
|
|
603
608
|
if (!/^[a-z0-9-]{1,40}$/.test(s.route)) return { ok: false, lines: ["--route must be a gateway route name such as flash or pro"] }
|
|
604
|
-
if (!/^[\w.:/-]{1,80}$/.test(s.model)) return { ok: false, lines: ["--model must be pro, flash or a plain model name"] }
|
|
609
|
+
if (!/^[\w.:/-]{1,80}$/.test(s.model)) return { ok: false, lines: ["--model must be pro, flash, auto or a plain model name"] }
|
|
605
610
|
const main = c.file(home, env)
|
|
606
611
|
if (!installedHere(c, home, env)) return { ok: false, lines: [`${c.label} has not been run on this machine (nothing at ${main}); run it once, then connect it`] }
|
|
607
612
|
let limit: string | undefined
|
|
@@ -748,7 +753,7 @@ const USAGE = [
|
|
|
748
753
|
"brain gateway login [--no-browser] sign in to 100xprompt in your browser, as `100xprompt auth login` does",
|
|
749
754
|
"brain gateway login --key-env VAR | --from-cli take the key from VAR (scripts, CI) or from the 100xprompt CLI's sign-in",
|
|
750
755
|
"brain gateway logout forget the key",
|
|
751
|
-
"brain gateway connect --tool <id> [--model pro|flash] [--route flash] [--url https://...] [--no-context-edit | --context-edit] [--dry-run] [--no-probe]",
|
|
756
|
+
"brain gateway connect --tool <id> [--model pro|flash|auto] [--route flash] [--url https://...] [--no-context-edit | --context-edit] [--dry-run] [--no-probe]",
|
|
752
757
|
"brain gateway disconnect --tool <id> [--dry-run]",
|
|
753
758
|
"brain gateway status | tools",
|
|
754
759
|
]
|
|
@@ -29,15 +29,12 @@
|
|
|
29
29
|
// Docs: https://aider.chat/docs/config/adv-model-settings.html (extra_params, extra_headers, metadata file),
|
|
30
30
|
// https://aider.chat/docs/llms/openai-compat.html (openai/<model> against an OpenAI-compatible API).
|
|
31
31
|
import { join } from "node:path"
|
|
32
|
-
import { type Codec, type Connector, EDIT_HEADER, type Json, readKey, routeUrl, yamlTopLevel } from "../common.ts"
|
|
32
|
+
import { type Codec, type Connector, EDIT_HEADER, type Json, limitsFor, readKey, routeUrl, yamlTopLevel } from "../common.ts"
|
|
33
33
|
|
|
34
34
|
/** The one key of brain's own entry in the settings list and in the metadata object. */
|
|
35
35
|
export const AIDER_ENTRY = "brain-gateway"
|
|
36
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
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
38
|
|
|
42
39
|
const esc = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
43
40
|
|
|
@@ -337,7 +334,8 @@ export const connectors: Connector[] = [
|
|
|
337
334
|
path: join(home, ".aider.model.metadata.json"),
|
|
338
335
|
format: "custom",
|
|
339
336
|
codec: modelMetadataJson,
|
|
340
|
-
|
|
337
|
+
// The Gemini Flash models' window and output limit, or for 100xprompt Auto what every model it may serve takes.
|
|
338
|
+
values: { [AIDER_ENTRY]: { [model]: { max_input_tokens: limitsFor(s).context, max_output_tokens: limitsFor(s).output, max_tokens: limitsFor(s).output, litellm_provider: "openai", mode: "chat" } } },
|
|
341
339
|
},
|
|
342
340
|
{
|
|
343
341
|
path: join(home, ".aider.conf.yml"),
|
|
@@ -26,12 +26,9 @@
|
|
|
26
26
|
// not followed; CLINE_DIR and CLINE_DATA_DIR are, when they exist. The VS Code extension (id `cline`, see cline.ts) shares
|
|
27
27
|
// ~/.cline/data but chooses its provider from its own globalState.json, so this connector does not change it.
|
|
28
28
|
import { join } from "node:path"
|
|
29
|
-
import { type Connector, dirFromEnv, EDIT_HEADER, type Env, modelName, readKey, routeUrl } from "../common.ts"
|
|
29
|
+
import { type Connector, dirFromEnv, EDIT_HEADER, type Env, limitsFor, modelName, readKey, routeUrl } from "../common.ts"
|
|
30
30
|
|
|
31
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
32
|
/** Cline requires an ISO date here; a fixed one keeps edits() free of side effects (Cline sets its own when it saves). */
|
|
36
33
|
const UPDATED_AT = "2026-01-01T00:00:00.000Z"
|
|
37
34
|
|
|
@@ -56,7 +53,7 @@ export const connectors: Connector[] = [
|
|
|
56
53
|
version: 1,
|
|
57
54
|
[`providers/${ID}`]: {
|
|
58
55
|
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:
|
|
56
|
+
models: { [s.model]: { id: s.model, name: modelName(s.model), contextWindow: limitsFor(s).context, maxTokens: limitsFor(s).output, capabilities: ["tools", "streaming", "images"] } },
|
|
60
57
|
},
|
|
61
58
|
},
|
|
62
59
|
},
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
// apiKey: ${{ secrets.BRAIN_GATEWAY_KEY }}, roles chat/edit/apply/summarize,
|
|
31
31
|
// capabilities tool_use and image_input (Continue only guesses tool support for provider openai from
|
|
32
32
|
// gpt-/o- model names, toolSupport.ts ~277661, so without it Agent mode would be off),
|
|
33
|
-
// contextLength 1048576 (an unknown model gets 32768, ~238017
|
|
33
|
+
// contextLength 1048576 (an unknown model gets 32768, ~238017; for 100xprompt Auto the
|
|
34
|
+
// smallest window auto may serve, common.ts limitsFor), and the context-edit header.
|
|
34
35
|
// First in the list makes it the default: the extension and the CLI take the first chat model unless
|
|
35
36
|
// one was picked before (extension rectifySelectedModelsFromGlobalContext ~798703; CLI
|
|
36
37
|
// services/ModelService.ts:53-120 and util/modelPersistence.ts, `cliSelectedModel`).
|
|
@@ -42,7 +43,7 @@
|
|
|
42
43
|
// stay, and removing the entry gives back the exact bytes, including an empty `models: []` or a list that was not there.
|
|
43
44
|
import { existsSync, readFileSync } from "node:fs"
|
|
44
45
|
import { isAbsolute, join, resolve } from "node:path"
|
|
45
|
-
import { type Codec, type Connector, EDIT_HEADER, type Env, type GatewaySettings, type Json, modelName, readKey, routeUrl, yamlTopLevel } from "../common.ts"
|
|
46
|
+
import { type Codec, type Connector, EDIT_HEADER, type Env, type GatewaySettings, type Json, limitsFor, modelName, readKey, routeUrl, yamlTopLevel } from "../common.ts"
|
|
46
47
|
|
|
47
48
|
/** The name the key is kept under in the tool's .env, and that its config refers to. */
|
|
48
49
|
export const SECRET = "BRAIN_GATEWAY_KEY"
|
|
@@ -50,8 +51,6 @@ export const SECRET = "BRAIN_GATEWAY_KEY"
|
|
|
50
51
|
const OWN_CONFIG = "100xprompt gateway"
|
|
51
52
|
/** The codec key of brain's entry in the models list (the entry itself is found by its apiKey, so a model switch keeps it). */
|
|
52
53
|
export const ENTRY_KEY = "models/100xprompt gateway"
|
|
53
|
-
/** Gemini models' context window; Continue assumes 32768 for a model it does not know. */
|
|
54
|
-
const CONTEXT = 1048576
|
|
55
54
|
|
|
56
55
|
const same = (a: unknown, b: unknown) => JSON.stringify(a) === JSON.stringify(b)
|
|
57
56
|
const isPlainObject = (v: unknown): v is Json => !!v && typeof v === "object" && !Array.isArray(v)
|
|
@@ -521,7 +520,7 @@ const headers = (s: GatewaySettings) => (s.contextEdit ? { requestOptions: { hea
|
|
|
521
520
|
|
|
522
521
|
/** The models entry in config.json form (older Continue, PearAI): `title`, the key by env name. */
|
|
523
522
|
export function jsonEntry(s: GatewaySettings, extra: Json = {}): Json {
|
|
524
|
-
return { title: modelName(s.model), provider: "openai", model: s.model, apiBase: `${routeUrl(s)}/v1`, apiKey: SECRET, contextLength:
|
|
523
|
+
return { title: modelName(s.model), provider: "openai", model: s.model, apiBase: `${routeUrl(s)}/v1`, apiKey: SECRET, contextLength: limitsFor(s).context, ...extra, ...headers(s) }
|
|
525
524
|
}
|
|
526
525
|
|
|
527
526
|
/** The .env edit both Continue and PearAI read their secret from. */
|
|
@@ -557,7 +556,7 @@ export const connectors: Connector[] = [
|
|
|
557
556
|
apiKey: `\${{ secrets.${SECRET} }}`,
|
|
558
557
|
roles: ["chat", "edit", "apply", "summarize"],
|
|
559
558
|
capabilities: ["tool_use", "image_input"],
|
|
560
|
-
defaultCompletionOptions: { contextLength:
|
|
559
|
+
defaultCompletionOptions: { contextLength: limitsFor(s).context },
|
|
561
560
|
...headers(s),
|
|
562
561
|
}
|
|
563
562
|
return [
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
// A model list is given, so Crush does not ask the gateway for /v1/models (403 there).
|
|
31
31
|
import { existsSync } from "node:fs"
|
|
32
32
|
import { isAbsolute, join, resolve } from "node:path"
|
|
33
|
-
import { type Codec, type Connector, EDIT_HEADER, type Env, getAt, type Json, keyPath, modelName, routeUrl, shellQuote } from "../common.ts"
|
|
33
|
+
import { type Codec, type Connector, EDIT_HEADER, type Env, getAt, type Json, keyPath, limitsFor, modelName, routeUrl, shellQuote } from "../common.ts"
|
|
34
34
|
|
|
35
35
|
/** Crush's data file, resolved as Crush does; the env overrides describe the person's real home, so they count only there. */
|
|
36
36
|
export function crushDataFile(home: string, env: Env): string {
|
|
@@ -297,7 +297,7 @@ export const connectors: Connector[] = [
|
|
|
297
297
|
base_url: `${routeUrl(s)}/v1`,
|
|
298
298
|
api_key: `$(cat ${shellQuote(keyPath(home))})`,
|
|
299
299
|
...(s.contextEdit ? { extra_headers: { [EDIT_HEADER]: "on" } } : {}),
|
|
300
|
-
models: [{ id: s.model, name: modelName(s.model), context_window:
|
|
300
|
+
models: [{ id: s.model, name: modelName(s.model), context_window: limitsFor(s).context, default_max_tokens: limitsFor(s).output, capabilities: { vision: true } }],
|
|
301
301
|
},
|
|
302
302
|
"models/large": pick,
|
|
303
303
|
"models/small": pick,
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
// stdout."), run with shell:true, cached for 5 minutes (apiKeyHelperTtlMs, FACTORY_API_KEY_HELPER_TTL_MS). So the key
|
|
15
15
|
// is read from the brain's key file at run time, never copied, and a new sign-in reaches Droid without a reconnect.
|
|
16
16
|
// - `extraHeaders` is sent with every request (case-insensitively unique names), so the context-edit header can go.
|
|
17
|
+
// - Context window and output limit: Droid infers a custom model's from its model id (the built-in model whose
|
|
18
|
+
// matchPatterns match it: gemini-3.8-flash gets Gemini 3.8 Flash's 1M in and 64K out), and an entry's maxContextLimit
|
|
19
|
+
// and maxOutputTokens take precedence (k4e and ev in the binary). 100xprompt Auto's id matches nothing, so for auto they
|
|
20
|
+
// are given: what every model auto may serve takes (common.ts limitsFor). Images stay on (noImageSupport unset).
|
|
17
21
|
// - A model's id is `custom:<displayName with spaces as ->-<n>` unless the entry names its own `id`; this connector gives
|
|
18
22
|
// its entry the id `custom:brain-gateway`, so the default stays put whatever else is in the list.
|
|
19
23
|
// - The default model is `sessionDefaultSettings.model` (older top-level `model` is folded into it on read and only
|
|
@@ -27,7 +31,7 @@
|
|
|
27
31
|
// - Checked for real (scripts/gateway-real/factory-droid.ts): `droid exec` with no -m, no Factory login, sends its
|
|
28
32
|
// request to <route>/v1/messages with x-api-key from the helper, the context-edit header and the gateway's model.
|
|
29
33
|
import { join } from "node:path"
|
|
30
|
-
import { type Codec, type Connector, dirFromEnv, EDIT_HEADER, type Env, type Json, keyPath, modelName, routeUrl, shellQuote } from "../common.ts"
|
|
34
|
+
import { type Codec, type Connector, dirFromEnv, EDIT_HEADER, type Env, isAuto, type Json, keyPath, limitsFor, modelName, routeUrl, shellQuote } from "../common.ts"
|
|
31
35
|
|
|
32
36
|
/** The id of the connector's entry in customModels, and the default model value that selects it. */
|
|
33
37
|
export const DROID_MODEL_ID = "custom:brain-gateway"
|
|
@@ -295,6 +299,7 @@ export const connectors: Connector[] = [
|
|
|
295
299
|
provider: "anthropic",
|
|
296
300
|
apiKeyHelper: `cat ${shellQuote(keyPath(home))}`,
|
|
297
301
|
...(s.contextEdit ? { extraHeaders: { [EDIT_HEADER]: "on" } } : {}),
|
|
302
|
+
...(isAuto(s) ? { maxContextLimit: limitsFor(s).context, maxOutputTokens: limitsFor(s).output } : {}),
|
|
298
303
|
},
|
|
299
304
|
"sessionDefaultSettings/model": DROID_MODEL_ID,
|
|
300
305
|
},
|
|
@@ -22,12 +22,10 @@
|
|
|
22
22
|
// The binary's strings list the same field names (`strings goose | grep api_key_env`), and the real-tool check
|
|
23
23
|
// (scripts/gateway-real/goose.ts) runs this exact setup.
|
|
24
24
|
import { isAbsolute, join, resolve } from "node:path"
|
|
25
|
-
import { type Connector, EDIT_HEADER, type Env, keyPath, modelName, routeUrl, yamlTopLevel } from "../common.ts"
|
|
25
|
+
import { type Connector, EDIT_HEADER, type Env, keyPath, limitsFor, modelName, routeUrl, yamlTopLevel } from "../common.ts"
|
|
26
26
|
|
|
27
27
|
/** The provider id (the custom provider's file name and `name`, and GOOSE_PROVIDER), as Codex and OpenCode name it. */
|
|
28
28
|
export const GOOSE_PROVIDER = "brain-gateway"
|
|
29
|
-
/** The context window Goose is told (it compacts the conversation near it): the Gemini Flash models' 1,048,576 tokens. */
|
|
30
|
-
const CONTEXT_LIMIT = 1_048_576
|
|
31
29
|
|
|
32
30
|
/** Goose's config folder, found the way Goose finds it. XDG_CONFIG_HOME describes the real home, so it counts only there. */
|
|
33
31
|
export function gooseDir(home: string, env: Env): string {
|
|
@@ -56,7 +54,9 @@ export const connectors: Connector[] = [
|
|
|
56
54
|
display_name: "100xprompt gateway",
|
|
57
55
|
description: `${modelName(s.model)} through the 100xprompt gateway (set up by brain gateway connect --tool goose)`,
|
|
58
56
|
base_url: `${routeUrl(s)}/v1`,
|
|
59
|
-
|
|
57
|
+
// The window Goose is told (it compacts the conversation near it): the Gemini Flash models' 1,048,576 tokens, or
|
|
58
|
+
// for 100xprompt Auto the smallest window it may serve (common.ts limitsFor).
|
|
59
|
+
models: [{ name: s.model, context_limit: limitsFor(s).context }],
|
|
60
60
|
...(s.contextEdit ? { headers: { [EDIT_HEADER]: "on" } } : {}),
|
|
61
61
|
supports_streaming: true,
|
|
62
62
|
requires_auth: true,
|
|
@@ -19,7 +19,10 @@
|
|
|
19
19
|
// - `extra_headers` on the provider entry is sent with every request on the OpenAI wire (runtime_provider.py
|
|
20
20
|
// _lift_extra_headers), so the context-edit header can go.
|
|
21
21
|
// - The gateway's /v1/models answers 403; Hermes then takes the context window from its catalog ("gemini" → 1M,
|
|
22
|
-
// agent/model_metadata.py), so nothing else needs setting.
|
|
22
|
+
// agent/model_metadata.py), so nothing else needs setting for a Gemini id. 100xprompt Auto's id is in no catalog (Hermes
|
|
23
|
+
// would assume 256,000: DEFAULT_FALLBACK_CONTEXT), so for auto the entry also gets `models: {<id>: {context_length}}`,
|
|
24
|
+
// the per-model override Hermes checks before any catalog (hermes_cli/config.py get_custom_provider_context_length,
|
|
25
|
+
// matched by the entry's base_url): what every model auto may serve takes (common.ts limitsFor).
|
|
23
26
|
// - $HERMES_HOME replaces ~/.hermes when set (a Hermes profile is its own HERMES_HOME).
|
|
24
27
|
// - Checked for real (scripts/gateway-real/hermes.ts): `hermes chat -q` sends its request to <route>/v1/chat/completions
|
|
25
28
|
// with Authorization: Bearer <key from .env>, the context-edit header and the gateway's model, even with the person's
|
|
@@ -27,7 +30,7 @@
|
|
|
27
30
|
// config.yaml is YAML with comments; the codec below edits `<section>/<entry>` lines in place and leaves every other line
|
|
28
31
|
// as it was.
|
|
29
32
|
import { join } from "node:path"
|
|
30
|
-
import { type Codec, type Connector, dirFromEnv, EDIT_HEADER, type Env, type Json, readKey, routeUrl, yamlTopLevel } from "../common.ts"
|
|
33
|
+
import { type Codec, type Connector, dirFromEnv, EDIT_HEADER, type Env, isAuto, type Json, limitsFor, readKey, routeUrl, yamlTopLevel } from "../common.ts"
|
|
31
34
|
|
|
32
35
|
export const HERMES_PROVIDER = "brain-gateway"
|
|
33
36
|
export const HERMES_KEY_VAR = "BRAIN_GATEWAY_API_KEY"
|
|
@@ -209,6 +212,7 @@ export const connectors: Connector[] = [
|
|
|
209
212
|
api_mode: "chat_completions",
|
|
210
213
|
default_model: s.model,
|
|
211
214
|
...(s.contextEdit ? { extra_headers: { [EDIT_HEADER]: "on" } } : {}),
|
|
215
|
+
...(isAuto(s) ? { models: { [s.model]: { context_length: limitsFor(s).context } } } : {}),
|
|
212
216
|
},
|
|
213
217
|
"model/provider": `custom:${HERMES_PROVIDER}`,
|
|
214
218
|
"model/default": s.model,
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
// back the exact bytes. Before any change the codec makes a trial edit and refuses a file where that would not land
|
|
27
27
|
// exactly on brain's entries (an inline `providers = {...}`, dotted keys, sub-tables of brain's tables, ...).
|
|
28
28
|
import { join } from "node:path"
|
|
29
|
-
import { type Codec, type Connector, dirFromEnv, EDIT_HEADER, type Json, modelName, readKey, routeUrl } from "../common.ts"
|
|
29
|
+
import { type Codec, type Connector, dirFromEnv, EDIT_HEADER, type Json, limitsFor, modelName, readKey, routeUrl } from "../common.ts"
|
|
30
30
|
|
|
31
31
|
export const kimiConfig = (home: string, env: Record<string, string | undefined>) => join(dirFromEnv(env, "KIMI_CODE_HOME", join(home, ".kimi-code")), "config.toml")
|
|
32
32
|
|
|
@@ -227,7 +227,7 @@ export const connectors: Connector[] = [
|
|
|
227
227
|
[`models/${PROVIDER}`]: {
|
|
228
228
|
provider: PROVIDER,
|
|
229
229
|
model: s.model,
|
|
230
|
-
max_context_size:
|
|
230
|
+
max_context_size: limitsFor(s).context,
|
|
231
231
|
capabilities: ["image_in", "tool_use"],
|
|
232
232
|
display_name: modelName(s.model),
|
|
233
233
|
},
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
// (src/backend/dev/pi-request-headers.ts).
|
|
34
34
|
import { existsSync } from "node:fs"
|
|
35
35
|
import { isAbsolute, join } from "node:path"
|
|
36
|
-
import { type Codec, type Connector, EDIT_HEADER, type Env, type GatewaySettings, type Json, keyPath, modelName, routeUrl } from "../common.ts"
|
|
36
|
+
import { type Codec, type Connector, EDIT_HEADER, type Env, type GatewaySettings, type Json, keyPath, limitsFor, modelName, routeUrl } from "../common.ts"
|
|
37
37
|
|
|
38
38
|
export const MOD_FILE = "brain-gateway.mjs"
|
|
39
39
|
const MARK = "// Written by `brain gateway connect --tool letta-code`"
|
|
@@ -136,18 +136,15 @@ export const lettaMod: Codec = {
|
|
|
136
136
|
},
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
/** What 100xprompt's models take (Gemini 3.x Flash: 1M tokens in, 64k out). */
|
|
140
|
-
const CONTEXT_WINDOW = 1_048_576
|
|
141
|
-
const MAX_TOKENS = 65_536
|
|
142
|
-
|
|
143
139
|
const modValues = (home: string, s: GatewaySettings): Record<string, unknown> => ({
|
|
144
140
|
provider: "brain-gateway",
|
|
145
141
|
name: "100xprompt gateway",
|
|
146
142
|
baseUrl: `${routeUrl(s)}/v1`,
|
|
147
143
|
model: s.model,
|
|
148
144
|
modelName: modelName(s.model),
|
|
149
|
-
|
|
150
|
-
|
|
145
|
+
// What the model takes (Gemini 3.x Flash: 1M tokens in, 64k out; for 100xprompt Auto, what every model it may serve takes).
|
|
146
|
+
contextWindow: limitsFor(s).context,
|
|
147
|
+
maxTokens: limitsFor(s).output,
|
|
151
148
|
keyFile: keyPath(home),
|
|
152
149
|
...(s.contextEdit ? { headers: { [EDIT_HEADER]: "on" } } : {}),
|
|
153
150
|
})
|
|
@@ -25,9 +25,14 @@
|
|
|
25
25
|
// (routing metadata for the previous endpoint; chat_options.py:83-85 sends it as extra_body). All of them come back
|
|
26
26
|
// on disconnect.
|
|
27
27
|
// - OpenHands has no named providers or model list (no profile is used by the CLI), so the LLM entry itself is switched.
|
|
28
|
+
// - With max_input_tokens and max_output_tokens null, the SDK looks the model up in LiteLLM's catalog, under the id and
|
|
29
|
+
// under the part after the last "/" (sdk/llm/utils/model_info.py get_litellm_model_info): gemini-3.8-flash is there
|
|
30
|
+
// (1,048,576 in, 65,536 out, images). 100xprompt Auto's id is not, so for auto both are set to what every model auto may
|
|
31
|
+
// serve takes (common.ts limitsFor). Images: OpenHands decides them from that same catalog only (llm.py
|
|
32
|
+
// _supports_vision; there is no setting to turn them on), so with auto it sends no images.
|
|
28
33
|
import { readFileSync } from "node:fs"
|
|
29
34
|
import { join } from "node:path"
|
|
30
|
-
import { type Codec, type Connector, dirFromEnv, EDIT_HEADER, type Env, type GatewaySettings, type Json, readKey, routeUrl } from "../common.ts"
|
|
35
|
+
import { type Codec, type Connector, dirFromEnv, EDIT_HEADER, type Env, type GatewaySettings, isAuto, type Json, limitsFor, readKey, routeUrl } from "../common.ts"
|
|
31
36
|
|
|
32
37
|
// ---- JSON edited in place ------------------------------------------------------------------------------------------------
|
|
33
38
|
|
|
@@ -261,8 +266,8 @@ const llmValues = (prefix: string, home: string, s: GatewaySettings): Record<str
|
|
|
261
266
|
[`${prefix}/api_key`]: readKey(home),
|
|
262
267
|
[`${prefix}/extra_headers`]: s.contextEdit ? { [EDIT_HEADER]: "on" } : null,
|
|
263
268
|
[`${prefix}/model_canonical_name`]: null,
|
|
264
|
-
[`${prefix}/max_input_tokens`]: null,
|
|
265
|
-
[`${prefix}/max_output_tokens`]: null,
|
|
269
|
+
[`${prefix}/max_input_tokens`]: isAuto(s) ? limitsFor(s).context : null,
|
|
270
|
+
[`${prefix}/max_output_tokens`]: isAuto(s) ? limitsFor(s).output : null,
|
|
266
271
|
[`${prefix}/litellm_extra_body`]: {},
|
|
267
272
|
})
|
|
268
273
|
|
|
@@ -289,7 +294,7 @@ export const connectors: Connector[] = [
|
|
|
289
294
|
},
|
|
290
295
|
notes: [
|
|
291
296
|
"OpenHands keeps the key in its agent_settings.json (it has no key-file option), so the file is now readable by you only (0600); a new sign-in replaces the key there.",
|
|
292
|
-
"Its agent and memory-condenser LLM now use the gateway's model over OpenAI Chat Completions; the model limits OpenHands had stored for your previous model are cleared so it looks them up again.",
|
|
297
|
+
"Its agent and memory-condenser LLM now use the gateway's model over OpenAI Chat Completions; the model limits OpenHands had stored for your previous model are cleared so it looks them up again (with 100xprompt Auto they are set instead, and OpenHands sends no images: it decides that from the model's name alone).",
|
|
293
298
|
"Run without --override-with-envs (or without LLM_API_KEY, LLM_BASE_URL and LLM_MODEL set): those override this file. Changing the LLM in OpenHands' settings screen replaces these values, and disconnect then leaves your choice alone.",
|
|
294
299
|
],
|
|
295
300
|
},
|
|
@@ -20,13 +20,18 @@
|
|
|
20
20
|
// under a name of its own (BRAIN_GATEWAY_KEY, so no OPENAI_API_KEY of the person's is touched), and that file is 0600.
|
|
21
21
|
// - Custom headers: generationConfig.customHeaders is sent with every request (chunks/chunk-ESPAP3UW.js buildHeaders),
|
|
22
22
|
// so `x-100x-context-edit: on` goes there.
|
|
23
|
+
// - Context window and input kinds: Qwen Code takes them from the model id (chunks/chunk-5TODR7F6.js tokenLimit and
|
|
24
|
+
// defaultModalities: gemini-* gets 1,000,000 tokens and every input kind; an id it does not know gets 200,000 and text
|
|
25
|
+
// only), unless the provider entry's generationConfig gives contextWindowSize and modalities (chunks/chunk-KUBWE5YV.js
|
|
26
|
+
// ModelRegistry, MODEL_GENERATION_CONFIG_FIELDS). 100xprompt Auto's id is one it cannot know, so for auto they are given:
|
|
27
|
+
// what every model auto may serve takes (common.ts limitsFor), images and text.
|
|
23
28
|
// What connect writes: settings.json gets a "brain-gateway" provider ("100xprompt gateway", model shown as its 100xprompt
|
|
24
29
|
// name), its providerProtocol mapping, and the selection (auth type, model.name, model.baseUrl); ~/.qwen/.env gets the
|
|
25
30
|
// key. The person's own providers, models and comments are left as they are; disconnect puts every key back.
|
|
26
31
|
// Needs Qwen Code 0.23 or later (custom provider ids through providerProtocol); older ones skip the provider.
|
|
27
32
|
import { existsSync } from "node:fs"
|
|
28
33
|
import { join } from "node:path"
|
|
29
|
-
import { type Codec, type Connector, EDIT_HEADER, type Env, type GatewaySettings, type Json, modelName, readKey, routeUrl } from "../common.ts"
|
|
34
|
+
import { type Codec, type Connector, EDIT_HEADER, type Env, type GatewaySettings, isAuto, type Json, limitsFor, modelName, readKey, routeUrl } from "../common.ts"
|
|
30
35
|
|
|
31
36
|
// ---- JSON with comments, edited in place ----------------------------------------------------------------------------
|
|
32
37
|
//
|
|
@@ -372,6 +377,15 @@ export function qwenDir(home: string, env: Env): string {
|
|
|
372
377
|
|
|
373
378
|
const base = (s: GatewaySettings) => `${routeUrl(s)}/v1`
|
|
374
379
|
|
|
380
|
+
/** The provider entry's generationConfig: the context-edit header, and for auto the window and input kinds (see above). */
|
|
381
|
+
function generationConfig(s: GatewaySettings): Json | undefined {
|
|
382
|
+
const g: Json = {
|
|
383
|
+
...(s.contextEdit ? { customHeaders: { [EDIT_HEADER]: "on" } } : {}),
|
|
384
|
+
...(isAuto(s) ? { contextWindowSize: limitsFor(s).context, modalities: { image: true } } : {}),
|
|
385
|
+
}
|
|
386
|
+
return Object.keys(g).length ? g : undefined
|
|
387
|
+
}
|
|
388
|
+
|
|
375
389
|
export const connectors: Connector[] = [
|
|
376
390
|
{
|
|
377
391
|
id: "qwen-code",
|
|
@@ -392,7 +406,7 @@ export const connectors: Connector[] = [
|
|
|
392
406
|
description: "100xprompt gateway",
|
|
393
407
|
baseUrl: base(s),
|
|
394
408
|
envKey: QWEN_KEY_VAR,
|
|
395
|
-
...(s
|
|
409
|
+
...(generationConfig(s) ? { generationConfig: generationConfig(s) } : {}),
|
|
396
410
|
},
|
|
397
411
|
],
|
|
398
412
|
[`providerProtocol/${PROVIDER}`]: "openai",
|
|
@@ -27,13 +27,10 @@
|
|
|
27
27
|
// Only the default VS Code profile and VS Code Stable ("Code") are handled; Insiders, VSCodium and other profiles keep
|
|
28
28
|
// their own User folders.
|
|
29
29
|
import { join, resolve } from "node:path"
|
|
30
|
-
import { type Codec, type Connector, EDIT_HEADER, type Env, type Json, modelName, readKey, routeUrl } from "../common.ts"
|
|
30
|
+
import { type Codec, type Connector, EDIT_HEADER, type Env, type Json, limitsFor, modelName, readKey, routeUrl } from "../common.ts"
|
|
31
31
|
|
|
32
32
|
const GROUP = "100xprompt gateway"
|
|
33
33
|
const VENDOR = "customendpoint"
|
|
34
|
-
/** Gemini 3.x Flash, which the gateway's models are: a 1M-token window, 64K out. */
|
|
35
|
-
const MAX_OUTPUT = 65_536
|
|
36
|
-
const MAX_INPUT = 1_048_576 - MAX_OUTPUT
|
|
37
34
|
|
|
38
35
|
/** VS Code's user folder: ~/Library/Application Support/Code/User on macOS, $XDG_CONFIG_HOME/Code/User or ~/.config/Code/User elsewhere. */
|
|
39
36
|
export function vscodeUserDir(home: string, env: Env, platform: string = process.platform): string {
|
|
@@ -268,8 +265,9 @@ export const connectors: Connector[] = [
|
|
|
268
265
|
apiType: "chat-completions",
|
|
269
266
|
toolCalling: true,
|
|
270
267
|
vision: true,
|
|
271
|
-
|
|
272
|
-
|
|
268
|
+
// The window less the output (Gemini 3.x Flash: 1M in, 64K out; 100xprompt Auto: what all it may serve take).
|
|
269
|
+
maxInputTokens: limitsFor(s).context - limitsFor(s).output,
|
|
270
|
+
maxOutputTokens: limitsFor(s).output,
|
|
273
271
|
requestHeaders: { Authorization: `Bearer ${readKey(home)}`, ...(s.contextEdit ? { [EDIT_HEADER]: "on" } : {}) },
|
|
274
272
|
},
|
|
275
273
|
],
|