- Added locale support in various components, including the Assistant's execution context and agent tools, to improve internationalization. - Updated the `pickNodeByFilter` function to include a fallback mechanism for host_exec nodes when no container nodes are available. - Introduced a new `agent_reference` tool for downloading agent source code for read-only study, alongside updates to the `agent_download` tool to restrict downloads to the 'smith' namespace. - Enhanced documentation and tests to reflect the new functionalities and ensure robust locale extraction and handling.
129 lines
2.8 KiB
Go
129 lines
2.8 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
_ "embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/yaoapp/gou/process"
|
|
taiworkspace "github.com/yaoapp/yao/tai/workspace"
|
|
ws "github.com/yaoapp/yao/workspace"
|
|
"google.golang.org/grpc/metadata"
|
|
)
|
|
|
|
//go:embed list_schema.json
|
|
var ListSchemaJSON []byte
|
|
|
|
//go:embed download_schema.json
|
|
var DownloadSchemaJSON []byte
|
|
|
|
//go:embed deploy_schema.json
|
|
var DeploySchemaJSON []byte
|
|
|
|
//go:embed reference_schema.json
|
|
var ReferenceSchemaJSON []byte
|
|
|
|
//go:embed connectors_schema.json
|
|
var ConnectorsSchemaJSON []byte
|
|
|
|
const allowedDeployNamespace = "smith"
|
|
|
|
type agentInfo struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Capabilities string `json:"capabilities,omitempty"`
|
|
}
|
|
|
|
type packageDSL struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Capabilities string `json:"capabilities"`
|
|
}
|
|
|
|
func resolveWorkspaceFS(proc *process.Process) (taiworkspace.FS, error) {
|
|
workspaceID := extractWorkspaceID(proc)
|
|
if workspaceID == "" {
|
|
return nil, fmt.Errorf("workspace_id not available (container must set CTX_WORKSPACE_ID)")
|
|
}
|
|
|
|
fs, err := ws.M().FS(context.Background(), workspaceID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("workspace %s: %w", workspaceID, err)
|
|
}
|
|
return fs, nil
|
|
}
|
|
|
|
func extractWorkspaceID(proc *process.Process) string {
|
|
if proc.Context == nil {
|
|
return ""
|
|
}
|
|
md, ok := metadata.FromIncomingContext(proc.Context)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
ids := md.Get("x-workspace-id")
|
|
if len(ids) > 0 && ids[0] != "" {
|
|
return ids[0]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func extractLocale(proc *process.Process) string {
|
|
if proc.Context == nil {
|
|
return "en-us"
|
|
}
|
|
md, ok := metadata.FromIncomingContext(proc.Context)
|
|
if !ok {
|
|
return "en-us"
|
|
}
|
|
vals := md.Get("x-locale")
|
|
if len(vals) > 0 && vals[0] != "" {
|
|
return strings.ToLower(vals[0])
|
|
}
|
|
return "en-us"
|
|
}
|
|
|
|
func validateID(id string) error {
|
|
if strings.Contains(id, "..") {
|
|
return fmt.Errorf("invalid id: path traversal not allowed")
|
|
}
|
|
if strings.ContainsAny(id, "/\\") {
|
|
return fmt.Errorf("invalid id: use dot notation (e.g. 'yao.slides')")
|
|
}
|
|
parts := strings.SplitN(id, ".", 2)
|
|
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
|
return fmt.Errorf("invalid id format: expected 'namespace.name' (e.g. 'yao.slides')")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func idToPath(id string) string {
|
|
return strings.Replace(id, ".", "/", 1)
|
|
}
|
|
|
|
func settingStr(setting map[string]interface{}, key string) string {
|
|
if v, ok := setting[key]; ok {
|
|
if s, ok := v.(string); ok {
|
|
return s
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func sanitizeCapabilities(caps interface{}) interface{} {
|
|
data, err := json.Marshal(caps)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
var m map[string]interface{}
|
|
if err := json.Unmarshal(data, &m); err != nil {
|
|
return caps
|
|
}
|
|
delete(m, "key")
|
|
delete(m, "secret")
|
|
delete(m, "token")
|
|
return m
|
|
}
|