@downcity/city 1.1.12 → 1.1.17
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 +21 -3
- package/bin/cli/Index.js +44 -0
- package/bin/cli/Index.js.map +1 -1
- package/bin/cli/agent/Run.d.ts.map +1 -1
- package/bin/cli/agent/Run.js +3 -3
- package/bin/cli/agent/Run.js.map +1 -1
- package/bin/cli/model/ModelManageCommand.js +2 -2
- package/bin/cli/model/ModelManageCommand.js.map +1 -1
- package/bin/cli/model/ModelManager.js +2 -2
- package/bin/cli/model/ModelManager.js.map +1 -1
- package/bin/cli/shared/IndexAgentCommand.d.ts +2 -0
- package/bin/cli/shared/IndexAgentCommand.d.ts.map +1 -1
- package/bin/cli/shared/IndexAgentCommand.js +1 -0
- package/bin/cli/shared/IndexAgentCommand.js.map +1 -1
- package/bin/control/ModelPoolService.js +2 -2
- package/bin/control/ModelPoolService.js.map +1 -1
- package/bin/control/instant/InstantSessionService.d.ts.map +1 -1
- package/bin/control/instant/InstantSessionService.js +10 -4
- package/bin/control/instant/InstantSessionService.js.map +1 -1
- package/bin/control/instant/InstantSystemComposer.d.ts +2 -2
- package/bin/control/instant/InstantSystemComposer.d.ts.map +1 -1
- package/bin/control/instant/InstantSystemComposer.js +2 -3
- package/bin/control/instant/InstantSystemComposer.js.map +1 -1
- package/bin/model/runtime/CreateRuntimeModel.d.ts +52 -0
- package/bin/model/runtime/CreateRuntimeModel.d.ts.map +1 -0
- package/bin/model/runtime/CreateRuntimeModel.js +371 -0
- package/bin/model/runtime/CreateRuntimeModel.js.map +1 -0
- package/package.json +2 -2
- package/src/cli/Index.ts +52 -0
- package/src/cli/agent/Run.ts +2 -3
- package/src/cli/model/ModelManageCommand.ts +2 -2
- package/src/cli/model/ModelManager.ts +2 -2
- package/src/cli/shared/IndexAgentCommand.ts +3 -0
- package/src/control/ModelPoolService.ts +2 -2
- package/src/control/instant/InstantSessionService.ts +11 -4
- package/src/control/instant/InstantSystemComposer.ts +2 -3
- package/src/model/runtime/CreateRuntimeModel.ts +462 -0
|
@@ -42,6 +42,8 @@ import {
|
|
|
42
42
|
export interface AgentCommandRegistrationContext {
|
|
43
43
|
/** 当前 CLI 版本号。 */
|
|
44
44
|
version: string;
|
|
45
|
+
/** 当前 city 绑定的 agent runtime 版本号。 */
|
|
46
|
+
agentVersion: string;
|
|
45
47
|
/** commander 的隐藏 Option 构造器。 */
|
|
46
48
|
hiddenPortOption: typeof Option;
|
|
47
49
|
}
|
|
@@ -56,6 +58,7 @@ export function registerAgentCommands(
|
|
|
56
58
|
const agent = program
|
|
57
59
|
.command("agent")
|
|
58
60
|
.description("管理 Agent:创建/列出/启停/重启(无参数时启动交互式管理器)")
|
|
61
|
+
.version(`city ${context.version} (agent ${context.agentVersion})`, "-v, --version")
|
|
59
62
|
.helpOption("--help", "display help for command")
|
|
60
63
|
.action(createVersionBanner(context.version, async () => {
|
|
61
64
|
if (process.stdin.isTTY === true && process.stdout.isTTY === true) {
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
import { generateText } from "ai";
|
|
11
11
|
import type { LlmProviderType } from "@downcity/agent";
|
|
12
12
|
import { PlatformStore } from "@/platform/store/index.js";
|
|
13
|
-
import { createModel } from "@downcity/agent";
|
|
14
13
|
import { discoverProviderModels } from "@/cli/model/ModelSupport.js";
|
|
14
|
+
import { createRuntimeModel } from "@/model/runtime/CreateRuntimeModel.js";
|
|
15
15
|
|
|
16
16
|
const SUPPORTED_PROVIDER_TYPES: readonly LlmProviderType[] = [
|
|
17
17
|
"anthropic",
|
|
@@ -375,7 +375,7 @@ export class ModelPoolService {
|
|
|
375
375
|
const id = String(modelId || "").trim();
|
|
376
376
|
if (!id) throw new Error("modelId cannot be empty");
|
|
377
377
|
const actualPrompt = String(prompt || "").trim() || "Reply with exactly: OK";
|
|
378
|
-
const model = await
|
|
378
|
+
const model = await createRuntimeModel({
|
|
379
379
|
config: {
|
|
380
380
|
name: "console-model-test",
|
|
381
381
|
version: "1.0.0",
|
|
@@ -13,12 +13,12 @@ import os from "node:os";
|
|
|
13
13
|
import { mkdtemp } from "node:fs/promises";
|
|
14
14
|
import { generateId } from "@/utils/Id.js";
|
|
15
15
|
import {
|
|
16
|
-
createModel,
|
|
17
16
|
drainDeferredPersistedUserMessages,
|
|
18
17
|
Executor,
|
|
19
18
|
getLogger,
|
|
20
19
|
JsonlSessionCompactionComposer,
|
|
21
20
|
JsonlSessionHistoryComposer,
|
|
21
|
+
JsonlSessionHistoryStore,
|
|
22
22
|
loadStaticSystemPrompts,
|
|
23
23
|
pickLastSuccessfulChatSendText,
|
|
24
24
|
resolveAssistantMessageForPersistence,
|
|
@@ -31,6 +31,7 @@ import type {
|
|
|
31
31
|
PlatformInlineInstantService,
|
|
32
32
|
} from "@downcity/agent";
|
|
33
33
|
import { InstantSystemComposer } from "@/control/instant/InstantSystemComposer.js";
|
|
34
|
+
import { createRuntimeModel } from "@/model/runtime/CreateRuntimeModel.js";
|
|
34
35
|
import type { Logger as AgentLogger } from "@downcity/agent";
|
|
35
36
|
|
|
36
37
|
type InstantSessionServiceOptions = {
|
|
@@ -138,12 +139,13 @@ export class InstantSessionService implements PlatformInlineInstantService {
|
|
|
138
139
|
sessionId: string;
|
|
139
140
|
}): Promise<{
|
|
140
141
|
tempDirPath: string;
|
|
142
|
+
historyStore: JsonlSessionHistoryStore;
|
|
141
143
|
historyComposer: JsonlSessionHistoryComposer;
|
|
142
144
|
}> {
|
|
143
145
|
const tempDirPath = await mkdtemp(
|
|
144
146
|
path.join(os.tmpdir(), "downcity-inline-instant-"),
|
|
145
147
|
);
|
|
146
|
-
const
|
|
148
|
+
const historyStore = new JsonlSessionHistoryStore({
|
|
147
149
|
rootPath: params.rootPath,
|
|
148
150
|
sessionId: params.sessionId,
|
|
149
151
|
paths: {
|
|
@@ -154,8 +156,12 @@ export class InstantSessionService implements PlatformInlineInstantService {
|
|
|
154
156
|
archiveDirPath: path.join(tempDirPath, "archive"),
|
|
155
157
|
},
|
|
156
158
|
});
|
|
159
|
+
const historyComposer = new JsonlSessionHistoryComposer({
|
|
160
|
+
store: historyStore,
|
|
161
|
+
});
|
|
157
162
|
return {
|
|
158
163
|
tempDirPath,
|
|
164
|
+
historyStore,
|
|
159
165
|
historyComposer,
|
|
160
166
|
};
|
|
161
167
|
}
|
|
@@ -220,11 +226,11 @@ export class InstantSessionService implements PlatformInlineInstantService {
|
|
|
220
226
|
|
|
221
227
|
const sessionId = this.buildSessionId();
|
|
222
228
|
const rootPath = process.cwd();
|
|
223
|
-
const { tempDirPath, historyComposer } = await this.createTempHistoryComposer({
|
|
229
|
+
const { tempDirPath, historyStore, historyComposer } = await this.createTempHistoryComposer({
|
|
224
230
|
rootPath,
|
|
225
231
|
sessionId,
|
|
226
232
|
});
|
|
227
|
-
const model = await
|
|
233
|
+
const model = await createRuntimeModel({
|
|
228
234
|
config: {
|
|
229
235
|
name: "console-inline-instant-model",
|
|
230
236
|
version: "1.0.0",
|
|
@@ -242,6 +248,7 @@ export class InstantSessionService implements PlatformInlineInstantService {
|
|
|
242
248
|
|
|
243
249
|
const session = new Executor({
|
|
244
250
|
sessionId,
|
|
251
|
+
historyStore,
|
|
245
252
|
historyComposer,
|
|
246
253
|
getModel: () => model,
|
|
247
254
|
logger: this.logger as unknown as AgentLogger,
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
11
|
getSessionRunScope,
|
|
12
|
-
SessionSystemComposer,
|
|
13
12
|
transformPromptsIntoSystemMessages,
|
|
14
13
|
} from "@downcity/agent";
|
|
14
|
+
import type { SessionSystemComposer } from "@downcity/agent";
|
|
15
15
|
|
|
16
16
|
type InstantSystemComposerOptions = {
|
|
17
17
|
/**
|
|
@@ -31,14 +31,13 @@ type InstantSystemComposerOptions = {
|
|
|
31
31
|
/**
|
|
32
32
|
* 即时模式 system composer 默认实现。
|
|
33
33
|
*/
|
|
34
|
-
export class InstantSystemComposer
|
|
34
|
+
export class InstantSystemComposer implements SessionSystemComposer {
|
|
35
35
|
readonly name = "inline_instant_system";
|
|
36
36
|
|
|
37
37
|
private readonly prompts: string[];
|
|
38
38
|
private readonly projectRoot: string;
|
|
39
39
|
|
|
40
40
|
constructor(options: InstantSystemComposerOptions) {
|
|
41
|
-
super();
|
|
42
41
|
this.prompts = Array.isArray(options.prompts)
|
|
43
42
|
? options.prompts
|
|
44
43
|
.map((item) => String(item || "").trim())
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CreateRuntimeModel:city 宿主侧 LanguageModel 工厂。
|
|
3
|
+
*
|
|
4
|
+
* 关键点(中文)
|
|
5
|
+
* - `@downcity/agent` 只消费 `LanguageModel`,不再负责模型池解析。
|
|
6
|
+
* - `city` 负责把 `execution.modelId` 解析成平台模型池中的 provider/model 配置。
|
|
7
|
+
* - 这里统一承接 CLI、control plane、inline instant 等宿主场景的模型创建逻辑。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
11
|
+
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
12
|
+
import { createHuggingFace } from "@ai-sdk/huggingface";
|
|
13
|
+
import { createMoonshotAI } from "@ai-sdk/moonshotai";
|
|
14
|
+
import { createOpenResponses } from "@ai-sdk/open-responses";
|
|
15
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
16
|
+
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
17
|
+
import { createXai } from "@ai-sdk/xai";
|
|
18
|
+
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
19
|
+
import type { LanguageModel } from "ai";
|
|
20
|
+
import {
|
|
21
|
+
getLogger,
|
|
22
|
+
type AgentPlatformRuntime,
|
|
23
|
+
type DowncityConfig,
|
|
24
|
+
type LlmProviderType,
|
|
25
|
+
type StoredModel,
|
|
26
|
+
type StoredModelProvider,
|
|
27
|
+
} from "@downcity/agent";
|
|
28
|
+
import { PlatformStore } from "@/platform/store/index.js";
|
|
29
|
+
|
|
30
|
+
type ModelLogContext = {
|
|
31
|
+
/**
|
|
32
|
+
* 当前 session 标识,用于 LLM 请求日志追踪。
|
|
33
|
+
*/
|
|
34
|
+
sessionId?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type RuntimeModelFetch = (
|
|
38
|
+
input: Parameters<typeof fetch>[0],
|
|
39
|
+
init?: Parameters<typeof fetch>[1],
|
|
40
|
+
) => ReturnType<typeof fetch>;
|
|
41
|
+
|
|
42
|
+
type RuntimeModelFactoryInput = {
|
|
43
|
+
/**
|
|
44
|
+
* 当前项目配置。
|
|
45
|
+
*
|
|
46
|
+
* 关键点(中文)
|
|
47
|
+
* - 这里只依赖 `execution.modelId` 与 `llm.logMessages`。
|
|
48
|
+
* - provider/model 详情统一从平台模型池读取。
|
|
49
|
+
*/
|
|
50
|
+
config: DowncityConfig;
|
|
51
|
+
/**
|
|
52
|
+
* 可选 session run scope。
|
|
53
|
+
*
|
|
54
|
+
* 关键点(中文)
|
|
55
|
+
* - 仅用于把 sessionId 透传到 LLM 请求日志元数据。
|
|
56
|
+
*/
|
|
57
|
+
getSessionRunScope?: () => ModelLogContext | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* 可选宿主平台能力。
|
|
60
|
+
*
|
|
61
|
+
* 关键点(中文)
|
|
62
|
+
* - 若传入,则优先通过平台端口读取 provider/model。
|
|
63
|
+
* - 未传入时回退到 city 自己的 `PlatformStore`。
|
|
64
|
+
*/
|
|
65
|
+
platform?: AgentPlatformRuntime;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
function readProjectExecutionBinding(
|
|
69
|
+
config: DowncityConfig,
|
|
70
|
+
): { type: "api"; modelId: string } | null {
|
|
71
|
+
const execution = config.execution;
|
|
72
|
+
if (!execution || typeof execution !== "object") return null;
|
|
73
|
+
if (execution.type !== "api") return null;
|
|
74
|
+
const modelId = String(execution.modelId || "").trim();
|
|
75
|
+
if (!modelId) return null;
|
|
76
|
+
return {
|
|
77
|
+
type: "api",
|
|
78
|
+
modelId,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function buildResponsesUrl(baseUrl?: string): string {
|
|
83
|
+
const trimmed = String(baseUrl || "")
|
|
84
|
+
.trim()
|
|
85
|
+
.replace(/\/+$/, "");
|
|
86
|
+
if (!trimmed) return "https://api.openai.com/v1/responses";
|
|
87
|
+
if (trimmed.endsWith("/responses")) return trimmed;
|
|
88
|
+
return `${trimmed}/responses`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function normalizeOptionalBaseUrl(value: string | undefined): string | undefined {
|
|
92
|
+
const trimmed = String(value || "")
|
|
93
|
+
.trim()
|
|
94
|
+
.replace(/\/+$/, "");
|
|
95
|
+
return trimmed || undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function resolveProviderDefaultBaseUrl(
|
|
99
|
+
providerType: LlmProviderType,
|
|
100
|
+
): string | undefined {
|
|
101
|
+
if (providerType === "deepseek") return "https://api.deepseek.com/v1";
|
|
102
|
+
if (providerType === "moonshot-cn") return "https://api.moonshot.cn/v1";
|
|
103
|
+
if (providerType === "moonshot-ai") return "https://api.moonshot.ai/v1";
|
|
104
|
+
if (providerType === "kimi-code") return "https://api.kimi.com/coding/v1";
|
|
105
|
+
if (providerType === "xai") return "https://api.x.ai/v1";
|
|
106
|
+
if (providerType === "openrouter") return "https://openrouter.ai/api/v1";
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function resolveEnvPlaceholder(value: string | undefined): string | undefined {
|
|
111
|
+
if (!value) return value;
|
|
112
|
+
if (value.startsWith("${") && value.endsWith("}")) {
|
|
113
|
+
const envVar = value.slice(2, -1);
|
|
114
|
+
return process.env[envVar];
|
|
115
|
+
}
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function resolveApiKeyFallback(providerType: LlmProviderType): string | undefined {
|
|
120
|
+
if (providerType === "gemini") {
|
|
121
|
+
return (
|
|
122
|
+
process.env.GEMINI_API_KEY ||
|
|
123
|
+
process.env.GOOGLE_API_KEY ||
|
|
124
|
+
process.env.GOOGLE_GENERATIVE_AI_API_KEY ||
|
|
125
|
+
process.env.API_KEY
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
if (providerType === "anthropic") {
|
|
129
|
+
return process.env.ANTHROPIC_API_KEY || process.env.API_KEY;
|
|
130
|
+
}
|
|
131
|
+
if (providerType === "deepseek") {
|
|
132
|
+
return (
|
|
133
|
+
process.env.DEEPSEEK_API_KEY ||
|
|
134
|
+
process.env.OPENAI_API_KEY ||
|
|
135
|
+
process.env.API_KEY
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
if (providerType === "xai") {
|
|
139
|
+
return process.env.XAI_API_KEY || process.env.API_KEY;
|
|
140
|
+
}
|
|
141
|
+
if (providerType === "huggingface") {
|
|
142
|
+
return (
|
|
143
|
+
process.env.HUGGINGFACE_API_KEY ||
|
|
144
|
+
process.env.HF_TOKEN ||
|
|
145
|
+
process.env.API_KEY
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
if (providerType === "openrouter") {
|
|
149
|
+
return process.env.OPENROUTER_API_KEY || process.env.API_KEY;
|
|
150
|
+
}
|
|
151
|
+
if (providerType === "moonshot-cn" || providerType === "moonshot-ai") {
|
|
152
|
+
return (
|
|
153
|
+
process.env.MOONSHOT_API_KEY ||
|
|
154
|
+
process.env.KIMI_API_KEY ||
|
|
155
|
+
process.env.API_KEY
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (providerType === "kimi-code") {
|
|
159
|
+
return (
|
|
160
|
+
process.env.KIMI_CODE_API_KEY ||
|
|
161
|
+
process.env.KIMI_API_KEY ||
|
|
162
|
+
process.env.MOONSHOT_API_KEY ||
|
|
163
|
+
process.env.API_KEY
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return process.env.OPENAI_API_KEY || process.env.API_KEY;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function normalizeProviderType(value: unknown): LlmProviderType | null {
|
|
170
|
+
if (value === "anthropic") return value;
|
|
171
|
+
if (value === "openai") return value;
|
|
172
|
+
if (value === "deepseek") return value;
|
|
173
|
+
if (value === "gemini") return value;
|
|
174
|
+
if (value === "open-compatible") return value;
|
|
175
|
+
if (value === "open-responses") return value;
|
|
176
|
+
if (value === "moonshot-cn") return value;
|
|
177
|
+
if (value === "moonshot-ai") return value;
|
|
178
|
+
if (value === "kimi-code") return value;
|
|
179
|
+
if (value === "xai") return value;
|
|
180
|
+
if (value === "huggingface") return value;
|
|
181
|
+
if (value === "openrouter") return value;
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function readFetchUrl(input: Parameters<typeof fetch>[0]): string {
|
|
186
|
+
if (typeof input === "string") return input;
|
|
187
|
+
if (input instanceof URL) return input.toString();
|
|
188
|
+
return input.url;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function readFetchMethod(
|
|
192
|
+
input: Parameters<typeof fetch>[0],
|
|
193
|
+
init?: Parameters<typeof fetch>[1],
|
|
194
|
+
): string {
|
|
195
|
+
const methodFromInit = String(init?.method || "").trim().toUpperCase();
|
|
196
|
+
if (methodFromInit) return methodFromInit;
|
|
197
|
+
if (typeof input === "object" && "method" in input) {
|
|
198
|
+
const requestMethod = String(input.method || "").trim().toUpperCase();
|
|
199
|
+
if (requestMethod) return requestMethod;
|
|
200
|
+
}
|
|
201
|
+
return "POST";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function createRuntimeModelLoggingFetch(args: {
|
|
205
|
+
enabled: boolean;
|
|
206
|
+
getSessionRunScope?: () => ModelLogContext | undefined;
|
|
207
|
+
}): RuntimeModelFetch {
|
|
208
|
+
const logger = getLogger();
|
|
209
|
+
const baseFetch = globalThis.fetch.bind(globalThis);
|
|
210
|
+
|
|
211
|
+
return async (input, init) => {
|
|
212
|
+
const sessionId = args.getSessionRunScope?.()?.sessionId;
|
|
213
|
+
const url = readFetchUrl(input);
|
|
214
|
+
const method = readFetchMethod(input, init);
|
|
215
|
+
try {
|
|
216
|
+
const response = await baseFetch(input, init);
|
|
217
|
+
if (args.enabled) {
|
|
218
|
+
void logger.log("info", "[city] llm.fetch", {
|
|
219
|
+
kind: "llm_fetch",
|
|
220
|
+
url,
|
|
221
|
+
method,
|
|
222
|
+
status: response.status,
|
|
223
|
+
...(sessionId ? { sessionId } : {}),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return response;
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (args.enabled) {
|
|
229
|
+
void logger.log("error", "[city] llm.fetch.error", {
|
|
230
|
+
kind: "llm_fetch_error",
|
|
231
|
+
url,
|
|
232
|
+
method,
|
|
233
|
+
error: String(error || "unknown_error"),
|
|
234
|
+
...(sessionId ? { sessionId } : {}),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function resolveConfiguredModel(input: RuntimeModelFactoryInput & {
|
|
243
|
+
primaryModelId: string;
|
|
244
|
+
}): Promise<{
|
|
245
|
+
model: StoredModel;
|
|
246
|
+
provider: StoredModelProvider;
|
|
247
|
+
}> {
|
|
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
|
+
const store = new PlatformStore();
|
|
265
|
+
try {
|
|
266
|
+
const resolved = await store.getResolvedModel(input.primaryModelId);
|
|
267
|
+
if (!resolved) {
|
|
268
|
+
throw new Error(
|
|
269
|
+
`LLM model config not found in platform store: ${input.primaryModelId}`,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
return resolved;
|
|
273
|
+
} finally {
|
|
274
|
+
store.close();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* 创建 LanguageModel 实例。
|
|
280
|
+
*
|
|
281
|
+
* 解析策略(中文)
|
|
282
|
+
* 1) 读取 `execution.modelId`。
|
|
283
|
+
* 2) 从宿主平台或 `PlatformStore` 解析 provider/model。
|
|
284
|
+
* 3) 按 provider type 分发到对应 AI SDK 工厂。
|
|
285
|
+
*/
|
|
286
|
+
export async function createRuntimeModel(
|
|
287
|
+
input: RuntimeModelFactoryInput,
|
|
288
|
+
): Promise<LanguageModel> {
|
|
289
|
+
const logger = getLogger();
|
|
290
|
+
const execution = readProjectExecutionBinding(input.config);
|
|
291
|
+
if (!execution) {
|
|
292
|
+
await logger.log("warn", "No agent execution configured");
|
|
293
|
+
throw new Error("No agent execution configured");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const configLog = input.config.llm?.logMessages;
|
|
297
|
+
const logLlmMessages = typeof configLog === "boolean" ? configLog : true;
|
|
298
|
+
const loggingFetch = createRuntimeModelLoggingFetch({
|
|
299
|
+
enabled: logLlmMessages,
|
|
300
|
+
getSessionRunScope: input.getSessionRunScope,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const primaryModelId = execution.modelId;
|
|
304
|
+
const { model: modelConfig, provider: providerConfig } =
|
|
305
|
+
await resolveConfiguredModel({
|
|
306
|
+
...input,
|
|
307
|
+
primaryModelId,
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
if (modelConfig.isPaused === true) {
|
|
311
|
+
await logger.log(
|
|
312
|
+
"warn",
|
|
313
|
+
`LLM model is paused in platform store: ${primaryModelId}`,
|
|
314
|
+
);
|
|
315
|
+
throw new Error(`LLM model is paused: ${primaryModelId}`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const providerKey = providerConfig.id;
|
|
319
|
+
const providerType = normalizeProviderType(providerConfig.type);
|
|
320
|
+
if (!providerType) {
|
|
321
|
+
await logger.log(
|
|
322
|
+
"warn",
|
|
323
|
+
`Unsupported LLM provider type: ${providerConfig.type}`,
|
|
324
|
+
);
|
|
325
|
+
throw new Error(`Unsupported LLM provider type: ${providerConfig.type}`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const resolvedModel = resolveEnvPlaceholder(modelConfig.name);
|
|
329
|
+
if (!resolvedModel || resolvedModel === "${}") {
|
|
330
|
+
await logger.log("warn", "No LLM model name configured");
|
|
331
|
+
throw new Error("No LLM model name configured");
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const resolvedBaseUrl = normalizeOptionalBaseUrl(
|
|
335
|
+
resolveEnvPlaceholder(providerConfig.baseUrl) ||
|
|
336
|
+
resolveProviderDefaultBaseUrl(providerType),
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
let resolvedApiKey = resolveEnvPlaceholder(providerConfig.apiKey);
|
|
340
|
+
if (!resolvedApiKey) {
|
|
341
|
+
resolvedApiKey = resolveApiKeyFallback(providerType);
|
|
342
|
+
}
|
|
343
|
+
if (!resolvedApiKey) {
|
|
344
|
+
await logger.log("warn", "No API Key configured, will use simulation mode");
|
|
345
|
+
throw new Error("No API Key configured, will use simulation mode");
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
await logger.log(
|
|
349
|
+
"info",
|
|
350
|
+
`[main] model primary=${primaryModelId} provider=${providerType}/${providerKey} name=${resolvedModel}${resolvedBaseUrl ? ` baseUrl=${resolvedBaseUrl}` : ""}`,
|
|
351
|
+
{
|
|
352
|
+
kind: "llm_model_ready",
|
|
353
|
+
primaryModel: primaryModelId,
|
|
354
|
+
providerType,
|
|
355
|
+
providerKey,
|
|
356
|
+
model: resolvedModel,
|
|
357
|
+
...(resolvedBaseUrl ? { baseUrl: resolvedBaseUrl } : {}),
|
|
358
|
+
logMessages: logLlmMessages,
|
|
359
|
+
},
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
if (providerType === "anthropic") {
|
|
363
|
+
const anthropicProvider = createAnthropic({
|
|
364
|
+
apiKey: resolvedApiKey,
|
|
365
|
+
baseURL: resolvedBaseUrl,
|
|
366
|
+
fetch: loggingFetch as typeof fetch,
|
|
367
|
+
});
|
|
368
|
+
return anthropicProvider(resolvedModel);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (providerType === "gemini") {
|
|
372
|
+
const googleProvider = createGoogleGenerativeAI({
|
|
373
|
+
apiKey: resolvedApiKey,
|
|
374
|
+
baseURL: resolvedBaseUrl,
|
|
375
|
+
fetch: loggingFetch as typeof fetch,
|
|
376
|
+
});
|
|
377
|
+
return googleProvider(resolvedModel);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (providerType === "open-responses") {
|
|
381
|
+
const responsesProvider = createOpenResponses({
|
|
382
|
+
url: buildResponsesUrl(resolvedBaseUrl),
|
|
383
|
+
name: providerKey,
|
|
384
|
+
apiKey: resolvedApiKey,
|
|
385
|
+
fetch: loggingFetch as typeof fetch,
|
|
386
|
+
});
|
|
387
|
+
return responsesProvider(resolvedModel);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (providerType === "open-compatible") {
|
|
391
|
+
const compatibleProvider = createOpenAICompatible({
|
|
392
|
+
name: providerKey,
|
|
393
|
+
baseURL: resolvedBaseUrl || "https://api.openai.com/v1",
|
|
394
|
+
apiKey: resolvedApiKey,
|
|
395
|
+
fetch: loggingFetch as typeof fetch,
|
|
396
|
+
});
|
|
397
|
+
return compatibleProvider(resolvedModel);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (providerType === "kimi-code") {
|
|
401
|
+
const compatibleProvider = createOpenAICompatible({
|
|
402
|
+
name: providerKey,
|
|
403
|
+
baseURL: resolvedBaseUrl || "https://api.kimi.com/coding/v1",
|
|
404
|
+
apiKey: resolvedApiKey,
|
|
405
|
+
fetch: loggingFetch as typeof fetch,
|
|
406
|
+
});
|
|
407
|
+
return compatibleProvider(resolvedModel);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (providerType === "moonshot-cn" || providerType === "moonshot-ai") {
|
|
411
|
+
const moonshotProvider = createMoonshotAI({
|
|
412
|
+
baseURL: resolvedBaseUrl,
|
|
413
|
+
apiKey: resolvedApiKey,
|
|
414
|
+
fetch: loggingFetch as typeof fetch,
|
|
415
|
+
});
|
|
416
|
+
return moonshotProvider(resolvedModel);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (providerType === "xai") {
|
|
420
|
+
const xaiProvider = createXai({
|
|
421
|
+
baseURL: resolvedBaseUrl,
|
|
422
|
+
apiKey: resolvedApiKey,
|
|
423
|
+
fetch: loggingFetch as typeof fetch,
|
|
424
|
+
});
|
|
425
|
+
return xaiProvider(resolvedModel);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (providerType === "huggingface") {
|
|
429
|
+
const huggingFaceProvider = createHuggingFace({
|
|
430
|
+
baseURL: resolvedBaseUrl,
|
|
431
|
+
apiKey: resolvedApiKey,
|
|
432
|
+
fetch: loggingFetch as typeof fetch,
|
|
433
|
+
});
|
|
434
|
+
return huggingFaceProvider(resolvedModel);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (providerType === "openrouter") {
|
|
438
|
+
const openRouterProvider = createOpenRouter({
|
|
439
|
+
baseURL: resolvedBaseUrl,
|
|
440
|
+
apiKey: resolvedApiKey,
|
|
441
|
+
fetch: loggingFetch as typeof fetch,
|
|
442
|
+
});
|
|
443
|
+
return openRouterProvider(resolvedModel);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (providerType === "deepseek") {
|
|
447
|
+
const deepseekCompatibleProvider = createOpenAICompatible({
|
|
448
|
+
name: providerKey,
|
|
449
|
+
baseURL: resolvedBaseUrl || "https://api.deepseek.com/v1",
|
|
450
|
+
apiKey: resolvedApiKey,
|
|
451
|
+
fetch: loggingFetch as typeof fetch,
|
|
452
|
+
});
|
|
453
|
+
return deepseekCompatibleProvider(resolvedModel);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const openaiProvider = createOpenAI({
|
|
457
|
+
apiKey: resolvedApiKey,
|
|
458
|
+
baseURL: resolvedBaseUrl,
|
|
459
|
+
fetch: loggingFetch as typeof fetch,
|
|
460
|
+
});
|
|
461
|
+
return openaiProvider(resolvedModel);
|
|
462
|
+
}
|