manifest 5.32.0 → 5.33.0

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 (136) hide show
  1. package/README.md +36 -63
  2. package/dist/backend/analytics/analytics.module.js +2 -0
  3. package/dist/backend/analytics/controllers/agent-analytics.controller.js +2 -2
  4. package/dist/backend/analytics/controllers/agents.controller.js +7 -7
  5. package/dist/backend/analytics/controllers/costs.controller.js +3 -3
  6. package/dist/backend/analytics/controllers/tokens.controller.js +3 -3
  7. package/dist/backend/analytics/services/agent-lifecycle.service.js +102 -0
  8. package/dist/backend/analytics/services/aggregation.service.js +1 -73
  9. package/dist/backend/analytics/services/timeseries-queries.service.js +17 -112
  10. package/dist/backend/app.module.js +1 -3
  11. package/dist/backend/common/guards/api-key.guard.js +5 -1
  12. package/dist/backend/common/utils/cost-calculator.js +18 -0
  13. package/dist/backend/{routing → common/utils}/provider-aliases.js +1 -1
  14. package/dist/backend/database/local-bootstrap.service.js +2 -2
  15. package/dist/backend/database/ollama-sync.service.js +1 -1
  16. package/dist/backend/main.js +0 -7
  17. package/dist/backend/{routing/model-discovery → model-discovery}/model-discovery.module.js +4 -4
  18. package/dist/backend/{routing/model-discovery → model-discovery}/model-discovery.service.js +8 -8
  19. package/dist/backend/{routing/model-discovery → model-discovery}/model-fallback.js +2 -2
  20. package/dist/backend/{routing/model-discovery → model-discovery}/provider-model-fetcher.service.js +3 -3
  21. package/dist/backend/{routing/model-discovery → model-discovery}/provider-model-registry.service.js +1 -1
  22. package/dist/backend/model-prices/model-name-normalizer.js +5 -0
  23. package/dist/backend/model-prices/model-prices.module.js +1 -1
  24. package/dist/backend/model-prices/model-pricing-cache.service.js +1 -1
  25. package/dist/backend/otlp/guards/{otlp-auth.guard.js → agent-key-auth.guard.js} +15 -11
  26. package/dist/backend/otlp/otlp.module.js +4 -38
  27. package/dist/backend/otlp/services/api-key.service.js +2 -2
  28. package/dist/backend/routing/copilot.controller.js +79 -0
  29. package/dist/backend/routing/{custom-provider.controller.js → custom-provider/custom-provider.controller.js} +12 -12
  30. package/dist/backend/routing/custom-provider/custom-provider.module.js +27 -0
  31. package/dist/backend/routing/{custom-provider.service.js → custom-provider/custom-provider.service.js} +12 -12
  32. package/dist/backend/routing/model.controller.js +105 -0
  33. package/dist/backend/routing/oauth/minimax-oauth-helpers.js +70 -0
  34. package/dist/backend/routing/{minimax-oauth.controller.js → oauth/minimax-oauth.controller.js} +2 -2
  35. package/dist/backend/routing/{minimax-oauth.service.js → oauth/minimax-oauth.service.js} +32 -86
  36. package/dist/backend/routing/oauth/oauth.module.js +29 -0
  37. package/dist/backend/routing/{openai-oauth.controller.js → oauth/openai-oauth.controller.js} +15 -11
  38. package/dist/backend/routing/{openai-oauth.service.js → oauth/openai-oauth.service.js} +10 -13
  39. package/dist/backend/routing/provider.controller.js +152 -0
  40. package/dist/backend/routing/proxy/provider-client-converters.js +179 -0
  41. package/dist/backend/routing/proxy/provider-client.js +14 -155
  42. package/dist/backend/routing/proxy/proxy-fallback.service.js +213 -0
  43. package/dist/backend/routing/proxy/proxy-message-dedup.js +121 -0
  44. package/dist/backend/routing/proxy/proxy-message-recorder.js +31 -137
  45. package/dist/backend/routing/proxy/proxy-response-handler.js +162 -0
  46. package/dist/backend/routing/proxy/proxy-transport.js +90 -0
  47. package/dist/backend/{otlp/interfaces/otlp-log.js → routing/proxy/proxy-types.js} +1 -1
  48. package/dist/backend/routing/proxy/proxy.controller.js +19 -137
  49. package/dist/backend/routing/proxy/proxy.module.js +58 -0
  50. package/dist/backend/routing/proxy/proxy.service.js +36 -238
  51. package/dist/backend/routing/{resolve.controller.js → resolve/resolve.controller.js} +11 -11
  52. package/dist/backend/routing/resolve/resolve.module.js +28 -0
  53. package/dist/backend/routing/{resolve.service.js → resolve/resolve.service.js} +20 -16
  54. package/dist/backend/routing/routing-core/provider-key.service.js +180 -0
  55. package/dist/backend/routing/{routing.service.js → routing-core/provider.service.js} +66 -309
  56. package/dist/backend/routing/{resolve-agent.service.js → routing-core/resolve-agent.service.js} +2 -2
  57. package/dist/backend/routing/routing-core/routing-core.module.js +56 -0
  58. package/dist/backend/routing/{routing-invalidation.service.js → routing-core/routing-invalidation.service.js} +2 -2
  59. package/dist/backend/routing/{tier-auto-assign.service.js → routing-core/tier-auto-assign.service.js} +3 -3
  60. package/dist/backend/routing/routing-core/tier.service.js +179 -0
  61. package/dist/backend/routing/routing.module.js +20 -78
  62. package/dist/backend/routing/tier.controller.js +129 -0
  63. package/dist/backend/{routing/scorer → scoring}/__tests__/fixtures.js +5 -4
  64. package/dist/backend/scoring/config.js +383 -0
  65. package/dist/backend/{routing/scorer → scoring}/dimensions/contextual-dimensions.js +1 -3
  66. package/dist/backend/{routing/scorer → scoring}/dimensions/keyword-dimensions.js +1 -2
  67. package/dist/backend/{routing/scorer → scoring}/dimensions/structural-dimensions.js +1 -3
  68. package/dist/backend/{routing/scorer → scoring}/index.js +48 -35
  69. package/dist/backend/{routing/scorer → scoring}/momentum.js +1 -3
  70. package/dist/backend/{routing/scorer → scoring}/sigmoid.js +1 -5
  71. package/dist/index.js +8 -32
  72. package/dist/openclaw.plugin.json +14 -54
  73. package/dist/package.json +0 -1
  74. package/openclaw.plugin.json +14 -54
  75. package/package.json +9 -48
  76. package/public/assets/{Account-DAuDXzZL.js → Account-q2AcI1Fo.js} +1 -1
  77. package/public/assets/CopyButton-BrMDM22y.js +1 -0
  78. package/public/assets/{Limits-Do11RNu3.js → Limits-ReFWKJgq.js} +1 -1
  79. package/public/assets/{Login-CspKakT4.js → Login-BglUBPGW.js} +1 -1
  80. package/public/assets/MessageLog-D9C9HoEv.js +1 -0
  81. package/public/assets/{ModelPrices-D5TKbvU4.js → ModelPrices-BcaC13F4.js} +1 -1
  82. package/public/assets/Overview-C2sdWWbc.js +1 -0
  83. package/public/assets/{Register-D7KJjUkO.js → Register-C1SJfrvo.js} +1 -1
  84. package/public/assets/{ResetPassword-D2dm6bNn.js → ResetPassword-D1orZ45X.js} +1 -1
  85. package/public/assets/Routing-CznazwUp.js +3 -0
  86. package/public/assets/Settings-BKqYei3a.js +1 -0
  87. package/public/assets/SetupStepAddProvider-lHaGi8dV.js +4 -0
  88. package/public/assets/{SocialButtons-D-eCHUAq.js → SocialButtons-z4qd8zv0.js} +1 -1
  89. package/public/assets/index-DBDD7Bu4.css +1 -0
  90. package/public/assets/{index-DaGKqcbR.js → index-fn4HLThn.js} +2 -2
  91. package/public/assets/{model-display-q35tNU84.js → model-display-j5Ud1w_i.js} +1 -1
  92. package/public/assets/overview-CZD_Wg_N.css +1 -0
  93. package/public/assets/overview-CezrfEru.js +1 -0
  94. package/public/index.html +2 -2
  95. package/LICENSE.md +0 -21
  96. package/dist/backend/otlp/interfaces/index.js +0 -21
  97. package/dist/backend/otlp/interfaces/otlp-common.js +0 -3
  98. package/dist/backend/otlp/interfaces/otlp-metric.js +0 -3
  99. package/dist/backend/otlp/interfaces/otlp-trace.js +0 -3
  100. package/dist/backend/otlp/otlp.controller.js +0 -132
  101. package/dist/backend/otlp/proto/otlp-proto-defs.js +0 -187
  102. package/dist/backend/otlp/services/log-ingest.service.js +0 -61
  103. package/dist/backend/otlp/services/metric-ingest.service.js +0 -119
  104. package/dist/backend/otlp/services/otlp-decoder.service.js +0 -86
  105. package/dist/backend/otlp/services/otlp-helpers.js +0 -84
  106. package/dist/backend/otlp/services/trace-ingest.service.js +0 -570
  107. package/dist/backend/routing/routing.controller.js +0 -335
  108. package/dist/backend/routing/scorer/config.js +0 -130
  109. package/dist/backend/telemetry/dto/create-telemetry.dto.js +0 -89
  110. package/dist/backend/telemetry/telemetry.controller.js +0 -51
  111. package/dist/backend/telemetry/telemetry.module.js +0 -26
  112. package/dist/backend/telemetry/telemetry.service.js +0 -121
  113. package/dist/index.js.map +0 -7
  114. package/dist/json-file.js +0 -3
  115. package/dist/local-mode.js +0 -22
  116. package/dist/subscription.js +0 -3
  117. package/public/assets/MessageLog-DjaiR720.js +0 -1
  118. package/public/assets/Overview-ZRP_-v3U.js +0 -1
  119. package/public/assets/Routing-CqViGh6v.js +0 -3
  120. package/public/assets/Settings-BAFYf_4D.js +0 -1
  121. package/public/assets/SetupStepInstall-gm9pOgFh.js +0 -1
  122. package/public/assets/SetupStepVerify-B01hQCeb.js +0 -3
  123. package/public/assets/index-CbWrZHJK.css +0 -1
  124. package/public/assets/overview-7SUic0us.js +0 -1
  125. package/public/assets/overview-C3ErufU1.css +0 -1
  126. package/skills/manifest/SKILL.md +0 -247
  127. /package/dist/backend/{routing → common/utils}/subscription-support.js +0 -0
  128. /package/dist/backend/{routing/model-discovery → model-discovery}/model-fetcher.js +0 -0
  129. /package/dist/backend/routing/{copilot-device-auth.service.js → oauth/copilot-device-auth.service.js} +0 -0
  130. /package/dist/backend/routing/{openai-oauth.types.js → oauth/openai-oauth.types.js} +0 -0
  131. /package/dist/backend/routing/{routing-cache.service.js → routing-core/routing-cache.service.js} +0 -0
  132. /package/dist/backend/{routing/scorer → scoring}/dimensions/index.js +0 -0
  133. /package/dist/backend/{routing/scorer → scoring}/keyword-trie.js +0 -0
  134. /package/dist/backend/{routing/scorer → scoring}/overrides.js +0 -0
  135. /package/dist/backend/{routing/scorer → scoring}/text-extractor.js +0 -0
  136. /package/dist/backend/{routing/scorer → scoring}/types.js +0 -0
