Files
Codex Local bf1233d5df
ci / verify (push) Canceled after 0s
feat: expand companion platform, device controls, and memoir workflows
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
2026-10-08 10:06:44 +08:00

203 lines
8.0 KiB
Go

package config
import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
)
var volcAccountNames = strings.Fields(`VOLC_ACCESS_KEY_ID VOLC_SECRET_ACCESS_KEY VOLC_SESSION_TOKEN VOLC_RTC_BASE_URL VOLC_RTC_REGION VOLC_RTC_API_VERSION RTC_APP_ID RTC_APP_KEY RTC_TOKEN_ISSUER VOLC_HARDWARE_INSTANCE_ID VOLC_HARDWARE_PRODUCT_KEY VOLC_HARDWARE_PRODUCT_SECRET VOLC_HARDWARE_AGENT_BOT_ID VOLC_HARDWARE_PROVISIONING_ENABLED VOLC_HARDWARE_AGENT_TRANSPORT VOLC_HARDWARE_AGENT_MINIMUM_SDK_VERSION VOLC_HARDWARE_REGISTER_URL VOLC_HARDWARE_DEVICE_NAME_PREFIX MIGRATION_HARDWARE_RTC_APP_ID MIGRATION_HARDWARE_LICENSE_READY S2S_APP_ID S2S_ACCESS_TOKEN S2S_MODEL S2S_SPEAKER VIKING_MEMORY_RESOURCE_ID VIKING_MEMORY_API_KEY VIKING_MEMORY_COLLECTION_NAME VIKING_MEMORY_EVENT_TYPE VIKING_MEMORY_PROFILE_TYPE TOS_RECORDING_ACCOUNT_ID TOS_RECORDING_BUCKET TOS_RECORDING_REGION TOS_RECORDING_PLAYBACK_ENDPOINT ARK_API_KEY ARK_BASE_URL ARK_CONTENT_MODEL ARK_EMBEDDING_MODEL CONTENT_GENERATOR EMBEDDING_PROVIDER VOLC_SUBTITLE_CALLBACK_URL VOLC_SUBTITLE_PRIMARY MIGRATION_NOTES`)
// ReadVolcAccountFile reads a literal worksheet, never shell commands or expansion.
// Errors identify the field/line only; credentials must never enter logs.
func ReadVolcAccountFile(path string) (map[string]string, error) {
if !filepath.IsAbs(path) {
return nil, errors.New("VOLC_ACCOUNT_CONFIG_FILE must be absolute")
}
raw, err := os.ReadFile(path)
if err != nil {
return nil, errors.New("Volc account configuration unavailable")
}
if len(raw) > 65536 {
return nil, errors.New("Volc account configuration too large")
}
allowed := map[string]bool{}
for _, name := range volcAccountNames {
allowed[name] = true
}
values := map[string]string{}
s := bufio.NewScanner(bytes.NewReader(bytes.TrimPrefix(raw, []byte{0xef, 0xbb, 0xbf})))
line := 0
for s.Scan() {
line++
text := strings.TrimSpace(s.Text())
if text == "" || strings.HasPrefix(text, "#") {
continue
}
name, value, ok := strings.Cut(text, "=")
name = strings.TrimSpace(name)
if !ok || !allowed[name] {
return nil, fmt.Errorf("Volc account configuration invalid field at line %d", line)
}
if _, found := values[name]; found {
return nil, fmt.Errorf("Volc account configuration duplicate %s", name)
}
value = strings.TrimSpace(value)
if len(value) >= 2 && ((value[0] == '\'' && value[len(value)-1] == '\'') || (value[0] == '"' && value[len(value)-1] == '"')) {
value = value[1 : len(value)-1]
}
if strings.ContainsAny(value, "\x00\r\n") {
return nil, fmt.Errorf("Volc account configuration invalid %s", name)
}
values[name] = value
}
if s.Err() != nil {
return nil, errors.New("Volc account configuration unreadable")
}
for _, name := range volcAccountNames {
if strings.HasPrefix(name, "MIGRATION_") {
continue
}
if _, found := values[name]; !found {
return nil, fmt.Errorf("Volc account configuration missing %s", name)
}
}
return values, nil
}
func loadVolcAccount() (map[string]string, error) {
path := strings.TrimSpace(os.Getenv("VOLC_ACCOUNT_CONFIG_FILE"))
if path == "" {
return nil, nil
}
return ReadVolcAccountFile(path)
}
func loadSecretValuesWithAccount(account map[string]string, names ...string) (map[string]string, error) {
values := make(map[string]string, len(names))
for _, name := range names {
if value, found := account[name]; found {
values[name] = value
continue
}
value, err := secretEnv(name)
if err != nil {
return nil, err
}
values[name] = value
}
return values, nil
}
func (c *Config) applyVolcAccount(values map[string]string) error {
c.TOSRecordingAccountID = strings.TrimSpace(os.Getenv("TOS_RECORDING_ACCOUNT_ID"))
c.TOSRecordingBucket = strings.TrimSpace(os.Getenv("TOS_RECORDING_BUCKET"))
c.TOSRecordingRegion = strings.TrimSpace(os.Getenv("TOS_RECORDING_REGION"))
c.TOSRecordingPlaybackEndpoint = strings.TrimSpace(os.Getenv("TOS_RECORDING_PLAYBACK_ENDPOINT"))
if values == nil {
return nil
}
c.volcAccount = values
fields := map[string]*string{
"VOLC_ACCESS_KEY_ID": &c.VolcAccessKeyID, "VOLC_SECRET_ACCESS_KEY": &c.VolcSecretAccessKey, "VOLC_SESSION_TOKEN": &c.VolcSessionToken,
"RTC_APP_ID": &c.RTCAppID, "RTC_APP_KEY": &c.RTCAppKey, "RTC_TOKEN_ISSUER": &c.RTCTokenIssuer,
"VOLC_RTC_BASE_URL": &c.VolcRTCBaseURL, "VOLC_RTC_REGION": &c.VolcRTCRegion, "VOLC_RTC_API_VERSION": &c.VolcRTCAPIVersion,
"VOLC_HARDWARE_INSTANCE_ID": &c.VolcHardwareInstanceID, "VOLC_HARDWARE_PRODUCT_KEY": &c.VolcHardwareProductKey, "VOLC_HARDWARE_PRODUCT_SECRET": &c.VolcHardwareProductSecret,
"VOLC_HARDWARE_AGENT_BOT_ID": &c.VolcHardwareAgentBotID, "VOLC_HARDWARE_AGENT_TRANSPORT": &c.VolcHardwareTransport, "VOLC_HARDWARE_AGENT_MINIMUM_SDK_VERSION": &c.VolcHardwareMinimumSDKVersion,
"VOLC_HARDWARE_REGISTER_URL": &c.VolcHardwareRegisterURL, "VOLC_HARDWARE_DEVICE_NAME_PREFIX": &c.VolcHardwareDeviceNamePrefix,
"VIKING_MEMORY_RESOURCE_ID": &c.VikingResourceID, "VIKING_MEMORY_API_KEY": &c.VikingAPIKey, "VIKING_MEMORY_COLLECTION_NAME": &c.VikingCollectionName,
"VIKING_MEMORY_EVENT_TYPE": &c.VikingEventType, "VIKING_MEMORY_PROFILE_TYPE": &c.VikingProfileType,
"TOS_RECORDING_ACCOUNT_ID": &c.TOSRecordingAccountID, "TOS_RECORDING_BUCKET": &c.TOSRecordingBucket, "TOS_RECORDING_REGION": &c.TOSRecordingRegion, "TOS_RECORDING_PLAYBACK_ENDPOINT": &c.TOSRecordingPlaybackEndpoint,
"ARK_API_KEY": &c.ArkAPIKey, "ARK_BASE_URL": &c.ArkBaseURL, "ARK_CONTENT_MODEL": &c.ArkContentModel, "ARK_EMBEDDING_MODEL": &c.ArkEmbeddingModel,
"CONTENT_GENERATOR": &c.ContentGenerator, "EMBEDDING_PROVIDER": &c.EmbeddingProvider, "VOLC_SUBTITLE_CALLBACK_URL": &c.SubtitleCallbackURL,
}
for name, field := range fields {
*field = values[name]
}
for name, field := range map[string]*bool{"VOLC_HARDWARE_PROVISIONING_ENABLED": &c.VolcHardwareProvisioningEnabled, "VOLC_SUBTITLE_PRIMARY": &c.SubtitlePrimary} {
parsed, err := strconv.ParseBool(values[name])
if err != nil {
return fmt.Errorf("Volc account configuration invalid boolean %s", name)
}
*field = parsed
}
for _, name := range []string{"VOLC_ACCESS_KEY_ID", "VOLC_SECRET_ACCESS_KEY", "RTC_APP_ID", "RTC_APP_KEY", "S2S_APP_ID", "S2S_ACCESS_TOKEN", "S2S_MODEL", "S2S_SPEAKER"} {
if values[name] == "" {
return fmt.Errorf("Volc account configuration requires %s", name)
}
}
return nil
}
// ApplyVolcVoiceTemplate supplies credentials from the account file at startup.
// Templates retain prompts and behaviour but are never an alternative key source.
func (c Config) ApplyVolcVoiceTemplate(raw []byte) ([]byte, error) {
if c.volcAccount == nil {
return raw, nil
}
var root map[string]any
if json.Unmarshal(raw, &root) != nil {
return nil, errors.New("invalid Volc voice template")
}
items := []map[string]any{root}
if bundle, ok := root["templates"].(map[string]any); ok {
items = nil
for _, entry := range bundle {
item, ok := entry.(map[string]any)
if !ok {
return nil, errors.New("invalid Volc voice template entry")
}
items = append(items, item)
}
}
updated := 0
for _, item := range items {
conf, _ := item["Config"].(map[string]any)
s2s, _ := conf["S2SConfig"].(map[string]any)
if s2s == nil {
continue
}
pp, _ := s2s["ProviderParams"].(map[string]any)
if pp == nil {
pp = map[string]any{}
s2s["ProviderParams"] = pp
}
app, _ := pp["app"].(map[string]any)
if app == nil {
app = map[string]any{}
pp["app"] = app
}
app["appid"] = c.volcAccount["S2S_APP_ID"]
app["token"] = c.volcAccount["S2S_ACCESS_TOKEN"]
dialog, _ := pp["dialog"].(map[string]any)
if dialog == nil {
dialog = map[string]any{}
pp["dialog"] = dialog
}
extra, _ := dialog["extra"].(map[string]any)
if extra == nil {
extra = map[string]any{}
dialog["extra"] = extra
}
extra["model"] = c.volcAccount["S2S_MODEL"]
tts, _ := pp["tts"].(map[string]any)
if tts == nil {
tts = map[string]any{}
pp["tts"] = tts
}
tts["speaker"] = c.volcAccount["S2S_SPEAKER"]
conf["MemoryConfig"] = map[string]any{"Enable": false}
updated++
}
if updated == 0 {
return nil, errors.New("Volc account requires an S2S voice template")
}
return json.Marshal(root)
}