@react-grab/gemini 0.0.87 → 0.0.89
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 +1 -1
- package/dist/cli.js +1 -1
- package/dist/client.cjs +104 -60
- package/dist/client.global.js +4 -4
- package/dist/client.js +104 -60
- package/dist/server.cjs +189 -140
- package/dist/server.d.cts +7 -1
- package/dist/server.d.ts +7 -1
- package/dist/server.js +189 -141
- package/package.json +3 -3
package/dist/cli.cjs
CHANGED
|
@@ -7138,7 +7138,7 @@ var import_picocolors = __toESM(require_picocolors());
|
|
|
7138
7138
|
var DEFAULT_PORT = 8567;
|
|
7139
7139
|
|
|
7140
7140
|
// src/cli.ts
|
|
7141
|
-
var VERSION = "0.0.
|
|
7141
|
+
var VERSION = "0.0.89";
|
|
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 = 8567;
|
|
7131
7131
|
|
|
7132
7132
|
// src/cli.ts
|
|
7133
|
-
var VERSION = "0.0.
|
|
7133
|
+
var VERSION = "0.0.89";
|
|
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*
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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(
|
|
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 @@ async function* streamFromServer(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
|
|
114
|
+
let isDone = false;
|
|
89
115
|
let pendingNext = iterator.next();
|
|
90
116
|
let lastStatus = null;
|
|
91
|
-
while (!
|
|
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" }),
|
|
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
|
-
|
|
105
|
-
if (!
|
|
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 ===
|
|
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`;
|
|
@@ -118,57 +144,74 @@ async function* streamFromServer(serverUrl, context, signal) {
|
|
|
118
144
|
} finally {
|
|
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 = 8567;
|
|
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 createGeminiAgentProvider = (providerOptions = {}) => {
|
|
123
172
|
const { serverUrl = DEFAULT_SERVER_URL, getOptions } = providerOptions;
|
|
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 mergedContext = {
|
|
132
184
|
...context,
|
|
133
185
|
options: mergeOptions(context.options)
|
|
134
186
|
};
|
|
135
|
-
yield*
|
|
187
|
+
yield* streamAgentStatusFromServer(
|
|
188
|
+
{ serverUrl, completedStatus: COMPLETED_STATUS },
|
|
189
|
+
mergedContext,
|
|
190
|
+
signal
|
|
191
|
+
);
|
|
136
192
|
},
|
|
137
193
|
resume: async function* (sessionId, signal, storage) {
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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 mergedContext = {
|
|
149
202
|
...context,
|
|
150
203
|
options: mergeOptions(context.options)
|
|
151
204
|
};
|
|
152
205
|
yield "Resuming...";
|
|
153
|
-
yield*
|
|
206
|
+
yield* streamAgentStatusFromServer(
|
|
207
|
+
{ serverUrl, completedStatus: COMPLETED_STATUS },
|
|
208
|
+
mergedContext,
|
|
209
|
+
signal
|
|
210
|
+
);
|
|
154
211
|
},
|
|
155
212
|
supportsResume: true,
|
|
156
213
|
supportsFollowUp: true,
|
|
157
|
-
checkConnection
|
|
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
|
abort: async (sessionId) => {
|
|
173
216
|
try {
|
|
174
217
|
await fetch(`${serverUrl}/abort/${sessionId}`, { method: "POST" });
|
|
@@ -186,24 +229,25 @@ var createGeminiAgentProvider = (providerOptions = {}) => {
|
|
|
186
229
|
var attachAgent = async () => {
|
|
187
230
|
if (typeof window === "undefined") return;
|
|
188
231
|
const provider = createGeminiAgentProvider();
|
|
189
|
-
const attach = (
|
|
190
|
-
|
|
232
|
+
const attach = (api) => {
|
|
233
|
+
api.setAgent({ provider, storage: sessionStorage });
|
|
191
234
|
};
|
|
192
|
-
const
|
|
193
|
-
if (
|
|
194
|
-
attach(
|
|
235
|
+
const existingApi = window.__REACT_GRAB__;
|
|
236
|
+
if (isReactGrabApi(existingApi)) {
|
|
237
|
+
attach(existingApi);
|
|
195
238
|
return;
|
|
196
239
|
}
|
|
197
240
|
window.addEventListener(
|
|
198
241
|
"react-grab:init",
|
|
199
242
|
(event) => {
|
|
200
|
-
|
|
201
|
-
|
|
243
|
+
if (!(event instanceof CustomEvent)) return;
|
|
244
|
+
if (!isReactGrabApi(event.detail)) return;
|
|
245
|
+
attach(event.detail);
|
|
202
246
|
},
|
|
203
247
|
{ once: true }
|
|
204
248
|
);
|
|
205
249
|
const apiAfterListener = window.__REACT_GRAB__;
|
|
206
|
-
if (apiAfterListener) {
|
|
250
|
+
if (isReactGrabApi(apiAfterListener)) {
|
|
207
251
|
attach(apiAfterListener);
|
|
208
252
|
}
|
|
209
253
|
};
|
package/dist/client.global.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
var ReactGrabGemini=(function(exports){'use strict';var
|
|
2
|
-
`))
|
|
1
|
+
var ReactGrabGemini=(function(exports){'use strict';var A=5e3,v="react-grab:agent-sessions",C=e=>{let t="",n="";for(let r of e.split(`
|
|
2
|
+
`))r.startsWith("event:")?t=r.slice(6).trim():r.startsWith("data:")&&(n=r.slice(5).trim());return {eventType:t,data:n}},T=async function*(e,t){let n=e.getReader(),r=new TextDecoder,o="",s=false,a=()=>{s=true,n.cancel().catch(()=>{});};t.addEventListener("abort",a);try{if(t.aborted)throw new DOMException("Aborted","AbortError");for(;;){let d=await n.read();if(s||t.aborted)throw new DOMException("Aborted","AbortError");let{done:i,value:l}=d;l&&(o+=r.decode(l,{stream:!0}));let c;for(;(c=o.indexOf(`
|
|
3
3
|
|
|
4
|
-
`))!==-1;){let{eventType:
|
|
5
|
-
exports.attachAgent=
|
|
4
|
+
`))!==-1;){let{eventType:f,data:p}=C(o.slice(0,c));if(o=o.slice(c+2),f==="done")return;if(f==="error")throw new Error(p||"Agent error");p&&(yield p);}if(i)break}}finally{t.removeEventListener("abort",a);try{n.releaseLock();}catch{}}},g=e=>typeof e=="object"&&e!==null,E=(e,t,n=v)=>{let r=e.getItem(n);if(!r)throw new Error("No sessions to resume");let o;try{o=JSON.parse(r);}catch{throw new Error("Failed to parse stored sessions")}if(!g(o))throw new Error("Invalid stored sessions");let s=o[t];if(!g(s))throw new Error(`Session ${t} not found`);let a=s.context;if(!g(a))throw new Error(`Session ${t} is invalid`);let d=a.content,i=a.prompt;if(typeof d!="string"||typeof i!="string")throw new Error(`Session ${t} is invalid`);let l=a.options,c=a.sessionId;return {content:d,prompt:i,options:l,sessionId:typeof c=="string"?c:void 0}},h=async function*(e,t,n){let r=Date.now(),o=t.sessionId,s=e.pollIntervalMs??100,a=`${e.serverUrl}${e.agentPath??"/agent"}`,d=()=>{if(!o)return;let i=e.abortPath?.(o)??`/abort/${o}`;fetch(`${e.serverUrl}${i}`,{method:"POST"}).catch(()=>{});};n.addEventListener("abort",d);try{let i=await fetch(a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t),signal:n});if(!i.ok)throw new Error(`Server error: ${i.status}`);if(!i.body)throw new Error("No response body");let l=T(i.body,n)[Symbol.asyncIterator](),c=!1,f=l.next(),p=null;for(;!c;){let S=await Promise.race([f.then(u=>({type:"status",iteratorResult:u})),new Promise(u=>setTimeout(()=>u({type:"timeout"}),s))]),m=(Date.now()-r)/1e3;if(S.type==="status"){let u=S.iteratorResult;c=u.done??!1,!c&&u.value&&(p=u.value,f=l.next());}p===e.completedStatus?yield `Completed in ${m.toFixed(1)}s`:p?yield `${p} ${m.toFixed(1)}s`:yield `Working\u2026 ${m.toFixed(1)}s`;}}finally{n.removeEventListener("abort",d);}},b=(e,t=A)=>{let n=null;return async()=>{let r=Date.now();if(n&&r-n.timestamp<t)return n.result;try{let o=await e();return n={result:o,timestamp:r},o}catch{return n={result:false,timestamp:r},false}}};var y="Completed successfully";var x=`http://localhost:${8567}`,w=e=>typeof e=="object"&&e!==null&&"setAgent"in e,_=(e={})=>{let{serverUrl:t=x,getOptions:n}=e,r=s=>({...n?.()??{},...s??{}}),o=b(async()=>(await fetch(`${t}/health`,{method:"GET"})).ok,A);return {send:async function*(s,a){let d={...s,options:r(s.options)};yield*h({serverUrl:t,completedStatus:y},d,a);},resume:async function*(s,a,d){let i=E(d,s),l={content:i.content,prompt:i.prompt,options:i.options,sessionId:i.sessionId??s},c={...l,options:r(l.options)};yield "Resuming...",yield*h({serverUrl:t,completedStatus:y},c,a);},supportsResume:true,supportsFollowUp:true,checkConnection:o,abort:async s=>{try{await fetch(`${t}/abort/${s}`,{method:"POST"});}catch{}},undo:async()=>{try{await fetch(`${t}/undo`,{method:"POST"});}catch{}}}},R=async()=>{if(typeof window>"u")return;let e=_(),t=o=>{o.setAgent({provider:e,storage:sessionStorage});},n=window.__REACT_GRAB__;if(w(n)){t(n);return}window.addEventListener("react-grab:init",o=>{o instanceof CustomEvent&&w(o.detail)&&t(o.detail);},{once:true});let r=window.__REACT_GRAB__;w(r)&&t(r);};R();
|
|
5
|
+
exports.attachAgent=R;exports.createGeminiAgentProvider=_;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*
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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(
|
|
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 @@ async function* streamFromServer(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
|
|
112
|
+
let isDone = false;
|
|
87
113
|
let pendingNext = iterator.next();
|
|
88
114
|
let lastStatus = null;
|
|
89
|
-
while (!
|
|
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" }),
|
|
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
|
-
|
|
103
|
-
if (!
|
|
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 ===
|
|
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`;
|
|
@@ -116,57 +142,74 @@ async function* streamFromServer(serverUrl, context, signal) {
|
|
|
116
142
|
} finally {
|
|
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 = 8567;
|
|
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 createGeminiAgentProvider = (providerOptions = {}) => {
|
|
121
170
|
const { serverUrl = DEFAULT_SERVER_URL, getOptions } = providerOptions;
|
|
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 mergedContext = {
|
|
130
182
|
...context,
|
|
131
183
|
options: mergeOptions(context.options)
|
|
132
184
|
};
|
|
133
|
-
yield*
|
|
185
|
+
yield* streamAgentStatusFromServer(
|
|
186
|
+
{ serverUrl, completedStatus: COMPLETED_STATUS },
|
|
187
|
+
mergedContext,
|
|
188
|
+
signal
|
|
189
|
+
);
|
|
134
190
|
},
|
|
135
191
|
resume: async function* (sessionId, signal, storage) {
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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 mergedContext = {
|
|
147
200
|
...context,
|
|
148
201
|
options: mergeOptions(context.options)
|
|
149
202
|
};
|
|
150
203
|
yield "Resuming...";
|
|
151
|
-
yield*
|
|
204
|
+
yield* streamAgentStatusFromServer(
|
|
205
|
+
{ serverUrl, completedStatus: COMPLETED_STATUS },
|
|
206
|
+
mergedContext,
|
|
207
|
+
signal
|
|
208
|
+
);
|
|
152
209
|
},
|
|
153
210
|
supportsResume: true,
|
|
154
211
|
supportsFollowUp: true,
|
|
155
|
-
checkConnection
|
|
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
|
abort: async (sessionId) => {
|
|
171
214
|
try {
|
|
172
215
|
await fetch(`${serverUrl}/abort/${sessionId}`, { method: "POST" });
|
|
@@ -184,24 +227,25 @@ var createGeminiAgentProvider = (providerOptions = {}) => {
|
|
|
184
227
|
var attachAgent = async () => {
|
|
185
228
|
if (typeof window === "undefined") return;
|
|
186
229
|
const provider = createGeminiAgentProvider();
|
|
187
|
-
const attach = (
|
|
188
|
-
|
|
230
|
+
const attach = (api) => {
|
|
231
|
+
api.setAgent({ provider, storage: sessionStorage });
|
|
189
232
|
};
|
|
190
|
-
const
|
|
191
|
-
if (
|
|
192
|
-
attach(
|
|
233
|
+
const existingApi = window.__REACT_GRAB__;
|
|
234
|
+
if (isReactGrabApi(existingApi)) {
|
|
235
|
+
attach(existingApi);
|
|
193
236
|
return;
|
|
194
237
|
}
|
|
195
238
|
window.addEventListener(
|
|
196
239
|
"react-grab:init",
|
|
197
240
|
(event) => {
|
|
198
|
-
|
|
199
|
-
|
|
241
|
+
if (!(event instanceof CustomEvent)) return;
|
|
242
|
+
if (!isReactGrabApi(event.detail)) return;
|
|
243
|
+
attach(event.detail);
|
|
200
244
|
},
|
|
201
245
|
{ once: true }
|
|
202
246
|
);
|
|
203
247
|
const apiAfterListener = window.__REACT_GRAB__;
|
|
204
|
-
if (apiAfterListener) {
|
|
248
|
+
if (isReactGrabApi(apiAfterListener)) {
|
|
205
249
|
attach(apiAfterListener);
|
|
206
250
|
}
|
|
207
251
|
};
|
package/dist/server.cjs
CHANGED
|
@@ -12129,9 +12129,11 @@ Check that the command is executable: chmod +x $(which ${commandName})`;
|
|
|
12129
12129
|
};
|
|
12130
12130
|
|
|
12131
12131
|
// src/server.ts
|
|
12132
|
-
var VERSION = "0.0.
|
|
12132
|
+
var VERSION = "0.0.89";
|
|
12133
12133
|
try {
|
|
12134
|
-
fetch(
|
|
12134
|
+
fetch(
|
|
12135
|
+
`https://www.react-grab.com/api/version?source=gemini&t=${Date.now()}`
|
|
12136
|
+
).catch(() => {
|
|
12135
12137
|
});
|
|
12136
12138
|
} catch {
|
|
12137
12139
|
}
|
|
@@ -12147,6 +12149,180 @@ var parseStreamLine = (line) => {
|
|
|
12147
12149
|
return null;
|
|
12148
12150
|
}
|
|
12149
12151
|
};
|
|
12152
|
+
var runAgent = async function* (prompt, options) {
|
|
12153
|
+
const geminiArgs = ["--output-format", "stream-json", "--yolo"];
|
|
12154
|
+
if (options?.model) {
|
|
12155
|
+
geminiArgs.push("--model", options.model);
|
|
12156
|
+
}
|
|
12157
|
+
if (options?.includeDirectories) {
|
|
12158
|
+
geminiArgs.push("--include-directories", options.includeDirectories);
|
|
12159
|
+
}
|
|
12160
|
+
geminiArgs.push(prompt);
|
|
12161
|
+
let geminiProcess;
|
|
12162
|
+
let stderrBuffer = "";
|
|
12163
|
+
try {
|
|
12164
|
+
yield { type: "status", content: "Thinking\u2026" };
|
|
12165
|
+
geminiProcess = execa("gemini", geminiArgs, {
|
|
12166
|
+
stdin: "pipe",
|
|
12167
|
+
stdout: "pipe",
|
|
12168
|
+
stderr: "pipe",
|
|
12169
|
+
env: { ...process.env },
|
|
12170
|
+
cwd: options?.cwd ?? process.env.REACT_GRAB_CWD ?? process.cwd()
|
|
12171
|
+
});
|
|
12172
|
+
if (options?.sessionId) {
|
|
12173
|
+
activeProcesses.set(options.sessionId, geminiProcess);
|
|
12174
|
+
}
|
|
12175
|
+
if (geminiProcess.stderr) {
|
|
12176
|
+
geminiProcess.stderr.on("data", (chunk) => {
|
|
12177
|
+
stderrBuffer += chunk.toString();
|
|
12178
|
+
});
|
|
12179
|
+
}
|
|
12180
|
+
const messageQueue = [];
|
|
12181
|
+
let resolveWait = null;
|
|
12182
|
+
let processEnded = false;
|
|
12183
|
+
let capturedSessionId;
|
|
12184
|
+
const enqueueMessage = (message) => {
|
|
12185
|
+
messageQueue.push(message);
|
|
12186
|
+
if (resolveWait) {
|
|
12187
|
+
resolveWait();
|
|
12188
|
+
resolveWait = null;
|
|
12189
|
+
}
|
|
12190
|
+
};
|
|
12191
|
+
const processLine = (line) => {
|
|
12192
|
+
const event = parseStreamLine(line);
|
|
12193
|
+
if (!event) return;
|
|
12194
|
+
if (!capturedSessionId && event.session_id) {
|
|
12195
|
+
capturedSessionId = event.session_id;
|
|
12196
|
+
}
|
|
12197
|
+
switch (event.type) {
|
|
12198
|
+
case "init":
|
|
12199
|
+
enqueueMessage({ type: "status", content: "Session started..." });
|
|
12200
|
+
break;
|
|
12201
|
+
case "message":
|
|
12202
|
+
if (event.role === "assistant" && event.content) {
|
|
12203
|
+
enqueueMessage({ type: "status", content: event.content });
|
|
12204
|
+
}
|
|
12205
|
+
break;
|
|
12206
|
+
case "tool_use":
|
|
12207
|
+
if (event.tool_name) {
|
|
12208
|
+
enqueueMessage({
|
|
12209
|
+
type: "status",
|
|
12210
|
+
content: `Using ${event.tool_name}...`
|
|
12211
|
+
});
|
|
12212
|
+
}
|
|
12213
|
+
break;
|
|
12214
|
+
case "tool_result":
|
|
12215
|
+
if (event.status === "error" && event.output) {
|
|
12216
|
+
enqueueMessage({
|
|
12217
|
+
type: "status",
|
|
12218
|
+
content: `Tool error: ${event.output}`
|
|
12219
|
+
});
|
|
12220
|
+
}
|
|
12221
|
+
break;
|
|
12222
|
+
case "error":
|
|
12223
|
+
if (event.content) {
|
|
12224
|
+
enqueueMessage({ type: "error", content: event.content });
|
|
12225
|
+
}
|
|
12226
|
+
break;
|
|
12227
|
+
case "result":
|
|
12228
|
+
if (event.status === "success") {
|
|
12229
|
+
enqueueMessage({ type: "status", content: COMPLETED_STATUS });
|
|
12230
|
+
} else if (event.status === "error") {
|
|
12231
|
+
enqueueMessage({ type: "error", content: "Task failed" });
|
|
12232
|
+
}
|
|
12233
|
+
break;
|
|
12234
|
+
}
|
|
12235
|
+
};
|
|
12236
|
+
let buffer = "";
|
|
12237
|
+
if (geminiProcess.stdout) {
|
|
12238
|
+
geminiProcess.stdout.on("data", (chunk) => {
|
|
12239
|
+
buffer += chunk.toString();
|
|
12240
|
+
let newlineIndex;
|
|
12241
|
+
while ((newlineIndex = buffer.indexOf("\n")) !== -1) {
|
|
12242
|
+
const line = buffer.slice(0, newlineIndex);
|
|
12243
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
12244
|
+
processLine(line);
|
|
12245
|
+
}
|
|
12246
|
+
});
|
|
12247
|
+
}
|
|
12248
|
+
const childProcess4 = geminiProcess;
|
|
12249
|
+
childProcess4.on("close", (code) => {
|
|
12250
|
+
if (options?.sessionId) {
|
|
12251
|
+
activeProcesses.delete(options.sessionId);
|
|
12252
|
+
}
|
|
12253
|
+
if (buffer.trim()) {
|
|
12254
|
+
processLine(buffer);
|
|
12255
|
+
}
|
|
12256
|
+
if (options?.sessionId && capturedSessionId) {
|
|
12257
|
+
geminiSessionMap.set(options.sessionId, capturedSessionId);
|
|
12258
|
+
}
|
|
12259
|
+
if (capturedSessionId) {
|
|
12260
|
+
lastGeminiSessionId = capturedSessionId;
|
|
12261
|
+
}
|
|
12262
|
+
processEnded = true;
|
|
12263
|
+
if (code !== 0 && !childProcess4.killed) {
|
|
12264
|
+
enqueueMessage({
|
|
12265
|
+
type: "error",
|
|
12266
|
+
content: `gemini exited with code ${code}`
|
|
12267
|
+
});
|
|
12268
|
+
}
|
|
12269
|
+
enqueueMessage({ type: "done", content: "" });
|
|
12270
|
+
if (resolveWait) {
|
|
12271
|
+
resolveWait();
|
|
12272
|
+
resolveWait = null;
|
|
12273
|
+
}
|
|
12274
|
+
});
|
|
12275
|
+
childProcess4.on("error", (error) => {
|
|
12276
|
+
if (options?.sessionId) {
|
|
12277
|
+
activeProcesses.delete(options.sessionId);
|
|
12278
|
+
}
|
|
12279
|
+
processEnded = true;
|
|
12280
|
+
const errorMessage = formatSpawnError(error, "gemini");
|
|
12281
|
+
const stderrContent = stderrBuffer.trim();
|
|
12282
|
+
const fullError = stderrContent ? `${errorMessage}
|
|
12283
|
+
|
|
12284
|
+
stderr:
|
|
12285
|
+
${stderrContent}` : errorMessage;
|
|
12286
|
+
enqueueMessage({ type: "error", content: fullError });
|
|
12287
|
+
enqueueMessage({ type: "done", content: "" });
|
|
12288
|
+
if (resolveWait) {
|
|
12289
|
+
resolveWait();
|
|
12290
|
+
resolveWait = null;
|
|
12291
|
+
}
|
|
12292
|
+
});
|
|
12293
|
+
while (true) {
|
|
12294
|
+
if (options?.signal?.aborted) {
|
|
12295
|
+
if (geminiProcess && !geminiProcess.killed) {
|
|
12296
|
+
geminiProcess.kill("SIGTERM");
|
|
12297
|
+
}
|
|
12298
|
+
return;
|
|
12299
|
+
}
|
|
12300
|
+
if (messageQueue.length > 0) {
|
|
12301
|
+
const message = messageQueue.shift();
|
|
12302
|
+
if (message.type === "done") {
|
|
12303
|
+
yield message;
|
|
12304
|
+
return;
|
|
12305
|
+
}
|
|
12306
|
+
yield message;
|
|
12307
|
+
} else if (processEnded) {
|
|
12308
|
+
return;
|
|
12309
|
+
} else {
|
|
12310
|
+
await new Promise((resolve) => {
|
|
12311
|
+
resolveWait = resolve;
|
|
12312
|
+
});
|
|
12313
|
+
}
|
|
12314
|
+
}
|
|
12315
|
+
} catch (error) {
|
|
12316
|
+
const errorMessage = error instanceof Error ? formatSpawnError(error, "gemini") : "Unknown error";
|
|
12317
|
+
const stderrContent = stderrBuffer.trim();
|
|
12318
|
+
const fullError = stderrContent ? `${errorMessage}
|
|
12319
|
+
|
|
12320
|
+
stderr:
|
|
12321
|
+
${stderrContent}` : errorMessage;
|
|
12322
|
+
yield { type: "error", content: fullError };
|
|
12323
|
+
yield { type: "done", content: "" };
|
|
12324
|
+
}
|
|
12325
|
+
};
|
|
12150
12326
|
var createServer = () => {
|
|
12151
12327
|
const app = new Hono2();
|
|
12152
12328
|
app.use("*", cors());
|
|
@@ -12159,146 +12335,18 @@ var createServer = () => {
|
|
|
12159
12335
|
|
|
12160
12336
|
${content}`;
|
|
12161
12337
|
return streamSSE(context, async (stream2) => {
|
|
12162
|
-
const
|
|
12163
|
-
|
|
12164
|
-
|
|
12165
|
-
}
|
|
12166
|
-
|
|
12167
|
-
|
|
12168
|
-
|
|
12169
|
-
|
|
12170
|
-
let geminiProcess;
|
|
12171
|
-
let stderrBuffer = "";
|
|
12172
|
-
try {
|
|
12173
|
-
await stream2.writeSSE({ data: "Thinking\u2026", event: "status" });
|
|
12174
|
-
geminiProcess = execa("gemini", geminiArgs, {
|
|
12175
|
-
stdin: "pipe",
|
|
12176
|
-
stdout: "pipe",
|
|
12177
|
-
stderr: "pipe",
|
|
12178
|
-
env: { ...process.env },
|
|
12179
|
-
cwd: process.env.REACT_GRAB_CWD ?? process.cwd()
|
|
12180
|
-
});
|
|
12181
|
-
if (sessionId) {
|
|
12182
|
-
activeProcesses.set(sessionId, geminiProcess);
|
|
12183
|
-
}
|
|
12184
|
-
if (geminiProcess.stderr) {
|
|
12185
|
-
geminiProcess.stderr.on("data", (chunk) => {
|
|
12186
|
-
stderrBuffer += chunk.toString();
|
|
12338
|
+
for await (const message of runAgent(userPrompt, {
|
|
12339
|
+
...options,
|
|
12340
|
+
sessionId
|
|
12341
|
+
})) {
|
|
12342
|
+
if (message.type === "error") {
|
|
12343
|
+
await stream2.writeSSE({
|
|
12344
|
+
data: `Error: ${message.content}`,
|
|
12345
|
+
event: "error"
|
|
12187
12346
|
});
|
|
12347
|
+
} else {
|
|
12348
|
+
await stream2.writeSSE({ data: message.content, event: message.type });
|
|
12188
12349
|
}
|
|
12189
|
-
let buffer = "";
|
|
12190
|
-
let capturedSessionId;
|
|
12191
|
-
const processLine = async (line) => {
|
|
12192
|
-
const event = parseStreamLine(line);
|
|
12193
|
-
if (!event) return;
|
|
12194
|
-
if (!capturedSessionId && event.session_id) {
|
|
12195
|
-
capturedSessionId = event.session_id;
|
|
12196
|
-
}
|
|
12197
|
-
switch (event.type) {
|
|
12198
|
-
case "init":
|
|
12199
|
-
await stream2.writeSSE({
|
|
12200
|
-
data: "Session started...",
|
|
12201
|
-
event: "status"
|
|
12202
|
-
});
|
|
12203
|
-
break;
|
|
12204
|
-
case "message":
|
|
12205
|
-
if (event.role === "assistant" && event.content) {
|
|
12206
|
-
await stream2.writeSSE({ data: event.content, event: "status" });
|
|
12207
|
-
}
|
|
12208
|
-
break;
|
|
12209
|
-
case "tool_use":
|
|
12210
|
-
if (event.tool_name) {
|
|
12211
|
-
await stream2.writeSSE({
|
|
12212
|
-
data: `Using ${event.tool_name}...`,
|
|
12213
|
-
event: "status"
|
|
12214
|
-
});
|
|
12215
|
-
}
|
|
12216
|
-
break;
|
|
12217
|
-
case "tool_result":
|
|
12218
|
-
if (event.status === "error" && event.output) {
|
|
12219
|
-
await stream2.writeSSE({
|
|
12220
|
-
data: `Tool error: ${event.output}`,
|
|
12221
|
-
event: "status"
|
|
12222
|
-
});
|
|
12223
|
-
}
|
|
12224
|
-
break;
|
|
12225
|
-
case "error":
|
|
12226
|
-
if (event.content) {
|
|
12227
|
-
await stream2.writeSSE({
|
|
12228
|
-
data: `Error: ${event.content}`,
|
|
12229
|
-
event: "error"
|
|
12230
|
-
});
|
|
12231
|
-
}
|
|
12232
|
-
break;
|
|
12233
|
-
case "result":
|
|
12234
|
-
if (event.status === "success") {
|
|
12235
|
-
await stream2.writeSSE({
|
|
12236
|
-
data: COMPLETED_STATUS,
|
|
12237
|
-
event: "status"
|
|
12238
|
-
});
|
|
12239
|
-
} else if (event.status === "error") {
|
|
12240
|
-
await stream2.writeSSE({
|
|
12241
|
-
data: "Task failed",
|
|
12242
|
-
event: "error"
|
|
12243
|
-
});
|
|
12244
|
-
}
|
|
12245
|
-
break;
|
|
12246
|
-
}
|
|
12247
|
-
};
|
|
12248
|
-
if (geminiProcess.stdout) {
|
|
12249
|
-
geminiProcess.stdout.on("data", async (chunk) => {
|
|
12250
|
-
buffer += chunk.toString();
|
|
12251
|
-
let newlineIndex;
|
|
12252
|
-
while ((newlineIndex = buffer.indexOf("\n")) !== -1) {
|
|
12253
|
-
const line = buffer.slice(0, newlineIndex);
|
|
12254
|
-
buffer = buffer.slice(newlineIndex + 1);
|
|
12255
|
-
await processLine(line);
|
|
12256
|
-
}
|
|
12257
|
-
});
|
|
12258
|
-
}
|
|
12259
|
-
if (geminiProcess) {
|
|
12260
|
-
const childProcess4 = geminiProcess;
|
|
12261
|
-
await new Promise((resolve, reject) => {
|
|
12262
|
-
childProcess4.on("close", (code) => {
|
|
12263
|
-
if (sessionId) {
|
|
12264
|
-
activeProcesses.delete(sessionId);
|
|
12265
|
-
}
|
|
12266
|
-
if (code === 0 || childProcess4.killed) {
|
|
12267
|
-
resolve();
|
|
12268
|
-
} else {
|
|
12269
|
-
reject(new Error(`gemini exited with code ${code}`));
|
|
12270
|
-
}
|
|
12271
|
-
});
|
|
12272
|
-
childProcess4.on("error", (error) => {
|
|
12273
|
-
if (sessionId) {
|
|
12274
|
-
activeProcesses.delete(sessionId);
|
|
12275
|
-
}
|
|
12276
|
-
reject(error);
|
|
12277
|
-
});
|
|
12278
|
-
});
|
|
12279
|
-
}
|
|
12280
|
-
if (buffer.trim()) {
|
|
12281
|
-
await processLine(buffer);
|
|
12282
|
-
}
|
|
12283
|
-
if (sessionId && capturedSessionId) {
|
|
12284
|
-
geminiSessionMap.set(sessionId, capturedSessionId);
|
|
12285
|
-
}
|
|
12286
|
-
if (capturedSessionId) {
|
|
12287
|
-
lastGeminiSessionId = capturedSessionId;
|
|
12288
|
-
}
|
|
12289
|
-
await stream2.writeSSE({ data: "", event: "done" });
|
|
12290
|
-
} catch (error) {
|
|
12291
|
-
const errorMessage = error instanceof Error ? formatSpawnError(error, "gemini") : "Unknown error";
|
|
12292
|
-
const stderrContent = stderrBuffer.trim();
|
|
12293
|
-
const fullError = stderrContent ? `${errorMessage}
|
|
12294
|
-
|
|
12295
|
-
stderr:
|
|
12296
|
-
${stderrContent}` : errorMessage;
|
|
12297
|
-
await stream2.writeSSE({
|
|
12298
|
-
data: `Error: ${fullError}`,
|
|
12299
|
-
event: "error"
|
|
12300
|
-
});
|
|
12301
|
-
await stream2.writeSSE({ data: "", event: "done" });
|
|
12302
12350
|
}
|
|
12303
12351
|
});
|
|
12304
12352
|
});
|
|
@@ -12357,4 +12405,5 @@ if ((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filen
|
|
|
12357
12405
|
}
|
|
12358
12406
|
|
|
12359
12407
|
exports.createServer = createServer;
|
|
12408
|
+
exports.runAgent = runAgent;
|
|
12360
12409
|
exports.startServer = startServer;
|
package/dist/server.d.cts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import * as hono_types from 'hono/types';
|
|
2
2
|
import { Hono } from 'hono';
|
|
3
|
+
import { AgentCoreOptions, AgentMessage } from '@react-grab/utils/server';
|
|
3
4
|
|
|
5
|
+
interface GeminiAgentOptions extends AgentCoreOptions {
|
|
6
|
+
model?: string;
|
|
7
|
+
includeDirectories?: string;
|
|
8
|
+
}
|
|
9
|
+
declare const runAgent: (prompt: string, options?: GeminiAgentOptions) => AsyncGenerator<AgentMessage>;
|
|
4
10
|
declare const createServer: () => Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
|
|
5
11
|
declare const startServer: (port?: number) => Promise<void>;
|
|
6
12
|
|
|
7
|
-
export { createServer, startServer };
|
|
13
|
+
export { type GeminiAgentOptions, createServer, runAgent, startServer };
|
package/dist/server.d.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import * as hono_types from 'hono/types';
|
|
2
2
|
import { Hono } from 'hono';
|
|
3
|
+
import { AgentCoreOptions, AgentMessage } from '@react-grab/utils/server';
|
|
3
4
|
|
|
5
|
+
interface GeminiAgentOptions extends AgentCoreOptions {
|
|
6
|
+
model?: string;
|
|
7
|
+
includeDirectories?: string;
|
|
8
|
+
}
|
|
9
|
+
declare const runAgent: (prompt: string, options?: GeminiAgentOptions) => AsyncGenerator<AgentMessage>;
|
|
4
10
|
declare const createServer: () => Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
|
|
5
11
|
declare const startServer: (port?: number) => Promise<void>;
|
|
6
12
|
|
|
7
|
-
export { createServer, startServer };
|
|
13
|
+
export { type GeminiAgentOptions, createServer, runAgent, startServer };
|
package/dist/server.js
CHANGED
|
@@ -12117,9 +12117,11 @@ Check that the command is executable: chmod +x $(which ${commandName})`;
|
|
|
12117
12117
|
};
|
|
12118
12118
|
|
|
12119
12119
|
// src/server.ts
|
|
12120
|
-
var VERSION = "0.0.
|
|
12120
|
+
var VERSION = "0.0.89";
|
|
12121
12121
|
try {
|
|
12122
|
-
fetch(
|
|
12122
|
+
fetch(
|
|
12123
|
+
`https://www.react-grab.com/api/version?source=gemini&t=${Date.now()}`
|
|
12124
|
+
).catch(() => {
|
|
12123
12125
|
});
|
|
12124
12126
|
} catch {
|
|
12125
12127
|
}
|
|
@@ -12135,6 +12137,180 @@ var parseStreamLine = (line) => {
|
|
|
12135
12137
|
return null;
|
|
12136
12138
|
}
|
|
12137
12139
|
};
|
|
12140
|
+
var runAgent = async function* (prompt, options) {
|
|
12141
|
+
const geminiArgs = ["--output-format", "stream-json", "--yolo"];
|
|
12142
|
+
if (options?.model) {
|
|
12143
|
+
geminiArgs.push("--model", options.model);
|
|
12144
|
+
}
|
|
12145
|
+
if (options?.includeDirectories) {
|
|
12146
|
+
geminiArgs.push("--include-directories", options.includeDirectories);
|
|
12147
|
+
}
|
|
12148
|
+
geminiArgs.push(prompt);
|
|
12149
|
+
let geminiProcess;
|
|
12150
|
+
let stderrBuffer = "";
|
|
12151
|
+
try {
|
|
12152
|
+
yield { type: "status", content: "Thinking\u2026" };
|
|
12153
|
+
geminiProcess = execa("gemini", geminiArgs, {
|
|
12154
|
+
stdin: "pipe",
|
|
12155
|
+
stdout: "pipe",
|
|
12156
|
+
stderr: "pipe",
|
|
12157
|
+
env: { ...process.env },
|
|
12158
|
+
cwd: options?.cwd ?? process.env.REACT_GRAB_CWD ?? process.cwd()
|
|
12159
|
+
});
|
|
12160
|
+
if (options?.sessionId) {
|
|
12161
|
+
activeProcesses.set(options.sessionId, geminiProcess);
|
|
12162
|
+
}
|
|
12163
|
+
if (geminiProcess.stderr) {
|
|
12164
|
+
geminiProcess.stderr.on("data", (chunk) => {
|
|
12165
|
+
stderrBuffer += chunk.toString();
|
|
12166
|
+
});
|
|
12167
|
+
}
|
|
12168
|
+
const messageQueue = [];
|
|
12169
|
+
let resolveWait = null;
|
|
12170
|
+
let processEnded = false;
|
|
12171
|
+
let capturedSessionId;
|
|
12172
|
+
const enqueueMessage = (message) => {
|
|
12173
|
+
messageQueue.push(message);
|
|
12174
|
+
if (resolveWait) {
|
|
12175
|
+
resolveWait();
|
|
12176
|
+
resolveWait = null;
|
|
12177
|
+
}
|
|
12178
|
+
};
|
|
12179
|
+
const processLine = (line) => {
|
|
12180
|
+
const event = parseStreamLine(line);
|
|
12181
|
+
if (!event) return;
|
|
12182
|
+
if (!capturedSessionId && event.session_id) {
|
|
12183
|
+
capturedSessionId = event.session_id;
|
|
12184
|
+
}
|
|
12185
|
+
switch (event.type) {
|
|
12186
|
+
case "init":
|
|
12187
|
+
enqueueMessage({ type: "status", content: "Session started..." });
|
|
12188
|
+
break;
|
|
12189
|
+
case "message":
|
|
12190
|
+
if (event.role === "assistant" && event.content) {
|
|
12191
|
+
enqueueMessage({ type: "status", content: event.content });
|
|
12192
|
+
}
|
|
12193
|
+
break;
|
|
12194
|
+
case "tool_use":
|
|
12195
|
+
if (event.tool_name) {
|
|
12196
|
+
enqueueMessage({
|
|
12197
|
+
type: "status",
|
|
12198
|
+
content: `Using ${event.tool_name}...`
|
|
12199
|
+
});
|
|
12200
|
+
}
|
|
12201
|
+
break;
|
|
12202
|
+
case "tool_result":
|
|
12203
|
+
if (event.status === "error" && event.output) {
|
|
12204
|
+
enqueueMessage({
|
|
12205
|
+
type: "status",
|
|
12206
|
+
content: `Tool error: ${event.output}`
|
|
12207
|
+
});
|
|
12208
|
+
}
|
|
12209
|
+
break;
|
|
12210
|
+
case "error":
|
|
12211
|
+
if (event.content) {
|
|
12212
|
+
enqueueMessage({ type: "error", content: event.content });
|
|
12213
|
+
}
|
|
12214
|
+
break;
|
|
12215
|
+
case "result":
|
|
12216
|
+
if (event.status === "success") {
|
|
12217
|
+
enqueueMessage({ type: "status", content: COMPLETED_STATUS });
|
|
12218
|
+
} else if (event.status === "error") {
|
|
12219
|
+
enqueueMessage({ type: "error", content: "Task failed" });
|
|
12220
|
+
}
|
|
12221
|
+
break;
|
|
12222
|
+
}
|
|
12223
|
+
};
|
|
12224
|
+
let buffer = "";
|
|
12225
|
+
if (geminiProcess.stdout) {
|
|
12226
|
+
geminiProcess.stdout.on("data", (chunk) => {
|
|
12227
|
+
buffer += chunk.toString();
|
|
12228
|
+
let newlineIndex;
|
|
12229
|
+
while ((newlineIndex = buffer.indexOf("\n")) !== -1) {
|
|
12230
|
+
const line = buffer.slice(0, newlineIndex);
|
|
12231
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
12232
|
+
processLine(line);
|
|
12233
|
+
}
|
|
12234
|
+
});
|
|
12235
|
+
}
|
|
12236
|
+
const childProcess4 = geminiProcess;
|
|
12237
|
+
childProcess4.on("close", (code) => {
|
|
12238
|
+
if (options?.sessionId) {
|
|
12239
|
+
activeProcesses.delete(options.sessionId);
|
|
12240
|
+
}
|
|
12241
|
+
if (buffer.trim()) {
|
|
12242
|
+
processLine(buffer);
|
|
12243
|
+
}
|
|
12244
|
+
if (options?.sessionId && capturedSessionId) {
|
|
12245
|
+
geminiSessionMap.set(options.sessionId, capturedSessionId);
|
|
12246
|
+
}
|
|
12247
|
+
if (capturedSessionId) {
|
|
12248
|
+
lastGeminiSessionId = capturedSessionId;
|
|
12249
|
+
}
|
|
12250
|
+
processEnded = true;
|
|
12251
|
+
if (code !== 0 && !childProcess4.killed) {
|
|
12252
|
+
enqueueMessage({
|
|
12253
|
+
type: "error",
|
|
12254
|
+
content: `gemini exited with code ${code}`
|
|
12255
|
+
});
|
|
12256
|
+
}
|
|
12257
|
+
enqueueMessage({ type: "done", content: "" });
|
|
12258
|
+
if (resolveWait) {
|
|
12259
|
+
resolveWait();
|
|
12260
|
+
resolveWait = null;
|
|
12261
|
+
}
|
|
12262
|
+
});
|
|
12263
|
+
childProcess4.on("error", (error) => {
|
|
12264
|
+
if (options?.sessionId) {
|
|
12265
|
+
activeProcesses.delete(options.sessionId);
|
|
12266
|
+
}
|
|
12267
|
+
processEnded = true;
|
|
12268
|
+
const errorMessage = formatSpawnError(error, "gemini");
|
|
12269
|
+
const stderrContent = stderrBuffer.trim();
|
|
12270
|
+
const fullError = stderrContent ? `${errorMessage}
|
|
12271
|
+
|
|
12272
|
+
stderr:
|
|
12273
|
+
${stderrContent}` : errorMessage;
|
|
12274
|
+
enqueueMessage({ type: "error", content: fullError });
|
|
12275
|
+
enqueueMessage({ type: "done", content: "" });
|
|
12276
|
+
if (resolveWait) {
|
|
12277
|
+
resolveWait();
|
|
12278
|
+
resolveWait = null;
|
|
12279
|
+
}
|
|
12280
|
+
});
|
|
12281
|
+
while (true) {
|
|
12282
|
+
if (options?.signal?.aborted) {
|
|
12283
|
+
if (geminiProcess && !geminiProcess.killed) {
|
|
12284
|
+
geminiProcess.kill("SIGTERM");
|
|
12285
|
+
}
|
|
12286
|
+
return;
|
|
12287
|
+
}
|
|
12288
|
+
if (messageQueue.length > 0) {
|
|
12289
|
+
const message = messageQueue.shift();
|
|
12290
|
+
if (message.type === "done") {
|
|
12291
|
+
yield message;
|
|
12292
|
+
return;
|
|
12293
|
+
}
|
|
12294
|
+
yield message;
|
|
12295
|
+
} else if (processEnded) {
|
|
12296
|
+
return;
|
|
12297
|
+
} else {
|
|
12298
|
+
await new Promise((resolve) => {
|
|
12299
|
+
resolveWait = resolve;
|
|
12300
|
+
});
|
|
12301
|
+
}
|
|
12302
|
+
}
|
|
12303
|
+
} catch (error) {
|
|
12304
|
+
const errorMessage = error instanceof Error ? formatSpawnError(error, "gemini") : "Unknown error";
|
|
12305
|
+
const stderrContent = stderrBuffer.trim();
|
|
12306
|
+
const fullError = stderrContent ? `${errorMessage}
|
|
12307
|
+
|
|
12308
|
+
stderr:
|
|
12309
|
+
${stderrContent}` : errorMessage;
|
|
12310
|
+
yield { type: "error", content: fullError };
|
|
12311
|
+
yield { type: "done", content: "" };
|
|
12312
|
+
}
|
|
12313
|
+
};
|
|
12138
12314
|
var createServer = () => {
|
|
12139
12315
|
const app = new Hono2();
|
|
12140
12316
|
app.use("*", cors());
|
|
@@ -12147,146 +12323,18 @@ var createServer = () => {
|
|
|
12147
12323
|
|
|
12148
12324
|
${content}`;
|
|
12149
12325
|
return streamSSE(context, async (stream2) => {
|
|
12150
|
-
const
|
|
12151
|
-
|
|
12152
|
-
|
|
12153
|
-
}
|
|
12154
|
-
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
|
|
12158
|
-
let geminiProcess;
|
|
12159
|
-
let stderrBuffer = "";
|
|
12160
|
-
try {
|
|
12161
|
-
await stream2.writeSSE({ data: "Thinking\u2026", event: "status" });
|
|
12162
|
-
geminiProcess = execa("gemini", geminiArgs, {
|
|
12163
|
-
stdin: "pipe",
|
|
12164
|
-
stdout: "pipe",
|
|
12165
|
-
stderr: "pipe",
|
|
12166
|
-
env: { ...process.env },
|
|
12167
|
-
cwd: process.env.REACT_GRAB_CWD ?? process.cwd()
|
|
12168
|
-
});
|
|
12169
|
-
if (sessionId) {
|
|
12170
|
-
activeProcesses.set(sessionId, geminiProcess);
|
|
12171
|
-
}
|
|
12172
|
-
if (geminiProcess.stderr) {
|
|
12173
|
-
geminiProcess.stderr.on("data", (chunk) => {
|
|
12174
|
-
stderrBuffer += chunk.toString();
|
|
12326
|
+
for await (const message of runAgent(userPrompt, {
|
|
12327
|
+
...options,
|
|
12328
|
+
sessionId
|
|
12329
|
+
})) {
|
|
12330
|
+
if (message.type === "error") {
|
|
12331
|
+
await stream2.writeSSE({
|
|
12332
|
+
data: `Error: ${message.content}`,
|
|
12333
|
+
event: "error"
|
|
12175
12334
|
});
|
|
12335
|
+
} else {
|
|
12336
|
+
await stream2.writeSSE({ data: message.content, event: message.type });
|
|
12176
12337
|
}
|
|
12177
|
-
let buffer = "";
|
|
12178
|
-
let capturedSessionId;
|
|
12179
|
-
const processLine = async (line) => {
|
|
12180
|
-
const event = parseStreamLine(line);
|
|
12181
|
-
if (!event) return;
|
|
12182
|
-
if (!capturedSessionId && event.session_id) {
|
|
12183
|
-
capturedSessionId = event.session_id;
|
|
12184
|
-
}
|
|
12185
|
-
switch (event.type) {
|
|
12186
|
-
case "init":
|
|
12187
|
-
await stream2.writeSSE({
|
|
12188
|
-
data: "Session started...",
|
|
12189
|
-
event: "status"
|
|
12190
|
-
});
|
|
12191
|
-
break;
|
|
12192
|
-
case "message":
|
|
12193
|
-
if (event.role === "assistant" && event.content) {
|
|
12194
|
-
await stream2.writeSSE({ data: event.content, event: "status" });
|
|
12195
|
-
}
|
|
12196
|
-
break;
|
|
12197
|
-
case "tool_use":
|
|
12198
|
-
if (event.tool_name) {
|
|
12199
|
-
await stream2.writeSSE({
|
|
12200
|
-
data: `Using ${event.tool_name}...`,
|
|
12201
|
-
event: "status"
|
|
12202
|
-
});
|
|
12203
|
-
}
|
|
12204
|
-
break;
|
|
12205
|
-
case "tool_result":
|
|
12206
|
-
if (event.status === "error" && event.output) {
|
|
12207
|
-
await stream2.writeSSE({
|
|
12208
|
-
data: `Tool error: ${event.output}`,
|
|
12209
|
-
event: "status"
|
|
12210
|
-
});
|
|
12211
|
-
}
|
|
12212
|
-
break;
|
|
12213
|
-
case "error":
|
|
12214
|
-
if (event.content) {
|
|
12215
|
-
await stream2.writeSSE({
|
|
12216
|
-
data: `Error: ${event.content}`,
|
|
12217
|
-
event: "error"
|
|
12218
|
-
});
|
|
12219
|
-
}
|
|
12220
|
-
break;
|
|
12221
|
-
case "result":
|
|
12222
|
-
if (event.status === "success") {
|
|
12223
|
-
await stream2.writeSSE({
|
|
12224
|
-
data: COMPLETED_STATUS,
|
|
12225
|
-
event: "status"
|
|
12226
|
-
});
|
|
12227
|
-
} else if (event.status === "error") {
|
|
12228
|
-
await stream2.writeSSE({
|
|
12229
|
-
data: "Task failed",
|
|
12230
|
-
event: "error"
|
|
12231
|
-
});
|
|
12232
|
-
}
|
|
12233
|
-
break;
|
|
12234
|
-
}
|
|
12235
|
-
};
|
|
12236
|
-
if (geminiProcess.stdout) {
|
|
12237
|
-
geminiProcess.stdout.on("data", async (chunk) => {
|
|
12238
|
-
buffer += chunk.toString();
|
|
12239
|
-
let newlineIndex;
|
|
12240
|
-
while ((newlineIndex = buffer.indexOf("\n")) !== -1) {
|
|
12241
|
-
const line = buffer.slice(0, newlineIndex);
|
|
12242
|
-
buffer = buffer.slice(newlineIndex + 1);
|
|
12243
|
-
await processLine(line);
|
|
12244
|
-
}
|
|
12245
|
-
});
|
|
12246
|
-
}
|
|
12247
|
-
if (geminiProcess) {
|
|
12248
|
-
const childProcess4 = geminiProcess;
|
|
12249
|
-
await new Promise((resolve, reject) => {
|
|
12250
|
-
childProcess4.on("close", (code) => {
|
|
12251
|
-
if (sessionId) {
|
|
12252
|
-
activeProcesses.delete(sessionId);
|
|
12253
|
-
}
|
|
12254
|
-
if (code === 0 || childProcess4.killed) {
|
|
12255
|
-
resolve();
|
|
12256
|
-
} else {
|
|
12257
|
-
reject(new Error(`gemini exited with code ${code}`));
|
|
12258
|
-
}
|
|
12259
|
-
});
|
|
12260
|
-
childProcess4.on("error", (error) => {
|
|
12261
|
-
if (sessionId) {
|
|
12262
|
-
activeProcesses.delete(sessionId);
|
|
12263
|
-
}
|
|
12264
|
-
reject(error);
|
|
12265
|
-
});
|
|
12266
|
-
});
|
|
12267
|
-
}
|
|
12268
|
-
if (buffer.trim()) {
|
|
12269
|
-
await processLine(buffer);
|
|
12270
|
-
}
|
|
12271
|
-
if (sessionId && capturedSessionId) {
|
|
12272
|
-
geminiSessionMap.set(sessionId, capturedSessionId);
|
|
12273
|
-
}
|
|
12274
|
-
if (capturedSessionId) {
|
|
12275
|
-
lastGeminiSessionId = capturedSessionId;
|
|
12276
|
-
}
|
|
12277
|
-
await stream2.writeSSE({ data: "", event: "done" });
|
|
12278
|
-
} catch (error) {
|
|
12279
|
-
const errorMessage = error instanceof Error ? formatSpawnError(error, "gemini") : "Unknown error";
|
|
12280
|
-
const stderrContent = stderrBuffer.trim();
|
|
12281
|
-
const fullError = stderrContent ? `${errorMessage}
|
|
12282
|
-
|
|
12283
|
-
stderr:
|
|
12284
|
-
${stderrContent}` : errorMessage;
|
|
12285
|
-
await stream2.writeSSE({
|
|
12286
|
-
data: `Error: ${fullError}`,
|
|
12287
|
-
event: "error"
|
|
12288
|
-
});
|
|
12289
|
-
await stream2.writeSSE({ data: "", event: "done" });
|
|
12290
12338
|
}
|
|
12291
12339
|
});
|
|
12292
12340
|
});
|
|
@@ -12344,4 +12392,4 @@ if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
|
12344
12392
|
startServer(DEFAULT_PORT).catch(console.error);
|
|
12345
12393
|
}
|
|
12346
12394
|
|
|
12347
|
-
export { createServer, startServer };
|
|
12395
|
+
export { createServer, runAgent, startServer };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-grab/gemini",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.89",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"react-grab-gemini": "./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.
|
|
29
|
+
"@react-grab/utils": "0.0.89"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@hono/node-server": "^1.19.6",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"fkill": "^9.0.0",
|
|
35
35
|
"hono": "^4.0.0",
|
|
36
36
|
"picocolors": "^1.1.1",
|
|
37
|
-
"react-grab": "0.0.
|
|
37
|
+
"react-grab": "0.0.89"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"dev": "tsup --watch",
|