glitool 2.0.3 → 2.0.4

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/agent.js CHANGED
@@ -19,8 +19,8 @@ import { runPlanningAgent } from "./agents/planningAgent.js";
19
19
  import { runDebugger } from "./agents/debugger.js";
20
20
  import { runRefactorer } from "./agents/refactorer.js";
21
21
  import { runGitAgent } from "./agents/git-agent.js";
22
- import { ToolMessage } from "@langchain/core/messages";
23
22
  import { makeLlm, startNewRequest } from './llm/factory.js';
23
+ import { emit } from './monitor.js';
24
24
  const __filename = fileURLToPath(import.meta.url);
25
25
  const __dirname = dirname(__filename);
26
26
  loadEnv({ path: join(os.homedir(), '.glitool', '.env') });
@@ -195,7 +195,9 @@ function extractTarget(args) {
195
195
  }
196
196
  export async function chat(userInput, onToolCall, onStatus, onToken, onEscalation, onUsage, onStageEvent) {
197
197
  startNewRequest();
198
+ emit('user_prompt', { text: userInput });
198
199
  const decision = await route(userInput, sessionMessages.slice(-6));
200
+ emit('router', { domain: decision.domain, tier: decision.tier, model: decision.recommendedModel, reason: decision.reason });
199
201
  logRouting(userInput, decision);
200
202
  const cleanedInput = decision.source === 'explicit' ? stripExplicitPrefix(userInput) : userInput;
201
203
  sessionMessages.push(new HumanMessage(cleanedInput));
@@ -206,6 +208,7 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
206
208
  return shortcut;
207
209
  }
