Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cmd/aima/infra.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,10 +300,19 @@ func buildNativeRuntime(dataDir string, engineAssets []knowledge.EngineAsset) ru
platform := goruntime.GOOS + "-" + goruntime.GOARCH
distDir := filepath.Join(dataDir, "dist", platform)
bm := engine.NewBinaryManager(distDir)
// Pre-installed engine dirs (AIMA_ENGINE_DIR) — the SAME source the engine
// scanner reads — so a scanned native engine binary is also launchable.
var engineDirs []string
for _, dir := range filepath.SplitList(os.Getenv("AIMA_ENGINE_DIR")) {
if dir = strings.TrimSpace(dir); dir != "" {
engineDirs = append(engineDirs, dir)
}
}
return runtime.NewNativeRuntime(
filepath.Join(dataDir, "logs"),
distDir,
filepath.Join(dataDir, "deployments"),
runtime.WithEngineDirs(engineDirs),
runtime.WithBinaryResolver(func(ctx context.Context, src *engine.BinarySource) (string, error) {
if !deployAutoPullAllowed(ctx) {
name := "engine binary"
Expand Down
63 changes: 47 additions & 16 deletions internal/runtime/native.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ type BinaryResolveFunc func(ctx context.Context, source *engine.BinarySource) (s
// NativeRuntime manages inference engines as direct OS processes.
type NativeRuntime struct {
logDir string
distDir string // e.g. ~/.aima/dist/windows-amd64/
deployDir string // e.g. ~/.aima/deployments/ — persisted deployment metadata
distDir string // e.g. ~/.aima/dist/windows-amd64/
engineDirs []string // pre-installed engine dirs (AIMA_ENGINE_DIR) — same source the engine scanner uses
deployDir string // e.g. ~/.aima/deployments/ — persisted deployment metadata
resolveBinary BinaryResolveFunc
engineAssets []knowledge.EngineAsset
processes map[string]*nativeProcess
Expand Down Expand Up @@ -101,6 +102,16 @@ func WithNativeEngineAssets(assets []knowledge.EngineAsset) NativeOption {
}
}

// WithEngineDirs registers pre-installed engine directories (from AIMA_ENGINE_DIR).
// The native binary resolver checks these — the SAME dirs the engine scanner uses —
// so a bare engine name (e.g. "llama-server") that was discovered by scanning is also
// launchable, even when it is neither in dist nor on PATH. Keeps "scanned ⇒ launchable".
func WithEngineDirs(dirs []string) NativeOption {
return func(r *NativeRuntime) {
r.engineDirs = dirs
}
}

func (r *NativeRuntime) Name() string { return "native" }

func (r *NativeRuntime) Deploy(ctx context.Context, req *DeployRequest) error {
Expand Down Expand Up @@ -177,17 +188,19 @@ func (r *NativeRuntime) Deploy(ctx context.Context, req *DeployRequest) error {
return fmt.Errorf("create log file: %w", err)
}

// Resolve binary: dist/ first, then auto-download if source is available
if r.distDir != "" {
if resolved := r.findInDist(command[0]); resolved != "" {
// Resolve binary: dist/ first, then the pre-installed engine dirs
// (AIMA_ENGINE_DIR — the SAME dirs the engine scanner uses, so anything scanning
// found is launchable), then auto-download if a source is available.
if resolved := r.findInDist(command[0]); resolved != "" {
command[0] = resolved
} else if resolved := r.findInEngineDirs(command[0]); resolved != "" {
command[0] = resolved
} else if r.resolveBinary != nil && req.BinarySource != nil {
slog.Info("binary not in dist or engine dirs, attempting auto-download", "binary", command[0])
if resolved, err := r.resolveBinary(ctx, req.BinarySource); err == nil {
command[0] = resolved
} else if r.resolveBinary != nil && req.BinarySource != nil {
slog.Info("binary not in dist, attempting auto-download", "binary", command[0])
if resolved, err := r.resolveBinary(ctx, req.BinarySource); err == nil {
command[0] = resolved
} else {
slog.Warn("auto-download failed, will try PATH", "binary", command[0], "error", err)
}
} else {
slog.Warn("auto-download failed, will try PATH", "binary", command[0], "error", err)
}
}

Expand Down Expand Up @@ -852,14 +865,32 @@ func (r *NativeRuntime) loadAllMeta() []*deploymentMeta {
// findInDist checks for a binary in the dist directory.
// On Windows, also tries with .exe suffix.
func (r *NativeRuntime) findInDist(name string) string {
return findBinaryIn([]string{r.distDir}, name)
}

// findInEngineDirs resolves a bare engine binary name against the pre-installed
// engine dirs (AIMA_ENGINE_DIR). This mirrors how the engine scanner discovers
// binaries, so an engine that scanning found is also the one that gets launched.
func (r *NativeRuntime) findInEngineDirs(name string) string {
return findBinaryIn(r.engineDirs, name)
}

// findBinaryIn returns the absolute path of name (with .exe on Windows) in the
// first of dirs that contains it.
func findBinaryIn(dirs []string, name string) string {
candidates := []string{name}
if goruntime.GOOS == "windows" && !strings.HasSuffix(name, ".exe") {
candidates = append(candidates, name+".exe")
}
for _, c := range candidates {
p := filepath.Join(r.distDir, c)
if _, err := os.Stat(p); err == nil {
return p
for _, dir := range dirs {
if dir == "" {
continue
}
for _, c := range candidates {
p := filepath.Join(dir, c)
if _, err := os.Stat(p); err == nil {
return p
}
}
}
return ""
Expand Down
28 changes: 28 additions & 0 deletions internal/runtime/native_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1132,3 +1132,31 @@ func TestDeployAppendsCustomPortFlags(t *testing.T) {
t.Fatalf("command = %q, should not contain synthesized --port flag", argStr)
}
}

func TestFindInEngineDirsResolvesScannedBinary(t *testing.T) {
// A native engine binary discovered by scanning AIMA_ENGINE_DIR must also be
// launchable: the native runtime resolves the bare binary name against the same
// engine dirs, so "scanned ⇒ launchable" holds even when it is not in dist/PATH.
dir := t.TempDir()
fileName := "llama-server"
if runtime.GOOS == "windows" {
fileName += ".exe"
}
want := filepath.Join(dir, fileName)
if err := os.WriteFile(want, []byte("stub"), 0o755); err != nil {
t.Fatalf("write stub: %v", err)
}

// Empty dir entries are skipped; the bare name resolves to the absolute path.
r := &NativeRuntime{engineDirs: []string{"", dir}}
if got := r.findInEngineDirs("llama-server"); got != want {
t.Errorf("findInEngineDirs = %q, want %q", got, want)
}
// Missing binary and no configured dirs both resolve to empty.
if got := r.findInEngineDirs("nope"); got != "" {
t.Errorf("missing binary: got %q, want empty", got)
}
if got := (&NativeRuntime{}).findInEngineDirs("llama-server"); got != "" {
t.Errorf("no engine dirs: got %q, want empty", got)
}
}
Loading