@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.
Files changed (80) hide show
  1. package/bin/cli/Index.js +8 -3
  2. package/bin/cli/Index.js.map +1 -1
  3. package/bin/cli/agent/Init.d.ts.map +1 -1
  4. package/bin/cli/agent/Init.js +8 -8
  5. package/bin/cli/agent/Init.js.map +1 -1
  6. package/bin/cli/agent/Run.d.ts.map +1 -1
  7. package/bin/cli/agent/Run.js +8 -14
  8. package/bin/cli/agent/Run.js.map +1 -1
  9. package/bin/cli/control-plane/ControlPlaneProcess.d.ts.map +1 -1
  10. package/bin/cli/control-plane/ControlPlaneProcess.js +2 -3
  11. package/bin/cli/control-plane/ControlPlaneProcess.js.map +1 -1
  12. package/bin/cli/shared/ChatAuth.d.ts.map +1 -1
  13. package/bin/cli/shared/ChatAuth.js +5 -7
  14. package/bin/cli/shared/ChatAuth.js.map +1 -1
  15. package/bin/cli/shared/ChatManager.d.ts.map +1 -1
  16. package/bin/cli/shared/ChatManager.js +2 -3
  17. package/bin/cli/shared/ChatManager.js.map +1 -1
  18. package/bin/cli/shared/ManagedPluginActionCommands.d.ts +2 -1
  19. package/bin/cli/shared/ManagedPluginActionCommands.d.ts.map +1 -1
  20. package/bin/cli/shared/ManagedPluginActionCommands.js +5 -4
  21. package/bin/cli/shared/ManagedPluginActionCommands.js.map +1 -1
  22. package/bin/cli/shared/PluginScheduleCommand.d.ts +3 -2
  23. package/bin/cli/shared/PluginScheduleCommand.d.ts.map +1 -1
  24. package/bin/cli/shared/PluginScheduleCommand.js +11 -10
  25. package/bin/cli/shared/PluginScheduleCommand.js.map +1 -1
  26. package/bin/cli/shared/PluginTargetSupport.d.ts +4 -4
  27. package/bin/cli/shared/PluginTargetSupport.d.ts.map +1 -1
  28. package/bin/cli/shared/PluginTargetSupport.js +4 -5
  29. package/bin/cli/shared/PluginTargetSupport.js.map +1 -1
  30. package/bin/cli/shared/Plugins.d.ts.map +1 -1
  31. package/bin/cli/shared/Plugins.js +12 -6
  32. package/bin/cli/shared/Plugins.js.map +1 -1
  33. package/bin/control/ChannelAccountApiRoutes.d.ts.map +1 -1
  34. package/bin/control/ChannelAccountApiRoutes.js +2 -3
  35. package/bin/control/ChannelAccountApiRoutes.js.map +1 -1
  36. package/bin/control/ControlGateway.d.ts.map +1 -1
  37. package/bin/control/ControlGateway.js +3 -2
  38. package/bin/control/ControlGateway.js.map +1 -1
  39. package/bin/control/PluginApiRoutes.d.ts.map +1 -1
  40. package/bin/control/PluginApiRoutes.js +33 -25
  41. package/bin/control/PluginApiRoutes.js.map +1 -1
  42. package/bin/control/gateway/AgentActions.d.ts.map +1 -1
  43. package/bin/control/gateway/AgentActions.js +16 -11
  44. package/bin/control/gateway/AgentActions.js.map +1 -1
  45. package/bin/model/runtime/CreateRuntimeModel.d.ts +2 -10
  46. package/bin/model/runtime/CreateRuntimeModel.d.ts.map +1 -1
  47. package/bin/model/runtime/CreateRuntimeModel.js +1 -16
  48. package/bin/model/runtime/CreateRuntimeModel.js.map +1 -1
  49. package/bin/model/runtime/ExecutionModelBinding.d.ts +46 -0
  50. package/bin/model/runtime/ExecutionModelBinding.d.ts.map +1 -0
  51. package/bin/model/runtime/ExecutionModelBinding.js +96 -0
  52. package/bin/model/runtime/ExecutionModelBinding.js.map +1 -0
  53. package/bin/process/registry/AgentHostRuntime.d.ts +1 -9
  54. package/bin/process/registry/AgentHostRuntime.d.ts.map +1 -1
  55. package/bin/process/registry/AgentHostRuntime.js +1 -155
  56. package/bin/process/registry/AgentHostRuntime.js.map +1 -1
  57. package/package.json +3 -2
  58. package/src/cli/Index.ts +11 -3
  59. package/src/cli/agent/Init.ts +9 -8
  60. package/src/cli/agent/Run.ts +8 -13
  61. package/src/cli/control-plane/ControlPlaneProcess.ts +2 -3
  62. package/src/cli/shared/ChatAuth.ts +10 -11
  63. package/src/cli/shared/ChatManager.ts +2 -3
  64. package/src/cli/shared/ManagedPluginActionCommands.ts +11 -8
  65. package/src/cli/shared/PluginScheduleCommand.ts +11 -10
  66. package/src/cli/shared/PluginTargetSupport.ts +6 -7
  67. package/src/cli/shared/Plugins.ts +16 -10
  68. package/src/control/ChannelAccountApiRoutes.ts +2 -3
  69. package/src/control/ControlGateway.ts +3 -2
  70. package/src/control/PluginApiRoutes.ts +37 -27
  71. package/src/control/gateway/AgentActions.ts +16 -11
  72. package/src/model/runtime/CreateRuntimeModel.ts +1 -26
  73. package/src/model/runtime/ExecutionModelBinding.ts +120 -0
  74. package/src/process/registry/AgentHostRuntime.ts +1 -163
  75. package/tsconfig.json +2 -1
  76. package/bin/platform/chatAuthorization/Store.d.ts +0 -31
  77. package/bin/platform/chatAuthorization/Store.d.ts.map +0 -1
  78. package/bin/platform/chatAuthorization/Store.js +0 -145
  79. package/bin/platform/chatAuthorization/Store.js.map +0 -1
  80. 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
