Skip to content

Commit 57a29b5

Browse files
authored
Merge pull request SeemSeam#125 from LeoLin990405/feat/multi-instance-provider
feat: support multiple instances of the same provider (SeemSeam#117)
2 parents 91d67d3 + 1f4e2f7 commit 57a29b5

23 files changed

Lines changed: 593 additions & 113 deletions

bin/ask

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ from compat import read_stdin_text, setup_windows_encoding
4646
setup_windows_encoding()
4747

4848
from cli_output import EXIT_ERROR, EXIT_OK
49+
from providers import parse_qualified_provider
4950
from session_utils import find_project_session_file
5051

5152

@@ -500,18 +501,21 @@ def main(argv: list[str]) -> int:
500501
return EXIT_ERROR
501502

502503
# First argument must be the provider
503-
provider = argv[1].lower()
504+
raw_provider = argv[1].lower()
504505

505-
if provider in ("-h", "--help"):
506+
if raw_provider in ("-h", "--help"):
506507
_usage()
507508
return EXIT_OK
508509

509-
if provider not in PROVIDER_DAEMONS:
510-
print(f"[ERROR] Unknown provider: {provider}", file=sys.stderr)
510+
base_provider, instance = parse_qualified_provider(raw_provider)
511+
512+
if base_provider not in PROVIDER_DAEMONS:
513+
print(f"[ERROR] Unknown provider: {base_provider}", file=sys.stderr)
511514
print(f"[ERROR] Available: {', '.join(PROVIDER_DAEMONS.keys())}", file=sys.stderr)
512515
return EXIT_ERROR
513516

514-
daemon_cmd = PROVIDER_DAEMONS[provider]
517+
daemon_cmd = PROVIDER_DAEMONS[base_provider]
518+
provider = raw_provider # keep full qualified key for daemon routing
515519

516520
# Parse remaining arguments
517521
timeout: float = 3600.0

lib/askd/adapters/base.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ class ProviderRequest:
2828
email_req_id: str = ""
2929
email_msg_id: str = ""
3030
email_from: str = ""
31+
# Multi-instance support: optional instance identifier (e.g., 'auth', 'payment')
32+
instance: Optional[str] = None
3133

3234

3335
@dataclass
@@ -93,12 +95,12 @@ def session_filename(self) -> str:
9395
...
9496

9597
@abstractmethod
96-
def load_session(self, work_dir: Path) -> Optional[Any]:
98+
def load_session(self, work_dir: Path, instance: Optional[str] = None) -> Optional[Any]:
9799
"""Load session for the given work directory."""
98100
...
99101

100102
@abstractmethod
101-
def compute_session_key(self, session: Any) -> str:
103+
def compute_session_key(self, session: Any, instance: Optional[str] = None) -> str:
102104
"""Compute a unique session key for routing."""
103105
...
104106

lib/askd/adapters/claude.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -487,20 +487,21 @@ def on_stop(self) -> None:
487487
except Exception:
488488
pass
489489

490-
def load_session(self, work_dir: Path) -> Optional[Any]:
491-
return load_project_session(work_dir)
490+
def load_session(self, work_dir: Path, instance: Optional[str] = None) -> Optional[Any]:
491+
return load_project_session(work_dir, instance)
492492

493-
def compute_session_key(self, session: Any) -> str:
494-
return compute_session_key(session) if session else "claude:unknown"
493+
def compute_session_key(self, session: Any, instance: Optional[str] = None) -> str:
494+
return compute_session_key(session, instance) if session else "claude:unknown"
495495

496496
def handle_task(self, task: QueuedTask) -> ProviderResult:
497497
started_ms = _now_ms()
498498
req = task.request
499499
work_dir = Path(req.work_dir)
500500
_write_log(f"[INFO] start provider=claude req_id={task.req_id} work_dir={req.work_dir}")
501501

502-
session = load_project_session(work_dir)
503-
session_key = self.compute_session_key(session)
502+
instance = task.request.instance
503+
session = load_project_session(work_dir, instance)
504+
session_key = self.compute_session_key(session, instance)
504505

505506
if not session:
506507
return ProviderResult(

lib/askd/adapters/codebuddy.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,21 @@ def spec(self):
6262
def session_filename(self) -> str:
6363
return ".codebuddy-session"
6464

65-
def load_session(self, work_dir: Path) -> Optional[Any]:
66-
return load_project_session(work_dir)
65+
def load_session(self, work_dir: Path, instance: Optional[str] = None) -> Optional[Any]:
66+
return load_project_session(work_dir, instance)
6767

68-
def compute_session_key(self, session: Any) -> str:
69-
return compute_session_key(session) if session else "codebuddy:unknown"
68+
def compute_session_key(self, session: Any, instance: Optional[str] = None) -> str:
69+
return compute_session_key(session, instance) if session else "codebuddy:unknown"
7070

7171
def handle_task(self, task: QueuedTask) -> ProviderResult:
7272
started_ms = _now_ms()
7373
req = task.request
7474
work_dir = Path(req.work_dir)
7575
_write_log(f"[INFO] start provider=codebuddy req_id={task.req_id} work_dir={req.work_dir}")
7676

77-
session = load_project_session(work_dir)
78-
session_key = self.compute_session_key(session)
77+
instance = task.request.instance
78+
session = load_project_session(work_dir, instance)
79+
session_key = self.compute_session_key(session, instance)
7980

8081
if not session:
8182
return ProviderResult(

lib/askd/adapters/codex.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,11 @@ def spec(self):
8383
def session_filename(self) -> str:
8484
return ".codex-session"
8585

86-
def load_session(self, work_dir: Path) -> Optional[CodexProjectSession]:
87-
return load_project_session(work_dir)
86+
def load_session(self, work_dir: Path, instance: Optional[str] = None) -> Optional[CodexProjectSession]:
87+
return load_project_session(work_dir, instance)
8888

89-
def compute_session_key(self, session: Any) -> str:
90-
return compute_session_key(session) if session else "codex:unknown"
89+
def compute_session_key(self, session: Any, instance: Optional[str] = None) -> str:
90+
return compute_session_key(session, instance) if session else "codex:unknown"
9191

9292
def handle_task(self, task: QueuedTask) -> ProviderResult:
9393
started_ms = _now_ms()
@@ -96,8 +96,9 @@ def handle_task(self, task: QueuedTask) -> ProviderResult:
9696
work_dir = Path(req.work_dir)
9797
_write_log(f"[INFO] start provider=codex req_id={task.req_id} work_dir={req.work_dir} caller={req.caller}")
9898

99-
session = load_project_session(work_dir)
100-
session_key = self.compute_session_key(session)
99+
instance = task.request.instance
100+
session = load_project_session(work_dir, instance)
101+
session_key = self.compute_session_key(session, instance)
101102

102103
if not session:
103104
return ProviderResult(

lib/askd/adapters/copilot.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,21 @@ def spec(self):
6262
def session_filename(self) -> str:
6363
return ".copilot-session"
6464

65-
def load_session(self, work_dir: Path) -> Optional[Any]:
66-
return load_project_session(work_dir)
65+
def load_session(self, work_dir: Path, instance: Optional[str] = None) -> Optional[Any]:
66+
return load_project_session(work_dir, instance)
6767

68-
def compute_session_key(self, session: Any) -> str:
69-
return compute_session_key(session) if session else "copilot:unknown"
68+
def compute_session_key(self, session: Any, instance: Optional[str] = None) -> str:
69+
return compute_session_key(session, instance) if session else "copilot:unknown"
7070

7171
def handle_task(self, task: QueuedTask) -> ProviderResult:
7272
started_ms = _now_ms()
7373
req = task.request
7474
work_dir = Path(req.work_dir)
7575
_write_log(f"[INFO] start provider=copilot req_id={task.req_id} work_dir={req.work_dir}")
7676

77-
session = load_project_session(work_dir)
78-
session_key = self.compute_session_key(session)
77+
instance = task.request.instance
78+
session = load_project_session(work_dir, instance)
79+
session_key = self.compute_session_key(session, instance)
7980

8081
if not session:
8182
return ProviderResult(

lib/askd/adapters/droid.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,21 @@ def spec(self):
6262
def session_filename(self) -> str:
6363
return ".droid-session"
6464

65-
def load_session(self, work_dir: Path) -> Optional[Any]:
66-
return load_project_session(work_dir)
65+
def load_session(self, work_dir: Path, instance: Optional[str] = None) -> Optional[Any]:
66+
return load_project_session(work_dir, instance)
6767

68-
def compute_session_key(self, session: Any) -> str:
69-
return compute_session_key(session) if session else "droid:unknown"
68+
def compute_session_key(self, session: Any, instance: Optional[str] = None) -> str:
69+
return compute_session_key(session, instance) if session else "droid:unknown"
7070

7171
def handle_task(self, task: QueuedTask) -> ProviderResult:
7272
started_ms = _now_ms()
7373
req = task.request
7474
work_dir = Path(req.work_dir)
7575
_write_log(f"[INFO] start provider=droid req_id={task.req_id} work_dir={req.work_dir}")
7676

77-
session = load_project_session(work_dir)
78-
session_key = self.compute_session_key(session)
77+
instance = task.request.instance
78+
session = load_project_session(work_dir, instance)
79+
session_key = self.compute_session_key(session, instance)
7980

8081
if not session:
8182
return ProviderResult(

lib/askd/adapters/gemini.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,20 +113,21 @@ def spec(self):
113113
def session_filename(self) -> str:
114114
return ".gemini-session"
115115

116-
def load_session(self, work_dir: Path) -> Optional[Any]:
117-
return load_project_session(work_dir)
116+
def load_session(self, work_dir: Path, instance: Optional[str] = None) -> Optional[Any]:
117+
return load_project_session(work_dir, instance)
118118

119-
def compute_session_key(self, session: Any) -> str:
120-
return compute_session_key(session) if session else "gemini:unknown"
119+
def compute_session_key(self, session: Any, instance: Optional[str] = None) -> str:
120+
return compute_session_key(session, instance) if session else "gemini:unknown"
121121

122122
def handle_task(self, task: QueuedTask) -> ProviderResult:
123123
started_ms = _now_ms()
124124
req = task.request
125125
work_dir = Path(req.work_dir)
126126
_write_log(f"[INFO] start provider=gemini req_id={task.req_id} work_dir={req.work_dir}")
127127

128-
session = load_project_session(work_dir)
129-
session_key = self.compute_session_key(session)
128+
instance = task.request.instance
129+
session = load_project_session(work_dir, instance)
130+
session_key = self.compute_session_key(session, instance)
130131

131132
if not session:
132133
return ProviderResult(

lib/askd/adapters/opencode.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ def spec(self):
5757
def session_filename(self) -> str:
5858
return ".opencode-session"
5959

60-
def load_session(self, work_dir: Path) -> Optional[Any]:
61-
return load_project_session(work_dir)
60+
def load_session(self, work_dir: Path, instance: Optional[str] = None) -> Optional[Any]:
61+
return load_project_session(work_dir, instance)
6262

63-
def compute_session_key(self, session: Any) -> str:
63+
def compute_session_key(self, session: Any, instance: Optional[str] = None) -> str:
6464
if not session:
6565
return "opencode:unknown"
6666
ccb_project_id = ""
@@ -70,16 +70,18 @@ def compute_session_key(self, session: Any) -> str:
7070
ccb_project_id = compute_ccb_project_id(Path(session.work_dir))
7171
except Exception:
7272
pass
73-
return f"opencode:{ccb_project_id}" if ccb_project_id else "opencode:unknown"
73+
prefix = f"opencode:{instance}" if instance else "opencode"
74+
return f"{prefix}:{ccb_project_id}" if ccb_project_id else f"{prefix}:unknown"
7475

7576
def handle_task(self, task: QueuedTask) -> ProviderResult:
7677
started_ms = _now_ms()
7778
req = task.request
7879
work_dir = Path(req.work_dir)
7980
_write_log(f"[INFO] start provider=opencode req_id={task.req_id} work_dir={req.work_dir}")
8081

81-
session = load_project_session(work_dir)
82-
session_key = self.compute_session_key(session)
82+
instance = task.request.instance
83+
session = load_project_session(work_dir, instance)
84+
session_key = self.compute_session_key(session, instance)
8385

8486
if not session:
8587
return ProviderResult(

lib/askd/adapters/qwen.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,21 @@ def spec(self):
6262
def session_filename(self) -> str:
6363
return ".qwen-session"
6464

65-
def load_session(self, work_dir: Path) -> Optional[Any]:
66-
return load_project_session(work_dir)
65+
def load_session(self, work_dir: Path, instance: Optional[str] = None) -> Optional[Any]:
66+
return load_project_session(work_dir, instance)
6767

68-
def compute_session_key(self, session: Any) -> str:
69-
return compute_session_key(session) if session else "qwen:unknown"
68+
def compute_session_key(self, session: Any, instance: Optional[str] = None) -> str:
69+
return compute_session_key(session, instance) if session else "qwen:unknown"
7070

7171
def handle_task(self, task: QueuedTask) -> ProviderResult:
7272
started_ms = _now_ms()
7373
req = task.request
7474
work_dir = Path(req.work_dir)
7575
_write_log(f"[INFO] start provider=qwen req_id={task.req_id} work_dir={req.work_dir}")
7676

77-
session = load_project_session(work_dir)
78-
session_key = self.compute_session_key(session)
77+
instance = task.request.instance
78+
session = load_project_session(work_dir, instance)
79+
session_key = self.compute_session_key(session, instance)
7980

8081
if not session:
8182
return ProviderResult(

0 commit comments

Comments
 (0)