Skip to content

Commit d5154c3

Browse files
Merge pull request Light-Heart-Labs#987 from Light-Heart-Labs/codex/windows-installer-llm-endpoint-fixes
[codex] Fix Windows local LLM endpoint resolution
2 parents ef0780e + aac1fda commit d5154c3

10 files changed

Lines changed: 619 additions & 101 deletions

dream-server/installers/windows/dream.ps1

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ $LibDir = Join-Path $ScriptDir "lib"
3939
. (Join-Path $LibDir "ui.ps1")
4040
. (Join-Path $LibDir "compose-diagnostics.ps1")
4141
. (Join-Path $LibDir "detection.ps1")
42+
. (Join-Path $LibDir "llm-endpoint.ps1")
4243
. (Join-Path $LibDir "install-report.ps1")
4344

4445
# ── Resolve install directory ──
@@ -124,20 +125,7 @@ function Read-DreamEnv {
124125
.SYNOPSIS
125126
Safely load .env file into a hashtable (no eval, no injection).
126127
#>
127-
$envFile = Join-Path $InstallDir ".env"
128-
$result = @{}
129-
if (-not (Test-Path $envFile)) { return $result }
130-
131-
Get-Content $envFile | ForEach-Object {
132-
$line = $_.Trim()
133-
if ($line -match "^#" -or $line -eq "") { return }
134-
if ($line -match "^([A-Za-z_][A-Za-z0-9_]*)=(.*)$") {
135-
$key = $Matches[1]
136-
$val = $Matches[2].Trim('"').Trim("'")
137-
$result[$key] = $val
138-
}
139-
}
140-
return $result
128+
return Get-WindowsDreamEnvMap -InstallDir $InstallDir
141129
}
142130

