@jingyi0605/codingns 0.1.4 → 0.1.5

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 (209) hide show
  1. package/dist/public/assets/{TerminalPage-4ulgBhv9.js → TerminalPage-4p6EBqrR.js} +1 -1
  2. package/dist/public/assets/gemini-D4G1NbrE.png +0 -0
  3. package/dist/public/assets/index-CxeghocY.css +1 -0
  4. package/dist/public/assets/index-DXusStl0.js +108 -0
  5. package/dist/public/assets/kimi-BWNNSh7e.png +0 -0
  6. package/dist/public/index.html +2 -2
  7. package/dist/server/config/env.d.ts +6 -0
  8. package/dist/server/config/env.js +145 -0
  9. package/dist/server/config/env.js.map +1 -1
  10. package/dist/server/config/opencode-system-probe-helper-process.d.ts +24 -0
  11. package/dist/server/config/opencode-system-probe-helper-process.js +70 -5
  12. package/dist/server/config/opencode-system-probe-helper-process.js.map +1 -1
  13. package/dist/server/modules/butler/butler-action-context-service.d.ts +30 -0
  14. package/dist/server/modules/butler/butler-action-context-service.js +108 -0
  15. package/dist/server/modules/butler/butler-action-context-service.js.map +1 -0
  16. package/dist/server/modules/butler/butler-auth-service.d.ts +17 -0
  17. package/dist/server/modules/butler/butler-auth-service.js +91 -0
  18. package/dist/server/modules/butler/butler-auth-service.js.map +1 -0
  19. package/dist/server/modules/butler/butler-control-action-service.d.ts +65 -0
  20. package/dist/server/modules/butler/butler-control-action-service.js +296 -0
  21. package/dist/server/modules/butler/butler-control-action-service.js.map +1 -0
  22. package/dist/server/modules/butler/butler-control-session-service.d.ts +55 -0
  23. package/dist/server/modules/butler/butler-control-session-service.js +367 -0
  24. package/dist/server/modules/butler/butler-control-session-service.js.map +1 -0
  25. package/dist/server/modules/butler/butler-controller.d.ts +367 -0
  26. package/dist/server/modules/butler/butler-controller.js +475 -0
  27. package/dist/server/modules/butler/butler-controller.js.map +1 -0
  28. package/dist/server/modules/butler/butler-follow-up-evaluation-instruction-adapter.d.ts +34 -0
  29. package/dist/server/modules/butler/butler-follow-up-evaluation-instruction-adapter.js +77 -0
  30. package/dist/server/modules/butler/butler-follow-up-evaluation-instruction-adapter.js.map +1 -0
  31. package/dist/server/modules/butler/butler-follow-up-scheduler.d.ts +23 -0
  32. package/dist/server/modules/butler/butler-follow-up-scheduler.js +57 -0
  33. package/dist/server/modules/butler/butler-follow-up-scheduler.js.map +1 -0
  34. package/dist/server/modules/butler/butler-follow-up-service.d.ts +86 -0
  35. package/dist/server/modules/butler/butler-follow-up-service.js +948 -0
  36. package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -0
  37. package/dist/server/modules/butler/butler-inbox-service.d.ts +35 -0
  38. package/dist/server/modules/butler/butler-inbox-service.js +136 -0
  39. package/dist/server/modules/butler/butler-inbox-service.js.map +1 -0
  40. package/dist/server/modules/butler/butler-notification-service.d.ts +12 -0
  41. package/dist/server/modules/butler/butler-notification-service.js +45 -0
  42. package/dist/server/modules/butler/butler-notification-service.js.map +1 -0
  43. package/dist/server/modules/butler/butler-profile-service.d.ts +26 -0
  44. package/dist/server/modules/butler/butler-profile-service.js +529 -0
  45. package/dist/server/modules/butler/butler-profile-service.js.map +1 -0
  46. package/dist/server/modules/butler/butler-project-service.d.ts +48 -0
  47. package/dist/server/modules/butler/butler-project-service.js +253 -0
  48. package/dist/server/modules/butler/butler-project-service.js.map +1 -0
  49. package/dist/server/modules/butler/butler-session-service.d.ts +79 -0
  50. package/dist/server/modules/butler/butler-session-service.js +503 -0
  51. package/dist/server/modules/butler/butler-session-service.js.map +1 -0
  52. package/dist/server/modules/butler/butler-session-summary-service.d.ts +55 -0
  53. package/dist/server/modules/butler/butler-session-summary-service.js +382 -0
  54. package/dist/server/modules/butler/butler-session-summary-service.js.map +1 -0
  55. package/dist/server/modules/butler/context-aggregator.d.ts +187 -0
  56. package/dist/server/modules/butler/context-aggregator.js +807 -0
  57. package/dist/server/modules/butler/context-aggregator.js.map +1 -0
  58. package/dist/server/modules/butler/instruction-adapter.d.ts +28 -0
  59. package/dist/server/modules/butler/instruction-adapter.js +101 -0
  60. package/dist/server/modules/butler/instruction-adapter.js.map +1 -0
  61. package/dist/server/modules/butler/patrol-execution-service.d.ts +47 -0
  62. package/dist/server/modules/butler/patrol-execution-service.js +347 -0
  63. package/dist/server/modules/butler/patrol-execution-service.js.map +1 -0
  64. package/dist/server/modules/butler/patrol-plan-service.d.ts +54 -0
  65. package/dist/server/modules/butler/patrol-plan-service.js +272 -0
  66. package/dist/server/modules/butler/patrol-plan-service.js.map +1 -0
  67. package/dist/server/modules/butler/patrol-run-service.d.ts +60 -0
  68. package/dist/server/modules/butler/patrol-run-service.js +185 -0
  69. package/dist/server/modules/butler/patrol-run-service.js.map +1 -0
  70. package/dist/server/modules/butler/patrol-scheduler.d.ts +36 -0
  71. package/dist/server/modules/butler/patrol-scheduler.js +99 -0
  72. package/dist/server/modules/butler/patrol-scheduler.js.map +1 -0
  73. package/dist/server/modules/butler/project-memory-service.d.ts +30 -0
  74. package/dist/server/modules/butler/project-memory-service.js +103 -0
  75. package/dist/server/modules/butler/project-memory-service.js.map +1 -0
  76. package/dist/server/modules/butler/provider-adapter-registry.d.ts +61 -0
  77. package/dist/server/modules/butler/provider-adapter-registry.js +430 -0
  78. package/dist/server/modules/butler/provider-adapter-registry.js.map +1 -0
  79. package/dist/server/modules/butler/session-summary-instruction-adapter.d.ts +28 -0
  80. package/dist/server/modules/butler/session-summary-instruction-adapter.js +79 -0
  81. package/dist/server/modules/butler/session-summary-instruction-adapter.js.map +1 -0
  82. package/dist/server/modules/butler/session-summary-scheduler.d.ts +23 -0
  83. package/dist/server/modules/butler/session-summary-scheduler.js +57 -0
  84. package/dist/server/modules/butler/session-summary-scheduler.js.map +1 -0
  85. package/dist/server/modules/butler/verification-run-service.d.ts +73 -0
  86. package/dist/server/modules/butler/verification-run-service.js +633 -0
  87. package/dist/server/modules/butler/verification-run-service.js.map +1 -0
  88. package/dist/server/modules/preferences/profile-service.js +8 -2
  89. package/dist/server/modules/preferences/profile-service.js.map +1 -1
  90. package/dist/server/modules/sessions/claude-runtime-helper-process.js +1 -1
  91. package/dist/server/modules/sessions/claude-runtime-helper-process.js.map +1 -1
  92. package/dist/server/modules/sessions/codex-app-server-helper-client.d.ts +5 -1
  93. package/dist/server/modules/sessions/codex-app-server-helper-client.js +10 -2
  94. package/dist/server/modules/sessions/codex-app-server-helper-client.js.map +1 -1
  95. package/dist/server/modules/sessions/session-controller.d.ts +3 -1
  96. package/dist/server/modules/sessions/session-controller.js +11 -2
  97. package/dist/server/modules/sessions/session-controller.js.map +1 -1
  98. package/dist/server/modules/sessions/session-history-service.d.ts +14 -1
  99. package/dist/server/modules/sessions/session-history-service.js +291 -30
  100. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  101. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +25 -2
  102. package/dist/server/modules/sessions/session-live-runtime-service.js +526 -158
  103. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
  104. package/dist/server/modules/sessions/session-provider-error-mapper.js +28 -0
  105. package/dist/server/modules/sessions/session-provider-error-mapper.js.map +1 -1
  106. package/dist/server/modules/workbench/workbench-service.d.ts +7 -1
  107. package/dist/server/modules/workbench/workbench-service.js +31 -7
  108. package/dist/server/modules/workbench/workbench-service.js.map +1 -1
  109. package/dist/server/routes/butler.d.ts +3 -0
  110. package/dist/server/routes/butler.js +54 -0
  111. package/dist/server/routes/butler.js.map +1 -0
  112. package/dist/server/server/create-server.d.ts +61 -0
  113. package/dist/server/server/create-server.js +148 -4
  114. package/dist/server/server/create-server.js.map +1 -1
  115. package/dist/server/storage/repositories/butler-control-event-repository.d.ts +8 -0
  116. package/dist/server/storage/repositories/butler-control-event-repository.js +78 -0
  117. package/dist/server/storage/repositories/butler-control-event-repository.js.map +1 -0
  118. package/dist/server/storage/repositories/butler-control-session-repository.d.ts +11 -0
  119. package/dist/server/storage/repositories/butler-control-session-repository.js +86 -0
  120. package/dist/server/storage/repositories/butler-control-session-repository.js.map +1 -0
  121. package/dist/server/storage/repositories/butler-follow-up-task-repository.d.ts +16 -0
  122. package/dist/server/storage/repositories/butler-follow-up-task-repository.js +252 -0
  123. package/dist/server/storage/repositories/butler-follow-up-task-repository.js.map +1 -0
  124. package/dist/server/storage/repositories/butler-inbox-item-repository.d.ts +15 -0
  125. package/dist/server/storage/repositories/butler-inbox-item-repository.js +111 -0
  126. package/dist/server/storage/repositories/butler-inbox-item-repository.js.map +1 -0
  127. package/dist/server/storage/repositories/butler-notification-archive-repository.d.ts +9 -0
  128. package/dist/server/storage/repositories/butler-notification-archive-repository.js +48 -0
  129. package/dist/server/storage/repositories/butler-notification-archive-repository.js.map +1 -0
  130. package/dist/server/storage/repositories/butler-profile-repository.d.ts +9 -0
  131. package/dist/server/storage/repositories/butler-profile-repository.js +86 -0
  132. package/dist/server/storage/repositories/butler-profile-repository.js.map +1 -0
  133. package/dist/server/storage/repositories/butler-project-repository.d.ts +14 -0
  134. package/dist/server/storage/repositories/butler-project-repository.js +140 -0
  135. package/dist/server/storage/repositories/butler-project-repository.js.map +1 -0
  136. package/dist/server/storage/repositories/butler-session-repository.d.ts +11 -0
  137. package/dist/server/storage/repositories/butler-session-repository.js +106 -0
  138. package/dist/server/storage/repositories/butler-session-repository.js.map +1 -0
  139. package/dist/server/storage/repositories/butler-session-summary-state-repository.d.ts +8 -0
  140. package/dist/server/storage/repositories/butler-session-summary-state-repository.js +62 -0
  141. package/dist/server/storage/repositories/butler-session-summary-state-repository.js.map +1 -0
  142. package/dist/server/storage/repositories/patrol-plan-repository.d.ts +27 -0
  143. package/dist/server/storage/repositories/patrol-plan-repository.js +119 -0
  144. package/dist/server/storage/repositories/patrol-plan-repository.js.map +1 -0
  145. package/dist/server/storage/repositories/patrol-run-repository.d.ts +28 -0
  146. package/dist/server/storage/repositories/patrol-run-repository.js +121 -0
  147. package/dist/server/storage/repositories/patrol-run-repository.js.map +1 -0
  148. package/dist/server/storage/repositories/project-memory-repository.d.ts +15 -0
  149. package/dist/server/storage/repositories/project-memory-repository.js +150 -0
  150. package/dist/server/storage/repositories/project-memory-repository.js.map +1 -0
  151. package/dist/server/storage/repositories/session-checkpoint-repository.d.ts +9 -0
  152. package/dist/server/storage/repositories/session-checkpoint-repository.js +72 -0
  153. package/dist/server/storage/repositories/session-checkpoint-repository.js.map +1 -0
  154. package/dist/server/storage/repositories/session-message-origin-repository.d.ts +10 -0
  155. package/dist/server/storage/repositories/session-message-origin-repository.js +93 -0
  156. package/dist/server/storage/repositories/session-message-origin-repository.js.map +1 -0
  157. package/dist/server/storage/repositories/verification-run-repository.d.ts +29 -0
  158. package/dist/server/storage/repositories/verification-run-repository.js +125 -0
  159. package/dist/server/storage/repositories/verification-run-repository.js.map +1 -0
  160. package/dist/server/storage/sqlite/client.js +39 -0
  161. package/dist/server/storage/sqlite/client.js.map +1 -1
  162. package/dist/server/storage/sqlite/schema.sql +324 -0
  163. package/dist/server/types/domain.d.ts +261 -1
  164. package/dist/server/ws/ws-server.d.ts +2 -1
  165. package/dist/server/ws/ws-server.js +2 -1
  166. package/dist/server/ws/ws-server.js.map +1 -1
  167. package/node_modules/@codingns/session-sync-core/dist/index.d.ts +4 -0
  168. package/node_modules/@codingns/session-sync-core/dist/index.js +4 -0
  169. package/node_modules/@codingns/session-sync-core/dist/index.js.map +1 -1
  170. package/node_modules/@codingns/session-sync-core/dist/kimi-message-normalizer.d.ts +18 -0
  171. package/node_modules/@codingns/session-sync-core/dist/kimi-message-normalizer.js +659 -0
  172. package/node_modules/@codingns/session-sync-core/dist/kimi-message-normalizer.js.map +1 -0
  173. package/node_modules/@codingns/session-sync-core/dist/kimi-shared.d.ts +11 -0
  174. package/node_modules/@codingns/session-sync-core/dist/kimi-shared.js +72 -0
  175. package/node_modules/@codingns/session-sync-core/dist/kimi-shared.js.map +1 -0
  176. package/node_modules/@codingns/session-sync-core/dist/patch-builder.d.ts +8 -0
  177. package/node_modules/@codingns/session-sync-core/dist/patch-builder.js +89 -0
  178. package/node_modules/@codingns/session-sync-core/dist/patch-builder.js.map +1 -1
  179. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +4 -1
  180. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
  181. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.d.ts +41 -0
  182. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js +1086 -0
  183. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js.map +1 -0
  184. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.d.ts +29 -0
  185. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js +578 -0
  186. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js.map +1 -0
  187. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +2 -1
  188. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
  189. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js +30 -2
  190. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js.map +1 -1
  191. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.d.ts +2 -0
  192. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js +43 -5
  193. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js.map +1 -1
  194. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.d.ts +2 -0
  195. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +320 -69
  196. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
  197. package/node_modules/@codingns/session-sync-core/dist/runtime/gemini-runtime.d.ts +21 -0
  198. package/node_modules/@codingns/session-sync-core/dist/runtime/gemini-runtime.js +537 -0
  199. package/node_modules/@codingns/session-sync-core/dist/runtime/gemini-runtime.js.map +1 -0
  200. package/node_modules/@codingns/session-sync-core/dist/runtime/kimi-runtime.d.ts +38 -0
  201. package/node_modules/@codingns/session-sync-core/dist/runtime/kimi-runtime.js +911 -0
  202. package/node_modules/@codingns/session-sync-core/dist/runtime/kimi-runtime.js.map +1 -0
  203. package/node_modules/@codingns/session-sync-core/dist/sqlite/node-sqlite.d.ts +6 -0
  204. package/node_modules/@codingns/session-sync-core/dist/sqlite/node-sqlite.js +9 -0
  205. package/node_modules/@codingns/session-sync-core/dist/sqlite/node-sqlite.js.map +1 -0
  206. package/node_modules/@codingns/session-sync-core/package.json +8 -0
  207. package/package.json +1 -1
  208. package/dist/public/assets/index-C5lu52cQ.css +0 -1
  209. package/dist/public/assets/index-WpdUo_Vs.js +0 -108
