Files
Kai (Tam Nhu) Tran 30708ae79d feat(providers): support Codex OAuth pools with inherited routing defaults
* feat(auth): support named chatgpt oauth providers

- add provider-scoped ChatGPT OAuth routes and CLI support

- persist refresh tokens per provider and reject provider-type collisions

- wire provider OAuth setup flows in the dashboard and setup UI

Refs #448

* feat(agent): add chatgpt oauth account routing

- add agent other_config routing for manual and round-robin selection

- reuse routed provider resolution across resolver and pending loaders

- add router, parser, and agent advanced dialog coverage for multi-account use

Refs #448

* docs(api): describe chatgpt oauth routing

- document named-provider ChatGPT OAuth auth routes

- describe agent-side account routing and round-robin behavior

- update OpenAPI agent config schema and provider type enum

Refs #448

* fix(store): add missing agent key context helpers

* feat(ui): clarify chatgpt oauth account setup and routing

* docs(providers): align chatgpt oauth alias examples

* feat(agent): add codex pool activity dashboard

* fix(providers): harden codex oauth alias setup

* feat(codex-pool): improve routing dashboard UX

- redesign the Codex/OpenAI pool page around saved-pool checkpoints and live evidence

- add clearer selection, attention, and recent-proof states for pool members

- make the lower panels fill the remaining desktop viewport while staying responsive

* fix(store): resolve context helper merge duplication

* feat(oauth): add codex pool quota and observation APIs

- add quota inspection and observation endpoints for ChatGPT Subscription (OAuth) providers

- teach codex routing to surface pool activity, observation metadata, and quota-aware readiness

- extend tests and HTTP docs/OpenAPI for the new pool monitoring flows

* feat(web): add codex pool quota monitor and controls

- add provider quota fetching, readiness badges, and live routing evidence on the account pool page

- redesign pool setup and activity panels for multi-account management with localized copy updates

- keep the live monitor internally scrollable and compact the account cards for better viewport fit

* fix(web): clarify pool routing labels

- rename the recent request badge from Direct to Selected

- restore compact quota bars in the live pool cards

* feat(codex-pool): add runtime health dashboard

- derive per-provider success and failure health from routed Codex traces

- surface routing, quota, and recent request evidence in the pool UI

- align provider alias guidance and owner access with the dashboard role model

* docs(auth): document tenant scoping and key roles

* fix(auth): harden tenant and codex pool access control

* fix(providers): align codex pool runtime defaults

* feat(ui): tighten codex pool responsive layout

* feat(chatgpt-oauth): refine codex pool management UX

* feat(chatgpt-oauth): surface quota bars on provider pages

- add compact quota bars to Codex provider rows and provider detail

- fetch quota only for ready visible provider rows and ready detail aliases

- fix managed-member detail visibility and tighten provider locale copy
2026-03-27 09:35:57 +07:00

123 lines
2.9 KiB
Go

package http
import (
"strings"
)
var identityFieldPrefixes = []string{
"- **Name:**",
"- **Creature:**",
"- **Purpose:**",
"- **Vibe:**",
"- **Emoji:**",
"- **Avatar:**",
"Name:",
"Creature:",
"Purpose:",
"Vibe:",
"Emoji:",
"Avatar:",
}
// extractIdentityName extracts the Name field from IDENTITY.md content.
// Accepts only an inline Name value and ignores markdown field spillover.
func extractIdentityName(content string) string {
if content == "" {
return ""
}
for rawLine := range strings.SplitSeq(content, "\n") {
line := strings.TrimSpace(rawLine)
switch {
case strings.HasPrefix(line, "- **Name:**"):
return normalizeIdentityName(strings.TrimSpace(strings.TrimPrefix(line, "- **Name:**")))
case strings.HasPrefix(line, "Name:"):
return normalizeIdentityName(strings.TrimSpace(strings.TrimPrefix(line, "Name:")))
}
}
return ""
}
func normalizeIdentityName(value string) string {
value = strings.TrimSpace(value)
if value == "" || looksLikeIdentityField(value) {
return ""
}
for {
next := trimMarkdownWrapper(value)
if next == value {
return value
}
value = strings.TrimSpace(next)
if value == "" || looksLikeIdentityField(value) {
return ""
}
}
}
func looksLikeIdentityField(value string) bool {
for _, prefix := range identityFieldPrefixes {
if strings.HasPrefix(value, prefix) {
return true
}
}
return false
}
func trimMarkdownWrapper(value string) string {
wrappers := [][2]string{
{"**", "**"},
{"__", "__"},
{"`", "`"},
{"*", "*"},
{"_", "_"},
}
for _, wrapper := range wrappers {
if strings.HasPrefix(value, wrapper[0]) && strings.HasSuffix(value, wrapper[1]) && len(value) > len(wrapper[0])+len(wrapper[1]) {
return strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(value, wrapper[0]), wrapper[1]))
}
}
return value
}
// suffixString returns the last n runes of s.
func suffixString(s string, n int) string {
runes := []rune(s)
if len(runes) <= n {
return s
}
return string(runes[len(runes)-n:])
}
// truncateUTF8 truncates s to at most maxLen runes, appending "…" if truncated.
func truncateUTF8(s string, maxLen int) string {
runes := []rune(s)
if len(runes) <= maxLen {
return s
}
return string(runes[:maxLen]) + "…"
}
// parseFileResponse extracts file contents and frontmatter from XML-tagged LLM output.
// Frontmatter is stored under the special key "__frontmatter__".
func parseFileResponse(content string) map[string]string {
files := make(map[string]string)
matches := fileTagRe.FindAllStringSubmatch(content, -1)
for _, m := range matches {
name := strings.TrimSpace(m[1])
body := strings.TrimSpace(m[2])
if name != "" && body != "" {
files[name] = body
}
}
// Extract frontmatter tag if present
if fm := frontmatterTagRe.FindStringSubmatch(content); len(fm) > 1 {
if trimmed := strings.TrimSpace(fm[1]); trimmed != "" {
files[frontmatterKey] = trimmed
}
}
return files
}