feat: add backup CLI commands, AI correction/modifier services, and timezone-aware date display

- Add `devplace backups` CLI subcommands (list, run, prune, clear) with job enqueueing and orphan cleanup
- Introduce `BACKUPS_DIR` and `BACKUP_STAGING_DIR` config paths for backup storage
- Implement `schedule_correction` and `schedule_modification` calls in content creation, comment creation, and comment editing flows
- Add `DEFAULT_CORRECTION_PROMPT` and `DEFAULT_MODIFIER_PROMPT` config constants for AI content processing
- Document timezone-aware date display using `local_dt`/`dt_ago` Jinja globals with client-side `Intl` localization
- Update README with AI content correction/modifier support in direct messages via `@ai` inline instructions
- Add `track_action(user["uid"], "vote")` call on upvote in `apply_vote`
This commit is contained in:
2026-06-16 03:32:19 +00:00
parent e59bc2d34e
commit 15bd4ad87c
115 changed files with 5839 additions and 187 deletions
+39 -19
View File
@@ -20,6 +20,7 @@ from devplacepy.services.openai_gateway.usage import (
extract_params,
parse_context_map,
pricing_from_cfg,
usage_response_headers,
)
from devplacepy.services.openai_gateway.vision import VisionAugmenter, VisionCache
@@ -294,10 +295,12 @@ class GatewayRuntime:
base["success"] = success
base["error_category"] = category
base["usage"] = usage
self._ledger.record(base, pricing, context_map)
return usage_response_headers(
self._ledger.record(base, pricing, context_map)
)
if timing["circuit_open"]:
finalize(503, False, "circuit_open")
resp_headers = finalize(503, False, "circuit_open")
return JSONResponse(
status_code=503,
content={
@@ -306,9 +309,10 @@ class GatewayRuntime:
"type": "circuit_open",
}
},
headers=resp_headers,
)
if exc is not None:
finalize(502, False, classify_error(0, exc))
resp_headers = finalize(502, False, classify_error(0, exc))
return JSONResponse(
status_code=502,
content={
@@ -317,9 +321,10 @@ class GatewayRuntime:
"type": "upstream_error",
}
},
headers=resp_headers,
)
if resp.status_code != 200:
finalize(
resp_headers = finalize(
resp.status_code,
False,
classify_error(resp.status_code, None, resp.text),
@@ -328,12 +333,13 @@ class GatewayRuntime:
return JSONResponse(
status_code=resp.status_code,
content={"error": {"message": resp.text, "type": "upstream_error"}},
headers=resp_headers,
)
try:
data = resp.json()
except ValueError:
self.errors += 1
finalize(502, False, "gateway")
resp_headers = finalize(502, False, "gateway")
log("chat upstream returned 200 but body was not valid JSON")
return JSONResponse(
status_code=502,
@@ -343,14 +349,17 @@ class GatewayRuntime:
"type": "upstream_error",
}
},
headers=resp_headers,
)
finalize(200, True, None, data.get("usage"))
resp_headers = finalize(200, True, None, data.get("usage"))
log(f"chat POST -> 200 ({timing['upstream_latency_ms']:.0f}ms)")
if stream:
return StreamingResponse(
_fake_stream(data, model), media_type="text/event-stream"
_fake_stream(data, model),
media_type="text/event-stream",
headers=resp_headers,
)
return JSONResponse(content=data)
return JSONResponse(content=data, headers=resp_headers)
async def handle_embeddings(
self, body: dict, cfg: dict, owner: tuple, user_agent: str, log=None
@@ -447,10 +456,12 @@ class GatewayRuntime:
base["success"] = success
base["error_category"] = category
base["usage"] = usage
self._ledger.record(base, pricing, context_map)
return usage_response_headers(
self._ledger.record(base, pricing, context_map)
)
if timing["circuit_open"]:
finalize(503, False, "circuit_open")
resp_headers = finalize(503, False, "circuit_open")
return JSONResponse(
status_code=503,
content={
@@ -459,9 +470,10 @@ class GatewayRuntime:
"type": "circuit_open",
}
},
headers=resp_headers,
)
if exc is not None:
finalize(502, False, classify_error(0, exc))
resp_headers = finalize(502, False, classify_error(0, exc))
return JSONResponse(
status_code=502,
content={
@@ -470,9 +482,10 @@ class GatewayRuntime:
"type": "upstream_error",
}
},
headers=resp_headers,
)
if resp.status_code != 200:
finalize(
resp_headers = finalize(
resp.status_code,
False,
classify_error(resp.status_code, None, resp.text),
@@ -481,12 +494,13 @@ class GatewayRuntime:
return JSONResponse(
status_code=resp.status_code,
content={"error": {"message": resp.text, "type": "upstream_error"}},
headers=resp_headers,
)
try:
data = resp.json()
except ValueError:
self.errors += 1
finalize(502, False, "gateway")
resp_headers = finalize(502, False, "gateway")
log("embed upstream returned 200 but body was not valid JSON")
return JSONResponse(
status_code=502,
@@ -496,11 +510,12 @@ class GatewayRuntime:
"type": "upstream_error",
}
},
headers=resp_headers,
)
self.embed_calls += 1
finalize(200, True, None, data.get("usage"))
resp_headers = finalize(200, True, None, data.get("usage"))
log(f"embed POST -> 200 ({timing['upstream_latency_ms']:.0f}ms)")
return JSONResponse(content=data)
return JSONResponse(content=data, headers=resp_headers)
async def handle_passthrough(
self,
@@ -559,10 +574,12 @@ class GatewayRuntime:
base["success"] = success
base["error_category"] = category
base["usage"] = usage
self._ledger.record(base, pricing, context_map)
return usage_response_headers(
self._ledger.record(base, pricing, context_map)
)
if timing["circuit_open"]:
finalize(503, False, "circuit_open")
resp_headers = finalize(503, False, "circuit_open")
return JSONResponse(
status_code=503,
content={
@@ -571,9 +588,10 @@ class GatewayRuntime:
"type": "circuit_open",
}
},
headers=resp_headers,
)
if exc is not None:
finalize(502, False, classify_error(0, exc))
resp_headers = finalize(502, False, classify_error(0, exc))
return JSONResponse(
status_code=502,
content={
@@ -582,6 +600,7 @@ class GatewayRuntime:
"type": "upstream_error",
}
},
headers=resp_headers,
)
usage = None
if resp.status_code < 400 and "application/json" in (
@@ -591,7 +610,7 @@ class GatewayRuntime:
usage = resp.json().get("usage")
except ValueError:
usage = None
finalize(
resp_headers = finalize(
resp.status_code,
resp.status_code < 400,
None
@@ -606,6 +625,7 @@ class GatewayRuntime:
content=resp.content,
status_code=resp.status_code,
media_type=resp.headers.get("content-type"),
headers=resp_headers,
)
def metrics(self) -> dict:
+33 -1
View File
@@ -219,8 +219,38 @@ def audit_actor_for(owner_kind: str, owner_id: str) -> tuple[str, Optional[str],
return actor_kind, actor_uid, actor_role
def usage_response_headers(row: Optional[dict]) -> dict:
if not row:
return {}
headers = {
"X-Gateway-Model": str(row.get("model") or ""),
"X-Gateway-Backend": str(row.get("backend") or ""),
"X-Gateway-Prompt-Tokens": str(int(row.get("prompt_tokens") or 0)),
"X-Gateway-Completion-Tokens": str(int(row.get("completion_tokens") or 0)),
"X-Gateway-Total-Tokens": str(int(row.get("total_tokens") or 0)),
"X-Gateway-Cache-Hit-Tokens": str(int(row.get("cache_hit_tokens") or 0)),
"X-Gateway-Cache-Miss-Tokens": str(int(row.get("cache_miss_tokens") or 0)),
"X-Gateway-Reasoning-Tokens": str(int(row.get("reasoning_tokens") or 0)),
"X-Gateway-Cost-USD": f"{float(row.get('cost_usd') or 0.0):.8f}",
"X-Gateway-Input-Cost-USD": f"{float(row.get('input_cost_usd') or 0.0):.8f}",
"X-Gateway-Output-Cost-USD": f"{float(row.get('output_cost_usd') or 0.0):.8f}",
"X-Gateway-Cost-Native": "1" if row.get("native_cost") else "0",
"X-Gateway-Tokens-Per-Second": str(row.get("tokens_per_second") or 0),
"X-Gateway-Upstream-Latency-Ms": str(row.get("upstream_latency_ms") or 0),
"X-Gateway-Total-Latency-Ms": str(row.get("total_latency_ms") or 0),
"X-Gateway-Gateway-Overhead-Ms": str(row.get("gateway_overhead_ms") or 0),
"X-Gateway-Queue-Wait-Ms": str(row.get("queue_wait_ms") or 0),
"X-Gateway-Connect-Ms": str(row.get("connect_ms") or 0),
}
if row.get("context_window"):
headers["X-Gateway-Context-Window"] = str(int(row["context_window"]))
if row.get("context_utilization") is not None:
headers["X-Gateway-Context-Utilization"] = str(row["context_utilization"])
return headers
class GatewayUsageLedger:
def record(self, raw: dict, pricing: Pricing, context_map: dict) -> None:
def record(self, raw: dict, pricing: Pricing, context_map: dict) -> Optional[dict]:
try:
usage = raw.get("usage") or {}
norm = normalize_usage(usage)
@@ -278,8 +308,10 @@ class GatewayUsageLedger:
}
get_table(GATEWAY_LEDGER).insert(row)
self._audit(raw, norm, cost_usd)
return row
except Exception as exc:
logger.warning("gateway usage record failed: %s", exc)
return None
def _audit(self, raw: dict, norm: dict, cost_usd: float) -> None:
from devplacepy.services.audit import record as audit