@didim365/agent-cli-core 0.2.9 → 0.2.11

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 (51) hide show
  1. package/README.md +53 -43
  2. package/dist/src/config/costEstimation.d.ts +65 -0
  3. package/dist/src/config/costEstimation.js +133 -0
  4. package/dist/src/config/costEstimation.js.map +1 -0
  5. package/dist/src/core/loggingContentGenerator.d.ts +7 -1
  6. package/dist/src/core/loggingContentGenerator.js +81 -5
  7. package/dist/src/core/loggingContentGenerator.js.map +1 -1
  8. package/dist/src/core/recordingContentGenerator.d.ts +3 -0
  9. package/dist/src/core/recordingContentGenerator.js +6 -0
  10. package/dist/src/core/recordingContentGenerator.js.map +1 -1
  11. package/dist/src/generated/git-commit.d.ts +1 -1
  12. package/dist/src/generated/git-commit.js +1 -1
  13. package/dist/src/index.d.ts +1 -0
  14. package/dist/src/index.js +1 -0
  15. package/dist/src/index.js.map +1 -1
  16. package/dist/src/output/stream-json-formatter.js +3 -0
  17. package/dist/src/output/stream-json-formatter.js.map +1 -1
  18. package/dist/src/output/types.d.ts +1 -0
  19. package/dist/src/providers/claude/adapter.js +19 -5
  20. package/dist/src/providers/claude/adapter.js.map +1 -1
  21. package/dist/src/providers/claude/converter.js +3 -1
  22. package/dist/src/providers/claude/converter.js.map +1 -1
  23. package/dist/src/providers/events.d.ts +11 -0
  24. package/dist/src/providers/events.js.map +1 -1
  25. package/dist/src/providers/openai/adapter.js +28 -5
  26. package/dist/src/providers/openai/adapter.js.map +1 -1
  27. package/dist/src/providers/rateLimitUtils.d.ts +43 -0
  28. package/dist/src/providers/rateLimitUtils.js +75 -0
  29. package/dist/src/providers/rateLimitUtils.js.map +1 -0
  30. package/dist/src/providers/telemetryBridge.js +2 -0
  31. package/dist/src/providers/telemetryBridge.js.map +1 -1
  32. package/dist/src/providers/types.d.ts +2 -0
  33. package/dist/src/providers/types.js.map +1 -1
  34. package/dist/src/telemetry/index.d.ts +2 -0
  35. package/dist/src/telemetry/index.js +1 -0
  36. package/dist/src/telemetry/index.js.map +1 -1
  37. package/dist/src/telemetry/loggers.d.ts +19 -0
  38. package/dist/src/telemetry/loggers.js +72 -0
  39. package/dist/src/telemetry/loggers.js.map +1 -1
  40. package/dist/src/telemetry/metrics.d.ts +2 -2
  41. package/dist/src/telemetry/metrics.js.map +1 -1
  42. package/dist/src/telemetry/providerQuotaService.d.ts +27 -0
  43. package/dist/src/telemetry/providerQuotaService.js +28 -0
  44. package/dist/src/telemetry/providerQuotaService.js.map +1 -0
  45. package/dist/src/telemetry/types.d.ts +15 -0
  46. package/dist/src/telemetry/types.js +2 -0
  47. package/dist/src/telemetry/types.js.map +1 -1
  48. package/dist/src/telemetry/uiTelemetry.d.ts +29 -0
  49. package/dist/src/telemetry/uiTelemetry.js +63 -2
  50. package/dist/src/telemetry/uiTelemetry.js.map +1 -1
  51. package/package.json +6 -2
package/README.md CHANGED
@@ -1,30 +1,31 @@
1
- # Gemini CLI
1
+ # Didim Agent CLI
2
2
 
