Skip to content

Commit 2b9c512

Browse files
committed
nives 2.5.0: warn before the balance runs out
The add-on holds the user's key, and the key can describe itself: the endpoint's key-info route returns remaining credit and rolling usage windows. A new server job polls it every six hours, estimates days of typical use left (weekly/7, else monthly/30, else a default burn), and walks an ok -> warned (<=3 days) -> paused (<=1 day, or under $0.05) state machine latched in /data — one HA persistent notification per transition, never one per poll. Notification first, latch second: if HA is briefly unreachable the transition retries next poll instead of being lost. A top-up flips the state back to ok and dismisses both. Triple-gated so it can never mislead: cloud mode only (options-to-env.sh now passes LLM_MODE through — a BYOK user must not be told to top up at nives.house), keys with a scheduled limit reset are skipped (a monthly allowance is a different message, handled by email), and unlimited keys have nothing to watch. Conveniently, today's keys all have monthly resets, so the watcher goes live per user exactly when their key is converted to a prepaid balance — no launch-day coordination. The integration's out-of-usage reply (HTTP 402) is reworded from monthly allowance framing to out-of-balance framing, covering both cloud top-ups and BYOK users' own provider accounts. Bundles server 0.15.0. Mirrors the cloud-side balance watcher thresholds (nives-cloud 1.6.1); estimation is deliberately simpler — no account age is visible here, and the empty floor catches every truly-empty case.
1 parent 1252e4a commit 2b9c512

12 files changed

Lines changed: 505 additions & 10 deletions

File tree

nives/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## 2.5.0
4+
5+
- **Nives now warns you before your balance runs out.** If you use Nives with a key from nives.house, the add-on keeps an eye on the remaining balance and, when about three days of typical use are left, posts a Home Assistant notification inviting you to top up. If the balance does run out, a clearer notification says so. Once you top up, both clear on their own and Nives carries on where it left off — nothing to configure, nothing to restart. If you bring your own key, nothing changes: Nives only watches nives.house balances.
6+
- **Nives says it plainly when it can't answer for lack of balance.** If a reply is refused because the balance is used up before the warning could reach you, Nives now tells you it's out of balance right in the conversation and points you to nives.house — or to your own provider's account, if you bring your own key. Bundles server 0.15.0.
7+
38
## 2.4.24
49

510
- **Bring-your-own-key now works with OpenAI's newest models.** Pointing Nives at GPT-5 or the o-series with your own OpenAI key failed before the model ever got a chance to answer: those models renamed the setting that limits how long a reply may be, and Nives was still sending the previous name. Nives now asks the endpoint which name it expects and remembers the answer, so the setting arrives correctly everywhere — OpenAI's newest models, OpenAI's older ones, and every other OpenAI-compatible endpoint Nives supports, local models included. There is nothing to configure, and setups that already worked are untouched. Thanks to @Kristofer-KNE for the report (#60).

nives/DOCS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ Use Nives Cloud — a managed AI service with a monthly token budget. No API key
4848
| LLM Mode | Set to `cloud` |
4949
| Nives API Key | Your key from nives.house |
5050

51+
### Balance heads-up
52+
53+
Nives keeps an eye on your remaining balance. When about three days of typical use are left, it posts a Home Assistant notification inviting you to top up at [nives.house](https://nives.house); if the balance runs out, a clearer one says so. After a top-up both clear on their own — nothing to configure. This applies to Cloud mode only; keys you bring yourself are never watched.
54+
5155
---
5256

5357
## BYOK Mode (Bring Your Own Key)

nives/config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: "Nives"
2-
version: "2.4.24"
2+
version: "2.5.0"
33
slug: "nives"
44
description: "AI assistant with cognitive memory for Home Assistant"
55
url: "https://github.com/hoornet/nives"

nives/rootfs/opt/nives/conversation.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,18 +104,20 @@ async def async_process(self, user_input: ConversationInput) -> ConversationResu
104104
"persistent_notification",
105105
"create",
106106
{
107-
"title": "Nives — Usage Limit Reached",
107+
"title": "Nives — Out of balance",
108108
"message": (
109-
"Your monthly usage allowance is depleted. "
110-
"Visit nives.house to renew or upgrade."
109+
"Nives has used up its available balance. "
110+
"Top up at nives.house — or, if you bring your own "
111+
"key, add credit with your provider — and Nives "
112+
"picks right back up."
111113
),
112114
"notification_id": "nives_usage_limit",
113115
},
114116
)
115117
intent_response = intent.IntentResponse(language=user_input.language)
116118
intent_response.async_set_speech(
117-
"Your monthly token allowance is depleted. "
118-
"Please visit nives.house to renew or upgrade."
119+
"I'm out of balance. "
120+
"Please top up at nives.house and I'll pick right back up."
119121
)
120122
return ConversationResult(
121123
response=intent_response,

nives/rootfs/opt/nives/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@
99
"integration_type": "service",
1010
"iot_class": "local_polling",
1111
"requirements": ["aiohttp>=3.8.0"],
12-
"version": "2.4.23"
12+
"version": "2.5.0"
1313
}

