nolo-cli 0.1.18 → 0.1.20
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 +9 -1
- package/agent-runtime/agentConfigOptions.ts +12 -0
- package/agent-runtime/agentRecordConfig.ts +99 -0
- package/agent-runtime/agentRecordKeys.ts +14 -0
- package/agent-runtime/dialogMessageRecord.ts +16 -0
- package/agent-runtime/dialogWritePlan.ts +130 -0
- package/agent-runtime/hostAdapter.ts +14 -0
- package/agent-runtime/hybridRecordStore.ts +147 -0
- package/agent-runtime/index.ts +69 -0
- package/agent-runtime/localLoop.ts +78 -6
- package/agent-runtime/localToolPolicy.ts +130 -0
- package/agent-runtime/localWorkspaceTools.ts +1532 -0
- package/agent-runtime/openAiCompatibleProvider.ts +70 -0
- package/agent-runtime/openAiCompatibleProviderConfig.ts +38 -0
- package/agent-runtime/platformChatProvider.ts +241 -0
- package/agent-runtime/taskWorkspace.ts +193 -0
- package/agent-runtime/types.ts +2 -0
- package/agent-runtime/workspaceSession.ts +76 -0
- package/agentAliases.ts +37 -0
- package/agentPullCommand.ts +1 -1
- package/agentRunCommand.ts +289 -54
- package/agentRuntimeCommands.ts +354 -164
- package/agentRuntimeLocal.ts +38 -0
- package/ai/agent/agentSlice.ts +10 -0
- package/ai/agent/buildEditingContext.ts +5 -0
- package/ai/agent/buildSystemPrompt.ts +41 -18
- package/ai/agent/canvasEditingContext.ts +49 -0
- package/ai/agent/cliExecutor.ts +15 -4
- package/ai/agent/createAgentSchema.ts +2 -0
- package/ai/agent/executeToolCall.ts +3 -2
- package/ai/agent/hooks/usePublicAgents.ts +6 -0
- package/ai/agent/pageBuilderHandoffRules.ts +75 -0
- package/ai/agent/runAgentClientLoop.ts +4 -1
- package/ai/agent/runtimeGuidance.ts +19 -0
- package/ai/agent/server/fetchPublicAgents.ts +51 -1
- package/ai/agent/streamAgentChatTurn.ts +20 -2
- package/ai/agent/streamAgentChatTurnUtils.ts +60 -16
- package/ai/chat/accumulateToolCallChunks.ts +40 -9
- package/ai/chat/parseApiError.ts +3 -0
- package/ai/chat/sendOpenAICompletionsRequest.native.ts +23 -10
- package/ai/chat/sendOpenAICompletionsRequest.ts +13 -1
- package/ai/chat/updateTotalUsage.ts +26 -9
- package/ai/llm/deepinfra.ts +51 -0
- package/ai/llm/getPricing.ts +6 -0
- package/ai/llm/kimi.ts +2 -0
- package/ai/llm/openrouterModels.ts +0 -135
- package/ai/llm/providers.ts +1 -0
- package/ai/llm/types.ts +8 -0
- package/ai/taskRun/taskRunProtocol.ts +823 -0
- package/ai/token/calculatePrice.ts +30 -0
- package/ai/token/externalToolCost.ts +49 -29
- package/ai/token/prepareTokenUsageData.ts +6 -1
- package/ai/token/serverTokenWriter.ts +4 -2
- package/ai/tools/agent/agentTools.ts +21 -0
- package/ai/tools/agent/presets/appBuilderPreset.ts +7 -0
- package/ai/tools/agent/streamParallelAgentsTool.ts +2 -1
- package/ai/tools/agent/taskRunTool.ts +112 -0
- package/ai/tools/applyEditTool.ts +6 -3
- package/ai/tools/applyLineEditsTool.ts +6 -3
- package/ai/tools/checkEnvTool.ts +14 -9
- package/ai/tools/codeSearchTool.ts +17 -5
- package/ai/tools/execBashTool.ts +33 -29
- package/ai/tools/fetchWebpageSupport.ts +24 -0
- package/ai/tools/fetchWebpageTool.ts +18 -5
- package/ai/tools/index.ts +158 -0
- package/ai/tools/jdProductScraperTool.ts +821 -0
- package/ai/tools/listFilesTool.ts +6 -3
- package/ai/tools/localFilesTool.ts +200 -0
- package/ai/tools/readFileTool.ts +6 -3
- package/ai/tools/searchRepoTool.ts +6 -3
- package/ai/tools/table/rowTools.ts +6 -1
- package/ai/tools/taobaoTmallProductScraperTool.ts +49 -0
- package/ai/tools/toolApiClient.ts +20 -6
- package/ai/tools/wereadGatewayTool.ts +152 -0
- package/ai/tools/writeFileTool.ts +6 -3
- package/client/agentConfigResolver.test.ts +70 -0
- package/client/agentConfigResolver.ts +1 -0
- package/client/agentRun.test.ts +361 -7
- package/client/agentRun.ts +449 -63
- package/client/hybridRecordStore.test.ts +115 -0
- package/client/hybridRecordStore.ts +41 -0
- package/client/localAgentRecords.test.ts +27 -0
- package/client/localAgentRecords.ts +7 -0
- package/client/localDialogRecords.test.ts +124 -0
- package/client/localDialogRecords.ts +30 -0
- package/client/localProviderResolver.test.ts +78 -0
- package/client/localProviderResolver.ts +1 -0
- package/client/localRuntimeAdapter.test.ts +813 -20
- package/client/localRuntimeAdapter.ts +279 -232
- package/client/localRuntimeDryRun.test.ts +116 -0
- package/client/localToolPolicy.ts +8 -81
- package/client/taskRunPrompt.ts +26 -0
- package/client/taskWorktree.ts +8 -0
- package/client/workspaceSession.test.ts +57 -0
- package/client/workspaceSession.ts +11 -0
- package/commandRegistry.ts +23 -6
- package/connectorRunArtifact.ts +121 -0
- package/database/actions/write.ts +16 -2
- package/database/hooks/useUserData.ts +9 -3
- package/database/server/dataHandlers.ts +18 -20
- package/database/server/emailRepository.ts +3 -3
- package/database/server/patch.ts +18 -10
- package/database/server/query.ts +43 -4
- package/database/server/read.ts +24 -38
- package/database/server/recordIdentity.ts +100 -0
- package/database/server/write.ts +21 -25
- package/index.ts +70 -33
- package/machineCommands.ts +318 -144
- package/package.json +4 -1
- package/tableCommands.ts +181 -0
- package/taskRunCommand.ts +237 -0
|
@@ -3,27 +3,65 @@ import type {
|
|
|
3
3
|
AgentRuntimeHostAdapter,
|
|
4
4
|
AgentRuntimeSaveTurnInput,
|
|
5
5
|
} from "../agentRuntimeLocal";
|
|
6
|
-
import
|
|
6
|
+
import {
|
|
7
|
+
buildLocalWorkspacePolicyToolNames,
|
|
8
|
+
buildLocalWorkspaceToolset,
|
|
9
|
+
buildLocalWorkspaceOpenAiTools,
|
|
10
|
+
buildOpenAiCompatibleChatCompletionRequest,
|
|
11
|
+
buildPlatformChatCompletionRequest,
|
|
12
|
+
createLocalWorkspaceToolExecutors,
|
|
13
|
+
parseOpenAiCompatibleChatCompletionResponse,
|
|
14
|
+
parsePlatformChatCompletionData,
|
|
15
|
+
parsePlatformChatCompletionResponse,
|
|
16
|
+
resolvePlatformChatProviderConfig,
|
|
17
|
+
shouldUsePlatformChatProvider,
|
|
18
|
+
} from "../agentRuntimeLocal";
|
|
19
|
+
import type { AgentRuntimeChatMessage } from "../agent-runtime";
|
|
7
20
|
import { getDefaultCliLocalRuntimeDb } from "../localRuntimeDb";
|
|
21
|
+
import { resolveAgentRuntimeConfigFromRecord } from "./agentConfigResolver";
|
|
22
|
+
import { resolveCliOpenAiProviderConfig } from "./localProviderResolver";
|
|
23
|
+
import {
|
|
24
|
+
buildLocalDialogWritePlan,
|
|
25
|
+
localDialogMessageRecordToRuntimeMessage,
|
|
26
|
+
} from "./localDialogRecords";
|
|
27
|
+
import {
|
|
28
|
+
buildLocalAgentLookupKeys,
|
|
29
|
+
shouldReadAgentKeyRemotely,
|
|
30
|
+
} from "./localAgentRecords";
|
|
31
|
+
import {
|
|
32
|
+
createCliHybridRecordStore,
|
|
33
|
+
type CliKvDb,
|
|
34
|
+
type HybridRecordStore,
|
|
35
|
+
} from "./hybridRecordStore";
|
|
8
36
|
import { executeLocalToolWithPolicy } from "./localToolPolicy";
|
|
37
|
+
import { prepareTaskWorktree } from "./taskWorktree";
|
|
38
|
+
import {
|
|
39
|
+
activateWorkspaceSession,
|
|
40
|
+
createWorkspaceSession,
|
|
41
|
+
formatWorkspaceSessionActivation,
|
|
42
|
+
type WorkspaceSession,
|
|
43
|
+
} from "./workspaceSession";
|
|
9
44
|
|
|
10
45
|
type EnvLike = Record<string, string | undefined>;
|
|
46
|
+
type FetchInput = Parameters<typeof fetch>[0];
|
|
47
|
+
type FetchInit = Parameters<typeof fetch>[1];
|
|
48
|
+
const TRANSIENT_FETCH_MAX_ATTEMPTS = 8;
|
|
49
|
+
const TRANSIENT_FETCH_RETRY_BASE_DELAY_MS = 250;
|
|
11
50
|
|
|
12
|
-
export type CliLocalRuntimeDb =
|
|
13
|
-
get(key: string): Promise<any>;
|
|
14
|
-
put(key: string, value: any): Promise<unknown>;
|
|
15
|
-
batch(ops: Array<{ type: "put"; key: string; value: any }>): Promise<unknown>;
|
|
16
|
-
iterator(options: { gte: string; lte?: string; lt?: string; reverse?: boolean; limit?: number }): AsyncIterable<[string, any]>;
|
|
17
|
-
};
|
|
51
|
+
export type CliLocalRuntimeDb = CliKvDb;
|
|
18
52
|
|
|
19
53
|
export type CliLocalRuntimeAdapterDeps = {
|
|
20
54
|
env: EnvLike;
|
|
21
55
|
db?: CliLocalRuntimeDb;
|
|
56
|
+
store?: HybridRecordStore;
|
|
22
57
|
now?: () => number;
|
|
23
58
|
createId?: () => string;
|
|
24
59
|
fetchImpl?: typeof fetch;
|
|
25
60
|
cwd?: string;
|
|
61
|
+
output?: { write(chunk: string): unknown };
|
|
62
|
+
prepareTaskWorktree?: typeof prepareTaskWorktree;
|
|
26
63
|
localToolExecutors?: Record<string, (call: any) => Promise<{ content: string; metadata?: Record<string, unknown> }>>;
|
|
64
|
+
sleep?: (ms: number) => Promise<void>;
|
|
27
65
|
};
|
|
28
66
|
|
|
29
67
|
async function defaultLocalRuntimeDb(): Promise<CliLocalRuntimeDb> {
|
|
@@ -34,231 +72,176 @@ function createFallbackId() {
|
|
|
34
72
|
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`.toUpperCase();
|
|
35
73
|
}
|
|
36
74
|
|
|
37
|
-
function
|
|
38
|
-
return
|
|
39
|
-
.replace(/\/+$/, "");
|
|
75
|
+
function resolveLocalUserId(env: EnvLike) {
|
|
76
|
+
return env.NOLO_LOCAL_USER_ID || env.NOLO_USER_ID || "local";
|
|
40
77
|
}
|
|
41
78
|
|
|
42
|
-
function
|
|
43
|
-
return
|
|
79
|
+
function shellModeEnabled(env: EnvLike) {
|
|
80
|
+
return ["worktree", "dangerous", "1", "true"].includes(env.NOLO_LOCAL_SHELL_MODE || "");
|
|
44
81
|
}
|
|
45
82
|
|
|
46
|
-
function
|
|
47
|
-
|
|
83
|
+
function parseLocalToolBudgets(env: EnvLike) {
|
|
84
|
+
const raw = env.NOLO_LOCAL_TOOL_BUDGETS?.trim();
|
|
85
|
+
if (!raw) return {};
|
|
86
|
+
const budgets: Record<string, number> = {};
|
|
87
|
+
for (const part of raw.split(",")) {
|
|
88
|
+
const [name, value] = part.split("=").map((item) => item.trim());
|
|
89
|
+
const limit = Number(value);
|
|
90
|
+
if (name && Number.isFinite(limit) && limit >= 0) budgets[name] = Math.floor(limit);
|
|
91
|
+
}
|
|
92
|
+
return budgets;
|
|
48
93
|
}
|
|
49
94
|
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
},
|
|
64
|
-
required: ["cmd"],
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
};
|
|
95
|
+
function assertWithinLocalToolBudget(args: {
|
|
96
|
+
toolName: string;
|
|
97
|
+
budgets: Record<string, number>;
|
|
98
|
+
usage: Map<string, number>;
|
|
99
|
+
}) {
|
|
100
|
+
const limit = args.budgets[args.toolName];
|
|
101
|
+
if (typeof limit !== "number") return;
|
|
102
|
+
const nextCount = (args.usage.get(args.toolName) ?? 0) + 1;
|
|
103
|
+
args.usage.set(args.toolName, nextCount);
|
|
104
|
+
if (nextCount <= limit) return;
|
|
105
|
+
throw new Error(
|
|
106
|
+
`${args.toolName} exceeded local tool budget ${limit}. Stop broad discovery; edit the narrowest likely file or report a blocker.`
|
|
107
|
+
);
|
|
68
108
|
}
|
|
69
109
|
|
|
70
|
-
function
|
|
71
|
-
|
|
110
|
+
function isTransientFetchError(error: unknown) {
|
|
111
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
112
|
+
return /certificate|handshake|network|socket|timed out|timeout|ECONNRESET/i.test(message);
|
|
72
113
|
}
|
|
73
114
|
|
|
74
|
-
function
|
|
75
|
-
|
|
76
|
-
? [buildExecShellTool()]
|
|
77
|
-
: [];
|
|
115
|
+
async function defaultSleep(ms: number) {
|
|
116
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
78
117
|
}
|
|
79
118
|
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
119
|
+
function transientFetchRetryDelayMs(attempt: number) {
|
|
120
|
+
return Math.min(attempt * TRANSIENT_FETCH_RETRY_BASE_DELAY_MS, 2_000);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function fetchWithTransientRetry(
|
|
124
|
+
fetchImpl: typeof fetch,
|
|
125
|
+
input: FetchInput,
|
|
126
|
+
init?: FetchInit,
|
|
127
|
+
options: { sleep?: (ms: number) => Promise<void> } = {}
|
|
128
|
+
) {
|
|
129
|
+
let lastError: unknown;
|
|
130
|
+
for (let attempt = 1; attempt <= TRANSIENT_FETCH_MAX_ATTEMPTS; attempt += 1) {
|
|
131
|
+
try {
|
|
132
|
+
return await fetchImpl(input, init);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (!isTransientFetchError(error)) throw error;
|
|
135
|
+
lastError = error;
|
|
136
|
+
if (attempt < TRANSIENT_FETCH_MAX_ATTEMPTS) {
|
|
137
|
+
await (options.sleep ?? defaultSleep)(transientFetchRetryDelayMs(attempt));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
86
140
|
}
|
|
141
|
+
throw lastError;
|
|
87
142
|
}
|
|
88
143
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
144
|
+
function buildOpenAiTools(args: { toolNames?: string[]; env: EnvLike }) {
|
|
145
|
+
const toolset = buildLocalWorkspaceToolsetForEnv(args);
|
|
146
|
+
return buildLocalWorkspaceOpenAiTools({
|
|
147
|
+
toolNames: toolset.toolNames,
|
|
148
|
+
exposeShellTools: toolset.exposeShellTools,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function buildLocalWorkspaceToolsetForEnv(args: { toolNames?: string[]; env: EnvLike }) {
|
|
153
|
+
const toolset = buildLocalWorkspaceToolset({
|
|
154
|
+
declaredToolNames: args.toolNames,
|
|
155
|
+
exposeShellTools: shellModeEnabled(args.env),
|
|
156
|
+
});
|
|
157
|
+
return toolset;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function buildLocalPolicyToolNames(args: { toolNames?: string[]; env: EnvLike }) {
|
|
161
|
+
return buildLocalWorkspacePolicyToolNames({
|
|
162
|
+
declaredToolNames: args.toolNames,
|
|
163
|
+
exposeShellTools: shellModeEnabled(args.env),
|
|
101
164
|
});
|
|
102
|
-
const [stdout, stderr, exitCode] = await Promise.all([
|
|
103
|
-
new Response(proc.stdout).text(),
|
|
104
|
-
new Response(proc.stderr).text(),
|
|
105
|
-
proc.exited,
|
|
106
|
-
]);
|
|
107
|
-
return {
|
|
108
|
-
content: [
|
|
109
|
-
stdout.trim() ? `stdout:\n${stdout.trim()}` : "",
|
|
110
|
-
stderr.trim() ? `stderr:\n${stderr.trim()}` : "",
|
|
111
|
-
`exitCode: ${exitCode}`,
|
|
112
|
-
].filter(Boolean).join("\n\n"),
|
|
113
|
-
metadata: { exitCode },
|
|
114
|
-
};
|
|
115
165
|
}
|
|
116
166
|
|
|
117
|
-
function buildLocalToolExecutors(
|
|
167
|
+
function buildLocalToolExecutors(args: {
|
|
168
|
+
workspaceRoot: string;
|
|
169
|
+
localToolExecutors?: CliLocalRuntimeAdapterDeps["localToolExecutors"];
|
|
170
|
+
}) {
|
|
118
171
|
return {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
cwd: deps.cwd ?? process.cwd(),
|
|
122
|
-
}),
|
|
123
|
-
...(deps.localToolExecutors ?? {}),
|
|
172
|
+
...createLocalWorkspaceToolExecutors({ workspaceRoot: args.workspaceRoot }),
|
|
173
|
+
...(args.localToolExecutors ?? {}),
|
|
124
174
|
};
|
|
125
175
|
}
|
|
126
176
|
|
|
127
|
-
|
|
128
|
-
|
|
177
|
+
function readLocalWorkspaceMode(agentConfig: AgentRuntimeAgentConfig | null) {
|
|
178
|
+
if (agentConfig?.localWorkspaceMode === "task-worktree") return "task-worktree";
|
|
179
|
+
const runtimeBinding = agentConfig?.runtimeBinding;
|
|
180
|
+
const value = runtimeBinding?.localWorkspaceMode ?? runtimeBinding?.workspaceMode;
|
|
181
|
+
return value === "task-worktree" ? "task-worktree" : "current";
|
|
129
182
|
}
|
|
130
183
|
|
|
131
|
-
async function
|
|
132
|
-
|
|
184
|
+
async function resolveStore(deps: CliLocalRuntimeAdapterDeps) {
|
|
185
|
+
if (deps.store) return deps.store;
|
|
186
|
+
return createCliHybridRecordStore({
|
|
187
|
+
db: deps.db ?? await defaultLocalRuntimeDb(),
|
|
188
|
+
env: deps.env,
|
|
189
|
+
fetchImpl: deps.fetchImpl,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function readAgentFromStore(args: {
|
|
194
|
+
store: HybridRecordStore;
|
|
133
195
|
agentRef: string;
|
|
134
196
|
userId: string;
|
|
135
197
|
}): Promise<AgentRuntimeAgentConfig | null> {
|
|
136
|
-
const
|
|
137
|
-
args.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
try {
|
|
143
|
-
const record = await args.db.get(key);
|
|
144
|
-
if (!record || typeof record !== "object") continue;
|
|
145
|
-
return {
|
|
146
|
-
key,
|
|
147
|
-
...(typeof record.name === "string" ? { name: record.name } : {}),
|
|
148
|
-
...(typeof record.prompt === "string" ? { prompt: record.prompt } : {}),
|
|
149
|
-
...(typeof record.model === "string" ? { model: record.model } : {}),
|
|
150
|
-
...(typeof record.provider === "string" ? { provider: record.provider } : {}),
|
|
151
|
-
...(Array.isArray(record.toolNames) ? { toolNames: record.toolNames } : {}),
|
|
152
|
-
};
|
|
153
|
-
} catch {
|
|
154
|
-
// Try the next local LevelDB key convention.
|
|
155
|
-
}
|
|
198
|
+
for (const key of buildLocalAgentLookupKeys(args)) {
|
|
199
|
+
const record = await args.store.read(key, {
|
|
200
|
+
remote: shouldReadAgentKeyRemotely(key),
|
|
201
|
+
});
|
|
202
|
+
if (!record || typeof record !== "object") continue;
|
|
203
|
+
return resolveAgentRuntimeConfigFromRecord(key, record);
|
|
156
204
|
}
|
|
157
205
|
return null;
|
|
158
206
|
}
|
|
159
207
|
|
|
160
|
-
function toOpenAiMessages(messages: AgentRuntimeChatMessage[]) {
|
|
161
|
-
return messages.map((message) => ({
|
|
162
|
-
role: message.role,
|
|
163
|
-
content: message.content ?? "",
|
|
164
|
-
}));
|
|
165
|
-
}
|
|
166
|
-
|
|
167
208
|
async function readDialogMessages(args: {
|
|
168
|
-
|
|
209
|
+
store: HybridRecordStore;
|
|
169
210
|
dialogId: string;
|
|
170
211
|
}) {
|
|
171
212
|
const messages: AgentRuntimeChatMessage[] = [];
|
|
172
213
|
const prefix = `dialog-${args.dialogId}-msg-`;
|
|
173
|
-
const iterator = args.
|
|
214
|
+
const iterator = args.store.iterator({ gte: prefix, lte: `${prefix}\uffff` });
|
|
174
215
|
for await (const [, value] of iterator) {
|
|
175
|
-
|
|
176
|
-
if (
|
|
177
|
-
if (value.role === "user" || value.role === "assistant" || value.role === "tool") {
|
|
178
|
-
messages.push({
|
|
179
|
-
role: value.role,
|
|
180
|
-
content: value.content ?? null,
|
|
181
|
-
...(typeof value.toolCallId === "string" ? { tool_call_id: value.toolCallId } : {}),
|
|
182
|
-
...(Array.isArray(value.tool_calls) ? { tool_calls: value.tool_calls } : {}),
|
|
183
|
-
});
|
|
184
|
-
}
|
|
216
|
+
const message = localDialogMessageRecordToRuntimeMessage(value);
|
|
217
|
+
if (message) messages.push(message);
|
|
185
218
|
}
|
|
186
219
|
return messages;
|
|
187
220
|
}
|
|
188
221
|
|
|
189
222
|
async function writeDialog(args: {
|
|
190
|
-
|
|
223
|
+
store: HybridRecordStore;
|
|
191
224
|
input: AgentRuntimeSaveTurnInput;
|
|
192
225
|
userId: string;
|
|
193
226
|
now: () => number;
|
|
194
227
|
createId: () => string;
|
|
228
|
+
cwd?: string;
|
|
195
229
|
}) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
:
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
type: "put",
|
|
212
|
-
key: dialogKey,
|
|
213
|
-
value: {
|
|
214
|
-
id: dialogId,
|
|
215
|
-
dbKey: dialogKey,
|
|
216
|
-
type: "dialog",
|
|
217
|
-
userId: args.userId,
|
|
218
|
-
cybots: [args.input.agentKey],
|
|
219
|
-
primaryAgentKey: args.input.agentKey,
|
|
220
|
-
title: lastUserText.trim()
|
|
221
|
-
? lastUserText.trim().slice(0, 80)
|
|
222
|
-
: "Local agent run",
|
|
223
|
-
status: "done",
|
|
224
|
-
triggerType: "cli-local",
|
|
225
|
-
executionMode: "foreground",
|
|
226
|
-
createdAt: nowIso,
|
|
227
|
-
updatedAt: nowIso,
|
|
228
|
-
finishedAt: now,
|
|
229
|
-
usage: args.input.result.usage,
|
|
230
|
-
},
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
type: "put",
|
|
234
|
-
key: `dialog-${dialogId}-msg-user`,
|
|
235
|
-
value: {
|
|
236
|
-
id: "msg-user",
|
|
237
|
-
dbKey: `dialog-${dialogId}-msg-user`,
|
|
238
|
-
dialogId,
|
|
239
|
-
role: "user",
|
|
240
|
-
content: lastUser?.content ?? "",
|
|
241
|
-
userId: args.userId,
|
|
242
|
-
createdAt: nowIso,
|
|
243
|
-
},
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
type: "put",
|
|
247
|
-
key: `dialog-${dialogId}-msg-assistant`,
|
|
248
|
-
value: {
|
|
249
|
-
id: "msg-assistant",
|
|
250
|
-
dbKey: `dialog-${dialogId}-msg-assistant`,
|
|
251
|
-
dialogId,
|
|
252
|
-
role: "assistant",
|
|
253
|
-
content: args.input.result.content,
|
|
254
|
-
agentKey: args.input.agentKey,
|
|
255
|
-
cybotKey: args.input.agentKey,
|
|
256
|
-
createdAt: nowIso,
|
|
257
|
-
},
|
|
258
|
-
},
|
|
259
|
-
];
|
|
260
|
-
await args.db.batch(ops);
|
|
261
|
-
return { dialogId };
|
|
230
|
+
let existingDialog: any = null;
|
|
231
|
+
if (args.input.continueDialogId) {
|
|
232
|
+
const dialogKey = `dialog-${args.userId}-${args.input.continueDialogId}`;
|
|
233
|
+
existingDialog = await args.store.read(dialogKey);
|
|
234
|
+
}
|
|
235
|
+
const plan = buildLocalDialogWritePlan({
|
|
236
|
+
input: args.input,
|
|
237
|
+
userId: args.userId,
|
|
238
|
+
now: args.now(),
|
|
239
|
+
createId: args.createId,
|
|
240
|
+
existingDialog,
|
|
241
|
+
cwd: args.cwd,
|
|
242
|
+
});
|
|
243
|
+
await args.store.batch(plan.ops);
|
|
244
|
+
return { dialogId: plan.dialogId };
|
|
262
245
|
}
|
|
263
246
|
|
|
264
247
|
export function createCliLocalRuntimeAdapter(
|
|
@@ -268,75 +251,139 @@ export function createCliLocalRuntimeAdapter(
|
|
|
268
251
|
const createId = deps.createId ?? createFallbackId;
|
|
269
252
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
270
253
|
const userId = resolveLocalUserId(deps.env);
|
|
254
|
+
const localToolBudgets = parseLocalToolBudgets(deps.env);
|
|
255
|
+
const localToolUsage = new Map<string, number>();
|
|
271
256
|
let activeAgentToolNames: string[] = [];
|
|
272
|
-
|
|
257
|
+
let workspaceSession: WorkspaceSession = createWorkspaceSession({ cwd: deps.cwd });
|
|
258
|
+
let localToolExecutors = buildLocalToolExecutors({
|
|
259
|
+
workspaceRoot: workspaceSession.workspaceRoot,
|
|
260
|
+
localToolExecutors: deps.localToolExecutors,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
async function activateAgentWorkspaceSession(args: {
|
|
264
|
+
agentRef: string;
|
|
265
|
+
agentConfig: AgentRuntimeAgentConfig | null;
|
|
266
|
+
}) {
|
|
267
|
+
const previousWorkspaceRoot = workspaceSession.workspaceRoot;
|
|
268
|
+
workspaceSession = await activateWorkspaceSession(workspaceSession, {
|
|
269
|
+
agentKey: args.agentConfig?.key ?? args.agentRef,
|
|
270
|
+
mode: readLocalWorkspaceMode(args.agentConfig),
|
|
271
|
+
env: deps.env,
|
|
272
|
+
prepareTaskWorkspace: deps.prepareTaskWorktree,
|
|
273
|
+
});
|
|
274
|
+
if (workspaceSession.workspaceRoot === previousWorkspaceRoot) return;
|
|
275
|
+
localToolExecutors = buildLocalToolExecutors({
|
|
276
|
+
workspaceRoot: workspaceSession.workspaceRoot,
|
|
277
|
+
localToolExecutors: deps.localToolExecutors,
|
|
278
|
+
});
|
|
279
|
+
deps.output?.write(formatWorkspaceSessionActivation(workspaceSession));
|
|
280
|
+
}
|
|
273
281
|
|
|
274
282
|
return {
|
|
275
283
|
host: "cli",
|
|
276
|
-
capabilities: ["leveldb-agent-config", "local-provider", "leveldb-persistence"],
|
|
284
|
+
capabilities: ["leveldb-agent-config", "local-provider", "leveldb-persistence", "local-tools"],
|
|
277
285
|
loadAgentConfig: async (agentRef) => {
|
|
278
|
-
const agentConfig = await
|
|
286
|
+
const agentConfig = await readAgentFromStore({
|
|
279
287
|
agentRef,
|
|
280
|
-
|
|
288
|
+
store: await resolveStore(deps),
|
|
281
289
|
userId,
|
|
282
290
|
});
|
|
283
|
-
activeAgentToolNames =
|
|
291
|
+
activeAgentToolNames = buildLocalPolicyToolNames({
|
|
292
|
+
toolNames: agentConfig?.toolNames,
|
|
293
|
+
env: deps.env,
|
|
294
|
+
});
|
|
295
|
+
await activateAgentWorkspaceSession({ agentRef, agentConfig });
|
|
284
296
|
return agentConfig;
|
|
285
297
|
},
|
|
286
298
|
loadDialogHistory: async (dialogId) => readDialogMessages({
|
|
287
299
|
dialogId,
|
|
288
|
-
|
|
300
|
+
store: await resolveStore(deps),
|
|
289
301
|
}),
|
|
290
302
|
saveTurn: async (input) => writeDialog({
|
|
291
|
-
|
|
303
|
+
store: await resolveStore(deps),
|
|
292
304
|
input,
|
|
293
305
|
userId,
|
|
294
306
|
now,
|
|
295
307
|
createId,
|
|
308
|
+
cwd: workspaceSession.workspaceRoot,
|
|
296
309
|
}),
|
|
297
|
-
resolveProvider: async (agentConfig) =>
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
toolNames: agentConfig.toolNames,
|
|
310
|
+
resolveProvider: async (agentConfig) => {
|
|
311
|
+
if (shouldUsePlatformChatProvider(deps.env, agentConfig)) {
|
|
312
|
+
const providerConfig = resolvePlatformChatProviderConfig({
|
|
313
|
+
agentConfig,
|
|
302
314
|
env: deps.env,
|
|
303
315
|
});
|
|
304
|
-
const res = await fetchImpl(`${resolveOpenAiCompatibleBaseUrl(deps.env)}/chat/completions`, {
|
|
305
|
-
method: "POST",
|
|
306
|
-
headers: {
|
|
307
|
-
"Content-Type": "application/json",
|
|
308
|
-
...(resolveApiKey(deps.env)
|
|
309
|
-
? { Authorization: `Bearer ${resolveApiKey(deps.env)}` }
|
|
310
|
-
: {}),
|
|
311
|
-
},
|
|
312
|
-
body: JSON.stringify({
|
|
313
|
-
model: agentConfig.model || "gpt-4.1-mini",
|
|
314
|
-
messages: toOpenAiMessages(messages),
|
|
315
|
-
stream: false,
|
|
316
|
-
...(tools.length > 0 ? { tools } : {}),
|
|
317
|
-
}),
|
|
318
|
-
});
|
|
319
|
-
const data = await res.json().catch(() => ({}));
|
|
320
|
-
if (!res.ok) {
|
|
321
|
-
throw new Error(`local provider failed: HTTP ${res.status} ${JSON.stringify(data)}`);
|
|
322
|
-
}
|
|
323
|
-
const choiceMessage = data?.choices?.[0]?.message ?? {};
|
|
324
|
-
const content = String(choiceMessage?.content ?? "");
|
|
325
316
|
return {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
317
|
+
model: providerConfig.model,
|
|
318
|
+
complete: async (messages) => {
|
|
319
|
+
const tools = buildOpenAiTools({
|
|
320
|
+
toolNames: agentConfig.toolNames,
|
|
321
|
+
env: deps.env,
|
|
322
|
+
});
|
|
323
|
+
const request = buildPlatformChatCompletionRequest({
|
|
324
|
+
providerConfig,
|
|
325
|
+
messages,
|
|
326
|
+
tools,
|
|
327
|
+
});
|
|
328
|
+
const res = await fetchWithTransientRetry(fetchImpl, request.url, request.init, {
|
|
329
|
+
sleep: deps.sleep,
|
|
330
|
+
});
|
|
331
|
+
const data = parsePlatformChatCompletionData(await res.text().catch(() => ""));
|
|
332
|
+
if (!res.ok) {
|
|
333
|
+
throw new Error(`platform provider failed: HTTP ${res.status} ${JSON.stringify(data)}`);
|
|
334
|
+
}
|
|
335
|
+
return parsePlatformChatCompletionResponse({
|
|
336
|
+
providerConfig,
|
|
337
|
+
data,
|
|
338
|
+
trace: messages,
|
|
339
|
+
});
|
|
340
|
+
},
|
|
332
341
|
};
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const providerConfig = resolveCliOpenAiProviderConfig({
|
|
345
|
+
agentConfig,
|
|
346
|
+
env: deps.env,
|
|
347
|
+
});
|
|
348
|
+
return {
|
|
349
|
+
model: providerConfig.model,
|
|
350
|
+
complete: async (messages) => {
|
|
351
|
+
const tools = buildOpenAiTools({
|
|
352
|
+
toolNames: agentConfig.toolNames,
|
|
353
|
+
env: deps.env,
|
|
354
|
+
});
|
|
355
|
+
const request = buildOpenAiCompatibleChatCompletionRequest({
|
|
356
|
+
providerConfig,
|
|
357
|
+
messages,
|
|
358
|
+
tools,
|
|
359
|
+
});
|
|
360
|
+
const res = await fetchWithTransientRetry(fetchImpl, request.url, request.init, {
|
|
361
|
+
sleep: deps.sleep,
|
|
362
|
+
});
|
|
363
|
+
const data = await res.json().catch(() => ({}));
|
|
364
|
+
if (!res.ok) {
|
|
365
|
+
throw new Error(`local provider failed: HTTP ${res.status} ${JSON.stringify(data)}`);
|
|
366
|
+
}
|
|
367
|
+
return parseOpenAiCompatibleChatCompletionResponse({
|
|
368
|
+
providerConfig,
|
|
369
|
+
data,
|
|
370
|
+
trace: messages,
|
|
371
|
+
});
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
},
|
|
375
|
+
executeTool: async (call) => {
|
|
376
|
+
assertWithinLocalToolBudget({
|
|
377
|
+
toolName: call.name,
|
|
378
|
+
budgets: localToolBudgets,
|
|
379
|
+
usage: localToolUsage,
|
|
380
|
+
});
|
|
381
|
+
return executeLocalToolWithPolicy({
|
|
382
|
+
env: deps.env,
|
|
383
|
+
agentToolNames: activeAgentToolNames,
|
|
384
|
+
call,
|
|
385
|
+
executors: localToolExecutors,
|
|
386
|
+
});
|
|
387
|
+
},
|
|
341
388
|
};
|
|
342
389
|
}
|