tlc-claude-code 1.4.9 → 1.5.2

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 (122) hide show
  1. package/CLAUDE.md +23 -0
  2. package/CODING-STANDARDS.md +408 -0
  3. package/bin/install.js +2 -0
  4. package/dashboard/dist/components/QualityGatePane.d.ts +38 -0
  5. package/dashboard/dist/components/QualityGatePane.js +31 -0
  6. package/dashboard/dist/components/QualityGatePane.test.d.ts +1 -0
  7. package/dashboard/dist/components/QualityGatePane.test.js +147 -0
  8. package/dashboard/dist/components/orchestration/AgentCard.d.ts +26 -0
  9. package/dashboard/dist/components/orchestration/AgentCard.js +60 -0
  10. package/dashboard/dist/components/orchestration/AgentCard.test.d.ts +1 -0
  11. package/dashboard/dist/components/orchestration/AgentCard.test.js +63 -0
  12. package/dashboard/dist/components/orchestration/AgentControls.d.ts +11 -0
  13. package/dashboard/dist/components/orchestration/AgentControls.js +20 -0
  14. package/dashboard/dist/components/orchestration/AgentControls.test.d.ts +1 -0
  15. package/dashboard/dist/components/orchestration/AgentControls.test.js +52 -0
  16. package/dashboard/dist/components/orchestration/AgentDetail.d.ts +35 -0
  17. package/dashboard/dist/components/orchestration/AgentDetail.js +37 -0
  18. package/dashboard/dist/components/orchestration/AgentDetail.test.d.ts +1 -0
  19. package/dashboard/dist/components/orchestration/AgentDetail.test.js +79 -0
  20. package/dashboard/dist/components/orchestration/AgentList.d.ts +31 -0
  21. package/dashboard/dist/components/orchestration/AgentList.js +47 -0
  22. package/dashboard/dist/components/orchestration/AgentList.test.d.ts +1 -0
  23. package/dashboard/dist/components/orchestration/AgentList.test.js +64 -0
  24. package/dashboard/dist/components/orchestration/CostMeter.d.ts +11 -0
  25. package/dashboard/dist/components/orchestration/CostMeter.js +28 -0
  26. package/dashboard/dist/components/orchestration/CostMeter.test.d.ts +1 -0
  27. package/dashboard/dist/components/orchestration/CostMeter.test.js +50 -0
  28. package/dashboard/dist/components/orchestration/ModelSelector.d.ts +20 -0
  29. package/dashboard/dist/components/orchestration/ModelSelector.js +12 -0
  30. package/dashboard/dist/components/orchestration/ModelSelector.test.d.ts +1 -0
  31. package/dashboard/dist/components/orchestration/ModelSelector.test.js +56 -0
  32. package/dashboard/dist/components/orchestration/OrchestrationDashboard.d.ts +28 -0
  33. package/dashboard/dist/components/orchestration/OrchestrationDashboard.js +28 -0
  34. package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.d.ts +1 -0
  35. package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.js +56 -0
  36. package/dashboard/dist/components/orchestration/QualityIndicator.d.ts +11 -0
  37. package/dashboard/dist/components/orchestration/QualityIndicator.js +37 -0
  38. package/dashboard/dist/components/orchestration/QualityIndicator.test.d.ts +1 -0
  39. package/dashboard/dist/components/orchestration/QualityIndicator.test.js +52 -0
  40. package/dashboard/dist/components/orchestration/index.d.ts +8 -0
  41. package/dashboard/dist/components/orchestration/index.js +8 -0
  42. package/package.json +1 -1
  43. package/server/lib/access-control.js +352 -0
  44. package/server/lib/access-control.test.js +322 -0
  45. package/server/lib/agents-cancel-command.js +139 -0
  46. package/server/lib/agents-cancel-command.test.js +180 -0
  47. package/server/lib/agents-get-command.js +159 -0
  48. package/server/lib/agents-get-command.test.js +167 -0
  49. package/server/lib/agents-list-command.js +150 -0
  50. package/server/lib/agents-list-command.test.js +149 -0
  51. package/server/lib/agents-logs-command.js +126 -0
  52. package/server/lib/agents-logs-command.test.js +198 -0
  53. package/server/lib/agents-retry-command.js +117 -0
  54. package/server/lib/agents-retry-command.test.js +192 -0
  55. package/server/lib/budget-limits.js +222 -0
  56. package/server/lib/budget-limits.test.js +214 -0
  57. package/server/lib/code-generator.js +291 -0
  58. package/server/lib/code-generator.test.js +307 -0
  59. package/server/lib/cost-command.js +290 -0
  60. package/server/lib/cost-command.test.js +202 -0
  61. package/server/lib/cost-optimizer.js +404 -0
  62. package/server/lib/cost-optimizer.test.js +232 -0
  63. package/server/lib/cost-projections.js +302 -0
  64. package/server/lib/cost-projections.test.js +217 -0
  65. package/server/lib/cost-reports.js +277 -0
  66. package/server/lib/cost-reports.test.js +254 -0
  67. package/server/lib/cost-tracker.js +216 -0
  68. package/server/lib/cost-tracker.test.js +302 -0
  69. package/server/lib/crypto-patterns.js +433 -0
  70. package/server/lib/crypto-patterns.test.js +346 -0
  71. package/server/lib/design-command.js +385 -0
  72. package/server/lib/design-command.test.js +249 -0
  73. package/server/lib/design-parser.js +237 -0
  74. package/server/lib/design-parser.test.js +290 -0
  75. package/server/lib/gemini-vision.js +377 -0
  76. package/server/lib/gemini-vision.test.js +282 -0
  77. package/server/lib/input-validator.js +360 -0
  78. package/server/lib/input-validator.test.js +295 -0
  79. package/server/lib/litellm-client.js +232 -0
  80. package/server/lib/litellm-client.test.js +267 -0
  81. package/server/lib/litellm-command.js +291 -0
  82. package/server/lib/litellm-command.test.js +260 -0
  83. package/server/lib/litellm-config.js +273 -0
  84. package/server/lib/litellm-config.test.js +212 -0
  85. package/server/lib/model-pricing.js +189 -0
  86. package/server/lib/model-pricing.test.js +178 -0
  87. package/server/lib/models-command.js +223 -0
  88. package/server/lib/models-command.test.js +193 -0
  89. package/server/lib/optimize-command.js +197 -0
  90. package/server/lib/optimize-command.test.js +193 -0
  91. package/server/lib/orchestration-integration.js +206 -0
  92. package/server/lib/orchestration-integration.test.js +235 -0
  93. package/server/lib/output-encoder.js +308 -0
  94. package/server/lib/output-encoder.test.js +312 -0
  95. package/server/lib/quality-evaluator.js +396 -0
  96. package/server/lib/quality-evaluator.test.js +337 -0
  97. package/server/lib/quality-gate-command.js +340 -0
  98. package/server/lib/quality-gate-command.test.js +321 -0
  99. package/server/lib/quality-gate-scorer.js +378 -0
  100. package/server/lib/quality-gate-scorer.test.js +376 -0
  101. package/server/lib/quality-history.js +265 -0
  102. package/server/lib/quality-history.test.js +359 -0
  103. package/server/lib/quality-presets.js +288 -0
  104. package/server/lib/quality-presets.test.js +269 -0
  105. package/server/lib/quality-retry.js +323 -0
  106. package/server/lib/quality-retry.test.js +325 -0
  107. package/server/lib/quality-thresholds.js +255 -0
  108. package/server/lib/quality-thresholds.test.js +237 -0
  109. package/server/lib/secure-auth.js +333 -0
  110. package/server/lib/secure-auth.test.js +288 -0
  111. package/server/lib/secure-code-command.js +540 -0
  112. package/server/lib/secure-code-command.test.js +309 -0
  113. package/server/lib/secure-errors.js +521 -0
  114. package/server/lib/secure-errors.test.js +298 -0
  115. package/server/lib/vision-command.js +372 -0
  116. package/server/lib/vision-command.test.js +255 -0
  117. package/server/lib/visual-command.js +350 -0
  118. package/server/lib/visual-command.test.js +256 -0
  119. package/server/lib/visual-testing.js +315 -0
  120. package/server/lib/visual-testing.test.js +357 -0
  121. package/server/package-lock.json +2 -2
  122. package/server/package.json +1 -1
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Agents Retry Command
3
+ * Retry failed agents
4
+ */
5
+
6
+ /**
7
+ * Check if agent can be retried
8
+ * @param {object} agent - Agent to check
9
+ * @returns {object} Result with canRetry and reason
10
+ */
11
+ function canRetry(agent) {
12
+ if (!agent) {
13
+ return { canRetry: false, reason: 'Agent not found' };
14
+ }
15
+
16
+ if (agent.status === 'failed' || agent.status === 'cancelled') {
17
+ return { canRetry: true };
18
+ }
19
+
20
+ return {
21
+ canRetry: false,
22
+ reason: `Cannot retry agent with status '${agent.status}'. Only failed or cancelled agents can be retried.`,
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Get retry context from failed agent
28
+ * @param {object} agent - Failed agent
29
+ * @returns {string} Context string
30
+ */
31
+ function getRetryContext(agent) {
32
+ const parts = [];
33
+
34
+ if (agent.error?.message) {
35
+ parts.push(`Previous error: ${agent.error.message}`);
36
+ }
37
+
38
+ if (agent.retryCount) {
39
+ parts.push(`Previous retries: ${agent.retryCount}`);
40
+ }
41
+
42
+ return parts.join('\n');
43
+ }
44
+
45
+ /**
46
+ * Create retry agent from failed agent
47
+ * @param {object} parent - Parent agent that failed
48
+ * @param {object} options - Retry options
49
+ * @returns {object} New agent data
50
+ */
51
+ function createRetryAgent(parent, options = {}) {
52
+ return {
53
+ parentId: parent.id,
54
+ model: options.model || parent.model,
55
+ prompt: parent.prompt,
56
+ status: 'queued',
57
+ startTime: new Date(),
58
+ tokens: { input: 0, output: 0 },
59
+ cost: 0,
60
+ retryCount: (parent.retryCount || 0) + 1,
61
+ retryContext: getRetryContext(parent),
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Execute agents retry command
67
+ * @param {object} context - Execution context
68
+ * @returns {Promise<object>} Command result
69
+ */
70
+ async function execute(context) {
71
+ const { registry, agentId, options = {} } = context;
72
+
73
+ const agent = registry.getAgent(agentId);
74
+
75
+ if (!agent) {
76
+ return {
77
+ success: false,
78
+ error: `Agent not found: ${agentId}`,
79
+ };
80
+ }
81
+
82
+ const { canRetry: allowed, reason } = canRetry(agent);
83
+
84
+ if (!allowed) {
85
+ return {
86
+ success: false,
87
+ error: reason,
88
+ };
89
+ }
90
+
91
+ // Check budget
92
+ if (registry.budget && registry.budget.remaining <= 0) {
93
+ return {
94
+ success: false,
95
+ error: 'Budget exhausted. Cannot retry.',
96
+ };
97
+ }
98
+
99
+ // Create new agent
100
+ const retryData = createRetryAgent(agent, options);
101
+ const newAgent = registry.createAgent(retryData);
102
+
103
+ return {
104
+ success: true,
105
+ newAgentId: newAgent.id,
106
+ parentId: agent.id,
107
+ model: newAgent.model,
108
+ message: `Created retry agent ${newAgent.id} (from ${agent.id})`,
109
+ };
110
+ }
111
+
112
+ module.exports = {
113
+ execute,
114
+ createRetryAgent,
115
+ canRetry,
116
+ getRetryContext,
117
+ };
@@ -0,0 +1,192 @@
1
+ const { describe, it, beforeEach, mock } = require('node:test');
2
+ const assert = require('node:assert');
3
+ const {
4
+ execute,
5
+ createRetryAgent,
6
+ canRetry,
7
+ getRetryContext,
8
+ } = require('./agents-retry-command.js');
9
+
10
+ describe('agents-retry-command', () => {
11
+ const failedAgent = {
12
+ id: 'agent-failed',
13
+ name: 'Failed Builder',
14
+ model: 'gpt-4',
15
+ status: 'failed',
16
+ prompt: 'Build the feature',
17
+ error: { message: 'Rate limit exceeded' },
18
+ cost: 0.10,
19
+ };
20
+
21
+ describe('execute', () => {
22
+ const createMockRegistry = (agents = [], budget = { remaining: 100 }) => ({
23
+ getAgent: (id) => agents.find(a => a.id === id),
24
+ createAgent: (data) => {
25
+ const newAgent = { id: `agent-${Date.now()}`, ...data };
26
+ agents.push(newAgent);
27
+ return newAgent;
28
+ },
29
+ budget,
30
+ });
31
+
32
+ it('retries failed agent', async () => {
33
+ const agents = [{ ...failedAgent }];
34
+ const result = await execute({
35
+ registry: createMockRegistry(agents),
36
+ agentId: 'agent-failed',
37
+ });
38
+ assert.ok(result.success);
39
+ assert.ok(result.newAgentId);
40
+ });
41
+
42
+ it('creates new agent', async () => {
43
+ const agents = [{ ...failedAgent }];
44
+ const registry = createMockRegistry(agents);
45
+ const result = await execute({
46
+ registry,
47
+ agentId: 'agent-failed',
48
+ });
49
+ assert.strictEqual(agents.length, 2);
50
+ });
51
+
52
+ it('includes failure context', async () => {
53
+ const agents = [{ ...failedAgent }];
54
+ const registry = createMockRegistry(agents);
55
+ const result = await execute({
56
+ registry,
57
+ agentId: 'agent-failed',
58
+ });
59
+ const newAgent = agents.find(a => a.id === result.newAgentId);
60
+ assert.ok(newAgent.retryContext || newAgent.parentId);
61
+ });
62
+
63
+ it('with --model overrides', async () => {
64
+ const agents = [{ ...failedAgent }];
65
+ const registry = createMockRegistry(agents);
66
+ const result = await execute({
67
+ registry,
68
+ agentId: 'agent-failed',
69
+ options: { model: 'claude-3-opus' },
70
+ });
71
+ const newAgent = agents.find(a => a.id === result.newAgentId);
72
+ assert.strictEqual(newAgent.model, 'claude-3-opus');
73
+ });
74
+
75
+ it('handles not failed agent', async () => {
76
+ const agents = [{ id: 'agent-1', status: 'running' }];
77
+ const result = await execute({
78
+ registry: createMockRegistry(agents),
79
+ agentId: 'agent-1',
80
+ });
81
+ assert.strictEqual(result.success, false);
82
+ assert.ok(result.error.includes('not failed') || result.error.includes('Cannot retry'));
83
+ });
84
+
85
+ it('handles unknown agent ID', async () => {
86
+ const result = await execute({
87
+ registry: createMockRegistry([]),
88
+ agentId: 'unknown',
89
+ });
90
+ assert.strictEqual(result.success, false);
91
+ assert.ok(result.error.includes('not found'));
92
+ });
93
+
94
+ it('shows new agent ID', async () => {
95
+ const agents = [{ ...failedAgent }];
96
+ const result = await execute({
97
+ registry: createMockRegistry(agents),
98
+ agentId: 'agent-failed',
99
+ });
100
+ assert.ok(result.newAgentId);
101
+ assert.ok(result.message.includes(result.newAgentId));
102
+ });
103
+
104
+ it('tracks retry parent', async () => {
105
+ const agents = [{ ...failedAgent }];
106
+ const registry = createMockRegistry(agents);
107
+ const result = await execute({
108
+ registry,
109
+ agentId: 'agent-failed',
110
+ });
111
+ const newAgent = agents.find(a => a.id === result.newAgentId);
112
+ assert.strictEqual(newAgent.parentId, 'agent-failed');
113
+ });
114
+
115
+ it('inherits original prompt', async () => {
116
+ const agents = [{ ...failedAgent }];
117
+ const registry = createMockRegistry(agents);
118
+ const result = await execute({
119
+ registry,
120
+ agentId: 'agent-failed',
121
+ });
122
+ const newAgent = agents.find(a => a.id === result.newAgentId);
123
+ assert.strictEqual(newAgent.prompt, 'Build the feature');
124
+ });
125
+
126
+ it('respects budget limits', async () => {
127
+ const agents = [{ ...failedAgent }];
128
+ const result = await execute({
129
+ registry: createMockRegistry(agents, { remaining: 0 }),
130
+ agentId: 'agent-failed',
131
+ });
132
+ assert.strictEqual(result.success, false);
133
+ assert.ok(result.error.includes('budget') || result.error.includes('Budget'));
134
+ });
135
+ });
136
+
137
+ describe('createRetryAgent', () => {
138
+ it('creates agent with parent reference', () => {
139
+ const parent = { id: 'parent-1', model: 'gpt-4', prompt: 'test' };
140
+ const agent = createRetryAgent(parent, {});
141
+ assert.strictEqual(agent.parentId, 'parent-1');
142
+ });
143
+
144
+ it('uses parent model by default', () => {
145
+ const parent = { id: 'parent-1', model: 'gpt-4', prompt: 'test' };
146
+ const agent = createRetryAgent(parent, {});
147
+ assert.strictEqual(agent.model, 'gpt-4');
148
+ });
149
+
150
+ it('allows model override', () => {
151
+ const parent = { id: 'parent-1', model: 'gpt-4', prompt: 'test' };
152
+ const agent = createRetryAgent(parent, { model: 'claude' });
153
+ assert.strictEqual(agent.model, 'claude');
154
+ });
155
+ });
156
+
157
+ describe('canRetry', () => {
158
+ it('returns true for failed agent', () => {
159
+ const result = canRetry({ status: 'failed' });
160
+ assert.ok(result.canRetry);
161
+ });
162
+
163
+ it('returns true for cancelled agent', () => {
164
+ const result = canRetry({ status: 'cancelled' });
165
+ assert.ok(result.canRetry);
166
+ });
167
+
168
+ it('returns false for running agent', () => {
169
+ const result = canRetry({ status: 'running' });
170
+ assert.strictEqual(result.canRetry, false);
171
+ });
172
+
173
+ it('returns false for completed agent', () => {
174
+ const result = canRetry({ status: 'completed' });
175
+ assert.strictEqual(result.canRetry, false);
176
+ });
177
+ });
178
+
179
+ describe('getRetryContext', () => {
180
+ it('includes error message', () => {
181
+ const agent = { error: { message: 'Rate limit' } };
182
+ const context = getRetryContext(agent);
183
+ assert.ok(context.includes('Rate limit'));
184
+ });
185
+
186
+ it('includes retry count', () => {
187
+ const agent = { retryCount: 2 };
188
+ const context = getRetryContext(agent);
189
+ assert.ok(context.includes('2'));
190
+ });
191
+ });
192
+ });
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Budget Limits Module
3
+ *
4
+ * Configurable budget limits with enforcement
5
+ */
6
+
7
+ /**
8
+ * Create a budget manager instance
9
+ * @returns {Object} Budget manager
10
+ */
11
+ function createBudgetManager() {
12
+ return {
13
+ daily: null,
14
+ monthly: null,
15
+ byModel: {},
16
+ warnThresholds: [0.5, 0.8],
17
+ };
18
+ }
19
+
20
+ /**
21
+ * Set a budget limit
22
+ * @param {Object} manager - Budget manager instance
23
+ * @param {Object} options - Budget options
24
+ * @param {string} options.type - Budget type: 'daily', 'monthly', or 'model'
25
+ * @param {number} options.limit - Budget limit in dollars
26
+ * @param {string} [options.model] - Model name (for model budgets)
27
+ * @param {number[]} [options.warnAt] - Warning thresholds (e.g., [0.5, 0.8])
28
+ */
29
+ function setBudget(manager, options) {
30
+ const { type, limit, model, warnAt } = options;
31
+
32
+ if (type === 'daily') {
33
+ manager.daily = limit;
34
+ } else if (type === 'monthly') {
35
+ manager.monthly = limit;
36
+ } else if (type === 'model' && model) {
37
+ manager.byModel[model] = limit;
38
+ }
39
+
40
+ if (warnAt) {
41
+ manager.warnThresholds = warnAt;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Check budget status
47
+ * @param {Object} manager - Budget manager instance
48
+ * @param {Object} options - Check options
49
+ * @param {number} options.currentSpend - Current spend amount
50
+ * @param {string} [options.type='daily'] - Budget type to check
51
+ * @param {string} [options.model] - Model to check (for model budgets)
52
+ * @returns {Object} Status result
53
+ */
54
+ function checkBudget(manager, options) {
55
+ const { currentSpend, type = 'daily', model } = options;
56
+
57
+ let limit;
58
+ if (type === 'model' && model) {
59
+ limit = manager.byModel[model];
60
+ } else if (type === 'monthly') {
61
+ limit = manager.monthly;
62
+ } else {
63
+ limit = manager.daily;
64
+ }
65
+
66
+ if (limit === null || limit === undefined) {
67
+ return { status: 'ok', message: 'No budget set' };
68
+ }
69
+
70
+ const percentage = currentSpend / limit;
71
+
72
+ if (percentage >= 1) {
73
+ return {
74
+ status: 'exceeded',
75
+ message: `Budget exceeded: $${currentSpend.toFixed(2)} / $${limit.toFixed(2)}`,
76
+ percentage: percentage * 100,
77
+ };
78
+ }
79
+
80
+ // Check warning thresholds (in reverse order to get highest matching)
81
+ for (let i = manager.warnThresholds.length - 1; i >= 0; i--) {
82
+ const threshold = manager.warnThresholds[i];
83
+ if (percentage >= threshold) {
84
+ return {
85
+ status: 'warning',
86
+ message: `${Math.round(threshold * 100)}% of budget used: $${currentSpend.toFixed(2)} / $${limit.toFixed(2)}`,
87
+ percentage: percentage * 100,
88
+ };
89
+ }
90
+ }
91
+
92
+ return {
93
+ status: 'ok',
94
+ message: `Under budget: $${currentSpend.toFixed(2)} / $${limit.toFixed(2)}`,
95
+ percentage: percentage * 100,
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Enforce budget (determine if operation should proceed)
101
+ * @param {Object} manager - Budget manager instance
102
+ * @param {Object} options - Enforcement options
103
+ * @param {number} options.currentSpend - Current spend
104
+ * @param {number} options.projectedCost - Cost of planned operation
105
+ * @param {boolean} [options.override] - Admin override flag
106
+ * @param {string} [options.type='daily'] - Budget type
107
+ * @returns {Object} Enforcement result
108
+ */
109
+ function enforceBudget(manager, options) {
110
+ const { currentSpend, projectedCost, override, type = 'daily' } = options;
111
+
112
+ if (override) {
113
+ return { allowed: true, reason: 'Admin override' };
114
+ }
115
+
116
+ let limit;
117
+ if (type === 'monthly') {
118
+ limit = manager.monthly;
119
+ } else {
120
+ limit = manager.daily;
121
+ }
122
+
123
+ if (limit === null || limit === undefined) {
124
+ return { allowed: true, reason: 'No budget set' };
125
+ }
126
+
127
+ const totalAfter = currentSpend + projectedCost;
128
+
129
+ if (currentSpend >= limit) {
130
+ return {
131
+ allowed: false,
132
+ reason: `Daily budget exceeded: $${currentSpend.toFixed(2)} >= $${limit.toFixed(2)}`,
133
+ };
134
+ }
135
+
136
+ if (totalAfter > limit) {
137
+ return {
138
+ allowed: false,
139
+ reason: `Operation would exceed budget: $${totalAfter.toFixed(2)} > $${limit.toFixed(2)}`,
140
+ };
141
+ }
142
+
143
+ return { allowed: true, reason: 'Within budget' };
144
+ }
145
+
146
+ /**
147
+ * Get daily budget limit
148
+ * @param {Object} manager - Budget manager instance
149
+ * @returns {number|null} Daily budget or null if not set
150
+ */
151
+ function getDailyBudget(manager) {
152
+ return manager.daily;
153
+ }
154
+
155
+ /**
156
+ * Get monthly budget limit
157
+ * @param {Object} manager - Budget manager instance
158
+ * @returns {number|null} Monthly budget or null if not set
159
+ */
160
+ function getMonthlyBudget(manager) {
161
+ return manager.monthly;
162
+ }
163
+
164
+ /**
165
+ * Get budget for a specific model
166
+ * @param {Object} manager - Budget manager instance
167
+ * @param {string} model - Model name
168
+ * @returns {number|null} Model budget or null if not set
169
+ */
170
+ function getModelBudget(manager, model) {
171
+ return manager.byModel[model] || null;
172
+ }
173
+
174
+ /**
175
+ * Reset budget spend tracking
176
+ * @param {Object} manager - Budget manager instance
177
+ * @param {Object} options - Reset options
178
+ * @param {string} options.type - Budget type to reset
179
+ */
180
+ function resetBudget(manager, options) {
181
+ // Note: The tracker handles actual spend tracking
182
+ // This is mainly for admin operations
183
+ const { type } = options;
184
+
185
+ // Budgets themselves remain, just the tracking resets
186
+ // This is handled by the cost tracker, not here
187
+ }
188
+
189
+ /**
190
+ * Calculate remaining budget
191
+ * @param {Object} manager - Budget manager instance
192
+ * @param {Object} options - Options with current spend
193
+ * @param {number} options.currentSpend - Current spend amount
194
+ * @returns {Object} Remaining amounts
195
+ */
196
+ function budgetRemaining(manager, options) {
197
+ const { currentSpend } = options;
198
+
199
+ const result = {};
200
+
201
+ if (manager.daily !== null) {
202
+ result.daily = Math.max(0, manager.daily - currentSpend);
203
+ }
204
+
205
+ if (manager.monthly !== null) {
206
+ result.monthly = Math.max(0, manager.monthly - currentSpend);
207
+ }
208
+
209
+ return result;
210
+ }
211
+
212
+ module.exports = {
213
+ createBudgetManager,
214
+ setBudget,
215
+ checkBudget,
216
+ enforceBudget,
217
+ getDailyBudget,
218
+ getMonthlyBudget,
219
+ getModelBudget,
220
+ resetBudget,
221
+ budgetRemaining,
222
+ };