@axhub/genie 0.2.5 → 0.2.7

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.
Files changed (139) hide show
  1. package/dist/assets/App-BWSqiXAT.js +220 -0
  2. package/dist/assets/App-DrlLKa8f.css +1 -0
  3. package/dist/assets/ReviewApp-nz3mbArg.js +1 -0
  4. package/dist/assets/{_basePickBy-CFRQvihx.js → _basePickBy-C19AekOu.js} +1 -1
  5. package/dist/assets/{_baseUniq-Dhh8nCvs.js → _baseUniq-JsnevLw_.js} +1 -1
  6. package/dist/assets/{arc-DQ0v3dU4.js → arc-BLpcuBlf.js} +1 -1
  7. package/dist/assets/architectureDiagram-2XIMDMQ5-CarjBOOv.js +36 -0
  8. package/dist/assets/{blockDiagram-WCTKOSBZ-Bbxhj5KC.js → blockDiagram-WCTKOSBZ-DQBLwsUS.js} +3 -3
  9. package/dist/assets/c4Diagram-IC4MRINW-CGobwBIj.js +10 -0
  10. package/dist/assets/channel-DkFNxV_H.js +1 -0
  11. package/dist/assets/{chunk-4BX2VUAB-DlvtrM0q.js → chunk-4BX2VUAB-De63kbgc.js} +1 -1
  12. package/dist/assets/{chunk-55IACEB6-DJUSHyTa.js → chunk-55IACEB6-DtTDDdM9.js} +1 -1
  13. package/dist/assets/{chunk-FMBD7UC4-C6Ch-htf.js → chunk-FMBD7UC4-DHuwd8tw.js} +1 -1
  14. package/dist/assets/{chunk-JSJVCQXG-DzQIht58.js → chunk-JSJVCQXG-BgytFtmO.js} +1 -1
  15. package/dist/assets/{chunk-KX2RTZJC-C05jARMH.js → chunk-KX2RTZJC-nZdp86aN.js} +1 -1
  16. package/dist/assets/chunk-NQ4KR5QH-CMH6EDP2.js +220 -0
  17. package/dist/assets/{chunk-QZHKN3VN-jxti9HTX.js → chunk-QZHKN3VN-DvUQ3mnO.js} +1 -1
  18. package/dist/assets/chunk-WL4C6EOR-Dn7db_6t.js +189 -0
  19. package/dist/assets/classDiagram-VBA2DB6C-DtwCEe8S.js +1 -0
  20. package/dist/assets/classDiagram-v2-RAHNMMFH-DtwCEe8S.js +1 -0
  21. package/dist/assets/clone-C0lCEIEO.js +1 -0
  22. package/dist/assets/cose-bilkent-S5V4N54A-DD_nzqsz.js +1 -0
  23. package/dist/assets/cytoscape.esm-5J0xJHOV.js +321 -0
  24. package/dist/assets/{dagre-KLK3FWXG-DJ3dNSYk.js → dagre-KLK3FWXG-CHYIvW47.js} +1 -1
  25. package/dist/assets/diagram-E7M64L7V-TVdvHtGc.js +24 -0
  26. package/dist/assets/{diagram-IFDJBPK2-Da6K4aP-.js → diagram-IFDJBPK2-Dzsiln_C.js} +1 -1
  27. package/dist/assets/{diagram-P4PSJMXO-vZZKB92A.js → diagram-P4PSJMXO-DKnGbUpE.js} +1 -1
  28. package/dist/assets/erDiagram-INFDFZHY-5Kw0bByo.js +70 -0
  29. package/dist/assets/{flowDiagram-PKNHOUZH-DUV13pHi.js → flowDiagram-PKNHOUZH-BAZ2-jKp.js} +4 -4
  30. package/dist/assets/ganttDiagram-A5KZAMGK-CsADFkcq.js +292 -0
  31. package/dist/assets/{gitGraphDiagram-K3NZZRJ6-BZ5gW69I.js → gitGraphDiagram-K3NZZRJ6-BflpyjGy.js} +1 -1
  32. package/dist/assets/{graph-BbvHswRd.js → graph-suelaXFh.js} +1 -1
  33. package/dist/assets/highlighted-body-OFNGDK62-CZrBMazC.js +1 -0
  34. package/dist/assets/index-B01NxbUv.css +1 -0
  35. package/dist/assets/index-DW5pGgQ_.js +2 -0
  36. package/dist/assets/{infoDiagram-LFFYTUFH-8auUIPKW.js → infoDiagram-LFFYTUFH-pfD1FA3p.js} +1 -1
  37. package/dist/assets/ishikawaDiagram-PHBUUO56-ndm9snwO.js +70 -0
  38. package/dist/assets/journeyDiagram-4ABVD52K-HgF2t7z5.js +139 -0
  39. package/dist/assets/{kanban-definition-K7BYSVSG-Bappd2YO.js → kanban-definition-K7BYSVSG-FWinmur1.js} +5 -5
  40. package/dist/assets/{layout-BmbfFZKy.js → layout-vcz43XvZ.js} +1 -1
  41. package/dist/assets/{linear-WZnF-PT6.js → linear-le4gc0vx.js} +1 -1
  42. package/dist/assets/mermaid-GHXKKRXX-CK8m3lad.js +870 -0
  43. package/dist/assets/mindmap-definition-YRQLILUH-CNq9SKj4.js +68 -0
  44. package/dist/assets/{pieDiagram-SKSYHLDU-uxjlAy1t.js → pieDiagram-SKSYHLDU-C7PKDh3b.js} +2 -2
  45. package/dist/assets/quadrantDiagram-337W2JSQ-B7FnztNO.js +7 -0
  46. package/dist/assets/requirementDiagram-Z7DCOOCP-Bl_BM2Th.js +73 -0
  47. package/dist/assets/{sankeyDiagram-WA2Y5GQK-2-FHHM-R.js → sankeyDiagram-WA2Y5GQK-4gulcOP4.js} +3 -3
  48. package/dist/assets/sequenceDiagram-2WXFIKYE-VEuJDwyJ.js +145 -0
  49. package/dist/assets/{stateDiagram-RAJIS63D-DoW8U53H.js → stateDiagram-RAJIS63D-CB4Vl7qM.js} +1 -1
  50. package/dist/assets/stateDiagram-v2-FVOUBMTO-C85ucl39.js +1 -0
  51. package/dist/assets/timeline-definition-YZTLITO2-BPGKhi7f.js +61 -0
  52. package/dist/assets/{treemap-KZPCXAKY-ajdAP-72.js → treemap-KZPCXAKY-DZSEE6Hz.js} +58 -58
  53. package/dist/assets/vendor-codemirror-CyOKkaQZ.js +31 -0
  54. package/dist/assets/vendor-react-CP4yFTs7.js +8 -0
  55. package/dist/assets/vendor-xterm-DfcmCpbH.js +66 -0
  56. package/dist/assets/{vennDiagram-LZ73GAT5-C9If0AT0.js → vennDiagram-LZ73GAT5-8E_G06fI.js} +4 -4
  57. package/dist/assets/xychartDiagram-JWTSCODW-CbBk50-O.js +7 -0
  58. package/dist/favicon.png +0 -0
  59. package/dist/icons/icon-128x128.png +0 -0
  60. package/dist/icons/icon-144x144.png +0 -0
  61. package/dist/icons/icon-152x152.png +0 -0
  62. package/dist/icons/icon-192x192.png +0 -0
  63. package/dist/icons/icon-384x384.png +0 -0
  64. package/dist/icons/icon-512x512.png +0 -0
  65. package/dist/icons/icon-72x72.png +0 -0
  66. package/dist/icons/icon-96x96.png +0 -0
  67. package/dist/index.html +4 -5
  68. package/dist/logo-128.png +0 -0
  69. package/dist/logo-256.png +0 -0
  70. package/dist/logo-32.png +0 -0
  71. package/dist/logo-512.png +0 -0
  72. package/dist/logo-64.png +0 -0
  73. package/package.json +2 -1
  74. package/server/_legacy-providers/README.md +30 -0
  75. package/server/_legacy-providers/claude-sdk.js +956 -0
  76. package/server/_legacy-providers/gemini-cli.js +368 -0
  77. package/server/_legacy-providers/openai-codex.js +705 -0
  78. package/server/_legacy-providers/opencode-cli.js +674 -0
  79. package/server/acp-runtime/client.js +1805 -0
  80. package/server/acp-runtime/client.test.js +688 -0
  81. package/server/acp-runtime/index.js +419 -0
  82. package/server/acp-runtime/registry.js +45 -0
  83. package/server/acp-runtime/session-store.js +254 -0
  84. package/server/acp-runtime/session-store.test.js +89 -0
  85. package/server/channels/runtime/AgentRuntimeAdapter.js +21 -70
  86. package/server/claude-sdk.js +24 -944
  87. package/server/cli.js +11 -5
  88. package/server/external-agent/service.js +77 -63
  89. package/server/gemini-cli.js +23 -360
  90. package/server/index.js +54 -46
  91. package/server/openai-codex.js +24 -698
  92. package/server/opencode-cli.js +70 -640
  93. package/server/routes/agent.js +2 -0
  94. package/server/routes/codex.js +5 -5
  95. package/server/routes/git.js +3 -20
  96. package/server/routes/mcp.js +18 -34
  97. package/server/routes/session-core.js +44 -10
  98. package/server/session-core/abortSession.js +2 -18
  99. package/server/session-core/eventStore.js +5 -1
  100. package/server/session-core/providerAdapters.js +98 -10
  101. package/server/session-core/providerDiscovery.js +2 -2
  102. package/server/session-core/runtimeState.js +16 -17
  103. package/server/session-core/runtimeWriter.js +19 -12
  104. package/server/utils/codexPath.js +3 -1
  105. package/server/utils/spawnCommand.js +7 -0
  106. package/shared/conversationEvents.js +347 -10
  107. package/shared/conversationEvents.test.js +403 -0
  108. package/dist/assets/App-BxazfNJn.js +0 -484
  109. package/dist/assets/App-qxJ8_QYu.css +0 -32
  110. package/dist/assets/ReviewApp-CsqTAlGU.js +0 -1
  111. package/dist/assets/architectureDiagram-2XIMDMQ5-DmUHdvQH.js +0 -36
  112. package/dist/assets/c4Diagram-IC4MRINW-BOivDlQU.js +0 -10
  113. package/dist/assets/channel-Cj8xVD0X.js +0 -1
  114. package/dist/assets/chunk-NQ4KR5QH-Ci-n7jfu.js +0 -220
  115. package/dist/assets/chunk-WL4C6EOR-C559Mk71.js +0 -189
  116. package/dist/assets/classDiagram-VBA2DB6C-CI2zklxw.js +0 -1
  117. package/dist/assets/classDiagram-v2-RAHNMMFH-CI2zklxw.js +0 -1
  118. package/dist/assets/clone-BEVqubrI.js +0 -1
  119. package/dist/assets/cose-bilkent-S5V4N54A-DNO9ncXL.js +0 -1
  120. package/dist/assets/cytoscape.esm-2ZfV8NB5.js +0 -331
  121. package/dist/assets/diagram-E7M64L7V-Ba_LGLun.js +0 -24
  122. package/dist/assets/erDiagram-INFDFZHY-Csb8dFdP.js +0 -70
  123. package/dist/assets/ganttDiagram-A5KZAMGK-B5Kv9Wfz.js +0 -292
  124. package/dist/assets/highlighted-body-TPN3WLV5-DZJajMGm.js +0 -1
  125. package/dist/assets/index-BFX9lxRB.css +0 -1
  126. package/dist/assets/index-BiErUGrv.js +0 -2
  127. package/dist/assets/ishikawaDiagram-PHBUUO56-JmsNlo2I.js +0 -70
  128. package/dist/assets/journeyDiagram-4ABVD52K-Cuudv7Vv.js +0 -139
  129. package/dist/assets/mermaid-O7DHMXV3-D-2fQRvw.js +0 -988
  130. package/dist/assets/mindmap-definition-YRQLILUH-BQHnzzud.js +0 -68
  131. package/dist/assets/quadrantDiagram-337W2JSQ-DpwZU-f_.js +0 -7
  132. package/dist/assets/requirementDiagram-Z7DCOOCP-C_9ClOWm.js +0 -73
  133. package/dist/assets/sequenceDiagram-2WXFIKYE-egns-0XI.js +0 -145
  134. package/dist/assets/stateDiagram-v2-FVOUBMTO-BoFZZ4Ds.js +0 -1
  135. package/dist/assets/timeline-definition-YZTLITO2-chPa8ppH.js +0 -61
  136. package/dist/assets/vendor-codemirror-Dz7_EqNA.js +0 -39
  137. package/dist/assets/vendor-react-Cpt6D04s.js +0 -59
  138. package/dist/assets/vendor-xterm-DfaPXD3y.js +0 -66
  139. package/dist/assets/xychartDiagram-JWTSCODW-DD42U6Or.js +0 -7
