@downcity/city 1.1.32 → 1.1.39
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/bin/cli/Index.js +8 -3
- package/bin/cli/Index.js.map +1 -1
- package/bin/cli/agent/Init.d.ts.map +1 -1
- package/bin/cli/agent/Init.js +8 -8
- package/bin/cli/agent/Init.js.map +1 -1
- package/bin/cli/agent/Run.d.ts.map +1 -1
- package/bin/cli/agent/Run.js +8 -14
- package/bin/cli/agent/Run.js.map +1 -1
- package/bin/cli/control-plane/ControlPlaneProcess.d.ts.map +1 -1
- package/bin/cli/control-plane/ControlPlaneProcess.js +2 -3
- package/bin/cli/control-plane/ControlPlaneProcess.js.map +1 -1
- package/bin/cli/shared/ChatAuth.d.ts.map +1 -1
- package/bin/cli/shared/ChatAuth.js +5 -7
- package/bin/cli/shared/ChatAuth.js.map +1 -1
- package/bin/cli/shared/ChatManager.d.ts.map +1 -1
- package/bin/cli/shared/ChatManager.js +2 -3
- package/bin/cli/shared/ChatManager.js.map +1 -1
- package/bin/cli/shared/ManagedPluginActionCommands.d.ts +2 -1
- package/bin/cli/shared/ManagedPluginActionCommands.d.ts.map +1 -1
- package/bin/cli/shared/ManagedPluginActionCommands.js +5 -4
- package/bin/cli/shared/ManagedPluginActionCommands.js.map +1 -1
- package/bin/cli/shared/PluginScheduleCommand.d.ts +3 -2
- package/bin/cli/shared/PluginScheduleCommand.d.ts.map +1 -1
- package/bin/cli/shared/PluginScheduleCommand.js +11 -10
- package/bin/cli/shared/PluginScheduleCommand.js.map +1 -1
- package/bin/cli/shared/PluginTargetSupport.d.ts +4 -4
- package/bin/cli/shared/PluginTargetSupport.d.ts.map +1 -1
- package/bin/cli/shared/PluginTargetSupport.js +4 -5
- package/bin/cli/shared/PluginTargetSupport.js.map +1 -1
- package/bin/cli/shared/Plugins.d.ts.map +1 -1
- package/bin/cli/shared/Plugins.js +12 -6
- package/bin/cli/shared/Plugins.js.map +1 -1
- package/bin/control/ChannelAccountApiRoutes.d.ts.map +1 -1
- package/bin/control/ChannelAccountApiRoutes.js +2 -3
- package/bin/control/ChannelAccountApiRoutes.js.map +1 -1
- package/bin/control/ControlGateway.d.ts.map +1 -1
- package/bin/control/ControlGateway.js +3 -2
- package/bin/control/ControlGateway.js.map +1 -1
- package/bin/control/PluginApiRoutes.d.ts.map +1 -1
- package/bin/control/PluginApiRoutes.js +33 -25
- package/bin/control/PluginApiRoutes.js.map +1 -1
- package/bin/control/gateway/AgentActions.d.ts.map +1 -1
- package/bin/control/gateway/AgentActions.js +16 -11
- package/bin/control/gateway/AgentActions.js.map +1 -1
- package/bin/model/runtime/CreateRuntimeModel.d.ts +2 -10
- package/bin/model/runtime/CreateRuntimeModel.d.ts.map +1 -1
- package/bin/model/runtime/CreateRuntimeModel.js +1 -16
- package/bin/model/runtime/CreateRuntimeModel.js.map +1 -1
- package/bin/model/runtime/ExecutionModelBinding.d.ts +46 -0
- package/bin/model/runtime/ExecutionModelBinding.d.ts.map +1 -0
- package/bin/model/runtime/ExecutionModelBinding.js +96 -0
- package/bin/model/runtime/ExecutionModelBinding.js.map +1 -0
- package/bin/process/registry/AgentHostRuntime.d.ts +1 -9
- package/bin/process/registry/AgentHostRuntime.d.ts.map +1 -1
- package/bin/process/registry/AgentHostRuntime.js +1 -155
- package/bin/process/registry/AgentHostRuntime.js.map +1 -1
- package/package.json +3 -2
- package/src/cli/Index.ts +11 -3
- package/src/cli/agent/Init.ts +9 -8
- package/src/cli/agent/Run.ts +8 -13
- package/src/cli/control-plane/ControlPlaneProcess.ts +2 -3
- package/src/cli/shared/ChatAuth.ts +10 -11
- package/src/cli/shared/ChatManager.ts +2 -3
- package/src/cli/shared/ManagedPluginActionCommands.ts +11 -8
- package/src/cli/shared/PluginScheduleCommand.ts +11 -10
- package/src/cli/shared/PluginTargetSupport.ts +6 -7
- package/src/cli/shared/Plugins.ts +16 -10
- package/src/control/ChannelAccountApiRoutes.ts +2 -3
- package/src/control/ControlGateway.ts +3 -2
- package/src/control/PluginApiRoutes.ts +37 -27
- package/src/control/gateway/AgentActions.ts +16 -11
- package/src/model/runtime/CreateRuntimeModel.ts +1 -26
- package/src/model/runtime/ExecutionModelBinding.ts +120 -0
- package/src/process/registry/AgentHostRuntime.ts +1 -163
- package/tsconfig.json +2 -1
- package/bin/platform/chatAuthorization/Store.d.ts +0 -31
- package/bin/platform/chatAuthorization/Store.d.ts.map +0 -1
- package/bin/platform/chatAuthorization/Store.js +0 -145
- package/bin/platform/chatAuthorization/Store.js.map +0 -1
- package/src/platform/chatAuthorization/Store.ts +0 -181
|
@@ -13,13 +13,13 @@ import type { Command } from "commander";
|
|
|
13
13
|
import prompts from "prompts";
|
|
14
14
|
import {
|
|
15
15
|
buildStaticPluginAvailability,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
listStaticPluginViews,
|
|
16
|
+
findPluginByName,
|
|
17
|
+
listPluginViews,
|
|
18
|
+
listPluginsWithLifecycle,
|
|
19
|
+
listPluginsWithoutLifecycle,
|
|
21
20
|
runLocalPluginAction,
|
|
22
21
|
} from "@downcity/agent";
|
|
22
|
+
import { createBuiltinPlugins } from "@downcity/plugins";
|
|
23
23
|
import { printResult } from "@/utils/cli/CliOutput.js";
|
|
24
24
|
import type { JsonValue } from "@downcity/agent";
|
|
25
25
|
import { getDowncityJsonPath } from "@/config/Paths.js";
|
|
@@ -46,6 +46,10 @@ type StaticCatalogEntry = {
|
|
|
46
46
|
note?: string;
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
+
function createPluginCatalog() {
|
|
50
|
+
return createBuiltinPlugins();
|
|
51
|
+
}
|
|
52
|
+
|
|
49
53
|
async function resolvePluginProjectRoot(options: PluginCliBaseOptions): Promise<{
|
|
50
54
|
projectRoot?: string;
|
|
51
55
|
error?: string;
|
|
@@ -80,6 +84,7 @@ function buildSafeStaticPluginAvailability(pluginName: string): {
|
|
|
80
84
|
} {
|
|
81
85
|
try {
|
|
82
86
|
const availability = buildStaticPluginAvailability({
|
|
87
|
+
plugins: createPluginCatalog(),
|
|
83
88
|
pluginName,
|
|
84
89
|
});
|
|
85
90
|
const normalizedReasons = availability.reasons.map((reason) => {
|
|
@@ -167,7 +172,8 @@ function renderPluginCatalogTable(rows: Array<{
|
|
|
167
172
|
}
|
|
168
173
|
|
|
169
174
|
function listStaticCatalogEntries(): StaticCatalogEntry[] {
|
|
170
|
-
const
|
|
175
|
+
const plugins = createPluginCatalog();
|
|
176
|
+
const managedEntries = listPluginsWithLifecycle(plugins).map((plugin) => ({
|
|
171
177
|
name: plugin.name,
|
|
172
178
|
title: String(plugin.title || plugin.name || "").trim() || plugin.name,
|
|
173
179
|
kind: "managed" as const,
|
|
@@ -179,9 +185,8 @@ function listStaticCatalogEntries(): StaticCatalogEntry[] {
|
|
|
179
185
|
note: "Managed plugin. Use `city plugin start/stop/restart/status` with an agent target for live state.",
|
|
180
186
|
}));
|
|
181
187
|
|
|
182
|
-
const
|
|
183
|
-
const localEntries =
|
|
184
|
-
.filter((plugin) => localNames.has(plugin.name))
|
|
188
|
+
const localPlugins = listPluginsWithoutLifecycle(plugins);
|
|
189
|
+
const localEntries = listPluginViews(localPlugins)
|
|
185
190
|
.map((plugin) => {
|
|
186
191
|
const availability = buildSafeStaticPluginAvailability(plugin.name);
|
|
187
192
|
return {
|
|
@@ -420,7 +425,7 @@ async function runPluginLifecycleCommand(params: {
|
|
|
420
425
|
enabled: boolean;
|
|
421
426
|
asJson?: boolean;
|
|
422
427
|
}): Promise<void> {
|
|
423
|
-
const plugin =
|
|
428
|
+
const plugin = findPluginByName(createPluginCatalog(), params.pluginName);
|
|
424
429
|
if (!plugin) {
|
|
425
430
|
printResult({
|
|
426
431
|
asJson: params.asJson === true,
|
|
@@ -556,6 +561,7 @@ async function runPluginActionCommand(params: {
|
|
|
556
561
|
|
|
557
562
|
const payload = parseCommandPayload(params.payload);
|
|
558
563
|
const local = await runLocalPluginAction({
|
|
564
|
+
plugins: createPluginCatalog(),
|
|
559
565
|
projectRoot: resolved.projectRoot,
|
|
560
566
|
pluginName: params.pluginName,
|
|
561
567
|
actionName: params.actionName,
|
|
@@ -7,15 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { Hono } from "hono";
|
|
10
|
-
import { ChatChannelAccountManager } from "@downcity/
|
|
11
|
-
import { createAgentPlatformRuntime } from "@/process/registry/AgentHostRuntime.js";
|
|
10
|
+
import { ChatChannelAccountManager } from "@downcity/plugins";
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* 注册 Channel Account API 路由。
|
|
15
14
|
*/
|
|
16
15
|
export function registerPlatformChannelAccountRoutes(params: { app: Hono }): void {
|
|
17
16
|
const app = params.app;
|
|
18
|
-
const manager = new ChatChannelAccountManager(
|
|
17
|
+
const manager = new ChatChannelAccountManager();
|
|
19
18
|
|
|
20
19
|
app.get("/api/ui/channel-accounts", async (c) => {
|
|
21
20
|
try {
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
buildPlatformUpstreamUrl,
|
|
41
41
|
forwardPlatformRequest,
|
|
42
42
|
} from "@/control/gateway/Proxy.js";
|
|
43
|
+
import { listPluginAuthPolicies } from "@downcity/agent";
|
|
43
44
|
import type {
|
|
44
45
|
PlatformAgentOption,
|
|
45
46
|
PlatformAgentsResponse,
|
|
@@ -49,7 +50,7 @@ import type {
|
|
|
49
50
|
PlatformLocalModelsResponse,
|
|
50
51
|
} from "@downcity/agent";
|
|
51
52
|
import type { AgentProjectInitializationResult } from "@downcity/agent";
|
|
52
|
-
import {
|
|
53
|
+
import { createBuiltinPlugins } from "@downcity/plugins";
|
|
53
54
|
import { AuthService } from "@/http/auth/AuthService.js";
|
|
54
55
|
import { registerAuthRoutes } from "@/http/auth/AuthRoutes.js";
|
|
55
56
|
import {
|
|
@@ -120,7 +121,7 @@ export class ControlGateway {
|
|
|
120
121
|
this.authService,
|
|
121
122
|
[
|
|
122
123
|
...CONTROL_PLANE_AUTH_ROUTE_POLICIES,
|
|
123
|
-
...
|
|
124
|
+
...listPluginAuthPolicies(createBuiltinPlugins()),
|
|
124
125
|
],
|
|
125
126
|
),
|
|
126
127
|
);
|
|
@@ -9,15 +9,17 @@
|
|
|
9
9
|
|
|
10
10
|
import type { Hono } from "hono";
|
|
11
11
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
findPluginByName,
|
|
13
|
+
listPluginViews,
|
|
14
14
|
runLocalPluginAction,
|
|
15
15
|
} from "@downcity/agent";
|
|
16
|
+
import { createBuiltinPlugins } from "@downcity/plugins";
|
|
16
17
|
import type { PlatformAgentOption } from "@downcity/agent";
|
|
17
18
|
import type {
|
|
18
19
|
PluginActionResult,
|
|
19
20
|
PluginAction,
|
|
20
21
|
PluginAvailability,
|
|
22
|
+
Plugin,
|
|
21
23
|
PluginSetupDefinition,
|
|
22
24
|
PluginUsageDefinition,
|
|
23
25
|
PluginView,
|
|
@@ -75,8 +77,12 @@ function getErrorMessage(error: unknown): string {
|
|
|
75
77
|
return String(error);
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
function createPluginCatalog() {
|
|
81
|
+
return createBuiltinPlugins();
|
|
82
|
+
}
|
|
83
|
+
|
|
78
84
|
function buildPluginActionConfig(
|
|
79
|
-
plugin:
|
|
85
|
+
plugin: Plugin | null,
|
|
80
86
|
): PluginActionConfigItem[] {
|
|
81
87
|
if (!plugin) return [];
|
|
82
88
|
const actions = (plugin.actions || {}) as Record<string, PluginAction>;
|
|
@@ -94,19 +100,19 @@ function buildPluginConfigMap(): Map<string, {
|
|
|
94
100
|
setup?: PluginSetupDefinition;
|
|
95
101
|
usage?: PluginUsageDefinition;
|
|
96
102
|
}> {
|
|
103
|
+
const plugins = createPluginCatalog();
|
|
97
104
|
return new Map(
|
|
98
|
-
|
|
99
|
-
view.name
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
: {}),
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
] as const),
|
|
105
|
+
listPluginViews(plugins).map((view) => {
|
|
106
|
+
const plugin = findPluginByName(plugins, view.name);
|
|
107
|
+
return [
|
|
108
|
+
view.name,
|
|
109
|
+
{
|
|
110
|
+
actions: buildPluginActionConfig(plugin),
|
|
111
|
+
...(plugin?.setup ? { setup: plugin.setup } : {}),
|
|
112
|
+
...(plugin?.usage ? { usage: plugin.usage } : {}),
|
|
113
|
+
},
|
|
114
|
+
] as const;
|
|
115
|
+
}),
|
|
110
116
|
);
|
|
111
117
|
}
|
|
112
118
|
|
|
@@ -114,16 +120,18 @@ function buildGlobalPluginConfigMap(): Map<string, {
|
|
|
114
120
|
actions: PluginActionConfigItem[];
|
|
115
121
|
setup?: PluginSetupDefinition;
|
|
116
122
|
}> {
|
|
123
|
+
const plugins = createPluginCatalog();
|
|
117
124
|
return new Map(
|
|
118
|
-
|
|
119
|
-
view.name
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
: {}),
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
listPluginViews(plugins).map((view) => {
|
|
126
|
+
const plugin = findPluginByName(plugins, view.name);
|
|
127
|
+
return [
|
|
128
|
+
view.name,
|
|
129
|
+
{
|
|
130
|
+
actions: buildPluginActionConfig(plugin),
|
|
131
|
+
...(plugin?.setup ? { setup: plugin.setup } : {}),
|
|
132
|
+
},
|
|
133
|
+
] as const;
|
|
134
|
+
}),
|
|
127
135
|
);
|
|
128
136
|
}
|
|
129
137
|
|
|
@@ -132,7 +140,7 @@ function buildGlobalPluginPayload(): PluginUiResponse {
|
|
|
132
140
|
return {
|
|
133
141
|
success: true,
|
|
134
142
|
runtimeConnected: false,
|
|
135
|
-
plugins:
|
|
143
|
+
plugins: listPluginViews(createPluginCatalog()).map((view) => ({
|
|
136
144
|
...view,
|
|
137
145
|
availability: {
|
|
138
146
|
enabled: isCityPluginEnabled(view.name),
|
|
@@ -157,7 +165,7 @@ function buildAgentPluginPayload(params?: {
|
|
|
157
165
|
success: true,
|
|
158
166
|
runtimeConnected: params?.runtimeConnected === true,
|
|
159
167
|
...(reason ? { runtimeError: reason } : {}),
|
|
160
|
-
plugins:
|
|
168
|
+
plugins: listPluginViews(createPluginCatalog()).map((view) => ({
|
|
161
169
|
...view,
|
|
162
170
|
availability: {
|
|
163
171
|
enabled: isCityPluginEnabled(view.name),
|
|
@@ -282,7 +290,8 @@ async function runGlobalPluginAction(input: {
|
|
|
282
290
|
}): Promise<PluginActionResult<JsonValue>> {
|
|
283
291
|
const pluginName = String(input.pluginName || "").trim();
|
|
284
292
|
const actionName = String(input.actionName || "").trim();
|
|
285
|
-
const
|
|
293
|
+
const plugins = createPluginCatalog();
|
|
294
|
+
const plugin = findPluginByName(plugins, pluginName);
|
|
286
295
|
if (!plugin) {
|
|
287
296
|
return {
|
|
288
297
|
success: false,
|
|
@@ -320,6 +329,7 @@ async function runGlobalPluginAction(input: {
|
|
|
320
329
|
}
|
|
321
330
|
|
|
322
331
|
return runLocalPluginAction({
|
|
332
|
+
plugins,
|
|
323
333
|
projectRoot,
|
|
324
334
|
pluginName: plugin.name,
|
|
325
335
|
actionName,
|
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
} from "@/process/daemon/Manager.js";
|
|
19
19
|
import { buildRunArgsFromOptions } from "@/process/daemon/CliArgs.js";
|
|
20
20
|
import {
|
|
21
|
-
ensureRuntimeExecutionBindingReady,
|
|
22
21
|
initializeAgentProject,
|
|
23
22
|
isAgentProjectInitialized,
|
|
24
23
|
} from "@downcity/agent";
|
|
@@ -33,7 +32,10 @@ import type { AgentProjectInitializationResult } from "@downcity/agent";
|
|
|
33
32
|
import type {
|
|
34
33
|
ExecutionBindingConfig,
|
|
35
34
|
} from "@downcity/agent";
|
|
36
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
assertPlatformModelReady,
|
|
37
|
+
assertProjectExecutionModelReady,
|
|
38
|
+
} from "@/model/runtime/ExecutionModelBinding.js";
|
|
37
39
|
|
|
38
40
|
function resolveExecutionInput(params: {
|
|
39
41
|
modelId?: unknown;
|
|
@@ -70,16 +72,17 @@ export async function initializePlatformAgentProject(params: {
|
|
|
70
72
|
modelId?: unknown;
|
|
71
73
|
forceOverwriteShipJson?: unknown;
|
|
72
74
|
}): Promise<AgentProjectInitializationResult> {
|
|
75
|
+
const execution = resolveExecutionInput({
|
|
76
|
+
modelId: params.modelId,
|
|
77
|
+
});
|
|
78
|
+
assertPlatformModelReady(execution.modelId);
|
|
73
79
|
return initializeAgentProject(
|
|
74
80
|
{
|
|
75
81
|
projectRoot: params.projectRoot,
|
|
76
82
|
agentName: String(params.agentName || "").trim() || undefined,
|
|
77
|
-
execution
|
|
78
|
-
modelId: params.modelId,
|
|
79
|
-
}),
|
|
83
|
+
execution,
|
|
80
84
|
forceOverwriteShipJson: params.forceOverwriteShipJson === true,
|
|
81
85
|
},
|
|
82
|
-
createAgentPlatformRuntime(),
|
|
83
86
|
);
|
|
84
87
|
}
|
|
85
88
|
|
|
@@ -103,6 +106,7 @@ export async function updatePlatformAgentExecution(params: {
|
|
|
103
106
|
if (!modelId) {
|
|
104
107
|
throw new Error("modelId is required");
|
|
105
108
|
}
|
|
109
|
+
assertPlatformModelReady(modelId);
|
|
106
110
|
ship.execution = {
|
|
107
111
|
type: "api",
|
|
108
112
|
modelId,
|
|
@@ -270,16 +274,17 @@ export async function startManagedAgentByProjectRoot(params: {
|
|
|
270
274
|
`Project not ready: ${normalizedRoot}. Required files: PROFILE.md and downcity.json`,
|
|
271
275
|
);
|
|
272
276
|
}
|
|
277
|
+
const execution = resolveExecutionInput({
|
|
278
|
+
modelId: params.initialization?.modelId,
|
|
279
|
+
});
|
|
280
|
+
assertPlatformModelReady(execution.modelId);
|
|
273
281
|
await initializeAgentProject(
|
|
274
282
|
{
|
|
275
283
|
projectRoot: normalizedRoot,
|
|
276
284
|
agentName: String(params.initialization?.agentName || "").trim() || undefined,
|
|
277
|
-
execution
|
|
278
|
-
modelId: params.initialization?.modelId,
|
|
279
|
-
}),
|
|
285
|
+
execution,
|
|
280
286
|
forceOverwriteShipJson: params.initialization?.forceOverwriteShipJson === true,
|
|
281
287
|
},
|
|
282
|
-
createAgentPlatformRuntime(),
|
|
283
288
|
);
|
|
284
289
|
} else {
|
|
285
290
|
const profilePath = getProfileMdPath(normalizedRoot);
|
|
@@ -291,7 +296,7 @@ export async function startManagedAgentByProjectRoot(params: {
|
|
|
291
296
|
}
|
|
292
297
|
}
|
|
293
298
|
|
|
294
|
-
|
|
299
|
+
assertProjectExecutionModelReady(normalizedRoot);
|
|
295
300
|
const args = await buildRunArgsFromOptions(normalizedRoot, {});
|
|
296
301
|
const started = await startDaemonProcess({
|
|
297
302
|
projectRoot: normalizedRoot,
|
|
@@ -19,7 +19,6 @@ import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
|
19
19
|
import type { LanguageModel } from "ai";
|
|
20
20
|
import {
|
|
21
21
|
getLogger,
|
|
22
|
-
type AgentPlatformRuntime,
|
|
23
22
|
type DowncityConfig,
|
|
24
23
|
type LlmProviderType,
|
|
25
24
|
type StoredModel,
|
|
@@ -55,14 +54,6 @@ type RuntimeModelFactoryInput = {
|
|
|
55
54
|
* - 仅用于把 sessionId 透传到 LLM 请求日志元数据。
|
|
56
55
|
*/
|
|
57
56
|
getSessionRunScope?: () => ModelLogContext | undefined;
|
|
58
|
-
/**
|
|
59
|
-
* 可选宿主平台能力。
|
|
60
|
-
*
|
|
61
|
-
* 关键点(中文)
|
|
62
|
-
* - 若传入,则优先通过平台端口读取 provider/model。
|
|
63
|
-
* - 未传入时回退到 city 自己的 `PlatformStore`。
|
|
64
|
-
*/
|
|
65
|
-
platform?: AgentPlatformRuntime;
|
|
66
57
|
};
|
|
67
58
|
|
|
68
59
|
function readProjectExecutionBinding(
|
|
@@ -245,22 +236,6 @@ async function resolveConfiguredModel(input: RuntimeModelFactoryInput & {
|
|
|
245
236
|
model: StoredModel;
|
|
246
237
|
provider: StoredModelProvider;
|
|
247
238
|
}> {
|
|
248
|
-
const platform = input.platform;
|
|
249
|
-
if (platform) {
|
|
250
|
-
const model = platform.getModel(input.primaryModelId);
|
|
251
|
-
const providers = await (platform.listProviders?.() || Promise.resolve([]));
|
|
252
|
-
const providerMap = new Map(providers.map((item) => [item.id, item] as const));
|
|
253
|
-
const provider = model
|
|
254
|
-
? providerMap.get(String(model.providerId || "").trim()) || null
|
|
255
|
-
: null;
|
|
256
|
-
if (model && provider) {
|
|
257
|
-
return {
|
|
258
|
-
model,
|
|
259
|
-
provider,
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
239
|
const store = new PlatformStore();
|
|
265
240
|
try {
|
|
266
241
|
const resolved = await store.getResolvedModel(input.primaryModelId);
|
|
@@ -280,7 +255,7 @@ async function resolveConfiguredModel(input: RuntimeModelFactoryInput & {
|
|
|
280
255
|
*
|
|
281
256
|
* 解析策略(中文)
|
|
282
257
|
* 1) 读取 `execution.modelId`。
|
|
283
|
-
* 2)
|
|
258
|
+
* 2) 从 city 平台模型池解析 provider/model。
|
|
284
259
|
* 3) 按 provider type 分发到对应 AI SDK 工厂。
|
|
285
260
|
*/
|
|
286
261
|
export async function createRuntimeModel(
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExecutionModelBinding:city 宿主侧执行模型绑定辅助。
|
|
3
|
+
*
|
|
4
|
+
* 职责说明(中文)
|
|
5
|
+
* - 统一承接平台模型池读取、模型候选列表构建与项目 execution.modelId 校验。
|
|
6
|
+
* - 保证 `Agent` 只接收最终 `LanguageModel`,不再承担模型池查询职责。
|
|
7
|
+
* - 让 CLI、control gateway、前台启动等宿主入口复用同一套模型绑定规则。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from "fs-extra";
|
|
11
|
+
import type { DowncityConfig, StoredModelProvider } from "@downcity/agent";
|
|
12
|
+
import { assertProjectExecutionTarget } from "@downcity/agent";
|
|
13
|
+
import { getDowncityJsonPath } from "@/config/Paths.js";
|
|
14
|
+
import { PlatformStore } from "@/platform/store/index.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 平台模型下拉候选项。
|
|
18
|
+
*/
|
|
19
|
+
export interface PlatformModelChoice {
|
|
20
|
+
/**
|
|
21
|
+
* 下拉展示文案。
|
|
22
|
+
*/
|
|
23
|
+
title: string;
|
|
24
|
+
/**
|
|
25
|
+
* 实际写入 `execution.modelId` 的模型 ID。
|
|
26
|
+
*/
|
|
27
|
+
value: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 读取平台模型候选列表。
|
|
32
|
+
*
|
|
33
|
+
* 关键点(中文)
|
|
34
|
+
* - 输出结果面向 CLI/Console 的模型选择界面。
|
|
35
|
+
* - provider 信息会拼到标题中,便于区分同名模型。
|
|
36
|
+
*/
|
|
37
|
+
export async function listPlatformModelChoices(): Promise<PlatformModelChoice[]> {
|
|
38
|
+
const store = new PlatformStore();
|
|
39
|
+
try {
|
|
40
|
+
const models = store.listModels();
|
|
41
|
+
const providers = await store.listProviders();
|
|
42
|
+
const providerMap = new Map(providers.map((item) => [item.id, item] as const));
|
|
43
|
+
return models
|
|
44
|
+
.map((item) => buildPlatformModelChoice(item.id, item.providerId, providerMap))
|
|
45
|
+
.filter((item): item is PlatformModelChoice => item !== null);
|
|
46
|
+
} finally {
|
|
47
|
+
store.close();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function buildPlatformModelChoice(
|
|
52
|
+
modelId: string,
|
|
53
|
+
providerId: string,
|
|
54
|
+
providerMap: Map<string, StoredModelProvider>,
|
|
55
|
+
): PlatformModelChoice | null {
|
|
56
|
+
const id = String(modelId || "").trim();
|
|
57
|
+
if (!id) return null;
|
|
58
|
+
const normalizedProviderId = String(providerId || "").trim();
|
|
59
|
+
const providerType = String(providerMap.get(normalizedProviderId)?.type || "").trim();
|
|
60
|
+
const providerLabel = normalizedProviderId
|
|
61
|
+
? providerType
|
|
62
|
+
? `${normalizedProviderId} (${providerType})`
|
|
63
|
+
: normalizedProviderId
|
|
64
|
+
: "-";
|
|
65
|
+
return {
|
|
66
|
+
title: `${id} · ${providerLabel}`,
|
|
67
|
+
value: id,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 断言指定平台模型可用于 agent execution。
|
|
73
|
+
*
|
|
74
|
+
* 关键点(中文)
|
|
75
|
+
* - 当前只校验“存在且未暂停”。
|
|
76
|
+
* - 供应商连通性与 API Key 可用性仍交给真正创建模型实例时再校验。
|
|
77
|
+
*/
|
|
78
|
+
export function assertPlatformModelReady(modelId: string): void {
|
|
79
|
+
const normalizedModelId = String(modelId || "").trim();
|
|
80
|
+
if (!normalizedModelId) {
|
|
81
|
+
throw new Error("execution.modelId is required");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const store = new PlatformStore();
|
|
85
|
+
try {
|
|
86
|
+
const model = store.getModel(normalizedModelId);
|
|
87
|
+
if (!model) {
|
|
88
|
+
throw new Error(`Model not found in platform model pool: ${normalizedModelId}`);
|
|
89
|
+
}
|
|
90
|
+
if (model.isPaused === true) {
|
|
91
|
+
throw new Error(`Model is paused: ${normalizedModelId}`);
|
|
92
|
+
}
|
|
93
|
+
} finally {
|
|
94
|
+
store.close();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 断言项目 execution 绑定已声明且目标模型可用。
|
|
100
|
+
*
|
|
101
|
+
* 关键点(中文)
|
|
102
|
+
* - 这里是 city 启动/控制面入口的宿主前置校验。
|
|
103
|
+
* - 失败时抛出稳定错误,交由 CLI 或 HTTP 层决定如何展示。
|
|
104
|
+
*/
|
|
105
|
+
export function assertProjectExecutionModelReady(projectRoot: string): void {
|
|
106
|
+
const config = readProjectDowncityConfig(projectRoot);
|
|
107
|
+
assertProjectExecutionTarget(config);
|
|
108
|
+
const primaryModelId = String(config.execution?.type === "api" ? config.execution.modelId || "" : "").trim();
|
|
109
|
+
if (!primaryModelId) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
'Invalid downcity.json: "execution" is required and must be { "type": "api", "modelId": "..." }',
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
assertPlatformModelReady(primaryModelId);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function readProjectDowncityConfig(projectRoot: string): DowncityConfig {
|
|
118
|
+
const shipJsonPath = getDowncityJsonPath(projectRoot);
|
|
119
|
+
return fs.readJsonSync(shipJsonPath) as DowncityConfig;
|
|
120
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 关键点(中文)
|
|
5
5
|
* - `main/agent/*` 负责创建这些宿主能力对象,再注入到 AgentRuntime。
|
|
6
6
|
* - plugin runtimes / session / plugins 只消费这些对象,不再直接 import `main/*`。
|
|
7
|
-
* - 当前由 city
|
|
7
|
+
* - 当前由 city 在这里统一装配路径与 plugin 配置持久化两类宿主对象。
|
|
8
8
|
*/
|
|
9
9
|
import {
|
|
10
10
|
getCacheDirPath,
|
|
@@ -21,76 +21,9 @@ import {
|
|
|
21
21
|
import { persistProjectPluginConfig } from "@downcity/agent";
|
|
22
22
|
import type {
|
|
23
23
|
AgentPathRuntime,
|
|
24
|
-
AgentPlatformRuntime,
|
|
25
24
|
AgentPluginConfigRuntime,
|
|
26
|
-
ChatChannelAccountListItem,
|
|
27
|
-
StoredChannelAccount,
|
|
28
|
-
StoredChannelAccountChannel,
|
|
29
25
|
} from "@downcity/agent";
|
|
30
26
|
import type { DowncityConfig } from "@downcity/agent";
|
|
31
|
-
import { PlatformStore } from "@/platform/store/index.js";
|
|
32
|
-
import {
|
|
33
|
-
isCityPluginEnabled,
|
|
34
|
-
setCityPluginEnabled,
|
|
35
|
-
} from "@/platform/PluginLifecycle.js";
|
|
36
|
-
import {
|
|
37
|
-
readChatAuthorizationConfigSync,
|
|
38
|
-
setChatAuthorizationUserRole,
|
|
39
|
-
writeChatAuthorizationConfig,
|
|
40
|
-
} from "@/platform/chatAuthorization/Store.js";
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* 脱敏显示密钥。
|
|
44
|
-
*
|
|
45
|
-
* 关键点(中文)
|
|
46
|
-
* - channel account 列表只返回安全视图,避免在 CLI/UI 展示链路泄露明文。
|
|
47
|
-
* - 保持和现有 model provider 脱敏规则一致,减少用户心智负担。
|
|
48
|
-
*/
|
|
49
|
-
function maskSecret(value: string | undefined): string | undefined {
|
|
50
|
-
const raw = String(value || "").trim();
|
|
51
|
-
if (!raw) return undefined;
|
|
52
|
-
if (raw.length <= 8) return "***";
|
|
53
|
-
return `${raw.slice(0, 4)}***${raw.slice(-4)}`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* 将存储层 channel account 转成对外安全视图。
|
|
58
|
-
*/
|
|
59
|
-
function toChannelAccountListItem(
|
|
60
|
-
account: StoredChannelAccount,
|
|
61
|
-
): ChatChannelAccountListItem {
|
|
62
|
-
return {
|
|
63
|
-
id: account.id,
|
|
64
|
-
channel: account.channel,
|
|
65
|
-
name: account.name,
|
|
66
|
-
identity: account.identity,
|
|
67
|
-
owner: account.owner,
|
|
68
|
-
creator: account.creator,
|
|
69
|
-
domain: account.domain,
|
|
70
|
-
sandbox: account.sandbox === true,
|
|
71
|
-
hasBotToken: !!String(account.botToken || "").trim(),
|
|
72
|
-
hasAppId: !!String(account.appId || "").trim(),
|
|
73
|
-
hasAppSecret: !!String(account.appSecret || "").trim(),
|
|
74
|
-
botTokenMasked: maskSecret(account.botToken),
|
|
75
|
-
appIdMasked: maskSecret(account.appId),
|
|
76
|
-
appSecretMasked: maskSecret(account.appSecret),
|
|
77
|
-
createdAt: account.createdAt,
|
|
78
|
-
updatedAt: account.updatedAt,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* 归一化并校验 channel account 渠道名。
|
|
84
|
-
*/
|
|
85
|
-
function normalizeChannelAccountChannel(
|
|
86
|
-
channelInput: string,
|
|
87
|
-
): StoredChannelAccountChannel {
|
|
88
|
-
const channel = String(channelInput || "").trim().toLowerCase();
|
|
89
|
-
if (channel === "telegram" || channel === "feishu" || channel === "qq") {
|
|
90
|
-
return channel;
|
|
91
|
-
}
|
|
92
|
-
throw new Error(`Unsupported channel account channel: ${channelInput}`);
|
|
93
|
-
}
|
|
94
27
|
|
|
95
28
|
/**
|
|
96
29
|
* 创建当前项目的路径能力集合。
|
|
@@ -134,98 +67,3 @@ export function createAgentPluginConfigRuntime(projectRoot: string): AgentPlugin
|
|
|
134
67
|
},
|
|
135
68
|
};
|
|
136
69
|
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* 创建当前项目的平台能力集合。
|
|
140
|
-
*
|
|
141
|
-
* 关键点(中文)
|
|
142
|
-
* - 这里先暴露 agent 当前已经需要的最小平台读能力。
|
|
143
|
-
* - 具体平台数据仍由 city 自己管理,agent 只消费这一层接口。
|
|
144
|
-
*/
|
|
145
|
-
export function createAgentPlatformRuntime(): AgentPlatformRuntime {
|
|
146
|
-
return {
|
|
147
|
-
getGlobalEnv: () => {
|
|
148
|
-
const store = new PlatformStore();
|
|
149
|
-
try {
|
|
150
|
-
return store.getGlobalEnvMapSync();
|
|
151
|
-
} finally {
|
|
152
|
-
store.close();
|
|
153
|
-
}
|
|
154
|
-
},
|
|
155
|
-
getAgentEnv: (projectRoot) => {
|
|
156
|
-
const store = new PlatformStore();
|
|
157
|
-
try {
|
|
158
|
-
return store.getAgentEnvMapSync(projectRoot);
|
|
159
|
-
} finally {
|
|
160
|
-
store.close();
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
listModels: () => {
|
|
164
|
-
const store = new PlatformStore();
|
|
165
|
-
try {
|
|
166
|
-
return store.listModels();
|
|
167
|
-
} finally {
|
|
168
|
-
store.close();
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
listProviders: async () => {
|
|
172
|
-
const store = new PlatformStore();
|
|
173
|
-
try {
|
|
174
|
-
return await store.listProviders();
|
|
175
|
-
} finally {
|
|
176
|
-
store.close();
|
|
177
|
-
}
|
|
178
|
-
},
|
|
179
|
-
getModel: (modelId) => {
|
|
180
|
-
const store = new PlatformStore();
|
|
181
|
-
try {
|
|
182
|
-
return store.getModel(modelId);
|
|
183
|
-
} finally {
|
|
184
|
-
store.close();
|
|
185
|
-
}
|
|
186
|
-
},
|
|
187
|
-
getChannelAccount: (channelAccountId) => {
|
|
188
|
-
const store = new PlatformStore();
|
|
189
|
-
try {
|
|
190
|
-
return store.getChannelAccountSync(channelAccountId);
|
|
191
|
-
} finally {
|
|
192
|
-
store.close();
|
|
193
|
-
}
|
|
194
|
-
},
|
|
195
|
-
listChannelAccounts: async () => {
|
|
196
|
-
const store = new PlatformStore();
|
|
197
|
-
try {
|
|
198
|
-
const accounts = await store.listChannelAccounts();
|
|
199
|
-
return accounts.map(toChannelAccountListItem);
|
|
200
|
-
} finally {
|
|
201
|
-
store.close();
|
|
202
|
-
}
|
|
203
|
-
},
|
|
204
|
-
updateChannelAccount: async (input) => {
|
|
205
|
-
const store = new PlatformStore();
|
|
206
|
-
try {
|
|
207
|
-
await store.upsertChannelAccount({
|
|
208
|
-
...input,
|
|
209
|
-
channel: normalizeChannelAccountChannel(input.channel),
|
|
210
|
-
});
|
|
211
|
-
} finally {
|
|
212
|
-
store.close();
|
|
213
|
-
}
|
|
214
|
-
},
|
|
215
|
-
removeChannelAccount: async (channelAccountId) => {
|
|
216
|
-
const store = new PlatformStore();
|
|
217
|
-
try {
|
|
218
|
-
store.removeChannelAccount(channelAccountId);
|
|
219
|
-
} finally {
|
|
220
|
-
store.close();
|
|
221
|
-
}
|
|
222
|
-
},
|
|
223
|
-
readChatAuthorizationConfig: (projectRoot) => readChatAuthorizationConfigSync(projectRoot),
|
|
224
|
-
writeChatAuthorizationConfig,
|
|
225
|
-
setChatAuthorizationUserRole,
|
|
226
|
-
isPluginEnabled: (pluginName) => isCityPluginEnabled(pluginName),
|
|
227
|
-
setPluginEnabled: (pluginName, enabled) => {
|
|
228
|
-
setCityPluginEnabled(pluginName, enabled);
|
|
229
|
-
},
|
|
230
|
-
};
|
|
231
|
-
}
|