3
- [![Gemini CLI CI](https://github.com/google-gemini/gemini-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/google-gemini/gemini-cli/actions/workflows/ci.yml)
4
- [![Gemini CLI E2E (Chained)](https://github.com/google-gemini/gemini-cli/actions/workflows/chained_e2e.yml/badge.svg)](https://github.com/google-gemini/gemini-cli/actions/workflows/chained_e2e.yml)
3
+ [![CI](https://github.com/google-gemini/gemini-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/google-gemini/gemini-cli/actions/workflows/ci.yml)
4
+ [![E2E](https://github.com/google-gemini/gemini-cli/actions/workflows/chained_e2e.yml/badge.svg)](https://github.com/google-gemini/gemini-cli/actions/workflows/chained_e2e.yml)
5
5
  [![Version](https://img.shields.io/npm/v/@didim365/agent-cli)](https://www.npmjs.com/package/@didim365/agent-cli)
6
6
  [![License](https://img.shields.io/github/license/google-gemini/gemini-cli)](https://github.com/google-gemini/gemini-cli/blob/main/LICENSE)
7
- [![View Code Wiki](https://assets.codewiki.google/readme-badge/static.svg)](https://codewiki.google/github.com/google-gemini/gemini-cli?utm_source=badge&utm_medium=github&utm_campaign=github.com/google-gemini/gemini-cli)
8
7
 
9
- ![Gemini CLI Screenshot](./docs/assets/gemini-screenshot.png)
8
+ ![Didim Agent CLI Screenshot](./docs/assets/gemini-screenshot.png)
10
9
 
11
- Gemini CLI is an open-source AI agent that brings the power of multiple AI
12
- providers directly into your terminal. It supports **Gemini**, **Claude**,
13
- **OpenAI**, and **OpenAI-compatible** (vLLM, Ollama, LM Studio) endpoints,
14
- giving you the most direct path from your prompt to your preferred model.
10
+ Didim Agent CLI is an open-source AI agent that brings the power of multiple AI
11
+ providers directly into your terminal. Built on the Gemini CLI foundation, it
12
+ supports **Gemini**, **Claude**, **OpenAI**, and **OpenAI-compatible** (vLLM,
13
+ Ollama, LM Studio) endpoints through a unified **provider adapter
14
+ architecture**, giving you the most direct path from your prompt to your
15
+ preferred model.
15
16
 
16
- Learn all about Gemini CLI in our [documentation](https://geminicli.com/docs/).
17
+ Learn all about Didim Agent CLI in our [documentation](./docs/index.md).
17
18
 
18
- ## 🚀 Why Gemini CLI?
19
+ ## 🚀 Why Didim Agent CLI?
19
20
 
20
- - **🎯 Free tier**: 60 requests/min and 1,000 requests/day with personal Google
21
- account.
22
21
  - **🧠 Multi-provider support**: Use Gemini, Claude, OpenAI, or local models
23
- (vLLM/Ollama) — switch providers and models with `/model`.
22
+ (vLLM/Ollama) — switch providers and models with `/model` or `/auth login`.
24
23
  - **🔧 Built-in tools**: Google Search grounding, file operations, shell
25
- commands, web fetching.
26
- - **🔌 Extensible**: MCP (Model Context Protocol) support for custom
27
- integrations.
24
+ commands, web fetching — all tools work across providers.
25
+ - **🔌 Extensible**: MCP (Model Context Protocol) support with deterministic
26
+ tool naming and sLM-compatible parameter normalization.
27
+ - **🤖 Sub-agent support**: Sub-agents work with all providers via the
28
+ provider-independent `llm*` pipeline.
28
29
  - **💻 Terminal-first**: Designed for developers who live in the command line.
29
30
  - **🛡️ Open source**: Apache 2.0 licensed.
30
31
 
@@ -129,7 +130,7 @@ npm install -g @didim365/agent-cli@nightly
129
130
  [Google Search](https://ai.google.dev/gemini-api/docs/grounding) for real-time
130
131
  information
131
132
  - Conversation checkpointing to save and resume complex sessions
132
- - Custom context files (GEMINI.md) to tailor behavior for your projects
133
+ - Custom context files (AGENTS.md) to tailor behavior for your projects
133
134
 
134
135
  ### GitHub Integration
135
136
 
@@ -151,12 +152,16 @@ Choose the authentication method that best fits your needs. You can also use
151
152
  `/auth login` inside the CLI to interactively select a provider and enter your
152
153
  API key.
153
154
 
155
+ > **Note:** Both `DIDIM_*` and `GEMINI_*` environment variable prefixes are
156
+ > supported. The CLI uses a central `resolveEnv()` utility that checks `DIDIM_*`
157
+ > first, then falls back to `GEMINI_*` for backward compatibility.
158
+
154
159
  ### Option 1: Login with Google (Gemini)
155
160
 
156
161
  **✨ Best for:** Individual developers and Gemini Code Assist license holders.
157
162
 
158
163
  ```bash
159
- gemini
164
+ didim
160
165
  # Select "Login with Google" and follow the browser authentication flow
161
166
  ```
162
167
 
@@ -164,7 +169,7 @@ For organization accounts, set your Google Cloud project first:
164
169
 
165
170
  ```bash
166
171
  export GOOGLE_CLOUD_PROJECT="YOUR_PROJECT_ID"
167
- gemini
172
+ didim
168
173
  ```
169
174
 
170
175
  ### Option 2: Gemini API Key
@@ -173,7 +178,7 @@ gemini
173
178
 
174
179
  ```bash
175
180
  export GEMINI_API_KEY="YOUR_API_KEY"
176
- gemini
181
+ didim
177
182
  ```
178
183
 
179
184
  ### Option 3: Claude (Anthropic)
@@ -182,7 +187,7 @@ gemini
182
187
 
183
188
  ```bash
184
189
  export ANTHROPIC_API_KEY="YOUR_API_KEY"
185
- gemini
190
+ didim
186
191
  ```
187
192
 
188
193
  ### Option 4: OpenAI
@@ -191,7 +196,7 @@ gemini
191
196
 
192
197
  ```bash
193
198
  export OPENAI_API_KEY="YOUR_API_KEY"
194
- gemini
199
+ didim
195
200
  ```
196
201
 
197
202
  ### Option 5: Vertex AI
@@ -201,7 +206,7 @@ gemini
201
206
  ```bash
202
207
  export GOOGLE_API_KEY="YOUR_API_KEY"
203
208
  export GOOGLE_GENAI_USE_VERTEXAI=true
204
- gemini
209
+ didim
205
210
  ```
206
211
 
207
212
  ### Option 6: OpenAI-compatible (vLLM, Ollama, LM Studio)
@@ -212,7 +217,7 @@ gemini
212
217
  export ENABLE_MULTI_PROVIDER=true
213
218
  export LLM_PROVIDER=openai-compatible
214
219
  export LLM_BASE_URL="http://localhost:8000/v1"
215
- gemini
220
+ didim
216
221
  ```
217
222
 
218
223
  For detailed setup for each provider, see the
@@ -226,21 +231,21 @@ For detailed setup for each provider, see the
226
231
  #### Start in current directory
227
232
 
228
233
  ```bash
229
- gemini
234
+ didim
230
235
  ```
231
236
 
232
237
  #### Include multiple directories
233
238
 
234
239
  ```bash
235
- gemini --include-directories ../lib,../docs
240
+ didim --include-directories ../lib,../docs
236
241
  ```
237
242
 
238
243
  #### Use specific model
239
244
 
240
245
  ```bash
241
- gemini -m gemini-2.5-flash # Gemini
242
- gemini -m claude-sonnet-4-5-20250929 # Claude
243
- gemini -m gpt-4.1 # OpenAI
246
+ didim -m gemini-2.5-flash # Gemini
247
+ didim -m claude-sonnet-4-5-20250929 # Claude
248
+ didim -m gpt-4.1 # OpenAI
244
249
  ```
245
250
 
246
251
  #### Non-interactive mode for scripts
@@ -248,21 +253,21 @@ gemini -m gpt-4.1 # OpenAI
248
253
  Get a simple text response:
249
254
 
250
255
  ```bash
251
- gemini -p "Explain the architecture of this codebase"
256
+ didim -p "Explain the architecture of this codebase"
252
257
  ```
253
258
 
254
259
  For more advanced scripting, including how to parse JSON and handle errors, use
255
260
  the `--output-format json` flag to get structured output:
256
261
 
257
262
  ```bash
258
- gemini -p "Explain the architecture of this codebase" --output-format json
263
+ didim -p "Explain the architecture of this codebase" --output-format json
259
264
  ```
260
265
 
261
266
  For real-time event streaming (useful for monitoring long-running operations),
262
267
  use `--output-format stream-json` to get newline-delimited JSON events:
263
268
 
264
269
  ```bash
265
- gemini -p "Run tests and deploy" --output-format stream-json
270
+ didim -p "Run tests and deploy" --output-format stream-json
266
271
  ```
267
272
 
268
273
  ### Quick Examples
@@ -271,16 +276,16 @@ gemini -p "Run tests and deploy" --output-format stream-json
271
276
 
272
277
  ```bash
273
278
  cd new-project/
274
- gemini
279
+ didim
275
280
  > Write me a Discord bot that answers questions using a FAQ.md file I will provide
276
281
  ```
277
282
 
278
283
  #### Analyze existing code
279
284
 
280
285
  ```bash
281
- git clone https://github.com/google-gemini/gemini-cli
282
- cd gemini-cli
283
- gemini
286
+ git clone https://github.com/user/project
287
+ cd project
288
+ didim
284
289
  > Give me a summary of all of the changes that went in yesterday
285
290
  ```
286
291
 
@@ -303,8 +308,8 @@ gemini
303
308
  (`/help`, `/chat`, etc).
304
309
  - [**Custom Commands**](./docs/cli/custom-commands.md) - Create your own
305
310
  reusable commands.
306
- - [**Context Files (GEMINI.md)**](./docs/cli/gemini-md.md) - Provide persistent
307
- context to Gemini CLI.
311
+ - [**Context Files (AGENTS.md)**](./docs/cli/gemini-md.md) - Provide persistent
312
+ context to the CLI.
308
313
  - [**Checkpointing**](./docs/cli/checkpointing.md) - Save and resume
309
314
  conversations.
310
315
  - [**Token Caching**](./docs/cli/token-caching.md) - Optimize token usage.
@@ -352,7 +357,7 @@ export ENABLE_MULTI_PROVIDER=true
352
357
  export LLM_PROVIDER=openai-compatible
353
358
  export LLM_BASE_URL="http://localhost:8000/v1"
354
359
  export LLM_MODEL="Qwen/Qwen2.5-7B-Instruct"
355
- gemini -m Qwen/Qwen2.5-7B-Instruct
360
+ didim -m Qwen/Qwen2.5-7B-Instruct
356
361
  ```
357
362
 
358
363
  ### Troubleshooting & Support
@@ -364,8 +369,8 @@ gemini -m Qwen/Qwen2.5-7B-Instruct
364
369
 
365
370
  ### Using MCP Servers
366
371
 
367
- Configure MCP servers in `~/.gemini/settings.json` to extend Gemini CLI with
368
- custom tools:
372
+ Configure MCP servers in `~/.didim/settings.json` (or `~/.gemini/settings.json`
373
+ for backward compatibility) to extend the CLI with custom tools:
369
374
 
370
375
  ```text
371
376
  > @github List my open pull requests
@@ -373,6 +378,11 @@ custom tools:
373
378
  > @database Run a query to find inactive users
374
379
  ```
375
380
 
381
+ MCP tool naming is **deterministic** — tools are registered with consistent
382
+ names regardless of server discovery order. Tool parameters are automatically
383
+ normalized via schema-based coercion, with enhanced tolerance for sLM (small
384
+ Language Model) tool call formatting.
385
+
376
386
  See the [MCP Server Integration guide](./docs/tools/mcp-server.md) for setup
377
387
  instructions.
378
388
 
@@ -416,5 +426,5 @@ See the [Uninstall Guide](docs/cli/uninstall.md) for removal instructions.
416
426
  ---
417
427
 
418
428
  <p align="center">
419
- Built with ❤️ by Google and the open source community
429
+ Built on Gemini CLI by Google extended by Didim365
420
430
  </p>
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ /**
7
+ * Pricing information for a single model.
8
+ *
9
+ * All prices are in USD per 1 million tokens.
10
+ */
11
+ export interface ModelPricing {
12
+ /** USD per 1M input tokens */
13
+ inputPerMToken: number;
14
+ /** USD per 1M output tokens */
15
+ outputPerMToken: number;
16
+ /** USD per 1M cached input tokens (optional — fallback to inputPerMToken) */
17
+ cachedPerMToken?: number;
18
+ }
19
+ /**
20
+ * Static pricing table: provider → model → pricing.
21
+ *
22
+ * Prices sourced from official API pricing pages (as of 2026-02).
23
+ * Models not listed here will be treated as $0 (unknown pricing).
24
+ */
25
+ export declare const MODEL_PRICING: Record<string, Record<string, ModelPricing>>;
26
+ /**
27
+ * Result of cost estimation across all models.
28
+ */
29
+ export interface CostEstimate {
30
+ /** Total estimated cost in USD */
31
+ totalCost: number;
32
+ /** Cost breakdown per provider in USD */
33
+ byProvider: Record<string, number>;
34
+ }
35
+ /**
36
+ * Token shape expected by estimateCost — subset of ModelMetrics.tokens.
37
+ */
38
+ interface TokenInfo {
39
+ input: number;
40
+ cached: number;
41
+ candidates: number;
42
+ }
43
+ /**
44
+ * Estimate cost based on token usage and static pricing table.
45
+ *
46
+ * @param models - Record of composite-key → token info
47
+ * @param parseKey - Function to extract { provider, model } from composite key
48
+ * @returns CostEstimate with total and per-provider breakdown
49
+ */
50
+ export declare function estimateCost(models: Record<string, {
51
+ tokens: TokenInfo;
52
+ }>, parseKey: (key: string) => {
53
+ provider: string;
54
+ model: string;
55
+ }): CostEstimate;
56
+ /**
57
+ * Format a CostEstimate into a human-readable string.
58
+ *
59
+ * - Zero: "$0.00"
60
+ * - Very small (< $0.01): "< $0.01"
61
+ * - Single provider: "$1.23"
62
+ * - Multi-provider: "$5.68 (Gemini $2.35 + Claude $3.33)"
63
+ */
64
+ export declare function formatCostString(estimate: CostEstimate): string;
65
+ export {};
@@ -0,0 +1,133 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ /**
7
+ * Static pricing table: provider → model → pricing.
8
+ *
9
+ * Prices sourced from official API pricing pages (as of 2026-02).
10
+ * Models not listed here will be treated as $0 (unknown pricing).
11
+ */
12
+ export const MODEL_PRICING = {
13
+ gemini: {
14
+ 'gemini-2.5-pro': {
15
+ inputPerMToken: 1.25,
16
+ outputPerMToken: 10.0,
17
+ cachedPerMToken: 0.125,
18
+ },
19
+ 'gemini-2.5-flash': {
20
+ inputPerMToken: 0.3,
21
+ outputPerMToken: 2.5,
22
+ cachedPerMToken: 0.03,
23
+ },
24
+ 'gemini-2.5-flash-lite': {
25
+ inputPerMToken: 0.1,
26
+ outputPerMToken: 0.4,
27
+ cachedPerMToken: 0.01,
28
+ },
29
+ },
30
+ claude: {
31
+ 'claude-opus-4-6': {
32
+ inputPerMToken: 5.0,
33
+ outputPerMToken: 25.0,
34
+ cachedPerMToken: 0.5,
35
+ },
36
+ 'claude-sonnet-4-5-20250929': {
37
+ inputPerMToken: 3.0,
38
+ outputPerMToken: 15.0,
39
+ cachedPerMToken: 0.3,
40
+ },
41
+ 'claude-haiku-4-5-20251001': {
42
+ inputPerMToken: 1.0,
43
+ outputPerMToken: 5.0,
44
+ cachedPerMToken: 0.1,
45
+ },
46
+ },
47
+ openai: {
48
+ 'gpt-5.2': {
49
+ inputPerMToken: 1.25,
50
+ outputPerMToken: 10.0,
51
+ cachedPerMToken: 0.625,
52
+ },
53
+ 'gpt-5-mini': {
54
+ inputPerMToken: 0.4,
55
+ outputPerMToken: 1.6,
56
+ cachedPerMToken: 0.1,
57
+ },
58
+ 'gpt-4.1': {
59
+ inputPerMToken: 2.0,
60
+ outputPerMToken: 8.0,
61
+ cachedPerMToken: 0.5,
62
+ },
63
+ 'gpt-4.1-mini': {
64
+ inputPerMToken: 0.4,
65
+ outputPerMToken: 1.6,
66
+ cachedPerMToken: 0.1,
67
+ },
68
+ o3: {
69
+ inputPerMToken: 2.0,
70
+ outputPerMToken: 8.0,
71
+ },
72
+ 'o4-mini': {
73
+ inputPerMToken: 1.1,
74
+ outputPerMToken: 4.4,
75
+ cachedPerMToken: 0.275,
76
+ },
77
+ },
78
+ };
79
+ const TOKENS_PER_MILLION = 1_000_000;
80
+ /**
81
+ * Estimate cost based on token usage and static pricing table.
82
+ *
83
+ * @param models - Record of composite-key → token info
84
+ * @param parseKey - Function to extract { provider, model } from composite key
85
+ * @returns CostEstimate with total and per-provider breakdown
86
+ */
87
+ export function estimateCost(models, parseKey) {
88
+ const byProvider = {};
89
+ for (const [key, entry] of Object.entries(models)) {
90
+ const { provider, model } = parseKey(key);
91
+ const pricing = MODEL_PRICING[provider]?.[model];
92
+ if (!pricing)
93
+ continue;
94
+ const nonCachedInput = Math.max(0, entry.tokens.input - entry.tokens.cached);
95
+ const cachedRate = pricing.cachedPerMToken ?? pricing.inputPerMToken;
96
+ const inputCost = (nonCachedInput / TOKENS_PER_MILLION) * pricing.inputPerMToken;
97
+ const cachedCost = (entry.tokens.cached / TOKENS_PER_MILLION) * cachedRate;
98
+ const outputCost = (entry.tokens.candidates / TOKENS_PER_MILLION) * pricing.outputPerMToken;
99
+ const modelCost = inputCost + cachedCost + outputCost;
100
+ byProvider[provider] = (byProvider[provider] ?? 0) + modelCost;
101
+ }
102
+ const totalCost = Object.values(byProvider).reduce((sum, v) => sum + v, 0);
103
+ return { totalCost, byProvider };
104
+ }
105
+ /**
106
+ * Capitalize first letter of a string.
107
+ */
108
+ function capitalize(s) {
109
+ return s.charAt(0).toUpperCase() + s.slice(1);
110
+ }
111
+ /**
112
+ * Format a CostEstimate into a human-readable string.
113
+ *
114
+ * - Zero: "$0.00"
115
+ * - Very small (< $0.01): "< $0.01"
116
+ * - Single provider: "$1.23"
117
+ * - Multi-provider: "$5.68 (Gemini $2.35 + Claude $3.33)"
118
+ */
119
+ export function formatCostString(estimate) {
120
+ if (estimate.totalCost === 0)
121
+ return '$0.00';
122
+ if (estimate.totalCost > 0 && estimate.totalCost < 0.01)
123
+ return '< $0.01';
124
+ const providers = Object.entries(estimate.byProvider);
125
+ const totalStr = `$${estimate.totalCost.toFixed(2)}`;
126
+ if (providers.length <= 1)
127
+ return totalStr;
128
+ const breakdown = providers
129
+ .map(([name, cost]) => `${capitalize(name)} $${cost.toFixed(2)}`)
130
+ .join(' + ');
131
+ return `${totalStr} (${breakdown})`;
132
+ }
133
+ //# sourceMappingURL=costEstimation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"costEstimation.js","sourceRoot":"","sources":["../../../src/config/costEstimation.ts"],"names":[],"mappings":"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"}
@@ -8,13 +8,17 @@ import type { Config } from '../config/config.js';
8
8
  import type { UserTierId } from '../code_assist/types.js';
9
9
  import type { LlmGenerateRequest, LlmGenerateResponse, LlmTokenCount, GenerateOptions } from '../providers/types.js';
10
10
  import type { LlmEventStream } from '../providers/events.js';
11
+ import type { ProviderQuotaService } from '../telemetry/providerQuotaService.js';
11
12
  /**
12
13
  * A decorator that wraps a ContentGenerator to add logging to API calls.
13
14
  */
14
15
  export declare class LoggingContentGenerator implements ContentGenerator {
15
16
  private readonly wrapped;
16
17
  private readonly config;
17
- constructor(wrapped: ContentGenerator, config: Config);
18
+ private providerQuotaService?;
19
+ constructor(wrapped: ContentGenerator, config: Config, providerQuotaService?: ProviderQuotaService);
20
+ /** Set the ProviderQuotaService after construction (for late binding). */
21
+ setProviderQuotaService(service: ProviderQuotaService): void;
18
22
  getWrapped(): ContentGenerator;
19
23
  get userTier(): UserTierId | undefined;
20
24
  get userTierName(): string | undefined;
@@ -31,5 +35,7 @@ export declare class LoggingContentGenerator implements ContentGenerator {
31
35
  llmGenerateContent(request: LlmGenerateRequest, userPromptId: string, options?: GenerateOptions): Promise<LlmGenerateResponse>;
32
36
  llmGenerateContentStream(request: LlmGenerateRequest, userPromptId: string, options?: GenerateOptions): LlmEventStream;
33
37
  private llmLoggingStreamWrapper;
38
+ private _logLlmApiResponse;
39
+ private _logLlmApiError;
34
40
  llmCountTokens(request: LlmGenerateRequest): Promise<LlmTokenCount>;
35
41
  }
@@ -4,7 +4,9 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import { ApiRequestEvent, ApiResponseEvent, ApiErrorEvent, } from '../telemetry/types.js';
7
- import { logApiError, logApiRequest, logApiResponse, } from '../telemetry/loggers.js';
7
+ import { logApiError, logApiRequest, logApiResponse, logProviderApiResponse, logProviderApiError, } from '../telemetry/loggers.js';
8
+ import { LlmEventType } from '../providers/events.js';
9
+ import { createProviderApiResponseEvent, createProviderApiErrorEvent, } from '../providers/telemetryBridge.js';
8
10
  import { debugLogger } from '../utils/debugLogger.js';
9
11
  import { CodeAssistServer } from '../code_assist/server.js';
10
12
  import { toContents } from '../code_assist/converter.js';
@@ -16,9 +18,15 @@ import { runInDevTraceSpan } from '../telemetry/trace.js';
16
18
  export class LoggingContentGenerator {
17
19
  wrapped;
18
20
  config;
19
- constructor(wrapped, config) {
21
+ providerQuotaService;
22
+ constructor(wrapped, config, providerQuotaService) {
20
23
  this.wrapped = wrapped;
21
24
  this.config = config;
25
+ this.providerQuotaService = providerQuotaService;
26
+ }
27
+ /** Set the ProviderQuotaService after construction (for late binding). */
28
+ setProviderQuotaService(service) {
29
+ this.providerQuotaService = service;
22
30
  }
23
31
  getWrapped() {
24
32
  return this.wrapped;
@@ -208,16 +216,23 @@ export class LoggingContentGenerator {
208
216
  throw new Error('Wrapped generator does not support provider-independent API');
209
217
  }
210
218
  const startTime = Date.now();
219
+ const provider = this.wrapped.providerName ?? 'unknown';
211
220
  debugLogger.debug(`[LLM] generateContent model=${request.model} promptId=${userPromptId}`);
212
221
  try {
213
222
  const response = await this.wrapped.llmGenerateContent(request, userPromptId, options);
214
223
  const durationMs = Date.now() - startTime;
215
224
  debugLogger.debug(`[LLM] generateContent completed in ${durationMs}ms`);
225
+ this._logLlmApiResponse(request.model, durationMs, userPromptId, provider, response.usage);
226
+ // Bridge rate-limit data to ProviderQuotaService (non-stream path)
227
+ if (response.rateLimits && this.providerQuotaService) {
228
+ this.providerQuotaService.update(provider, response.rateLimits);
229
+ }
216
230
  return response;
217
231
  }
218
232
  catch (error) {
219
233
  const durationMs = Date.now() - startTime;
220
234
  debugLogger.debug(`[LLM] generateContent error after ${durationMs}ms: ${error}`);
235
+ this._logLlmApiError(request.model, durationMs, userPromptId, provider, error);
221
236
  throw error;
222
237
  }
223
238
  }
@@ -226,24 +241,85 @@ export class LoggingContentGenerator {
226
241
  throw new Error('Wrapped generator does not support provider-independent API');
227
242
  }
228
243
  debugLogger.debug(`[LLM] generateContentStream model=${request.model} promptId=${userPromptId}`);
244
+ const startTime = Date.now();
229
245
  const stream = this.wrapped.llmGenerateContentStream(request, userPromptId, options);
230
- return this.llmLoggingStreamWrapper(stream, request.model, userPromptId);
246
+ return this.llmLoggingStreamWrapper(stream, request.model, userPromptId, startTime);
231
247
  }
232
- async *llmLoggingStreamWrapper(stream, model, userPromptId) {
233
- const startTime = Date.now();
248
+ async *llmLoggingStreamWrapper(stream, model, userPromptId, startTime) {
249
+ const provider = this.wrapped.providerName ?? 'unknown';
250
+ let collectedUsage;
251
+ let hasError = false;
234
252
  try {
235
253
  for await (const event of stream) {
254
+ // Collect usage from MessageEnd or Finished event.
255
+ // OpenAI puts usage in MessageEnd; Claude puts usage in Finished.
256
+ if ((event.type === LlmEventType.MessageEnd ||
257
+ event.type === LlmEventType.Finished) &&
258
+ event.usage) {
259
+ collectedUsage = event.usage;
260
+ }
261
+ // Bridge rate-limit data to ProviderQuotaService
262
+ if (event.type === LlmEventType.MessageEnd) {
263
+ const rateLimits = event
264
+ .rateLimits;
265
+ if (rateLimits && this.providerQuotaService) {
266
+ this.providerQuotaService.update(provider, rateLimits);
267
+ }
268
+ }
269
+ // Detect Error events (yielded, not thrown)
270
+ if (event.type === LlmEventType.Error) {
271
+ hasError = true;
272
+ const durationMs = Date.now() - startTime;
273
+ this._logLlmApiError(model, durationMs, userPromptId, provider, event.error);
274
+ }
236
275
  yield event;
237
276
  }
238
277
  const durationMs = Date.now() - startTime;
239
278
  debugLogger.debug(`[LLM] generateContentStream completed in ${durationMs}ms`);
279
+ // Log response telemetry only if no error event was encountered
280
+ if (!hasError) {
281
+ this._logLlmApiResponse(model, durationMs, userPromptId, provider, collectedUsage);
282
+ }
240
283
  }
241
284
  catch (error) {
242
285
  const durationMs = Date.now() - startTime;
243
286
  debugLogger.debug(`[LLM] generateContentStream error after ${durationMs}ms model=${model} promptId=${userPromptId}: ${error}`);
287
+ // Skip error telemetry if:
288
+ // - Error was already logged via yielded Error event (prevents double recording)
289
+ // - User cancelled the operation (AbortError is not an API failure)
290
+ const isAbort = error instanceof Error && error.name === 'AbortError';
291
+ if (!hasError && !isAbort) {
292
+ this._logLlmApiError(model, durationMs, userPromptId, provider, error);
293
+ }
244
294
  throw error;
245
295
  }
246
296
  }
297
+ _logLlmApiResponse(model, durationMs, promptId, provider, usage) {
298
+ const defaultUsage = {
299
+ promptTokens: 0,
300
+ completionTokens: 0,
301
+ totalTokens: 0,
302
+ };
303
+ const event = createProviderApiResponseEvent({
304
+ model,
305
+ durationMs,
306
+ promptId,
307
+ usage: usage ?? defaultUsage,
308
+ provider,
309
+ });
310
+ logProviderApiResponse(this.config, event);
311
+ }
312
+ _logLlmApiError(model, durationMs, promptId, provider, error) {
313
+ const errorMsg = error instanceof Error ? error.message : String(error ?? 'Unknown error');
314
+ const event = createProviderApiErrorEvent({
315
+ model,
316
+ error: errorMsg,
317
+ durationMs,
318
+ promptId,
319
+ provider,
320
+ });
321
+ logProviderApiError(this.config, event);
322
+ }
247
323
  async llmCountTokens(request) {
248
324
  if (!this.wrapped.llmCountTokens) {
249
325
  throw new Error('Wrapped generator does not support provider-independent API');