@machina.ai/cell-cli-core 1.18.4-rc1 → 1.19.4-rc2

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 (164) hide show
  1. package/dist/package.json +2 -2
  2. package/dist/src/agents/codebase-investigator.js +1 -1
  3. package/dist/src/availability/modelAvailabilityService.d.ts +34 -0
  4. package/dist/src/availability/modelAvailabilityService.js +84 -0
  5. package/dist/src/availability/modelAvailabilityService.js.map +1 -0
  6. package/dist/src/availability/modelAvailabilityService.test.d.ts +6 -0
  7. package/dist/src/availability/modelAvailabilityService.test.js +140 -0
  8. package/dist/src/availability/modelAvailabilityService.test.js.map +1 -0
  9. package/dist/src/availability/modelPolicy.d.ts +42 -0
  10. package/dist/src/availability/modelPolicy.js +7 -0
  11. package/dist/src/availability/modelPolicy.js.map +1 -0
  12. package/dist/src/availability/policyCatalog.d.ts +20 -0
  13. package/dist/src/availability/policyCatalog.js +83 -0
  14. package/dist/src/availability/policyCatalog.js.map +1 -0
  15. package/dist/src/availability/policyCatalog.test.d.ts +6 -0
  16. package/dist/src/availability/policyCatalog.test.js +70 -0
  17. package/dist/src/availability/policyCatalog.test.js.map +1 -0
  18. package/dist/src/config/config.d.ts +10 -5
  19. package/dist/src/config/config.js +31 -18
  20. package/dist/src/config/config.js.map +1 -1
  21. package/dist/src/config/config.test.js +4 -73
  22. package/dist/src/config/config.test.js.map +1 -1
  23. package/dist/src/config/defaultModelConfigs.js +25 -0
  24. package/dist/src/config/defaultModelConfigs.js.map +1 -1
  25. package/dist/src/confirmation-bus/message-bus.d.ts +6 -0
  26. package/dist/src/confirmation-bus/message-bus.js +63 -2
  27. package/dist/src/confirmation-bus/message-bus.js.map +1 -1
  28. package/dist/src/confirmation-bus/types.d.ts +25 -2
  29. package/dist/src/confirmation-bus/types.js +3 -0
  30. package/dist/src/confirmation-bus/types.js.map +1 -1
  31. package/dist/src/core/AuthenticatedContentGenerator.d.ts +20 -0
  32. package/dist/src/core/AuthenticatedContentGenerator.js +115 -0
  33. package/dist/src/core/AuthenticatedContentGenerator.js.map +1 -0
  34. package/dist/src/core/baseLlmClient.d.ts +27 -1
  35. package/dist/src/core/baseLlmClient.js +66 -45
  36. package/dist/src/core/baseLlmClient.js.map +1 -1
  37. package/dist/src/core/baseLlmClient.test.js +94 -6
  38. package/dist/src/core/baseLlmClient.test.js.map +1 -1
  39. package/dist/src/core/client.js +77 -4
  40. package/dist/src/core/client.js.map +1 -1
  41. package/dist/src/core/client.test.js +57 -3
  42. package/dist/src/core/client.test.js.map +1 -1
  43. package/dist/src/core/clientHookTriggers.d.ts +36 -0
  44. package/dist/src/core/clientHookTriggers.js +76 -0
  45. package/dist/src/core/clientHookTriggers.js.map +1 -0
  46. package/dist/src/core/contentGenerator.js +8 -2
  47. package/dist/src/core/contentGenerator.js.map +1 -1
  48. package/dist/src/core/contentGenerator.test.js +108 -0
  49. package/dist/src/core/contentGenerator.test.js.map +1 -1
  50. package/dist/src/core/coreToolScheduler.test.js +2 -1
  51. package/dist/src/core/coreToolScheduler.test.js.map +1 -1
  52. package/dist/src/core/geminiChat.js +1 -1
  53. package/dist/src/core/geminiChat.js.map +1 -1
  54. package/dist/src/core/nonInteractiveToolExecutor.test.js +10 -2
  55. package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
  56. package/dist/src/core/prompts.js +21 -6
  57. package/dist/src/core/prompts.js.map +1 -1
  58. package/dist/src/core/prompts.test.js +23 -0
  59. package/dist/src/core/prompts.test.js.map +1 -1
  60. package/dist/src/core/turn.d.ts +5 -0
  61. package/dist/src/core/turn.js +10 -0
  62. package/dist/src/core/turn.js.map +1 -1
  63. package/dist/src/fallback/handler.js +1 -1
  64. package/dist/src/fallback/handler.js.map +1 -1
  65. package/dist/src/generated/git-commit.d.ts +2 -2
  66. package/dist/src/generated/git-commit.js +2 -2
  67. package/dist/src/hooks/hookEventHandler.d.ts +109 -0
  68. package/dist/src/hooks/hookEventHandler.js +486 -0
  69. package/dist/src/hooks/hookEventHandler.js.map +1 -0
  70. package/dist/src/hooks/hookEventHandler.test.d.ts +6 -0
  71. package/dist/src/hooks/hookEventHandler.test.js +384 -0
  72. package/dist/src/hooks/hookEventHandler.test.js.map +1 -0
  73. package/dist/src/hooks/hookSystem.d.ts +48 -0
  74. package/dist/src/hooks/hookSystem.js +83 -0
  75. package/dist/src/hooks/hookSystem.js.map +1 -0
  76. package/dist/src/hooks/hookSystem.test.d.ts +6 -0
  77. package/dist/src/hooks/hookSystem.test.js +213 -0
  78. package/dist/src/hooks/hookSystem.test.js.map +1 -0
  79. package/dist/src/hooks/index.d.ts +15 -0
  80. package/dist/src/hooks/index.js +15 -0
  81. package/dist/src/hooks/index.js.map +1 -0
  82. package/dist/src/index.d.ts +1 -0
  83. package/dist/src/index.js +2 -0
  84. package/dist/src/index.js.map +1 -1
  85. package/dist/src/policy/policy-engine.d.ts +17 -1
  86. package/dist/src/policy/policy-engine.js +90 -1
  87. package/dist/src/policy/policy-engine.js.map +1 -1
  88. package/dist/src/policy/policy-engine.test.js +222 -0
  89. package/dist/src/policy/policy-engine.test.js.map +1 -1
  90. package/dist/src/policy/types.d.ts +51 -1
  91. package/dist/src/policy/types.js +21 -0
  92. package/dist/src/policy/types.js.map +1 -1
  93. package/dist/src/services/chatCompressionService.d.ts +1 -0
  94. package/dist/src/services/chatCompressionService.js +22 -6
  95. package/dist/src/services/chatCompressionService.js.map +1 -1
  96. package/dist/src/services/chatCompressionService.test.js +27 -32
  97. package/dist/src/services/chatCompressionService.test.js.map +1 -1
  98. package/dist/src/services/gitService.js +3 -1
  99. package/dist/src/services/gitService.js.map +1 -1
  100. package/dist/src/services/gitService.test.js +10 -0
  101. package/dist/src/services/gitService.test.js.map +1 -1
  102. package/dist/src/services/modelConfigService.d.ts +1 -0
  103. package/dist/src/services/modelConfigService.js +6 -2
  104. package/dist/src/services/modelConfigService.js.map +1 -1
  105. package/dist/src/services/modelConfigService.test.js +111 -0
  106. package/dist/src/services/modelConfigService.test.js.map +1 -1
  107. package/dist/src/services/shellExecutionService.d.ts +2 -0
  108. package/dist/src/services/shellExecutionService.js +31 -3
  109. package/dist/src/services/shellExecutionService.js.map +1 -1
  110. package/dist/src/services/shellExecutionService.test.js +78 -1
  111. package/dist/src/services/shellExecutionService.test.js.map +1 -1
  112. package/dist/src/services/test-data/resolved-aliases.golden.json +20 -0
  113. package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +2 -1
  114. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +64 -30
  115. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
  116. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +6 -16
  117. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
  118. package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +1 -0
  119. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +2 -0
  120. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
  121. package/dist/src/telemetry/loggers.d.ts +2 -1
  122. package/dist/src/telemetry/loggers.js +12 -1
  123. package/dist/src/telemetry/loggers.js.map +1 -1
  124. package/dist/src/telemetry/loggers.test.js +0 -3
  125. package/dist/src/telemetry/loggers.test.js.map +1 -1
  126. package/dist/src/telemetry/metrics.d.ts +22 -0
  127. package/dist/src/telemetry/metrics.js +32 -0
  128. package/dist/src/telemetry/metrics.js.map +1 -1
  129. package/dist/src/telemetry/metrics.test.js +64 -0
  130. package/dist/src/telemetry/metrics.test.js.map +1 -1
  131. package/dist/src/telemetry/sanitize.d.ts +25 -0
  132. package/dist/src/telemetry/sanitize.js +48 -0
  133. package/dist/src/telemetry/sanitize.js.map +1 -0
  134. package/dist/src/telemetry/sanitize.test.d.ts +6 -0
  135. package/dist/src/telemetry/sanitize.test.js +279 -0
  136. package/dist/src/telemetry/sanitize.test.js.map +1 -0
  137. package/dist/src/telemetry/semantic.js +1 -1
  138. package/dist/src/telemetry/semantic.js.map +1 -1
  139. package/dist/src/telemetry/types.d.ts +19 -0
  140. package/dist/src/telemetry/types.js +65 -0
  141. package/dist/src/telemetry/types.js.map +1 -1
  142. package/dist/src/test-utils/mock-message-bus.d.ts +60 -0
  143. package/dist/src/test-utils/mock-message-bus.js +131 -0
  144. package/dist/src/test-utils/mock-message-bus.js.map +1 -0
  145. package/dist/src/test-utils/mock-tool.js +1 -1
  146. package/dist/src/test-utils/mock-tool.js.map +1 -1
  147. package/dist/src/tools/mcp-client.js +31 -0
  148. package/dist/src/tools/mcp-client.js.map +1 -1
  149. package/dist/src/tools/smart-edit.test.js +1 -1
  150. package/dist/src/tools/smart-edit.test.js.map +1 -1
  151. package/dist/src/utils/customHeaderUtils.d.ts +9 -0
  152. package/dist/src/utils/customHeaderUtils.js +34 -0
  153. package/dist/src/utils/customHeaderUtils.js.map +1 -0
  154. package/dist/src/utils/customHeaderUtils.test.d.ts +6 -0
  155. package/dist/src/utils/customHeaderUtils.test.js +77 -0
  156. package/dist/src/utils/customHeaderUtils.test.js.map +1 -0
  157. package/dist/src/utils/package.d.ts +14 -0
  158. package/dist/src/utils/package.js +15 -2
  159. package/dist/src/utils/package.js.map +1 -1
  160. package/dist/src/utils/schemaValidator.d.ts +1 -1
  161. package/dist/src/utils/schemaValidator.js +1 -1
  162. package/dist/src/utils/shell-utils.js +1 -1
  163. package/dist/tsconfig.tsbuildinfo +1 -1
  164. package/package.json +2 -2
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@machina.ai/cell-cli-core",
3
- "version": "1.18.4-rc1",
3
+ "version": "1.19.4-rc2",
4
4
  "description": "Cell CLI Core",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,7 +18,7 @@
