@bubblebrain-ai/bubble 0.0.46 → 0.0.48
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/dist/agent/child-runner.js +11 -0
- package/dist/agent/internal-reminder-sanitizer.d.ts +7 -0
- package/dist/agent/internal-reminder-sanitizer.js +13 -0
- package/dist/agent/subagent/router.d.ts +65 -0
- package/dist/agent/subagent/router.js +198 -0
- package/dist/agent/subagent/runtime.d.ts +203 -0
- package/dist/agent/subagent/runtime.js +789 -0
- package/dist/agent/subagent-control.d.ts +5 -0
- package/dist/agent/subagent-store.d.ts +18 -2
- package/dist/agent/subagent-store.js +32 -1
- package/dist/agent/tool-intent.d.ts +10 -0
- package/dist/agent/tool-intent.js +18 -0
- package/dist/agent/tool-visibility.d.ts +10 -0
- package/dist/agent/tool-visibility.js +6 -0
- package/dist/agent/workflow/runs.d.ts +72 -0
- package/dist/agent/workflow/runs.js +157 -0
- package/dist/agent.d.ts +78 -96
- package/dist/agent.js +266 -1072
- package/dist/context/compact-llm.js +14 -5
- package/dist/context/compact.d.ts +9 -0
- package/dist/context/compact.js +36 -10
- package/dist/context/compaction-files.d.ts +44 -0
- package/dist/context/compaction-files.js +148 -0
- package/dist/context/llm-compactor.js +24 -5
- package/dist/feishu/agent-host/run-driver.js +3 -0
- package/dist/feishu/serve.js +13 -9
- package/dist/goal/tools.js +12 -3
- package/dist/main.js +24 -19
- package/dist/model-catalog.js +36 -2
- package/dist/model-config.d.ts +2 -0
- package/dist/model-data.generated.d.ts +16 -0
- package/dist/model-data.generated.js +275 -0
- package/dist/model-pricing.d.ts +11 -7
- package/dist/model-pricing.js +0 -0
- package/dist/oauth/grok.d.ts +1 -1
- package/dist/oauth/grok.js +5 -2
- package/dist/oauth/storage.d.ts +9 -2
- package/dist/oauth/storage.js +16 -9
- package/dist/orchestrator/default-hooks.d.ts +20 -0
- package/dist/orchestrator/default-hooks.js +47 -295
- package/dist/orchestrator/hooks.d.ts +6 -39
- package/dist/permission/mode.js +1 -1
- package/dist/prompt/reminders.d.ts +9 -35
- package/dist/prompt/reminders.js +30 -136
- package/dist/prompt/task-reminders.d.ts +0 -31
- package/dist/prompt/task-reminders.js +0 -87
- package/dist/provider-ai-sdk.d.ts +2 -0
- package/dist/provider-ai-sdk.js +1 -0
- package/dist/provider-anthropic.d.ts +2 -0
- package/dist/provider-anthropic.js +3 -1
- package/dist/provider-ark-responses.d.ts +2 -0
- package/dist/provider-ark-responses.js +1 -0
- package/dist/provider-grok.d.ts +26 -0
- package/dist/provider-grok.js +39 -0
- package/dist/provider-registry.d.ts +14 -1
- package/dist/provider-registry.js +131 -3
- package/dist/provider-transform.js +37 -1
- package/dist/provider.d.ts +7 -0
- package/dist/provider.js +13 -1
- package/dist/sdk/index.d.ts +152 -0
- package/dist/sdk/index.js +400 -0
- package/dist/session.js +9 -1
- package/dist/slash-commands/commands.js +33 -19
- package/dist/tools/exit-plan-mode.js +8 -0
- package/dist/tui/detect-theme.d.ts +1 -0
- package/dist/tui/detect-theme.js +11 -5
- package/dist/tui-ink/app.d.ts +2 -6
- package/dist/tui-ink/app.js +11 -18
- package/dist/tui-ink/display-history.d.ts +0 -2
- package/dist/tui-ink/display-history.js +0 -52
- package/dist/tui-ink/footer.js +2 -2
- package/dist/tui-ink/input-box.js +18 -6
- package/dist/tui-ink/message-list.js +62 -23
- package/dist/tui-ink/run.js +10 -5
- package/dist/tui-ink/sync-output.d.ts +17 -0
- package/dist/tui-ink/sync-output.js +35 -0
- package/dist/tui-ink/theme.d.ts +0 -1
- package/dist/tui-ink/theme.js +0 -2
- package/dist/tui-ink/trace-groups.js +1 -1
- package/dist/types.d.ts +21 -1
- package/package.json +16 -2
- package/dist/agent/discovery-barrier.d.ts +0 -21
- package/dist/agent/discovery-barrier.js +0 -173
- package/dist/agent/evidence-tracker.d.ts +0 -15
- package/dist/agent/evidence-tracker.js +0 -93
- package/dist/agent/execution-governor.d.ts +0 -36
- package/dist/agent/execution-governor.js +0 -218
- package/dist/agent/task-classifier.d.ts +0 -3
- package/dist/agent/task-classifier.js +0 -114
- package/dist/agent/task-size.d.ts +0 -9
- package/dist/agent/task-size.js +0 -33
- package/dist/agent/tool-arbiter.d.ts +0 -7
- package/dist/agent/tool-arbiter.js +0 -33
- package/dist/orchestrator/workflow.d.ts +0 -10
- package/dist/orchestrator/workflow.js +0 -22
|
@@ -10,10 +10,58 @@ import { ModelConfig } from "./model-config.js";
|
|
|
10
10
|
import { AuthStorage } from "./oauth/index.js";
|
|
11
11
|
import { fetchGeminiModels } from "./provider-ai-sdk.js";
|
|
12
12
|
import { extractChatGptAccountId, fetchOpenAICodexModelCatalog } from "./provider-openai-codex.js";
|
|
13
|
+
import { fetchGrokSubscriptionModels, inferGrokReasoningLevels } from "./provider-grok.js";
|
|
13
14
|
import { refreshOpenAICodex } from "./oauth/openai-codex.js";
|
|
14
15
|
import { refreshGrok } from "./oauth/grok.js";
|
|
16
|
+
/** True for providers speaking the OpenAI chat-completions protocol (the default). */
|
|
17
|
+
export function isOpenAICompatibleProtocol(protocol) {
|
|
18
|
+
return protocol === undefined || protocol === "openai-chat";
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Ids that are clearly not text chat models. Vendors with one big catalog
|
|
22
|
+
* (alibaba returns 236 entries) would otherwise flood the model picker.
|
|
23
|
+
* Deliberately conservative: vision/preview chat models must pass.
|
|
24
|
+
*/
|
|
25
|
+
const NON_CHAT_ID_PATTERN = /(^|[-_/])(embed|embedding|embeddings|rerank|reranker|tts|asr|whisper|ocr|moderation|guard|image|video|audio|speech|voice|paint|draw)([-_/]|$)/i;
|
|
26
|
+
export function isLikelyChatModelId(id) {
|
|
27
|
+
return !NON_CHAT_ID_PATTERN.test(id);
|
|
28
|
+
}
|
|
29
|
+
/** GET {baseURL}/models, tolerating the common response shapes. */
|
|
30
|
+
async function fetchOpenAICompatibleModelIds(provider) {
|
|
31
|
+
const base = provider.baseURL.replace(/\/+$/, "");
|
|
32
|
+
const response = await fetch(`${base}/models`, {
|
|
33
|
+
headers: {
|
|
34
|
+
Authorization: `Bearer ${provider.apiKey}`,
|
|
35
|
+
...(provider.headers ?? {}),
|
|
36
|
+
},
|
|
37
|
+
signal: AbortSignal.timeout(MODEL_DISCOVERY_TIMEOUT_MS),
|
|
38
|
+
});
|
|
39
|
+
if (!response.ok)
|
|
40
|
+
throw new Error(`HTTP ${response.status}`);
|
|
41
|
+
const payload = (await response.json());
|
|
42
|
+
const entries = payload.data ?? payload.models ?? [];
|
|
43
|
+
return entries
|
|
44
|
+
.filter((entry) => typeof entry?.id === "string" && entry.id.trim().length > 0)
|
|
45
|
+
.map((entry) => ({
|
|
46
|
+
id: entry.id,
|
|
47
|
+
name: typeof entry.name === "string" ? entry.name : undefined,
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
/** Keep only string-valued headers; returns undefined for empty/invalid maps. */
|
|
51
|
+
export function sanitizeProviderHeaders(value) {
|
|
52
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
53
|
+
return undefined;
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
56
|
+
if (typeof raw === "string" && key.trim())
|
|
57
|
+
out[key] = raw;
|
|
58
|
+
}
|
|
59
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
60
|
+
}
|
|
15
61
|
const MODEL_DISCOVERY_SUCCESS_TTL_MS = 60_000;
|
|
16
62
|
const MODEL_DISCOVERY_FAILURE_TTL_MS = 10_000;
|
|
63
|
+
/** Discovery must never delay startup or a model picker for long. */
|
|
64
|
+
const MODEL_DISCOVERY_TIMEOUT_MS = 5_000;
|
|
17
65
|
export const BUILTIN_PROVIDERS = CATALOG_PROVIDERS;
|
|
18
66
|
export const USER_VISIBLE_PROVIDER_IDS = BUILTIN_PROVIDERS
|
|
19
67
|
.filter((provider) => !provider.hidden && provider.id !== "openrouter" && provider.id !== "openai-codex")
|
|
@@ -230,6 +278,7 @@ export class ProviderRegistry {
|
|
|
230
278
|
enabled: true,
|
|
231
279
|
authType: "api",
|
|
232
280
|
protocol: resolveConfiguredProtocol(id, baseURL, cfg.protocol),
|
|
281
|
+
headers: sanitizeProviderHeaders(cfg.headers),
|
|
233
282
|
};
|
|
234
283
|
});
|
|
235
284
|
}
|
|
@@ -241,6 +290,7 @@ export class ProviderRegistry {
|
|
|
241
290
|
...provider,
|
|
242
291
|
baseURL,
|
|
243
292
|
protocol: resolveConfiguredProtocol(provider.id, baseURL, provider.protocol),
|
|
293
|
+
headers: sanitizeProviderHeaders(provider.headers),
|
|
244
294
|
};
|
|
245
295
|
});
|
|
246
296
|
}
|
|
@@ -472,6 +522,79 @@ export class ProviderRegistry {
|
|
|
472
522
|
return this.fallbackDiscovery(provider, error);
|
|
473
523
|
}
|
|
474
524
|
}
|
|
525
|
+
// Grok subscription: the CLI chat proxy gates on the grok-cli identity
|
|
526
|
+
// headers and a fresh OAuth bearer, neither of which the generic path below
|
|
527
|
+
// sends. Discover through the refreshing subscription fetch, then merge the
|
|
528
|
+
// curated catalog with any remote-only ids so newly-released models surface
|
|
529
|
+
// without a code change. Authoritative because the merged list is what the
|
|
530
|
+
// routing catalog and model picker should treat as the account's catalog.
|
|
531
|
+
if (provider.id === "grok" && provider.authType === "oauth" && provider.apiKey) {
|
|
532
|
+
try {
|
|
533
|
+
await this.prepareProvider("grok");
|
|
534
|
+
const currentProvider = this.getConfigured().find((item) => item.id === "grok") ?? provider;
|
|
535
|
+
const grokAuth = this.createGrokAuthAdapter("grok");
|
|
536
|
+
if (!grokAuth)
|
|
537
|
+
throw new Error("Grok OAuth credentials are unavailable.");
|
|
538
|
+
const remote = await fetchGrokSubscriptionModels(currentProvider.baseURL, grokAuth);
|
|
539
|
+
const local = this.localModelsForProvider(currentProvider);
|
|
540
|
+
const known = new Set(local.map((model) => model.id));
|
|
541
|
+
const extras = remote
|
|
542
|
+
.filter((entry) => !known.has(entry.id) && isLikelyChatModelId(entry.id))
|
|
543
|
+
.map((entry) => {
|
|
544
|
+
const inferred = inferGrokReasoningLevels(entry.id);
|
|
545
|
+
return {
|
|
546
|
+
id: entry.id,
|
|
547
|
+
name: entry.name || entry.id,
|
|
548
|
+
providerId: currentProvider.id,
|
|
549
|
+
reasoningLevels: inferred.levels,
|
|
550
|
+
...(inferred.defaultLevel ? { defaultReasoningLevel: inferred.defaultLevel } : {}),
|
|
551
|
+
};
|
|
552
|
+
})
|
|
553
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
554
|
+
return {
|
|
555
|
+
models: [...local, ...extras],
|
|
556
|
+
source: "remote",
|
|
557
|
+
authoritative: true,
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
catch (error) {
|
|
561
|
+
return this.fallbackDiscovery(provider, error);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
// Generic OpenAI-compatible discovery: most vendors expose GET /models on
|
|
565
|
+
// the same base URL. Probed against every configured provider, their lists
|
|
566
|
+
// turned out to be neither complete nor clean — zhipuai omits glm-5.2,
|
|
567
|
+
// stepfun omits step-3.7-flash (both demonstrably usable), alibaba returns
|
|
568
|
+
// 236 entries including image/audio models, fireworks 412s. So the remote
|
|
569
|
+
// list AUGMENTS the curated catalog instead of replacing it: builtin
|
|
570
|
+
// entries always survive (they carry tier/reasoning metadata and some work
|
|
571
|
+
// while unlisted), remote-only ids are appended, obvious non-chat
|
|
572
|
+
// modalities are filtered out. Not authoritative — membership is a union,
|
|
573
|
+
// so nothing downstream may treat it as a closed allowlist.
|
|
574
|
+
if (isOpenAICompatibleProtocol(provider.protocol) && provider.apiKey) {
|
|
575
|
+
try {
|
|
576
|
+
const remote = await fetchOpenAICompatibleModelIds(provider);
|
|
577
|
+
const local = this.localModelsForProvider(provider);
|
|
578
|
+
const known = new Set(local.map((model) => model.id));
|
|
579
|
+
const extras = remote
|
|
580
|
+
.filter((entry) => !known.has(entry.id) && isLikelyChatModelId(entry.id))
|
|
581
|
+
.map((entry) => ({
|
|
582
|
+
id: entry.id,
|
|
583
|
+
name: entry.name || entry.id,
|
|
584
|
+
providerId: provider.id,
|
|
585
|
+
}))
|
|
586
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
587
|
+
if (extras.length === 0) {
|
|
588
|
+
return { models: local, source: "static", authoritative: true };
|
|
589
|
+
}
|
|
590
|
+
return { models: [...local, ...extras], source: "remote", authoritative: false };
|
|
591
|
+
}
|
|
592
|
+
catch {
|
|
593
|
+
// Vendors that don't implement /models (or gate it) fall back silently:
|
|
594
|
+
// the curated catalog is the contract, discovery is a bonus.
|
|
595
|
+
return { models: this.localModelsForProvider(provider), source: "static", authoritative: true };
|
|
596
|
+
}
|
|
597
|
+
}
|
|
475
598
|
return {
|
|
476
599
|
models: this.localModelsForProvider(provider),
|
|
477
600
|
source: "static",
|
|
@@ -559,7 +682,9 @@ export class ProviderRegistry {
|
|
|
559
682
|
? "openai-codex"
|
|
560
683
|
: provider.id === "google" && provider.protocol === "ai-sdk"
|
|
561
684
|
? "google"
|
|
562
|
-
:
|
|
685
|
+
: provider.id === "grok"
|
|
686
|
+
? "grok"
|
|
687
|
+
: undefined;
|
|
563
688
|
if (!dynamicProviderId)
|
|
564
689
|
return;
|
|
565
690
|
if (!result.authoritative) {
|
|
@@ -570,8 +695,11 @@ export class ProviderRegistry {
|
|
|
570
695
|
id: model.id,
|
|
571
696
|
name: model.name,
|
|
572
697
|
providerId: dynamicProviderId,
|
|
573
|
-
//
|
|
574
|
-
|
|
698
|
+
// A remote model with no trusted effort metadata still has to be
|
|
699
|
+
// selectable: default it to the plain "off" level (no reasoning control)
|
|
700
|
+
// rather than an empty ladder, which the model picker would render
|
|
701
|
+
// "unavailable" and skip during arrow-key navigation.
|
|
702
|
+
reasoningLevels: model.reasoningLevels ?? ["off"],
|
|
575
703
|
defaultReasoningLevel: model.defaultReasoningLevel,
|
|
576
704
|
contextWindow: model.contextWindow,
|
|
577
705
|
useResponsesLite: model.useResponsesLite,
|
|
@@ -2,7 +2,17 @@ import { getBuiltinModel } from "./model-catalog.js";
|
|
|
2
2
|
import { getAvailableThinkingLevels, normalizeInheritedThinkingLevel, normalizeThinkingLevel } from "./variant/variant-resolver.js";
|
|
3
3
|
export { getAvailableThinkingLevels, getDefaultThinkingLevel, isThinkingOnlyLevels, isThinkingOnlyModel, isThinkingToggleModel, normalizeInheritedThinkingLevel, normalizeThinkingLevel } from "./variant/variant-resolver.js";
|
|
4
4
|
const MOONSHOT_PROVIDER_IDS = new Set(["moonshot-cn", "moonshot-intl", "kimi-for-coding"]);
|
|
5
|
-
const KIMI_K27_FAMILY = new Set([
|
|
5
|
+
const KIMI_K27_FAMILY = new Set([
|
|
6
|
+
"kimi-k2.7-code",
|
|
7
|
+
"kimi-k2.7-code-highspeed",
|
|
8
|
+
// Coding-plan slot ids for the same generation (GET /coding/v1/models).
|
|
9
|
+
"kimi-for-coding",
|
|
10
|
+
"kimi-for-coding-highspeed",
|
|
11
|
+
]);
|
|
12
|
+
// K3 takes OpenAI-style reasoning_effort (low/high/max, no "medium"); "off"
|
|
13
|
+
// disables thinking via top-level thinking.type. Verified live: effort changes
|
|
14
|
+
// reasoning-token spend, and nested extra_body.thinking is ignored server-side.
|
|
15
|
+
const KIMI_K3_FAMILY = new Set(["k3", "k3-256k", "kimi-k3"]);
|
|
6
16
|
const KIMI_TOGGLE_THINKING_FAMILY = new Set(["kimi-k2.5", "kimi-k2.6"]);
|
|
7
17
|
const KIMI_K26_DEFAULT_MAX_TOKENS = 32768;
|
|
8
18
|
const MINIMAX_M3_FAMILY = new Set(["MiniMax-M3"]);
|
|
@@ -60,6 +70,18 @@ export function resolveProviderRequestConfig(providerId, modelId, requestedLevel
|
|
|
60
70
|
},
|
|
61
71
|
};
|
|
62
72
|
}
|
|
73
|
+
// Bailian token plan speaks OpenAI-compatible reasoning_effort, with "none"
|
|
74
|
+
// as its off value (verified: enable_thinking=false works too, but "none"
|
|
75
|
+
// keeps one parameter for every level). Grades are validated server-side —
|
|
76
|
+
// an unknown value is a 400 — so the catalog's per-model ladder matters.
|
|
77
|
+
if (providerId === "bailian-token-plan") {
|
|
78
|
+
return {
|
|
79
|
+
effectiveThinkingLevel,
|
|
80
|
+
extraBody: {
|
|
81
|
+
reasoning_effort: effectiveThinkingLevel === "off" ? "none" : effectiveThinkingLevel,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
63
85
|
if (providerId === "stepfun") {
|
|
64
86
|
return {
|
|
65
87
|
effectiveThinkingLevel,
|
|
@@ -137,6 +159,20 @@ export function resolveProviderRequestConfig(providerId, modelId, requestedLevel
|
|
|
137
159
|
},
|
|
138
160
|
};
|
|
139
161
|
}
|
|
162
|
+
if (KIMI_K3_FAMILY.has(modelId)) {
|
|
163
|
+
return {
|
|
164
|
+
effectiveThinkingLevel,
|
|
165
|
+
omitTemperature: true,
|
|
166
|
+
reasoningContentEcho: "tool_calls",
|
|
167
|
+
// extraBody spreads onto the request body verbatim. The sibling
|
|
168
|
+
// `reasoningEffort` field is NOT the same thing: the generic chat path
|
|
169
|
+
// turns it into OpenRouter's `reasoning: {enabled: true}`, which drops
|
|
170
|
+
// the grade entirely.
|
|
171
|
+
extraBody: effectiveThinkingLevel === "off"
|
|
172
|
+
? { thinking: { type: "disabled" } }
|
|
173
|
+
: { reasoning_effort: effectiveThinkingLevel },
|
|
174
|
+
};
|
|
175
|
+
}
|
|
140
176
|
if (KIMI_TOGGLE_THINKING_FAMILY.has(modelId)) {
|
|
141
177
|
return {
|
|
142
178
|
effectiveThinkingLevel,
|
package/dist/provider.d.ts
CHANGED
|
@@ -28,6 +28,13 @@ export interface ProviderInstanceOptions {
|
|
|
28
28
|
/** Stable per-session seed for provider prompt caches. */
|
|
29
29
|
promptCacheKey?: string;
|
|
30
30
|
protocol?: ProviderProtocol;
|
|
31
|
+
/**
|
|
32
|
+
* User-configured extra request headers (provider entry `headers` in
|
|
33
|
+
* config.json/models.json). Merged over protocol defaults, so a coding-plan
|
|
34
|
+
* endpoint's client-identity gate (usually a User-Agent allowlist) can be
|
|
35
|
+
* satisfied from config.
|
|
36
|
+
*/
|
|
37
|
+
headers?: Record<string, string>;
|
|
31
38
|
/** Dynamic OAuth access-token loader/refresh hook for ChatGPT Codex requests. */
|
|
32
39
|
openAICodexAuth?: OpenAICodexAuthAdapter;
|
|
33
40
|
/** Dynamic OAuth access-token loader/refresh hook for Grok subscription requests. */
|
package/dist/provider.js
CHANGED
|
@@ -102,13 +102,19 @@ export function createProviderInstance(options) {
|
|
|
102
102
|
// CLI identity headers the proxy gates on, plus a fetch that keeps the
|
|
103
103
|
// short-lived OAuth bearer fresh (and proxy-aware) across long sessions.
|
|
104
104
|
const grokSubscription = options.providerId === "grok" || isGrokSubscriptionBaseUrl(options.baseURL);
|
|
105
|
+
// User-configured headers merge over the built-in identity headers so a
|
|
106
|
+
// config entry can override even the Grok defaults deliberately.
|
|
107
|
+
const defaultHeaders = {
|
|
108
|
+
...(grokSubscription ? buildGrokSubscriptionHeaders() : {}),
|
|
109
|
+
...(options.headers ?? {}),
|
|
110
|
+
};
|
|
105
111
|
const client = new OpenAI({
|
|
106
112
|
apiKey: options.apiKey,
|
|
107
113
|
baseURL: options.baseURL,
|
|
108
114
|
timeout: resolveRequestTimeoutMs(process.env.BUBBLE_PROVIDER_REQUEST_TIMEOUT_MS),
|
|
115
|
+
...(Object.keys(defaultHeaders).length > 0 ? { defaultHeaders } : {}),
|
|
109
116
|
...(grokSubscription
|
|
110
117
|
? {
|
|
111
|
-
defaultHeaders: buildGrokSubscriptionHeaders(),
|
|
112
118
|
fetch: (options.grokAuth
|
|
113
119
|
? createGrokSubscriptionFetch(options.grokAuth)
|
|
114
120
|
: getChatGptFetch()),
|
|
@@ -266,10 +272,16 @@ function shouldRequestStreamUsage(options) {
|
|
|
266
272
|
|| providerId === "deepseek"
|
|
267
273
|
|| providerId === "moonshot-cn"
|
|
268
274
|
|| providerId === "moonshot-intl"
|
|
275
|
+
// Verified 2026-08-04: this endpoint streams NO usage at all without the
|
|
276
|
+
// flag, so every coding-plan session was silently unpriced and left the
|
|
277
|
+
// context-budget anchor guessing.
|
|
278
|
+
|| providerId === "kimi-for-coding"
|
|
269
279
|
|| providerId === "zhipuai"
|
|
270
280
|
|| providerId === "zhipuai-coding-plan"
|
|
271
281
|
|| providerId === "zai"
|
|
272
282
|
|| providerId === "zai-coding-plan"
|
|
283
|
+
// Bailian sends usage either way; ask explicitly so it stays guaranteed.
|
|
284
|
+
|| providerId === "bailian-token-plan"
|
|
273
285
|
|| isMiniMaxOpenAICompatible(options);
|
|
274
286
|
}
|
|
275
287
|
function shouldUseNonStreamingToolCalls(options, tools, toolChoice) {
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bubble SDK — the programmatic embedding surface for external hosts.
|
|
3
|
+
*
|
|
4
|
+
* Two layers:
|
|
5
|
+
* 1. `BubbleSdk` — a high-level facade that owns config/provider resolution and
|
|
6
|
+
* assembles a full agent turn (tools, approvals, questions, plan mode, MCP,
|
|
7
|
+
* skills, hooks, session persistence) behind a single `runTurn()` async
|
|
8
|
+
* iterator of `AgentEvent`s. This is what a host like an Electron app should
|
|
9
|
+
* consume: wire the three interaction callbacks (approval / question / plan)
|
|
10
|
+
* to its own UI and map the event stream to its own message format.
|
|
11
|
+
* 2. Curated re-exports of the underlying building blocks, for hosts that need
|
|
12
|
+
* to assemble a custom loop the facade doesn't cover.
|
|
13
|
+
*
|
|
14
|
+
* Runs under plain Node (>=20); nothing here touches the TUI layer.
|
|
15
|
+
*/
|
|
16
|
+
import { type SessionSummary } from "../session.js";
|
|
17
|
+
import type { ApprovalDecision, ApprovalRequest } from "../approval/types.js";
|
|
18
|
+
import { UserConfig } from "../config.js";
|
|
19
|
+
import { ProviderRegistry } from "../provider-registry.js";
|
|
20
|
+
import { type QuestionAnswer, type QuestionRequest } from "../question/controller.js";
|
|
21
|
+
import { SkillRegistry } from "../skills/registry.js";
|
|
22
|
+
import type { SkillSummary } from "../skills/types.js";
|
|
23
|
+
import type { AgentEvent, ContentPart, Message, PermissionMode, ThinkingLevel } from "../types.js";
|
|
24
|
+
export interface BubbleSdkOptions {
|
|
25
|
+
/** Fallback working directory for sessions created without an explicit cwd. */
|
|
26
|
+
defaultCwd?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Load MCP servers from user/project settings (default true). Hermetic hosts
|
|
29
|
+
* (evals, tests) set false so runs never depend on ambient MCP config.
|
|
30
|
+
*/
|
|
31
|
+
mcp?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** Resolved turn configuration, reported via onStart (e.g. for a host's system-init event). */
|
|
34
|
+
export interface TurnStartInfo {
|
|
35
|
+
providerId: string;
|
|
36
|
+
model: string;
|
|
37
|
+
thinkingLevel: ThinkingLevel;
|
|
38
|
+
mode: PermissionMode;
|
|
39
|
+
tools: string[];
|
|
40
|
+
skills: string[];
|
|
41
|
+
}
|
|
42
|
+
/** Host-side interaction callbacks for one turn. All optional; missing handlers fail safe (reject). */
|
|
43
|
+
export interface TurnHandlers {
|
|
44
|
+
/**
|
|
45
|
+
* Tool call needs user permission (bash / edit / write / …). When the turn is
|
|
46
|
+
* aborted, any still-pending request auto-resolves to reject — the host's
|
|
47
|
+
* promise may settle later and is then ignored.
|
|
48
|
+
*/
|
|
49
|
+
onApproval?: (req: ApprovalRequest) => Promise<ApprovalDecision>;
|
|
50
|
+
/** Agent asked the user a structured question. Return answers, or null to decline. */
|
|
51
|
+
onQuestion?: (req: QuestionRequest) => Promise<QuestionAnswer[] | null>;
|
|
52
|
+
/** Plan-mode proposal. Return true to approve executing the plan. */
|
|
53
|
+
onPlanApproval?: (planMarkdown: string) => Promise<boolean>;
|
|
54
|
+
/** Fired once per turn after provider/model/tools are resolved, before the first event. */
|
|
55
|
+
onStart?: (info: TurnStartInfo) => void;
|
|
56
|
+
}
|
|
57
|
+
export interface RunTurnOptions extends TurnHandlers {
|
|
58
|
+
/** Plain text, or content parts (text + base64 images) for attachments. */
|
|
59
|
+
prompt: string | ContentPart[];
|
|
60
|
+
/** "provider:model" or bare model id (resolved against the default provider). */
|
|
61
|
+
model?: string;
|
|
62
|
+
mode?: PermissionMode;
|
|
63
|
+
thinkingLevel?: ThinkingLevel;
|
|
64
|
+
/**
|
|
65
|
+
* Extra text appended to the built system prompt. Lets hosts (and the eval
|
|
66
|
+
* harness) A/B prompt variants without forking the prompt builder.
|
|
67
|
+
*/
|
|
68
|
+
appendSystemPrompt?: string;
|
|
69
|
+
signal?: AbortSignal;
|
|
70
|
+
}
|
|
71
|
+
export interface SdkSessionRef {
|
|
72
|
+
id: string;
|
|
73
|
+
cwd: string;
|
|
74
|
+
}
|
|
75
|
+
export declare class BubbleSdk {
|
|
76
|
+
readonly userConfig: UserConfig;
|
|
77
|
+
readonly registry: ProviderRegistry;
|
|
78
|
+
private readonly defaultCwd;
|
|
79
|
+
private readonly mcpEnabled;
|
|
80
|
+
private readonly cwdBySession;
|
|
81
|
+
private readonly bashAllowlists;
|
|
82
|
+
private readonly activeTurns;
|
|
83
|
+
private readonly mcpToolsByCwd;
|
|
84
|
+
/** id -> {cwd,file} rebuilt from disk so sessions survive host restarts. */
|
|
85
|
+
private sessionIndex;
|
|
86
|
+
constructor(options?: BubbleSdkOptions);
|
|
87
|
+
listSessions(): SessionSummary[];
|
|
88
|
+
createSession(options?: {
|
|
89
|
+
cwd?: string;
|
|
90
|
+
id?: string;
|
|
91
|
+
}): SdkSessionRef;
|
|
92
|
+
getHistory(sessionId: string): Message[];
|
|
93
|
+
deleteSession(sessionId: string): void;
|
|
94
|
+
/** Abort the in-flight turn of a session, if any. */
|
|
95
|
+
stop(sessionId: string): void;
|
|
96
|
+
listSkills(cwd?: string): SkillSummary[];
|
|
97
|
+
/** Configured providers + default model, for a host's model picker. */
|
|
98
|
+
getModelConfig(): {
|
|
99
|
+
defaultProviderId: string;
|
|
100
|
+
defaultModel: string;
|
|
101
|
+
providers: Array<{
|
|
102
|
+
id: string;
|
|
103
|
+
baseURL?: string;
|
|
104
|
+
hasApiKey: boolean;
|
|
105
|
+
}>;
|
|
106
|
+
};
|
|
107
|
+
runTurn(sessionId: string, options: RunTurnOptions): AsyncGenerator<AgentEvent>;
|
|
108
|
+
private bashAllowlistFor;
|
|
109
|
+
/** MCP servers are started lazily, once per cwd (McpManager has no stop). */
|
|
110
|
+
private mcpToolsFor;
|
|
111
|
+
private resolveSession;
|
|
112
|
+
private resolveProvider;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Attach a `cost` field to turn_end events that carry usage, priced via the
|
|
116
|
+
* static pricing table. Pricing is an accounting concern, so it rides along
|
|
117
|
+
* here in the SDK rather than in the agent core. Multi-step turns emit several
|
|
118
|
+
* turn_end events; each cost covers only that step's usage, so hosts sum them.
|
|
119
|
+
* Events for unpriced models (or without usage) pass through untouched.
|
|
120
|
+
*/
|
|
121
|
+
export declare function attachTurnCost(event: AgentEvent, providerId: string, modelId: string): AgentEvent;
|
|
122
|
+
/**
|
|
123
|
+
* If the prompt (or its first text part) is a "/<skill-name> <task>" skill
|
|
124
|
+
* invocation, rewrite it into the same explicit execution prompt the TUI
|
|
125
|
+
* sends. Non-matching prompts pass through untouched.
|
|
126
|
+
*/
|
|
127
|
+
export declare function rewriteSkillInvocationPrompt(prompt: string | ContentPart[], registry: SkillRegistry): string | ContentPart[];
|
|
128
|
+
export { Agent, AgentAbortError, type AgentOptions, type AgentRunOptions } from "../agent.js";
|
|
129
|
+
export { SessionManager, getSessionsDir, type SessionSummary, type UserTurn, type RewindResult, } from "../session.js";
|
|
130
|
+
export { PermissionAwareApprovalController } from "../approval/controller.js";
|
|
131
|
+
export { BashAllowlist } from "../approval/session-cache.js";
|
|
132
|
+
export type { ApprovalController, ApprovalDecision, ApprovalRequest } from "../approval/types.js";
|
|
133
|
+
export { createAllTools, buildToolPromptOptions, type PlanController } from "../tools/index.js";
|
|
134
|
+
export { buildSystemPrompt } from "../system-prompt.js";
|
|
135
|
+
export { FileStateTracker } from "../tools/file-state.js";
|
|
136
|
+
export { BudgetLedger } from "../agent/budget-ledger.js";
|
|
137
|
+
export { UserConfig } from "../config.js";
|
|
138
|
+
export { ProviderRegistry, encodeModel, decodeModel, displayModel, type ProviderProfile, type ModelInfo, } from "../provider-registry.js";
|
|
139
|
+
export { createProviderInstance } from "../provider.js";
|
|
140
|
+
export { getDefaultThinkingLevel } from "../variant/variant-resolver.js";
|
|
141
|
+
export { QuestionController, QuestionRejectedError, type QuestionAnswer, type QuestionEvent, type QuestionRequest, } from "../question/controller.js";
|
|
142
|
+
export { SkillRegistry } from "../skills/registry.js";
|
|
143
|
+
export { parseSkillInvocation, type SkillInvocation } from "../skills/invocation.js";
|
|
144
|
+
export type { SkillSummary } from "../skills/types.js";
|
|
145
|
+
export { GoalStore } from "../goal/store.js";
|
|
146
|
+
export { McpManager } from "../mcp/manager.js";
|
|
147
|
+
export { loadMcpConfig } from "../mcp/config.js";
|
|
148
|
+
export { ExternalHookController } from "../hooks/controller.js";
|
|
149
|
+
export { calculateUsageCost, getModelPricing, type ModelPricing, type PricingCurrency, } from "../model-pricing.js";
|
|
150
|
+
export { registry as slashCommandRegistry } from "../slash-commands/index.js";
|
|
151
|
+
export type { SlashCommandContext } from "../slash-commands/types.js";
|
|
152
|
+
export type { AgentEvent, ContentPart, Message, PermissionMode, Provider, ThinkingLevel, Todo, TokenUsage, UsageCost, ToolRegistryEntry, ToolResult, ToolUpdate, } from "../types.js";
|