nolo-cli 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/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 +882 -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 +430 -7
- package/client/agentRun.ts +504 -64
- 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 +265 -0
package/README.md
CHANGED
|
@@ -169,12 +169,20 @@ nolo agent list
|
|
|
169
169
|
nolo agent switch <agent>
|
|
170
170
|
nolo agent read <agent>
|
|
171
171
|
nolo agent run <agent> "检查最近失败任务"
|
|
172
|
+
nolo agent run frontend-implementer --msg "修一个小的通知弹窗 CSS 问题"
|
|
172
173
|
nolo dialog list --agent <agent>
|
|
173
174
|
nolo dialog read <dialog>
|
|
174
175
|
nolo doc list --agent <agent>
|
|
175
|
-
nolo table query
|
|
176
|
+
nolo table query --table meta-b2e06f801f-NOLOTASKBOARD --limit 20
|
|
177
|
+
nolo table query --table meta-b2e06f801f-NOLOTASKBOARD --columns '["title","status","owner","priority","codeStatus"]' --no-base-fields --output items
|
|
178
|
+
nolo table update-row --table meta-b2e06f801f-NOLOTASKBOARD --row 01ROWID --changes '{"status":"已完成"}'
|
|
179
|
+
nolo table add-column --table meta-b2e06f801f-NOLOTASKBOARD --schema-write-ok --name "blockedBy" --label "Blocked By"
|
|
176
180
|
```
|
|
177
181
|
|
|
182
|
+
`nolo table add-column` mutates table schema metadata, so it requires
|
|
183
|
+
`--schema-write-ok` and should be run serially per table. Row-level table writes
|
|
184
|
+
do not require this flag.
|
|
185
|
+
|
|
178
186
|
Inside the current TUI, examples of supported slash commands include:
|
|
179
187
|
`/agent`, `/agents`, `/switch`, `/context` (alias `/ctx`), `/dialog`, `/doc`,
|
|
180
188
|
`/help`, `/new`, `/customize`, `/login`, `/profile`, `/version`, `/quit`
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AgentRuntimeAgentConfig } from "./hostAdapter";
|
|
2
|
+
|
|
3
|
+
export function pickAgentRuntimeInferenceOptions(agentConfig: AgentRuntimeAgentConfig) {
|
|
4
|
+
return {
|
|
5
|
+
...(agentConfig.temperature !== undefined ? { temperature: agentConfig.temperature } : {}),
|
|
6
|
+
...(agentConfig.top_p !== undefined ? { top_p: agentConfig.top_p } : {}),
|
|
7
|
+
...(agentConfig.frequency_penalty !== undefined ? { frequency_penalty: agentConfig.frequency_penalty } : {}),
|
|
8
|
+
...(agentConfig.presence_penalty !== undefined ? { presence_penalty: agentConfig.presence_penalty } : {}),
|
|
9
|
+
...(agentConfig.max_tokens !== undefined ? { max_tokens: agentConfig.max_tokens } : {}),
|
|
10
|
+
...(agentConfig.reasoning_effort ? { reasoning_effort: agentConfig.reasoning_effort } : {}),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { AgentRuntimeAgentConfig } from "./hostAdapter";
|
|
2
|
+
|
|
3
|
+
type AgentRecord = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
function stringField(record: AgentRecord, key: string) {
|
|
6
|
+
const value = record[key];
|
|
7
|
+
return typeof value === "string" ? value : undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function numberField(record: AgentRecord, key: string) {
|
|
11
|
+
const value = record[key];
|
|
12
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function objectField(record: AgentRecord, key: string): Record<string, unknown> | undefined {
|
|
16
|
+
const value = record[key];
|
|
17
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
18
|
+
? value as Record<string, unknown>
|
|
19
|
+
: undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function localWorkspaceModeField(record: AgentRecord): "current" | "task-worktree" | undefined {
|
|
23
|
+
const directValue = stringField(record, "localWorkspaceMode");
|
|
24
|
+
const runtimeBinding = objectField(record, "runtimeBinding");
|
|
25
|
+
const runtimeValue = typeof runtimeBinding?.localWorkspaceMode === "string"
|
|
26
|
+
? runtimeBinding.localWorkspaceMode
|
|
27
|
+
: typeof runtimeBinding?.workspaceMode === "string"
|
|
28
|
+
? runtimeBinding.workspaceMode
|
|
29
|
+
: undefined;
|
|
30
|
+
const value = directValue ?? runtimeValue;
|
|
31
|
+
return value === "task-worktree" || value === "current" ? value : undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function appendUniqueStrings(values: string[], next: unknown) {
|
|
35
|
+
if (!Array.isArray(next)) return values;
|
|
36
|
+
for (const value of next) {
|
|
37
|
+
const toolName = typeof value === "string"
|
|
38
|
+
? value
|
|
39
|
+
: value && typeof value === "object" && typeof (value as any).name === "string"
|
|
40
|
+
? (value as any).name
|
|
41
|
+
: value && typeof value === "object" && typeof (value as any).function?.name === "string"
|
|
42
|
+
? (value as any).function.name
|
|
43
|
+
: "";
|
|
44
|
+
if (toolName && !values.includes(toolName)) values.push(toolName);
|
|
45
|
+
}
|
|
46
|
+
return values;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function resolveAgentRuntimeToolNames(record: AgentRecord) {
|
|
50
|
+
const tools = appendUniqueStrings(
|
|
51
|
+
appendUniqueStrings([], record.toolNames),
|
|
52
|
+
record.tools,
|
|
53
|
+
);
|
|
54
|
+
return tools.length > 0 ? tools : undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function resolveAgentRuntimeConfigFromRecord(
|
|
58
|
+
key: string,
|
|
59
|
+
record: AgentRecord,
|
|
60
|
+
): AgentRuntimeAgentConfig {
|
|
61
|
+
const name = stringField(record, "name");
|
|
62
|
+
const prompt = stringField(record, "prompt");
|
|
63
|
+
const model = stringField(record, "model");
|
|
64
|
+
const apiSource = stringField(record, "apiSource");
|
|
65
|
+
const cliProvider = stringField(record, "cliProvider");
|
|
66
|
+
const customProviderUrl = stringField(record, "customProviderUrl");
|
|
67
|
+
const provider = stringField(record, "provider") ?? stringField(record, "apiSource");
|
|
68
|
+
const toolNames = resolveAgentRuntimeToolNames(record);
|
|
69
|
+
const temperature = numberField(record, "temperature");
|
|
70
|
+
const topP = numberField(record, "top_p");
|
|
71
|
+
const frequencyPenalty = numberField(record, "frequency_penalty");
|
|
72
|
+
const presencePenalty = numberField(record, "presence_penalty");
|
|
73
|
+
const maxTokens = numberField(record, "max_tokens");
|
|
74
|
+
const reasoningEffort = stringField(record, "reasoning_effort");
|
|
75
|
+
const runtimeBinding = objectField(record, "runtimeBinding");
|
|
76
|
+
const localWorkspaceMode = localWorkspaceModeField(record);
|
|
77
|
+
const delegation = objectField(record, "delegation");
|
|
78
|
+
return {
|
|
79
|
+
key,
|
|
80
|
+
...(name ? { name } : {}),
|
|
81
|
+
...(prompt ? { prompt } : {}),
|
|
82
|
+
...(model ? { model } : {}),
|
|
83
|
+
...(provider ? { provider } : {}),
|
|
84
|
+
...(apiSource ? { apiSource } : {}),
|
|
85
|
+
...(cliProvider ? { cliProvider } : {}),
|
|
86
|
+
...(customProviderUrl ? { customProviderUrl } : {}),
|
|
87
|
+
...(toolNames ? { toolNames } : {}),
|
|
88
|
+
...(temperature !== undefined ? { temperature } : {}),
|
|
89
|
+
...(topP !== undefined ? { top_p: topP } : {}),
|
|
90
|
+
...(frequencyPenalty !== undefined ? { frequency_penalty: frequencyPenalty } : {}),
|
|
91
|
+
...(presencePenalty !== undefined ? { presence_penalty: presencePenalty } : {}),
|
|
92
|
+
...(maxTokens !== undefined ? { max_tokens: maxTokens } : {}),
|
|
93
|
+
...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),
|
|
94
|
+
...(runtimeBinding ? { runtimeBinding } : {}),
|
|
95
|
+
...(localWorkspaceMode ? { localWorkspaceMode } : {}),
|
|
96
|
+
...(delegation ? { delegation } : {}),
|
|
97
|
+
rawRecord: record,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function buildAgentRuntimeAgentLookupKeys(args: {
|
|
2
|
+
agentRef: string;
|
|
3
|
+
userId: string;
|
|
4
|
+
}) {
|
|
5
|
+
return [
|
|
6
|
+
args.agentRef,
|
|
7
|
+
`agent-${args.userId}-${args.agentRef}`,
|
|
8
|
+
`cybot-${args.userId}-${args.agentRef}`,
|
|
9
|
+
];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function shouldFetchAgentRuntimeRecordRemotely(dbKey: string) {
|
|
13
|
+
return /^(agent|cybot)(-pub)?-/.test(dbKey);
|
|
14
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AgentRuntimeChatMessage } from "./types";
|
|
2
|
+
|
|
3
|
+
type DialogMessageRecord = Record<string, any>;
|
|
4
|
+
|
|
5
|
+
export function dialogMessageRecordToAgentRuntimeMessage(
|
|
6
|
+
record: DialogMessageRecord
|
|
7
|
+
): AgentRuntimeChatMessage | null {
|
|
8
|
+
if (!record || typeof record !== "object") return null;
|
|
9
|
+
if (record.role !== "user" && record.role !== "assistant" && record.role !== "tool") return null;
|
|
10
|
+
return {
|
|
11
|
+
role: record.role,
|
|
12
|
+
content: record.content ?? null,
|
|
13
|
+
...(typeof record.toolCallId === "string" ? { tool_call_id: record.toolCallId } : {}),
|
|
14
|
+
...(Array.isArray(record.tool_calls) ? { tool_calls: record.tool_calls } : {}),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentRuntimeChatMessage,
|
|
3
|
+
AgentRuntimeHost,
|
|
4
|
+
} from "./types";
|
|
5
|
+
import type { AgentRuntimeSaveTurnInput } from "./hostAdapter";
|
|
6
|
+
|
|
7
|
+
type DialogRecord = Record<string, any>;
|
|
8
|
+
type DialogWriteOp = {
|
|
9
|
+
type: "put";
|
|
10
|
+
key: string;
|
|
11
|
+
value: DialogRecord;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function extractLastUserText(messages: AgentRuntimeChatMessage[]) {
|
|
15
|
+
const lastUser = [...messages].reverse().find((message) => message.role === "user");
|
|
16
|
+
if (typeof lastUser?.content === "string") return lastUser.content;
|
|
17
|
+
if (Array.isArray(lastUser?.content)) {
|
|
18
|
+
return lastUser.content
|
|
19
|
+
.filter((part) => part.type === "text")
|
|
20
|
+
.map((part) => part.text)
|
|
21
|
+
.join(" ");
|
|
22
|
+
}
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveDialogTitle(args: {
|
|
27
|
+
existingDialog?: DialogRecord | null;
|
|
28
|
+
messages: AgentRuntimeChatMessage[];
|
|
29
|
+
}) {
|
|
30
|
+
if (typeof args.existingDialog?.title === "string" && args.existingDialog.title.trim()) {
|
|
31
|
+
return args.existingDialog.title;
|
|
32
|
+
}
|
|
33
|
+
const lastUserText = extractLastUserText(args.messages).trim();
|
|
34
|
+
return lastUserText ? lastUserText.slice(0, 80) : "Local agent run";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function buildDialogMessageWriteOps(args: {
|
|
38
|
+
dialogId: string;
|
|
39
|
+
input: AgentRuntimeSaveTurnInput;
|
|
40
|
+
userId: string;
|
|
41
|
+
now: number;
|
|
42
|
+
nowIso: string;
|
|
43
|
+
}): DialogWriteOp[] {
|
|
44
|
+
return args.input.messages
|
|
45
|
+
.filter((message) => message.role !== "system")
|
|
46
|
+
.map((message, index) => {
|
|
47
|
+
// Keep timestamp-prefix ordering for LevelDB range scans. Do not switch
|
|
48
|
+
// to opaque ids until dialog query/continuation callers are reviewed.
|
|
49
|
+
const id = `${args.now}-${String(index + 1).padStart(3, "0")}`;
|
|
50
|
+
const key = `dialog-${args.dialogId}-msg-${id}`;
|
|
51
|
+
return {
|
|
52
|
+
type: "put" as const,
|
|
53
|
+
key,
|
|
54
|
+
value: {
|
|
55
|
+
id,
|
|
56
|
+
dbKey: key,
|
|
57
|
+
dialogId: args.dialogId,
|
|
58
|
+
role: message.role,
|
|
59
|
+
content: message.content ?? "",
|
|
60
|
+
...(message.role === "user" ? { userId: args.userId } : {}),
|
|
61
|
+
...(message.role === "assistant" ? {
|
|
62
|
+
agentKey: args.input.agentKey,
|
|
63
|
+
cybotKey: args.input.agentKey,
|
|
64
|
+
} : {}),
|
|
65
|
+
...(message.tool_call_id ? { toolCallId: message.tool_call_id } : {}),
|
|
66
|
+
...(Array.isArray(message.tool_calls) ? { tool_calls: message.tool_calls } : {}),
|
|
67
|
+
...(message.tool_result_metadata ? { metadata: message.tool_result_metadata } : {}),
|
|
68
|
+
createdAt: args.nowIso,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function buildAgentRuntimeDialogWritePlan(args: {
|
|
75
|
+
input: AgentRuntimeSaveTurnInput;
|
|
76
|
+
userId: string;
|
|
77
|
+
now: number;
|
|
78
|
+
createId: () => string;
|
|
79
|
+
runtimeHost: AgentRuntimeHost;
|
|
80
|
+
runtimeMetadata?: Record<string, unknown>;
|
|
81
|
+
existingDialog?: DialogRecord | null;
|
|
82
|
+
}): { dialogId: string; ops: DialogWriteOp[] } {
|
|
83
|
+
const dialogId = args.input.continueDialogId || args.createId();
|
|
84
|
+
const nowIso = new Date(args.now).toISOString();
|
|
85
|
+
const dialogKey = `dialog-${args.userId}-${dialogId}`;
|
|
86
|
+
const dialogRecord = {
|
|
87
|
+
...(args.existingDialog && typeof args.existingDialog === "object" ? args.existingDialog : {}),
|
|
88
|
+
id: dialogId,
|
|
89
|
+
dbKey: dialogKey,
|
|
90
|
+
type: "dialog",
|
|
91
|
+
userId: args.userId,
|
|
92
|
+
cybots: [args.input.agentKey],
|
|
93
|
+
primaryAgentKey: args.input.agentKey,
|
|
94
|
+
title: resolveDialogTitle({
|
|
95
|
+
existingDialog: args.existingDialog,
|
|
96
|
+
messages: args.input.messages,
|
|
97
|
+
}),
|
|
98
|
+
status: "done",
|
|
99
|
+
triggerType: `${args.runtimeHost}-local`,
|
|
100
|
+
executionMode: "foreground",
|
|
101
|
+
createdAt: args.existingDialog?.createdAt ?? nowIso,
|
|
102
|
+
updatedAt: nowIso,
|
|
103
|
+
finishedAt: args.now,
|
|
104
|
+
usage: args.input.result.usage,
|
|
105
|
+
...(typeof args.input.result.toolCallCount === "number"
|
|
106
|
+
? { toolCallCount: args.input.result.toolCallCount }
|
|
107
|
+
: {}),
|
|
108
|
+
localRuntime: {
|
|
109
|
+
host: args.runtimeHost,
|
|
110
|
+
...(args.runtimeMetadata ?? {}),
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
dialogId,
|
|
115
|
+
ops: [
|
|
116
|
+
{
|
|
117
|
+
type: "put",
|
|
118
|
+
key: dialogKey,
|
|
119
|
+
value: dialogRecord,
|
|
120
|
+
},
|
|
121
|
+
...buildDialogMessageWriteOps({
|
|
122
|
+
dialogId,
|
|
123
|
+
input: args.input,
|
|
124
|
+
userId: args.userId,
|
|
125
|
+
now: args.now,
|
|
126
|
+
nowIso,
|
|
127
|
+
}),
|
|
128
|
+
],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -10,7 +10,20 @@ export type AgentRuntimeAgentConfig = {
|
|
|
10
10
|
prompt?: string;
|
|
11
11
|
model?: string;
|
|
12
12
|
provider?: string;
|
|
13
|
+
apiSource?: string;
|
|
14
|
+
cliProvider?: string;
|
|
15
|
+
customProviderUrl?: string;
|
|
13
16
|
toolNames?: string[];
|
|
17
|
+
temperature?: number;
|
|
18
|
+
top_p?: number;
|
|
19
|
+
frequency_penalty?: number;
|
|
20
|
+
presence_penalty?: number;
|
|
21
|
+
max_tokens?: number;
|
|
22
|
+
reasoning_effort?: string;
|
|
23
|
+
runtimeBinding?: Record<string, unknown>;
|
|
24
|
+
localWorkspaceMode?: "current" | "task-worktree";
|
|
25
|
+
delegation?: Record<string, unknown>;
|
|
26
|
+
rawRecord?: Record<string, unknown>;
|
|
14
27
|
};
|
|
15
28
|
|
|
16
29
|
export type AgentRuntimeProvider = {
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
export type HybridRecordKvDb = {
|
|
2
|
+
get(key: string): Promise<any>;
|
|
3
|
+
put(key: string, value: any): Promise<unknown>;
|
|
4
|
+
batch(ops: Array<{ type: "put"; key: string; value: any }>): Promise<unknown>;
|
|
5
|
+
iterator(options: { gte: string; lte?: string; lt?: string; reverse?: boolean; limit?: number }): AsyncIterable<[string, any]>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type HybridRecordStore = {
|
|
9
|
+
read(dbKey: string, options?: { preferredServerOrigin?: string | null; remote?: boolean }): Promise<any>;
|
|
10
|
+
write(dbKey: string, record: any): Promise<any>;
|
|
11
|
+
batch(ops: Array<{ type: "put"; key: string; value: any }>): Promise<unknown>;
|
|
12
|
+
iterator(options: { gte: string; lte?: string; lt?: string; reverse?: boolean; limit?: number }): AsyncIterable<[string, any]>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type HybridRecordStoreDeps = {
|
|
16
|
+
db: HybridRecordKvDb;
|
|
17
|
+
defaultServer: string;
|
|
18
|
+
authToken?: string;
|
|
19
|
+
fetchImpl?: typeof fetch;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function normalizeHybridServer(value: string) {
|
|
23
|
+
return value.trim().replace(/\/+$/, "");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveHybridServers(defaultServer: string, preferredServerOrigin?: string | null) {
|
|
27
|
+
const raw = [
|
|
28
|
+
preferredServerOrigin || "",
|
|
29
|
+
defaultServer,
|
|
30
|
+
].filter((value) => value.trim().length > 0);
|
|
31
|
+
return [...new Set(raw.map(normalizeHybridServer))];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizeHybridRecord(dbKey: string, record: any, serverOrigin?: string | null) {
|
|
35
|
+
if (!record || typeof record !== "object") return null;
|
|
36
|
+
return {
|
|
37
|
+
...record,
|
|
38
|
+
dbKey: typeof record.dbKey === "string" && record.dbKey ? record.dbKey : dbKey,
|
|
39
|
+
...(serverOrigin ? { serverOrigin } : {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getHybridRecordTimestamp(record: any): number {
|
|
44
|
+
if (!record || typeof record !== "object") return 0;
|
|
45
|
+
const candidates = [
|
|
46
|
+
record.updatedAt,
|
|
47
|
+
record.updated_at,
|
|
48
|
+
record.createdAt,
|
|
49
|
+
record.created,
|
|
50
|
+
record?.meta?.createdAt,
|
|
51
|
+
];
|
|
52
|
+
for (const candidate of candidates) {
|
|
53
|
+
if (typeof candidate === "number" && Number.isFinite(candidate) && candidate > 0) return candidate;
|
|
54
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
55
|
+
const parsed = Date.parse(candidate);
|
|
56
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isHybridTombstoneRecord(record: any): boolean {
|
|
63
|
+
if (!record || typeof record !== "object") return false;
|
|
64
|
+
if (typeof record.deletedAt === "string") return record.deletedAt.trim().length > 0;
|
|
65
|
+
return Boolean(record.deletedAt);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function shouldReplaceWithHybridRecord(nextRecord: any, currentRecord: any): boolean {
|
|
69
|
+
const nextTs = getHybridRecordTimestamp(nextRecord);
|
|
70
|
+
const currentTs = getHybridRecordTimestamp(currentRecord);
|
|
71
|
+
if (nextTs !== currentTs) return nextTs > currentTs;
|
|
72
|
+
return isHybridTombstoneRecord(nextRecord) && !isHybridTombstoneRecord(currentRecord);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function readHybridLocalRecord(db: HybridRecordKvDb, dbKey: string) {
|
|
76
|
+
try {
|
|
77
|
+
return await db.get(dbKey);
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function fetchHybridRemoteRecord(args: {
|
|
84
|
+
dbKey: string;
|
|
85
|
+
server: string;
|
|
86
|
+
authToken?: string;
|
|
87
|
+
fetchImpl: typeof fetch;
|
|
88
|
+
}) {
|
|
89
|
+
const response = await args.fetchImpl(
|
|
90
|
+
`${args.server}/api/v1/db/read/${encodeURIComponent(args.dbKey)}`,
|
|
91
|
+
{
|
|
92
|
+
headers: {
|
|
93
|
+
"Content-Type": "application/json",
|
|
94
|
+
...(args.authToken ? { Authorization: `Bearer ${args.authToken}` } : {}),
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
if (!response.ok) return null;
|
|
99
|
+
const payload = await response.json().catch(() => null);
|
|
100
|
+
return normalizeHybridRecord(args.dbKey, payload?.data ?? payload, args.server);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function shouldCacheHybridRemoteRecord(remoteRecord: any, localRecord: any) {
|
|
104
|
+
if (!remoteRecord || typeof remoteRecord !== "object") return false;
|
|
105
|
+
if (!localRecord || typeof localRecord !== "object") return true;
|
|
106
|
+
return shouldReplaceWithHybridRecord(remoteRecord, localRecord);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function createHybridRecordStore(
|
|
110
|
+
deps: HybridRecordStoreDeps
|
|
111
|
+
): HybridRecordStore {
|
|
112
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
read: async (dbKey, options) => {
|
|
116
|
+
const localRecord = await readHybridLocalRecord(deps.db, dbKey);
|
|
117
|
+
if (localRecord) return { ...localRecord, dbKey };
|
|
118
|
+
if (options?.remote === false) return null;
|
|
119
|
+
|
|
120
|
+
for (const server of resolveHybridServers(deps.defaultServer, options?.preferredServerOrigin)) {
|
|
121
|
+
const remoteRecord = await fetchHybridRemoteRecord({
|
|
122
|
+
dbKey,
|
|
123
|
+
server,
|
|
124
|
+
authToken: deps.authToken,
|
|
125
|
+
fetchImpl,
|
|
126
|
+
});
|
|
127
|
+
if (!remoteRecord) continue;
|
|
128
|
+
if (shouldCacheHybridRemoteRecord(remoteRecord, localRecord)) {
|
|
129
|
+
await deps.db.put(dbKey, remoteRecord);
|
|
130
|
+
}
|
|
131
|
+
return remoteRecord;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return null;
|
|
135
|
+
},
|
|
136
|
+
write: async (dbKey, record) => {
|
|
137
|
+
const nextRecord = normalizeHybridRecord(dbKey, record) ?? { dbKey };
|
|
138
|
+
await deps.db.put(dbKey, nextRecord);
|
|
139
|
+
return nextRecord;
|
|
140
|
+
},
|
|
141
|
+
batch: async (ops) => deps.db.batch(ops.map((op) => ({
|
|
142
|
+
...op,
|
|
143
|
+
value: normalizeHybridRecord(op.key, op.value) ?? { dbKey: op.key },
|
|
144
|
+
}))),
|
|
145
|
+
iterator: (options) => deps.db.iterator(options),
|
|
146
|
+
};
|
|
147
|
+
}
|
package/agent-runtime/index.ts
CHANGED
|
@@ -2,8 +2,55 @@ export const AGENT_RUNTIME_PACKAGE_ID = "agent-runtime";
|
|
|
2
2
|
|
|
3
3
|
export { createRuntimeHostDescriptor } from "./hostAdapter";
|
|
4
4
|
export { runLocalAgentTurn } from "./localLoop";
|
|
5
|
+
export {
|
|
6
|
+
pickAgentRuntimeInferenceOptions,
|
|
7
|
+
} from "./agentConfigOptions";
|
|
8
|
+
export {
|
|
9
|
+
buildOpenAiCompatibleChatCompletionRequest,
|
|
10
|
+
parseOpenAiCompatibleChatCompletionResponse,
|
|
11
|
+
} from "./openAiCompatibleProvider";
|
|
12
|
+
export { resolveOpenAiCompatibleProviderConfig } from "./openAiCompatibleProviderConfig";
|
|
13
|
+
export {
|
|
14
|
+
buildPlatformChatCompletionRequest,
|
|
15
|
+
canUsePlatformChatProvider,
|
|
16
|
+
hasDirectOpenAiCompatibleProvider,
|
|
17
|
+
parsePlatformChatCompletionData,
|
|
18
|
+
parsePlatformChatCompletionResponse,
|
|
19
|
+
resolvePlatformChatProviderConfig,
|
|
20
|
+
shouldUsePlatformChatProvider,
|
|
21
|
+
} from "./platformChatProvider";
|
|
5
22
|
export { resolveAgentRuntimeDecision } from "./runtimeDecision";
|
|
6
23
|
export { buildAgentRuntimeDecisionInput } from "./runtimeFacts";
|
|
24
|
+
export {
|
|
25
|
+
buildAgentRuntimeAgentLookupKeys,
|
|
26
|
+
shouldFetchAgentRuntimeRecordRemotely,
|
|
27
|
+
} from "./agentRecordKeys";
|
|
28
|
+
export { resolveAgentRuntimeConfigFromRecord } from "./agentRecordConfig";
|
|
29
|
+
export { dialogMessageRecordToAgentRuntimeMessage } from "./dialogMessageRecord";
|
|
30
|
+
export { buildAgentRuntimeDialogWritePlan } from "./dialogWritePlan";
|
|
31
|
+
export {
|
|
32
|
+
createHybridRecordStore,
|
|
33
|
+
shouldCacheHybridRemoteRecord,
|
|
34
|
+
} from "./hybridRecordStore";
|
|
35
|
+
export {
|
|
36
|
+
activateWorkspaceSession,
|
|
37
|
+
createWorkspaceSession,
|
|
38
|
+
formatWorkspaceSessionActivation,
|
|
39
|
+
} from "./workspaceSession";
|
|
40
|
+
export {
|
|
41
|
+
formatPreparedGitTaskWorkspace,
|
|
42
|
+
prepareGitTaskWorkspace,
|
|
43
|
+
} from "./taskWorkspace";
|
|
44
|
+
export {
|
|
45
|
+
executeLocalToolWithPolicy,
|
|
46
|
+
resolveLocalToolPolicy,
|
|
47
|
+
} from "./localToolPolicy";
|
|
48
|
+
export {
|
|
49
|
+
buildLocalWorkspaceOpenAiTools,
|
|
50
|
+
buildLocalWorkspacePolicyToolNames,
|
|
51
|
+
buildLocalWorkspaceToolset,
|
|
52
|
+
createLocalWorkspaceToolExecutors,
|
|
53
|
+
} from "./localWorkspaceTools";
|
|
7
54
|
export type {
|
|
8
55
|
AgentRuntimeAgentConfig,
|
|
9
56
|
AgentRuntimeHostAdapter,
|
|
@@ -16,6 +63,28 @@ export type {
|
|
|
16
63
|
LocalAgentTurnInput,
|
|
17
64
|
LocalAgentTurnResult,
|
|
18
65
|
} from "./localLoop";
|
|
66
|
+
export type {
|
|
67
|
+
HybridRecordKvDb,
|
|
68
|
+
HybridRecordStore,
|
|
69
|
+
} from "./hybridRecordStore";
|
|
70
|
+
export type {
|
|
71
|
+
PreparedTaskWorkspace,
|
|
72
|
+
PrepareTaskWorkspace,
|
|
73
|
+
WorkspaceSession,
|
|
74
|
+
WorkspaceSessionMode,
|
|
75
|
+
} from "./workspaceSession";
|
|
76
|
+
export type {
|
|
77
|
+
PreparedGitTaskWorkspace,
|
|
78
|
+
} from "./taskWorkspace";
|
|
79
|
+
export type {
|
|
80
|
+
LocalToolPolicyDecision,
|
|
81
|
+
} from "./localToolPolicy";
|
|
82
|
+
export type {
|
|
83
|
+
OpenAiCompatibleProviderConfig,
|
|
84
|
+
} from "./openAiCompatibleProvider";
|
|
85
|
+
export type {
|
|
86
|
+
PlatformChatProviderConfig,
|
|
87
|
+
} from "./platformChatProvider";
|
|
19
88
|
export type {
|
|
20
89
|
AgentRuntimeChatMessage,
|
|
21
90
|
AgentRuntimeDecision,
|
|
@@ -13,12 +13,42 @@ export type LocalAgentTurnInput = {
|
|
|
13
13
|
input: AgentRuntimeMessageContent;
|
|
14
14
|
continueDialogId?: string;
|
|
15
15
|
maxToolRounds?: number;
|
|
16
|
+
onToolEvent?: (event: LocalAgentToolEvent) => void;
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
export type LocalAgentTurnResult = AgentRuntimeResult & {
|
|
19
20
|
dialogId: string;
|
|
20
21
|
};
|
|
21
22
|
|
|
23
|
+
export type LocalAgentToolEvent = {
|
|
24
|
+
type: "tool-call" | "tool-result" | "tool-error";
|
|
25
|
+
round: number;
|
|
26
|
+
toolCallId: string;
|
|
27
|
+
toolName: string;
|
|
28
|
+
message?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const DEFAULT_LOCAL_AGENT_MAX_TOOL_ROUNDS = 16;
|
|
32
|
+
|
|
33
|
+
function formatToolExecutionError(args: {
|
|
34
|
+
toolName: string;
|
|
35
|
+
error: unknown;
|
|
36
|
+
}) {
|
|
37
|
+
const message = args.error instanceof Error ? args.error.message : String(args.error);
|
|
38
|
+
return `${args.toolName} failed: ${message}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function shouldReturnToolExecutionErrors(adapter: AgentRuntimeHostAdapter) {
|
|
42
|
+
return adapter.capabilities.includes("local-tools");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function emitToolEvent(
|
|
46
|
+
input: LocalAgentTurnInput,
|
|
47
|
+
event: LocalAgentToolEvent
|
|
48
|
+
) {
|
|
49
|
+
input.onToolEvent?.(event);
|
|
50
|
+
}
|
|
51
|
+
|
|
22
52
|
function buildMessages(args: {
|
|
23
53
|
prompt?: string;
|
|
24
54
|
history: AgentRuntimeChatMessage[];
|
|
@@ -52,7 +82,7 @@ export async function runLocalAgentTurn(
|
|
|
52
82
|
input: input.input,
|
|
53
83
|
});
|
|
54
84
|
const provider = await input.adapter.resolveProvider(agentConfig);
|
|
55
|
-
const maxToolRounds = input.maxToolRounds ??
|
|
85
|
+
const maxToolRounds = input.maxToolRounds ?? DEFAULT_LOCAL_AGENT_MAX_TOOL_ROUNDS;
|
|
56
86
|
let toolCallCount = 0;
|
|
57
87
|
let result: AgentRuntimeResult;
|
|
58
88
|
for (let round = 0; round <= maxToolRounds; round += 1) {
|
|
@@ -66,14 +96,48 @@ export async function runLocalAgentTurn(
|
|
|
66
96
|
messages.push({
|
|
67
97
|
role: "assistant",
|
|
68
98
|
content: result.content || null,
|
|
99
|
+
...(result.reasoning_content ? { reasoning_content: result.reasoning_content } : {}),
|
|
69
100
|
tool_calls: toolCalls,
|
|
70
101
|
});
|
|
71
102
|
for (const toolCall of toolCalls) {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
103
|
+
const toolName = toolCall.function.name;
|
|
104
|
+
let toolResult;
|
|
105
|
+
emitToolEvent(input, {
|
|
106
|
+
type: "tool-call",
|
|
107
|
+
round,
|
|
108
|
+
toolCallId: toolCall.id,
|
|
109
|
+
toolName,
|
|
76
110
|
});
|
|
111
|
+
try {
|
|
112
|
+
toolResult = await input.adapter.executeTool({
|
|
113
|
+
id: toolCall.id,
|
|
114
|
+
name: toolName,
|
|
115
|
+
arguments: toolCall.function.arguments,
|
|
116
|
+
});
|
|
117
|
+
emitToolEvent(input, {
|
|
118
|
+
type: "tool-result",
|
|
119
|
+
round,
|
|
120
|
+
toolCallId: toolCall.id,
|
|
121
|
+
toolName,
|
|
122
|
+
});
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (!shouldReturnToolExecutionErrors(input.adapter)) throw error;
|
|
125
|
+
emitToolEvent(input, {
|
|
126
|
+
type: "tool-error",
|
|
127
|
+
round,
|
|
128
|
+
toolCallId: toolCall.id,
|
|
129
|
+
toolName,
|
|
130
|
+
message: error instanceof Error ? error.message : String(error),
|
|
131
|
+
});
|
|
132
|
+
toolResult = {
|
|
133
|
+
content: formatToolExecutionError({ toolName, error }),
|
|
134
|
+
metadata: {
|
|
135
|
+
error: true,
|
|
136
|
+
toolName,
|
|
137
|
+
message: error instanceof Error ? error.message : String(error),
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
77
141
|
messages.push({
|
|
78
142
|
role: "tool",
|
|
79
143
|
content: toolResult.content,
|