@@ -0,0 +1,1086 @@
1
+ import { execFile as nodeExecFile } from "node:child_process";
2
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
3
+ import { basename, dirname, extname, join } from "node:path";
4
+ import { promisify } from "node:util";
5
+ import { buildApplyPatchFromStructuredFileTool } from "../patch-builder.js";
6
+ import { ensureText, extractTextBlocks, messageIdFromRawRef, nextTimestamp, normalizeWorkspacePath, sliceHistory, stringifyStructuredValue } from "./utils.js";
7
+ const execFile = promisify(nodeExecFile);
8
+ const GEMINI_RAW_STORE_PREFIX = "gemini://session/";
9
+ const DEFAULT_GEMINI_TITLE_LENGTH = 48;
10
+ export class GeminiAdapter {
11
+ options;
12
+ providerId = "gemini";
13
+ constructor(options) {
14
+ this.options = options;
15
+ }
16
+ async detectSessions(workspacePath, options) {
17
+ const discovery = await this.detectSessionsDetailed(workspacePath, options);
18
+ return discovery.sessions;
19
+ }
20
+ async detectSessionsDetailed(workspacePath, options) {
21
+ const targetPath = normalizeWorkspacePath(workspacePath);
22
+ const knownSessions = (options?.knownSessions ?? []).filter((session) => session.provider === this.providerId);
23
+ const knownByProviderSessionId = new Map(knownSessions.map((session) => [session.providerSessionId, session]));
24
+ const localSessions = this.readLocalSessions();
25
+ const cliResult = await this.readCliSessions(workspacePath);
26
+ const mergedByProviderSessionId = new Map();
27
+ for (const localSession of localSessions) {
28
+ const known = knownByProviderSessionId.get(localSession.providerSessionId);
29
+ const resolvedWorkspacePath = localSession.workspacePath || ensureNonEmptyText(known?.workspacePath);
30
+ if (!this.matchesWorkspace(resolvedWorkspacePath, targetPath)) {
31
+ continue;
32
+ }
33
+ mergedByProviderSessionId.set(localSession.providerSessionId, {
34
+ provider: this.providerId,
35
+ providerSessionId: localSession.providerSessionId,
36
+ title: localSession.title,
37
+ workspacePath: resolvedWorkspacePath || workspacePath,
38
+ rawStoreRef: buildGeminiRawStoreRef(localSession.providerSessionId),
39
+ lastMessageAt: localSession.lastMessageAt ?? known?.lastMessageAt ?? null,
40
+ messageCount: localSession.messageCount,
41
+ sourceMtimeMs: localSession.sourceMtimeMs,
42
+ sourceSizeBytes: localSession.sourceSizeBytes
43
+ });
44
+ }
45
+ for (const cliSession of cliResult.sessions) {
46
+ const known = knownByProviderSessionId.get(cliSession.providerSessionId);
47
+ const local = mergedByProviderSessionId.get(cliSession.providerSessionId);
48
+ const resolvedWorkspacePath = cliSession.workspacePath ||
49
+ local?.workspacePath ||
50
+ ensureNonEmptyText(known?.workspacePath);
51
+ if (!this.matchesWorkspace(resolvedWorkspacePath, targetPath)) {
52
+ continue;
53
+ }
54
+ mergedByProviderSessionId.set(cliSession.providerSessionId, {
55
+ provider: this.providerId,
56
+ providerSessionId: cliSession.providerSessionId,
57
+ title: local?.title ||
58
+ cliSession.title ||
59
+ known?.title ||
60
+ cliSession.providerSessionId,
61
+ workspacePath: resolvedWorkspacePath || workspacePath,
62
+ rawStoreRef: buildGeminiRawStoreRef(cliSession.providerSessionId),
63
+ lastMessageAt: local?.lastMessageAt ??
64
+ cliSession.lastMessageAt ??
65
+ known?.lastMessageAt ??
66
+ null,
67
+ messageCount: local?.messageCount ??
68
+ cliSession.messageCount ??
69
+ known?.messageCount ??
70
+ 0,
71
+ sourceMtimeMs: local?.sourceMtimeMs ?? known?.sourceMtimeMs,
72
+ sourceSizeBytes: local?.sourceSizeBytes ?? known?.sourceSizeBytes
73
+ });
74
+ }
75
+ // 当 CLI 临时失败时,用 knownSessions 补回最近一次已发现的会话,避免列表突然丢失。
76
+ if (!cliResult.isComplete) {
77
+ for (const known of knownSessions) {
78
+ if (mergedByProviderSessionId.has(known.providerSessionId)) {
79
+ continue;
80
+ }
81
+ if (!this.matchesWorkspace(known.workspacePath, targetPath)) {
82
+ continue;
83
+ }
84
+ mergedByProviderSessionId.set(known.providerSessionId, known);
85
+ }
86
+ }
87
+ return {
88
+ sessions: [...mergedByProviderSessionId.values()].sort((left, right) => (right.lastMessageAt ?? "").localeCompare(left.lastMessageAt ?? "")),
89
+ isComplete: cliResult.isComplete
90
+ };
91
+ }
92
+ async readSessionHistory(providerSessionId, rawStoreRef, cursor, limit, direction = "forward") {
93
+ const resolvedProviderSessionId = this.resolveProviderSessionId(providerSessionId, rawStoreRef);
94
+ const parsedChat = this.readParsedChatBySessionId(resolvedProviderSessionId);
95
+ return sliceHistory(parsedChat.messages, cursor, limit, direction);
96
+ }
97
+ subscribeSession(providerSessionId, rawStoreRef, cursor, limit, onEvent) {
98
+ const sessionRef = this.resolveSessionRef(providerSessionId, rawStoreRef);
99
+ let currentCursor = cursor;
100
+ let lastSeenSignature = "";
101
+ let closed = false;
102
+ const timer = setInterval(() => {
103
+ if (closed || !sessionRef.providerSessionId) {
104
+ return;
105
+ }
106
+ let page;
107
+ try {
108
+ page = this.readSessionHistorySync(sessionRef.providerSessionId, rawStoreRef, currentCursor, limit);
109
+ }
110
+ catch {
111
+ return;
112
+ }
113
+ if (page.messages.length === 0) {
114
+ return;
115
+ }
116
+ const signature = `${page.messages.at(-1)?.messageId ?? ""}:${page.cursor ?? ""}`;
117
+ if (signature === lastSeenSignature) {
118
+ return;
119
+ }
120
+ lastSeenSignature = signature;
121
+ currentCursor = page.cursor;
122
+ void onEvent({
123
+ messages: page.messages,
124
+ cursor: page.cursor
125
+ });
126
+ }, 700);
127
+ return {
128
+ close() {
129
+ closed = true;
130
+ clearInterval(timer);
131
+ }
132
+ };
133
+ }
134
+ async resumeSession(providerSessionId, rawStoreRef) {
135
+ const resolvedProviderSessionId = this.resolveProviderSessionId(providerSessionId, rawStoreRef);
136
+ this.readParsedChatBySessionId(resolvedProviderSessionId);
137
+ return {
138
+ provider: this.providerId,
139
+ providerSessionId: resolvedProviderSessionId,
140
+ resumedAt: nextTimestamp(),
141
+ rawStoreRef: buildGeminiRawStoreRef(resolvedProviderSessionId)
142
+ };
143
+ }
144
+ async startSession(_workspacePath, _options) {
145
+ throw new Error("GEMINI_READ_ONLY_PROVIDER");
146
+ }
147
+ async sendMessage(_providerSessionId, _rawStoreRef, _content, _clientRequestId, _permissionMode) {
148
+ throw new Error("GEMINI_READ_ONLY_PROVIDER");
149
+ }
150
+ async readSessionTitle(providerSessionId, rawStoreRef) {
151
+ const resolvedProviderSessionId = this.resolveProviderSessionId(providerSessionId, rawStoreRef);
152
+ const parsedChat = this.readParsedChatBySessionId(resolvedProviderSessionId);
153
+ return parsedChat.title;
154
+ }
155
+ async renameSessionTitle(_providerSessionId, _rawStoreRef, _title) {
156
+ throw new Error("GEMINI_READ_ONLY_PROVIDER");
157
+ }
158
+ async updateSessionArchiveState(_providerSessionId, _rawStoreRef, _isArchived) {
159
+ throw new Error("GEMINI_ARCHIVE_NOT_SUPPORTED");
160
+ }
161
+ getProviderCapabilities() {
162
+ return {
163
+ provider: this.providerId,
164
+ canStartSession: true,
165
+ canResumeSession: true,
166
+ canSendMessage: true,
167
+ inRunInputMode: "none",
168
+ supportsSubagents: false,
169
+ supportsInterrupt: true,
170
+ supportsStructuredToolCalls: true,
171
+ supportsTokenUsage: false,
172
+ supportsAttachments: false,
173
+ supportsPermissionPrompt: false,
174
+ supportsCheckpoint: false,
175
+ limitations: [
176
+ "当前 Gemini 仅接入会话发现与历史只读能力,运行时链路尚未启用",
177
+ "本地 chats schema 属于非稳定公开协议,升级 CLI 后需要通过 fixture 回归"
178
+ ]
179
+ };
180
+ }
181
+ async getSessionCapabilities(_providerSessionId) {
182
+ return this.getProviderCapabilities();
183
+ }
184
+ readSessionHistorySync(providerSessionId, rawStoreRef, cursor, limit, direction = "forward") {
185
+ const resolvedProviderSessionId = this.resolveProviderSessionId(providerSessionId, rawStoreRef);
186
+ const parsedChat = this.readParsedChatBySessionId(resolvedProviderSessionId);
187
+ return sliceHistory(parsedChat.messages, cursor, limit, direction);
188
+ }
189
+ resolveProviderSessionId(providerSessionId, rawStoreRef) {
190
+ const sessionRef = this.resolveSessionRef(providerSessionId, rawStoreRef);
191
+ if (!sessionRef.providerSessionId) {
192
+ throw new Error("PROVIDER_SESSION_ID_REQUIRED");
193
+ }
194
+ return sessionRef.providerSessionId;
195
+ }
196
+ resolveSessionRef(providerSessionId, rawStoreRef) {
197
+ const trimmedProviderSessionId = providerSessionId.trim();
198
+ if (trimmedProviderSessionId) {
199
+ return {
200
+ providerSessionId: trimmedProviderSessionId,
201
+ fromRawStoreRef: false
202
+ };
203
+ }
204
+ return {
205
+ providerSessionId: parseGeminiRawStoreRef(rawStoreRef),
206
+ fromRawStoreRef: true
207
+ };
208
+ }
209
+ async readCliSessions(workspacePath) {
210
+ try {
211
+ const sessions = this.options.listSessions
212
+ ? await this.options.listSessions()
213
+ : await this.readCliSessionsFromCommand(workspacePath);
214
+ return {
215
+ sessions,
216
+ isComplete: true
217
+ };
218
+ }
219
+ catch {
220
+ return {
221
+ sessions: [],
222
+ isComplete: false
223
+ };
224
+ }
225
+ }
226
+ async readCliSessionsFromCommand(workspacePath) {
227
+ const commandPath = this.options.commandPath?.trim() || "gemini";
228
+ const env = {
229
+ ...process.env,
230
+ GEMINI_HOME: this.options.homeDir
231
+ };
232
+ const attempts = [
233
+ ["--list-sessions", "--output-format", "json"],
234
+ ["--list-sessions"]
235
+ ];
236
+ let lastError = null;
237
+ for (const args of attempts) {
238
+ try {
239
+ const result = await execFile(commandPath, args, {
240
+ env,
241
+ cwd: workspacePath,
242
+ timeout: 8_000,
243
+ windowsHide: true,
244
+ shell: shouldUseShellForCommand(commandPath)
245
+ });
246
+ return parseGeminiCliSessionOutput(result.stdout, workspacePath);
247
+ }
248
+ catch (error) {
249
+ lastError = error;
250
+ }
251
+ }
252
+ throw lastError ?? new Error("GEMINI_LIST_SESSIONS_FAILED");
253
+ }
254
+ readLocalSessions() {
255
+ const sessions = [];
256
+ const latestByProviderSessionId = new Map();
257
+ for (const filePath of listGeminiChatFiles(this.options.homeDir)) {
258
+ let parsedChat;
259
+ try {
260
+ parsedChat = this.parseLocalChatFile(filePath);
261
+ }
262
+ catch {
263
+ continue;
264
+ }
265
+ const existing = latestByProviderSessionId.get(parsedChat.providerSessionId);
266
+ if (existing &&
267
+ (existing.sourceMtimeMs > parsedChat.sourceMtimeMs ||
268
+ (existing.sourceMtimeMs === parsedChat.sourceMtimeMs &&
269
+ existing.sourceSizeBytes >= parsedChat.sourceSizeBytes))) {
270
+ continue;
271
+ }
272
+ latestByProviderSessionId.set(parsedChat.providerSessionId, {
273
+ providerSessionId: parsedChat.providerSessionId,
274
+ workspacePath: parsedChat.workspacePath,
275
+ title: parsedChat.title,
276
+ lastMessageAt: parsedChat.lastMessageAt,
277
+ messageCount: parsedChat.messages.length,
278
+ filePath,
279
+ sourceMtimeMs: parsedChat.sourceMtimeMs,
280
+ sourceSizeBytes: parsedChat.sourceSizeBytes
281
+ });
282
+ }
283
+ sessions.push(...latestByProviderSessionId.values());
284
+ return sessions;
285
+ }
286
+ readParsedChatBySessionId(providerSessionId) {
287
+ const sessionId = providerSessionId.trim();
288
+ if (!sessionId) {
289
+ throw new Error("PROVIDER_SESSION_ID_REQUIRED");
290
+ }
291
+ const chatFiles = listGeminiChatFiles(this.options.homeDir);
292
+ let matchedByName = null;
293
+ for (const filePath of chatFiles) {
294
+ if (basename(filePath, ".json") === sessionId) {
295
+ matchedByName = filePath;
296
+ }
297
+ let parsed;
298
+ try {
299
+ parsed = this.parseLocalChatFile(filePath);
300
+ }
301
+ catch (error) {
302
+ if (basename(filePath, ".json") === sessionId) {
303
+ throw wrapGeminiSchemaError(filePath, error);
304
+ }
305
+ continue;
306
+ }
307
+ if (parsed.providerSessionId === sessionId) {
308
+ return parsed;
309
+ }
310
+ }
311
+ if (matchedByName) {
312
+ try {
313
+ return this.parseLocalChatFile(matchedByName);
314
+ }
315
+ catch (error) {
316
+ throw wrapGeminiSchemaError(matchedByName, error);
317
+ }
318
+ }
319
+ throw new Error("GEMINI_CHAT_NOT_FOUND");
320
+ }
321
+ parseLocalChatFile(filePath) {
322
+ const stats = statSync(filePath);
323
+ const raw = readFileSync(filePath, "utf8").trim();
324
+ if (!raw) {
325
+ throw new Error("GEMINI_CHAT_SCHEMA_INVALID");
326
+ }
327
+ let parsedRaw;
328
+ try {
329
+ parsedRaw = JSON.parse(raw);
330
+ }
331
+ catch (error) {
332
+ throw wrapGeminiSchemaError(filePath, error);
333
+ }
334
+ const parsedRecord = toRecord(parsedRaw);
335
+ const providerSessionId = this.resolveLocalProviderSessionId(parsedRecord, filePath);
336
+ const messageNodes = readMessageNodes(parsedRecord);
337
+ const messages = normalizeMessageNodes({
338
+ sessionId: providerSessionId,
339
+ filePath,
340
+ messageNodes
341
+ });
342
+ const title = resolveStringField(parsedRecord, ["title", "name", "chatTitle"]) ||
343
+ messages.find((message) => message.role === "user")?.content.slice(0, DEFAULT_GEMINI_TITLE_LENGTH) ||
344
+ providerSessionId;
345
+ const workspacePath = resolveWorkspacePath(parsedRecord, messageNodes, filePath);
346
+ const lastMessageAt = messages.at(-1)?.timestamp ||
347
+ resolveStringField(parsedRecord, [
348
+ "updatedAt",
349
+ "updated_at",
350
+ "lastUpdated",
351
+ "last_updated",
352
+ "lastMessageAt",
353
+ "last_message_at",
354
+ "startTime",
355
+ "start_time",
356
+ "createdAt",
357
+ "created_at"
358
+ ]) ||
359
+ null;
360
+ return {
361
+ providerSessionId,
362
+ workspacePath,
363
+ title,
364
+ lastMessageAt,
365
+ messages,
366
+ sourceMtimeMs: stats.mtimeMs,
367
+ sourceSizeBytes: stats.size
368
+ };
369
+ }
370
+ resolveLocalProviderSessionId(record, filePath) {
371
+ const sessionId = resolveStringField(record, [
372
+ "sessionId",
373
+ "session_id",
374
+ "id",
375
+ "chatId",
376
+ "conversationId",
377
+ "conversation_id"
378
+ ]) || basename(filePath, ".json");
379
+ if (!sessionId.trim()) {
380
+ throw new Error("GEMINI_CHAT_SCHEMA_INVALID");
381
+ }
382
+ return sessionId.trim();
383
+ }
384
+ matchesWorkspace(workspacePath, targetPath) {
385
+ const normalizedWorkspacePath = normalizeWorkspacePath(workspacePath ?? "");
386
+ if (!normalizedWorkspacePath) {
387
+ return false;
388
+ }
389
+ return normalizedWorkspacePath === targetPath;
390
+ }
391
+ }
392
+ function parseGeminiRawStoreRef(rawStoreRef) {
393
+ const trimmed = rawStoreRef.trim();
394
+ if (!trimmed.startsWith(GEMINI_RAW_STORE_PREFIX)) {
395
+ return null;
396
+ }
397
+ const rawSessionId = trimmed.slice(GEMINI_RAW_STORE_PREFIX.length).split(/[?#]/, 1)[0];
398
+ return rawSessionId ? decodeURIComponent(rawSessionId) : null;
399
+ }
400
+ function buildGeminiRawStoreRef(providerSessionId) {
401
+ return `${GEMINI_RAW_STORE_PREFIX}${encodeURIComponent(providerSessionId)}`;
402
+ }
403
+ function listGeminiChatFiles(homeDir) {
404
+ const tmpRoot = join(homeDir, "tmp");
405
+ if (!existsSync(tmpRoot)) {
406
+ return [];
407
+ }
408
+ const queue = [tmpRoot];
409
+ const chatFiles = [];
410
+ while (queue.length > 0) {
411
+ const current = queue.shift();
412
+ if (!current) {
413
+ continue;
414
+ }
415
+ const entries = readdirSync(current, { withFileTypes: true });
416
+ for (const entry of entries) {
417
+ const entryPath = join(current, entry.name);
418
+ if (entry.isDirectory()) {
419
+ queue.push(entryPath);
420
+ continue;
421
+ }
422
+ if (!entry.isFile() || !entry.name.endsWith(".json")) {
423
+ continue;
424
+ }
425
+ if (!isGeminiChatFile(entryPath)) {
426
+ continue;
427
+ }
428
+ chatFiles.push(entryPath);
429
+ }
430
+ }
431
+ return chatFiles;
432
+ }
433
+ function isGeminiChatFile(filePath) {
434
+ return filePath.replaceAll("\\", "/").includes("/chats/");
435
+ }
436
+ function parseGeminiCliSessionOutput(stdout, workspacePathFallback = null) {
437
+ const trimmed = stdout.trim();
438
+ if (!trimmed) {
439
+ return [];
440
+ }
441
+ const parsedAsWhole = parseJsonSafe(trimmed);
442
+ const normalizedWhole = normalizeCliSessionsPayload(parsedAsWhole, workspacePathFallback);
443
+ if (normalizedWhole.length > 0) {
444
+ return normalizedWhole;
445
+ }
446
+ const normalizedText = parseGeminiPlainTextSessions(trimmed, workspacePathFallback);
447
+ if (normalizedText.length > 0) {
448
+ return normalizedText;
449
+ }
450
+ const sessions = [];
451
+ for (const line of trimmed.split(/\r?\n/)) {
452
+ const parsedLine = parseJsonSafe(line.trim());
453
+ if (!parsedLine) {
454
+ continue;
455
+ }
456
+ sessions.push(...normalizeCliSessionsPayload(parsedLine, workspacePathFallback));
457
+ }
458
+ return dedupeCliSessions(sessions);
459
+ }
460
+ function normalizeCliSessionsPayload(payload, workspacePathFallback) {
461
+ if (!payload) {
462
+ return [];
463
+ }
464
+ if (Array.isArray(payload)) {
465
+ return dedupeCliSessions(payload
466
+ .map((item) => normalizeCliSessionRecord(item, workspacePathFallback))
467
+ .filter((item) => item !== null));
468
+ }
469
+ const record = toRecord(payload);
470
+ const wrappedArray = arrayFromUnknown(record.sessions) ||
471
+ arrayFromUnknown(record.items) ||
472
+ arrayFromUnknown(record.data);
473
+ if (wrappedArray) {
474
+ return dedupeCliSessions(wrappedArray
475
+ .map((item) => normalizeCliSessionRecord(item, workspacePathFallback))
476
+ .filter((item) => item !== null));
477
+ }
478
+ const single = normalizeCliSessionRecord(record, workspacePathFallback);
479
+ return single ? [single] : [];
480
+ }
481
+ function normalizeCliSessionRecord(payload, workspacePathFallback) {
482
+ const record = toRecord(payload);
483
+ const providerSessionId = resolveStringField(record, [
484
+ "sessionId",
485
+ "session_id",
486
+ "id",
487
+ "conversationId",
488
+ "conversation_id"
489
+ ]);
490
+ if (!providerSessionId) {
491
+ return null;
492
+ }
493
+ const messageCountValue = resolveNumberField(record, ["messageCount", "message_count"]);
494
+ return {
495
+ providerSessionId,
496
+ workspacePath: resolveStringField(record, [
497
+ "workspacePath",
498
+ "workspace_path",
499
+ "cwd",
500
+ "directory",
501
+ "projectPath",
502
+ "project_path"
503
+ ]) || workspacePathFallback,
504
+ title: resolveStringField(record, ["title", "name", "summary"]) || null,
505
+ lastMessageAt: resolveStringField(record, [
506
+ "updatedAt",
507
+ "updated_at",
508
+ "lastUpdated",
509
+ "last_updated",
510
+ "lastMessageAt",
511
+ "last_message_at",
512
+ "startTime",
513
+ "start_time",
514
+ "createdAt",
515
+ "created_at"
516
+ ]) || null,
517
+ messageCount: messageCountValue === null ? null : messageCountValue
518
+ };
519
+ }
520
+ function dedupeCliSessions(sessions) {
521
+ const deduped = new Map();
522
+ for (const session of sessions) {
523
+ const existing = deduped.get(session.providerSessionId);
524
+ if (!existing ||
525
+ (existing.lastMessageAt ?? "").localeCompare(session.lastMessageAt ?? "") < 0) {
526
+ deduped.set(session.providerSessionId, session);
527
+ }
528
+ }
529
+ return [...deduped.values()];
530
+ }
531
+ function resolveWorkspacePath(record, messageNodes, filePath) {
532
+ const directWorkspace = resolveStringField(record, [
533
+ "workspacePath",
534
+ "workspace_path",
535
+ "cwd",
536
+ "projectPath",
537
+ "project_path"
538
+ ]);
539
+ if (directWorkspace) {
540
+ return directWorkspace;
541
+ }
542
+ for (const node of messageNodes) {
543
+ const nodeRecord = toRecord(node);
544
+ const workspace = resolveStringField(nodeRecord, [
545
+ "workspacePath",
546
+ "workspace_path",
547
+ "cwd",
548
+ "projectPath",
549
+ "project_path"
550
+ ]);
551
+ if (workspace) {
552
+ return workspace;
553
+ }
554
+ }
555
+ return resolveWorkspacePathFromChatFile(filePath);
556
+ }
557
+ function readMessageNodes(record) {
558
+ const candidates = [
559
+ record.messages,
560
+ record.history,
561
+ record.events,
562
+ record.contents,
563
+ record.turns,
564
+ toRecord(record.chat).messages,
565
+ toRecord(record.conversation).messages,
566
+ toRecord(record.transcript).messages
567
+ ];
568
+ for (const candidate of candidates) {
569
+ const array = arrayFromUnknown(candidate);
570
+ if (array && array.length > 0) {
571
+ return array;
572
+ }
573
+ }
574
+ return [];
575
+ }
576
+ function normalizeMessageNodes(input) {
577
+ const messages = [];
578
+ let sequence = 0;
579
+ for (let index = 0; index < input.messageNodes.length; index += 1) {
580
+ const node = input.messageNodes[index];
581
+ const nodeRecord = toRecord(node);
582
+ const role = resolveGeminiRole(nodeRecord);
583
+ const timestamp = resolveMessageTimestamp(nodeRecord);
584
+ const descriptors = readMessageDescriptors(nodeRecord, role);
585
+ if (descriptors.length === 0) {
586
+ continue;
587
+ }
588
+ for (let descriptorIndex = 0; descriptorIndex < descriptors.length; descriptorIndex += 1) {
589
+ const descriptor = descriptors[descriptorIndex];
590
+ sequence += 1;
591
+ const rawRef = buildGeminiMessageRawRef(input.sessionId, input.filePath, index, descriptorIndex);
592
+ messages.push({
593
+ messageId: messageIdFromRawRef(rawRef),
594
+ provider: "gemini",
595
+ providerSessionId: input.sessionId,
596
+ role: descriptor.role,
597
+ kind: descriptor.kind,
598
+ content: descriptor.content,
599
+ toolCall: descriptor.toolCall,
600
+ timestamp: descriptor.timestamp ?? timestamp,
601
+ sequence,
602
+ rawRef
603
+ });
604
+ }
605
+ }
606
+ return messages;
607
+ }
608
+ function readMessageDescriptors(nodeRecord, fallbackRole) {
609
+ const descriptors = [];
610
+ descriptors.push(...readGeminiThoughtDescriptors(nodeRecord));
611
+ descriptors.push(...readGeminiToolCallDescriptors(nodeRecord));
612
+ const parts = arrayFromUnknown(nodeRecord.parts) ||
613
+ arrayFromUnknown(nodeRecord.content) ||
614
+ arrayFromUnknown(toRecord(nodeRecord.message).parts) ||
615
+ null;
616
+ const partDescriptors = [];
617
+ if (parts && parts.length > 0) {
618
+ for (const part of parts) {
619
+ const descriptor = normalizeMessagePart(part, fallbackRole);
620
+ if (descriptor) {
621
+ partDescriptors.push(descriptor);
622
+ }
623
+ }
624
+ }
625
+ if (partDescriptors.length > 0) {
626
+ descriptors.push(...partDescriptors);
627
+ return descriptors;
628
+ }
629
+ const fallbackDescriptor = normalizeMessagePart(nodeRecord, fallbackRole);
630
+ if (fallbackDescriptor) {
631
+ descriptors.push(fallbackDescriptor);
632
+ }
633
+ return descriptors;
634
+ }
635
+ function normalizeMessagePart(value, fallbackRole) {
636
+ const record = toRecord(value);
637
+ const toolCallPayload = maybeRecord(record.tool_use) ??
638
+ maybeRecord(record.toolUse) ??
639
+ maybeRecord(record.functionCall) ??
640
+ (record.type === "tool_use" || record.type === "function_call" ? record : null);
641
+ if (toolCallPayload) {
642
+ const timestamp = resolveOptionalMessageTimestamp(record);
643
+ const patchText = buildGeminiApplyPatchFromInput(toolCallPayload);
644
+ const callId = resolveStringField(toolCallPayload, ["id", "toolCallId", "call_id"]) || "gemini-call";
645
+ const name = patchText
646
+ ? "apply_patch"
647
+ : resolveStringField(toolCallPayload, ["name", "toolName", "tool_name"]) || "unknown_tool";
648
+ const inputPayload = toolCallPayload.input ??
649
+ toolCallPayload.arguments ??
650
+ toolCallPayload.args ??
651
+ null;
652
+ const inputText = patchText || stringifyStructuredValue(inputPayload);
653
+ return {
654
+ role: patchText ? "tool" : "assistant",
655
+ kind: "tool_call",
656
+ content: inputText,
657
+ toolCall: {
658
+ callId,
659
+ name,
660
+ input: inputText,
661
+ output: null,
662
+ error: null,
663
+ status: "running"
664
+ },
665
+ timestamp
666
+ };
667
+ }
668
+ const toolResultPayload = maybeRecord(record.tool_result) ??
669
+ maybeRecord(record.toolResult) ??
670
+ maybeRecord(record.functionResponse) ??
671
+ (record.type === "tool_result" || record.type === "function_response" ? record : null);
672
+ if (toolResultPayload) {
673
+ const timestamp = resolveOptionalMessageTimestamp(record);
674
+ const errorDetail = resolveStringField(toolResultPayload, ["error", "error_message"]);
675
+ const outputPayload = toolResultPayload.output ??
676
+ toolResultPayload.result ??
677
+ toolResultPayload.content ??
678
+ null;
679
+ const normalizedName = resolveStringField(toolResultPayload, [
680
+ "name",
681
+ "tool_name",
682
+ "toolName"
683
+ ]) || "unknown_tool";
684
+ const name = isGeminiEditableToolName(normalizedName) ? "apply_patch" : normalizedName;
685
+ return {
686
+ role: "tool",
687
+ kind: "tool_result",
688
+ content: extractTextBlocks(outputPayload).trim() || stringifyStructuredValue(outputPayload),
689
+ toolCall: {
690
+ callId: resolveStringField(toolResultPayload, [
691
+ "tool_use_id",
692
+ "toolUseId",
693
+ "call_id",
694
+ "callId"
695
+ ]) || "gemini-tool-result",
696
+ name,
697
+ input: "",
698
+ output: stringifyStructuredValue(outputPayload),
699
+ error: errorDetail,
700
+ status: errorDetail ? "failed" : "completed"
701
+ },
702
+ timestamp
703
+ };
704
+ }
705
+ const text = extractTextBlocks(record.text ??
706
+ record.content ??
707
+ record.output ??
708
+ record.message ??
709
+ value).trim();
710
+ if (!text) {
711
+ return null;
712
+ }
713
+ const partType = ensureText(record.type).toLowerCase();
714
+ const kind = partType.includes("think") ? "thinking" : "text";
715
+ return {
716
+ role: fallbackRole,
717
+ kind,
718
+ content: text,
719
+ toolCall: null,
720
+ timestamp: resolveOptionalMessageTimestamp(record)
721
+ };
722
+ }
723
+ function readGeminiThoughtDescriptors(nodeRecord) {
724
+ const thoughts = arrayFromUnknown(nodeRecord.thoughts) ??
725
+ arrayFromUnknown(nodeRecord.reasoning) ??
726
+ null;
727
+ if (!thoughts || thoughts.length === 0) {
728
+ return [];
729
+ }
730
+ return thoughts
731
+ .map((thought) => normalizeGeminiThoughtDescriptor(thought))
732
+ .filter((descriptor) => descriptor !== null);
733
+ }
734
+ function normalizeGeminiThoughtDescriptor(value) {
735
+ const record = toRecord(value);
736
+ const subject = resolveStringField(record, ["subject", "title", "name"]);
737
+ const description = resolveStringField(record, [
738
+ "description",
739
+ "content",
740
+ "text",
741
+ "message"
742
+ ]);
743
+ const content = [subject, description].filter((item) => Boolean(item)).join("\n\n").trim();
744
+ if (!content) {
745
+ return null;
746
+ }
747
+ return {
748
+ role: "assistant",
749
+ kind: "thinking",
750
+ content,
751
+ toolCall: null,
752
+ timestamp: resolveOptionalMessageTimestamp(record)
753
+ };
754
+ }
755
+ function readGeminiToolCallDescriptors(nodeRecord) {
756
+ const toolCalls = arrayFromUnknown(nodeRecord.toolCalls) ??
757
+ arrayFromUnknown(nodeRecord.tool_calls) ??
758
+ null;
759
+ if (!toolCalls || toolCalls.length === 0) {
760
+ return [];
761
+ }
762
+ const descriptors = [];
763
+ for (let index = 0; index < toolCalls.length; index += 1) {
764
+ const descriptorSet = normalizeGeminiToolCall(toolCalls[index], index);
765
+ if (descriptorSet.call) {
766
+ descriptors.push(descriptorSet.call);
767
+ }
768
+ if (descriptorSet.result) {
769
+ descriptors.push(descriptorSet.result);
770
+ }
771
+ }
772
+ return descriptors;
773
+ }
774
+ function normalizeGeminiToolCall(value, index) {
775
+ const record = toRecord(value);
776
+ const timestamp = resolveOptionalMessageTimestamp(record);
777
+ const patchText = buildGeminiApplyPatchFromInput(record);
778
+ const callId = resolveStringField(record, ["id", "toolCallId", "call_id"]) ||
779
+ `gemini-tool-call-${index + 1}`;
780
+ const rawName = resolveStringField(record, ["name", "toolName", "tool_name", "displayName"]) || "tool";
781
+ const name = patchText ? "apply_patch" : rawName;
782
+ const inputPayload = record.args ?? record.input ?? record.arguments ?? null;
783
+ const inputText = patchText || stringifyStructuredValue(inputPayload);
784
+ const callDescriptor = {
785
+ role: patchText ? "tool" : "assistant",
786
+ kind: "tool_call",
787
+ content: inputText || name,
788
+ toolCall: {
789
+ callId,
790
+ name,
791
+ input: inputText,
792
+ output: null,
793
+ error: null,
794
+ status: "running"
795
+ },
796
+ timestamp
797
+ };
798
+ return {
799
+ call: callDescriptor,
800
+ result: normalizeGeminiToolCallResult(record, callId, name, timestamp)
801
+ };
802
+ }
803
+ function normalizeGeminiToolCallResult(record, callId, name, fallbackTimestamp) {
804
+ const output = extractGeminiToolCallOutput(record);
805
+ const error = extractGeminiToolCallError(record);
806
+ const hasResultPayload = Array.isArray(record.result) ||
807
+ record.result !== undefined ||
808
+ record.output !== undefined ||
809
+ record.resultDisplay !== undefined ||
810
+ error !== null;
811
+ if (!hasResultPayload) {
812
+ return null;
813
+ }
814
+ const status = normalizeGeminiToolCallStatus(record, error);
815
+ const content = output || error || name;
816
+ if (!content.trim()) {
817
+ return null;
818
+ }
819
+ return {
820
+ role: "tool",
821
+ kind: "tool_result",
822
+ content,
823
+ toolCall: {
824
+ callId,
825
+ name,
826
+ input: "",
827
+ output: output || null,
828
+ error,
829
+ status
830
+ },
831
+ timestamp: resolveStringField(record, [
832
+ "timestamp",
833
+ "updatedAt",
834
+ "updated_at",
835
+ "lastUpdated",
836
+ "last_updated"
837
+ ]) || fallbackTimestamp
838
+ };
839
+ }
840
+ function extractGeminiToolCallOutput(record) {
841
+ const resultItems = arrayFromUnknown(record.result) ?? [];
842
+ for (const item of resultItems) {
843
+ const itemRecord = toRecord(item);
844
+ const functionResponse = toRecord(itemRecord.functionResponse);
845
+ const response = toRecord(functionResponse.response);
846
+ const outputText = extractTextBlocks(response.output ??
847
+ itemRecord.output ??
848
+ itemRecord.result ??
849
+ itemRecord.content).trim();
850
+ if (outputText) {
851
+ return outputText;
852
+ }
853
+ }
854
+ const directOutput = extractTextBlocks(record.output ??
855
+ record.result ??
856
+ toRecord(record.resultDisplay).fileDiff ??
857
+ toRecord(record.resultDisplay).newContent).trim();
858
+ return directOutput;
859
+ }
860
+ function extractGeminiToolCallError(record) {
861
+ const directError = resolveStringField(record, [
862
+ "error",
863
+ "error_message",
864
+ "failure",
865
+ "description"
866
+ ]);
867
+ if (directError && normalizeGeminiToolCallStatus(record, null) === "failed") {
868
+ return directError;
869
+ }
870
+ return null;
871
+ }
872
+ function normalizeGeminiToolCallStatus(record, error) {
873
+ if (error) {
874
+ return "failed";
875
+ }
876
+ const normalizedStatus = ensureText(record.status).trim().toLowerCase();
877
+ if (!normalizedStatus || normalizedStatus === "success" || normalizedStatus === "completed") {
878
+ return "completed";
879
+ }
880
+ if (["error", "failed", "failure", "cancelled", "canceled"].includes(normalizedStatus)) {
881
+ return "failed";
882
+ }
883
+ return "completed";
884
+ }
885
+ function buildGeminiApplyPatchFromInput(value) {
886
+ const input = value && typeof value === "object" && !Array.isArray(value)
887
+ ? value
888
+ : null;
889
+ if (!input) {
890
+ return null;
891
+ }
892
+ const candidates = [toRecord(input.input), toRecord(input.arguments), toRecord(input.args), input];
893
+ for (const candidate of candidates) {
894
+ const patchText = buildApplyPatchFromStructuredFileTool(candidate);
895
+ if (patchText) {
896
+ return patchText;
897
+ }
898
+ }
899
+ return null;
900
+ }
901
+ function isGeminiEditableToolName(toolName) {
902
+ const normalized = toolName.trim().toLowerCase();
903
+ return [
904
+ "write_file",
905
+ "writefile",
906
+ "create_file",
907
+ "edit_file",
908
+ "replace",
909
+ "replace_file",
910
+ "update_file",
911
+ "multi_edit",
912
+ "multiedit"
913
+ ].includes(normalized);
914
+ }
915
+ function resolveGeminiRole(record) {
916
+ const candidate = resolveStringField(record, [
917
+ "role",
918
+ "author",
919
+ "sender",
920
+ "source",
921
+ "participant",
922
+ "type"
923
+ ])?.toLowerCase();
924
+ if (!candidate) {
925
+ return "assistant";
926
+ }
927
+ if (candidate.includes("user") || candidate.includes("human")) {
928
+ return "user";
929
+ }
930
+ if (candidate.includes("tool")) {
931
+ return "tool";
932
+ }
933
+ if (candidate.includes("system")) {
934
+ return "system";
935
+ }
936
+ if (candidate.includes("assistant")
937
+ || candidate.includes("model")
938
+ || candidate.includes("gemini")) {
939
+ return "assistant";
940
+ }
941
+ return "assistant";
942
+ }
943
+ function resolveMessageTimestamp(record) {
944
+ const direct = resolveStringField(record, [
945
+ "timestamp",
946
+ "time",
947
+ "createdAt",
948
+ "created_at",
949
+ "updatedAt",
950
+ "updated_at"
951
+ ]);
952
+ if (direct) {
953
+ return direct;
954
+ }
955
+ return nextTimestamp();
956
+ }
957
+ function resolveOptionalMessageTimestamp(record) {
958
+ return resolveStringField(record, [
959
+ "timestamp",
960
+ "time",
961
+ "createdAt",
962
+ "created_at",
963
+ "updatedAt",
964
+ "updated_at",
965
+ "lastUpdated",
966
+ "last_updated"
967
+ ]);
968
+ }
969
+ function buildGeminiMessageRawRef(sessionId, filePath, messageIndex, partIndex) {
970
+ return `${GEMINI_RAW_STORE_PREFIX}${encodeURIComponent(sessionId)}#file=${encodeURIComponent(filePath.replaceAll("\\", "/"))}&index=${messageIndex}&part=${partIndex}`;
971
+ }
972
+ function resolveStringField(record, fieldNames) {
973
+ for (const fieldName of fieldNames) {
974
+ const value = ensureNonEmptyText(record[fieldName]);
975
+ if (value) {
976
+ return value;
977
+ }
978
+ }
979
+ const metadata = toRecord(record.metadata);
980
+ for (const fieldName of fieldNames) {
981
+ const value = ensureNonEmptyText(metadata[fieldName]);
982
+ if (value) {
983
+ return value;
984
+ }
985
+ }
986
+ return null;
987
+ }
988
+ function resolveNumberField(record, fieldNames) {
989
+ for (const fieldName of fieldNames) {
990
+ const value = record[fieldName];
991
+ if (typeof value === "number" && Number.isFinite(value)) {
992
+ return Math.max(0, Math.trunc(value));
993
+ }
994
+ if (typeof value === "string") {
995
+ const parsed = Number.parseInt(value, 10);
996
+ if (Number.isFinite(parsed)) {
997
+ return Math.max(0, parsed);
998
+ }
999
+ }
1000
+ }
1001
+ return null;
1002
+ }
1003
+ function parseGeminiPlainTextSessions(stdout, workspacePathFallback) {
1004
+ const sessions = stdout
1005
+ .split(/\r?\n/)
1006
+ .map((line) => normalizePlainTextCliSessionLine(line, workspacePathFallback))
1007
+ .filter((item) => item !== null);
1008
+ return dedupeCliSessions(sessions);
1009
+ }
1010
+ function normalizePlainTextCliSessionLine(line, workspacePathFallback) {
1011
+ const trimmed = line.trim();
1012
+ if (!trimmed) {
1013
+ return null;
1014
+ }
1015
+ const sessionIdMatch = trimmed.match(/\[([^\]]+)\]\s*$/);
1016
+ if (!sessionIdMatch?.[1]) {
1017
+ return null;
1018
+ }
1019
+ const providerSessionId = sessionIdMatch[1].trim();
1020
+ if (!providerSessionId) {
1021
+ return null;
1022
+ }
1023
+ let prefix = trimmed.slice(0, sessionIdMatch.index).trim();
1024
+ prefix = prefix.replace(/^\d+\.\s*/, "").trim();
1025
+ const timeSuffixMatch = prefix.match(/\(([^()]*)\)\s*$/);
1026
+ const title = (timeSuffixMatch && typeof timeSuffixMatch.index === "number"
1027
+ ? prefix.slice(0, timeSuffixMatch.index)
1028
+ : prefix).trim() || providerSessionId;
1029
+ return {
1030
+ providerSessionId,
1031
+ workspacePath: workspacePathFallback,
1032
+ title,
1033
+ lastMessageAt: null,
1034
+ messageCount: null
1035
+ };
1036
+ }
1037
+ function ensureNonEmptyText(value) {
1038
+ const text = ensureText(value).trim();
1039
+ return text.length > 0 ? text : null;
1040
+ }
1041
+ function parseJsonSafe(value) {
1042
+ if (!value) {
1043
+ return null;
1044
+ }
1045
+ try {
1046
+ return JSON.parse(value);
1047
+ }
1048
+ catch {
1049
+ return null;
1050
+ }
1051
+ }
1052
+ function arrayFromUnknown(value) {
1053
+ return Array.isArray(value) ? value : null;
1054
+ }
1055
+ function toRecord(value) {
1056
+ if (value && typeof value === "object" && !Array.isArray(value)) {
1057
+ return value;
1058
+ }
1059
+ return {};
1060
+ }
1061
+ function maybeRecord(value) {
1062
+ if (value && typeof value === "object" && !Array.isArray(value)) {
1063
+ return value;
1064
+ }
1065
+ return null;
1066
+ }
1067
+ function resolveWorkspacePathFromChatFile(filePath) {
1068
+ const projectRootFile = join(dirname(dirname(filePath)), ".project_root");
1069
+ if (!existsSync(projectRootFile)) {
1070
+ return null;
1071
+ }
1072
+ try {
1073
+ return ensureNonEmptyText(readFileSync(projectRootFile, "utf8"));
1074
+ }
1075
+ catch {
1076
+ return null;
1077
+ }
1078
+ }
1079
+ function shouldUseShellForCommand(commandPath) {
1080
+ return process.platform === "win32" && [".cmd", ".bat"].includes(extname(commandPath).toLowerCase());
1081
+ }
1082
+ function wrapGeminiSchemaError(filePath, error) {
1083
+ const detail = error instanceof Error ? error.message : ensureText(error);
1084
+ return new Error(`GEMINI_CHAT_SCHEMA_INVALID: file=${filePath.replaceAll("\\", "/")} detail=${detail}`);
1085
+ }
1086
+ //# sourceMappingURL=gemini.js.map