Skip to main content

objectiveai_cli/command/api/
spawn.rs

1//! `api spawn` — start the `objectiveai-api` server in the background.
2//!
3//! The api is machine-wide (one per `OBJECTIVEAI_DIR`): its lock lives
4//! at `<dir>/bin/locks` key `api`, and the lock contents are the
5//! server's client-connect URL. If the lock is already held the server
6//! is already up and its published URL is returned as-is.
7
8use objectiveai_sdk::cli::command::api::spawn::{Request, Response};
9
10use crate::context::Context;
11use crate::error::Error;
12
13/// Every env key the api's config reads (`EnvConfigBuilder` in
14/// `objectiveai-api/src/run.rs`) except `OBJECTIVEAI_DIR`, which the
15/// spawn sets explicitly. The child inherits the cli's environment
16/// (toolchain + machine identity — the dev `bin` entries are cargo-run
17/// shims); scrubbing exactly the api's own config keys keeps the
18/// spawning shell's settings from leaking into a server that other
19/// processes will share. Deliberately scrubbed rather than forwarded:
20/// OBJECTIVEAI_STATE (the api is global; it resolves its own state
21/// default rather than inheriting whichever state the spawning cli
22/// happened to run in) and ADDRESS/PORT (the api defaults to
23/// 127.0.0.1 on an ephemeral port and publishes the bound URL in its
24/// lock). The api's config then comes from `<OBJECTIVEAI_DIR>/.env`
25/// (its main loads it non-overridingly) and its own defaults.
26const API_CONFIG_ENV: &[&str] = &[
27    "OBJECTIVEAI_ADDRESS",
28    "OBJECTIVEAI_AUTHORIZATION",
29    "OPENROUTER_ADDRESS",
30    "OPENROUTER_AUTHORIZATION",
31    "GITHUB_AUTHORIZATION",
32    "MCP_AUTHORIZATION",
33    "USER_AGENT",
34    "HTTP_REFERER",
35    "X_TITLE",
36    "COMMIT_AUTHOR_NAME",
37    "COMMIT_AUTHOR_EMAIL",
38    "CLAUDE_AGENT_SDK_ENABLED",
39    "CLAUDE_AGENT_SDK_RATE_LIMIT_MAX_RETRIES",
40    "CLAUDE_AGENT_SDK_RATE_LIMIT_MAX_WAIT_SECS",
41    "CLAUDE_AGENT_SDK_QUERY_LIMIT",
42    "CODEX_SDK_ENABLED",
43    "CODEX_SDK_RATE_LIMIT_MAX_RETRIES",
44    "CODEX_SDK_RATE_LIMIT_MAX_WAIT_SECS",
45    "CODEX_SDK_QUERY_LIMIT",
46    "AGENT_COMPLETIONS_BACKOFF_CURRENT_INTERVAL",
47    "AGENT_COMPLETIONS_BACKOFF_INITIAL_INTERVAL",
48    "AGENT_COMPLETIONS_BACKOFF_RANDOMIZATION_FACTOR",
49    "AGENT_COMPLETIONS_BACKOFF_MULTIPLIER",
50    "AGENT_COMPLETIONS_BACKOFF_MAX_INTERVAL",
51    "AGENT_COMPLETIONS_BACKOFF_MAX_ELAPSED_TIME",
52    "MCP_BACKOFF_CURRENT_INTERVAL",
53    "MCP_BACKOFF_INITIAL_INTERVAL",
54    "MCP_BACKOFF_RANDOMIZATION_FACTOR",
55    "MCP_BACKOFF_MULTIPLIER",
56    "MCP_BACKOFF_MAX_INTERVAL",
57    "MCP_BACKOFF_MAX_ELAPSED_TIME",
58    "GITHUB_BACKOFF_CURRENT_INTERVAL",
59    "GITHUB_BACKOFF_INITIAL_INTERVAL",
60    "GITHUB_BACKOFF_RANDOMIZATION_FACTOR",
61    "GITHUB_BACKOFF_MULTIPLIER",
62    "GITHUB_BACKOFF_MAX_INTERVAL",
63    "GITHUB_BACKOFF_MAX_ELAPSED_TIME",
64    "AGENT_COMPLETIONS_FIRST_CHUNK_TIMEOUT",
65    "AGENT_COMPLETIONS_OTHER_CHUNK_TIMEOUT",
66    "MCP_CONNECT_TIMEOUT",
67    "MCP_CALL_TIMEOUT",
68    "REVERSE_CHANNEL_TIMEOUT",
69    "MCP_ENCRYPTION_KEY",
70    "OBJECTIVEAI_STATE",
71    "PERSISTENT_CACHE_TRANSIENT_TTL_MS",
72    "MOCK_DELAY_MS",
73    "MOCK_MAX_TOOL_CALLS",
74    "ADDRESS",
75    "PORT",
76];
77
78/// The spawn flow itself, callable in-process (used by
79/// `Context::api_client()` as well as the `api spawn` command).
80/// Idempotent and cheap when the server is already up: a try_read of
81/// the lock returns the published URL without spawning.
82pub async fn spawn(ctx: &Context) -> Result<String, Error> {
83    let bin = if cfg!(windows) {
84        "objectiveai-api.exe"
85    } else {
86        "objectiveai-api"
87    };
88    let exe = ctx.filesystem.bin_dir().join(bin);
89    let lock_dir = ctx.filesystem.bin_dir().join("locks");
90
91    crate::spawn::spawn_until_lock_published(&exe, &lock_dir, "api", |cmd| {
92        for key in API_CONFIG_ENV {
93            cmd.env_remove(key);
94        }
95        cmd.env("OBJECTIVEAI_DIR", ctx.filesystem.dir())
96            .env("SUPPRESS_OUTPUT", "true");
97    })
98    .await
99}
100
101pub async fn execute(ctx: &Context, _request: Request) -> Result<Response, Error> {
102    Ok(Response {
103        listening: spawn(ctx).await?,
104    })
105}
106
107pub mod request_schema {
108    use objectiveai_sdk::cli::command::api::spawn as sdk;
109    use objectiveai_sdk::cli::command::api::spawn::request_schema::{Request, Response};
110
111    use crate::context::Context;
112    use crate::error::Error;
113
114    pub async fn execute(_ctx: &Context, _request: Request) -> Result<Response, Error> {
115        Ok(objectiveai_sdk::cli::command::ResponseSchema(schemars::schema_for!(sdk::Request)))
116    }
117}
118
119pub mod response_schema {
120    use objectiveai_sdk::cli::command::api::spawn as sdk;
121    use objectiveai_sdk::cli::command::api::spawn::response_schema::{Request, Response};
122
123    use crate::context::Context;
124    use crate::error::Error;
125
126    pub async fn execute(_ctx: &Context, _request: Request) -> Result<Response, Error> {
127        Ok(objectiveai_sdk::cli::command::ResponseSchema(schemars::schema_for!(sdk::Response)))
128    }
129}