@bubblebrain-ai/bubble 0.0.48 → 0.0.50
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 +17 -0
- package/dist/agent/abort-errors.d.ts +4 -0
- package/dist/agent/abort-errors.js +30 -0
- package/dist/agent/provider-fingerprint.d.ts +6 -0
- package/dist/agent/provider-fingerprint.js +89 -0
- package/dist/agent/resident-history.d.ts +7 -0
- package/dist/agent/resident-history.js +61 -0
- package/dist/agent/transcript-helpers.d.ts +20 -0
- package/dist/agent/transcript-helpers.js +62 -0
- package/dist/agent/update-queue.d.ts +13 -0
- package/dist/agent/update-queue.js +69 -0
- package/dist/agent.d.ts +33 -16
- package/dist/agent.js +90 -300
- package/dist/external-runtime/grok-runtime.js +1 -1
- package/dist/feishu/agent-host/run-driver.js +1 -1
- package/dist/lsp/service.js +2 -2
- package/dist/model-catalog.d.ts +13 -0
- package/dist/model-catalog.js +52 -8
- package/dist/permissions/settings.d.ts +0 -1
- package/dist/permissions/settings.js +0 -2
- package/dist/provider-grok.d.ts +16 -18
- package/dist/provider-grok.js +28 -20
- package/dist/provider-registry.d.ts +9 -0
- package/dist/provider-registry.js +109 -6
- package/dist/provider-transform.js +7 -8
- package/dist/provider.js +11 -0
- package/dist/session-log.js +1 -0
- package/dist/slash-commands/commands.js +13 -9
- package/dist/tools/grep.js +1 -1
- package/dist/tui-ink/app-helpers.d.ts +43 -0
- package/dist/tui-ink/app-helpers.js +150 -0
- package/dist/tui-ink/app.d.ts +4 -11
- package/dist/tui-ink/app.js +104 -635
- package/dist/tui-ink/command-palette.d.ts +32 -0
- package/dist/tui-ink/command-palette.js +158 -0
- package/dist/tui-ink/display-history.d.ts +2 -0
- package/dist/tui-ink/display-reconstruct.d.ts +3 -0
- package/dist/tui-ink/display-reconstruct.js +82 -0
- package/dist/tui-ink/footer.d.ts +14 -3
- package/dist/tui-ink/footer.js +19 -5
- package/dist/tui-ink/rewind-picker.d.ts +8 -0
- package/dist/tui-ink/rewind-picker.js +102 -0
- package/dist/tui-ink/session-picker.js +1 -1
- package/dist/tui-ink/stats-panel.d.ts +11 -0
- package/dist/tui-ink/stats-panel.js +65 -0
- package/dist/tui-ink/waiting-indicator.d.ts +12 -0
- package/dist/tui-ink/waiting-indicator.js +129 -0
- package/dist/tui-ink/wordmark.d.ts +8 -0
- package/dist/tui-ink/wordmark.js +103 -0
- package/dist/types.d.ts +6 -0
- package/package.json +1 -1
|
@@ -25,7 +25,6 @@ const KNOWN_MODES = new Set([
|
|
|
25
25
|
"bypassPermissions",
|
|
26
26
|
]);
|
|
27
27
|
export class SettingsManager {
|
|
28
|
-
cwd;
|
|
29
28
|
paths;
|
|
30
29
|
raw = {
|
|
31
30
|
user: null,
|
|
@@ -34,7 +33,6 @@ export class SettingsManager {
|
|
|
34
33
|
};
|
|
35
34
|
fileDiagnostics = [];
|
|
36
35
|
constructor(cwd, options = {}) {
|
|
37
|
-
this.cwd = cwd;
|
|
38
36
|
const bubbleHome = options.bubbleHome ?? getBubbleHome();
|
|
39
37
|
this.paths = {
|
|
40
38
|
user: join(bubbleHome, "settings.json"),
|
package/dist/provider-grok.d.ts
CHANGED
|
@@ -9,21 +9,10 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { OAuthCredentials } from "./oauth/types.js";
|
|
11
11
|
import { type ChatGptFetch } from "./network/chatgpt-transport.js";
|
|
12
|
-
import type
|
|
12
|
+
import { type ThinkingLevel } from "./types.js";
|
|
13
13
|
export declare const GROK_SUBSCRIPTION_BASE_URL = "https://cli-chat-proxy.grok.com/v1";
|
|
14
14
|
export declare const GROK_CLIENT_VERSION = "0.2.93";
|
|
15
15
|
export declare function isGrokSubscriptionBaseUrl(baseURL: string): boolean;
|
|
16
|
-
/**
|
|
17
|
-
* Infer reasoning levels for a Grok model id the /models endpoint returned
|
|
18
|
-
* without metadata. Flagship reasoning models (grok-N.M) expose
|
|
19
|
-
* low/medium/high with thinking always on — matching the curated grok-4.5
|
|
20
|
-
* entry — while composer/fast/mini/flash variants have no effort control and
|
|
21
|
-
* map to the plain "off" ladder.
|
|
22
|
-
*/
|
|
23
|
-
export declare function inferGrokReasoningLevels(modelId: string): {
|
|
24
|
-
levels: ThinkingLevel[];
|
|
25
|
-
defaultLevel?: ThinkingLevel;
|
|
26
|
-
};
|
|
27
16
|
export declare function buildGrokSubscriptionHeaders(): Record<string, string>;
|
|
28
17
|
export interface GrokAuthAdapter {
|
|
29
18
|
getCredentials: () => OAuthCredentials | undefined | Promise<OAuthCredentials | undefined>;
|
|
@@ -36,17 +25,26 @@ export interface GrokAuthAdapter {
|
|
|
36
25
|
* proactively refreshes) the stored OAuth credentials per request instead.
|
|
37
26
|
*/
|
|
38
27
|
export declare function createGrokSubscriptionFetch(auth: GrokAuthAdapter, baseFetch?: ChatGptFetch): ChatGptFetch;
|
|
28
|
+
export interface GrokModelDescriptor {
|
|
29
|
+
id: string;
|
|
30
|
+
name?: string;
|
|
31
|
+
/** Server-declared context window (the /models `context_window` field). */
|
|
32
|
+
contextWindow?: number;
|
|
33
|
+
/** Server-declared reasoning effort ladder, sorted ascending. */
|
|
34
|
+
reasoningEfforts?: ThinkingLevel[];
|
|
35
|
+
/** Server-declared default effort (the /models `reasoning_effort` field). */
|
|
36
|
+
defaultReasoningEffort?: ThinkingLevel;
|
|
37
|
+
}
|
|
39
38
|
/**
|
|
40
39
|
* Fetch the model list for a Grok subscription account. The CLI chat proxy
|
|
41
40
|
* gates on the grok-cli identity headers plus a fresh OAuth bearer, so this
|
|
42
41
|
* rides the same refreshing fetch used for chat requests instead of a raw
|
|
43
|
-
* global fetch.
|
|
44
|
-
*
|
|
42
|
+
* global fetch. The /models payload carries richer metadata than id+name —
|
|
43
|
+
* `context_window`, `reasoning_efforts`, and the default `reasoning_effort` —
|
|
44
|
+
* which this surfaces so discovery can stop inferring them. Throws on a
|
|
45
|
+
* non-2xx so callers fall back to the curated catalog.
|
|
45
46
|
*/
|
|
46
47
|
export declare function fetchGrokSubscriptionModels(baseURL: string, auth: GrokAuthAdapter, options?: {
|
|
47
48
|
timeoutMs?: number;
|
|
48
49
|
fetch?: ChatGptFetch;
|
|
49
|
-
}): Promise<
|
|
50
|
-
id: string;
|
|
51
|
-
name?: string;
|
|
52
|
-
}>>;
|
|
50
|
+
}): Promise<GrokModelDescriptor[]>;
|
package/dist/provider-grok.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* OAuth bearer fresh across long sessions.
|
|
9
9
|
*/
|
|
10
10
|
import { getChatGptFetch } from "./network/chatgpt-transport.js";
|
|
11
|
+
import { THINKING_LEVELS } from "./types.js";
|
|
11
12
|
export const GROK_SUBSCRIPTION_BASE_URL = "https://cli-chat-proxy.grok.com/v1";
|
|
12
13
|
// The proxy rejects requests below a minimum client version; identify as the
|
|
13
14
|
// same pinned CLI build Bubble verifies elsewhere.
|
|
@@ -17,20 +18,6 @@ export function isGrokSubscriptionBaseUrl(baseURL) {
|
|
|
17
18
|
const normalized = baseURL.trim().replace(/\/+$/, "");
|
|
18
19
|
return normalized === GROK_SUBSCRIPTION_BASE_URL || normalized.startsWith(`${GROK_SUBSCRIPTION_BASE_URL}/`);
|
|
19
20
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Infer reasoning levels for a Grok model id the /models endpoint returned
|
|
22
|
-
* without metadata. Flagship reasoning models (grok-N.M) expose
|
|
23
|
-
* low/medium/high with thinking always on — matching the curated grok-4.5
|
|
24
|
-
* entry — while composer/fast/mini/flash variants have no effort control and
|
|
25
|
-
* map to the plain "off" ladder.
|
|
26
|
-
*/
|
|
27
|
-
export function inferGrokReasoningLevels(modelId) {
|
|
28
|
-
const id = modelId.toLowerCase();
|
|
29
|
-
if (/(composer|fast|mini|flash)/.test(id)) {
|
|
30
|
-
return { levels: ["off"] };
|
|
31
|
-
}
|
|
32
|
-
return { levels: ["low", "medium", "high"], defaultLevel: "high" };
|
|
33
|
-
}
|
|
34
21
|
export function buildGrokSubscriptionHeaders() {
|
|
35
22
|
return {
|
|
36
23
|
"User-Agent": "grok-cli",
|
|
@@ -75,8 +62,10 @@ export function createGrokSubscriptionFetch(auth, baseFetch = getChatGptFetch())
|
|
|
75
62
|
* Fetch the model list for a Grok subscription account. The CLI chat proxy
|
|
76
63
|
* gates on the grok-cli identity headers plus a fresh OAuth bearer, so this
|
|
77
64
|
* rides the same refreshing fetch used for chat requests instead of a raw
|
|
78
|
-
* global fetch.
|
|
79
|
-
*
|
|
65
|
+
* global fetch. The /models payload carries richer metadata than id+name —
|
|
66
|
+
* `context_window`, `reasoning_efforts`, and the default `reasoning_effort` —
|
|
67
|
+
* which this surfaces so discovery can stop inferring them. Throws on a
|
|
68
|
+
* non-2xx so callers fall back to the curated catalog.
|
|
80
69
|
*/
|
|
81
70
|
export async function fetchGrokSubscriptionModels(baseURL, auth, options = {}) {
|
|
82
71
|
const fetchImpl = createGrokSubscriptionFetch(auth, options.fetch);
|
|
@@ -91,8 +80,27 @@ export async function fetchGrokSubscriptionModels(baseURL, auth, options = {}) {
|
|
|
91
80
|
const entries = payload.data ?? payload.models ?? [];
|
|
92
81
|
return entries
|
|
93
82
|
.filter((entry) => typeof entry?.id === "string" && entry.id.trim().length > 0)
|
|
94
|
-
.map((entry) =>
|
|
95
|
-
id
|
|
96
|
-
|
|
97
|
-
|
|
83
|
+
.map((entry) => {
|
|
84
|
+
const id = entry.id;
|
|
85
|
+
const rawEfforts = Array.isArray(entry.reasoning_efforts) ? entry.reasoning_efforts : [];
|
|
86
|
+
const reasoningEfforts = rawEfforts
|
|
87
|
+
.map((effort) => (effort && typeof effort === "object" && typeof effort.value === "string"
|
|
88
|
+
? effort.value
|
|
89
|
+
: undefined))
|
|
90
|
+
.filter((value) => typeof value === "string" && THINKING_LEVELS.includes(value))
|
|
91
|
+
.sort((a, b) => THINKING_LEVELS.indexOf(a) - THINKING_LEVELS.indexOf(b));
|
|
92
|
+
const defaultReasoningEffort = typeof entry.reasoning_effort === "string"
|
|
93
|
+
&& THINKING_LEVELS.includes(entry.reasoning_effort)
|
|
94
|
+
? entry.reasoning_effort
|
|
95
|
+
: undefined;
|
|
96
|
+
return {
|
|
97
|
+
id,
|
|
98
|
+
name: typeof entry.name === "string" ? entry.name : undefined,
|
|
99
|
+
contextWindow: typeof entry.context_window === "number" && entry.context_window > 0
|
|
100
|
+
? entry.context_window
|
|
101
|
+
: undefined,
|
|
102
|
+
reasoningEfforts: reasoningEfforts.length > 0 ? reasoningEfforts : undefined,
|
|
103
|
+
defaultReasoningEffort,
|
|
104
|
+
};
|
|
105
|
+
});
|
|
98
106
|
}
|
|
@@ -77,6 +77,7 @@ export declare class ProviderRegistry {
|
|
|
77
77
|
private modelConfig;
|
|
78
78
|
private authStorage;
|
|
79
79
|
private modelDiscoveryCache;
|
|
80
|
+
private readonly discoveryDiskCachePath;
|
|
80
81
|
private modelDiscoveryInFlight;
|
|
81
82
|
private modelDiscoveryGeneration;
|
|
82
83
|
/** Last membership seen per discovery key — survives cache eviction/TTL so
|
|
@@ -85,6 +86,14 @@ export declare class ProviderRegistry {
|
|
|
85
86
|
private routingRevision;
|
|
86
87
|
private identityFingerprints;
|
|
87
88
|
constructor(config: UserConfig);
|
|
89
|
+
/** The on-disk cache is production-only; disable it under the test runner so
|
|
90
|
+
* tests sharing one isolated BUBBLE_HOME never contaminate each other. */
|
|
91
|
+
private get discoveryDiskCacheEnabled();
|
|
92
|
+
/** Restore a prior successful discovery so dynamic models resolve without a
|
|
93
|
+
* network round-trip right after startup (no fetch on every launch). */
|
|
94
|
+
private loadDiscoveryDiskCache;
|
|
95
|
+
/** Persist successful discoveries so the next launch can reuse them. */
|
|
96
|
+
private saveDiscoveryDiskCache;
|
|
88
97
|
/**
|
|
89
98
|
* Monotonic counter of semantic routing-world changes: provider set,
|
|
90
99
|
* credential identity, provider key/config, discovery membership.
|
|
@@ -5,12 +5,15 @@
|
|
|
5
5
|
* Reads provider configuration from models.json first, then falls back to config.json.
|
|
6
6
|
*/
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
|
-
import {
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
import { getBubbleHome } from "./bubble-home.js";
|
|
11
|
+
import { BUILTIN_PROVIDERS as CATALOG_PROVIDERS, clearDynamicModelMetadata, getBuiltinModel, getBuiltinProvider, inferGrokModelMetadata, listBuiltinModels, replaceDynamicModelMetadata, } from "./model-catalog.js";
|
|
9
12
|
import { ModelConfig } from "./model-config.js";
|
|
10
13
|
import { AuthStorage } from "./oauth/index.js";
|
|
11
14
|
import { fetchGeminiModels } from "./provider-ai-sdk.js";
|
|
12
15
|
import { extractChatGptAccountId, fetchOpenAICodexModelCatalog } from "./provider-openai-codex.js";
|
|
13
|
-
import { fetchGrokSubscriptionModels
|
|
16
|
+
import { fetchGrokSubscriptionModels } from "./provider-grok.js";
|
|
14
17
|
import { refreshOpenAICodex } from "./oauth/openai-codex.js";
|
|
15
18
|
import { refreshGrok } from "./oauth/grok.js";
|
|
16
19
|
/** True for providers speaking the OpenAI chat-completions protocol (the default). */
|
|
@@ -60,6 +63,8 @@ export function sanitizeProviderHeaders(value) {
|
|
|
60
63
|
}
|
|
61
64
|
const MODEL_DISCOVERY_SUCCESS_TTL_MS = 60_000;
|
|
62
65
|
const MODEL_DISCOVERY_FAILURE_TTL_MS = 10_000;
|
|
66
|
+
/** How long a successful discovery survives on disk before a fresh fetch. */
|
|
67
|
+
const MODEL_DISCOVERY_DISK_TTL_MS = 24 * 60 * 60 * 1000;
|
|
63
68
|
/** Discovery must never delay startup or a model picker for long. */
|
|
64
69
|
const MODEL_DISCOVERY_TIMEOUT_MS = 5_000;
|
|
65
70
|
export const BUILTIN_PROVIDERS = CATALOG_PROVIDERS;
|
|
@@ -74,6 +79,7 @@ export class ProviderRegistry {
|
|
|
74
79
|
modelConfig;
|
|
75
80
|
authStorage;
|
|
76
81
|
modelDiscoveryCache = new Map();
|
|
82
|
+
discoveryDiskCachePath = join(getBubbleHome(), "model-discovery-cache.json");
|
|
77
83
|
modelDiscoveryInFlight = new Map();
|
|
78
84
|
modelDiscoveryGeneration = new Map();
|
|
79
85
|
/** Last membership seen per discovery key — survives cache eviction/TTL so
|
|
@@ -91,6 +97,72 @@ export class ProviderRegistry {
|
|
|
91
97
|
this.identityFingerprints.set(authKey, fingerprint);
|
|
92
98
|
}
|
|
93
99
|
this.authStorage.onMutation((authKey) => this.handleAuthMutation(authKey));
|
|
100
|
+
this.loadDiscoveryDiskCache();
|
|
101
|
+
}
|
|
102
|
+
/** The on-disk cache is production-only; disable it under the test runner so
|
|
103
|
+
* tests sharing one isolated BUBBLE_HOME never contaminate each other. */
|
|
104
|
+
get discoveryDiskCacheEnabled() {
|
|
105
|
+
const v = process.env.VITEST?.trim().toLowerCase();
|
|
106
|
+
return v !== "true" && v !== "1";
|
|
107
|
+
}
|
|
108
|
+
/** Restore a prior successful discovery so dynamic models resolve without a
|
|
109
|
+
* network round-trip right after startup (no fetch on every launch). */
|
|
110
|
+
loadDiscoveryDiskCache() {
|
|
111
|
+
if (!this.discoveryDiskCacheEnabled)
|
|
112
|
+
return;
|
|
113
|
+
try {
|
|
114
|
+
if (!existsSync(this.discoveryDiskCachePath))
|
|
115
|
+
return;
|
|
116
|
+
const parsed = JSON.parse(readFileSync(this.discoveryDiskCachePath, "utf8"));
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
for (const [key, entry] of Object.entries(parsed)) {
|
|
119
|
+
if (!entry || typeof entry.expiresAt !== "number" || entry.expiresAt <= now)
|
|
120
|
+
continue;
|
|
121
|
+
if (!entry.result || !Array.isArray(entry.result.models))
|
|
122
|
+
continue;
|
|
123
|
+
this.modelDiscoveryCache.set(key, {
|
|
124
|
+
result: entry.result,
|
|
125
|
+
expiresAt: entry.expiresAt,
|
|
126
|
+
identityKey: entry.identityKey ?? "unknown",
|
|
127
|
+
providerId: entry.providerId ?? "",
|
|
128
|
+
authType: entry.authType,
|
|
129
|
+
protocol: entry.protocol,
|
|
130
|
+
});
|
|
131
|
+
// Rebuild the dynamic overlay so context window / reasoning levels
|
|
132
|
+
// resolve from the cached catalog at startup, not only on /model open.
|
|
133
|
+
if (entry.providerId) {
|
|
134
|
+
this.applyDynamicDiscoveryMetadata({ id: entry.providerId, authType: entry.authType, protocol: entry.protocol }, entry.result);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Missing/corrupt cache is fine; the next discovery re-fetches.
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** Persist successful discoveries so the next launch can reuse them. */
|
|
143
|
+
saveDiscoveryDiskCache() {
|
|
144
|
+
if (!this.discoveryDiskCacheEnabled)
|
|
145
|
+
return;
|
|
146
|
+
try {
|
|
147
|
+
const data = {};
|
|
148
|
+
for (const [key, entry] of this.modelDiscoveryCache) {
|
|
149
|
+
if (entry.result.error)
|
|
150
|
+
continue;
|
|
151
|
+
data[key] = {
|
|
152
|
+
result: entry.result,
|
|
153
|
+
expiresAt: Date.now() + MODEL_DISCOVERY_DISK_TTL_MS,
|
|
154
|
+
identityKey: entry.identityKey,
|
|
155
|
+
providerId: entry.providerId,
|
|
156
|
+
authType: entry.authType,
|
|
157
|
+
protocol: entry.protocol,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
mkdirSync(dirname(this.discoveryDiskCachePath), { recursive: true });
|
|
161
|
+
writeFileSync(this.discoveryDiskCachePath, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// Persistence is best-effort.
|
|
165
|
+
}
|
|
94
166
|
}
|
|
95
167
|
/**
|
|
96
168
|
* Monotonic counter of semantic routing-world changes: provider set,
|
|
@@ -386,7 +458,19 @@ export class ProviderRegistry {
|
|
|
386
458
|
if (!options.forceRefresh) {
|
|
387
459
|
const cached = this.modelDiscoveryCache.get(key);
|
|
388
460
|
if (cached && cached.expiresAt > now) {
|
|
389
|
-
|
|
461
|
+
// A disk cache can outlive the application version that wrote it. Keep
|
|
462
|
+
// the current curated catalog authoritative over stale cached metadata,
|
|
463
|
+
// then retain cached remote-only ids for non-authoritative union results.
|
|
464
|
+
// This lets newly-added builtins surface immediately after an upgrade
|
|
465
|
+
// without waiting up to 24 hours or requiring a manual Ctrl+R refresh.
|
|
466
|
+
const local = this.localModelsForProvider(provider);
|
|
467
|
+
const localIds = new Set(local.map((model) => model.id));
|
|
468
|
+
const models = cached.result.source === "static"
|
|
469
|
+
? local
|
|
470
|
+
: cached.result.authoritative
|
|
471
|
+
? cached.result.models
|
|
472
|
+
: [...local, ...cached.result.models.filter((model) => !localIds.has(model.id))];
|
|
473
|
+
const result = { ...cached.result, models, source: "cache" };
|
|
390
474
|
const current = this.getConfigured().find((item) => item.id === provider.id);
|
|
391
475
|
if (!current || this.modelDiscoveryKey(current) === key) {
|
|
392
476
|
this.applyDynamicDiscoveryMetadata(provider, result);
|
|
@@ -420,7 +504,12 @@ export class ProviderRegistry {
|
|
|
420
504
|
result: { ...result, source: result.source === "cache" ? "remote" : result.source },
|
|
421
505
|
expiresAt: Date.now() + (result.error ? MODEL_DISCOVERY_FAILURE_TTL_MS : MODEL_DISCOVERY_SUCCESS_TTL_MS),
|
|
422
506
|
identityKey: this.discoveryIdentity(provider),
|
|
507
|
+
providerId: provider.id,
|
|
508
|
+
authType: provider.authType,
|
|
509
|
+
protocol: provider.protocol,
|
|
423
510
|
});
|
|
511
|
+
if (!result.error)
|
|
512
|
+
this.saveDiscoveryDiskCache();
|
|
424
513
|
this.lastDiscoveryMembership.set(key, {
|
|
425
514
|
ids: result.models.map((model) => model.id),
|
|
426
515
|
authoritative: result.authoritative,
|
|
@@ -541,13 +630,27 @@ export class ProviderRegistry {
|
|
|
541
630
|
const extras = remote
|
|
542
631
|
.filter((entry) => !known.has(entry.id) && isLikelyChatModelId(entry.id))
|
|
543
632
|
.map((entry) => {
|
|
544
|
-
const inferred =
|
|
633
|
+
const inferred = inferGrokModelMetadata(entry.id);
|
|
634
|
+
// Prefer the server-declared effort ladder and context window over
|
|
635
|
+
// id-based inference; fall back to inference only when the endpoint
|
|
636
|
+
// omitted them (or for ids that don't match a known family).
|
|
637
|
+
const levels = entry.reasoningEfforts && entry.reasoningEfforts.length > 0
|
|
638
|
+
? entry.reasoningEfforts
|
|
639
|
+
: inferred.levels;
|
|
640
|
+
const defaultLevel = entry.defaultReasoningEffort && levels.includes(entry.defaultReasoningEffort)
|
|
641
|
+
? entry.defaultReasoningEffort
|
|
642
|
+
: inferred.defaultLevel;
|
|
545
643
|
return {
|
|
546
644
|
id: entry.id,
|
|
547
645
|
name: entry.name || entry.id,
|
|
548
646
|
providerId: currentProvider.id,
|
|
549
|
-
reasoningLevels:
|
|
550
|
-
...(
|
|
647
|
+
reasoningLevels: levels,
|
|
648
|
+
...(defaultLevel ? { defaultReasoningLevel: defaultLevel } : {}),
|
|
649
|
+
...(entry.contextWindow !== undefined
|
|
650
|
+
? { contextWindow: entry.contextWindow }
|
|
651
|
+
: inferred.contextWindow !== undefined
|
|
652
|
+
? { contextWindow: inferred.contextWindow }
|
|
653
|
+
: {}),
|
|
551
654
|
};
|
|
552
655
|
})
|
|
553
656
|
.sort((a, b) => a.id.localeCompare(b.id));
|
|
@@ -114,16 +114,15 @@ export function resolveProviderRequestConfig(providerId, modelId, requestedLevel
|
|
|
114
114
|
// Zhipu/Z.AI OpenAI-compatible endpoints expose reasoning via a provider-specific
|
|
115
115
|
// `thinking` block rather than OpenAI's `reasoning_effort` shape.
|
|
116
116
|
if (["zhipuai", "zhipuai-coding-plan", "zai", "zai-coding-plan"].includes(providerId)) {
|
|
117
|
-
// GLM-5.2
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
if (modelId === "glm-5.2") {
|
|
117
|
+
// GLM-5.2+ also accept `reasoning_effort`. GLM-5.2 can disable thinking;
|
|
118
|
+
// GLM-5.3 requires thinking and Coding Plan normalizes its effort into
|
|
119
|
+
// low/high/max. The catalog clamps 5.3 choices before this serialization
|
|
120
|
+
// boundary. The effort field rides inside the body alongside `thinking`,
|
|
121
|
+
// so it goes in extraBody, not the OpenRouter-style config field.
|
|
122
|
+
if (modelId === "glm-5.2" || modelId === "glm-5.3") {
|
|
124
123
|
return {
|
|
125
124
|
effectiveThinkingLevel,
|
|
126
|
-
extraBody: effectiveThinkingLevel === "off"
|
|
125
|
+
extraBody: modelId === "glm-5.2" && effectiveThinkingLevel === "off"
|
|
127
126
|
? { thinking: { type: "disabled" } }
|
|
128
127
|
: {
|
|
129
128
|
thinking: { type: "enabled", clear_thinking: false },
|
package/dist/provider.js
CHANGED
|
@@ -397,6 +397,9 @@ function extractBalancedJson(s, start) {
|
|
|
397
397
|
* execute safely.
|
|
398
398
|
*/
|
|
399
399
|
export async function* translateOpenAIFullResponse(response) {
|
|
400
|
+
if (typeof response?.system_fingerprint === "string" && response.system_fingerprint) {
|
|
401
|
+
yield { type: "response_metadata", systemFingerprint: response.system_fingerprint };
|
|
402
|
+
}
|
|
400
403
|
const usageChunk = usageToStreamChunk(response?.usage);
|
|
401
404
|
if (usageChunk)
|
|
402
405
|
yield usageChunk;
|
|
@@ -502,6 +505,7 @@ export async function* translateOpenAIStream(stream, options = {}) {
|
|
|
502
505
|
let reasoningBuffer = "";
|
|
503
506
|
let textBuffer = "";
|
|
504
507
|
let rawChunkSeq = 0;
|
|
508
|
+
let systemFingerprint;
|
|
505
509
|
// DeepSeek (and some inference re-hosts) sometimes deliver reasoning twice:
|
|
506
510
|
// once via a dedicated `reasoning_content` / `thinking` field, and again
|
|
507
511
|
// embedded as `<think>...</think>` inside `delta.content`. Track whether we
|
|
@@ -553,6 +557,13 @@ export async function* translateOpenAIStream(stream, options = {}) {
|
|
|
553
557
|
const delta = choice?.delta;
|
|
554
558
|
const usage = chunk.usage ?? choice?.usage;
|
|
555
559
|
const finishReason = choice?.finish_reason;
|
|
560
|
+
if (typeof chunk?.system_fingerprint === "string"
|
|
561
|
+
&& chunk.system_fingerprint
|
|
562
|
+
&& chunk.system_fingerprint !== systemFingerprint) {
|
|
563
|
+
const nextSystemFingerprint = chunk.system_fingerprint;
|
|
564
|
+
systemFingerprint = nextSystemFingerprint;
|
|
565
|
+
yield { type: "response_metadata", systemFingerprint: nextSystemFingerprint };
|
|
566
|
+
}
|
|
556
567
|
debugReasoningStream({
|
|
557
568
|
stage: "provider_raw",
|
|
558
569
|
providerId: options.debugProviderId,
|
package/dist/session-log.js
CHANGED
|
@@ -203,6 +203,7 @@ function normalizeMessageToEntries(message, id, timestamp) {
|
|
|
203
203
|
providerId: message.providerId,
|
|
204
204
|
modelId: message.modelId,
|
|
205
205
|
usage: message.usage,
|
|
206
|
+
systemFingerprint: message.systemFingerprint,
|
|
206
207
|
error: message.error,
|
|
207
208
|
providerMetadata: sanitizeAssistantProviderMetadata(cloneProviderMetadata(message.providerMetadata)),
|
|
208
209
|
},
|
|
@@ -16,7 +16,7 @@ import { collectUsageStatsBundle, formatStatsText } from "../stats/usage.js";
|
|
|
16
16
|
import { buildMemoryPrompt, getMemoryStatus, isMemoryDisabled, resetMemory, searchMemory, } from "../memory/index.js";
|
|
17
17
|
import { feishuCommand } from "./feishu.js";
|
|
18
18
|
import { GROK_LOCAL_COMMAND_HELP } from "../external-runtime/grok-input-policy.js";
|
|
19
|
-
import { isGrokSubscriptionProviderId
|
|
19
|
+
import { isGrokSubscriptionProviderId } from "../external-runtime/grok-provider.js";
|
|
20
20
|
import { classifyExternalRuntimeBinding } from "../external-runtime/session-policy.js";
|
|
21
21
|
const VALID_SCOPES = ["user", "project", "local"];
|
|
22
22
|
const VALID_LISTS = ["allow", "deny"];
|
|
@@ -107,7 +107,7 @@ function persistSelectedModel(model, ctx) {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
function syncSystemPrompt(ctx, model) {
|
|
110
|
-
const { providerId
|
|
110
|
+
const { providerId } = decodeModel(model);
|
|
111
111
|
const toolPromptOptions = typeof ctx.agent.getSystemPromptToolOptions === "function"
|
|
112
112
|
? ctx.agent.getSystemPromptToolOptions()
|
|
113
113
|
: {};
|
|
@@ -412,14 +412,14 @@ const builtinSlashCommandEntries = [
|
|
|
412
412
|
{
|
|
413
413
|
name: "skills",
|
|
414
414
|
description: "Open the searchable skills picker",
|
|
415
|
-
async handler(
|
|
415
|
+
async handler(_args, ctx) {
|
|
416
416
|
ctx.openPicker("skill");
|
|
417
417
|
},
|
|
418
418
|
},
|
|
419
419
|
{
|
|
420
420
|
name: "help",
|
|
421
421
|
description: "Show available slash commands",
|
|
422
|
-
async handler(
|
|
422
|
+
async handler(_args, ctx) {
|
|
423
423
|
if (hasExternalRuntimeSession(ctx)) {
|
|
424
424
|
const grok = isGrokSessionActive(ctx);
|
|
425
425
|
return [
|
|
@@ -448,14 +448,14 @@ const builtinSlashCommandEntries = [
|
|
|
448
448
|
{
|
|
449
449
|
name: "context",
|
|
450
450
|
description: "Show current context window usage and breakdown",
|
|
451
|
-
async handler(
|
|
451
|
+
async handler(_args, ctx) {
|
|
452
452
|
return `${formatContextUsage(ctx.agent.getContextUsageSnapshot())}\n\n${formatMcpContextStatus(ctx)}`;
|
|
453
453
|
},
|
|
454
454
|
},
|
|
455
455
|
{
|
|
456
456
|
name: "quit",
|
|
457
457
|
description: "Exit the application",
|
|
458
|
-
async handler(
|
|
458
|
+
async handler(_args, ctx) {
|
|
459
459
|
ctx.exit();
|
|
460
460
|
},
|
|
461
461
|
},
|
|
@@ -482,8 +482,12 @@ const builtinSlashCommandEntries = [
|
|
|
482
482
|
{
|
|
483
483
|
name: "clear",
|
|
484
484
|
description: "Clear the current conversation history",
|
|
485
|
-
async handler(
|
|
485
|
+
async handler(_args, ctx) {
|
|
486
486
|
ctx.agent.messages = ctx.agent.messages.filter((m) => m.role === "system" || m.role === "meta");
|
|
487
|
+
// The resident history just shrank: drop the incremental usage anchor
|
|
488
|
+
// too (same as /compact and /rewind), or budget accounting keeps
|
|
489
|
+
// measuring against the pre-clear message count.
|
|
490
|
+
ctx.agent.resetContextUsageAnchor();
|
|
487
491
|
ctx.sessionManager?.appendMarker("conversation_clear", "");
|
|
488
492
|
ctx.sessionManager?.clearTitleMetadata?.();
|
|
489
493
|
if (ctx.agent.getTodos().length > 0) {
|
|
@@ -845,7 +849,7 @@ const builtinSlashCommandEntries = [
|
|
|
845
849
|
{
|
|
846
850
|
name: "plan",
|
|
847
851
|
description: "Toggle plan mode on/off (Tab switches Build/Plan)",
|
|
848
|
-
async handler(
|
|
852
|
+
async handler(_args, ctx) {
|
|
849
853
|
const next = ctx.agent.mode === "plan" ? "default" : "plan";
|
|
850
854
|
ctx.agent.setMode(next);
|
|
851
855
|
return next === "plan"
|
|
@@ -1069,7 +1073,7 @@ const builtinSlashCommandEntries = [
|
|
|
1069
1073
|
{
|
|
1070
1074
|
name: "compact",
|
|
1071
1075
|
description: "Manually compact the current session context",
|
|
1072
|
-
async handler(
|
|
1076
|
+
async handler(_args, ctx) {
|
|
1073
1077
|
if (!ctx.sessionManager) {
|
|
1074
1078
|
return "Compaction requires session persistence. Start an interactive session first.";
|
|
1075
1079
|
}
|
package/dist/tools/grep.js
CHANGED
|
@@ -54,7 +54,7 @@ export function createGrepTool(cwd) {
|
|
|
54
54
|
}
|
|
55
55
|
rgArgs.push(searchPath);
|
|
56
56
|
return new Promise((resolve) => {
|
|
57
|
-
execFile("rg", rgArgs, { cwd, maxBuffer: 10 * 1024 * 1024 }, (
|
|
57
|
+
execFile("rg", rgArgs, { cwd, maxBuffer: 10 * 1024 * 1024 }, (_error, stdout, _stderr) => {
|
|
58
58
|
// rg returns exit code 1 when no matches found, which is not an error for us
|
|
59
59
|
const lines = stdout.split("\n").filter((l) => l.trim() !== "");
|
|
60
60
|
const matches = [];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Agent } from "../agent.js";
|
|
2
|
+
import { type ProviderRegistry } from "../provider-registry.js";
|
|
3
|
+
import type { BackgroundTaskInfo } from "../tasks/manager.js";
|
|
4
|
+
import { type DisplayMessage } from "./display-history.js";
|
|
5
|
+
export declare const TASK_WAKE_DEBOUNCE_MS = 2000;
|
|
6
|
+
export declare function buildTips(agent: Agent, registry: ProviderRegistry, grokActive?: boolean): string[];
|
|
7
|
+
/**
|
|
8
|
+
* Slash-command results arrive as plain strings; recognize the /model
|
|
9
|
+
* confirmations by shape so they render as accent-colored UI notices — the
|
|
10
|
+
* same treatment the picker path applies — instead of assistant prose.
|
|
11
|
+
*/
|
|
12
|
+
export declare function slashResultNoticeKind(result: string): DisplayMessage["syntheticKind"] | undefined;
|
|
13
|
+
/** Status-row summary for running background tasks (design §2.5). */
|
|
14
|
+
export declare function taskRowSummary(tasks: BackgroundTaskInfo[], nowTick: number): string;
|
|
15
|
+
export declare function friendlyCwd(cwd: string): string;
|
|
16
|
+
export declare function sessionBasename(sessionFile: string | undefined): string | undefined;
|
|
17
|
+
export declare function truncate(value: string, max: number): string;
|
|
18
|
+
export declare function formatCompactTokens(n: number): string;
|
|
19
|
+
export declare function formatContextUsageLabel(snapshot: {
|
|
20
|
+
usedTokens: number;
|
|
21
|
+
contextWindow?: number;
|
|
22
|
+
}): string;
|
|
23
|
+
/**
|
|
24
|
+
* Streaming tool arguments arrive as an incomplete JSON buffer. We can't
|
|
25
|
+
* JSON.parse() until the closing brace lands, but the user wants to see the
|
|
26
|
+
* short identifying fields (path, command, …) as soon as the model emits
|
|
27
|
+
* them so the tool row header reflects what's happening.
|
|
28
|
+
*
|
|
29
|
+
* Intentionally limited to short, single-line fields. Long fields like
|
|
30
|
+
* `content` are *not* surfaced live: rendering thousands of partial lines
|
|
31
|
+
* per delta floods the terminal and the partial value can break around
|
|
32
|
+
* unescaped sequences. The final value lands when the tool actually
|
|
33
|
+
* executes and tool_start delivers canonical args.
|
|
34
|
+
*/
|
|
35
|
+
export declare function parsePartialArgs(buffer: string, previous: Record<string, any>): Record<string, any>;
|
|
36
|
+
/**
|
|
37
|
+
* Coerce a freshly-constructed DisplayMessage into one that carries a stable
|
|
38
|
+
* `key`. Centralizes the safety net so callers don't have to remember to call
|
|
39
|
+
* nextDisplayMessageKey on every push.
|
|
40
|
+
*/
|
|
41
|
+
export declare function withMessageKey(message: DisplayMessage): DisplayMessage;
|
|
42
|
+
export declare const STREAMING_FLUSH_INTERVAL_MS = 40;
|
|
43
|
+
export declare function clampWindowStartForIndex(total: number, selectedIndex: number, maxVisible: number): number;
|