nolo-cli 0.1.19 → 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 +13 -0
- package/agent-runtime/hybridRecordStore.ts +147 -0
- package/agent-runtime/index.ts +69 -0
- package/agent-runtime/localLoop.ts +69 -5
- 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 +1 -0
- package/agent-runtime/workspaceSession.ts +76 -0
- package/agentAliases.ts +37 -0
- package/agentPullCommand.ts +1 -1
- package/agentRunCommand.ts +278 -52
- 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 +621 -9
- package/client/localRuntimeAdapter.ts +275 -250
- 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,252 +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
|
-
|
|
77
|
-
|
|
115
|
+
async function defaultSleep(ms: number) {
|
|
116
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function transientFetchRetryDelayMs(attempt: number) {
|
|
120
|
+
return Math.min(attempt * TRANSIENT_FETCH_RETRY_BASE_DELAY_MS, 2_000);
|
|
78
121
|
}
|
|
79
122
|
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
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
|
-
const cmd = String(parsed.cmd ?? parsed.command ?? "").trim();
|
|
95
|
-
if (!cmd) throw new Error("execShell requires a non-empty cmd argument.");
|
|
96
|
-
const proc = Bun.spawn(["/bin/sh", "-lc", cmd], {
|
|
97
|
-
cwd: args.cwd,
|
|
98
|
-
stdout: "pipe",
|
|
99
|
-
stderr: "pipe",
|
|
100
|
-
stdin: "ignore",
|
|
144
|
+
function buildOpenAiTools(args: { toolNames?: string[]; env: EnvLike }) {
|
|
145
|
+
const toolset = buildLocalWorkspaceToolsetForEnv(args);
|
|
146
|
+
return buildLocalWorkspaceOpenAiTools({
|
|
147
|
+
toolNames: toolset.toolNames,
|
|
148
|
+
exposeShellTools: toolset.exposeShellTools,
|
|
101
149
|
});
|
|
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
150
|
}
|
|
116
151
|
|
|
117
|
-
function
|
|
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),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
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
|
-
...(message.tool_call_id ? { tool_call_id: message.tool_call_id } : {}),
|
|
165
|
-
...(Array.isArray(message.tool_calls) ? { tool_calls: message.tool_calls } : {}),
|
|
166
|
-
}));
|
|
167
|
-
}
|
|
168
|
-
|
|
169
208
|
async function readDialogMessages(args: {
|
|
170
|
-
|
|
209
|
+
store: HybridRecordStore;
|
|
171
210
|
dialogId: string;
|
|
172
211
|
}) {
|
|
173
212
|
const messages: AgentRuntimeChatMessage[] = [];
|
|
174
213
|
const prefix = `dialog-${args.dialogId}-msg-`;
|
|
175
|
-
const iterator = args.
|
|
214
|
+
const iterator = args.store.iterator({ gte: prefix, lte: `${prefix}\uffff` });
|
|
176
215
|
for await (const [, value] of iterator) {
|
|
177
|
-
|
|
178
|
-
if (
|
|
179
|
-
if (value.role === "user" || value.role === "assistant" || value.role === "tool") {
|
|
180
|
-
messages.push({
|
|
181
|
-
role: value.role,
|
|
182
|
-
content: value.content ?? null,
|
|
183
|
-
...(typeof value.toolCallId === "string" ? { tool_call_id: value.toolCallId } : {}),
|
|
184
|
-
...(Array.isArray(value.tool_calls) ? { tool_calls: value.tool_calls } : {}),
|
|
185
|
-
});
|
|
186
|
-
}
|
|
216
|
+
const message = localDialogMessageRecordToRuntimeMessage(value);
|
|
217
|
+
if (message) messages.push(message);
|
|
187
218
|
}
|
|
188
219
|
return messages;
|
|
189
220
|
}
|
|
190
221
|
|
|
191
222
|
async function writeDialog(args: {
|
|
192
|
-
|
|
223
|
+
store: HybridRecordStore;
|
|
193
224
|
input: AgentRuntimeSaveTurnInput;
|
|
194
225
|
userId: string;
|
|
195
226
|
now: () => number;
|
|
196
227
|
createId: () => string;
|
|
197
228
|
cwd?: string;
|
|
198
229
|
}) {
|
|
199
|
-
const dialogId = args.input.continueDialogId || args.createId();
|
|
200
|
-
const now = args.now();
|
|
201
|
-
const nowIso = new Date(now).toISOString();
|
|
202
|
-
const dialogKey = `dialog-${args.userId}-${dialogId}`;
|
|
203
|
-
const lastUser = [...args.input.messages].reverse().find((message) => message.role === "user");
|
|
204
|
-
const lastUserText = typeof lastUser?.content === "string"
|
|
205
|
-
? lastUser.content
|
|
206
|
-
: Array.isArray(lastUser?.content)
|
|
207
|
-
? lastUser.content
|
|
208
|
-
.filter((part) => part.type === "text")
|
|
209
|
-
.map((part) => part.text)
|
|
210
|
-
.join(" ")
|
|
211
|
-
: "";
|
|
212
230
|
let existingDialog: any = null;
|
|
213
231
|
if (args.input.continueDialogId) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
} catch {
|
|
217
|
-
existingDialog = null;
|
|
218
|
-
}
|
|
232
|
+
const dialogKey = `dialog-${args.userId}-${args.input.continueDialogId}`;
|
|
233
|
+
existingDialog = await args.store.read(dialogKey);
|
|
219
234
|
}
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
dbKey: key,
|
|
231
|
-
dialogId,
|
|
232
|
-
role: message.role,
|
|
233
|
-
content: message.content ?? "",
|
|
234
|
-
...(message.role === "user" ? { userId: args.userId } : {}),
|
|
235
|
-
...(message.role === "assistant" ? {
|
|
236
|
-
agentKey: args.input.agentKey,
|
|
237
|
-
cybotKey: args.input.agentKey,
|
|
238
|
-
} : {}),
|
|
239
|
-
...(message.tool_call_id ? { toolCallId: message.tool_call_id } : {}),
|
|
240
|
-
...(Array.isArray(message.tool_calls) ? { tool_calls: message.tool_calls } : {}),
|
|
241
|
-
...(message.tool_result_metadata ? { metadata: message.tool_result_metadata } : {}),
|
|
242
|
-
createdAt: nowIso,
|
|
243
|
-
},
|
|
244
|
-
};
|
|
245
|
-
});
|
|
246
|
-
const ops: Array<{ type: "put"; key: string; value: any }> = [
|
|
247
|
-
{
|
|
248
|
-
type: "put",
|
|
249
|
-
key: dialogKey,
|
|
250
|
-
value: {
|
|
251
|
-
...(existingDialog && typeof existingDialog === "object" ? existingDialog : {}),
|
|
252
|
-
id: dialogId,
|
|
253
|
-
dbKey: dialogKey,
|
|
254
|
-
type: "dialog",
|
|
255
|
-
userId: args.userId,
|
|
256
|
-
cybots: [args.input.agentKey],
|
|
257
|
-
primaryAgentKey: args.input.agentKey,
|
|
258
|
-
title: typeof existingDialog?.title === "string" && existingDialog.title.trim()
|
|
259
|
-
? existingDialog.title
|
|
260
|
-
: lastUserText.trim()
|
|
261
|
-
? lastUserText.trim().slice(0, 80)
|
|
262
|
-
: "Local agent run",
|
|
263
|
-
status: "done",
|
|
264
|
-
triggerType: "cli-local",
|
|
265
|
-
executionMode: "foreground",
|
|
266
|
-
createdAt: existingDialog?.createdAt ?? nowIso,
|
|
267
|
-
updatedAt: nowIso,
|
|
268
|
-
finishedAt: now,
|
|
269
|
-
usage: args.input.result.usage,
|
|
270
|
-
...(typeof args.input.result.toolCallCount === "number"
|
|
271
|
-
? { toolCallCount: args.input.result.toolCallCount }
|
|
272
|
-
: {}),
|
|
273
|
-
localRuntime: {
|
|
274
|
-
host: "cli",
|
|
275
|
-
...(args.cwd ? { worktreePath: args.cwd } : {}),
|
|
276
|
-
},
|
|
277
|
-
},
|
|
278
|
-
},
|
|
279
|
-
...messageOps,
|
|
280
|
-
];
|
|
281
|
-
await args.db.batch(ops);
|
|
282
|
-
return { dialogId };
|
|
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 };
|
|
283
245
|
}
|
|
284
246
|
|
|
285
247
|
export function createCliLocalRuntimeAdapter(
|
|
@@ -289,76 +251,139 @@ export function createCliLocalRuntimeAdapter(
|
|
|
289
251
|
const createId = deps.createId ?? createFallbackId;
|
|
290
252
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
291
253
|
const userId = resolveLocalUserId(deps.env);
|
|
254
|
+
const localToolBudgets = parseLocalToolBudgets(deps.env);
|
|
255
|
+
const localToolUsage = new Map<string, number>();
|
|
292
256
|
let activeAgentToolNames: string[] = [];
|
|
293
|
-
|
|
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
|
+
}
|
|
294
281
|
|
|
295
282
|
return {
|
|
296
283
|
host: "cli",
|
|
297
|
-
capabilities: ["leveldb-agent-config", "local-provider", "leveldb-persistence"],
|
|
284
|
+
capabilities: ["leveldb-agent-config", "local-provider", "leveldb-persistence", "local-tools"],
|
|
298
285
|
loadAgentConfig: async (agentRef) => {
|
|
299
|
-
const agentConfig = await
|
|
286
|
+
const agentConfig = await readAgentFromStore({
|
|
300
287
|
agentRef,
|
|
301
|
-
|
|
288
|
+
store: await resolveStore(deps),
|
|
302
289
|
userId,
|
|
303
290
|
});
|
|
304
|
-
activeAgentToolNames =
|
|
291
|
+
activeAgentToolNames = buildLocalPolicyToolNames({
|
|
292
|
+
toolNames: agentConfig?.toolNames,
|
|
293
|
+
env: deps.env,
|
|
294
|
+
});
|
|
295
|
+
await activateAgentWorkspaceSession({ agentRef, agentConfig });
|
|
305
296
|
return agentConfig;
|
|
306
297
|
},
|
|
307
298
|
loadDialogHistory: async (dialogId) => readDialogMessages({
|
|
308
299
|
dialogId,
|
|
309
|
-
|
|
300
|
+
store: await resolveStore(deps),
|
|
310
301
|
}),
|
|
311
302
|
saveTurn: async (input) => writeDialog({
|
|
312
|
-
|
|
303
|
+
store: await resolveStore(deps),
|
|
313
304
|
input,
|
|
314
305
|
userId,
|
|
315
306
|
now,
|
|
316
307
|
createId,
|
|
317
|
-
cwd:
|
|
308
|
+
cwd: workspaceSession.workspaceRoot,
|
|
318
309
|
}),
|
|
319
|
-
resolveProvider: async (agentConfig) =>
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
toolNames: agentConfig.toolNames,
|
|
310
|
+
resolveProvider: async (agentConfig) => {
|
|
311
|
+
if (shouldUsePlatformChatProvider(deps.env, agentConfig)) {
|
|
312
|
+
const providerConfig = resolvePlatformChatProviderConfig({
|
|
313
|
+
agentConfig,
|
|
324
314
|
env: deps.env,
|
|
325
315
|
});
|
|
326
|
-
const res = await fetchImpl(`${resolveOpenAiCompatibleBaseUrl(deps.env)}/chat/completions`, {
|
|
327
|
-
method: "POST",
|
|
328
|
-
headers: {
|
|
329
|
-
"Content-Type": "application/json",
|
|
330
|
-
...(resolveApiKey(deps.env)
|
|
331
|
-
? { Authorization: `Bearer ${resolveApiKey(deps.env)}` }
|
|
332
|
-
: {}),
|
|
333
|
-
},
|
|
334
|
-
body: JSON.stringify({
|
|
335
|
-
model: agentConfig.model || "gpt-4.1-mini",
|
|
336
|
-
messages: toOpenAiMessages(messages),
|
|
337
|
-
stream: false,
|
|
338
|
-
...(tools.length > 0 ? { tools } : {}),
|
|
339
|
-
}),
|
|
340
|
-
});
|
|
341
|
-
const data = await res.json().catch(() => ({}));
|
|
342
|
-
if (!res.ok) {
|
|
343
|
-
throw new Error(`local provider failed: HTTP ${res.status} ${JSON.stringify(data)}`);
|
|
344
|
-
}
|
|
345
|
-
const choiceMessage = data?.choices?.[0]?.message ?? {};
|
|
346
|
-
const content = String(choiceMessage?.content ?? "");
|
|
347
316
|
return {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
+
},
|
|
354
341
|
};
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
+
},
|
|
363
388
|
};
|
|
364
389
|
}
|