nolo-cli 0.1.33 → 0.1.34
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/README.md +4 -4
- package/agent-runtime/agentRecordConfig.ts +8 -0
- package/agent-runtime/hostAdapter.ts +9 -1
- package/agent-runtime/index.ts +10 -0
- package/agent-runtime/localLoop.ts +49 -2
- package/agent-runtime/localToolPolicy.ts +70 -21
- package/agent-runtime/openAiCompatibleProvider.ts +7 -3
- package/agent-runtime/openAiCompatibleProviderConfig.ts +15 -26
- package/agent-runtime/platformChatProvider.ts +48 -71
- package/agent-runtime/providerResolution.ts +244 -0
- package/agent-runtime/runtimeToolPolicy.ts +33 -0
- package/agent-runtime/shellCommandPolicy.ts +120 -0
- package/agentInternalCommandEntries.ts +2 -0
- package/agentListCommands.ts +2 -2
- package/agentPullCommand.ts +6 -11
- package/agentRecordCommands.ts +87 -2
- package/agentRecordHelpers.ts +87 -4
- package/agentRuntimeLocal.ts +3 -0
- package/ai/tools/toolNameAliases.ts +70 -0
- package/cli/agentInternalCommandEntries.ts +2 -0
- package/cli/agentListCommands.ts +2 -2
- package/cli/agentPullCommand.ts +6 -11
- package/cli/agentRecordCommands.ts +87 -2
- package/cli/agentRecordHelpers.ts +87 -4
- package/cli/agentRuntimeLocal.ts +3 -0
- package/cli/cliCommandDispatch.ts +2 -2
- package/cli/cliEnvHelpers.ts +80 -12
- package/cli/client/agentRun.ts +1 -0
- package/cli/client/localRuntimeAdapter.ts +44 -7
- package/cli/commandRegistry.ts +2 -2
- package/cli/machineCommands.ts +1 -1
- package/cli/machineWsRunDispatch.ts +29 -9
- package/cli/offlineMarxistsAgentCommand.ts +1 -1
- package/cli/runtimeDoctorCommands.ts +10 -4
- package/cli/tableCommands.ts +1 -1
- package/cli/taskRunCommand.ts +1 -1
- package/cliCommandDispatch.ts +2 -2
- package/cliEnvHelpers.ts +80 -12
- package/client/agentRun.ts +1 -0
- package/client/localProviderResolver.test.ts +26 -2
- package/client/localRuntimeAdapter.test.ts +287 -3
- package/client/localRuntimeAdapter.ts +44 -7
- package/commandRegistry.ts +2 -2
- package/database/server/authorityStoreTypes.ts +31 -0
- package/database/server/db.ts +15 -34
- package/database/server/legacyServerDb.ts +58 -0
- package/database/server/levelAuthorityStore.ts +75 -0
- package/database/server/memoryAuthorityStore.ts +133 -0
- package/database/server/serverStoreFactory.ts +118 -0
- package/machineCommands.ts +1 -1
- package/machineWsRunDispatch.ts +29 -9
- package/offlineMarxistsAgentCommand.ts +1 -1
- package/package.json +10 -2
- package/runtimeDoctorCommands.ts +10 -4
- package/tableCommands.ts +1 -1
- package/taskRunCommand.ts +1 -1
package/README.md
CHANGED
|
@@ -90,7 +90,7 @@ nolo chat
|
|
|
90
90
|
nolo connect
|
|
91
91
|
nolo connect --watch
|
|
92
92
|
nolo connect --daemon
|
|
93
|
-
nolo daemon --server-url https://api.nolo.chat --
|
|
93
|
+
nolo daemon --server-url https://api.nolo.chat --machine-key sk_machine_xxx
|
|
94
94
|
nolo machine status
|
|
95
95
|
nolo run "summarize my latest agent dialogs"
|
|
96
96
|
nolo doc create --title "Trip Notes" --body "hello" --sync local,us --dry-run
|
|
@@ -116,7 +116,7 @@ nolo connect # send one machine heartbeat
|
|
|
116
116
|
nolo connect --watch # keep this terminal process online with periodic heartbeats
|
|
117
117
|
nolo connect --ws # keep a live connector websocket for bound agent runs
|
|
118
118
|
nolo connect --daemon # start connect --ws silently in the background
|
|
119
|
-
nolo daemon --server-url https://api.nolo.chat --
|
|
119
|
+
nolo daemon --server-url https://api.nolo.chat --machine-key sk_machine_xxx
|
|
120
120
|
nolo machine status # list machines registered to the current profile
|
|
121
121
|
```
|
|
122
122
|
|
|
@@ -126,13 +126,13 @@ They do not expose shell, file-write, or raw local LLM endpoints.
|
|
|
126
126
|
Run a Slock-style connector command:
|
|
127
127
|
|
|
128
128
|
```bash
|
|
129
|
-
nolo daemon --server-url https://api.nolo.chat --
|
|
129
|
+
nolo daemon --server-url https://api.nolo.chat --machine-key sk_machine_xxx
|
|
130
130
|
```
|
|
131
131
|
|
|
132
132
|
Future published package shape:
|
|
133
133
|
|
|
134
134
|
```bash
|
|
135
|
-
npx @nolo/daemon@latest --server-url https://api.nolo.chat --
|
|
135
|
+
npx @nolo/daemon@latest --server-url https://api.nolo.chat --machine-key sk_machine_xxx
|
|
136
136
|
```
|
|
137
137
|
|
|
138
138
|
The daemon registers this computer, reports local CLI capabilities, keeps a live
|
|
@@ -64,6 +64,10 @@ export function resolveAgentRuntimeConfigFromRecord(
|
|
|
64
64
|
const apiSource = stringField(record, "apiSource");
|
|
65
65
|
const cliProvider = stringField(record, "cliProvider");
|
|
66
66
|
const customProviderUrl = stringField(record, "customProviderUrl");
|
|
67
|
+
const apiKey = stringField(record, "apiKey");
|
|
68
|
+
const apiKeyHeader = stringField(record, "apiKeyHeader");
|
|
69
|
+
const apiKeyFromAgentKey = stringField(record, "apiKeyFromAgentKey");
|
|
70
|
+
const useServerProxy = record.useServerProxy === true;
|
|
67
71
|
const provider = stringField(record, "provider") ?? stringField(record, "apiSource");
|
|
68
72
|
const toolNames = resolveAgentRuntimeToolNames(record);
|
|
69
73
|
const temperature = numberField(record, "temperature");
|
|
@@ -87,6 +91,10 @@ export function resolveAgentRuntimeConfigFromRecord(
|
|
|
87
91
|
...(apiSource ? { apiSource } : {}),
|
|
88
92
|
...(cliProvider ? { cliProvider } : {}),
|
|
89
93
|
...(customProviderUrl ? { customProviderUrl } : {}),
|
|
94
|
+
...(apiKey ? { apiKey } : {}),
|
|
95
|
+
...(apiKeyHeader ? { apiKeyHeader } : {}),
|
|
96
|
+
...(apiKeyFromAgentKey ? { apiKeyFromAgentKey } : {}),
|
|
97
|
+
...(useServerProxy ? { useServerProxy } : {}),
|
|
90
98
|
...(toolNames ? { toolNames } : {}),
|
|
91
99
|
...(temperature !== undefined ? { temperature } : {}),
|
|
92
100
|
...(topP !== undefined ? { top_p: topP } : {}),
|
|
@@ -14,6 +14,10 @@ export type AgentRuntimeAgentConfig = {
|
|
|
14
14
|
apiSource?: string;
|
|
15
15
|
cliProvider?: string;
|
|
16
16
|
customProviderUrl?: string;
|
|
17
|
+
apiKey?: string;
|
|
18
|
+
apiKeyHeader?: string;
|
|
19
|
+
apiKeyFromAgentKey?: string;
|
|
20
|
+
useServerProxy?: boolean;
|
|
17
21
|
toolNames?: string[];
|
|
18
22
|
temperature?: number;
|
|
19
23
|
top_p?: number;
|
|
@@ -30,13 +34,17 @@ export type AgentRuntimeAgentConfig = {
|
|
|
30
34
|
|
|
31
35
|
export type AgentRuntimeProvider = {
|
|
32
36
|
model: string;
|
|
33
|
-
complete(
|
|
37
|
+
complete(
|
|
38
|
+
messages: AgentRuntimeChatMessage[],
|
|
39
|
+
options?: { timeoutMs?: number }
|
|
40
|
+
): Promise<AgentRuntimeResult>;
|
|
34
41
|
};
|
|
35
42
|
|
|
36
43
|
export type AgentRuntimeToolCallInput = {
|
|
37
44
|
id: string;
|
|
38
45
|
name: string;
|
|
39
46
|
arguments: string;
|
|
47
|
+
userInput?: string;
|
|
40
48
|
};
|
|
41
49
|
|
|
42
50
|
export type AgentRuntimeToolResult = {
|
package/agent-runtime/index.ts
CHANGED
|
@@ -10,6 +10,11 @@ export {
|
|
|
10
10
|
parseOpenAiCompatibleChatCompletionResponse,
|
|
11
11
|
} from "./openAiCompatibleProvider";
|
|
12
12
|
export { resolveOpenAiCompatibleProviderConfig } from "./openAiCompatibleProviderConfig";
|
|
13
|
+
export {
|
|
14
|
+
buildProviderAuthHeaders,
|
|
15
|
+
buildProviderExecutionPlan,
|
|
16
|
+
resolveAgentProviderMode,
|
|
17
|
+
} from "./providerResolution";
|
|
13
18
|
export {
|
|
14
19
|
buildPlatformChatCompletionRequest,
|
|
15
20
|
canUsePlatformChatProvider,
|
|
@@ -45,9 +50,14 @@ export {
|
|
|
45
50
|
executeLocalToolWithPolicy,
|
|
46
51
|
resolveLocalToolPolicy,
|
|
47
52
|
} from "./localToolPolicy";
|
|
53
|
+
export {
|
|
54
|
+
evaluateShellCommandPolicy,
|
|
55
|
+
isDestructiveShellCommand,
|
|
56
|
+
} from "./shellCommandPolicy";
|
|
48
57
|
export {
|
|
49
58
|
mergeAgentRuntimeToolPolicies,
|
|
50
59
|
normalizeAgentRuntimeToolPolicy,
|
|
60
|
+
resolveRequestedRuntimeToolNames,
|
|
51
61
|
resolveEffectiveRuntimeToolPolicy,
|
|
52
62
|
} from "./runtimeToolPolicy";
|
|
53
63
|
export {
|
|
@@ -13,6 +13,7 @@ export type LocalAgentTurnInput = {
|
|
|
13
13
|
input: AgentRuntimeMessageContent;
|
|
14
14
|
continueDialogId?: string;
|
|
15
15
|
maxToolRounds?: number;
|
|
16
|
+
timeoutMs?: number;
|
|
16
17
|
onToolEvent?: (event: LocalAgentToolEvent) => void;
|
|
17
18
|
};
|
|
18
19
|
|
|
@@ -40,6 +41,29 @@ function formatToolExecutionError(args: {
|
|
|
40
41
|
return `${args.toolName} failed: ${message}`;
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
function formatStructuredToolExecutionError(args: {
|
|
45
|
+
toolName: string;
|
|
46
|
+
error: unknown;
|
|
47
|
+
}) {
|
|
48
|
+
if (!args.error || typeof args.error !== "object") return null;
|
|
49
|
+
const error = args.error as {
|
|
50
|
+
code?: unknown;
|
|
51
|
+
message?: unknown;
|
|
52
|
+
policy?: unknown;
|
|
53
|
+
};
|
|
54
|
+
if (typeof error.code !== "string") return null;
|
|
55
|
+
return JSON.stringify({
|
|
56
|
+
error: error.code,
|
|
57
|
+
message:
|
|
58
|
+
typeof error.message === "string"
|
|
59
|
+
? error.message
|
|
60
|
+
: formatToolExecutionError(args),
|
|
61
|
+
...(error.policy && typeof error.policy === "object"
|
|
62
|
+
? { policy: error.policy }
|
|
63
|
+
: {}),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
43
67
|
function shouldReturnToolExecutionErrors(adapter: AgentRuntimeHostAdapter) {
|
|
44
68
|
return adapter.capabilities.includes("local-tools");
|
|
45
69
|
}
|
|
@@ -66,6 +90,16 @@ function buildMessages(args: {
|
|
|
66
90
|
];
|
|
67
91
|
}
|
|
68
92
|
|
|
93
|
+
function extractUserInputText(content: AgentRuntimeMessageContent): string {
|
|
94
|
+
if (typeof content === "string") return content;
|
|
95
|
+
if (!Array.isArray(content)) return "";
|
|
96
|
+
return content
|
|
97
|
+
.map((part) => (part?.type === "text" ? part.text : ""))
|
|
98
|
+
.filter(Boolean)
|
|
99
|
+
.join("\n")
|
|
100
|
+
.trim();
|
|
101
|
+
}
|
|
102
|
+
|
|
69
103
|
export async function runLocalAgentTurn(
|
|
70
104
|
input: LocalAgentTurnInput
|
|
71
105
|
): Promise<LocalAgentTurnResult> {
|
|
@@ -92,10 +126,13 @@ export async function runLocalAgentTurn(
|
|
|
92
126
|
});
|
|
93
127
|
const provider = await input.adapter.resolveProvider(agentConfig);
|
|
94
128
|
const maxToolRounds = input.maxToolRounds ?? DEFAULT_LOCAL_AGENT_MAX_TOOL_ROUNDS;
|
|
129
|
+
const userInputText = extractUserInputText(input.input);
|
|
95
130
|
let toolCallCount = 0;
|
|
96
131
|
let result: AgentRuntimeResult;
|
|
97
132
|
for (let round = 0; round <= maxToolRounds; round += 1) {
|
|
98
|
-
result = await provider.complete(messages
|
|
133
|
+
result = await provider.complete(messages, {
|
|
134
|
+
...(typeof input.timeoutMs === "number" ? { timeoutMs: input.timeoutMs } : {}),
|
|
135
|
+
});
|
|
99
136
|
const toolCalls = result.tool_calls ?? [];
|
|
100
137
|
if (toolCalls.length === 0) break;
|
|
101
138
|
if (round === maxToolRounds) {
|
|
@@ -122,6 +159,7 @@ export async function runLocalAgentTurn(
|
|
|
122
159
|
id: toolCall.id,
|
|
123
160
|
name: toolName,
|
|
124
161
|
arguments: toolCall.function.arguments,
|
|
162
|
+
...(userInputText ? { userInput: userInputText } : {}),
|
|
125
163
|
});
|
|
126
164
|
emitToolEvent(input, {
|
|
127
165
|
type: "tool-result",
|
|
@@ -140,11 +178,20 @@ export async function runLocalAgentTurn(
|
|
|
140
178
|
message: error instanceof Error ? error.message : String(error),
|
|
141
179
|
});
|
|
142
180
|
toolResult = {
|
|
143
|
-
content:
|
|
181
|
+
content:
|
|
182
|
+
formatStructuredToolExecutionError({ toolName, error }) ??
|
|
183
|
+
formatToolExecutionError({ toolName, error }),
|
|
144
184
|
metadata: {
|
|
145
185
|
error: true,
|
|
146
186
|
toolName,
|
|
147
187
|
message: error instanceof Error ? error.message : String(error),
|
|
188
|
+
...(
|
|
189
|
+
error &&
|
|
190
|
+
typeof error === "object" &&
|
|
191
|
+
typeof (error as { code?: unknown }).code === "string"
|
|
192
|
+
? { code: (error as { code: string }).code }
|
|
193
|
+
: {}
|
|
194
|
+
),
|
|
148
195
|
},
|
|
149
196
|
};
|
|
150
197
|
}
|
|
@@ -2,6 +2,8 @@ import type {
|
|
|
2
2
|
AgentRuntimeToolCallInput,
|
|
3
3
|
AgentRuntimeToolResult,
|
|
4
4
|
} from "./types";
|
|
5
|
+
import { canonicalizeToolName } from "../ai/tools/toolNameAliases";
|
|
6
|
+
import { evaluateShellCommandPolicy } from "./shellCommandPolicy";
|
|
5
7
|
|
|
6
8
|
type EnvLike = Record<string, string | undefined>;
|
|
7
9
|
|
|
@@ -48,6 +50,22 @@ function parseToolAllowlist(value: string | undefined) {
|
|
|
48
50
|
.filter(Boolean);
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
function normalizeLocalToolName(toolName: string) {
|
|
54
|
+
return canonicalizeToolName(
|
|
55
|
+
String(toolName ?? "").replace(/^functions\./, "").trim(),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function parseShellCommandPayload(rawArguments: string) {
|
|
60
|
+
try {
|
|
61
|
+
const parsed = JSON.parse(rawArguments || "{}");
|
|
62
|
+
if (!parsed || typeof parsed !== "object") return {};
|
|
63
|
+
return parsed as { command?: unknown; cmd?: unknown; input?: unknown };
|
|
64
|
+
} catch {
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
51
69
|
function isLocalShellModeEnabled(env: EnvLike) {
|
|
52
70
|
return ["worktree", "dangerous", "1", "true"].includes(env.NOLO_LOCAL_SHELL_MODE || "");
|
|
53
71
|
}
|
|
@@ -72,51 +90,62 @@ export function resolveLocalToolPolicy(args: {
|
|
|
72
90
|
agentToolNames?: string[];
|
|
73
91
|
toolName: string;
|
|
74
92
|
}): LocalToolPolicyDecision {
|
|
93
|
+
const toolName = normalizeLocalToolName(args.toolName);
|
|
75
94
|
const restrictedMode = isRestrictedLocalToolMode(args.env);
|
|
76
|
-
if (
|
|
95
|
+
if (!toolName) {
|
|
96
|
+
return {
|
|
97
|
+
allowed: false,
|
|
98
|
+
toolName,
|
|
99
|
+
reason: "Tool name is required.",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (toolName === "execShell") {
|
|
77
104
|
if (isLocalShellModeEnabled(args.env)) {
|
|
78
|
-
return { allowed: true, toolName
|
|
105
|
+
return { allowed: true, toolName };
|
|
79
106
|
}
|
|
80
107
|
return {
|
|
81
108
|
allowed: false,
|
|
82
|
-
toolName
|
|
109
|
+
toolName,
|
|
83
110
|
reason: "execShell requires NOLO_LOCAL_SHELL_MODE=worktree.",
|
|
84
111
|
};
|
|
85
112
|
}
|
|
86
113
|
|
|
87
|
-
if (NEVER_LOCAL_TOOLS.has(
|
|
114
|
+
if (NEVER_LOCAL_TOOLS.has(toolName)) {
|
|
88
115
|
return {
|
|
89
116
|
allowed: false,
|
|
90
|
-
toolName
|
|
91
|
-
reason: `${
|
|
117
|
+
toolName,
|
|
118
|
+
reason: `${toolName} is blocked by the local runtime safety policy.`,
|
|
92
119
|
};
|
|
93
120
|
}
|
|
94
121
|
|
|
95
|
-
if (REMOVED_LOCAL_TOOLS.has(
|
|
122
|
+
if (REMOVED_LOCAL_TOOLS.has(toolName)) {
|
|
96
123
|
return {
|
|
97
124
|
allowed: false,
|
|
98
|
-
toolName
|
|
99
|
-
reason: `${
|
|
125
|
+
toolName,
|
|
126
|
+
reason: `${toolName} has been removed from the local runtime tool surface. Use execShell in shell-enabled workspace runs when shell is needed.`,
|
|
100
127
|
};
|
|
101
128
|
}
|
|
102
129
|
|
|
103
130
|
const allowedByEnv = new Set(parseToolAllowlist(args.env.NOLO_LOCAL_ALLOWED_TOOLS));
|
|
104
|
-
const agentTools = new Set(
|
|
105
|
-
|
|
106
|
-
|
|
131
|
+
const agentTools = new Set(
|
|
132
|
+
(args.agentToolNames ?? []).map(normalizeLocalToolName).filter(Boolean),
|
|
133
|
+
);
|
|
134
|
+
if (!restrictedMode && agentTools.has(toolName)) {
|
|
135
|
+
return { allowed: true, toolName };
|
|
107
136
|
}
|
|
108
|
-
if (DEFAULT_LOCAL_TOOLS.has(
|
|
109
|
-
return { allowed: true, toolName
|
|
137
|
+
if (DEFAULT_LOCAL_TOOLS.has(toolName) && agentTools.has(toolName)) {
|
|
138
|
+
return { allowed: true, toolName };
|
|
110
139
|
}
|
|
111
|
-
if (allowedByEnv.has(
|
|
112
|
-
return { allowed: true, toolName
|
|
140
|
+
if (allowedByEnv.has(toolName) && agentTools.has(toolName)) {
|
|
141
|
+
return { allowed: true, toolName };
|
|
113
142
|
}
|
|
114
143
|
|
|
115
144
|
return {
|
|
116
145
|
allowed: false,
|
|
117
|
-
toolName
|
|
146
|
+
toolName,
|
|
118
147
|
reason:
|
|
119
|
-
`${
|
|
148
|
+
`${toolName} is not enabled for local runtime runs. ` +
|
|
120
149
|
"Set NOLO_LOCAL_ALLOWED_TOOLS and make sure the agent declares the tool before using it locally.",
|
|
121
150
|
};
|
|
122
151
|
}
|
|
@@ -134,9 +163,29 @@ export async function executeLocalToolWithPolicy(args: {
|
|
|
134
163
|
});
|
|
135
164
|
if (!decision.allowed) throw new Error(decision.reason);
|
|
136
165
|
|
|
137
|
-
const executor = args.executors?.[
|
|
166
|
+
const executor = args.executors?.[decision.toolName];
|
|
138
167
|
if (!executor) {
|
|
139
|
-
throw new Error(`${
|
|
168
|
+
throw new Error(`${decision.toolName} is allowed by policy but no local executor is registered.`);
|
|
140
169
|
}
|
|
141
|
-
|
|
170
|
+
if (decision.toolName === "execShell") {
|
|
171
|
+
const parsed = parseShellCommandPayload(args.call.arguments);
|
|
172
|
+
const shellPolicy = evaluateShellCommandPolicy({
|
|
173
|
+
command: parsed.command ?? parsed.cmd,
|
|
174
|
+
input: parsed.input,
|
|
175
|
+
userInput: args.call.userInput,
|
|
176
|
+
});
|
|
177
|
+
if (shellPolicy.verdict === "forbidden") {
|
|
178
|
+
const error = new Error(shellPolicy.reason) as Error & {
|
|
179
|
+
code?: string;
|
|
180
|
+
policy?: Record<string, unknown>;
|
|
181
|
+
};
|
|
182
|
+
error.code = shellPolicy.code;
|
|
183
|
+
error.policy = shellPolicy.policy;
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return executor({
|
|
188
|
+
...args.call,
|
|
189
|
+
name: decision.toolName,
|
|
190
|
+
});
|
|
142
191
|
}
|
|
@@ -2,11 +2,13 @@ import type {
|
|
|
2
2
|
AgentRuntimeChatMessage,
|
|
3
3
|
AgentRuntimeResult,
|
|
4
4
|
} from "./types";
|
|
5
|
+
import { buildProviderAuthHeaders } from "./providerResolution";
|
|
5
6
|
|
|
6
7
|
export type OpenAiCompatibleProviderConfig = {
|
|
7
8
|
model: string;
|
|
8
9
|
endpoint: string;
|
|
9
10
|
apiKey: string;
|
|
11
|
+
apiKeyHeader?: string;
|
|
10
12
|
provider: string;
|
|
11
13
|
requestOptions: Record<string, number | string>;
|
|
12
14
|
};
|
|
@@ -41,9 +43,11 @@ export function buildOpenAiCompatibleChatCompletionRequest(args: {
|
|
|
41
43
|
method: "POST",
|
|
42
44
|
headers: {
|
|
43
45
|
"Content-Type": "application/json",
|
|
44
|
-
...(
|
|
45
|
-
|
|
46
|
-
:
|
|
46
|
+
...buildProviderAuthHeaders({
|
|
47
|
+
endpoint: args.providerConfig.endpoint,
|
|
48
|
+
apiKey: args.providerConfig.apiKey,
|
|
49
|
+
apiKeyHeader: args.providerConfig.apiKeyHeader,
|
|
50
|
+
}),
|
|
47
51
|
},
|
|
48
52
|
body: JSON.stringify(body),
|
|
49
53
|
},
|
|
@@ -1,38 +1,27 @@
|
|
|
1
1
|
import type { AgentRuntimeAgentConfig } from "./hostAdapter";
|
|
2
2
|
import type { OpenAiCompatibleProviderConfig } from "./openAiCompatibleProvider";
|
|
3
|
-
import {
|
|
3
|
+
import { buildProviderExecutionPlan } from "./providerResolution";
|
|
4
4
|
|
|
5
5
|
type EnvLike = Record<string, string | undefined>;
|
|
6
6
|
|
|
7
|
-
function trimTrailingSlash(value: string) {
|
|
8
|
-
return value.replace(/\/+$/, "");
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function resolveChatCompletionsEndpoint(value: string) {
|
|
12
|
-
const trimmed = trimTrailingSlash(value.trim());
|
|
13
|
-
return trimmed.endsWith("/chat/completions")
|
|
14
|
-
? trimmed
|
|
15
|
-
: `${trimmed}/chat/completions`;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function resolveOpenAiCompatibleBaseUrl(env: EnvLike) {
|
|
19
|
-
return env.NOLO_LOCAL_OPENAI_BASE_URL || env.OPENAI_BASE_URL || "https://api.openai.com/v1";
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function resolveOpenAiCompatibleApiKey(env: EnvLike) {
|
|
23
|
-
return env.OPENAI_API_KEY || env.NOLO_LOCAL_OPENAI_API_KEY || "";
|
|
24
|
-
}
|
|
25
|
-
|
|
26
7
|
export function resolveOpenAiCompatibleProviderConfig(args: {
|
|
27
8
|
agentConfig: AgentRuntimeAgentConfig;
|
|
28
9
|
env: EnvLike;
|
|
29
10
|
}): OpenAiCompatibleProviderConfig {
|
|
30
|
-
const
|
|
11
|
+
const plan = buildProviderExecutionPlan({
|
|
12
|
+
agentConfig: args.agentConfig,
|
|
13
|
+
env: args.env,
|
|
14
|
+
runtimeKind: "local",
|
|
15
|
+
});
|
|
16
|
+
if (plan.mode === "cli" || plan.transport !== "direct") {
|
|
17
|
+
throw new Error("OpenAI-compatible provider config requires a direct provider execution plan.");
|
|
18
|
+
}
|
|
31
19
|
return {
|
|
32
|
-
model:
|
|
33
|
-
endpoint:
|
|
34
|
-
apiKey:
|
|
35
|
-
|
|
36
|
-
|
|
20
|
+
model: plan.model,
|
|
21
|
+
endpoint: plan.endpoint,
|
|
22
|
+
apiKey: plan.apiKey,
|
|
23
|
+
...(plan.apiKeyHeader ? { apiKeyHeader: plan.apiKeyHeader } : {}),
|
|
24
|
+
provider: plan.provider,
|
|
25
|
+
requestOptions: plan.requestOptions,
|
|
37
26
|
};
|
|
38
27
|
}
|
|
@@ -3,23 +3,19 @@ import type {
|
|
|
3
3
|
AgentRuntimeChatMessage,
|
|
4
4
|
AgentRuntimeResult,
|
|
5
5
|
} from "./types";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
buildProviderExecutionPlan,
|
|
8
|
+
canUsePlatformChatProvider as canUsePlatformChatProviderFromEnv,
|
|
9
|
+
hasDirectOpenAiCompatibleProvider as hasDirectOpenAiCompatibleProviderFromEnv,
|
|
10
|
+
resolvePlatformAuthToken,
|
|
11
|
+
resolvePlatformServerUrl,
|
|
12
|
+
shouldUsePlatformProxyTransport,
|
|
13
|
+
} from "./providerResolution";
|
|
7
14
|
|
|
8
15
|
type EnvLike = Record<string, string | undefined>;
|
|
9
16
|
|
|
10
17
|
const CHAT_PROXY_PATH = "/api/v1/chat";
|
|
11
18
|
|
|
12
|
-
const PROVIDER_ENDPOINTS: Record<string, string> = {
|
|
13
|
-
deepinfra: "https://api.deepinfra.com/v1/openai/chat/completions",
|
|
14
|
-
deepseek: "https://api.deepseek.com/chat/completions",
|
|
15
|
-
fireworks: "https://api.fireworks.ai/inference/v1/chat/completions",
|
|
16
|
-
google: "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions",
|
|
17
|
-
mistral: "https://api.mistral.ai/v1/chat/completions",
|
|
18
|
-
mimo: "https://token-plan-cn.xiaomimimo.com/v1/chat/completions",
|
|
19
|
-
openai: "https://api.openai.com/v1/chat/completions",
|
|
20
|
-
openrouter: "https://openrouter.ai/api/v1/chat/completions",
|
|
21
|
-
};
|
|
22
|
-
|
|
23
19
|
export type PlatformChatProviderConfig = {
|
|
24
20
|
serverUrl: string;
|
|
25
21
|
authToken: string;
|
|
@@ -29,46 +25,12 @@ export type PlatformChatProviderConfig = {
|
|
|
29
25
|
endpoint: string;
|
|
30
26
|
requestOptions: Record<string, number | string>;
|
|
31
27
|
apiKey?: string;
|
|
28
|
+
apiKeyHeader?: string;
|
|
32
29
|
apiSource?: string;
|
|
33
30
|
};
|
|
34
31
|
|
|
35
32
|
type PlatformChatTool = Record<string, unknown>;
|
|
36
33
|
|
|
37
|
-
function trimTrailingSlash(value: string) {
|
|
38
|
-
return value.trim().replace(/\/+$/, "");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function resolveChatCompletionsEndpoint(value: string) {
|
|
42
|
-
const trimmed = trimTrailingSlash(value);
|
|
43
|
-
return trimmed.endsWith("/chat/completions")
|
|
44
|
-
? trimmed
|
|
45
|
-
: `${trimmed}/chat/completions`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function resolvePlatformServerUrl(env: EnvLike) {
|
|
49
|
-
return trimTrailingSlash(env.NOLO_SERVER || env.BASE_URL || "https://nolo.chat");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function resolvePlatformAuthToken(env: EnvLike) {
|
|
53
|
-
return env.AUTH_TOKEN || env.AUTH || env.BENCHMARK_AUTH_TOKEN || "";
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function resolveProviderEndpoint(agentConfig: AgentRuntimeAgentConfig) {
|
|
57
|
-
const customProviderUrl = agentConfig.customProviderUrl?.trim();
|
|
58
|
-
if (customProviderUrl) return resolveChatCompletionsEndpoint(customProviderUrl);
|
|
59
|
-
|
|
60
|
-
const provider = agentConfig.provider?.trim().toLowerCase();
|
|
61
|
-
if (!provider) {
|
|
62
|
-
throw new Error("Platform chat provider requires agentConfig.provider.");
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const endpoint = PROVIDER_ENDPOINTS[provider];
|
|
66
|
-
if (!endpoint) {
|
|
67
|
-
throw new Error(`Platform chat provider does not support provider "${provider}".`);
|
|
68
|
-
}
|
|
69
|
-
return endpoint;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
34
|
function shouldDisableThinking(providerConfig: PlatformChatProviderConfig) {
|
|
73
35
|
return (
|
|
74
36
|
providerConfig.provider.toLowerCase() === "mimo" ||
|
|
@@ -90,19 +52,39 @@ export function resolvePlatformChatProviderConfig(args: {
|
|
|
90
52
|
agentConfig: AgentRuntimeAgentConfig;
|
|
91
53
|
env: EnvLike;
|
|
92
54
|
}): PlatformChatProviderConfig {
|
|
93
|
-
const
|
|
55
|
+
const plan = buildProviderExecutionPlan({
|
|
56
|
+
agentConfig: args.agentConfig,
|
|
57
|
+
env: args.env,
|
|
58
|
+
runtimeKind: "local",
|
|
59
|
+
});
|
|
60
|
+
if (plan.mode === "cli") {
|
|
61
|
+
throw new Error("Platform chat provider does not support cli agents.");
|
|
62
|
+
}
|
|
63
|
+
if (plan.transport === "direct") {
|
|
64
|
+
return {
|
|
65
|
+
serverUrl: resolvePlatformServerUrl(args.env),
|
|
66
|
+
authToken: resolvePlatformAuthToken(args.env),
|
|
67
|
+
agentKey: args.agentConfig.key,
|
|
68
|
+
model: plan.model,
|
|
69
|
+
provider: plan.provider,
|
|
70
|
+
endpoint: plan.endpoint,
|
|
71
|
+
requestOptions: plan.requestOptions,
|
|
72
|
+
...(plan.apiKey ? { apiKey: plan.apiKey } : {}),
|
|
73
|
+
...(plan.apiKeyHeader ? { apiKeyHeader: plan.apiKeyHeader } : {}),
|
|
74
|
+
...(args.agentConfig.apiSource ? { apiSource: args.agentConfig.apiSource } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
94
77
|
return {
|
|
95
|
-
serverUrl:
|
|
96
|
-
authToken:
|
|
97
|
-
agentKey:
|
|
98
|
-
model:
|
|
99
|
-
provider,
|
|
100
|
-
endpoint:
|
|
101
|
-
requestOptions:
|
|
102
|
-
...(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
...(args.agentConfig.apiSource ? { apiSource: args.agentConfig.apiSource } : {}),
|
|
78
|
+
serverUrl: plan.serverUrl,
|
|
79
|
+
authToken: plan.authToken,
|
|
80
|
+
agentKey: plan.agentKey,
|
|
81
|
+
model: plan.model,
|
|
82
|
+
provider: plan.provider,
|
|
83
|
+
endpoint: plan.endpoint,
|
|
84
|
+
requestOptions: plan.requestOptions,
|
|
85
|
+
...(plan.apiKey ? { apiKey: plan.apiKey } : {}),
|
|
86
|
+
...(plan.apiKeyHeader ? { apiKeyHeader: plan.apiKeyHeader } : {}),
|
|
87
|
+
...(plan.apiSource ? { apiSource: plan.apiSource } : {}),
|
|
106
88
|
};
|
|
107
89
|
}
|
|
108
90
|
|
|
@@ -123,6 +105,7 @@ export function buildPlatformChatCompletionRequest(args: {
|
|
|
123
105
|
agentKey: args.providerConfig.agentKey,
|
|
124
106
|
...(args.providerConfig.apiSource ? { apiSource: args.providerConfig.apiSource } : {}),
|
|
125
107
|
...(args.providerConfig.apiKey ? { KEY: args.providerConfig.apiKey } : {}),
|
|
108
|
+
...(args.providerConfig.apiKeyHeader ? { apiKeyHeader: args.providerConfig.apiKeyHeader } : {}),
|
|
126
109
|
};
|
|
127
110
|
|
|
128
111
|
return {
|
|
@@ -216,21 +199,17 @@ export function parsePlatformChatCompletionResponse(args: {
|
|
|
216
199
|
}
|
|
217
200
|
|
|
218
201
|
export function canUsePlatformChatProvider(env: EnvLike) {
|
|
219
|
-
return
|
|
202
|
+
return canUsePlatformChatProviderFromEnv(env);
|
|
220
203
|
}
|
|
221
204
|
|
|
222
205
|
export function hasDirectOpenAiCompatibleProvider(env: EnvLike) {
|
|
223
|
-
return
|
|
224
|
-
env.NOLO_LOCAL_OPENAI_API_KEY ||
|
|
225
|
-
env.OPENAI_API_KEY ||
|
|
226
|
-
env.NOLO_LOCAL_OPENAI_BASE_URL ||
|
|
227
|
-
env.OPENAI_BASE_URL ||
|
|
228
|
-
env.OLLAMA_BASE_URL
|
|
229
|
-
);
|
|
206
|
+
return hasDirectOpenAiCompatibleProviderFromEnv(env);
|
|
230
207
|
}
|
|
231
208
|
|
|
232
209
|
function agentRequiresPlatformChatProvider(agentConfig?: AgentRuntimeAgentConfig) {
|
|
233
|
-
|
|
210
|
+
if (agentConfig?.apiSource === "platform") return true;
|
|
211
|
+
if (agentConfig?.apiSource === "custom" || agentConfig?.customProviderUrl?.trim()) return false;
|
|
212
|
+
return agentConfig?.useServerProxy === true;
|
|
234
213
|
}
|
|
235
214
|
|
|
236
215
|
export function shouldUsePlatformChatProvider(
|
|
@@ -238,7 +217,5 @@ export function shouldUsePlatformChatProvider(
|
|
|
238
217
|
agentConfig?: AgentRuntimeAgentConfig
|
|
239
218
|
) {
|
|
240
219
|
if (agentRequiresPlatformChatProvider(agentConfig)) return true;
|
|
241
|
-
|
|
242
|
-
if (env.NOLO_LOCAL_LLM === "direct") return false;
|
|
243
|
-
return !hasDirectOpenAiCompatibleProvider(env) && canUsePlatformChatProvider(env);
|
|
220
|
+
return shouldUsePlatformProxyTransport(env, agentConfig);
|
|
244
221
|
}
|