package/server/cli.js CHANGED
@@ -740,6 +740,8 @@ function showStatus(options = {}) {
740
740
 
741
741
  // Show help
742
742
  function showHelp() {
743
+ const defaultProjectUrl = 'https://github.com/lintendo/AI-Web-UI';
744
+ const defaultIssuesUrl = `${defaultProjectUrl}/issues`;
743
745
  console.log(`
744
746
  ╔═══════════════════════════════════════════════════════════════╗
745
747
  ║ Axhub Genie - Command Line Tool ║
@@ -805,10 +807,10 @@ Environment Variables:
805
807
  AXHUB_GENIE_NO_OPEN Set to true to disable auto-opening the homepage
806
808
 
807
809
  Documentation:
808
- ${packageJson.homepage || 'https://github.com/siteboon/claudecodeui'}
810
+ ${packageJson.homepage || defaultProjectUrl}
809
811
 
810
812
  Report Issues:
811
- ${packageJson.bugs?.url || 'https://github.com/siteboon/claudecodeui/issues'}
813
+ ${packageJson.bugs?.url || defaultIssuesUrl}
812
814
  `);
813
815
  }
814
816
 
@@ -817,6 +819,10 @@ function showVersion() {
817
819
  console.log(`${packageJson.version}`);
818
820
  }
819
821
 
822
+ function getManualUpdateCommand(packageName = packageJson.name || '@axhub/genie') {
823
+ return `npm install -g ${packageName}@latest`;
824
+ }
825
+
820
826
  // Compare semver versions, returns true if v1 > v2
821
827
  function isNewerVersion(v1, v2) {
822
828
  const parts1 = v1.split('.').map(Number);
@@ -838,7 +844,7 @@ async function checkForUpdates(silent = false) {
838
844
 
839
845
  if (isNewerVersion(latestVersion, currentVersion)) {
840
846
  console.log(`\n${c.warn('[UPDATE]')} New version available: ${c.bright(latestVersion)} (current: ${currentVersion})`);
841
- console.log(` Run ${c.bright('axhub-genie update')} to update\n`);
847
+ console.log(` Run ${c.bright(getManualUpdateCommand(packageName))} to update\n`);
842
848
  return { hasUpdate: true, latestVersion, currentVersion };
843
849
  } else if (!silent) {
844
850
  console.log(`${c.ok('[OK]')} You are on the latest version (${currentVersion})`);
@@ -867,11 +873,11 @@ async function updatePackage() {
867
873
  }
868
874
 
869
875
  console.log(`${c.info('[INFO]')} Updating from ${currentVersion} to ${latestVersion}...`);
870
- execSync(`npm update -g ${packageName}`, { stdio: 'inherit' });
876
+ execSync(getManualUpdateCommand(packageName), { stdio: 'inherit' });
871
877
  console.log(`${c.ok('[OK]')} Update complete! Restart axhub-genie to use the new version.`);
872
878
  } catch (e) {
873
879
  console.error(`${c.error('[ERROR]')} Update failed: ${e.message}`);
874
- console.log(`${c.tip('[TIP]')} Try running manually: npm update -g ${packageJson.name || '@axhub/genie'}`);
880
+ console.log(`${c.tip('[TIP]')} Try running manually: ${getManualUpdateCommand()}`);
875
881
  }
876
882
  }
877
883
 
@@ -1,10 +1,12 @@
1
1
  import { promises as fs } from 'fs';
2
2
 
3
3
  import { addProjectManually } from '../projects.js';
4
- import { queryClaudeSDK } from '../claude-sdk.js';
5
- import { queryCodex } from '../openai-codex.js';
6
- import { queryGemini } from '../gemini-cli.js';
7
- import { queryOpencode } from '../opencode-cli.js';
4
+ import {
5
+ applyConversationEventToTimelineMessages,
6
+ CONVERSATION_EVENT_KINDS
7
+ } from '../../shared/conversationEvents.js';
8
+ import { executeAgentPrompt } from '../acp-runtime/index.js';
9
+ import { detectProviderInstallationStatus } from '../routes/cli-auth.js';
8
10
  import {
9
11
  buildAgentCallbackPayload,
10
12
  createAgentCallbackEventId,
@@ -15,7 +17,6 @@ import {
15
17
  } from '../utils/agentCallback.js';
16
18
  import { normalizeExternalImages } from '../utils/agentImages.js';
17
19
  import { resolveWorkingDirectory } from '../utils/defaultWorkingDirectory.js';
18
- import { CODEX_MODELS, GEMINI_MODELS, OPENCODE_MODELS } from '../../shared/modelConstants.js';
19
20
 
20
21
  export const SUPPORTED_EXTERNAL_AGENT_PROVIDERS = ['claude', 'codex', 'gemini', 'opencode'];
21
22
 
@@ -38,6 +39,29 @@ function getDeprecatedGitHubFields(body) {
38
39
  return deprecatedFields.filter((field) => body[field] !== undefined);
39
40
  }
40
41
 
42
+ async function ensureProviderInstalled(provider) {
43
+ const status = await detectProviderInstallationStatus(provider);
44
+ if (status?.installed) {
45
+ return status;
46
+ }
47
+
48
+ const installHint = typeof status?.installHint === 'string' && status.installHint.trim()
49
+ ? status.installHint.trim()
50
+ : null;
51
+ const reason = typeof status?.reason === 'string' && status.reason.trim()
52
+ ? status.reason.trim()
53
+ : null;
54
+
55
+ const messageParts = [`Provider "${provider}" is not installed.`];
56
+ if (installHint) {
57
+ messageParts.push(installHint);
58
+ } else if (reason) {
59
+ messageParts.push(reason);
60
+ }
61
+
62
+ throw createRequestError(messageParts.join(' '), 400);
63
+ }
64
+
41
65
  export function buildSessionNavigation(sessionId) {
42
66
  const normalizedSessionId = typeof sessionId === 'string' && sessionId.trim() ? sessionId.trim() : null;
43
67
  const sessionPath = normalizedSessionId ? `/session/${normalizedSessionId}` : null;
@@ -250,7 +274,7 @@ function extractTerminalErrorMessage(payload) {
250
274
  export class CallbackCaptureWriter {
251
275
  constructor() {
252
276
  this.sessionId = null;
253
- this.assistantMessages = [];
277
+ this.timelineMessages = [];
254
278
  this.tokenSummary = createEmptyTokenSummary();
255
279
  this.terminalState = null;
256
280
  this.terminalErrorMessage = null;
@@ -279,24 +303,34 @@ export class CallbackCaptureWriter {
279
303
  };
280
304
  }
281
305
 
282
- if (payload.type === 'claude-response' && payload.data?.type === 'assistant') {
283
- this.assistantMessages.push(payload.data);
284
- }
306
+ if (payload.type === 'conversation-event' && payload.event) {
307
+ this.timelineMessages = applyConversationEventToTimelineMessages(
308
+ this.timelineMessages,
309
+ payload.event,
310
+ payload.event.provider || 'claude'
311
+ );
312
+
313
+ if (payload.event.kind === CONVERSATION_EVENT_KINDS.ERROR) {
314
+ this.terminalState = 'errored';
315
+ this.terminalErrorMessage = payload.event.payload?.message || this.terminalErrorMessage || 'Agent session failed';
316
+ return;
317
+ }
285
318
 
286
- if (
287
- payload.type === 'codex-response' &&
288
- payload.data?.type === 'item_done' &&
289
- payload.data?.itemType === 'agent_message' &&
290
- typeof payload.data?.content === 'string' &&
291
- payload.data.content.trim()
292
- ) {
293
- this.assistantMessages.push({
294
- type: 'assistant',
295
- message: {
296
- role: 'assistant',
297
- content: payload.data.content
319
+ if (payload.event.kind === CONVERSATION_EVENT_KINDS.SESSION_STATE_CHANGED) {
320
+ const nextState = String(payload.event.payload?.state || '').trim().toLowerCase();
321
+ if (nextState === 'completed') {
322
+ this.terminalState = 'completed';
323
+ this.terminalErrorMessage = null;
324
+ } else if (nextState === 'aborted') {
325
+ this.terminalState = 'aborted';
326
+ this.terminalErrorMessage = 'Session aborted';
327
+ } else if (nextState === 'errored') {
328
+ this.terminalState = 'errored';
329
+ this.terminalErrorMessage = payload.event.payload?.message || this.terminalErrorMessage || 'Agent session failed';
298
330
  }
299
- });
331
+ }
332
+
333
+ return;
300
334
  }
301
335
 
302
336
  if (payload.type === 'session-aborted') {
@@ -335,7 +369,16 @@ export class CallbackCaptureWriter {
335
369
  }
336
370
 
337
371
  getAssistantMessages() {
338
- return this.assistantMessages;
372
+ return this.timelineMessages
373
+ .filter((message) => message?.type === 'assistant' && !message?.isToolUse && !message?.isSystemNotice)
374
+ .map((message) => ({
375
+ type: 'assistant',
376
+ message: {
377
+ role: 'assistant',
378
+ content: message.content || ''
379
+ },
380
+ timestamp: message.timestamp || null
381
+ }));
339
382
  }
340
383
 
341
384
  getTotalTokens() {
@@ -394,52 +437,19 @@ async function ensureProjectPathExists(projectPath) {
394
437
  }
395
438
 
396
439
  async function runProviderSession({ provider, message, images, finalProjectPath, sessionId, model, writer }) {
397
- if (provider === 'claude') {
398
- await queryClaudeSDK(message, {
440
+ await executeAgentPrompt({
441
+ agentKey: provider,
442
+ command: message,
443
+ options: {
399
444
  projectPath: finalProjectPath,
400
445
  cwd: finalProjectPath,
401
446
  sessionId,
402
447
  model,
403
448
  permissionMode: 'bypassPermissions',
404
449
  images
405
- }, writer);
406
- return;
407
- }
408
-
409
- if (provider === 'codex') {
410
- await queryCodex(message, {
411
- projectPath: finalProjectPath,
412
- cwd: finalProjectPath,
413
- sessionId,
414
- model: model || CODEX_MODELS.DEFAULT,
415
- permissionMode: 'bypassPermissions',
416
- images
417
- }, writer);
418
- return;
419
- }
420
-
421
- if (provider === 'gemini') {
422
- await queryGemini(message, {
423
- projectPath: finalProjectPath,
424
- cwd: finalProjectPath,
425
- sessionId,
426
- resume: !!sessionId,
427
- model: model || GEMINI_MODELS.DEFAULT,
428
- permissionMode: 'bypassPermissions'
429
- }, writer);
430
- return;
431
- }
432
-
433
- if (provider === 'opencode') {
434
- await queryOpencode(message, {
435
- projectPath: finalProjectPath,
436
- cwd: finalProjectPath,
437
- sessionId,
438
- resume: !!sessionId,
439
- model: model || OPENCODE_MODELS.DEFAULT,
440
- permissionMode: 'bypassPermissions'
441
- }, writer);
442
- }
450
+ },
451
+ writer
452
+ });
443
453
  }
444
454
 
445
455
  export async function runExternalAgentRequest({
@@ -459,6 +469,7 @@ export async function runExternalAgentRequest({
459
469
  const response = {
460
470
  success: true,
461
471
  openOnly: true,
472
+ runtime: 'acp',
462
473
  sessionId: normalized.normalizedSessionId,
463
474
  ...buildSessionNavigation(normalized.normalizedSessionId),
464
475
  isResumed: true,
@@ -467,6 +478,7 @@ export async function runExternalAgentRequest({
467
478
 
468
479
  transportWriter?.send?.({
469
480
  type: 'open-only',
481
+ runtime: 'acp',
470
482
  ...response
471
483
  });
472
484
 
@@ -484,6 +496,7 @@ export async function runExternalAgentRequest({
484
496
  let callbackResult = null;
485
497
 
486
498
  try {
499
+ await ensureProviderInstalled(normalized.provider);
487
500
  await ensureProjectPathExists(finalProjectPath);
488
501
 
489
502
  try {
@@ -525,6 +538,7 @@ export async function runExternalAgentRequest({
525
538
  const response = {
526
539
  success: true,
527
540
  sessionId: callbackSessionId,
541
+ runtime: 'acp',
528
542
  ...navigation,
529
543
  isResumed: !!normalized.normalizedSessionId,
530
544
  messages: callbackCaptureWriter.getAssistantMessages(),
@@ -1,368 +1,31 @@
1
- import { spawn } from 'child_process';
2
- import crossSpawn from 'cross-spawn';
3
- import { resolveWorkingDirectory } from './utils/defaultWorkingDirectory.js';
4
-
5
- const spawnFunction = process.platform === 'win32' ? crossSpawn : spawn;
6
-
7
- const activeGeminiProcesses = new Map();
8
-
9
- function collectTextChunks(payload) {
10
- if (!payload) return [];
11
- if (typeof payload === 'string') return payload.trim() ? [payload] : [];
12
-
13
- const chunks = [];
14
-
15
- if (Array.isArray(payload)) {
16
- payload.forEach(item => {
17
- chunks.push(...collectTextChunks(item));
18
- });
19
- return chunks;
20
- }
21
-
22
- if (typeof payload !== 'object') {
23
- return chunks;
24
- }
25
-
26
- const directKeys = ['text', 'response', 'content', 'message', 'delta'];
27
- for (const key of directKeys) {
28
- if (payload[key] !== undefined) {
29
- chunks.push(...collectTextChunks(payload[key]));
30
- }
31
- }
32
-
33
- if (Array.isArray(payload.parts)) {
34
- payload.parts.forEach(part => {
35
- chunks.push(...collectTextChunks(part));
36
- });
37
- }
38
-
39
- if (payload.data && typeof payload.data === 'object') {
40
- chunks.push(...collectTextChunks(payload.data));
41
- }
42
-
43
- return chunks;
44
- }
45
-
46
- function getEventRole(event) {
47
- return (
48
- event?.role ||
49
- event?.author ||
50
- event?.sender ||
51
- event?.message?.role ||
52
- event?.data?.role ||
53
- event?.content?.role ||
54
- null
55
- );
56
- }
57
-
58
- function extractAssistantTextChunks(event, command) {
59
- const role = String(getEventRole(event) || '').toLowerCase();
60
- if (role === 'user') return [];
61
-
62
- const eventType = String(event?.type || event?.event || event?.kind || '').toLowerCase();
63
- const hasAssistantPayload = !!(event?.response || event?.candidates || event?.delta || event?.text || event?.content || event?.message);
64
- if (eventType.includes('prompt') && !hasAssistantPayload) {
65
- return [];
66
- }
67
-
68
- const normalizedPrompt = String(command || '').trim();
69
- return collectTextChunks(event).filter((text) => {
70
- const trimmed = String(text || '').trim();
71
- if (!trimmed) return false;
72
- if (normalizedPrompt && trimmed === normalizedPrompt) return false;
73
- return true;
74
- });
75
- }
76
-
77
- function parseGeminiJsonLine(line) {
78
- const trimmed = line.trim();
79
- if (!trimmed) return null;
80
-
81
- const payload = trimmed.startsWith('data:') ? trimmed.slice(5).trim() : trimmed;
82
- if (!payload) return null;
83
-
84
- return JSON.parse(payload);
85
- }
86
-
87
- function emitTextChunks(ws, sessionId, chunks) {
88
- chunks.forEach((text) => {
89
- if (!text) return;
90
- ws.send({
91
- type: 'claude-response',
92
- data: {
93
- type: 'content_block_delta',
94
- delta: {
95
- type: 'text_delta',
96
- text
97
- }
98
- },
99
- provider: 'gemini',
100
- sessionId
101
- });
102
- });
103
- }
104
-
105
- function formatGeminiProcessError(code, stderrOutput) {
106
- const normalizedStderr = String(stderrOutput || '').trim();
107
- if (normalizedStderr) {
108
- const apiMessageMatch =
109
- normalizedStderr.match(/"message":"([^"]+)"/) ||
110
- normalizedStderr.match(/message:\s*"([^"]+)"/i) ||
111
- normalizedStderr.match(/ApiError:\s*\{.*?"message":"([^"]+)"/i) ||
112
- normalizedStderr.match(/Error when talking to Gemini API[\s\S]*?message":"([^"]+)"/i);
113
-
114
- if (apiMessageMatch?.[1]) {
115
- return apiMessageMatch[1];
116
- }
117
-
118
- const firstMeaningfulLine = normalizedStderr
119
- .split('\n')
120
- .map((line) => line.trim())
121
- .find((line) => line && !line.startsWith('at '));
122
-
123
- if (firstMeaningfulLine) {
124
- return firstMeaningfulLine;
125
- }
126
-
127
- return normalizedStderr;
128
- }
129
-
130
- return `Gemini CLI exited with code ${code}`;
131
- }
132
-
133
- function extractGeminiEventError(event) {
134
- if (!event || typeof event !== 'object') {
135
- return null;
136
- }
137
-
138
- const candidates = [
139
- event?.error?.message,
140
- event?.error?.details,
141
- event?.result?.error?.message,
142
- event?.result?.error,
143
- event?.data?.error?.message,
144
- event?.data?.error,
145
- event?.message
146
- ];
147
-
148
- for (const candidate of candidates) {
149
- const normalized = String(candidate || '').trim();
150
- if (normalized) {
151
- return normalized;
152
- }
153
- }
154
-
155
- return null;
156
- }
157
-
158
- async function queryGemini(command, options = {}, ws) {
159
- return new Promise((resolve, reject) => {
160
- const { sessionId, cwd, projectPath, resume, model, permissionMode } = options;
161
- let capturedSessionId = sessionId;
162
- let sentSessionCreated = false;
163
- let stderrBuffer = '';
164
- let fatalErrorSent = false;
165
- let sawSuccessfulTerminalEvent = false;
166
- let sawErroredTerminalEvent = false;
167
-
168
- const args = ['-y', '@google/gemini-cli'];
169
-
170
- if (sessionId && (resume || !command || !command.trim())) {
171
- args.push('--resume', sessionId);
172
- }
173
-
174
- if (command && command.trim()) {
175
- args.push('--prompt', command);
176
- args.push('--output-format', 'stream-json');
177
- }
178
-
179
- if (model) {
180
- args.push('--model', model);
181
- }
182
-
183
- if (permissionMode === 'bypassPermissions' || permissionMode === 'acceptEdits') {
184
- args.push('--yolo');
185
- }
186
-
187
- const workingDir = resolveWorkingDirectory({ cwd, projectPath });
188
-
189
- const geminiProcess = spawnFunction('npx', args, {
190
- cwd: workingDir,
191
- stdio: ['pipe', 'pipe', 'pipe'],
192
- env: {
193
- ...process.env,
194
- GEMINI_NONINTERACTIVE: '1'
195
- }
196
- });
197
-
198
- const processKey = capturedSessionId || Date.now().toString();
199
- let processRegistryKey = processKey;
200
- activeGeminiProcesses.set(processRegistryKey, geminiProcess);
201
-
202
- const finalizeSessionId = () => capturedSessionId || sessionId || null;
203
- const emitFatalError = (errorMessage) => {
204
- if (fatalErrorSent || !errorMessage) {
205
- return;
206
- }
207
-
208
- fatalErrorSent = true;
209
- ws.send({
210
- type: 'claude-error',
211
- error: errorMessage,
212
- provider: 'gemini',
213
- sessionId: finalizeSessionId()
214
- });
215
- };
216
-
217
- const handleJsonEvent = (event) => {
218
- const incomingSessionId = event?.session_id || event?.sessionId || event?.data?.session_id || event?.data?.sessionId;
219
- if (incomingSessionId && !capturedSessionId) {
220
- capturedSessionId = incomingSessionId;
221
- if (ws.setSessionId && typeof ws.setSessionId === 'function') {
222
- ws.setSessionId(capturedSessionId);
223
- }
224
-
225
- if (processRegistryKey !== capturedSessionId) {
226
- activeGeminiProcesses.delete(processRegistryKey);
227
- activeGeminiProcesses.set(capturedSessionId, geminiProcess);
228
- processRegistryKey = capturedSessionId;
229
- }
230
-
231
- if (!sessionId && !sentSessionCreated) {
232
- sentSessionCreated = true;
233
- ws.send({
234
- type: 'session-created',
235
- sessionId: capturedSessionId,
236
- provider: 'gemini'
237
- });
238
- }
239
- }
240
-
241
- const type = event?.type || event?.event || event?.kind;
242
- if (type === 'result' || type === 'done' || type === 'complete') {
243
- const eventStatus = String(event?.status || event?.result?.status || '').trim().toLowerCase();
244
- const eventError = extractGeminiEventError(event);
245
- if (eventStatus === 'error' || eventError) {
246
- sawErroredTerminalEvent = true;
247
- emitFatalError(eventError || 'Gemini CLI request failed');
248
- return;
249
- }
250
-
251
- sawSuccessfulTerminalEvent = true;
252
- ws.send({
253
- type: 'claude-response',
254
- data: { type: 'content_block_stop' },
255
- provider: 'gemini',
256
- sessionId: finalizeSessionId()
257
- });
258
-
259
- ws.send({
260
- type: 'gemini-result',
261
- data: event,
262
- sessionId: finalizeSessionId()
263
- });
264
- return;
265
- }
266
-
267
- const textChunks = extractAssistantTextChunks(event, command);
268
- emitTextChunks(ws, finalizeSessionId(), textChunks);
269
- };
270
-
271
- let stdoutBuffer = '';
272
- geminiProcess.stdout.on('data', (data) => {
273
- stdoutBuffer += data.toString();
274
- const lines = stdoutBuffer.split('\n');
275
- stdoutBuffer = lines.pop() || '';
276
-
277
- for (const line of lines) {
278
- try {
279
- const event = parseGeminiJsonLine(line);
280
- if (!event) continue;
281
- handleJsonEvent(event);
282
- } catch {}
283
- }
284
- });
285
-
286
- geminiProcess.stderr.on('data', (data) => {
287
- const chunk = data.toString();
288
- stderrBuffer += chunk;
289
-
290
- const normalizedChunk = chunk.trim();
291
- if (normalizedChunk) {
292
- console.warn('Gemini CLI stderr:', normalizedChunk);
293
- }
294
- });
295
-
296
- geminiProcess.on('close', (code) => {
297
- if (stdoutBuffer.trim()) {
298
- try {
299
- const finalEvent = parseGeminiJsonLine(stdoutBuffer);
300
- if (finalEvent) {
301
- handleJsonEvent(finalEvent);
302
- }
303
- } catch {}
304
- }
305
-
306
- activeGeminiProcesses.delete(processRegistryKey);
307
-
308
- if (code === 0 && !sawErroredTerminalEvent) {
309
- if (!sawSuccessfulTerminalEvent) {
310
- ws.send({
311
- type: 'claude-response',
312
- data: { type: 'content_block_stop' },
313
- provider: 'gemini',
314
- sessionId: finalizeSessionId()
315
- });
316
- }
317
-
318
- ws.send({
319
- type: 'claude-complete',
320
- sessionId: finalizeSessionId(),
321
- provider: 'gemini',
322
- exitCode: code,
323
- isNewSession: !sessionId && !!command
324
- });
325
- resolve();
326
- } else {
327
- const errorMessage = formatGeminiProcessError(code, stderrBuffer);
328
- emitFatalError(errorMessage);
329
- reject(new Error(errorMessage));
330
- }
331
- });
332
-
333
- geminiProcess.on('error', (error) => {
334
- activeGeminiProcesses.delete(processRegistryKey);
335
-
336
- emitFatalError(error.message);
337
-
338
- reject(error);
339
- });
340
-
341
- geminiProcess.stdin.end();
1
+ import {
2
+ abortAgentSession,
3
+ executeAgentPrompt,
4
+ getActiveAgentSessions,
5
+ isAgentSessionActive
6
+ } from './acp-runtime/index.js';
7
+ import { GEMINI_MODELS } from '../shared/modelConstants.js';
8
+
9
+ export async function queryGemini(command, options = {}, writer) {
10
+ return executeAgentPrompt({
11
+ agentKey: 'gemini',
12
+ command,
13
+ options: {
14
+ ...options,
15
+ model: options.model || GEMINI_MODELS.DEFAULT
16
+ },
17
+ writer
342
18
  });
343
19
  }
344
20
 
345
- function abortGeminiSession(sessionId) {
346
- const process = activeGeminiProcesses.get(sessionId);
347
- if (process) {
348
- process.kill('SIGTERM');
349
- activeGeminiProcesses.delete(sessionId);
350
- return true;
351
- }
352
- return false;
21
+ export async function abortGeminiSession(sessionId) {
22
+ return abortAgentSession('gemini', sessionId);
353
23
  }
354
24
 
355
- function isGeminiSessionActive(sessionId) {
356
- return activeGeminiProcesses.has(sessionId);
25
+ export function isGeminiSessionActive(sessionId) {
26
+ return isAgentSessionActive('gemini', sessionId);
357
27
  }
358
28
 
359
- function getActiveGeminiSessions() {
360
- return Array.from(activeGeminiProcesses.keys());
29
+ export function getActiveGeminiSessions() {
30
+ return getActiveAgentSessions('gemini');
361
31
  }
362
-
363
- export {
364
- queryGemini,
365
- abortGeminiSession,
366
- isGeminiSessionActive,
367
- getActiveGeminiSessions
368
- };