- findBuiltinPlugin,
17
- findStaticPluginView,
18
- listLocalPlugins,
19
- listManagedPlugins,
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 managedEntries = listManagedPlugins().map((plugin) => ({
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 localNames = new Set(listLocalPlugins().map((plugin) => plugin.name));
183
- const localEntries = listStaticPluginViews()
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 = findBuiltinPlugin(params.pluginName);
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/agent";
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(createAgentPlatformRuntime());
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 { listBuiltinPluginAuthPolicies } from "@downcity/agent";
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
- ...listBuiltinPluginAuthPolicies(),
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
- findBuiltinPlugin,
13
- listStaticPluginViews,
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: ReturnType<typeof findBuiltinPlugin>,
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
- listStaticPluginViews().map((view) => [
99
- view.name,
100
- {
101
- actions: buildPluginActionConfig(findBuiltinPlugin(view.name)),
102
- ...(findBuiltinPlugin(view.name)?.setup
103
- ? { setup: findBuiltinPlugin(view.name)?.setup }
104
- : {}),
105
- ...(findBuiltinPlugin(view.name)?.usage
106
- ? { usage: findBuiltinPlugin(view.name)?.usage }
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
- listStaticPluginViews().map((view) => [
119
- view.name,
120
- {
121
- actions: buildPluginActionConfig(findBuiltinPlugin(view.name)),
122
- ...(findBuiltinPlugin(view.name)?.setup
123
- ? { setup: findBuiltinPlugin(view.name)?.setup }
124
- : {}),
125
- },
126
- ] as const),
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: listStaticPluginViews().map((view) => ({
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: listStaticPluginViews().map((view) => ({
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 plugin = findBuiltinPlugin(pluginName);
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 { createAgentPlatformRuntime } from "@/process/registry/AgentHostRuntime.js";
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: resolveExecutionInput({
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: resolveExecutionInput({
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
- ensureRuntimeExecutionBindingReady(normalizedRoot, createAgentPlatformRuntime());
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) 从宿主平台或 `PlatformStore` 解析 provider/model。
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 在这里统一装配路径、plugin 配置持久化与平台能力三类宿主对象。
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
- }