@koreanhole/opencodex 2.95.8 → 2.95.9
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/gui/dist/assets/{App-DNOitkaI.js → App-BCJkUFz0.js} +3 -3
- package/gui/dist/assets/{Tray-CEBkgHil.js → Tray-4YdW0xTe.js} +1 -1
- package/gui/dist/assets/{index-Dh04knNW.js → index-C2Pfq5Sa.js} +2 -2
- package/gui/dist/assets/{quota-summary-jEJ_zgb0.js → quota-summary-DbyjpSFU.js} +1 -1
- package/gui/dist/index.html +1 -1
- package/package.json +2 -1
- package/src/adapters/cli-agent/cline/acp.ts +1006 -0
- package/src/adapters/cli-agent/cline/adapter.ts +149 -55
- package/src/adapters/cli-agent/cline/binary.ts +29 -0
- package/src/adapters/cli-agent/cline/live-models.ts +68 -0
- package/src/adapters/cli-agent/cline/settings.ts +17 -3
- package/src/adapters/cli-agent/cline/stream.ts +15 -5
- package/src/cli/dispatch.ts +7 -1
- package/src/cli/registry.ts +3 -2
- package/src/codex/catalog/model-hints.ts +3 -0
- package/src/codex/catalog/provider-models.ts +45 -0
- package/src/generated/compatibility-version.json +24 -12
- package/src/oauth/login-cli.ts +51 -1
- package/src/providers/registry/entries-extended.ts +2 -2
- package/src/router.ts +5 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { fileURLToPath } from "node:url";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
2
|
import { writeFile } from "node:fs/promises";
|
|
4
|
-
import {
|
|
3
|
+
import { join } from "node:path";
|
|
5
4
|
import type { AdapterEvent, OcxParsedRequest, OcxProviderConfig } from "../../../types";
|
|
6
5
|
import type { AdapterRequest, ProviderAdapter } from "../../base";
|
|
7
6
|
import { buildCodingAgentToolCatalog, CodingAgentToolCatalogError } from "../../coding-agent/tool-catalog";
|
|
@@ -11,18 +10,26 @@ import { isStandaloneBinary } from "../../../lib/standalone";
|
|
|
11
10
|
import { runCliAgentTurn } from "../turn";
|
|
12
11
|
import type { CliAgentTurnDeps } from "../types";
|
|
13
12
|
import {
|
|
14
|
-
|
|
13
|
+
assertClineAdvisoryDestinationSafe,
|
|
15
14
|
buildClineChildEnv,
|
|
16
15
|
ClineSettingsError,
|
|
17
16
|
mergeClineDisabledTools,
|
|
18
17
|
preflightClineTools,
|
|
19
|
-
probeClineFlagCapabilities,
|
|
20
18
|
resolveClineProviderSettingsPath,
|
|
21
19
|
stageClineTurn,
|
|
22
|
-
type ClineFlagCapabilities,
|
|
23
|
-
type ClineSelection,
|
|
24
20
|
type ClineStagedTurn,
|
|
25
21
|
} from "./settings";
|
|
22
|
+
import { resolveClineBinary } from "./binary";
|
|
23
|
+
import {
|
|
24
|
+
buildClineAcpEnv,
|
|
25
|
+
ClineAcpError,
|
|
26
|
+
probeClineAcpSupport,
|
|
27
|
+
runClineAcpTurn,
|
|
28
|
+
stageClineAcpWorkspace,
|
|
29
|
+
type ClineAcpDeps,
|
|
30
|
+
type ClineAcpMode,
|
|
31
|
+
type ClineAcpTurnInput,
|
|
32
|
+
} from "./acp";
|
|
26
33
|
import {
|
|
27
34
|
classifyClineFailure,
|
|
28
35
|
createClineStreamState,
|
|
@@ -49,21 +56,13 @@ export interface ClineCliAdapterDeps extends CliAgentTurnDeps {
|
|
|
49
56
|
providerSettingsPath?: string;
|
|
50
57
|
stageTurn?: typeof stageClineTurn;
|
|
51
58
|
preflightTools?: typeof preflightClineTools;
|
|
52
|
-
|
|
59
|
+
probeAcp?: typeof probeClineAcpSupport;
|
|
60
|
+
stageAcp?: typeof stageClineAcpWorkspace;
|
|
61
|
+
runAcp?: (input: ClineAcpTurnInput, emit: (event: AdapterEvent) => void, deps?: ClineAcpDeps) => Promise<void>;
|
|
62
|
+
acpDeps?: ClineAcpDeps;
|
|
53
63
|
}
|
|
54
64
|
|
|
55
|
-
|
|
56
|
-
export function resolveClineBinary(
|
|
57
|
-
which: WhichFn = whichFromPath,
|
|
58
|
-
env: NodeJS.ProcessEnv = process.env,
|
|
59
|
-
): string | undefined {
|
|
60
|
-
const explicit = env.CLINE_BIN_PATH?.trim();
|
|
61
|
-
if (explicit && existsSync(explicit)) return explicit;
|
|
62
|
-
const resolved = which("cline");
|
|
63
|
-
if (!resolved) return undefined;
|
|
64
|
-
const cached = join(dirname(resolved), ".cline");
|
|
65
|
-
return existsSync(cached) ? cached : resolved;
|
|
66
|
-
}
|
|
65
|
+
export { resolveClineBinary } from "./binary";
|
|
67
66
|
|
|
68
67
|
function hasUnsupportedMedia(parsed: OcxParsedRequest): boolean {
|
|
69
68
|
return parsed.context.messages.some(message =>
|
|
@@ -107,23 +106,12 @@ export function buildClinePrompt(
|
|
|
107
106
|
].filter(Boolean).join("\n\n");
|
|
108
107
|
}
|
|
109
108
|
|
|
110
|
-
export function buildClineArgs(
|
|
111
|
-
selection?: ClineSelection,
|
|
112
|
-
capabilities: ClineFlagCapabilities = {},
|
|
113
|
-
): string[] {
|
|
109
|
+
export function buildClineArgs(): string[] {
|
|
114
110
|
// The CLI surface differs between Cline and its forks. Keep the invocation to the
|
|
115
|
-
// core JSON mode;
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
if (capabilities.providerFlag && selection?.providerId) {
|
|
120
|
-
args.push(capabilities.providerFlag, selection.providerId);
|
|
121
|
-
}
|
|
122
|
-
if (capabilities.modelFlag && selection?.model) {
|
|
123
|
-
args.push(capabilities.modelFlag, selection.model);
|
|
124
|
-
}
|
|
125
|
-
args.push("Follow the piped conversation and instructions.");
|
|
126
|
-
return args;
|
|
111
|
+
// core JSON mode; staged CLINE_* paths and settings carry config/tool policy, while
|
|
112
|
+
// the real HOME and the vendor provider store remain advisory. The fixed bridge
|
|
113
|
+
// text travels in the private stdin prompt.
|
|
114
|
+
return ["--json", "Follow the piped conversation and instructions."];
|
|
127
115
|
}
|
|
128
116
|
|
|
129
117
|
function toolNameMap(catalog: ReturnType<typeof buildCodingAgentToolCatalog>): Map<string, string> {
|
|
@@ -171,6 +159,27 @@ function settingsError(error: unknown): Extract<AdapterEvent, { type: "error" }>
|
|
|
171
159
|
};
|
|
172
160
|
}
|
|
173
161
|
|
|
162
|
+
function acpFailure(error: unknown): Extract<AdapterEvent, { type: "error" }> {
|
|
163
|
+
if (error instanceof ClineAcpError) {
|
|
164
|
+
return {
|
|
165
|
+
type: "error",
|
|
166
|
+
message: error.message,
|
|
167
|
+
status: error.status,
|
|
168
|
+
errorType: error.errorType,
|
|
169
|
+
code: error.code,
|
|
170
|
+
retryable: error.retryable,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
type: "error",
|
|
175
|
+
message: error instanceof Error ? error.message : String(error),
|
|
176
|
+
status: 502,
|
|
177
|
+
errorType: "upstream_error",
|
|
178
|
+
code: "acp_transport_error",
|
|
179
|
+
retryable: false,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
174
183
|
/** Create the additive, keyless Cline CLI provider adapter. */
|
|
175
184
|
export function createClineCliAdapter(
|
|
176
185
|
provider: OcxProviderConfig,
|
|
@@ -199,10 +208,11 @@ export function createClineCliAdapter(
|
|
|
199
208
|
});
|
|
200
209
|
return;
|
|
201
210
|
}
|
|
202
|
-
|
|
211
|
+
const requestedModel = typeof parsed.modelId === "string" ? parsed.modelId.trim() : "";
|
|
212
|
+
if (!requestedModel) {
|
|
203
213
|
emit({
|
|
204
214
|
type: "error",
|
|
205
|
-
message:
|
|
215
|
+
message: "Cline CLI requires a model selector.",
|
|
206
216
|
status: 400,
|
|
207
217
|
errorType: "invalid_request_error",
|
|
208
218
|
code: "model_not_found",
|
|
@@ -252,11 +262,106 @@ export function createClineCliAdapter(
|
|
|
252
262
|
return;
|
|
253
263
|
}
|
|
254
264
|
|
|
265
|
+
const vendorNames = toolNameMap(catalog);
|
|
266
|
+
const vendorNameByWire = new Map(
|
|
267
|
+
[...catalog.emittedNameMap.values()].map(wireName => [
|
|
268
|
+
wireName,
|
|
269
|
+
wireName.startsWith("opencodex__") ? wireName : `opencodex__${wireName}`,
|
|
270
|
+
]),
|
|
271
|
+
);
|
|
272
|
+
const fixedSystem = systemPromptWithToolNames(catalog);
|
|
273
|
+
const acpDeps: ClineAcpDeps = {
|
|
274
|
+
...deps.acpDeps,
|
|
275
|
+
env: sourceEnv,
|
|
276
|
+
...(deps.spawn ? { spawn: deps.spawn as ClineAcpDeps["spawn"] } : {}),
|
|
277
|
+
...(deps.platform ? { platform: deps.platform } : {}),
|
|
278
|
+
...(deps.timeoutMs !== undefined ? { timeoutMs: deps.timeoutMs } : {}),
|
|
279
|
+
...(deps.killGraceMs !== undefined ? { killGraceMs: deps.killGraceMs } : {}),
|
|
280
|
+
...(deps.reapTimeoutMs !== undefined ? { reapTimeoutMs: deps.reapTimeoutMs } : {}),
|
|
281
|
+
...(deps.killWindowsProcessTree ? { killWindowsProcessTree: deps.killWindowsProcessTree } : {}),
|
|
282
|
+
};
|
|
283
|
+
const acpEnv = buildClineAcpEnv(sourceEnv);
|
|
284
|
+
let acpMode: ClineAcpMode = { acp: false };
|
|
285
|
+
try {
|
|
286
|
+
acpMode = await (deps.probeAcp ?? probeClineAcpSupport)(binary, acpEnv, acpDeps);
|
|
287
|
+
} catch {
|
|
288
|
+
acpMode = { acp: false };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (acpMode.acp) {
|
|
292
|
+
let workspace: Awaited<ReturnType<typeof stageClineAcpWorkspace>> | undefined;
|
|
293
|
+
try {
|
|
294
|
+
workspace = await (deps.stageAcp ?? stageClineAcpWorkspace)(catalog.tools);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
emit(settingsError(error));
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const mcpServers = workspace.catalogPath && catalog.emittedNameMap.size > 0
|
|
300
|
+
? [{
|
|
301
|
+
type: "stdio",
|
|
302
|
+
name: CLINE_MCP_SERVER_NAME,
|
|
303
|
+
command: process.execPath,
|
|
304
|
+
args: isStandaloneBinary()
|
|
305
|
+
? ["__cli-agent-mcp", workspace.catalogPath]
|
|
306
|
+
: [CLINE_MCP_SERVER_PATH, workspace.catalogPath],
|
|
307
|
+
env: [],
|
|
308
|
+
}]
|
|
309
|
+
: [];
|
|
310
|
+
let acpOutput = false;
|
|
311
|
+
const acpEmit = (event: AdapterEvent): void => {
|
|
312
|
+
if (event.type === "text_delta" || event.type === "thinking_delta"
|
|
313
|
+
|| event.type === "tool_call_start" || event.type === "tool_call_delta" || event.type === "tool_call_end") {
|
|
314
|
+
acpOutput = true;
|
|
315
|
+
}
|
|
316
|
+
emit(event);
|
|
317
|
+
};
|
|
318
|
+
try {
|
|
319
|
+
await (deps.runAcp ?? runClineAcpTurn)({
|
|
320
|
+
binary,
|
|
321
|
+
env: acpEnv,
|
|
322
|
+
cwd: workspace.cwd,
|
|
323
|
+
modelId: requestedModel,
|
|
324
|
+
prompt: buildClinePrompt(parsed, vendorNameByWire, fixedSystem),
|
|
325
|
+
toolNames: vendorNames,
|
|
326
|
+
mcpServers,
|
|
327
|
+
args: acpMode.autoApproveFlag ? ["--acp", "--auto-approve", "false"] : ["--acp"],
|
|
328
|
+
maxToolCalls: 16,
|
|
329
|
+
requireToolCall: catalog.requireToolCall,
|
|
330
|
+
incoming,
|
|
331
|
+
deps: acpDeps,
|
|
332
|
+
}, acpEmit, acpDeps);
|
|
333
|
+
return;
|
|
334
|
+
} catch (error) {
|
|
335
|
+
if (!(error instanceof ClineAcpError && error.fallbackAllowed && !acpOutput && requestedModel === "default")) {
|
|
336
|
+
emit(acpFailure(error));
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (error.authRequired) {
|
|
340
|
+
console.warn("[opencodex] Cline ACP authentication required [code=acp_auth_required]; using one-shot fallback.");
|
|
341
|
+
} else {
|
|
342
|
+
console.warn("[opencodex] Cline ACP unavailable; using one-shot fallback.");
|
|
343
|
+
}
|
|
344
|
+
} finally {
|
|
345
|
+
await workspace.cleanup();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (requestedModel !== CLINE_CLI_MODEL_ID) {
|
|
350
|
+
emit({
|
|
351
|
+
type: "error",
|
|
352
|
+
message: `Cline ACP is required to select model "${requestedModel}". Enable ACP or use the default model.`,
|
|
353
|
+
status: 400,
|
|
354
|
+
errorType: "invalid_request_error",
|
|
355
|
+
code: "cline_model_unsupported",
|
|
356
|
+
retryable: false,
|
|
357
|
+
});
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
255
361
|
const providerSettingsPath = deps.providerSettingsPath
|
|
256
362
|
?? resolveClineProviderSettingsPath(sourceEnv);
|
|
257
|
-
let selection: ClineSelection;
|
|
258
363
|
try {
|
|
259
|
-
|
|
364
|
+
await assertClineAdvisoryDestinationSafe(providerSettingsPath);
|
|
260
365
|
} catch (error) {
|
|
261
366
|
emit(settingsError(error));
|
|
262
367
|
return;
|
|
@@ -307,25 +412,14 @@ export function createClineCliAdapter(
|
|
|
307
412
|
message: preflight.detail || "Cline tool preflight failed.",
|
|
308
413
|
status: 502,
|
|
309
414
|
errorType: "upstream_error",
|
|
310
|
-
code: "cline_tool_preflight_failed",
|
|
415
|
+
code: preflight.unsupported ? "cline_tool_preflight_unsupported" : "cline_tool_preflight_failed",
|
|
311
416
|
retryable: false,
|
|
312
417
|
});
|
|
313
418
|
return;
|
|
314
419
|
}
|
|
315
420
|
|
|
316
|
-
const vendorNames = toolNameMap(catalog);
|
|
317
|
-
const vendorNameByWire = new Map(
|
|
318
|
-
[...catalog.emittedNameMap.values()].map(wireName => [
|
|
319
|
-
wireName,
|
|
320
|
-
wireName.startsWith("opencodex__") ? wireName : `opencodex__${wireName}`,
|
|
321
|
-
]),
|
|
322
|
-
);
|
|
323
|
-
const toolNames = vendorNames;
|
|
324
421
|
const state = createClineStreamState();
|
|
325
|
-
const
|
|
326
|
-
const fixedSystem = systemPromptWithToolNames(catalog);
|
|
327
|
-
const capabilities = await (deps.probeFlags ?? probeClineFlagCapabilities)(binary, env);
|
|
328
|
-
const args = buildClineArgs(selection, capabilities);
|
|
422
|
+
const capabilities = buildClineArgs();
|
|
329
423
|
const promptFile = join(staged.root, "prompt.txt");
|
|
330
424
|
try {
|
|
331
425
|
await writeFile(promptFile, buildClinePrompt(parsed, vendorNameByWire, fixedSystem), {
|
|
@@ -349,7 +443,7 @@ export function createClineCliAdapter(
|
|
|
349
443
|
await runCliAgentTurn({
|
|
350
444
|
label: "Cline CLI",
|
|
351
445
|
binary,
|
|
352
|
-
args,
|
|
446
|
+
args: capabilities,
|
|
353
447
|
env,
|
|
354
448
|
cwd: staged.workspaceDir,
|
|
355
449
|
stdin: "",
|
|
@@ -358,8 +452,8 @@ export function createClineCliAdapter(
|
|
|
358
452
|
requireToolCall: catalog.requireToolCall,
|
|
359
453
|
classifyFailure: ({ exitCode, stderr }) => classifyClineFailure(exitCode, stderr),
|
|
360
454
|
parseLine: (message, current) => mapClineMessage(message, current, {
|
|
361
|
-
toolNames,
|
|
362
|
-
maxToolCalls,
|
|
455
|
+
toolNames: vendorNames,
|
|
456
|
+
maxToolCalls: 16,
|
|
363
457
|
}),
|
|
364
458
|
cleanup: staged.cleanup,
|
|
365
459
|
}, incoming, emit, deps);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, statSync } from "node:fs";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { whichFromPath, type WhichFn } from "../../coding-agent/profile";
|
|
5
|
+
|
|
6
|
+
/** Prefer the npm wrapper's cached native executable so stdio remains a regular file fd. */
|
|
7
|
+
export function resolveClineBinary(
|
|
8
|
+
which: WhichFn = whichFromPath,
|
|
9
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
10
|
+
): string | undefined {
|
|
11
|
+
const explicit = env.CLINE_BIN_PATH?.trim();
|
|
12
|
+
if (explicit && existsSync(explicit)) return explicit;
|
|
13
|
+
const resolved = which("cline");
|
|
14
|
+
if (!resolved) return undefined;
|
|
15
|
+
const cached = join(dirname(resolved), ".cline");
|
|
16
|
+
return existsSync(cached) ? cached : resolved;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Irreversible cache authority for one installed Cline binary. */
|
|
20
|
+
export function clineBinaryFingerprintSync(binary: string): string {
|
|
21
|
+
const path = resolve(binary);
|
|
22
|
+
try {
|
|
23
|
+
const value = statSync(path);
|
|
24
|
+
return createHash("sha256").update(JSON.stringify([path, value.mtimeMs, value.size])).digest("hex");
|
|
25
|
+
} catch {
|
|
26
|
+
return createHash("sha256").update(path).digest("hex");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ClineAcpClient, ClineAcpError, buildClineAcpEnv, clineAcpEnabled, clineModelSource, probeClineAcpSupport, stageClineAcpWorkspace, type ClineAcpDeps } from "./acp";
|
|
2
|
+
import { clineBinaryFingerprintSync, resolveClineBinary } from "./binary";
|
|
3
|
+
|
|
4
|
+
export type ClineLiveModelsFailure = "not_found" | "unsupported" | "auth_required" | "transport";
|
|
5
|
+
|
|
6
|
+
export type ClineLiveModelsResult =
|
|
7
|
+
| {
|
|
8
|
+
ok: true;
|
|
9
|
+
models: string[];
|
|
10
|
+
source: "models" | "configOptions" | "default";
|
|
11
|
+
fingerprint: string;
|
|
12
|
+
}
|
|
13
|
+
| {
|
|
14
|
+
ok: false;
|
|
15
|
+
reason: ClineLiveModelsFailure;
|
|
16
|
+
detail: string;
|
|
17
|
+
fingerprint?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export interface ClineLiveModelsOptions {
|
|
21
|
+
binary?: string;
|
|
22
|
+
env?: NodeJS.ProcessEnv;
|
|
23
|
+
deps?: ClineAcpDeps;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* One short ACP discovery session. The caller owns catalog freshness; this function only
|
|
28
|
+
* proves which native models the current binary and vendor account expose.
|
|
29
|
+
*/
|
|
30
|
+
export async function discoverClineLiveModels(options: ClineLiveModelsOptions = {}): Promise<ClineLiveModelsResult> {
|
|
31
|
+
const sourceEnv = options.env ?? process.env;
|
|
32
|
+
if (!clineAcpEnabled(sourceEnv)) {
|
|
33
|
+
return { ok: false, reason: "unsupported", detail: "ACP is disabled by OPENCODEX_CLINE_ACP=0." };
|
|
34
|
+
}
|
|
35
|
+
const binary = options.binary ?? resolveClineBinary(undefined, sourceEnv);
|
|
36
|
+
if (!binary) return { ok: false, reason: "not_found", detail: "Cline CLI not found on PATH." };
|
|
37
|
+
const fingerprint = clineBinaryFingerprintSync(binary);
|
|
38
|
+
const env = buildClineAcpEnv(sourceEnv);
|
|
39
|
+
const mode = await probeClineAcpSupport(binary, env, { ...options.deps, env: sourceEnv });
|
|
40
|
+
if (!mode.acp) return { ok: false, reason: "unsupported", detail: "Cline --help does not advertise --acp.", fingerprint };
|
|
41
|
+
const workspace = await stageClineAcpWorkspace();
|
|
42
|
+
let client: ClineAcpClient | undefined;
|
|
43
|
+
try {
|
|
44
|
+
client = await ClineAcpClient.connect({
|
|
45
|
+
binary,
|
|
46
|
+
env,
|
|
47
|
+
cwd: workspace.cwd,
|
|
48
|
+
args: mode.autoApproveFlag ? ["--acp", "--auto-approve", "false"] : ["--acp"],
|
|
49
|
+
}, options.deps);
|
|
50
|
+
const session = await client.newSession({ cwd: workspace.cwd, mcpServers: [] });
|
|
51
|
+
const discovered = clineModelSource(session);
|
|
52
|
+
return { ok: true, models: discovered.ids, source: discovered.source, fingerprint };
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error instanceof ClineAcpError && error.authRequired) {
|
|
55
|
+
return { ok: false, reason: "auth_required", detail: error.message, fingerprint };
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
reason: "transport",
|
|
60
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
61
|
+
fingerprint,
|
|
62
|
+
};
|
|
63
|
+
} finally {
|
|
64
|
+
await client?.terminate();
|
|
65
|
+
await workspace.cleanup();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
@@ -75,6 +75,7 @@ export interface ClineEnabledTool {
|
|
|
75
75
|
|
|
76
76
|
export interface ClineToolPreflightResult {
|
|
77
77
|
ok: boolean;
|
|
78
|
+
unsupported?: boolean;
|
|
78
79
|
detail?: string;
|
|
79
80
|
enabledTools?: readonly ClineEnabledTool[];
|
|
80
81
|
}
|
|
@@ -278,6 +279,19 @@ export async function assertClineSelectionSafe(providerSettingsPath: string): Pr
|
|
|
278
279
|
};
|
|
279
280
|
}
|
|
280
281
|
|
|
282
|
+
/**
|
|
283
|
+
* The vendor store is advisory in one-shot mode: absence or another schema is Cline's
|
|
284
|
+
* business. Only an explicit route back through this proxy remains a hard refusal.
|
|
285
|
+
*/
|
|
286
|
+
export async function assertClineAdvisoryDestinationSafe(providerSettingsPath: string): Promise<void> {
|
|
287
|
+
try {
|
|
288
|
+
await assertClineSelectionSafe(providerSettingsPath);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
if (error instanceof ClineSettingsError && error.code === "cli_provider_recursion") throw error;
|
|
291
|
+
if (!(error instanceof ClineSettingsError)) throw error;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
281
295
|
function clineGlobalSettings(): Record<string, unknown> {
|
|
282
296
|
return {
|
|
283
297
|
telemetryOptOut: true,
|
|
@@ -634,16 +648,16 @@ export async function preflightClineTools(input: {
|
|
|
634
648
|
try {
|
|
635
649
|
result = await runPreflightProcess(input.binary, ["config", "tools", "--json"], input.env, input.cwd);
|
|
636
650
|
} catch (error) {
|
|
637
|
-
return { ok: false, detail: error instanceof Error ? error.message : String(error) };
|
|
651
|
+
return { ok: false, unsupported: true, detail: error instanceof Error ? error.message : String(error) };
|
|
638
652
|
}
|
|
639
653
|
if (result.exitCode !== 0) {
|
|
640
|
-
return { ok: false, detail: result.stderr.trim() || `Cline tool preflight exited with code ${result.exitCode}.` };
|
|
654
|
+
return { ok: false, unsupported: true, detail: result.stderr.trim() || `Cline tool preflight exited with code ${result.exitCode}.` };
|
|
641
655
|
}
|
|
642
656
|
let parsed: unknown;
|
|
643
657
|
try {
|
|
644
658
|
parsed = JSON.parse(result.stdout);
|
|
645
659
|
} catch {
|
|
646
|
-
return { ok: false, detail: "Cline tool preflight returned non-JSON output." };
|
|
660
|
+
return { ok: false, unsupported: true, detail: "Cline tool preflight returned non-JSON output." };
|
|
647
661
|
}
|
|
648
662
|
return validateClineToolCatalog(parsed);
|
|
649
663
|
}
|
|
@@ -104,12 +104,14 @@ function toolArguments(input: unknown): string {
|
|
|
104
104
|
return serialized;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
/** Map a validated tool intent from either Cline's NDJSON or ACP event shape. */
|
|
108
|
+
export function mapClineToolIntent(
|
|
109
|
+
vendorName: string,
|
|
110
|
+
toolCallId: string | undefined,
|
|
111
|
+
input: unknown,
|
|
109
112
|
state: CliAgentStreamState,
|
|
110
113
|
options: ClineStreamOptions,
|
|
111
114
|
): AdapterEvent[] {
|
|
112
|
-
const vendorName = asString(event.toolName);
|
|
113
115
|
if (!vendorName) throw new ClineStreamProtocolError("Cline returned a tool call without a name.");
|
|
114
116
|
const wireName = options.toolNames.get(vendorName);
|
|
115
117
|
if (!wireName) {
|
|
@@ -120,8 +122,8 @@ function mapToolCall(
|
|
|
120
122
|
throw new ClineStreamProtocolError("Cline exceeded the per-turn tool-call limit.");
|
|
121
123
|
}
|
|
122
124
|
const ordinal = ++state.nextToolOrdinal;
|
|
123
|
-
const id =
|
|
124
|
-
const argumentsJson = toolArguments(
|
|
125
|
+
const id = toolCallId?.trim() || `cline-tool-${ordinal}`;
|
|
126
|
+
const argumentsJson = toolArguments(input);
|
|
125
127
|
state.toolCalls++;
|
|
126
128
|
return [
|
|
127
129
|
{ type: "tool_call_start", id, name: wireName },
|
|
@@ -130,6 +132,14 @@ function mapToolCall(
|
|
|
130
132
|
];
|
|
131
133
|
}
|
|
132
134
|
|
|
135
|
+
function mapToolCall(
|
|
136
|
+
event: Record<string, unknown>,
|
|
137
|
+
state: CliAgentStreamState,
|
|
138
|
+
options: ClineStreamOptions,
|
|
139
|
+
): AdapterEvent[] {
|
|
140
|
+
return mapClineToolIntent(asString(event.toolName) ?? "", asString(event.toolCallId), event.input, state, options);
|
|
141
|
+
}
|
|
142
|
+
|
|
133
143
|
function mapAgentEvent(
|
|
134
144
|
event: Record<string, unknown>,
|
|
135
145
|
state: CliAgentStreamState,
|
package/src/cli/dispatch.ts
CHANGED
|
@@ -377,6 +377,8 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
377
377
|
// It is deliberately not `--code`: that name carries a secret value everywhere else in the
|
|
378
378
|
// CLI (`ocx account login <provider> --code <value>`) and its operand is scrubbed from stderr.
|
|
379
379
|
const codeMode = loginArgs.includes("--headless");
|
|
380
|
+
const json = loginArgs.includes("--json");
|
|
381
|
+
const noWait = loginArgs.includes("--no-wait");
|
|
380
382
|
const positional = loginArgs.filter(arg => !arg.startsWith("--"));
|
|
381
383
|
// 'ocx login codex' is the command people type first, and until now it answered with
|
|
382
384
|
// the full provider wall because the Codex pool lives behind 'ocx account login'.
|
|
@@ -397,7 +399,11 @@ const commandRunners: Record<string, CommandRunner> = {
|
|
|
397
399
|
return code ?? 1;
|
|
398
400
|
}
|
|
399
401
|
const { handleLogin } = await import("../oauth/login-cli");
|
|
400
|
-
|
|
402
|
+
if (positional[0] !== "cline-cli" && (json || noWait)) {
|
|
403
|
+
console.error("--json and --no-wait are only supported by `ocx login cline-cli`.");
|
|
404
|
+
return 1;
|
|
405
|
+
}
|
|
406
|
+
await handleLogin(positional[0], {}, { codeMode, json, noWait });
|
|
401
407
|
return 0;
|
|
402
408
|
},
|
|
403
409
|
logout: async deps => {
|
package/src/cli/registry.ts
CHANGED
|
@@ -237,10 +237,11 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
237
237
|
},
|
|
238
238
|
{
|
|
239
239
|
name: "login",
|
|
240
|
-
usage: "ocx login <provider> [--headless]",
|
|
241
|
-
summary: "OAuth
|
|
240
|
+
usage: "ocx login <provider> [--headless] [--json] [--no-wait]",
|
|
241
|
+
summary: "OAuth, API-key, or ACP login for a provider.",
|
|
242
242
|
details: [
|
|
243
243
|
"--headless (anthropic, anthropic2): no browser and no local port; approve in any browser and paste the displayed code.",
|
|
244
|
+
"cline-cli: ACP auth; --json emits bounded method/instruction state and --no-wait returns after relaying instructions.",
|
|
244
245
|
"Codex/ChatGPT: ocx login codex runs the Codex account-pool login (same flow as ocx account login codex).",
|
|
245
246
|
"That one needs a running proxy; the OAuth and API-key providers log in locally.",
|
|
246
247
|
"'chatgpt' and 'openai' are the same route; an OpenAI platform API key is 'ocx login openai-apikey'.",
|
|
@@ -500,6 +500,9 @@ export function suppressedSyntheticMaxCatalogSlugs(
|
|
|
500
500
|
export const QUIET_AUTHORITATIVE_CATALOG_PROVIDERS = new Set(["kimi", "xai", "google-antigravity"]);
|
|
501
501
|
|
|
502
502
|
export const CALLABLE_CONFIGURED_COMPATIBILITY_MODELS: Readonly<Record<string, ReadonlySet<string>>> = {
|
|
503
|
+
// Cline's live ACP roster names concrete native model ids, while `default` remains the
|
|
504
|
+
// callable compatibility selector for the vendor's current model.
|
|
505
|
+
"cline-cli": new Set(["default"]),
|
|
503
506
|
// CodeBuddy's vendor defaults are real callable selectors — both the bundled manifests
|
|
504
507
|
// (`product.json` / `product.internal.json`) and `--model` accept them — but the key-scoped
|
|
505
508
|
// configuration roster omits them. Without this entry, a successful live roster
|
|
@@ -67,6 +67,7 @@ import { fetchQoderModels } from "../../adapters/qoder/live-models";
|
|
|
67
67
|
import { resolveQoderProfile } from "../../adapters/qoder/profiles";
|
|
68
68
|
import { CODEBUDDY_PROFILES, type CodeBuddyProfile } from "../../adapters/codebuddy/profiles";
|
|
69
69
|
import { fetchCodeBuddyModels } from "../../adapters/codebuddy/live-models";
|
|
70
|
+
import { clineBinaryFingerprintSync, resolveClineBinary } from "../../adapters/cli-agent/cline/binary";
|
|
70
71
|
import { resolveProfileByBaseUrl } from "../../adapters/coding-agent/profile";
|
|
71
72
|
import { fetchDevinUsableModels } from "../../adapters/devin/live-models";
|
|
72
73
|
import { resolveDevinApiBaseUrl } from "../../oauth/devin/api-base";
|
|
@@ -272,6 +273,50 @@ export async function fetchProviderModelsWithAuth(
|
|
|
272
273
|
}
|
|
273
274
|
return observed(configured, "authoritative");
|
|
274
275
|
}
|
|
276
|
+
if (prov.adapter === "cline-cli") {
|
|
277
|
+
const binary = resolveClineBinary(undefined, process.env);
|
|
278
|
+
if (!binary) return observed(configured, "degraded");
|
|
279
|
+
const authorityIdentity = clineBinaryFingerprintSync(binary);
|
|
280
|
+
const fresh = getFreshCached(name, ttlMs, Date.now(), authorityIdentity);
|
|
281
|
+
if (fresh) {
|
|
282
|
+
return observed(withConfiguredRetention(
|
|
283
|
+
applyConfigHintsToCachedModels(name, prov, fresh, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir, false),
|
|
284
|
+
), "authoritative");
|
|
285
|
+
}
|
|
286
|
+
const stale = getStaleCached(name, authorityIdentity);
|
|
287
|
+
if (isModelsFetchCoolingDown(name, 60_000, Date.now(), authorityIdentity)) {
|
|
288
|
+
return observed(withConfiguredRetention(
|
|
289
|
+
stale
|
|
290
|
+
? applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir, false)
|
|
291
|
+
: configured,
|
|
292
|
+
), "degraded");
|
|
293
|
+
}
|
|
294
|
+
const { discoverClineLiveModels } = await import("../../adapters/cli-agent/cline/live-models");
|
|
295
|
+
const live = await discoverClineLiveModels({ binary });
|
|
296
|
+
if (live.ok) {
|
|
297
|
+
const discovered = live.models.map(id => ({
|
|
298
|
+
id,
|
|
299
|
+
provider: name,
|
|
300
|
+
...catalogHintsFromProviderConfig(name, prov, id, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir, false),
|
|
301
|
+
}));
|
|
302
|
+
const forCache = withConfiguredRetention(discovered, { retainComboTargets: false });
|
|
303
|
+
if (!setCached(name, forCache, Date.now(), cacheGeneration, authorityIdentity)) {
|
|
304
|
+
return observed(withConfiguredRetention(configured), "degraded");
|
|
305
|
+
}
|
|
306
|
+
markProviderDiscoveryOk(name, live.models.length);
|
|
307
|
+
return observed(withConfiguredRetention(forCache, { warnDrops: true }), "authoritative");
|
|
308
|
+
}
|
|
309
|
+
if (isCurrentCacheGeneration()) {
|
|
310
|
+
markModelsFetchFailure(name, Date.now(), authorityIdentity);
|
|
311
|
+
markProviderDiscoveryFailed(name, { reason: "provider" });
|
|
312
|
+
console.warn(
|
|
313
|
+
`[opencodex] Cline ACP model discovery failed [${live.reason}]${live.detail ? `: ${live.detail}` : ""}; using stale/static catalog degradation.`,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
return observed(withConfiguredRetention(
|
|
317
|
+
stale ? applyConfigHintsToCachedModels(name, prov, stale, contextCap, metadataModelIdCaseFold, captured.effectiveAlias, metadataConfigDir, false) : configured,
|
|
318
|
+
), "degraded");
|
|
319
|
+
}
|
|
275
320
|
const auth: ModelsAuthResolution = prov.authMode === "oauth" && !mayResolveModelsOAuth(name, prov)
|
|
276
321
|
? { apiKey: undefined, observed: resolveAuth.kind === "observed" }
|
|
277
322
|
: captured.observedAuth ?? (resolveAuth.kind === "refreshing"
|