203 lines
8.0 KiB
Go
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)
|
||
|
|
}
|