@react-grab/opencode 0.0.88 → 0.0.90

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/cli.cjs CHANGED
@@ -7138,7 +7138,7 @@ var import_picocolors = __toESM(require_picocolors());
7138
7138
  var DEFAULT_PORT = 6567;
7139
7139
 
7140
7140
  // src/cli.ts
7141
- var VERSION = "0.0.88";
7141
+ var VERSION = "0.0.90";
7142
7142
  var serverPath = path2.join(__dirname, "server.cjs");
7143
7143
  execa(process.execPath, [serverPath], {
7144
7144
  detached: true,
package/dist/cli.js CHANGED
@@ -7130,7 +7130,7 @@ var import_picocolors = __toESM(require_picocolors());
7130
7130
  var DEFAULT_PORT = 6567;
7131
7131
 
7132
7132
  // src/cli.ts
7133
- var VERSION = "0.0.88";
7133
+ var VERSION = "0.0.90";
7134
7134
  var serverPath = join(__dirname, "server.cjs");
7135
7135
  execa(process.execPath, [serverPath], {
7136
7136
  detached: true,
package/dist/client.cjs CHANGED
@@ -12,7 +12,7 @@ var parseSSEEvent = (eventBlock) => {
12
12
  }
13
13
  return { eventType, data };
14
14
  };
15
- async function* streamSSE(stream, signal) {
15
+ var streamSSE = async function* (stream, signal) {
16
16
  const reader = stream.getReader();
17
17
  const decoder = new TextDecoder();
18
18
  let buffer = "";
@@ -51,28 +51,54 @@ async function* streamSSE(stream, signal) {
51
51
  } catch {
52
52
  }
53
53
  }
54
- }
55
-
56
- // src/constants.ts
57
- var DEFAULT_PORT = 6567;
58
- var COMPLETED_STATUS = "Completed successfully";
59
-
60
- // src/client.ts
61
- var DEFAULT_SERVER_URL = `http://localhost:${DEFAULT_PORT}`;
62
- var streamFromServer = async function* (serverUrl, context, signal) {
54
+ };
55
+ var isRecord = (value) => typeof value === "object" && value !== null;
56
+ var getStoredAgentContext = (storage, sessionId, storageKey = STORAGE_KEY) => {
57
+ const rawSessions = storage.getItem(storageKey);
58
+ if (!rawSessions) throw new Error("No sessions to resume");
59
+ let parsed;
60
+ try {
61
+ parsed = JSON.parse(rawSessions);
62
+ } catch {
63
+ throw new Error("Failed to parse stored sessions");
64
+ }
65
+ if (!isRecord(parsed)) throw new Error("Invalid stored sessions");
66
+ const storedSession = parsed[sessionId];
67
+ if (!isRecord(storedSession)) {
68
+ throw new Error(`Session ${sessionId} not found`);
69
+ }
70
+ const context = storedSession.context;
71
+ if (!isRecord(context)) throw new Error(`Session ${sessionId} is invalid`);
72
+ const content = context.content;
73
+ const prompt = context.prompt;
74
+ if (typeof content !== "string" || typeof prompt !== "string") {
75
+ throw new Error(`Session ${sessionId} is invalid`);
76
+ }
77
+ const options = context.options;
78
+ const storedSessionId = context.sessionId;
79
+ return {
80
+ content,
81
+ prompt,
82
+ options,
83
+ sessionId: typeof storedSessionId === "string" ? storedSessionId : void 0
84
+ };
85
+ };
86
+ var streamAgentStatusFromServer = async function* (options, context, signal) {
63
87
  const startTime = Date.now();
64
88
  const sessionId = context.sessionId;
89
+ const pollIntervalMs = options.pollIntervalMs ?? 100;
90
+ const agentUrl = `${options.serverUrl}${options.agentPath ?? "/agent"}`;
65
91
  const handleAbort = () => {
66
- if (sessionId) {
67
- fetch(`${serverUrl}/abort/${sessionId}`, { method: "POST" }).catch(
68
- () => {
69
- }
70
- );
71
- }
92
+ if (!sessionId) return;
93
+ const abortPath = options.abortPath?.(sessionId) ?? `/abort/${sessionId}`;
94
+ fetch(`${options.serverUrl}${abortPath}`, { method: "POST" }).catch(
95
+ () => {
96
+ }
97
+ );
72
98
  };
73
99
  signal.addEventListener("abort", handleAbort);
74
100
  try {
75
- const response = await fetch(`${serverUrl}/agent`, {
101
+ const response = await fetch(agentUrl, {
76
102
  method: "POST",
77
103
  headers: { "Content-Type": "application/json" },
78
104
  body: JSON.stringify(context),
@@ -85,29 +111,29 @@ var streamFromServer = async function* (serverUrl, context, signal) {
85
111
  throw new Error("No response body");
86
112
  }
87
113
  const iterator = streamSSE(response.body, signal)[Symbol.asyncIterator]();
88
- let done = false;
114
+ let isDone = false;
89
115
  let pendingNext = iterator.next();
90
116
  let lastStatus = null;
91
- while (!done) {
117
+ while (!isDone) {
92
118
  const result = await Promise.race([
93
119
  pendingNext.then((iteratorResult) => ({
94
120
  type: "status",
95
121
  iteratorResult
96
122
  })),
97
123
  new Promise(
98
- (resolve) => setTimeout(() => resolve({ type: "timeout" }), 100)
124
+ (resolve) => setTimeout(() => resolve({ type: "timeout" }), pollIntervalMs)
99
125
  )
100
126
  ]);
101
127
  const elapsedSeconds = (Date.now() - startTime) / 1e3;
102
128
  if (result.type === "status") {
103
129
  const iteratorResult = result.iteratorResult;
104
- done = iteratorResult.done ?? false;
105
- if (!done && iteratorResult.value) {
130
+ isDone = iteratorResult.done ?? false;
131
+ if (!isDone && iteratorResult.value) {
106
132
  lastStatus = iteratorResult.value;
107
133
  pendingNext = iterator.next();
108
134
  }
109
135
  }
110
- if (lastStatus === COMPLETED_STATUS) {
136
+ if (lastStatus === options.completedStatus) {
111
137
  yield `Completed in ${elapsedSeconds.toFixed(1)}s`;
112
138
  } else if (lastStatus) {
113
139
  yield `${lastStatus} ${elapsedSeconds.toFixed(1)}s`;
@@ -119,56 +145,73 @@ var streamFromServer = async function* (serverUrl, context, signal) {
119
145
  signal.removeEventListener("abort", handleAbort);
120
146
  }
121
147
  };
148
+ var createCachedConnectionChecker = (checkConnection, ttlMs = CONNECTION_CHECK_TTL_MS) => {
149
+ let cache = null;
150
+ return async () => {
151
+ const now = Date.now();
152
+ if (cache && now - cache.timestamp < ttlMs) return cache.result;
153
+ try {
154
+ const result = await checkConnection();
155
+ cache = { result, timestamp: now };
156
+ return result;
157
+ } catch {
158
+ cache = { result: false, timestamp: now };
159
+ return false;
160
+ }
161
+ };
162
+ };
163
+
164
+ // src/constants.ts
165
+ var DEFAULT_PORT = 6567;
166
+ var COMPLETED_STATUS = "Completed successfully";
167
+
168
+ // src/client.ts
169
+ var DEFAULT_SERVER_URL = `http://localhost:${DEFAULT_PORT}`;
170
+ var isReactGrabApi = (value) => typeof value === "object" && value !== null && "setAgent" in value;
122
171
  var createOpenCodeAgentProvider = (options = {}) => {
123
172
  const { serverUrl = DEFAULT_SERVER_URL, getOptions } = options;
124
- let connectionCache = null;
125
173
  const mergeOptions = (contextOptions) => ({
126
174
  ...getOptions?.() ?? {},
127
175
  ...contextOptions ?? {}
128
176
  });
177
+ const checkConnection = createCachedConnectionChecker(async () => {
178
+ const response = await fetch(`${serverUrl}/health`, { method: "GET" });
179
+ return response.ok;
180
+ }, CONNECTION_CHECK_TTL_MS);
129
181
  return {
130
182
  send: async function* (context, signal) {
131
183
  const combinedContext = {
132
184
  ...context,
133
185
  options: mergeOptions(context.options)
134
186
  };
135
- yield* streamFromServer(serverUrl, combinedContext, signal);
187
+ yield* streamAgentStatusFromServer(
188
+ { serverUrl, completedStatus: COMPLETED_STATUS },
189
+ combinedContext,
190
+ signal
191
+ );
136
192
  },
137
193
  resume: async function* (sessionId, signal, storage) {
138
- const storedSessions = storage.getItem(STORAGE_KEY);
139
- if (!storedSessions) {
140
- throw new Error("No sessions to resume");
141
- }
142
- const parsedSessions = JSON.parse(storedSessions);
143
- const session = parsedSessions[sessionId];
144
- if (!session) {
145
- throw new Error(`Session ${sessionId} not found`);
146
- }
147
- const context = session.context;
194
+ const storedContext = getStoredAgentContext(storage, sessionId);
195
+ const context = {
196
+ content: storedContext.content,
197
+ prompt: storedContext.prompt,
198
+ options: storedContext.options,
199
+ sessionId: storedContext.sessionId ?? sessionId
200
+ };
148
201
  const combinedContext = {
149
202
  ...context,
150
203
  options: mergeOptions(context.options)
151
204
  };
152
205
  yield "Resuming...";
153
- yield* streamFromServer(serverUrl, combinedContext, signal);
206
+ yield* streamAgentStatusFromServer(
207
+ { serverUrl, completedStatus: COMPLETED_STATUS },
208
+ combinedContext,
209
+ signal
210
+ );
154
211
  },
155
212
  supportsResume: true,
156
213
  supportsFollowUp: true,
157
- checkConnection: async () => {
158
- const now = Date.now();
159
- if (connectionCache && now - connectionCache.timestamp < CONNECTION_CHECK_TTL_MS) {
160
- return connectionCache.result;
161
- }
162
- try {
163
- const response = await fetch(`${serverUrl}/health`, { method: "GET" });
164
- const result = response.ok;
165
- connectionCache = { result, timestamp: now };
166
- return result;
167
- } catch {
168
- connectionCache = { result: false, timestamp: now };
169
- return false;
170
- }
171
- },
214
+ checkConnection,
172
215
  undo: async () => {
173
216
  try {
174
217
  await fetch(`${serverUrl}/undo`, { method: "POST" });
@@ -180,24 +223,25 @@ var createOpenCodeAgentProvider = (options = {}) => {
180
223
  var attachAgent = async () => {
181
224
  if (typeof window === "undefined") return;
182
225
  const provider = createOpenCodeAgentProvider();
183
- const attach = (api2) => {
184
- api2.setAgent({ provider, storage: sessionStorage });
226
+ const attach = (api) => {
227
+ api.setAgent({ provider, storage: sessionStorage });
185
228
  };
186
- const api = window.__REACT_GRAB__;
187
- if (api) {
188
- attach(api);
229
+ const existingApi = window.__REACT_GRAB__;
230
+ if (isReactGrabApi(existingApi)) {
231
+ attach(existingApi);
189
232
  return;
190
233
  }
191
234
  window.addEventListener(
192
235
  "react-grab:init",
193
236
  (event) => {
194
- const customEvent = event;
195
- attach(customEvent.detail);
237
+ if (!(event instanceof CustomEvent)) return;
238
+ if (!isReactGrabApi(event.detail)) return;
239
+ attach(event.detail);
196
240
  },
197
241
  { once: true }
198
242
  );
199
243
  const apiAfterListener = window.__REACT_GRAB__;
200
- if (apiAfterListener) {
244
+ if (isReactGrabApi(apiAfterListener)) {
201
245
  attach(apiAfterListener);
202
246
  }
203
247
  };
@@ -1,5 +1,5 @@
1
- var ReactGrabOpenCode=(function(exports){'use strict';var g=5e3,m="react-grab:agent-sessions",C=c=>{let e="",o="";for(let n of c.split(`
2
- `))n.startsWith("event:")?e=n.slice(6).trim():n.startsWith("data:")&&(o=n.slice(5).trim());return {eventType:e,data:o}};async function*A(c,e){let o=c.getReader(),n=new TextDecoder,r="",t=false,s=()=>{t=true,o.cancel().catch(()=>{});};e.addEventListener("abort",s);try{if(e.aborted)throw new DOMException("Aborted","AbortError");for(;;){let i=await o.read();if(t||e.aborted)throw new DOMException("Aborted","AbortError");let{done:l,value:f}=i;f&&(r+=n.decode(f,{stream:!0}));let a;for(;(a=r.indexOf(`
1
+ var ReactGrabOpenCode=(function(exports){'use strict';var A=5e3,O="react-grab:agent-sessions",b=t=>{let e="",o="";for(let r of t.split(`
2
+ `))r.startsWith("event:")?e=r.slice(6).trim():r.startsWith("data:")&&(o=r.slice(5).trim());return {eventType:e,data:o}},v=async function*(t,e){let o=t.getReader(),r=new TextDecoder,n="",s=false,a=()=>{s=true,o.cancel().catch(()=>{});};e.addEventListener("abort",a);try{if(e.aborted)throw new DOMException("Aborted","AbortError");for(;;){let d=await o.read();if(s||e.aborted)throw new DOMException("Aborted","AbortError");let{done:i,value:p}=d;p&&(n+=r.decode(p,{stream:!0}));let c;for(;(c=n.indexOf(`
3
3
 
4
- `))!==-1;){let{eventType:p,data:d}=C(r.slice(0,a));if(r=r.slice(a+2),p==="done")return;if(p==="error")throw new Error(d||"Agent error");d&&(yield d);}if(l)break}}finally{e.removeEventListener("abort",s);try{o.releaseLock();}catch{}}}var y="Completed successfully";var w=`http://localhost:${6567}`,E=async function*(c,e,o){let n=Date.now(),r=e.sessionId,t=()=>{r&&fetch(`${c}/abort/${r}`,{method:"POST"}).catch(()=>{});};o.addEventListener("abort",t);try{let s=await fetch(`${c}/agent`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:o});if(!s.ok)throw new Error(`Server error: ${s.status}`);if(!s.body)throw new Error("No response body");let i=A(s.body,o)[Symbol.asyncIterator](),l=!1,f=i.next(),a=null;for(;!l;){let p=await Promise.race([f.then(u=>({type:"status",iteratorResult:u})),new Promise(u=>setTimeout(()=>u({type:"timeout"}),100))]),d=(Date.now()-n)/1e3;if(p.type==="status"){let u=p.iteratorResult;l=u.done??!1,!l&&u.value&&(a=u.value,f=i.next());}a===y?yield `Completed in ${d.toFixed(1)}s`:a?yield `${a} ${d.toFixed(1)}s`:yield `Working\u2026 ${d.toFixed(1)}s`;}}finally{o.removeEventListener("abort",t);}},S=(c={})=>{let{serverUrl:e=w,getOptions:o}=c,n=null,r=t=>({...o?.()??{},...t??{}});return {send:async function*(t,s){let i={...t,options:r(t.options)};yield*E(e,i,s);},resume:async function*(t,s,i){let l=i.getItem(m);if(!l)throw new Error("No sessions to resume");let a=JSON.parse(l)[t];if(!a)throw new Error(`Session ${t} not found`);let p=a.context,d={...p,options:r(p.options)};yield "Resuming...",yield*E(e,d,s);},supportsResume:true,supportsFollowUp:true,checkConnection:async()=>{let t=Date.now();if(n&&t-n.timestamp<g)return n.result;try{let i=(await fetch(`${e}/health`,{method:"GET"})).ok;return n={result:i,timestamp:t},i}catch{return n={result:false,timestamp:t},false}},undo:async()=>{try{await fetch(`${e}/undo`,{method:"POST"});}catch{}}}},b=async()=>{if(typeof window>"u")return;let c=S(),e=r=>{r.setAgent({provider:c,storage:sessionStorage});},o=window.__REACT_GRAB__;if(o){e(o);return}window.addEventListener("react-grab:init",r=>{e(r.detail);},{once:true});let n=window.__REACT_GRAB__;n&&e(n);};b();
5
- exports.attachAgent=b;exports.createOpenCodeAgentProvider=S;return exports;})({});
4
+ `))!==-1;){let{eventType:f,data:l}=b(n.slice(0,c));if(n=n.slice(c+2),f==="done")return;if(f==="error")throw new Error(l||"Agent error");l&&(yield l);}if(i)break}}finally{e.removeEventListener("abort",a);try{o.releaseLock();}catch{}}},m=t=>typeof t=="object"&&t!==null,S=(t,e,o=O)=>{let r=t.getItem(o);if(!r)throw new Error("No sessions to resume");let n;try{n=JSON.parse(r);}catch{throw new Error("Failed to parse stored sessions")}if(!m(n))throw new Error("Invalid stored sessions");let s=n[e];if(!m(s))throw new Error(`Session ${e} not found`);let a=s.context;if(!m(a))throw new Error(`Session ${e} is invalid`);let d=a.content,i=a.prompt;if(typeof d!="string"||typeof i!="string")throw new Error(`Session ${e} is invalid`);let p=a.options,c=a.sessionId;return {content:d,prompt:i,options:p,sessionId:typeof c=="string"?c:void 0}},y=async function*(t,e,o){let r=Date.now(),n=e.sessionId,s=t.pollIntervalMs??100,a=`${t.serverUrl}${t.agentPath??"/agent"}`,d=()=>{if(!n)return;let i=t.abortPath?.(n)??`/abort/${n}`;fetch(`${t.serverUrl}${i}`,{method:"POST"}).catch(()=>{});};o.addEventListener("abort",d);try{let i=await fetch(a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:o});if(!i.ok)throw new Error(`Server error: ${i.status}`);if(!i.body)throw new Error("No response body");let p=v(i.body,o)[Symbol.asyncIterator](),c=!1,f=p.next(),l=null;for(;!c;){let C=await Promise.race([f.then(u=>({type:"status",iteratorResult:u})),new Promise(u=>setTimeout(()=>u({type:"timeout"}),s))]),g=(Date.now()-r)/1e3;if(C.type==="status"){let u=C.iteratorResult;c=u.done??!1,!c&&u.value&&(l=u.value,f=p.next());}l===t.completedStatus?yield `Completed in ${g.toFixed(1)}s`:l?yield `${l} ${g.toFixed(1)}s`:yield `Working\u2026 ${g.toFixed(1)}s`;}}finally{o.removeEventListener("abort",d);}},E=(t,e=A)=>{let o=null;return async()=>{let r=Date.now();if(o&&r-o.timestamp<e)return o.result;try{let n=await t();return o={result:n,timestamp:r},n}catch{return o={result:false,timestamp:r},false}}};var w="Completed successfully";var x=`http://localhost:${6567}`,h=t=>typeof t=="object"&&t!==null&&"setAgent"in t,_=(t={})=>{let{serverUrl:e=x,getOptions:o}=t,r=s=>({...o?.()??{},...s??{}}),n=E(async()=>(await fetch(`${e}/health`,{method:"GET"})).ok,A);return {send:async function*(s,a){let d={...s,options:r(s.options)};yield*y({serverUrl:e,completedStatus:w},d,a);},resume:async function*(s,a,d){let i=S(d,s),p={content:i.content,prompt:i.prompt,options:i.options,sessionId:i.sessionId??s},c={...p,options:r(p.options)};yield "Resuming...",yield*y({serverUrl:e,completedStatus:w},c,a);},supportsResume:true,supportsFollowUp:true,checkConnection:n,undo:async()=>{try{await fetch(`${e}/undo`,{method:"POST"});}catch{}}}},R=async()=>{if(typeof window>"u")return;let t=_(),e=n=>{n.setAgent({provider:t,storage:sessionStorage});},o=window.__REACT_GRAB__;if(h(o)){e(o);return}window.addEventListener("react-grab:init",n=>{n instanceof CustomEvent&&h(n.detail)&&e(n.detail);},{once:true});let r=window.__REACT_GRAB__;h(r)&&e(r);};R();
5
+ exports.attachAgent=R;exports.createOpenCodeAgentProvider=_;return exports;})({});
package/dist/client.js CHANGED
@@ -10,7 +10,7 @@ var parseSSEEvent = (eventBlock) => {
10
10
  }
11
11
  return { eventType, data };
12
12
  };
13
- async function* streamSSE(stream, signal) {
13
+ var streamSSE = async function* (stream, signal) {
14
14
  const reader = stream.getReader();
15
15
  const decoder = new TextDecoder();
16
16
  let buffer = "";
@@ -49,28 +49,54 @@ async function* streamSSE(stream, signal) {
49
49
  } catch {
50
50
  }
51
51
  }
52
- }
53
-
54
- // src/constants.ts
55
- var DEFAULT_PORT = 6567;
56
- var COMPLETED_STATUS = "Completed successfully";
57
-
58
- // src/client.ts
59
- var DEFAULT_SERVER_URL = `http://localhost:${DEFAULT_PORT}`;
60
- var streamFromServer = async function* (serverUrl, context, signal) {
52
+ };
53
+ var isRecord = (value) => typeof value === "object" && value !== null;
54
+ var getStoredAgentContext = (storage, sessionId, storageKey = STORAGE_KEY) => {
55
+ const rawSessions = storage.getItem(storageKey);
56
+ if (!rawSessions) throw new Error("No sessions to resume");
57
+ let parsed;
58
+ try {
59
+ parsed = JSON.parse(rawSessions);
60
+ } catch {
61
+ throw new Error("Failed to parse stored sessions");
62
+ }
63
+ if (!isRecord(parsed)) throw new Error("Invalid stored sessions");
64
+ const storedSession = parsed[sessionId];
65
+ if (!isRecord(storedSession)) {
66
+ throw new Error(`Session ${sessionId} not found`);
67
+ }
68
+ const context = storedSession.context;
69
+ if (!isRecord(context)) throw new Error(`Session ${sessionId} is invalid`);
70
+ const content = context.content;
71
+ const prompt = context.prompt;
72
+ if (typeof content !== "string" || typeof prompt !== "string") {
73
+ throw new Error(`Session ${sessionId} is invalid`);
74
+ }
75
+ const options = context.options;
76
+ const storedSessionId = context.sessionId;
77
+ return {
78
+ content,
79
+ prompt,
80
+ options,
81
+ sessionId: typeof storedSessionId === "string" ? storedSessionId : void 0
82
+ };
83
+ };
84
+ var streamAgentStatusFromServer = async function* (options, context, signal) {
61
85
  const startTime = Date.now();
62
86
  const sessionId = context.sessionId;
87
+ const pollIntervalMs = options.pollIntervalMs ?? 100;
88
+ const agentUrl = `${options.serverUrl}${options.agentPath ?? "/agent"}`;
63
89
  const handleAbort = () => {
64
- if (sessionId) {
65
- fetch(`${serverUrl}/abort/${sessionId}`, { method: "POST" }).catch(
66
- () => {
67
- }
68
- );
69
- }
90
+ if (!sessionId) return;
91
+ const abortPath = options.abortPath?.(sessionId) ?? `/abort/${sessionId}`;
92
+ fetch(`${options.serverUrl}${abortPath}`, { method: "POST" }).catch(
93
+ () => {
94
+ }
95
+ );
70
96
  };
71
97
  signal.addEventListener("abort", handleAbort);
72
98
  try {
73
- const response = await fetch(`${serverUrl}/agent`, {
99
+ const response = await fetch(agentUrl, {
74
100
  method: "POST",
75
101
  headers: { "Content-Type": "application/json" },
76
102
  body: JSON.stringify(context),
@@ -83,29 +109,29 @@ var streamFromServer = async function* (serverUrl, context, signal) {
83
109
  throw new Error("No response body");
84
110
  }
85
111
  const iterator = streamSSE(response.body, signal)[Symbol.asyncIterator]();
86
- let done = false;
112
+ let isDone = false;
87
113
  let pendingNext = iterator.next();
88
114
  let lastStatus = null;
89
- while (!done) {
115
+ while (!isDone) {
90
116
  const result = await Promise.race([
91
117
  pendingNext.then((iteratorResult) => ({
92
118
  type: "status",
93
119
  iteratorResult
94
120
  })),
95
121
  new Promise(
96
- (resolve) => setTimeout(() => resolve({ type: "timeout" }), 100)
122
+ (resolve) => setTimeout(() => resolve({ type: "timeout" }), pollIntervalMs)
97
123
  )
98
124
  ]);
99
125
  const elapsedSeconds = (Date.now() - startTime) / 1e3;
100
126
  if (result.type === "status") {
101
127
  const iteratorResult = result.iteratorResult;
102
- done = iteratorResult.done ?? false;
103
- if (!done && iteratorResult.value) {
128
+ isDone = iteratorResult.done ?? false;
129
+ if (!isDone && iteratorResult.value) {
104
130
  lastStatus = iteratorResult.value;
105
131
  pendingNext = iterator.next();
106
132
  }
107
133
  }
108
- if (lastStatus === COMPLETED_STATUS) {
134
+ if (lastStatus === options.completedStatus) {
109
135
  yield `Completed in ${elapsedSeconds.toFixed(1)}s`;
110
136
  } else if (lastStatus) {
111
137
  yield `${lastStatus} ${elapsedSeconds.toFixed(1)}s`;
@@ -117,56 +143,73 @@ var streamFromServer = async function* (serverUrl, context, signal) {
117
143
  signal.removeEventListener("abort", handleAbort);
118
144
  }
119
145
  };
146
+ var createCachedConnectionChecker = (checkConnection, ttlMs = CONNECTION_CHECK_TTL_MS) => {
147
+ let cache = null;
148
+ return async () => {
149
+ const now = Date.now();
150
+ if (cache && now - cache.timestamp < ttlMs) return cache.result;
151
+ try {
152
+ const result = await checkConnection();
153
+ cache = { result, timestamp: now };
154
+ return result;
155
+ } catch {
156
+ cache = { result: false, timestamp: now };
157
+ return false;
158
+ }
159
+ };
160
+ };
161
+
162
+ // src/constants.ts
163
+ var DEFAULT_PORT = 6567;
164
+ var COMPLETED_STATUS = "Completed successfully";
165
+
166
+ // src/client.ts
167
+ var DEFAULT_SERVER_URL = `http://localhost:${DEFAULT_PORT}`;
168
+ var isReactGrabApi = (value) => typeof value === "object" && value !== null && "setAgent" in value;
120
169
  var createOpenCodeAgentProvider = (options = {}) => {
121
170
  const { serverUrl = DEFAULT_SERVER_URL, getOptions } = options;
122
- let connectionCache = null;
123
171
  const mergeOptions = (contextOptions) => ({
124
172
  ...getOptions?.() ?? {},
125
173
  ...contextOptions ?? {}
126
174
  });
175
+ const checkConnection = createCachedConnectionChecker(async () => {
176
+ const response = await fetch(`${serverUrl}/health`, { method: "GET" });
177
+ return response.ok;
178
+ }, CONNECTION_CHECK_TTL_MS);
127
179
  return {
128
180
  send: async function* (context, signal) {
129
181
  const combinedContext = {
130
182
  ...context,
131
183
  options: mergeOptions(context.options)
132
184
  };
133
- yield* streamFromServer(serverUrl, combinedContext, signal);
185
+ yield* streamAgentStatusFromServer(
186
+ { serverUrl, completedStatus: COMPLETED_STATUS },
187
+ combinedContext,
188
+ signal
189
+ );
134
190
  },
135
191
  resume: async function* (sessionId, signal, storage) {
136
- const storedSessions = storage.getItem(STORAGE_KEY);
137
- if (!storedSessions) {
138
- throw new Error("No sessions to resume");
139
- }
140
- const parsedSessions = JSON.parse(storedSessions);
141
- const session = parsedSessions[sessionId];
142
- if (!session) {
143
- throw new Error(`Session ${sessionId} not found`);
144
- }
145
- const context = session.context;
192
+ const storedContext = getStoredAgentContext(storage, sessionId);
193
+ const context = {
194
+ content: storedContext.content,
195
+ prompt: storedContext.prompt,
196
+ options: storedContext.options,
197
+ sessionId: storedContext.sessionId ?? sessionId
198
+ };
146
199
  const combinedContext = {
147
200
  ...context,
148
201
  options: mergeOptions(context.options)
149
202
  };
150
203
  yield "Resuming...";
151
- yield* streamFromServer(serverUrl, combinedContext, signal);
204
+ yield* streamAgentStatusFromServer(
205
+ { serverUrl, completedStatus: COMPLETED_STATUS },
206
+ combinedContext,
207
+ signal
208
+ );
152
209
  },
153
210
  supportsResume: true,
154
211
  supportsFollowUp: true,
155
- checkConnection: async () => {
156
- const now = Date.now();
157
- if (connectionCache && now - connectionCache.timestamp < CONNECTION_CHECK_TTL_MS) {
158
- return connectionCache.result;
159
- }
160
- try {
161
- const response = await fetch(`${serverUrl}/health`, { method: "GET" });
162
- const result = response.ok;
163
- connectionCache = { result, timestamp: now };
164
- return result;
165
- } catch {
166
- connectionCache = { result: false, timestamp: now };
167
- return false;
168
- }
169
- },
212
+ checkConnection,
170
213
  undo: async () => {
171
214
  try {
172
215
  await fetch(`${serverUrl}/undo`, { method: "POST" });
@@ -178,24 +221,25 @@ var createOpenCodeAgentProvider = (options = {}) => {
178
221
  var attachAgent = async () => {
179
222
  if (typeof window === "undefined") return;
180
223
  const provider = createOpenCodeAgentProvider();
181
- const attach = (api2) => {
182
- api2.setAgent({ provider, storage: sessionStorage });
224
+ const attach = (api) => {
225
+ api.setAgent({ provider, storage: sessionStorage });
183
226
  };
184
- const api = window.__REACT_GRAB__;
185
- if (api) {
186
- attach(api);
227
+ const existingApi = window.__REACT_GRAB__;
228
+ if (isReactGrabApi(existingApi)) {
229
+ attach(existingApi);
187
230
  return;
188
231
  }
189
232
  window.addEventListener(
190
233
  "react-grab:init",
191
234
  (event) => {
192
- const customEvent = event;
193
- attach(customEvent.detail);
235
+ if (!(event instanceof CustomEvent)) return;
236
+ if (!isReactGrabApi(event.detail)) return;
237
+ attach(event.detail);
194
238
  },
195
239
  { once: true }
196
240
  );
197
241
  const apiAfterListener = window.__REACT_GRAB__;
198
- if (apiAfterListener) {
242
+ if (isReactGrabApi(apiAfterListener)) {
199
243
  attach(apiAfterListener);
200
244
  }
201
245
  };
package/dist/server.cjs CHANGED
@@ -7343,9 +7343,11 @@ var COMPLETED_STATUS = "Completed successfully";
7343
7343
 
7344
7344
  // ../utils/dist/server.js
7345
7345
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
7346
- var VERSION = "0.0.88";
7346
+ var VERSION = "0.0.90";
7347
7347
  try {
7348
- fetch(`https://www.react-grab.com/api/version?source=opencode&t=${Date.now()}`).catch(() => {
7348
+ fetch(
7349
+ `https://www.react-grab.com/api/version?source=opencode&t=${Date.now()}`
7350
+ ).catch(() => {
7349
7351
  });
7350
7352
  } catch {
7351
7353
  }
@@ -7412,7 +7414,10 @@ var executeOpenCodePrompt = async (prompt, options, onStatus, reactGrabSessionId
7412
7414
  const part = eventData.properties.part;
7413
7415
  if (part.sessionID !== opencodeSessionId) continue;
7414
7416
  if (part.messageID) {
7415
- lastMessageInfo = { sessionId: opencodeSessionId, messageId: part.messageID };
7417
+ lastMessageInfo = {
7418
+ sessionId: opencodeSessionId,
7419
+ messageId: part.messageID
7420
+ };
7416
7421
  }
7417
7422
  if (part.type === "text" && part.text) {
7418
7423
  const truncatedText = part.text.length > 100 ? `${part.text.slice(0, 100)}...` : part.text;
@@ -7425,6 +7430,94 @@ var executeOpenCodePrompt = async (prompt, options, onStatus, reactGrabSessionId
7425
7430
  }
7426
7431
  return opencodeSessionId;
7427
7432
  };
7433
+ var runAgent = async function* (prompt, options) {
7434
+ const sessionId = options?.sessionId;
7435
+ const signal = { aborted: false };
7436
+ const isAborted = () => {
7437
+ if (options?.signal?.aborted) {
7438
+ signal.aborted = true;
7439
+ return true;
7440
+ }
7441
+ if (sessionId && abortedSessions.has(sessionId)) {
7442
+ signal.aborted = true;
7443
+ return true;
7444
+ }
7445
+ return false;
7446
+ };
7447
+ const messageQueue = [];
7448
+ let resolveWait = null;
7449
+ const enqueueMessage = (message) => {
7450
+ messageQueue.push(message);
7451
+ if (resolveWait) {
7452
+ resolveWait();
7453
+ resolveWait = null;
7454
+ }
7455
+ };
7456
+ try {
7457
+ const executePromise = executeOpenCodePrompt(
7458
+ prompt,
7459
+ options,
7460
+ (text) => {
7461
+ if (!isAborted()) {
7462
+ enqueueMessage({ type: "status", content: text });
7463
+ }
7464
+ },
7465
+ sessionId,
7466
+ signal
7467
+ );
7468
+ let isDone = false;
7469
+ executePromise.then(() => {
7470
+ if (!isAborted()) {
7471
+ enqueueMessage({ type: "status", content: COMPLETED_STATUS });
7472
+ enqueueMessage({ type: "done", content: "" });
7473
+ }
7474
+ isDone = true;
7475
+ if (resolveWait) {
7476
+ resolveWait();
7477
+ resolveWait = null;
7478
+ }
7479
+ }).catch((error) => {
7480
+ if (!isAborted()) {
7481
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
7482
+ const stderr = error instanceof Error && "stderr" in error ? String(error.stderr) : void 0;
7483
+ const fullError = stderr && stderr.trim() ? `${errorMessage}
7484
+
7485
+ stderr:
7486
+ ${stderr.trim()}` : errorMessage;
7487
+ enqueueMessage({ type: "error", content: fullError });
7488
+ enqueueMessage({ type: "done", content: "" });
7489
+ }
7490
+ isDone = true;
7491
+ if (resolveWait) {
7492
+ resolveWait();
7493
+ resolveWait = null;
7494
+ }
7495
+ });
7496
+ while (true) {
7497
+ if (isAborted()) {
7498
+ return;
7499
+ }
7500
+ if (messageQueue.length > 0) {
7501
+ const message = messageQueue.shift();
7502
+ if (message.type === "done") {
7503
+ yield message;
7504
+ return;
7505
+ }
7506
+ yield message;
7507
+ } else if (isDone) {
7508
+ return;
7509
+ } else {
7510
+ await new Promise((resolve) => {
7511
+ resolveWait = resolve;
7512
+ });
7513
+ }
7514
+ }
7515
+ } finally {
7516
+ if (sessionId) {
7517
+ abortedSessions.delete(sessionId);
7518
+ }
7519
+ }
7520
+ };
7428
7521
  var createServer = () => {
7429
7522
  const honoApplication = new Hono2();
7430
7523
  honoApplication.use("*", cors());
@@ -7439,53 +7532,17 @@ Context:
7439
7532
  ${content}
7440
7533
  `;
7441
7534
  return streamSSE(context, async (stream2) => {
7442
- const signal = { aborted: false };
7443
- const isAborted = () => {
7444
- if (sessionId && abortedSessions.has(sessionId)) {
7445
- signal.aborted = true;
7446
- return true;
7447
- }
7448
- return false;
7449
- };
7450
- try {
7451
- await executeOpenCodePrompt(
7452
- formattedPrompt,
7453
- options,
7454
- (text) => {
7455
- if (isAborted()) return;
7456
- stream2.writeSSE({
7457
- data: text,
7458
- event: "status"
7459
- }).catch(() => {
7460
- });
7461
- },
7462
- sessionId,
7463
- signal
7464
- );
7465
- if (!isAborted()) {
7535
+ for await (const message of runAgent(formattedPrompt, {
7536
+ ...options,
7537
+ sessionId
7538
+ })) {
7539
+ if (message.type === "error") {
7466
7540
  await stream2.writeSSE({
7467
- data: COMPLETED_STATUS,
7468
- event: "status"
7469
- });
7470
- await stream2.writeSSE({ data: "", event: "done" });
7471
- }
7472
- } catch (error) {
7473
- if (!isAborted()) {
7474
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
7475
- const stderr = error instanceof Error && "stderr" in error ? String(error.stderr) : void 0;
7476
- const fullError = stderr && stderr.trim() ? `${errorMessage}
7477
-
7478
- stderr:
7479
- ${stderr.trim()}` : errorMessage;
7480
- await stream2.writeSSE({
7481
- data: `Error: ${fullError}`,
7541
+ data: `Error: ${message.content}`,
7482
7542
  event: "error"
7483
7543
  });
7484
- await stream2.writeSSE({ data: "", event: "done" });
7485
- }
7486
- } finally {
7487
- if (sessionId) {
7488
- abortedSessions.delete(sessionId);
7544
+ } else {
7545
+ await stream2.writeSSE({ data: message.content, event: message.type });
7489
7546
  }
7490
7547
  }
7491
7548
  });
@@ -7532,4 +7589,5 @@ if ((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filen
7532
7589
  }
7533
7590
 
7534
7591
  exports.createServer = createServer;
7592
+ exports.runAgent = runAgent;
7535
7593
  exports.startServer = startServer;
package/dist/server.js CHANGED
@@ -7332,9 +7332,11 @@ var COMPLETED_STATUS = "Completed successfully";
7332
7332
 
7333
7333
  // ../utils/dist/server.js
7334
7334
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
7335
- var VERSION = "0.0.88";
7335
+ var VERSION = "0.0.90";
7336
7336
  try {
7337
- fetch(`https://www.react-grab.com/api/version?source=opencode&t=${Date.now()}`).catch(() => {
7337
+ fetch(
7338
+ `https://www.react-grab.com/api/version?source=opencode&t=${Date.now()}`
7339
+ ).catch(() => {
7338
7340
  });
7339
7341
  } catch {
7340
7342
  }
@@ -7401,7 +7403,10 @@ var executeOpenCodePrompt = async (prompt, options, onStatus, reactGrabSessionId
7401
7403
  const part = eventData.properties.part;
7402
7404
  if (part.sessionID !== opencodeSessionId) continue;
7403
7405
  if (part.messageID) {
7404
- lastMessageInfo = { sessionId: opencodeSessionId, messageId: part.messageID };
7406
+ lastMessageInfo = {
7407
+ sessionId: opencodeSessionId,
7408
+ messageId: part.messageID
7409
+ };
7405
7410
  }
7406
7411
  if (part.type === "text" && part.text) {
7407
7412
  const truncatedText = part.text.length > 100 ? `${part.text.slice(0, 100)}...` : part.text;
@@ -7414,6 +7419,94 @@ var executeOpenCodePrompt = async (prompt, options, onStatus, reactGrabSessionId
7414
7419
  }
7415
7420
  return opencodeSessionId;
7416
7421
  };
7422
+ var runAgent = async function* (prompt, options) {
7423
+ const sessionId = options?.sessionId;
7424
+ const signal = { aborted: false };
7425
+ const isAborted = () => {
7426
+ if (options?.signal?.aborted) {
7427
+ signal.aborted = true;
7428
+ return true;
7429
+ }
7430
+ if (sessionId && abortedSessions.has(sessionId)) {
7431
+ signal.aborted = true;
7432
+ return true;
7433
+ }
7434
+ return false;
7435
+ };
7436
+ const messageQueue = [];
7437
+ let resolveWait = null;
7438
+ const enqueueMessage = (message) => {
7439
+ messageQueue.push(message);
7440
+ if (resolveWait) {
7441
+ resolveWait();
7442
+ resolveWait = null;
7443
+ }
7444
+ };
7445
+ try {
7446
+ const executePromise = executeOpenCodePrompt(
7447
+ prompt,
7448
+ options,
7449
+ (text) => {
7450
+ if (!isAborted()) {
7451
+ enqueueMessage({ type: "status", content: text });
7452
+ }
7453
+ },
7454
+ sessionId,
7455
+ signal
7456
+ );
7457
+ let isDone = false;
7458
+ executePromise.then(() => {
7459
+ if (!isAborted()) {
7460
+ enqueueMessage({ type: "status", content: COMPLETED_STATUS });
7461
+ enqueueMessage({ type: "done", content: "" });
7462
+ }
7463
+ isDone = true;
7464
+ if (resolveWait) {
7465
+ resolveWait();
7466
+ resolveWait = null;
7467
+ }
7468
+ }).catch((error) => {
7469
+ if (!isAborted()) {
7470
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
7471
+ const stderr = error instanceof Error && "stderr" in error ? String(error.stderr) : void 0;
7472
+ const fullError = stderr && stderr.trim() ? `${errorMessage}
7473
+
7474
+ stderr:
7475
+ ${stderr.trim()}` : errorMessage;
7476
+ enqueueMessage({ type: "error", content: fullError });
7477
+ enqueueMessage({ type: "done", content: "" });
7478
+ }
7479
+ isDone = true;
7480
+ if (resolveWait) {
7481
+ resolveWait();
7482
+ resolveWait = null;
7483
+ }
7484
+ });
7485
+ while (true) {
7486
+ if (isAborted()) {
7487
+ return;
7488
+ }
7489
+ if (messageQueue.length > 0) {
7490
+ const message = messageQueue.shift();
7491
+ if (message.type === "done") {
7492
+ yield message;
7493
+ return;
7494
+ }
7495
+ yield message;
7496
+ } else if (isDone) {
7497
+ return;
7498
+ } else {
7499
+ await new Promise((resolve) => {
7500
+ resolveWait = resolve;
7501
+ });
7502
+ }
7503
+ }
7504
+ } finally {
7505
+ if (sessionId) {
7506
+ abortedSessions.delete(sessionId);
7507
+ }
7508
+ }
7509
+ };
7417
7510
  var createServer = () => {
7418
7511
  const honoApplication = new Hono2();
7419
7512
  honoApplication.use("*", cors());
@@ -7428,53 +7521,17 @@ Context:
7428
7521
  ${content}
7429
7522
  `;
7430
7523
  return streamSSE(context, async (stream2) => {
7431
- const signal = { aborted: false };
7432
- const isAborted = () => {
7433
- if (sessionId && abortedSessions.has(sessionId)) {
7434
- signal.aborted = true;
7435
- return true;
7436
- }
7437
- return false;
7438
- };
7439
- try {
7440
- await executeOpenCodePrompt(
7441
- formattedPrompt,
7442
- options,
7443
- (text) => {
7444
- if (isAborted()) return;
7445
- stream2.writeSSE({
7446
- data: text,
7447
- event: "status"
7448
- }).catch(() => {
7449
- });
7450
- },
7451
- sessionId,
7452
- signal
7453
- );
7454
- if (!isAborted()) {
7524
+ for await (const message of runAgent(formattedPrompt, {
7525
+ ...options,
7526
+ sessionId
7527
+ })) {
7528
+ if (message.type === "error") {
7455
7529
  await stream2.writeSSE({
7456
- data: COMPLETED_STATUS,
7457
- event: "status"
7458
- });
7459
- await stream2.writeSSE({ data: "", event: "done" });
7460
- }
7461
- } catch (error) {
7462
- if (!isAborted()) {
7463
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
7464
- const stderr = error instanceof Error && "stderr" in error ? String(error.stderr) : void 0;
7465
- const fullError = stderr && stderr.trim() ? `${errorMessage}
7466
-
7467
- stderr:
7468
- ${stderr.trim()}` : errorMessage;
7469
- await stream2.writeSSE({
7470
- data: `Error: ${fullError}`,
7530
+ data: `Error: ${message.content}`,
7471
7531
  event: "error"
7472
7532
  });
7473
- await stream2.writeSSE({ data: "", event: "done" });
7474
- }
7475
- } finally {
7476
- if (sessionId) {
7477
- abortedSessions.delete(sessionId);
7533
+ } else {
7534
+ await stream2.writeSSE({ data: message.content, event: message.type });
7478
7535
  }
7479
7536
  }
7480
7537
  });
@@ -7520,4 +7577,4 @@ if (import.meta.url === pathToFileURL(process.argv[1]).href) {
7520
7577
  startServer(DEFAULT_PORT).catch(console.error);
7521
7578
  }
7522
7579
 
7523
- export { createServer, startServer };
7580
+ export { createServer, runAgent, startServer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-grab/opencode",
3
- "version": "0.0.88",
3
+ "version": "0.0.90",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "react-grab-opencode": "./dist/cli.cjs"
@@ -26,7 +26,7 @@
26
26
  "devDependencies": {
27
27
  "@types/node": "^22.10.7",
28
28
  "tsup": "^8.4.0",
29
- "@react-grab/utils": "0.0.88"
29
+ "@react-grab/utils": "0.0.90"
30
30
  },
31
31
  "dependencies": {
32
32
  "@hono/node-server": "^1.19.6",
@@ -35,7 +35,7 @@
35
35
  "fkill": "^9.0.0",
36
36
  "hono": "^4.0.0",
37
37
  "picocolors": "^1.1.1",
38
- "react-grab": "0.0.88"
38
+ "react-grab": "0.0.90"
39
39
  },
40
40
  "scripts": {
41
41
  "dev": "tsup --watch",