988 lines
31 KiB
Go
988 lines
31 KiB
Go
package multiagentv2
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/home"
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
|
sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator"
|
|
"github.com/tidwall/gjson"
|
|
"github.com/tidwall/sjson"
|
|
)
|
|
|
|
const (
|
|
codexSpawnAgentDescriptionMarker = "Spawns an agent"
|
|
codexSpawnAgentModelsHeading = "Available model overrides (optional; inherited parent model is preferred):"
|
|
codexCollaborationNamespace = "collaboration"
|
|
codexOptimizedCollaborationNamespace = "collaboration-optimize"
|
|
codexOptimizedCollaborationNamePrefix = codexOptimizedCollaborationNamespace + "__"
|
|
)
|
|
|
|
// CodexMultiAgentV2ToolsPreparedContextKey marks a request whose collaboration
|
|
// tool definitions were prepared at the Responses API boundary.
|
|
const CodexMultiAgentV2ToolsPreparedContextKey = "codex_multi_agent_v2_tools_prepared"
|
|
|
|
// codexCollaborationMessageTools are the collaboration tool names whose
|
|
// parameters.properties.message.encrypted field must be stripped so that
|
|
// message content remains readable by the proxy.
|
|
var codexCollaborationMessageTools = map[string]struct{}{
|
|
"spawn_agent": {},
|
|
"send_message": {},
|
|
"followup_task": {},
|
|
}
|
|
|
|
type codexSpawnAgentModel struct {
|
|
id string
|
|
description string
|
|
reasoningEfforts []string
|
|
defaultReasoningEffort string
|
|
serviceTiers []string
|
|
priority int
|
|
displayName string
|
|
}
|
|
|
|
type codexClientModelsCatalog struct {
|
|
Models []map[string]any `json:"models"`
|
|
}
|
|
|
|
// RewriteCodexSpawnAgentDescription optimizes spawn_agent definitions for
|
|
// official Codex clients when multi-agent v2 optimization is enabled.
|
|
func RewriteCodexSpawnAgentDescription(ctx context.Context, headers http.Header, payload []byte, cfg *config.Config) []byte {
|
|
updated, _ := OptimizeCodexMultiAgentV2Request(ctx, headers, payload, cfg)
|
|
return updated
|
|
}
|
|
|
|
// RewriteCodexMultiAgentV2Input converts official Codex multi-agent input into
|
|
// standard Responses API messages when multi-agent v2 optimization is enabled.
|
|
func RewriteCodexMultiAgentV2Input(ctx context.Context, headers http.Header, payload []byte, cfg *config.Config) []byte {
|
|
if !codexMultiAgentV2Enabled(ctx, headers, cfg) {
|
|
return payload
|
|
}
|
|
return rewriteCodexAgentMessageInput(payload)
|
|
}
|
|
|
|
// TranslateRequestWithCodexMultiAgentV2 normalizes official Codex multi-agent
|
|
// input before translating it to a non-Codex target protocol.
|
|
func TranslateRequestWithCodexMultiAgentV2(ctx context.Context, headers http.Header, cfg *config.Config, from, to sdktranslator.Format, model string, payload []byte, stream bool) []byte {
|
|
if from == sdktranslator.FormatOpenAIResponse && to != sdktranslator.FormatCodex && to != sdktranslator.FormatOpenAIResponse {
|
|
payload = RewriteCodexMultiAgentV2Input(ctx, headers, payload, cfg)
|
|
}
|
|
return sdktranslator.TranslateRequest(from, to, model, payload, stream)
|
|
}
|
|
|
|
// PrepareCodexMultiAgentV2Tools prepares collaboration tool definitions at the
|
|
// Responses API boundary without changing the collaboration namespace.
|
|
func PrepareCodexMultiAgentV2Tools(ctx context.Context, headers http.Header, payload []byte, enabled, homeEnabled bool) ([]byte, bool) {
|
|
if !codexMultiAgentV2ClientEnabled(ctx, headers, enabled) {
|
|
return payload, false
|
|
}
|
|
|
|
toolPaths := codexSpawnAgentToolPaths(payload)
|
|
messageToolPaths := codexCollaborationMessageToolPaths(payload)
|
|
if len(toolPaths) == 0 && len(messageToolPaths) == 0 {
|
|
return payload, true
|
|
}
|
|
if hasCodexOptimizedCollaborationConflict(payload) {
|
|
return removeCodexCollaborationMessageEncryption(payload, messageToolPaths), true
|
|
}
|
|
|
|
var models []codexSpawnAgentModel
|
|
var formattedMarkdown string
|
|
if len(toolPaths) > 0 {
|
|
models, formattedMarkdown = codexSpawnAgentModelsAndMarkdownForRequest(ctx, headers, homeEnabled)
|
|
}
|
|
|
|
updated := rewriteCodexCollaborationTools(payload, messageToolPaths, toolPaths, models, formattedMarkdown)
|
|
return updated, true
|
|
}
|
|
|
|
// OptimizeCodexMultiAgentV2Request rewrites an eligible spawn_agent request and
|
|
// reports whether the collaboration namespace was renamed for upstream use.
|
|
func OptimizeCodexMultiAgentV2Request(ctx context.Context, headers http.Header, payload []byte, cfg *config.Config) ([]byte, bool) {
|
|
if !codexMultiAgentV2Enabled(ctx, headers, cfg) {
|
|
return payload, false
|
|
}
|
|
updated := rewriteCodexAgentMessageContent(payload)
|
|
if codexMultiAgentV2ToolsPrepared(ctx) {
|
|
updated = removeCodexCollaborationMessageEncryption(updated, codexCollaborationMessageToolPaths(updated))
|
|
} else {
|
|
updated, _ = PrepareCodexMultiAgentV2Tools(ctx, headers, updated, cfg.Codex.OptimizeMultiAgentV2, cfg.Home.Enabled)
|
|
}
|
|
toolPaths := codexSpawnAgentToolPaths(updated)
|
|
if len(toolPaths) == 0 || hasCodexOptimizedCollaborationConflict(updated) {
|
|
return updated, false
|
|
}
|
|
return optimizeCodexCollaborationNamespace(updated, toolPaths)
|
|
}
|
|
|
|
func codexMultiAgentV2Enabled(ctx context.Context, headers http.Header, cfg *config.Config) bool {
|
|
return cfg != nil && codexMultiAgentV2ClientEnabled(ctx, headers, cfg.Codex.OptimizeMultiAgentV2)
|
|
}
|
|
|
|
func codexMultiAgentV2ClientEnabled(ctx context.Context, headers http.Header, enabled bool) bool {
|
|
return enabled && isCodexMultiAgentClient(codexClientUserAgent(ctx, headers))
|
|
}
|
|
|
|
func codexMultiAgentV2ToolsPrepared(ctx context.Context) bool {
|
|
if ctx == nil {
|
|
return false
|
|
}
|
|
ginCtx, ok := ctx.Value("gin").(*gin.Context)
|
|
if !ok || ginCtx == nil {
|
|
return false
|
|
}
|
|
prepared, ok := ginCtx.Get(CodexMultiAgentV2ToolsPreparedContextKey)
|
|
isPrepared, _ := prepared.(bool)
|
|
return ok && isPrepared
|
|
}
|
|
|
|
func codexClientUserAgent(ctx context.Context, headers http.Header) string {
|
|
if ctx != nil {
|
|
if ginCtx, ok := ctx.Value("gin").(*gin.Context); ok && ginCtx != nil && ginCtx.Request != nil {
|
|
return headerValueCaseInsensitive(ginCtx.Request.Header, "User-Agent")
|
|
}
|
|
}
|
|
return headerValueCaseInsensitive(headers, "User-Agent")
|
|
}
|
|
|
|
func headerValueCaseInsensitive(headers http.Header, name string) string {
|
|
if headers == nil {
|
|
return ""
|
|
}
|
|
if value := strings.TrimSpace(headers.Get(name)); value != "" {
|
|
return value
|
|
}
|
|
for key, values := range headers {
|
|
if !strings.EqualFold(key, name) {
|
|
continue
|
|
}
|
|
for _, value := range values {
|
|
if value = strings.TrimSpace(value); value != "" {
|
|
return value
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// IsCodexClientUserAgent reports whether a request uses an official Codex client identity.
|
|
func IsCodexClientUserAgent(userAgent string) bool {
|
|
userAgent = strings.TrimSpace(userAgent)
|
|
return strings.HasPrefix(userAgent, "Codex Desktop/") ||
|
|
strings.HasPrefix(userAgent, "codex-tui/") ||
|
|
userAgent == "codex_cli_rs" ||
|
|
strings.HasPrefix(userAgent, "codex_cli_rs/")
|
|
}
|
|
|
|
func isCodexMultiAgentClient(userAgent string) bool {
|
|
return IsCodexClientUserAgent(userAgent)
|
|
}
|
|
|
|
var (
|
|
codexCatalogTemplatesMu sync.RWMutex
|
|
codexCatalogTemplatesLoaded bool
|
|
codexCatalogTemplatesRevision uint64
|
|
codexCatalogTemplates map[string]map[string]any
|
|
codexCatalogDefaultTemplate map[string]any
|
|
|
|
codexSpawnAgentCacheMu sync.RWMutex
|
|
codexSpawnAgentCacheRevision uint64
|
|
codexSpawnAgentCacheGeneration uint64
|
|
codexSpawnAgentCachedModels []codexSpawnAgentModel
|
|
codexSpawnAgentCachedMarkdown string
|
|
)
|
|
|
|
func loadCodexCatalogTemplates() (map[string]map[string]any, map[string]any, uint64, error) {
|
|
currentRevision := registry.GetCodexClientModelsRevision()
|
|
|
|
codexCatalogTemplatesMu.RLock()
|
|
if codexCatalogTemplatesLoaded && codexCatalogTemplatesRevision == currentRevision {
|
|
templates := codexCatalogTemplates
|
|
defaultTemplate := codexCatalogDefaultTemplate
|
|
codexCatalogTemplatesMu.RUnlock()
|
|
return templates, defaultTemplate, currentRevision, nil
|
|
}
|
|
codexCatalogTemplatesMu.RUnlock()
|
|
|
|
codexCatalogTemplatesMu.Lock()
|
|
defer codexCatalogTemplatesMu.Unlock()
|
|
if codexCatalogTemplatesLoaded && codexCatalogTemplatesRevision == currentRevision {
|
|
return codexCatalogTemplates, codexCatalogDefaultTemplate, currentRevision, nil
|
|
}
|
|
|
|
raw, revision := registry.GetCodexClientModelsSnapshot()
|
|
|
|
var catalog codexClientModelsCatalog
|
|
errUnmarshal := json.Unmarshal(raw, &catalog)
|
|
if errUnmarshal != nil || len(catalog.Models) == 0 {
|
|
codexCatalogTemplatesLoaded = true
|
|
codexCatalogTemplatesRevision = revision
|
|
codexCatalogTemplates = nil
|
|
codexCatalogDefaultTemplate = nil
|
|
return nil, nil, revision, errUnmarshal
|
|
}
|
|
|
|
templates := make(map[string]map[string]any, len(catalog.Models))
|
|
var defaultTemplate map[string]any
|
|
for _, model := range catalog.Models {
|
|
modelID := mapString(model, "slug")
|
|
if modelID == "" {
|
|
continue
|
|
}
|
|
templates[modelID] = model
|
|
if modelID == "gpt-5.5" {
|
|
defaultTemplate = model
|
|
}
|
|
}
|
|
|
|
codexCatalogTemplatesLoaded = true
|
|
codexCatalogTemplatesRevision = revision
|
|
codexCatalogTemplates = templates
|
|
codexCatalogDefaultTemplate = defaultTemplate
|
|
return templates, defaultTemplate, revision, nil
|
|
}
|
|
|
|
func codexSpawnAgentModelsAndMarkdownForRequest(ctx context.Context, headers http.Header, homeEnabled bool) ([]codexSpawnAgentModel, string) {
|
|
if homeEnabled {
|
|
availableModels := codexHomeAvailableModels(ctx, headers)
|
|
templates, defaultTemplate, _, errLoad := loadCodexCatalogTemplates()
|
|
if errLoad != nil || defaultTemplate == nil {
|
|
return nil, ""
|
|
}
|
|
models := codexSpawnAgentModelsFromTemplates(availableModels, templates, defaultTemplate, func(modelID string) *registry.ModelInfo {
|
|
return registry.LookupModelInfo(modelID)
|
|
})
|
|
formatted := formatCodexSpawnAgentModels(models)
|
|
return models, formatted
|
|
}
|
|
|
|
currentRevision := registry.GetCodexClientModelsRevision()
|
|
currentGeneration := registry.GetGlobalRegistry().GetGeneration()
|
|
|
|
codexSpawnAgentCacheMu.RLock()
|
|
if codexSpawnAgentCachedModels != nil && codexSpawnAgentCacheRevision == currentRevision && codexSpawnAgentCacheGeneration == currentGeneration {
|
|
models := codexSpawnAgentCachedModels
|
|
markdown := codexSpawnAgentCachedMarkdown
|
|
codexSpawnAgentCacheMu.RUnlock()
|
|
return models, markdown
|
|
}
|
|
codexSpawnAgentCacheMu.RUnlock()
|
|
|
|
templates, defaultTemplate, _, errLoad := loadCodexCatalogTemplates()
|
|
if errLoad != nil || defaultTemplate == nil {
|
|
return nil, ""
|
|
}
|
|
|
|
availableModels := registry.GetGlobalRegistry().GetAvailableModels("openai")
|
|
lookup := func(modelID string) *registry.ModelInfo {
|
|
return registry.LookupModelInfo(modelID)
|
|
}
|
|
models := codexSpawnAgentModelsFromTemplates(availableModels, templates, defaultTemplate, lookup)
|
|
formatted := formatCodexSpawnAgentModels(models)
|
|
|
|
codexSpawnAgentCacheMu.Lock()
|
|
if currentRevision == registry.GetCodexClientModelsRevision() && currentGeneration == registry.GetGlobalRegistry().GetGeneration() {
|
|
codexSpawnAgentCacheRevision = currentRevision
|
|
codexSpawnAgentCacheGeneration = currentGeneration
|
|
codexSpawnAgentCachedModels = models
|
|
codexSpawnAgentCachedMarkdown = formatted
|
|
}
|
|
codexSpawnAgentCacheMu.Unlock()
|
|
|
|
return models, formatted
|
|
}
|
|
|
|
func codexSpawnAgentModelsForRequest(ctx context.Context, headers http.Header, homeEnabled bool) []codexSpawnAgentModel {
|
|
models, _ := codexSpawnAgentModelsAndMarkdownForRequest(ctx, headers, homeEnabled)
|
|
return models
|
|
}
|
|
|
|
func formatCodexSpawnAgentModelsForRequest(ctx context.Context, headers http.Header, homeEnabled bool) string {
|
|
_, formatted := codexSpawnAgentModelsAndMarkdownForRequest(ctx, headers, homeEnabled)
|
|
return formatted
|
|
}
|
|
|
|
func codexHomeAvailableModels(ctx context.Context, headers http.Header) []map[string]any {
|
|
client := home.Current()
|
|
if client == nil {
|
|
return nil
|
|
}
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
requestHeaders := headers
|
|
if ginCtx, ok := ctx.Value("gin").(*gin.Context); ok && ginCtx != nil && ginCtx.Request != nil {
|
|
requestHeaders = ginCtx.Request.Header
|
|
}
|
|
query := make(url.Values)
|
|
query.Set("client_version", "")
|
|
raw, errGet := client.GetModels(ctx, requestHeaders, query)
|
|
if errGet != nil {
|
|
return nil
|
|
}
|
|
return decodeCodexHomeAvailableModels(raw)
|
|
}
|
|
|
|
func decodeCodexHomeAvailableModels(raw []byte) []map[string]any {
|
|
var sections map[string][]map[string]any
|
|
if err := json.Unmarshal(raw, §ions); err != nil || len(sections) == 0 {
|
|
return nil
|
|
}
|
|
|
|
seen := make(map[string]struct{})
|
|
models := make([]map[string]any, 0, 256)
|
|
for _, sectionModels := range sections {
|
|
for _, model := range sectionModels {
|
|
modelID := mapString(model, "id")
|
|
if modelID == "" {
|
|
modelID = strings.TrimPrefix(mapString(model, "name"), "models/")
|
|
}
|
|
if modelID == "" {
|
|
continue
|
|
}
|
|
if _, exists := seen[modelID]; exists {
|
|
continue
|
|
}
|
|
seen[modelID] = struct{}{}
|
|
|
|
displayName := mapString(model, "display_name")
|
|
if displayName == "" {
|
|
displayName = mapString(model, "displayName")
|
|
}
|
|
entry := map[string]any{"id": modelID}
|
|
if displayName != "" {
|
|
entry["display_name"] = displayName
|
|
entry["description"] = displayName
|
|
}
|
|
models = append(models, entry)
|
|
}
|
|
}
|
|
sort.Slice(models, func(i, j int) bool {
|
|
return mapString(models[i], "id") < mapString(models[j], "id")
|
|
})
|
|
return models
|
|
}
|
|
|
|
func codexSpawnAgentModelsFromSources(availableModels []map[string]any, catalogJSON []byte, lookupModel func(string) *registry.ModelInfo) []codexSpawnAgentModel {
|
|
var catalog codexClientModelsCatalog
|
|
if err := json.Unmarshal(catalogJSON, &catalog); err != nil || len(catalog.Models) == 0 {
|
|
return nil
|
|
}
|
|
|
|
templates := make(map[string]map[string]any, len(catalog.Models))
|
|
var defaultTemplate map[string]any
|
|
for _, model := range catalog.Models {
|
|
modelID := mapString(model, "slug")
|
|
if modelID == "" {
|
|
continue
|
|
}
|
|
templates[modelID] = model
|
|
if modelID == "gpt-5.5" {
|
|
defaultTemplate = model
|
|
}
|
|
}
|
|
if defaultTemplate == nil {
|
|
return nil
|
|
}
|
|
|
|
return codexSpawnAgentModelsFromTemplates(availableModels, templates, defaultTemplate, lookupModel)
|
|
}
|
|
|
|
func codexSpawnAgentModelsFromTemplates(availableModels []map[string]any, templates map[string]map[string]any, defaultTemplate map[string]any, lookupModel func(string) *registry.ModelInfo) []codexSpawnAgentModel {
|
|
if defaultTemplate == nil {
|
|
return nil
|
|
}
|
|
|
|
seen := make(map[string]struct{}, len(availableModels))
|
|
templateModels := make([]codexSpawnAgentModel, 0, len(availableModels))
|
|
synthesizedModels := make([]codexSpawnAgentModel, 0, len(availableModels))
|
|
for _, availableModel := range availableModels {
|
|
modelID := mapString(availableModel, "id")
|
|
if modelID == "" {
|
|
continue
|
|
}
|
|
if _, exists := seen[modelID]; exists {
|
|
continue
|
|
}
|
|
seen[modelID] = struct{}{}
|
|
|
|
if template, ok := templates[modelID]; ok {
|
|
templateModels = append(templateModels, codexSpawnAgentModelFromMetadata(modelID, template))
|
|
continue
|
|
}
|
|
|
|
profile := codexSpawnAgentModelFromMetadata(modelID, defaultTemplate)
|
|
profile.id = modelID
|
|
profile.description = mapString(availableModel, "description")
|
|
profile.displayName = mapString(availableModel, "display_name")
|
|
if profile.displayName == "" {
|
|
profile.displayName = modelID
|
|
}
|
|
if lookupModel != nil {
|
|
if info := lookupModel(modelID); info != nil {
|
|
if strings.TrimSpace(info.Description) != "" {
|
|
profile.description = strings.TrimSpace(info.Description)
|
|
}
|
|
applyCodexSpawnAgentThinking(&profile, info.Thinking)
|
|
}
|
|
}
|
|
if profile.description == "" {
|
|
profile.description = modelID
|
|
}
|
|
profile.serviceTiers = nil
|
|
synthesizedModels = append(synthesizedModels, profile)
|
|
}
|
|
|
|
sort.SliceStable(templateModels, func(i, j int) bool {
|
|
if templateModels[i].priority == templateModels[j].priority {
|
|
return templateModels[i].id < templateModels[j].id
|
|
}
|
|
return templateModels[i].priority < templateModels[j].priority
|
|
})
|
|
sort.SliceStable(synthesizedModels, func(i, j int) bool {
|
|
left := strings.ToLower(synthesizedModels[i].displayName)
|
|
right := strings.ToLower(synthesizedModels[j].displayName)
|
|
if left == right {
|
|
return synthesizedModels[i].id < synthesizedModels[j].id
|
|
}
|
|
return left < right
|
|
})
|
|
return append(templateModels, synthesizedModels...)
|
|
}
|
|
|
|
func codexSpawnAgentModelFromMetadata(modelID string, metadata map[string]any) codexSpawnAgentModel {
|
|
profile := codexSpawnAgentModel{
|
|
id: modelID,
|
|
description: mapString(metadata, "description"),
|
|
displayName: mapString(metadata, "display_name"),
|
|
priority: mapInt(metadata, "priority"),
|
|
}
|
|
profile.reasoningEfforts, profile.defaultReasoningEffort = codexReasoningMetadata(metadata)
|
|
profile.serviceTiers = codexServiceTierIDs(metadata)
|
|
return profile
|
|
}
|
|
|
|
func applyCodexSpawnAgentThinking(profile *codexSpawnAgentModel, thinking *registry.ThinkingSupport) {
|
|
if profile == nil || thinking == nil || len(thinking.Levels) == 0 {
|
|
return
|
|
}
|
|
|
|
efforts := make([]string, 0, len(thinking.Levels))
|
|
defaultEffort := ""
|
|
firstEffort := ""
|
|
for _, rawEffort := range thinking.Levels {
|
|
effort := normalizeCodexReasoningEffort(rawEffort)
|
|
if effort == "" {
|
|
continue
|
|
}
|
|
if firstEffort == "" {
|
|
firstEffort = effort
|
|
}
|
|
if (defaultEffort == "" && effort != "none") || effort == "medium" {
|
|
defaultEffort = effort
|
|
}
|
|
efforts = append(efforts, effort)
|
|
}
|
|
if len(efforts) == 0 {
|
|
return
|
|
}
|
|
if defaultEffort == "" {
|
|
defaultEffort = firstEffort
|
|
}
|
|
profile.reasoningEfforts = efforts
|
|
profile.defaultReasoningEffort = defaultEffort
|
|
}
|
|
|
|
func codexReasoningMetadata(metadata map[string]any) ([]string, string) {
|
|
rawLevels, _ := metadata["supported_reasoning_levels"].([]any)
|
|
efforts := make([]string, 0, len(rawLevels))
|
|
allowed := make(map[string]struct{}, len(rawLevels))
|
|
for _, rawLevel := range rawLevels {
|
|
level, _ := rawLevel.(map[string]any)
|
|
effort := normalizeCodexReasoningEffort(mapString(level, "effort"))
|
|
if effort == "" {
|
|
continue
|
|
}
|
|
efforts = append(efforts, effort)
|
|
allowed[effort] = struct{}{}
|
|
}
|
|
if len(efforts) == 0 {
|
|
return nil, ""
|
|
}
|
|
|
|
defaultEffort := normalizeCodexReasoningEffort(mapString(metadata, "default_reasoning_level"))
|
|
if _, ok := allowed[defaultEffort]; !ok {
|
|
defaultEffort = efforts[0]
|
|
}
|
|
return efforts, defaultEffort
|
|
}
|
|
|
|
func normalizeCodexReasoningEffort(effort string) string {
|
|
effort = strings.ToLower(strings.TrimSpace(effort))
|
|
switch effort {
|
|
case "none", "low", "medium", "high", "xhigh", "max", "ultra":
|
|
return effort
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func codexServiceTierIDs(metadata map[string]any) []string {
|
|
rawTiers, _ := metadata["service_tiers"].([]any)
|
|
tiers := make([]string, 0, len(rawTiers))
|
|
seen := make(map[string]struct{}, len(rawTiers))
|
|
for _, rawTier := range rawTiers {
|
|
tier, _ := rawTier.(map[string]any)
|
|
tierID := mapString(tier, "id")
|
|
if tierID == "" {
|
|
continue
|
|
}
|
|
if _, exists := seen[tierID]; exists {
|
|
continue
|
|
}
|
|
seen[tierID] = struct{}{}
|
|
tiers = append(tiers, tierID)
|
|
}
|
|
return tiers
|
|
}
|
|
|
|
func mapString(values map[string]any, key string) string {
|
|
if values == nil {
|
|
return ""
|
|
}
|
|
value, _ := values[key].(string)
|
|
return strings.TrimSpace(value)
|
|
}
|
|
|
|
func mapInt(values map[string]any, key string) int {
|
|
if values == nil {
|
|
return 0
|
|
}
|
|
switch value := values[key].(type) {
|
|
case int:
|
|
return value
|
|
case int64:
|
|
return int(value)
|
|
case float64:
|
|
return int(value)
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func rewriteCodexSpawnAgentDescription(payload []byte, models []codexSpawnAgentModel) []byte {
|
|
return rewriteCodexSpawnAgentTools(payload, codexSpawnAgentToolPaths(payload), models)
|
|
}
|
|
|
|
func rewriteCodexSpawnAgentTools(payload []byte, toolPaths []string, models []codexSpawnAgentModel) []byte {
|
|
return rewriteCodexCollaborationTools(payload, toolPaths, toolPaths, models, "")
|
|
}
|
|
|
|
func rewriteCodexCollaborationTools(payload []byte, messageToolPaths, spawnAgentToolPaths []string, models []codexSpawnAgentModel, modelList string) []byte {
|
|
if len(messageToolPaths) == 0 && len(spawnAgentToolPaths) == 0 {
|
|
return payload
|
|
}
|
|
if modelList == "" && len(models) > 0 {
|
|
modelList = formatCodexSpawnAgentModels(models)
|
|
}
|
|
updated := payload
|
|
for _, toolPath := range spawnAgentToolPaths {
|
|
descriptionPath := toolPath + ".description"
|
|
description := gjson.GetBytes(updated, descriptionPath)
|
|
if description.Type == gjson.String && modelList != "" {
|
|
rewritten := replaceCodexSpawnAgentModels(description.String(), modelList)
|
|
if rewritten != description.String() {
|
|
var errSet error
|
|
updated, errSet = sjson.SetBytes(updated, descriptionPath, rewritten)
|
|
if errSet != nil {
|
|
return payload
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, toolPath := range messageToolPaths {
|
|
encryptedPath := toolPath + ".parameters.properties.message.encrypted"
|
|
if gjson.GetBytes(updated, encryptedPath).Exists() {
|
|
var errDelete error
|
|
updated, errDelete = sjson.DeleteBytes(updated, encryptedPath)
|
|
if errDelete != nil {
|
|
return payload
|
|
}
|
|
}
|
|
}
|
|
return updated
|
|
}
|
|
|
|
// HasCodexMultiAgentV2NamespaceConflict reports whether the request defines
|
|
// the reserved optimized namespace, which must remain untouched.
|
|
func HasCodexMultiAgentV2NamespaceConflict(payload []byte) bool {
|
|
return hasCodexOptimizedCollaborationConflict(payload)
|
|
}
|
|
|
|
func hasCodexOptimizedCollaborationConflict(payload []byte) bool {
|
|
if codexToolsHaveOptimizedCollaborationConflict(gjson.GetBytes(payload, "tools")) {
|
|
return true
|
|
}
|
|
input := gjson.GetBytes(payload, "input")
|
|
if !input.IsArray() {
|
|
return false
|
|
}
|
|
for _, item := range input.Array() {
|
|
if strings.TrimSpace(item.Get("type").String()) == "additional_tools" && codexToolsHaveOptimizedCollaborationConflict(item.Get("tools")) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func codexToolsHaveOptimizedCollaborationConflict(tools gjson.Result) bool {
|
|
if !tools.IsArray() {
|
|
return false
|
|
}
|
|
for _, tool := range tools.Array() {
|
|
name := strings.TrimSpace(tool.Get("name").String())
|
|
if name == codexOptimizedCollaborationNamespace || strings.HasPrefix(name, codexOptimizedCollaborationNamePrefix) {
|
|
return true
|
|
}
|
|
if strings.TrimSpace(tool.Get("type").String()) == "namespace" && codexToolsHaveOptimizedCollaborationConflict(tool.Get("tools")) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func optimizeCodexCollaborationNamespace(payload []byte, toolPaths []string) ([]byte, bool) {
|
|
updated := payload
|
|
optimized := false
|
|
for _, toolPath := range toolPaths {
|
|
separatorIndex := strings.LastIndex(toolPath, ".tools.")
|
|
if separatorIndex < 0 {
|
|
continue
|
|
}
|
|
namespacePath := toolPath[:separatorIndex]
|
|
namespace := gjson.GetBytes(updated, namespacePath)
|
|
if strings.TrimSpace(namespace.Get("type").String()) != "namespace" || strings.TrimSpace(namespace.Get("name").String()) != codexCollaborationNamespace {
|
|
continue
|
|
}
|
|
var errSet error
|
|
updated, errSet = sjson.SetBytes(updated, namespacePath+".name", codexOptimizedCollaborationNamespace)
|
|
if errSet != nil {
|
|
return payload, false
|
|
}
|
|
optimized = true
|
|
}
|
|
return updated, optimized
|
|
}
|
|
|
|
// RestoreCodexMultiAgentV2Response restores optimized collaboration namespace
|
|
// values before an upstream response is translated and returned to the client.
|
|
func RestoreCodexMultiAgentV2Response(payload []byte, optimized bool) []byte {
|
|
if !optimized || len(payload) == 0 || !gjson.ValidBytes(payload) {
|
|
return payload
|
|
}
|
|
|
|
decoder := json.NewDecoder(bytes.NewReader(payload))
|
|
decoder.UseNumber()
|
|
var value any
|
|
if errDecode := decoder.Decode(&value); errDecode != nil {
|
|
return payload
|
|
}
|
|
if !restoreCodexCollaborationValue(value) {
|
|
return payload
|
|
}
|
|
restored, errMarshal := json.Marshal(value)
|
|
if errMarshal != nil {
|
|
return payload
|
|
}
|
|
return restored
|
|
}
|
|
|
|
func restoreCodexCollaborationValue(value any) bool {
|
|
changed := false
|
|
switch typed := value.(type) {
|
|
case []any:
|
|
for _, item := range typed {
|
|
if restoreCodexCollaborationValue(item) {
|
|
changed = true
|
|
}
|
|
}
|
|
case map[string]any:
|
|
itemType := strings.TrimSpace(mapString(typed, "type"))
|
|
isToolCall := itemType == "function_call" || itemType == "custom_tool_call"
|
|
if isToolCall {
|
|
if namespace, ok := typed["namespace"].(string); ok && namespace == codexOptimizedCollaborationNamespace {
|
|
typed["namespace"] = codexCollaborationNamespace
|
|
changed = true
|
|
}
|
|
}
|
|
if name, ok := typed["name"].(string); ok {
|
|
switch {
|
|
case name == codexOptimizedCollaborationNamespace && itemType == "namespace":
|
|
typed["name"] = codexCollaborationNamespace
|
|
changed = true
|
|
case isToolCall && strings.HasPrefix(name, codexOptimizedCollaborationNamePrefix):
|
|
typed["name"] = codexCollaborationNamespace + "__" + strings.TrimPrefix(name, codexOptimizedCollaborationNamePrefix)
|
|
changed = true
|
|
}
|
|
}
|
|
for key, child := range typed {
|
|
if key == "arguments" || key == "input" || key == "output" && (itemType == "function_call_output" || itemType == "custom_tool_call_output") {
|
|
continue
|
|
}
|
|
if restoreCodexCollaborationValue(child) {
|
|
changed = true
|
|
}
|
|
}
|
|
}
|
|
return changed
|
|
}
|
|
|
|
func rewriteCodexAgentMessageInput(payload []byte) []byte {
|
|
input := gjson.GetBytes(payload, "input")
|
|
if !input.IsArray() {
|
|
return payload
|
|
}
|
|
|
|
updated := rewriteCodexAgentMessageContent(payload)
|
|
for itemIndex, item := range input.Array() {
|
|
if strings.TrimSpace(item.Get("type").String()) != "agent_message" {
|
|
continue
|
|
}
|
|
itemPath := fmt.Sprintf("input.%d", itemIndex)
|
|
var errSet error
|
|
updated, errSet = sjson.SetBytes(updated, itemPath+".role", "user")
|
|
if errSet != nil {
|
|
return payload
|
|
}
|
|
updated, errSet = sjson.SetBytes(updated, itemPath+".type", "message")
|
|
if errSet != nil {
|
|
return payload
|
|
}
|
|
}
|
|
return updated
|
|
}
|
|
|
|
func rewriteCodexAgentMessageContent(payload []byte) []byte {
|
|
input := gjson.GetBytes(payload, "input")
|
|
if !input.IsArray() {
|
|
return payload
|
|
}
|
|
|
|
updated := payload
|
|
for itemIndex, item := range input.Array() {
|
|
if strings.TrimSpace(item.Get("type").String()) != "agent_message" {
|
|
continue
|
|
}
|
|
content := item.Get("content")
|
|
if !content.IsArray() {
|
|
continue
|
|
}
|
|
for partIndex, part := range content.Array() {
|
|
if strings.TrimSpace(part.Get("type").String()) != "encrypted_content" {
|
|
continue
|
|
}
|
|
encryptedContent := part.Get("encrypted_content")
|
|
if encryptedContent.Type != gjson.String {
|
|
continue
|
|
}
|
|
partPath := fmt.Sprintf("input.%d.content.%d", itemIndex, partIndex)
|
|
var errSet error
|
|
updated, errSet = sjson.SetBytes(updated, partPath+".type", "input_text")
|
|
if errSet != nil {
|
|
return payload
|
|
}
|
|
updated, errSet = sjson.SetBytes(updated, partPath+".text", encryptedContent.String())
|
|
if errSet != nil {
|
|
return payload
|
|
}
|
|
updated, errSet = sjson.DeleteBytes(updated, partPath+".encrypted_content")
|
|
if errSet != nil {
|
|
return payload
|
|
}
|
|
}
|
|
}
|
|
return updated
|
|
}
|
|
|
|
func codexSpawnAgentToolPaths(payload []byte) []string {
|
|
return codexToolPathsByNames(payload, map[string]struct{}{"spawn_agent": {}})
|
|
}
|
|
|
|
// codexCollaborationMessageToolPaths discovers function tools named
|
|
// spawn_agent, send_message, or followup_task inside top-level tools arrays and
|
|
// input[].additional_tools arrays, including nested namespace tools.
|
|
func codexCollaborationMessageToolPaths(payload []byte) []string {
|
|
return codexToolPathsByNames(payload, codexCollaborationMessageTools)
|
|
}
|
|
|
|
func codexToolPathsByNames(payload []byte, names map[string]struct{}) []string {
|
|
paths := make([]string, 0, len(names))
|
|
collectCodexToolPathsByNames(gjson.GetBytes(payload, "tools"), "tools", &paths, names)
|
|
|
|
input := gjson.GetBytes(payload, "input")
|
|
if input.IsArray() {
|
|
for index, item := range input.Array() {
|
|
if strings.TrimSpace(item.Get("type").String()) != "additional_tools" {
|
|
continue
|
|
}
|
|
collectCodexToolPathsByNames(item.Get("tools"), fmt.Sprintf("input.%d.tools", index), &paths, names)
|
|
}
|
|
}
|
|
return paths
|
|
}
|
|
|
|
func collectCodexToolPathsByNames(tools gjson.Result, path string, paths *[]string, names map[string]struct{}) {
|
|
if !tools.IsArray() {
|
|
return
|
|
}
|
|
for index, tool := range tools.Array() {
|
|
toolPath := fmt.Sprintf("%s.%d", path, index)
|
|
toolType := strings.TrimSpace(tool.Get("type").String())
|
|
if toolType == "function" {
|
|
if _, ok := names[strings.TrimSpace(tool.Get("name").String())]; ok {
|
|
*paths = append(*paths, toolPath)
|
|
}
|
|
}
|
|
if toolType == "namespace" {
|
|
collectCodexToolPathsByNames(tool.Get("tools"), toolPath+".tools", paths, names)
|
|
}
|
|
}
|
|
}
|
|
|
|
// removeCodexCollaborationMessageEncryption deletes the
|
|
// parameters.properties.message.encrypted field from each discovered
|
|
// collaboration message tool so the proxy can read the plaintext message.
|
|
func removeCodexCollaborationMessageEncryption(payload []byte, toolPaths []string) []byte {
|
|
updated := payload
|
|
for _, toolPath := range toolPaths {
|
|
encryptedPath := toolPath + ".parameters.properties.message.encrypted"
|
|
if !gjson.GetBytes(updated, encryptedPath).Exists() {
|
|
continue
|
|
}
|
|
var errDelete error
|
|
updated, errDelete = sjson.DeleteBytes(updated, encryptedPath)
|
|
if errDelete != nil {
|
|
return payload
|
|
}
|
|
}
|
|
return updated
|
|
}
|
|
|
|
func formatCodexSpawnAgentModels(models []codexSpawnAgentModel) string {
|
|
var modelList strings.Builder
|
|
for _, model := range models {
|
|
modelID := strings.Join(strings.Fields(model.id), " ")
|
|
if modelID == "" {
|
|
continue
|
|
}
|
|
modelList.WriteString("- ")
|
|
modelList.WriteString(markdownCode(modelID))
|
|
modelList.WriteString(": ")
|
|
hasDetails := false
|
|
if description := strings.Join(strings.Fields(model.description), " "); description != "" {
|
|
writeSentence(&modelList, description)
|
|
hasDetails = true
|
|
}
|
|
if len(model.reasoningEfforts) > 0 {
|
|
if hasDetails {
|
|
modelList.WriteByte(' ')
|
|
}
|
|
modelList.WriteString("Reasoning efforts: ")
|
|
for index, effort := range model.reasoningEfforts {
|
|
if index > 0 {
|
|
modelList.WriteString(", ")
|
|
}
|
|
modelList.WriteString(effort)
|
|
if effort == model.defaultReasoningEffort {
|
|
modelList.WriteString(" (default)")
|
|
}
|
|
}
|
|
modelList.WriteByte('.')
|
|
hasDetails = true
|
|
}
|
|
if len(model.serviceTiers) > 0 {
|
|
if hasDetails {
|
|
modelList.WriteByte(' ')
|
|
}
|
|
modelList.WriteString("Service tiers: ")
|
|
modelList.WriteString(strings.Join(model.serviceTiers, ", "))
|
|
modelList.WriteByte('.')
|
|
}
|
|
modelList.WriteByte('\n')
|
|
}
|
|
return strings.TrimSuffix(modelList.String(), "\n")
|
|
}
|
|
|
|
func markdownCode(value string) string {
|
|
if strings.Contains(value, "`") {
|
|
return "`` " + value + " ``"
|
|
}
|
|
return "`" + value + "`"
|
|
}
|
|
|
|
func writeSentence(builder *strings.Builder, value string) {
|
|
builder.WriteString(value)
|
|
if !strings.ContainsAny(value[len(value)-1:], ".!?") {
|
|
builder.WriteByte('.')
|
|
}
|
|
}
|
|
|
|
func replaceCodexSpawnAgentModels(description, modelList string) string {
|
|
if modelList == "" {
|
|
return description
|
|
}
|
|
|
|
cleaned, headingIndent := removeCodexSpawnAgentModelSections(description)
|
|
section := headingIndent + codexSpawnAgentModelsHeading + "\n" + modelList + "\n"
|
|
markerIndex := strings.Index(cleaned, codexSpawnAgentDescriptionMarker)
|
|
if markerIndex >= 0 {
|
|
markerLineStart := strings.LastIndex(cleaned[:markerIndex], "\n") + 1
|
|
return cleaned[:markerLineStart] + section + cleaned[markerLineStart:]
|
|
}
|
|
separator := ""
|
|
if cleaned != "" && !strings.HasSuffix(cleaned, "\n") {
|
|
separator = "\n\n"
|
|
}
|
|
return cleaned + separator + strings.TrimSuffix(section, "\n")
|
|
}
|
|
|
|
func removeCodexSpawnAgentModelSections(description string) (string, string) {
|
|
if !strings.Contains(description, codexSpawnAgentModelsHeading) {
|
|
return description, ""
|
|
}
|
|
lines := strings.SplitAfter(description, "\n")
|
|
var cleaned strings.Builder
|
|
headingIndent := ""
|
|
for index := 0; index < len(lines); {
|
|
line := lines[index]
|
|
trimmedLine := strings.TrimSpace(line)
|
|
if trimmedLine != codexSpawnAgentModelsHeading {
|
|
cleaned.WriteString(line)
|
|
index++
|
|
continue
|
|
}
|
|
|
|
if headingIndent == "" {
|
|
headingIndex := strings.Index(line, codexSpawnAgentModelsHeading)
|
|
if headingIndex > 0 {
|
|
headingIndent = line[:headingIndex]
|
|
}
|
|
}
|
|
index++
|
|
for index < len(lines) && strings.HasPrefix(strings.TrimSpace(lines[index]), "- ") {
|
|
index++
|
|
}
|
|
}
|
|
return cleaned.String(), headingIndent
|
|
}
|