@@ -23,13 +23,17 @@ pub struct LifecycleConfig {
2323}
2424
2525impl LifecycleConfig {
26- pub fn from_storage ( storage : & ConfigStorage , foreground : bool ) -> Result < Self > {
26+ pub fn from_storage (
27+ storage : & ConfigStorage ,
28+ foreground : bool ,
29+ capture_official : bool ,
30+ ) -> Result < Self > {
2731 let home = dirs:: home_dir ( ) . context ( "could not find home directory" ) ?;
2832 let cc_switch_dir = home. join ( ".cc-switch" ) ;
2933 std:: fs:: create_dir_all ( & cc_switch_dir)
3034 . with_context ( || format ! ( "failed to create {}" , cc_switch_dir. display( ) ) ) ?;
3135
32- let upstreams = dedupe_upstreams ( storage) ;
36+ let upstreams = dedupe_upstreams ( storage, capture_official ) ;
3337
3438 Ok ( Self {
3539 state_path : cc_switch_dir. join ( "daemon-state.json" ) ,
@@ -41,7 +45,7 @@ impl LifecycleConfig {
4145 }
4246}
4347
44- fn dedupe_upstreams ( storage : & ConfigStorage ) -> Vec < Upstream > {
48+ fn dedupe_upstreams ( storage : & ConfigStorage , capture_official : bool ) -> Vec < Upstream > {
4549 let mut seen = BTreeSet :: new ( ) ;
4650 let mut result = Vec :: new ( ) ;
4751 for config in storage. configurations . values ( ) {
@@ -53,15 +57,19 @@ fn dedupe_upstreams(storage: &ConfigStorage) -> Vec<Upstream> {
5357 result. push ( key) ;
5458 }
5559 }
56- // Always include the official Anthropic upstream so `cc use official`
57- // routes through the daemon. Dedup naturally handles the (rare) case
58- // where a user-defined alias points at the same URL.
59- let official = (
60- "claude" . to_string ( ) ,
61- crate :: daemon:: OFFICIAL_UPSTREAM . to_string ( ) ,
62- ) ;
63- if seen. insert ( official. clone ( ) ) {
64- result. push ( official) ;
60+ // The implicit official Anthropic upstream is opt-in: only spawn a proxy
61+ // for `cc use official` when the daemon was started with
62+ // `--capture-official`. By default official traffic flows direct to
63+ // Anthropic. A user-defined alias that points at the official URL is
64+ // handled above and deduped here, so it always gets its proxy.
65+ if capture_official {
66+ let official = (
67+ "claude" . to_string ( ) ,
68+ crate :: daemon:: OFFICIAL_UPSTREAM . to_string ( ) ,
69+ ) ;
70+ if seen. insert ( official. clone ( ) ) {
71+ result. push ( official) ;
72+ }
6573 }
6674 result
6775}
@@ -365,7 +373,7 @@ mod tests {
365373 "https://api.anthropic.com" ,
366374 "https://other.example.com/v1" ,
367375 ] ) ;
368- let result = dedupe_upstreams ( & storage) ;
376+ let result = dedupe_upstreams ( & storage, false ) ;
369377 assert_eq ! ( result. len( ) , 2 ) ;
370378 assert_eq ! ( result[ 0 ] . 1 , "https://api.anthropic.com" ) ;
371379 assert_eq ! ( result[ 1 ] . 1 , "https://other.example.com/v1" ) ;
@@ -374,7 +382,7 @@ mod tests {
374382 #[ test]
375383 fn dedupe_upstreams_skips_empty_urls ( ) {
376384 let storage = make_storage ( & [ "" , "https://api.anthropic.com" ] ) ;
377- let result = dedupe_upstreams ( & storage) ;
385+ let result = dedupe_upstreams ( & storage, false ) ;
378386 assert_eq ! ( result. len( ) , 1 ) ;
379387 assert_eq ! ( result[ 0 ] . 1 , "https://api.anthropic.com" ) ;
380388 }
@@ -395,23 +403,37 @@ mod tests {
395403 }
396404
397405 #[ test]
398- fn dedupe_upstreams_always_includes_official ( ) {
399- let result = dedupe_upstreams ( & make_storage ( & [ ] ) ) ;
406+ fn dedupe_upstreams_excludes_official_by_default ( ) {
407+ // Default: do NOT spawn the implicit official proxy. `cc use official`
408+ // traffic flows direct to Anthropic unless capture is explicitly enabled.
409+ let result = dedupe_upstreams ( & make_storage ( & [ ] ) , false ) ;
410+ assert ! (
411+ !result. contains( & (
412+ "claude" . to_string( ) ,
413+ crate :: daemon:: OFFICIAL_UPSTREAM . to_string( )
414+ ) ) ,
415+ "OFFICIAL_UPSTREAM must be absent by default, got {result:?}" ,
416+ ) ;
417+ }
418+
419+ #[ test]
420+ fn dedupe_upstreams_includes_official_when_capture_enabled ( ) {
421+ let result = dedupe_upstreams ( & make_storage ( & [ ] ) , true ) ;
400422 assert ! (
401423 result. contains( & (
402424 "claude" . to_string( ) ,
403425 crate :: daemon:: OFFICIAL_UPSTREAM . to_string( )
404426 ) ) ,
405- "OFFICIAL_UPSTREAM must always be in dedupe_upstreams output , got {result:?}" ,
427+ "OFFICIAL_UPSTREAM must be present when capture_official=true , got {result:?}" ,
406428 ) ;
407429 }
408430
409431 #[ test]
410432 fn dedupe_upstreams_dedupes_when_user_has_official_url ( ) {
411433 // Belt-and-suspenders: user shouldn't normally do this, but if they
412434 // configure an alias with the official URL, we must not spawn two
413- // proxies for the same URL.
414- let result = dedupe_upstreams ( & make_storage ( & [ crate :: daemon:: OFFICIAL_UPSTREAM ] ) ) ;
435+ // proxies for the same URL even with capture enabled .
436+ let result = dedupe_upstreams ( & make_storage ( & [ crate :: daemon:: OFFICIAL_UPSTREAM ] ) , true ) ;
415437 let count = result
416438 . iter ( )
417439 . filter ( |( _, url) | url == crate :: daemon:: OFFICIAL_UPSTREAM )
@@ -421,4 +443,19 @@ mod tests {
421443 "OFFICIAL_UPSTREAM must appear exactly once, got {result:?}"
422444 ) ;
423445 }
446+
447+ #[ test]
448+ fn dedupe_upstreams_keeps_user_official_alias_without_capture ( ) {
449+ // A user-defined alias explicitly pointing at the official URL still
450+ // gets its proxy even when implicit official capture is off.
451+ let result = dedupe_upstreams ( & make_storage ( & [ crate :: daemon:: OFFICIAL_UPSTREAM ] ) , false ) ;
452+ let count = result
453+ . iter ( )
454+ . filter ( |( _, url) | url == crate :: daemon:: OFFICIAL_UPSTREAM )
455+ . count ( ) ;
456+ assert_eq ! (
457+ count, 1 ,
458+ "user-defined official alias must still spawn its proxy, got {result:?}"
459+ ) ;
460+ }
424461}
0 commit comments