@bubblebrain-ai/bubble 0.0.46 → 0.0.47
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-registry.d.ts +13 -0
- package/dist/provider-registry.js +83 -0
- 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/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
package/dist/prompt/reminders.js
CHANGED
|
@@ -9,12 +9,21 @@
|
|
|
9
9
|
export function wrapInSystemReminder(content) {
|
|
10
10
|
return content.trim();
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Headlines of the mode reminders below. Identity is content-based because a
|
|
14
|
+
* reminder is just text once it lands in the message stream; keeping the
|
|
15
|
+
* headlines in one place stops the matcher from drifting when the bodies are
|
|
16
|
+
* reworded.
|
|
17
|
+
*/
|
|
18
|
+
const MODE_REMINDER_HEADLINES = [
|
|
19
|
+
"Plan mode is now ACTIVE",
|
|
20
|
+
"Permission mode is now: bypassPermissions",
|
|
21
|
+
"Permission mode is now: default Build mode",
|
|
22
|
+
];
|
|
12
23
|
export function isPermissionModeReminder(content) {
|
|
13
24
|
if (typeof content !== "string")
|
|
14
25
|
return false;
|
|
15
|
-
return content.includes(
|
|
16
|
-
|| content.includes("Permission mode is now: bypassPermissions")
|
|
17
|
-
|| content.includes("Permission mode is now: default Build mode");
|
|
26
|
+
return MODE_REMINDER_HEADLINES.some((headline) => content.includes(headline));
|
|
18
27
|
}
|
|
19
28
|
const PLAN_MODE_ENTER = `
|
|
20
29
|
Plan mode is now ACTIVE.
|
|
@@ -32,6 +41,7 @@ Rules while in plan mode:
|
|
|
32
41
|
const BYPASS_ENTER = `
|
|
33
42
|
Permission mode is now: bypassPermissions.
|
|
34
43
|
|
|
44
|
+
This replaces every earlier mode reminder in this conversation. Plan mode is NOT active; exit_plan_mode does not exist in your tool list and must not be called or searched for.
|
|
35
45
|
ALL tool calls auto-approve with no user confirmation. The user has explicitly opted into this.
|
|
36
46
|
Proceed with extra care — explain risky actions in the chat BEFORE performing them, and
|
|
37
47
|
prefer reversible operations when possible.
|
|
@@ -40,6 +50,7 @@ Do not perform destructive operations, credential exposure, or unrelated reversi
|
|
|
40
50
|
const DEFAULT_ENTER = `
|
|
41
51
|
Permission mode is now: default Build mode.
|
|
42
52
|
|
|
53
|
+
This replaces every earlier mode reminder in this conversation. Plan mode is NOT active; exit_plan_mode does not exist in your tool list and must not be called or searched for.
|
|
43
54
|
File edits and writes auto-approve. Bash commands and other destructive tools still require explicit approval unless allowed by rules.
|
|
44
55
|
Execute the requested change end to end; do not stop at analysis unless blocked or the user explicitly asks for discussion only.
|
|
45
56
|
`;
|
|
@@ -72,75 +83,6 @@ export function buildDeferredToolsReminder(names) {
|
|
|
72
83
|
];
|
|
73
84
|
return wrapInSystemReminder(lines.join("\n"));
|
|
74
85
|
}
|
|
75
|
-
export function buildInvestigationReminder() {
|
|
76
|
-
return wrapInSystemReminder(`
|
|
77
|
-
Security/configuration investigation workflow is active.
|
|
78
|
-
|
|
79
|
-
For this task, gather evidence in this order:
|
|
80
|
-
- locate config load paths
|
|
81
|
-
- locate environment variable reads
|
|
82
|
-
- locate persistent storage paths
|
|
83
|
-
- check whether sensitive values are masked or redacted
|
|
84
|
-
- check whether values can reach logs, client bundles, or user-visible surfaces
|
|
85
|
-
|
|
86
|
-
Stop once these categories are covered. Do not keep repeating near-identical searches when they are not producing new evidence.
|
|
87
|
-
`);
|
|
88
|
-
}
|
|
89
|
-
export function buildLoopWarningReminder(reason) {
|
|
90
|
-
return wrapInSystemReminder(`
|
|
91
|
-
Further broad exploration is low value unless there is a concrete remaining evidence gap.
|
|
92
|
-
|
|
93
|
-
${reason}
|
|
94
|
-
|
|
95
|
-
Do not repeat near-identical reads or searches unless the path or hypothesis is materially different.
|
|
96
|
-
If current evidence is sufficient, answer with the findings.
|
|
97
|
-
`);
|
|
98
|
-
}
|
|
99
|
-
export function buildSearchFreezeReminder(reason) {
|
|
100
|
-
return wrapInSystemReminder(`
|
|
101
|
-
Search tools are now constrained for this task.
|
|
102
|
-
|
|
103
|
-
Reason: ${reason}
|
|
104
|
-
|
|
105
|
-
Do not continue blind keyword searching. Use the evidence already gathered to reason about the answer.
|
|
106
|
-
You may still read specific files if you already know where the relevant configuration or persistence logic lives.
|
|
107
|
-
`);
|
|
108
|
-
}
|
|
109
|
-
export function buildExplorationFreezeReminder(reason) {
|
|
110
|
-
return wrapInSystemReminder(`
|
|
111
|
-
Implementation phase has advanced from exploration to modification.
|
|
112
|
-
|
|
113
|
-
Reason: ${reason}
|
|
114
|
-
|
|
115
|
-
You have enough context to act. Do not continue reading, searching, or delegating exploration.
|
|
116
|
-
Choose one of:
|
|
117
|
-
1. Use edit/write to make the requested change.
|
|
118
|
-
2. If no safe change can be made from the gathered context, explain the concrete blocker.
|
|
119
|
-
3. If files were already changed, verify the change and run the affected module's existing tests, or finish with the result.
|
|
120
|
-
`);
|
|
121
|
-
}
|
|
122
|
-
export function buildCompletionSelfCheckReminder(options = {}) {
|
|
123
|
-
const sections = [];
|
|
124
|
-
const modifiedTests = options.modifiedExistingTests ?? [];
|
|
125
|
-
if (modifiedTests.length > 0) {
|
|
126
|
-
// Disclosure, not accusation: legitimate test updates (requirement
|
|
127
|
-
// changes, fixing a broken test, refactors) are normal engineering — the
|
|
128
|
-
// rule is that they must be declared, never silent. Confrontational
|
|
129
|
-
// wording risks a model rolling back a user-requested test update.
|
|
130
|
-
const listed = modifiedTests
|
|
131
|
-
.slice(0, 8)
|
|
132
|
-
.map((t) => `- ${t.path}${t.deletedLines > 0 ? ` (${t.deletedLines} line${t.deletedLines === 1 ? "" : "s"} removed)` : ""}`)
|
|
133
|
-
.join("\n");
|
|
134
|
-
sections.push(`This run modified pre-existing test files:
|
|
135
|
-
${listed}${modifiedTests.length > 8 ? `\n- …and ${modifiedTests.length - 8} more` : ""}
|
|
136
|
-
|
|
137
|
-
If a change you made caused an existing test to fail and you edited the test so the suite passes, restore the original behavior instead — weakening a test to make it pass is never acceptable. If the test changes are intentional (the request requires new expected behavior, or the test itself was wrong), keep them and state each modified test and the reason in your final summary.`);
|
|
138
|
-
}
|
|
139
|
-
sections.push(`You appear to be finishing. Before giving your final answer, re-read the user's original request in this conversation and check whether any explicit requirement in it remains unfulfilled — including requirements about how the work should be delivered or reported, not only whether the implementation works.
|
|
140
|
-
|
|
141
|
-
If something is missing, complete it now. If everything is done, give your final answer directly — do not repeat this check or re-verify work you have already verified.`);
|
|
142
|
-
return wrapInSystemReminder(`\n${sections.join("\n\n")}\n`);
|
|
143
|
-
}
|
|
144
86
|
export function buildToolFreezeReminder(reason) {
|
|
145
87
|
return wrapInSystemReminder(`
|
|
146
88
|
CRITICAL - MAXIMUM STEPS REACHED
|
|
@@ -163,79 +105,31 @@ Response must include:
|
|
|
163
105
|
Respond with text ONLY.
|
|
164
106
|
`);
|
|
165
107
|
}
|
|
166
|
-
export function buildWorkflowPhaseReminder(input) {
|
|
167
|
-
const phaseInstructions = {
|
|
168
|
-
investigate: "Collect direct evidence. Prefer targeted reads and structured searches over blind keyword churn.",
|
|
169
|
-
correlate: "Stop broad searching. Correlate the evidence you already have and fill only the most specific remaining gaps.",
|
|
170
|
-
conclude: "You have enough evidence to answer. Do not continue exploring unless you discover a concrete contradiction in the current evidence.",
|
|
171
|
-
};
|
|
172
|
-
const covered = input.covered.length > 0 ? input.covered.map((item) => `- ${item}`).join("\n") : "- none yet";
|
|
173
|
-
const pending = input.pending.length > 0 ? input.pending.map((item) => `- ${item}`).join("\n") : "- none";
|
|
174
|
-
return wrapInSystemReminder(`
|
|
175
|
-
Workflow phase: ${input.phase}
|
|
176
|
-
|
|
177
|
-
${phaseInstructions[input.phase]}
|
|
178
|
-
|
|
179
|
-
Covered evidence:
|
|
180
|
-
${covered}
|
|
181
|
-
|
|
182
|
-
Remaining evidence to check:
|
|
183
|
-
${pending}
|
|
184
|
-
`);
|
|
185
|
-
}
|
|
186
108
|
// Removed: buildVerificationReminder / buildVerificationFailureReminder.
|
|
187
109
|
// The verification reminder ladder pressured the model to run a "verification"
|
|
188
110
|
// after every file change. For models with hex-tokenization blind spots (e.g.
|
|
189
111
|
// DeepSeek v4-pro), this triggered death loops where the model wrote ad-hoc
|
|
190
112
|
// validation scripts that found the bug but could never fix it. CC trusts the
|
|
191
113
|
// model to decide when verification is meaningful; we follow that.
|
|
114
|
+
// Removed: buildFinalizeOpportunityReminder. Was paired with the verification
|
|
115
|
+
// nag ladder. Without the ladder, "you can finalize now" advice is redundant —
|
|
116
|
+
// the model finalises whenever its own judgement says the task is done.
|
|
192
117
|
/**
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*/
|
|
199
|
-
export function buildEditRetryEscalationReminder(reason) {
|
|
200
|
-
return wrapInSystemReminder(`
|
|
201
|
-
A file mutation just failed in a way that usually means your local view of the file is stale or the edit anchor is wrong.
|
|
202
|
-
|
|
203
|
-
${reason}
|
|
204
|
-
|
|
205
|
-
Stop retrying from memory. Pick one of:
|
|
206
|
-
- Re-read the target file and compare the actual bytes to your intended oldText / newText. Trailing whitespace, unicode lookalikes, or off-by-one boundaries are common causes.
|
|
207
|
-
- If you intended to add a single character (e.g. fixing a 5-digit hex color to 6 digits), confirm that your newText string actually contains the added character before sending again.
|
|
208
|
-
- Use the write tool with the full new content instead of edit — useful when the change spans many lines or the diff anchor is ambiguous.
|
|
209
|
-
- If you cannot determine the cause, ask the user for clarification.
|
|
210
|
-
`);
|
|
211
|
-
}
|
|
212
|
-
/**
|
|
213
|
-
* Fired the FIRST time the model re-reads a file it already read in this turn.
|
|
214
|
-
* Soft — does not freeze the tool. The model may still re-read when context was
|
|
215
|
-
* pruned, the requested range changed, or a later mutation needs verification.
|
|
216
|
-
*/
|
|
217
|
-
export function buildRedundantReadReminder(path) {
|
|
218
|
-
return wrapInSystemReminder(`
|
|
219
|
-
You already read ${path} earlier in this turn. If that content is still available and nothing changed, rely on it rather than re-reading.
|
|
220
|
-
It is okay to re-read when you need to recover pruned context, inspect a different range, or verify a later edit/write/bash change.
|
|
221
|
-
`);
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Injected once at task start when the user's input looks like a small,
|
|
225
|
-
* focused task (e.g. "write an HTML page about X"). Counterweight to the
|
|
226
|
-
* default protocol which biases toward thorough exploration.
|
|
118
|
+
* Modified-existing-tests disclosure (docs/harness-thinning.md): a fact from
|
|
119
|
+
* git ground truth — bash writes and subagent worktree merges do not appear
|
|
120
|
+
* in the model's own tool memory. Disclosure, not accusation: legitimate
|
|
121
|
+
* test updates are normal engineering; the rule is that they must be
|
|
122
|
+
* declared, never silent.
|
|
227
123
|
*/
|
|
228
|
-
export function
|
|
124
|
+
export function buildModifiedTestsDisclosure(modifiedExistingTests) {
|
|
125
|
+
const listed = modifiedExistingTests
|
|
126
|
+
.slice(0, 8)
|
|
127
|
+
.map((t) => `- ${t.path}${t.deletedLines > 0 ? ` (${t.deletedLines} line${t.deletedLines === 1 ? "" : "s"} removed)` : ""}`)
|
|
128
|
+
.join("\n");
|
|
229
129
|
return wrapInSystemReminder(`
|
|
230
|
-
|
|
130
|
+
Fact from git: this run modified pre-existing test files:
|
|
131
|
+
${listed}${modifiedExistingTests.length > 8 ? `\n- …and ${modifiedExistingTests.length - 8} more` : ""}
|
|
231
132
|
|
|
232
|
-
|
|
233
|
-
- If the target file path is given or obvious, use write/edit directly.
|
|
234
|
-
- Do not glob, read, or grep adjacent files unless the request explicitly references them.
|
|
235
|
-
- Do not pre-plan with todo_write for tasks that can be done in one or two tool calls.
|
|
236
|
-
- Skip the "investigate the codebase" step that applies to larger changes.
|
|
133
|
+
If a change you made caused an existing test to fail and you edited the test so the suite passes, restore the original behavior instead — weakening a test to make it pass is never acceptable. If the test changes are intentional (the request requires new expected behavior, or the test itself was wrong), keep them and state each modified test and the reason in your final summary. Then finish normally.
|
|
237
134
|
`);
|
|
238
135
|
}
|
|
239
|
-
// Removed: buildFinalizeOpportunityReminder. Was paired with the verification
|
|
240
|
-
// nag ladder. Without the ladder, "you can finalize now" advice is redundant —
|
|
241
|
-
// the model finalises whenever its own judgement says the task is done.
|
|
@@ -1,18 +1,4 @@
|
|
|
1
|
-
import type { TaskType } from "../agent/task-classifier.js";
|
|
2
1
|
import { type RoutableModelEntry } from "../agent/routing-catalog.js";
|
|
3
|
-
export interface TaskReminderOptions {
|
|
4
|
-
/** Whether this agent has the delegation tools (parent agents only). */
|
|
5
|
-
canDelegate?: boolean;
|
|
6
|
-
}
|
|
7
|
-
export declare function reminderForTaskType(taskType: TaskType, options?: TaskReminderOptions): string | undefined;
|
|
8
|
-
/**
|
|
9
|
-
* Deterministic detector for an explicit user request for a coordinated
|
|
10
|
-
* multi-agent run. Three rounds of prompt wording lost to the model's
|
|
11
|
-
* "agent team = parallel spawns" prior in live tests (opus-4.8, 2026-07-06);
|
|
12
|
-
* per the task-reminder principle above, a reminder injected at the decision
|
|
13
|
-
* turn is the lever that actually works — the harness remembers so the model
|
|
14
|
-
* does not have to.
|
|
15
|
-
*/
|
|
16
2
|
/**
|
|
17
3
|
* User-named model resolution (model-routing design v3.6): when the user's
|
|
18
4
|
* message names a model that exists in the routable catalog, hand the main
|
|
@@ -23,21 +9,4 @@ export declare function reminderForTaskType(taskType: TaskType, options?: TaskRe
|
|
|
23
9
|
* this, 2026-07-12 live case). Silent on no match: zero standing noise.
|
|
24
10
|
*/
|
|
25
11
|
export declare function userNamedModelReminder(input: string | import("../types.js").ContentPart[], routableModels: RoutableModelEntry[] | undefined, canSpawn: boolean): string | undefined;
|
|
26
|
-
/**
|
|
27
|
-
* Large-change checkpoint (large-task-delegation design §3): injected once
|
|
28
|
-
* per session at the first mutation of a broad turn. Carries the routing
|
|
29
|
-
* decision criteria (independent vs entangled) and the real worktree
|
|
30
|
-
* mechanics (children fork from the last commit; disjoint file sets) —
|
|
31
|
-
* never a command. The "smallest coherent edit" clause is lifted verbatim
|
|
32
|
-
* from the implementation-workflow reminder so the two compose.
|
|
33
|
-
*/
|
|
34
|
-
export interface LargeTaskReminderInput {
|
|
35
|
-
exploredFiles: number;
|
|
36
|
-
pendingTodos: number;
|
|
37
|
-
/** Mutations already applied this turn (the current batch lands regardless). */
|
|
38
|
-
appliedEdits: number;
|
|
39
|
-
/** orchestrationRequestReminder fired this turn: only offer run_workflow. */
|
|
40
|
-
orchestrationRequested: boolean;
|
|
41
|
-
}
|
|
42
|
-
export declare function largeImplementationTaskReminder(input: LargeTaskReminderInput): string;
|
|
43
12
|
export declare function orchestrationRequestReminder(input: string | import("../types.js").ContentPart[], canRunWorkflow: boolean): string | undefined;
|
|
@@ -1,76 +1,4 @@
|
|
|
1
1
|
import { normalizeModelToken } from "../agent/routing-catalog.js";
|
|
2
|
-
import { wrapInSystemReminder } from "./reminders.js";
|
|
3
|
-
/**
|
|
4
|
-
* Delegation nudge for exploration-shaped tasks: injected at the decision
|
|
5
|
-
* point (start of turn), where it carries far more weight for weakly
|
|
6
|
-
* delegating models than the session-start system prompt. Task-type gating
|
|
7
|
-
* keeps it away from ordinary implementation/debugging turns, so it cannot
|
|
8
|
-
* amplify over-delegation.
|
|
9
|
-
*/
|
|
10
|
-
const DELEGATION_NUDGE = "- If answering needs scanning many files and only the conclusion matters, delegate to a background subagent (spawn_agent); when it is the same read-only question over several independent items, fan out with a run_workflow script.";
|
|
11
|
-
export function reminderForTaskType(taskType, options = {}) {
|
|
12
|
-
switch (taskType) {
|
|
13
|
-
case "debugging":
|
|
14
|
-
return wrapInSystemReminder(`
|
|
15
|
-
Debugging workflow:
|
|
16
|
-
- Reproduce or identify the failing boundary before editing.
|
|
17
|
-
- Trace input, transformation, and output paths.
|
|
18
|
-
- Prefer fixing the mechanism over raising thresholds or adding superficial fallbacks.
|
|
19
|
-
- Verify the specific failure path after the change.
|
|
20
|
-
`);
|
|
21
|
-
case "implementation":
|
|
22
|
-
return wrapInSystemReminder(`
|
|
23
|
-
Implementation workflow:
|
|
24
|
-
- Do not stop at a proposal when the user asked for a change.
|
|
25
|
-
- Inspect the relevant files first, then make the smallest coherent edit.
|
|
26
|
-
- Keep unrelated files and behavior out of scope.
|
|
27
|
-
- Verify the change, then run the existing tests of the affected module — not only the cases you added — or explain why they cannot be run.
|
|
28
|
-
`);
|
|
29
|
-
case "code_review":
|
|
30
|
-
return wrapInSystemReminder(`
|
|
31
|
-
Code review workflow:
|
|
32
|
-
- Lead with concrete findings, ordered by severity.
|
|
33
|
-
- Reference file paths and line numbers when possible.
|
|
34
|
-
- Prioritize bugs, regressions, missing tests, security, and user-visible risk.
|
|
35
|
-
- Keep summaries secondary to findings.
|
|
36
|
-
`);
|
|
37
|
-
case "code_explanation":
|
|
38
|
-
return wrapInSystemReminder(`
|
|
39
|
-
Code explanation workflow:
|
|
40
|
-
- Answer the direct question first.
|
|
41
|
-
- Ground claims in concrete files, functions, and call paths.
|
|
42
|
-
- Distinguish current source evidence from inference.
|
|
43
|
-
- Avoid proposing changes unless the user asks for them.
|
|
44
|
-
`);
|
|
45
|
-
case "repo_orientation":
|
|
46
|
-
return wrapInSystemReminder(`
|
|
47
|
-
Repository orientation workflow:
|
|
48
|
-
- Start with the repo purpose and main execution paths.
|
|
49
|
-
- Inspect README/package metadata plus core runtime files before summarizing.
|
|
50
|
-
- Keep the first pass read-only unless the user asks for changes or runtime verification.
|
|
51
|
-
${options.canDelegate ? `${DELEGATION_NUDGE}\n` : ""}`);
|
|
52
|
-
case "product_discussion":
|
|
53
|
-
return wrapInSystemReminder(`
|
|
54
|
-
Product discussion workflow:
|
|
55
|
-
- Clarify the product goal, user workflow, and tradeoffs before suggesting implementation.
|
|
56
|
-
- Give direct product judgment when the user asks for direction.
|
|
57
|
-
- Avoid drifting into code changes unless the user explicitly asks to execute.
|
|
58
|
-
`);
|
|
59
|
-
case "security_investigation":
|
|
60
|
-
case "code_search":
|
|
61
|
-
case "general":
|
|
62
|
-
default:
|
|
63
|
-
return undefined;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Deterministic detector for an explicit user request for a coordinated
|
|
68
|
-
* multi-agent run. Three rounds of prompt wording lost to the model's
|
|
69
|
-
* "agent team = parallel spawns" prior in live tests (opus-4.8, 2026-07-06);
|
|
70
|
-
* per the task-reminder principle above, a reminder injected at the decision
|
|
71
|
-
* turn is the lever that actually works — the harness remembers so the model
|
|
72
|
-
* does not have to.
|
|
73
|
-
*/
|
|
74
2
|
/**
|
|
75
3
|
* User-named model resolution (model-routing design v3.6): when the user's
|
|
76
4
|
* message names a model that exists in the routable catalog, hand the main
|
|
@@ -119,21 +47,6 @@ export function userNamedModelReminder(input, routableModels, canSpawn) {
|
|
|
119
47
|
"Use these exact ids in spawn_agent / run_workflow `model` params; do not retype model ids from memory.",
|
|
120
48
|
].join("\n");
|
|
121
49
|
}
|
|
122
|
-
export function largeImplementationTaskReminder(input) {
|
|
123
|
-
const plan = input.pendingTodos > 0 ? ` (plan: ${input.pendingTodos} open todo items)` : "";
|
|
124
|
-
const applied = input.appliedEdits > 0
|
|
125
|
-
? ` ${input.appliedEdits} edit${input.appliedEdits === 1 ? "" : "s"} from your current batch will land regardless.`
|
|
126
|
-
: "";
|
|
127
|
-
const mechanism = input.orchestrationRequested
|
|
128
|
-
? "one run_workflow script (the user asked for a coordinated workflow — keep everything in it)"
|
|
129
|
-
: "write_worktree subagents (spawn_agent with the implementer profile) or one run_workflow script";
|
|
130
|
-
return wrapInSystemReminder(`
|
|
131
|
-
Large-change checkpoint: you have read ${input.exploredFiles} files this turn${plan} and are starting to edit.${applied}
|
|
132
|
-
- If the REMAINING edits form INDEPENDENT groups (per-module, per-file, same shape repeated), split them across ${mechanism}. Assign each child a DISJOINT file set; keep shared files (types, registries, exports) for yourself. Children fork from the last COMMIT — commit your applied edits first, or fold them into the child briefings. Each child still makes the smallest coherent edit for its group.
|
|
133
|
-
- If the edits are ENTANGLED (shared state, one file feeding the next, order matters), delegation only adds merge risk — proceed yourself.
|
|
134
|
-
- Work already small enough to finish directly: just finish it.
|
|
135
|
-
`);
|
|
136
|
-
}
|
|
137
50
|
const ORCHESTRATION_REQUEST = /\b(?:workflows?|orchestrat\w*|agent[ -]?teams?|fan[ -]?out)\b|工作流|编排|(?:智能体|代理|agent)\s*(?:团队|小队)/i;
|
|
138
51
|
export function orchestrationRequestReminder(input, canRunWorkflow) {
|
|
139
52
|
if (!canRunWorkflow)
|
|
@@ -17,6 +17,8 @@ export interface AiSdkProviderOptions {
|
|
|
17
17
|
apiKey: string;
|
|
18
18
|
baseURL?: string;
|
|
19
19
|
thinkingLevel?: ThinkingLevel;
|
|
20
|
+
/** User-configured extra headers, merged last (client-identity gates). */
|
|
21
|
+
headers?: Record<string, string>;
|
|
20
22
|
/** Transport override for tests; defaults to the shared provider fetch. */
|
|
21
23
|
fetch?: ProviderFetch;
|
|
22
24
|
}
|
package/dist/provider-ai-sdk.js
CHANGED
|
@@ -21,6 +21,7 @@ const AI_SDK_PROVIDER_FACTORIES = {
|
|
|
21
21
|
const provider = createGoogleGenerativeAI({
|
|
22
22
|
apiKey: options.apiKey,
|
|
23
23
|
...(options.baseURL ? { baseURL: normalizeBaseURL(options.baseURL) } : {}),
|
|
24
|
+
...(options.headers ? { headers: options.headers } : {}),
|
|
24
25
|
fetch: (options.fetch ?? createProviderFetch({
|
|
25
26
|
providerName: "Google Gemini",
|
|
26
27
|
verboseEnvVar: "BUBBLE_AI_SDK_FETCH_VERBOSE",
|
|
@@ -7,6 +7,8 @@ export interface AnthropicProviderOptions {
|
|
|
7
7
|
apiKey: string;
|
|
8
8
|
baseURL: string;
|
|
9
9
|
thinkingLevel?: ThinkingLevel;
|
|
10
|
+
/** User-configured extra headers, merged last (client-identity gates). */
|
|
11
|
+
headers?: Record<string, string>;
|
|
10
12
|
}
|
|
11
13
|
interface AnthropicRequest {
|
|
12
14
|
model: string;
|
|
@@ -627,7 +627,9 @@ function buildAnthropicHeaders(options, stream) {
|
|
|
627
627
|
}
|
|
628
628
|
if (stream)
|
|
629
629
|
headers.accept = "text/event-stream";
|
|
630
|
-
|
|
630
|
+
// User-configured headers win last: anthropic-protocol coding plans gate on
|
|
631
|
+
// client identity (e.g. a specific User-Agent), configured per provider.
|
|
632
|
+
return { ...headers, ...(options.headers ?? {}) };
|
|
631
633
|
}
|
|
632
634
|
async function readAnthropicErrorDetail(response) {
|
|
633
635
|
try {
|
|
@@ -4,6 +4,8 @@ export interface ArkResponsesProviderOptions {
|
|
|
4
4
|
apiKey: string;
|
|
5
5
|
baseURL: string;
|
|
6
6
|
thinkingLevel?: ThinkingLevel;
|
|
7
|
+
/** User-configured extra headers, merged last (client-identity gates). */
|
|
8
|
+
headers?: Record<string, string>;
|
|
7
9
|
}
|
|
8
10
|
export declare function createArkResponsesProvider(options: ArkResponsesProviderOptions): Provider;
|
|
9
11
|
export declare function buildArkResponsesBody(messages: ProviderMessage[], options: {
|
|
@@ -124,6 +124,7 @@ async function sendArkResponsesRequest(options, body, requestOptions) {
|
|
|
124
124
|
"Authorization": `Bearer ${options.apiKey}`,
|
|
125
125
|
"Content-Type": "application/json",
|
|
126
126
|
"Accept": body.stream ? "text/event-stream" : "application/json",
|
|
127
|
+
...(options.headers ?? {}),
|
|
127
128
|
},
|
|
128
129
|
body: JSON.stringify(body),
|
|
129
130
|
signal: requestOptions.signal,
|
|
@@ -19,7 +19,20 @@ export interface ProviderProfile {
|
|
|
19
19
|
enabled: boolean;
|
|
20
20
|
authType?: "api" | "oauth";
|
|
21
21
|
protocol?: ProviderProtocol;
|
|
22
|
+
/**
|
|
23
|
+
* Extra request headers sent with every call to this provider. The knob for
|
|
24
|
+
* client-identity gates: coding-plan endpoints commonly allowlist specific
|
|
25
|
+
* User-Agent strings (Grok wants "grok-cli", plans that "support Claude
|
|
26
|
+
* Code" check for its UA), so users can satisfy them from config without
|
|
27
|
+
* code changes. Merged over protocol defaults; same-name keys win.
|
|
28
|
+
*/
|
|
29
|
+
headers?: Record<string, string>;
|
|
22
30
|
}
|
|
31
|
+
/** True for providers speaking the OpenAI chat-completions protocol (the default). */
|
|
32
|
+
export declare function isOpenAICompatibleProtocol(protocol: ProviderProtocol | undefined): boolean;
|
|
33
|
+
export declare function isLikelyChatModelId(id: string): boolean;
|
|
34
|
+
/** Keep only string-valued headers; returns undefined for empty/invalid maps. */
|
|
35
|
+
export declare function sanitizeProviderHeaders(value: unknown): Record<string, string> | undefined;
|
|
23
36
|
export interface ModelInfo {
|
|
24
37
|
id: string;
|
|
25
38
|
name: string;
|
|
@@ -12,8 +12,55 @@ import { fetchGeminiModels } from "./provider-ai-sdk.js";
|
|
|
12
12
|
import { extractChatGptAccountId, fetchOpenAICodexModelCatalog } from "./provider-openai-codex.js";
|
|
13
13
|
import { refreshOpenAICodex } from "./oauth/openai-codex.js";
|
|
14
14
|
import { refreshGrok } from "./oauth/grok.js";
|
|
15
|
+
/** True for providers speaking the OpenAI chat-completions protocol (the default). */
|
|
16
|
+
export function isOpenAICompatibleProtocol(protocol) {
|
|
17
|
+
return protocol === undefined || protocol === "openai-chat";
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Ids that are clearly not text chat models. Vendors with one big catalog
|
|
21
|
+
* (alibaba returns 236 entries) would otherwise flood the model picker.
|
|
22
|
+
* Deliberately conservative: vision/preview chat models must pass.
|
|
23
|
+
*/
|
|
24
|
+
const NON_CHAT_ID_PATTERN = /(^|[-_/])(embed|embedding|embeddings|rerank|reranker|tts|asr|whisper|ocr|moderation|guard|image|video|audio|speech|voice|paint|draw)([-_/]|$)/i;
|
|
25
|
+
export function isLikelyChatModelId(id) {
|
|
26
|
+
return !NON_CHAT_ID_PATTERN.test(id);
|
|
27
|
+
}
|
|
28
|
+
/** GET {baseURL}/models, tolerating the common response shapes. */
|
|
29
|
+
async function fetchOpenAICompatibleModelIds(provider) {
|
|
30
|
+
const base = provider.baseURL.replace(/\/+$/, "");
|
|
31
|
+
const response = await fetch(`${base}/models`, {
|
|
32
|
+
headers: {
|
|
33
|
+
Authorization: `Bearer ${provider.apiKey}`,
|
|
34
|
+
...(provider.headers ?? {}),
|
|
35
|
+
},
|
|
36
|
+
signal: AbortSignal.timeout(MODEL_DISCOVERY_TIMEOUT_MS),
|
|
37
|
+
});
|
|
38
|
+
if (!response.ok)
|
|
39
|
+
throw new Error(`HTTP ${response.status}`);
|
|
40
|
+
const payload = (await response.json());
|
|
41
|
+
const entries = payload.data ?? payload.models ?? [];
|
|
42
|
+
return entries
|
|
43
|
+
.filter((entry) => typeof entry?.id === "string" && entry.id.trim().length > 0)
|
|
44
|
+
.map((entry) => ({
|
|
45
|
+
id: entry.id,
|
|
46
|
+
name: typeof entry.name === "string" ? entry.name : undefined,
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
/** Keep only string-valued headers; returns undefined for empty/invalid maps. */
|
|
50
|
+
export function sanitizeProviderHeaders(value) {
|
|
51
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
52
|
+
return undefined;
|
|
53
|
+
const out = {};
|
|
54
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
55
|
+
if (typeof raw === "string" && key.trim())
|
|
56
|
+
out[key] = raw;
|
|
57
|
+
}
|
|
58
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
59
|
+
}
|
|
15
60
|
const MODEL_DISCOVERY_SUCCESS_TTL_MS = 60_000;
|
|
16
61
|
const MODEL_DISCOVERY_FAILURE_TTL_MS = 10_000;
|
|
62
|
+
/** Discovery must never delay startup or a model picker for long. */
|
|
63
|
+
const MODEL_DISCOVERY_TIMEOUT_MS = 5_000;
|
|
17
64
|
export const BUILTIN_PROVIDERS = CATALOG_PROVIDERS;
|
|
18
65
|
export const USER_VISIBLE_PROVIDER_IDS = BUILTIN_PROVIDERS
|
|
19
66
|
.filter((provider) => !provider.hidden && provider.id !== "openrouter" && provider.id !== "openai-codex")
|
|
@@ -230,6 +277,7 @@ export class ProviderRegistry {
|
|
|
230
277
|
enabled: true,
|
|
231
278
|
authType: "api",
|
|
232
279
|
protocol: resolveConfiguredProtocol(id, baseURL, cfg.protocol),
|
|
280
|
+
headers: sanitizeProviderHeaders(cfg.headers),
|
|
233
281
|
};
|
|
234
282
|
});
|
|
235
283
|
}
|
|
@@ -241,6 +289,7 @@ export class ProviderRegistry {
|
|
|
241
289
|
...provider,
|
|
242
290
|
baseURL,
|
|
243
291
|
protocol: resolveConfiguredProtocol(provider.id, baseURL, provider.protocol),
|
|
292
|
+
headers: sanitizeProviderHeaders(provider.headers),
|
|
244
293
|
};
|
|
245
294
|
});
|
|
246
295
|
}
|
|
@@ -472,6 +521,40 @@ export class ProviderRegistry {
|
|
|
472
521
|
return this.fallbackDiscovery(provider, error);
|
|
473
522
|
}
|
|
474
523
|
}
|
|
524
|
+
// Generic OpenAI-compatible discovery: most vendors expose GET /models on
|
|
525
|
+
// the same base URL. Probed against every configured provider, their lists
|
|
526
|
+
// turned out to be neither complete nor clean — zhipuai omits glm-5.2,
|
|
527
|
+
// stepfun omits step-3.7-flash (both demonstrably usable), alibaba returns
|
|
528
|
+
// 236 entries including image/audio models, fireworks 412s. So the remote
|
|
529
|
+
// list AUGMENTS the curated catalog instead of replacing it: builtin
|
|
530
|
+
// entries always survive (they carry tier/reasoning metadata and some work
|
|
531
|
+
// while unlisted), remote-only ids are appended, obvious non-chat
|
|
532
|
+
// modalities are filtered out. Not authoritative — membership is a union,
|
|
533
|
+
// so nothing downstream may treat it as a closed allowlist.
|
|
534
|
+
if (isOpenAICompatibleProtocol(provider.protocol) && provider.apiKey) {
|
|
535
|
+
try {
|
|
536
|
+
const remote = await fetchOpenAICompatibleModelIds(provider);
|
|
537
|
+
const local = this.localModelsForProvider(provider);
|
|
538
|
+
const known = new Set(local.map((model) => model.id));
|
|
539
|
+
const extras = remote
|
|
540
|
+
.filter((entry) => !known.has(entry.id) && isLikelyChatModelId(entry.id))
|
|
541
|
+
.map((entry) => ({
|
|
542
|
+
id: entry.id,
|
|
543
|
+
name: entry.name || entry.id,
|
|
544
|
+
providerId: provider.id,
|
|
545
|
+
}))
|
|
546
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
547
|
+
if (extras.length === 0) {
|
|
548
|
+
return { models: local, source: "static", authoritative: true };
|
|
549
|
+
}
|
|
550
|
+
return { models: [...local, ...extras], source: "remote", authoritative: false };
|
|
551
|
+
}
|
|
552
|
+
catch {
|
|
553
|
+
// Vendors that don't implement /models (or gate it) fall back silently:
|
|
554
|
+
// the curated catalog is the contract, discovery is a bonus.
|
|
555
|
+
return { models: this.localModelsForProvider(provider), source: "static", authoritative: true };
|
|
556
|
+
}
|
|
557
|
+
}
|
|
475
558
|
return {
|
|
476
559
|
models: this.localModelsForProvider(provider),
|
|
477
560
|
source: "static",
|
|
@@ -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. */
|