newmark-agent 0.3.12 → 0.4.2
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/cli-commands.d.ts +1 -0
- package/dist/cli-commands.js +11 -2
- package/dist/context/domain/types.d.ts +37 -0
- package/dist/context/services/context-orchestrator.js +2 -0
- package/dist/conversation-utility-host.bundle.cjs +1259 -132
- package/dist/conversation-utility-host.js +3 -0
- package/dist/core/agent.d.ts +139 -5
- package/dist/core/agent.js +964 -82
- package/dist/core/agentKernel/agent-loop.js +29 -3
- package/dist/core/agentKernel/types.d.ts +7 -0
- package/dist/core/agentKernelRunner.d.ts +2 -0
- package/dist/core/agentKernelRunner.js +121 -19
- package/dist/core/conversationKernel.d.ts +5 -0
- package/dist/core/conversationKernel.js +29 -0
- package/dist/core/dshCompatibility.d.ts +198 -0
- package/dist/core/dshCompatibility.js +600 -0
- package/dist/core/electronUtilityAgentClient.d.ts +4 -0
- package/dist/core/electronUtilityAgentClient.js +4 -0
- package/dist/core/electronUtilityRuntimePool.d.ts +8 -0
- package/dist/core/electronUtilityRuntimePool.js +14 -0
- package/dist/core/mcpManager.d.ts +1 -0
- package/dist/core/mcpManager.js +100 -10
- package/dist/core/subagent.d.ts +6 -0
- package/dist/core/subagent.js +22 -1
- package/dist/core/toolPolicy.d.ts +15 -0
- package/dist/core/toolPolicy.js +174 -1
- package/dist/core/types.d.ts +1 -1
- package/dist/core/utilityAgentProtocol.d.ts +8 -1
- package/dist/core/workspace.d.ts +9 -0
- package/dist/core/workspace.js +48 -1
- package/dist/core/wslAgentClient.d.ts +4 -0
- package/dist/core/wslAgentClient.js +4 -0
- package/dist/core/wslAgentProtocol.d.ts +8 -1
- package/dist/core/wslAgentRuntimePool.d.ts +8 -0
- package/dist/core/wslAgentRuntimePool.js +15 -0
- package/dist/launcher.js +8 -0
- package/dist/llm/provider.d.ts +1 -1
- package/dist/llm/provider.js +4 -3
- package/dist/main.js +163 -11
- package/dist/preload.js +11 -0
- package/dist/providers/chat-completions.adapter.js +41 -15
- package/dist/providers/provider-adapter.d.ts +3 -0
- package/dist/toolchain/registry/tool-registry.d.ts +13 -1
- package/dist/toolchain/registry/tool-registry.js +8 -0
- package/dist/toolchain/registry-seeder.js +52 -6
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +39 -2
- package/dist/tools/nativeTools.js +6 -1
- package/dist/tui/src/app.js +24 -0
- package/dist/tui/src/i18n.js +151 -0
- package/dist/tui/src/render.js +152 -61
- package/dist/tui/src/state.js +83 -0
- package/dist/ui/index.html +2669 -234
- package/dist/ui/lucide-sprite.svg +31 -0
- package/dist/wsl-agent-host.bundle.cjs +1259 -132
- package/dist/wsl-agent-host.js +3 -0
- package/package.json +6 -10
- package/Flow/Electron-Debug-Release.Flow.json +0 -43
- package/Flow/Flow.md +0 -9
- package/Flow/UI-Feature-Integration.Flow.json +0 -96
|
@@ -11,6 +11,10 @@ export interface WslTargetRuntimeClient {
|
|
|
11
11
|
requestStop(target: ConversationRuntimeTarget, runId?: string): Promise<WslAgentStopResult>;
|
|
12
12
|
enqueueGuide(target: ConversationRuntimeTarget, envelope: ConversationInputEnvelope): Promise<GuideReceipt>;
|
|
13
13
|
checkpoint(target: ConversationRuntimeTarget): Promise<Record<string, unknown>>;
|
|
14
|
+
contextCompress?(target: ConversationRuntimeTarget, options?: {
|
|
15
|
+
keepRecent?: number;
|
|
16
|
+
force?: boolean;
|
|
17
|
+
}): Promise<Record<string, unknown>>;
|
|
14
18
|
rateAutoRoute?(target: ConversationRuntimeTarget, score: number, routeId?: string): Promise<WslAutoRouteRatingResult>;
|
|
15
19
|
setWorkRunExpanded(target: ConversationRuntimeTarget, runId: string, expanded: boolean): Promise<boolean>;
|
|
16
20
|
setMode?(target: ConversationRuntimeTarget, mode: AgentMode): Promise<AgentMode>;
|
|
@@ -69,6 +73,10 @@ export declare class WslAgentRuntimePool {
|
|
|
69
73
|
requestStop(target: ConversationRuntimeTarget, runId?: string): Promise<WslPoolStopResult>;
|
|
70
74
|
enqueueGuide(envelope: ConversationInputEnvelope): Promise<GuideReceipt>;
|
|
71
75
|
checkpoint(target: ConversationRuntimeTarget): Promise<Record<string, unknown>>;
|
|
76
|
+
contextCompress(target: ConversationRuntimeTarget, options?: {
|
|
77
|
+
keepRecent?: number;
|
|
78
|
+
force?: boolean;
|
|
79
|
+
}): Promise<Record<string, unknown>>;
|
|
72
80
|
rateAutoRoute(target: ConversationRuntimeTarget, score: number, routeId?: string): Promise<WslAutoRouteRatingResult>;
|
|
73
81
|
setWorkRunExpanded(target: ConversationRuntimeTarget, runId: string, expanded: boolean): Promise<boolean>;
|
|
74
82
|
setInputMode(target: ConversationRuntimeTarget, mode: string): Promise<'guide' | 'next' | null>;
|
|
@@ -214,6 +214,21 @@ class WslAgentRuntimePool {
|
|
|
214
214
|
this.release(entry, true);
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
|
+
async contextCompress(target, options = {}) {
|
|
218
|
+
const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
|
|
219
|
+
const entry = await this.acquire(normalized);
|
|
220
|
+
try {
|
|
221
|
+
if (entry.stopIntent)
|
|
222
|
+
return { ok: false, error: 'Context compression is unavailable while this conversation is stopping.' };
|
|
223
|
+
if (!entry.client.contextCompress)
|
|
224
|
+
return { ok: false, error: 'Context compression is unavailable in this runtime.' };
|
|
225
|
+
entry.lastSnapshot = null;
|
|
226
|
+
return await entry.client.contextCompress(normalized, options);
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
this.release(entry, true);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
217
232
|
async rateAutoRoute(target, score, routeId = '') {
|
|
218
233
|
const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
|
|
219
234
|
const entry = await this.acquireExisting(normalized);
|
package/dist/launcher.js
CHANGED
|
@@ -49,6 +49,7 @@ const cli_help_1 = require("./cli-help");
|
|
|
49
49
|
const rawArgs = process.argv.slice(2);
|
|
50
50
|
const args = rawArgs[0] === '--' ? rawArgs.slice(1) : rawArgs;
|
|
51
51
|
const hasCliCommand = args.some(a => cli_commands_1.CLI_COMMANDS.includes(a));
|
|
52
|
+
const cliCommand = args.find(a => cli_commands_1.CLI_COMMANDS.includes(a));
|
|
52
53
|
const isEdit = args[0] === 'edit';
|
|
53
54
|
const editFile = isEdit ? args[1] : '';
|
|
54
55
|
const isFlow = args[0] === 'flow';
|
|
@@ -64,6 +65,13 @@ if (invalidArgument) {
|
|
|
64
65
|
console.error(`Invalid Newmark argument: ${invalidArgument}`);
|
|
65
66
|
process.exit(2);
|
|
66
67
|
}
|
|
68
|
+
// Command help is a terminating, read-only discovery operation. Resolve it
|
|
69
|
+
// before first-run initialization or GUI forwarding so `Newmark.exe send
|
|
70
|
+
// --help` cannot accidentally enter Electron or instantiate an Agent.
|
|
71
|
+
if (hasCliCommand && cliCommand && (0, cli_commands_1.cliHelpRequested)(args)) {
|
|
72
|
+
console.log((0, cli_commands_1.cliCommandHelp)(cliCommand));
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
67
75
|
function pathArgValue(values, key) {
|
|
68
76
|
const prefix = `${key}=`;
|
|
69
77
|
const inlineIdx = values.findIndex(a => a.startsWith(prefix));
|
package/dist/llm/provider.d.ts
CHANGED
|
@@ -97,7 +97,7 @@ export declare class LLMProvider {
|
|
|
97
97
|
private toTransportResponse;
|
|
98
98
|
private toNormalizedMessages;
|
|
99
99
|
private toNormalizedTools;
|
|
100
|
-
chatStreamWithTools(model: string, messages: Array<Record<string, unknown>>, systemPrompt: string | null, temperature: number, maxTokens: number, tools: unknown[], signal?: AbortSignal, reasoningTier?: string): AsyncGenerator<StreamToken>;
|
|
100
|
+
chatStreamWithTools(model: string, messages: Array<Record<string, unknown>>, systemPrompt: string | null, temperature: number, maxTokens: number, tools: unknown[], signal?: AbortSignal, reasoningTier?: string, sessionId?: string): AsyncGenerator<StreamToken>;
|
|
101
101
|
/**
|
|
102
102
|
* GitHub Models streaming path. Preserved as a dedicated implementation
|
|
103
103
|
* because the provider adapters (V2) do not serialize the GitHub Models
|
package/dist/llm/provider.js
CHANGED
|
@@ -843,7 +843,7 @@ class LLMProvider {
|
|
|
843
843
|
* The emitted request body and StreamToken stream are byte-equivalent to
|
|
844
844
|
* the legacy inlined path.
|
|
845
845
|
*/
|
|
846
|
-
async *chatStreamWithToolsV2(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier) {
|
|
846
|
+
async *chatStreamWithToolsV2(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier, sessionId) {
|
|
847
847
|
const mode = this.openAITransportMode();
|
|
848
848
|
if (mode === 'responses') {
|
|
849
849
|
yield* this.adapterResponsesBridge(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier);
|
|
@@ -861,6 +861,7 @@ class LLMProvider {
|
|
|
861
861
|
maxOutputTokens: maxTokens,
|
|
862
862
|
apiKey: this.apiKey,
|
|
863
863
|
baseUrl: this.cleanBaseUrl(),
|
|
864
|
+
...(sessionId ? { sessionId } : {}),
|
|
864
865
|
};
|
|
865
866
|
const serialized = await adapter.serializeRequest(request);
|
|
866
867
|
serialized.body.stream = mode === 'chat' ? false : true;
|
|
@@ -1080,7 +1081,7 @@ class LLMProvider {
|
|
|
1080
1081
|
};
|
|
1081
1082
|
});
|
|
1082
1083
|
}
|
|
1083
|
-
async *chatStreamWithTools(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier) {
|
|
1084
|
+
async *chatStreamWithTools(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier, sessionId) {
|
|
1084
1085
|
if (signal?.aborted)
|
|
1085
1086
|
throw abortFailure(signal);
|
|
1086
1087
|
if (this.protocol() === 'anthropic') {
|
|
@@ -1092,7 +1093,7 @@ class LLMProvider {
|
|
|
1092
1093
|
return;
|
|
1093
1094
|
}
|
|
1094
1095
|
if (this.useProviderAdaptersV2) {
|
|
1095
|
-
yield* this.chatStreamWithToolsV2(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier);
|
|
1096
|
+
yield* this.chatStreamWithToolsV2(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier, sessionId);
|
|
1096
1097
|
return;
|
|
1097
1098
|
}
|
|
1098
1099
|
throw new Error('LLMProvider legacy OpenAI streaming was removed in dev-0.3.0: enable provider_adapters_v2 (useProviderAdaptersV2).');
|
package/dist/main.js
CHANGED
|
@@ -73,6 +73,7 @@ const startupPrewarm_1 = require("./core/startupPrewarm");
|
|
|
73
73
|
const runtimeShutdown_1 = require("./core/runtimeShutdown");
|
|
74
74
|
const runtimeLifecycle_1 = require("./core/runtimeLifecycle");
|
|
75
75
|
const compat_1 = require("./core/compat");
|
|
76
|
+
const dshCompatibility_1 = require("./core/dshCompatibility");
|
|
76
77
|
const mcpManager_1 = require("./core/mcpManager");
|
|
77
78
|
const cli_help_1 = require("./cli-help");
|
|
78
79
|
const APP_NAME = 'Newmark Agent';
|
|
@@ -123,6 +124,7 @@ let browserUseEngine = null;
|
|
|
123
124
|
// the authoritative Browser-Use/right-sidebar binding.
|
|
124
125
|
const browserGuestContentsByHost = new Map();
|
|
125
126
|
const browserGuestBindingsByRuntime = new Map();
|
|
127
|
+
const browserGuestKeyboardBridgeIds = new Set();
|
|
126
128
|
function browserGuestRuntimeKey(target) {
|
|
127
129
|
return (0, conversationTarget_1.normalizeConversationTarget)(target).runtimeKey;
|
|
128
130
|
}
|
|
@@ -686,9 +688,13 @@ function resolveTuiWorkspacePath(args, root) {
|
|
|
686
688
|
// while making the safe one-argument form fully self-contained.
|
|
687
689
|
return pathArgValue(args, '--root') ? root : process.cwd();
|
|
688
690
|
}
|
|
691
|
+
// Set immediately after argument resolution so startup failures from an
|
|
692
|
+
// explicit temporary root are recorded beside that root instead of leaking a
|
|
693
|
+
// diagnostic file into the user's canonical runtime.
|
|
694
|
+
let startupRuntimeRoot = '';
|
|
689
695
|
function startupLogPath() {
|
|
690
696
|
try {
|
|
691
|
-
const userData = userRuntimeRoot();
|
|
697
|
+
const userData = startupRuntimeRoot || userRuntimeRoot();
|
|
692
698
|
fs.mkdirSync(userData, { recursive: true });
|
|
693
699
|
return path.join(userData, 'startup.log');
|
|
694
700
|
}
|
|
@@ -841,7 +847,29 @@ function registerBrowserGuest(host, guest, requestedTarget) {
|
|
|
841
847
|
conversationId: target.conversationId,
|
|
842
848
|
});
|
|
843
849
|
browserGuestContentsByHost.set(host.id, guest.id);
|
|
850
|
+
if (!browserGuestKeyboardBridgeIds.has(guest.id)) {
|
|
851
|
+
browserGuestKeyboardBridgeIds.add(guest.id);
|
|
852
|
+
// WebView keyboard events do not bubble to the host renderer. Reserve only
|
|
853
|
+
// the two app-wide discovery surfaces and leave navigation/editing keys to
|
|
854
|
+
// the page itself.
|
|
855
|
+
guest.on('before-input-event', (event, input) => {
|
|
856
|
+
if (input.type !== 'keyDown' || input.isAutoRepeat || host.isDestroyed())
|
|
857
|
+
return;
|
|
858
|
+
const key = String(input.key || '').toLowerCase();
|
|
859
|
+
const noSecondaryModifiers = !input.alt && !input.control && !input.meta && !input.shift;
|
|
860
|
+
let commandId = '';
|
|
861
|
+
if (key === 'f1' && noSecondaryModifiers)
|
|
862
|
+
commandId = 'help.keyboardShortcuts';
|
|
863
|
+
else if (key === 'p' && (input.control || input.meta) && input.shift && !input.alt)
|
|
864
|
+
commandId = 'app.commandPalette';
|
|
865
|
+
if (!commandId)
|
|
866
|
+
return;
|
|
867
|
+
event.preventDefault();
|
|
868
|
+
host.send('keyboard:command', { id: commandId, source: 'browserGuest' });
|
|
869
|
+
});
|
|
870
|
+
}
|
|
844
871
|
guest.once('destroyed', () => {
|
|
872
|
+
browserGuestKeyboardBridgeIds.delete(guest.id);
|
|
845
873
|
if (browserGuestContentsByHost.get(host.id) === guest.id)
|
|
846
874
|
browserGuestContentsByHost.delete(host.id);
|
|
847
875
|
const binding = browserGuestBindingsByRuntime.get(target.runtimeKey);
|
|
@@ -1035,6 +1063,7 @@ const args = userArgs();
|
|
|
1035
1063
|
const command = args.find(a => a === 'flow' || a === 'edit');
|
|
1036
1064
|
const isTuiArg = args.some(arg => arg.toLowerCase() === '--tui');
|
|
1037
1065
|
const hasCliCommand = args.some(a => cli_commands_1.CLI_COMMANDS.includes(a));
|
|
1066
|
+
const cliCommand = args.find(a => cli_commands_1.CLI_COMMANDS.includes(a));
|
|
1038
1067
|
const isFlowArg = command === 'flow';
|
|
1039
1068
|
const isEditArg = command === 'edit';
|
|
1040
1069
|
const isHelpArg = !hasCliCommand && (args.some(arg => ['--help', '-h'].includes(arg.toLowerCase())) || args[0]?.toLowerCase() === 'help');
|
|
@@ -1048,18 +1077,34 @@ if (invalidArgument) {
|
|
|
1048
1077
|
console.error(`Invalid Newmark argument: ${invalidArgument}`);
|
|
1049
1078
|
process.exit(2);
|
|
1050
1079
|
}
|
|
1080
|
+
// Keep command-specific help on the same early-exit path as top-level help.
|
|
1081
|
+
// This matters for the packaged Electron entry: a command such as
|
|
1082
|
+
// `send --help` must never create a GUI window, start prewarm work, or touch a
|
|
1083
|
+
// user-data profile merely to print its contract.
|
|
1084
|
+
if (hasCliCommand && cliCommand && (0, cli_commands_1.cliHelpRequested)(args)) {
|
|
1085
|
+
console.log((0, cli_commands_1.cliCommandHelp)(cliCommand));
|
|
1086
|
+
process.exit(0);
|
|
1087
|
+
}
|
|
1051
1088
|
// Electron's Chromium profile is a separate state boundary from Newmark's
|
|
1052
1089
|
// business root. Bind both before any ready event so --root cannot leave
|
|
1053
1090
|
// Preferences, DIPS, DevTools ports, cookies, or session storage in the real
|
|
1054
1091
|
// default AppData directory. The dedicated subdirectories keep Chromium's
|
|
1055
1092
|
// files separate from the durable Newmark config/workspace files.
|
|
1056
1093
|
const runtimeRoot = resolveRoot(args);
|
|
1057
|
-
|
|
1094
|
+
startupRuntimeRoot = runtimeRoot;
|
|
1095
|
+
const explicitElectronUserDataRoot = pathArgValue(args, '--user-data-dir');
|
|
1096
|
+
const electronUserDataRoot = path.resolve(explicitElectronUserDataRoot || path.join(runtimeRoot, 'Electron'));
|
|
1058
1097
|
const electronSessionDataRoot = path.join(electronUserDataRoot, 'session-data');
|
|
1059
1098
|
try {
|
|
1060
1099
|
fs.mkdirSync(electronSessionDataRoot, { recursive: true });
|
|
1061
1100
|
electron_1.app.setPath('userData', electronUserDataRoot);
|
|
1062
1101
|
electron_1.app.setPath('sessionData', electronSessionDataRoot);
|
|
1102
|
+
// app.setPath is the Electron API contract; the switch makes the same
|
|
1103
|
+
// boundary visible to Chromium children, including the console-wrapper
|
|
1104
|
+
// `--` forwarding path where an early child may otherwise retain the default
|
|
1105
|
+
// profile before the first BrowserWindow is created.
|
|
1106
|
+
electron_1.app.commandLine.removeSwitch('user-data-dir');
|
|
1107
|
+
electron_1.app.commandLine.appendSwitch('user-data-dir', electronUserDataRoot);
|
|
1063
1108
|
}
|
|
1064
1109
|
catch (error) {
|
|
1065
1110
|
console.error(`Unable to isolate Electron user-data directory: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1659,6 +1704,12 @@ else {
|
|
|
1659
1704
|
win.focus();
|
|
1660
1705
|
}, 150);
|
|
1661
1706
|
});
|
|
1707
|
+
win.webContents.on('unresponsive', () => {
|
|
1708
|
+
logStartupFailure('renderer-unresponsive', new Error(`Window renderer became unresponsive (webContents ${win.webContents.id})`));
|
|
1709
|
+
});
|
|
1710
|
+
win.webContents.on('responsive', () => {
|
|
1711
|
+
recordStartup(`renderer-responsive-${win.webContents.id}`);
|
|
1712
|
+
});
|
|
1662
1713
|
if (!automationWakeMode)
|
|
1663
1714
|
win.maximize();
|
|
1664
1715
|
if (!automationWakeMode && showWindow) {
|
|
@@ -1674,15 +1725,18 @@ else {
|
|
|
1674
1725
|
return;
|
|
1675
1726
|
if (agent) {
|
|
1676
1727
|
const closeBehavior = agent.config.getStr('general', 'close_behavior');
|
|
1728
|
+
recordStartup(`window-close-requested-${closeBehavior || 'exit'}`);
|
|
1677
1729
|
if (closeBehavior === 'minimize') {
|
|
1678
1730
|
e.preventDefault();
|
|
1679
1731
|
win.hide();
|
|
1680
1732
|
createTray();
|
|
1733
|
+
recordStartup('window-hidden-to-tray');
|
|
1681
1734
|
return;
|
|
1682
1735
|
}
|
|
1683
1736
|
if (agent.config.getBool('general', 'auto_archive_on_close')) {
|
|
1684
1737
|
agent.archiveSession();
|
|
1685
1738
|
}
|
|
1739
|
+
recordStartup('window-close-exit');
|
|
1686
1740
|
}
|
|
1687
1741
|
});
|
|
1688
1742
|
win.on('closed', () => {
|
|
@@ -2299,6 +2353,7 @@ else {
|
|
|
2299
2353
|
(0, runtimeLifecycle_1.markRuntimeLifecycleClean)(root, 'main');
|
|
2300
2354
|
};
|
|
2301
2355
|
electron_1.app.on('will-quit', event => {
|
|
2356
|
+
recordStartup('will-quit');
|
|
2302
2357
|
// Window-close and tray-exit both enter this path. The runtime pools
|
|
2303
2358
|
// must get a bounded graceful-shutdown window regardless of which
|
|
2304
2359
|
// surface initiated the close; otherwise a stuck child can keep the
|
|
@@ -2462,6 +2517,32 @@ else {
|
|
|
2462
2517
|
activeFlowsByRuntimeKey.delete(key);
|
|
2463
2518
|
agent.clearStoredFlowSuspension(state.target.conversationId);
|
|
2464
2519
|
};
|
|
2520
|
+
// Archive is a destructive lifecycle boundary. It must cancel the Flow
|
|
2521
|
+
// owner itself before touching the runtime pool; Flow runs bypass the
|
|
2522
|
+
// ConversationKernel and therefore cannot be stopped by the pool alone.
|
|
2523
|
+
// Marking the state before aborting is important: the provider may reject
|
|
2524
|
+
// on the same turn, and its late catch/finally must not recreate a paused
|
|
2525
|
+
// conversation after archive has removed it.
|
|
2526
|
+
const interruptActiveFlowForArchive = (target) => {
|
|
2527
|
+
const key = activeFlowStateKey(target);
|
|
2528
|
+
const state = activeFlowsByRuntimeKey.get(key) || null;
|
|
2529
|
+
if (!state)
|
|
2530
|
+
return;
|
|
2531
|
+
state.archiveRequested = true;
|
|
2532
|
+
try {
|
|
2533
|
+
state.abortController?.abort(new Error(`Flow discarded because conversation was archived: ${state.name || state.workflow.name}`));
|
|
2534
|
+
}
|
|
2535
|
+
catch { }
|
|
2536
|
+
state.flowAgent?.abortActiveKernelRun();
|
|
2537
|
+
state.flowAgent?.interruptRunningConversationWorkRuns(state.target, 'force_interrupted');
|
|
2538
|
+
state.flowAgent?.clearStoredFlowSuspension(state.target.conversationId);
|
|
2539
|
+
if (agent?.workspace.current
|
|
2540
|
+
&& target.workspace
|
|
2541
|
+
&& path.resolve(agent.workspace.current.path) === path.resolve(target.workspace.path)) {
|
|
2542
|
+
agent.clearStoredFlowSuspension(target.conversationId);
|
|
2543
|
+
}
|
|
2544
|
+
activeFlowsByRuntimeKey.delete(key);
|
|
2545
|
+
};
|
|
2465
2546
|
const utilityHostToolHandler = (0, utilityHostToolRouter_1.createUtilityHostToolHandler)({
|
|
2466
2547
|
persistenceRoot: root,
|
|
2467
2548
|
isToolEnabled: toolName => !!agent && (0, nativeTools_1.isNativeToolEnabled)(toolName, agent.config.nativeToolEnabled()),
|
|
@@ -2851,6 +2932,14 @@ else {
|
|
|
2851
2932
|
};
|
|
2852
2933
|
}
|
|
2853
2934
|
catch (e) {
|
|
2935
|
+
if (flowState.archiveRequested) {
|
|
2936
|
+
// Archive already force-finalized the current Build ledger. Do not
|
|
2937
|
+
// persist an interrupted Flow suspension or return Flow ownership
|
|
2938
|
+
// to the renderer after the target has been removed.
|
|
2939
|
+
flowAgent.pendingOptions = [];
|
|
2940
|
+
suspended = false;
|
|
2941
|
+
return { ok: false, archived: true, error: 'Flow discarded because the conversation was archived.' };
|
|
2942
|
+
}
|
|
2854
2943
|
if (e instanceof flow_runner_1.FlowQuestionPendingError) {
|
|
2855
2944
|
suspended = true;
|
|
2856
2945
|
flowAgent.flowPc = e.componentId;
|
|
@@ -2944,6 +3033,11 @@ else {
|
|
|
2944
3033
|
const flowKey = activeFlowStateKey(flowTarget);
|
|
2945
3034
|
const flowAgent = isolatedConversationAgent(flowTarget);
|
|
2946
3035
|
suspension.flowAgent = flowAgent;
|
|
3036
|
+
// A previous Flow may have been interrupted just before its isolated
|
|
3037
|
+
// Agent flushed the work ledger. Reconcile only this explicitly paused
|
|
3038
|
+
// target before starting the next component; the normal runner guard
|
|
3039
|
+
// remains intact for genuine concurrent Builds.
|
|
3040
|
+
flowAgent.interruptRunningConversationWorkRuns(flowTarget, 'interrupted');
|
|
2947
3041
|
const flowAbortController = new AbortController();
|
|
2948
3042
|
suspension.abortController = flowAbortController;
|
|
2949
3043
|
activeFlowsByRuntimeKey.set(flowKey, suspension);
|
|
@@ -2978,6 +3072,11 @@ else {
|
|
|
2978
3072
|
};
|
|
2979
3073
|
}
|
|
2980
3074
|
catch (error) {
|
|
3075
|
+
if (suspension.archiveRequested) {
|
|
3076
|
+
flowAgent.pendingOptions = [];
|
|
3077
|
+
suspendedAgain = false;
|
|
3078
|
+
return { ok: false, archived: true, error: 'Flow discarded because the conversation was archived.' };
|
|
3079
|
+
}
|
|
2981
3080
|
if (error instanceof flow_runner_1.FlowQuestionPendingError) {
|
|
2982
3081
|
suspendedAgain = true;
|
|
2983
3082
|
flowAgent.flowPc = error.componentId;
|
|
@@ -2999,9 +3098,9 @@ else {
|
|
|
2999
3098
|
workRuns: flowAgent.getConversationSnapshot(flowAgent.activeConversationId).workRuns,
|
|
3000
3099
|
};
|
|
3001
3100
|
}
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3101
|
+
// A Stop/Esc during a resumed Flow is another pause, not a terminal
|
|
3102
|
+
// IPC failure. Fall through to the same interrupted-suspension path as
|
|
3103
|
+
// the initial Flow run so repeated pause/resume clicks remain valid.
|
|
3005
3104
|
suspendedAgain = true;
|
|
3006
3105
|
const resumeFailure = error;
|
|
3007
3106
|
const interruptedComponentId = typeof resumeFailure.componentId === 'number'
|
|
@@ -3154,6 +3253,12 @@ else {
|
|
|
3154
3253
|
runtimeDeferred: false,
|
|
3155
3254
|
};
|
|
3156
3255
|
});
|
|
3256
|
+
electron_1.ipcMain.handle('agent:setConversationBranchCommunication', async (_event, targetInput, enabled) => {
|
|
3257
|
+
if (!agent)
|
|
3258
|
+
return false;
|
|
3259
|
+
const target = conversationRuntimeTarget(targetInput || agent.activeConversationId || 'default');
|
|
3260
|
+
return mutateTargetConversation(target, () => isolatedConversationAgent(target).setBranchCommunication(enabled !== false));
|
|
3261
|
+
});
|
|
3157
3262
|
electron_1.ipcMain.handle('agent:computerUseState', async (_event, targetInput) => {
|
|
3158
3263
|
if (!agent)
|
|
3159
3264
|
return { enabled: false, occupied: false, runtimeKey: '' };
|
|
@@ -3691,6 +3796,18 @@ else {
|
|
|
3691
3796
|
? await ensureWslConversationPool().checkpoint(target)
|
|
3692
3797
|
: await ensureElectronUtilityPool().checkpoint(target);
|
|
3693
3798
|
});
|
|
3799
|
+
electron_1.ipcMain.handle('agent:compressContext', async (_event, request) => {
|
|
3800
|
+
if (!agent)
|
|
3801
|
+
throw new Error('Agent not initialized');
|
|
3802
|
+
const target = conversationRuntimeTarget(request);
|
|
3803
|
+
const options = {
|
|
3804
|
+
keepRecent: Number.isFinite(Number(request?.keepRecent)) ? Math.floor(Number(request.keepRecent)) : undefined,
|
|
3805
|
+
force: request?.force !== false,
|
|
3806
|
+
};
|
|
3807
|
+
return wslBackendEnabled()
|
|
3808
|
+
? await ensureWslConversationPool().contextCompress(target, options)
|
|
3809
|
+
: await ensureElectronUtilityPool().contextCompress(target, options);
|
|
3810
|
+
});
|
|
3694
3811
|
electron_1.ipcMain.handle('agent:rateAutoRoute', async (_event, request) => {
|
|
3695
3812
|
if (!agent)
|
|
3696
3813
|
return { ok: false, reason: 'no_active_auto_route' };
|
|
@@ -3847,9 +3964,14 @@ else {
|
|
|
3847
3964
|
mutatingRuntimeKeys.add(normalized.runtimeKey);
|
|
3848
3965
|
try {
|
|
3849
3966
|
// Archive is a destructive lifecycle command. It intentionally
|
|
3850
|
-
// bypasses the normal mutation/active-prompt guard
|
|
3851
|
-
//
|
|
3852
|
-
|
|
3967
|
+
// bypasses the normal mutation/active-prompt guard. Cancel the
|
|
3968
|
+
// conversation-local Flow synchronously, then start the resident
|
|
3969
|
+
// runtime hard-stop in the background so a stuck child cannot hold
|
|
3970
|
+
// the archive click hostage.
|
|
3971
|
+
interruptActiveFlowForArchive(normalized);
|
|
3972
|
+
void forceStopTargetRuntime(normalized).catch(error => {
|
|
3973
|
+
console.error('[Newmark] archive runtime force-stop failed:', error instanceof Error ? error.message : String(error));
|
|
3974
|
+
});
|
|
3853
3975
|
const currentWorkspacePath = path.resolve(agent.workspace.current?.path || '');
|
|
3854
3976
|
const targetWorkspacePath = path.resolve(normalized.workspace?.path || '');
|
|
3855
3977
|
const ownsTargetWorkspace = !!normalized.workspace
|
|
@@ -3861,6 +3983,7 @@ else {
|
|
|
3861
3983
|
// latest locked state snapshot, so rapid clicks do not serialize on
|
|
3862
3984
|
// large Markdown bodies or lose a sibling deletion.
|
|
3863
3985
|
const archiveOwner = ownsTargetWorkspace ? agent : isolatedConversationAgent(normalized);
|
|
3986
|
+
archiveOwner.clearStoredFlowSuspension(normalized.conversationId);
|
|
3864
3987
|
const archived = await archiveOwner.archiveConversationAsync(normalized.conversationId);
|
|
3865
3988
|
if (!archived)
|
|
3866
3989
|
return { ok: false, error: 'Conversation archive could not be written.' };
|
|
@@ -4205,6 +4328,20 @@ else {
|
|
|
4205
4328
|
}
|
|
4206
4329
|
return { error: 'Agent not initialized' };
|
|
4207
4330
|
});
|
|
4331
|
+
electron_1.ipcMain.handle('app:openWebUrl', async (_event, rawUrl) => {
|
|
4332
|
+
try {
|
|
4333
|
+
const target = new URL(String(rawUrl || ''));
|
|
4334
|
+
const allowedHosts = new Set(['github.com', 'www.github.com', 'npmjs.com', 'www.npmjs.com']);
|
|
4335
|
+
if (target.protocol !== 'https:' || !allowedHosts.has(target.hostname.toLowerCase()) || !!target.username || !!target.password || (!!target.port && target.port !== '443')) {
|
|
4336
|
+
return { ok: false, error: 'Only approved HTTPS documentation hosts can be opened.' };
|
|
4337
|
+
}
|
|
4338
|
+
await electron_1.shell.openExternal(target.toString());
|
|
4339
|
+
return { ok: true };
|
|
4340
|
+
}
|
|
4341
|
+
catch (error) {
|
|
4342
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
4343
|
+
}
|
|
4344
|
+
});
|
|
4208
4345
|
electron_1.ipcMain.handle('agent:selectWorkspace', async (_event, id) => {
|
|
4209
4346
|
if (agent) {
|
|
4210
4347
|
const requested = String(id || '').trim();
|
|
@@ -4502,13 +4639,25 @@ else {
|
|
|
4502
4639
|
const controller = new AbortController();
|
|
4503
4640
|
editorCompletionControllers.set(ownerId, controller);
|
|
4504
4641
|
try {
|
|
4505
|
-
|
|
4642
|
+
const requestId = String(request.requestId || '');
|
|
4643
|
+
const onTextDelta = requestId
|
|
4644
|
+
? (text) => {
|
|
4645
|
+
if (!controller.signal.aborted && !event.sender.isDestroyed())
|
|
4646
|
+
event.sender.send('agent:editorCompletionDelta', { requestId, text });
|
|
4647
|
+
}
|
|
4648
|
+
: undefined;
|
|
4649
|
+
return await (agent?.editorModelRequest({ ...request, completion: true, preferCopilot: true, onTextDelta }, controller.signal) || { ok: false, text: '', error: 'Agent not initialized' });
|
|
4506
4650
|
}
|
|
4507
4651
|
finally {
|
|
4508
4652
|
if (editorCompletionControllers.get(ownerId) === controller)
|
|
4509
4653
|
editorCompletionControllers.delete(ownerId);
|
|
4510
4654
|
}
|
|
4511
4655
|
});
|
|
4656
|
+
electron_1.ipcMain.handle('agent:editorCompleteCancel', async (event) => {
|
|
4657
|
+
const ownerId = event.sender.id;
|
|
4658
|
+
editorCompletionControllers.get(ownerId)?.abort(new Error('Editor completion cancelled'));
|
|
4659
|
+
return { ok: true };
|
|
4660
|
+
});
|
|
4512
4661
|
electron_1.ipcMain.handle('agent:editorAssist', async (_event, request) => {
|
|
4513
4662
|
return agent?.editorModelRequest({ ...request, completion: false }) || { ok: false, text: '', error: 'Agent not initialized' };
|
|
4514
4663
|
});
|
|
@@ -4605,6 +4754,7 @@ else {
|
|
|
4605
4754
|
})));
|
|
4606
4755
|
return { servers: mcpManager.list(), discovered };
|
|
4607
4756
|
});
|
|
4757
|
+
electron_1.ipcMain.handle('dsh:discover', async () => (0, dshCompatibility_1.discoverDshCompatibility)(root));
|
|
4608
4758
|
electron_1.ipcMain.handle('mcp:upsert', async (_event, input) => {
|
|
4609
4759
|
if (!mcpManager)
|
|
4610
4760
|
return { ok: false, error: 'MCP manager is unavailable.' };
|
|
@@ -4619,12 +4769,14 @@ else {
|
|
|
4619
4769
|
electron_1.ipcMain.handle('mcp:setEnabled', async (_event, id, enabled) => {
|
|
4620
4770
|
if (!mcpManager)
|
|
4621
4771
|
return { ok: false, error: 'MCP manager is unavailable.' };
|
|
4622
|
-
|
|
4772
|
+
const ok = mcpManager.setEnabled(id, enabled);
|
|
4773
|
+
return { ok, error: ok ? undefined : 'MCP server was not found.', servers: mcpManager.list() };
|
|
4623
4774
|
});
|
|
4624
4775
|
electron_1.ipcMain.handle('mcp:remove', async (_event, id) => {
|
|
4625
4776
|
if (!mcpManager)
|
|
4626
4777
|
return { ok: false, error: 'MCP manager is unavailable.' };
|
|
4627
|
-
|
|
4778
|
+
const ok = mcpManager.remove(id);
|
|
4779
|
+
return { ok, error: ok ? undefined : 'MCP server was not found.', servers: mcpManager.list() };
|
|
4628
4780
|
});
|
|
4629
4781
|
electron_1.ipcMain.handle('memoryLab:read', async (_event, selector) => {
|
|
4630
4782
|
if (!agent)
|
package/dist/preload.js
CHANGED
|
@@ -12,6 +12,7 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
12
12
|
sendMessage: (message, target) => ipcRenderer.invoke('agent:send', message, target),
|
|
13
13
|
enqueueGuide: (envelope) => ipcRenderer.invoke('agent:enqueueGuide', envelope),
|
|
14
14
|
checkpointConversation: (request) => ipcRenderer.invoke('agent:checkpointConversation', request),
|
|
15
|
+
compressContext: (request) => ipcRenderer.invoke('agent:compressContext', request),
|
|
15
16
|
rateAutoRoute: (request) => ipcRenderer.invoke('agent:rateAutoRoute', request),
|
|
16
17
|
stopConversation: (request) => ipcRenderer.invoke('agent:stopConversation', request),
|
|
17
18
|
setWorkRunExpanded: (request) => ipcRenderer.invoke('agent:setWorkRunExpanded', request),
|
|
@@ -32,12 +33,16 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
32
33
|
getConversationPlan: (conversationId) => ipcRenderer.invoke('agent:getConversationPlan', conversationId),
|
|
33
34
|
updateConversationPlan: (plan, conversationId) => ipcRenderer.invoke('agent:updateConversationPlan', plan, conversationId),
|
|
34
35
|
setConversationPinned: (id, pinned) => ipcRenderer.invoke('agent:setConversationPinned', id, pinned),
|
|
36
|
+
setConversationBranchCommunication: (target, enabled) => ipcRenderer.invoke('agent:setConversationBranchCommunication', target, enabled),
|
|
35
37
|
renameConversation: (id, title) => ipcRenderer.invoke('agent:renameConversation', id, title),
|
|
36
38
|
reorderConversations: (ids) => ipcRenderer.invoke('agent:reorderConversations', ids),
|
|
37
39
|
browserRegisterGuest: (guestContentsId, target) => ipcRenderer.invoke('browser:registerGuest', guestContentsId, target),
|
|
38
40
|
onBrowserEnsureGuest: (callback) => {
|
|
39
41
|
ipcRenderer.on('browser:ensureGuest', (_event, target) => callback(target));
|
|
40
42
|
},
|
|
43
|
+
onKeyboardCommand: (callback) => {
|
|
44
|
+
ipcRenderer.on('keyboard:command', (_event, payload) => callback(payload));
|
|
45
|
+
},
|
|
41
46
|
browserControl: (request) => ipcRenderer.invoke('browser:control', request),
|
|
42
47
|
computerUseState: (target) => ipcRenderer.invoke('agent:computerUseState', target),
|
|
43
48
|
setComputerUseEnabled: (target, enabled) => ipcRenderer.invoke('agent:setComputerUseEnabled', target, enabled),
|
|
@@ -72,6 +77,10 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
72
77
|
readWorkspacePrompt: () => ipcRenderer.invoke('workspace:readPrompt'),
|
|
73
78
|
saveWorkspacePrompt: (content) => ipcRenderer.invoke('workspace:savePrompt', content),
|
|
74
79
|
editorComplete: (request) => ipcRenderer.invoke('agent:editorComplete', request),
|
|
80
|
+
editorCompleteCancel: () => ipcRenderer.invoke('agent:editorCompleteCancel'),
|
|
81
|
+
onEditorCompletionDelta: (callback) => {
|
|
82
|
+
ipcRenderer.on('agent:editorCompletionDelta', (_event, payload) => callback(payload));
|
|
83
|
+
},
|
|
75
84
|
editorAssist: (request) => ipcRenderer.invoke('agent:editorAssist', request),
|
|
76
85
|
filePathForFile: (file) => {
|
|
77
86
|
try {
|
|
@@ -85,6 +94,7 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
85
94
|
selectFolder: () => ipcRenderer.invoke('dialog:selectFolder'),
|
|
86
95
|
executeBash: (cmd, shell, cwd) => ipcRenderer.invoke('agent:executeBash', cmd, shell, cwd),
|
|
87
96
|
openExternal: (path) => ipcRenderer.invoke('agent:openExternal', path),
|
|
97
|
+
openWebUrl: (url) => ipcRenderer.invoke('app:openWebUrl', url),
|
|
88
98
|
selectWorkspace: (id) => ipcRenderer.invoke('agent:selectWorkspace', id),
|
|
89
99
|
createWorkspace: (name) => ipcRenderer.invoke('agent:createWorkspace', name),
|
|
90
100
|
createExternalWorkspace: (name, dirPath) => ipcRenderer.invoke('agent:createExternalWorkspace', name, dirPath),
|
|
@@ -113,6 +123,7 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
113
123
|
removeSkill: (name) => ipcRenderer.invoke('skills:remove', name),
|
|
114
124
|
refreshSkills: () => ipcRenderer.invoke('skills:refresh'),
|
|
115
125
|
listMcpServers: () => ipcRenderer.invoke('mcp:list'),
|
|
126
|
+
discoverDshCompatibility: () => ipcRenderer.invoke('dsh:discover'),
|
|
116
127
|
upsertMcpServer: (input) => ipcRenderer.invoke('mcp:upsert', input),
|
|
117
128
|
setMcpServerEnabled: (id, enabled) => ipcRenderer.invoke('mcp:setEnabled', id, enabled),
|
|
118
129
|
removeMcpServer: (id) => ipcRenderer.invoke('mcp:remove', id),
|
|
@@ -48,6 +48,10 @@ class ChatCompletionsAdapter {
|
|
|
48
48
|
};
|
|
49
49
|
if (request.reasoningEffort)
|
|
50
50
|
body.reasoning_effort = request.reasoningEffort;
|
|
51
|
+
// 会话标识透传:仅当上层(支持 session_id 语义的 provider)显式填充时写进
|
|
52
|
+
// body,否则省略,避免严格 API 拒绝未知字段。
|
|
53
|
+
if (request.sessionId)
|
|
54
|
+
body.session_id = request.sessionId;
|
|
51
55
|
const base = request.baseUrl.replace(/\/+$/, '');
|
|
52
56
|
return {
|
|
53
57
|
url: `${base}/chat/completions`,
|
|
@@ -90,7 +94,10 @@ class ChatCompletionsAdapter {
|
|
|
90
94
|
}
|
|
91
95
|
const decoder = new TextDecoder();
|
|
92
96
|
let buffer = '';
|
|
93
|
-
|
|
97
|
+
const toolCalls = new Map();
|
|
98
|
+
const toolCallOrder = [];
|
|
99
|
+
let syntheticToolIndex = 0;
|
|
100
|
+
let lastToolIndex = 0;
|
|
94
101
|
let contentPolicyBlocked = false;
|
|
95
102
|
let emittedContent = false;
|
|
96
103
|
let emittedTool = false;
|
|
@@ -137,32 +144,51 @@ class ChatCompletionsAdapter {
|
|
|
137
144
|
emittedContent = true;
|
|
138
145
|
yield { type: 'text.delta', delta: textDelta };
|
|
139
146
|
}
|
|
140
|
-
const
|
|
141
|
-
for (const raw of
|
|
147
|
+
const deltaToolCalls = Array.isArray(delta.tool_calls) ? delta.tool_calls : [];
|
|
148
|
+
for (const raw of deltaToolCalls) {
|
|
142
149
|
const tc = raw;
|
|
143
150
|
const fn = tc.function && typeof tc.function === 'object' ? tc.function : {};
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
151
|
+
const rawIndex = Number(tc.index);
|
|
152
|
+
const index = Number.isInteger(rawIndex) && rawIndex >= 0
|
|
153
|
+
? rawIndex
|
|
154
|
+
: (tc.id ? syntheticToolIndex++ : lastToolIndex);
|
|
155
|
+
lastToolIndex = index;
|
|
156
|
+
let currentToolCall = toolCalls.get(index);
|
|
157
|
+
if (!currentToolCall && tc.id) {
|
|
149
158
|
currentToolCall = {
|
|
150
159
|
id: String(tc.id || ''),
|
|
151
160
|
name: (0, chat_messages_1.openAIToolName)(String(fn.name || '')),
|
|
152
|
-
|
|
161
|
+
argumentParts: [],
|
|
153
162
|
};
|
|
163
|
+
toolCalls.set(index, currentToolCall);
|
|
164
|
+
toolCallOrder.push(index);
|
|
154
165
|
yield { type: 'tool_call.started', id: currentToolCall.id, name: currentToolCall.name };
|
|
155
166
|
}
|
|
156
|
-
|
|
157
|
-
currentToolCall.
|
|
158
|
-
|
|
167
|
+
if (currentToolCall && fn.name && !currentToolCall.name)
|
|
168
|
+
currentToolCall.name = (0, chat_messages_1.openAIToolName)(String(fn.name));
|
|
169
|
+
if (currentToolCall && fn.arguments !== undefined && fn.arguments !== null) {
|
|
170
|
+
const argumentDelta = String(fn.arguments);
|
|
171
|
+
if (argumentDelta) {
|
|
172
|
+
currentToolCall.argumentParts.push(argumentDelta);
|
|
173
|
+
yield { type: 'tool_call.arguments.delta', id: currentToolCall.id, delta: argumentDelta };
|
|
174
|
+
}
|
|
159
175
|
}
|
|
160
176
|
}
|
|
161
177
|
}
|
|
162
178
|
}
|
|
163
|
-
if (
|
|
164
|
-
|
|
165
|
-
|
|
179
|
+
if (toolCallOrder.length) {
|
|
180
|
+
for (const index of toolCallOrder) {
|
|
181
|
+
const currentToolCall = toolCalls.get(index);
|
|
182
|
+
if (!currentToolCall)
|
|
183
|
+
continue;
|
|
184
|
+
emittedTool = true;
|
|
185
|
+
yield {
|
|
186
|
+
type: 'tool_call.completed',
|
|
187
|
+
id: currentToolCall.id,
|
|
188
|
+
name: currentToolCall.name,
|
|
189
|
+
arguments: currentToolCall.argumentParts.join(''),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
166
192
|
}
|
|
167
193
|
else if (!emittedContent && !emittedTool && contentPolicyBlocked) {
|
|
168
194
|
yield { type: 'response.failed', error: '[Error] Content policy refusal (content_filter).' };
|
|
@@ -55,6 +55,9 @@ export interface NormalizedAgentRequest {
|
|
|
55
55
|
reasoningEffort?: 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
56
56
|
apiKey: string;
|
|
57
57
|
baseUrl: string;
|
|
58
|
+
/** 可选的会话标识。仅当目标 provider 显式支持 session_id 语义的上下文缓存
|
|
59
|
+
* 时才由上层填充;adapter 在存在时透传,否则省略该字段(避免严格 API 拒绝未知字段)。 */
|
|
60
|
+
sessionId?: string;
|
|
58
61
|
}
|
|
59
62
|
export interface TokenEstimate {
|
|
60
63
|
inputTokens: number;
|