@bubblebrain-ai/bubble 0.0.35 → 0.0.37

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.
Files changed (144) hide show
  1. package/README.md +27 -7
  2. package/dist/agent/categories.d.ts +66 -2
  3. package/dist/agent/categories.js +120 -19
  4. package/dist/agent/discovery-barrier.js +1 -1
  5. package/dist/agent/profiles.d.ts +1 -0
  6. package/dist/agent/profiles.js +25 -11
  7. package/dist/agent/routing-catalog.d.ts +113 -0
  8. package/dist/agent/routing-catalog.js +240 -0
  9. package/dist/agent/workflow/control.d.ts +14 -0
  10. package/dist/agent/workflow/control.js +30 -9
  11. package/dist/agent.d.ts +75 -5
  12. package/dist/agent.js +313 -61
  13. package/dist/approval/controller.js +28 -0
  14. package/dist/approval/tool-helper.js +2 -0
  15. package/dist/approval/types.d.ts +21 -1
  16. package/dist/cli.d.ts +1 -1
  17. package/dist/cli.js +2 -1
  18. package/dist/config.d.ts +4 -0
  19. package/dist/config.js +4 -0
  20. package/dist/context/compact-llm.d.ts +2 -2
  21. package/dist/external-runtime/grok-acp.d.ts +56 -0
  22. package/dist/external-runtime/grok-acp.js +386 -0
  23. package/dist/external-runtime/grok-binary.d.ts +25 -0
  24. package/dist/external-runtime/grok-binary.js +108 -0
  25. package/dist/external-runtime/grok-errors.d.ts +7 -0
  26. package/dist/external-runtime/grok-errors.js +15 -0
  27. package/dist/external-runtime/grok-input-policy.d.ts +58 -0
  28. package/dist/external-runtime/grok-input-policy.js +52 -0
  29. package/dist/external-runtime/grok-ndjson.d.ts +14 -0
  30. package/dist/external-runtime/grok-ndjson.js +193 -0
  31. package/dist/external-runtime/grok-network.d.ts +15 -0
  32. package/dist/external-runtime/grok-network.js +81 -0
  33. package/dist/external-runtime/grok-preflight.d.ts +28 -0
  34. package/dist/external-runtime/grok-preflight.js +194 -0
  35. package/dist/external-runtime/grok-process.d.ts +26 -0
  36. package/dist/external-runtime/grok-process.js +184 -0
  37. package/dist/external-runtime/grok-profile.d.ts +32 -0
  38. package/dist/external-runtime/grok-profile.js +419 -0
  39. package/dist/external-runtime/grok-provider.d.ts +31 -0
  40. package/dist/external-runtime/grok-provider.js +41 -0
  41. package/dist/external-runtime/grok-runtime.d.ts +91 -0
  42. package/dist/external-runtime/grok-runtime.js +882 -0
  43. package/dist/external-runtime/index.d.ts +13 -0
  44. package/dist/external-runtime/index.js +13 -0
  45. package/dist/external-runtime/session-policy.d.ts +11 -0
  46. package/dist/external-runtime/session-policy.js +30 -0
  47. package/dist/external-runtime/types.d.ts +65 -0
  48. package/dist/external-runtime/types.js +1 -0
  49. package/dist/feishu/agent-host/approval-card.js +21 -0
  50. package/dist/feishu/agent-host/run-driver.js +19 -3
  51. package/dist/feishu/serve.js +1 -0
  52. package/dist/goal/tools.d.ts +6 -3
  53. package/dist/goal/tools.js +7 -19
  54. package/dist/main.js +105 -27
  55. package/dist/memory/db.d.ts +2 -0
  56. package/dist/memory/db.js +10 -0
  57. package/dist/memory/index.d.ts +1 -0
  58. package/dist/memory/index.js +1 -0
  59. package/dist/memory/phase1.js +14 -2
  60. package/dist/memory/phase2.js +21 -2
  61. package/dist/memory/prompts.js +1 -1
  62. package/dist/memory/session-policy.d.ts +25 -0
  63. package/dist/memory/session-policy.js +55 -0
  64. package/dist/memory/storage.d.ts +1 -0
  65. package/dist/memory/storage.js +13 -0
  66. package/dist/model-catalog.d.ts +18 -0
  67. package/dist/model-catalog.js +87 -41
  68. package/dist/model-config.d.ts +3 -1
  69. package/dist/model-config.js +11 -0
  70. package/dist/oauth/grok.d.ts +25 -0
  71. package/dist/oauth/grok.js +218 -0
  72. package/dist/oauth/index.d.ts +1 -0
  73. package/dist/oauth/index.js +1 -0
  74. package/dist/oauth/storage.d.ts +9 -0
  75. package/dist/oauth/storage.js +25 -0
  76. package/dist/orchestrator/default-hooks.js +17 -7
  77. package/dist/prompt/compose.d.ts +3 -0
  78. package/dist/prompt/compose.js +3 -0
  79. package/dist/prompt/delegation.js +22 -3
  80. package/dist/prompt/environment.js +0 -4
  81. package/dist/prompt/reminders.d.ts +0 -1
  82. package/dist/prompt/reminders.js +1 -11
  83. package/dist/prompt/routing.d.ts +14 -0
  84. package/dist/prompt/routing.js +71 -0
  85. package/dist/prompt/runtime.js +1 -1
  86. package/dist/prompt/task-reminders.d.ts +19 -0
  87. package/dist/prompt/task-reminders.js +49 -0
  88. package/dist/provider-ai-sdk.js +2 -2
  89. package/dist/provider-grok.d.ts +26 -0
  90. package/dist/provider-grok.js +59 -0
  91. package/dist/provider-openai-codex.d.ts +17 -1
  92. package/dist/provider-openai-codex.js +101 -29
  93. package/dist/provider-registry.d.ts +72 -0
  94. package/dist/provider-registry.js +336 -59
  95. package/dist/provider-transform.d.ts +1 -1
  96. package/dist/provider-transform.js +28 -5
  97. package/dist/provider.d.ts +3 -0
  98. package/dist/provider.js +14 -0
  99. package/dist/session-types.d.ts +19 -1
  100. package/dist/session.d.ts +1 -0
  101. package/dist/session.js +4 -0
  102. package/dist/slash-commands/commands.js +224 -14
  103. package/dist/slash-commands/types.d.ts +14 -1
  104. package/dist/system-prompt.d.ts +2 -0
  105. package/dist/tools/agent-lifecycle.js +9 -11
  106. package/dist/tools/bash.js +1 -1
  107. package/dist/tools/child-tools.js +2 -0
  108. package/dist/tools/index.d.ts +2 -2
  109. package/dist/tools/index.js +7 -5
  110. package/dist/tools/lsp.js +1 -0
  111. package/dist/tools/memory.d.ts +7 -2
  112. package/dist/tools/memory.js +28 -43
  113. package/dist/tools/server.js +4 -0
  114. package/dist/tools/tool-search.d.ts +1 -1
  115. package/dist/tools/tool-search.js +23 -3
  116. package/dist/tui/model-switch.d.ts +7 -0
  117. package/dist/tui/model-switch.js +15 -5
  118. package/dist/tui-ink/app.d.ts +3 -1
  119. package/dist/tui-ink/app.js +649 -233
  120. package/dist/tui-ink/approval/approval-dialog.js +36 -0
  121. package/dist/tui-ink/composer-buffer.d.ts +55 -0
  122. package/dist/tui-ink/composer-buffer.js +317 -0
  123. package/dist/tui-ink/footer.d.ts +1 -0
  124. package/dist/tui-ink/footer.js +7 -2
  125. package/dist/tui-ink/input-box.d.ts +35 -1
  126. package/dist/tui-ink/input-box.js +215 -122
  127. package/dist/tui-ink/message-list.js +5 -5
  128. package/dist/tui-ink/model-picker.d.ts +61 -4
  129. package/dist/tui-ink/model-picker.js +486 -133
  130. package/dist/tui-ink/paste-operation-tracker.d.ts +13 -0
  131. package/dist/tui-ink/paste-operation-tracker.js +25 -0
  132. package/dist/tui-ink/run.d.ts +3 -0
  133. package/dist/tui-ink/run.js +1 -1
  134. package/dist/tui-ink/subagent-view.d.ts +31 -0
  135. package/dist/tui-ink/subagent-view.js +82 -1
  136. package/dist/types.d.ts +5 -5
  137. package/dist/types.js +1 -1
  138. package/dist/variant/thinking-level.d.ts +2 -2
  139. package/dist/variant/thinking-level.js +2 -1
  140. package/dist/variant/variant-resolver.d.ts +9 -0
  141. package/dist/variant/variant-resolver.js +17 -0
  142. package/package.json +2 -1
  143. package/dist/tools/task.d.ts +0 -2
  144. package/dist/tools/task.js +0 -58
