@livedesk/hub 0.1.19 → 0.1.21
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/package.json +1 -1
- package/src/agents/agent-manager.js +17 -56
- package/src/agents/agent-runtime-error.js +9 -0
- package/src/agents/agent-settings.js +2 -35
- package/src/agents/codex-agent-runtime.js +28 -28
- package/src/remote-hub.js +28 -0
- package/src/server.js +22 -59
- package/src/agents/provider-errors.js +0 -12
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { AgentSettingsStore
|
|
4
|
-
import {
|
|
3
|
+
import { AgentSettingsStore } from './agent-settings.js';
|
|
4
|
+
import { AgentRuntimeError } from './agent-runtime-error.js';
|
|
5
5
|
|
|
6
6
|
function publicCodexStatus(status = {}) {
|
|
7
7
|
return {
|
|
@@ -13,6 +13,15 @@ function publicCodexStatus(status = {}) {
|
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function publicCodexConnection(result = {}) {
|
|
17
|
+
const latencyMs = Number(result.latencyMs);
|
|
18
|
+
return {
|
|
19
|
+
ok: result.ok === true,
|
|
20
|
+
latencyMs: Number.isFinite(latencyMs) ? Math.max(0, Math.round(latencyMs)) : 0,
|
|
21
|
+
threadId: String(result.threadId || '').slice(0, 200)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
export function createAgentManager({
|
|
17
26
|
dataDir = path.join(os.homedir(), '.livedesk'),
|
|
18
27
|
settingsStore,
|
|
@@ -42,68 +51,21 @@ export function createAgentManager({
|
|
|
42
51
|
|
|
43
52
|
async function publicSettings() {
|
|
44
53
|
const current = await settings.get();
|
|
45
|
-
const {
|
|
46
|
-
codexMaxTurns: _codexMaxTurns,
|
|
47
|
-
provider: _provider,
|
|
48
|
-
...exposedSettings
|
|
49
|
-
} = current;
|
|
50
54
|
const codex = await getCodexStatus();
|
|
51
|
-
const securityStatus = runtime?.getSecurityStatus?.() || {
|
|
52
|
-
isolatedCodexHome: false,
|
|
53
|
-
restrictedEnvironment: false,
|
|
54
|
-
livedeskMcpOnly: false,
|
|
55
|
-
selectedDeviceScopeEnforced: false,
|
|
56
|
-
failClosed: true,
|
|
57
|
-
verified: false,
|
|
58
|
-
state: 'unavailable'
|
|
59
|
-
};
|
|
60
55
|
return {
|
|
61
|
-
|
|
56
|
+
enabled: current.enabled === true,
|
|
62
57
|
codexInstallation: codex.installed ? 'installed' : 'not-installed',
|
|
63
58
|
codexAuth: codex.authenticated,
|
|
64
|
-
codexStatus: codex.status
|
|
65
|
-
codexPath: codex.codexPath,
|
|
66
|
-
agentWorkspacePath: current.codexWorkingDirectory,
|
|
67
|
-
securityStatus
|
|
59
|
+
codexStatus: codex.status
|
|
68
60
|
};
|
|
69
61
|
}
|
|
70
62
|
|
|
71
63
|
return {
|
|
72
64
|
getSettings: publicSettings,
|
|
73
|
-
async updateSettings(patch = {}) {
|
|
74
|
-
const nextPatch = { ...patch };
|
|
75
|
-
const requestedProvider = String(nextPatch.provider || AGENT_PROVIDER_CODEX).trim().toLowerCase();
|
|
76
|
-
const suppliedLegacyKey = typeof nextPatch.apiKey === 'string' && nextPatch.apiKey.trim().length > 0;
|
|
77
|
-
delete nextPatch.provider;
|
|
78
|
-
delete nextPatch.apiKey;
|
|
79
|
-
if (requestedProvider !== AGENT_PROVIDER_CODEX || suppliedLegacyKey) {
|
|
80
|
-
throw new AgentProviderError(
|
|
81
|
-
'agent-provider-retired',
|
|
82
|
-
'LiveDesk uses the Hub-hosted Codex Agent and does not configure alternate model providers.',
|
|
83
|
-
{ status: 400 }
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
await settings.update({ ...nextPatch, provider: AGENT_PROVIDER_CODEX });
|
|
87
|
-
statusCache = { expiresAt: 0, value: null };
|
|
88
|
-
return publicSettings();
|
|
89
|
-
},
|
|
90
|
-
async resetSettings() {
|
|
91
|
-
await settings.reset();
|
|
92
|
-
statusCache = { expiresAt: 0, value: null };
|
|
93
|
-
return publicSettings();
|
|
94
|
-
},
|
|
95
|
-
// Kept as a harmless compatibility endpoint for an older cached web UI.
|
|
96
|
-
// No credential is read, written, or returned by the current Hub.
|
|
97
|
-
async deleteApiKey() {
|
|
98
|
-
return publicSettings();
|
|
99
|
-
},
|
|
100
65
|
async testConnection() {
|
|
101
|
-
if (!runtime) throw new
|
|
66
|
+
if (!runtime) throw new AgentRuntimeError('codex-sdk-not-installed', 'Codex SDK is not installed.', { status: 503 });
|
|
102
67
|
statusCache = { expiresAt: 0, value: null };
|
|
103
|
-
return runtime.testConnection();
|
|
104
|
-
},
|
|
105
|
-
async createPlan() {
|
|
106
|
-
throw new AgentProviderError('agent-codex-run-required', 'Codex Agent requests use the LiveDesk tool loop.', { status: 409 });
|
|
68
|
+
return publicCodexConnection(await runtime.testConnection());
|
|
107
69
|
},
|
|
108
70
|
async createSummary(input) {
|
|
109
71
|
const source = input && typeof input === 'object' ? input : {};
|
|
@@ -113,9 +75,8 @@ export function createAgentManager({
|
|
|
113
75
|
};
|
|
114
76
|
},
|
|
115
77
|
async startRun(input) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
throw new AgentProviderError('agent-codex-not-active', 'Codex SDK is not the active Agent runtime.', { status: 409 });
|
|
78
|
+
if (!runtime) {
|
|
79
|
+
throw new AgentRuntimeError('agent-codex-not-active', 'Codex SDK is not available for LiveDesk Agent commands.', { status: 409 });
|
|
119
80
|
}
|
|
120
81
|
return runtime.start(input);
|
|
121
82
|
},
|
|
@@ -2,23 +2,13 @@ import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
|
|
5
|
-
export const AGENT_PROVIDER_CODEX = 'codex';
|
|
6
|
-
export const DEFAULT_AGENT_PROVIDER = AGENT_PROVIDER_CODEX;
|
|
7
|
-
export const DEFAULT_AGENT_WORKSPACE = path.join(os.homedir(), '.livedesk', 'agent-workspace');
|
|
8
5
|
export const DEFAULT_AGENT_SETTINGS = Object.freeze({
|
|
9
|
-
settingsVersion:
|
|
6
|
+
settingsVersion: 5,
|
|
10
7
|
enabled: true,
|
|
11
|
-
provider: DEFAULT_AGENT_PROVIDER,
|
|
12
|
-
codexSandboxMode: 'read-only',
|
|
13
|
-
codexApprovalPolicy: 'never',
|
|
14
|
-
codexWorkingDirectory: DEFAULT_AGENT_WORKSPACE,
|
|
15
8
|
codexTaskTimeoutMs: 600000,
|
|
16
9
|
codexMaxTurns: 12,
|
|
17
10
|
codexMaxToolCalls: 20,
|
|
18
11
|
codexResumeSessions: true,
|
|
19
|
-
codexShowDetailedEvents: true,
|
|
20
|
-
codexNetworkAccessEnabled: false,
|
|
21
|
-
codexWebSearchMode: 'disabled',
|
|
22
12
|
maxConcurrentRequests: 2
|
|
23
13
|
});
|
|
24
14
|
|
|
@@ -33,32 +23,15 @@ function numberValue(value, min, max, fallback, integer = false) {
|
|
|
33
23
|
return integer ? Math.round(clamped) : clamped;
|
|
34
24
|
}
|
|
35
25
|
|
|
36
|
-
function normalizeWorkingDirectory() {
|
|
37
|
-
// The agent runtime owns this directory. Do not allow a browser/API caller
|
|
38
|
-
// to move Codex into a user project or an arbitrary filesystem root.
|
|
39
|
-
return DEFAULT_AGENT_WORKSPACE;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
26
|
export function normalizeAgentSettings(value = {}) {
|
|
43
27
|
const source = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
44
28
|
return {
|
|
45
|
-
settingsVersion:
|
|
29
|
+
settingsVersion: 5,
|
|
46
30
|
enabled: booleanValue(source.enabled, DEFAULT_AGENT_SETTINGS.enabled),
|
|
47
|
-
// LiveDesk has one Agent runtime. Old provider values are deliberately
|
|
48
|
-
// normalized to Codex so an upgrade cannot reactivate a retired provider.
|
|
49
|
-
provider: AGENT_PROVIDER_CODEX,
|
|
50
|
-
// These are safety settings, not user-controlled execution toggles. They
|
|
51
|
-
// are accepted for compatibility but always normalized to the safe floor.
|
|
52
|
-
codexSandboxMode: 'read-only',
|
|
53
|
-
codexApprovalPolicy: 'never',
|
|
54
|
-
codexWorkingDirectory: normalizeWorkingDirectory(),
|
|
55
31
|
codexTaskTimeoutMs: numberValue(source.codexTaskTimeoutMs, 30000, 1800000, DEFAULT_AGENT_SETTINGS.codexTaskTimeoutMs, true),
|
|
56
32
|
codexMaxTurns: numberValue(source.codexMaxTurns, 1, 40, DEFAULT_AGENT_SETTINGS.codexMaxTurns, true),
|
|
57
33
|
codexMaxToolCalls: numberValue(source.codexMaxToolCalls, 1, 100, DEFAULT_AGENT_SETTINGS.codexMaxToolCalls, true),
|
|
58
34
|
codexResumeSessions: booleanValue(source.codexResumeSessions, DEFAULT_AGENT_SETTINGS.codexResumeSessions),
|
|
59
|
-
codexShowDetailedEvents: booleanValue(source.codexShowDetailedEvents, DEFAULT_AGENT_SETTINGS.codexShowDetailedEvents),
|
|
60
|
-
codexNetworkAccessEnabled: false,
|
|
61
|
-
codexWebSearchMode: 'disabled',
|
|
62
35
|
maxConcurrentRequests: numberValue(source.maxConcurrentRequests, 1, 8, DEFAULT_AGENT_SETTINGS.maxConcurrentRequests, true)
|
|
63
36
|
};
|
|
64
37
|
}
|
|
@@ -95,10 +68,4 @@ export class AgentSettingsStore {
|
|
|
95
68
|
return { ...next };
|
|
96
69
|
}
|
|
97
70
|
|
|
98
|
-
async reset() {
|
|
99
|
-
const next = normalizeAgentSettings(DEFAULT_AGENT_SETTINGS);
|
|
100
|
-
await writeJsonAtomic(this.filePath, next);
|
|
101
|
-
this.settings = next;
|
|
102
|
-
return { ...next };
|
|
103
|
-
}
|
|
104
71
|
}
|
|
@@ -4,7 +4,7 @@ import os from 'node:os';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
6
|
import { createRequire } from 'node:module';
|
|
7
|
-
import {
|
|
7
|
+
import { AgentRuntimeError } from './agent-runtime-error.js';
|
|
8
8
|
import { AGENT_TOOL_NAMES } from './agent-tool-registry.js';
|
|
9
9
|
import { createAgentPermissionPolicy, hashAgentPermissionPolicy } from './agent-permissions.js';
|
|
10
10
|
|
|
@@ -57,12 +57,12 @@ function isAbortError(error) {
|
|
|
57
57
|
|
|
58
58
|
function normalizeCodexError(error) {
|
|
59
59
|
const message = safeText(error?.message, 800) || 'Codex run failed.';
|
|
60
|
-
if (error instanceof
|
|
61
|
-
if (/out of credits|usage limit|rate limit|too many requests/i.test(message)) return new
|
|
62
|
-
if (/refresh[_ -]?token[_ -]?(?:already[_ -]?)?used|token refresh|refresh credential/i.test(message)) return new
|
|
63
|
-
if (/not authenticated|not logged in|authentication|unauthorized|login required/i.test(message)) return new
|
|
64
|
-
if (/user cancelled MCP tool call/i.test(message)) return new
|
|
65
|
-
return new
|
|
60
|
+
if (error instanceof AgentRuntimeError && error.code !== 'codex-run-failed') return error;
|
|
61
|
+
if (/out of credits|usage limit|rate limit|too many requests/i.test(message)) return new AgentRuntimeError('codex-usage-limit-reached', 'The Codex workspace has no remaining usage.', { status: 402, retryable: true });
|
|
62
|
+
if (/refresh[_ -]?token[_ -]?(?:already[_ -]?)?used|token refresh|refresh credential/i.test(message)) return new AgentRuntimeError('codex-auth-stale', 'The saved Codex sign-in is stale. LiveDesk refreshed its Codex authentication bridge and will retry.', { status: 401, retryable: true });
|
|
63
|
+
if (/not authenticated|not logged in|authentication|unauthorized|login required/i.test(message)) return new AgentRuntimeError('codex-auth-required', 'Sign in with the Codex CLI first.', { status: 401 });
|
|
64
|
+
if (/user cancelled MCP tool call/i.test(message)) return new AgentRuntimeError('codex-mcp-tool-rejected', 'Codex rejected the LiveDesk MCP tool before it reached the Hub.', { status: 502 });
|
|
65
|
+
return new AgentRuntimeError('codex-run-failed', message, { status: 502, retryable: true });
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
function codexThreadOptions(workspace) {
|
|
@@ -164,16 +164,16 @@ async function ensureCodexHome(codexHome, globalCodexHome, { preferGlobalAuth =
|
|
|
164
164
|
const resolvedHome = path.resolve(codexHome);
|
|
165
165
|
const globalHome = path.resolve(globalCodexHome);
|
|
166
166
|
if (!resolvedHome || resolvedHome === globalHome) {
|
|
167
|
-
throw new
|
|
167
|
+
throw new AgentRuntimeError('codex-isolation-unavailable', 'Codex requires a dedicated LiveDesk home.', { status: 503 });
|
|
168
168
|
}
|
|
169
169
|
try {
|
|
170
170
|
await mkdir(resolvedHome, { recursive: true, mode: 0o700 });
|
|
171
171
|
if (await access(path.join(resolvedHome, 'config.toml')).then(() => true, () => false)) {
|
|
172
|
-
throw new
|
|
172
|
+
throw new AgentRuntimeError('codex-environment-isolation-failed', 'The dedicated Codex home contains an unmanaged config.toml.', { status: 503 });
|
|
173
173
|
}
|
|
174
174
|
} catch (error) {
|
|
175
|
-
if (error instanceof
|
|
176
|
-
const wrapped = new
|
|
175
|
+
if (error instanceof AgentRuntimeError) throw error;
|
|
176
|
+
const wrapped = new AgentRuntimeError('codex-environment-isolation-failed', 'The dedicated Codex home could not be prepared.', { status: 503 });
|
|
177
177
|
wrapped.cause = error;
|
|
178
178
|
throw wrapped;
|
|
179
179
|
}
|
|
@@ -200,7 +200,7 @@ async function ensureCodexHome(codexHome, globalCodexHome, { preferGlobalAuth =
|
|
|
200
200
|
await replaceAuthHardLink(sourceAuthPath, targetAuthPath);
|
|
201
201
|
}
|
|
202
202
|
} catch (error) {
|
|
203
|
-
const wrapped = new
|
|
203
|
+
const wrapped = new AgentRuntimeError('codex-environment-isolation-failed', 'The Codex authentication bridge could not be prepared.', { status: 503 });
|
|
204
204
|
wrapped.cause = error;
|
|
205
205
|
throw wrapped;
|
|
206
206
|
}
|
|
@@ -243,7 +243,7 @@ function runAgeMs(run) {
|
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
function codexAbortError(code, message) {
|
|
246
|
-
return new
|
|
246
|
+
return new AgentRuntimeError(code, message, { status: code === 'codex-run-timeout' ? 504 : 409, retryable: code === 'codex-run-timeout' });
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
function safeAgentDeviceIds(value) {
|
|
@@ -331,7 +331,7 @@ export function createCodexAgentRuntime({
|
|
|
331
331
|
function assertSecurityConfiguration() {
|
|
332
332
|
const status = securityStatus();
|
|
333
333
|
if (!status.isolatedCodexHome || !status.restrictedEnvironment || !status.livedeskMcpOnly || !status.selectedDeviceScopeEnforced) {
|
|
334
|
-
throw new
|
|
334
|
+
throw new AgentRuntimeError('codex-unsupported-security-config', 'Codex security isolation is not available.', { status: 503 });
|
|
335
335
|
}
|
|
336
336
|
}
|
|
337
337
|
|
|
@@ -364,7 +364,7 @@ export function createCodexAgentRuntime({
|
|
|
364
364
|
|
|
365
365
|
async function loadCodex() {
|
|
366
366
|
if (!codexModulePromise) codexModulePromise = import('@openai/codex-sdk').catch(error => {
|
|
367
|
-
const wrapped = new
|
|
367
|
+
const wrapped = new AgentRuntimeError('codex-sdk-not-installed', 'Codex SDK is not installed in this LiveDesk package.', { status: 503 });
|
|
368
368
|
wrapped.cause = error;
|
|
369
369
|
throw wrapped;
|
|
370
370
|
});
|
|
@@ -395,7 +395,7 @@ export function createCodexAgentRuntime({
|
|
|
395
395
|
? 'signed-in'
|
|
396
396
|
: 'not-signed-in';
|
|
397
397
|
} catch (error) {
|
|
398
|
-
if (error instanceof
|
|
398
|
+
if (error instanceof AgentRuntimeError) throw error;
|
|
399
399
|
detail = safeText(error?.message, 300);
|
|
400
400
|
}
|
|
401
401
|
return { installed: true, authenticated, status: authenticated === 'signed-in' ? 'ready' : 'auth-required', codexPath: pathToCli, detail };
|
|
@@ -405,8 +405,8 @@ export function createCodexAgentRuntime({
|
|
|
405
405
|
assertSecurityConfiguration();
|
|
406
406
|
const startedAt = Date.now();
|
|
407
407
|
const status = await getStatus();
|
|
408
|
-
if (!status.installed) throw new
|
|
409
|
-
if (status.authenticated !== 'signed-in') throw new
|
|
408
|
+
if (!status.installed) throw new AgentRuntimeError('codex-sdk-not-installed', 'Codex SDK is not installed.', { status: 503 });
|
|
409
|
+
if (status.authenticated !== 'signed-in') throw new AgentRuntimeError('codex-auth-required', 'Sign in with the Codex CLI first.', { status: 401 });
|
|
410
410
|
const settings = await settingsStore.get();
|
|
411
411
|
await mkdir(workspace, { recursive: true });
|
|
412
412
|
await prepareCodexHome();
|
|
@@ -416,7 +416,7 @@ export function createCodexAgentRuntime({
|
|
|
416
416
|
try {
|
|
417
417
|
const thread = codex.startThread(codexThreadOptions(workspace));
|
|
418
418
|
const turn = await thread.run('Reply with the single word READY. Do not use tools.', { signal: controller.signal });
|
|
419
|
-
if (!String(turn.finalResponse || '').trim()) throw new
|
|
419
|
+
if (!String(turn.finalResponse || '').trim()) throw new AgentRuntimeError('codex-empty-response', 'Codex returned an empty response.', { status: 502 });
|
|
420
420
|
return { ok: true, latencyMs: Date.now() - startedAt, threadId: thread.id || '' };
|
|
421
421
|
} catch (error) {
|
|
422
422
|
throw normalizeCodexError(error);
|
|
@@ -492,15 +492,15 @@ export function createCodexAgentRuntime({
|
|
|
492
492
|
timeoutTimer.unref?.();
|
|
493
493
|
await mkdir(workspace, { recursive: true });
|
|
494
494
|
if (activeCount > settings.maxConcurrentRequests) {
|
|
495
|
-
throw new
|
|
495
|
+
throw new AgentRuntimeError('agent-concurrency-limit', 'The LiveDesk Agent concurrency limit is reached.', { status: 429, retryable: true });
|
|
496
496
|
}
|
|
497
497
|
if (run.abortController.signal.aborted) {
|
|
498
498
|
if (run.abortReason === 'timeout') throw codexAbortError('codex-run-timeout', 'The Codex task exceeded its timeout.');
|
|
499
499
|
throw Object.assign(new Error('The Codex task was cancelled.'), { name: 'AbortError' });
|
|
500
500
|
}
|
|
501
501
|
const status = await getStatus();
|
|
502
|
-
if (!status.installed) throw new
|
|
503
|
-
if (status.authenticated !== 'signed-in') throw new
|
|
502
|
+
if (!status.installed) throw new AgentRuntimeError('codex-sdk-not-installed', 'Codex SDK is not installed.', { status: 503 });
|
|
503
|
+
if (status.authenticated !== 'signed-in') throw new AgentRuntimeError('codex-auth-required', 'Sign in with the Codex CLI first.', { status: 401 });
|
|
504
504
|
reportProgress(run, 'preparing-tools', 'Codex is ready. Preparing the LiveDesk tools for this Client.');
|
|
505
505
|
session = createMcpSession({
|
|
506
506
|
runId: run.runId,
|
|
@@ -552,14 +552,14 @@ export function createCodexAgentRuntime({
|
|
|
552
552
|
run.abortReason = 'turn-limit';
|
|
553
553
|
run.abortController.abort();
|
|
554
554
|
session.cancel();
|
|
555
|
-
throw new
|
|
555
|
+
throw new AgentRuntimeError('codex-turn-limit-reached', 'Codex exceeded the LiveDesk turn limit.', { status: 409 });
|
|
556
556
|
}
|
|
557
557
|
}
|
|
558
558
|
if (run.toolCallCount > run.permissionPolicy.maxToolCalls) {
|
|
559
559
|
run.abortReason = 'tool-limit';
|
|
560
560
|
run.abortController.abort();
|
|
561
561
|
session.cancel();
|
|
562
|
-
throw new
|
|
562
|
+
throw new AgentRuntimeError('codex-tool-limit-reached', 'Codex exceeded the LiveDesk tool-call limit.', { status: 409 });
|
|
563
563
|
}
|
|
564
564
|
if (event.type === 'item.completed' && event.item?.type === 'mcp_tool_call' && event.item?.status === 'failed') {
|
|
565
565
|
throw new Error(safeText(event.item?.error?.message, 800) || `LiveDesk tool ${safeText(event.item?.tool, 120) || 'call'} failed.`);
|
|
@@ -641,14 +641,14 @@ export function createCodexAgentRuntime({
|
|
|
641
641
|
|
|
642
642
|
async function start(input = {}) {
|
|
643
643
|
const instruction = safeText(input.instruction, 4000);
|
|
644
|
-
if (!instruction) throw new
|
|
644
|
+
if (!instruction) throw new AgentRuntimeError('agent-invalid-instruction', 'Instruction is required.', { status: 400 });
|
|
645
645
|
const deviceIds = safeAgentDeviceIds(input.deviceIds);
|
|
646
|
-
if (deviceIds.length === 0) throw new
|
|
646
|
+
if (deviceIds.length === 0) throw new AgentRuntimeError('agent-no-target-devices', 'Select at least one connected device.', { status: 400 });
|
|
647
647
|
const settings = await settingsStore.get();
|
|
648
|
-
if (!settings.enabled) throw new
|
|
648
|
+
if (!settings.enabled) throw new AgentRuntimeError('agent-disabled', 'Codex Agent is disabled in Settings.', { status: 409 });
|
|
649
649
|
assertSecurityConfiguration();
|
|
650
650
|
pruneRuns();
|
|
651
|
-
if (runs.size >= MAX_RUNS) throw new
|
|
651
|
+
if (runs.size >= MAX_RUNS) throw new AgentRuntimeError('agent-run-limit', 'The LiveDesk Agent run history is full.', { status: 429, retryable: true });
|
|
652
652
|
const permissionPolicy = freezeAgentPermissionPolicy(input.permissionPolicy && typeof input.permissionPolicy === 'object'
|
|
653
653
|
? input.permissionPolicy
|
|
654
654
|
: createAgentPermissionPolicy({ mode: input.permissionMode || 'safe-auto', deviceIds, maxToolCalls: settings.codexMaxToolCalls }));
|
package/src/remote-hub.js
CHANGED
|
@@ -3892,20 +3892,34 @@ export function createRemoteHub(options = {}) {
|
|
|
3892
3892
|
agentQueueBeforePaceMs: Number.isFinite(Number(message.agentQueueBeforePaceMs)) ? Number(message.agentQueueBeforePaceMs) : 0,
|
|
3893
3893
|
agentFrameAgeMs: Number.isFinite(Number(message.agentFrameAgeMs)) ? Number(message.agentFrameAgeMs) : 0,
|
|
3894
3894
|
nativeCaptureTelemetryAvailable: message.nativeCaptureTelemetryAvailable === true,
|
|
3895
|
+
nativeCaptureTelemetryVersion: Number.isFinite(Number(message.nativeCaptureTelemetryVersion)) ? Number(message.nativeCaptureTelemetryVersion) : 0,
|
|
3896
|
+
nativeCaptureTelemetryAgeMs: message.nativeCaptureTelemetryAgeMs === null
|
|
3897
|
+
|| message.nativeCaptureTelemetryAgeMs === undefined
|
|
3898
|
+
? undefined
|
|
3899
|
+
: Number.isFinite(Number(message.nativeCaptureTelemetryAgeMs))
|
|
3900
|
+
? Math.max(0, Number(message.nativeCaptureTelemetryAgeMs))
|
|
3901
|
+
: undefined,
|
|
3895
3902
|
nativeEncoderHardwareAccelerationKnown: message.nativeEncoderHardwareAccelerationKnown === true,
|
|
3896
3903
|
nativeEncoderHardwareAccelerated: typeof message.nativeEncoderHardwareAccelerated === 'boolean'
|
|
3897
3904
|
? message.nativeEncoderHardwareAccelerated
|
|
3898
3905
|
: null,
|
|
3899
3906
|
nativeEncoderHardwareQueryStatus: safeString(message.nativeEncoderHardwareQueryStatus, 80),
|
|
3900
3907
|
nativeCaptureFps: Number.isFinite(Number(message.nativeCaptureFps)) ? Number(message.nativeCaptureFps) : 0,
|
|
3908
|
+
nativeCompressionFps: Number.isFinite(Number(message.nativeCompressionFps)) ? Number(message.nativeCompressionFps) : 0,
|
|
3901
3909
|
nativeEncodedFps: Number.isFinite(Number(message.nativeEncodedFps)) ? Number(message.nativeEncodedFps) : 0,
|
|
3902
3910
|
nativeCaptureSampleCount: Number.isFinite(Number(message.nativeCaptureSampleCount)) ? Number(message.nativeCaptureSampleCount) : 0,
|
|
3903
3911
|
nativeEncodableFrameCount: Number.isFinite(Number(message.nativeEncodableFrameCount)) ? Number(message.nativeEncodableFrameCount) : 0,
|
|
3904
3912
|
nativeSkippedFrameCount: Number.isFinite(Number(message.nativeSkippedFrameCount)) ? Number(message.nativeSkippedFrameCount) : 0,
|
|
3913
|
+
nativeCompressionOutputCount: Number.isFinite(Number(message.nativeCompressionOutputCount)) ? Number(message.nativeCompressionOutputCount) : 0,
|
|
3905
3914
|
nativeEncodedFrameCount: Number.isFinite(Number(message.nativeEncodedFrameCount)) ? Number(message.nativeEncodedFrameCount) : 0,
|
|
3906
3915
|
nativeEncodeFramesInFlight: Number.isFinite(Number(message.nativeEncodeFramesInFlight)) ? Number(message.nativeEncodeFramesInFlight) : 0,
|
|
3907
3916
|
nativeBackpressureSkippedFrameCount: Number.isFinite(Number(message.nativeBackpressureSkippedFrameCount)) ? Number(message.nativeBackpressureSkippedFrameCount) : 0,
|
|
3908
3917
|
nativeIdleSkippedFrameCount: Number.isFinite(Number(message.nativeIdleSkippedFrameCount)) ? Number(message.nativeIdleSkippedFrameCount) : 0,
|
|
3918
|
+
nativeOutputQueueDepth: Number.isFinite(Number(message.nativeOutputQueueDepth)) ? Number(message.nativeOutputQueueDepth) : 0,
|
|
3919
|
+
nativeOutputQueueBytes: Number.isFinite(Number(message.nativeOutputQueueBytes)) ? Number(message.nativeOutputQueueBytes) : 0,
|
|
3920
|
+
nativeOutputQueueHighWatermark: Number.isFinite(Number(message.nativeOutputQueueHighWatermark)) ? Number(message.nativeOutputQueueHighWatermark) : 0,
|
|
3921
|
+
nativeOutputQueueDroppedCount: Number.isFinite(Number(message.nativeOutputQueueDroppedCount)) ? Number(message.nativeOutputQueueDroppedCount) : 0,
|
|
3922
|
+
nativeOutputQueueAwaitingKeyFrame: message.nativeOutputQueueAwaitingKeyFrame === true,
|
|
3909
3923
|
nativeCaptureTelemetryAt: safeString(message.nativeCaptureTelemetryAt, 80),
|
|
3910
3924
|
droppedByAgent: Number.isFinite(Number(message.droppedByAgent)) ? Number(message.droppedByAgent) : 0,
|
|
3911
3925
|
hardwareEncoder: safeString(message.hardwareEncoder, 80),
|
|
@@ -4274,20 +4288,34 @@ export function createRemoteHub(options = {}) {
|
|
|
4274
4288
|
agentQueueBeforePaceMs: Number.isFinite(Number(message.agentQueueBeforePaceMs)) ? Number(message.agentQueueBeforePaceMs) : 0,
|
|
4275
4289
|
agentFrameAgeMs: Number.isFinite(Number(message.agentFrameAgeMs)) ? Number(message.agentFrameAgeMs) : 0,
|
|
4276
4290
|
nativeCaptureTelemetryAvailable: message.nativeCaptureTelemetryAvailable === true,
|
|
4291
|
+
nativeCaptureTelemetryVersion: Number.isFinite(Number(message.nativeCaptureTelemetryVersion)) ? Number(message.nativeCaptureTelemetryVersion) : 0,
|
|
4292
|
+
nativeCaptureTelemetryAgeMs: message.nativeCaptureTelemetryAgeMs === null
|
|
4293
|
+
|| message.nativeCaptureTelemetryAgeMs === undefined
|
|
4294
|
+
? undefined
|
|
4295
|
+
: Number.isFinite(Number(message.nativeCaptureTelemetryAgeMs))
|
|
4296
|
+
? Math.max(0, Number(message.nativeCaptureTelemetryAgeMs))
|
|
4297
|
+
: undefined,
|
|
4277
4298
|
nativeEncoderHardwareAccelerationKnown: message.nativeEncoderHardwareAccelerationKnown === true,
|
|
4278
4299
|
nativeEncoderHardwareAccelerated: typeof message.nativeEncoderHardwareAccelerated === 'boolean'
|
|
4279
4300
|
? message.nativeEncoderHardwareAccelerated
|
|
4280
4301
|
: null,
|
|
4281
4302
|
nativeEncoderHardwareQueryStatus: safeString(message.nativeEncoderHardwareQueryStatus, 80),
|
|
4282
4303
|
nativeCaptureFps: Number.isFinite(Number(message.nativeCaptureFps)) ? Number(message.nativeCaptureFps) : 0,
|
|
4304
|
+
nativeCompressionFps: Number.isFinite(Number(message.nativeCompressionFps)) ? Number(message.nativeCompressionFps) : 0,
|
|
4283
4305
|
nativeEncodedFps: Number.isFinite(Number(message.nativeEncodedFps)) ? Number(message.nativeEncodedFps) : 0,
|
|
4284
4306
|
nativeCaptureSampleCount: Number.isFinite(Number(message.nativeCaptureSampleCount)) ? Number(message.nativeCaptureSampleCount) : 0,
|
|
4285
4307
|
nativeEncodableFrameCount: Number.isFinite(Number(message.nativeEncodableFrameCount)) ? Number(message.nativeEncodableFrameCount) : 0,
|
|
4286
4308
|
nativeSkippedFrameCount: Number.isFinite(Number(message.nativeSkippedFrameCount)) ? Number(message.nativeSkippedFrameCount) : 0,
|
|
4309
|
+
nativeCompressionOutputCount: Number.isFinite(Number(message.nativeCompressionOutputCount)) ? Number(message.nativeCompressionOutputCount) : 0,
|
|
4287
4310
|
nativeEncodedFrameCount: Number.isFinite(Number(message.nativeEncodedFrameCount)) ? Number(message.nativeEncodedFrameCount) : 0,
|
|
4288
4311
|
nativeEncodeFramesInFlight: Number.isFinite(Number(message.nativeEncodeFramesInFlight)) ? Number(message.nativeEncodeFramesInFlight) : 0,
|
|
4289
4312
|
nativeBackpressureSkippedFrameCount: Number.isFinite(Number(message.nativeBackpressureSkippedFrameCount)) ? Number(message.nativeBackpressureSkippedFrameCount) : 0,
|
|
4290
4313
|
nativeIdleSkippedFrameCount: Number.isFinite(Number(message.nativeIdleSkippedFrameCount)) ? Number(message.nativeIdleSkippedFrameCount) : 0,
|
|
4314
|
+
nativeOutputQueueDepth: Number.isFinite(Number(message.nativeOutputQueueDepth)) ? Number(message.nativeOutputQueueDepth) : 0,
|
|
4315
|
+
nativeOutputQueueBytes: Number.isFinite(Number(message.nativeOutputQueueBytes)) ? Number(message.nativeOutputQueueBytes) : 0,
|
|
4316
|
+
nativeOutputQueueHighWatermark: Number.isFinite(Number(message.nativeOutputQueueHighWatermark)) ? Number(message.nativeOutputQueueHighWatermark) : 0,
|
|
4317
|
+
nativeOutputQueueDroppedCount: Number.isFinite(Number(message.nativeOutputQueueDroppedCount)) ? Number(message.nativeOutputQueueDroppedCount) : 0,
|
|
4318
|
+
nativeOutputQueueAwaitingKeyFrame: message.nativeOutputQueueAwaitingKeyFrame === true,
|
|
4291
4319
|
nativeCaptureTelemetryAt: safeString(message.nativeCaptureTelemetryAt, 80),
|
|
4292
4320
|
droppedByAgent: Number.isFinite(Number(message.droppedByAgent)) ? Number(message.droppedByAgent) : 0,
|
|
4293
4321
|
hubIngressDropped: Number.isFinite(Number(message.hubIngressDropped)) ? Number(message.hubIngressDropped) : 0,
|
package/src/server.js
CHANGED
|
@@ -19,7 +19,7 @@ import { createAgentManager } from './agents/agent-manager.js';
|
|
|
19
19
|
import { AgentSettingsStore } from './agents/agent-settings.js';
|
|
20
20
|
import { createCodexAgentRuntime } from './agents/codex-agent-runtime.js';
|
|
21
21
|
import { createAgentDeviceScope, resolveAgentTargetIds } from './agents/agent-device-scope.js';
|
|
22
|
-
import {
|
|
22
|
+
import { AgentRuntimeError } from './agents/agent-runtime-error.js';
|
|
23
23
|
import { AGENT_PERMISSION_MODES, createAgentPermissionPolicy, evaluateAgentToolPermission, hashAgentPermissionPolicy } from './agents/agent-permissions.js';
|
|
24
24
|
import { createAgentPermissionStore } from './agents/agent-permission-store.js';
|
|
25
25
|
import { createAgentAuditStore } from './agents/agent-audit-store.js';
|
|
@@ -931,7 +931,7 @@ function createAgentMcpSession({ runId, signal, allowedDeviceIds: allowedDeviceI
|
|
|
931
931
|
const allowedDeviceIds = createAgentDeviceScope(allowedDeviceIdsInput);
|
|
932
932
|
const initialResolution = resolveAgentTargetIds({ allowedDeviceIds, connectedDeviceIds: connectedAgentDeviceIds() });
|
|
933
933
|
if (initialResolution.deviceIds.length === 0) {
|
|
934
|
-
throw new
|
|
934
|
+
throw new AgentRuntimeError(initialResolution.error || 'agent-no-target-devices', 'No selected connected device is available.', { status: 400 });
|
|
935
935
|
}
|
|
936
936
|
pruneAgentMcpRunBatches();
|
|
937
937
|
const effectivePermissionPolicy = permissionPolicy || createAgentPermissionPolicy({ mode: 'ask', deviceIds: allowedDeviceIdsInput, maxToolCalls });
|
|
@@ -1376,7 +1376,7 @@ function sendAgentError(res, error) {
|
|
|
1376
1376
|
const code = String(error?.code || 'agent-request-failed').replace(/[^a-z0-9-]/gi, '-').toLowerCase().slice(0, 80);
|
|
1377
1377
|
const status = Number.isInteger(error?.status) && error.status >= 400 && error.status <= 599
|
|
1378
1378
|
? error.status
|
|
1379
|
-
: code === '
|
|
1379
|
+
: code === 'codex-auth-required' ? 401
|
|
1380
1380
|
: code === 'agent-no-target-devices' || code === 'agent-target-outside-selection' || code === 'codex-tool-not-allowed' || code === 'agent-permission-denied' || code === 'agent-approval-required' || code === 'agent-approval-rejected' || code === 'agent-approval-expired' || code === 'agent-tool-single-target-required' || code === 'agent-tool-platform-unsupported' || code === 'agent-tool-capability-unavailable' ? 400
|
|
1381
1381
|
: code === 'agent-mcp-loopback-only' ? 403
|
|
1382
1382
|
: code === 'codex-isolation-unavailable' || code === 'codex-environment-isolation-failed' || code === 'codex-unsupported-security-config' ? 503
|
|
@@ -2102,20 +2102,34 @@ function buildRemoteFrameBinaryPacket(frameEvent, diagnostics = {}) {
|
|
|
2102
2102
|
agentQueueBeforePaceMs: Number(frame.agentQueueBeforePaceMs || 0) || 0,
|
|
2103
2103
|
agentFrameAgeMs: Number(frame.agentFrameAgeMs || 0) || 0,
|
|
2104
2104
|
nativeCaptureTelemetryAvailable: frame.nativeCaptureTelemetryAvailable === true,
|
|
2105
|
+
nativeCaptureTelemetryVersion: Number(frame.nativeCaptureTelemetryVersion || 0) || 0,
|
|
2106
|
+
nativeCaptureTelemetryAgeMs: frame.nativeCaptureTelemetryAgeMs === null
|
|
2107
|
+
|| frame.nativeCaptureTelemetryAgeMs === undefined
|
|
2108
|
+
? undefined
|
|
2109
|
+
: Number.isFinite(Number(frame.nativeCaptureTelemetryAgeMs))
|
|
2110
|
+
? Math.max(0, Number(frame.nativeCaptureTelemetryAgeMs))
|
|
2111
|
+
: undefined,
|
|
2105
2112
|
nativeEncoderHardwareAccelerationKnown: frame.nativeEncoderHardwareAccelerationKnown === true,
|
|
2106
2113
|
nativeEncoderHardwareAccelerated: typeof frame.nativeEncoderHardwareAccelerated === 'boolean'
|
|
2107
2114
|
? frame.nativeEncoderHardwareAccelerated
|
|
2108
2115
|
: null,
|
|
2109
2116
|
nativeEncoderHardwareQueryStatus: frame.nativeEncoderHardwareQueryStatus || '',
|
|
2110
2117
|
nativeCaptureFps: Number(frame.nativeCaptureFps || 0) || 0,
|
|
2118
|
+
nativeCompressionFps: Number(frame.nativeCompressionFps || 0) || 0,
|
|
2111
2119
|
nativeEncodedFps: Number(frame.nativeEncodedFps || 0) || 0,
|
|
2112
2120
|
nativeCaptureSampleCount: Number(frame.nativeCaptureSampleCount || 0) || 0,
|
|
2113
2121
|
nativeEncodableFrameCount: Number(frame.nativeEncodableFrameCount || 0) || 0,
|
|
2114
2122
|
nativeSkippedFrameCount: Number(frame.nativeSkippedFrameCount || 0) || 0,
|
|
2123
|
+
nativeCompressionOutputCount: Number(frame.nativeCompressionOutputCount || 0) || 0,
|
|
2115
2124
|
nativeEncodedFrameCount: Number(frame.nativeEncodedFrameCount || 0) || 0,
|
|
2116
2125
|
nativeEncodeFramesInFlight: Number(frame.nativeEncodeFramesInFlight || 0) || 0,
|
|
2117
2126
|
nativeBackpressureSkippedFrameCount: Number(frame.nativeBackpressureSkippedFrameCount || 0) || 0,
|
|
2118
2127
|
nativeIdleSkippedFrameCount: Number(frame.nativeIdleSkippedFrameCount || 0) || 0,
|
|
2128
|
+
nativeOutputQueueDepth: Number(frame.nativeOutputQueueDepth || 0) || 0,
|
|
2129
|
+
nativeOutputQueueBytes: Number(frame.nativeOutputQueueBytes || 0) || 0,
|
|
2130
|
+
nativeOutputQueueHighWatermark: Number(frame.nativeOutputQueueHighWatermark || 0) || 0,
|
|
2131
|
+
nativeOutputQueueDroppedCount: Number(frame.nativeOutputQueueDroppedCount || 0) || 0,
|
|
2132
|
+
nativeOutputQueueAwaitingKeyFrame: frame.nativeOutputQueueAwaitingKeyFrame === true,
|
|
2119
2133
|
nativeCaptureTelemetryAt: frame.nativeCaptureTelemetryAt || '',
|
|
2120
2134
|
droppedByAgent: Number(frame.droppedByAgent || 0) || 0,
|
|
2121
2135
|
hubIngressDropped: Number(frame.hubIngressDropped || 0) || 0,
|
|
@@ -2925,48 +2939,7 @@ app.get('/api/settings/agent/audit', async (req, res) => {
|
|
|
2925
2939
|
res.json({ ok: true, audit: await agentAuditStore.list({ runId: req.query?.runId || '', limit: req.query?.limit }) });
|
|
2926
2940
|
});
|
|
2927
2941
|
|
|
2928
|
-
app.
|
|
2929
|
-
noStore(res);
|
|
2930
|
-
try {
|
|
2931
|
-
const patch = req.body && typeof req.body === 'object' && !Array.isArray(req.body) ? { ...req.body } : {};
|
|
2932
|
-
delete patch.enabled;
|
|
2933
|
-
patch.enabled = await synchronizeAgentEnablement();
|
|
2934
|
-
res.json({ ok: true, settings: await agentManager.updateSettings(patch) });
|
|
2935
|
-
} catch (error) {
|
|
2936
|
-
sendAgentError(res, error);
|
|
2937
|
-
}
|
|
2938
|
-
});
|
|
2939
|
-
|
|
2940
|
-
app.post('/api/settings/agent/reset', async (_req, res) => {
|
|
2941
|
-
noStore(res);
|
|
2942
|
-
try {
|
|
2943
|
-
await agentManager.resetSettings();
|
|
2944
|
-
await synchronizeAgentEnablement();
|
|
2945
|
-
res.json({ ok: true, settings: await agentManager.getSettings() });
|
|
2946
|
-
} catch (error) {
|
|
2947
|
-
sendAgentError(res, error);
|
|
2948
|
-
}
|
|
2949
|
-
});
|
|
2950
|
-
|
|
2951
|
-
app.delete('/api/settings/agent/api-key', async (_req, res) => {
|
|
2952
|
-
noStore(res);
|
|
2953
|
-
try {
|
|
2954
|
-
res.json({ ok: true, settings: await agentManager.deleteApiKey() });
|
|
2955
|
-
} catch (error) {
|
|
2956
|
-
sendAgentError(res, error);
|
|
2957
|
-
}
|
|
2958
|
-
});
|
|
2959
|
-
|
|
2960
|
-
app.get('/api/settings/agent/models', async (_req, res) => {
|
|
2961
|
-
noStore(res);
|
|
2962
|
-
res.status(410).json({
|
|
2963
|
-
ok: false,
|
|
2964
|
-
error: 'agent-model-catalog-retired',
|
|
2965
|
-
message: 'LiveDesk uses the Codex SDK defaults and does not manage models.'
|
|
2966
|
-
});
|
|
2967
|
-
});
|
|
2968
|
-
|
|
2969
|
-
app.post('/api/settings/agent/test', async (_req, res) => {
|
|
2942
|
+
app.post('/api/settings/agent/test', async (_req, res) => {
|
|
2970
2943
|
noStore(res);
|
|
2971
2944
|
try {
|
|
2972
2945
|
res.json({ ok: true, connection: await agentManager.testConnection() });
|
|
@@ -3001,17 +2974,17 @@ app.post('/api/settings/agent/run', async (req, res) => {
|
|
|
3001
2974
|
noStore(res);
|
|
3002
2975
|
try {
|
|
3003
2976
|
if (!(await synchronizeAgentEnablement())) {
|
|
3004
|
-
throw new
|
|
2977
|
+
throw new AgentRuntimeError('agent-disabled', 'Enable Codex Agent in Settings before running commands.', { status: 409 });
|
|
3005
2978
|
}
|
|
3006
2979
|
const instruction = typeof req.body?.instruction === 'string' ? req.body.instruction.slice(0, 4000) : '';
|
|
3007
2980
|
const deviceIds = normalizeDeviceIds(req.body?.deviceIds).slice(0, 500);
|
|
3008
2981
|
const connectedDeviceIds = new Set(connectedAgentDeviceIds());
|
|
3009
2982
|
if (deviceIds.length === 0 || deviceIds.some(deviceId => !connectedDeviceIds.has(deviceId))) {
|
|
3010
|
-
throw new
|
|
2983
|
+
throw new AgentRuntimeError('agent-target-not-connected', 'Every selected Client must have an active Agent control channel.', { status: 409 });
|
|
3011
2984
|
}
|
|
3012
2985
|
const permissionMode = String(req.body?.permissionMode || 'ask').trim().toLowerCase();
|
|
3013
2986
|
if (!AGENT_PERMISSION_MODES.includes(permissionMode)) {
|
|
3014
|
-
throw new
|
|
2987
|
+
throw new AgentRuntimeError('agent-policy-invalid', 'Select a valid Agent permission mode.', { status: 400 });
|
|
3015
2988
|
}
|
|
3016
2989
|
const settings = await agentSettingsStore.get();
|
|
3017
2990
|
const customPolicy = permissionMode === 'custom' ? await agentPermissionStore.get(req.body?.customPolicyId || 'custom-default') : null;
|
|
@@ -3109,17 +3082,7 @@ app.post('/api/settings/agent/runs/cancel-all', async (_req, res) => {
|
|
|
3109
3082
|
res.json({ ok: true, cancelledRuns, cancelledSessions, cancelledBatches: cancelledBatchIds.size });
|
|
3110
3083
|
});
|
|
3111
3084
|
|
|
3112
|
-
app.post('/api/settings/agent/
|
|
3113
|
-
noStore(res);
|
|
3114
|
-
try {
|
|
3115
|
-
const instruction = typeof req.body?.instruction === 'string' ? req.body.instruction.slice(0, 2000) : '';
|
|
3116
|
-
res.json({ ok: true, plan: await agentManager.createPlan({ instruction }) });
|
|
3117
|
-
} catch (error) {
|
|
3118
|
-
sendAgentError(res, error);
|
|
3119
|
-
}
|
|
3120
|
-
});
|
|
3121
|
-
|
|
3122
|
-
app.post('/api/settings/agent/summary', async (req, res) => {
|
|
3085
|
+
app.post('/api/settings/agent/summary', async (req, res) => {
|
|
3123
3086
|
noStore(res);
|
|
3124
3087
|
try {
|
|
3125
3088
|
res.json({ ok: true, ...await agentManager.createSummary(req.body || {}) });
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export class AgentProviderError extends Error {
|
|
2
|
-
constructor(code, message, { status = 502, retryable = false } = {}) {
|
|
3
|
-
super(message);
|
|
4
|
-
this.name = 'AgentProviderError';
|
|
5
|
-
this.code = code;
|
|
6
|
-
this.status = status;
|
|
7
|
-
this.retryable = retryable;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
export function isRetryableProviderError(error) {
|
|
11
|
-
return error?.retryable === true || ['agent-provider-timeout', 'agent-provider-network', 'agent-provider-rate-limited', 'agent-provider-upstream'].includes(error?.code);
|
|
12
|
-
}
|