mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-29 03:09:45 +02:00
wololo
This commit is contained in:
Generated
-72
@@ -948,7 +948,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"which",
|
||||
"zip",
|
||||
]
|
||||
@@ -977,15 +976,6 @@ version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
@@ -1096,29 +1086,6 @@ dependencies = [
|
||||
"pathdiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathdiff"
|
||||
version = "0.2.3"
|
||||
@@ -1337,15 +1304,6 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.3"
|
||||
@@ -1495,12 +1453,6 @@ version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.27"
|
||||
@@ -1585,16 +1537,6 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||
dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.8"
|
||||
@@ -1784,25 +1726,11 @@ dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.26.4"
|
||||
|
||||
@@ -13,7 +13,6 @@ path = "src/main.rs"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = { version = "1" }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
reqwest = { version = "0.12", features = ["rustls-tls", "blocking"], default-features = false }
|
||||
zip = "2"
|
||||
indexmap = { version = "2", features = ["serde"] }
|
||||
|
||||
+1
-5
@@ -1,7 +1,3 @@
|
||||
fn main() {
|
||||
// Make version available at compile time
|
||||
println!(
|
||||
"cargo:rustc-env=CARGO_PKG_VERSION={}",
|
||||
env!("CARGO_PKG_VERSION")
|
||||
);
|
||||
// Cargo already sets CARGO_PKG_VERSION, nothing to do
|
||||
}
|
||||
|
||||
+111
-133
@@ -7,11 +7,25 @@ use console::style;
|
||||
use crate::analytics;
|
||||
use crate::config::validate_config_file;
|
||||
|
||||
/// Find the Python interpreter, preferring python3 over python.
|
||||
fn find_python() -> Result<String, String> {
|
||||
for candidate in &["python3", "python"] {
|
||||
if which::which(candidate).is_ok() {
|
||||
return Ok(candidate.to_string());
|
||||
}
|
||||
}
|
||||
Err(
|
||||
"Python not found. The `langgraph dev` command requires Python >= 3.11 with \
|
||||
langgraph-cli[inmem] installed.\n\
|
||||
Install with: pip install -U \"langgraph-cli[inmem]\""
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Run the LangGraph API server in development mode (in-memory, via Python subprocess).
|
||||
///
|
||||
/// This spawns `python -c "from langgraph_api.cli import run_server; ..."` as a subprocess,
|
||||
/// passing the parsed config as arguments. For JS graphs, an error is returned since the
|
||||
/// in-memory server doesn't support them in this CLI.
|
||||
/// This passes the config as a JSON object via stdin to a Python bootstrap script,
|
||||
/// avoiding any string interpolation into Python source code.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn run(
|
||||
host: &str,
|
||||
@@ -48,162 +62,126 @@ pub fn run(
|
||||
);
|
||||
}
|
||||
|
||||
// Build the graphs JSON
|
||||
let graphs_json = serde_json::to_string(&config_json.graphs)
|
||||
.map_err(|e| format!("Failed to serialize graphs: {e}"))?;
|
||||
let python = find_python()?;
|
||||
|
||||
// Build env JSON (optional)
|
||||
let env_json = config_json
|
||||
.env
|
||||
.as_ref()
|
||||
.map(|e| serde_json::to_string(e).unwrap_or_else(|_| "null".to_string()))
|
||||
.unwrap_or_else(|| "None".to_string());
|
||||
// Pre-check that langgraph_api is importable
|
||||
let check = Command::new(&python)
|
||||
.args(["-c", "from langgraph_api.cli import run_server"])
|
||||
.output();
|
||||
|
||||
// Build store JSON (optional)
|
||||
let store_json = config_json
|
||||
.store
|
||||
.as_ref()
|
||||
.map(|s| serde_json::to_string(s).unwrap_or_else(|_| "null".to_string()))
|
||||
.unwrap_or_else(|| "None".to_string());
|
||||
match check {
|
||||
Ok(output) if !output.status.success() => {
|
||||
return Err(
|
||||
"Required package 'langgraph-api' is not installed.\n\
|
||||
Please install it with:\n\n\
|
||||
pip install -U \"langgraph-cli[inmem]\"\n\n\
|
||||
Note: The in-mem server requires Python 3.11 or higher."
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(format!(
|
||||
"Failed to run {python}. The `langgraph dev` command requires Python >= 3.11 with \
|
||||
langgraph-cli[inmem] installed.\n\
|
||||
Install with: pip install -U \"langgraph-cli[inmem]\""
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Build auth JSON (optional)
|
||||
let auth_json = config_json
|
||||
.auth
|
||||
.as_ref()
|
||||
.map(|a| serde_json::to_string(a).unwrap_or_else(|_| "null".to_string()))
|
||||
.unwrap_or_else(|| "None".to_string());
|
||||
// Build a JSON config object to pass via stdin.
|
||||
// This avoids interpolating user data into Python source code.
|
||||
let dev_config = serde_json::json!({
|
||||
"host": host,
|
||||
"port": port,
|
||||
"reload": !no_reload,
|
||||
"open_browser": !no_browser,
|
||||
"wait_for_client": wait_for_client,
|
||||
"allow_blocking": allow_blocking,
|
||||
"tunnel": tunnel,
|
||||
"server_log_level": server_log_level,
|
||||
"dependencies": config_json.dependencies,
|
||||
"graphs": config_json.graphs,
|
||||
"n_jobs_per_worker": n_jobs_per_worker,
|
||||
"debug_port": debug_port,
|
||||
"studio_url": studio_url,
|
||||
"env": config_json.env,
|
||||
"store": config_json.store,
|
||||
"auth": config_json.auth,
|
||||
"http": config_json.http,
|
||||
"ui": config_json.ui,
|
||||
"ui_config": config_json.ui_config,
|
||||
"webhooks": config_json.webhooks,
|
||||
});
|
||||
|
||||
// Build http JSON (optional)
|
||||
let http_json = config_json
|
||||
.http
|
||||
.as_ref()
|
||||
.map(|h| serde_json::to_string(h).unwrap_or_else(|_| "null".to_string()))
|
||||
.unwrap_or_else(|| "None".to_string());
|
||||
|
||||
// Build ui JSON (optional)
|
||||
let ui_json = config_json
|
||||
.ui
|
||||
.as_ref()
|
||||
.map(|u| serde_json::to_string(u).unwrap_or_else(|_| "null".to_string()))
|
||||
.unwrap_or_else(|| "None".to_string());
|
||||
|
||||
// Build ui_config JSON (optional)
|
||||
let ui_config_json = config_json
|
||||
.ui_config
|
||||
.as_ref()
|
||||
.map(|u| serde_json::to_string(u).unwrap_or_else(|_| "null".to_string()))
|
||||
.unwrap_or_else(|| "None".to_string());
|
||||
|
||||
// Build webhooks JSON (optional)
|
||||
let webhooks_json = config_json
|
||||
.webhooks
|
||||
.as_ref()
|
||||
.map(|w| serde_json::to_string(w).unwrap_or_else(|_| "null".to_string()))
|
||||
.unwrap_or_else(|| "None".to_string());
|
||||
|
||||
// Build n_jobs_per_worker
|
||||
let n_jobs_str = n_jobs_per_worker
|
||||
.map(|n| n.to_string())
|
||||
.unwrap_or_else(|| "None".to_string());
|
||||
|
||||
// Build debug_port
|
||||
let debug_port_str = debug_port
|
||||
.map(|p| p.to_string())
|
||||
.unwrap_or_else(|| "None".to_string());
|
||||
|
||||
// Build studio_url
|
||||
let studio_url_str = studio_url
|
||||
.map(|s| format!("\"{}\"", s.replace('"', "\\\"")))
|
||||
.unwrap_or_else(|| "None".to_string());
|
||||
|
||||
// Construct the Python code to execute
|
||||
let python_code = format!(
|
||||
r#"
|
||||
import sys, os, json
|
||||
// Python bootstrap: reads JSON from stdin, calls run_server
|
||||
let python_code = r#"
|
||||
import sys, os, json, pathlib
|
||||
config = json.loads(sys.stdin.read())
|
||||
cwd = os.getcwd()
|
||||
sys.path.append(cwd)
|
||||
deps = json.loads('{deps_json}')
|
||||
for dep in deps:
|
||||
import pathlib
|
||||
for dep in config.get('dependencies', []):
|
||||
dep_path = pathlib.Path(cwd) / dep
|
||||
if dep_path.is_dir() and dep_path.exists():
|
||||
sys.path.append(str(dep_path))
|
||||
from langgraph_api.cli import run_server
|
||||
graphs = json.loads('{graphs}')
|
||||
run_server(
|
||||
"{host}",
|
||||
{port},
|
||||
{reload},
|
||||
graphs,
|
||||
n_jobs_per_worker={n_jobs},
|
||||
open_browser={open_browser},
|
||||
debug_port={debug_port},
|
||||
env={env},
|
||||
store={store},
|
||||
wait_for_client={wait_for_client},
|
||||
auth={auth},
|
||||
http={http},
|
||||
ui={ui},
|
||||
ui_config={ui_config},
|
||||
webhooks={webhooks},
|
||||
studio_url={studio_url},
|
||||
allow_blocking={allow_blocking},
|
||||
tunnel={tunnel},
|
||||
server_level="{server_log_level}",
|
||||
config['host'],
|
||||
config['port'],
|
||||
config['reload'],
|
||||
config['graphs'],
|
||||
n_jobs_per_worker=config.get('n_jobs_per_worker'),
|
||||
open_browser=config['open_browser'],
|
||||
debug_port=config.get('debug_port'),
|
||||
env=config.get('env'),
|
||||
store=config.get('store'),
|
||||
wait_for_client=config['wait_for_client'],
|
||||
auth=config.get('auth'),
|
||||
http=config.get('http'),
|
||||
ui=config.get('ui'),
|
||||
ui_config=config.get('ui_config'),
|
||||
webhooks=config.get('webhooks'),
|
||||
studio_url=config.get('studio_url'),
|
||||
allow_blocking=config['allow_blocking'],
|
||||
tunnel=config['tunnel'],
|
||||
server_level=config['server_log_level'],
|
||||
)
|
||||
"#,
|
||||
deps_json = serde_json::to_string(&config_json.dependencies)
|
||||
.unwrap_or_else(|_| "[]".to_string())
|
||||
.replace('\'', "\\'"),
|
||||
graphs = graphs_json.replace('\'', "\\'"),
|
||||
host = host,
|
||||
port = port,
|
||||
reload = if no_reload { "False" } else { "True" },
|
||||
n_jobs = n_jobs_str,
|
||||
open_browser = if no_browser { "False" } else { "True" },
|
||||
debug_port = debug_port_str,
|
||||
env = env_json,
|
||||
store = store_json,
|
||||
wait_for_client = if wait_for_client { "True" } else { "False" },
|
||||
auth = auth_json,
|
||||
http = http_json,
|
||||
ui = ui_json,
|
||||
ui_config = ui_config_json,
|
||||
webhooks = webhooks_json,
|
||||
studio_url = studio_url_str,
|
||||
allow_blocking = if allow_blocking { "True" } else { "False" },
|
||||
tunnel = if tunnel { "True" } else { "False" },
|
||||
server_log_level = server_log_level,
|
||||
);
|
||||
"#;
|
||||
|
||||
eprintln!(
|
||||
"{}",
|
||||
style("Starting LangGraph API server in development mode...").green()
|
||||
);
|
||||
|
||||
// Spawn Python subprocess
|
||||
let status = Command::new("python")
|
||||
// Spawn Python subprocess with config on stdin
|
||||
let mut child = Command::new(&python)
|
||||
.arg("-c")
|
||||
.arg(&python_code)
|
||||
.arg(python_code)
|
||||
.current_dir(
|
||||
config_path
|
||||
.parent()
|
||||
.unwrap_or_else(|| Path::new(".")),
|
||||
)
|
||||
.stdin(std::process::Stdio::inherit())
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stdout(std::process::Stdio::inherit())
|
||||
.stderr(std::process::Stdio::inherit())
|
||||
.status()
|
||||
.map_err(|e| {
|
||||
if e.kind() == std::io::ErrorKind::NotFound {
|
||||
"Python not found. The `langgraph dev` command requires Python >= 3.11 with \
|
||||
langgraph-cli[inmem] installed.\n\
|
||||
Install with: pip install -U \"langgraph-cli[inmem]\""
|
||||
.to_string()
|
||||
} else {
|
||||
format!("Failed to start Python: {e}")
|
||||
}
|
||||
})?;
|
||||
.spawn()
|
||||
.map_err(|e| format!("Failed to start Python: {e}"))?;
|
||||
|
||||
// Write JSON config to stdin
|
||||
if let Some(ref mut stdin) = child.stdin {
|
||||
use std::io::Write;
|
||||
let json_bytes = dev_config.to_string();
|
||||
stdin
|
||||
.write_all(json_bytes.as_bytes())
|
||||
.map_err(|e| format!("Failed to write config to Python stdin: {e}"))?;
|
||||
}
|
||||
// Drop stdin to signal EOF
|
||||
drop(child.stdin.take());
|
||||
|
||||
let status = child
|
||||
.wait()
|
||||
.map_err(|e| format!("Failed to wait for Python: {e}"))?;
|
||||
|
||||
if !status.success() {
|
||||
let code = status.code().unwrap_or(1);
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::config::validate_config_file;
|
||||
use crate::docker::capabilities::{check_capabilities, ComposeType};
|
||||
use crate::docker::compose::compose;
|
||||
use crate::docker::dockerfile::config_to_compose;
|
||||
use crate::exec::{run_command, run_command_streaming};
|
||||
use crate::exec::{run_command, run_command_streaming_with_callback};
|
||||
use crate::progress::Progress;
|
||||
use crate::util::warn_non_wolfi_distro;
|
||||
|
||||
@@ -153,10 +153,46 @@ pub fn run(
|
||||
cmd_args.push(a.as_str());
|
||||
}
|
||||
|
||||
progress.finish();
|
||||
// Run docker compose with streaming output, intercepting stdout
|
||||
// to detect startup and show Ready! URLs
|
||||
let mut ready_printed = false;
|
||||
let debugger_base_url_query = debugger_base_url
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_else(|| format!("http://127.0.0.1:{port}"));
|
||||
|
||||
// Run docker compose with streaming output
|
||||
run_command_streaming(compose_cmd[0], &cmd_args, Some(&compose_stdin), verbose)?;
|
||||
run_command_streaming_with_callback(
|
||||
compose_cmd[0],
|
||||
&cmd_args,
|
||||
Some(&compose_stdin),
|
||||
verbose,
|
||||
|line| {
|
||||
if !ready_printed {
|
||||
if line.contains("unpacking to docker.io") {
|
||||
progress.set_message("Starting...");
|
||||
} else if line.contains("Application startup complete") {
|
||||
progress.finish();
|
||||
ready_printed = true;
|
||||
|
||||
let debugger_origin = if let Some(dp) = debugger_port {
|
||||
format!("http://localhost:{dp}")
|
||||
} else {
|
||||
"https://smith.langchain.com".to_string()
|
||||
};
|
||||
|
||||
println!(
|
||||
"Ready!\n\
|
||||
- API: http://localhost:{port}\n\
|
||||
- Docs: http://localhost:{port}/docs\n\
|
||||
- LangGraph Studio: {debugger_origin}/studio/?baseUrl={debugger_base_url_query}"
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
)?;
|
||||
|
||||
if !ready_printed {
|
||||
progress.finish();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -78,13 +78,6 @@ pub enum GraphSpec {
|
||||
}
|
||||
|
||||
impl GraphSpec {
|
||||
pub fn get_path(&self) -> Option<&str> {
|
||||
match self {
|
||||
GraphSpec::Path(s) => Some(s),
|
||||
GraphSpec::Dict(m) => m.get("path").and_then(|v| v.as_str()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_path(&mut self, new_path: String) {
|
||||
match self {
|
||||
GraphSpec::Path(s) => *s = new_path,
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
pub const DEFAULT_CONFIG: &str = "langgraph.json";
|
||||
pub const DEFAULT_PORT: u16 = 8123;
|
||||
|
||||
pub const MIN_NODE_VERSION: u32 = 20;
|
||||
pub const DEFAULT_NODE_VERSION: u32 = 20;
|
||||
|
||||
|
||||
@@ -28,7 +28,9 @@ pub enum ComposeType {
|
||||
/// Docker capabilities detected on the system.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DockerCapabilities {
|
||||
#[allow(dead_code)]
|
||||
pub version_docker: Version,
|
||||
#[allow(dead_code)]
|
||||
pub version_compose: Version,
|
||||
pub healthcheck_start_interval: bool,
|
||||
pub compose_type: ComposeType,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
|
||||
@@ -13,13 +14,15 @@ use crate::constants::{BUILD_TOOLS, DEFAULT_NODE_VERSION};
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
static IMAGE_VERSION_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r":(\d+(?:\.\d+)?(?:\.\d+)?)(?:-|$)").unwrap());
|
||||
|
||||
/// Check if a base image supports uv.
|
||||
fn image_supports_uv(base_image: &str) -> bool {
|
||||
if base_image == "langchain/langgraph-trial" {
|
||||
return false;
|
||||
}
|
||||
let re = Regex::new(r":(\d+(?:\.\d+)?(?:\.\d+)?)(?:-|$)").unwrap();
|
||||
match re.find(base_image) {
|
||||
match IMAGE_VERSION_RE.find(base_image) {
|
||||
None => true, // Default image supports it
|
||||
Some(m) => {
|
||||
let raw = &base_image[m.start() + 1..m.end()];
|
||||
@@ -109,6 +112,66 @@ fn get_pip_cleanup_lines(
|
||||
commands.join("\n")
|
||||
}
|
||||
|
||||
/// Build ENV lines for all config-driven environment variables (shared between Python and Node).
|
||||
fn build_config_env_vars(config: &Config) -> Vec<String> {
|
||||
let mut env_vars = Vec::new();
|
||||
|
||||
if let Some(ref store) = config.store {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_STORE='{}'",
|
||||
serde_json::to_string(store).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref auth) = config.auth {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_AUTH='{}'",
|
||||
serde_json::to_string(auth).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref encryption) = config.encryption {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_ENCRYPTION='{}'",
|
||||
serde_json::to_string(encryption).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref http) = config.http {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_HTTP='{}'",
|
||||
serde_json::to_string(http).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref webhooks) = config.webhooks {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_WEBHOOKS='{}'",
|
||||
serde_json::to_string(webhooks).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref checkpointer) = config.checkpointer {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_CHECKPOINTER='{}'",
|
||||
serde_json::to_string(checkpointer).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref ui) = config.ui {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_UI='{}'",
|
||||
serde_json::to_string(ui).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref ui_config) = config.ui_config {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_UI_CONFIG='{}'",
|
||||
serde_json::to_string(ui_config).unwrap()
|
||||
));
|
||||
}
|
||||
env_vars.push(format!(
|
||||
"ENV LANGSERVE_GRAPHS='{}'",
|
||||
serde_json::to_string(&config.graphs).unwrap()
|
||||
));
|
||||
|
||||
env_vars
|
||||
}
|
||||
|
||||
/// Detect the Node.js package manager install command.
|
||||
fn get_node_pm_install_cmd(config_path: &Path, _config: &Config) -> String {
|
||||
let parent = config_path.parent().unwrap();
|
||||
@@ -275,60 +338,7 @@ pub fn python_config_to_docker(
|
||||
let installs = installs.join("\n\n");
|
||||
|
||||
// Environment variables
|
||||
let mut env_vars = Vec::new();
|
||||
|
||||
if let Some(ref store) = config.store {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_STORE='{}'",
|
||||
serde_json::to_string(store).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref auth) = config.auth {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_AUTH='{}'",
|
||||
serde_json::to_string(auth).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref encryption) = config.encryption {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_ENCRYPTION='{}'",
|
||||
serde_json::to_string(encryption).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref http) = config.http {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_HTTP='{}'",
|
||||
serde_json::to_string(http).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref webhooks) = config.webhooks {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_WEBHOOKS='{}'",
|
||||
serde_json::to_string(webhooks).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref checkpointer) = config.checkpointer {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_CHECKPOINTER='{}'",
|
||||
serde_json::to_string(checkpointer).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref ui) = config.ui {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_UI='{}'",
|
||||
serde_json::to_string(ui).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref ui_config) = config.ui_config {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_UI_CONFIG='{}'",
|
||||
serde_json::to_string(ui_config).unwrap()
|
||||
));
|
||||
}
|
||||
env_vars.push(format!(
|
||||
"ENV LANGSERVE_GRAPHS='{}'",
|
||||
serde_json::to_string(&config.graphs).unwrap()
|
||||
));
|
||||
let env_vars = build_config_env_vars(config);
|
||||
|
||||
// JS install
|
||||
let js_inst_str =
|
||||
@@ -451,59 +461,7 @@ pub fn node_config_to_docker(
|
||||
let image_str = docker_tag(config, Some(base_image), api_version);
|
||||
|
||||
// Environment variables
|
||||
let mut env_vars = Vec::new();
|
||||
if let Some(ref store) = config.store {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_STORE='{}'",
|
||||
serde_json::to_string(store).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref auth) = config.auth {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_AUTH='{}'",
|
||||
serde_json::to_string(auth).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref encryption) = config.encryption {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_ENCRYPTION='{}'",
|
||||
serde_json::to_string(encryption).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref http) = config.http {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_HTTP='{}'",
|
||||
serde_json::to_string(http).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref webhooks) = config.webhooks {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_WEBHOOKS='{}'",
|
||||
serde_json::to_string(webhooks).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref checkpointer) = config.checkpointer {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_CHECKPOINTER='{}'",
|
||||
serde_json::to_string(checkpointer).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref ui) = config.ui {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_UI='{}'",
|
||||
serde_json::to_string(ui).unwrap()
|
||||
));
|
||||
}
|
||||
if let Some(ref ui_config) = config.ui_config {
|
||||
env_vars.push(format!(
|
||||
"ENV LANGGRAPH_UI_CONFIG='{}'",
|
||||
serde_json::to_string(ui_config).unwrap()
|
||||
));
|
||||
}
|
||||
env_vars.push(format!(
|
||||
"ENV LANGSERVE_GRAPHS='{}'",
|
||||
serde_json::to_string(&config.graphs).unwrap()
|
||||
));
|
||||
let env_vars = build_config_env_vars(config);
|
||||
|
||||
let (install_step, build_step) = if let Some(_bc) = build_context {
|
||||
let cr = container_root.as_ref().unwrap();
|
||||
|
||||
+76
-1
@@ -1,4 +1,4 @@
|
||||
use std::io::Write;
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
/// Run a command synchronously, optionally piping stdin, and capturing stdout/stderr.
|
||||
@@ -134,3 +134,78 @@ pub fn run_command_streaming(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run a command, streaming stderr to the parent process, while intercepting
|
||||
/// stdout line-by-line through a callback. Each stdout line is forwarded to
|
||||
/// the parent's stdout after the callback processes it.
|
||||
pub fn run_command_streaming_with_callback<F>(
|
||||
cmd: &str,
|
||||
args: &[&str],
|
||||
input: Option<&str>,
|
||||
verbose: bool,
|
||||
mut on_stdout: F,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
F: FnMut(&str),
|
||||
{
|
||||
if verbose {
|
||||
let cmd_str = format!("+ {} {}", cmd, args.join(" "));
|
||||
if let Some(inp) = input {
|
||||
let filtered: Vec<&str> = inp.lines().filter(|l| !l.is_empty()).collect();
|
||||
println!("{} <\n{}", cmd_str, filtered.join("\n"));
|
||||
} else {
|
||||
println!("{cmd_str}");
|
||||
}
|
||||
}
|
||||
|
||||
let stdin_cfg = if input.is_some() {
|
||||
Stdio::piped()
|
||||
} else {
|
||||
Stdio::null()
|
||||
};
|
||||
|
||||
let mut child = Command::new(cmd)
|
||||
.args(args)
|
||||
.stdin(stdin_cfg)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::inherit())
|
||||
.spawn()
|
||||
.map_err(|e| format!("Failed to execute `{cmd}`: {e}"))?;
|
||||
|
||||
if let Some(input_data) = input {
|
||||
if let Some(ref mut stdin_handle) = child.stdin {
|
||||
stdin_handle
|
||||
.write_all(input_data.as_bytes())
|
||||
.map_err(|e| format!("Failed to write to stdin of `{cmd}`: {e}"))?;
|
||||
}
|
||||
drop(child.stdin.take());
|
||||
}
|
||||
|
||||
// Read stdout line by line, forward to our stdout, and call callback
|
||||
if let Some(stdout) = child.stdout.take() {
|
||||
let reader = BufReader::new(stdout);
|
||||
for line in reader.lines() {
|
||||
match line {
|
||||
Ok(line) => {
|
||||
println!("{line}");
|
||||
on_stdout(&line);
|
||||
}
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let status = child
|
||||
.wait()
|
||||
.map_err(|e| format!("Failed to wait for `{cmd}`: {e}"))?;
|
||||
|
||||
if !status.success() {
|
||||
let code = status.code().unwrap_or(-1);
|
||||
if code == 130 {
|
||||
return Ok(());
|
||||
}
|
||||
return Err(format!("Command `{cmd}` exited with code {code}"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+11
-11
@@ -47,9 +47,9 @@ enum Commands {
|
||||
#[arg(long)]
|
||||
recreate: bool,
|
||||
|
||||
/// Pull latest images before running
|
||||
#[arg(long, default_value_t = true)]
|
||||
pull: bool,
|
||||
/// Skip pulling latest images before running
|
||||
#[arg(long)]
|
||||
no_pull: bool,
|
||||
|
||||
/// Wait for services to be healthy before returning
|
||||
#[arg(long)]
|
||||
@@ -90,9 +90,9 @@ enum Commands {
|
||||
#[arg(short, long)]
|
||||
tag: String,
|
||||
|
||||
/// Pull latest images before building
|
||||
#[arg(long, default_value_t = true)]
|
||||
pull: bool,
|
||||
/// Skip pulling latest images before building
|
||||
#[arg(long)]
|
||||
no_pull: bool,
|
||||
|
||||
/// Base image for the LangGraph API server
|
||||
#[arg(long)]
|
||||
@@ -152,7 +152,7 @@ enum Commands {
|
||||
no_reload: bool,
|
||||
|
||||
/// Path to configuration file
|
||||
#[arg(long, default_value = "langgraph.json")]
|
||||
#[arg(short, long, default_value = "langgraph.json")]
|
||||
config: String,
|
||||
|
||||
/// Max concurrent jobs per worker
|
||||
@@ -210,7 +210,7 @@ fn main() {
|
||||
verbose,
|
||||
watch,
|
||||
recreate,
|
||||
pull,
|
||||
no_pull,
|
||||
wait,
|
||||
debugger_port,
|
||||
debugger_base_url,
|
||||
@@ -225,7 +225,7 @@ fn main() {
|
||||
verbose,
|
||||
watch,
|
||||
recreate,
|
||||
pull,
|
||||
!no_pull,
|
||||
wait,
|
||||
debugger_port,
|
||||
debugger_base_url.as_deref(),
|
||||
@@ -237,7 +237,7 @@ fn main() {
|
||||
Commands::Build {
|
||||
config,
|
||||
tag,
|
||||
pull,
|
||||
no_pull,
|
||||
base_image,
|
||||
api_version,
|
||||
install_command,
|
||||
@@ -246,7 +246,7 @@ fn main() {
|
||||
} => commands::build_cmd::run(
|
||||
&config,
|
||||
&tag,
|
||||
pull,
|
||||
!no_pull,
|
||||
base_image.as_deref(),
|
||||
api_version.as_deref(),
|
||||
install_command.as_deref(),
|
||||
|
||||
Reference in New Issue
Block a user