package/README.md CHANGED
@@ -7,8 +7,7 @@
7
7
  </picture>
8
8
  </p>
9
9
  <p align="center">
10
- 🦞 Take control of your
11
- OpenClaw costs
10
+ Take control of your OpenClaw costs
12
11
  </p>
13
12
 
14
13
 
@@ -34,94 +33,68 @@ OpenClaw costs
34
33
  <a href="https://trendshift.io/repositories/12890" target="_blank"><img src="https://trendshift.io/api/badge/repositories/12890" alt="mnfst%2Fmanifest | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
35
34
  </p>
36
35
 
37
- ## What do you get?
36
+ ## What is Manifest?
38
37
 
39
- - 🔀 **Route requests to the right model** cut costs up to 70%
40
- - 🔄 **Automatic fallbacks** — if a model fails, retry with backup models instantly
41
- - 🔔 **Set limits** — get alerts when usage goes over a threshold
38
+ Manifest is a model provider for OpenClaw. It sits between your agent and your LLM providers, scores each request, and routes it to the cheapest model that can handle it. Simple questions go to fast, cheap models. Hard problems go to expensive ones. You save money without thinking about it.
42
39
 
43
- ## Why Manifest
40
+ - Route requests to the right model -- cut costs up to 70%
41
+ - Automatic fallbacks -- if a model fails, the next one picks up
42
+ - Set limits -- get alerts when usage crosses a threshold
44
43
 
