@yuandc/aica 0.1.0
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 -0
- package/dist/acp/agent.js +54 -0
- package/dist/acp/client/acp-client.js +102 -0
- package/dist/acp/client/acp-content.js +13 -0
- package/dist/acp/client/acp-events.js +106 -0
- package/dist/acp/client/acp-process.js +34 -0
- package/dist/acp/client/acp-runtime-pool.js +248 -0
- package/dist/acp/client/context-usage.js +29 -0
- package/dist/acp/client/json-rpc.js +128 -0
- package/dist/acp/provider-types.js +1 -0
- package/dist/acp/providers/codex/codex-process.js +51 -0
- package/dist/acp/providers/codex/events.js +1473 -0
- package/dist/acp/providers/codex/permissions.js +49 -0
- package/dist/acp/providers/codex/provider.js +376 -0
- package/dist/acp/providers/codex-acp/adapter.js +947 -0
- package/dist/acp/providers/codex-acp/context-maintenance.js +148 -0
- package/dist/acp/providers/codex-acp/launch.js +35 -0
- package/dist/acp/providers/codex-acp/provider.js +486 -0
- package/dist/acp/providers/mimo/provider.js +448 -0
- package/dist/acp/providers/opencode/provider.js +489 -0
- package/dist/acp/providers/registry.js +23 -0
- package/dist/acp/standard-events.js +167 -0
- package/dist/commands/start.js +137 -0
- package/dist/commands/worker-auth.js +100 -0
- package/dist/commands/worker-project.js +57 -0
- package/dist/core/aca-config.js +74 -0
- package/dist/core/aca-server-client.js +57 -0
- package/dist/core/acp-event-coalescer.js +108 -0
- package/dist/core/acp-event-upload-filter.js +16 -0
- package/dist/core/acp-orphan-cleanup.js +91 -0
- package/dist/core/affected-files.js +268 -0
- package/dist/core/auth.js +36 -0
- package/dist/core/file-transfer-worker.js +169 -0
- package/dist/core/fs.js +28 -0
- package/dist/core/heartbeat.js +578 -0
- package/dist/core/job-permission-policy.js +42 -0
- package/dist/core/job-worker.js +749 -0
- package/dist/core/logger.js +42 -0
- package/dist/core/long-poll-worker.js +26 -0
- package/dist/core/machine-filesystem-worker.js +352 -0
- package/dist/core/paths.js +26 -0
- package/dist/core/process-identity.js +34 -0
- package/dist/core/process.js +33 -0
- package/dist/core/provider-health.js +54 -0
- package/dist/core/runtime-options.js +38 -0
- package/dist/core/worktree.js +95 -0
- package/dist/worker-cli.js +27 -0
- package/dist/worker-single-cli.js +17 -0
- package/package.json +35 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { eventFromAcpSessionUpdate, statusFromAcpSessionUpdate } from "../../client/acp-events.js";
|
|
4
|
+
import { contextUsageFromAcpSessionUpdate } from "../../client/context-usage.js";
|
|
5
|
+
import { defaultAcpRuntimePool } from "../../client/acp-runtime-pool.js";
|
|
6
|
+
export const mimoCodeCapabilities = {
|
|
7
|
+
sessionResume: true,
|
|
8
|
+
imageInput: true,
|
|
9
|
+
fileAttachment: true,
|
|
10
|
+
filesystem: true,
|
|
11
|
+
terminal: true,
|
|
12
|
+
permissionRequest: true,
|
|
13
|
+
configOptions: true,
|
|
14
|
+
usage: true,
|
|
15
|
+
contextUsage: true,
|
|
16
|
+
contextCompaction: false,
|
|
17
|
+
plan: true,
|
|
18
|
+
diff: false
|
|
19
|
+
};
|
|
20
|
+
export class MimoCodeProvider {
|
|
21
|
+
id = "mimocode";
|
|
22
|
+
name = "MimoCode";
|
|
23
|
+
capabilities = mimoCodeCapabilities;
|
|
24
|
+
async createSession(input) {
|
|
25
|
+
return new MimoCodeSession(input.cwd, input.providerSessionId ?? null, input.timeoutMs);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
class MimoCodeSession {
|
|
29
|
+
cwd;
|
|
30
|
+
providerSessionId;
|
|
31
|
+
defaultTimeoutMs;
|
|
32
|
+
lease = null;
|
|
33
|
+
acpSessionResponse = null;
|
|
34
|
+
statusCallback = null;
|
|
35
|
+
updateCallback = null;
|
|
36
|
+
permissionCallback = null;
|
|
37
|
+
userInputCallback = null;
|
|
38
|
+
canExecuteTools = true;
|
|
39
|
+
abortHandler = null;
|
|
40
|
+
statusTimeline = [];
|
|
41
|
+
updates = [];
|
|
42
|
+
content = "";
|
|
43
|
+
latestContextUsage = null;
|
|
44
|
+
contextWindow = 0;
|
|
45
|
+
constructor(cwd, providerSessionId, defaultTimeoutMs = readMimoPromptTimeoutMs()) {
|
|
46
|
+
this.cwd = cwd;
|
|
47
|
+
this.providerSessionId = providerSessionId;
|
|
48
|
+
this.defaultTimeoutMs = defaultTimeoutMs;
|
|
49
|
+
}
|
|
50
|
+
async sendPrompt(input) {
|
|
51
|
+
if (!fs.existsSync(input.cwd) || !fs.statSync(input.cwd).isDirectory()) {
|
|
52
|
+
throw new Error(`Project root does not exist or is not a directory: ${input.cwd}`);
|
|
53
|
+
}
|
|
54
|
+
const startedAt = Date.now();
|
|
55
|
+
this.statusCallback = input.onStatus ?? null;
|
|
56
|
+
this.updateCallback = input.onUpdate ?? null;
|
|
57
|
+
this.permissionCallback = input.onPermissionRequest ?? null;
|
|
58
|
+
this.userInputCallback = input.onUserInputRequest ?? null;
|
|
59
|
+
this.canExecuteTools = input.canExecuteTools !== false;
|
|
60
|
+
this.attachAbortSignal(input.signal);
|
|
61
|
+
this.content = "";
|
|
62
|
+
this.updates.length = 0;
|
|
63
|
+
this.statusTimeline.length = 0;
|
|
64
|
+
this.latestContextUsage = null;
|
|
65
|
+
this.contextWindow = positiveNumber(input.contextWindow);
|
|
66
|
+
let client = null;
|
|
67
|
+
let started = null;
|
|
68
|
+
let acpSessionId = this.providerSessionId;
|
|
69
|
+
let promptResponse;
|
|
70
|
+
try {
|
|
71
|
+
this.emitStatus("initializing", "MimoCode ACP 启动中", "mimo acp");
|
|
72
|
+
const lease = await this.acquireRuntime(input.cwd);
|
|
73
|
+
this.lease = lease;
|
|
74
|
+
client = lease.client;
|
|
75
|
+
started = lease.started;
|
|
76
|
+
await this.initialize(lease);
|
|
77
|
+
acpSessionId = await this.establishSession(lease, input.cwd);
|
|
78
|
+
await this.applyConfig(client, acpSessionId, input);
|
|
79
|
+
this.emitStatus("thinking", "思考中");
|
|
80
|
+
promptResponse = await client.prompt({
|
|
81
|
+
sessionId: acpSessionId,
|
|
82
|
+
prompt: promptBlocksToMimoAcpContent(input.prompt, input.promptBlocks)
|
|
83
|
+
}, input.timeoutMs ?? this.defaultTimeoutMs);
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
if (this.abortHandler && input.signal) {
|
|
87
|
+
input.signal.removeEventListener("abort", this.abortHandler);
|
|
88
|
+
this.abortHandler = null;
|
|
89
|
+
}
|
|
90
|
+
this.releaseLease();
|
|
91
|
+
}
|
|
92
|
+
this.emitStatus("completed", "已完成");
|
|
93
|
+
return {
|
|
94
|
+
providerSessionId: acpSessionId,
|
|
95
|
+
cliType: "builtin",
|
|
96
|
+
agentType: "mimo",
|
|
97
|
+
effectiveConfig: {
|
|
98
|
+
model: input.model || readMimoDefaultModel(),
|
|
99
|
+
mode: input.mode || null,
|
|
100
|
+
configOptionValues: input.configOptionValues ?? null,
|
|
101
|
+
command: started.command,
|
|
102
|
+
processPid: started.child.pid ?? null,
|
|
103
|
+
provider: "mimocode",
|
|
104
|
+
protocol: "acp"
|
|
105
|
+
},
|
|
106
|
+
command: started.command,
|
|
107
|
+
args: started.args,
|
|
108
|
+
cwd: input.cwd,
|
|
109
|
+
exitCode: started.child.exitCode,
|
|
110
|
+
signal: started.child.signalCode,
|
|
111
|
+
durationMs: Date.now() - startedAt,
|
|
112
|
+
stdout: client.rpc.stdout,
|
|
113
|
+
stderr: client.rpc.stderr,
|
|
114
|
+
content: this.content.trim(),
|
|
115
|
+
updates: [...this.updates],
|
|
116
|
+
statusTimeline: [...this.statusTimeline],
|
|
117
|
+
contextUsage: this.latestContextUsage,
|
|
118
|
+
promptResponse
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async cancel() {
|
|
122
|
+
if (this.providerSessionId)
|
|
123
|
+
this.lease?.client.cancel(this.providerSessionId);
|
|
124
|
+
}
|
|
125
|
+
async close() {
|
|
126
|
+
this.statusCallback = null;
|
|
127
|
+
this.updateCallback = null;
|
|
128
|
+
this.permissionCallback = null;
|
|
129
|
+
this.userInputCallback = null;
|
|
130
|
+
this.canExecuteTools = true;
|
|
131
|
+
if (this.abortHandler)
|
|
132
|
+
this.abortHandler = null;
|
|
133
|
+
this.releaseLease();
|
|
134
|
+
}
|
|
135
|
+
async acquireRuntime(cwd) {
|
|
136
|
+
const command = process.env.ACA_MIMO_COMMAND || "mimo";
|
|
137
|
+
const args = ["acp", "--cwd", cwd];
|
|
138
|
+
return defaultAcpRuntimePool.acquire({
|
|
139
|
+
providerId: "mimocode",
|
|
140
|
+
cwd,
|
|
141
|
+
providerSessionId: this.providerSessionId,
|
|
142
|
+
command,
|
|
143
|
+
args,
|
|
144
|
+
onSessionUpdate: (params) => this.handleSessionUpdate(params),
|
|
145
|
+
onClientRequest: (method, params) => this.handleClientRequest(method, params)
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
async initialize(lease) {
|
|
149
|
+
if (lease.initialized)
|
|
150
|
+
return;
|
|
151
|
+
await lease.client.initialize(60_000);
|
|
152
|
+
lease.markInitialized();
|
|
153
|
+
}
|
|
154
|
+
async establishSession(lease, cwd) {
|
|
155
|
+
const client = lease.client;
|
|
156
|
+
if (lease.providerSessionId && lease.sessionEstablished) {
|
|
157
|
+
this.providerSessionId = lease.providerSessionId;
|
|
158
|
+
this.acpSessionResponse = lease.sessionResponse;
|
|
159
|
+
return lease.providerSessionId;
|
|
160
|
+
}
|
|
161
|
+
if (this.providerSessionId) {
|
|
162
|
+
try {
|
|
163
|
+
this.emitStatus("resuming", "MimoCode 会话恢复中", "session/resume");
|
|
164
|
+
const resumed = await client.resumeSession({ sessionId: this.providerSessionId, cwd }, 60_000);
|
|
165
|
+
this.acpSessionResponse = objectOrNull(resumed);
|
|
166
|
+
const sessionId = sessionIdFromResponse(resumed) || this.providerSessionId;
|
|
167
|
+
this.providerSessionId = sessionId;
|
|
168
|
+
lease.setProviderSession({ providerSessionId: sessionId, sessionResponse: this.acpSessionResponse });
|
|
169
|
+
return sessionId;
|
|
170
|
+
}
|
|
171
|
+
catch (resumeError) {
|
|
172
|
+
try {
|
|
173
|
+
this.emitStatus("resuming", "MimoCode 会话加载中", "session/load");
|
|
174
|
+
const loaded = await client.loadSession({ sessionId: this.providerSessionId, cwd }, 60_000);
|
|
175
|
+
this.acpSessionResponse = objectOrNull(loaded);
|
|
176
|
+
const sessionId = sessionIdFromResponse(loaded) || this.providerSessionId;
|
|
177
|
+
this.providerSessionId = sessionId;
|
|
178
|
+
lease.setProviderSession({ providerSessionId: sessionId, sessionResponse: this.acpSessionResponse });
|
|
179
|
+
return sessionId;
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
this.appendStderr(`MimoCode resume failed, creating new session: ${errorMessage(resumeError)}\n`);
|
|
183
|
+
this.providerSessionId = null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
this.emitStatus("acp", "MimoCode 会话创建中", "session/new");
|
|
188
|
+
const created = await client.newSession({ cwd }, 120_000);
|
|
189
|
+
this.acpSessionResponse = objectOrNull(created);
|
|
190
|
+
const sessionId = sessionIdFromResponse(created);
|
|
191
|
+
if (!sessionId)
|
|
192
|
+
throw new Error("MimoCode ACP session/new did not return sessionId");
|
|
193
|
+
this.providerSessionId = sessionId;
|
|
194
|
+
lease.setProviderSession({ providerSessionId: sessionId, sessionResponse: this.acpSessionResponse });
|
|
195
|
+
return sessionId;
|
|
196
|
+
}
|
|
197
|
+
async applyConfig(client, sessionId, input) {
|
|
198
|
+
const modelConfigId = configIdByCategory(this.acpSessionResponse, "model") || "model";
|
|
199
|
+
const modeConfigId = configIdByCategory(this.acpSessionResponse, "mode") || "mode";
|
|
200
|
+
const modelValue = selectMimoModelValue(input.model || readMimoDefaultModel(), input.configOptionValues, this.acpSessionResponse);
|
|
201
|
+
if (modelValue) {
|
|
202
|
+
await client.setSessionConfigOption({ sessionId, configId: modelConfigId, value: modelValue }, 60_000).catch((error) => {
|
|
203
|
+
this.appendStderr(`MimoCode model config failed: ${errorMessage(error)}\n`);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
const modeValue = mimoModeValue(input.mode);
|
|
207
|
+
if (modeValue) {
|
|
208
|
+
await client.setSessionMode({ sessionId, modeId: modeValue }, 60_000).catch(async () => {
|
|
209
|
+
await client.setSessionConfigOption({ sessionId, configId: modeConfigId, value: modeValue }, 60_000).catch((error) => {
|
|
210
|
+
this.appendStderr(`MimoCode mode config failed: ${errorMessage(error)}\n`);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
for (const [configId, value] of Object.entries(input.configOptionValues ?? {})) {
|
|
215
|
+
if (["variant", "reasoning_effort", "effort", "model", "mode"].includes(configId))
|
|
216
|
+
continue;
|
|
217
|
+
await client.setSessionConfigOption({ sessionId, configId, value }, 60_000).catch((error) => {
|
|
218
|
+
this.appendStderr(`MimoCode config ${configId} failed: ${errorMessage(error)}\n`);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
handleSessionUpdate(params) {
|
|
223
|
+
this.updates.push({ method: "session/update", params });
|
|
224
|
+
this.latestContextUsage = contextUsageFromAcpSessionUpdate(params, this.contextWindow) ?? this.latestContextUsage;
|
|
225
|
+
const event = eventFromAcpSessionUpdate(params);
|
|
226
|
+
if (event) {
|
|
227
|
+
if (event.type === "agent_message_chunk")
|
|
228
|
+
this.content += event.text ?? "";
|
|
229
|
+
this.updateCallback?.(event);
|
|
230
|
+
}
|
|
231
|
+
const status = statusFromAcpSessionUpdate(params);
|
|
232
|
+
if (status)
|
|
233
|
+
this.emitStatus(status.phase, status.label, status.detail, status.updateType);
|
|
234
|
+
}
|
|
235
|
+
async handleClientRequest(method, params) {
|
|
236
|
+
if (!this.canExecuteTools && (method === "fs/read_text_file" || method === "fs/write_text_file")) {
|
|
237
|
+
throw new Error(`Chat Room role policy disabled tool execution: ${method}`);
|
|
238
|
+
}
|
|
239
|
+
switch (method) {
|
|
240
|
+
case "fs/read_text_file":
|
|
241
|
+
return this.readTextFile(params);
|
|
242
|
+
case "fs/write_text_file":
|
|
243
|
+
return this.writeTextFile(params);
|
|
244
|
+
case "session/request_permission":
|
|
245
|
+
return this.requestPermission(params);
|
|
246
|
+
case "elicitation/create":
|
|
247
|
+
case "session/request_user_input":
|
|
248
|
+
return this.requestUserInput(params);
|
|
249
|
+
default:
|
|
250
|
+
throw new Error(`Unsupported ACP client request: ${method}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
readTextFile(params) {
|
|
254
|
+
const filePath = resolveScopedPath(this.cwd, stringParam(params, "path") || stringParam(params, "filePath"));
|
|
255
|
+
return { content: fs.readFileSync(filePath, "utf8") };
|
|
256
|
+
}
|
|
257
|
+
writeTextFile(params) {
|
|
258
|
+
const filePath = resolveScopedPath(this.cwd, stringParam(params, "path") || stringParam(params, "filePath"));
|
|
259
|
+
const content = stringParam(params, "content") ?? stringParam(params, "text") ?? "";
|
|
260
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
261
|
+
fs.writeFileSync(filePath, content, "utf8");
|
|
262
|
+
return {};
|
|
263
|
+
}
|
|
264
|
+
async requestPermission(params) {
|
|
265
|
+
if (!this.permissionCallback)
|
|
266
|
+
return { outcome: { outcome: "cancelled" } };
|
|
267
|
+
const record = params && typeof params === "object" && !Array.isArray(params) ? params : {};
|
|
268
|
+
const rawOptions = Array.isArray(record.options) ? record.options : [];
|
|
269
|
+
const options = rawOptions.map((option, index) => {
|
|
270
|
+
const item = option && typeof option === "object" && !Array.isArray(option) ? option : {};
|
|
271
|
+
return {
|
|
272
|
+
optionId: String(item.optionId ?? item.id ?? index),
|
|
273
|
+
kind: String(item.kind ?? item.outcome ?? "choice"),
|
|
274
|
+
...(typeof item.label === "string" ? { label: item.label } : {})
|
|
275
|
+
};
|
|
276
|
+
});
|
|
277
|
+
return this.permissionCallback({
|
|
278
|
+
requestId: String(record.requestId ?? record.id ?? `mimo-permission-${Date.now()}`),
|
|
279
|
+
params,
|
|
280
|
+
options
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
async requestUserInput(params) {
|
|
284
|
+
if (!this.userInputCallback)
|
|
285
|
+
return { outcome: { outcome: "cancelled" } };
|
|
286
|
+
const record = params && typeof params === "object" && !Array.isArray(params) ? params : {};
|
|
287
|
+
return this.userInputCallback({
|
|
288
|
+
requestId: String(record.requestId ?? record.id ?? `mimo-input-${Date.now()}`),
|
|
289
|
+
prompt: String(record.prompt ?? record.message ?? record.question ?? ""),
|
|
290
|
+
params,
|
|
291
|
+
...(typeof record.defaultValue === "string" ? { defaultValue: record.defaultValue } : {})
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
attachAbortSignal(signal) {
|
|
295
|
+
if (!signal)
|
|
296
|
+
return;
|
|
297
|
+
this.abortHandler = () => {
|
|
298
|
+
void this.cancel();
|
|
299
|
+
};
|
|
300
|
+
if (signal.aborted)
|
|
301
|
+
throw new Error("MimoCode prompt cancelled");
|
|
302
|
+
signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
303
|
+
}
|
|
304
|
+
emitStatus(phase, label, detail, updateType) {
|
|
305
|
+
const status = {
|
|
306
|
+
phase,
|
|
307
|
+
label,
|
|
308
|
+
...(detail ? { detail } : {}),
|
|
309
|
+
...(updateType ? { updateType } : {}),
|
|
310
|
+
atMs: Date.now()
|
|
311
|
+
};
|
|
312
|
+
const last = this.statusTimeline[this.statusTimeline.length - 1];
|
|
313
|
+
if (last && last.phase === status.phase && last.label === status.label && last.detail === status.detail && last.updateType === status.updateType)
|
|
314
|
+
return;
|
|
315
|
+
this.statusTimeline.push(status);
|
|
316
|
+
this.statusCallback?.(status);
|
|
317
|
+
}
|
|
318
|
+
requireClient() {
|
|
319
|
+
if (!this.lease)
|
|
320
|
+
throw new Error("MimoCode ACP client is not started");
|
|
321
|
+
return this.lease.client;
|
|
322
|
+
}
|
|
323
|
+
appendStderr(value) {
|
|
324
|
+
const client = this.lease?.client;
|
|
325
|
+
if (client)
|
|
326
|
+
client.rpc.stderr += value;
|
|
327
|
+
}
|
|
328
|
+
releaseLease() {
|
|
329
|
+
this.lease?.release();
|
|
330
|
+
this.lease = null;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function sessionIdFromResponse(value) {
|
|
334
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
335
|
+
return null;
|
|
336
|
+
const sessionId = value.sessionId;
|
|
337
|
+
return typeof sessionId === "string" && sessionId.trim() ? sessionId.trim() : null;
|
|
338
|
+
}
|
|
339
|
+
function objectOrNull(value) {
|
|
340
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
341
|
+
}
|
|
342
|
+
function positiveNumber(value) {
|
|
343
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
344
|
+
}
|
|
345
|
+
function configIdByCategory(response, category) {
|
|
346
|
+
const options = response?.configOptions;
|
|
347
|
+
if (!Array.isArray(options))
|
|
348
|
+
return null;
|
|
349
|
+
for (const option of options) {
|
|
350
|
+
if (!option || typeof option !== "object" || Array.isArray(option))
|
|
351
|
+
continue;
|
|
352
|
+
const record = option;
|
|
353
|
+
if (record.category === category && typeof record.id === "string" && record.id.trim())
|
|
354
|
+
return record.id.trim();
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
function selectMimoModelValue(model, values, response) {
|
|
359
|
+
const variant = firstConfigString(values?.variant, values?.reasoning_effort, values?.effort);
|
|
360
|
+
if (!variant || model.endsWith(`/${variant}`))
|
|
361
|
+
return model;
|
|
362
|
+
const candidate = `${model}/${variant}`;
|
|
363
|
+
return modelOptionExists(response, candidate) ? candidate : model;
|
|
364
|
+
}
|
|
365
|
+
function modelOptionExists(response, model) {
|
|
366
|
+
const options = response?.configOptions;
|
|
367
|
+
if (!Array.isArray(options))
|
|
368
|
+
return false;
|
|
369
|
+
for (const option of options) {
|
|
370
|
+
if (!option || typeof option !== "object" || Array.isArray(option))
|
|
371
|
+
continue;
|
|
372
|
+
const record = option;
|
|
373
|
+
if (record.category !== "model" || !Array.isArray(record.options))
|
|
374
|
+
continue;
|
|
375
|
+
if (record.options.some((item) => optionValue(item) === model))
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
function optionValue(value) {
|
|
381
|
+
if (typeof value === "string" && value.trim())
|
|
382
|
+
return value.trim();
|
|
383
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
384
|
+
return null;
|
|
385
|
+
const record = value;
|
|
386
|
+
for (const key of ["value", "id", "modelId", "model_id"]) {
|
|
387
|
+
const item = record[key];
|
|
388
|
+
if (typeof item === "string" && item.trim())
|
|
389
|
+
return item.trim();
|
|
390
|
+
}
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
function stringParam(params, key) {
|
|
394
|
+
if (!params || typeof params !== "object" || Array.isArray(params))
|
|
395
|
+
return null;
|
|
396
|
+
const value = params[key];
|
|
397
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
398
|
+
}
|
|
399
|
+
function resolveScopedPath(root, filePath) {
|
|
400
|
+
if (!filePath)
|
|
401
|
+
throw new Error("ACP file request did not include a path");
|
|
402
|
+
const resolved = path.resolve(root, filePath);
|
|
403
|
+
const rootResolved = path.resolve(root);
|
|
404
|
+
if (resolved !== rootResolved && !resolved.startsWith(`${rootResolved}${path.sep}`)) {
|
|
405
|
+
throw new Error(`ACP file request is outside project root: ${filePath}`);
|
|
406
|
+
}
|
|
407
|
+
return resolved;
|
|
408
|
+
}
|
|
409
|
+
function mimoModeValue(mode) {
|
|
410
|
+
const normalized = String(mode || "").trim();
|
|
411
|
+
if (!normalized)
|
|
412
|
+
return null;
|
|
413
|
+
if (["agent", "agent-full-access", "full-access", "bypassPermissions", "danger-full-access"].includes(normalized))
|
|
414
|
+
return "build";
|
|
415
|
+
if (normalized === "read-only")
|
|
416
|
+
return "plan";
|
|
417
|
+
return normalized;
|
|
418
|
+
}
|
|
419
|
+
function firstConfigString(...values) {
|
|
420
|
+
for (const value of values) {
|
|
421
|
+
if (typeof value === "string" && value.trim())
|
|
422
|
+
return value.trim();
|
|
423
|
+
}
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
function errorMessage(error) {
|
|
427
|
+
return error instanceof Error ? error.message : String(error);
|
|
428
|
+
}
|
|
429
|
+
function readMimoDefaultModel() {
|
|
430
|
+
return process.env.ACA_MIMO_DEFAULT_MODEL || "mimo/mimo-auto";
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Mimo ACP 不接受图片块中的本地 uri 字段,只接受 ACP 标准的 base64 data。
|
|
434
|
+
* 附件仍由 Job Worker 落到工作区,文本提示会保留可供工具读取的本地路径。
|
|
435
|
+
*/
|
|
436
|
+
function promptBlocksToMimoAcpContent(prompt, blocks) {
|
|
437
|
+
const inputBlocks = blocks?.length ? blocks : [{ type: "text", text: prompt }];
|
|
438
|
+
return inputBlocks.map((block) => {
|
|
439
|
+
if (block.type === "image") {
|
|
440
|
+
return { type: "image", data: block.data, mimeType: block.mimeType };
|
|
441
|
+
}
|
|
442
|
+
return { type: "text", text: block.text };
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
function readMimoPromptTimeoutMs() {
|
|
446
|
+
const parsed = Number.parseInt(process.env.ACA_MIMO_PROMPT_TIMEOUT_MS ?? process.env.ACA_ACP_PROMPT_TIMEOUT_MS ?? "", 10);
|
|
447
|
+
return Number.isInteger(parsed) && parsed >= 30_000 ? parsed : 6 * 60 * 60 * 1000;
|
|
448
|
+
}
|