Replace standalone Telegram bot with full CodeAnywhere framework fork. BetterBot shares all framework code and customizes only: - instance.py: BetterBot identity, system prompt, feature flags - tools/site_editing/: list_files, read_file, write_file with auto git push - .env: model defaults and site directory paths - compose/: Docker setup with betterlifesg + memoraiz mounts - deploy script: RackNerd with Infisical secrets
145 lines
4.7 KiB
Python
145 lines
4.7 KiB
Python
"""Credential meta-tools (T039-T041).
|
|
|
|
Provides tools for users to inspect their service access and credentials.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from tool_registry import ToolSet, openai_tools_to_copilot
|
|
|
|
META_SYSTEM_PROMPT = """\
|
|
You can help users check their service access and available integrations. \
|
|
When a user asks about their accounts, services, or credentials, use the \
|
|
list_my_services and get_my_credentials tools. \
|
|
Never reveal passwords unless the system configuration explicitly allows it.
|
|
"""
|
|
|
|
TOOLS = [
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "list_my_services",
|
|
"description": (
|
|
"List all services available to the user, showing which are active "
|
|
"(have credentials), which are available for setup, and their capabilities."
|
|
),
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {},
|
|
"required": [],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "get_my_credentials",
|
|
"description": (
|
|
"Get the user's credentials for a specific service. Returns the username "
|
|
"and service URL. Password is only shown if system configuration allows it."
|
|
),
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"service": {
|
|
"type": "string",
|
|
"description": "The service name (e.g. 'vikunja', 'karakeep')",
|
|
},
|
|
},
|
|
"required": ["service"],
|
|
},
|
|
},
|
|
},
|
|
]
|
|
|
|
|
|
def _build_meta_tools(user_context: dict[str, Any]) -> list:
|
|
"""Factory that creates Copilot SDK meta-tools."""
|
|
user = user_context.get("_user")
|
|
|
|
async def dispatcher(name: str, arguments: dict, **_kw: Any) -> str:
|
|
if name == "list_my_services":
|
|
return _handle_list_my_services(user)
|
|
if name == "get_my_credentials":
|
|
return _handle_get_my_credentials(user, arguments.get("service", ""))
|
|
return f"Unknown meta-tool: {name}"
|
|
|
|
return openai_tools_to_copilot(schemas=TOOLS, dispatcher=dispatcher)
|
|
|
|
|
|
def _handle_list_my_services(user: Any) -> str:
|
|
if user is None:
|
|
return "Unable to determine your identity."
|
|
|
|
from tool_pipeline import get_provisionable_services
|
|
|
|
services = get_provisionable_services(user)
|
|
if not services:
|
|
return "No services are currently configured."
|
|
|
|
lines = []
|
|
for s in services:
|
|
status = s["status"].upper()
|
|
caps = s["capabilities"] or "general"
|
|
lines.append(f"- **{s['service']}** ({caps}): {status}")
|
|
return "Your services:\n" + "\n".join(lines)
|
|
|
|
|
|
def _handle_get_my_credentials(user: Any, service: str) -> str:
|
|
if user is None:
|
|
return "Unable to determine your identity."
|
|
if not service:
|
|
return "Please specify which service you want credentials for."
|
|
|
|
from config import settings
|
|
from user_store import get_store
|
|
|
|
store = get_store()
|
|
cred = store.get_credential(user.id, service)
|
|
if cred is None:
|
|
return f"You don't have credentials for {service}. Say 'set up {service}' to get started."
|
|
|
|
parts = [f"**Service**: {service}"]
|
|
if cred.service_username:
|
|
parts.append(f"**Username**: {cred.service_username}")
|
|
|
|
# Service URL from settings
|
|
url_attr = f"{service.upper()}_API_URL"
|
|
url = getattr(settings, url_attr, "")
|
|
if url:
|
|
# Strip /api/v1 suffix for display
|
|
display_url = url.rstrip("/")
|
|
for suffix in ("/api/v1", "/api"):
|
|
if display_url.endswith(suffix):
|
|
display_url = display_url[: -len(suffix)]
|
|
break
|
|
parts.append(f"**URL**: {display_url}")
|
|
|
|
if cred.expires_at:
|
|
parts.append(f"**Expires**: {cred.expires_at}")
|
|
|
|
if settings.ALLOW_CREDENTIAL_REVEAL_IN_CHAT:
|
|
from user_store import decrypt
|
|
|
|
try:
|
|
token = decrypt(cred.encrypted_token)
|
|
parts.append(f"**API Token**: `{token}`")
|
|
parts.append("⚠️ Be careful — this token grants full access to your account.")
|
|
except Exception:
|
|
parts.append("**API Token**: (decryption failed)")
|
|
else:
|
|
parts.append("**Password/Token**: Stored securely. Access the service directly at the URL above.")
|
|
|
|
return "\n".join(parts)
|
|
|
|
|
|
meta_toolset = ToolSet(
|
|
name="meta",
|
|
description="Service access and credential information",
|
|
capability="account_management",
|
|
system_prompt_fragment=META_SYSTEM_PROMPT,
|
|
build_tools=_build_meta_tools,
|
|
required_keys=[], # Available to all authenticated users
|
|
)
|