45
- OpenClaw sends all your requests to the same model, which is not cost-effective since you summon big models for tiny tasks. Manifest solves it by redirecting queries to the most cost-effective model.
44
+ ## Quick start
46
45
 
47
- Manifest is an OpenClaw plugin that intercepts your query, passes it through a 23-dimension scoring algorithm in <2ms and sends it to the most suitable model.
48
-
49
- Unlike almost all alternatives, everything stays on your machine. No suspicious installer, no black box, no third party, no crypto.
50
-
51
- ## Quick Start
52
-
53
- ### Cloud vs Local
54
-
55
- Manifest is available in cloud and local versions. While both versions install the same OpenClaw Plugin, the local version stores the telemetry data on your computer and the cloud version uses our secure platform.
56
-
57
- #### Use cloud if
58
- - You want a quick install
59
- - You want to access the dashboard from different devices
60
- - You want to connect multiple agents
61
-
62
- #### Use local if
63
- - You don't want the telemetry data to move from your computer
64
- - You don’t need multi-device access
65
- - You don't want to subscribe to a cloud service
66
- - You are using a local model like Ollama
67
-
68
- If you don't know which version to choose, start with the **cloud version**.
69
-
70
- ### Cloud (default)
46
+ ### Cloud
71
47
 
72
48
  ```bash
73
- openclaw plugins install manifest
74
- openclaw config set plugins.entries.manifest.config.apiKey "mnfst_YOUR_KEY"
49
+ openclaw plugins install manifest-provider
50
+ openclaw providers setup manifest-provider
75
51
  openclaw gateway restart
76
52
  ```
77
53
 