143131
function Set-DreamEnvValue {
@@ -464,13 +452,9 @@ function Invoke-Status {
464452
Write-Host " Health Checks" -ForegroundColor Cyan
465453
Write-Host (" " + ("-" * 40)) -ForegroundColor DarkGray
466454

467-
$llmHealthUrl = $(if ((Get-NativeInferenceBackend) -eq "lemonade") {
468-
$script:LEMONADE_HEALTH_URL
469-
} else {
470-
"http://localhost:8080/health"
471-
})
455+
$llmEndpoint = Get-WindowsLocalLlmEndpoint -InstallDir $InstallDir -NativeBackend (Get-NativeInferenceBackend)
472456
$endpoints = @(
473-
@{ Name = "LLM API"; Url = $llmHealthUrl }
457+
@{ Name = "LLM API"; Url = $llmEndpoint.HealthUrl }
474458
@{ Name = "Chat UI"; Url = "http://localhost:3000" }
475459
@{ Name = "Dashboard"; Url = "http://localhost:3001" }
476460
)
@@ -702,9 +686,9 @@ function Invoke-Chat {
702686
)
703687
} | ConvertTo-Json -Depth 3
704688

705-
$chatBasePath = $(if ((Get-NativeInferenceBackend) -eq "lemonade") { "/api/v1" } else { "/v1" })
689+
$llmEndpoint = Get-WindowsLocalLlmEndpoint -InstallDir $InstallDir -NativeBackend (Get-NativeInferenceBackend)
706690
try {
707-
$resp = Invoke-RestMethod -Uri "http://localhost:8080${chatBasePath}/chat/completions" `
691+
$resp = Invoke-RestMethod -Uri $llmEndpoint.ChatCompletionsUrl `
708692
-Method POST -Body $body -ContentType "application/json" -TimeoutSec 120
709693

710694
if ($resp.choices -and $resp.choices[0].message) {

dream-server/installers/windows/install-windows.ps1

Lines changed: 104 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ $LibDir = Join-Path $ScriptDir "lib"
6969
. (Join-Path $LibDir "tier-map.ps1")
7070
. (Join-Path $LibDir "detection.ps1")
7171
. (Join-Path $LibDir "env-generator.ps1")
72+
. (Join-Path $LibDir "llm-endpoint.ps1")
73+
. (Join-Path $LibDir "opencode-config.ps1")
7274

7375
# ── Phase context variables ───────────────────────────────────────────────────
7476
# These are plain (non-$script:) variables set in the orchestrator scope.
@@ -93,6 +95,65 @@ $installDir = $script:DS_INSTALL_DIR
9395
$sourceRoot = $SourceRoot
9496

9597
# ── Phase dispatcher ──────────────────────────────────────────────────────────
98+
function Get-UsableWindowsBash {
99+
<#
100+
.SYNOPSIS
101+
Prefer a Git Bash-style shell for bootstrap-upgrade.sh on Windows.
102+
#>
103+
param(
104+
[string]$InstallPath = $installDir
105+
)
106+
107+
$probeCommand = "command -v bash >/dev/null 2>&1"
108+
if ($InstallPath -match "^([A-Za-z]):") {
109+
$probeCommand += " && test -d /$($Matches[1].ToLower())"
110+
}
111+
112+
$candidates = New-Object 'System.Collections.Generic.List[string]'
113+
114+
$gitCmd = Get-Command git -ErrorAction SilentlyContinue
115+
if ($gitCmd -and $gitCmd.Source) {
116+
$gitRoot = Split-Path (Split-Path $gitCmd.Source -Parent) -Parent
117+
$gitBash = Join-Path $gitRoot "bin\bash.exe"
118+
if (Test-Path $gitBash) {
119+
[void]$candidates.Add($gitBash)
120+
}
121+
}
122+
123+
$programFilesBash = Join-Path $env:ProgramFiles "Git\bin\bash.exe"
124+
if (Test-Path $programFilesBash) {
125+
[void]$candidates.Add($programFilesBash)
126+
}
127+
128+
if (${env:ProgramFiles(x86)} -and $env:ProgramFiles -ne ${env:ProgramFiles(x86)}) {
129+
$programFilesX86Bash = Join-Path ${env:ProgramFiles(x86)} "Git\bin\bash.exe"
130+
if (Test-Path $programFilesX86Bash) {
131+
[void]$candidates.Add($programFilesX86Bash)
132+
}
133+
}
134+
135+
$bashCmd = Get-Command bash -ErrorAction SilentlyContinue
136+
if ($bashCmd -and $bashCmd.Source) {
137+
[void]$candidates.Add($bashCmd.Source)
138+
}
139+
140+
$seen = @{}
141+
foreach ($candidate in $candidates) {
142+
if ([string]::IsNullOrWhiteSpace($candidate)) { continue }
143+
if ($seen.ContainsKey($candidate)) { continue }
144+
$seen[$candidate] = $true
145+
146+
try {
147+
& $candidate -lc $probeCommand *> $null
148+
if ($LASTEXITCODE -eq 0) {
149+
return $candidate
150+
}
151+
} catch { }
152+
}
153+
154+
return $null
155+
}
156+
96157
$PhasesDir = Join-Path $ScriptDir "phases"
97158

98159
Write-DreamBanner
@@ -575,13 +636,29 @@ exec bash "$bashScript" "$bashInstallDir" "$($fullTierConfig.GgufFile)" "$($full
575636
"@
576637
[System.IO.File]::WriteAllText($wrapperScript, $wrapperContent.Replace("`r`n", "`n"), (New-Object System.Text.UTF8Encoding($false)))
577638

578-
Start-Process -FilePath "bash" -ArgumentList $wrapperScript `
579-
-WindowStyle Hidden `
580-
-RedirectStandardOutput $upgradeLog `
581-
-RedirectStandardError $upgradeErrLog
582-
583-
Write-AI "Full model ($($fullTierConfig.LlmModel)) downloading in background."
584-
Write-AI "Check progress: Get-Content '$upgradeLog' -Tail 10"
639+
$bashPath = Get-UsableWindowsBash -InstallPath $installDir
640+
if ($bashPath) {
641+
$upgradeProc = Start-Process -FilePath $bashPath -ArgumentList $wrapperScript `
642+
-WindowStyle Hidden `
643+
-RedirectStandardOutput $upgradeLog `
644+
-RedirectStandardError $upgradeErrLog `
645+
-PassThru
646+
647+
Start-Sleep -Seconds 2
648+
$upgradeProc.Refresh()
649+
650+
if ($upgradeProc.HasExited) {
651+
Write-AIWarn "Background full-model download exited immediately (exit code: $($upgradeProc.ExitCode))."
652+
Write-AI " Retry manually with: & '$bashPath' '$wrapperScript'"
653+
Write-AI " Error log: $upgradeErrLog"
654+
} else {
655+
Write-AI "Full model ($($fullTierConfig.LlmModel)) downloading in background."
656+
Write-AI "Check progress: Get-Content '$upgradeLog' -Tail 10"
657+
}
658+
} else {
659+
Write-AIWarn "No Git Bash-compatible shell was found for bootstrap-upgrade.sh."
660+
Write-AI " Install Git for Windows or run the upgrade script manually after adding bash.exe to PATH."
661+
}
585662
} else {
586663
Write-AIWarn "bootstrap-upgrade.sh not found at $upgradeScript"
587664
Write-AIWarn "Download the full model manually or re-run the installer."
@@ -608,22 +685,31 @@ if ($dryRun) {
608685
}
609686

610687
# ── Service health checks ─────────────────────────────────────────────────────
611-
# Use Lemonade health endpoint when AMD + Lemonade is active, llama-server otherwise
612-
$llmHealthUrl = $(if ($useLemonade) {
613-
$script:LEMONADE_HEALTH_URL
614-
} else {
615-
"http://localhost:8080/health"
616-
})
617-
$llmHealthName = $(if ($useLemonade) { "LLM (Lemonade)" } else { "LLM (llama-server)" })
688+
$opencodeSync = Sync-WindowsOpenCodeConfigFromEnv -InstallDir $installDir `
689+
-GpuBackend $gpuInfo.Backend -UseLemonade:$useLemonade -CloudMode:$cloudMode `
690+
-DefaultModelId $tierConfig.GgufFile -DefaultModelName $tierConfig.LlmModel `
691+
-DefaultContextLimit ([int]$tierConfig.MaxContext) -SkipIfUnavailable
692+
switch ($opencodeSync.Status) {
693+
"created" {
694+
Write-AISuccess "OpenCode config synced to active model (model: $($opencodeSync.ModelName))"
695+
}
696+
"updated" {
697+
Write-AISuccess "OpenCode config synced to active model (model: $($opencodeSync.ModelName))"
698+
}
699+
"regenerated" {
700+
Write-AISuccess "OpenCode config regenerated for active model (model: $($opencodeSync.ModelName))"
701+
}
702+
}
703+
704+
$llmEndpoint = Get-WindowsLocalLlmEndpoint -InstallDir $installDir `
705+
-EnvMap (Get-WindowsDreamEnvMap -InstallDir $installDir) `
706+
-UseLemonade:$useLemonade -GpuBackend $gpuInfo.Backend -CloudMode:$cloudMode
618707
$healthChecks = @(
619-
@{ Name = $llmHealthName; Url = $llmHealthUrl }
708+
@{ Name = $llmEndpoint.Name; Url = $llmEndpoint.HealthUrl }
620709
@{ Name = "Chat UI (Open WebUI)"; Url = "http://localhost:3000" }
621710
)
622711
if ($enableVoice) { $healthChecks += @{ Name = "Whisper (STT)"; Url = "http://localhost:9000/health" } }
623712
if ($enableWorkflows) { $healthChecks += @{ Name = "n8n (Workflows)"; Url = "http://localhost:5678/healthz" } }
624-
if (Test-Path $script:OPENCODE_EXE) {
625-
$healthChecks += @{ Name = "OpenCode (IDE)"; Url = "http://localhost:$($script:OPENCODE_PORT)/" }
626-
}
627713

628714
Write-AI "Running health checks..."
629715
$maxAttempts = 60; $allHealthy = $true

dream-server/installers/windows/lib/install-report.ps1

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# ============================================================================
44
# Part of: installers/windows/lib/
55
# Purpose: Build a shareable support bundle with compose + system diagnostics.
6-
# Requires: ui.ps1 and detection.ps1 sourced first.
6+
# Requires: ui.ps1, detection.ps1, and llm-endpoint.ps1 sourced first.
77
# ============================================================================
88

99
function Invoke-OptionalCommand {
@@ -66,6 +66,7 @@ function Test-HttpEndpoint {
6666

6767
function New-DreamInstallReport {
6868
param(
69+
[string]$InstallDir = $script:DS_INSTALL_DIR,
6970
[Parameter(Mandatory = $true)]
7071
[AllowEmptyCollection()]
7172
[string[]]$ComposeFlags
@@ -80,6 +81,7 @@ function New-DreamInstallReport {
8081
if (Get-Command Get-NativeInferenceBackend -ErrorAction SilentlyContinue) {
8182
$nativeBackend = Get-NativeInferenceBackend
8283
}
84+
$llmEndpoint = Get-WindowsLocalLlmEndpoint -InstallDir $InstallDir -GpuBackend $gpu.Backend -NativeBackend $nativeBackend
8385

8486
$composeConfigArgs = @("compose") + $ComposeFlags + @("config")
8587
$composePsArgs = @("compose") + $ComposeFlags + @("ps", "-a")
@@ -108,7 +110,7 @@ function New-DreamInstallReport {
108110
compose_ps = Invoke-OptionalCommand -Command "docker" -CommandArgs $composePsArgs -MaxLines 80
109111
}
110112
health = [ordered]@{
111-
llm_api = Test-HttpEndpoint -Url "http://localhost:8080/health"
113+
llm_api = Test-HttpEndpoint -Url $llmEndpoint.HealthUrl
112114
open_webui = Test-HttpEndpoint -Url "http://localhost:3000"
113115
dashboard = Test-HttpEndpoint -Url "http://localhost:3001"
114116
dashboard_api = Test-HttpEndpoint -Url "http://localhost:3002/health"
@@ -133,7 +135,7 @@ function Write-DreamInstallReport {
133135
$jsonPath = Join-Path $artifactsDir "report.json"
134136
$txtPath = Join-Path $artifactsDir "report.txt"
135137

136-
$report = New-DreamInstallReport -ComposeFlags $ComposeFlags
138+
$report = New-DreamInstallReport -InstallDir $InstallDir -ComposeFlags $ComposeFlags
137139
($report | ConvertTo-Json -Depth 8) | Set-Content -Path $jsonPath -Encoding UTF8
138140

139141
$lines = @()

0 commit comments

Comments
 (0)