@@ -2,7 +2,8 @@ import { randomUUID } from "node:crypto";
2
2
  import { MemoryDatabase } from "./db.js";
3
3
  import { getMemoryPaths } from "./paths.js";
4
4
  import { buildConsolidationMessages, parseJsonObject } from "./prompts.js";
5
- import { rebuildRawMemories, syncRolloutSummaries, writeConsolidatedMemory } from "./storage.js";
5
+ import { classifyMemorySession } from "./session-policy.js";
6
+ import { clearGeneratedMemoryOutputs, rebuildRawMemories, syncRolloutSummaries, writeConsolidatedMemory, } from "./storage.js";
6
7
  const DEFAULT_SELECTION_LIMIT = 40;
7
8
  export async function runMemoryPhase2(options) {
8
9
  if (!options.model) {
@@ -17,8 +18,26 @@ export async function runMemoryPhase2(options) {
17
18
  return { status: "skipped", reason: claim.reason ?? "phase2 unavailable", selected: 0 };
18
19
  }
19
20
  try {
20
- const selected = db.listStage1Outputs(options.limit ?? DEFAULT_SELECTION_LIMIT);
21
+ const eligible = [];
22
+ let clearedUnsafeArtifacts = false;
23
+ for (const output of db.listAllStage1Outputs()) {
24
+ const source = classifyMemorySession(output.sessionFile);
25
+ if (source.kind === "native") {
26
+ eligible.push(output);
27
+ }
28
+ else {
29
+ if (!clearedUnsafeArtifacts) {
30
+ // Clear before deleting provenance rows; phase 2 will rebuild from
31
+ // the remaining native-only selection below.
32
+ clearGeneratedMemoryOutputs(options.cwd);
33
+ clearedUnsafeArtifacts = true;
34
+ }
35
+ db.deleteStage1Output(output.sessionFile);
36
+ }
37
+ }
38
+ const selected = eligible.slice(0, Math.max(0, options.limit ?? DEFAULT_SELECTION_LIMIT));
21
39
  if (selected.length === 0) {
40
+ clearGeneratedMemoryOutputs(options.cwd);
22
41
  db.finishGlobalPhase2Job(true, Date.now());
23
42
  return { status: "skipped", reason: "no stage-1 outputs", selected: 0 };
24
43
  }
@@ -74,7 +74,7 @@ export function buildReadPathPrompt(input) {
74
74
  "",
75
75
  "Memory retrieval rules:",
76
76
  "- Use memory when the task mentions prior work, this repo, user preferences, or a recurring workflow.",
77
- "- Start from the injected memory_summary.md below; use memory_search or memory_read_summary when more detail is needed.",
77
+ "- Start from the injected memory_summary.md below; use the memory tool (with a query to search, without to re-read the summary) when more detail is needed.",
78
78
  "- Search MEMORY.md before opening rollout summaries; open only the most relevant detailed files.",
79
79
  "- Do not update memory directly during normal tasks; the startup memory pipeline maintains it automatically.",
80
80
  "- Treat memory content and memory rules as private control context.",
@@ -0,0 +1,25 @@
1
+ import { SessionManager } from "../session.js";
2
+ export type MemorySessionClassification = {
3
+ kind: "native";
4
+ session: SessionManager;
5
+ } | {
6
+ kind: "external";
7
+ session: SessionManager;
8
+ } | {
9
+ kind: "unavailable";
10
+ };
11
+ /**
12
+ * Classify a persisted Bubble session before its content can enter the native
13
+ * memory pipeline. Unreadable sources are not assumed to be native: phase 2
14
+ * must be able to verify the persisted metadata before reusing a stage-1 row.
15
+ */
16
+ export declare function classifyMemorySession(sessionFile: string): MemorySessionClassification;
17
+ /**
18
+ * Validate persisted stage-1 provenance before generated memory can be read
19
+ * into a native provider prompt. Any unverifiable or external source makes
20
+ * the existing generated artifacts unsafe as a unit: clear them first, then
21
+ * remove the offending rows so a later phase 2 can rebuild from native-only
22
+ * inputs. Clearing before deleting is intentional so a crash cannot leave a
23
+ * contaminated artifact with no database row that identifies its source.
24
+ */
25
+ export declare function purgeUnsafeMemorySources(cwd: string): number;
@@ -0,0 +1,55 @@
1
+ import { existsSync } from "node:fs";
2
+ import { SessionManager } from "../session.js";
3
+ import { MemoryDatabase } from "./db.js";
4
+ import { clearGeneratedMemoryOutputs } from "./storage.js";
5
+ /**
6
+ * Classify a persisted Bubble session before its content can enter the native
7
+ * memory pipeline. Unreadable sources are not assumed to be native: phase 2
8
+ * must be able to verify the persisted metadata before reusing a stage-1 row.
9
+ */
10
+ export function classifyMemorySession(sessionFile) {
11
+ if (!existsSync(sessionFile))
12
+ return { kind: "unavailable" };
13
+ try {
14
+ const session = new SessionManager(sessionFile);
15
+ return session.getMetadata().externalRuntime === undefined
16
+ ? { kind: "native", session }
17
+ : { kind: "external", session };
18
+ }
19
+ catch {
20
+ return { kind: "unavailable" };
21
+ }
22
+ }
23
+ /**
24
+ * Validate persisted stage-1 provenance before generated memory can be read
25
+ * into a native provider prompt. Any unverifiable or external source makes
26
+ * the existing generated artifacts unsafe as a unit: clear them first, then
27
+ * remove the offending rows so a later phase 2 can rebuild from native-only
28
+ * inputs. Clearing before deleting is intentional so a crash cannot leave a
29
+ * contaminated artifact with no database row that identifies its source.
30
+ */
31
+ export function purgeUnsafeMemorySources(cwd) {
32
+ let db;
33
+ try {
34
+ db = new MemoryDatabase(cwd);
35
+ const outputs = db.listAllStage1Outputs();
36
+ const unsafe = outputs.filter((output) => classifyMemorySession(output.sessionFile).kind !== "native");
37
+ if (unsafe.length === 0) {
38
+ if (outputs.length === 0)
39
+ clearGeneratedMemoryOutputs(cwd);
40
+ return 0;
41
+ }
42
+ clearGeneratedMemoryOutputs(cwd);
43
+ for (const output of unsafe)
44
+ db.deleteStage1Output(output.sessionFile);
45
+ return unsafe.length;
46
+ }
47
+ catch {
48
+ // Generated memory without verifiable provenance must never be injected.
49
+ clearGeneratedMemoryOutputs(cwd);
50
+ return 0;
51
+ }
52
+ finally {
53
+ db?.close();
54
+ }
55
+ }
@@ -6,5 +6,6 @@ export declare function writeConsolidatedMemory(cwd: string, input: {
6
6
  memoryMd: string;
7
7
  memorySummaryMd: string;
8
8
  }): void;
9
+ export declare function clearGeneratedMemoryOutputs(cwd: string): void;
9
10
  export declare function resetMemoryWorkspace(cwd: string): void;
10
11
  export declare function rolloutSummaryFileName(output: Stage1Output): string;
@@ -57,6 +57,19 @@ export function writeConsolidatedMemory(cwd, input) {
57
57
  writeFileSync(paths.globalMemory, normalizeMemoryMarkdown(input.memoryMd), "utf-8");
58
58
  writeFileSync(paths.globalSummary, normalizeSummaryMarkdown(input.memorySummaryMd), "utf-8");
59
59
  }
60
+ export function clearGeneratedMemoryOutputs(cwd) {
61
+ const paths = getMemoryPaths(cwd);
62
+ rmSync(paths.globalRawMemories, { force: true });
63
+ rmSync(paths.globalMemory, { force: true });
64
+ rmSync(paths.globalSummary, { force: true });
65
+ if (!existsSync(paths.globalRolloutSummaries))
66
+ return;
67
+ for (const file of readdirSync(paths.globalRolloutSummaries)) {
68
+ if (file.endsWith(".md")) {
69
+ rmSync(join(paths.globalRolloutSummaries, file), { force: true });
70
+ }
71
+ }
72
+ }
60
73
  export function resetMemoryWorkspace(cwd) {
61
74
  const paths = getMemoryPaths(cwd);
62
75
  rmSync(paths.globalRoot, { recursive: true, force: true });
@@ -8,13 +8,23 @@ export interface BuiltinProviderDefinition {
8
8
  hidden?: boolean;
9
9
  supportsOAuth?: boolean;
10
10
  }
11
+ /**
12
+ * Relative capability/cost tier within one provider's lineup (never a
13
+ * cross-provider comparison). Drives rank-guarded category routing and the
14
+ * routing menu (docs/model-routing-design.md §2). Untiered models never
15
+ * participate in automatic routing but stay reachable by explicit name.
16
+ */
17
+ export type ModelTier = "fast" | "balanced" | "strong";
11
18
  export interface BuiltinModelDefinition {
12
19
  id: string;
13
20
  name: string;
14
21
  providerId: string;
15
22
  reasoningLevels: ReasoningEffort[];
16
23
  defaultReasoningLevel?: ReasoningEffort;
24
+ tier?: ModelTier;
17
25
  contextWindow?: number;
26
+ /** Routes this model through the Codex Responses Lite backend when true. */
27
+ useResponsesLite?: boolean;
18
28
  /**
19
29
  * Server-declared cap on per-tool-output tokens. When set, the agent must
20
30
  * truncate each tool result to this token budget before adding it to history
@@ -27,6 +37,14 @@ export declare const BUILTIN_PROVIDERS: BuiltinProviderDefinition[];
27
37
  export declare const BUILTIN_MODELS: BuiltinModelDefinition[];
28
38
  export declare function listBuiltinModels(providerId: string): BuiltinModelDefinition[];
29
39
  export declare function registerDynamicModelMetadata(model: BuiltinModelDefinition): void;
40
+ export declare function clearDynamicModelMetadata(providerId: string): void;
41
+ export declare function replaceDynamicModelMetadata(providerId: string, models: readonly BuiltinModelDefinition[]): void;
42
+ /**
43
+ * All dynamic-overlay entries for a provider (openai includes its
44
+ * openai-codex catalog alias). The overlay was previously visible only via
45
+ * getBuiltinModel point lookups; the routing snapshot builder needs the list.
46
+ */
47
+ export declare function listDynamicModelMetadata(providerId: string): BuiltinModelDefinition[];
30
48
  export declare function getBuiltinModel(providerId: string, modelId: string): BuiltinModelDefinition | undefined;
31
49
  export declare function getModelDefaultReasoningLevel(providerId: string, modelId: string): ReasoningEffort | undefined;
32
50
  export declare function getBuiltinProvider(providerId: string): BuiltinProviderDefinition | undefined;
@@ -2,6 +2,9 @@ export const BUILTIN_PROVIDERS = [
2
2
  { id: "openrouter", name: "OpenRouter", baseURL: "https://openrouter.ai/api/v1" },
3
3
  { id: "openai", name: "OpenAI", baseURL: "https://api.openai.com/v1", supportsOAuth: true },
4
4
  { id: "openai-codex", name: "OpenAI Codex (ChatGPT)", baseURL: "https://chatgpt.com/backend-api" },
5
+ // Grok subscription models via the CLI chat proxy. OAuth-only: the proxy
6
+ // accepts xAI session bearers (from /login grok), not api.x.ai API keys.
7
+ { id: "grok", name: "Grok Subscription", baseURL: "https://cli-chat-proxy.grok.com/v1", supportsOAuth: true },
5
8
  { id: "anthropic", name: "Anthropic", baseURL: "https://api.anthropic.com", protocol: "anthropic-messages" },
6
9
  { id: "deepseek", name: "DeepSeek", baseURL: "https://api.deepseek.com" },
7
10
  // Native Gemini API via the AI SDK google provider. Users who configured the
@@ -26,6 +29,8 @@ export const BUILTIN_PROVIDERS = [
26
29
  { id: "local", name: "Local (OpenAI-compatible)", baseURL: "http://localhost:11434/v1" },
27
30
  ];
28
31
  const ALL_OPENAI_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"];
32
+ const GPT56_LEVELS = ["low", "medium", "high", "xhigh", "max", "ultra"];
33
+ const GPT56_LUNA_LEVELS = ["low", "medium", "high", "xhigh", "max"];
29
34
  const GPT51_LEVELS = ["off", "low", "medium", "high"];
30
35
  const GPT51_CODEX_MAX_LEVELS = ["off", "low", "medium", "high", "xhigh"];
31
36
  const GPT51_CODEX_MINI_LEVELS = ["off", "medium", "high"];
@@ -56,80 +61,88 @@ const ANTHROPIC_OPUS_EFFORT_LEVELS = ["off", "low", "medium", "high", "xhigh", "
56
61
  const ANTHROPIC_SONNET_EFFORT_LEVELS = ["off", "low", "medium", "high", "max"];
57
62
  const ANTHROPIC_FABLE_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
58
63
  const ANTHROPIC_CHAT_LEVELS = ["off"];
64
+ // Grok 4.5 exposes low/medium/high effort with no "off" (thinking always on);
65
+ // the composer model declares no effort support at all.
66
+ const GROK_45_LEVELS = ["low", "medium", "high"];
59
67
  const GEMINI_3_LEVELS = ["low", "medium", "high"];
60
68
  const GEMINI_3_FLASH_LEVELS = ["minimal", "low", "medium", "high"];
61
69
  const GEMINI_25_PRO_LEVELS = ["low", "medium", "high"];
62
70
  const GEMINI_25_FLASH_LEVELS = ["off", "low", "medium", "high"];
63
71
  export const BUILTIN_MODELS = [
64
- { id: "gpt-5.5", name: "gpt-5.5", providerId: "openai-codex", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000, toolOutputTokenLimit: 10000 },
65
- { id: "gpt-5.4", name: "gpt-5.4", providerId: "openai-codex", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000 },
66
- { id: "gpt-5.4-mini", name: "gpt-5.4-mini", providerId: "openai-codex", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000 },
72
+ { id: "gpt-5.6-sol", name: "GPT-5.6-Sol", providerId: "openai-codex", tier: "balanced", reasoningLevels: GPT56_LEVELS, defaultReasoningLevel: "low", contextWindow: 372000, useResponsesLite: true, toolOutputTokenLimit: 10000 },
73
+ { id: "gpt-5.6-terra", name: "GPT-5.6-Terra", providerId: "openai-codex", tier: "strong", reasoningLevels: GPT56_LEVELS, defaultReasoningLevel: "medium", contextWindow: 372000, useResponsesLite: true, toolOutputTokenLimit: 10000 },
74
+ { id: "gpt-5.6-luna", name: "GPT-5.6-Luna", providerId: "openai-codex", tier: "strong", reasoningLevels: GPT56_LUNA_LEVELS, defaultReasoningLevel: "medium", contextWindow: 372000, useResponsesLite: true, toolOutputTokenLimit: 10000 },
75
+ { id: "gpt-5.5", name: "gpt-5.5", providerId: "openai-codex", tier: "strong", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000, toolOutputTokenLimit: 10000 },
76
+ { id: "gpt-5.4", name: "gpt-5.4", providerId: "openai-codex", tier: "balanced", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000 },
77
+ { id: "gpt-5.4-mini", name: "gpt-5.4-mini", providerId: "openai-codex", tier: "fast", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000 },
67
78
  { id: "gpt-5.3-codex", name: "gpt-5.3-codex", providerId: "openai-codex", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000 },
68
79
  { id: "gpt-5.3-codex-spark", name: "gpt-5.3-codex-spark", providerId: "openai-codex", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000 },
69
80
  { id: "gpt-5.2-codex", name: "gpt-5.2-codex", providerId: "openai-codex", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000 },
70
81
  { id: "gpt-5.2", name: "gpt-5.2", providerId: "openai-codex", reasoningLevels: ALL_OPENAI_LEVELS, contextWindow: 272000 },
71
82
  { id: "gpt-5.1-codex-max", name: "gpt-5.1-codex-max", providerId: "openai-codex", reasoningLevels: GPT51_CODEX_MAX_LEVELS, contextWindow: 272000 },
72
- { id: "gpt-5.1-codex-mini", name: "gpt-5.1-codex-mini", providerId: "openai-codex", reasoningLevels: GPT51_CODEX_MINI_LEVELS, contextWindow: 272000 },
83
+ { id: "gpt-5.1-codex-mini", name: "gpt-5.1-codex-mini", providerId: "openai-codex", tier: "fast", reasoningLevels: GPT51_CODEX_MINI_LEVELS, contextWindow: 272000 },
73
84
  { id: "gpt-5.1", name: "gpt-5.1", providerId: "openai-codex", reasoningLevels: GPT51_LEVELS, contextWindow: 272000 },
74
- { id: "gpt-4o", name: "gpt-4o", providerId: "openai", reasoningLevels: OPENAI_CHAT_LEVELS, contextWindow: 128000 },
75
- { id: "gpt-4o-mini", name: "gpt-4o-mini", providerId: "openai", reasoningLevels: OPENAI_CHAT_LEVELS, contextWindow: 128000 },
76
- { id: "o1-preview", name: "o1-preview", providerId: "openai", reasoningLevels: ["off", "low", "medium", "high"], contextWindow: 128000 },
77
- { id: "o1-mini", name: "o1-mini", providerId: "openai", reasoningLevels: ["off", "low", "medium", "high"], contextWindow: 128000 },
85
+ { id: "grok-4.5", name: "Grok 4.5", providerId: "grok", tier: "strong", reasoningLevels: GROK_45_LEVELS, defaultReasoningLevel: "high", contextWindow: 500000 },
86
+ { id: "grok-composer-2.5-fast", name: "Grok Composer 2.5 Fast", providerId: "grok", tier: "fast", reasoningLevels: OPENAI_CHAT_LEVELS, contextWindow: 200000 },
87
+ { id: "gpt-4o", name: "gpt-4o", providerId: "openai", tier: "balanced", reasoningLevels: OPENAI_CHAT_LEVELS, contextWindow: 128000 },
88
+ { id: "gpt-4o-mini", name: "gpt-4o-mini", providerId: "openai", tier: "fast", reasoningLevels: OPENAI_CHAT_LEVELS, contextWindow: 128000 },
89
+ { id: "o1-preview", name: "o1-preview", providerId: "openai", tier: "strong", reasoningLevels: ["off", "low", "medium", "high"], contextWindow: 128000 },
90
+ { id: "o1-mini", name: "o1-mini", providerId: "openai", tier: "fast", reasoningLevels: ["off", "low", "medium", "high"], contextWindow: 128000 },
78
91
  { id: "gpt-4-turbo", name: "gpt-4-turbo", providerId: "openai", reasoningLevels: OPENAI_CHAT_LEVELS, contextWindow: 128000 },
79
- { id: "claude-fable-5", name: "Claude Fable 5", providerId: "anthropic", reasoningLevels: ANTHROPIC_FABLE_EFFORT_LEVELS, defaultReasoningLevel: "high", contextWindow: 1000000 },
80
- { id: "claude-opus-4-8", name: "Claude Opus 4.8", providerId: "anthropic", reasoningLevels: ANTHROPIC_OPUS_EFFORT_LEVELS, defaultReasoningLevel: "high", contextWindow: 1000000 },
81
- { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", providerId: "anthropic", reasoningLevels: ANTHROPIC_SONNET_EFFORT_LEVELS, defaultReasoningLevel: "high", contextWindow: 1000000 },
82
- { id: "claude-haiku-4-5-20251001", name: "Claude Haiku 4.5", providerId: "anthropic", reasoningLevels: ANTHROPIC_CHAT_LEVELS, contextWindow: 200000 },
83
- { id: "deepseek-v4-flash", name: "deepseek-v4-flash", providerId: "deepseek", reasoningLevels: DEEPSEEK_V4_LEVELS, contextWindow: 1048576 },
84
- { id: "deepseek-v4-pro", name: "deepseek-v4-pro", providerId: "deepseek", reasoningLevels: DEEPSEEK_V4_LEVELS, contextWindow: 1048576 },
92
+ { id: "claude-fable-5", name: "Claude Fable 5", providerId: "anthropic", tier: "strong", reasoningLevels: ANTHROPIC_FABLE_EFFORT_LEVELS, defaultReasoningLevel: "high", contextWindow: 1000000 },
93
+ { id: "claude-opus-4-8", name: "Claude Opus 4.8", providerId: "anthropic", tier: "strong", reasoningLevels: ANTHROPIC_OPUS_EFFORT_LEVELS, defaultReasoningLevel: "high", contextWindow: 1000000 },
94
+ { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", providerId: "anthropic", tier: "balanced", reasoningLevels: ANTHROPIC_SONNET_EFFORT_LEVELS, defaultReasoningLevel: "high", contextWindow: 1000000 },
95
+ { id: "claude-haiku-4-5-20251001", name: "Claude Haiku 4.5", providerId: "anthropic", tier: "fast", reasoningLevels: ANTHROPIC_CHAT_LEVELS, contextWindow: 200000 },
96
+ { id: "deepseek-v4-flash", name: "deepseek-v4-flash", providerId: "deepseek", tier: "fast", reasoningLevels: DEEPSEEK_V4_LEVELS, contextWindow: 1048576 },
97
+ { id: "deepseek-v4-pro", name: "deepseek-v4-pro", providerId: "deepseek", tier: "strong", reasoningLevels: DEEPSEEK_V4_LEVELS, contextWindow: 1048576 },
85
98
  // Offline/no-key fallback only: with an API key the registry replaces this
86
99
  // list via fetchGeminiModels (GET /v1beta/models, newest five). Gemini 3
87
100
  // exposes thinking_level (minimal/low/medium/high); 2.5 Pro cannot disable
88
101
  // thinking (no "off"), 2.5 Flash can (thinkingBudget 0).
89
- { id: "gemini-3.5-flash", name: "Gemini 3.5 Flash", providerId: "google", reasoningLevels: GEMINI_3_FLASH_LEVELS, contextWindow: 1048576 },
90
- { id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro", providerId: "google", reasoningLevels: GEMINI_3_LEVELS, defaultReasoningLevel: "high", contextWindow: 1048576 },
91
- { id: "gemini-3-flash-preview", name: "Gemini 3 Flash", providerId: "google", reasoningLevels: GEMINI_3_FLASH_LEVELS, contextWindow: 1048576 },
92
- { id: "gemini-2.5-pro", name: "Gemini 2.5 Pro", providerId: "google", reasoningLevels: GEMINI_25_PRO_LEVELS, defaultReasoningLevel: "high", contextWindow: 1048576 },
93
- { id: "gemini-2.5-flash", name: "Gemini 2.5 Flash", providerId: "google", reasoningLevels: GEMINI_25_FLASH_LEVELS, contextWindow: 1048576 },
94
- { id: "glm-5.2", name: "GLM-5.2", providerId: "zhipuai", reasoningLevels: GLM_5_2_LEVELS, contextWindow: 1000000 },
95
- { id: "glm-5.1", name: "GLM-5.1", providerId: "zhipuai", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 200000 },
102
+ { id: "gemini-3.5-flash", name: "Gemini 3.5 Flash", providerId: "google", tier: "fast", reasoningLevels: GEMINI_3_FLASH_LEVELS, contextWindow: 1048576 },
103
+ { id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro", providerId: "google", tier: "strong", reasoningLevels: GEMINI_3_LEVELS, defaultReasoningLevel: "high", contextWindow: 1048576 },
104
+ { id: "gemini-3-flash-preview", name: "Gemini 3 Flash", providerId: "google", tier: "fast", reasoningLevels: GEMINI_3_FLASH_LEVELS, contextWindow: 1048576 },
105
+ { id: "gemini-2.5-pro", name: "Gemini 2.5 Pro", providerId: "google", tier: "strong", reasoningLevels: GEMINI_25_PRO_LEVELS, defaultReasoningLevel: "high", contextWindow: 1048576 },
106
+ { id: "gemini-2.5-flash", name: "Gemini 2.5 Flash", providerId: "google", tier: "fast", reasoningLevels: GEMINI_25_FLASH_LEVELS, contextWindow: 1048576 },
107
+ { id: "glm-5.2", name: "GLM-5.2", providerId: "zhipuai", tier: "strong", reasoningLevels: GLM_5_2_LEVELS, contextWindow: 1000000 },
108
+ { id: "glm-5.1", name: "GLM-5.1", providerId: "zhipuai", tier: "balanced", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 200000 },
96
109
  { id: "glm-4.7", name: "GLM-4.7", providerId: "zhipuai", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 204800 },
97
110
  { id: "glm-4.6", name: "GLM-4.6", providerId: "zhipuai", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 204800 },
98
- { id: "glm-5.2", name: "GLM-5.2", providerId: "zhipuai-coding-plan", reasoningLevels: GLM_5_2_LEVELS, contextWindow: 1000000 },
99
- { id: "glm-5.1", name: "GLM-5.1", providerId: "zhipuai-coding-plan", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 200000 },
111
+ { id: "glm-5.2", name: "GLM-5.2", providerId: "zhipuai-coding-plan", tier: "strong", reasoningLevels: GLM_5_2_LEVELS, contextWindow: 1000000 },
112
+ { id: "glm-5.1", name: "GLM-5.1", providerId: "zhipuai-coding-plan", tier: "balanced", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 200000 },
100
113
  { id: "glm-4.7", name: "GLM-4.7", providerId: "zhipuai-coding-plan", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 204800 },
101
114
  { id: "glm-4.6", name: "GLM-4.6", providerId: "zhipuai-coding-plan", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 204800 },
102
- { id: "glm-5.2", name: "GLM-5.2", providerId: "zai", reasoningLevels: GLM_5_2_LEVELS, contextWindow: 1000000 },
103
- { id: "glm-5.1", name: "GLM-5.1", providerId: "zai", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 200000 },
115
+ { id: "glm-5.2", name: "GLM-5.2", providerId: "zai", tier: "strong", reasoningLevels: GLM_5_2_LEVELS, contextWindow: 1000000 },
116
+ { id: "glm-5.1", name: "GLM-5.1", providerId: "zai", tier: "balanced", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 200000 },
104
117
  { id: "glm-4.7", name: "GLM-4.7", providerId: "zai", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 204800 },
105
118
  { id: "glm-4.6", name: "GLM-4.6", providerId: "zai", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 204800 },
106
- { id: "glm-5.2", name: "GLM-5.2", providerId: "zai-coding-plan", reasoningLevels: GLM_5_2_LEVELS, contextWindow: 1000000 },
107
- { id: "glm-5-turbo", name: "GLM-5-Turbo", providerId: "zai-coding-plan", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 200000 },
119
+ { id: "glm-5.2", name: "GLM-5.2", providerId: "zai-coding-plan", tier: "strong", reasoningLevels: GLM_5_2_LEVELS, contextWindow: 1000000 },
120
+ { id: "glm-5-turbo", name: "GLM-5-Turbo", providerId: "zai-coding-plan", tier: "fast", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 200000 },
108
121
  { id: "glm-4.7", name: "GLM-4.7", providerId: "zai-coding-plan", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 204800 },
109
122
  { id: "glm-4.6", name: "GLM-4.6", providerId: "zai-coding-plan", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 200000 },
110
- { id: "qwen3.6-plus", name: "Qwen3.6 Plus", providerId: "alibaba", reasoningLevels: ["off"], contextWindow: 1048576 },
111
- { id: "qwen3.7-max", name: "Qwen3.7 Max", providerId: "alibaba", reasoningLevels: ["off"], contextWindow: 1048576 },
123
+ { id: "qwen3.6-plus", name: "Qwen3.6 Plus", providerId: "alibaba", tier: "balanced", reasoningLevels: ["off"], contextWindow: 1048576 },
124
+ { id: "qwen3.7-max", name: "Qwen3.7 Max", providerId: "alibaba", tier: "strong", reasoningLevels: ["off"], contextWindow: 1048576 },
112
125
  { id: "doubao-seed-2-1-pro-260628", name: "Doubao Seed 2.1 Pro", providerId: "doubao", reasoningLevels: DOUBAO_SEED_REASONING_LEVELS, defaultReasoningLevel: "high" },
113
- { id: "MiniMax-M3", name: "MiniMax M3", providerId: "minimax", reasoningLevels: MINIMAX_M3_REASONING_LEVELS, contextWindow: 1000000 },
114
- { id: "MiniMax-M2.7", name: "MiniMax M2.7", providerId: "minimax", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
115
- { id: "MiniMax-M2.7-highspeed", name: "MiniMax M2.7 Highspeed", providerId: "minimax", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
116
- { id: "MiniMax-M3", name: "MiniMax M3", providerId: "minimax-anthropic", reasoningLevels: MINIMAX_M3_REASONING_LEVELS, contextWindow: 1000000 },
117
- { id: "MiniMax-M2.7", name: "MiniMax M2.7", providerId: "minimax-anthropic", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
118
- { id: "MiniMax-M2.7-highspeed", name: "MiniMax M2.7 Highspeed", providerId: "minimax-anthropic", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
126
+ { id: "MiniMax-M3", name: "MiniMax M3", providerId: "minimax", tier: "strong", reasoningLevels: MINIMAX_M3_REASONING_LEVELS, contextWindow: 1000000 },
127
+ { id: "MiniMax-M2.7", name: "MiniMax M2.7", providerId: "minimax", tier: "balanced", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
128
+ { id: "MiniMax-M2.7-highspeed", name: "MiniMax M2.7 Highspeed", providerId: "minimax", tier: "fast", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
129
+ { id: "MiniMax-M3", name: "MiniMax M3", providerId: "minimax-anthropic", tier: "strong", reasoningLevels: MINIMAX_M3_REASONING_LEVELS, contextWindow: 1000000 },
130
+ { id: "MiniMax-M2.7", name: "MiniMax M2.7", providerId: "minimax-anthropic", tier: "balanced", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
131
+ { id: "MiniMax-M2.7-highspeed", name: "MiniMax M2.7 Highspeed", providerId: "minimax-anthropic", tier: "fast", reasoningLevels: MINIMAX_REASONING_LEVELS, contextWindow: 204800 },
119
132
  { id: "step-3.7-flash", name: "Step 3.7 Flash", providerId: "stepfun", reasoningLevels: STEPFUN_REASONING_LEVELS, contextWindow: 256000 },
120
133
  { id: "step-3.5-flash-2603", name: "Step 3.5 Flash 2603", providerId: "stepfun", reasoningLevels: STEPFUN_REASONING_LEVELS },
121
134
  { id: "step-3.5-flash", name: "Step 3.5 Flash", providerId: "stepfun", reasoningLevels: STEPFUN_REASONING_LEVELS },
122
135
  { id: "step-router-v1", name: "Step Router V1", providerId: "stepfun", reasoningLevels: STEPFUN_REASONING_LEVELS },
123
- { id: "kimi-k2.7-code", name: "Kimi K2.7 Code", providerId: "moonshot-cn", reasoningLevels: KIMI_THINKING_ONLY_LEVELS, contextWindow: 262144 },
124
- { id: "kimi-k2.7-code-highspeed", name: "Kimi K2.7 Code Highspeed", providerId: "moonshot-cn", reasoningLevels: KIMI_THINKING_ONLY_LEVELS, contextWindow: 262144 },
136
+ { id: "kimi-k2.7-code", name: "Kimi K2.7 Code", providerId: "moonshot-cn", tier: "strong", reasoningLevels: KIMI_THINKING_ONLY_LEVELS, contextWindow: 262144 },
137
+ { id: "kimi-k2.7-code-highspeed", name: "Kimi K2.7 Code Highspeed", providerId: "moonshot-cn", tier: "fast", reasoningLevels: KIMI_THINKING_ONLY_LEVELS, contextWindow: 262144 },
125
138
  { id: "kimi-k2.6", name: "Kimi K2.6", providerId: "moonshot-cn", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 256000 },
126
139
  { id: "kimi-k2.5", name: "Kimi K2.5", providerId: "moonshot-cn", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 256000 },
127
- { id: "kimi-k2.7-code", name: "Kimi K2.7 Code", providerId: "moonshot-intl", reasoningLevels: KIMI_THINKING_ONLY_LEVELS, contextWindow: 262144 },
128
- { id: "kimi-k2.7-code-highspeed", name: "Kimi K2.7 Code Highspeed", providerId: "moonshot-intl", reasoningLevels: KIMI_THINKING_ONLY_LEVELS, contextWindow: 262144 },
140
+ { id: "kimi-k2.7-code", name: "Kimi K2.7 Code", providerId: "moonshot-intl", tier: "strong", reasoningLevels: KIMI_THINKING_ONLY_LEVELS, contextWindow: 262144 },
141
+ { id: "kimi-k2.7-code-highspeed", name: "Kimi K2.7 Code Highspeed", providerId: "moonshot-intl", tier: "fast", reasoningLevels: KIMI_THINKING_ONLY_LEVELS, contextWindow: 262144 },
129
142
  { id: "kimi-k2.6", name: "Kimi K2.6", providerId: "moonshot-intl", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 256000 },
130
143
  { id: "kimi-k2.5", name: "Kimi K2.5", providerId: "moonshot-intl", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 256000 },
131
- { id: "kimi-k2.7-code", name: "Kimi K2.7 Code", providerId: "kimi-for-coding", reasoningLevels: KIMI_THINKING_ONLY_LEVELS, contextWindow: 262144 },
132
- { id: "kimi-k2.7-code-highspeed", name: "Kimi K2.7 Code Highspeed", providerId: "kimi-for-coding", reasoningLevels: KIMI_THINKING_ONLY_LEVELS, contextWindow: 262144 },
144
+ { id: "kimi-k2.7-code", name: "Kimi K2.7 Code", providerId: "kimi-for-coding", tier: "strong", reasoningLevels: KIMI_THINKING_ONLY_LEVELS, contextWindow: 262144 },
145
+ { id: "kimi-k2.7-code-highspeed", name: "Kimi K2.7 Code Highspeed", providerId: "kimi-for-coding", tier: "fast", reasoningLevels: KIMI_THINKING_ONLY_LEVELS, contextWindow: 262144 },
133
146
  { id: "kimi-k2.6", name: "Kimi K2.6", providerId: "kimi-for-coding", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 256000 },
134
147
  { id: "kimi-k2.5", name: "Kimi K2.5", providerId: "kimi-for-coding", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 256000 },
135
148
  { id: "llama-3.3-70b-versatile", name: "llama-3.3-70b-versatile", providerId: "groq", reasoningLevels: ["off"], contextWindow: 32768 },
@@ -154,6 +167,39 @@ function overlayKey(providerId, modelId) {
154
167
  export function registerDynamicModelMetadata(model) {
155
168
  dynamicOverlay.set(overlayKey(model.providerId, model.id), model);
156
169
  }
170
+ export function clearDynamicModelMetadata(providerId) {
171
+ const providerIds = providerId === "openai"
172
+ ? ["openai", "openai-codex"]
173
+ : [providerId];
174
+ for (const key of dynamicOverlay.keys()) {
175
+ if (providerIds.some((id) => key.startsWith(`${id}:`)))
176
+ dynamicOverlay.delete(key);
177
+ }
178
+ }
179
+ export function replaceDynamicModelMetadata(providerId, models) {
180
+ clearDynamicModelMetadata(providerId);
181
+ const acceptedProviderIds = providerId === "openai"
182
+ ? new Set(["openai", "openai-codex"])
183
+ : new Set([providerId]);
184
+ for (const model of models) {
185
+ if (acceptedProviderIds.has(model.providerId))
186
+ registerDynamicModelMetadata(model);
187
+ }
188
+ }
189
+ /**
190
+ * All dynamic-overlay entries for a provider (openai includes its
191
+ * openai-codex catalog alias). The overlay was previously visible only via
192
+ * getBuiltinModel point lookups; the routing snapshot builder needs the list.
193
+ */
194
+ export function listDynamicModelMetadata(providerId) {
195
+ const providerIds = providerId === "openai" ? ["openai", "openai-codex"] : [providerId];
196
+ const out = [];
197
+ for (const [key, model] of dynamicOverlay) {
198
+ if (providerIds.some((id) => key.startsWith(`${id}:`)))
199
+ out.push(model);
200
+ }
201
+ return out;
202
+ }
157
203
  export function getBuiltinModel(providerId, modelId) {
158
204
  const overlayHit = dynamicOverlay.get(overlayKey(providerId, modelId))
159
205
  || (providerId === "openai" ? dynamicOverlay.get(overlayKey("openai-codex", modelId)) : undefined);
@@ -4,7 +4,7 @@
4
4
  * Users can define providers, API keys, base URLs, and custom models
5
5
  * in ~/.bubble/models.json.
6
6
  */
7
- import type { ProviderProtocol } from "./model-catalog.js";
7
+ import type { ModelTier, ProviderProtocol } from "./model-catalog.js";
8
8
  import type { ModelInfo } from "./provider-registry.js";
9
9
  export interface ProviderModelConfig {
10
10
  baseURL?: string;
@@ -13,6 +13,8 @@ export interface ProviderModelConfig {
13
13
  models?: Array<{
14
14
  id: string;
15
15
  name?: string;
16
+ tier?: ModelTier;
17
+ routingPriority?: number;
16
18
  }>;
17
19
  }
18
20
  export interface ModelsConfig {
@@ -8,6 +8,15 @@ import { existsSync, readFileSync } from "node:fs";
8
8
  import { join } from "node:path";
9
9
  import { homedir } from "node:os";
10
10
  const MODELS_PATH = join(homedir(), ".bubble", "models.json");
11
+ const MODEL_TIERS = ["fast", "balanced", "strong"];
12
+ function sanitizeTier(value) {
13
+ return typeof value === "string" && MODEL_TIERS.includes(value)
14
+ ? value
15
+ : undefined;
16
+ }
17
+ function sanitizeRoutingPriority(value) {
18
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
19
+ }
11
20
  export class ModelConfig {
12
21
  data;
13
22
  loadError;
@@ -48,6 +57,8 @@ export class ModelConfig {
48
57
  id: m.id,
49
58
  name: m.name || m.id,
50
59
  providerId,
60
+ tier: sanitizeTier(m.tier),
61
+ routingPriority: sanitizeRoutingPriority(m.routingPriority),
51
62
  }));
52
63
  }
53
64
  getApiKey(providerId) {
@@ -0,0 +1,25 @@
1
+ /**
2
+ * xAI Grok subscription OAuth login (PKCE + local callback).
3
+ *
4
+ * Mirrors the public OAuth client used by the official Grok CLI so a Grok
5
+ * subscription can drive Bubble's native agent loop the same way ChatGPT
6
+ * OAuth does: browser sign-in once, then bearer + refresh tokens stored in
7
+ * ~/.bubble/auth.json.
8
+ */
9
+ import type { OAuthTokens } from "./types.js";
10
+ import { type ChatGptFetch } from "../network/chatgpt-transport.js";
11
+ export interface GrokLoginCallbacks {
12
+ onStatus: (message: string) => void;
13
+ }
14
+ export declare function loginGrok(callbacks?: GrokLoginCallbacks, options?: {
15
+ fetch?: ChatGptFetch;
16
+ }): Promise<OAuthTokens>;
17
+ export declare function refreshGrok(refreshToken: string, options?: {
18
+ fetch?: ChatGptFetch;
19
+ }): Promise<OAuthTokens>;
20
+ /**
21
+ * One-time credential import from the official Grok CLI's auth store (either
22
+ * Bubble's isolated runtime profile or the user's real ~/.grok). Users who
23
+ * already completed `grok login` skip the browser round-trip entirely.
24
+ */
25
+ export declare function importGrokCliCredentials(bubbleHome?: string): OAuthTokens | undefined;