208
210
  if (decision.domain === 'planning') {
211
+ emit('agent', { name: 'planning' });
209
212
  onStatus?.('Planning...');
210
213
  const result = await runPlanningAgent(cleanedInput, (inputTokens, outputTokens) => {
211
214
  onUsage?.(inputTokens + outputTokens, estimateCost('gpt-5.4', inputTokens, outputTokens));
@@ -215,6 +218,7 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
215
218
  return result;
216
219
  }
217
220
  if (decision.domain === 'review') {
221
+ emit('agent', { name: 'reviewer' });
218
222
  onStageEvent?.({ type: 'stage_start', stage: 'reviewer' });
219
223
  const result = await runReviewer(cleanedInput, (name, args) => {
220
224
  onStageEvent?.({ type: 'tool', stage: 'reviewer', tool: name, target: extractTarget(args) });
@@ -226,6 +230,7 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
226
230
  return result;
227
231
  }
228
232
  if (decision.domain === 'debugging') {
233
+ emit('agent', { name: 'debugger' });
229
234
  onStageEvent?.({ type: 'stage_start', stage: 'debugger' });
230
235
  const result = await runDebugger(cleanedInput, (name, args) => {
231
236
  onStageEvent?.({ type: 'tool', stage: 'debugger', tool: name, target: extractTarget(args) });
@@ -237,6 +242,7 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
237
242
  return result;
238
243
  }
239
244
  if (decision.domain === 'refactoring') {
245
+ emit('agent', { name: 'refactorer' });
240
246
  onStageEvent?.({ type: 'stage_start', stage: 'refactorer' });
241
247
  const result = await runRefactorer(cleanedInput, (name, args) => {
242
248
  onStageEvent?.({ type: 'tool', stage: 'refactorer', tool: name, target: extractTarget(args) });
@@ -248,6 +254,7 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
248
254
  return result;
249
255
  }
250
256
  if (decision.domain === 'git') {
257
+ emit('agent', { name: 'git' });
251
258
  onStageEvent?.({ type: 'stage_start', stage: 'git_agent' });
252
259
  const result = await runGitAgent(cleanedInput, (name, args) => {
253
260
  onStageEvent?.({ type: 'tool', stage: 'git_agent', tool: name, target: extractTarget(args) });
@@ -259,6 +266,7 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
259
266
  return result;
260
267
  }
261
268
  if (decision.domain === 'coding') {
269
+ emit('agent', { name: 'coder' });
262
270
  const graphResult = await runAgentGraph(cleanedInput, buildSystemPrompt(), onToolCall, onStatus ?? (() => { }), decision, onStageEvent // ← add this
263
271
  );
264
272
  if (graphResult.escalated && onEscalation) {
@@ -275,6 +283,7 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
275
283
  return graphResult.finalOutput;
276
284
  }
277
285
  }
286
+ emit('agent', { name: 'chat' });
278
287
  const simpleAgent = createReactAgent({
279
288
  llm: createLlm(decision.recommendedModel),
280
289
  tools,
@@ -307,12 +316,14 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
307
316
  }
308
317
  if (event === 'on_tool_start') {
309
318
  onToolCall(eventName, data.input);
319
+ emit('tool_call', { name: eventName, input: data.input });
310
320
  }
311
321
  if (event === 'on_chat_model_end') {
312
322
  const usage = data.output?.usage_metadata;
313
323
  if (usage) {
314
324
  totalInputTokens += usage.input_tokens ?? 0;
315
325
  totalOutputTokens += usage.output_tokens ?? 0;
326
+ emit('llm_call', { tokens_in: usage.input_tokens ?? 0, tokens_out: usage.output_tokens ?? 0 });
316
327
  }
317
328
  if (!finalResponse) {
318
329
  const output = data.output;
@@ -331,5 +342,7 @@ export async function chat(userInput, onToolCall, onStatus, onToken, onEscalatio
331
342
  onUsage(totalInputTokens + totalOutputTokens, estimateCost(model, totalInputTokens, totalOutputTokens));
332
343
  }
333
344
  saveSession(sessionMessages);
345
+ emit('response', { text: finalResponse });
346
+ emit('done', { total_tokens: totalInputTokens + totalOutputTokens });
334
347
  return finalResponse;
335
348
  }
@@ -1,7 +1,9 @@
1
1
  import { ChatOpenAI } from '@langchain/openai';
2
2
  import { randomUUID } from 'crypto';
3
3
  import { getAuthToken, getOrCreateAnonId } from '../auth.js';
4
- const BACKEND_URL = process.env.GLITOOL_BACKEND ?? 'https://api.glit.in';
4
+ function backendUrl() {
5
+ return process.env.GLITOOL_BACKEND ?? 'https://api.glit.in';
6
+ }
5
7
  let currentRequestId = null;
6
8
  export function startNewRequest() {
7
9
  currentRequestId = randomUUID();
@@ -12,15 +14,16 @@ function requestIdHeader() {
12
14
  }
13
15
  export function makeLlm(model, extras = {}) {
14
16
  if (process.env.OPENAI_API_KEY) {
15
- return new ChatOpenAI({ model, apiKey: process.env.OPENAI_API_KEY, ...extras });
17
+ return new ChatOpenAI({ model, apiKey: process.env.OPENAI_API_KEY, streaming: true, ...extras });
16
18
  }
17
19
  const token = getAuthToken();
18
20
  if (token) {
19
21
  return new ChatOpenAI({
20
22
  model,
21
23
  apiKey: token,
24
+ streaming: true,
22
25
  configuration: {
23
- baseURL: `${BACKEND_URL}/v1`,
26
+ baseURL: `${backendUrl()}/v1`,
24
27
  defaultHeaders: requestIdHeader(),
25
28
  },
26
29
  ...extras,
@@ -29,8 +32,9 @@ export function makeLlm(model, extras = {}) {
29
32
  return new ChatOpenAI({
30
33
  model,
31
34
  apiKey: 'anon',
35
+ streaming: true,
32
36
  configuration: {
33
- baseURL: `${BACKEND_URL}/v1`,
37
+ baseURL: `${backendUrl()}/v1`,
34
38
  defaultHeaders: { 'X-Anon-ID': getOrCreateAnonId(), ...requestIdHeader() },
35
39
  },
36
40
  ...extras,
@@ -46,7 +50,7 @@ export function makeInternalLlm(model, extras = {}) {
46
50
  model,
47
51
  apiKey: token ?? 'anon',
48
52
  configuration: {
49
- baseURL: `${BACKEND_URL}/v1`,
53
+ baseURL: `${backendUrl()}/v1`,
50
54
  defaultHeaders: {
51
55
  ...anonHeaders,
52
56
  'X-Glitool-Internal': 'true',
@@ -0,0 +1,9 @@
1
+ export function emit(type, data = {}) {
2
+ if (!process.env.GLITOOL_DEV_MONITOR)
3
+ return;
4
+ fetch('http://localhost:4000/event', {
5
+ method: 'POST',
6
+ headers: { 'Content-Type': 'application/json' },
7
+ body: JSON.stringify({ type, ...data, t: new Date().toISOString() }),
8
+ }).catch(() => { });
9
+ }
@@ -28,6 +28,16 @@ function formatCost(c) {
28
28
  export const StatusBar = ({ state, detail, tier, anonLeft, model, tokens, cost }) => {
29
29
  const dotColor = STATE_COLOR[state];
30
30
  const stateLabel = STATE_LABEL[state];
31
+ // Animate dot when working
32
+ const SPINNER_FRAMES = ['◐', '◓', '◑', '◒'];
33
+ const [frame, setFrame] = React.useState(0);
34
+ React.useEffect(() => {
35
+ if (state !== 'working')
36
+ return;
37
+ const id = setInterval(() => setFrame(f => (f + 1) % SPINNER_FRAMES.length), 150);
38
+ return () => clearInterval(id);
39
+ }, [state]);
40
+ const dotChar = state === 'working' ? SPINNER_FRAMES[frame] : symbols.statusDot;
31
41
  const leftParts = [stateLabel];
32
42
  if (detail)
33
43
  leftParts.push(detail);
@@ -40,5 +50,5 @@ export const StatusBar = ({ state, detail, tier, anonLeft, model, tokens, cost }
40
50
  }
41
51
  rightParts.push(model);
42
52
  rightParts.push(formatTokens(tokens));
43
- return (_jsxs(Box, { borderStyle: "single", borderColor: colors.line, paddingX: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { color: dotColor, children: symbols.statusDot }), _jsx(Text, { color: colors.ink2, children: leftParts.join(' . ') })] }), _jsx(Box, { children: _jsx(Text, { color: colors.muted, children: rightParts.join(' · ') }) })] }));
53
+ return (_jsxs(Box, { borderStyle: "single", borderColor: colors.line, paddingX: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { color: dotColor, children: dotChar }), _jsxs(Text, { color: colors.ink2, children: [" ", leftParts.join(' . ')] })] }), _jsx(Box, { children: _jsx(Text, { color: colors.muted, children: rightParts.join(' · ') }) })] }));
44
54
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glitool",
3
- "version": "2.0.3",
3
+ "version": "2.0.4",
4
4
  "description": "AI coding assistant for your terminal",
5
5
  "main": "index.js",
6
6
  "type": "module",