@bubblebrain-ai/bubble 0.0.11 → 0.0.13
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/input-controller.d.ts +11 -0
- package/dist/agent/input-controller.js +30 -0
- package/dist/agent.d.ts +6 -4
- package/dist/agent.js +39 -2
- package/dist/feishu/agent-host/run-driver.js +13 -6
- package/dist/feishu/agent-host/runtime-deps.d.ts +2 -2
- package/dist/feishu/router/commands.js +2 -1
- package/dist/feishu/scope/session-binder.js +1 -1
- package/dist/feishu/serve.js +3 -3
- package/dist/main.js +78 -12
- package/dist/prompt/compose.js +3 -3
- package/dist/prompt/environment.js +2 -0
- package/dist/prompt/reminders.js +1 -1
- package/dist/provider-openai-codex.d.ts +8 -1
- package/dist/provider-openai-codex.js +33 -9
- package/dist/provider.d.ts +2 -0
- package/dist/session-title.d.ts +16 -0
- package/dist/session-title.js +134 -0
- package/dist/session-types.d.ts +5 -0
- package/dist/session.d.ts +5 -0
- package/dist/session.js +75 -9
- package/dist/skills/invocation.js +0 -18
- package/dist/skills/registry.d.ts +1 -0
- package/dist/skills/registry.js +2 -0
- package/dist/slash-commands/commands.js +29 -22
- package/dist/slash-commands/registry.js +1 -1
- package/dist/slash-commands/types.d.ts +10 -0
- package/dist/text-display.d.ts +3 -0
- package/dist/text-display.js +25 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +3 -1
- package/dist/tools/skill-search.d.ts +10 -0
- package/dist/tools/skill-search.js +134 -0
- package/dist/tools/skill.js +1 -4
- package/dist/tui/clipboard.d.ts +1 -0
- package/dist/tui/clipboard.js +53 -0
- package/dist/tui/detect-theme.d.ts +2 -0
- package/dist/tui/detect-theme.js +87 -0
- package/dist/tui/display-history.d.ts +62 -0
- package/dist/tui/display-history.js +305 -0
- package/dist/tui/edit-diff.d.ts +11 -0
- package/dist/tui/edit-diff.js +52 -0
- package/dist/tui/escape-confirmation.d.ts +15 -0
- package/dist/tui/escape-confirmation.js +30 -0
- package/dist/tui/file-mentions.d.ts +29 -0
- package/dist/tui/file-mentions.js +174 -0
- package/dist/tui/global-key-router.d.ts +3 -0
- package/dist/tui/global-key-router.js +87 -0
- package/dist/tui/image-paste.d.ts +95 -0
- package/dist/tui/image-paste.js +505 -0
- package/dist/tui/input-history.d.ts +16 -0
- package/dist/tui/input-history.js +79 -0
- package/dist/tui/markdown-inline.d.ts +22 -0
- package/dist/tui/markdown-inline.js +68 -0
- package/dist/tui/markdown-theme-rules.d.ts +23 -0
- package/dist/tui/markdown-theme-rules.js +164 -0
- package/dist/tui/markdown-theme.d.ts +5 -0
- package/dist/tui/markdown-theme.js +27 -0
- package/dist/tui/opencode-spinner.d.ts +22 -0
- package/dist/tui/opencode-spinner.js +216 -0
- package/dist/tui/prompt-keybindings.d.ts +42 -0
- package/dist/tui/prompt-keybindings.js +35 -0
- package/dist/tui/recent-activity.d.ts +8 -0
- package/dist/tui/recent-activity.js +71 -0
- package/dist/tui/render-signature.d.ts +1 -0
- package/dist/tui/render-signature.js +7 -0
- package/dist/tui/run.d.ts +45 -0
- package/dist/tui/run.js +8816 -0
- package/dist/tui/session-display.d.ts +6 -0
- package/dist/tui/session-display.js +12 -0
- package/dist/tui/sidebar-mcp.d.ts +31 -0
- package/dist/tui/sidebar-mcp.js +62 -0
- package/dist/tui/sidebar-state.d.ts +12 -0
- package/dist/tui/sidebar-state.js +69 -0
- package/dist/tui/streaming-tool-args.d.ts +15 -0
- package/dist/tui/streaming-tool-args.js +30 -0
- package/dist/tui/tool-renderers/fallback.d.ts +2 -0
- package/dist/tui/tool-renderers/fallback.js +75 -0
- package/dist/tui/tool-renderers/registry.d.ts +3 -0
- package/dist/tui/tool-renderers/registry.js +11 -0
- package/dist/tui/tool-renderers/subagent.d.ts +2 -0
- package/dist/tui/tool-renderers/subagent.js +135 -0
- package/dist/tui/tool-renderers/types.d.ts +36 -0
- package/dist/tui/tool-renderers/types.js +1 -0
- package/dist/tui/tool-renderers/write-preview.d.ts +12 -0
- package/dist/tui/tool-renderers/write-preview.js +30 -0
- package/dist/tui/tool-renderers/write.d.ts +6 -0
- package/dist/tui/tool-renderers/write.js +88 -0
- package/dist/tui/trace-groups.d.ts +27 -0
- package/dist/tui/trace-groups.js +412 -0
- package/dist/tui/wordmark.d.ts +15 -0
- package/dist/tui/wordmark.js +179 -0
- package/dist/tui-ink/app.js +98 -70
- package/dist/tui-ink/input-box.d.ts +22 -1
- package/dist/tui-ink/input-box.js +105 -11
- package/dist/tui-ink/message-list.js +12 -3
- package/dist/tui-ink/model-picker.d.ts +18 -0
- package/dist/tui-ink/model-picker.js +80 -23
- package/dist/tui-ink/session-picker.js +5 -7
- package/dist/tui-ink/theme.d.ts +3 -9
- package/dist/tui-ink/theme.js +39 -45
- package/dist/tui-ink/welcome.js +22 -78
- package/dist/tui-opentui/app.d.ts +54 -0
- package/dist/tui-opentui/app.js +1363 -0
- package/dist/tui-opentui/approval/approval-dialog.d.ts +15 -0
- package/dist/tui-opentui/approval/approval-dialog.js +139 -0
- package/dist/tui-opentui/approval/diff-view.d.ts +9 -0
- package/dist/tui-opentui/approval/diff-view.js +43 -0
- package/dist/tui-opentui/approval/select.d.ts +37 -0
- package/dist/tui-opentui/approval/select.js +91 -0
- package/dist/tui-opentui/detect-theme.d.ts +2 -0
- package/dist/tui-opentui/detect-theme.js +87 -0
- package/dist/tui-opentui/display-history.d.ts +55 -0
- package/dist/tui-opentui/display-history.js +129 -0
- package/dist/tui-opentui/edit-diff.d.ts +11 -0
- package/dist/tui-opentui/edit-diff.js +52 -0
- package/dist/tui-opentui/feedback-dialog.d.ts +21 -0
- package/dist/tui-opentui/feedback-dialog.js +164 -0
- package/dist/tui-opentui/feishu-setup-picker.d.ts +7 -0
- package/dist/tui-opentui/feishu-setup-picker.js +272 -0
- package/dist/tui-opentui/file-mentions.d.ts +29 -0
- package/dist/tui-opentui/file-mentions.js +174 -0
- package/dist/tui-opentui/footer.d.ts +26 -0
- package/dist/tui-opentui/footer.js +40 -0
- package/dist/tui-opentui/image-paste.d.ts +54 -0
- package/dist/tui-opentui/image-paste.js +288 -0
- package/dist/tui-opentui/input-box.d.ts +34 -0
- package/dist/tui-opentui/input-box.js +471 -0
- package/dist/tui-opentui/input-history.d.ts +16 -0
- package/dist/tui-opentui/input-history.js +79 -0
- package/dist/tui-opentui/markdown.d.ts +66 -0
- package/dist/tui-opentui/markdown.js +127 -0
- package/dist/tui-opentui/message-list.d.ts +31 -0
- package/dist/tui-opentui/message-list.js +125 -0
- package/dist/tui-opentui/model-picker.d.ts +63 -0
- package/dist/tui-opentui/model-picker.js +450 -0
- package/dist/tui-opentui/plan-confirm.d.ts +9 -0
- package/dist/tui-opentui/plan-confirm.js +124 -0
- package/dist/tui-opentui/question-dialog.d.ts +10 -0
- package/dist/tui-opentui/question-dialog.js +110 -0
- package/dist/tui-opentui/recent-activity.d.ts +8 -0
- package/dist/tui-opentui/recent-activity.js +71 -0
- package/dist/tui-opentui/run-session-picker.d.ts +10 -0
- package/dist/tui-opentui/run-session-picker.js +28 -0
- package/dist/tui-opentui/run.d.ts +38 -0
- package/dist/tui-opentui/run.js +48 -0
- package/dist/tui-opentui/session-picker.d.ts +12 -0
- package/dist/tui-opentui/session-picker.js +120 -0
- package/dist/tui-opentui/theme.d.ts +89 -0
- package/dist/tui-opentui/theme.js +157 -0
- package/dist/tui-opentui/todos.d.ts +9 -0
- package/dist/tui-opentui/todos.js +45 -0
- package/dist/tui-opentui/trace-groups.d.ts +27 -0
- package/dist/tui-opentui/trace-groups.js +412 -0
- package/dist/tui-opentui/use-terminal-size.d.ts +4 -0
- package/dist/tui-opentui/use-terminal-size.js +5 -0
- package/dist/tui-opentui/welcome.d.ts +25 -0
- package/dist/tui-opentui/welcome.js +77 -0
- package/dist/types.d.ts +24 -0
- package/package.json +5 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AgentInputController, AgentRunInput } from "../types.js";
|
|
2
|
+
export declare class AgentRunInputQueue implements AgentInputController {
|
|
3
|
+
private readonly idPrefix;
|
|
4
|
+
private pending;
|
|
5
|
+
private nextInputId;
|
|
6
|
+
constructor(idPrefix?: string);
|
|
7
|
+
enqueue(content: string): AgentRunInput;
|
|
8
|
+
drainPendingInputs(): AgentRunInput[];
|
|
9
|
+
pendingInputCount(): number;
|
|
10
|
+
clear(): AgentRunInput[];
|
|
11
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export class AgentRunInputQueue {
|
|
2
|
+
idPrefix;
|
|
3
|
+
pending = [];
|
|
4
|
+
nextInputId = 0;
|
|
5
|
+
constructor(idPrefix = "input") {
|
|
6
|
+
this.idPrefix = idPrefix;
|
|
7
|
+
}
|
|
8
|
+
enqueue(content) {
|
|
9
|
+
const input = {
|
|
10
|
+
id: `${this.idPrefix}-${++this.nextInputId}`,
|
|
11
|
+
content,
|
|
12
|
+
submittedAt: Date.now(),
|
|
13
|
+
};
|
|
14
|
+
this.pending.push(input);
|
|
15
|
+
return input;
|
|
16
|
+
}
|
|
17
|
+
drainPendingInputs() {
|
|
18
|
+
if (this.pending.length === 0)
|
|
19
|
+
return [];
|
|
20
|
+
const inputs = this.pending;
|
|
21
|
+
this.pending = [];
|
|
22
|
+
return inputs;
|
|
23
|
+
}
|
|
24
|
+
pendingInputCount() {
|
|
25
|
+
return this.pending.length;
|
|
26
|
+
}
|
|
27
|
+
clear() {
|
|
28
|
+
return this.drainPendingInputs();
|
|
29
|
+
}
|
|
30
|
+
}
|
package/dist/agent.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* It maintains message state, calls the LLM, executes tools, and auto-continues.
|
|
4
4
|
*/
|
|
5
5
|
import { type ContextUsageSnapshot } from "./context/usage.js";
|
|
6
|
-
import type { AgentEvent, ContentPart, PermissionMode, Message, Provider, ThinkingLevel, Todo, ToolResult, ToolRegistryEntry, ToolUpdate } from "./types.js";
|
|
6
|
+
import type { AgentEvent, AgentInputController, ContentPart, PermissionMode, Message, Provider, ThinkingLevel, Todo, ToolResult, ToolRegistryEntry, ToolUpdate } from "./types.js";
|
|
7
7
|
import { type TurnHooks } from "./orchestrator/hooks.js";
|
|
8
8
|
import { type AgentCategoriesConfig, type ResolvedSubagentRoute } from "./agent/categories.js";
|
|
9
9
|
import { BudgetLedger } from "./agent/budget-ledger.js";
|
|
@@ -46,6 +46,10 @@ export interface AgentOptions {
|
|
|
46
46
|
agentCategories?: AgentCategoriesConfig;
|
|
47
47
|
providerFactory?: (route: ResolvedSubagentRoute) => Provider | Promise<Provider>;
|
|
48
48
|
}
|
|
49
|
+
export interface AgentRunOptions {
|
|
50
|
+
abortSignal?: AbortSignal;
|
|
51
|
+
inputController?: AgentInputController;
|
|
52
|
+
}
|
|
49
53
|
export declare class Agent {
|
|
50
54
|
messages: Message[];
|
|
51
55
|
private provider;
|
|
@@ -116,9 +120,7 @@ export declare class Agent {
|
|
|
116
120
|
/** Internal: snapshot counter that bumps on every setTodos. Used by run loop to detect mutations. */
|
|
117
121
|
get todosVersion(): number;
|
|
118
122
|
setSystemPrompt(prompt: string): void;
|
|
119
|
-
run(userInput: string | ContentPart[], cwd: string, options?:
|
|
120
|
-
abortSignal?: AbortSignal;
|
|
121
|
-
}): AsyncIterable<AgentEvent>;
|
|
123
|
+
run(userInput: string | ContentPart[], cwd: string, options?: AgentRunOptions): AsyncIterable<AgentEvent>;
|
|
122
124
|
private recoverFromOverflow;
|
|
123
125
|
compactResidentHistory(): void;
|
|
124
126
|
private maybeCompactWithLLM;
|
package/dist/agent.js
CHANGED
|
@@ -236,6 +236,7 @@ export class Agent {
|
|
|
236
236
|
}
|
|
237
237
|
async *run(userInput, cwd, options = {}) {
|
|
238
238
|
const abortSignal = composeAbortSignals([options.abortSignal, this.budgetLedger?.signal]);
|
|
239
|
+
const inputController = options.inputController;
|
|
239
240
|
throwIfAborted(abortSignal);
|
|
240
241
|
const hookBus = new HookBus();
|
|
241
242
|
for (const hooks of createDefaultHooks()) {
|
|
@@ -249,6 +250,39 @@ export class Agent {
|
|
|
249
250
|
const queueReminder = (reminder) => {
|
|
250
251
|
reminderQueue.push(reminder);
|
|
251
252
|
};
|
|
253
|
+
const pendingInputCount = () => inputController?.pendingInputCount() ?? 0;
|
|
254
|
+
const applyPendingInputs = () => {
|
|
255
|
+
const pendingInputs = inputController?.drainPendingInputs() ?? [];
|
|
256
|
+
if (pendingInputs.length === 0)
|
|
257
|
+
return [];
|
|
258
|
+
for (const input of pendingInputs) {
|
|
259
|
+
this.appendMessage({ role: "user", content: input.content });
|
|
260
|
+
}
|
|
261
|
+
return [
|
|
262
|
+
...pendingInputs.map((input) => ({
|
|
263
|
+
type: "input_applied",
|
|
264
|
+
id: input.id,
|
|
265
|
+
content: input.content,
|
|
266
|
+
target: "current_turn",
|
|
267
|
+
})),
|
|
268
|
+
{ type: "input_pending_changed", pending: pendingInputCount() },
|
|
269
|
+
];
|
|
270
|
+
};
|
|
271
|
+
const rejectPendingInputs = (reason) => {
|
|
272
|
+
const pendingInputs = inputController?.drainPendingInputs() ?? [];
|
|
273
|
+
if (pendingInputs.length === 0)
|
|
274
|
+
return [];
|
|
275
|
+
return [
|
|
276
|
+
...pendingInputs.map((input) => ({
|
|
277
|
+
type: "input_rejected",
|
|
278
|
+
id: input.id,
|
|
279
|
+
content: input.content,
|
|
280
|
+
reason,
|
|
281
|
+
target: "next_turn",
|
|
282
|
+
})),
|
|
283
|
+
{ type: "input_pending_changed", pending: pendingInputCount() },
|
|
284
|
+
];
|
|
285
|
+
};
|
|
252
286
|
const flushGovernorReminders = () => {
|
|
253
287
|
for (const reminder of reminderQueue.splice(0, reminderQueue.length)) {
|
|
254
288
|
this.injectSystemReminder(reminder);
|
|
@@ -275,6 +309,8 @@ export class Agent {
|
|
|
275
309
|
flushGovernorReminders();
|
|
276
310
|
for (const update of this.drainSubagentToolUpdates())
|
|
277
311
|
yield update;
|
|
312
|
+
for (const event of applyPendingInputs())
|
|
313
|
+
yield event;
|
|
278
314
|
yield { type: "turn_start" };
|
|
279
315
|
step += 1;
|
|
280
316
|
hookState.turnCount = step;
|
|
@@ -604,6 +640,8 @@ export class Agent {
|
|
|
604
640
|
delete hookState.forceContinuationReason;
|
|
605
641
|
continue;
|
|
606
642
|
}
|
|
643
|
+
for (const event of rejectPendingInputs("no_continuation"))
|
|
644
|
+
yield event;
|
|
607
645
|
break;
|
|
608
646
|
}
|
|
609
647
|
for (const update of this.drainSubagentToolUpdates())
|
|
@@ -1061,7 +1099,6 @@ export class Agent {
|
|
|
1061
1099
|
mode: "plan",
|
|
1062
1100
|
workingDir: cwd,
|
|
1063
1101
|
tools: childToolNames,
|
|
1064
|
-
skills: childToolNames.includes("skill") ? this.skillSummaries : undefined,
|
|
1065
1102
|
memoryPrompt: childToolNames.some((name) => name === "memory_search" || name === "memory_read_summary")
|
|
1066
1103
|
? this.memoryPrompt
|
|
1067
1104
|
: undefined,
|
|
@@ -1257,7 +1294,7 @@ export class Agent {
|
|
|
1257
1294
|
if (this._mode === "plan" && !tool.readOnly) {
|
|
1258
1295
|
return {
|
|
1259
1296
|
content: `Error: Tool "${toolCall.name}" is not allowed in plan mode. ` +
|
|
1260
|
-
`In plan mode you may only use read-only tools (read, glob, grep, lsp, web_search, web_fetch, spawn_agent, wait_agent, send_input, close_agent, skill, todo_write, tool_search, question, exit_plan_mode). ` +
|
|
1297
|
+
`In plan mode you may only use read-only tools (read, glob, grep, lsp, web_search, web_fetch, spawn_agent, wait_agent, send_input, close_agent, skill_search, skill, todo_write, tool_search, question, exit_plan_mode). ` +
|
|
1261
1298
|
`To modify files or run commands, present your proposal and call exit_plan_mode so the user can review and approve it.`,
|
|
1262
1299
|
isError: true,
|
|
1263
1300
|
};
|
|
@@ -23,6 +23,7 @@ import { createAllTools } from "../../tools/index.js";
|
|
|
23
23
|
import { displayModel, encodeModel, decodeModel } from "../../provider-registry.js";
|
|
24
24
|
import { buildMemoryPrompt, recordMemoryCitations } from "../../memory/index.js";
|
|
25
25
|
import { getDefaultThinkingLevel } from "../../provider-transform.js";
|
|
26
|
+
import { createSessionTitleUpdater } from "../../session-title.js";
|
|
26
27
|
import { applyCardBudget } from "../card/budget.js";
|
|
27
28
|
import { renderCard } from "../card/renderer.js";
|
|
28
29
|
import { createInitialRunState } from "../card/run-state-types.js";
|
|
@@ -78,7 +79,8 @@ export class RunDriver {
|
|
|
78
79
|
// the question tool to the agent.
|
|
79
80
|
});
|
|
80
81
|
tools.push(...this.opts.deps.mcpManager.getToolEntries());
|
|
81
|
-
const
|
|
82
|
+
const promptCacheKey = session.manager.getOrCreatePromptCacheKey();
|
|
83
|
+
const { provider, providerId, model } = await this.resolveProvider(session, promptCacheKey);
|
|
82
84
|
const skills = this.opts.deps.skillRegistry.summaries();
|
|
83
85
|
const memoryPrompt = buildMemoryPrompt(session.cwd);
|
|
84
86
|
const thinkingLevel = this.opts.deps.userConfig.getDefaultThinkingLevel()
|
|
@@ -93,10 +95,10 @@ export class RunDriver {
|
|
|
93
95
|
mode: initialMode,
|
|
94
96
|
workingDir: session.cwd,
|
|
95
97
|
tools: tools.map((t) => t.name),
|
|
96
|
-
skills,
|
|
97
98
|
memoryPrompt,
|
|
98
99
|
});
|
|
99
100
|
const budgetLedger = new BudgetLedger();
|
|
101
|
+
let sessionTitleUpdater;
|
|
100
102
|
const agent = new Agent({
|
|
101
103
|
provider,
|
|
102
104
|
providerId,
|
|
@@ -112,6 +114,7 @@ export class RunDriver {
|
|
|
112
114
|
if (message.role === "system" || message.role === "meta")
|
|
113
115
|
return;
|
|
114
116
|
session.manager.appendMessage(message);
|
|
117
|
+
sessionTitleUpdater?.handlePersistedMessage(message);
|
|
115
118
|
if (message.role === "assistant") {
|
|
116
119
|
recordMemoryCitations(session.cwd, message.content);
|
|
117
120
|
}
|
|
@@ -133,11 +136,15 @@ export class RunDriver {
|
|
|
133
136
|
memoryPrompt,
|
|
134
137
|
fileStateTracker,
|
|
135
138
|
agentCategories: this.opts.deps.userConfig.getAgentCategories(),
|
|
136
|
-
providerFactory: this.opts.deps.createProviderForRoute,
|
|
139
|
+
providerFactory: (route) => this.opts.deps.createProviderForRoute(route, promptCacheKey),
|
|
140
|
+
});
|
|
141
|
+
sessionTitleUpdater = createSessionTitleUpdater({
|
|
142
|
+
sessionManager: session.manager,
|
|
143
|
+
complete: (messages, completeOptions) => agent.complete(messages, completeOptions),
|
|
137
144
|
});
|
|
138
145
|
agentRef = agent;
|
|
139
146
|
agentForPlan = agent;
|
|
140
|
-
session.manager.
|
|
147
|
+
session.manager.updateMetadata({
|
|
141
148
|
...(agent.model ? { model: agent.model } : {}),
|
|
142
149
|
cwd: session.cwd,
|
|
143
150
|
thinkingLevel: agent.thinking,
|
|
@@ -245,7 +252,7 @@ export class RunDriver {
|
|
|
245
252
|
this.opts.approvalUI.cancelForChat(req.chatId, "Run ended");
|
|
246
253
|
}
|
|
247
254
|
}
|
|
248
|
-
async resolveProvider(session) {
|
|
255
|
+
async resolveProvider(session, promptCacheKey) {
|
|
249
256
|
const registry = this.opts.deps.providerRegistry;
|
|
250
257
|
const userConfig = this.opts.deps.userConfig;
|
|
251
258
|
// Read session metadata for an explicit model preference, fall back to
|
|
@@ -272,7 +279,7 @@ export class RunDriver {
|
|
|
272
279
|
const activeModel = effectiveModelId
|
|
273
280
|
? encodeModel(activeProviderId, effectiveModelId)
|
|
274
281
|
: "";
|
|
275
|
-
const provider = this.opts.deps.createProvider(activeProviderId, target.apiKey, target.baseURL);
|
|
282
|
+
const provider = this.opts.deps.createProvider(activeProviderId, target.apiKey, target.baseURL, promptCacheKey);
|
|
276
283
|
return { provider, providerId: activeProviderId, model: activeModel };
|
|
277
284
|
}
|
|
278
285
|
}
|
|
@@ -23,11 +23,11 @@ export interface FeishuRuntimeDeps {
|
|
|
23
23
|
/** Live MCP tool source; tools list includes MCP entries. */
|
|
24
24
|
mcpManager: McpManager;
|
|
25
25
|
/** Factory used by main provider + subagent routes. */
|
|
26
|
-
createProvider: (providerId: string, apiKey: string, baseURL: string) => Provider;
|
|
26
|
+
createProvider: (providerId: string, apiKey: string, baseURL: string, promptCacheKey?: string) => Provider;
|
|
27
27
|
createProviderForRoute: (route: {
|
|
28
28
|
providerId: string;
|
|
29
29
|
model: string;
|
|
30
|
-
}) => Promise<Provider>;
|
|
30
|
+
}, promptCacheKey?: string) => Promise<Provider>;
|
|
31
31
|
/** Resolved owner open_id (from config.app.ownerOpenId). */
|
|
32
32
|
ownerOpenId: string;
|
|
33
33
|
}
|
|
@@ -192,7 +192,7 @@ register({
|
|
|
192
192
|
const lines = ["最近 session:", ""];
|
|
193
193
|
for (const s of recent) {
|
|
194
194
|
const stamp = new Date(s.mtime).toISOString().slice(0, 19).replace("T", " ");
|
|
195
|
-
lines.push(`- \`${s.name}\` · ${stamp} · ${s.messageCount} msgs · ${s.
|
|
195
|
+
lines.push(`- \`${s.name}\` · ${stamp} · ${s.messageCount} msgs · ${s.title.slice(0, 50)}`);
|
|
196
196
|
}
|
|
197
197
|
lines.push("");
|
|
198
198
|
lines.push("用 `/resume <name>` 恢复。");
|
|
@@ -239,6 +239,7 @@ register({
|
|
|
239
239
|
}
|
|
240
240
|
const opened = ctx.sessionBinder.openOrBootstrap(input.scopeKey, entry.cwd, entry.permissionMode);
|
|
241
241
|
opened.manager.appendMarker("conversation_clear", String(Date.now()));
|
|
242
|
+
opened.manager.clearTitleMetadata();
|
|
242
243
|
await ctx.channel.send(input.chatId, { text: "🧹 已插入清除标记。下次发消息从空上下文开始。" });
|
|
243
244
|
},
|
|
244
245
|
});
|
|
@@ -37,7 +37,7 @@ export class SessionBinder {
|
|
|
37
37
|
// Persist metadata immediately so the on-disk file exists from this point
|
|
38
38
|
// on — otherwise openOrBootstrap() on the next call would see the pointer
|
|
39
39
|
// but no file and re-bootstrap, losing the pointer.
|
|
40
|
-
manager.
|
|
40
|
+
manager.updateMetadata({ cwd });
|
|
41
41
|
const entry = {
|
|
42
42
|
sessionFile: manager.getSessionFile(),
|
|
43
43
|
cwd,
|
package/dist/feishu/serve.js
CHANGED
|
@@ -91,13 +91,13 @@ export async function serveFeishu(opts = {}) {
|
|
|
91
91
|
if (mcpLoaded.servers.length > 0) {
|
|
92
92
|
await mcpManager.start();
|
|
93
93
|
}
|
|
94
|
-
const createProvider = (providerId, apiKey, baseURL) => createProviderInstance({ providerId, apiKey, baseURL });
|
|
95
|
-
const createProviderForRoute = async (route) => {
|
|
94
|
+
const createProvider = (providerId, apiKey, baseURL, promptCacheKey) => createProviderInstance({ providerId, apiKey, baseURL, promptCacheKey });
|
|
95
|
+
const createProviderForRoute = async (route, promptCacheKey) => {
|
|
96
96
|
const target = providerRegistry.getConfigured().find((p) => p.id === route.providerId);
|
|
97
97
|
if (!target?.apiKey) {
|
|
98
98
|
throw new Error(`Subagent route requires provider "${route.providerId}", not configured.`);
|
|
99
99
|
}
|
|
100
|
-
return createProvider(route.providerId, target.apiKey, target.baseURL);
|
|
100
|
+
return createProvider(route.providerId, target.apiKey, target.baseURL, promptCacheKey);
|
|
101
101
|
};
|
|
102
102
|
const deps = {
|
|
103
103
|
settingsManager,
|
package/dist/main.js
CHANGED
|
@@ -11,6 +11,7 @@ import { createProviderInstance, createUnavailableProvider } from "./provider.js
|
|
|
11
11
|
import { getDefaultThinkingLevel } from "./provider-transform.js";
|
|
12
12
|
import { ProviderRegistry, displayModel, encodeModel, decodeModel } from "./provider-registry.js";
|
|
13
13
|
import { SessionManager } from "./session.js";
|
|
14
|
+
import { createSessionTitleUpdater } from "./session-title.js";
|
|
14
15
|
import { buildSystemPrompt } from "./system-prompt.js";
|
|
15
16
|
import { SkillRegistry } from "./skills/registry.js";
|
|
16
17
|
import { createAllTools } from "./tools/index.js";
|
|
@@ -24,6 +25,8 @@ import { McpManager } from "./mcp/manager.js";
|
|
|
24
25
|
import { QuestionController } from "./question/index.js";
|
|
25
26
|
import { buildMemoryPrompt, formatMemoryStartupResult, recordMemoryCitations, runMemoryPhase2, runMemoryStartupPipeline, startMemoryStartupTask, } from "./memory/index.js";
|
|
26
27
|
import { basename } from "node:path";
|
|
28
|
+
import { normalizeSingleLine, truncateVisual } from "./text-display.js";
|
|
29
|
+
import { BUBBLE_WORDMARK } from "./tui/wordmark.js";
|
|
27
30
|
async function main() {
|
|
28
31
|
const args = parseArgs(process.argv.slice(2));
|
|
29
32
|
if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
@@ -61,15 +64,23 @@ async function main() {
|
|
|
61
64
|
}
|
|
62
65
|
const defaultProvider = registry.getDefault();
|
|
63
66
|
const unavailableProviderMessage = "No provider configured. Use /login for ChatGPT or /provider --add <id> before sending a prompt.";
|
|
67
|
+
let sessionPromptCacheKey;
|
|
64
68
|
const provider = defaultProvider
|
|
65
69
|
? createProviderInstance({
|
|
66
70
|
providerId: defaultProvider.id,
|
|
67
71
|
apiKey: defaultProvider.apiKey,
|
|
68
72
|
baseURL: defaultProvider.baseURL,
|
|
69
73
|
thinkingLevel: args.thinkingLevel,
|
|
74
|
+
promptCacheKey: sessionPromptCacheKey,
|
|
70
75
|
})
|
|
71
76
|
: createUnavailableProvider(unavailableProviderMessage);
|
|
72
|
-
const createProvider = (providerId, apiKey, baseURL) => createProviderInstance({
|
|
77
|
+
const createProvider = (providerId, apiKey, baseURL) => createProviderInstance({
|
|
78
|
+
providerId,
|
|
79
|
+
apiKey,
|
|
80
|
+
baseURL,
|
|
81
|
+
thinkingLevel: args.thinkingLevel,
|
|
82
|
+
promptCacheKey: sessionPromptCacheKey,
|
|
83
|
+
});
|
|
73
84
|
const createProviderForRoute = async (route) => {
|
|
74
85
|
const providerId = route.providerId;
|
|
75
86
|
if (!providerId) {
|
|
@@ -174,7 +185,7 @@ async function main() {
|
|
|
174
185
|
// - --resume (no name): show interactive picker
|
|
175
186
|
let sessionManager;
|
|
176
187
|
let resumedExistingSession = false;
|
|
177
|
-
// Resolved before any
|
|
188
|
+
// Resolved before any TUI render so picker and main TUI share the same value
|
|
178
189
|
// and we only run OSC 11 once.
|
|
179
190
|
let preResolvedTheme;
|
|
180
191
|
if (args.resume && !args.sessionName) {
|
|
@@ -186,13 +197,13 @@ async function main() {
|
|
|
186
197
|
else {
|
|
187
198
|
const themeConfig = userConfig.getTheme();
|
|
188
199
|
if (themeConfig.mode === "auto") {
|
|
189
|
-
const { detectTerminalTheme } = await import("./tui
|
|
200
|
+
const { detectTerminalTheme } = await import("./tui/detect-theme.js");
|
|
190
201
|
preResolvedTheme = await detectTerminalTheme();
|
|
191
202
|
}
|
|
192
203
|
else {
|
|
193
204
|
preResolvedTheme = themeConfig.mode;
|
|
194
205
|
}
|
|
195
|
-
const { runSessionPicker } = await import("./tui-
|
|
206
|
+
const { runSessionPicker } = await import("./tui-opentui/run-session-picker.js");
|
|
196
207
|
const picked = await runSessionPicker({
|
|
197
208
|
currentCwd: args.cwd,
|
|
198
209
|
currentSessions,
|
|
@@ -216,6 +227,7 @@ async function main() {
|
|
|
216
227
|
: SessionManager.createFresh(args.cwd);
|
|
217
228
|
resumedExistingSession = false;
|
|
218
229
|
}
|
|
230
|
+
sessionPromptCacheKey = sessionManager.getOrCreatePromptCacheKey();
|
|
219
231
|
// Model resolution:
|
|
220
232
|
// 1. Session metadata 2. User-configured default model 3. CLI flag
|
|
221
233
|
// No implicit built-in model fallback.
|
|
@@ -262,10 +274,10 @@ async function main() {
|
|
|
262
274
|
mode: initialMode,
|
|
263
275
|
workingDir: args.cwd,
|
|
264
276
|
tools: tools.map((tool) => tool.name),
|
|
265
|
-
skills: skillSummaries,
|
|
266
277
|
memoryPrompt,
|
|
267
278
|
});
|
|
268
279
|
const budgetLedger = new BudgetLedger();
|
|
280
|
+
let sessionTitleUpdater;
|
|
269
281
|
const agent = new Agent({
|
|
270
282
|
provider: activeProvider
|
|
271
283
|
? createProvider(activeProviderId, activeProvider.apiKey, activeProvider.baseURL)
|
|
@@ -289,6 +301,7 @@ async function main() {
|
|
|
289
301
|
if (message.role === "meta")
|
|
290
302
|
return;
|
|
291
303
|
sessionManager.appendMessage(message);
|
|
304
|
+
sessionTitleUpdater?.handlePersistedMessage(message);
|
|
292
305
|
if (message.role === "assistant") {
|
|
293
306
|
recordMemoryCitations(args.cwd, message.content);
|
|
294
307
|
}
|
|
@@ -318,7 +331,13 @@ async function main() {
|
|
|
318
331
|
});
|
|
319
332
|
agentRef = agent;
|
|
320
333
|
if (sessionManager) {
|
|
321
|
-
|
|
334
|
+
sessionTitleUpdater = createSessionTitleUpdater({
|
|
335
|
+
sessionManager,
|
|
336
|
+
complete: (messages, completeOptions) => agent.complete(messages, completeOptions),
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
if (sessionManager) {
|
|
340
|
+
sessionManager.updateMetadata({
|
|
322
341
|
...(agent.model ? { model: agent.model } : {}),
|
|
323
342
|
cwd: args.cwd,
|
|
324
343
|
thinkingLevel: agent.thinking,
|
|
@@ -405,9 +424,9 @@ async function main() {
|
|
|
405
424
|
detectedTheme = preResolvedTheme;
|
|
406
425
|
}
|
|
407
426
|
else if (themeConfig.mode === "auto") {
|
|
408
|
-
// Probe before
|
|
409
|
-
// runtime
|
|
410
|
-
const { detectTerminalTheme } = await import("./tui
|
|
427
|
+
// Probe before OpenTUI owns stdin. OSC 11 needs raw mode, and the
|
|
428
|
+
// runtime renderer can consume the reply before startup code sees it.
|
|
429
|
+
const { detectTerminalTheme } = await import("./tui/detect-theme.js");
|
|
411
430
|
detectedTheme = await detectTerminalTheme();
|
|
412
431
|
}
|
|
413
432
|
else {
|
|
@@ -430,7 +449,7 @@ async function main() {
|
|
|
430
449
|
runMemorySummary,
|
|
431
450
|
runMemoryRefresh,
|
|
432
451
|
};
|
|
433
|
-
const { runTui } = await import("./tui
|
|
452
|
+
const { runTui } = await import("./tui/run.js");
|
|
434
453
|
await runTui(agent, args, {
|
|
435
454
|
...commonOptions,
|
|
436
455
|
themeMode: themeConfig.mode,
|
|
@@ -439,14 +458,61 @@ async function main() {
|
|
|
439
458
|
onThemeModeChange: (mode) => userConfig.setThemeMode(mode),
|
|
440
459
|
});
|
|
441
460
|
if (sessionManager) {
|
|
442
|
-
|
|
443
|
-
|
|
461
|
+
printOpenTuiExitSummary(sessionManager, {
|
|
462
|
+
resumed: resumedExistingSession,
|
|
463
|
+
theme: detectedTheme,
|
|
464
|
+
});
|
|
444
465
|
}
|
|
445
466
|
}
|
|
446
467
|
finally {
|
|
447
468
|
await shutdownRuntime();
|
|
448
469
|
}
|
|
449
470
|
}
|
|
471
|
+
function printOpenTuiExitSummary(sessionManager, options) {
|
|
472
|
+
if (!process.stdout.isTTY)
|
|
473
|
+
return;
|
|
474
|
+
const sessionName = basename(sessionManager.getSessionFile());
|
|
475
|
+
const sessionId = sessionName.replace(/\.jsonl$/, "");
|
|
476
|
+
const title = truncateVisual(normalizeSingleLine(sessionManager.getMetadata().title ?? ""), 64);
|
|
477
|
+
const sessionLabel = title || `${options.resumed ? "Session" : "New session"} - ${sessionId}`;
|
|
478
|
+
const continueCommand = `bubble --resume --session ${sessionName}`;
|
|
479
|
+
const colors = options.theme === "light"
|
|
480
|
+
? {
|
|
481
|
+
markMuted: chalk.hex("#8C8C8C"),
|
|
482
|
+
markStrong: chalk.hex("#1C1C1C"),
|
|
483
|
+
markBrand: chalk.hex("#8B4A00"),
|
|
484
|
+
label: chalk.hex("#6F7377"),
|
|
485
|
+
value: chalk.hex("#171717").bold,
|
|
486
|
+
}
|
|
487
|
+
: {
|
|
488
|
+
markMuted: chalk.hex("#9CA3AF"),
|
|
489
|
+
markStrong: chalk.hex("#F4F4F5"),
|
|
490
|
+
markBrand: chalk.hex("#F5A742"),
|
|
491
|
+
label: chalk.hex("#808080"),
|
|
492
|
+
value: chalk.hex("#EEEEEE").bold,
|
|
493
|
+
};
|
|
494
|
+
const label = (value) => colors.label(value.padEnd(10));
|
|
495
|
+
const logoColor = (tone) => {
|
|
496
|
+
switch (tone) {
|
|
497
|
+
case "brand": return colors.markBrand;
|
|
498
|
+
case "ink": return colors.markStrong;
|
|
499
|
+
case "stone": return colors.markMuted;
|
|
500
|
+
case "soft": return colors.label;
|
|
501
|
+
case "caption": return colors.label;
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
for (const line of BUBBLE_WORDMARK) {
|
|
505
|
+
if (line.segments) {
|
|
506
|
+
console.log(line.segments.map((segment) => logoColor(segment.tone)(segment.text)).join(""));
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
console.log(logoColor(line.tone ?? "caption")(line.text ?? ""));
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
console.log();
|
|
513
|
+
console.log(`${label("Session")}${colors.value(sessionLabel)}`);
|
|
514
|
+
console.log(`${label("Continue")}${colors.value(continueCommand)}`);
|
|
515
|
+
}
|
|
450
516
|
async function readPipedStdin() {
|
|
451
517
|
if (process.stdin.isTTY)
|
|
452
518
|
return undefined;
|
package/dist/prompt/compose.js
CHANGED
|
@@ -8,7 +8,6 @@ import { buildGptProviderPrompt } from "./provider-prompts/gpt.js";
|
|
|
8
8
|
import { buildKimiProviderPrompt } from "./provider-prompts/kimi.js";
|
|
9
9
|
import { buildEnvironmentPrompt, defaultToolNames } from "./environment.js";
|
|
10
10
|
import { buildRuntimePrompt } from "./runtime.js";
|
|
11
|
-
import { buildSkillsPrompt } from "./skills.js";
|
|
12
11
|
export function composeSystemPrompt(options = {}) {
|
|
13
12
|
const agentName = options.agentName ?? "Bubble";
|
|
14
13
|
const providerPrompt = buildProviderPrompt(agentName, options.configuredProvider, options.configuredModelId, options.configuredModel);
|
|
@@ -26,14 +25,12 @@ export function composeSystemPrompt(options = {}) {
|
|
|
26
25
|
mode: options.mode,
|
|
27
26
|
guidelines: buildGuidelines(options.tools ?? defaultToolNames, options.guidelines ?? []),
|
|
28
27
|
});
|
|
29
|
-
const skillsPrompt = buildSkillsPrompt(options.skills ?? []);
|
|
30
28
|
return [
|
|
31
29
|
providerPrompt,
|
|
32
30
|
environmentPrompt,
|
|
33
31
|
runtimePrompt,
|
|
34
32
|
options.agentProfilePrompt,
|
|
35
33
|
options.memoryPrompt,
|
|
36
|
-
skillsPrompt,
|
|
37
34
|
].filter(Boolean).join("\n\n");
|
|
38
35
|
}
|
|
39
36
|
function buildProviderPrompt(agentName, providerId, modelId, modelName) {
|
|
@@ -80,6 +77,9 @@ function buildGuidelines(tools, extraGuidelines) {
|
|
|
80
77
|
if (tools.includes("question")) {
|
|
81
78
|
add("When the user is explicitly discussing, brainstorming, or shaping an approach instead of asking for immediate execution, use the question tool for targeted clarification or preference choices when it would materially improve the discussion; do not use it for generic permission-to-proceed questions");
|
|
82
79
|
}
|
|
80
|
+
if (tools.includes("skill_search") && tools.includes("skill")) {
|
|
81
|
+
add("Skills may provide specialized workflows. When a task appears to match a specialized workflow, call skill_search to find relevant skills, then call skill with the exact name to load the selected skill before applying it");
|
|
82
|
+
}
|
|
83
83
|
if (tools.includes("todo_write")) {
|
|
84
84
|
add("Use todo_write to plan any task that needs three or more concrete steps before you start. Mark each item completed as soon as it is done; do not batch updates");
|
|
85
85
|
}
|
|
@@ -14,6 +14,7 @@ export const defaultToolSnippets = {
|
|
|
14
14
|
send_input: "Send follow-up input to an existing subagent thread",
|
|
15
15
|
close_agent: "Close or cancel a spawned subagent thread",
|
|
16
16
|
question: "Ask the user structured questions when clarification or preference choices would materially improve the work",
|
|
17
|
+
skill_search: "Search available skills by name, description, tags, and source",
|
|
17
18
|
skill: "Load a named skill with specialized instructions and bundled resources",
|
|
18
19
|
todo_write: "Plan and track multi-step work. Mark each task completed as soon as it is done — do not batch.",
|
|
19
20
|
};
|
|
@@ -32,6 +33,7 @@ export const defaultToolNames = [
|
|
|
32
33
|
"send_input",
|
|
33
34
|
"close_agent",
|
|
34
35
|
"question",
|
|
36
|
+
"skill_search",
|
|
35
37
|
"skill",
|
|
36
38
|
"todo_write",
|
|
37
39
|
];
|
package/dist/prompt/reminders.js
CHANGED
|
@@ -20,7 +20,7 @@ const PLAN_MODE_ENTER = `
|
|
|
20
20
|
Plan mode is now ACTIVE.
|
|
21
21
|
|
|
22
22
|
Rules while in plan mode:
|
|
23
|
-
- Only read-only tools are allowed, including read, glob, grep, lsp, web_search, web_fetch, spawn_agent, wait_agent, send_input, close_agent, skill, todo_write, tool_search, question, and exit_plan_mode.
|
|
23
|
+
- Only read-only tools are allowed, including read, glob, grep, lsp, web_search, web_fetch, spawn_agent, wait_agent, send_input, close_agent, skill_search, skill, todo_write, tool_search, question, and exit_plan_mode.
|
|
24
24
|
- Writes, edits, and shell commands WILL be rejected by the harness; do not try them.
|
|
25
25
|
- Do not edit files or claim implementation is complete while plan mode is active.
|
|
26
26
|
- Investigate the codebase, then use the question tool to clarify important ambiguities, tradeoffs, requirements, or preference choices that would materially change the plan.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Provider, ReasoningEffort, ThinkingLevel } from "./types.js";
|
|
1
|
+
import type { Provider, ReasoningEffort, ThinkingLevel, TokenUsage } from "./types.js";
|
|
2
2
|
export interface CodexModelDescriptor {
|
|
3
3
|
id: string;
|
|
4
4
|
displayName?: string;
|
|
@@ -17,7 +17,14 @@ export declare function createOpenAICodexProvider(options: {
|
|
|
17
17
|
apiKey: string;
|
|
18
18
|
baseURL: string;
|
|
19
19
|
thinkingLevel?: ThinkingLevel;
|
|
20
|
+
promptCacheKey?: string;
|
|
20
21
|
}): Provider;
|
|
22
|
+
export declare function normalizeOpenAICodexUsage(usage: any): TokenUsage;
|
|
23
|
+
export declare function buildOpenAICodexPromptCacheKey(input: {
|
|
24
|
+
seed?: string;
|
|
25
|
+
providerId?: string;
|
|
26
|
+
model: string;
|
|
27
|
+
}): string | undefined;
|
|
21
28
|
export declare function fetchOpenAICodexModels(options: {
|
|
22
29
|
baseURL: string;
|
|
23
30
|
accessToken: string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { listBuiltinModels } from "./model-catalog.js";
|
|
2
3
|
import { resolveProviderRequestConfig } from "./provider-transform.js";
|
|
3
4
|
const DEFAULT_CODEX_BASE_URL = "https://chatgpt.com/backend-api";
|
|
@@ -53,6 +54,8 @@ export function createOpenAICodexProvider(options) {
|
|
|
53
54
|
tools: chatOptions.tools,
|
|
54
55
|
reasoningEffort: requestConfig.reasoningEffort,
|
|
55
56
|
sessionId,
|
|
57
|
+
providerId: options.providerId,
|
|
58
|
+
promptCacheKey: options.promptCacheKey,
|
|
56
59
|
})),
|
|
57
60
|
});
|
|
58
61
|
if (!response.ok) {
|
|
@@ -164,14 +167,7 @@ export function createOpenAICodexProvider(options) {
|
|
|
164
167
|
if (usage) {
|
|
165
168
|
yield {
|
|
166
169
|
type: "usage",
|
|
167
|
-
usage:
|
|
168
|
-
promptTokens: typeof usage.input_tokens === "number" ? usage.input_tokens : 0,
|
|
169
|
-
completionTokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0,
|
|
170
|
-
reasoningTokens: typeof usage.output_tokens_details?.reasoning_tokens === "number"
|
|
171
|
-
? usage.output_tokens_details.reasoning_tokens
|
|
172
|
-
: undefined,
|
|
173
|
-
totalTokens: typeof usage.total_tokens === "number" ? usage.total_tokens : undefined,
|
|
174
|
-
},
|
|
170
|
+
usage: normalizeOpenAICodexUsage(usage),
|
|
175
171
|
};
|
|
176
172
|
}
|
|
177
173
|
continue;
|
|
@@ -195,6 +191,30 @@ export function createOpenAICodexProvider(options) {
|
|
|
195
191
|
}
|
|
196
192
|
return { streamChat, complete };
|
|
197
193
|
}
|
|
194
|
+
export function normalizeOpenAICodexUsage(usage) {
|
|
195
|
+
const promptTokens = typeof usage?.input_tokens === "number" ? usage.input_tokens : 0;
|
|
196
|
+
const cachedTokens = typeof usage?.input_tokens_details?.cached_tokens === "number"
|
|
197
|
+
? usage.input_tokens_details.cached_tokens
|
|
198
|
+
: undefined;
|
|
199
|
+
return {
|
|
200
|
+
promptTokens,
|
|
201
|
+
completionTokens: typeof usage?.output_tokens === "number" ? usage.output_tokens : 0,
|
|
202
|
+
promptCacheHitTokens: cachedTokens,
|
|
203
|
+
promptCacheMissTokens: cachedTokens !== undefined ? Math.max(0, promptTokens - cachedTokens) : undefined,
|
|
204
|
+
reasoningTokens: typeof usage?.output_tokens_details?.reasoning_tokens === "number"
|
|
205
|
+
? usage.output_tokens_details.reasoning_tokens
|
|
206
|
+
: undefined,
|
|
207
|
+
totalTokens: typeof usage?.total_tokens === "number" ? usage.total_tokens : undefined,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
export function buildOpenAICodexPromptCacheKey(input) {
|
|
211
|
+
const seed = input.seed?.trim();
|
|
212
|
+
if (!seed)
|
|
213
|
+
return undefined;
|
|
214
|
+
return createHash("sha256")
|
|
215
|
+
.update(`bubble:${input.providerId || "openai-codex"}:${input.model}:${seed}`)
|
|
216
|
+
.digest("hex");
|
|
217
|
+
}
|
|
198
218
|
export async function fetchOpenAICodexModels(options) {
|
|
199
219
|
const accountId = extractChatGptAccountId(options.accessToken);
|
|
200
220
|
if (!accountId) {
|
|
@@ -228,7 +248,11 @@ function buildRequestBody(messages, options) {
|
|
|
228
248
|
instructions: instructions || undefined,
|
|
229
249
|
input,
|
|
230
250
|
include: ["reasoning.encrypted_content"],
|
|
231
|
-
prompt_cache_key:
|
|
251
|
+
prompt_cache_key: buildOpenAICodexPromptCacheKey({
|
|
252
|
+
seed: options.promptCacheKey ?? options.sessionId,
|
|
253
|
+
providerId: options.providerId,
|
|
254
|
+
model: options.model,
|
|
255
|
+
}),
|
|
232
256
|
tool_choice: "auto",
|
|
233
257
|
parallel_tool_calls: true,
|
|
234
258
|
text: { verbosity: "medium" },
|
package/dist/provider.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface ProviderInstanceOptions {
|
|
|
21
21
|
baseURL: string;
|
|
22
22
|
/** Requested thinking level */
|
|
23
23
|
thinkingLevel?: ThinkingLevel;
|
|
24
|
+
/** Stable per-session seed for provider prompt caches. */
|
|
25
|
+
promptCacheKey?: string;
|
|
24
26
|
}
|
|
25
27
|
export declare function createUnavailableProvider(message: string): Provider;
|
|
26
28
|
export declare function createProviderInstance(options: ProviderInstanceOptions): Provider;
|