mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-30 00:22:10 -04:00
feat: add ChatGPT Subscription provider (#2876)
* feat: Add ChatGPT Subscription support and related features - Introduced a new provider option for ChatGPT Subscription in the endpoint selection UI. - Implemented OAuth flow for ChatGPT Subscription sign-in, including polling for authorization status. - Updated admin interface to handle ChatGPT Subscription, including disabling API key input and providing user guidance. - Enhanced cost tracking logic to differentiate between subscription and non-subscription endpoints. - Added new slash commands for managing skills, including listing, searching, and invoking skills. - Implemented caching for skill catalog to optimize performance. - Updated tests to cover new ChatGPT Subscription functionality and ensure proper endpoint probing. - Refactored existing code to accommodate new features and improve maintainability. * refactor: share provider device-flow setup - reuse one device-flow backend for Copilot and ChatGPT Subscription - add one frontend device-flow helper for Settings and /setup - put GitHub Copilot back into Add Models, now as a dropdown option - make provider selection just select; clicking Add starts sign-in - stop ChatGPT Subscription setup from opening auth tabs automatically - make /setup copilot and /setup chatgpt-subscription work from chat - show ChatGPT Subscription in the /setup suggestions - show the real error message when setup fails - add focused tests for the shared flow and setup UI * feat(chatgpt-subscription): harden credential lifecycle and streamline auth UX Backend: - Resolve runtime bearer for provider-auth endpoints at probe time via a shared _resolve_probe_key() that delegates to resolve_endpoint_runtime, applied across all probe/refresh call sites. - Skip live completion probes and health pings for discovery-only providers (centralized behind _is_discovery_only_provider) — the Codex/Responses API has no such endpoints, so status is derived from cached models. - Never persist the short lived ChatGPT bearer to the plaintext sessions table; proactively clear any stale bearer left by an earlier code path. - Revoke orphaned ProviderAuthSession credentials when the last endpoint backing them is deleted (_delete_orphaned_provider_auth), surfaced via cleared_provider_auth in the delete response. Frontend (admin.js): - Auto-start the device-auth flow on provider selection so the authorization panel (code + Authorize) shows immediately instead of behind a "Sign in" click. - Remove the redundant top button for device auth providers, move retry into the panel via an inline "Try again". - Drop the self-evident hint text and add an execCommand clipboard fallback so Copy works in non-secure (HTTP/LAN) contexts. * fix: harden chatgpt subscription provider * chore: remove PR media from branch * Fix chatgpt subscription recovery and token handling --------- Co-authored-by: 5p00kyy <admin@5p00ky.dev>
This commit is contained in:
@@ -1109,6 +1109,35 @@ def setup_skills_routes(skills_manager: SkillsManager) -> APIRouter:
|
||||
idx = skills_manager.index_for(owner=user)
|
||||
return {"index": idx, "count": len(idx)}
|
||||
|
||||
@router.get("/slash-catalog")
|
||||
async def get_slash_catalog(request: Request):
|
||||
"""Return skills that are available as slash commands.
|
||||
|
||||
Mirrors the agent prompt's published-skill index so the UI never offers
|
||||
a slash command the model would not normally be allowed to discover.
|
||||
"""
|
||||
user = _owner(request)
|
||||
all_skills = {s.get("name"): s for s in skills_manager.load(owner=user)}
|
||||
entries = []
|
||||
for s in skills_manager.index_for(owner=user):
|
||||
name = (s.get("name") or "").strip()
|
||||
if not name:
|
||||
continue
|
||||
full = all_skills.get(name) or {}
|
||||
category = (s.get("category") or full.get("category") or "general").strip() or "general"
|
||||
entries.append({
|
||||
"type": "skill",
|
||||
"token": f"/{name}",
|
||||
"name": name,
|
||||
"category": f"Skills / {category}",
|
||||
"help": s.get("description") or full.get("description") or "",
|
||||
"usage": f"/{name} <request>",
|
||||
"uses": int(full.get("uses") or 0),
|
||||
"last_used": full.get("last_used"),
|
||||
})
|
||||
entries.sort(key=lambda row: row["name"])
|
||||
return {"skills": entries, "count": len(entries)}
|
||||
|
||||
@router.get("/builtin")
|
||||
async def list_builtin_skills(request: Request):
|
||||
"""Read-only list of the agent's built-in tool capabilities (research,
|
||||
@@ -1272,6 +1301,47 @@ def setup_skills_routes(skills_manager: SkillsManager) -> APIRouter:
|
||||
_fire_skill_added(user)
|
||||
return {"ok": True, "deduped": bool(entry.get("_deduped")), "skill": entry}
|
||||
|
||||
@router.post("/{skill_id}/invoke")
|
||||
async def invoke_skill(request: Request, skill_id: str):
|
||||
"""Build a skill-pinned prompt for slash-command invocation.
|
||||
|
||||
This is intentionally server-side so availability, ownership, and usage
|
||||
accounting use the same rules as the SkillsManager.
|
||||
"""
|
||||
user = _owner(request)
|
||||
try:
|
||||
body = await request.json()
|
||||
except Exception:
|
||||
body = {}
|
||||
request_text = (body.get("request") or "").strip() if isinstance(body, dict) else ""
|
||||
|
||||
invokable = {
|
||||
s.get("name"): s for s in skills_manager.index_for(owner=user)
|
||||
if (s.get("name") or "").strip()
|
||||
}
|
||||
match = invokable.get(skill_id)
|
||||
if not match:
|
||||
raise HTTPException(404, "Skill is not available for slash invocation")
|
||||
|
||||
name = match.get("name")
|
||||
md = skills_manager.read_skill_md(name, owner=user)
|
||||
if md is None:
|
||||
raise HTTPException(404, "Skill source unavailable")
|
||||
|
||||
skills_manager.record_use(name, owner=user)
|
||||
message = (
|
||||
"Apply the skill below to my request, following its Procedure / Pitfalls / Verification.\n\n"
|
||||
f"--- BEGIN SKILL ---\n{md}\n--- END SKILL ---\n\n"
|
||||
+ (f"Request: {request_text}" if request_text else "Request: (use the skill as appropriate)")
|
||||
)
|
||||
return {
|
||||
"ok": True,
|
||||
"type": "skill",
|
||||
"name": name,
|
||||
"command": f"/{name}",
|
||||
"message": message,
|
||||
}
|
||||
|
||||
@router.get("/{skill_id}")
|
||||
async def get_skill(request: Request, skill_id: str):
|
||||
user = _owner(request)
|
||||
|
||||
Reference in New Issue
Block a user