@yagr/agent 0.2.12 → 0.2.13

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 (212) hide show
  1. package/dist/agent.d.ts +16 -12
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +40 -33
  4. package/dist/agent.js.map +1 -1
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +107 -15
  8. package/dist/cli.js.map +1 -1
  9. package/dist/config/n8n-config-service.d.ts +16 -0
  10. package/dist/config/n8n-config-service.d.ts.map +1 -1
  11. package/dist/config/n8n-config-service.js +32 -1
  12. package/dist/config/n8n-config-service.js.map +1 -1
  13. package/dist/config/yagr-config-service.d.ts +16 -0
  14. package/dist/config/yagr-config-service.d.ts.map +1 -1
  15. package/dist/config/yagr-config-service.js.map +1 -1
  16. package/dist/engine/engine.d.ts +15 -1
  17. package/dist/engine/engine.d.ts.map +1 -1
  18. package/dist/gateway/cli.d.ts +2 -2
  19. package/dist/gateway/cli.d.ts.map +1 -1
  20. package/dist/gateway/cli.js +6 -3
  21. package/dist/gateway/cli.js.map +1 -1
  22. package/dist/gateway/format-message.d.ts +8 -4
  23. package/dist/gateway/format-message.d.ts.map +1 -1
  24. package/dist/gateway/format-message.js +10 -5
  25. package/dist/gateway/format-message.js.map +1 -1
  26. package/dist/gateway/interactive-ui.d.ts +2 -2
  27. package/dist/gateway/interactive-ui.d.ts.map +1 -1
  28. package/dist/gateway/interactive-ui.js +87 -82
  29. package/dist/gateway/interactive-ui.js.map +1 -1
  30. package/dist/gateway/manager.d.ts +6 -6
  31. package/dist/gateway/manager.d.ts.map +1 -1
  32. package/dist/gateway/manager.js.map +1 -1
  33. package/dist/gateway/telegram.d.ts +5 -5
  34. package/dist/gateway/telegram.d.ts.map +1 -1
  35. package/dist/gateway/telegram.js +100 -101
  36. package/dist/gateway/telegram.js.map +1 -1
  37. package/dist/gateway/webui.d.ts +52 -5
  38. package/dist/gateway/webui.d.ts.map +1 -1
  39. package/dist/gateway/webui.js +107 -235
  40. package/dist/gateway/webui.js.map +1 -1
  41. package/dist/gateway/workflow-diagram.d.ts +19 -0
  42. package/dist/gateway/workflow-diagram.d.ts.map +1 -0
  43. package/dist/gateway/workflow-diagram.js +124 -0
  44. package/dist/gateway/workflow-diagram.js.map +1 -0
  45. package/dist/index.d.ts +9 -2
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +6 -1
  48. package/dist/index.js.map +1 -1
  49. package/dist/llm/anthropic-account.d.ts +2 -1
  50. package/dist/llm/anthropic-account.d.ts.map +1 -1
  51. package/dist/llm/anthropic-account.js +1 -1
  52. package/dist/llm/anthropic-account.js.map +1 -1
  53. package/dist/llm/capability-resolver.d.ts +10 -0
  54. package/dist/llm/capability-resolver.d.ts.map +1 -0
  55. package/dist/llm/capability-resolver.js +93 -0
  56. package/dist/llm/capability-resolver.js.map +1 -0
  57. package/dist/llm/copilot-account.d.ts +2 -1
  58. package/dist/llm/copilot-account.d.ts.map +1 -1
  59. package/dist/llm/copilot-account.js +323 -32
  60. package/dist/llm/copilot-account.js.map +1 -1
  61. package/dist/llm/create-language-model.d.ts.map +1 -1
  62. package/dist/llm/create-language-model.js +12 -171
  63. package/dist/llm/create-language-model.js.map +1 -1
  64. package/dist/llm/model-capabilities.d.ts +32 -0
  65. package/dist/llm/model-capabilities.d.ts.map +1 -0
  66. package/dist/llm/model-capabilities.js +144 -0
  67. package/dist/llm/model-capabilities.js.map +1 -0
  68. package/dist/llm/openai-account.d.ts +2 -1
  69. package/dist/llm/openai-account.d.ts.map +1 -1
  70. package/dist/llm/openai-account.js +29 -17
  71. package/dist/llm/openai-account.js.map +1 -1
  72. package/dist/llm/provider-discovery.d.ts +1 -1
  73. package/dist/llm/provider-discovery.d.ts.map +1 -1
  74. package/dist/llm/provider-discovery.js +3 -34
  75. package/dist/llm/provider-discovery.js.map +1 -1
  76. package/dist/llm/provider-metadata.d.ts +27 -0
  77. package/dist/llm/provider-metadata.d.ts.map +1 -0
  78. package/dist/llm/provider-metadata.js +327 -0
  79. package/dist/llm/provider-metadata.js.map +1 -0
  80. package/dist/llm/provider-plugin.d.ts +41 -0
  81. package/dist/llm/provider-plugin.d.ts.map +1 -0
  82. package/dist/llm/provider-plugin.js +429 -0
  83. package/dist/llm/provider-plugin.js.map +1 -0
  84. package/dist/llm/provider-registry.d.ts +5 -2
  85. package/dist/llm/provider-registry.d.ts.map +1 -1
  86. package/dist/llm/provider-registry.js +7 -25
  87. package/dist/llm/provider-registry.js.map +1 -1
  88. package/dist/llm/proxy-runtime.d.ts.map +1 -1
  89. package/dist/llm/proxy-runtime.js +16 -63
  90. package/dist/llm/proxy-runtime.js.map +1 -1
  91. package/dist/llm/test-model-policy.d.ts.map +1 -1
  92. package/dist/llm/test-model-policy.js +8 -10
  93. package/dist/llm/test-model-policy.js.map +1 -1
  94. package/dist/llm/tool-schema.d.ts +4 -0
  95. package/dist/llm/tool-schema.d.ts.map +1 -0
  96. package/dist/llm/tool-schema.js +80 -0
  97. package/dist/llm/tool-schema.js.map +1 -0
  98. package/dist/prompt/build-system-prompt.d.ts +3 -3
  99. package/dist/prompt/build-system-prompt.d.ts.map +1 -1
  100. package/dist/prompt/build-system-prompt.js +2 -0
  101. package/dist/prompt/build-system-prompt.js.map +1 -1
  102. package/dist/runtime/completion-gate.d.ts +4 -0
  103. package/dist/runtime/completion-gate.d.ts.map +1 -1
  104. package/dist/runtime/completion-gate.js +13 -2
  105. package/dist/runtime/completion-gate.js.map +1 -1
  106. package/dist/runtime/context-compaction.d.ts.map +1 -1
  107. package/dist/runtime/context-compaction.js +6 -5
  108. package/dist/runtime/context-compaction.js.map +1 -1
  109. package/dist/runtime/outcome.d.ts +3 -0
  110. package/dist/runtime/outcome.d.ts.map +1 -1
  111. package/dist/runtime/outcome.js +40 -3
  112. package/dist/runtime/outcome.js.map +1 -1
  113. package/dist/runtime/policy-hooks.d.ts +4 -0
  114. package/dist/runtime/policy-hooks.d.ts.map +1 -1
  115. package/dist/runtime/policy-hooks.js +137 -0
  116. package/dist/runtime/policy-hooks.js.map +1 -1
  117. package/dist/runtime/required-actions.d.ts +5 -0
  118. package/dist/runtime/required-actions.d.ts.map +1 -1
  119. package/dist/runtime/required-actions.js +22 -4
  120. package/dist/runtime/required-actions.js.map +1 -1
  121. package/dist/runtime/run-engine.d.ts +6 -3
  122. package/dist/runtime/run-engine.d.ts.map +1 -1
  123. package/dist/runtime/run-engine.js +699 -97
  124. package/dist/runtime/run-engine.js.map +1 -1
  125. package/dist/runtime/tool-runtime-strategy.d.ts +29 -0
  126. package/dist/runtime/tool-runtime-strategy.d.ts.map +1 -0
  127. package/dist/runtime/tool-runtime-strategy.js +102 -0
  128. package/dist/runtime/tool-runtime-strategy.js.map +1 -0
  129. package/dist/runtime/user-visible-updates.d.ts +12 -0
  130. package/dist/runtime/user-visible-updates.d.ts.map +1 -0
  131. package/dist/runtime/user-visible-updates.js +93 -0
  132. package/dist/runtime/user-visible-updates.js.map +1 -0
  133. package/dist/setup/application-services.d.ts +199 -0
  134. package/dist/setup/application-services.d.ts.map +1 -0
  135. package/dist/setup/application-services.js +468 -0
  136. package/dist/setup/application-services.js.map +1 -0
  137. package/dist/setup/setup-wizard.d.ts +1 -0
  138. package/dist/setup/setup-wizard.d.ts.map +1 -1
  139. package/dist/setup/setup-wizard.js +11 -13
  140. package/dist/setup/setup-wizard.js.map +1 -1
  141. package/dist/setup/status.d.ts +21 -0
  142. package/dist/setup/status.d.ts.map +1 -0
  143. package/dist/setup/status.js +47 -0
  144. package/dist/setup/status.js.map +1 -0
  145. package/dist/setup.d.ts +3 -14
  146. package/dist/setup.d.ts.map +1 -1
  147. package/dist/setup.js +30 -256
  148. package/dist/setup.js.map +1 -1
  149. package/dist/tools/build-tools.d.ts +160 -18
  150. package/dist/tools/build-tools.d.ts.map +1 -1
  151. package/dist/tools/build-tools.js +11 -1
  152. package/dist/tools/build-tools.js.map +1 -1
  153. package/dist/tools/deploy.d.ts +2 -2
  154. package/dist/tools/deploy.d.ts.map +1 -1
  155. package/dist/tools/deploy.js.map +1 -1
  156. package/dist/tools/generate-workflow.d.ts +9 -9
  157. package/dist/tools/generate-workflow.d.ts.map +1 -1
  158. package/dist/tools/generate-workflow.js.map +1 -1
  159. package/dist/tools/index.d.ts +1 -1
  160. package/dist/tools/index.d.ts.map +1 -1
  161. package/dist/tools/index.js.map +1 -1
  162. package/dist/tools/list-workflows.d.ts +2 -2
  163. package/dist/tools/list-workflows.d.ts.map +1 -1
  164. package/dist/tools/list-workflows.js.map +1 -1
  165. package/dist/tools/manage-workflow.d.ts +2 -2
  166. package/dist/tools/manage-workflow.d.ts.map +1 -1
  167. package/dist/tools/manage-workflow.js.map +1 -1
  168. package/dist/tools/n8nac.d.ts +121 -4
  169. package/dist/tools/n8nac.d.ts.map +1 -1
  170. package/dist/tools/n8nac.js +183 -38
  171. package/dist/tools/n8nac.js.map +1 -1
  172. package/dist/tools/node-info.d.ts +2 -2
  173. package/dist/tools/node-info.d.ts.map +1 -1
  174. package/dist/tools/node-info.js.map +1 -1
  175. package/dist/tools/observer.d.ts +6 -35
  176. package/dist/tools/observer.d.ts.map +1 -1
  177. package/dist/tools/observer.js +18 -0
  178. package/dist/tools/observer.js.map +1 -1
  179. package/dist/tools/present-workflow-result.d.ts +1 -0
  180. package/dist/tools/present-workflow-result.d.ts.map +1 -1
  181. package/dist/tools/present-workflow-result.js +24 -5
  182. package/dist/tools/present-workflow-result.js.map +1 -1
  183. package/dist/tools/request-required-action.d.ts +7 -3
  184. package/dist/tools/request-required-action.d.ts.map +1 -1
  185. package/dist/tools/request-required-action.js +5 -3
  186. package/dist/tools/request-required-action.js.map +1 -1
  187. package/dist/tools/search-nodes.d.ts +2 -2
  188. package/dist/tools/search-nodes.d.ts.map +1 -1
  189. package/dist/tools/search-nodes.js.map +1 -1
  190. package/dist/tools/search-templates.d.ts +2 -2
  191. package/dist/tools/search-templates.d.ts.map +1 -1
  192. package/dist/tools/search-templates.js.map +1 -1
  193. package/dist/tools/toolsets.d.ts +11 -0
  194. package/dist/tools/toolsets.d.ts.map +1 -0
  195. package/dist/tools/toolsets.js +49 -0
  196. package/dist/tools/toolsets.js.map +1 -0
  197. package/dist/tools/validate.d.ts +2 -2
  198. package/dist/tools/validate.d.ts.map +1 -1
  199. package/dist/tools/validate.js.map +1 -1
  200. package/dist/tools/write-workspace-file.d.ts +25 -9
  201. package/dist/tools/write-workspace-file.d.ts.map +1 -1
  202. package/dist/tools/write-workspace-file.js +27 -4
  203. package/dist/tools/write-workspace-file.js.map +1 -1
  204. package/dist/types.d.ts +1 -0
  205. package/dist/types.d.ts.map +1 -1
  206. package/dist/webui/app.js +97 -101
  207. package/dist/webui/app.js.map +4 -4
  208. package/package.json +6 -5
  209. package/dist/llm/google-account.d.ts +0 -31
  210. package/dist/llm/google-account.d.ts.map +0 -1
  211. package/dist/llm/google-account.js +0 -851
  212. package/dist/llm/google-account.js.map +0 -1
