diff --git a/README.md b/README.md index a44926f..34e4c5d 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ AI Agent (MCP stdio) Enterprise Web (browser/API) | `crates/local-bridge` | CLI and local MCP stdio endpoint | Read-only prototype | | CloudFile adapter | Connects installed Agent2 via a user-configured executable | Read-only `--component` probe | | Enterprise Channels | Server describes allowed navigation and built-in capabilities | Offline manifest validation only | -| Personal Agent (opt-in) | Launch the user's trusted OpenCode or Pi CLI | Free-model discovery + chat-only launcher, no MCP execution | +| Personal Agent (opt-in) | Pi-first personal chat; OpenCode coding | Fixed free-tier defaults + no MCP execution | | File editing / Checkout | CloudFile’s actual file workflows | Continue in the existing Agent2 repo; **not yet bridged into MCP** | ## 构建及本地测试 @@ -72,18 +72,33 @@ cargo run -p local-bridge -- validate-channel examples/etech-channels.json Read [security boundaries](docs/SECURITY.md) before integrating Web or AI workloads. Schema validation alone never authenticates an enterprise server. -## Personal Agent (optional, local-first) +## Personal Agent (optional, Pi first) -The user may start with an existing OpenCode CLI and its currently listed `-free` models, or choose Pi with their own provider login. The bridge **does not** fetch executables or credentials, impersonate third-party clients, or expose Agent execution through MCP. +**No model-choice step.** The standard entry launches **Pi** with a preselected +Google Gemini 3.5 Flash-Lite free-tier model. Use OpenCode only for coding: ```bash -local-bridge agent doctor -local-bridge agent models -local-bridge agent # Official OpenCode free-model chat -local-bridge agent pi # Pi; its chosen provider may be paid +local-bridge agent doctor # check installation +local-bridge agent # Pi personal chat, preset free-tier model +local-bridge agent code # OpenCode coding, preset -free model ``` -The launcher creates a separate user workspace and requires confirmation for OpenCode shell/file writes; Pi starts with all tools disabled. See [Personal Agent quick start](docs/PERSONAL_AGENT.md). +Both run in a separate user workspace by default. To work in the same selected +folder, pass `--dir /your/project` to both. There is no automatic agent-to-agent +delegation, shared conversations or background subprocess orchestration yet. + +**First-use credential:** Pi's selected Google model needs your own **Google AI +Studio API key** on a free-tier project (use `/login google` in Pi once). The +free tier is region/quota limited; a key from a paid project can incur charges. +No billing switch, API key, model picker, or fallback is managed by Local Bridge. + +OpenCode uses an official `opencode/deepseek-v4.1-flash-free` catalog entry; this +may disappear or require authentication. The bridge fails visibly rather than +silently changing to a paid/random model. Pi has no local tools enabled; OpenCode +runs build mode with approval before shell/file modifications and is opt-in only. + +See [Personal Agent quick start](docs/PERSONAL_AGENT.md). Neither agent is exposed +as an MCP tool or enterprise Channel. ## Existing repositories diff --git a/crates/local-bridge/src/agent.rs b/crates/local-bridge/src/agent.rs index ab1e29f..12b38c4 100644 --- a/crates/local-bridge/src/agent.rs +++ b/crates/local-bridge/src/agent.rs @@ -7,23 +7,23 @@ use std::process::{Command, Stdio}; use std::time::{Duration, Instant}; const CATALOG_TIMEOUT: Duration = Duration::from_secs(15); -const PREFERRED_MODELS: &[&str] = &[ - "opencode/mimo-v2.6-flash-free", - "opencode/mimo-v2.5-free", - "opencode/nemotron-3.5-lightning-free", -]; +// Preset one reasonable free-tier model per mode; never select a random candidate. +const DEFAULT_PI_PROVIDER: &str = "google"; +const DEFAULT_PI_MODEL: &str = "gemini-3.5-flash-lite"; +const DEFAULT_CODE_MODEL: &str = "opencode/deepseek-v4.1-flash-free"; fn usage() { println!( - "Local Bridge Personal Agent (local, opt-in)\n\ + "Local Bridge Personal Agent\n\ Commands:\n\ - agent OpenCode free-model terminal chat\n\ - agent doctor Check Pi / OpenCode installation\n\ - agent models Show OpenCode free-model candidates\n\ - agent opencode [--model opencode/name-free] [--dir ]\n\ - agent pi [--model provider/model] [--dir ]\n\ - The default workspace is separate from your working directory.\n\ - Free quotas may change. No automatic download, paid fallback or remote execution." + agent Chat with Pi (preset Gemini free-tier model)\n\ + agent chat Same as agent\n\ + agent code Code with OpenCode (preset free candidate)\n\ + agent doctor Check required CLIs\n\ + agent models Diagnose OpenCode free candidates\n\ + agent [chat|code] --dir \n\ + Advanced: agent pi|opencode [--model provider/model] [--dir ]\n\ + The user supplies provider credentials once. No automatic paid fallback." ); } @@ -39,11 +39,11 @@ fn installed(program: &str) -> bool { fn install_guide() { eprintln!( - "Install official clients as your normal user:\n\ - OpenCode https://opencode.ai/docs/\n\ - Pi https://pi.dev/\n\ - Restart your terminal and run: local-bridge agent doctor\n\ - Provider credentials stay with the official CLIs." + "Install Pi as your normal user: https://pi.dev/docs/latest/quickstart\n\ + Optional coding mode: https://opencode.ai/docs/\n\ + For Pi's default free tier, use a Google AI Studio API key on a project\n\ + without paid billing: https://aistudio.google.com/api-keys\n\ + In Pi, run /login google once. No model selection required." ); } @@ -70,15 +70,6 @@ fn parse_free_models(output: &str) -> Vec { .collect() } -fn choose_default(models: &[String]) -> Option<&str> { - for preferred in PREFERRED_MODELS { - if models.iter().any(|value| value == preferred) { - return Some(preferred); - } - } - models.first().map(String::as_str) -} - /// Model IDs are supplied by the official installed OpenCode CLI, not a /// third-party proxy. A bounded local subprocess is never exposed through MCP. fn free_models() -> Result> { @@ -215,28 +206,26 @@ fn safe_opencode_config() -> Result { } fn opencode_chat(selected: &Options) -> Result<()> { + let model = selected.model.as_deref().unwrap_or(DEFAULT_CODE_MODEL); + if !is_free_model(model) { + bail!("coding starter only accepts official OpenCode -free model IDs"); + } let available = free_models()?; - let model = match &selected.model { - Some(model) if !is_free_model(model) => { - bail!("starter only allows official OpenCode model IDs ending in -free") - } - Some(model) if !available.contains(model) => { - bail!("model not in the current free catalog; run local-bridge agent models") - } - Some(model) => model.as_str(), - None => choose_default(&available).context( - "no -free models in current OpenCode catalog; try opencode models --refresh", - )?, - }; + if !available.iter().any(|listed| listed == model) { + bail!( + "preset coding model {model} is not in the current OpenCode free catalog. \ + No paid or random fallback. Use local-bridge agent models for diagnosis." + ); + } let directory = workspace(selected.directory.as_deref())?; eprintln!( - "Local Bridge > OpenCode: {model}\nWorkspace: {}\n\ - Messages may leave your device; provider quotas can change.\n\ + "Local Bridge > OpenCode coding (build mode): {model}\nWorkspace: {}\n\ + Free quotas and eligibility can change.\n\ Shell/file writes require local confirmation. No MCP remote execution.", directory.display() ); let status = Command::new("opencode") - .args(["--model", model, "--agent", "plan"]) + .args(["--model", model, "--agent", "build"]) .current_dir(directory) .env("OPENCODE_CONFIG_CONTENT", safe_opencode_config()?) .stdin(Stdio::inherit()) @@ -250,18 +239,35 @@ fn opencode_chat(selected: &Options) -> Result<()> { Ok(()) } +fn pi_args(custom_model: Option<&str>) -> Vec<&str> { + match custom_model { + Some(model) => vec!["--no-tools", "--model", model], + None => vec![ + "--no-tools", + "--provider", + DEFAULT_PI_PROVIDER, + "--model", + DEFAULT_PI_MODEL, + ], + } +} + fn pi_chat(selected: &Options) -> Result<()> { let directory = workspace(selected.directory.as_deref())?; + let custom_model = selected.model.as_deref(); + let shown_model = custom_model.unwrap_or("google/gemini-3.5-flash-lite"); eprintln!( - "Local Bridge > Pi (chat-only, no tools)\nWorkspace: {}\n\ - Use Pi /login for your own account. Model choice may be billable.", + "Local Bridge > Pi personal chat (no local tools)\n\ + Model: {shown_model}\nWorkspace: {}\n\ + On first use, enter your Google AI Studio free-tier key in Pi via /login google.\n\ + Free tier depends on regional eligibility, quotas and project billing settings.", directory.display() ); - let mut command = Command::new("pi"); - command.arg("--no-tools"); - if let Some(model) = selected.model.as_deref() { - command.args(["--model", model]); + if custom_model.is_some() { + eprintln!("Custom Pi model was explicitly requested; check its pricing."); } + let mut command = Command::new("pi"); + command.args(pi_args(custom_model)); let status = command .current_dir(directory) .stdin(Stdio::inherit()) @@ -276,20 +282,24 @@ fn pi_chat(selected: &Options) -> Result<()> { } fn doctor() { - let opencode = installed("opencode"); let pi = installed("pi"); + let opencode = installed("opencode"); println!("Local Bridge Personal Agent"); - println!("OpenCode installed: {opencode}"); - println!("Pi installed: {pi}"); - println!("Start: local-bridge agent"); - if !opencode || !pi { + println!("Pi installed (chat): {pi}"); + println!("OpenCode installed (optional coding): {opencode}"); + println!("Default chat: {DEFAULT_PI_PROVIDER}/{DEFAULT_PI_MODEL}"); + println!("Default coding: {DEFAULT_CODE_MODEL}"); + println!("No model picker. A free-tier Google API key is needed once for Pi."); + if !pi { install_guide(); + } else if !opencode { + eprintln!("For optional coding mode: https://opencode.ai/docs/"); } } pub fn run(args: &[String]) -> Result<()> { match args.split_first() { - None => opencode_chat(&Options::empty()), + None => pi_chat(&Options::empty()), Some((command, _)) if matches!(command.as_str(), "-h" | "--help" | "help") => { usage(); Ok(()) @@ -309,8 +319,12 @@ pub fn run(args: &[String]) -> Result<()> { } Ok(()) } - Some((command, rest)) if command == "opencode" => opencode_chat(&options(rest)?), - Some((command, rest)) if command == "pi" => pi_chat(&options(rest)?), + Some((command, rest)) if matches!(command.as_str(), "code" | "opencode") => { + opencode_chat(&options(rest)?) + } + Some((command, rest)) if matches!(command.as_str(), "chat" | "pi") => { + pi_chat(&options(rest)?) + } _ => { usage(); bail!("unknown Personal Agent command") @@ -331,11 +345,29 @@ mod tests { opencode/a-free --auto\n", ); assert_eq!(models.len(), 2); + assert_eq!(DEFAULT_CODE_MODEL, "opencode/deepseek-v4.1-flash-free"); + assert_eq!(DEFAULT_PI_PROVIDER, "google"); + assert_eq!(DEFAULT_PI_MODEL, "gemini-3.5-flash-lite"); + assert!(!is_free_model("opencode/mimo-v2.6-flash")); + } + + #[test] + fn pi_defaults_are_fixed_and_do_not_enable_tools() { assert_eq!( - choose_default(&models), - Some("opencode/mimo-v2.6-flash-free") + pi_args(None), + vec![ + "--no-tools", + "--provider", + "google", + "--model", + "gemini-3.5-flash-lite" + ] ); - assert!(!is_free_model("opencode/mimo-v2.6-flash")); + assert_eq!( + pi_args(Some("custom/model")), + vec!["--no-tools", "--model", "custom/model"] + ); + assert!(is_free_model(DEFAULT_CODE_MODEL)); } #[test] diff --git a/crates/local-bridge/src/main.rs b/crates/local-bridge/src/main.rs index 6eff2f0..cd5cc6e 100644 --- a/crates/local-bridge/src/main.rs +++ b/crates/local-bridge/src/main.rs @@ -49,7 +49,7 @@ fn run() -> Result<()> { } [] | [_] if args.first().is_none_or(|s| s == "--help" || s == "-h") => { println!( - "Local Bridge {}\nCommands: status | mcp | agent [doctor|models|opencode|pi] | validate-channel | --version", + "Local Bridge {}\nCommands: status | mcp | agent [chat|code|doctor|models] | validate-channel | --version", env!("CARGO_PKG_VERSION") ); } diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index e000546..b83c8d6 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -19,7 +19,10 @@ CLI in a user-controlled workspace. This is separate from the trust boundary of CloudFile and enterprise Channels; there is **no AI execution capability in MCP**. Model access and authentication remain in the vendor CLIs. No remote script execution, shell delegation through manifests or background server is added. -This launcher is not a full agent/chat runtime. +Pi chat and OpenCode coding can share an explicit user-selected folder but not +credentials, sessions, memories or privileges. The chosen free-tier models +require provider authorization and may be restricted by quotas, region or +project billing settings. This launcher is not a full agent/chat runtime. See [Personal Agent starter](PERSONAL_AGENT.md) for exact commands. diff --git a/docs/PERSONAL_AGENT.md b/docs/PERSONAL_AGENT.md index 8821a29..e0069d3 100644 --- a/docs/PERSONAL_AGENT.md +++ b/docs/PERSONAL_AGENT.md @@ -1,85 +1,82 @@ -# Personal Agent — quick start (experimental) +# Personal Agent quick start (Pi first, experimental) -Local Bridge can launch two **user-installed, official** Agent CLIs. This is a local, -opt-in convenience layer, **not** a hosted Personal Agent, social account, remote -shell, or MCP tool. +Local Bridge is a **launcher**, not another AI runtime. Users install official +Pi/OpenCode and keep their own provider accounts and data. Default models are +preselected: **no model-choice step** for ordinary users. -## Before first use +## 1. Install once -1. Download the portable `local-bridge` binary from a successful CI build for - your operating system, or run `cargo build -p local-bridge --release --locked` - with Rust 1.90+. Windows users can run the .exe from their user directory - without administrator privileges. -2. Install **OpenCode** via the official guide: - https://opencode.ai/docs/ . No bridge-managed installer or script download. -3. Open a *new terminal* so the CLI is on PATH. +1. Install the Local Bridge binary for your OS from a successful CI build, + or compile with Rust 1.90+. +2. Install **Pi**: https://pi.dev/docs/latest/quickstart +3. Optional **OpenCode** for programming: https://opencode.ai/docs/ +4. For Pi, get a **Google AI Studio API key** at + https://aistudio.google.com/api-keys. + Use an eligible **free-tier project with no paid billing enabled**. + In Pi, run `/login google` once and paste the key. **No model selection**. + The credential stays in Pi's own user profile; Local Bridge never reads, + stores or distributes keys. -```sh -local-bridge agent doctor -local-bridge agent models -local-bridge agent -``` - -`agent` opens the **official OpenCode terminal UI** in a private, separate -workspace (`~/.local-bridge/agent-workspace`; on Windows, under LOCALAPPDATA). -It selects a currently listed `opencode/*-free` model. If OpenCode has -removed the model, authentication or free quota has changed, the upstream CLI -may still reject the request. We do not bypass its login or free-tier restrictions. - -To choose an existing free model explicitly: +## 2. Use ```sh -local-bridge agent opencode --model opencode/mimo-v2.6-flash-free +local-bridge agent doctor +local-bridge agent # Pi, the default +local-bridge agent code # OpenCode, optional for programming ``` -That is **an example model ID**, not a promise that it is currently available. -Run `local-bridge agent models` first. Only model IDs returned by the installed -OpenCode catalog and ending with `-free` are accepted by this starter. - -### Pi Agent (optional) - -Install Pi via https://pi.dev/ (official installer). It is **not** automatically -installed and can be used without OpenCode: +| Role | Official CLI | Fixed default | Access | +|---|---|---|---| +| Personal chat | Pi | google/gemini-3.5-flash-lite | Google AI Studio free API tier; own API key | +| Coding | OpenCode | opencode/deepseek-v4.1-flash-free | Current -free catalog; eligibility may vary | -```sh -local-bridge agent pi -local-bridge agent pi --model opencode/mimo-v2.6-flash-free -``` +No model picker, automatic paid model fallback or impersonation of OpenCode. +`agent models` is a debugging command, not part of onboarding. Both programs +have an advanced opt-in `--model` override for experts; **that override may +be paid**. Do not use it for the default user experience. -The optional `--model` selection uses **Pi's own official provider and account -authentication**. It requires a supported /login or API key as appropriate: -a free model *price* is not proof that anonymous third-party API access works. -Pi is launched with `--no-tools` so it can chat without running local commands. -Depending on your Pi account/model, requests may be billable. Verify pricing in Pi. +If a model disappears or is unavailable to a region/project, startup reports +the provider error. No silent substitution. Any default change should be +explicitly reviewed, tested and shipped in an update. -For personal files, choose an explicit directory: +### Pi + OpenCode side by side ```sh -local-bridge agent opencode --dir /path/to/your/project -local-bridge agent pi --dir /path/to/your/project +local-bridge agent chat --dir /path/to/your/project +local-bridge agent code --dir /path/to/your/project ``` -A provided `--dir` must already exist. By default the launcher creates a -separate user-owned workspace and does not inspect the current enterprise work -directory. OpenCode uses the built-in `plan` Agent and asks the user before -shell/file writes; Pi has tools disabled completely. Both may send the messages -and user-selected content to their respective provider; do not put secrets in -prompts or select enterprise folders without authorization. - -## What this MVP does **not** do - -- Does not install/update Pi or OpenCode, reuse OpenCode internal private APIs, - impersonate its client, share its credential files, or automatically supply keys. -- Does not expose AI tool execution through the Local Bridge MCP or Channel manifest. - Existing `bridge.status` and `cloudfile.status` stay read-only. -- Does not add a localhost HTTP listener, UI, tray icon, autostart, shared - multi-user state, chat memory migration, or mobile/WeChat integration. -- Does not write CloudFile documents: those remain in CloudFile Agent2. - -Future work should add a reviewed user-approval task interface for native tools -*before* integrating full Pi/OpenCode tool execution. No silent remote execution. - -Sources: [OpenCode CLI](https://docs.opencode.ai/docs/cli/), -[OpenCode model/pricing listing](https://docs.opencode.ai/docs/zen/), -[Pi CLI and tool flags](https://pi.dev/docs/latest/cli), -[Pi providers/models](https://pi.dev/docs/latest/models). +Both use the same **user-selected directory** but retain separate sessions, +context and authentication. They do **not** automatically exchange prompts, +commit code or share long-term personal memories. + +Without `--dir`, the launcher uses a dedicated user workspace +`~/.local-bridge/agent-workspace` (Windows: below LOCALAPPDATA), not the +current enterprise directory. A provided `--dir` must already exist. + +## Safety and billing boundaries + +- Pi runs with **`--no-tools`**: built-in local file-write/shell tools are + disabled. Review any installed extensions. +- OpenCode uses the built-in **build** agent and asks before Bash/edit/external + directory actions. CLI extensions and model responses are still external + software and services. +- **Free API tier is not a universal no-billing guarantee**: eligibility, + account status, project billing settings and quotas matter. A paid project + can incur charges. Keep first-use projects unbilled. +- Google may use data submitted to the *free API tier* to improve its products. + Do not send confidential chats, sensitive personal records or enterprise + files to the free provider without informed consent. +- Local Bridge downloads no third-party binaries or login credentials. + It adds no remote AI scripts, HTTP listener or MCP execution tools. +- CloudFile checkout, file edits and versioning remain in Agent2; the bridge + cannot bypass its user permissions or journal. + +The next major UX milestone is a small GUI/tray + official installation flow, +**not** a new model selector or a second AI engine. + +Official references: +[Pi CLI](https://pi.dev/docs/latest/cli) · +[Pi provider setup](https://pi.dev/docs/latest/providers) · +[Google Gemini API pricing](https://ai.google.dev/gemini-api/docs/pricing) · +[OpenCode](https://opencode.ai/docs/). diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index db2c4cb..2b99b59 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -7,8 +7,12 @@ - Cross-platform CI for Rust. **0.1.x — opt-in Personal Agent pilot (without changing enterprise/MCP boundaries)** -- User-launched OpenCode free-model TUI with live model-ID discovery. -- Optional Pi CLI launcher; no authentication emulation or credential storage. +- **Pi as default** personal chat with a pinned Google Gemini free-tier model, + own Google AI Studio API key (no model picker). +- OpenCode as optional coding mode with pinned -free model. Catalog discovery + remains for diagnostics, not a randomly selected user-facing fallback. +- Both can share a user-selected working folder, but no automatic delegation. +- No authentication emulation, credential reading or storage. - Separate default workspace, non-admin portable binary in CI, explicit tool gates. - Follow-up only with evidence: task approval UI, native capability binding and persistent user identity are **not** delivered in this iteration. diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 0f795e6..cf15308 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -16,11 +16,19 @@ or command options, never installs a model CLI automatically, and never opens an HTTP listener. It starts only `pi` and `opencode` from the user's PATH. - Default Agent workspace is separate from the current enterprise directory. - OpenCode uses the built-in plan agent, with shell/edit/external access requiring + OpenCode uses the built-in build agent, with shell/edit/external access requiring user confirmation in runtime config; Pi uses `--no-tools`. These are not guarantees that a third-party CLI or installed extension cannot access data. Use only trusted, up-to-date binaries and review third-party plugins. -- OpenCode's `-free` catalog listing is not a billing, quota or uptime guarantee. +- **Pi is the default chat entry**, pinned to Google's Gemini 3.5 Flash-Lite + API model. Its free tier requires an eligible project and user API key; + account billing, region and quotas can change costs or access. Google's + free tier may use conversation inputs/outputs to improve its products. + No paid-model fallback. Do not send sensitive or enterprise data without + informed consent. +- OpenCode is a separate, optional coding mode pinned to a known `-free` + model, rather than a random discovered default. The `-free` catalog listing + is not a billing, quota or uptime guarantee. The Bridge never falls back silently to a paid model or borrows credentials from a different client's private login state. - The optional `--dir` path is provided by the local user only. Data shared in