@bubblebrain-ai/bubble 0.0.34 → 0.0.36
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 +27 -7
- package/dist/agent/abort-errors.d.ts +2 -2
- package/dist/agent/budget-ledger.d.ts +4 -45
- package/dist/agent/budget-ledger.js +5 -75
- package/dist/agent/categories.d.ts +66 -2
- package/dist/agent/categories.js +120 -19
- package/dist/agent/child-runner.d.ts +1 -3
- package/dist/agent/child-runner.js +6 -37
- package/dist/agent/discovery-barrier.js +1 -1
- package/dist/agent/profiles.d.ts +1 -2
- package/dist/agent/profiles.js +25 -14
- package/dist/agent/routing-catalog.d.ts +113 -0
- package/dist/agent/routing-catalog.js +240 -0
- package/dist/agent/subagent-control.d.ts +5 -12
- package/dist/agent/subagent-control.js +0 -2
- package/dist/agent/subagent-store.d.ts +1 -1
- package/dist/agent/subagent-store.js +1 -3
- package/dist/agent/workflow/control.d.ts +21 -0
- package/dist/agent/workflow/control.js +50 -8
- package/dist/agent/workflow/runtime.d.ts +15 -2
- package/dist/agent/workflow/runtime.js +112 -30
- package/dist/agent.d.ts +82 -48
- package/dist/agent.js +360 -232
- package/dist/approval/controller.js +28 -0
- package/dist/approval/tool-helper.js +2 -0
- package/dist/approval/types.d.ts +21 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +2 -1
- package/dist/config.d.ts +5 -3
- package/dist/config.js +10 -5
- package/dist/context/compact-llm.d.ts +2 -2
- package/dist/external-runtime/grok-acp.d.ts +56 -0
- package/dist/external-runtime/grok-acp.js +386 -0
- package/dist/external-runtime/grok-binary.d.ts +25 -0
- package/dist/external-runtime/grok-binary.js +108 -0
- package/dist/external-runtime/grok-errors.d.ts +7 -0
- package/dist/external-runtime/grok-errors.js +15 -0
- package/dist/external-runtime/grok-input-policy.d.ts +58 -0
- package/dist/external-runtime/grok-input-policy.js +52 -0
- package/dist/external-runtime/grok-ndjson.d.ts +14 -0
- package/dist/external-runtime/grok-ndjson.js +193 -0
- package/dist/external-runtime/grok-network.d.ts +15 -0
- package/dist/external-runtime/grok-network.js +81 -0
- package/dist/external-runtime/grok-preflight.d.ts +28 -0
- package/dist/external-runtime/grok-preflight.js +194 -0
- package/dist/external-runtime/grok-process.d.ts +26 -0
- package/dist/external-runtime/grok-process.js +184 -0
- package/dist/external-runtime/grok-profile.d.ts +32 -0
- package/dist/external-runtime/grok-profile.js +419 -0
- package/dist/external-runtime/grok-provider.d.ts +31 -0
- package/dist/external-runtime/grok-provider.js +41 -0
- package/dist/external-runtime/grok-runtime.d.ts +91 -0
- package/dist/external-runtime/grok-runtime.js +882 -0
- package/dist/external-runtime/index.d.ts +13 -0
- package/dist/external-runtime/index.js +13 -0
- package/dist/external-runtime/session-policy.d.ts +11 -0
- package/dist/external-runtime/session-policy.js +30 -0
- package/dist/external-runtime/types.d.ts +65 -0
- package/dist/external-runtime/types.js +1 -0
- package/dist/feishu/agent-host/approval-card.js +21 -0
- package/dist/feishu/agent-host/run-driver.js +19 -3
- package/dist/feishu/serve.js +1 -0
- package/dist/goal/tools.d.ts +6 -3
- package/dist/goal/tools.js +7 -19
- package/dist/main.js +114 -28
- package/dist/memory/db.d.ts +2 -0
- package/dist/memory/db.js +10 -0
- package/dist/memory/index.d.ts +1 -0
- package/dist/memory/index.js +1 -0
- package/dist/memory/phase1.js +14 -2
- package/dist/memory/phase2.js +21 -2
- package/dist/memory/prompts.js +1 -1
- package/dist/memory/session-policy.d.ts +25 -0
- package/dist/memory/session-policy.js +55 -0
- package/dist/memory/storage.d.ts +1 -0
- package/dist/memory/storage.js +13 -0
- package/dist/model-catalog.d.ts +18 -0
- package/dist/model-catalog.js +92 -52
- package/dist/model-config.d.ts +3 -1
- package/dist/model-config.js +11 -0
- package/dist/oauth/grok.d.ts +25 -0
- package/dist/oauth/grok.js +218 -0
- package/dist/oauth/index.d.ts +1 -0
- package/dist/oauth/index.js +1 -0
- package/dist/oauth/storage.d.ts +9 -0
- package/dist/oauth/storage.js +25 -0
- package/dist/orchestrator/default-hooks.js +23 -7
- package/dist/prompt/compose.d.ts +3 -0
- package/dist/prompt/compose.js +3 -0
- package/dist/prompt/delegation.d.ts +1 -1
- package/dist/prompt/delegation.js +41 -8
- package/dist/prompt/environment.js +0 -4
- package/dist/prompt/reminders.d.ts +0 -1
- package/dist/prompt/reminders.js +1 -11
- package/dist/prompt/routing.d.ts +14 -0
- package/dist/prompt/routing.js +71 -0
- package/dist/prompt/runtime.js +1 -1
- package/dist/prompt/task-reminders.d.ts +20 -0
- package/dist/prompt/task-reminders.js +73 -1
- package/dist/provider-ai-sdk.js +2 -2
- package/dist/provider-grok.d.ts +26 -0
- package/dist/provider-grok.js +59 -0
- package/dist/provider-openai-codex.d.ts +17 -1
- package/dist/provider-openai-codex.js +101 -29
- package/dist/provider-registry.d.ts +72 -0
- package/dist/provider-registry.js +336 -59
- package/dist/provider-transform.d.ts +1 -1
- package/dist/provider-transform.js +35 -17
- package/dist/provider.d.ts +3 -0
- package/dist/provider.js +14 -0
- package/dist/session-types.d.ts +19 -1
- package/dist/session.d.ts +1 -0
- package/dist/session.js +4 -0
- package/dist/slash-commands/commands.js +224 -14
- package/dist/slash-commands/types.d.ts +14 -1
- package/dist/system-prompt.d.ts +2 -0
- package/dist/tools/agent-lifecycle.d.ts +1 -9
- package/dist/tools/agent-lifecycle.js +42 -272
- package/dist/tools/bash.js +1 -1
- package/dist/tools/child-tools.js +2 -0
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +7 -5
- package/dist/tools/lsp.js +1 -0
- package/dist/tools/memory.d.ts +7 -2
- package/dist/tools/memory.js +28 -43
- package/dist/tools/server.js +4 -0
- package/dist/tools/tool-search.d.ts +1 -1
- package/dist/tools/tool-search.js +23 -3
- package/dist/tui/model-switch.d.ts +7 -0
- package/dist/tui/model-switch.js +15 -5
- package/dist/tui-ink/app.d.ts +3 -1
- package/dist/tui-ink/app.js +672 -243
- package/dist/tui-ink/approval/approval-dialog.js +37 -1
- package/dist/tui-ink/approval/diff-view.js +4 -4
- package/dist/tui-ink/approval/select.js +2 -2
- package/dist/tui-ink/composer-buffer.d.ts +55 -0
- package/dist/tui-ink/composer-buffer.js +317 -0
- package/dist/tui-ink/feedback-dialog.js +2 -2
- package/dist/tui-ink/feishu-setup-picker.js +3 -3
- package/dist/tui-ink/footer.d.ts +1 -0
- package/dist/tui-ink/footer.js +7 -2
- package/dist/tui-ink/input-box.d.ts +36 -7
- package/dist/tui-ink/input-box.js +229 -157
- package/dist/tui-ink/markdown.js +11 -9
- package/dist/tui-ink/message-list.d.ts +5 -1
- package/dist/tui-ink/message-list.js +15 -2
- package/dist/tui-ink/model-picker.d.ts +61 -4
- package/dist/tui-ink/model-picker.js +464 -128
- package/dist/tui-ink/paste-operation-tracker.d.ts +13 -0
- package/dist/tui-ink/paste-operation-tracker.js +25 -0
- package/dist/tui-ink/plan-confirm.js +1 -1
- package/dist/tui-ink/run.d.ts +3 -0
- package/dist/tui-ink/run.js +1 -1
- package/dist/tui-ink/subagent-view.d.ts +34 -1
- package/dist/tui-ink/subagent-view.js +82 -1
- package/dist/tui-ink/theme.d.ts +19 -2
- package/dist/tui-ink/theme.js +25 -8
- package/dist/tui-ink/welcome.d.ts +3 -2
- package/dist/tui-ink/welcome.js +10 -6
- package/dist/types.d.ts +8 -31
- package/dist/types.js +1 -1
- package/dist/variant/thinking-level.d.ts +2 -2
- package/dist/variant/thinking-level.js +2 -1
- package/dist/variant/variant-resolver.d.ts +22 -0
- package/dist/variant/variant-resolver.js +43 -0
- package/package.json +6 -2
- package/dist/tools/task.d.ts +0 -2
- package/dist/tools/task.js +0 -58
|
@@ -4,12 +4,16 @@
|
|
|
4
4
|
* Supports OpenAI-compatible providers with dynamic or static model lists.
|
|
5
5
|
* Reads provider configuration from models.json first, then falls back to config.json.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import { BUILTIN_PROVIDERS as CATALOG_PROVIDERS, clearDynamicModelMetadata, getBuiltinModel, getBuiltinProvider, listBuiltinModels, replaceDynamicModelMetadata, } from "./model-catalog.js";
|
|
8
9
|
import { ModelConfig } from "./model-config.js";
|
|
9
10
|
import { AuthStorage } from "./oauth/index.js";
|
|
10
11
|
import { fetchGeminiModels } from "./provider-ai-sdk.js";
|
|
11
|
-
import {
|
|
12
|
+
import { extractChatGptAccountId, fetchOpenAICodexModelCatalog } from "./provider-openai-codex.js";
|
|
12
13
|
import { refreshOpenAICodex } from "./oauth/openai-codex.js";
|
|
14
|
+
import { refreshGrok } from "./oauth/grok.js";
|
|
15
|
+
const MODEL_DISCOVERY_SUCCESS_TTL_MS = 60_000;
|
|
16
|
+
const MODEL_DISCOVERY_FAILURE_TTL_MS = 10_000;
|
|
13
17
|
export const BUILTIN_PROVIDERS = CATALOG_PROVIDERS;
|
|
14
18
|
export const USER_VISIBLE_PROVIDER_IDS = BUILTIN_PROVIDERS
|
|
15
19
|
.filter((provider) => !provider.hidden && provider.id !== "openrouter" && provider.id !== "openai-codex")
|
|
@@ -21,10 +25,59 @@ export class ProviderRegistry {
|
|
|
21
25
|
config;
|
|
22
26
|
modelConfig;
|
|
23
27
|
authStorage;
|
|
28
|
+
modelDiscoveryCache = new Map();
|
|
29
|
+
modelDiscoveryInFlight = new Map();
|
|
30
|
+
modelDiscoveryGeneration = new Map();
|
|
31
|
+
/** Last membership seen per discovery key — survives cache eviction/TTL so
|
|
32
|
+
* a re-discovery with identical membership never bumps the revision. */
|
|
33
|
+
lastDiscoveryMembership = new Map();
|
|
34
|
+
routingRevision = 0;
|
|
35
|
+
identityFingerprints = new Map();
|
|
24
36
|
constructor(config) {
|
|
25
37
|
this.config = config;
|
|
26
38
|
this.modelConfig = new ModelConfig();
|
|
27
39
|
this.authStorage = new AuthStorage();
|
|
40
|
+
for (const authKey of this.authStorage.list()) {
|
|
41
|
+
const fingerprint = this.credentialFingerprint(authKey);
|
|
42
|
+
if (fingerprint)
|
|
43
|
+
this.identityFingerprints.set(authKey, fingerprint);
|
|
44
|
+
}
|
|
45
|
+
this.authStorage.onMutation((authKey) => this.handleAuthMutation(authKey));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Monotonic counter of semantic routing-world changes: provider set,
|
|
49
|
+
* credential identity, provider key/config, discovery membership.
|
|
50
|
+
* Same-account OAuth token rotation deliberately does NOT bump it
|
|
51
|
+
* (docs/model-routing-design.md §1.6).
|
|
52
|
+
*/
|
|
53
|
+
getRoutingRevision() {
|
|
54
|
+
return this.routingRevision;
|
|
55
|
+
}
|
|
56
|
+
bumpRoutingRevision() {
|
|
57
|
+
this.routingRevision++;
|
|
58
|
+
}
|
|
59
|
+
/** Stable identity for stored credentials: account id, else refresh-token hash. */
|
|
60
|
+
credentialFingerprint(authKey) {
|
|
61
|
+
const credentials = this.authStorage.get(authKey);
|
|
62
|
+
if (!credentials)
|
|
63
|
+
return undefined;
|
|
64
|
+
if (credentials.accountId)
|
|
65
|
+
return credentials.accountId;
|
|
66
|
+
const stable = credentials.refreshToken || credentials.accessToken;
|
|
67
|
+
return createHash("sha256").update(stable).digest("hex").slice(0, 16);
|
|
68
|
+
}
|
|
69
|
+
handleAuthMutation(authKey) {
|
|
70
|
+
const next = this.credentialFingerprint(authKey);
|
|
71
|
+
const prev = this.identityFingerprints.get(authKey);
|
|
72
|
+
if (next === prev)
|
|
73
|
+
return; // same-identity token rotation: not a routing change
|
|
74
|
+
if (next === undefined) {
|
|
75
|
+
this.identityFingerprints.delete(authKey);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
this.identityFingerprints.set(authKey, next);
|
|
79
|
+
}
|
|
80
|
+
this.bumpRoutingRevision();
|
|
28
81
|
}
|
|
29
82
|
getModelConfig() {
|
|
30
83
|
return this.modelConfig;
|
|
@@ -84,6 +137,37 @@ export class ProviderRegistry {
|
|
|
84
137
|
},
|
|
85
138
|
};
|
|
86
139
|
}
|
|
140
|
+
createGrokAuthAdapter(providerId) {
|
|
141
|
+
if (providerId !== "grok" || !this.authStorage.has("grok"))
|
|
142
|
+
return undefined;
|
|
143
|
+
let refreshPromise;
|
|
144
|
+
return {
|
|
145
|
+
getCredentials: () => this.authStorage.get("grok"),
|
|
146
|
+
isExpired: (_credentials, graceMs) => this.authStorage.isExpired("grok", graceMs),
|
|
147
|
+
refreshCredentials: async () => {
|
|
148
|
+
if (!refreshPromise) {
|
|
149
|
+
refreshPromise = (async () => {
|
|
150
|
+
const current = this.authStorage.get("grok");
|
|
151
|
+
if (!current?.refreshToken) {
|
|
152
|
+
throw new Error("Grok OAuth credentials are missing a refresh token. Run /login grok again.");
|
|
153
|
+
}
|
|
154
|
+
const refreshed = await refreshGrok(current.refreshToken);
|
|
155
|
+
const next = {
|
|
156
|
+
type: "oauth",
|
|
157
|
+
accessToken: refreshed.accessToken,
|
|
158
|
+
refreshToken: refreshed.refreshToken,
|
|
159
|
+
expiresAt: refreshed.expiresAt,
|
|
160
|
+
};
|
|
161
|
+
this.authStorage.set("grok", next);
|
|
162
|
+
return next;
|
|
163
|
+
})().finally(() => {
|
|
164
|
+
refreshPromise = undefined;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return refreshPromise;
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
87
171
|
getDefaultModel(providerId, authType = "api") {
|
|
88
172
|
const customModels = this.modelConfig.getCustomModels(providerId);
|
|
89
173
|
if (customModels.length > 0) {
|
|
@@ -95,6 +179,19 @@ export class ProviderRegistry {
|
|
|
95
179
|
return listBuiltinModels(providerId)[0]?.id;
|
|
96
180
|
}
|
|
97
181
|
async prepareProvider(providerId) {
|
|
182
|
+
if (providerId === "grok" && this.authStorage.isExpired("grok")) {
|
|
183
|
+
const creds = this.authStorage.get("grok");
|
|
184
|
+
if (creds?.refreshToken) {
|
|
185
|
+
const refreshed = await refreshGrok(creds.refreshToken);
|
|
186
|
+
this.authStorage.set("grok", {
|
|
187
|
+
type: "oauth",
|
|
188
|
+
accessToken: refreshed.accessToken,
|
|
189
|
+
refreshToken: refreshed.refreshToken,
|
|
190
|
+
expiresAt: refreshed.expiresAt,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
98
195
|
const authKey = this.resolveOAuthAuthKey(providerId);
|
|
99
196
|
if ((providerId === "openai" || providerId === "openai-codex") && this.authStorage.isExpired(authKey)) {
|
|
100
197
|
const creds = this.authStorage.get(authKey);
|
|
@@ -213,11 +310,13 @@ export class ProviderRegistry {
|
|
|
213
310
|
providers.push(profile);
|
|
214
311
|
}
|
|
215
312
|
this.config.setProviders(providers);
|
|
313
|
+
this.bumpRoutingRevision();
|
|
216
314
|
return true;
|
|
217
315
|
}
|
|
218
316
|
removeProvider(id) {
|
|
219
317
|
const providers = this.config.getProviders().filter((p) => p.id !== id);
|
|
220
318
|
this.config.setProviders(providers);
|
|
319
|
+
this.bumpRoutingRevision();
|
|
221
320
|
}
|
|
222
321
|
updateProviderKey(id, apiKey) {
|
|
223
322
|
const providers = this.config.getProviders();
|
|
@@ -225,28 +324,92 @@ export class ProviderRegistry {
|
|
|
225
324
|
if (p) {
|
|
226
325
|
p.apiKey = apiKey;
|
|
227
326
|
this.config.setProviders(providers);
|
|
327
|
+
this.bumpRoutingRevision();
|
|
228
328
|
}
|
|
229
329
|
}
|
|
230
330
|
async listModels(provider) {
|
|
231
|
-
|
|
331
|
+
return (await this.discoverModels(provider)).models;
|
|
332
|
+
}
|
|
333
|
+
async discoverModels(provider, options = {}) {
|
|
334
|
+
const key = this.modelDiscoveryKey(provider);
|
|
335
|
+
const now = Date.now();
|
|
336
|
+
if (!options.forceRefresh) {
|
|
337
|
+
const cached = this.modelDiscoveryCache.get(key);
|
|
338
|
+
if (cached && cached.expiresAt > now) {
|
|
339
|
+
const result = { ...cached.result, source: "cache" };
|
|
340
|
+
const current = this.getConfigured().find((item) => item.id === provider.id);
|
|
341
|
+
if (!current || this.modelDiscoveryKey(current) === key) {
|
|
342
|
+
this.applyDynamicDiscoveryMetadata(provider, result);
|
|
343
|
+
}
|
|
344
|
+
return result;
|
|
345
|
+
}
|
|
346
|
+
const inFlight = this.modelDiscoveryInFlight.get(key);
|
|
347
|
+
if (inFlight)
|
|
348
|
+
return inFlight;
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
this.modelDiscoveryCache.delete(key);
|
|
352
|
+
// Do not let a superseded request's finally-handler remove this retry.
|
|
353
|
+
this.modelDiscoveryInFlight.delete(key);
|
|
354
|
+
}
|
|
355
|
+
const generation = (this.modelDiscoveryGeneration.get(provider.id) ?? 0) + 1;
|
|
356
|
+
this.modelDiscoveryGeneration.set(provider.id, generation);
|
|
357
|
+
let pending;
|
|
358
|
+
pending = this.performModelDiscovery(provider).then((result) => {
|
|
359
|
+
if (!this.isCurrentModelDiscovery(provider, key, generation)) {
|
|
360
|
+
return {
|
|
361
|
+
models: this.localModelsForProvider(this.getConfigured().find((item) => item.id === provider.id) ?? provider),
|
|
362
|
+
source: "fallback",
|
|
363
|
+
authoritative: false,
|
|
364
|
+
error: "Model catalog changed while it was refreshing.",
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
this.applyDynamicDiscoveryMetadata(provider, result);
|
|
368
|
+
const previous = this.lastDiscoveryMembership.get(key);
|
|
369
|
+
this.modelDiscoveryCache.set(key, {
|
|
370
|
+
result: { ...result, source: result.source === "cache" ? "remote" : result.source },
|
|
371
|
+
expiresAt: Date.now() + (result.error ? MODEL_DISCOVERY_FAILURE_TTL_MS : MODEL_DISCOVERY_SUCCESS_TTL_MS),
|
|
372
|
+
identityKey: this.discoveryIdentity(provider),
|
|
373
|
+
});
|
|
374
|
+
this.lastDiscoveryMembership.set(key, {
|
|
375
|
+
ids: result.models.map((model) => model.id),
|
|
376
|
+
authoritative: result.authoritative,
|
|
377
|
+
});
|
|
378
|
+
if (discoveryMembershipChanged(previous, result)) {
|
|
379
|
+
this.bumpRoutingRevision();
|
|
380
|
+
}
|
|
381
|
+
return result;
|
|
382
|
+
}).finally(() => {
|
|
383
|
+
if (this.modelDiscoveryInFlight.get(key) === pending) {
|
|
384
|
+
this.modelDiscoveryInFlight.delete(key);
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
this.modelDiscoveryInFlight.set(key, pending);
|
|
388
|
+
return pending;
|
|
389
|
+
}
|
|
390
|
+
async performModelDiscovery(provider) {
|
|
232
391
|
const customModels = this.modelConfig.getCustomModels(provider.id);
|
|
233
392
|
if (customModels.length > 0) {
|
|
234
|
-
return customModels;
|
|
393
|
+
return { models: customModels, source: "static", authoritative: true };
|
|
235
394
|
}
|
|
236
|
-
// 2. Built-in provider dynamic/static lists
|
|
237
395
|
if (provider.id === "openrouter") {
|
|
238
396
|
try {
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
397
|
+
const response = await fetch("https://openrouter.ai/api/v1/models");
|
|
398
|
+
if (!response.ok)
|
|
399
|
+
throw new Error(`HTTP ${response.status}`);
|
|
400
|
+
const data = (await response.json());
|
|
401
|
+
return {
|
|
402
|
+
models: (data.data ?? []).map((model) => ({
|
|
403
|
+
id: model.id,
|
|
404
|
+
name: model.name || model.id,
|
|
405
|
+
providerId: provider.id,
|
|
406
|
+
})),
|
|
407
|
+
source: "remote",
|
|
408
|
+
authoritative: true,
|
|
409
|
+
};
|
|
247
410
|
}
|
|
248
|
-
catch {
|
|
249
|
-
|
|
411
|
+
catch (error) {
|
|
412
|
+
return this.fallbackDiscovery(provider, error);
|
|
250
413
|
}
|
|
251
414
|
}
|
|
252
415
|
if (provider.id === "google" && provider.protocol === "ai-sdk" && provider.apiKey) {
|
|
@@ -255,68 +418,182 @@ export class ProviderRegistry {
|
|
|
255
418
|
apiKey: provider.apiKey,
|
|
256
419
|
baseURL: provider.baseURL,
|
|
257
420
|
});
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const catalogEntry = getBuiltinModel("google",
|
|
261
|
-
|
|
262
|
-
id:
|
|
263
|
-
name:
|
|
264
|
-
providerId:
|
|
265
|
-
reasoningLevels:
|
|
266
|
-
defaultReasoningLevel:
|
|
267
|
-
contextWindow:
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
|
|
421
|
+
return {
|
|
422
|
+
models: descriptors.map((descriptor) => {
|
|
423
|
+
const catalogEntry = getBuiltinModel("google", descriptor.id);
|
|
424
|
+
return {
|
|
425
|
+
id: descriptor.id,
|
|
426
|
+
name: descriptor.name,
|
|
427
|
+
providerId: provider.id,
|
|
428
|
+
reasoningLevels: descriptor.reasoningLevels,
|
|
429
|
+
defaultReasoningLevel: descriptor.defaultReasoningLevel ?? catalogEntry?.defaultReasoningLevel,
|
|
430
|
+
contextWindow: descriptor.contextWindow ?? catalogEntry?.contextWindow,
|
|
431
|
+
};
|
|
432
|
+
}),
|
|
433
|
+
source: "remote",
|
|
434
|
+
authoritative: true,
|
|
435
|
+
};
|
|
272
436
|
}
|
|
273
|
-
catch {
|
|
274
|
-
|
|
437
|
+
catch (error) {
|
|
438
|
+
return this.fallbackDiscovery(provider, error);
|
|
275
439
|
}
|
|
276
440
|
}
|
|
277
441
|
if (provider.id === "openai" && provider.authType === "oauth" && provider.apiKey) {
|
|
278
442
|
try {
|
|
279
443
|
await this.prepareProvider(provider.id);
|
|
280
|
-
const currentProvider = this.getConfigured().find((
|
|
281
|
-
const
|
|
444
|
+
const currentProvider = this.getConfigured().find((item) => item.id === provider.id) ?? provider;
|
|
445
|
+
const catalog = await fetchOpenAICodexModelCatalog({
|
|
282
446
|
baseURL: currentProvider.baseURL,
|
|
283
447
|
accessToken: currentProvider.apiKey,
|
|
284
448
|
});
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
for (const d of visible) {
|
|
288
|
-
const catalogEntry = getBuiltinModel("openai-codex", d.id);
|
|
289
|
-
registerDynamicModelMetadata({
|
|
290
|
-
id: d.id,
|
|
291
|
-
name: d.displayName || catalogEntry?.name || d.id,
|
|
292
|
-
providerId: "openai-codex",
|
|
293
|
-
reasoningLevels: d.reasoningLevels ?? catalogEntry?.reasoningLevels ?? ["off"],
|
|
294
|
-
contextWindow: d.contextWindow ?? catalogEntry?.contextWindow,
|
|
295
|
-
toolOutputTokenLimit: d.toolOutputTokenLimit ?? catalogEntry?.toolOutputTokenLimit,
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
return visible.map((d) => ({
|
|
299
|
-
id: d.id,
|
|
300
|
-
name: d.displayName || d.id,
|
|
301
|
-
providerId: provider.id,
|
|
302
|
-
}));
|
|
449
|
+
if (catalog.status === "unavailable") {
|
|
450
|
+
throw new Error("OpenAI Codex model catalog is unavailable.");
|
|
303
451
|
}
|
|
452
|
+
const visible = catalog.descriptors.filter((descriptor) => descriptor.visibility !== "hide");
|
|
453
|
+
return {
|
|
454
|
+
models: visible.map((descriptor) => {
|
|
455
|
+
const catalogEntry = getBuiltinModel("openai-codex", descriptor.id);
|
|
456
|
+
return {
|
|
457
|
+
id: descriptor.id,
|
|
458
|
+
name: descriptor.displayName || catalogEntry?.name || descriptor.id,
|
|
459
|
+
providerId: provider.id,
|
|
460
|
+
reasoningLevels: descriptor.reasoningLevels ?? catalogEntry?.reasoningLevels,
|
|
461
|
+
defaultReasoningLevel: descriptor.defaultReasoningLevel ?? catalogEntry?.defaultReasoningLevel,
|
|
462
|
+
contextWindow: descriptor.contextWindow ?? catalogEntry?.contextWindow,
|
|
463
|
+
useResponsesLite: descriptor.useResponsesLite ?? catalogEntry?.useResponsesLite,
|
|
464
|
+
toolOutputTokenLimit: descriptor.toolOutputTokenLimit ?? catalogEntry?.toolOutputTokenLimit,
|
|
465
|
+
};
|
|
466
|
+
}),
|
|
467
|
+
source: "remote",
|
|
468
|
+
authoritative: true,
|
|
469
|
+
};
|
|
304
470
|
}
|
|
305
|
-
catch {
|
|
306
|
-
|
|
471
|
+
catch (error) {
|
|
472
|
+
return this.fallbackDiscovery(provider, error);
|
|
307
473
|
}
|
|
308
|
-
return listBuiltinModels("openai-codex").map((model) => ({
|
|
309
|
-
id: model.id,
|
|
310
|
-
name: model.name,
|
|
311
|
-
providerId: provider.id,
|
|
312
|
-
}));
|
|
313
474
|
}
|
|
314
|
-
return
|
|
475
|
+
return {
|
|
476
|
+
models: this.localModelsForProvider(provider),
|
|
477
|
+
source: "static",
|
|
478
|
+
authoritative: true,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
fallbackDiscovery(provider, error) {
|
|
482
|
+
return {
|
|
483
|
+
models: this.localModelsForProvider(provider),
|
|
484
|
+
source: "fallback",
|
|
485
|
+
authoritative: false,
|
|
486
|
+
error: modelDiscoveryError(error),
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
localModelsForProvider(provider) {
|
|
490
|
+
const customModels = this.modelConfig.getCustomModels(provider.id);
|
|
491
|
+
if (customModels.length > 0)
|
|
492
|
+
return customModels;
|
|
493
|
+
const catalogProviderId = provider.id === "openai" && provider.authType === "oauth"
|
|
494
|
+
? "openai-codex"
|
|
495
|
+
: provider.id;
|
|
496
|
+
return listBuiltinModels(catalogProviderId).map((model) => ({
|
|
315
497
|
id: model.id,
|
|
316
498
|
name: model.name,
|
|
317
499
|
providerId: provider.id,
|
|
500
|
+
reasoningLevels: model.reasoningLevels,
|
|
501
|
+
defaultReasoningLevel: model.defaultReasoningLevel,
|
|
502
|
+
contextWindow: model.contextWindow,
|
|
503
|
+
useResponsesLite: model.useResponsesLite,
|
|
504
|
+
toolOutputTokenLimit: model.toolOutputTokenLimit,
|
|
318
505
|
}));
|
|
319
506
|
}
|
|
507
|
+
discoveryIdentity(provider) {
|
|
508
|
+
if (provider.id === "openai" && provider.authType === "oauth") {
|
|
509
|
+
const credentials = this.authStorage.get(this.resolveOAuthAuthKey(provider.id));
|
|
510
|
+
return credentials?.accountId
|
|
511
|
+
|| extractChatGptAccountId(provider.apiKey)
|
|
512
|
+
|| "unknown-account";
|
|
513
|
+
}
|
|
514
|
+
if (provider.apiKey) {
|
|
515
|
+
return createHash("sha256").update(provider.apiKey).digest("hex").slice(0, 16);
|
|
516
|
+
}
|
|
517
|
+
return "anonymous";
|
|
518
|
+
}
|
|
519
|
+
modelDiscoveryKey(provider) {
|
|
520
|
+
return JSON.stringify([
|
|
521
|
+
provider.id,
|
|
522
|
+
normalizeBaseURL(provider.baseURL),
|
|
523
|
+
provider.authType ?? "api",
|
|
524
|
+
provider.protocol ?? "default",
|
|
525
|
+
this.discoveryIdentity(provider),
|
|
526
|
+
]);
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Read-only view of the cached discovery result for the provider's CURRENT
|
|
530
|
+
* configuration/identity (docs/model-routing-design.md §1.4). Returns
|
|
531
|
+
* undefined when nothing is cached or the cache has expired. `complete` is
|
|
532
|
+
* derived strictly: only a successful full remote catalog qualifies —
|
|
533
|
+
* never static or fallback results, whose internal `authoritative` flag
|
|
534
|
+
* this design does not trust.
|
|
535
|
+
*/
|
|
536
|
+
getCachedDiscoverySnapshot(providerId) {
|
|
537
|
+
const provider = this.getConfigured().find((item) => item.id === providerId);
|
|
538
|
+
if (!provider)
|
|
539
|
+
return undefined;
|
|
540
|
+
const cached = this.modelDiscoveryCache.get(this.modelDiscoveryKey(provider));
|
|
541
|
+
if (!cached || cached.expiresAt <= Date.now())
|
|
542
|
+
return undefined;
|
|
543
|
+
return {
|
|
544
|
+
models: cached.result.models,
|
|
545
|
+
source: cached.result.source,
|
|
546
|
+
complete: cached.result.source === "remote" && cached.result.authoritative && !cached.result.error,
|
|
547
|
+
expiresAt: cached.expiresAt,
|
|
548
|
+
identityKey: cached.identityKey,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
isCurrentModelDiscovery(provider, key, generation) {
|
|
552
|
+
if (this.modelDiscoveryGeneration.get(provider.id) !== generation)
|
|
553
|
+
return false;
|
|
554
|
+
const current = this.getConfigured().find((item) => item.id === provider.id);
|
|
555
|
+
return !current || this.modelDiscoveryKey(current) === key;
|
|
556
|
+
}
|
|
557
|
+
applyDynamicDiscoveryMetadata(provider, result) {
|
|
558
|
+
const dynamicProviderId = provider.id === "openai" && provider.authType === "oauth"
|
|
559
|
+
? "openai-codex"
|
|
560
|
+
: provider.id === "google" && provider.protocol === "ai-sdk"
|
|
561
|
+
? "google"
|
|
562
|
+
: undefined;
|
|
563
|
+
if (!dynamicProviderId)
|
|
564
|
+
return;
|
|
565
|
+
if (!result.authoritative) {
|
|
566
|
+
clearDynamicModelMetadata(dynamicProviderId);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
replaceDynamicModelMetadata(dynamicProviderId, result.models.map((model) => ({
|
|
570
|
+
id: model.id,
|
|
571
|
+
name: model.name,
|
|
572
|
+
providerId: dynamicProviderId,
|
|
573
|
+
// Empty means the remote model exists but declared no trusted effort metadata.
|
|
574
|
+
reasoningLevels: model.reasoningLevels ?? [],
|
|
575
|
+
defaultReasoningLevel: model.defaultReasoningLevel,
|
|
576
|
+
contextWindow: model.contextWindow,
|
|
577
|
+
useResponsesLite: model.useResponsesLite,
|
|
578
|
+
toolOutputTokenLimit: model.toolOutputTokenLimit,
|
|
579
|
+
})));
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function modelDiscoveryError(error) {
|
|
583
|
+
if (error instanceof Error && error.message.trim())
|
|
584
|
+
return error.message;
|
|
585
|
+
return "Remote model catalog is unavailable.";
|
|
586
|
+
}
|
|
587
|
+
/** True when a discovery write changes the routing world: member ids or completeness. */
|
|
588
|
+
function discoveryMembershipChanged(previous, next) {
|
|
589
|
+
if (!previous)
|
|
590
|
+
return true;
|
|
591
|
+
if (previous.authoritative !== next.authoritative)
|
|
592
|
+
return true;
|
|
593
|
+
const previousIds = new Set(previous.ids);
|
|
594
|
+
if (previousIds.size !== next.models.length)
|
|
595
|
+
return true;
|
|
596
|
+
return next.models.some((model) => !previousIds.has(model.id));
|
|
320
597
|
}
|
|
321
598
|
/**
|
|
322
599
|
* Builtin defaults that were captured into stored profiles before a builtin's
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "./types.js";
|
|
2
|
-
export { getAvailableThinkingLevels, getDefaultThinkingLevel, normalizeThinkingLevel } from "./variant/variant-resolver.js";
|
|
2
|
+
export { getAvailableThinkingLevels, getDefaultThinkingLevel, isThinkingOnlyLevels, isThinkingOnlyModel, isThinkingToggleModel, normalizeInheritedThinkingLevel, normalizeThinkingLevel } from "./variant/variant-resolver.js";
|
|
3
3
|
export interface ProviderRequestConfig {
|
|
4
4
|
effectiveThinkingLevel: ThinkingLevel;
|
|
5
5
|
reasoningEffort?: ThinkingLevel;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { getBuiltinModel } from "./model-catalog.js";
|
|
2
|
+
import { getAvailableThinkingLevels, normalizeInheritedThinkingLevel, normalizeThinkingLevel } from "./variant/variant-resolver.js";
|
|
3
|
+
export { getAvailableThinkingLevels, getDefaultThinkingLevel, isThinkingOnlyLevels, isThinkingOnlyModel, isThinkingToggleModel, normalizeInheritedThinkingLevel, normalizeThinkingLevel } from "./variant/variant-resolver.js";
|
|
3
4
|
const MOONSHOT_PROVIDER_IDS = new Set(["moonshot-cn", "moonshot-intl", "kimi-for-coding"]);
|
|
4
|
-
const KIMI_K27_FAMILY = new Set(["kimi-k2.7-code"]);
|
|
5
|
-
const
|
|
6
|
-
const KIMI_THINKING_FAMILY = new Set(["kimi-k2-thinking", "kimi-k2-thinking-turbo"]);
|
|
5
|
+
const KIMI_K27_FAMILY = new Set(["kimi-k2.7-code", "kimi-k2.7-code-highspeed"]);
|
|
6
|
+
const KIMI_TOGGLE_THINKING_FAMILY = new Set(["kimi-k2.5", "kimi-k2.6"]);
|
|
7
7
|
const KIMI_K26_DEFAULT_MAX_TOKENS = 32768;
|
|
8
8
|
const MINIMAX_M3_FAMILY = new Set(["MiniMax-M3"]);
|
|
9
9
|
function isFireworksKimi(providerId, modelId) {
|
|
@@ -15,10 +15,32 @@ function isFireworksKimi(providerId, modelId) {
|
|
|
15
15
|
export function resolveProviderRequestConfig(providerId, modelId, requestedLevel) {
|
|
16
16
|
const supportedLevels = getAvailableThinkingLevels(providerId, modelId);
|
|
17
17
|
const effectiveThinkingLevel = normalizeThinkingLevel(requestedLevel, supportedLevels);
|
|
18
|
-
// ChatGPT OAuth via openai-codex currently rejects explicit reasoning params for this account path.
|
|
19
|
-
// Keep the session/UI state, but don't send reasoning flags on this provider until the protocol is clearer.
|
|
20
18
|
if (providerId === "openai-codex") {
|
|
21
|
-
|
|
19
|
+
const model = getBuiltinModel(providerId, modelId);
|
|
20
|
+
if (!model || model.reasoningLevels.length === 0) {
|
|
21
|
+
return { effectiveThinkingLevel: "off" };
|
|
22
|
+
}
|
|
23
|
+
// Provider serialization is the final defensive boundary. Upstream model
|
|
24
|
+
// selection validates explicit choices, while inherited stale state falls
|
|
25
|
+
// back to the model's declared default here rather than leaking an invalid
|
|
26
|
+
// effort to the ChatGPT Responses endpoint.
|
|
27
|
+
const codexThinkingLevel = normalizeInheritedThinkingLevel(providerId, modelId, requestedLevel);
|
|
28
|
+
return {
|
|
29
|
+
effectiveThinkingLevel: codexThinkingLevel,
|
|
30
|
+
reasoningEffort: codexThinkingLevel !== "off" && model.reasoningLevels.includes(codexThinkingLevel)
|
|
31
|
+
? codexThinkingLevel
|
|
32
|
+
: undefined,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// Grok's CLI chat proxy is OpenAI-compatible; effort rides in the body as
|
|
36
|
+
// `reasoning_effort`, and reasoning arrives back as `reasoning_content`.
|
|
37
|
+
if (providerId === "grok") {
|
|
38
|
+
return {
|
|
39
|
+
effectiveThinkingLevel,
|
|
40
|
+
extraBody: effectiveThinkingLevel === "off"
|
|
41
|
+
? undefined
|
|
42
|
+
: { reasoning_effort: effectiveThinkingLevel },
|
|
43
|
+
};
|
|
22
44
|
}
|
|
23
45
|
if (isFireworksKimi(providerId, modelId)) {
|
|
24
46
|
return {
|
|
@@ -99,12 +121,11 @@ export function resolveProviderRequestConfig(providerId, modelId, requestedLevel
|
|
|
99
121
|
},
|
|
100
122
|
};
|
|
101
123
|
}
|
|
102
|
-
// Moonshot / Kimi:
|
|
103
|
-
//
|
|
104
|
-
// kimi-k2-thinking family locks temperature=1.
|
|
124
|
+
// Moonshot / Kimi: K2.5/K2.6 lock temperature/top_p/n/penalties and expose
|
|
125
|
+
// thinking via extra_body.thinking.
|
|
105
126
|
if (MOONSHOT_PROVIDER_IDS.has(providerId)) {
|
|
106
|
-
//
|
|
107
|
-
// (any explicit value errors), thinking can never be disabled, and
|
|
127
|
+
// Kimi K2.7 Code variants are thinking-only: temperature is locked to 1.0
|
|
128
|
+
// server-side (any explicit value errors), thinking can never be disabled, and
|
|
108
129
|
// reasoning_content must be echoed back on tool-call turns.
|
|
109
130
|
if (KIMI_K27_FAMILY.has(modelId)) {
|
|
110
131
|
return {
|
|
@@ -116,7 +137,7 @@ export function resolveProviderRequestConfig(providerId, modelId, requestedLevel
|
|
|
116
137
|
},
|
|
117
138
|
};
|
|
118
139
|
}
|
|
119
|
-
if (
|
|
140
|
+
if (KIMI_TOGGLE_THINKING_FAMILY.has(modelId)) {
|
|
120
141
|
return {
|
|
121
142
|
effectiveThinkingLevel,
|
|
122
143
|
omitTemperature: true,
|
|
@@ -126,9 +147,6 @@ export function resolveProviderRequestConfig(providerId, modelId, requestedLevel
|
|
|
126
147
|
},
|
|
127
148
|
};
|
|
128
149
|
}
|
|
129
|
-
if (KIMI_THINKING_FAMILY.has(modelId)) {
|
|
130
|
-
return { effectiveThinkingLevel, omitTemperature: true };
|
|
131
|
-
}
|
|
132
150
|
return { effectiveThinkingLevel };
|
|
133
151
|
}
|
|
134
152
|
if (providerId === "openai"
|
package/dist/provider.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Works with OpenRouter, OpenAI, DeepSeek, Google, Groq, Together, and local OpenAI-compatible endpoints.
|
|
5
5
|
*/
|
|
6
6
|
import { type OpenAICodexAuthAdapter } from "./provider-openai-codex.js";
|
|
7
|
+
import { type GrokAuthAdapter } from "./provider-grok.js";
|
|
7
8
|
import type { ProviderProtocol } from "./model-catalog.js";
|
|
8
9
|
import type { Provider, ProviderMessage, StreamChunk, ThinkingLevel } from "./types.js";
|
|
9
10
|
type ReasoningContentEcho = "tool_calls" | "all" | "none" | "minimax";
|
|
@@ -29,6 +30,8 @@ export interface ProviderInstanceOptions {
|
|
|
29
30
|
protocol?: ProviderProtocol;
|
|
30
31
|
/** Dynamic OAuth access-token loader/refresh hook for ChatGPT Codex requests. */
|
|
31
32
|
openAICodexAuth?: OpenAICodexAuthAdapter;
|
|
33
|
+
/** Dynamic OAuth access-token loader/refresh hook for Grok subscription requests. */
|
|
34
|
+
grokAuth?: GrokAuthAdapter;
|
|
32
35
|
}
|
|
33
36
|
export declare function createUnavailableProvider(message: string): Provider;
|
|
34
37
|
export declare function createProviderInstance(options: ProviderInstanceOptions): Provider;
|
package/dist/provider.js
CHANGED
|
@@ -9,6 +9,8 @@ import { createAnthropicMessagesProvider } from "./provider-anthropic.js";
|
|
|
9
9
|
import { createArkResponsesProvider } from "./provider-ark-responses.js";
|
|
10
10
|
import { createAiSdkProvider } from "./provider-ai-sdk.js";
|
|
11
11
|
import { createOpenAICodexProvider, isOpenAICodexBaseUrl } from "./provider-openai-codex.js";
|
|
12
|
+
import { buildGrokSubscriptionHeaders, createGrokSubscriptionFetch, isGrokSubscriptionBaseUrl, } from "./provider-grok.js";
|
|
13
|
+
import { getChatGptFetch } from "./network/chatgpt-transport.js";
|
|
12
14
|
import { createProviderProtocolArtifactFilter } from "./provider-artifacts.js";
|
|
13
15
|
import { resolveProviderRequestConfig } from "./provider-transform.js";
|
|
14
16
|
import { debugReasoningStream, summarizeDebugText } from "./reasoning-debug.js";
|
|
@@ -95,10 +97,22 @@ export function createProviderInstance(options) {
|
|
|
95
97
|
auth: options.openAICodexAuth,
|
|
96
98
|
});
|
|
97
99
|
}
|
|
100
|
+
// Grok subscription rides the generic chat-completions path but needs the
|
|
101
|
+
// CLI identity headers the proxy gates on, plus a fetch that keeps the
|
|
102
|
+
// short-lived OAuth bearer fresh (and proxy-aware) across long sessions.
|
|
103
|
+
const grokSubscription = options.providerId === "grok" || isGrokSubscriptionBaseUrl(options.baseURL);
|
|
98
104
|
const client = new OpenAI({
|
|
99
105
|
apiKey: options.apiKey,
|
|
100
106
|
baseURL: options.baseURL,
|
|
101
107
|
timeout: resolveRequestTimeoutMs(process.env.BUBBLE_PROVIDER_REQUEST_TIMEOUT_MS),
|
|
108
|
+
...(grokSubscription
|
|
109
|
+
? {
|
|
110
|
+
defaultHeaders: buildGrokSubscriptionHeaders(),
|
|
111
|
+
fetch: (options.grokAuth
|
|
112
|
+
? createGrokSubscriptionFetch(options.grokAuth)
|
|
113
|
+
: getChatGptFetch()),
|
|
114
|
+
}
|
|
115
|
+
: {}),
|
|
102
116
|
});
|
|
103
117
|
const fallbackModel = "gpt-4o";
|
|
104
118
|
async function* streamChat(messages, chatOptions) {
|
package/dist/session-types.d.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import type { AssistantMessage, Message, ThinkingLevel, Todo, ToolCall, ToolMessage, UserMessage } from "./types.js";
|
|
2
2
|
import type { GoalState } from "./goal/store.js";
|
|
3
|
+
export interface ExternalRuntimeSessionMetadata {
|
|
4
|
+
/** External agent runtime that owns the conversational state. */
|
|
5
|
+
id: "grok";
|
|
6
|
+
/** Opaque ACP session identifier. Never contains credentials. */
|
|
7
|
+
sessionId?: string;
|
|
8
|
+
/** Server-selected model, when the runtime reports one. */
|
|
9
|
+
modelId?: string;
|
|
10
|
+
/** Reasoning effort selected for the external runtime. */
|
|
11
|
+
reasoningEffort?: ThinkingLevel;
|
|
12
|
+
/** Exact external runtime version used to create the session. */
|
|
13
|
+
version?: string;
|
|
14
|
+
}
|
|
3
15
|
export interface SessionMetadata {
|
|
4
16
|
model?: string;
|
|
5
17
|
thinkingLevel?: ThinkingLevel;
|
|
@@ -12,8 +24,14 @@ export interface SessionMetadata {
|
|
|
12
24
|
promptCacheKey?: string;
|
|
13
25
|
/** Persisted autonomous goal (see src/goal). Survives /session resume. */
|
|
14
26
|
goal?: GoalState;
|
|
27
|
+
/**
|
|
28
|
+
* External runtimes keep their own conversation state. Bubble persists only
|
|
29
|
+
* the opaque session binding and a UI transcript mirror; it never stores the
|
|
30
|
+
* runtime's OAuth credentials.
|
|
31
|
+
*/
|
|
32
|
+
externalRuntime?: ExternalRuntimeSessionMetadata;
|
|
15
33
|
}
|
|
16
|
-
export type SessionMarkerKind = "model_switch" | "provider_switch" | "thinking_level_switch" | "skill_activated" | "mode_switch" | "conversation_clear";
|
|
34
|
+
export type SessionMarkerKind = "model_switch" | "provider_switch" | "thinking_level_switch" | "skill_activated" | "mode_switch" | "runtime_switch" | "conversation_clear";
|
|
17
35
|
interface BaseSessionLogEntry {
|
|
18
36
|
id: string;
|
|
19
37
|
timestamp: number;
|