newmark-agent 0.3.11 → 0.4.0
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/config.example.json +6 -0
- package/dist/cli-commands.d.ts +8 -0
- package/dist/cli-commands.js +216 -16
- package/dist/cli-discovery.d.ts +15 -0
- package/dist/cli-discovery.js +182 -0
- package/dist/cli-help.d.ts +2 -0
- package/dist/cli-help.js +25 -1
- 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 +1503 -214
- package/dist/conversation-utility-host.js +3 -0
- package/dist/core/agent.d.ts +157 -8
- package/dist/core/agent.js +1176 -112
- 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 +174 -27
- package/dist/core/config.d.ts +7 -2
- package/dist/core/config.js +24 -6
- package/dist/core/conversationKernel.d.ts +5 -0
- package/dist/core/conversationKernel.js +30 -1
- 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 +19 -0
- package/dist/core/electronUtilityRuntimePool.js +76 -0
- package/dist/core/flow-runner.js +1 -1
- package/dist/core/mcpManager.d.ts +1 -0
- package/dist/core/mcpManager.js +100 -10
- package/dist/core/modelValidationStore.d.ts +4 -1
- package/dist/core/modelValidationStore.js +7 -1
- package/dist/core/subagent.d.ts +6 -0
- package/dist/core/subagent.js +22 -1
- package/dist/core/toolPolicy.d.ts +6 -0
- package/dist/core/toolPolicy.js +49 -1
- package/dist/core/types.d.ts +1 -1
- package/dist/core/utilityAgentProtocol.d.ts +8 -1
- package/dist/core/workspace.d.ts +15 -0
- package/dist/core/workspace.js +62 -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 +12 -0
- package/dist/core/wslAgentRuntimePool.js +71 -0
- package/dist/launcher.js +48 -11
- package/dist/llm/provider.d.ts +9 -6
- package/dist/llm/provider.js +89 -36
- package/dist/main.js +326 -52
- package/dist/preload.js +17 -0
- package/dist/providers/chat-completions.adapter.js +42 -20
- package/dist/providers/provider-adapter.d.ts +3 -0
- package/dist/providers/provider-events.d.ts +7 -0
- package/dist/providers/provider-events.js +44 -0
- package/dist/providers/responses.adapter.js +1 -3
- 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 +51 -5
- package/dist/tools/index.js +11 -2
- package/dist/tools/nativeTools.js +5 -1
- package/dist/tui/src/adapters/core-runtime-adapter.js +40 -3
- package/dist/tui/src/app.js +47 -13
- package/dist/tui/src/render.js +23 -7
- package/dist/tui/src/state.js +61 -9
- package/dist/ui/index.html +2775 -284
- package/dist/ui/lucide-sprite.svg +26 -0
- package/dist/wsl-agent-host.bundle.cjs +1503 -214
- package/dist/wsl-agent-host.js +3 -0
- package/package.json +16 -5
|
@@ -317,6 +317,10 @@ class WslAgentClient {
|
|
|
317
317
|
await this.start();
|
|
318
318
|
return await this.request('checkpoint', { target: await this.mapTarget(target) }, 5_000);
|
|
319
319
|
}
|
|
320
|
+
async contextCompress(target, options = {}) {
|
|
321
|
+
await this.start();
|
|
322
|
+
return await this.request('context_compress', { target: await this.mapTarget(target), options }, 120_000);
|
|
323
|
+
}
|
|
320
324
|
async rateAutoRoute(target, score, routeId = '') {
|
|
321
325
|
await this.start();
|
|
322
326
|
return await this.request('rate_auto_route', {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AgentMode, AgentWorkEvent, ConversationInputEnvelope, GuideReceipt } from './types';
|
|
2
|
-
import { AgentPromptMessage, ConversationKernelRunOptions, ConversationKernelRunResult, ConversationQueueMode, ConversationStopResult } from './conversationKernel';
|
|
2
|
+
import { AgentPromptMessage, ConversationContextCompressOptions, ConversationKernelRunOptions, ConversationKernelRunResult, ConversationQueueMode, ConversationStopResult } from './conversationKernel';
|
|
3
3
|
import { ConversationRuntimeTarget } from './conversationTarget';
|
|
4
4
|
import { TerminalTakeoverEvent, TerminalTakeoverOwnerFilter, TerminalTakeoverState } from '../tools/terminalTakeover';
|
|
5
5
|
import { BrowserUseRequest } from './browserUse';
|
|
@@ -114,6 +114,13 @@ export type WslAgentRequest = {
|
|
|
114
114
|
params: {
|
|
115
115
|
target: ConversationRuntimeTarget;
|
|
116
116
|
};
|
|
117
|
+
} | {
|
|
118
|
+
id: string;
|
|
119
|
+
method: 'context_compress';
|
|
120
|
+
params: {
|
|
121
|
+
target: ConversationRuntimeTarget;
|
|
122
|
+
options?: ConversationContextCompressOptions;
|
|
123
|
+
};
|
|
117
124
|
} | {
|
|
118
125
|
id: string;
|
|
119
126
|
method: 'rate_auto_route';
|
|
@@ -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>;
|
|
@@ -56,6 +60,7 @@ export declare class WslAgentRuntimePool {
|
|
|
56
60
|
private hostToolHandler;
|
|
57
61
|
private restarting;
|
|
58
62
|
private disposing;
|
|
63
|
+
private forceStopPromises;
|
|
59
64
|
private capacityTail;
|
|
60
65
|
private accessSequence;
|
|
61
66
|
constructor(distro: string, windowsRoot: string, windowsHostScript: string, createClient?: WslTargetRuntimeClientFactory, options?: WslAgentRuntimePoolOptions);
|
|
@@ -68,6 +73,10 @@ export declare class WslAgentRuntimePool {
|
|
|
68
73
|
requestStop(target: ConversationRuntimeTarget, runId?: string): Promise<WslPoolStopResult>;
|
|
69
74
|
enqueueGuide(envelope: ConversationInputEnvelope): Promise<GuideReceipt>;
|
|
70
75
|
checkpoint(target: ConversationRuntimeTarget): Promise<Record<string, unknown>>;
|
|
76
|
+
contextCompress(target: ConversationRuntimeTarget, options?: {
|
|
77
|
+
keepRecent?: number;
|
|
78
|
+
force?: boolean;
|
|
79
|
+
}): Promise<Record<string, unknown>>;
|
|
71
80
|
rateAutoRoute(target: ConversationRuntimeTarget, score: number, routeId?: string): Promise<WslAutoRouteRatingResult>;
|
|
72
81
|
setWorkRunExpanded(target: ConversationRuntimeTarget, runId: string, expanded: boolean): Promise<boolean>;
|
|
73
82
|
setInputMode(target: ConversationRuntimeTarget, mode: string): Promise<'guide' | 'next' | null>;
|
|
@@ -90,6 +99,9 @@ export declare class WslAgentRuntimePool {
|
|
|
90
99
|
hasActiveWorkspace(target: ConversationRuntimeTarget): Promise<boolean>;
|
|
91
100
|
stopWorkspace(target: ConversationRuntimeTarget): Promise<void>;
|
|
92
101
|
stopTarget(target: ConversationRuntimeTarget): Promise<void>;
|
|
102
|
+
/** Hard-stop a target for destructive lifecycle actions such as archive. */
|
|
103
|
+
forceStopTarget(target: ConversationRuntimeTarget): Promise<void>;
|
|
104
|
+
private forceStopTargetInternal;
|
|
93
105
|
stopAll(): Promise<void>;
|
|
94
106
|
private throwStopFailures;
|
|
95
107
|
private stopEntry;
|
|
@@ -20,6 +20,7 @@ class WslAgentRuntimePool {
|
|
|
20
20
|
hostToolHandler = null;
|
|
21
21
|
restarting = new Set();
|
|
22
22
|
disposing = new Set();
|
|
23
|
+
forceStopPromises = new Map();
|
|
23
24
|
capacityTail = Promise.resolve();
|
|
24
25
|
accessSequence = 0;
|
|
25
26
|
constructor(distro, windowsRoot, windowsHostScript, createClient = (target) => new wslAgentClient_1.WslAgentClient(distro, windowsRoot, windowsHostScript, target), options = {}) {
|
|
@@ -213,6 +214,21 @@ class WslAgentRuntimePool {
|
|
|
213
214
|
this.release(entry, true);
|
|
214
215
|
}
|
|
215
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
|
+
}
|
|
216
232
|
async rateAutoRoute(target, score, routeId = '') {
|
|
217
233
|
const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
|
|
218
234
|
const entry = await this.acquireExisting(normalized);
|
|
@@ -356,6 +372,61 @@ class WslAgentRuntimePool {
|
|
|
356
372
|
if (entry)
|
|
357
373
|
await this.stopEntry(entry);
|
|
358
374
|
}
|
|
375
|
+
/** Hard-stop a target for destructive lifecycle actions such as archive. */
|
|
376
|
+
async forceStopTarget(target) {
|
|
377
|
+
const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
|
|
378
|
+
const existing = this.forceStopPromises.get(normalized.runtimeKey);
|
|
379
|
+
if (existing)
|
|
380
|
+
return existing;
|
|
381
|
+
const operation = this.forceStopTargetInternal(normalized);
|
|
382
|
+
this.forceStopPromises.set(normalized.runtimeKey, operation);
|
|
383
|
+
try {
|
|
384
|
+
await operation;
|
|
385
|
+
}
|
|
386
|
+
finally {
|
|
387
|
+
if (this.forceStopPromises.get(normalized.runtimeKey) === operation) {
|
|
388
|
+
this.forceStopPromises.delete(normalized.runtimeKey);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
async forceStopTargetInternal(target) {
|
|
393
|
+
let entry;
|
|
394
|
+
await this.serializeCapacity(async () => {
|
|
395
|
+
entry = this.entries.get(target.runtimeKey);
|
|
396
|
+
if (entry)
|
|
397
|
+
this.disposing.add(target.runtimeKey);
|
|
398
|
+
});
|
|
399
|
+
if (!entry)
|
|
400
|
+
return;
|
|
401
|
+
const intent = entry.stopIntent || {
|
|
402
|
+
runId: entry.lastRunId,
|
|
403
|
+
generation: entry.lastGeneration,
|
|
404
|
+
checkpointed: false,
|
|
405
|
+
forcePromise: null,
|
|
406
|
+
};
|
|
407
|
+
if (!entry.stopIntent)
|
|
408
|
+
entry.stopIntent = intent;
|
|
409
|
+
try {
|
|
410
|
+
await this.forceTerminateEntry(entry, intent);
|
|
411
|
+
if (entry.client.status().connected) {
|
|
412
|
+
if (entry.client.forceStopRuntimeGroup)
|
|
413
|
+
await entry.client.forceStopRuntimeGroup();
|
|
414
|
+
else
|
|
415
|
+
await entry.client.forceRestartRuntimeGroup();
|
|
416
|
+
}
|
|
417
|
+
if (entry.client.status().connected) {
|
|
418
|
+
throw new Error(`WSL runtime ${target.runtimeKey} remained connected after force stop`);
|
|
419
|
+
}
|
|
420
|
+
if (this.entries.get(target.runtimeKey) === entry) {
|
|
421
|
+
this.entries.delete(target.runtimeKey);
|
|
422
|
+
entry.unsubscribe();
|
|
423
|
+
entry.client.setHostToolHandler(null);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
finally {
|
|
427
|
+
this.disposing.delete(target.runtimeKey);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
359
430
|
async stopAll() {
|
|
360
431
|
const targets = Array.from(this.entries.values(), entry => entry.target);
|
|
361
432
|
const results = await Promise.allSettled(targets.map(target => this.stopTarget(target)));
|
package/dist/launcher.js
CHANGED
|
@@ -43,19 +43,35 @@ const agent_1 = require("./core/agent");
|
|
|
43
43
|
const flow_1 = require("./core/flow");
|
|
44
44
|
const flow_runner_1 = require("./core/flow-runner");
|
|
45
45
|
const cli_commands_1 = require("./cli-commands");
|
|
46
|
+
const cli_discovery_1 = require("./cli-discovery");
|
|
46
47
|
const installUpdate_1 = require("./core/installUpdate");
|
|
47
48
|
const cli_help_1 = require("./cli-help");
|
|
48
|
-
const
|
|
49
|
+
const rawArgs = process.argv.slice(2);
|
|
50
|
+
const args = rawArgs[0] === '--' ? rawArgs.slice(1) : rawArgs;
|
|
49
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));
|
|
53
|
+
const isEdit = args[0] === 'edit';
|
|
54
|
+
const editFile = isEdit ? args[1] : '';
|
|
55
|
+
const isFlow = args[0] === 'flow';
|
|
50
56
|
const isHelpArg = !hasCliCommand && (args.some(arg => ['--help', '-h'].includes(arg.toLowerCase())) || args[0]?.toLowerCase() === 'help');
|
|
51
|
-
const isVersionArg = !hasCliCommand &&
|
|
57
|
+
const isVersionArg = !hasCliCommand && (0, cli_discovery_1.isVersionArgument)(args);
|
|
58
|
+
const isReadOnlyValidation = hasCliCommand && args.includes('validate-models') && !args.includes('--persist');
|
|
52
59
|
const isTui = args.some(arg => arg.toLowerCase() === '--tui');
|
|
53
60
|
const isGui = args.some(arg => arg.toLowerCase() === '--gui');
|
|
54
61
|
const isCli = args.includes('--cli');
|
|
55
62
|
const isServer = args.includes('--server');
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
const invalidArgument = (0, cli_discovery_1.invalidTopLevelArgument)(args);
|
|
64
|
+
if (invalidArgument) {
|
|
65
|
+
console.error(`Invalid Newmark argument: ${invalidArgument}`);
|
|
66
|
+
process.exit(2);
|
|
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
|
+
}
|
|
59
75
|
function pathArgValue(values, key) {
|
|
60
76
|
const prefix = `${key}=`;
|
|
61
77
|
const inlineIdx = values.findIndex(a => a.startsWith(prefix));
|
|
@@ -89,6 +105,15 @@ function pathArgValue(values, key) {
|
|
|
89
105
|
}
|
|
90
106
|
return best || parts.join(' ') || undefined;
|
|
91
107
|
}
|
|
108
|
+
function resolveTuiWorkspacePath(values, root) {
|
|
109
|
+
const explicitWorkspace = pathArgValue(values, '--workspace');
|
|
110
|
+
if (explicitWorkspace)
|
|
111
|
+
return explicitWorkspace;
|
|
112
|
+
// An explicitly isolated runtime must not silently register the caller's
|
|
113
|
+
// cwd as an external workspace. Keep the opt-in --workspace escape hatch,
|
|
114
|
+
// while making the safe one-argument form fully self-contained.
|
|
115
|
+
return pathArgValue(values, '--root') ? root : process.cwd();
|
|
116
|
+
}
|
|
92
117
|
function userRuntimeRoot() {
|
|
93
118
|
return path.join(os.homedir(), '.Newmark');
|
|
94
119
|
}
|
|
@@ -174,18 +199,30 @@ function writableRuntimeRoot(candidate) {
|
|
|
174
199
|
const explicitRoot = pathArgValue(args, '--root');
|
|
175
200
|
const root = explicitRoot ? writableRuntimeRoot(explicitRoot) : userRuntimeRoot();
|
|
176
201
|
if (isHelpArg) {
|
|
177
|
-
console.log((0, cli_help_1.newmarkHelpText)((0, installUpdate_1.currentAppVersion)()));
|
|
202
|
+
console.log(isFlow ? (0, cli_help_1.newmarkFlowHelpText)() : isEdit ? (0, cli_help_1.newmarkEditHelpText)() : (0, cli_help_1.newmarkHelpText)((0, installUpdate_1.currentAppVersion)()));
|
|
178
203
|
process.exit(0);
|
|
179
204
|
}
|
|
180
205
|
if (isVersionArg) {
|
|
181
206
|
console.log((0, installUpdate_1.currentAppVersion)());
|
|
182
207
|
process.exit(0);
|
|
183
208
|
}
|
|
184
|
-
|
|
209
|
+
const unknownCommand = !hasCliCommand && !isTui && !isGui && !isCli && !isServer && !isFlow && !isEdit
|
|
210
|
+
? (0, cli_discovery_1.unknownTopLevelCommand)(args)
|
|
211
|
+
: undefined;
|
|
212
|
+
if (unknownCommand) {
|
|
213
|
+
console.error(`Unknown Newmark command or argument: ${unknownCommand}. Run --help to see the supported entrypoints.`);
|
|
214
|
+
process.exit(2);
|
|
215
|
+
}
|
|
216
|
+
function firstRunInit(r, options = {}) {
|
|
185
217
|
fs.mkdirSync(r, { recursive: true });
|
|
186
|
-
|
|
218
|
+
// Legacy AppData migration is only valid for the canonical user runtime.
|
|
219
|
+
// An explicit --root is an isolation boundary (tests, portable roots, and
|
|
220
|
+
// caller-selected workspaces) and must never inherit the user's sessions,
|
|
221
|
+
// archives, providers, or workspaces.
|
|
222
|
+
if (path.resolve(r) === path.resolve(userRuntimeRoot()))
|
|
223
|
+
migrateLegacyRuntimeRoot(r);
|
|
187
224
|
const { ensureRootConfig } = require('./core/config');
|
|
188
|
-
ensureRootConfig(r);
|
|
225
|
+
ensureRootConfig(r, options);
|
|
189
226
|
if (!fs.existsSync(path.join(r, 'agent.md'))) {
|
|
190
227
|
fs.writeFileSync(path.join(r, 'agent.md'), '# Newmark Agent\n\nYou are a powerful coding assistant.\n', 'utf-8');
|
|
191
228
|
}
|
|
@@ -270,13 +307,13 @@ function launchGui() {
|
|
|
270
307
|
console.error('Unable to locate the Newmark GUI runtime. Reinstall newmark-agent with optional dependencies enabled, or install a Newmark desktop package.');
|
|
271
308
|
process.exit(1);
|
|
272
309
|
}
|
|
273
|
-
firstRunInit(root);
|
|
310
|
+
firstRunInit(root, { readOnly: isReadOnlyValidation });
|
|
274
311
|
if (isGui) {
|
|
275
312
|
launchGui();
|
|
276
313
|
}
|
|
277
314
|
else if (isTui) {
|
|
278
315
|
const { start } = require('./tui/src/app');
|
|
279
|
-
start({ root, workspacePath:
|
|
316
|
+
start({ root, workspacePath: resolveTuiWorkspacePath(args, root), desktopDist: __dirname });
|
|
280
317
|
}
|
|
281
318
|
else if (hasCliCommand) {
|
|
282
319
|
(0, cli_commands_1.runCliCommand)(root, args).then(handled => {
|
package/dist/llm/provider.d.ts
CHANGED
|
@@ -28,9 +28,12 @@ export declare class LLMProvider {
|
|
|
28
28
|
explicitProtocol?: ProviderProtocol | undefined;
|
|
29
29
|
openAIMode: OpenAITransportMode | boolean;
|
|
30
30
|
useProviderAdaptersV2: boolean;
|
|
31
|
+
requestTimeoutMs: number;
|
|
31
32
|
static nodeHttpTransport: ((method: 'GET' | 'POST', url: string, headers: Record<string, string>, body?: string) => Promise<NodeHttpResult>) | null;
|
|
32
33
|
static powershellTransport: ((method: 'GET' | 'POST', url: string, headers: Record<string, string>, body?: string) => Promise<NodeHttpResult>) | null;
|
|
33
|
-
constructor(name: string, baseUrl: string, apiKey: string, explicitProtocol?: ProviderProtocol | undefined, openAIMode?: OpenAITransportMode | boolean, useProviderAdaptersV2?: boolean);
|
|
34
|
+
constructor(name: string, baseUrl: string, apiKey: string, explicitProtocol?: ProviderProtocol | undefined, openAIMode?: OpenAITransportMode | boolean, useProviderAdaptersV2?: boolean, requestTimeoutMs?: number);
|
|
35
|
+
private effectiveRequestTimeout;
|
|
36
|
+
private withRequestTimeout;
|
|
34
37
|
intelligenceConfig(tier: string): IntelligenceConfig;
|
|
35
38
|
private reasoningEffort;
|
|
36
39
|
private applyChatReasoningEffort;
|
|
@@ -85,16 +88,16 @@ export declare class LLMProvider {
|
|
|
85
88
|
private toStreamUsage;
|
|
86
89
|
private shouldDowngradeToResponses;
|
|
87
90
|
/**
|
|
88
|
-
* Loopback-aware transport injected into adapter `execute`.
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
91
|
+
* Loopback-aware transport injected into adapter `execute`. Streaming
|
|
92
|
+
* requests retain the fetch-to-node fallback for transport failures, while
|
|
93
|
+
* a local deadline is returned directly so one request cannot become a
|
|
94
|
+
* second Windows fallback request.
|
|
92
95
|
*/
|
|
93
96
|
private buildProviderAdapterTransport;
|
|
94
97
|
private toTransportResponse;
|
|
95
98
|
private toNormalizedMessages;
|
|
96
99
|
private toNormalizedTools;
|
|
97
|
-
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>;
|
|
98
101
|
/**
|
|
99
102
|
* GitHub Models streaming path. Preserved as a dedicated implementation
|
|
100
103
|
* because the provider adapters (V2) do not serialize the GitHub Models
|
package/dist/llm/provider.js
CHANGED
|
@@ -43,6 +43,19 @@ const child_process_1 = require("child_process");
|
|
|
43
43
|
const agentKernelDiagnostics_1 = require("../core/agentKernelDiagnostics");
|
|
44
44
|
const chat_messages_1 = require("../providers/chat-messages");
|
|
45
45
|
const providers_1 = require("../providers");
|
|
46
|
+
// Keep provider requests below the release-harness/user-visible command
|
|
47
|
+
// deadline. A provider that does not answer must produce one bounded error;
|
|
48
|
+
// it must not restart the same request through every Windows transport.
|
|
49
|
+
const DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS = 90_000;
|
|
50
|
+
const MIN_PROVIDER_REQUEST_TIMEOUT_MS = 50;
|
|
51
|
+
function providerTimeoutError(timeoutMs) {
|
|
52
|
+
const error = new Error(`Provider request timed out after ${timeoutMs}ms`);
|
|
53
|
+
error.name = 'TimeoutError';
|
|
54
|
+
return error;
|
|
55
|
+
}
|
|
56
|
+
function isProviderTimeoutError(error) {
|
|
57
|
+
return error instanceof Error && error.name === 'TimeoutError';
|
|
58
|
+
}
|
|
46
59
|
function abortFailure(signal) {
|
|
47
60
|
const reason = signal?.reason;
|
|
48
61
|
const error = reason instanceof Error ? reason : new Error(reason ? String(reason) : 'LLM request aborted');
|
|
@@ -84,15 +97,37 @@ class LLMProvider {
|
|
|
84
97
|
explicitProtocol;
|
|
85
98
|
openAIMode;
|
|
86
99
|
useProviderAdaptersV2;
|
|
100
|
+
requestTimeoutMs;
|
|
87
101
|
static nodeHttpTransport = null;
|
|
88
102
|
static powershellTransport = null;
|
|
89
|
-
constructor(name, baseUrl, apiKey, explicitProtocol, openAIMode = 'chat_stream', useProviderAdaptersV2 = false) {
|
|
103
|
+
constructor(name, baseUrl, apiKey, explicitProtocol, openAIMode = 'chat_stream', useProviderAdaptersV2 = false, requestTimeoutMs = DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS) {
|
|
90
104
|
this.name = name;
|
|
91
105
|
this.baseUrl = baseUrl;
|
|
92
106
|
this.apiKey = apiKey;
|
|
93
107
|
this.explicitProtocol = explicitProtocol;
|
|
94
108
|
this.openAIMode = openAIMode;
|
|
95
109
|
this.useProviderAdaptersV2 = useProviderAdaptersV2;
|
|
110
|
+
this.requestTimeoutMs = requestTimeoutMs;
|
|
111
|
+
}
|
|
112
|
+
effectiveRequestTimeout(timeoutMs) {
|
|
113
|
+
const requested = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS;
|
|
114
|
+
const configured = Number.isFinite(this.requestTimeoutMs) && this.requestTimeoutMs > 0
|
|
115
|
+
? this.requestTimeoutMs
|
|
116
|
+
: DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS;
|
|
117
|
+
return Math.max(MIN_PROVIDER_REQUEST_TIMEOUT_MS, Math.min(requested, configured));
|
|
118
|
+
}
|
|
119
|
+
async withRequestTimeout(promise, timeoutMs, signal) {
|
|
120
|
+
let timer;
|
|
121
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
122
|
+
timer = setTimeout(() => reject(providerTimeoutError(timeoutMs)), timeoutMs);
|
|
123
|
+
});
|
|
124
|
+
try {
|
|
125
|
+
return await abortable(Promise.race([promise, timeoutPromise]), signal);
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
if (timer)
|
|
129
|
+
clearTimeout(timer);
|
|
130
|
+
}
|
|
96
131
|
}
|
|
97
132
|
intelligenceConfig(tier) {
|
|
98
133
|
switch (tier) {
|
|
@@ -212,6 +247,7 @@ class LLMProvider {
|
|
|
212
247
|
};
|
|
213
248
|
}
|
|
214
249
|
async postJsonWithFetchFallback(url, headers, body, timeoutMs = 120000, signal) {
|
|
250
|
+
const effectiveTimeout = this.effectiveRequestTimeout(timeoutMs);
|
|
215
251
|
// Electron utility processes can leave an undici response body pending when
|
|
216
252
|
// several isolated workers concurrently call a plain-HTTP local provider.
|
|
217
253
|
// Node's HTTP client owns the full body lifecycle and is deterministic for
|
|
@@ -224,7 +260,7 @@ class LLMProvider {
|
|
|
224
260
|
return '';
|
|
225
261
|
} })();
|
|
226
262
|
this.transportDiagnostic('loopback:start', pathname);
|
|
227
|
-
const local = await this.nodeHttpJson('POST', url, headers, JSON.stringify(body), signal);
|
|
263
|
+
const local = await this.nodeHttpJson('POST', url, headers, JSON.stringify(body), signal, effectiveTimeout);
|
|
228
264
|
this.transportDiagnostic('loopback:complete', `status=${local.status} bytes=${Buffer.byteLength(local.body || '')}`);
|
|
229
265
|
return {
|
|
230
266
|
ok: local.status >= 200 && local.status < 300,
|
|
@@ -240,7 +276,7 @@ class LLMProvider {
|
|
|
240
276
|
forwardAbort();
|
|
241
277
|
else
|
|
242
278
|
signal?.addEventListener('abort', forwardAbort, { once: true });
|
|
243
|
-
const timer = setTimeout(() => abort.abort(),
|
|
279
|
+
const timer = setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout);
|
|
244
280
|
try {
|
|
245
281
|
const response = await fetch(url, {
|
|
246
282
|
method: 'POST',
|
|
@@ -253,9 +289,11 @@ class LLMProvider {
|
|
|
253
289
|
catch (e) {
|
|
254
290
|
if (signal?.aborted)
|
|
255
291
|
throw abortFailure(signal);
|
|
292
|
+
if (abort.signal.aborted)
|
|
293
|
+
throw abortFailure(abort.signal);
|
|
256
294
|
if (!this.shouldUseNodeHttpFallback(e))
|
|
257
295
|
throw e;
|
|
258
|
-
const fallback = await this.nodeHttpJson('POST', url, headers, JSON.stringify(body), signal);
|
|
296
|
+
const fallback = await this.nodeHttpJson('POST', url, headers, JSON.stringify(body), signal, effectiveTimeout);
|
|
259
297
|
return {
|
|
260
298
|
ok: fallback.status >= 200 && fallback.status < 300,
|
|
261
299
|
status: fallback.status,
|
|
@@ -270,16 +308,19 @@ class LLMProvider {
|
|
|
270
308
|
}
|
|
271
309
|
}
|
|
272
310
|
async getJsonWithFetchFallback(url, headers, timeoutMs = 30000) {
|
|
311
|
+
const effectiveTimeout = this.effectiveRequestTimeout(timeoutMs);
|
|
273
312
|
const abort = new AbortController();
|
|
274
|
-
const timer = setTimeout(() => abort.abort(),
|
|
313
|
+
const timer = setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout);
|
|
275
314
|
try {
|
|
276
315
|
const response = await fetch(url, { method: 'GET', headers, signal: abort.signal });
|
|
277
316
|
return response;
|
|
278
317
|
}
|
|
279
318
|
catch (e) {
|
|
319
|
+
if (abort.signal.aborted)
|
|
320
|
+
throw abortFailure(abort.signal);
|
|
280
321
|
if (!this.shouldUseNodeHttpFallback(e))
|
|
281
322
|
throw e;
|
|
282
|
-
const fallback = await this.nodeHttpJson('GET', url, headers);
|
|
323
|
+
const fallback = await this.nodeHttpJson('GET', url, headers, '', undefined, effectiveTimeout);
|
|
283
324
|
return {
|
|
284
325
|
ok: fallback.status >= 200 && fallback.status < 300,
|
|
285
326
|
status: fallback.status,
|
|
@@ -293,16 +334,21 @@ class LLMProvider {
|
|
|
293
334
|
}
|
|
294
335
|
}
|
|
295
336
|
shouldUseNodeHttpFallback(error) {
|
|
296
|
-
|
|
297
|
-
|
|
337
|
+
// Abort is a completed control decision (user cancellation or our own
|
|
338
|
+
// deadline), not evidence that a second transport can succeed. Retrying it
|
|
339
|
+
// on Windows used to create a second 120s request after the first timeout.
|
|
340
|
+
return error instanceof TypeError && /fetch failed/i.test(error.message);
|
|
298
341
|
}
|
|
299
|
-
nodeHttpJson(method, urlValue, headers, body = '', signal) {
|
|
342
|
+
nodeHttpJson(method, urlValue, headers, body = '', signal, timeoutMs = DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS) {
|
|
343
|
+
const effectiveTimeout = this.effectiveRequestTimeout(timeoutMs);
|
|
300
344
|
if (LLMProvider.nodeHttpTransport) {
|
|
301
|
-
return
|
|
345
|
+
return this.withRequestTimeout(LLMProvider.nodeHttpTransport(method, urlValue, headers, body), effectiveTimeout, signal).catch(error => {
|
|
302
346
|
if (signal?.aborted)
|
|
303
347
|
throw abortFailure(signal);
|
|
348
|
+
if (isProviderTimeoutError(error))
|
|
349
|
+
throw error;
|
|
304
350
|
if (process.platform === 'win32') {
|
|
305
|
-
return this.powershellJson(method, urlValue, headers, body, signal);
|
|
351
|
+
return this.powershellJson(method, urlValue, headers, body, signal, effectiveTimeout);
|
|
306
352
|
}
|
|
307
353
|
throw error;
|
|
308
354
|
});
|
|
@@ -350,8 +396,8 @@ class LLMProvider {
|
|
|
350
396
|
fail(new Error('Node HTTP response closed before completion'));
|
|
351
397
|
});
|
|
352
398
|
});
|
|
353
|
-
req.setTimeout(
|
|
354
|
-
req.destroy(
|
|
399
|
+
req.setTimeout(effectiveTimeout, () => {
|
|
400
|
+
req.destroy(providerTimeoutError(effectiveTimeout));
|
|
355
401
|
});
|
|
356
402
|
req.on('error', reject);
|
|
357
403
|
const onAbort = () => req.destroy(abortFailure(signal));
|
|
@@ -366,15 +412,18 @@ class LLMProvider {
|
|
|
366
412
|
}).catch(error => {
|
|
367
413
|
if (signal?.aborted)
|
|
368
414
|
throw abortFailure(signal);
|
|
415
|
+
if (isProviderTimeoutError(error))
|
|
416
|
+
throw error;
|
|
369
417
|
if (process.platform === 'win32') {
|
|
370
|
-
return this.powershellJson(method, urlValue, headers, body, signal);
|
|
418
|
+
return this.powershellJson(method, urlValue, headers, body, signal, effectiveTimeout);
|
|
371
419
|
}
|
|
372
420
|
throw error;
|
|
373
421
|
});
|
|
374
422
|
}
|
|
375
|
-
powershellJson(method, urlValue, headers, body = '', signal) {
|
|
423
|
+
powershellJson(method, urlValue, headers, body = '', signal, timeoutMs = DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS) {
|
|
424
|
+
const effectiveTimeout = this.effectiveRequestTimeout(timeoutMs);
|
|
376
425
|
if (LLMProvider.powershellTransport) {
|
|
377
|
-
return LLMProvider.powershellTransport(method, urlValue, headers, body);
|
|
426
|
+
return this.withRequestTimeout(LLMProvider.powershellTransport(method, urlValue, headers, body), effectiveTimeout, signal);
|
|
378
427
|
}
|
|
379
428
|
return new Promise((resolve, reject) => {
|
|
380
429
|
const headerJson = JSON.stringify(headers);
|
|
@@ -403,7 +452,7 @@ class LLMProvider {
|
|
|
403
452
|
' $raw = $headerJson | ConvertFrom-Json',
|
|
404
453
|
' foreach ($p in $raw.PSObject.Properties) { $headers[$p.Name] = [string]$p.Value }',
|
|
405
454
|
'}',
|
|
406
|
-
'$params = @{ Uri = $uri; Method = $method; Headers = $headers; UseBasicParsing = $true; TimeoutSec =
|
|
455
|
+
`'$params = @{ Uri = $uri; Method = $method; Headers = $headers; UseBasicParsing = $true; TimeoutSec = ${Math.max(1, Math.ceil(effectiveTimeout / 1000))} }`,
|
|
407
456
|
'if ($method -eq "POST") { $params["Body"] = $bodyJson }',
|
|
408
457
|
'if ($method -eq "POST") { $params["ContentType"] = "application/json; charset=utf-8" }',
|
|
409
458
|
'$resp = Invoke-WebRequest @params',
|
|
@@ -437,8 +486,8 @@ class LLMProvider {
|
|
|
437
486
|
const timer = setTimeout(() => {
|
|
438
487
|
child.kill();
|
|
439
488
|
cleanup();
|
|
440
|
-
reject(
|
|
441
|
-
},
|
|
489
|
+
reject(providerTimeoutError(effectiveTimeout));
|
|
490
|
+
}, effectiveTimeout + 5000);
|
|
442
491
|
child.stdout.setEncoding('utf8');
|
|
443
492
|
child.stderr.setEncoding('utf8');
|
|
444
493
|
child.stdout.on('data', chunk => { stdout += chunk; });
|
|
@@ -794,7 +843,7 @@ class LLMProvider {
|
|
|
794
843
|
* The emitted request body and StreamToken stream are byte-equivalent to
|
|
795
844
|
* the legacy inlined path.
|
|
796
845
|
*/
|
|
797
|
-
async *chatStreamWithToolsV2(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier) {
|
|
846
|
+
async *chatStreamWithToolsV2(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier, sessionId) {
|
|
798
847
|
const mode = this.openAITransportMode();
|
|
799
848
|
if (mode === 'responses') {
|
|
800
849
|
yield* this.adapterResponsesBridge(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier);
|
|
@@ -812,6 +861,7 @@ class LLMProvider {
|
|
|
812
861
|
maxOutputTokens: maxTokens,
|
|
813
862
|
apiKey: this.apiKey,
|
|
814
863
|
baseUrl: this.cleanBaseUrl(),
|
|
864
|
+
...(sessionId ? { sessionId } : {}),
|
|
815
865
|
};
|
|
816
866
|
const serialized = await adapter.serializeRequest(request);
|
|
817
867
|
serialized.body.stream = mode === 'chat' ? false : true;
|
|
@@ -926,10 +976,10 @@ class LLMProvider {
|
|
|
926
976
|
return this.shouldUseResponsesFallback(Number(match[1]), errorText);
|
|
927
977
|
}
|
|
928
978
|
/**
|
|
929
|
-
* Loopback-aware transport injected into adapter `execute`.
|
|
930
|
-
*
|
|
931
|
-
*
|
|
932
|
-
*
|
|
979
|
+
* Loopback-aware transport injected into adapter `execute`. Streaming
|
|
980
|
+
* requests retain the fetch-to-node fallback for transport failures, while
|
|
981
|
+
* a local deadline is returned directly so one request cannot become a
|
|
982
|
+
* second Windows fallback request.
|
|
933
983
|
*/
|
|
934
984
|
buildProviderAdapterTransport() {
|
|
935
985
|
return async (request, signal) => {
|
|
@@ -940,7 +990,8 @@ class LLMProvider {
|
|
|
940
990
|
forwardAbort();
|
|
941
991
|
else
|
|
942
992
|
signal?.addEventListener('abort', forwardAbort, { once: true });
|
|
943
|
-
const
|
|
993
|
+
const effectiveTimeout = this.effectiveRequestTimeout(120000);
|
|
994
|
+
const timer = setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout);
|
|
944
995
|
try {
|
|
945
996
|
try {
|
|
946
997
|
return await fetch(request.url, {
|
|
@@ -953,11 +1004,13 @@ class LLMProvider {
|
|
|
953
1004
|
catch (error) {
|
|
954
1005
|
if (signal?.aborted)
|
|
955
1006
|
throw abortFailure(signal);
|
|
1007
|
+
if (abort.signal.aborted)
|
|
1008
|
+
throw abortFailure(abort.signal);
|
|
956
1009
|
if (!this.shouldUseNodeHttpFallback(error))
|
|
957
1010
|
throw error;
|
|
958
1011
|
const fallbackHeaders = { ...request.headers };
|
|
959
1012
|
delete fallbackHeaders['Accept'];
|
|
960
|
-
const fallback = await this.postJsonWithFetchFallback(request.url, fallbackHeaders, { ...request.body, stream: false },
|
|
1013
|
+
const fallback = await this.postJsonWithFetchFallback(request.url, fallbackHeaders, { ...request.body, stream: false }, effectiveTimeout, signal);
|
|
961
1014
|
return this.toTransportResponse(fallback);
|
|
962
1015
|
}
|
|
963
1016
|
}
|
|
@@ -1028,7 +1081,7 @@ class LLMProvider {
|
|
|
1028
1081
|
};
|
|
1029
1082
|
});
|
|
1030
1083
|
}
|
|
1031
|
-
async *chatStreamWithTools(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier) {
|
|
1084
|
+
async *chatStreamWithTools(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier, sessionId) {
|
|
1032
1085
|
if (signal?.aborted)
|
|
1033
1086
|
throw abortFailure(signal);
|
|
1034
1087
|
if (this.protocol() === 'anthropic') {
|
|
@@ -1040,7 +1093,7 @@ class LLMProvider {
|
|
|
1040
1093
|
return;
|
|
1041
1094
|
}
|
|
1042
1095
|
if (this.useProviderAdaptersV2) {
|
|
1043
|
-
yield* this.chatStreamWithToolsV2(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier);
|
|
1096
|
+
yield* this.chatStreamWithToolsV2(model, messages, systemPrompt, temperature, maxTokens, tools, signal, reasoningTier, sessionId);
|
|
1044
1097
|
return;
|
|
1045
1098
|
}
|
|
1046
1099
|
throw new Error('LLMProvider legacy OpenAI streaming was removed in dev-0.3.0: enable provider_adapters_v2 (useProviderAdaptersV2).');
|
|
@@ -1071,7 +1124,8 @@ class LLMProvider {
|
|
|
1071
1124
|
forwardAbort();
|
|
1072
1125
|
else
|
|
1073
1126
|
signal?.addEventListener('abort', forwardAbort, { once: true });
|
|
1074
|
-
const
|
|
1127
|
+
const effectiveTimeout = this.effectiveRequestTimeout(120000);
|
|
1128
|
+
const timeout = setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout);
|
|
1075
1129
|
let reader = null;
|
|
1076
1130
|
try {
|
|
1077
1131
|
let response;
|
|
@@ -1084,11 +1138,13 @@ class LLMProvider {
|
|
|
1084
1138
|
});
|
|
1085
1139
|
}
|
|
1086
1140
|
catch (e) {
|
|
1141
|
+
if (signal?.aborted)
|
|
1142
|
+
throw abortFailure(signal);
|
|
1143
|
+
if (abort.signal.aborted)
|
|
1144
|
+
throw abortFailure(abort.signal);
|
|
1087
1145
|
if (!this.shouldUseNodeHttpFallback(e))
|
|
1088
1146
|
throw e;
|
|
1089
1147
|
clearTimeout(timeout);
|
|
1090
|
-
if (signal?.aborted)
|
|
1091
|
-
throw abortFailure(signal);
|
|
1092
1148
|
yield* this.githubModelsChatNonStreaming(url, body, signal);
|
|
1093
1149
|
return;
|
|
1094
1150
|
}
|
|
@@ -1108,12 +1164,9 @@ class LLMProvider {
|
|
|
1108
1164
|
let currentReasoningContent = '';
|
|
1109
1165
|
let contentPolicyBlocked = false;
|
|
1110
1166
|
let emittedContent = false;
|
|
1167
|
+
const streamSignal = signal || new AbortController().signal;
|
|
1111
1168
|
while (true) {
|
|
1112
|
-
|
|
1113
|
-
throw abortFailure(signal);
|
|
1114
|
-
const readPromise = reader.read();
|
|
1115
|
-
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Stream read timeout')), 30000));
|
|
1116
|
-
const { done, value } = await Promise.race([readPromise, timeoutPromise]);
|
|
1169
|
+
const { done, value } = await (0, providers_1.readProviderStreamChunk)(reader, streamSignal);
|
|
1117
1170
|
if (done)
|
|
1118
1171
|
break;
|
|
1119
1172
|
buffer += decoder.decode(value, { stream: true });
|