feature/sigit-agent-profile
claude/github-issue-40-0au5ua
claude/linked-issue-acp-tui-u4vvpa
claude/minor-release-cut-vkugow
claude/sigit-acp-local-chat-cx6380
claude/sigit-cloud-agent-expansion-reox0a
claude/tool-permission-system
claude/zen-feynman-0u78dk
development
feature/acp-cancel-request
feature/acp-session-list-cursor
feature/acp-v1-conformance
feature/agent-tools-multiedit-glob-todos-remember
feature/cargo-workspace
feature/hooks
feature/load-local-model-explicitly
feature/sigit-agent-profile
feature/sigit-code-cloud
feature/xcode-mcp-server
fix/acp-replay-loaded-session-history
fix/acp-replay-reasoning-and-attachments
fix/dsml-doubled-bar-markers
main
release/v1.5.10
release/v1.5.6
release/v1.6.1
| 1 | //! `SiGitCode` served in-process, the way an app hosts it, against the testkit's mock endpoint: |
| 2 | //! the method is advertised, a signed-out session is refused, a stored session reaches the |
| 3 | //! endpoint as the bearer, and signing out takes it away again. |
| 4 | //! |
| 5 | //! One test function: the sign-in reads process-wide environment variables. |
| 6 | |
| 7 | use std::sync::Arc; |
| 8 | use std::sync::atomic::Ordering; |
| 9 | |
| 10 | use agent_client_protocol::schema::ProtocolVersion; |
| 11 | use agent_client_protocol::schema::v1::{ |
| 12 | ContentBlock, InitializeRequest, LogoutRequest, NewSessionRequest, PromptRequest, StopReason, |
| 13 | TextContent, |
| 14 | }; |
| 15 | use agent_client_protocol::{Agent, Channel, Client, ConnectionTo}; |
| 16 | use ed_acp::ServeOptions; |
| 17 | use ed_acp_testkit::{MockStats, Script, Step, start_mock_llm}; |
| 18 | use sigit_agent::{METHOD_ID, SiGitCode, credentials}; |
| 19 | |
| 20 | #[tokio::test] |
| 21 | async fn the_profile_signs_in_through_its_provider_and_talks_to_the_cloud() { |
| 22 | let dir = std::env::temp_dir().join(format!("sigit_agent_embedded_{}", std::process::id())); |
| 23 | let _ = std::fs::remove_dir_all(&dir); |
| 24 | std::fs::create_dir_all(&dir).unwrap(); |
| 25 | |
| 26 | let stats = Arc::new(MockStats::default()); |
| 27 | let url = start_mock_llm( |
| 28 | Script(vec![Step::Final("hello from the cloud")]), |
| 29 | stats.clone(), |
| 30 | ) |
| 31 | .await; |
| 32 | // SAFETY: this is the only test in the binary, so nothing else reads the environment. |
| 33 | unsafe { |
| 34 | std::env::set_var("SIGIT_CONFIG_DIR", dir.join("config")); |
| 35 | std::env::set_var("SIGIT_AGENT_DATA_DIR", dir.join("data")); |
| 36 | std::env::set_var("SIGIT_CLOUD_URL", &url); |
| 37 | std::env::set_var("SIGIT_AGENT_MODEL", ed_acp_testkit::MODEL); |
| 38 | // The mock is the only server in play. |
| 39 | std::env::set_var("SIGIT_MCP_OFFICIAL", "off"); |
| 40 | } |
| 41 | assert!(credentials::load().is_none()); |
| 42 | |
| 43 | let (agent_end, client_end) = Channel::duplex(); |
| 44 | let opts = ServeOptions { |
| 45 | yolo: false, |
| 46 | surface: "app", |
| 47 | }; |
| 48 | tokio::spawn(ed_acp::serve( |
| 49 | Arc::new(SiGitCode::without_shell()), |
| 50 | opts, |
| 51 | agent_end, |
| 52 | )); |
| 53 | let cwd = dir.clone(); |
| 54 | let seen = stats.clone(); |
| 55 | |
| 56 | Client |
| 57 | .builder() |
| 58 | .connect_with(client_end, |conn: ConnectionTo<Agent>| async move { |
| 59 | let init = conn |
| 60 | .send_request(InitializeRequest::new(ProtocolVersion::V1)) |
| 61 | .block_task() |
| 62 | .await?; |
| 63 | assert_eq!(init.auth_methods.len(), 1); |
| 64 | assert_eq!(&*init.auth_methods[0].id().0, METHOD_ID); |
| 65 | assert_eq!(init.agent_info.unwrap().name, "sigit-agent"); |
| 66 | |
| 67 | let refused = conn |
| 68 | .send_request(NewSessionRequest::new(cwd.clone())) |
| 69 | .block_task() |
| 70 | .await; |
| 71 | assert!(refused.is_err(), "signed out, so no session"); |
| 72 | |
| 73 | // What a finished browser sign-in leaves behind. |
| 74 | credentials::store(&credentials::Credentials { |
| 75 | // The mock accepts one bearer. |
| 76 | access_token: ed_acp_testkit::TEST_KEY.into(), |
| 77 | email: Some("dev@sigit.si".into()), |
| 78 | }) |
| 79 | .unwrap(); |
| 80 | |
| 81 | let session = conn |
| 82 | .send_request(NewSessionRequest::new(cwd.clone())) |
| 83 | .block_task() |
| 84 | .await?; |
| 85 | let reply = conn |
| 86 | .send_request(PromptRequest::new( |
| 87 | session.session_id.clone(), |
| 88 | vec![ContentBlock::Text(TextContent::new("hi"))], |
| 89 | )) |
| 90 | .block_task() |
| 91 | .await?; |
| 92 | assert_eq!(reply.stop_reason, StopReason::EndTurn); |
| 93 | assert!(*seen.saw_bearer.lock().unwrap()); |
| 94 | assert_eq!(seen.requests.load(Ordering::SeqCst), 1); |
| 95 | |
| 96 | // The model saw siGit Code's prompt and no run_command. |
| 97 | let payload = seen.payloads.lock().unwrap()[0].clone(); |
| 98 | let system = payload["messages"][0]["content"] |
| 99 | .as_str() |
| 100 | .unwrap() |
| 101 | .to_string(); |
| 102 | assert!(system.contains("siGit")); |
| 103 | assert!(system.contains("cannot run commands")); |
| 104 | let tools = payload["tools"].to_string(); |
| 105 | assert!(tools.contains("edit_file")); |
| 106 | assert!(!tools.contains("run_command")); |
| 107 | |
| 108 | conn.send_request(LogoutRequest::new()).block_task().await?; |
| 109 | assert!(credentials::load().is_none()); |
| 110 | let after = conn |
| 111 | .send_request(NewSessionRequest::new(cwd.clone())) |
| 112 | .block_task() |
| 113 | .await; |
| 114 | assert!(after.is_err(), "signed out again"); |
| 115 | Ok(()) |
| 116 | }) |
| 117 | .await |
| 118 | .expect("conversation"); |
| 119 | |
| 120 | let _ = std::fs::remove_dir_all(&dir); |
| 121 | } |