@agentex/agent 0.0.27 → 0.0.28
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/CHANGELOG.md +108 -0
- package/README.md +58 -7
- package/dist/derived.d.ts.map +1 -1
- package/dist/derived.js +99 -2
- package/dist/derived.js.map +1 -1
- package/dist/goals/controller.d.ts.map +1 -1
- package/dist/goals/controller.js +21 -0
- package/dist/goals/controller.js.map +1 -1
- package/dist/goals/normalize.d.ts +1 -0
- package/dist/goals/normalize.d.ts.map +1 -1
- package/dist/goals/normalize.js +5 -0
- package/dist/goals/normalize.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/claude/attach.d.ts.map +1 -1
- package/dist/providers/claude/attach.js +50 -9
- package/dist/providers/claude/attach.js.map +1 -1
- package/dist/providers/claude/index.d.ts.map +1 -1
- package/dist/providers/claude/index.js +8 -0
- package/dist/providers/claude/index.js.map +1 -1
- package/dist/providers/claude/session.d.ts +2 -0
- package/dist/providers/claude/session.d.ts.map +1 -1
- package/dist/providers/claude/session.js +17 -10
- package/dist/providers/claude/session.js.map +1 -1
- package/dist/providers/codex/attach.d.ts.map +1 -1
- package/dist/providers/codex/attach.js +40 -3
- package/dist/providers/codex/attach.js.map +1 -1
- package/dist/providers/codex/index.d.ts.map +1 -1
- package/dist/providers/codex/index.js +8 -0
- package/dist/providers/codex/index.js.map +1 -1
- package/dist/providers/codex/transcript.d.ts +3 -1
- package/dist/providers/codex/transcript.d.ts.map +1 -1
- package/dist/providers/codex/transcript.js +3 -1
- package/dist/providers/codex/transcript.js.map +1 -1
- package/dist/providers/cursor/discovery.d.ts +6 -0
- package/dist/providers/cursor/discovery.d.ts.map +1 -0
- package/dist/providers/cursor/discovery.js +118 -0
- package/dist/providers/cursor/discovery.js.map +1 -0
- package/dist/providers/cursor/execute.d.ts.map +1 -1
- package/dist/providers/cursor/execute.js +83 -45
- package/dist/providers/cursor/execute.js.map +1 -1
- package/dist/providers/cursor/index.d.ts.map +1 -1
- package/dist/providers/cursor/index.js +17 -4
- package/dist/providers/cursor/index.js.map +1 -1
- package/dist/providers/cursor/parse.js +1 -1
- package/dist/providers/cursor/parse.js.map +1 -1
- package/dist/providers/cursor/probe.d.ts +3 -0
- package/dist/providers/cursor/probe.d.ts.map +1 -0
- package/dist/providers/cursor/probe.js +89 -0
- package/dist/providers/cursor/probe.js.map +1 -0
- package/dist/providers/cursor/runtime.d.ts +4 -0
- package/dist/providers/cursor/runtime.d.ts.map +1 -0
- package/dist/providers/cursor/runtime.js +12 -0
- package/dist/providers/cursor/runtime.js.map +1 -0
- package/dist/providers/cursor/session.d.ts +3 -0
- package/dist/providers/cursor/session.d.ts.map +1 -0
- package/dist/providers/cursor/session.js +14 -0
- package/dist/providers/cursor/session.js.map +1 -0
- package/dist/providers/opencode/client.d.ts +14 -0
- package/dist/providers/opencode/client.d.ts.map +1 -0
- package/dist/providers/opencode/client.js +40 -0
- package/dist/providers/opencode/client.js.map +1 -0
- package/dist/providers/opencode/discovery.d.ts +4 -0
- package/dist/providers/opencode/discovery.d.ts.map +1 -0
- package/dist/providers/opencode/discovery.js +68 -0
- package/dist/providers/opencode/discovery.js.map +1 -0
- package/dist/providers/opencode/execute.d.ts.map +1 -1
- package/dist/providers/opencode/execute.js +219 -164
- package/dist/providers/opencode/execute.js.map +1 -1
- package/dist/providers/opencode/history.d.ts +24 -0
- package/dist/providers/opencode/history.d.ts.map +1 -0
- package/dist/providers/opencode/history.js +242 -0
- package/dist/providers/opencode/history.js.map +1 -0
- package/dist/providers/opencode/http-session.d.ts.map +1 -1
- package/dist/providers/opencode/http-session.js +333 -49
- package/dist/providers/opencode/http-session.js.map +1 -1
- package/dist/providers/opencode/index.d.ts.map +1 -1
- package/dist/providers/opencode/index.js +26 -3
- package/dist/providers/opencode/index.js.map +1 -1
- package/dist/providers/opencode/manager.d.ts +11 -0
- package/dist/providers/opencode/manager.d.ts.map +1 -0
- package/dist/providers/opencode/manager.js +240 -0
- package/dist/providers/opencode/manager.js.map +1 -0
- package/dist/providers/opencode/modes.d.ts +3 -0
- package/dist/providers/opencode/modes.d.ts.map +1 -0
- package/dist/providers/opencode/modes.js +30 -0
- package/dist/providers/opencode/modes.js.map +1 -0
- package/dist/providers/opencode/parse.d.ts.map +1 -1
- package/dist/providers/opencode/parse.js +2 -22
- package/dist/providers/opencode/parse.js.map +1 -1
- package/dist/providers/opencode/probe.d.ts +3 -0
- package/dist/providers/opencode/probe.d.ts.map +1 -0
- package/dist/providers/opencode/probe.js +107 -0
- package/dist/providers/opencode/probe.js.map +1 -0
- package/dist/providers/opencode/runtime.d.ts +8 -0
- package/dist/providers/opencode/runtime.d.ts.map +1 -0
- package/dist/providers/opencode/runtime.js +12 -0
- package/dist/providers/opencode/runtime.js.map +1 -0
- package/dist/providers/opencode/server.d.ts +18 -3
- package/dist/providers/opencode/server.d.ts.map +1 -1
- package/dist/providers/opencode/server.js +0 -0
- package/dist/providers/opencode/server.js.map +1 -1
- package/dist/providers/opencode/skill-config.d.ts +11 -0
- package/dist/providers/opencode/skill-config.d.ts.map +1 -0
- package/dist/providers/opencode/skill-config.js +57 -0
- package/dist/providers/opencode/skill-config.js.map +1 -0
- package/dist/sessions/exec-backed.d.ts +10 -0
- package/dist/sessions/exec-backed.d.ts.map +1 -0
- package/dist/sessions/exec-backed.js +141 -0
- package/dist/sessions/exec-backed.js.map +1 -0
- package/dist/sessions/history.d.ts +4 -0
- package/dist/sessions/history.d.ts.map +1 -0
- package/dist/sessions/history.js +59 -0
- package/dist/sessions/history.js.map +1 -0
- package/dist/sessions/index.d.ts +3 -0
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +2 -0
- package/dist/sessions/index.js.map +1 -1
- package/dist/types.d.ts +147 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/auth.d.ts.map +1 -1
- package/dist/utils/auth.js +36 -6
- package/dist/utils/auth.js.map +1 -1
- package/dist/utils/binary.d.ts.map +1 -1
- package/dist/utils/binary.js +3 -0
- package/dist/utils/binary.js.map +1 -1
- package/dist/utils/endpoint.js +3 -3
- package/dist/utils/endpoint.js.map +1 -1
- package/package.json +4 -2
- package/src/derived.ts +126 -3
- package/src/goals/controller.ts +17 -0
- package/src/goals/normalize.ts +5 -0
- package/src/index.ts +13 -0
- package/src/providers/claude/attach.ts +48 -6
- package/src/providers/claude/index.ts +8 -0
- package/src/providers/claude/session.ts +15 -10
- package/src/providers/codex/attach.ts +48 -3
- package/src/providers/codex/index.ts +8 -0
- package/src/providers/codex/transcript.ts +5 -1
- package/src/providers/cursor/discovery.ts +114 -0
- package/src/providers/cursor/execute.ts +67 -30
- package/src/providers/cursor/index.ts +19 -5
- package/src/providers/cursor/parse.ts +1 -1
- package/src/providers/cursor/probe.ts +90 -0
- package/src/providers/cursor/runtime.ts +11 -0
- package/src/providers/cursor/session.ts +15 -0
- package/src/providers/opencode/client.ts +44 -0
- package/src/providers/opencode/discovery.ts +69 -0
- package/src/providers/opencode/execute.ts +64 -12
- package/src/providers/opencode/history.ts +270 -0
- package/src/providers/opencode/http-session.ts +319 -50
- package/src/providers/opencode/index.ts +26 -3
- package/src/providers/opencode/manager.ts +269 -0
- package/src/providers/opencode/modes.ts +31 -0
- package/src/providers/opencode/parse.ts +2 -22
- package/src/providers/opencode/probe.ts +113 -0
- package/src/providers/opencode/runtime.ts +13 -0
- package/src/providers/opencode/server.ts +0 -0
- package/src/providers/opencode/skill-config.ts +64 -0
- package/src/sessions/exec-backed.ts +169 -0
- package/src/sessions/history.ts +76 -0
- package/src/sessions/index.ts +3 -0
- package/src/types.ts +158 -3
- package/src/utils/auth.ts +35 -6
- package/src/utils/binary.ts +3 -0
- package/src/utils/endpoint.ts +3 -3
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import type {
|
|
3
|
+
ProviderAuthFlow,
|
|
4
|
+
ProviderAuthMethod,
|
|
5
|
+
ProviderRuntimeContext,
|
|
6
|
+
UpstreamProvider,
|
|
7
|
+
UpstreamProviderManager,
|
|
8
|
+
} from "../../types.js";
|
|
9
|
+
import type { OpenCodeServerHandle } from "./server.js";
|
|
10
|
+
import { acquireOpenCodeRuntime } from "./runtime.js";
|
|
11
|
+
|
|
12
|
+
const FLOW_TTL_MS = 10 * 60 * 1000;
|
|
13
|
+
const MAX_FLOWS = 128;
|
|
14
|
+
|
|
15
|
+
interface FlowRecord {
|
|
16
|
+
id: string;
|
|
17
|
+
providerId: string;
|
|
18
|
+
methodIndex: number;
|
|
19
|
+
expiresAt: number;
|
|
20
|
+
completion: "code" | "automatic";
|
|
21
|
+
server: OpenCodeServerHandle;
|
|
22
|
+
contextKey: string;
|
|
23
|
+
expiryTimer: ReturnType<typeof setTimeout>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const flows = new Map<string, FlowRecord>();
|
|
27
|
+
|
|
28
|
+
export class OpenCodeDisconnectUnsupportedError extends Error {
|
|
29
|
+
readonly code = "disconnect_unsupported";
|
|
30
|
+
constructor() {
|
|
31
|
+
super("The selected OpenCode protocol profile cannot safely remove this credential");
|
|
32
|
+
this.name = "OpenCodeDisconnectUnsupportedError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class OpenCodeAuthFlowExpiredError extends Error {
|
|
37
|
+
readonly code = "auth_flow_expired_or_restarted";
|
|
38
|
+
constructor() {
|
|
39
|
+
super("The OpenCode authentication flow expired or the process restarted");
|
|
40
|
+
this.name = "OpenCodeAuthFlowExpiredError";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function rec(value: unknown): Record<string, unknown> {
|
|
45
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
46
|
+
? value as Record<string, unknown>
|
|
47
|
+
: {};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function stableMethodId(providerId: string, index: number, method: Record<string, unknown>): string {
|
|
51
|
+
const hash = createHash("sha256")
|
|
52
|
+
.update(`${providerId}\0${method["type"] ?? ""}\0${method["label"] ?? ""}\0${index}`)
|
|
53
|
+
.digest("hex")
|
|
54
|
+
.slice(0, 16);
|
|
55
|
+
return `ocm_${hash}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function safeMethod(providerId: string, index: number, value: unknown): ProviderAuthMethod {
|
|
59
|
+
const method = rec(value);
|
|
60
|
+
const prompts = Array.isArray(method["prompts"])
|
|
61
|
+
? method["prompts"].map((raw) => {
|
|
62
|
+
const prompt = rec(raw);
|
|
63
|
+
const type = prompt["type"] === "select" ? "select" as const : "text" as const;
|
|
64
|
+
return {
|
|
65
|
+
id: typeof prompt["key"] === "string" ? prompt["key"] : "input",
|
|
66
|
+
label: typeof prompt["message"] === "string" ? prompt["message"] : "Input",
|
|
67
|
+
type,
|
|
68
|
+
...(type === "select" && Array.isArray(prompt["options"])
|
|
69
|
+
? { options: prompt["options"].map((item) => {
|
|
70
|
+
const option = rec(item);
|
|
71
|
+
return {
|
|
72
|
+
value: typeof option["value"] === "string" ? option["value"] : "",
|
|
73
|
+
label: typeof option["label"] === "string" ? option["label"] : "",
|
|
74
|
+
};
|
|
75
|
+
}).filter((item) => item.value) }
|
|
76
|
+
: {}),
|
|
77
|
+
};
|
|
78
|
+
})
|
|
79
|
+
: undefined;
|
|
80
|
+
return {
|
|
81
|
+
id: stableMethodId(providerId, index, method),
|
|
82
|
+
name: typeof method["label"] === "string" ? method["label"] : "Authentication",
|
|
83
|
+
type: method["type"] === "oauth" ? "oauth" : "api_key",
|
|
84
|
+
...(prompts?.length ? { prompts } : {}),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function contextKey(ctx: ProviderRuntimeContext): string {
|
|
89
|
+
return JSON.stringify({ cwd: ctx.cwd ?? process.cwd(), command: ctx.config?.command ?? null });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function cleanupFlows(): void {
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
for (const [id, flow] of flows) {
|
|
95
|
+
if (flow.expiresAt <= now) {
|
|
96
|
+
clearTimeout(flow.expiryTimer);
|
|
97
|
+
flow.server.release();
|
|
98
|
+
flows.delete(id);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
while (flows.size >= MAX_FLOWS) {
|
|
102
|
+
const oldest = flows.keys().next().value as string | undefined;
|
|
103
|
+
if (!oldest) break;
|
|
104
|
+
const flow = flows.get(oldest);
|
|
105
|
+
if (flow) {
|
|
106
|
+
clearTimeout(flow.expiryTimer);
|
|
107
|
+
flow.server.release();
|
|
108
|
+
}
|
|
109
|
+
flows.delete(oldest);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function authPayload(ctx: ProviderRuntimeContext) {
|
|
114
|
+
const runtime = await acquireOpenCodeRuntime(ctx);
|
|
115
|
+
const payload = await runtime.server.client.json<Record<string, unknown>>("/provider/auth");
|
|
116
|
+
return { runtime, payload };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function methodIndex(providerId: string, methodId: string, ctx: ProviderRuntimeContext) {
|
|
120
|
+
const { runtime, payload } = await authPayload(ctx);
|
|
121
|
+
const methods = Array.isArray(payload[providerId]) ? payload[providerId] : [];
|
|
122
|
+
const index = methods.findIndex((method, candidate) => stableMethodId(providerId, candidate, rec(method)) === methodId);
|
|
123
|
+
if (index < 0) {
|
|
124
|
+
runtime.server.release();
|
|
125
|
+
throw new Error("OpenCode authentication method is unavailable");
|
|
126
|
+
}
|
|
127
|
+
return { runtime, index };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function supportsProviderDelete(ctx: ProviderRuntimeContext): Promise<boolean> {
|
|
131
|
+
const runtime = await acquireOpenCodeRuntime(ctx);
|
|
132
|
+
try {
|
|
133
|
+
const doc = await runtime.server.client.json<Record<string, unknown>>("/doc");
|
|
134
|
+
const paths = rec(doc["paths"]);
|
|
135
|
+
const route = rec(paths["/auth/{providerID}"]);
|
|
136
|
+
return Boolean(route["delete"]);
|
|
137
|
+
} finally {
|
|
138
|
+
runtime.server.release();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const openCodeUpstreamProviders: UpstreamProviderManager = {
|
|
143
|
+
async list(ctx = {}): Promise<UpstreamProvider[]> {
|
|
144
|
+
const runtime = await acquireOpenCodeRuntime(ctx);
|
|
145
|
+
try {
|
|
146
|
+
const [providers, methods] = await Promise.all([
|
|
147
|
+
runtime.server.client.json<Record<string, unknown>>("/provider"),
|
|
148
|
+
runtime.server.client.json<Record<string, unknown>>("/provider/auth"),
|
|
149
|
+
]);
|
|
150
|
+
const connected = new Set(Array.isArray(providers["connected"])
|
|
151
|
+
? providers["connected"].filter((item): item is string => typeof item === "string")
|
|
152
|
+
: []);
|
|
153
|
+
return (Array.isArray(providers["all"]) ? providers["all"] : []).map((raw) => {
|
|
154
|
+
const provider = rec(raw);
|
|
155
|
+
const id = typeof provider["id"] === "string" ? provider["id"] : "";
|
|
156
|
+
const auth = Array.isArray(methods[id]) ? methods[id] : [];
|
|
157
|
+
return {
|
|
158
|
+
id,
|
|
159
|
+
name: typeof provider["name"] === "string" ? provider["name"] : id,
|
|
160
|
+
connected: connected.has(id),
|
|
161
|
+
authMethodIds: auth.map((method, index) => stableMethodId(id, index, rec(method))),
|
|
162
|
+
};
|
|
163
|
+
}).filter((provider) => provider.id);
|
|
164
|
+
} finally {
|
|
165
|
+
runtime.server.release();
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
async authMethods(providerId, ctx = {}) {
|
|
170
|
+
const { runtime, payload } = await authPayload(ctx);
|
|
171
|
+
try {
|
|
172
|
+
const methods = Array.isArray(payload[providerId]) ? payload[providerId] : [];
|
|
173
|
+
return methods.map((method, index) => safeMethod(providerId, index, method));
|
|
174
|
+
} finally {
|
|
175
|
+
runtime.server.release();
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
async setApiKey(providerId, key, ctx = {}) {
|
|
180
|
+
if (!key.trim()) throw new Error("OpenCode API key must not be empty");
|
|
181
|
+
const runtime = await acquireOpenCodeRuntime(ctx);
|
|
182
|
+
try {
|
|
183
|
+
await runtime.server.client.ok(`/auth/${encodeURIComponent(providerId)}`, {
|
|
184
|
+
method: "PUT",
|
|
185
|
+
body: JSON.stringify({ type: "api", key }),
|
|
186
|
+
});
|
|
187
|
+
await runtime.server.retireAuthStore();
|
|
188
|
+
} finally {
|
|
189
|
+
runtime.server.release();
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
async beginOAuth(providerId, methodId, inputs, ctx = {}) {
|
|
194
|
+
cleanupFlows();
|
|
195
|
+
const { runtime, index } = await methodIndex(providerId, methodId, ctx);
|
|
196
|
+
try {
|
|
197
|
+
const response = await runtime.server.client.json<Record<string, unknown>>(
|
|
198
|
+
`/provider/${encodeURIComponent(providerId)}/oauth/authorize`,
|
|
199
|
+
{ method: "POST", body: JSON.stringify({ method: index, ...(inputs ? { inputs } : {}) }) },
|
|
200
|
+
);
|
|
201
|
+
const id = `ocf_${randomUUID()}`;
|
|
202
|
+
const expiresAt = Date.now() + FLOW_TTL_MS;
|
|
203
|
+
const completion = response["method"] === "auto" ? "automatic" : "code";
|
|
204
|
+
const expiryTimer = setTimeout(() => {
|
|
205
|
+
const expired = flows.get(id);
|
|
206
|
+
if (!expired) return;
|
|
207
|
+
flows.delete(id);
|
|
208
|
+
expired.server.release();
|
|
209
|
+
}, FLOW_TTL_MS);
|
|
210
|
+
expiryTimer.unref();
|
|
211
|
+
flows.set(id, {
|
|
212
|
+
id,
|
|
213
|
+
providerId,
|
|
214
|
+
methodIndex: index,
|
|
215
|
+
expiresAt,
|
|
216
|
+
completion,
|
|
217
|
+
server: runtime.server,
|
|
218
|
+
contextKey: contextKey(ctx),
|
|
219
|
+
expiryTimer,
|
|
220
|
+
});
|
|
221
|
+
return {
|
|
222
|
+
id,
|
|
223
|
+
providerId,
|
|
224
|
+
url: typeof response["url"] === "string" ? response["url"] : null,
|
|
225
|
+
completion,
|
|
226
|
+
instructions: typeof response["instructions"] === "string" ? response["instructions"] : null,
|
|
227
|
+
expiresAt: new Date(expiresAt).toISOString(),
|
|
228
|
+
} satisfies ProviderAuthFlow;
|
|
229
|
+
} catch (error) {
|
|
230
|
+
runtime.server.release();
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
async completeOAuth(flowId, code, ctx = {}) {
|
|
236
|
+
cleanupFlows();
|
|
237
|
+
const flow = flows.get(flowId);
|
|
238
|
+
if (!flow || flow.expiresAt <= Date.now() || flow.contextKey !== contextKey(ctx)) {
|
|
239
|
+
throw new OpenCodeAuthFlowExpiredError();
|
|
240
|
+
}
|
|
241
|
+
flows.delete(flowId);
|
|
242
|
+
clearTimeout(flow.expiryTimer);
|
|
243
|
+
try {
|
|
244
|
+
await flow.server.client.ok(`/provider/${encodeURIComponent(flow.providerId)}/oauth/callback`, {
|
|
245
|
+
method: "POST",
|
|
246
|
+
body: JSON.stringify({ method: flow.methodIndex, ...(code ? { code } : {}) }),
|
|
247
|
+
});
|
|
248
|
+
await flow.server.retireAuthStore();
|
|
249
|
+
} finally {
|
|
250
|
+
flow.server.release();
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
canDisconnect(providerId, ctx = {}) {
|
|
255
|
+
void providerId;
|
|
256
|
+
return supportsProviderDelete(ctx);
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
async disconnect(providerId, ctx = {}) {
|
|
260
|
+
if (!await supportsProviderDelete(ctx)) throw new OpenCodeDisconnectUnsupportedError();
|
|
261
|
+
const runtime = await acquireOpenCodeRuntime(ctx);
|
|
262
|
+
try {
|
|
263
|
+
await runtime.server.client.ok(`/auth/${encodeURIComponent(providerId)}`, { method: "DELETE" });
|
|
264
|
+
await runtime.server.retireAuthStore({ force: true });
|
|
265
|
+
} finally {
|
|
266
|
+
runtime.server.release();
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { AgentMode, ListModesOptions } from "../../types.js";
|
|
2
|
+
import { acquireOpenCodeRuntime } from "./runtime.js";
|
|
3
|
+
|
|
4
|
+
function rec(value: unknown): Record<string, unknown> {
|
|
5
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
6
|
+
? value as Record<string, unknown>
|
|
7
|
+
: {};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function listOpenCodeModes(options: ListModesOptions = {}): Promise<AgentMode[]> {
|
|
11
|
+
const runtime = await acquireOpenCodeRuntime(options);
|
|
12
|
+
try {
|
|
13
|
+
const payload = await runtime.server.client.json<unknown[]>("/agent");
|
|
14
|
+
return (Array.isArray(payload) ? payload : []).map((raw) => {
|
|
15
|
+
const agent = rec(raw);
|
|
16
|
+
const id = typeof agent["name"] === "string"
|
|
17
|
+
? agent["name"]
|
|
18
|
+
: typeof agent["id"] === "string" ? agent["id"] : "";
|
|
19
|
+
const mode = typeof agent["mode"] === "string" ? agent["mode"] : "primary";
|
|
20
|
+
return {
|
|
21
|
+
id,
|
|
22
|
+
name: typeof agent["label"] === "string" ? agent["label"] : id,
|
|
23
|
+
description: typeof agent["description"] === "string" ? agent["description"] : undefined,
|
|
24
|
+
mode,
|
|
25
|
+
};
|
|
26
|
+
}).filter((agent) => agent.id && agent.mode !== "subagent")
|
|
27
|
+
.map(({ mode: _mode, ...agent }) => agent);
|
|
28
|
+
} finally {
|
|
29
|
+
runtime.server.release();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -159,31 +159,11 @@ export function parseOpenCodeStreamLine(line: string): StreamEvent | null {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
if (type === "step_finish") {
|
|
162
|
-
return {
|
|
163
|
-
type: "result",
|
|
164
|
-
text: "",
|
|
165
|
-
costUsd: null,
|
|
166
|
-
isError: false,
|
|
167
|
-
stopReason: null,
|
|
168
|
-
terminalReason: null,
|
|
169
|
-
numTurns: null,
|
|
170
|
-
durationMs: null,
|
|
171
|
-
...base,
|
|
172
|
-
};
|
|
162
|
+
return { type: "unknown", subtype: "step_finish", ...base };
|
|
173
163
|
}
|
|
174
164
|
|
|
175
165
|
if (type === "error") {
|
|
176
|
-
return {
|
|
177
|
-
type: "result",
|
|
178
|
-
text: asString(event["message"], ""),
|
|
179
|
-
costUsd: null,
|
|
180
|
-
isError: true,
|
|
181
|
-
stopReason: null,
|
|
182
|
-
terminalReason: null,
|
|
183
|
-
numTurns: null,
|
|
184
|
-
durationMs: null,
|
|
185
|
-
...base,
|
|
186
|
-
};
|
|
166
|
+
return { type: "unknown", subtype: "error", ...base };
|
|
187
167
|
}
|
|
188
168
|
|
|
189
169
|
// Forward-compat: surface unknown wire events rather than dropping them.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { ProviderRuntimeContext, ProviderRuntimeReport } from "../../types.js";
|
|
2
|
+
import { findBinary } from "../../utils/binary.js";
|
|
3
|
+
import { buildEnv, ensurePathInEnv } from "../../utils/env.js";
|
|
4
|
+
import { runChildProcess } from "../../utils/process.js";
|
|
5
|
+
import { acquireOpenCodeRuntime } from "./runtime.js";
|
|
6
|
+
|
|
7
|
+
function rec(value: unknown): Record<string, unknown> {
|
|
8
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
9
|
+
? value as Record<string, unknown>
|
|
10
|
+
: {};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function capability(
|
|
14
|
+
supported: boolean,
|
|
15
|
+
reason?: string,
|
|
16
|
+
): { supported: boolean; status: "supported" | "upgrade_required"; reason?: string } {
|
|
17
|
+
return {
|
|
18
|
+
supported,
|
|
19
|
+
status: supported ? "supported" : "upgrade_required",
|
|
20
|
+
...(reason ? { reason } : {}),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function probeOpenCodeCapabilities(
|
|
25
|
+
ctx: ProviderRuntimeContext = {},
|
|
26
|
+
): Promise<ProviderRuntimeReport> {
|
|
27
|
+
let resolved;
|
|
28
|
+
try {
|
|
29
|
+
resolved = await findBinary("opencode", ctx.config?.command);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
return {
|
|
32
|
+
binary: {
|
|
33
|
+
status: "missing",
|
|
34
|
+
command: null,
|
|
35
|
+
version: null,
|
|
36
|
+
protocolProfile: null,
|
|
37
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
38
|
+
},
|
|
39
|
+
capabilities: {},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const env = buildEnv(ctx.env);
|
|
44
|
+
ensurePathInEnv(env);
|
|
45
|
+
let version: string | null = null;
|
|
46
|
+
try {
|
|
47
|
+
const result = await runChildProcess({
|
|
48
|
+
runId: "opencode-version-probe",
|
|
49
|
+
command: resolved.bin,
|
|
50
|
+
args: [...resolved.prefixArgs, "--version"],
|
|
51
|
+
cwd: ctx.cwd ?? process.cwd(),
|
|
52
|
+
env,
|
|
53
|
+
timeoutSec: 5,
|
|
54
|
+
});
|
|
55
|
+
version = (result.stdout || result.stderr).match(/\d+\.\d+(?:\.\d+)?/)?.[0] ?? null;
|
|
56
|
+
} catch {
|
|
57
|
+
// The authenticated API probe below remains authoritative.
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const runtime = await acquireOpenCodeRuntime(ctx);
|
|
62
|
+
try {
|
|
63
|
+
const doc = await runtime.server.client.json<Record<string, unknown>>("/doc");
|
|
64
|
+
const agents = await runtime.server.client.json<unknown[]>("/agent").catch(() => []);
|
|
65
|
+
const paths = rec(doc["paths"]);
|
|
66
|
+
const has = (path: string, method: string) => Boolean(rec(paths[path])[method]);
|
|
67
|
+
const supportsDisconnect = has("/auth/{providerID}", "delete");
|
|
68
|
+
const capabilities: ProviderRuntimeReport["capabilities"] = {
|
|
69
|
+
sessions: capability(has("/session", "post") && has("/session/{sessionID}/message", "post")),
|
|
70
|
+
resume: capability(has("/session/{sessionID}", "get")),
|
|
71
|
+
modelDiscovery: capability(has("/provider", "get")),
|
|
72
|
+
modelVariants: capability(has("/provider", "get")),
|
|
73
|
+
permissionRequests: capability(has("/permission", "get") && has("/permission/{requestID}/reply", "post")),
|
|
74
|
+
questionRequests: capability(has("/question", "get") && has("/question/{requestID}/reply", "post")),
|
|
75
|
+
upstreamProviderDisconnect: capability(supportsDisconnect),
|
|
76
|
+
durableHistory: capability(has("/session/{sessionID}/message", "get")),
|
|
77
|
+
planMode: capability(agents.some((value) => {
|
|
78
|
+
const agent = rec(value);
|
|
79
|
+
return agent["name"] === "plan" || agent["id"] === "plan";
|
|
80
|
+
})),
|
|
81
|
+
modes: capability(agents.length > 0),
|
|
82
|
+
sessionModelChange: capability(true),
|
|
83
|
+
sessionVariantChange: capability(true),
|
|
84
|
+
sessionModeChange: capability(agents.length > 0),
|
|
85
|
+
};
|
|
86
|
+
const required = ["sessions", "modelDiscovery", "permissionRequests", "questionRequests"] as const;
|
|
87
|
+
const degraded = required.some((key) => !capabilities[key]?.supported);
|
|
88
|
+
return {
|
|
89
|
+
binary: {
|
|
90
|
+
status: degraded ? "degraded" : "supported",
|
|
91
|
+
command: resolved.bin,
|
|
92
|
+
version,
|
|
93
|
+
protocolProfile: supportsDisconnect ? "opencode-server-auth-v1" : "opencode-server-v1",
|
|
94
|
+
...(degraded ? { reason: "The OpenCode server schema is missing a required endpoint" } : {}),
|
|
95
|
+
},
|
|
96
|
+
capabilities,
|
|
97
|
+
};
|
|
98
|
+
} finally {
|
|
99
|
+
runtime.server.release();
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
binary: {
|
|
104
|
+
status: "degraded",
|
|
105
|
+
command: resolved.bin,
|
|
106
|
+
version,
|
|
107
|
+
protocolProfile: null,
|
|
108
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
109
|
+
},
|
|
110
|
+
capabilities: {},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ProviderRuntimeContext } from "../../types.js";
|
|
2
|
+
import { findBinary } from "../../utils/binary.js";
|
|
3
|
+
import { buildEnv, ensurePathInEnv } from "../../utils/env.js";
|
|
4
|
+
import { acquireOpenCodeServer } from "./server.js";
|
|
5
|
+
|
|
6
|
+
export async function acquireOpenCodeRuntime(ctx: ProviderRuntimeContext = {}) {
|
|
7
|
+
const resolved = await findBinary("opencode", ctx.config?.command);
|
|
8
|
+
const env = buildEnv(ctx.env);
|
|
9
|
+
ensurePathInEnv(env);
|
|
10
|
+
const cwd = ctx.cwd ?? process.cwd();
|
|
11
|
+
const server = await acquireOpenCodeServer(resolved.bin, resolved.prefixArgs, env, cwd);
|
|
12
|
+
return { resolved, env, cwd, server };
|
|
13
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
|
|
5
|
+
export interface OpenCodeSkillConfig {
|
|
6
|
+
env: Record<string, string>;
|
|
7
|
+
cleanup(): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function sourceConfigDir(env: Record<string, string>): string {
|
|
11
|
+
if (env["OPENCODE_CONFIG_DIR"]) return env["OPENCODE_CONFIG_DIR"];
|
|
12
|
+
const configHome = env["XDG_CONFIG_HOME"]
|
|
13
|
+
?? path.join(env["HOME"] ?? os.homedir(), ".config");
|
|
14
|
+
return path.join(configHome, "opencode");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Build an isolated OpenCode config directory containing requested skills.
|
|
19
|
+
* Existing global config is copied first, so config precedence is preserved
|
|
20
|
+
* without writing into the user's home or workspace.
|
|
21
|
+
*/
|
|
22
|
+
export async function prepareOpenCodeSkillConfig(
|
|
23
|
+
env: Record<string, string>,
|
|
24
|
+
skillDirs: string[] | undefined,
|
|
25
|
+
): Promise<OpenCodeSkillConfig> {
|
|
26
|
+
if (!skillDirs?.length) return { env, cleanup: async () => {} };
|
|
27
|
+
|
|
28
|
+
const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "agentex-opencode-"));
|
|
29
|
+
try {
|
|
30
|
+
await fs.cp(sourceConfigDir(env), configDir, {
|
|
31
|
+
recursive: true,
|
|
32
|
+
force: false,
|
|
33
|
+
errorOnExist: false,
|
|
34
|
+
});
|
|
35
|
+
} catch (error) {
|
|
36
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
37
|
+
if (code !== "ENOENT") {
|
|
38
|
+
await fs.rm(configDir, { recursive: true, force: true });
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const skillsDir = path.join(configDir, "skills");
|
|
45
|
+
await fs.mkdir(skillsDir, { recursive: true });
|
|
46
|
+
for (const directory of skillDirs) {
|
|
47
|
+
const source = path.resolve(directory);
|
|
48
|
+
const name = path.basename(source);
|
|
49
|
+
const stat = await fs.stat(path.join(source, "SKILL.md"));
|
|
50
|
+
if (!stat.isFile()) throw new Error(`OpenCode skill has no SKILL.md: ${source}`);
|
|
51
|
+
const target = path.join(skillsDir, name);
|
|
52
|
+
await fs.rm(target, { recursive: true, force: true });
|
|
53
|
+
await fs.symlink(source, target, process.platform === "win32" ? "junction" : "dir");
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
await fs.rm(configDir, { recursive: true, force: true });
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
env: { ...env, OPENCODE_CONFIG_DIR: configDir },
|
|
62
|
+
cleanup: () => fs.rm(configDir, { recursive: true, force: true }),
|
|
63
|
+
};
|
|
64
|
+
}
|