@open-cr-agent/runtime-opencode 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/LICENSE +202 -0
- package/README.md +3 -0
- package/dist/binary.d.ts +10 -0
- package/dist/binary.js +71 -0
- package/dist/failback.d.ts +14 -0
- package/dist/failback.js +81 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/models.d.ts +30 -0
- package/dist/models.js +88 -0
- package/dist/opencode-server.d.ts +14 -0
- package/dist/opencode-server.js +84 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.js +8 -0
- package/dist/quota.d.ts +9 -0
- package/dist/quota.js +38 -0
- package/dist/review-tools.d.ts +21 -0
- package/dist/review-tools.js +111 -0
- package/dist/runtime.d.ts +71 -0
- package/dist/runtime.js +246 -0
- package/dist/server-env.d.ts +10 -0
- package/dist/server-env.js +148 -0
- package/dist/session-outcome.d.ts +48 -0
- package/dist/session-outcome.js +47 -0
- package/dist/session-prompt.d.ts +30 -0
- package/dist/session-prompt.js +156 -0
- package/dist/tool-server.d.ts +8 -0
- package/dist/tool-server.js +63 -0
- package/dist/transport.d.ts +4 -0
- package/dist/transport.js +25 -0
- package/package.json +43 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Usage } from "@open-cr-agent/core";
|
|
2
|
+
import { type QuotaError } from "./quota.js";
|
|
3
|
+
export interface SessionMessage {
|
|
4
|
+
info: {
|
|
5
|
+
role: string;
|
|
6
|
+
cost?: number;
|
|
7
|
+
tokens?: {
|
|
8
|
+
input?: number;
|
|
9
|
+
output?: number;
|
|
10
|
+
reasoning?: number;
|
|
11
|
+
cache?: {
|
|
12
|
+
read?: number;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
error?: {
|
|
16
|
+
name?: string;
|
|
17
|
+
data?: {
|
|
18
|
+
message?: string;
|
|
19
|
+
statusCode?: number;
|
|
20
|
+
isRetryable?: boolean;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
parts: {
|
|
25
|
+
type: string;
|
|
26
|
+
text?: string;
|
|
27
|
+
tool?: string;
|
|
28
|
+
state?: {
|
|
29
|
+
status?: string;
|
|
30
|
+
input?: unknown;
|
|
31
|
+
};
|
|
32
|
+
}[];
|
|
33
|
+
}
|
|
34
|
+
export interface SessionOutcome {
|
|
35
|
+
findings: unknown[];
|
|
36
|
+
steps: number;
|
|
37
|
+
toolCalls: string[];
|
|
38
|
+
text: string;
|
|
39
|
+
resumed?: true;
|
|
40
|
+
usage: Usage;
|
|
41
|
+
error?: {
|
|
42
|
+
message: string;
|
|
43
|
+
retryable: boolean;
|
|
44
|
+
quota?: QuotaError;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export declare function summarizeSession(messages: readonly SessionMessage[], reportTool: string): SessionOutcome;
|
|
48
|
+
//# sourceMappingURL=session-outcome.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { parseQuotaError } from "./quota.js";
|
|
2
|
+
const AUTH_STATUS = new Set([401, 403]);
|
|
3
|
+
const AUTH_MESSAGE = /api key|unauthori[sz]ed|permission denied|forbidden/i;
|
|
4
|
+
export function summarizeSession(messages, reportTool) {
|
|
5
|
+
const assistant = messages.filter((m) => m.info.role === "assistant");
|
|
6
|
+
const tools = assistant.flatMap((m) => m.parts.filter((p) => p.type === "tool"));
|
|
7
|
+
const outcome = {
|
|
8
|
+
findings: tools
|
|
9
|
+
.filter((p) => p.tool === reportTool && p.state?.status === "completed")
|
|
10
|
+
.map((p) => p.state?.input),
|
|
11
|
+
steps: assistant.reduce((n, m) => n + m.parts.filter((p) => p.type === "step-start").length, 0),
|
|
12
|
+
toolCalls: tools.map((p) => p.tool ?? "unknown"),
|
|
13
|
+
text: assistant
|
|
14
|
+
.flatMap((m) => m.parts.filter((p) => p.type === "text").map((p) => p.text ?? ""))
|
|
15
|
+
.join("\n")
|
|
16
|
+
.trim(),
|
|
17
|
+
usage: {
|
|
18
|
+
inputTokens: sum(assistant, (m) => m.info.tokens?.input),
|
|
19
|
+
outputTokens: sum(assistant, (m) => m.info.tokens?.output),
|
|
20
|
+
reasoningTokens: sum(assistant, (m) => m.info.tokens?.reasoning),
|
|
21
|
+
cachedTokens: sum(assistant, (m) => m.info.tokens?.cache?.read),
|
|
22
|
+
costUsd: sum(assistant, (m) => m.info.cost),
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
const errors = assistant.flatMap((m) => (m.info.error ? [m.info.error] : []));
|
|
26
|
+
const last = errors.at(-1);
|
|
27
|
+
if (last) {
|
|
28
|
+
// Overloads and model-specific request rejections (for example Gemini 3.8
|
|
29
|
+
// refusing a request that ends with a model turn) are fixed by another
|
|
30
|
+
// model; only credential problems would fail on every model.
|
|
31
|
+
const message = last.data?.message ?? last.name ?? "unknown model error";
|
|
32
|
+
outcome.error = { message, retryable: !errors.some(isAuthError) };
|
|
33
|
+
const quota = parseQuotaError(message, last.data?.statusCode);
|
|
34
|
+
if (quota)
|
|
35
|
+
outcome.error.quota = quota;
|
|
36
|
+
}
|
|
37
|
+
return outcome;
|
|
38
|
+
}
|
|
39
|
+
function isAuthError(error) {
|
|
40
|
+
return (error.name === "ProviderAuthError" ||
|
|
41
|
+
AUTH_STATUS.has(error.data?.statusCode ?? 0) ||
|
|
42
|
+
AUTH_MESSAGE.test(error.data?.message ?? ""));
|
|
43
|
+
}
|
|
44
|
+
function sum(messages, pick) {
|
|
45
|
+
return messages.reduce((total, m) => total + (pick(m) ?? 0), 0);
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=session-outcome.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { OpencodeClient } from "@opencode-ai/sdk/v2";
|
|
2
|
+
import { type SessionMessage, type SessionOutcome } from "./session-outcome.js";
|
|
3
|
+
export declare const HARVEST_TIMEOUT_MS = 5000;
|
|
4
|
+
export declare const INACTIVITY_MS: number;
|
|
5
|
+
export declare const ACTIVITY_POLL_MS = 30000;
|
|
6
|
+
export interface ActivityOptions {
|
|
7
|
+
inactivityMs?: number;
|
|
8
|
+
pollMs?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface PromptInput {
|
|
11
|
+
title: string;
|
|
12
|
+
agent: string;
|
|
13
|
+
model: string;
|
|
14
|
+
system: string;
|
|
15
|
+
user: string;
|
|
16
|
+
tools: Record<string, boolean>;
|
|
17
|
+
resume?: ResumeOptions;
|
|
18
|
+
}
|
|
19
|
+
export interface ResumeOptions {
|
|
20
|
+
doneTool: string;
|
|
21
|
+
maxSteps: number;
|
|
22
|
+
message: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function stoppedEarly(outcome: SessionOutcome, resume: ResumeOptions): boolean;
|
|
25
|
+
type SessionApi = Pick<OpencodeClient["session"], "create" | "prompt" | "messages" | "abort">;
|
|
26
|
+
export declare function promptSession(session: SessionApi, input: PromptInput, reportTool: string, signal: AbortSignal, activity?: ActivityOptions): Promise<SessionOutcome>;
|
|
27
|
+
export declare function activitySignature(messages: readonly SessionMessage[]): string;
|
|
28
|
+
export declare function emptyOutcome(): SessionOutcome;
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=session-prompt.d.ts.map
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { emptyUsage, errorMessage } from "@open-cr-agent/core";
|
|
2
|
+
import { parseModel } from "./models.js";
|
|
3
|
+
import { summarizeSession } from "./session-outcome.js";
|
|
4
|
+
// A session that was cut off has still spent tokens and may have reported
|
|
5
|
+
// findings; this bounds the one extra request that collects them.
|
|
6
|
+
export const HARVEST_TIMEOUT_MS = 5_000;
|
|
7
|
+
// A prompt returns only when the agent is done, so silence on the request is
|
|
8
|
+
// normal; silence in the session is not. A session whose messages have not
|
|
9
|
+
// changed for this long (no new step, no streamed text, no tool progress) is
|
|
10
|
+
// stopped and the task moves to the next model instead of waiting for its
|
|
11
|
+
// full timeout. Generous, so a slow step that is still writing survives.
|
|
12
|
+
export const INACTIVITY_MS = 5 * 60_000;
|
|
13
|
+
export const ACTIVITY_POLL_MS = 30_000;
|
|
14
|
+
// About one review attempt in twelve on Gemini ended after a step or two
|
|
15
|
+
// with no text, no done tool and steps to spare (2026-09-28), and the task
|
|
16
|
+
// counted as completed with its files unread. Continuing the same session
|
|
17
|
+
// keeps what it read and is cheaper than starting over.
|
|
18
|
+
export function stoppedEarly(outcome, resume) {
|
|
19
|
+
return (!outcome.toolCalls.includes(resume.doneTool) &&
|
|
20
|
+
outcome.steps < resume.maxSteps &&
|
|
21
|
+
outcome.text.trim() === "");
|
|
22
|
+
}
|
|
23
|
+
export async function promptSession(session, input, reportTool, signal, activity = {}) {
|
|
24
|
+
const created = await session.create({ title: input.title }, { signal });
|
|
25
|
+
if (!created.data) {
|
|
26
|
+
throw new Error(`OpenCode could not create a session: ${JSON.stringify(created.error)}`);
|
|
27
|
+
}
|
|
28
|
+
const sessionID = created.data.id;
|
|
29
|
+
const stop = () => void session.abort({ sessionID }).catch(() => { });
|
|
30
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
31
|
+
const silence = watchActivity(session, sessionID, activity);
|
|
32
|
+
const attempt = AbortSignal.any([signal, silence.signal]);
|
|
33
|
+
try {
|
|
34
|
+
const response = await session.prompt({
|
|
35
|
+
sessionID,
|
|
36
|
+
agent: input.agent,
|
|
37
|
+
model: parseModel(input.model),
|
|
38
|
+
system: input.system,
|
|
39
|
+
tools: input.tools,
|
|
40
|
+
parts: [{ type: "text", text: input.user }],
|
|
41
|
+
}, { signal: attempt });
|
|
42
|
+
// The session ran even when the answer is an error, and it may have
|
|
43
|
+
// spent tokens and reported findings: keep them.
|
|
44
|
+
if (response.error) {
|
|
45
|
+
return {
|
|
46
|
+
...(await harvest(session, sessionID, reportTool)),
|
|
47
|
+
error: { message: JSON.stringify(response.error), retryable: false },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
let outcome;
|
|
51
|
+
try {
|
|
52
|
+
const messages = await session.messages({ sessionID }, { signal: attempt });
|
|
53
|
+
outcome = summarizeSession((messages.data ?? []), reportTool);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
// The session finished; running it again on the next model would pay
|
|
57
|
+
// twice. Keep what one more read gets, and do not retry.
|
|
58
|
+
return {
|
|
59
|
+
...(await harvest(session, sessionID, reportTool)),
|
|
60
|
+
error: {
|
|
61
|
+
message: `could not read the finished session: ${errorMessage(error)}`,
|
|
62
|
+
retryable: false,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (!input.resume || !stoppedEarly(outcome, input.resume))
|
|
67
|
+
return outcome;
|
|
68
|
+
const again = await session.prompt({
|
|
69
|
+
sessionID,
|
|
70
|
+
agent: input.agent,
|
|
71
|
+
model: parseModel(input.model),
|
|
72
|
+
system: input.system,
|
|
73
|
+
tools: input.tools,
|
|
74
|
+
parts: [{ type: "text", text: input.resume.message }],
|
|
75
|
+
}, { signal: attempt });
|
|
76
|
+
// Both turns are in the session: their findings and their spend.
|
|
77
|
+
const resumed = {
|
|
78
|
+
...(await harvest(session, sessionID, reportTool)),
|
|
79
|
+
resumed: true,
|
|
80
|
+
};
|
|
81
|
+
if (again.error)
|
|
82
|
+
resumed.error = { message: JSON.stringify(again.error), retryable: false };
|
|
83
|
+
return resumed;
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
// Aborted or cut off by the transport: OpenCode may still be running the
|
|
87
|
+
// session, spending tokens, so stop it and keep what it already did.
|
|
88
|
+
stop();
|
|
89
|
+
const partial = await harvest(session, sessionID, reportTool);
|
|
90
|
+
const seconds = Math.round((activity.inactivityMs ?? INACTIVITY_MS) / 1000);
|
|
91
|
+
return {
|
|
92
|
+
...partial,
|
|
93
|
+
error: signal.aborted
|
|
94
|
+
? { message: "cancelled", retryable: false }
|
|
95
|
+
: silence.signal.aborted
|
|
96
|
+
? { message: `no activity for ${seconds}s`, retryable: true }
|
|
97
|
+
: { message: errorMessage(error), retryable: true },
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
silence.stop();
|
|
102
|
+
signal.removeEventListener("abort", stop);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Aborts when the session's messages stop changing. A failed poll counts as
|
|
106
|
+
// no news, not as silence, so a slow server alone cannot end a task.
|
|
107
|
+
function watchActivity(session, sessionID, options) {
|
|
108
|
+
const controller = new AbortController();
|
|
109
|
+
const inactivityMs = options.inactivityMs ?? INACTIVITY_MS;
|
|
110
|
+
let last = "";
|
|
111
|
+
let changedAt = Date.now();
|
|
112
|
+
let polling = false;
|
|
113
|
+
const timer = setInterval(async () => {
|
|
114
|
+
if (polling)
|
|
115
|
+
return;
|
|
116
|
+
polling = true;
|
|
117
|
+
try {
|
|
118
|
+
const messages = await session.messages({ sessionID }, { signal: AbortSignal.timeout(HARVEST_TIMEOUT_MS) });
|
|
119
|
+
const now = activitySignature((messages.data ?? []));
|
|
120
|
+
if (now !== last) {
|
|
121
|
+
last = now;
|
|
122
|
+
changedAt = Date.now();
|
|
123
|
+
}
|
|
124
|
+
else if (Date.now() - changedAt >= inactivityMs) {
|
|
125
|
+
controller.abort();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// no news
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
polling = false;
|
|
133
|
+
}
|
|
134
|
+
}, options.pollMs ?? ACTIVITY_POLL_MS);
|
|
135
|
+
timer.unref?.();
|
|
136
|
+
return { signal: controller.signal, stop: () => clearInterval(timer) };
|
|
137
|
+
}
|
|
138
|
+
// What changes while an agent works: steps, parts, streamed text, tool states.
|
|
139
|
+
export function activitySignature(messages) {
|
|
140
|
+
return messages
|
|
141
|
+
.map((m) => m.parts.map((p) => `${p.type}:${p.state?.status ?? ""}:${p.text?.length ?? 0}`).join(","))
|
|
142
|
+
.join("|");
|
|
143
|
+
}
|
|
144
|
+
async function harvest(session, sessionID, reportTool) {
|
|
145
|
+
try {
|
|
146
|
+
const messages = await session.messages({ sessionID }, { signal: AbortSignal.timeout(HARVEST_TIMEOUT_MS) });
|
|
147
|
+
return summarizeSession((messages.data ?? []), reportTool);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return emptyOutcome();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
export function emptyOutcome() {
|
|
154
|
+
return { findings: [], steps: 0, toolCalls: [], text: "", usage: emptyUsage() };
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=session-prompt.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ReviewContext, type ToolDefinition } from "@open-cr-agent/core";
|
|
2
|
+
export interface ToolServer {
|
|
3
|
+
url: string;
|
|
4
|
+
headers: Record<string, string>;
|
|
5
|
+
close(): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export declare function startToolServer(tools: readonly ToolDefinition[], context: () => ReviewContext | undefined): Promise<ToolServer>;
|
|
8
|
+
//# sourceMappingURL=tool-server.d.ts.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
6
|
+
import { errorMessage } from "@open-cr-agent/core";
|
|
7
|
+
// src/ and dist/ both sit one level below the package root.
|
|
8
|
+
const VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
9
|
+
export async function startToolServer(tools, context) {
|
|
10
|
+
const token = randomBytes(32).toString("hex");
|
|
11
|
+
const expected = Buffer.from(`Bearer ${token}`);
|
|
12
|
+
const http = createServer(async (req, res) => {
|
|
13
|
+
if (!authorized(req, expected)) {
|
|
14
|
+
res.writeHead(401).end();
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const mcp = buildMcpServer(tools, context);
|
|
18
|
+
const transport = new StreamableHTTPServerTransport({});
|
|
19
|
+
res.on("close", () => {
|
|
20
|
+
void transport.close();
|
|
21
|
+
void mcp.close();
|
|
22
|
+
});
|
|
23
|
+
// The SDK's transport types are not written for exactOptionalPropertyTypes.
|
|
24
|
+
await mcp.connect(transport);
|
|
25
|
+
await transport.handleRequest(req, res);
|
|
26
|
+
});
|
|
27
|
+
await new Promise((resolve) => http.listen(0, "127.0.0.1", resolve));
|
|
28
|
+
const address = http.address();
|
|
29
|
+
const port = typeof address === "object" && address ? address.port : 0;
|
|
30
|
+
return {
|
|
31
|
+
url: `http://127.0.0.1:${port}/mcp`,
|
|
32
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
33
|
+
close: () => new Promise((resolve) => {
|
|
34
|
+
http.closeAllConnections();
|
|
35
|
+
http.close(() => resolve());
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function buildMcpServer(tools, context) {
|
|
40
|
+
const mcp = new McpServer({ name: "ocra", version: VERSION });
|
|
41
|
+
for (const tool of tools) {
|
|
42
|
+
mcp.registerTool(tool.name, { description: tool.description, inputSchema: tool.inputSchema.shape }, async (args) => {
|
|
43
|
+
const ctx = context();
|
|
44
|
+
if (!ctx)
|
|
45
|
+
return failure("No review is running.");
|
|
46
|
+
try {
|
|
47
|
+
return { content: [{ type: "text", text: await tool.execute(args, ctx) }] };
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
return failure(errorMessage(error));
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return mcp;
|
|
55
|
+
}
|
|
56
|
+
function failure(message) {
|
|
57
|
+
return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
|
|
58
|
+
}
|
|
59
|
+
function authorized(req, expected) {
|
|
60
|
+
const actual = Buffer.from(req.headers.authorization ?? "");
|
|
61
|
+
return actual.length === expected.length && timingSafeEqual(actual, expected);
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=tool-server.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Agent, fetch as undiciFetch } from "undici";
|
|
2
|
+
// OpenCode answers a prompt only when the agent has finished, which can take
|
|
3
|
+
// longer than fetch's default 300 s header and body timeouts; on Node those
|
|
4
|
+
// end the request as an error that is not an abort (the SDK's
|
|
5
|
+
// `timeout = false` only works on Bun). The task's own timeout and abort
|
|
6
|
+
// signal bound each request instead. undici's fetch and Agent are used
|
|
7
|
+
// together so no object crosses between it and Node's bundled copy.
|
|
8
|
+
export function createUntimedDispatcher() {
|
|
9
|
+
return new Agent({ headersTimeout: 0, bodyTimeout: 0 });
|
|
10
|
+
}
|
|
11
|
+
export function untimedFetch(dispatcher) {
|
|
12
|
+
return async (input, init) => {
|
|
13
|
+
const request = new Request(input, init);
|
|
14
|
+
const body = request.body === null ? undefined : await request.arrayBuffer();
|
|
15
|
+
const response = await undiciFetch(request.url, {
|
|
16
|
+
method: request.method,
|
|
17
|
+
headers: [...request.headers],
|
|
18
|
+
...(body === undefined ? {} : { body }),
|
|
19
|
+
signal: request.signal,
|
|
20
|
+
dispatcher,
|
|
21
|
+
});
|
|
22
|
+
return response;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=transport.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@open-cr-agent/runtime-opencode",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenCode-based agent runtime for Open-CR-Agent",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"code-review",
|
|
7
|
+
"opencode",
|
|
8
|
+
"agents"
|
|
9
|
+
],
|
|
10
|
+
"license": "Apache-2.0",
|
|
11
|
+
"homepage": "https://ocra.majincheng.com",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/jma49/Open-CR-Agent.git",
|
|
15
|
+
"directory": "packages/runtime-opencode"
|
|
16
|
+
},
|
|
17
|
+
"bugs": "https://github.com/jma49/Open-CR-Agent/issues",
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22.19"
|
|
20
|
+
},
|
|
21
|
+
"type": "module",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"!dist/**/*.map"
|
|
31
|
+
],
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": "^1.30.1",
|
|
34
|
+
"@open-cr-agent/core": "0.1.0",
|
|
35
|
+
"@opencode-ai/sdk": "1.18.32",
|
|
36
|
+
"opencode-ai": "1.18.32",
|
|
37
|
+
"undici": "^8.11.2",
|
|
38
|
+
"zod": "^4.6.5"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
}
|
|
43
|
+
}
|