18
18
  "@google/genai": "1.30.0",
19
19
  "@iarna/toml": "^2.2.5",
20
20
  "@joshua.litt/get-ripgrep": "^0.0.3",
21
- "@machina.ai/auth-keycloak": "1.11.0-rc0",
21
+ "@machina.ai/auth-keycloak": "1.19.4-rc0",
22
22
  "@machina.ai/config-yaml": "1.11.0-rc0",
23
23
  "@modelcontextprotocol/sdk": "^1.23.0",
24
24
  "@opentelemetry/api": "^1.9.0",
@@ -74,7 +74,7 @@ You are a sub-agent in a larger system. Your only responsibility is to provide d
74
74
  - **DO:** Find the key modules, classes, and functions that are part of the problem and its solution.
75
75
  - **DO:** Understand *why* the code is written the way it is. Question everything.
76
76
  - **DO:** Foresee the ripple effects of a change. If \`function A\` is modified, you must check its callers. If a data structure is altered, you must identify where its type definitions need to be updated.
77
- - **DO:** provide a conclusion and insights to the main agent that invoked you. If the agent is trying to solve a bug, you should provide the root cause of the bug, its impacts, how to fix it etc. If it's a new feature, you should provide insights on where to implement it, what chagnes are necessary etc.
77
+ - **DO:** provide a conclusion and insights to the main agent that invoked you. If the agent is trying to solve a bug, you should provide the root cause of the bug, its impacts, how to fix it etc. If it's a new feature, you should provide insights on where to implement it, what changes are necessary etc.
78
78
  - **DO NOT:** Write the final implementation code yourself.
79
79
  - **DO NOT:** Stop at the first relevant file. Your goal is a comprehensive understanding of the entire relevant subsystem.
80
80
  You operate in a non-interactive loop and must reason based on the information provided and the output of your tools.
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ export type ModelId = string;
7
+ export type TerminalUnavailabilityReason = 'quota' | 'capacity';
8
+ export type TurnUnavailabilityReason = 'retry_once_per_turn';
9
+ export type UnavailabilityReason = TerminalUnavailabilityReason | TurnUnavailabilityReason | 'unknown';
10
+ export type ModelHealthStatus = 'terminal' | 'sticky_retry';
11
+ export interface ModelAvailabilitySnapshot {
12
+ available: boolean;
13
+ reason?: UnavailabilityReason;
14
+ }
15
+ export interface ModelSelectionResult {
16
+ selected: ModelId | null;
17
+ attempts?: number;
18
+ skipped: Array<{
19
+ model: ModelId;
20
+ reason: UnavailabilityReason;
21
+ }>;
22
+ }
23
+ export declare class ModelAvailabilityService {
24
+ private readonly health;
25
+ markTerminal(model: ModelId, reason: TerminalUnavailabilityReason): void;
26
+ markHealthy(model: ModelId): void;
27
+ markRetryOncePerTurn(model: ModelId): void;
28
+ consumeStickyAttempt(model: ModelId): void;
29
+ snapshot(model: ModelId): ModelAvailabilitySnapshot;
30
+ selectFirstAvailable(models: ModelId[]): ModelSelectionResult;
31
+ resetTurn(): void;
32
+ private setState;
33
+ private clearState;
34
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ export class ModelAvailabilityService {
7
+ health = new Map();
8
+ markTerminal(model, reason) {
9
+ this.setState(model, {
10
+ status: 'terminal',
11
+ reason,
12
+ });
13
+ }
14
+ markHealthy(model) {
15
+ this.clearState(model);
16
+ }
17
+ markRetryOncePerTurn(model) {
18
+ const currentState = this.health.get(model);
19
+ // Do not override a terminal failure with a transient one.
20
+ if (currentState?.status === 'terminal') {
21
+ return;
22
+ }
23
+ // Only reset consumption if we are not already in the sticky_retry state.
24
+ // This prevents infinite loops if the model fails repeatedly in the same turn.
25
+ let consumed = false;
26
+ if (currentState?.status === 'sticky_retry') {
27
+ consumed = currentState.consumed;
28
+ }
29
+ this.setState(model, {
30
+ status: 'sticky_retry',
31
+ reason: 'retry_once_per_turn',
32
+ consumed,
33
+ });
34
+ }
35
+ consumeStickyAttempt(model) {
36
+ const state = this.health.get(model);
37
+ if (state?.status === 'sticky_retry') {
38
+ this.setState(model, { ...state, consumed: true });
39
+ }
40
+ }
41
+ snapshot(model) {
42
+ const state = this.health.get(model);
43
+ if (!state) {
44
+ return { available: true };
45
+ }
46
+ if (state.status === 'terminal') {
47
+ return { available: false, reason: state.reason };
48
+ }
49
+ if (state.status === 'sticky_retry' && state.consumed) {
50
+ return { available: false, reason: state.reason };
51
+ }
52
+ return { available: true };
53
+ }
54
+ selectFirstAvailable(models) {
55
+ const skipped = [];
56
+ for (const model of models) {
57
+ const snapshot = this.snapshot(model);
58
+ if (snapshot.available) {
59
+ const state = this.health.get(model);
60
+ // A sticky model is being attempted, so note that.
61
+ const attempts = state?.status === 'sticky_retry' ? 1 : undefined;
62
+ return { selected: model, skipped, attempts };
63
+ }
64
+ else {
65
+ skipped.push({ model, reason: snapshot.reason ?? 'unknown' });
66
+ }
67
+ }
68
+ return { selected: null, skipped };
69
+ }
70
+ resetTurn() {
71
+ for (const [model, state] of this.health.entries()) {
72
+ if (state.status === 'sticky_retry') {
73
+ this.setState(model, { ...state, consumed: false });
74
+ }
75
+ }
76
+ }
77
+ setState(model, nextState) {
78
+ this.health.set(model, nextState);
79
+ }
80
+ clearState(model) {
81
+ this.health.delete(model);
82
+ }
83
+ }
84
+ //# sourceMappingURL=modelAvailabilityService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modelAvailabilityService.js","sourceRoot":"","sources":["../../../src/availability/modelAvailabilityService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ export {};
@@ -0,0 +1,140 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { describe, expect, it, vi, beforeEach } from 'vitest';
7
+ import { ModelAvailabilityService } from './modelAvailabilityService.js';
8
+ describe('ModelAvailabilityService', () => {
9
+ let service;
10
+ const model = 'test-model';
11
+ beforeEach(() => {
12
+ service = new ModelAvailabilityService();
13
+ vi.useRealTimers();
14
+ });
15
+ it('returns available snapshot when no state recorded', () => {
16
+ expect(service.snapshot(model)).toEqual({ available: true });
17
+ });
18
+ it('tracks retry-once-per-turn failures', () => {
19
+ service.markRetryOncePerTurn(model);
20
+ expect(service.snapshot(model)).toEqual({ available: true });
21
+ service.consumeStickyAttempt(model);
22
+ expect(service.snapshot(model)).toEqual({
23
+ available: false,
24
+ reason: 'retry_once_per_turn',
25
+ });
26
+ service.resetTurn();
27
+ expect(service.snapshot(model)).toEqual({ available: true });
28
+ });
29
+ it('tracks terminal failures', () => {
30
+ service.markTerminal(model, 'quota');
31
+ expect(service.snapshot(model)).toEqual({
32
+ available: false,
33
+ reason: 'quota',
34
+ });
35
+ });
36
+ it('does not override terminal failure with sticky failure', () => {
37
+ service.markTerminal(model, 'quota');
38
+ service.markRetryOncePerTurn(model);
39
+ expect(service.snapshot(model)).toEqual({
40
+ available: false,
41
+ reason: 'quota',
42
+ });
43
+ });
44
+ it('selects models respecting terminal and sticky states', () => {
45
+ const stickyModel = 'stick-model';
46
+ const healthyModel = 'healthy-model';
47
+ service.markTerminal(model, 'capacity');
48
+ service.markRetryOncePerTurn(stickyModel);
49
+ const first = service.selectFirstAvailable([
50
+ model,
51
+ stickyModel,
52
+ healthyModel,
53
+ ]);
54
+ expect(first).toEqual({
55
+ selected: stickyModel,
56
+ attempts: 1,
57
+ skipped: [
58
+ {
59
+ model,
60
+ reason: 'capacity',
61
+ },
62
+ ],
63
+ });
64
+ service.consumeStickyAttempt(stickyModel);
65
+ const second = service.selectFirstAvailable([
66
+ model,
67
+ stickyModel,
68
+ healthyModel,
69
+ ]);
70
+ expect(second).toEqual({
71
+ selected: healthyModel,
72
+ skipped: [
73
+ {
74
+ model,
75
+ reason: 'capacity',
76
+ },
77
+ {
78
+ model: stickyModel,
79
+ reason: 'retry_once_per_turn',
80
+ },
81
+ ],
82
+ });
83
+ service.resetTurn();
84
+ const third = service.selectFirstAvailable([
85
+ model,
86
+ stickyModel,
87
+ healthyModel,
88
+ ]);
89
+ expect(third).toEqual({
90
+ selected: stickyModel,
91
+ attempts: 1,
92
+ skipped: [
93
+ {
94
+ model,
95
+ reason: 'capacity',
96
+ },
97
+ ],
98
+ });
99
+ });
100
+ it('preserves consumed state when marking retry-once-per-turn again', () => {
101
+ service.markRetryOncePerTurn(model);
102
+ service.consumeStickyAttempt(model);
103
+ // It is currently consumed
104
+ expect(service.snapshot(model).available).toBe(false);
105
+ // Marking it again should not reset the consumed flag
106
+ service.markRetryOncePerTurn(model);
107
+ expect(service.snapshot(model).available).toBe(false);
108
+ });
109
+ it('clears consumed state when marked healthy', () => {
110
+ service.markRetryOncePerTurn(model);
111
+ service.consumeStickyAttempt(model);
112
+ expect(service.snapshot(model).available).toBe(false);
113
+ service.markHealthy(model);
114
+ expect(service.snapshot(model).available).toBe(true);
115
+ // If we mark it sticky again, it should be fresh (not consumed)
116
+ service.markRetryOncePerTurn(model);
117
+ expect(service.snapshot(model).available).toBe(true);
118
+ });
119
+ it('resetTurn resets consumed state for multiple sticky models', () => {
120
+ const model2 = 'model-2';
121
+ service.markRetryOncePerTurn(model);
122
+ service.markRetryOncePerTurn(model2);
123
+ service.consumeStickyAttempt(model);
124
+ service.consumeStickyAttempt(model2);
125
+ expect(service.snapshot(model).available).toBe(false);
126
+ expect(service.snapshot(model2).available).toBe(false);
127
+ service.resetTurn();
128
+ expect(service.snapshot(model).available).toBe(true);
129
+ expect(service.snapshot(model2).available).toBe(true);
130
+ });
131
+ it('resetTurn does not affect terminal models', () => {
132
+ service.markTerminal(model, 'quota');
133
+ service.resetTurn();
134
+ expect(service.snapshot(model)).toEqual({
135
+ available: false,
136
+ reason: 'quota',
137
+ });
138
+ });
139
+ });
140
+ //# sourceMappingURL=modelAvailabilityService.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modelAvailabilityService.test.js","sourceRoot":"","sources":["../../../src/availability/modelAvailabilityService.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import type { ModelHealthStatus, ModelId } from './modelAvailabilityService.js';
7
+ /**
8
+ * Whether to prompt the user or fallback silently on a model API failure.
9
+ */
10
+ export type FallbackAction = 'silent' | 'prompt';
11
+ /**
12
+ * Type of possible errors from model API failures.
13
+ */
14
+ export type FailureKind = 'terminal' | 'transient' | 'not_found' | 'unknown';
15
+ /**
16
+ * Map from model API failure reason to user interaction.
17
+ */
18
+ export type ModelPolicyActionMap = Partial<Record<FailureKind, FallbackAction>>;
19
+ /**
20
+ * What state (e.g. Terminal, Sticky Retry) to set a model after failed API call.
21
+ */
22
+ export type ModelPolicyStateMap = Partial<Record<FailureKind, ModelHealthStatus>>;
23
+ /**
24
+ * Defines the policy for a single model in the availability chain.
25
+ *
26
+ * This includes:
27
+ * - Which model this policy applies to.
28
+ * - What actions to take (prompt vs silent fallback) for different failure kinds.
29
+ * - How the model's health status should transition upon failure.
30
+ * - Whether this model is considered a "last resort" (i.e. use if all models are unavailable).
31
+ */
32
+ export interface ModelPolicy {
33
+ model: ModelId;
34
+ actions: ModelPolicyActionMap;
35
+ stateTransitions: ModelPolicyStateMap;
36
+ isLastResort?: boolean;
37
+ }
38
+ /**
39
+ * A chain of model policies defining the priority and fallback behavior.
40
+ * The first model in the chain is the primary model.
41
+ */
42
+ export type ModelPolicyChain = ModelPolicy[];
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=modelPolicy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modelPolicy.js","sourceRoot":"","sources":["../../../src/availability/modelPolicy.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import type { ModelPolicy, ModelPolicyChain } from './modelPolicy.js';
7
+ import type { UserTierId } from '../code_assist/types.js';
8
+ export interface ModelPolicyOptions {
9
+ previewEnabled: boolean;
10
+ userTier?: UserTierId;
11
+ }
12
+ /**
13
+ * Returns the default ordered model policy chain for the user.
14
+ */
15
+ export declare function getModelPolicyChain(options: ModelPolicyOptions): ModelPolicyChain;
16
+ /**
17
+ * Provides a default policy scaffold for models not present in the catalog.
18
+ */
19
+ export declare function createDefaultPolicy(model: string): ModelPolicy;
20
+ export declare function validateModelPolicyChain(chain: ModelPolicyChain): void;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { DEFAULT_GEMINI_FLASH_MODEL, DEFAULT_GEMINI_MODEL, PREVIEW_GEMINI_MODEL, } from '../config/models.js';
7
+ const DEFAULT_ACTIONS = {
8
+ terminal: 'prompt',
9
+ transient: 'prompt',
10
+ not_found: 'prompt',
11
+ unknown: 'prompt',
12
+ };
13
+ const DEFAULT_STATE = {
14
+ terminal: 'terminal',
15
+ transient: 'terminal',
16
+ not_found: 'terminal',
17
+ unknown: 'terminal',
18
+ };
19
+ const DEFAULT_CHAIN = [
20
+ definePolicy({ model: DEFAULT_GEMINI_MODEL }),
21
+ definePolicy({ model: DEFAULT_GEMINI_FLASH_MODEL, isLastResort: true }),
22
+ ];
23
+ const PREVIEW_CHAIN = [
24
+ definePolicy({
25
+ model: PREVIEW_GEMINI_MODEL,
26
+ stateTransitions: { transient: 'sticky_retry' },
27
+ }),
28
+ definePolicy({ model: DEFAULT_GEMINI_MODEL }),
29
+ definePolicy({ model: DEFAULT_GEMINI_FLASH_MODEL, isLastResort: true }),
30
+ ];
31
+ /**
32
+ * Returns the default ordered model policy chain for the user.
33
+ */
34
+ export function getModelPolicyChain(options) {
35
+ if (options.previewEnabled) {
36
+ return cloneChain(PREVIEW_CHAIN);
37
+ }
38
+ return cloneChain(DEFAULT_CHAIN);
39
+ }
40
+ /**
41
+ * Provides a default policy scaffold for models not present in the catalog.
42
+ */
43
+ export function createDefaultPolicy(model) {
44
+ return definePolicy({ model });
45
+ }
46
+ export function validateModelPolicyChain(chain) {
47
+ if (chain.length === 0) {
48
+ throw new Error('Model policy chain must include at least one model.');
49
+ }
50
+ const lastResortCount = chain.filter((policy) => policy.isLastResort).length;
51
+ if (lastResortCount === 0) {
52
+ throw new Error('Model policy chain must include an `isLastResort` model.');
53
+ }
54
+ if (lastResortCount > 1) {
55
+ throw new Error('Model policy chain must only have one `isLastResort`.');
56
+ }
57
+ }
58
+ /**
59
+ * Helper to define a ModelPolicy with default actions and state transitions.
60
+ * Ensures every policy is a fresh instance to avoid shared state.
61
+ */
62
+ function definePolicy(config) {
63
+ return {
64
+ model: config.model,
65
+ isLastResort: config.isLastResort,
66
+ actions: { ...DEFAULT_ACTIONS, ...(config.actions ?? {}) },
67
+ stateTransitions: {
68
+ ...DEFAULT_STATE,
69
+ ...(config.stateTransitions ?? {}),
70
+ },
71
+ };
72
+ }
73
+ function clonePolicy(policy) {
74
+ return {
75
+ ...policy,
76
+ actions: { ...policy.actions },
77
+ stateTransitions: { ...policy.stateTransitions },
78
+ };
79
+ }
80
+ function cloneChain(chain) {
81
+ return chain.map(clonePolicy);
82
+ }
83
+ //# sourceMappingURL=policyCatalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policyCatalog.js","sourceRoot":"","sources":["../../../src/availability/policyCatalog.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAc7B,MAAM,eAAe,GAAyB;IAC5C,QAAQ,EAAE,QAAQ;IAClB,SAAS,EAAE,QAAQ;IACnB,SAAS,EAAE,QAAQ;IACnB,OAAO,EAAE,QAAQ;CAClB,CAAC;AAEF,MAAM,aAAa,GAAwB;IACzC,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,UAAU;IACrB,SAAS,EAAE,UAAU;IACrB,OAAO,EAAE,UAAU;CACpB,CAAC;AAEF,MAAM,aAAa,GAAqB;IACtC,YAAY,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAC7C,YAAY,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;CACxE,CAAC;AAEF,MAAM,aAAa,GAAqB;IACtC,YAAY,CAAC;QACX,KAAK,EAAE,oBAAoB;QAC3B,gBAAgB,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE;KAChD,CAAC;IACF,YAAY,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAC7C,YAAY,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;CACxE,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAA2B;IAE3B,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;QAC3B,OAAO,UAAU,CAAC,aAAa,CAAC,CAAC;IACnC,CAAC;IAED,OAAO,UAAU,CAAC,aAAa,CAAC,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,OAAO,YAAY,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,KAAuB;IAC9D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IACD,MAAM,eAAe,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC;IAC7E,IAAI,eAAe,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,eAAe,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;IAC3E,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,YAAY,CAAC,MAAoB;IACxC,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,OAAO,EAAE,EAAE,GAAG,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE;QAC1D,gBAAgB,EAAE;YAChB,GAAG,aAAa;YAChB,GAAG,CAAC,MAAM,CAAC,gBAAgB,IAAI,EAAE,CAAC;SACnC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,MAAmB;IACtC,OAAO;QACL,GAAG,MAAM;QACT,OAAO,EAAE,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE;QAC9B,gBAAgB,EAAE,EAAE,GAAG,MAAM,CAAC,gBAAgB,EAAE;KACjD,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,KAAuB;IACzC,OAAO,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;AAChC,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ export {};
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { describe, it, expect } from 'vitest';
7
+ import { createDefaultPolicy, getModelPolicyChain, validateModelPolicyChain, } from './policyCatalog.js';
8
+ import { DEFAULT_GEMINI_MODEL, PREVIEW_GEMINI_MODEL, } from '../config/models.js';
9
+ describe('policyCatalog', () => {
10
+ it('returns preview chain when preview enabled', () => {
11
+ const chain = getModelPolicyChain({ previewEnabled: true });
12
+ expect(chain[0]?.model).toBe(PREVIEW_GEMINI_MODEL);
13
+ expect(chain).toHaveLength(3);
14
+ });
15
+ it('returns default chain when preview disabled', () => {
16
+ const chain = getModelPolicyChain({ previewEnabled: false });
17
+ expect(chain[0]?.model).toBe(DEFAULT_GEMINI_MODEL);
18
+ expect(chain).toHaveLength(2);
19
+ });
20
+ it('marks preview transients as sticky retries', () => {
21
+ const [previewPolicy] = getModelPolicyChain({ previewEnabled: true });
22
+ expect(previewPolicy.model).toBe(PREVIEW_GEMINI_MODEL);
23
+ expect(previewPolicy.stateTransitions.transient).toBe('sticky_retry');
24
+ });
25
+ it('applies default actions and state transitions for unspecified kinds', () => {
26
+ const [previewPolicy] = getModelPolicyChain({ previewEnabled: true });
27
+ expect(previewPolicy.stateTransitions.not_found).toBe('terminal');
28
+ expect(previewPolicy.stateTransitions.unknown).toBe('terminal');
29
+ expect(previewPolicy.actions.unknown).toBe('prompt');
30
+ });
31
+ it('clones policy maps so edits do not leak between calls', () => {
32
+ const firstCall = getModelPolicyChain({ previewEnabled: false });
33
+ firstCall[0].actions.terminal = 'silent';
34
+ const secondCall = getModelPolicyChain({ previewEnabled: false });
35
+ expect(secondCall[0].actions.terminal).toBe('prompt');
36
+ });
37
+ it('passes when there is exactly one last-resort policy', () => {
38
+ const validChain = [
39
+ createDefaultPolicy('test-model'),
40
+ { ...createDefaultPolicy('last-resort'), isLastResort: true },
41
+ ];
42
+ expect(() => validateModelPolicyChain(validChain)).not.toThrow();
43
+ });
44
+ it('fails when no policies are marked last-resort', () => {
45
+ const chain = [
46
+ createDefaultPolicy('model-a'),
47
+ createDefaultPolicy('model-b'),
48
+ ];
49
+ expect(() => validateModelPolicyChain(chain)).toThrow('must include an `isLastResort`');
50
+ });
51
+ it('fails when a single-model chain is not last-resort', () => {
52
+ const chain = [createDefaultPolicy('lonely-model')];
53
+ expect(() => validateModelPolicyChain(chain)).toThrow('must include an `isLastResort`');
54
+ });
55
+ it('fails when multiple policies are marked last-resort', () => {
56
+ const chain = [
57
+ { ...createDefaultPolicy('model-a'), isLastResort: true },
58
+ { ...createDefaultPolicy('model-b'), isLastResort: true },
59
+ ];
60
+ expect(() => validateModelPolicyChain(chain)).toThrow('must only have one `isLastResort`');
61
+ });
62
+ it('createDefaultPolicy seeds default actions and states', () => {
63
+ const policy = createDefaultPolicy('custom');
64
+ expect(policy.actions.terminal).toBe('prompt');
65
+ expect(policy.actions.unknown).toBe('prompt');
66
+ expect(policy.stateTransitions.terminal).toBe('terminal');
67
+ expect(policy.stateTransitions.unknown).toBe('terminal');
68
+ });
69
+ });
70
+ //# sourceMappingURL=policyCatalog.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policyCatalog.test.js","sourceRoot":"","sources":["../../../src/availability/policyCatalog.test.ts"],"names":[],"mappings":"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"}
@@ -31,6 +31,7 @@ import type { EventEmitter } from 'node:events';
31
31
  import { MessageBus } from '../confirmation-bus/message-bus.js';
32
32
  import { PolicyEngine } from '../policy/policy-engine.js';
33
33
  import type { PolicyEngineConfig } from '../policy/types.js';
34
+ import { HookSystem } from '../hooks/index.js';
34
35
  import type { UserTierId } from '../code_assist/types.js';
35
36
  import type { Experiments } from '../code_assist/experiments/experiments.js';
36
37
  import { AgentRegistry } from '../agents/registry.js';
@@ -206,7 +207,6 @@ export interface ConfigParameters {
206
207
  useWriteTodos?: boolean;
207
208
  policyEngineConfig?: PolicyEngineConfig;
208
209
  output?: OutputSettings;
209
- useModelRouter?: boolean;
210
210
  enableMessageBusIntegration?: boolean;
211
211
  disableModelRouterForAuth?: AuthType[];
212
212
  codebaseInvestigatorSettings?: CodebaseInvestigatorSettings;
@@ -224,6 +224,7 @@ export interface ConfigParameters {
224
224
  [K in HookEventName]?: HookDefinition[];
225
225
  };
226
226
  previewFeatures?: boolean;
227
+ enableModelAvailabilityService?: boolean;
227
228
  }
228
229
  export declare class Config {
229
230
  private toolRegistry;
@@ -314,9 +315,6 @@ export declare class Config {
314
315
  private readonly messageBus;
315
316
  private readonly policyEngine;
316
317
  private readonly outputSettings;
317
- private useModelRouter;
318
- private readonly initialUseModelRouter;
319
- private readonly disableModelRouterForAuth?;
320
318
  private readonly enableMessageBusIntegration;
321
319
  private readonly codebaseInvestigatorSettings;
322
320
  private readonly continueOnFailedApiCall;
@@ -330,8 +328,10 @@ export declare class Config {
330
328
  private readonly hooks;
331
329
  private experiments;
332
330
  private experimentsPromise;
331
+ private hookSystem?;
333
332
  private previewModelFallbackMode;
334
333
  private previewModelBypassMode;
334
+ private readonly enableModelAvailabilityService;
335
335
  constructor(params: ConfigParameters);
336
336
  /**
337
337
  * Must only be called once, throws if called again.
@@ -339,6 +339,7 @@ export declare class Config {
339
339
  initialize(): Promise<void>;
340
340
  getContentGenerator(): ContentGenerator;
341
341
  refreshAuth(authMethod: AuthType, displayMessage?: (message: string) => void): Promise<void>;
342
+ getExperimentsAsync(): Promise<Experiments | undefined>;
342
343
  getUserTier(): UserTierId | undefined;
343
344
  /**
344
345
  * Provides access to the BaseLlmClient for stateless LLM operations.
@@ -455,6 +456,7 @@ export declare class Config {
455
456
  getExtensionLoader(): ExtensionLoader;
456
457
  getEnabledExtensions(): string[];
457
458
  getEnableExtensionReloading(): boolean;
459
+ isModelAvailabilityServiceEnabled(): boolean;
458
460
  getNoBrowser(): boolean;
459
461
  isBrowserLaunchSuppressed(): boolean;
460
462
  getSummarizeToolOutputConfig(): Record<string, SummarizeToolOutputSettings> | undefined;
@@ -500,7 +502,6 @@ export declare class Config {
500
502
  getUseSmartEdit(): boolean;
501
503
  getUseWriteTodos(): boolean;
502
504
  getOutputFormat(): OutputFormat;
503
- getUseModelRouter(): boolean;
504
505
  getGitService(): Promise<GitService>;
505
506
  getFileExclusions(): FileExclusions;
506
507
  getMessageBus(): MessageBus;
@@ -509,6 +510,10 @@ export declare class Config {
509
510
  getEnableHooks(): boolean;
510
511
  getCodebaseInvestigatorSettings(): CodebaseInvestigatorSettings;
511
512
  createToolRegistry(): Promise<ToolRegistry>;
513
+ /**
514
+ * Get the hook system instance
515
+ */
516
+ getHookSystem(): HookSystem | undefined;
512
517
  /**
513
518
  * Get hooks configuration
514
519
  */