Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
212 lines
7.1 KiB
Go
212 lines
7.1 KiB
Go
package runtimeprep
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
const codexModelsCacheAuthorityFile = ".models_cache.authority.sha256"
|
|
|
|
var codexModelsCacheAuthorityMu sync.Mutex
|
|
|
|
// exposeUserCodexModelsCache gives every run-scoped CODEX_HOME one shared,
|
|
// process-default model cache. Codex refreshes models_cache.json before it emits
|
|
// thread.started; keeping that writable cache behind a symlink lets a refresh
|
|
// from one AgentGUI session remove the cold catalog request from later sessions.
|
|
//
|
|
// Cache reuse is fenced by the current global config, auth and referenced model
|
|
// catalog. Codex's own cache file does not record provider identity, so carrying
|
|
// it across a global input change can expose another provider's model list. The
|
|
// link is installed even before the source exists; Codex treats that as a cache
|
|
// miss and writes the new authority-scoped cache through the link.
|
|
func exposeUserCodexModelsCache(codexHome, userCodexHome string) error {
|
|
codexModelsCacheAuthorityMu.Lock()
|
|
defer codexModelsCacheAuthorityMu.Unlock()
|
|
|
|
target := filepath.Join(codexHome, "models_cache.json")
|
|
if err := reconcileUserCodexModelsCacheAuthority(target, userCodexHome); err != nil {
|
|
return err
|
|
}
|
|
if _, err := os.Lstat(target); err == nil {
|
|
return nil
|
|
} else if !os.IsNotExist(err) {
|
|
return fmt.Errorf("inspect codex models cache: %w", err)
|
|
}
|
|
|
|
if err := os.MkdirAll(userCodexHome, 0o700); err != nil {
|
|
return fmt.Errorf("create shared codex home for models cache: %w", err)
|
|
}
|
|
source := filepath.Join(userCodexHome, "models_cache.json")
|
|
linkErr := exposeCodexFile(source, target, 0o600)
|
|
if linkErr == nil {
|
|
return nil
|
|
}
|
|
info, statErr := os.Stat(source)
|
|
if os.IsNotExist(statErr) {
|
|
if err := initializeCodexModelsCacheForWindows(source, target); err != nil {
|
|
return fmt.Errorf("initialize Windows codex models cache: %w", err)
|
|
}
|
|
// Platforms that cannot create the link have no cache to copy yet. The
|
|
// run remains usable and Codex will create its ordinary local cache.
|
|
return nil
|
|
}
|
|
if statErr != nil {
|
|
return fmt.Errorf("inspect shared codex models cache after link failure: %w", statErr)
|
|
}
|
|
if info.IsDir() {
|
|
return fmt.Errorf("shared codex models cache is a directory: %s", source)
|
|
}
|
|
if copyErr := copyFile(source, target, 0o600); copyErr != nil {
|
|
return fmt.Errorf("expose codex models cache: link failed: %v; copy failed: %w", linkErr, copyErr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func reconcileUserCodexModelsCacheAuthority(target, userCodexHome string) error {
|
|
authority, err := userCodexModelsCacheAuthority(userCodexHome)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fencePath := filepath.Join(userCodexHome, codexModelsCacheAuthorityFile)
|
|
if existing, readErr := os.ReadFile(fencePath); readErr == nil && strings.TrimSpace(string(existing)) == authority {
|
|
return nil
|
|
} else if readErr != nil && !os.IsNotExist(readErr) {
|
|
return fmt.Errorf("read codex models cache authority: %w", readErr)
|
|
}
|
|
|
|
sharedCache := filepath.Join(userCodexHome, "models_cache.json")
|
|
if err := removeCodexModelsCacheFile(sharedCache); err != nil {
|
|
return err
|
|
}
|
|
if info, statErr := os.Lstat(target); statErr == nil {
|
|
if info.Mode()&os.ModeSymlink == 0 {
|
|
if err := removeCodexModelsCacheFile(target); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else if !os.IsNotExist(statErr) {
|
|
return fmt.Errorf("inspect run-scoped codex models cache: %w", statErr)
|
|
}
|
|
return writeCodexModelsCacheAuthority(fencePath, authority)
|
|
}
|
|
|
|
func userCodexModelsCacheAuthority(userCodexHome string) (string, error) {
|
|
digest := sha256.New()
|
|
configPath := filepath.Join(userCodexHome, "config.toml")
|
|
config, err := hashCodexModelsCacheAuthorityFile(digest, "config", configPath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if _, err := hashCodexModelsCacheAuthorityFile(digest, "auth", filepath.Join(userCodexHome, "auth.json")); err != nil {
|
|
return "", err
|
|
}
|
|
if catalogPath := codexConfigRelativeFilePath(config, userCodexHome, "model_catalog_json"); catalogPath != "" {
|
|
if _, err := hashCodexModelsCacheAuthorityFile(digest, "model_catalog", catalogPath); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
if instructionsPath := codexConfigRelativeFilePath(config, userCodexHome, "model_instructions_file"); instructionsPath != "" {
|
|
if _, err := hashCodexModelsCacheAuthorityFile(digest, "model_instructions", instructionsPath); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
return hex.EncodeToString(digest.Sum(nil)), nil
|
|
}
|
|
|
|
func hashCodexModelsCacheAuthorityFile(digest io.Writer, label, path string) ([]byte, error) {
|
|
content, err := os.ReadFile(path)
|
|
switch {
|
|
case err == nil:
|
|
_, _ = io.WriteString(digest, label+"\x00present\x00")
|
|
_, _ = digest.Write(content)
|
|
_, _ = io.WriteString(digest, "\x00")
|
|
return content, nil
|
|
case os.IsNotExist(err):
|
|
_, _ = io.WriteString(digest, label+"\x00missing\x00")
|
|
return nil, nil
|
|
default:
|
|
return nil, fmt.Errorf("read codex models cache authority %s: %w", label, err)
|
|
}
|
|
}
|
|
|
|
func codexConfigRelativeFilePath(config []byte, userCodexHome string, key string) string {
|
|
lines := strings.Split(strings.ReplaceAll(string(config), "\r\n", "\n"), "\n")
|
|
for _, line := range lines {
|
|
trimmed := strings.TrimSpace(line)
|
|
if trimmed == "" || strings.HasPrefix(trimmed, "#") {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(trimmed, "[") {
|
|
return ""
|
|
}
|
|
value, ok := codexConfigStringAssignmentValue(trimmed, key)
|
|
if !ok {
|
|
continue
|
|
}
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return ""
|
|
}
|
|
if filepath.IsAbs(value) {
|
|
return filepath.Clean(value)
|
|
}
|
|
cleanRel := filepath.Clean(value)
|
|
if cleanRel == "." || cleanRel == ".." || strings.HasPrefix(cleanRel, ".."+string(filepath.Separator)) {
|
|
return ""
|
|
}
|
|
return filepath.Join(userCodexHome, cleanRel)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func removeCodexModelsCacheFile(path string) error {
|
|
info, err := os.Lstat(path)
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("inspect codex models cache %s: %w", path, err)
|
|
}
|
|
if info.IsDir() {
|
|
return fmt.Errorf("codex models cache is a directory: %s", path)
|
|
}
|
|
if err := os.Remove(path); err != nil {
|
|
return fmt.Errorf("remove stale codex models cache %s: %w", path, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeCodexModelsCacheAuthority(path, authority string) error {
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
|
|
return fmt.Errorf("create codex models cache authority directory: %w", err)
|
|
}
|
|
temporary, err := os.CreateTemp(filepath.Dir(path), ".models-cache-authority-*.tmp")
|
|
if err != nil {
|
|
return fmt.Errorf("create codex models cache authority: %w", err)
|
|
}
|
|
temporaryPath := temporary.Name()
|
|
defer func() {
|
|
_ = temporary.Close()
|
|
_ = os.Remove(temporaryPath)
|
|
}()
|
|
if err := temporary.Chmod(0o600); err != nil {
|
|
return fmt.Errorf("chmod codex models cache authority: %w", err)
|
|
}
|
|
if _, err := temporary.WriteString(authority + "\n"); err != nil {
|
|
return fmt.Errorf("write codex models cache authority: %w", err)
|
|
}
|
|
if err := temporary.Close(); err != nil {
|
|
return fmt.Errorf("close codex models cache authority: %w", err)
|
|
}
|
|
if err := os.Rename(temporaryPath, path); err != nil {
|
|
return fmt.Errorf("commit codex models cache authority: %w", err)
|
|
}
|
|
return nil
|
|
}
|