198 lines
7.3 KiB
PowerShell
198 lines
7.3 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Manage the langgraph dev server on Windows.
|
|
.DESCRIPTION
|
|
Start, stop, or check the status of the langgraph dev server.
|
|
The server starts from the skill root (where langgraph.json lives) so the
|
|
graph module is discovered correctly.
|
|
.PARAMETER Command
|
|
start | stop | status
|
|
.EXAMPLE
|
|
manage_dev_server.ps1 start # idempotent — safe to call if already running
|
|
manage_dev_server.ps1 stop # call once workflow returns {"status":"done"}
|
|
manage_dev_server.ps1 status # print running/stopped + Studio URL
|
|
#>
|
|
param([string]$Command = "status")
|
|
|
|
$SkillDir = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
|
$PidFile = Join-Path $env:TEMP "langgraph_dev_server.pid"
|
|
$StdoutLog = Join-Path $env:TEMP "langgraph_dev_server.stdout.log"
|
|
$StderrLog = Join-Path $env:TEMP "langgraph_dev_server.stderr.log"
|
|
$ApiUrl = "http://127.0.0.1:2024"
|
|
$MaxWait = 45 # seconds to wait for the server to become ready
|
|
|
|
# --- helpers -----------------------------------------------------------
|
|
|
|
function Test-HttpOk([string]$Url) {
|
|
try {
|
|
$r = Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 3 -ErrorAction Stop
|
|
return ($r.StatusCode -ge 200 -and $r.StatusCode -lt 300)
|
|
} catch {
|
|
return $false
|
|
}
|
|
}
|
|
|
|
function Get-StoredPid {
|
|
if (-not (Test-Path $PidFile)) { return 0 }
|
|
$raw = (Get-Content $PidFile -Raw -ErrorAction SilentlyContinue).Trim()
|
|
if (-not $raw) { return 0 }
|
|
[int]$raw
|
|
}
|
|
|
|
function Test-ServerRunning {
|
|
$storedPid = Get-StoredPid
|
|
if ($storedPid -eq 0) { return $false }
|
|
$proc = Get-Process -Id $storedPid -ErrorAction SilentlyContinue
|
|
if (-not $proc) { return $false }
|
|
return (Test-HttpOk "$ApiUrl/ok")
|
|
}
|
|
|
|
function Find-LangGraphExe {
|
|
$up = $env:USERPROFILE
|
|
$candidates = @(
|
|
"$up\.conda\envs\langgraph\Scripts\langgraph.exe",
|
|
"$up\miniconda3\envs\langgraph\Scripts\langgraph.exe",
|
|
"$up\anaconda3\envs\langgraph\Scripts\langgraph.exe",
|
|
"$up\miniforge3\envs\langgraph\Scripts\langgraph.exe",
|
|
"$up\AppData\Local\miniconda3\envs\langgraph\Scripts\langgraph.exe",
|
|
"C:\ProgramData\miniconda3\envs\langgraph\Scripts\langgraph.exe",
|
|
"C:\ProgramData\anaconda3\envs\langgraph\Scripts\langgraph.exe"
|
|
)
|
|
foreach ($c in $candidates) {
|
|
if (Test-Path $c) { return $c }
|
|
}
|
|
# Try PATH
|
|
$cmd = Get-Command langgraph -ErrorAction SilentlyContinue
|
|
if ($cmd) { return $cmd.Source }
|
|
# Try resolving via conda
|
|
$condaCandidates = @($env:CONDA_EXE, "C:\ProgramData\miniconda3\Scripts\conda.exe",
|
|
"$up\AppData\Local\miniconda3\Scripts\conda.exe")
|
|
foreach ($conda in $condaCandidates) {
|
|
if ($conda -and (Test-Path $conda)) {
|
|
try {
|
|
$lgPath = (& $conda run -n langgraph python -c "import shutil; print(shutil.which('langgraph') or '')" 2>$null).Trim()
|
|
if ($lgPath -and (Test-Path $lgPath)) { return $lgPath }
|
|
} catch {}
|
|
}
|
|
}
|
|
throw ("Cannot find langgraph.exe in the 'langgraph' conda env. " +
|
|
"Install it: conda run -n langgraph pip install 'langgraph-cli[inmem]'")
|
|
}
|
|
|
|
function Stop-ServerProcess([int]$PidValue) {
|
|
$stopped = $false
|
|
if ($PidValue -gt 0) {
|
|
$proc = Get-Process -Id $PidValue -ErrorAction SilentlyContinue
|
|
if ($proc) {
|
|
Stop-Process -Id $PidValue -Force -ErrorAction SilentlyContinue
|
|
$stopped = $true
|
|
}
|
|
}
|
|
# Fallback: kill whoever is listening on port 2024
|
|
if (-not $stopped) {
|
|
$conn = Get-NetTCPConnection -LocalPort 2024 -State Listen -ErrorAction SilentlyContinue |
|
|
Select-Object -First 1
|
|
if ($conn) {
|
|
Stop-Process -Id $conn.OwningProcess -Force -ErrorAction SilentlyContinue
|
|
Write-Host "[dev-server] Stopped process on port 2024 (PID $($conn.OwningProcess))"
|
|
$stopped = $true
|
|
}
|
|
}
|
|
return $stopped
|
|
}
|
|
|
|
# --- command dispatch ---------------------------------------------------
|
|
|
|
switch ($Command) {
|
|
|
|
"start" {
|
|
if (Test-ServerRunning) {
|
|
$storedPid = Get-StoredPid
|
|
Write-Host "[dev-server] Already running (PID $storedPid) at $ApiUrl"
|
|
Write-Host "[dev-server] Studio: https://smith.langchain.com/studio/?baseUrl=$ApiUrl"
|
|
exit 0
|
|
}
|
|
|
|
$lgExe = Find-LangGraphExe
|
|
Write-Host "[dev-server] Starting langgraph dev server..."
|
|
Write-Host "[dev-server] exe: $lgExe"
|
|
Write-Host "[dev-server] dir: $SkillDir"
|
|
|
|
# Remove stale logs
|
|
Remove-Item $StdoutLog -ErrorAction SilentlyContinue
|
|
Remove-Item $StderrLog -ErrorAction SilentlyContinue
|
|
|
|
# Force UTF-8 to avoid GBK decode errors on Chinese Windows systems
|
|
$env:PYTHONUTF8 = "1"
|
|
$env:PYTHONIOENCODING = "utf-8"
|
|
|
|
$proc = Start-Process -FilePath $lgExe `
|
|
-ArgumentList "dev", "--no-browser" `
|
|
-WorkingDirectory $SkillDir `
|
|
-RedirectStandardOutput $StdoutLog `
|
|
-RedirectStandardError $StderrLog `
|
|
-WindowStyle Hidden -PassThru
|
|
|
|
if (-not $proc) {
|
|
Write-Error "[dev-server] Failed to launch process"
|
|
exit 1
|
|
}
|
|
$proc.Id | Out-File -FilePath $PidFile -Encoding ascii -NoNewline
|
|
|
|
# Wait for server to respond
|
|
$elapsed = 0
|
|
while (-not (Test-HttpOk "$ApiUrl/ok") -and $elapsed -lt $MaxWait) {
|
|
Start-Sleep -Seconds 1
|
|
$elapsed++
|
|
if ($proc.HasExited) {
|
|
Write-Error "[dev-server] Process exited unexpectedly (code $($proc.ExitCode))"
|
|
Write-Host "--- stdout ($StdoutLog) ---"
|
|
if (Test-Path $StdoutLog) { Get-Content $StdoutLog -Tail 30 }
|
|
Write-Host "--- stderr ($StderrLog) ---"
|
|
if (Test-Path $StderrLog) { Get-Content $StderrLog -Tail 30 }
|
|
Remove-Item $PidFile -ErrorAction SilentlyContinue
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
if ($elapsed -ge $MaxWait) {
|
|
Write-Error "[dev-server] Server did not become ready within ${MaxWait}s"
|
|
Write-Host "--- stdout ($StdoutLog) ---"
|
|
if (Test-Path $StdoutLog) { Get-Content $StdoutLog -Tail 30 }
|
|
Write-Host "--- stderr ($StderrLog) ---"
|
|
if (Test-Path $StderrLog) { Get-Content $StderrLog -Tail 30 }
|
|
exit 1
|
|
}
|
|
|
|
Write-Host "[dev-server] Ready in ${elapsed}s"
|
|
Write-Host "[dev-server] API: $ApiUrl"
|
|
Write-Host "[dev-server] Studio: https://smith.langchain.com/studio/?baseUrl=$ApiUrl"
|
|
Write-Host "[dev-server] Logs: $StdoutLog / $StderrLog"
|
|
}
|
|
|
|
"stop" {
|
|
$storedPid = Get-StoredPid
|
|
if ((Stop-ServerProcess -PidValue $storedPid)) {
|
|
Write-Host "[dev-server] Stopped (PID $storedPid)"
|
|
} else {
|
|
Write-Host "[dev-server] Not running (nothing to stop)"
|
|
}
|
|
Remove-Item $PidFile -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
"status" {
|
|
if (Test-ServerRunning) {
|
|
$storedPid = Get-StoredPid
|
|
Write-Host "[dev-server] Running (PID $storedPid) at $ApiUrl"
|
|
Write-Host "[dev-server] Studio: https://smith.langchain.com/studio/?baseUrl=$ApiUrl"
|
|
} else {
|
|
Write-Host "[dev-server] Not running"
|
|
}
|
|
}
|
|
|
|
default {
|
|
Write-Error "Usage: manage_dev_server.ps1 {start|stop|status}"
|
|
exit 1
|
|
}
|
|
}
|