nives/rootfs/usr/local/bin/options-to-env.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ CUSTOM_PROMPT=$(jq -r '.custom_prompt // ""' "$OPTIONS")
5050
LOG_LEVEL=$(jq -r '.log_level // "info"' "$OPTIONS")
5151

5252
# --- LLM configuration ---
53+
# The server needs to know the mode itself (not just its consequences): the
54+
# balance watch runs only in cloud mode, where "top up at nives.house" is true.
55+
write_env "LLM_MODE" "$LLM_MODE"
5356
if [ "$LLM_MODE" = "cloud" ]; then
5457
# Cloud mode: user's managed OpenRouter key (created via Nives Cloud)
5558
write_env "LLM_PROVIDER" "openai"

server/src/home-mind-server/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/src/home-mind-server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@home-mind/server",
3-
"version": "0.14.0",
3+
"version": "0.15.0",
44
"description": "Home Mind API server - AI assistant with cognitive memory for Home Assistant",
55
"type": "module",
66
"main": "dist/index.js",

server/src/home-mind-server/src/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ const ConfigSchema = z
77
logLevel: z.enum(["debug", "info", "warn", "error"]).default("info"),
88

99
// LLM
10+
// cloud = key managed via nives.house; byok = user's own key. Set by the
11+
// add-on's options-to-env.sh; standalone deployments default to byok.
12+
llmMode: z.enum(["cloud", "byok"]).default("byok"),
1013
llmProvider: z.enum(["anthropic", "openai", "ollama"]).default("anthropic"),
1114
llmModel: z.string().default("claude-haiku-4-5-20251001"),
1215
anthropicApiKey: z.string().optional(),
@@ -93,6 +96,7 @@ export function loadConfig(): Config {
9396
const result = ConfigSchema.safeParse({
9497
port: process.env.PORT,
9598
logLevel: emptyToUndefined(process.env.LOG_LEVEL),
99+
llmMode: emptyToUndefined(process.env.LLM_MODE),
96100
llmProvider: emptyToUndefined(process.env.LLM_PROVIDER),
97101
llmModel: emptyToUndefined(process.env.LLM_MODEL),
98102
anthropicApiKey: emptyToUndefined(process.env.ANTHROPIC_API_KEY),

server/src/home-mind-server/src/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { createRouter } from "./api/routes.js";
1818
import { createSttService } from "./stt/stt-service.js";
1919
import { createTtsService } from "./tts/tts-service.js";
2020
import { MemoryCleanupJob } from "./jobs/memory-cleanup.js";
21+
import { BalanceWatchJob } from "./jobs/balance-watch.js";
2122

2223
// Load configuration
2324
const config = loadConfig();
@@ -154,10 +155,20 @@ Ready to accept requests at http://localhost:${config.port}
154155
const cleanupJob = new MemoryCleanupJob(memory, conversations, config.memoryCleanupIntervalHours);
155156
cleanupJob.start();
156157

158+
// Watch the cloud balance (no-op in BYOK mode)
159+
const balanceJob = new BalanceWatchJob({
160+
enabled: config.llmMode === "cloud" && config.llmProvider === "openai",
161+
apiKey: config.openaiApiKey,
162+
baseUrl: config.openaiBaseUrl,
163+
ha,
164+
});
165+
balanceJob.start();
166+
157167
// Graceful shutdown
158168
process.on("SIGTERM", () => {
159169
console.log("Shutting down...");
160170
cleanupJob.stop();
171+
balanceJob.stop();
161172
conversations.close();
162173
memory.close();
163174
process.exit(0);
@@ -166,6 +177,7 @@ process.on("SIGTERM", () => {
166177
process.on("SIGINT", () => {
167178
console.log("Shutting down...");
168179
cleanupJob.stop();
180+
balanceJob.stop();
169181
conversations.close();
170182
memory.close();
171183
process.exit(0);

0 commit comments

Comments
 (0)