@react-grab/opencode 0.0.77 → 0.0.80

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/client.js CHANGED
@@ -57,19 +57,33 @@ var streamSSE = async function* (stream, signal) {
57
57
  }
58
58
  };
59
59
  var streamFromServer = async function* (serverUrl, context, signal) {
60
- const response = await fetch(`${serverUrl}/agent`, {
61
- method: "POST",
62
- headers: { "Content-Type": "application/json" },
63
- body: JSON.stringify(context),
64
- signal
65
- });
66
- if (!response.ok) {
67
- throw new Error(`Server error: ${response.status}`);
68
- }
69
- if (!response.body) {
70
- throw new Error("No response body");
60
+ const sessionId = context.sessionId;
61
+ const handleAbort = () => {
62
+ if (sessionId) {
63
+ fetch(`${serverUrl}/abort/${sessionId}`, { method: "POST" }).catch(
64
+ () => {
65
+ }
66
+ );
67
+ }
68
+ };
69
+ signal.addEventListener("abort", handleAbort);
70
+ try {
71
+ const response = await fetch(`${serverUrl}/agent`, {
72
+ method: "POST",
73
+ headers: { "Content-Type": "application/json" },
74
+ body: JSON.stringify(context),
75
+ signal
76
+ });
77
+ if (!response.ok) {
78
+ throw new Error(`Server error: ${response.status}`);
79
+ }
80
+ if (!response.body) {
81
+ throw new Error("No response body");
82
+ }
83
+ yield* streamSSE(response.body, signal);
84
+ } finally {
85
+ signal.removeEventListener("abort", handleAbort);
71
86
  }
72
- yield* streamSSE(response.body, signal);
73
87
  };
74
88
  var createOpencodeAgentProvider = (options = {}) => {
75
89
  const { serverUrl = DEFAULT_SERVER_URL, getOptions } = options;
@@ -105,6 +119,7 @@ var createOpencodeAgentProvider = (options = {}) => {
105
119
  yield* streamFromServer(serverUrl, combinedContext, signal);
106
120
  },
107
121
  supportsResume: true,
122
+ supportsFollowUp: true,
108
123
  checkConnection: async () => {
109
124
  const now = Date.now();
110
125
  if (connectionCache && now - connectionCache.timestamp < CONNECTION_CHECK_TTL_MS) {
@@ -131,26 +146,26 @@ var createOpencodeAgentProvider = (options = {}) => {
131
146
  var attachAgent = async () => {
132
147
  if (typeof window === "undefined") return;
133
148
  const provider = createOpencodeAgentProvider();
149
+ const attach = (api2) => {
150
+ api2.setAgent({ provider, storage: sessionStorage });
151
+ };
134
152
  const api = window.__REACT_GRAB__;
135
153
  if (api) {
136
- api.setAgent({ provider, storage: sessionStorage });
154
+ attach(api);
137
155
  return;
138
156
  }
139
157
  window.addEventListener(
140
158
  "react-grab:init",
141
159
  (event) => {
142
- if (event instanceof CustomEvent) {
143
- const customEvent = event;
144
- if (customEvent.detail && typeof customEvent.detail.setAgent === "function") {
145
- customEvent.detail.setAgent({
146
- provider,
147
- storage: sessionStorage
148
- });
149
- }
150
- }
160
+ const customEvent = event;
161
+ attach(customEvent.detail);
151
162
  },
152
163
  { once: true }
153
164
  );
165
+ const apiAfterListener = window.__REACT_GRAB__;
166
+ if (apiAfterListener) {
167
+ attach(apiAfterListener);
168
+ }
154
169
  };
155
170
  attachAgent();
156
171
 
package/dist/server.cjs CHANGED
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
 
3
- var net = require('net');
4
3
  var child_process = require('child_process');
5
4
  var http = require('http');
6
5
  var http2 = require('http2');
@@ -11,7 +10,6 @@ var url = require('url');
11
10
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
12
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
12
 
14
- var net__default = /*#__PURE__*/_interopDefault(net);
15
13
  var crypto__default = /*#__PURE__*/_interopDefault(crypto);
16
14
 
17
15
  var __create = Object.create;
@@ -20,7 +18,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
20
18
  var __getOwnPropNames = Object.getOwnPropertyNames;
21
19
  var __getProtoOf = Object.getPrototypeOf;
22
20
  var __hasOwnProp = Object.prototype.hasOwnProperty;
23
- var __commonJS = (cb, mod) => function __require() {
21
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
22
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
23
+ }) : x)(function(x) {
24
+ if (typeof require !== "undefined") return require.apply(this, arguments);
25
+ throw Error('Dynamic require of "' + x + '" is not supported');
26
+ });
27
+ var __commonJS = (cb, mod) => function __require2() {
24
28
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
25
29
  };
26
30
  var __copyProps = (to, from, except, desc) => {
@@ -40,6 +44,84 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
40
44
  mod
41
45
  ));
42
46
 
47
+ // ../../node_modules/.pnpm/shell-exec@1.0.2/node_modules/shell-exec/index.js
48
+ var require_shell_exec = __commonJS({
49
+ "../../node_modules/.pnpm/shell-exec@1.0.2/node_modules/shell-exec/index.js"(exports, module) {
50
+ var childProcess = __require("child_process");
51
+ function shellExec(cmd = "", opts = {}) {
52
+ if (Array.isArray(cmd)) {
53
+ cmd = cmd.join(";");
54
+ }
55
+ opts = Object.assign({ stdio: "pipe", cwd: process.cwd() }, opts);
56
+ let child;
57
+ const shell = process.platform === "win32" ? { cmd: "cmd", arg: "/C" } : { cmd: "sh", arg: "-c" };
58
+ try {
59
+ child = childProcess.spawn(shell.cmd, [shell.arg, cmd], opts);
60
+ } catch (error) {
61
+ return Promise.reject(error);
62
+ }
63
+ return new Promise((resolve) => {
64
+ let stdout = "";
65
+ let stderr = "";
66
+ if (child.stdout) {
67
+ child.stdout.on("data", (data) => {
68
+ stdout += data;
69
+ });
70
+ }
71
+ if (child.stderr) {
72
+ child.stderr.on("data", (data) => {
73
+ stderr += data;
74
+ });
75
+ }
76
+ child.on("error", (error) => {
77
+ resolve({ error, stdout, stderr, cmd });
78
+ });
79
+ child.on("close", (code) => {
80
+ resolve({ stdout, stderr, cmd, code });
81
+ });
82
+ });
83
+ }
84
+ module.exports = shellExec;
85
+ }
86
+ });
87
+
88
+ // ../../node_modules/.pnpm/kill-port@2.0.1/node_modules/kill-port/index.js
89
+ var require_kill_port = __commonJS({
90
+ "../../node_modules/.pnpm/kill-port@2.0.1/node_modules/kill-port/index.js"(exports, module) {
91
+ var sh = require_shell_exec();
92
+ module.exports = function(port, method = "tcp") {
93
+ port = Number.parseInt(port);
94
+ if (!port) {
95
+ return Promise.reject(new Error("Invalid port number provided"));
96
+ }
97
+ if (process.platform === "win32") {
98
+ return sh("netstat -nao").then((res) => {
99
+ const { stdout } = res;
100
+ if (!stdout) return res;
101
+ const lines = stdout.split("\n");
102
+ const lineWithLocalPortRegEx = new RegExp(`^ *${method.toUpperCase()} *[^ ]*:${port}`, "gm");
103
+ const linesWithLocalPort = lines.filter((line) => line.match(lineWithLocalPortRegEx));
104
+ const pids = linesWithLocalPort.reduce((acc, line) => {
105
+ const match2 = line.match(/(\d*)\w*(\n|$)/gm);
106
+ return match2 && match2[0] && !acc.includes(match2[0]) ? acc.concat(match2[0]) : acc;
107
+ }, []);
108
+ return sh(`TaskKill /F /PID ${pids.join(" /PID ")}`);
109
+ });
110
+ }
111
+ return sh("lsof -i -P").then((res) => {
112
+ const { stdout } = res;
113
+ if (!stdout) return res;
114
+ const lines = stdout.split("\n");
115
+ const existProccess = lines.filter((line) => line.match(new RegExp(`:*${port}`))).length > 0;
116
+ if (!existProccess) return Promise.reject(new Error("No process running on port"));
117
+ return sh(
118
+ `lsof -i ${method === "udp" ? "udp" : "tcp"}:${port} | grep ${method === "udp" ? "UDP" : "LISTEN"} | awk '{print $2}' | xargs kill -9`
119
+ );
120
+ });
121
+ };
122
+ }
123
+ });
124
+
43
125
  // ../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js
44
126
  var require_picocolors = __commonJS({
45
127
  "../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js"(exports, module) {
@@ -1625,6 +1707,9 @@ async function createOpencode(options) {
1625
1707
  };
1626
1708
  }
1627
1709
 
1710
+ // src/server.ts
1711
+ var import_kill_port = __toESM(require_kill_port());
1712
+
1628
1713
  // ../../node_modules/.pnpm/hono@4.10.7/node_modules/hono/dist/compose.js
1629
1714
  var compose = (middleware, onError, onNotFound) => {
1630
1715
  return (context, next) => {
@@ -3877,8 +3962,11 @@ var import_picocolors = __toESM(require_picocolors());
3877
3962
 
3878
3963
  // src/constants.ts
3879
3964
  var DEFAULT_PORT = 6567;
3880
- var VERSION = "0.0.77";
3965
+ var VERSION = "0.0.80";
3881
3966
  var opencodeInstance = null;
3967
+ var sessionMap = /* @__PURE__ */ new Map();
3968
+ var abortedSessions = /* @__PURE__ */ new Set();
3969
+ var lastOpencodeSessionId;
3882
3970
  var getOpencodeClient = async () => {
3883
3971
  if (!opencodeInstance) {
3884
3972
  const instance = await createOpencode({
@@ -3889,22 +3977,31 @@ var getOpencodeClient = async () => {
3889
3977
  }
3890
3978
  return opencodeInstance.client;
3891
3979
  };
3892
- var executeOpencodePrompt = async (prompt, options, onStatus) => {
3980
+ var executeOpencodePrompt = async (prompt, options, onStatus, reactGrabSessionId) => {
3893
3981
  const client2 = await getOpencodeClient();
3894
3982
  onStatus?.("Thinking...");
3895
- const sessionResponse = await client2.session.create({
3896
- body: { title: "React Grab Session" }
3897
- });
3898
- if (sessionResponse.error || !sessionResponse.data) {
3899
- throw new Error("Failed to create session");
3983
+ let opencodeSessionId;
3984
+ if (reactGrabSessionId && sessionMap.has(reactGrabSessionId)) {
3985
+ opencodeSessionId = sessionMap.get(reactGrabSessionId);
3986
+ } else {
3987
+ const sessionResponse = await client2.session.create({
3988
+ body: { title: "React Grab Session" }
3989
+ });
3990
+ if (sessionResponse.error || !sessionResponse.data) {
3991
+ throw new Error("Failed to create session");
3992
+ }
3993
+ opencodeSessionId = sessionResponse.data.id;
3994
+ if (reactGrabSessionId) {
3995
+ sessionMap.set(reactGrabSessionId, opencodeSessionId);
3996
+ }
3900
3997
  }
3901
- const sessionId = sessionResponse.data.id;
3998
+ lastOpencodeSessionId = opencodeSessionId;
3902
3999
  const modelConfig = options?.model ? {
3903
4000
  providerID: options.model.split("/")[0],
3904
4001
  modelID: options.model.split("/")[1] || options.model
3905
4002
  } : void 0;
3906
4003
  const promptResponse = await client2.session.prompt({
3907
- path: { id: sessionId },
4004
+ path: { id: opencodeSessionId },
3908
4005
  body: {
3909
4006
  ...modelConfig && { model: modelConfig },
3910
4007
  parts: [{ type: "text", text: prompt }]
@@ -3917,65 +4014,96 @@ var executeOpencodePrompt = async (prompt, options, onStatus) => {
3917
4014
  }
3918
4015
  }
3919
4016
  }
4017
+ return opencodeSessionId;
3920
4018
  };
3921
4019
  var createServer = () => {
3922
4020
  const honoApplication = new Hono2();
3923
- honoApplication.use("/*", cors());
4021
+ honoApplication.use("*", cors());
3924
4022
  honoApplication.post("/agent", async (context) => {
3925
4023
  const requestBody = await context.req.json();
3926
- const { content, prompt, options } = requestBody;
3927
- const formattedPrompt = `
4024
+ const { content, prompt, options, sessionId } = requestBody;
4025
+ const isFollowUp = Boolean(sessionId && sessionMap.has(sessionId));
4026
+ const formattedPrompt = isFollowUp ? prompt : `
3928
4027
  User Request: ${prompt}
3929
4028
 
3930
4029
  Context:
3931
4030
  ${content}
3932
4031
  `;
3933
4032
  return streamSSE(context, async (stream2) => {
4033
+ const isAborted = () => sessionId && abortedSessions.has(sessionId);
3934
4034
  try {
3935
4035
  await executeOpencodePrompt(
3936
4036
  formattedPrompt,
3937
4037
  options,
3938
4038
  (text) => {
4039
+ if (isAborted()) return;
3939
4040
  stream2.writeSSE({
3940
4041
  data: text,
3941
4042
  event: "status"
3942
4043
  }).catch(() => {
3943
4044
  });
3944
- }
4045
+ },
4046
+ sessionId
3945
4047
  );
3946
- await stream2.writeSSE({
3947
- data: "Completed successfully",
3948
- event: "status"
3949
- });
3950
- await stream2.writeSSE({ data: "", event: "done" });
4048
+ if (!isAborted()) {
4049
+ await stream2.writeSSE({
4050
+ data: "Completed successfully",
4051
+ event: "status"
4052
+ });
4053
+ await stream2.writeSSE({ data: "", event: "done" });
4054
+ }
3951
4055
  } catch (error) {
3952
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
3953
- await stream2.writeSSE({
3954
- data: `Error: ${errorMessage}`,
3955
- event: "error"
3956
- });
3957
- await stream2.writeSSE({ data: "", event: "done" });
4056
+ if (!isAborted()) {
4057
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
4058
+ const stderr = error instanceof Error && "stderr" in error ? String(error.stderr) : void 0;
4059
+ const fullError = stderr && stderr.trim() ? `${errorMessage}
4060
+
4061
+ stderr:
4062
+ ${stderr.trim()}` : errorMessage;
4063
+ await stream2.writeSSE({
4064
+ data: `Error: ${fullError}`,
4065
+ event: "error"
4066
+ });
4067
+ await stream2.writeSSE({ data: "", event: "done" });
4068
+ }
4069
+ } finally {
4070
+ if (sessionId) {
4071
+ abortedSessions.delete(sessionId);
4072
+ }
3958
4073
  }
3959
4074
  });
3960
4075
  });
4076
+ honoApplication.post("/abort/:sessionId", (context) => {
4077
+ const { sessionId } = context.req.param();
4078
+ abortedSessions.add(sessionId);
4079
+ return context.json({ status: "ok" });
4080
+ });
4081
+ honoApplication.post("/undo", async (context) => {
4082
+ if (!lastOpencodeSessionId) {
4083
+ return context.json({ status: "error", message: "No session to undo" });
4084
+ }
4085
+ try {
4086
+ const client2 = await getOpencodeClient();
4087
+ await client2.session.prompt({
4088
+ path: { id: lastOpencodeSessionId },
4089
+ body: {
4090
+ parts: [{ type: "text", text: "/undo" }]
4091
+ }
4092
+ });
4093
+ return context.json({ status: "ok" });
4094
+ } catch (error) {
4095
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
4096
+ return context.json({ status: "error", message: errorMessage });
4097
+ }
4098
+ });
3961
4099
  honoApplication.get("/health", (context) => {
3962
4100
  return context.json({ status: "ok", provider: "opencode" });
3963
4101
  });
3964
4102
  return honoApplication;
3965
4103
  };
3966
- var isPortInUse = (port) => new Promise((resolve) => {
3967
- const netServer = net__default.default.createServer();
3968
- netServer.once("error", () => resolve(true));
3969
- netServer.once("listening", () => {
3970
- netServer.close();
3971
- resolve(false);
3972
- });
3973
- netServer.listen(port);
3974
- });
3975
4104
  var startServer = async (port = DEFAULT_PORT) => {
3976
- if (await isPortInUse(port)) {
3977
- return;
3978
- }
4105
+ await (0, import_kill_port.default)(port).catch(() => {
4106
+ });
3979
4107
  const honoApplication = createServer();
3980
4108
  serve({ fetch: honoApplication.fetch, port });
3981
4109
  console.log(`${import_picocolors.default.magenta("\u269B")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Opencode)")}`);
package/dist/server.js CHANGED
@@ -1,4 +1,3 @@
1
- import net from 'net';
2
1
  import { spawn } from 'child_process';
3
2
  import { createServer as createServer$1 } from 'http';
4
3
  import { Http2ServerRequest } from 'http2';
@@ -12,7 +11,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
11
  var __getOwnPropNames = Object.getOwnPropertyNames;
13
12
  var __getProtoOf = Object.getPrototypeOf;
14
13
  var __hasOwnProp = Object.prototype.hasOwnProperty;
15
- var __commonJS = (cb, mod) => function __require() {
14
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
15
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
16
+ }) : x)(function(x) {
17
+ if (typeof require !== "undefined") return require.apply(this, arguments);
18
+ throw Error('Dynamic require of "' + x + '" is not supported');
19
+ });
20
+ var __commonJS = (cb, mod) => function __require2() {
16
21
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
17
22
  };
18
23
  var __copyProps = (to, from, except, desc) => {
@@ -32,6 +37,84 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
32
37
  mod
33
38
  ));
34
39
 
40
+ // ../../node_modules/.pnpm/shell-exec@1.0.2/node_modules/shell-exec/index.js
41
+ var require_shell_exec = __commonJS({
42
+ "../../node_modules/.pnpm/shell-exec@1.0.2/node_modules/shell-exec/index.js"(exports, module) {
43
+ var childProcess = __require("child_process");
44
+ function shellExec(cmd = "", opts = {}) {
45
+ if (Array.isArray(cmd)) {
46
+ cmd = cmd.join(";");
47
+ }
48
+ opts = Object.assign({ stdio: "pipe", cwd: process.cwd() }, opts);
49
+ let child;
50
+ const shell = process.platform === "win32" ? { cmd: "cmd", arg: "/C" } : { cmd: "sh", arg: "-c" };
51
+ try {
52
+ child = childProcess.spawn(shell.cmd, [shell.arg, cmd], opts);
53
+ } catch (error) {
54
+ return Promise.reject(error);
55
+ }
56
+ return new Promise((resolve) => {
57
+ let stdout = "";
58
+ let stderr = "";
59
+ if (child.stdout) {
60
+ child.stdout.on("data", (data) => {
61
+ stdout += data;
62
+ });
63
+ }
64
+ if (child.stderr) {
65
+ child.stderr.on("data", (data) => {
66
+ stderr += data;
67
+ });
68
+ }
69
+ child.on("error", (error) => {
70
+ resolve({ error, stdout, stderr, cmd });
71
+ });
72
+ child.on("close", (code) => {
73
+ resolve({ stdout, stderr, cmd, code });
74
+ });
75
+ });
76
+ }
77
+ module.exports = shellExec;
78
+ }
79
+ });
80
+
81
+ // ../../node_modules/.pnpm/kill-port@2.0.1/node_modules/kill-port/index.js
82
+ var require_kill_port = __commonJS({
83
+ "../../node_modules/.pnpm/kill-port@2.0.1/node_modules/kill-port/index.js"(exports, module) {
84
+ var sh = require_shell_exec();
85
+ module.exports = function(port, method = "tcp") {
86
+ port = Number.parseInt(port);
87
+ if (!port) {
88
+ return Promise.reject(new Error("Invalid port number provided"));
89
+ }
90
+ if (process.platform === "win32") {
91
+ return sh("netstat -nao").then((res) => {
92
+ const { stdout } = res;
93
+ if (!stdout) return res;
94
+ const lines = stdout.split("\n");
95
+ const lineWithLocalPortRegEx = new RegExp(`^ *${method.toUpperCase()} *[^ ]*:${port}`, "gm");
96
+ const linesWithLocalPort = lines.filter((line) => line.match(lineWithLocalPortRegEx));
97
+ const pids = linesWithLocalPort.reduce((acc, line) => {
98
+ const match2 = line.match(/(\d*)\w*(\n|$)/gm);
99
+ return match2 && match2[0] && !acc.includes(match2[0]) ? acc.concat(match2[0]) : acc;
100
+ }, []);
101
+ return sh(`TaskKill /F /PID ${pids.join(" /PID ")}`);
102
+ });
103
+ }
104
+ return sh("lsof -i -P").then((res) => {
105
+ const { stdout } = res;
106
+ if (!stdout) return res;
107
+ const lines = stdout.split("\n");
108
+ const existProccess = lines.filter((line) => line.match(new RegExp(`:*${port}`))).length > 0;
109
+ if (!existProccess) return Promise.reject(new Error("No process running on port"));
110
+ return sh(
111
+ `lsof -i ${method === "udp" ? "udp" : "tcp"}:${port} | grep ${method === "udp" ? "UDP" : "LISTEN"} | awk '{print $2}' | xargs kill -9`
112
+ );
113
+ });
114
+ };
115
+ }
116
+ });
117
+
35
118
  // ../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js
36
119
  var require_picocolors = __commonJS({
37
120
  "../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js"(exports, module) {
@@ -1617,6 +1700,9 @@ async function createOpencode(options) {
1617
1700
  };
1618
1701
  }
1619
1702
 
1703
+ // src/server.ts
1704
+ var import_kill_port = __toESM(require_kill_port());
1705
+
1620
1706
  // ../../node_modules/.pnpm/hono@4.10.7/node_modules/hono/dist/compose.js
1621
1707
  var compose = (middleware, onError, onNotFound) => {
1622
1708
  return (context, next) => {
@@ -3869,8 +3955,11 @@ var import_picocolors = __toESM(require_picocolors());
3869
3955
 
3870
3956
  // src/constants.ts
3871
3957
  var DEFAULT_PORT = 6567;
3872
- var VERSION = "0.0.77";
3958
+ var VERSION = "0.0.80";
3873
3959
  var opencodeInstance = null;
3960
+ var sessionMap = /* @__PURE__ */ new Map();
3961
+ var abortedSessions = /* @__PURE__ */ new Set();
3962
+ var lastOpencodeSessionId;
3874
3963
  var getOpencodeClient = async () => {
3875
3964
  if (!opencodeInstance) {
3876
3965
  const instance = await createOpencode({
@@ -3881,22 +3970,31 @@ var getOpencodeClient = async () => {
3881
3970
  }
3882
3971
  return opencodeInstance.client;
3883
3972
  };
3884
- var executeOpencodePrompt = async (prompt, options, onStatus) => {
3973
+ var executeOpencodePrompt = async (prompt, options, onStatus, reactGrabSessionId) => {
3885
3974
  const client2 = await getOpencodeClient();
3886
3975
  onStatus?.("Thinking...");
3887
- const sessionResponse = await client2.session.create({
3888
- body: { title: "React Grab Session" }
3889
- });
3890
- if (sessionResponse.error || !sessionResponse.data) {
3891
- throw new Error("Failed to create session");
3976
+ let opencodeSessionId;
3977
+ if (reactGrabSessionId && sessionMap.has(reactGrabSessionId)) {
3978
+ opencodeSessionId = sessionMap.get(reactGrabSessionId);
3979
+ } else {
3980
+ const sessionResponse = await client2.session.create({
3981
+ body: { title: "React Grab Session" }
3982
+ });
3983
+ if (sessionResponse.error || !sessionResponse.data) {
3984
+ throw new Error("Failed to create session");
3985
+ }
3986
+ opencodeSessionId = sessionResponse.data.id;
3987
+ if (reactGrabSessionId) {
3988
+ sessionMap.set(reactGrabSessionId, opencodeSessionId);
3989
+ }
3892
3990
  }
3893
- const sessionId = sessionResponse.data.id;
3991
+ lastOpencodeSessionId = opencodeSessionId;
3894
3992
  const modelConfig = options?.model ? {
3895
3993
  providerID: options.model.split("/")[0],
3896
3994
  modelID: options.model.split("/")[1] || options.model
3897
3995
  } : void 0;
3898
3996
  const promptResponse = await client2.session.prompt({
3899
- path: { id: sessionId },
3997
+ path: { id: opencodeSessionId },
3900
3998
  body: {
3901
3999
  ...modelConfig && { model: modelConfig },
3902
4000
  parts: [{ type: "text", text: prompt }]
@@ -3909,65 +4007,96 @@ var executeOpencodePrompt = async (prompt, options, onStatus) => {
3909
4007
  }
3910
4008
  }
3911
4009
  }
4010
+ return opencodeSessionId;
3912
4011
  };
3913
4012
  var createServer = () => {
3914
4013
  const honoApplication = new Hono2();
3915
- honoApplication.use("/*", cors());
4014
+ honoApplication.use("*", cors());
3916
4015
  honoApplication.post("/agent", async (context) => {
3917
4016
  const requestBody = await context.req.json();
3918
- const { content, prompt, options } = requestBody;
3919
- const formattedPrompt = `
4017
+ const { content, prompt, options, sessionId } = requestBody;
4018
+ const isFollowUp = Boolean(sessionId && sessionMap.has(sessionId));
4019
+ const formattedPrompt = isFollowUp ? prompt : `
3920
4020
  User Request: ${prompt}
3921
4021
 
3922
4022
  Context:
3923
4023
  ${content}
3924
4024
  `;
3925
4025
  return streamSSE(context, async (stream2) => {
4026
+ const isAborted = () => sessionId && abortedSessions.has(sessionId);
3926
4027
  try {
3927
4028
  await executeOpencodePrompt(
3928
4029
  formattedPrompt,
3929
4030
  options,
3930
4031
  (text) => {
4032
+ if (isAborted()) return;
3931
4033
  stream2.writeSSE({
3932
4034
  data: text,
3933
4035
  event: "status"
3934
4036
  }).catch(() => {
3935
4037
  });
3936
- }
4038
+ },
4039
+ sessionId
3937
4040
  );
3938
- await stream2.writeSSE({
3939
- data: "Completed successfully",
3940
- event: "status"
3941
- });
3942
- await stream2.writeSSE({ data: "", event: "done" });
4041
+ if (!isAborted()) {
4042
+ await stream2.writeSSE({
4043
+ data: "Completed successfully",
4044
+ event: "status"
4045
+ });
4046
+ await stream2.writeSSE({ data: "", event: "done" });
4047
+ }
3943
4048
  } catch (error) {
3944
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
3945
- await stream2.writeSSE({
3946
- data: `Error: ${errorMessage}`,
3947
- event: "error"
3948
- });
3949
- await stream2.writeSSE({ data: "", event: "done" });
4049
+ if (!isAborted()) {
4050
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
4051
+ const stderr = error instanceof Error && "stderr" in error ? String(error.stderr) : void 0;
4052
+ const fullError = stderr && stderr.trim() ? `${errorMessage}
4053
+
4054
+ stderr:
4055
+ ${stderr.trim()}` : errorMessage;
4056
+ await stream2.writeSSE({
4057
+ data: `Error: ${fullError}`,
4058
+ event: "error"
4059
+ });
4060
+ await stream2.writeSSE({ data: "", event: "done" });
4061
+ }
4062
+ } finally {
4063
+ if (sessionId) {
4064
+ abortedSessions.delete(sessionId);
4065
+ }
3950
4066
  }
3951
4067
  });
3952
4068
  });
4069
+ honoApplication.post("/abort/:sessionId", (context) => {
4070
+ const { sessionId } = context.req.param();
4071
+ abortedSessions.add(sessionId);
4072
+ return context.json({ status: "ok" });
4073
+ });
4074
+ honoApplication.post("/undo", async (context) => {
4075
+ if (!lastOpencodeSessionId) {
4076
+ return context.json({ status: "error", message: "No session to undo" });
4077
+ }
4078
+ try {
4079
+ const client2 = await getOpencodeClient();
4080
+ await client2.session.prompt({
4081
+ path: { id: lastOpencodeSessionId },
4082
+ body: {
4083
+ parts: [{ type: "text", text: "/undo" }]
4084
+ }
4085
+ });
4086
+ return context.json({ status: "ok" });
4087
+ } catch (error) {
4088
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
4089
+ return context.json({ status: "error", message: errorMessage });
4090
+ }
4091
+ });
3953
4092
  honoApplication.get("/health", (context) => {
3954
4093
  return context.json({ status: "ok", provider: "opencode" });
3955
4094
  });
3956
4095
  return honoApplication;
3957
4096
  };
3958
- var isPortInUse = (port) => new Promise((resolve) => {
3959
- const netServer = net.createServer();
3960
- netServer.once("error", () => resolve(true));
3961
- netServer.once("listening", () => {
3962
- netServer.close();
3963
- resolve(false);
3964
- });
3965
- netServer.listen(port);
3966
- });
3967
4097
  var startServer = async (port = DEFAULT_PORT) => {
3968
- if (await isPortInUse(port)) {
3969
- return;
3970
- }
4098
+ await (0, import_kill_port.default)(port).catch(() => {
4099
+ });
3971
4100
  const honoApplication = createServer();
3972
4101
  serve({ fetch: honoApplication.fetch, port });
3973
4102
  console.log(`${import_picocolors.default.magenta("\u269B")} ${import_picocolors.default.bold("React Grab")} ${import_picocolors.default.gray(VERSION)} ${import_picocolors.default.dim("(Opencode)")}`);