78
- Sign up at [app.manifest.build](https://app.manifest.build) to get your API key.
54
+ The setup wizard prompts for your API key from [app.manifest.build](https://app.manifest.build). After setup, `manifest/auto` is available as a model.
79
55
 
80
56
  ### Local
81
57
 
58
+ For a self-contained setup where everything stays on your machine:
59
+
82
60
  ```bash
83
61
  openclaw plugins install manifest
84
- openclaw config set plugins.entries.manifest.config.mode local
85
62
  openclaw gateway restart
86
63
  ```
87
64
 
88
- Dashboard opens at **http://127.0.0.1:2099**. Telemetry from your agents flows in automatically.
65
+ Dashboard opens at **http://127.0.0.1:2099**. The plugin starts an embedded server, runs the dashboard locally, and registers itself as a provider automatically. No account or API key needed.
89
66
 
90
- To use tailsacle to proxy it to your network (needs Tailscale installed in both devices).
67
+ ### Cloud vs local
91
68
 
92
- ```
93
- tailscale serve --bg 2099
94
- ```
69
+ Pick **cloud** (`manifest-provider`) if you want quick setup, multi-device access, or multiple agents. Pick **local** (`manifest`) if you want all data on your machine, don't need remote access, or use local models like Ollama.
95
70
 
96
- ## Features
71
+ Not sure? Start with cloud. You can switch anytime.
97
72
 
98
- - **LLM Router** — scores each query and calls the most suitable model
99
- - **Real-time dashboard** — tokens, costs, messages, and model usage at a glance
100
- - **No coding required** — Simple install as OpenClaw plugin
101
- - **OTLP-native** — standard OpenTelemetry ingestion (traces, metrics, logs)
73
+ ## How it works
102
74
 
103
- ## Privacy by architecture
75
+ Every request to `manifest/auto` goes through a 23-dimension scoring algorithm (runs in under 2ms). The scorer picks a tier -- simple, standard, complex, or reasoning -- and routes to the best model in that tier from your connected providers.
104
76
 
105
- **In local mode, your data stays on your machine.** All agent messages, token counts, costs, and telemetry are stored locally. In cloud mode, only OpenTelemetry metadata (model, tokens, latency) is sent — message content is never collected.
77
+ All routing data (tokens, costs, model, duration) is recorded automatically. You see it in the dashboard. No extra setup.
106
78
 
107
- **In cloud mode, the blind proxy physically cannot read your prompts.** This is fundamentally different from services saying "trust us."
79
+ ## Privacy
108
80
 
109
- In local mode, all Manifest data stays on your machine. No analytics or telemetry data is sent externally.
81
+ **Cloud mode**: Manifest proxies your request to the LLM provider. It records metadata (model name, token counts, latency, cost) but never stores prompt or response content. The proxy is blind to your data by design.
110
82
 
83
+ **Local mode**: Everything stays on your machine. No data leaves your network.
111
84
 
112
85
  ## Manifest vs OpenRouter
113
86
 
114
- | | Manifest | OpenRouter |
115
- | ------------ | ---------------------------------------------------------- | ------------------------------------------------------------- |
116
- | Architecture | Runs locally data stays on your machine | Cloud proxy all traffic routes through their servers |
117
- | Cost | Free | 5% fee on every API call |
118
- | Source code | MIT licensed, fully open | Proprietary |
119
- | Data privacy | 100% local routing and logging | Your prompts and responses pass through a third party |
120
- | Transparency | Open scoring algorithm see exactly why a model is chosen | Black box routing, no visibility into how models are selected |
87
+ | | Manifest | OpenRouter |
88
+ | ------------ | ------------------------------------------------- | ------------------------------------------------------------- |
89
+ | Architecture | Proxy -- your requests, your providers | Cloud proxy -- all traffic through their servers |
90
+ | Cost | Free | 5% fee on every API call |
91
+ | Source code | MIT, fully open | Proprietary |
92
+ | Data privacy | Metadata only (cloud) or fully local | Prompts and responses pass through a third party |
93
+ | Transparency | Open scoring -- see exactly why a model is chosen | No visibility into routing decisions |
121
94
 
122
- ## Supported Providers
95
+ ## Supported providers
123
96
 
124
- Works with **300+ models** across these providers:
97
+ Works with 300+ models across these providers:
125
98
 
126
99
  | Provider | Models |
127
100
  |----------|--------|
@@ -137,15 +110,15 @@ Works with **300+ models** across these providers:
137
110
  | [Amazon Nova](https://aws.amazon.com/ai/nova/) | `nova-pro`, `nova-lite`, `nova-micro` + 5 more |
138
111
  | [Z.ai (Zhipu)](https://z.ai/) | `glm-5`, `glm-4.7`, `glm-4.5` + 5 more |
139
112
  | [OpenRouter](https://openrouter.ai/) | 300+ models from all providers |
140
- | [Ollama](https://ollama.com/) | Run any model locally (Llama, Gemma, Mistral, ) |
113
+ | [Ollama](https://ollama.com/) | Run any model locally (Llama, Gemma, Mistral, ...) |
141
114
 
142
115
  ## Contributing
143
116
 
144
- Manifest is open source under the [MIT license](LICENSE). See [CONTRIBUTING.md](CONTRIBUTING.md) for the development setup, architecture notes, and workflow. Join the conversation on [Discord](https://discord.gg/FepAked3W7).
117
+ Manifest is open source under the [MIT license](LICENSE). See [CONTRIBUTING.md](CONTRIBUTING.md) for dev setup, architecture, and workflow. Join the conversation on [Discord](https://discord.gg/FepAked3W7).
145
118
 
146
- > **Want a hosted version instead?** Check out [app.manifest.build](https://app.manifest.build)
119
+ > Want a hosted version? Check out [app.manifest.build](https://app.manifest.build)
147
120
 
148
- ## Quick Links
121
+ ## Quick links
149
122
 
150
123
  - [GitHub](https://github.com/mnfst/manifest)
151
124
  - [Docs](https://manifest.build/docs)
@@ -17,6 +17,7 @@ const tool_execution_entity_1 = require("../entities/tool-execution.entity");
17
17
  const agent_log_entity_1 = require("../entities/agent-log.entity");
18
18
  const otlp_module_1 = require("../otlp/otlp.module");
19
19
  const aggregation_service_1 = require("./services/aggregation.service");
20
+ const agent_lifecycle_service_1 = require("./services/agent-lifecycle.service");
20
21
  const timeseries_queries_service_1 = require("./services/timeseries-queries.service");
21
22
  const messages_query_service_1 = require("./services/messages-query.service");
22
23
  const message_details_service_1 = require("./services/message-details.service");
@@ -46,6 +47,7 @@ exports.AnalyticsModule = AnalyticsModule = __decorate([
46
47
  ],
47
48
  providers: [
48
49
  aggregation_service_1.AggregationService,
50
+ agent_lifecycle_service_1.AgentLifecycleService,
49
51
  timeseries_queries_service_1.TimeseriesQueriesService,
50
52
  messages_query_service_1.MessagesQueryService,
51
53
  message_details_service_1.MessageDetailsService,
@@ -16,7 +16,7 @@ exports.AgentAnalyticsController = void 0;
16
16
  const common_1 = require("@nestjs/common");
17
17
  const cache_manager_1 = require("@nestjs/cache-manager");
18
18
  const public_decorator_1 = require("../../common/decorators/public.decorator");
19
- const otlp_auth_guard_1 = require("../../otlp/guards/otlp-auth.guard");
19
+ const agent_key_auth_guard_1 = require("../../otlp/guards/agent-key-auth.guard");
20
20
  const agent_analytics_service_1 = require("../services/agent-analytics.service");
21
21
  const range_query_dto_1 = require("../../common/dto/range-query.dto");
22
22
  const agent_cache_interceptor_1 = require("../../common/interceptors/agent-cache.interceptor");
@@ -61,7 +61,7 @@ __decorate([
61
61
  exports.AgentAnalyticsController = AgentAnalyticsController = __decorate([
62
62
  (0, common_1.Controller)('api/v1/agent'),
63
63
  (0, public_decorator_1.Public)(),
64
- (0, common_1.UseGuards)(otlp_auth_guard_1.OtlpAuthGuard),
64
+ (0, common_1.UseGuards)(agent_key_auth_guard_1.AgentKeyAuthGuard),
65
65
  (0, common_1.UseInterceptors)(agent_cache_interceptor_1.AgentCacheInterceptor),
66
66
  (0, cache_manager_1.CacheTTL)(cache_constants_1.DASHBOARD_CACHE_TTL_MS),
67
67
  __metadata("design:paramtypes", [agent_analytics_service_1.AgentAnalyticsService])
@@ -18,7 +18,7 @@ const typeorm_1 = require("typeorm");
18
18
  const cache_manager_1 = require("@nestjs/cache-manager");
19
19
  const config_1 = require("@nestjs/config");
20
20
  const timeseries_queries_service_1 = require("../services/timeseries-queries.service");
21
- const aggregation_service_1 = require("../services/aggregation.service");
21
+ const agent_lifecycle_service_1 = require("../services/agent-lifecycle.service");
22
22
  const api_key_service_1 = require("../../otlp/services/api-key.service");
23
23
  const current_user_decorator_1 = require("../../auth/current-user.decorator");
24
24
  const create_agent_dto_1 = require("../../common/dto/create-agent.dto");
@@ -30,14 +30,14 @@ const slugify_1 = require("../../common/utils/slugify");
30
30
  const tenant_cache_service_1 = require("../../common/services/tenant-cache.service");
31
31
  let AgentsController = class AgentsController {
32
32
  timeseries;
33
- aggregation;
33
+ lifecycle;
34
34
  apiKeyGenerator;
35
35
  config;
36
36
  tenantCache;
37
37
  cacheManager;
38
- constructor(timeseries, aggregation, apiKeyGenerator, config, tenantCache, cacheManager) {
38
+ constructor(timeseries, lifecycle, apiKeyGenerator, config, tenantCache, cacheManager) {
39
39
  this.timeseries = timeseries;
40
- this.aggregation = aggregation;
40
+ this.lifecycle = lifecycle;
41
41
  this.apiKeyGenerator = apiKeyGenerator;
42
42
  this.config = config;
43
43
  this.tenantCache = tenantCache;
@@ -99,7 +99,7 @@ let AgentsController = class AgentsController {
99
99
  throw new common_1.BadRequestException('Agent name produces an empty slug');
100
100
  }
101
101
  const displayName = body.name.trim();
102
- await this.aggregation.renameAgent(user.id, agentName, slug, displayName);
102
+ await this.lifecycle.renameAgent(user.id, agentName, slug, displayName);
103
103
  await this.cacheManager.del(this.agentListCacheKey(user.id));
104
104
  return { renamed: true, name: slug, display_name: displayName };
105
105
  }
@@ -107,7 +107,7 @@ let AgentsController = class AgentsController {
107
107
  if (this.config.get('MANIFEST_MODE') === 'local' && agentName === local_mode_constants_1.LOCAL_AGENT_NAME) {
108
108
  throw new common_1.ForbiddenException('Cannot delete the default local agent');
109
109
  }
110
- await this.aggregation.deleteAgent(user.id, agentName);
110
+ await this.lifecycle.deleteAgent(user.id, agentName);
111
111
  await this.cacheManager.del(this.agentListCacheKey(user.id));
112
112
  return { deleted: true };
113
113
  }
@@ -167,7 +167,7 @@ exports.AgentsController = AgentsController = __decorate([
167
167
  (0, common_1.Controller)('api/v1'),
168
168
  __param(5, (0, common_1.Inject)(cache_manager_1.CACHE_MANAGER)),
169
169
  __metadata("design:paramtypes", [timeseries_queries_service_1.TimeseriesQueriesService,
170
- aggregation_service_1.AggregationService,
170
+ agent_lifecycle_service_1.AgentLifecycleService,
171
171
  api_key_service_1.ApiKeyGeneratorService,
172
172
  config_1.ConfigService,
173
173
  tenant_cache_service_1.TenantCacheService, Object])
@@ -32,9 +32,9 @@ let CostsController = class CostsController {
32
32
  async getCosts(query, user) {
33
33
  const range = query.range ?? '7d';
34
34
  const agentName = query.agent_name;
35
- const [hourly, daily, byModel, prevCost] = await Promise.all([
36
- this.timeseries.getHourlyCosts(range, user.id, agentName),
37
- this.timeseries.getDailyCosts(range, user.id, agentName),
35
+ const [{ costUsage: hourly }, { costUsage: daily }, byModel, prevCost] = await Promise.all([
36
+ this.timeseries.getTimeseries(range, user.id, true, undefined, agentName),
37
+ this.timeseries.getTimeseries(range, user.id, false, undefined, agentName),
38
38
  this.timeseries.getCostByModel(range, user.id, agentName),
39
39
  this.aggregation.getPreviousCostTotal(range, user.id, agentName),
40
40
  ]);
@@ -32,9 +32,9 @@ let TokensController = class TokensController {
32
32
  async getTokens(query, user) {
33
33
  const range = query.range ?? '24h';
34
34
  const agentName = query.agent_name;
35
- const [hourly, daily, prevTokens] = await Promise.all([
36
- this.timeseries.getHourlyTokens(range, user.id, agentName),
37
- this.timeseries.getDailyTokens(range, user.id, agentName),
35
+ const [{ tokenUsage: hourly }, { tokenUsage: daily }, prevTokens] = await Promise.all([
36
+ this.timeseries.getTimeseries(range, user.id, true, undefined, agentName),
37
+ this.timeseries.getTimeseries(range, user.id, false, undefined, agentName),
38
38
  this.aggregation.getPreviousTokenTotal(range, user.id, agentName),
39
39
  ]);
40
40
  const inputTotal = hourly.reduce((s, h) => s + h.input_tokens, 0);
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.AgentLifecycleService = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const typeorm_1 = require("@nestjs/typeorm");
18
+ const typeorm_2 = require("typeorm");
19
+ const agent_entity_1 = require("../../entities/agent.entity");
20
+ let AgentLifecycleService = class AgentLifecycleService {
21
+ agentRepo;
22
+ dataSource;
23
+ constructor(agentRepo, dataSource) {
24
+ this.agentRepo = agentRepo;
25
+ this.dataSource = dataSource;
26
+ }
27
+ async deleteAgent(userId, agentName) {
28
+ const agent = await this.agentRepo
29
+ .createQueryBuilder('a')
30
+ .leftJoin('a.tenant', 't')
31
+ .where('t.name = :userId', { userId })
32
+ .andWhere('a.name = :agentName', { agentName })
33
+ .getOne();
34
+ if (!agent) {
35
+ throw new common_1.NotFoundException(`Agent "${agentName}" not found`);
36
+ }
37
+ await this.agentRepo.delete(agent.id);
38
+ }
39
+ async renameAgent(userId, currentName, newName, displayName) {
40
+ const agent = await this.agentRepo
41
+ .createQueryBuilder('a')
42
+ .leftJoin('a.tenant', 't')
43
+ .where('t.name = :userId', { userId })
44
+ .andWhere('a.name = :currentName', { currentName })
45
+ .getOne();
46
+ if (!agent) {
47
+ throw new common_1.NotFoundException(`Agent "${currentName}" not found`);
48
+ }
49
+ if (newName === currentName) {
50
+ if (displayName !== undefined) {
51
+ await this.agentRepo
52
+ .createQueryBuilder()
53
+ .update('agents')
54
+ .set({ display_name: displayName })
55
+ .where('id = :id', { id: agent.id })
56
+ .execute();
57
+ }
58
+ return;
59
+ }
60
+ const duplicate = await this.agentRepo
61
+ .createQueryBuilder('a')
62
+ .leftJoin('a.tenant', 't')
63
+ .where('t.name = :userId', { userId })
64
+ .andWhere('a.name = :newName', { newName })
65
+ .getOne();
66
+ if (duplicate) {
67
+ throw new common_1.ConflictException(`Agent "${newName}" already exists`);
68
+ }
69
+ await this.dataSource.transaction(async (manager) => {
70
+ const agentUpdate = { name: newName };
71
+ if (displayName !== undefined)
72
+ agentUpdate['display_name'] = displayName;
73
+ await manager
74
+ .createQueryBuilder()
75
+ .update('agents')
76
+ .set(agentUpdate)
77
+ .where('id = :id', { id: agent.id })
78
+ .execute();
79
+ const tables = [
80
+ 'agent_messages',
81
+ 'notification_rules',
82
+ 'notification_logs',
83
+ 'token_usage_snapshots',
84
+ 'cost_snapshots',
85
+ ];
86
+ await Promise.all(tables.map((table) => manager
87
+ .createQueryBuilder()
88
+ .update(table)
89
+ .set({ agent_name: newName })
90
+ .where('agent_name = :currentName', { currentName })
91
+ .execute()));
92
+ });
93
+ }
94
+ };
95
+ exports.AgentLifecycleService = AgentLifecycleService;
96
+ exports.AgentLifecycleService = AgentLifecycleService = __decorate([
97
+ (0, common_1.Injectable)(),
98
+ __param(0, (0, typeorm_1.InjectRepository)(agent_entity_1.Agent)),
99
+ __metadata("design:paramtypes", [typeorm_2.Repository,
100
+ typeorm_2.DataSource])
101
+ ], AgentLifecycleService);
102
+ //# sourceMappingURL=agent-lifecycle.service.js.map
@@ -17,20 +17,17 @@ const common_1 = require("@nestjs/common");
17
17
  const typeorm_1 = require("@nestjs/typeorm");
18
18
  const typeorm_2 = require("typeorm");
19
19
  const agent_message_entity_1 = require("../../entities/agent-message.entity");
20
- const agent_entity_1 = require("../../entities/agent.entity");
21
20
  const range_util_1 = require("../../common/utils/range.util");
22
21
  const query_helpers_1 = require("./query-helpers");
23
22
  const tenant_cache_service_1 = require("../../common/services/tenant-cache.service");
24
23
  const sql_dialect_1 = require("../../common/utils/sql-dialect");
25
24
  let AggregationService = class AggregationService {
26
25
  turnRepo;
27
- agentRepo;
28
26
  dataSource;
29
27
  tenantCache;
30
28
  dialect;
31
- constructor(turnRepo, agentRepo, dataSource, tenantCache) {
29
+ constructor(turnRepo, dataSource, tenantCache) {
32
30
  this.turnRepo = turnRepo;
33
- this.agentRepo = agentRepo;
34
31
  this.dataSource = dataSource;
35
32
  this.tenantCache = tenantCache;
36
33
  this.dialect = (0, sql_dialect_1.detectDialect)(this.dataSource.options.type);
@@ -196,81 +193,12 @@ let AggregationService = class AggregationService {
196
193
  messages: { value: curMsgs, trend_pct: (0, query_helpers_1.computeTrend)(curMsgs, prevMsgs) },
197
194
  };
198
195
  }
199
- async deleteAgent(userId, agentName) {
200
- const agent = await this.agentRepo
201
- .createQueryBuilder('a')
202
- .leftJoin('a.tenant', 't')
203
- .where('t.name = :userId', { userId })
204
- .andWhere('a.name = :agentName', { agentName })
205
- .getOne();
206
- if (!agent) {
207
- throw new common_1.NotFoundException(`Agent "${agentName}" not found`);
208
- }
209
- await this.agentRepo.delete(agent.id);
210
- }
211
- async renameAgent(userId, currentName, newName, displayName) {
212
- const agent = await this.agentRepo
213
- .createQueryBuilder('a')
214
- .leftJoin('a.tenant', 't')
215
- .where('t.name = :userId', { userId })
216
- .andWhere('a.name = :currentName', { currentName })
217
- .getOne();
218
- if (!agent) {
219
- throw new common_1.NotFoundException(`Agent "${currentName}" not found`);
220
- }
221
- if (newName === currentName) {
222
- if (displayName !== undefined) {
223
- await this.agentRepo
224
- .createQueryBuilder()
225
- .update('agents')
226
- .set({ display_name: displayName })
227
- .where('id = :id', { id: agent.id })
228
- .execute();
229
- }
230
- return;
231
- }
232
- const duplicate = await this.agentRepo
233
- .createQueryBuilder('a')
234
- .leftJoin('a.tenant', 't')
235
- .where('t.name = :userId', { userId })
236
- .andWhere('a.name = :newName', { newName })
237
- .getOne();
238
- if (duplicate) {
239
- throw new common_1.ConflictException(`Agent "${newName}" already exists`);
240
- }
241
- await this.dataSource.transaction(async (manager) => {
242
- const agentUpdate = { name: newName };
243
- if (displayName !== undefined)
244
- agentUpdate['display_name'] = displayName;
245
- await manager
246
- .createQueryBuilder()
247
- .update('agents')
248
- .set(agentUpdate)
249
- .where('id = :id', { id: agent.id })
250
- .execute();
251
- const tables = [
252
- 'agent_messages',
253
- 'notification_rules',
254
- 'notification_logs',
255
- 'token_usage_snapshots',
256
- 'cost_snapshots',
257
- ];
258
- await Promise.all(tables.map((table) => manager
259
- .createQueryBuilder()
260
- .update(table)
261
- .set({ agent_name: newName })
262
- .where('agent_name = :currentName', { currentName })
263
- .execute()));
264
- });
265
- }
266
196
  };
267
197
  exports.AggregationService = AggregationService;
268
198
  exports.AggregationService = AggregationService = __decorate([
269
199
  (0, common_1.Injectable)(),
270
200
  __param(0, (0, typeorm_1.InjectRepository)(agent_message_entity_1.AgentMessage)),
271
- __param(1, (0, typeorm_1.InjectRepository)(agent_entity_1.Agent)),
272
201
  __metadata("design:paramtypes", [typeorm_2.Repository,
273
- typeorm_2.Repository,
274
202
  typeorm_2.DataSource,
275
203
  tenant_cache_service_1.TenantCacheService])
276
204
  ], AggregationService);
@@ -35,112 +35,6 @@ let TimeseriesQueriesService = class TimeseriesQueriesService {
35
35
  this.tenantCache = tenantCache;
36
36
  this.dialect = (0, sql_dialect_1.detectDialect)(this.dataSource.options.type);
37
37
  }
38
- async getHourlyTokens(range, userId, agentName) {
39
- const tenantId = (await this.tenantCache.resolve(userId)) ?? undefined;
40
- const interval = (0, range_util_1.rangeToInterval)(range);
41
- const cutoff = (0, sql_dialect_1.computeCutoff)(interval);
42
- const hourExpr = (0, sql_dialect_1.sqlHourBucket)('at.timestamp', this.dialect);
43
- const qb = this.turnRepo
44
- .createQueryBuilder('at')
45
- .select(hourExpr, 'hour')
46
- .addSelect('COALESCE(SUM(at.input_tokens), 0)', 'input_tokens')
47
- .addSelect('COALESCE(SUM(at.output_tokens), 0)', 'output_tokens')
48
- .where('at.timestamp >= :cutoff', { cutoff });
49
- (0, query_helpers_1.addTenantFilter)(qb, userId, agentName, tenantId);
50
- const rows = await qb.groupBy('hour').orderBy('hour', 'ASC').getRawMany();
51
- return rows.map((r) => ({
52
- hour: String(r['hour']),
53
- input_tokens: Number(r['input_tokens'] ?? 0),
54
- output_tokens: Number(r['output_tokens'] ?? 0),
55
- }));
56
- }
57
- async getDailyTokens(range, userId, agentName) {
58
- const tenantId = (await this.tenantCache.resolve(userId)) ?? undefined;
59
- const interval = (0, range_util_1.rangeToInterval)(range);
60
- const cutoff = (0, sql_dialect_1.computeCutoff)(interval);
61
- const dateExpr = (0, sql_dialect_1.sqlDateBucket)('at.timestamp', this.dialect);
62
- const qb = this.turnRepo
63
- .createQueryBuilder('at')
64
- .select(dateExpr, 'date')
65
- .addSelect('COALESCE(SUM(at.input_tokens), 0)', 'input_tokens')
66
- .addSelect('COALESCE(SUM(at.output_tokens), 0)', 'output_tokens')
67
- .where('at.timestamp >= :cutoff', { cutoff });
68
- (0, query_helpers_1.addTenantFilter)(qb, userId, agentName, tenantId);
69
- const rows = await qb.groupBy('date').orderBy('date', 'ASC').getRawMany();
70
- return rows.map((r) => ({
71
- date: String(r['date']),
72
- input_tokens: Number(r['input_tokens'] ?? 0),
73
- output_tokens: Number(r['output_tokens'] ?? 0),
74
- }));
75
- }
76
- async getHourlyCosts(range, userId, agentName) {
77
- const tenantId = (await this.tenantCache.resolve(userId)) ?? undefined;
78
- const interval = (0, range_util_1.rangeToInterval)(range);
79
- const cutoff = (0, sql_dialect_1.computeCutoff)(interval);
80
- const hourExpr = (0, sql_dialect_1.sqlHourBucket)('at.timestamp', this.dialect);
81
- const qb = this.turnRepo
82
- .createQueryBuilder('at')
83
- .select(hourExpr, 'hour')
84
- .addSelect(`COALESCE(SUM(${(0, sql_dialect_1.sqlSanitizeCost)('at.cost_usd')}), 0)`, 'cost')
85
- .where('at.timestamp >= :cutoff', { cutoff });
86
- (0, query_helpers_1.addTenantFilter)(qb, userId, agentName, tenantId);
87
- const rows = await qb.groupBy('hour').orderBy('hour', 'ASC').getRawMany();
88
- return rows.map((r) => ({
89
- hour: String(r['hour']),
90
- cost: Number(r['cost'] ?? 0),
91
- }));
92
- }
93
- async getDailyCosts(range, userId, agentName) {
94
- const tenantId = (await this.tenantCache.resolve(userId)) ?? undefined;
95
- const interval = (0, range_util_1.rangeToInterval)(range);
96
- const cutoff = (0, sql_dialect_1.computeCutoff)(interval);
97
- const dateExpr = (0, sql_dialect_1.sqlDateBucket)('at.timestamp', this.dialect);
98
- const qb = this.turnRepo
99
- .createQueryBuilder('at')
100
- .select(dateExpr, 'date')
101
- .addSelect(`COALESCE(SUM(${(0, sql_dialect_1.sqlSanitizeCost)('at.cost_usd')}), 0)`, 'cost')
102
- .where('at.timestamp >= :cutoff', { cutoff });
103
- (0, query_helpers_1.addTenantFilter)(qb, userId, agentName, tenantId);
104
- const rows = await qb.groupBy('date').orderBy('date', 'ASC').getRawMany();
105
- return rows.map((r) => ({
106
- date: String(r['date']),
107
- cost: Number(r['cost'] ?? 0),
108
- }));
109
- }
110
- async getHourlyMessages(range, userId, agentName) {
111
- const tenantId = (await this.tenantCache.resolve(userId)) ?? undefined;
112
- const interval = (0, range_util_1.rangeToInterval)(range);
113
- const cutoff = (0, sql_dialect_1.computeCutoff)(interval);
114
- const hourExpr = (0, sql_dialect_1.sqlHourBucket)('at.timestamp', this.dialect);
115
- const qb = this.turnRepo
116
- .createQueryBuilder('at')
117
- .select(hourExpr, 'hour')
118
- .addSelect('COUNT(*)', 'count')
119
- .where('at.timestamp >= :cutoff', { cutoff });
120
- (0, query_helpers_1.addTenantFilter)(qb, userId, agentName, tenantId);
121
- const rows = await qb.groupBy('hour').orderBy('hour', 'ASC').getRawMany();
122
- return rows.map((r) => ({
123
- hour: String(r['hour']),
124
- count: Number(r['count'] ?? 0),
125
- }));
126
- }
127
- async getDailyMessages(range, userId, agentName) {
128
- const tenantId = (await this.tenantCache.resolve(userId)) ?? undefined;
129
- const interval = (0, range_util_1.rangeToInterval)(range);
130
- const cutoff = (0, sql_dialect_1.computeCutoff)(interval);
131
- const dateExpr = (0, sql_dialect_1.sqlDateBucket)('at.timestamp', this.dialect);
132
- const qb = this.turnRepo
133
- .createQueryBuilder('at')
134
- .select(dateExpr, 'date')
135
- .addSelect('COUNT(*)', 'count')
136
- .where('at.timestamp >= :cutoff', { cutoff });
137
- (0, query_helpers_1.addTenantFilter)(qb, userId, agentName, tenantId);
138
- const rows = await qb.groupBy('date').orderBy('date', 'ASC').getRawMany();
139
- return rows.map((r) => ({
140
- date: String(r['date']),
141
- count: Number(r['count'] ?? 0),
142
- }));
143
- }
144
38
  async getTimeseries(range, userId, hourly, tenantId, agentName) {
145
39
  const interval = (0, range_util_1.rangeToInterval)(range);
146
40
  const cutoff = (0, sql_dialect_1.computeCutoff)(interval);
@@ -162,14 +56,15 @@ let TimeseriesQueriesService = class TimeseriesQueriesService {
162
56
  const costUsage = [];
163
57
  const messageUsage = [];
164
58
  for (const r of rows) {
165
- const bucket = String(r[bucketAlias]);
59
+ const parsed = this.parseBucketRow(r, bucketAlias);
60
+ const bucketKey = { hour: parsed.hour, date: parsed.date };
166
61
  tokenUsage.push({
167
- [bucketAlias]: bucket,
168
- input_tokens: Number(r['input_tokens'] ?? 0),
169
- output_tokens: Number(r['output_tokens'] ?? 0),
62
+ ...bucketKey,
63
+ input_tokens: parsed.input_tokens,
64
+ output_tokens: parsed.output_tokens,
170
65
  });
171
- costUsage.push({ [bucketAlias]: bucket, cost: Number(r['cost'] ?? 0) });
172
- messageUsage.push({ [bucketAlias]: bucket, count: Number(r['count'] ?? 0) });
66
+ costUsage.push({ ...bucketKey, cost: parsed.cost });
67
+ messageUsage.push({ ...bucketKey, count: parsed.count });
173
68
  }
174
69
  return { tokenUsage, costUsage, messageUsage };
175
70
  }
@@ -318,6 +213,16 @@ let TimeseriesQueriesService = class TimeseriesQueriesService {
318
213
  };
319
214
  });
320
215
  }
216
+ parseBucketRow(r, bucketAlias) {
217
+ const row = {
218
+ input_tokens: Number(r['input_tokens'] ?? 0),
219
+ output_tokens: Number(r['output_tokens'] ?? 0),
220
+ cost: Number(r['cost'] ?? 0),
221
+ count: Number(r['count'] ?? 0),
222
+ };
223
+ row[bucketAlias] = String(r[bucketAlias]);
224
+ return row;
225
+ }
321
226
  };
322
227
  exports.TimeseriesQueriesService = TimeseriesQueriesService;
323
228
  exports.TimeseriesQueriesService = TimeseriesQueriesService = __decorate([