@jait/gateway 0.1.415 → 0.1.419
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/dist/providers/acp-provider.d.ts +4 -2
- package/dist/providers/acp-provider.d.ts.map +1 -1
- package/dist/providers/acp-provider.js +262 -123
- package/dist/providers/acp-provider.js.map +1 -1
- package/dist/providers/contracts.d.ts +2 -2
- package/dist/providers/contracts.d.ts.map +1 -1
- package/dist/providers/registry.js +1 -1
- package/dist/providers/registry.js.map +1 -1
- package/dist/routes/chat.d.ts.map +1 -1
- package/dist/routes/chat.js +37 -21
- package/dist/routes/chat.js.map +1 -1
- package/package.json +1 -1
- package/web-dist/assets/{_basePickBy-Vej0n7ts.js → _basePickBy-DPfVIRAk.js} +1 -1
- package/web-dist/assets/{_baseUniq-IJ0sJU1m.js → _baseUniq-CoY2kA2J.js} +1 -1
- package/web-dist/assets/{arc-BeHu1aOb.js → arc-C1T2VsVa.js} +1 -1
- package/web-dist/assets/{architectureDiagram-2XIMDMQ5-Bp6z5ps0.js → architectureDiagram-2XIMDMQ5-yPpU9I5l.js} +1 -1
- package/web-dist/assets/{blockDiagram-WCTKOSBZ-Cx_zzsRg.js → blockDiagram-WCTKOSBZ-C1pfdQSr.js} +1 -1
- package/web-dist/assets/{c4Diagram-IC4MRINW-DUa0YpGC.js → c4Diagram-IC4MRINW-BVSh5Fqx.js} +1 -1
- package/web-dist/assets/channel-BDkYsH0o.js +1 -0
- package/web-dist/assets/{chunk-4BX2VUAB-tpj87VmD.js → chunk-4BX2VUAB-Ccr_kv0Q.js} +1 -1
- package/web-dist/assets/{chunk-55IACEB6-B_ZFHP09.js → chunk-55IACEB6-Z-XxFc_X.js} +1 -1
- package/web-dist/assets/{chunk-FMBD7UC4-CTvcgmSZ.js → chunk-FMBD7UC4-Pkno2glL.js} +1 -1
- package/web-dist/assets/{chunk-JSJVCQXG-B7h9KpNR.js → chunk-JSJVCQXG-BJ3W969q.js} +1 -1
- package/web-dist/assets/{chunk-KX2RTZJC-D9Vq1pQV.js → chunk-KX2RTZJC-BzELc_J8.js} +1 -1
- package/web-dist/assets/{chunk-NQ4KR5QH-C7aaLWfv.js → chunk-NQ4KR5QH-D7dBHFba.js} +1 -1
- package/web-dist/assets/{chunk-QZHKN3VN-DL14VDvc.js → chunk-QZHKN3VN-BplMEvVR.js} +1 -1
- package/web-dist/assets/{chunk-WL4C6EOR-Dj9yLTN_.js → chunk-WL4C6EOR-FlLV5EOE.js} +1 -1
- package/web-dist/assets/classDiagram-VBA2DB6C-ougbB9yj.js +1 -0
- package/web-dist/assets/classDiagram-v2-RAHNMMFH-ougbB9yj.js +1 -0
- package/web-dist/assets/clone-ChknCnMs.js +1 -0
- package/web-dist/assets/{cose-bilkent-S5V4N54A-BWtWPOzl.js → cose-bilkent-S5V4N54A-m4ikUNqH.js} +1 -1
- package/web-dist/assets/{dagre-KLK3FWXG-BGfWBJVo.js → dagre-KLK3FWXG-C4WXsuqt.js} +1 -1
- package/web-dist/assets/{diagram-E7M64L7V-Cke9c3vx.js → diagram-E7M64L7V-Csk2Jgho.js} +1 -1
- package/web-dist/assets/{diagram-IFDJBPK2-BRRl8D9K.js → diagram-IFDJBPK2-BZr0a637.js} +1 -1
- package/web-dist/assets/{diagram-P4PSJMXO-BuZo-ISM.js → diagram-P4PSJMXO-dkyXMiS6.js} +1 -1
- package/web-dist/assets/{erDiagram-INFDFZHY-Plok75TS.js → erDiagram-INFDFZHY-DbQqScPT.js} +1 -1
- package/web-dist/assets/{flowDiagram-PKNHOUZH-DrTFnv1o.js → flowDiagram-PKNHOUZH-B1zA3pR2.js} +1 -1
- package/web-dist/assets/{ganttDiagram-A5KZAMGK-lbd5t50L.js → ganttDiagram-A5KZAMGK-DdyvmvzW.js} +1 -1
- package/web-dist/assets/{gitGraphDiagram-K3NZZRJ6-4bqi_Grw.js → gitGraphDiagram-K3NZZRJ6-BD-jKOoD.js} +1 -1
- package/web-dist/assets/{graph-BJxBQW7G.js → graph-DTz_lMuX.js} +1 -1
- package/web-dist/assets/{index-BGe0OrqB.js → index-CiJZgv18.js} +1 -1
- package/web-dist/assets/{index-D-Q3NBqs.js → index-D3wvFOt1.js} +1 -1
- package/web-dist/assets/{index-DT-yEdLN.js → index-b6Lfsnr6.js} +162 -162
- package/web-dist/assets/{infoDiagram-LFFYTUFH-mOUWUKTg.js → infoDiagram-LFFYTUFH-DtGnxrtE.js} +1 -1
- package/web-dist/assets/{ishikawaDiagram-PHBUUO56-C-ISusUx.js → ishikawaDiagram-PHBUUO56-Djz5cOl0.js} +1 -1
- package/web-dist/assets/{journeyDiagram-4ABVD52K-znHcSODe.js → journeyDiagram-4ABVD52K-DeksSAXF.js} +1 -1
- package/web-dist/assets/{kanban-definition-K7BYSVSG-B9ekOJxL.js → kanban-definition-K7BYSVSG-gYeJU2K7.js} +1 -1
- package/web-dist/assets/{layout-B_RPzsC2.js → layout-orO3aKWY.js} +1 -1
- package/web-dist/assets/{linear-DZLBRrsE.js → linear-_QMRlue7.js} +1 -1
- package/web-dist/assets/{mindmap-definition-YRQLILUH-D3XCkmFm.js → mindmap-definition-YRQLILUH-8wRw1CND.js} +1 -1
- package/web-dist/assets/{pieDiagram-SKSYHLDU-B2rKp15l.js → pieDiagram-SKSYHLDU-BifZPLKU.js} +1 -1
- package/web-dist/assets/{quadrantDiagram-337W2JSQ-B3DzAIDT.js → quadrantDiagram-337W2JSQ-CdZKrW_Y.js} +1 -1
- package/web-dist/assets/{requirementDiagram-Z7DCOOCP-C4aT5lvg.js → requirementDiagram-Z7DCOOCP-QDuW92Qu.js} +1 -1
- package/web-dist/assets/{sankeyDiagram-WA2Y5GQK-CwdIKXWh.js → sankeyDiagram-WA2Y5GQK-DFZCLAHQ.js} +1 -1
- package/web-dist/assets/{sequenceDiagram-2WXFIKYE-C85lQ8Bt.js → sequenceDiagram-2WXFIKYE-D5dAX5pt.js} +1 -1
- package/web-dist/assets/{stateDiagram-RAJIS63D-C6gFkSrw.js → stateDiagram-RAJIS63D-BU9FdkI0.js} +1 -1
- package/web-dist/assets/stateDiagram-v2-FVOUBMTO-LBDX1YCg.js +1 -0
- package/web-dist/assets/{timeline-definition-YZTLITO2-B6sQ4e6y.js → timeline-definition-YZTLITO2-CX4lUF5E.js} +1 -1
- package/web-dist/assets/{treemap-KZPCXAKY-DqwxJSZi.js → treemap-KZPCXAKY-ByDWAYkU.js} +1 -1
- package/web-dist/assets/{vennDiagram-LZ73GAT5-BkJxDniY.js → vennDiagram-LZ73GAT5-B1sk3TR5.js} +1 -1
- package/web-dist/assets/{xychartDiagram-JWTSCODW-BbHdBYjX.js → xychartDiagram-JWTSCODW-GkXX82wb.js} +1 -1
- package/web-dist/index.html +1 -1
- package/web-dist/assets/channel-Dd_6YMtj.js +0 -1
- package/web-dist/assets/classDiagram-VBA2DB6C-DGa9Vmq1.js +0 -1
- package/web-dist/assets/classDiagram-v2-RAHNMMFH-DGa9Vmq1.js +0 -1
- package/web-dist/assets/clone-BZHgrnhF.js +0 -1
- package/web-dist/assets/stateDiagram-v2-FVOUBMTO-BsX0KLYS.js +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type SessionNotification } from "@agentclientprotocol/sdk";
|
|
2
2
|
import type { CliProviderAdapter, ProviderEvent, ProviderAuthStatus, ProviderLoginResult, ProviderLogoutResult, ProviderId, ProviderInfo, ProviderModelInfo, ProviderSession, RuntimeMode, StartSessionOptions } from "./contracts.js";
|
|
3
|
-
type AcpProviderAuthKind = "
|
|
3
|
+
type AcpProviderAuthKind = "acp";
|
|
4
4
|
export interface AcpProviderConfig {
|
|
5
5
|
id: ProviderId;
|
|
6
6
|
name: string;
|
|
@@ -19,11 +19,13 @@ export declare class AcpProvider implements CliProviderAdapter {
|
|
|
19
19
|
private readonly sessions;
|
|
20
20
|
private readonly emitter;
|
|
21
21
|
private authLoginProcess;
|
|
22
|
+
private cachedModels;
|
|
22
23
|
constructor(config: AcpProviderConfig);
|
|
23
24
|
checkAvailability(): Promise<boolean>;
|
|
24
25
|
listModels(): Promise<ProviderModelInfo[]>;
|
|
25
26
|
getAuthStatus(): Promise<ProviderAuthStatus>;
|
|
26
27
|
startLogin(): Promise<ProviderLoginResult>;
|
|
28
|
+
private checkProviderAuthenticated;
|
|
27
29
|
logout(): Promise<ProviderLogoutResult>;
|
|
28
30
|
startSession(options: StartSessionOptions): Promise<ProviderSession>;
|
|
29
31
|
sendTurn(sessionId: string, message: string, attachments?: string[]): Promise<void>;
|
|
@@ -34,7 +36,7 @@ export declare class AcpProvider implements CliProviderAdapter {
|
|
|
34
36
|
emitEvent(event: ProviderEvent): void;
|
|
35
37
|
handleSessionUpdate(sessionId: string, params: SessionNotification): void;
|
|
36
38
|
private getSession;
|
|
37
|
-
private
|
|
39
|
+
private probeAcpAuth;
|
|
38
40
|
}
|
|
39
41
|
export declare function loadAcpProviderConfigs(): AcpProviderConfig[];
|
|
40
42
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"acp-provider.d.ts","sourceRoot":"","sources":["../../src/providers/acp-provider.ts"],"names":[],"mappings":"AAMA,OAAO,
|
|
1
|
+
{"version":3,"file":"acp-provider.d.ts","sourceRoot":"","sources":["../../src/providers/acp-provider.ts"],"names":[],"mappings":"AAMA,OAAO,EAWL,KAAK,mBAAmB,EACzB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EACV,kBAAkB,EAElB,aAAa,EACb,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,UAAU,EACV,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,WAAW,EACX,mBAAmB,EACpB,MAAM,gBAAgB,CAAC;AASxB,KAAK,mBAAmB,GAAG,KAAK,CAAC;AAEjC,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,UAAU,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;IACtB,IAAI,CAAC,EAAE,mBAAmB,GAAG,KAAK,CAAC;CACpC;AAwDD,qBAAa,WAAY,YAAW,kBAAkB;IACpD,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAE5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA8E;IACrG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsC;IAC/D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,gBAAgB,CAA6B;IACrD,OAAO,CAAC,YAAY,CAAoC;gBAE5C,MAAM,EAAE,iBAAiB;IAmB/B,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC;IAmBrC,UAAU,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAmC1C,aAAa,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAmB5C,UAAU,IAAI,OAAO,CAAC,mBAAmB,CAAC;YAwDlC,0BAA0B;IAWlC,MAAM,IAAI,OAAO,CAAC,oBAAoB,CAAC;IA4BvC,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,eAAe,CAAC;IAgGpE,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BnF,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK/C,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAYzF,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcnD,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAAG,MAAM,IAAI;IAK5D,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAIrC,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,GAAG,IAAI;IAkEzE,OAAO,CAAC,UAAU;YAMJ,YAAY;CAgD3B;AAkHD,wBAAgB,sBAAsB,IAAI,iBAAiB,EAAE,CA4D5D"}
|
|
@@ -1,23 +1,34 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
|
-
import { existsSync, readFileSync
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { Readable, Writable } from "node:stream";
|
|
7
7
|
import { ClientSideConnection, PROTOCOL_VERSION, ndJsonStream, } from "@agentclientprotocol/sdk";
|
|
8
8
|
import { uuidv7 } from "../db/uuidv7.js";
|
|
9
|
-
import {
|
|
9
|
+
import { NO_PROVIDER_AUTH, killChildTree as killAuthChildTree, runAuthCommand, unsupportedLogin, unsupportedLogout, } from "./provider-auth.js";
|
|
10
10
|
class JaitAcpClient {
|
|
11
11
|
provider;
|
|
12
12
|
sessionId;
|
|
13
13
|
approvals;
|
|
14
|
-
|
|
14
|
+
runtimeMode;
|
|
15
|
+
constructor(provider, sessionId, approvals, runtimeMode) {
|
|
15
16
|
this.provider = provider;
|
|
16
17
|
this.sessionId = sessionId;
|
|
17
18
|
this.approvals = approvals;
|
|
19
|
+
this.runtimeMode = runtimeMode;
|
|
18
20
|
}
|
|
19
21
|
async requestPermission(params) {
|
|
22
|
+
const allowOptionId = params.options.find((option) => option.kind.startsWith("allow"))?.optionId ?? params.options[0]?.optionId ?? "allow";
|
|
23
|
+
const rejectOptionId = params.options.find((option) => option.kind.startsWith("reject"))?.optionId ?? params.options.at(-1)?.optionId ?? "reject";
|
|
24
|
+
// In full-access mode, auto-approve all tool requests without prompting.
|
|
25
|
+
if (this.runtimeMode === "full-access") {
|
|
26
|
+
return { outcome: { outcome: "selected", optionId: allowOptionId } };
|
|
27
|
+
}
|
|
20
28
|
const requestId = uuidv7();
|
|
29
|
+
const response = new Promise((resolve) => {
|
|
30
|
+
this.approvals.set(requestId, { allowOptionId, rejectOptionId, resolve });
|
|
31
|
+
});
|
|
21
32
|
this.provider.emitEvent({
|
|
22
33
|
type: "tool.approval-required",
|
|
23
34
|
sessionId: this.sessionId,
|
|
@@ -25,11 +36,7 @@ class JaitAcpClient {
|
|
|
25
36
|
args: params.toolCall.rawInput,
|
|
26
37
|
requestId,
|
|
27
38
|
});
|
|
28
|
-
|
|
29
|
-
const rejectOptionId = params.options.find((option) => option.kind.startsWith("reject"))?.optionId ?? params.options.at(-1)?.optionId ?? "reject";
|
|
30
|
-
return new Promise((resolve) => {
|
|
31
|
-
this.approvals.set(requestId, { allowOptionId, rejectOptionId, resolve });
|
|
32
|
-
});
|
|
39
|
+
return response;
|
|
33
40
|
}
|
|
34
41
|
async sessionUpdate(params) {
|
|
35
42
|
this.provider.handleSessionUpdate(this.sessionId, params);
|
|
@@ -43,9 +50,10 @@ export class AcpProvider {
|
|
|
43
50
|
sessions = new Map();
|
|
44
51
|
emitter = new EventEmitter();
|
|
45
52
|
authLoginProcess = null;
|
|
53
|
+
cachedModels = null;
|
|
46
54
|
constructor(config) {
|
|
47
55
|
this.id = config.id;
|
|
48
|
-
this.authKind = config.auth === false ? null :
|
|
56
|
+
this.authKind = config.auth === false ? null : "acp";
|
|
49
57
|
this.config = {
|
|
50
58
|
...config,
|
|
51
59
|
args: config.args ?? [],
|
|
@@ -58,7 +66,7 @@ export class AcpProvider {
|
|
|
58
66
|
description: config.description,
|
|
59
67
|
available: false,
|
|
60
68
|
modes: this.config.modes,
|
|
61
|
-
auth: this.authKind ?
|
|
69
|
+
auth: this.authKind ? { login: true, logout: false, deviceCode: false } : NO_PROVIDER_AUTH,
|
|
62
70
|
};
|
|
63
71
|
}
|
|
64
72
|
async checkAvailability() {
|
|
@@ -78,91 +86,143 @@ export class AcpProvider {
|
|
|
78
86
|
return true;
|
|
79
87
|
}
|
|
80
88
|
async listModels() {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
if (this.cachedModels)
|
|
90
|
+
return this.cachedModels;
|
|
91
|
+
try {
|
|
92
|
+
const probe = await this.probeAcpAuth();
|
|
93
|
+
try {
|
|
94
|
+
const newSession = await probe.connection.newSession({
|
|
95
|
+
cwd: process.cwd(),
|
|
96
|
+
mcpServers: [],
|
|
97
|
+
});
|
|
98
|
+
const models = [];
|
|
99
|
+
const modelState = newSession.models;
|
|
100
|
+
if (modelState && modelState.availableModels?.length) {
|
|
101
|
+
for (const model of modelState.availableModels) {
|
|
102
|
+
models.push({
|
|
103
|
+
id: model.modelId,
|
|
104
|
+
name: model.name,
|
|
105
|
+
description: model.description ?? undefined,
|
|
106
|
+
isDefault: model.modelId === modelState.currentModelId,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
await probe.connection.closeSession?.({ sessionId: newSession.sessionId }).catch(() => { });
|
|
111
|
+
this.cachedModels = models;
|
|
112
|
+
return models;
|
|
94
113
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const authenticated = !!process.env.ANTHROPIC_API_KEY?.trim() || status.ok;
|
|
98
|
-
return {
|
|
99
|
-
...DEVICE_PROVIDER_AUTH,
|
|
100
|
-
authenticated,
|
|
101
|
-
detail: authenticated
|
|
102
|
-
? "Claude Code credentials are configured."
|
|
103
|
-
: status.rawOutput ?? "Claude Code is not authenticated.",
|
|
104
|
-
};
|
|
114
|
+
finally {
|
|
115
|
+
probe.child.kill();
|
|
105
116
|
}
|
|
106
|
-
|
|
107
|
-
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return [];
|
|
108
120
|
}
|
|
109
121
|
}
|
|
122
|
+
async getAuthStatus() {
|
|
123
|
+
if (!this.authKind) {
|
|
124
|
+
return { ...NO_PROVIDER_AUTH, authenticated: null, detail: "Auth is managed by the ACP agent." };
|
|
125
|
+
}
|
|
126
|
+
const probe = await this.probeAcpAuth().catch(() => null);
|
|
127
|
+
const login = (probe?.authMethods.length ?? 0) > 0;
|
|
128
|
+
const logout = Boolean(probe?.initialized.agentCapabilities?.auth?.logout);
|
|
129
|
+
probe?.child.kill();
|
|
130
|
+
const authenticated = await this.checkProviderAuthenticated();
|
|
131
|
+
return {
|
|
132
|
+
login,
|
|
133
|
+
logout,
|
|
134
|
+
deviceCode: false,
|
|
135
|
+
authenticated,
|
|
136
|
+
detail: probe ? formatAcpAuthDetail(this.info.name, authenticated) : "Could not read ACP authentication capabilities.",
|
|
137
|
+
};
|
|
138
|
+
}
|
|
110
139
|
async startLogin() {
|
|
111
|
-
|
|
112
|
-
if (!login)
|
|
140
|
+
if (!this.authKind)
|
|
113
141
|
return unsupportedLogin(this.id, "Auth is managed by the ACP agent.");
|
|
114
142
|
if (this.authLoginProcess) {
|
|
115
143
|
killAuthChildTree(this.authLoginProcess);
|
|
116
144
|
this.authLoginProcess = null;
|
|
117
145
|
}
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
146
|
+
const probe = await this.probeAcpAuth();
|
|
147
|
+
const method = chooseAcpAuthMethod(probe.authMethods);
|
|
148
|
+
if (!method) {
|
|
149
|
+
probe.child.kill();
|
|
150
|
+
return unsupportedLogin(this.id, "ACP agent did not advertise a login method.");
|
|
151
|
+
}
|
|
152
|
+
if (isTerminalAuthMethod(method)) {
|
|
153
|
+
probe.child.kill();
|
|
154
|
+
const args = [...this.config.args, ...(method.args ?? [])];
|
|
155
|
+
const child = spawn(this.config.command, args, {
|
|
156
|
+
cwd: process.cwd(),
|
|
157
|
+
stdio: "ignore",
|
|
158
|
+
env: { ...process.env, ...this.config.env, ...method.env },
|
|
159
|
+
});
|
|
126
160
|
this.authLoginProcess = child;
|
|
127
161
|
child.on("exit", () => {
|
|
128
162
|
if (this.authLoginProcess === child)
|
|
129
163
|
this.authLoginProcess = null;
|
|
164
|
+
this.cachedModels = null;
|
|
130
165
|
void this.checkAvailability();
|
|
131
166
|
});
|
|
167
|
+
return {
|
|
168
|
+
ok: true,
|
|
169
|
+
status: "started",
|
|
170
|
+
providerId: this.id,
|
|
171
|
+
message: `${method.name} login started through ACP.`,
|
|
172
|
+
};
|
|
132
173
|
}
|
|
133
|
-
|
|
174
|
+
const child = probe.child;
|
|
175
|
+
this.authLoginProcess = child;
|
|
176
|
+
void probe.connection.authenticate({ methodId: method.id })
|
|
177
|
+
.catch(() => { })
|
|
178
|
+
.finally(() => {
|
|
179
|
+
if (this.authLoginProcess === child)
|
|
180
|
+
this.authLoginProcess = null;
|
|
181
|
+
child.kill();
|
|
182
|
+
this.cachedModels = null;
|
|
183
|
+
void this.checkAvailability();
|
|
184
|
+
});
|
|
185
|
+
return {
|
|
186
|
+
ok: true,
|
|
187
|
+
status: "started",
|
|
188
|
+
providerId: this.id,
|
|
189
|
+
message: `${method.name} login started through ACP.`,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
async checkProviderAuthenticated() {
|
|
193
|
+
if (this.id === "codex") {
|
|
194
|
+
return Boolean(process.env.OPENAI_API_KEY?.trim()) || checkCodexAuthFile();
|
|
195
|
+
}
|
|
196
|
+
if (this.id === "claude-code") {
|
|
197
|
+
const status = await runAuthCommand(this.id, "claude", ["auth", "status"], 10_000).catch(() => null);
|
|
198
|
+
return Boolean(process.env.ANTHROPIC_API_KEY?.trim()) || Boolean(status?.ok);
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
134
201
|
}
|
|
135
202
|
async logout() {
|
|
136
203
|
if (this.authLoginProcess) {
|
|
137
204
|
killAuthChildTree(this.authLoginProcess);
|
|
138
205
|
this.authLoginProcess = null;
|
|
139
206
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
ok: result.ok && cleared,
|
|
148
|
-
status: result.ok && cleared ? result.status : "error",
|
|
149
|
-
message: result.ok
|
|
150
|
-
? cleared
|
|
151
|
-
? "Codex logout completed."
|
|
152
|
-
: "Codex logout ran, but stored credentials could not be removed."
|
|
153
|
-
: result.message,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
case "claude-code": {
|
|
157
|
-
const result = await runAuthCommand(this.id, "claude", ["auth", "logout"]);
|
|
158
|
-
await this.checkAvailability().catch(() => false);
|
|
159
|
-
return {
|
|
160
|
-
...result,
|
|
161
|
-
message: result.ok ? "Claude Code logout completed." : result.message,
|
|
162
|
-
};
|
|
207
|
+
if (!this.authKind) {
|
|
208
|
+
return unsupportedLogout(this.id, "Auth is managed by the ACP agent.");
|
|
209
|
+
}
|
|
210
|
+
const probe = await this.probeAcpAuth();
|
|
211
|
+
try {
|
|
212
|
+
if (!probe.initialized.agentCapabilities?.auth?.logout) {
|
|
213
|
+
return unsupportedLogout(this.id, "ACP agent did not advertise logout support.");
|
|
163
214
|
}
|
|
164
|
-
|
|
165
|
-
|
|
215
|
+
await probe.connection.unstable_logout({});
|
|
216
|
+
return {
|
|
217
|
+
ok: true,
|
|
218
|
+
status: "completed",
|
|
219
|
+
providerId: this.id,
|
|
220
|
+
message: `${this.info.name} logout completed.`,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
probe.child.kill();
|
|
225
|
+
await this.checkAvailability().catch(() => false);
|
|
166
226
|
}
|
|
167
227
|
}
|
|
168
228
|
async startSession(options) {
|
|
@@ -179,7 +239,9 @@ export class AcpProvider {
|
|
|
179
239
|
cwd: options.workingDirectory,
|
|
180
240
|
stdio: ["pipe", "pipe", "pipe"],
|
|
181
241
|
env: { ...process.env, ...this.config.env, ...options.env },
|
|
242
|
+
shell: true,
|
|
182
243
|
});
|
|
244
|
+
child.on("error", () => { }); // prevent unhandled ENOENT on Windows
|
|
183
245
|
child.stderr?.on("data", (chunk) => {
|
|
184
246
|
const text = chunk.toString("utf8").trim();
|
|
185
247
|
if (text) {
|
|
@@ -191,7 +253,7 @@ export class AcpProvider {
|
|
|
191
253
|
const stream = ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));
|
|
192
254
|
const connection = new ClientSideConnection((agent) => {
|
|
193
255
|
capturedAgent = agent;
|
|
194
|
-
return new JaitAcpClient(this, sessionId, approvals);
|
|
256
|
+
return new JaitAcpClient(this, sessionId, approvals, options.mode);
|
|
195
257
|
}, stream);
|
|
196
258
|
child.once("exit", (code, signal) => {
|
|
197
259
|
const current = this.sessions.get(sessionId);
|
|
@@ -208,23 +270,31 @@ export class AcpProvider {
|
|
|
208
270
|
});
|
|
209
271
|
this.sessions.delete(sessionId);
|
|
210
272
|
});
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
273
|
+
let initialized;
|
|
274
|
+
let newSession;
|
|
275
|
+
try {
|
|
276
|
+
initialized = await connection.initialize({
|
|
277
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
278
|
+
clientInfo: { name: "Jait", version: "0.1" },
|
|
279
|
+
clientCapabilities: {
|
|
280
|
+
fs: { readTextFile: false, writeTextFile: false },
|
|
281
|
+
terminal: false,
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
newSession = await connection.newSession({
|
|
285
|
+
cwd: options.workingDirectory,
|
|
286
|
+
mcpServers: (options.mcpServers ?? []).map(toAcpMcpServer),
|
|
287
|
+
});
|
|
288
|
+
if (options.mode) {
|
|
289
|
+
await connection.setSessionMode({ sessionId: newSession.sessionId, modeId: options.mode }).catch(() => { });
|
|
290
|
+
}
|
|
291
|
+
if (options.model) {
|
|
292
|
+
await connection.unstable_setSessionModel({ sessionId: newSession.sessionId, modelId: options.model }).catch(() => { });
|
|
293
|
+
}
|
|
225
294
|
}
|
|
226
|
-
|
|
227
|
-
|
|
295
|
+
catch (error) {
|
|
296
|
+
child.kill();
|
|
297
|
+
throw error;
|
|
228
298
|
}
|
|
229
299
|
session.status = "running";
|
|
230
300
|
const state = {
|
|
@@ -289,7 +359,7 @@ export class AcpProvider {
|
|
|
289
359
|
const state = this.sessions.get(sessionId);
|
|
290
360
|
if (!state)
|
|
291
361
|
return;
|
|
292
|
-
await state.connection.closeSession?.({ sessionId: state.acpSessionId }).catch(() => { });
|
|
362
|
+
await withTimeout(state.connection.closeSession?.({ sessionId: state.acpSessionId }) ?? Promise.resolve(), 2_000).catch(() => { });
|
|
293
363
|
state.session.status = "completed";
|
|
294
364
|
state.session.completedAt = new Date().toISOString();
|
|
295
365
|
state.child.kill();
|
|
@@ -374,23 +444,60 @@ export class AcpProvider {
|
|
|
374
444
|
throw new Error(`Unknown ACP session: ${sessionId}`);
|
|
375
445
|
return state;
|
|
376
446
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
447
|
+
async probeAcpAuth() {
|
|
448
|
+
const child = spawn(this.config.command, this.config.args, {
|
|
449
|
+
cwd: process.cwd(),
|
|
450
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
451
|
+
env: { ...process.env, ...this.config.env },
|
|
452
|
+
shell: true,
|
|
453
|
+
});
|
|
454
|
+
// Prevent unhandled 'error' events from crashing the process (e.g. ENOENT on Windows)
|
|
455
|
+
const spawnError = new Promise((_, reject) => {
|
|
456
|
+
child.on("error", (err) => reject(err));
|
|
457
|
+
});
|
|
458
|
+
const stream = ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));
|
|
459
|
+
const connection = new ClientSideConnection(() => ({
|
|
460
|
+
async requestPermission() {
|
|
461
|
+
return { outcome: { outcome: "cancelled" } };
|
|
462
|
+
},
|
|
463
|
+
async sessionUpdate() { },
|
|
464
|
+
}), stream);
|
|
465
|
+
try {
|
|
466
|
+
const initialized = await Promise.race([
|
|
467
|
+
connection.initialize({
|
|
468
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
469
|
+
clientInfo: { name: "Jait", version: "0.1" },
|
|
470
|
+
clientCapabilities: {
|
|
471
|
+
auth: { terminal: true },
|
|
472
|
+
fs: { readTextFile: false, writeTextFile: false },
|
|
473
|
+
terminal: false,
|
|
474
|
+
},
|
|
475
|
+
}),
|
|
476
|
+
spawnError,
|
|
477
|
+
]);
|
|
478
|
+
return { child, connection, initialized, authMethods: initialized.authMethods ?? [] };
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
child.kill();
|
|
482
|
+
throw error;
|
|
385
483
|
}
|
|
386
484
|
}
|
|
387
485
|
}
|
|
388
|
-
function
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
486
|
+
function chooseAcpAuthMethod(methods) {
|
|
487
|
+
return (methods.find(isTerminalAuthMethod) ??
|
|
488
|
+
methods.find((method) => method.id === "chat-gpt") ??
|
|
489
|
+
methods[0] ??
|
|
490
|
+
null);
|
|
491
|
+
}
|
|
492
|
+
function isTerminalAuthMethod(method) {
|
|
493
|
+
return "type" in method && method.type === "terminal";
|
|
494
|
+
}
|
|
495
|
+
function formatAcpAuthDetail(providerName, authenticated) {
|
|
496
|
+
if (authenticated === true)
|
|
497
|
+
return `${providerName} credentials are configured. Login and logout are managed through ACP.`;
|
|
498
|
+
if (authenticated === false)
|
|
499
|
+
return `${providerName} credentials are not configured. Login and logout are managed through ACP.`;
|
|
500
|
+
return `${providerName} authentication is managed through ACP.`;
|
|
394
501
|
}
|
|
395
502
|
function getCodexAuthPath() {
|
|
396
503
|
const codexHome = process.env.CODEX_HOME ?? join(homedir(), ".codex");
|
|
@@ -423,26 +530,6 @@ function checkCodexAuthFile() {
|
|
|
423
530
|
return false;
|
|
424
531
|
}
|
|
425
532
|
}
|
|
426
|
-
function clearCodexAuthFile() {
|
|
427
|
-
const auth = readCodexAuthFile();
|
|
428
|
-
if (!auth)
|
|
429
|
-
return true;
|
|
430
|
-
delete auth.OPENAI_API_KEY;
|
|
431
|
-
delete auth.tokens;
|
|
432
|
-
const remaining = Object.entries(auth).filter(([, value]) => value !== undefined && value !== null);
|
|
433
|
-
try {
|
|
434
|
-
if (remaining.length === 0) {
|
|
435
|
-
unlinkSync(getCodexAuthPath());
|
|
436
|
-
}
|
|
437
|
-
else {
|
|
438
|
-
writeFileSync(getCodexAuthPath(), `${JSON.stringify(Object.fromEntries(remaining), null, 2)}\n`, "utf-8");
|
|
439
|
-
}
|
|
440
|
-
return true;
|
|
441
|
-
}
|
|
442
|
-
catch {
|
|
443
|
-
return false;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
533
|
function toAcpMcpServer(server) {
|
|
447
534
|
if (server.transport === "stdio") {
|
|
448
535
|
return {
|
|
@@ -452,6 +539,14 @@ function toAcpMcpServer(server) {
|
|
|
452
539
|
env: Object.entries(server.env ?? {}).map(([name, value]) => ({ name, value })),
|
|
453
540
|
};
|
|
454
541
|
}
|
|
542
|
+
if (server.transport === "http") {
|
|
543
|
+
return {
|
|
544
|
+
type: "http",
|
|
545
|
+
name: server.name,
|
|
546
|
+
url: server.url ?? "",
|
|
547
|
+
headers: [],
|
|
548
|
+
};
|
|
549
|
+
}
|
|
455
550
|
return {
|
|
456
551
|
type: "sse",
|
|
457
552
|
name: server.name,
|
|
@@ -474,6 +569,18 @@ function stringifyUnknown(value) {
|
|
|
474
569
|
return String(value);
|
|
475
570
|
}
|
|
476
571
|
}
|
|
572
|
+
function withTimeout(promise, timeoutMs) {
|
|
573
|
+
return new Promise((resolve, reject) => {
|
|
574
|
+
const timeout = setTimeout(() => reject(new Error("Operation timed out")), timeoutMs);
|
|
575
|
+
promise.then((value) => {
|
|
576
|
+
clearTimeout(timeout);
|
|
577
|
+
resolve(value);
|
|
578
|
+
}, (error) => {
|
|
579
|
+
clearTimeout(timeout);
|
|
580
|
+
reject(error);
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
}
|
|
477
584
|
export function loadAcpProviderConfigs() {
|
|
478
585
|
const defaults = [
|
|
479
586
|
{
|
|
@@ -490,6 +597,38 @@ export function loadAcpProviderConfigs() {
|
|
|
490
597
|
command: "npx",
|
|
491
598
|
args: ["-y", "@agentclientprotocol/claude-agent-acp"],
|
|
492
599
|
},
|
|
600
|
+
{
|
|
601
|
+
id: "cursor",
|
|
602
|
+
name: "Cursor",
|
|
603
|
+
description: "Cursor Agent via Agent Client Protocol",
|
|
604
|
+
command: "npx",
|
|
605
|
+
args: ["-y", "@blowmage/cursor-agent-acp"],
|
|
606
|
+
auth: false,
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
id: "pi",
|
|
610
|
+
name: "Pi",
|
|
611
|
+
description: "Pi coding agent via Agent Client Protocol",
|
|
612
|
+
command: "npx",
|
|
613
|
+
args: ["-y", "pi-acp"],
|
|
614
|
+
auth: false,
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
id: "pi-gemini",
|
|
618
|
+
name: "Pi Gemini",
|
|
619
|
+
description: "Gemini-backed Pi ACP provider",
|
|
620
|
+
command: "npx",
|
|
621
|
+
args: ["-y", "pi-gemini-acp"],
|
|
622
|
+
auth: false,
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
id: "deepagents",
|
|
626
|
+
name: "DeepAgents",
|
|
627
|
+
description: "DeepAgents via Agent Client Protocol",
|
|
628
|
+
command: "npx",
|
|
629
|
+
args: ["-y", "deepagents-acp"],
|
|
630
|
+
auth: false,
|
|
631
|
+
},
|
|
493
632
|
];
|
|
494
633
|
const raw = process.env.JAIT_ACP_PROVIDERS?.trim();
|
|
495
634
|
if (!raw)
|