@@ -1,851 +0,0 @@
1
- import fs from 'node:fs';
2
- import http from 'node:http';
3
- import os from 'node:os';
4
- import path from 'node:path';
5
- import { createHash, randomBytes } from 'node:crypto';
6
- import { ensureYagrHomeDir, getYagrPaths } from '../config/yagr-home.js';
7
- export const GEMINI_ACCOUNT_DEFAULT_MODEL = 'gemini-3-flash-preview';
8
- const GEMINI_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
9
- const GEMINI_TOKEN_URL = 'https://oauth2.googleapis.com/token';
10
- const GEMINI_USERINFO_URL = 'https://www.googleapis.com/oauth2/v1/userinfo?alt=json';
11
- const CODE_ASSIST_ENDPOINT = 'https://cloudcode-pa.googleapis.com';
12
- const CODE_ASSIST_API_VERSION = 'v1internal';
13
- const CODE_ASSIST_CLIENT_METADATA = JSON.stringify({
14
- ideType: 'IDE_UNSPECIFIED',
15
- platform: 'PLATFORM_UNSPECIFIED',
16
- pluginType: 'GEMINI',
17
- });
18
- const GEMINI_REDIRECT_URI = 'http://localhost:8085/oauth2callback';
19
- const GEMINI_SCOPES = [
20
- 'https://www.googleapis.com/auth/cloud-platform',
21
- 'https://www.googleapis.com/auth/userinfo.email',
22
- 'https://www.googleapis.com/auth/userinfo.profile',
23
- ];
24
- const GEMINI_OAUTH_PERSONAL_AUTH_TYPE = 'oauth-personal';
25
- let pendingGeminiCallbackServer;
26
- const completedGeminiCallbacks = new Map();
27
- // In-memory cache for the CodeAssist project ID (resolved once per process).
28
- let cachedCodeAssistProjectId;
29
- export function getGeminiConfigDir() {
30
- return process.env.YAGR_GEMINI_CONFIG_DIR || path.join(os.homedir(), '.gemini');
31
- }
32
- export function getGeminiAuthPath() {
33
- return process.env.YAGR_GEMINI_AUTH_PATH || path.join(getGeminiConfigDir(), 'oauth_creds.json');
34
- }
35
- export function getGeminiSettingsPath() {
36
- return process.env.YAGR_GEMINI_SETTINGS_PATH || path.join(getGeminiConfigDir(), 'settings.json');
37
- }
38
- export function getGeminiSessionPath() {
39
- const override = process.env.YAGR_GEMINI_SESSION_PATH?.trim();
40
- if (override) {
41
- return override;
42
- }
43
- ensureYagrHomeDir();
44
- return path.join(getYagrPaths().accountAuthDir, 'gemini-oauth.json');
45
- }
46
- export function getGeminiAccountSession() {
47
- const stored = readStoredGeminiSession();
48
- if (!stored) {
49
- return undefined;
50
- }
51
- return {
52
- accessToken: stored.accessToken,
53
- refreshToken: stored.refreshToken,
54
- expiresAt: stored.expiresAt,
55
- email: stored.email,
56
- projectId: stored.projectId,
57
- };
58
- }
59
- export async function ensureGeminiAccountSession() {
60
- const stored = readStoredGeminiSession();
61
- if (stored) {
62
- const refreshed = await refreshGeminiSessionIfNeeded(stored);
63
- syncGeminiCliFiles(refreshed);
64
- return {
65
- accessToken: refreshed.accessToken,
66
- refreshToken: refreshed.refreshToken,
67
- expiresAt: refreshed.expiresAt,
68
- email: refreshed.email,
69
- projectId: refreshed.projectId,
70
- };
71
- }
72
- return undefined;
73
- }
74
- export function ensureGeminiCliSettings() {
75
- const settingsPath = getGeminiSettingsPath();
76
- fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
77
- let settings = {};
78
- if (fs.existsSync(settingsPath)) {
79
- try {
80
- settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
81
- }
82
- catch {
83
- settings = {};
84
- }
85
- }
86
- if (settings.selectedAuthType !== GEMINI_OAUTH_PERSONAL_AUTH_TYPE) {
87
- settings.selectedAuthType = GEMINI_OAUTH_PERSONAL_AUTH_TYPE;
88
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
89
- }
90
- }
91
- export async function validateGeminiAccountRuntime(modelId = GEMINI_ACCOUNT_DEFAULT_MODEL) {
92
- if (process.env.YAGR_SKIP_GEMINI_RUNTIME_VALIDATION === '1') {
93
- return { ok: true, text: 'OK' };
94
- }
95
- try {
96
- const result = await runGeminiExec(modelId, {
97
- inputFormat: 'prompt',
98
- mode: { type: 'regular' },
99
- prompt: [{ role: 'user', content: [{ type: 'text', text: 'Reply with exactly OK.' }] }],
100
- });
101
- return {
102
- ok: result.text.trim().toUpperCase().includes('OK'),
103
- text: result.text,
104
- };
105
- }
106
- catch (error) {
107
- const message = error instanceof Error ? error.message : String(error);
108
- if (message.includes('HTTP 429') || message.includes('RESOURCE_EXHAUSTED')) {
109
- return {
110
- ok: true,
111
- text: 'Quota exhausted for current model; runtime endpoint is reachable.',
112
- };
113
- }
114
- return {
115
- ok: false,
116
- error: message,
117
- };
118
- }
119
- }
120
- // Models known to work with the CodeAssist backend via Google OAuth.
121
- // The generativelanguage.googleapis.com/v1beta/models API is NOT accessible
122
- // with CodeAssist OAuth scopes (cloud-platform), and there is no model listing
123
- // endpoint on the CodeAssist API itself. This curated list matches what
124
- // OpenClaw ships for its google-gemini-cli provider.
125
- const KNOWN_GEMINI_CODE_ASSIST_MODELS = [
126
- 'gemini-2.5-pro',
127
- 'gemini-2.5-flash',
128
- 'gemini-2.0-flash',
129
- 'gemini-1.5-flash',
130
- 'gemini-3-flash-preview',
131
- 'gemini-3-pro-preview',
132
- 'gemini-3.1-pro-preview',
133
- ];
134
- export async function fetchGeminiOAuthModels(_accessToken) {
135
- const envOverride = process.env.YAGR_GEMINI_MODEL_LIST?.split(',').map(s => s.trim()).filter(Boolean);
136
- const models = envOverride && envOverride.length > 0
137
- ? envOverride
138
- : [...KNOWN_GEMINI_CODE_ASSIST_MODELS];
139
- if (process.env.YAGR_DEBUG_MODEL_DISCOVERY === '1') {
140
- process.stderr.write(`[yagr] gemini models (curated list): ${models.join(', ')}\n`);
141
- }
142
- return models;
143
- }
144
- export function createGeminiAccountLanguageModel(modelId) {
145
- return {
146
- specificationVersion: 'v1',
147
- provider: 'google-proxy.gemini',
148
- modelId,
149
- defaultObjectGenerationMode: undefined,
150
- supportsImageUrls: false,
151
- supportsStructuredOutputs: false,
152
- async doGenerate(options) {
153
- const execution = await runGeminiExec(modelId, options);
154
- return {
155
- text: execution.text,
156
- finishReason: execution.finishReason,
157
- usage: execution.usage,
158
- rawCall: {
159
- rawPrompt: options.prompt,
160
- rawSettings: { modelId },
161
- },
162
- warnings: execution.warnings,
163
- response: {
164
- timestamp: new Date(),
165
- modelId,
166
- },
167
- };
168
- },
169
- async doStream(options) {
170
- const session = await ensureGeminiAccountSession();
171
- if (!session) {
172
- throw new Error('Gemini OAuth session not found. Run `yagr setup` again.');
173
- }
174
- const warnings = buildGeminiWarnings(options);
175
- const project = await resolveCodeAssistProject(session);
176
- const { systemInstruction, contents } = convertToGeminiFormat(options.prompt);
177
- const abortController = new AbortController();
178
- const timeoutTimer = setTimeout(() => abortController.abort(), 120_000);
179
- let fetchResponse;
180
- try {
181
- fetchResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/${CODE_ASSIST_API_VERSION}:streamGenerateContent?alt=sse`, {
182
- method: 'POST',
183
- signal: abortController.signal,
184
- headers: {
185
- Authorization: `Bearer ${session.accessToken}`,
186
- 'Content-Type': 'application/json',
187
- Accept: 'text/event-stream',
188
- 'User-Agent': 'google-cloud-sdk vscode_cloudshelleditor/0.1',
189
- 'X-Goog-Api-Client': `gl-node/${process.versions.node}`,
190
- 'Client-Metadata': CODE_ASSIST_CLIENT_METADATA,
191
- },
192
- body: JSON.stringify({
193
- model: modelId,
194
- project,
195
- request: {
196
- ...(systemInstruction ? { systemInstruction } : {}),
197
- contents,
198
- },
199
- userAgent: 'yagr',
200
- }),
201
- });
202
- }
203
- catch (error) {
204
- clearTimeout(timeoutTimer);
205
- throw error;
206
- }
207
- if (!fetchResponse.ok) {
208
- clearTimeout(timeoutTimer);
209
- const body = await fetchResponse.text().catch(() => '');
210
- throw formatCodeAssistError(fetchResponse.status, body, modelId);
211
- }
212
- const responseBody = fetchResponse.body;
213
- const stream = new ReadableStream({
214
- async start(controller) {
215
- if (!responseBody) {
216
- clearTimeout(timeoutTimer);
217
- controller.enqueue({ type: 'finish', finishReason: 'stop', usage: { promptTokens: 0, completionTokens: 0 } });
218
- controller.close();
219
- return;
220
- }
221
- try {
222
- const reader = responseBody.getReader();
223
- const decoder = new TextDecoder();
224
- let buffer = '';
225
- let promptTokens = 0;
226
- let completionTokens = 0;
227
- while (true) {
228
- const { done, value } = await reader.read();
229
- if (done)
230
- break;
231
- buffer += decoder.decode(value, { stream: true });
232
- const lines = buffer.split('\n');
233
- buffer = lines.pop() ?? '';
234
- for (const line of lines) {
235
- if (!line.startsWith('data: '))
236
- continue;
237
- const data = line.slice(6).trim();
238
- if (!data || data === '[DONE]')
239
- continue;
240
- try {
241
- const chunk = JSON.parse(data);
242
- const parts = chunk.response?.candidates?.[0]?.content?.parts ?? [];
243
- for (const part of parts) {
244
- if (part.text) {
245
- controller.enqueue({ type: 'text-delta', textDelta: part.text });
246
- }
247
- }
248
- if (chunk.response?.usageMetadata) {
249
- promptTokens = chunk.response.usageMetadata.promptTokenCount ?? promptTokens;
250
- completionTokens = chunk.response.usageMetadata.candidatesTokenCount ?? completionTokens;
251
- }
252
- }
253
- catch {
254
- // Malformed SSE chunk — skip
255
- }
256
- }
257
- }
258
- clearTimeout(timeoutTimer);
259
- controller.enqueue({ type: 'finish', finishReason: 'stop', usage: { promptTokens, completionTokens } });
260
- controller.close();
261
- }
262
- catch (error) {
263
- clearTimeout(timeoutTimer);
264
- controller.error(error);
265
- }
266
- },
267
- cancel() {
268
- clearTimeout(timeoutTimer);
269
- abortController.abort();
270
- },
271
- });
272
- return {
273
- stream,
274
- rawCall: { rawPrompt: options.prompt, rawSettings: { modelId } },
275
- warnings,
276
- };
277
- },
278
- };
279
- }
280
- function convertToGeminiFormat(prompt) {
281
- const systemParts = [];
282
- const contents = [];
283
- const push = (role, text) => {
284
- const last = contents[contents.length - 1];
285
- if (last?.role === role) {
286
- last.parts[0].text += '\n\n' + text;
287
- }
288
- else {
289
- contents.push({ role, parts: [{ text }] });
290
- }
291
- };
292
- for (const message of prompt) {
293
- if (message.role === 'system') {
294
- systemParts.push(message.content);
295
- continue;
296
- }
297
- if (message.role === 'user') {
298
- const text = message.content
299
- .map((part) => part.type === 'text' ? part.text : `[${part.type}]`)
300
- .join('\n');
301
- push('user', text);
302
- continue;
303
- }
304
- if (message.role === 'assistant') {
305
- const text = message.content.map((part) => {
306
- if (part.type === 'text' || part.type === 'reasoning')
307
- return part.text;
308
- if (part.type === 'tool-call')
309
- return `[Calling ${part.toolName}: ${JSON.stringify(part.args)}]`;
310
- return `[${part.type}]`;
311
- }).join('\n');
312
- push('model', text);
313
- continue;
314
- }
315
- // tool messages: fold results into the user turn
316
- const text = message.content
317
- .map((part) => `[Result of ${part.toolName}]:\n${JSON.stringify(part.result)}`)
318
- .join('\n\n');
319
- push('user', text);
320
- }
321
- if (contents.length === 0) {
322
- contents.push({ role: 'user', parts: [{ text: '' }] });
323
- }
324
- return {
325
- systemInstruction: systemParts.length > 0 ? { parts: [{ text: systemParts.join('\n\n') }] } : undefined,
326
- contents,
327
- };
328
- }
329
- async function runGeminiExec(modelId, options) {
330
- const session = await ensureGeminiAccountSession();
331
- if (!session) {
332
- throw new Error('Gemini OAuth session not found. Run `yagr setup` again.');
333
- }
334
- const warnings = buildGeminiWarnings(options);
335
- const { systemInstruction, contents } = convertToGeminiFormat(options.prompt);
336
- const project = await resolveCodeAssistProject(session);
337
- const response = await fetchWithTimeout(`${CODE_ASSIST_ENDPOINT}/${CODE_ASSIST_API_VERSION}:generateContent`, {
338
- method: 'POST',
339
- headers: {
340
- Authorization: `Bearer ${session.accessToken}`,
341
- 'Content-Type': 'application/json',
342
- Accept: 'application/json',
343
- 'User-Agent': 'google-cloud-sdk vscode_cloudshelleditor/0.1',
344
- 'X-Goog-Api-Client': `gl-node/${process.versions.node}`,
345
- 'Client-Metadata': CODE_ASSIST_CLIENT_METADATA,
346
- },
347
- body: JSON.stringify({
348
- model: modelId,
349
- project,
350
- request: {
351
- ...(systemInstruction ? { systemInstruction } : {}),
352
- contents,
353
- },
354
- userAgent: 'yagr',
355
- }),
356
- }, 30_000);
357
- if (!response.ok) {
358
- const body = await response.text().catch(() => '');
359
- throw formatCodeAssistError(response.status, body, modelId);
360
- }
361
- const payload = await response.json();
362
- const text = payload.response?.candidates?.[0]?.content?.parts?.map((part) => part.text || '').join('').trim() || '';
363
- return {
364
- text,
365
- finishReason: 'stop',
366
- usage: {
367
- promptTokens: payload.response?.usageMetadata?.promptTokenCount ?? 0,
368
- completionTokens: payload.response?.usageMetadata?.candidatesTokenCount ?? 0,
369
- },
370
- warnings,
371
- };
372
- }
373
- export async function beginGeminiAccountAuth() {
374
- const credentials = resolveGeminiOAuthClientConfig();
375
- const { verifier, challenge } = generatePkce();
376
- const callbackServerStarted = await startGeminiCallbackServer(verifier);
377
- return {
378
- authUrl: buildGeminiAuthUrl(credentials.clientId, challenge, verifier),
379
- verifier,
380
- callbackServerStarted,
381
- };
382
- }
383
- export async function completeGeminiAccountAuth(callbackInput, verifier) {
384
- const credentials = resolveGeminiOAuthClientConfig();
385
- const resolvedInput = callbackInput.trim() || await waitForGeminiCallbackRedirect(verifier);
386
- const parsed = parseGeminiCallbackInput(resolvedInput, verifier);
387
- if ('error' in parsed) {
388
- throw new Error(parsed.error);
389
- }
390
- const tokenResponse = await exchangeGeminiCodeForTokens(parsed.code, verifier, credentials);
391
- const email = await resolveGoogleEmail(tokenResponse.access_token);
392
- // Resolve the CodeAssist project immediately so it is persisted in the session
393
- // and subsequent inference calls skip the extra loadCodeAssist round-trip.
394
- let projectId;
395
- try {
396
- projectId = await loadGeminiCodeAssistProject(tokenResponse.access_token);
397
- cachedCodeAssistProjectId = projectId;
398
- }
399
- catch {
400
- // Non-fatal: will be resolved lazily on first inference call.
401
- }
402
- const nowIso = new Date().toISOString();
403
- const stored = {
404
- provider: 'google-proxy',
405
- accessToken: tokenResponse.access_token,
406
- refreshToken: tokenResponse.refresh_token || '',
407
- expiresAt: Date.now() + tokenResponse.expires_in * 1000 - 5 * 60 * 1000,
408
- email,
409
- projectId,
410
- createdAt: nowIso,
411
- updatedAt: nowIso,
412
- };
413
- if (!stored.refreshToken) {
414
- throw new Error('No refresh token received from Google OAuth. Retry the consent flow.');
415
- }
416
- writeStoredGeminiSession(stored);
417
- syncGeminiCliFiles(stored);
418
- return {
419
- accessToken: stored.accessToken,
420
- refreshToken: stored.refreshToken,
421
- expiresAt: stored.expiresAt,
422
- email: stored.email,
423
- projectId: stored.projectId,
424
- };
425
- }
426
- async function startGeminiCallbackServer(expectedState) {
427
- stopGeminiCallbackServer();
428
- let resolveRedirect;
429
- let rejectRedirect;
430
- const waitForRedirectUrl = new Promise((resolve, reject) => {
431
- resolveRedirect = resolve;
432
- rejectRedirect = reject;
433
- });
434
- const server = http.createServer((req, res) => {
435
- const requestUrl = new URL(req.url || '/', GEMINI_REDIRECT_URI);
436
- if (requestUrl.pathname !== '/oauth2callback') {
437
- res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
438
- res.end('Not Found');
439
- return;
440
- }
441
- const state = requestUrl.searchParams.get('state');
442
- const code = requestUrl.searchParams.get('code');
443
- const oauthError = requestUrl.searchParams.get('error');
444
- if (oauthError) {
445
- res.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' });
446
- res.end(`OAuth error: ${oauthError}. Return to terminal and retry.`);
447
- storeCompletedGeminiCallback(expectedState, requestUrl.toString());
448
- resolveRedirect?.(requestUrl.toString());
449
- stopGeminiCallbackServer();
450
- return;
451
- }
452
- if (!code || state !== expectedState) {
453
- res.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' });
454
- res.end('Invalid callback. Return to terminal and retry.');
455
- storeCompletedGeminiCallback(expectedState, requestUrl.toString());
456
- resolveRedirect?.(requestUrl.toString());
457
- stopGeminiCallbackServer();
458
- return;
459
- }
460
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
461
- res.end('<html><body><h3>Gemini account connected.</h3><p>You can return to Yagr.</p></body></html>');
462
- storeCompletedGeminiCallback(expectedState, requestUrl.toString());
463
- resolveRedirect?.(requestUrl.toString());
464
- stopGeminiCallbackServer();
465
- });
466
- const timeout = setTimeout(() => {
467
- rejectRedirect?.(new Error('Gemini OAuth callback timeout. Retry and complete sign-in in the browser.'));
468
- stopGeminiCallbackServer();
469
- }, 3 * 60_000);
470
- pendingGeminiCallbackServer = {
471
- expectedState,
472
- server,
473
- waitForRedirectUrl,
474
- timeout,
475
- };
476
- const listenResult = await new Promise((resolve) => {
477
- server.once('error', (error) => {
478
- rejectRedirect?.(error instanceof Error ? error : new Error(String(error)));
479
- stopGeminiCallbackServer();
480
- resolve(false);
481
- });
482
- server.listen(8085, () => {
483
- resolve(true);
484
- });
485
- });
486
- return listenResult;
487
- }
488
- async function waitForGeminiCallbackRedirect(expectedState) {
489
- const completed = completedGeminiCallbacks.get(expectedState);
490
- if (completed) {
491
- completedGeminiCallbacks.delete(expectedState);
492
- return completed.url;
493
- }
494
- const pending = pendingGeminiCallbackServer;
495
- if (!pending || pending.expectedState !== expectedState) {
496
- throw new Error('Gemini callback listener is not active. Paste the redirect URL manually.');
497
- }
498
- return await pending.waitForRedirectUrl;
499
- }
500
- function stopGeminiCallbackServer() {
501
- const pending = pendingGeminiCallbackServer;
502
- if (!pending) {
503
- return;
504
- }
505
- clearTimeout(pending.timeout);
506
- pendingGeminiCallbackServer = undefined;
507
- try {
508
- pending.server.close();
509
- }
510
- catch {
511
- // Ignore close errors.
512
- }
513
- }
514
- function storeCompletedGeminiCallback(state, url) {
515
- pruneCompletedGeminiCallbacks();
516
- completedGeminiCallbacks.set(state, { url, capturedAt: Date.now() });
517
- }
518
- function pruneCompletedGeminiCallbacks() {
519
- const cutoff = Date.now() - 5 * 60_000;
520
- for (const [state, callback] of completedGeminiCallbacks.entries()) {
521
- if (callback.capturedAt < cutoff) {
522
- completedGeminiCallbacks.delete(state);
523
- }
524
- }
525
- }
526
- async function refreshGeminiSessionIfNeeded(session) {
527
- if (session.expiresAt - Date.now() > 60_000) {
528
- return session;
529
- }
530
- const credentials = resolveGeminiOAuthClientConfig();
531
- const body = new URLSearchParams({
532
- client_id: credentials.clientId,
533
- grant_type: 'refresh_token',
534
- refresh_token: session.refreshToken,
535
- });
536
- if (credentials.clientSecret) {
537
- body.set('client_secret', credentials.clientSecret);
538
- }
539
- const response = await fetch(GEMINI_TOKEN_URL, {
540
- method: 'POST',
541
- headers: {
542
- 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
543
- Accept: '*/*',
544
- 'User-Agent': 'google-api-nodejs-client/9.15.1',
545
- },
546
- body,
547
- });
548
- if (!response.ok) {
549
- throw new Error(`Gemini token refresh failed: ${await response.text()}`);
550
- }
551
- const data = await response.json();
552
- const refreshed = {
553
- ...session,
554
- accessToken: data.access_token,
555
- refreshToken: data.refresh_token || session.refreshToken,
556
- expiresAt: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
557
- updatedAt: new Date().toISOString(),
558
- };
559
- writeStoredGeminiSession(refreshed);
560
- return refreshed;
561
- }
562
- function readStoredGeminiSession() {
563
- const sessionPath = getGeminiSessionPath();
564
- if (!fs.existsSync(sessionPath)) {
565
- return undefined;
566
- }
567
- try {
568
- const parsed = JSON.parse(fs.readFileSync(sessionPath, 'utf8'));
569
- if (!parsed.accessToken || !parsed.refreshToken) {
570
- return undefined;
571
- }
572
- return parsed;
573
- }
574
- catch {
575
- return undefined;
576
- }
577
- }
578
- function writeStoredGeminiSession(session) {
579
- const sessionPath = getGeminiSessionPath();
580
- fs.mkdirSync(path.dirname(sessionPath), { recursive: true });
581
- fs.writeFileSync(sessionPath, JSON.stringify(session, null, 2));
582
- }
583
- function syncGeminiCliFiles(session) {
584
- const authPath = getGeminiAuthPath();
585
- fs.mkdirSync(path.dirname(authPath), { recursive: true });
586
- const payload = {
587
- access_token: session.accessToken,
588
- scope: GEMINI_SCOPES.join(' '),
589
- token_type: 'Bearer',
590
- expiry_date: session.expiresAt,
591
- refresh_token: session.refreshToken,
592
- };
593
- fs.writeFileSync(authPath, JSON.stringify(payload, null, 2));
594
- ensureGeminiCliSettings();
595
- }
596
- function resolveGeminiOAuthClientConfig() {
597
- const envClientId = process.env.YAGR_GEMINI_OAUTH_CLIENT_ID?.trim()
598
- || process.env.OPENCLAW_GEMINI_OAUTH_CLIENT_ID?.trim()
599
- || process.env.GEMINI_CLI_OAUTH_CLIENT_ID?.trim();
600
- const envClientSecret = process.env.YAGR_GEMINI_OAUTH_CLIENT_SECRET?.trim()
601
- || process.env.OPENCLAW_GEMINI_OAUTH_CLIENT_SECRET?.trim()
602
- || process.env.GEMINI_CLI_OAUTH_CLIENT_SECRET?.trim();
603
- if (envClientId) {
604
- return {
605
- clientId: envClientId,
606
- clientSecret: envClientSecret || undefined,
607
- };
608
- }
609
- const extracted = extractGeminiCliCredentials();
610
- if (extracted) {
611
- return extracted;
612
- }
613
- throw new Error('Gemini OAuth client credentials could not be resolved. Install Gemini CLI or set YAGR_GEMINI_OAUTH_CLIENT_ID.');
614
- }
615
- function extractGeminiCliCredentials() {
616
- const geminiPath = findGeminiExecutablePath();
617
- if (!geminiPath) {
618
- return null;
619
- }
620
- const resolvedGeminiPath = safeRealpath(geminiPath);
621
- const binDir = path.dirname(geminiPath);
622
- const candidateDirs = dedupePaths([
623
- path.dirname(path.dirname(resolvedGeminiPath)),
624
- path.join(path.dirname(resolvedGeminiPath), 'node_modules', '@google', 'gemini-cli'),
625
- path.join(binDir, 'node_modules', '@google', 'gemini-cli'),
626
- path.join(path.dirname(binDir), 'node_modules', '@google', 'gemini-cli'),
627
- path.join(path.dirname(binDir), 'lib', 'node_modules', '@google', 'gemini-cli'),
628
- ]);
629
- const candidatePaths = [];
630
- for (const dir of candidateDirs) {
631
- candidatePaths.push(path.join(dir, 'node_modules', '@google', 'gemini-cli-core', 'dist', 'src', 'code_assist', 'oauth2.js'), path.join(dir, 'node_modules', '@google', 'gemini-cli-core', 'dist', 'code_assist', 'oauth2.js'));
632
- }
633
- for (const candidate of candidatePaths) {
634
- if (!fs.existsSync(candidate)) {
635
- continue;
636
- }
637
- const content = fs.readFileSync(candidate, 'utf8');
638
- const clientId = content.match(/(\d+-[a-z0-9]+\.apps\.googleusercontent\.com)/)?.[1];
639
- const clientSecret = content.match(/(GOCSPX-[A-Za-z0-9_-]+)/)?.[1];
640
- if (clientId && clientSecret) {
641
- return { clientId, clientSecret };
642
- }
643
- }
644
- return null;
645
- }
646
- function findGeminiExecutablePath() {
647
- const executable = process.env.YAGR_GEMINI_CLI_PATH?.trim();
648
- if (executable && fs.existsSync(executable)) {
649
- return executable;
650
- }
651
- for (const directory of (process.env.PATH ?? '').split(path.delimiter)) {
652
- for (const executableName of ['gemini', 'gemini.cmd', 'gemini.bat', 'gemini.exe']) {
653
- const candidate = path.join(directory, executableName);
654
- if (fs.existsSync(candidate)) {
655
- return candidate;
656
- }
657
- }
658
- }
659
- return undefined;
660
- }
661
- function safeRealpath(value) {
662
- try {
663
- return fs.realpathSync(value);
664
- }
665
- catch {
666
- return value;
667
- }
668
- }
669
- function dedupePaths(paths) {
670
- const deduped = [];
671
- const seen = new Set();
672
- for (const entry of paths) {
673
- const key = process.platform === 'win32' ? entry.replace(/\\/g, '/').toLowerCase() : entry;
674
- if (seen.has(key)) {
675
- continue;
676
- }
677
- seen.add(key);
678
- deduped.push(entry);
679
- }
680
- return deduped;
681
- }
682
- function generatePkce() {
683
- const verifier = randomBytes(32).toString('hex');
684
- const challenge = createHash('sha256').update(verifier).digest('base64url');
685
- return { verifier, challenge };
686
- }
687
- function buildGeminiAuthUrl(clientId, challenge, verifier) {
688
- const params = new URLSearchParams({
689
- client_id: clientId,
690
- response_type: 'code',
691
- redirect_uri: GEMINI_REDIRECT_URI,
692
- scope: GEMINI_SCOPES.join(' '),
693
- code_challenge: challenge,
694
- code_challenge_method: 'S256',
695
- state: verifier,
696
- access_type: 'offline',
697
- prompt: 'consent',
698
- });
699
- return `${GEMINI_AUTH_URL}?${params.toString()}`;
700
- }
701
- function parseGeminiCallbackInput(input, expectedState) {
702
- const trimmed = input.trim();
703
- if (!trimmed) {
704
- return { error: 'No input provided.' };
705
- }
706
- try {
707
- const url = new URL(trimmed);
708
- const code = url.searchParams.get('code');
709
- const state = url.searchParams.get('state') ?? expectedState;
710
- if (!code) {
711
- return { error: 'Missing code parameter in redirect URL.' };
712
- }
713
- if (state !== expectedState) {
714
- return { error: 'OAuth state mismatch.' };
715
- }
716
- return { code, state };
717
- }
718
- catch {
719
- return { error: 'Paste the full redirect URL, not only the code.' };
720
- }
721
- }
722
- async function exchangeGeminiCodeForTokens(code, verifier, credentials) {
723
- const body = new URLSearchParams({
724
- client_id: credentials.clientId,
725
- code,
726
- grant_type: 'authorization_code',
727
- redirect_uri: GEMINI_REDIRECT_URI,
728
- code_verifier: verifier,
729
- });
730
- if (credentials.clientSecret) {
731
- body.set('client_secret', credentials.clientSecret);
732
- }
733
- const response = await fetch(GEMINI_TOKEN_URL, {
734
- method: 'POST',
735
- headers: {
736
- 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
737
- Accept: '*/*',
738
- 'User-Agent': 'google-api-nodejs-client/9.15.1',
739
- },
740
- body,
741
- });
742
- if (!response.ok) {
743
- throw new Error(`Gemini token exchange failed: ${await response.text()}`);
744
- }
745
- return await response.json();
746
- }
747
- async function resolveGoogleEmail(accessToken) {
748
- try {
749
- const response = await fetch(GEMINI_USERINFO_URL, {
750
- headers: {
751
- Authorization: `Bearer ${accessToken}`,
752
- Accept: 'application/json',
753
- },
754
- });
755
- if (!response.ok) {
756
- return undefined;
757
- }
758
- const payload = await response.json();
759
- return typeof payload.email === 'string' ? payload.email : undefined;
760
- }
761
- catch {
762
- return undefined;
763
- }
764
- }
765
- function buildGeminiWarnings(options) {
766
- if (options.mode.type !== 'regular' || !options.mode.tools || options.mode.tools.length === 0) {
767
- return [];
768
- }
769
- return options.mode.tools.map((tool) => ({
770
- type: 'unsupported-tool',
771
- tool,
772
- details: 'Gemini OAuth currently executes through Code Assist runtime and does not expose Yagr tool-calls yet.',
773
- }));
774
- }
775
- async function fetchWithTimeout(input, init, timeoutMs) {
776
- const controller = new AbortController();
777
- const timer = setTimeout(() => controller.abort(), timeoutMs);
778
- try {
779
- return await fetch(input, {
780
- ...init,
781
- signal: controller.signal,
782
- });
783
- }
784
- finally {
785
- clearTimeout(timer);
786
- }
787
- }
788
- async function resolveCodeAssistProject(session) {
789
- // Return from in-memory cache (fastest)
790
- if (cachedCodeAssistProjectId) {
791
- return cachedCodeAssistProjectId;
792
- }
793
- // Return from stored session (avoids network call)
794
- if (session.projectId) {
795
- cachedCodeAssistProjectId = session.projectId;
796
- return session.projectId;
797
- }
798
- // Fetch from API (first time only)
799
- const project = await loadGeminiCodeAssistProject(session.accessToken);
800
- cachedCodeAssistProjectId = project;
801
- // Persist in session so future processes skip the API call too
802
- const stored = readStoredGeminiSession();
803
- if (stored && !stored.projectId) {
804
- stored.projectId = project;
805
- writeStoredGeminiSession(stored);
806
- }
807
- return project;
808
- }
809
- async function loadGeminiCodeAssistProject(accessToken) {
810
- const metadata = {
811
- ideType: 'IDE_UNSPECIFIED',
812
- platform: 'PLATFORM_UNSPECIFIED',
813
- pluginType: 'GEMINI',
814
- };
815
- const response = await fetchWithTimeout(`${CODE_ASSIST_ENDPOINT}/${CODE_ASSIST_API_VERSION}:loadCodeAssist`, {
816
- method: 'POST',
817
- headers: {
818
- Authorization: `Bearer ${accessToken}`,
819
- 'Content-Type': 'application/json',
820
- Accept: 'application/json',
821
- 'User-Agent': 'google-api-nodejs-client/9.15.1',
822
- 'X-Goog-Api-Client': `gl-node/${process.versions.node}`,
823
- 'Client-Metadata': JSON.stringify(metadata),
824
- },
825
- body: JSON.stringify({ metadata }),
826
- }, 8_000);
827
- if (!response.ok) {
828
- const body = await response.text().catch(() => '');
829
- throw new Error(`Gemini Code Assist load failed (HTTP ${response.status})${body ? `: ${body.slice(0, 200)}` : ''}`);
830
- }
831
- const payload = await response.json();
832
- const project = payload.cloudaicompanionProject?.trim();
833
- if (!project) {
834
- throw new Error('Gemini Code Assist did not return a project id.');
835
- }
836
- return project;
837
- }
838
- function formatCodeAssistError(status, body, modelId) {
839
- if (status === 429) {
840
- const resetMatch = body.match(/reset after (\d+)s/);
841
- const resetHint = resetMatch ? ` (resets in ${resetMatch[1]}s)` : '';
842
- return new Error(`Rate limit exceeded for model "${modelId}"${resetHint}. ` +
843
- 'The Gemini free tier has strict per-model quotas. Wait a moment or try a different model.');
844
- }
845
- if (status === 404) {
846
- return new Error(`Model "${modelId}" is not available on your Gemini account. ` +
847
- 'It may not be supported for your tier or region. Choose a different model.');
848
- }
849
- return new Error(`Gemini Code Assist request failed (HTTP ${status})${body ? `: ${body.slice(0, 200)}` : ''}`);
850
- }
851
- //# sourceMappingURL=google-account.js.map