code-analysis-client-dev 1.6.341__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,728 @@
1
+ """
2
+ Async façade over mcp-proxy-adapter JsonRpcClient for code-analysis-server.
3
+
4
+ Author: Vasiliy Zdanovskiy
5
+ email: vasilyvz@gmail.com
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ import warnings
12
+ from pathlib import Path
13
+ from typing import Any, Dict, Literal, Mapping, Optional, Union, overload
14
+
15
+ try:
16
+ from mcp_proxy_adapter.client.jsonrpc_client.client import JsonRpcClient
17
+ except Exception: # pragma: no cover - compatibility with older layouts
18
+ from mcp_proxy_adapter.client.jsonrpc_client import JsonRpcClient
19
+ from mcp_proxy_adapter.client.jsonrpc_client.exceptions import (
20
+ ClientRemoteResponseError,
21
+ CommandInnerFailureError,
22
+ CommandSessionFailedError,
23
+ CommandSessionTimeoutError,
24
+ )
25
+
26
+ from code_analysis_client.commands_proxy import ValidatedCommandsProxy
27
+ from code_analysis_client.config import (
28
+ adapter_settings_from_server_config,
29
+ adapter_settings_to_jsonrpc_kwargs,
30
+ load_server_config,
31
+ merged_client_ws_config,
32
+ )
33
+ from code_analysis_client.file_session import FileSessionClient
34
+ from code_analysis_client.check_chain import CheckChainClient
35
+ from code_analysis_client.project import ProjectClient
36
+ from code_analysis_client.worktree import WorktreeClient
37
+ from code_analysis_client.queue_wait import (
38
+ QueuedJob,
39
+ StatusHook,
40
+ check_wait_arguments,
41
+ invalid_params_error,
42
+ map_adapter_error,
43
+ result_from_unified,
44
+ validation_error_code_for_command,
45
+ )
46
+ from code_analysis_client.universal_file import UniversalFileClient
47
+ from code_analysis_client.schema_disk_cache import (
48
+ clear_cached_schemas,
49
+ load_cached_schema,
50
+ store_cached_schema,
51
+ )
52
+ from code_analysis_client.server_schema import fetch_command_schema_from_server
53
+ from code_analysis_client.exceptions import ClientValidationError
54
+ from code_analysis_client.symbol_tree import SymbolTreeNode
55
+ from code_analysis_client.validation import (
56
+ prepare_params_for_schema,
57
+ validate_params_against_schema,
58
+ )
59
+
60
+ _SYMBOL_TREE_OMITTED: Any = object()
61
+
62
+
63
+ def _remote_invalid_params_as_client_validation(
64
+ error: ClientRemoteResponseError, command: str,
65
+ ) -> ClientValidationError | None:
66
+ """Convert the adapter's JSON-RPC invalid-params response for client callers.
67
+
68
+ ``JsonRpcClient.execute_command()`` raises ``ClientRemoteResponseError``
69
+ for every top-level JSON-RPC error. Code-analysis validation errors use
70
+ the standard ``-32602`` code, however, and callers of this package already
71
+ handle local schema failures as ``ClientValidationError``. Preserve the
72
+ adapter's complete message and the server's validation payload while
73
+ presenting both local and remote invalid-parameter failures through that
74
+ one client contract.
75
+ """
76
+ details = error.details
77
+ if details.get("code") != -32602:
78
+ return None
79
+
80
+ # Generic commands retain the long-standing client API code. symbol_tree
81
+ # is the mapped exception: its public contract exposes the JSON-RPC code.
82
+ code = validation_error_code_for_command(command)
83
+ return invalid_params_error(str(error), details.get("data"), code=code)
84
+
85
+
86
+ class CodeAnalysisAsyncClient:
87
+ """Async client: domain commands via :meth:`call` / :meth:`call_unified`; full adapter API on :attr:`rpc`."""
88
+
89
+ __slots__ = ("_rpc", "_command_schema_cache", "_commands_proxy")
90
+
91
+ def __init__(self, **jsonrpc_kwargs: Any) -> None:
92
+ """Same keyword arguments as ``JsonRpcClient`` (``protocol``, ``host``, ``port``, ``cert``, …).
93
+
94
+ ``ws_config`` (``{"websocket": {...}}``) is merged over
95
+ :data:`code_analysis_client.config.DEFAULT_CLIENT_ADAPTER_CONFIG` key by key, so the
96
+ adapter's wait timeouts and ``on_timeout`` policy are always explicit (never the adapter's
97
+ 60 s / cancel constants).
98
+ """
99
+ jsonrpc_kwargs["ws_config"] = merged_client_ws_config(
100
+ jsonrpc_kwargs.get("ws_config")
101
+ )
102
+ self._rpc = JsonRpcClient(**jsonrpc_kwargs)
103
+ self._command_schema_cache: Dict[str, Dict[str, Any]] = {}
104
+ self._commands_proxy: Optional[ValidatedCommandsProxy] = None
105
+
106
+ @classmethod
107
+ def from_jsonrpc_kwargs(cls, **kwargs: Any) -> CodeAnalysisAsyncClient:
108
+ """Alias for ``CodeAnalysisAsyncClient(**kwargs)``."""
109
+ return cls(**kwargs)
110
+
111
+ @classmethod
112
+ def from_adapter_settings(
113
+ cls,
114
+ settings: Mapping[str, Any],
115
+ *,
116
+ timeout: float | None = 60.0,
117
+ check_hostname: bool = False,
118
+ token_header: str | None = None,
119
+ token: str | None = None,
120
+ ) -> CodeAnalysisAsyncClient:
121
+ """Build from a dict like ``PipelineConfig.generate_adapter_client_settings()``."""
122
+ kwargs = adapter_settings_to_jsonrpc_kwargs(
123
+ settings,
124
+ timeout=timeout,
125
+ check_hostname=check_hostname,
126
+ token_header=token_header,
127
+ token=token,
128
+ )
129
+ return cls(**kwargs)
130
+
131
+ @classmethod
132
+ def from_server_config(
133
+ cls,
134
+ config: Mapping[str, Any],
135
+ *,
136
+ timeout: float | None = 60.0,
137
+ check_hostname: bool = False,
138
+ token_header: str | None = None,
139
+ token: str | None = None,
140
+ ) -> CodeAnalysisAsyncClient:
141
+ """Derive connection settings from a code-analysis server config object."""
142
+ settings = adapter_settings_from_server_config(config)
143
+ return cls.from_adapter_settings(
144
+ settings,
145
+ timeout=timeout,
146
+ check_hostname=check_hostname,
147
+ token_header=token_header,
148
+ token=token,
149
+ )
150
+
151
+ @classmethod
152
+ def from_server_config_path(
153
+ cls,
154
+ path: str | Path,
155
+ *,
156
+ timeout: float | None = 60.0,
157
+ check_hostname: bool = False,
158
+ token_header: str | None = None,
159
+ token: str | None = None,
160
+ ) -> CodeAnalysisAsyncClient:
161
+ """Load ``config.json`` and connect."""
162
+ return cls.from_server_config(
163
+ load_server_config(path),
164
+ timeout=timeout,
165
+ check_hostname=check_hostname,
166
+ token_header=token_header,
167
+ token=token,
168
+ )
169
+
170
+ @property
171
+ def rpc(self) -> JsonRpcClient:
172
+ """Underlying mcp-proxy-adapter client (queue, transfer, ``help``, ``execute_command``, …)."""
173
+ return self._rpc
174
+
175
+ @property
176
+ def commands(self) -> ValidatedCommandsProxy:
177
+ """Dynamic validated wrappers: schema is loaded from the server via ``help`` (cached)."""
178
+ if self._commands_proxy is None:
179
+ self._commands_proxy = ValidatedCommandsProxy(self)
180
+ return self._commands_proxy
181
+
182
+ def clear_command_schema_cache(self) -> None:
183
+ """Drop cached command schemas (after ``reload`` or when server definitions change).
184
+
185
+ Clears both the in-memory cache and this server's on-disk schema
186
+ cache (:mod:`code_analysis_client.schema_disk_cache`, bug 8e6acb34) —
187
+ otherwise the very next call would just repopulate the in-memory
188
+ cache from a now-stale disk entry, defeating the point of clearing it.
189
+ """
190
+ self._command_schema_cache.clear()
191
+ clear_cached_schemas(self._rpc.base_url)
192
+
193
+ async def symbol_tree(
194
+ self,
195
+ *,
196
+ project_id: str,
197
+ file_path: str | None = _SYMBOL_TREE_OMITTED,
198
+ source: str | None = _SYMBOL_TREE_OMITTED,
199
+ source_name: str | None = _SYMBOL_TREE_OMITTED,
200
+ worktree_name: str | None = _SYMBOL_TREE_OMITTED,
201
+ options: dict[str, object] | None = _SYMBOL_TREE_OMITTED,
202
+ ) -> list[SymbolTreeNode]:
203
+ """Read declarations without saving raw source or changing project state.
204
+
205
+ Select exactly one of ``file_path`` and ``source``. Raw source requires
206
+ ``source_name``; its extension selects the plugin. Domain failures retain
207
+ the normal ``CommandFailedError`` result; validation uses
208
+ ``ClientValidationError``.
209
+ """
210
+ params: dict[str, Any] = {"project_id": project_id}
211
+ for key, value in (
212
+ ("file_path", file_path),
213
+ ("source", source),
214
+ ("source_name", source_name),
215
+ ("worktree_name", worktree_name),
216
+ ("options", options),
217
+ ):
218
+ if value is not _SYMBOL_TREE_OMITTED:
219
+ params[key] = value
220
+ result = await self.call("symbol_tree", params)
221
+ data = result.get("data")
222
+ if not isinstance(data, list):
223
+ raise RuntimeError("symbol_tree returned a non-list result")
224
+ return data
225
+
226
+ @property
227
+ def file_sessions(self) -> FileSessionClient:
228
+ """Session workflow: ``session_*``, ``subordinate_session_*``, transfer, advisory locks."""
229
+ return FileSessionClient(self)
230
+
231
+ @property
232
+ def projects(self) -> ProjectClient:
233
+ """Project commands outside the session workflow (documentation-server override)."""
234
+ return ProjectClient(self)
235
+
236
+ @property
237
+ def worktrees(self) -> WorktreeClient:
238
+ """Worktree management: ``worktree_*`` commands."""
239
+ return WorktreeClient(self)
240
+
241
+ @property
242
+ def check_chains(self) -> CheckChainClient:
243
+ """Check-chain registry and promote: ``check_chain_*``, ``file_check_and_promote``."""
244
+ return CheckChainClient(self)
245
+
246
+ @property
247
+ def universal_files(self) -> UniversalFileClient:
248
+ """Read-only structured preview (``universal_file_preview`` command)."""
249
+ return UniversalFileClient(self)
250
+
251
+ async def get_command_schema(
252
+ self, command: str, *, refresh: bool = False
253
+ ) -> Dict[str, Any]:
254
+ """Fetch input JSON schema for ``command`` using server ``help``.
255
+
256
+ Checked in order (bug 8e6acb34, Fix 1): the in-memory per-instance
257
+ cache, then a bounded-TTL on-disk cache shared across process
258
+ invocations against the same server
259
+ (:mod:`code_analysis_client.schema_disk_cache`) — this is what
260
+ removes the ~2.7ms ``help`` round trip that a fresh client instance
261
+ would otherwise pay on every first use of a given command, since most
262
+ real callers are short-lived processes that never benefit from an
263
+ in-memory-only cache. ``refresh=True`` skips both caches and always
264
+ re-fetches from the server, repopulating them afterward.
265
+ """
266
+ if not refresh and command in self._command_schema_cache:
267
+ return self._command_schema_cache[command]
268
+ if not refresh:
269
+ cached = load_cached_schema(self._rpc.base_url, command)
270
+ if cached is not None:
271
+ self._command_schema_cache[command] = cached
272
+ return cached
273
+ schema = await fetch_command_schema_from_server(self._rpc, command)
274
+ self._command_schema_cache[command] = schema
275
+ store_cached_schema(self._rpc.base_url, command, schema)
276
+ return schema
277
+
278
+ async def _execute(
279
+ self,
280
+ command: str,
281
+ params: Optional[Dict[str, Any]] = None,
282
+ *,
283
+ use_cmd_endpoint: bool = False,
284
+ auto_poll: bool = True,
285
+ timeout: Optional[float] = None,
286
+ poll_interval: Optional[float] = None,
287
+ status_hook: Optional[StatusHook] = None,
288
+ ) -> Union[Dict[str, Any], QueuedJob]:
289
+ """Single core every public entry point routes through: the adapter's standard call.
290
+
291
+ Runs ``command`` through ``JsonRpcClient.execute_command_unified`` (WebSocket command
292
+ session, event-driven wait inside the call, adapter RPC-polling fallback with a logged
293
+ WARNING when ``/ws`` is unreachable). Nothing here polls.
294
+
295
+ * ``auto_poll=True`` (default) - waits for the command and returns its inner result
296
+ envelope. ``timeout=None`` resolves to the client config
297
+ ``websocket.job_wait_timeout`` (shipped ``0`` = no bound); a positive ``timeout`` raises
298
+ :class:`~code_analysis_client.exceptions.JobTimeoutError` and detaches (the job keeps
299
+ running, ``websocket.on_timeout`` = ``detach``). ``poll_interval=None`` resolves to
300
+ ``websocket.terminal_poll_interval``. ``status_hook`` receives the adapter's WebSocket
301
+ events (accepted ... terminal); an exception raised by the hook propagates.
302
+ * ``auto_poll=False`` - detaches as soon as the job id is known and returns a
303
+ :class:`~code_analysis_client.queue_wait.QueuedJob` for EVERY command (also a formerly
304
+ immediate one); ``timeout``, ``poll_interval`` and ``status_hook`` are ignored.
305
+
306
+ An inner ``success=false`` raises
307
+ :class:`~code_analysis_client.exceptions.CommandFailedError` (never returned as a dict);
308
+ a queued job that ended failed raises
309
+ :class:`~code_analysis_client.exceptions.CommandFailedError` with the server error code,
310
+ message and data (same as an immediate error); a job that was stopped or cancelled raises
311
+ :class:`~code_analysis_client.exceptions.JobFailedError`; a server -32602 refusal raises
312
+ :class:`~code_analysis_client.exceptions.ClientValidationError`. A negative ``timeout`` or
313
+ a non-positive ``poll_interval`` raises ``ValueError`` before anything is sent.
314
+
315
+ ``use_cmd_endpoint`` remains accepted-and-ignored (documented no-op, kept only for
316
+ signature compatibility; the legacy ``/cmd`` route was removed in mcp-proxy-adapter 8.10.32).
317
+ """
318
+ check_wait_arguments(timeout, poll_interval)
319
+ waiting = bool(auto_poll)
320
+ try:
321
+ envelope = await self._rpc.execute_command_unified(
322
+ command,
323
+ params or {},
324
+ auto_poll=waiting,
325
+ timeout=timeout if waiting else None,
326
+ terminal_poll_interval=poll_interval if waiting else None,
327
+ status_hook=status_hook if waiting else None,
328
+ raise_on_inner_failure=True,
329
+ )
330
+ except (
331
+ ClientRemoteResponseError,
332
+ CommandSessionTimeoutError,
333
+ CommandSessionFailedError,
334
+ CommandInnerFailureError,
335
+ asyncio.TimeoutError,
336
+ ) as exc:
337
+ mapped = map_adapter_error(exc, command, timeout)
338
+ error: BaseException = exc if mapped is None else mapped
339
+ if isinstance(error, ClientRemoteResponseError):
340
+ validation_error = _remote_invalid_params_as_client_validation(
341
+ error, command
342
+ )
343
+ if validation_error is not None:
344
+ raise validation_error from exc
345
+ if error is exc:
346
+ raise
347
+ raise error from exc
348
+ if not waiting:
349
+ job_id = envelope.get("job_id")
350
+ if job_id:
351
+ return QueuedJob(
352
+ job_id=str(job_id),
353
+ envelope=envelope,
354
+ rpc=self._rpc,
355
+ command=command,
356
+ )
357
+ # /ws unreachable and the command ran without a job: it already executed.
358
+ return QueuedJob(
359
+ job_id="",
360
+ envelope=envelope,
361
+ rpc=self._rpc,
362
+ command=command,
363
+ resolved=result_from_unified(command, envelope),
364
+ )
365
+ return result_from_unified(command, envelope)
366
+
367
+ async def _execute_validated(
368
+ self,
369
+ command: str,
370
+ params: Dict[str, Any],
371
+ *,
372
+ use_cmd_endpoint: bool,
373
+ auto_poll: bool,
374
+ timeout: Optional[float],
375
+ poll_interval: Optional[float],
376
+ status_hook: Optional[StatusHook],
377
+ ) -> Union[Dict[str, Any], QueuedJob]:
378
+ """Execute after schema validation and reconcile a stale command catalog.
379
+
380
+ A cached schema can outlive a deployment that removes its command. If
381
+ execution reports JSON-RPC ``-32601``, discard the cached catalog view
382
+ and ask live ``help`` once. A command still present keeps the original
383
+ remote error; an absent command surfaces ``help``'s
384
+ :class:`ClientValidationError` with the remote response as its cause.
385
+ """
386
+ try:
387
+ return await self._execute(
388
+ command,
389
+ params,
390
+ use_cmd_endpoint=use_cmd_endpoint,
391
+ auto_poll=auto_poll,
392
+ timeout=timeout,
393
+ poll_interval=poll_interval,
394
+ status_hook=status_hook,
395
+ )
396
+ except ClientRemoteResponseError as exc:
397
+ if exc.details.get("code") != -32601:
398
+ raise
399
+ self.clear_command_schema_cache()
400
+ try:
401
+ await self.get_command_schema(command, refresh=True)
402
+ except ClientValidationError as schema_error:
403
+ raise schema_error from exc
404
+ raise
405
+
406
+ @overload
407
+ async def call_validated(
408
+ self,
409
+ command: str,
410
+ params: Optional[Dict[str, Any]] = None,
411
+ *,
412
+ use_cmd_endpoint: bool = False,
413
+ refresh_schema: bool = False,
414
+ auto_poll: Literal[True] = True,
415
+ timeout: Optional[float] = None,
416
+ poll_interval: Optional[float] = None,
417
+ status_hook: Optional[StatusHook] = None,
418
+ ) -> Dict[str, Any]: ...
419
+
420
+ @overload
421
+ async def call_validated(
422
+ self,
423
+ command: str,
424
+ params: Optional[Dict[str, Any]] = None,
425
+ *,
426
+ use_cmd_endpoint: bool = False,
427
+ refresh_schema: bool = False,
428
+ auto_poll: Literal[False],
429
+ timeout: Optional[float] = None,
430
+ poll_interval: Optional[float] = None,
431
+ status_hook: Optional[StatusHook] = None,
432
+ ) -> QueuedJob: ...
433
+
434
+ @overload
435
+ async def call_validated(
436
+ self,
437
+ command: str,
438
+ params: Optional[Dict[str, Any]] = None,
439
+ *,
440
+ use_cmd_endpoint: bool = False,
441
+ refresh_schema: bool = False,
442
+ auto_poll: bool,
443
+ timeout: Optional[float] = None,
444
+ poll_interval: Optional[float] = None,
445
+ status_hook: Optional[StatusHook] = None,
446
+ ) -> Union[Dict[str, Any], QueuedJob]: ...
447
+
448
+ async def call_validated(
449
+ self,
450
+ command: str,
451
+ params: Optional[Dict[str, Any]] = None,
452
+ *,
453
+ use_cmd_endpoint: bool = False,
454
+ refresh_schema: bool = False,
455
+ auto_poll: bool = True,
456
+ timeout: Optional[float] = None,
457
+ poll_interval: Optional[float] = None,
458
+ status_hook: Optional[StatusHook] = None,
459
+ ) -> Union[Dict[str, Any], QueuedJob]:
460
+ """``help`` → schema on server, shallow local validation, then the queue-aware core.
461
+
462
+ ``auto_poll`` (default ``True``) forwards to :meth:`_execute`: set it
463
+ to ``False`` to get a :class:`~code_analysis_client.queue_wait.QueuedJob`
464
+ handle back instead of blocking until a queued job completes.
465
+ """
466
+ schema = await self.get_command_schema(command, refresh=refresh_schema)
467
+ merged = dict(params or {})
468
+ prepared = prepare_params_for_schema(merged, schema)
469
+ validate_params_against_schema(prepared, schema, command_name=command)
470
+ return await self._execute_validated(
471
+ command,
472
+ prepared,
473
+ use_cmd_endpoint=use_cmd_endpoint,
474
+ auto_poll=auto_poll,
475
+ timeout=timeout,
476
+ poll_interval=poll_interval,
477
+ status_hook=status_hook,
478
+ )
479
+
480
+ @overload
481
+ async def call_unified_validated(
482
+ self,
483
+ command: str,
484
+ params: Optional[Dict[str, Any]] = None,
485
+ *,
486
+ refresh_schema: bool = False,
487
+ use_cmd_endpoint: bool = False,
488
+ expect_queue: Optional[bool] = None,
489
+ auto_poll: Literal[True] = True,
490
+ poll_interval: Optional[float] = None,
491
+ timeout: Optional[float] = None,
492
+ status_hook: Optional[StatusHook] = None,
493
+ ) -> Dict[str, Any]: ...
494
+
495
+ @overload
496
+ async def call_unified_validated(
497
+ self,
498
+ command: str,
499
+ params: Optional[Dict[str, Any]] = None,
500
+ *,
501
+ refresh_schema: bool = False,
502
+ use_cmd_endpoint: bool = False,
503
+ expect_queue: Optional[bool] = None,
504
+ auto_poll: Literal[False],
505
+ poll_interval: Optional[float] = None,
506
+ timeout: Optional[float] = None,
507
+ status_hook: Optional[StatusHook] = None,
508
+ ) -> QueuedJob: ...
509
+
510
+ @overload
511
+ async def call_unified_validated(
512
+ self,
513
+ command: str,
514
+ params: Optional[Dict[str, Any]] = None,
515
+ *,
516
+ refresh_schema: bool = False,
517
+ use_cmd_endpoint: bool = False,
518
+ expect_queue: Optional[bool] = None,
519
+ auto_poll: bool,
520
+ poll_interval: Optional[float] = None,
521
+ timeout: Optional[float] = None,
522
+ status_hook: Optional[StatusHook] = None,
523
+ ) -> Union[Dict[str, Any], QueuedJob]: ...
524
+
525
+ async def call_unified_validated(
526
+ self,
527
+ command: str,
528
+ params: Optional[Dict[str, Any]] = None,
529
+ *,
530
+ refresh_schema: bool = False,
531
+ use_cmd_endpoint: bool = False,
532
+ expect_queue: Optional[bool] = None,
533
+ auto_poll: bool = True,
534
+ poll_interval: Optional[float] = None,
535
+ timeout: Optional[float] = None,
536
+ status_hook: Optional[StatusHook] = None,
537
+ ) -> Union[Dict[str, Any], QueuedJob]:
538
+ """Deprecated alias for :meth:`call_validated`.
539
+
540
+ ``expect_queue`` remains accepted-and-ignored (documented no-op, kept
541
+ only for signature compatibility). ``auto_poll`` is now LIVE again and
542
+ forwards straight to the shared :meth:`_execute` core: ``auto_poll=False``
543
+ returns a :class:`~code_analysis_client.queue_wait.QueuedJob` handle
544
+ instead of a plain dict when the response is a queued envelope — a
545
+ return-type change versus the previous era where this parameter was
546
+ silently ignored and every path always blocked until completion.
547
+ Prefer :meth:`call_validated` directly; this alias emits
548
+ ``DeprecationWarning`` on every call.
549
+ """
550
+ warnings.warn(
551
+ "call_unified_validated is deprecated; use call_validated() "
552
+ "directly (its 'auto_poll' keyword works the same way).",
553
+ DeprecationWarning,
554
+ stacklevel=2,
555
+ )
556
+ schema = await self.get_command_schema(command, refresh=refresh_schema)
557
+ merged = dict(params or {})
558
+ prepared = prepare_params_for_schema(merged, schema)
559
+ validate_params_against_schema(prepared, schema, command_name=command)
560
+ return await self._execute_validated(
561
+ command,
562
+ prepared,
563
+ use_cmd_endpoint=use_cmd_endpoint,
564
+ auto_poll=auto_poll,
565
+ timeout=timeout,
566
+ poll_interval=poll_interval,
567
+ status_hook=status_hook,
568
+ )
569
+
570
+ @overload
571
+ async def call(
572
+ self,
573
+ command: str,
574
+ params: Optional[Dict[str, Any]] = None,
575
+ *,
576
+ use_cmd_endpoint: bool = False,
577
+ auto_poll: Literal[True] = True,
578
+ timeout: Optional[float] = None,
579
+ poll_interval: Optional[float] = None,
580
+ status_hook: Optional[StatusHook] = None,
581
+ ) -> Dict[str, Any]: ...
582
+
583
+ @overload
584
+ async def call(
585
+ self,
586
+ command: str,
587
+ params: Optional[Dict[str, Any]] = None,
588
+ *,
589
+ use_cmd_endpoint: bool = False,
590
+ auto_poll: Literal[False],
591
+ timeout: Optional[float] = None,
592
+ poll_interval: Optional[float] = None,
593
+ status_hook: Optional[StatusHook] = None,
594
+ ) -> QueuedJob: ...
595
+
596
+ @overload
597
+ async def call(
598
+ self,
599
+ command: str,
600
+ params: Optional[Dict[str, Any]] = None,
601
+ *,
602
+ use_cmd_endpoint: bool = False,
603
+ auto_poll: bool,
604
+ timeout: Optional[float] = None,
605
+ poll_interval: Optional[float] = None,
606
+ status_hook: Optional[StatusHook] = None,
607
+ ) -> Union[Dict[str, Any], QueuedJob]: ...
608
+
609
+ async def call(
610
+ self,
611
+ command: str,
612
+ params: Optional[Dict[str, Any]] = None,
613
+ *,
614
+ use_cmd_endpoint: bool = False,
615
+ auto_poll: bool = True,
616
+ timeout: Optional[float] = None,
617
+ poll_interval: Optional[float] = None,
618
+ status_hook: Optional[StatusHook] = None,
619
+ ) -> Union[Dict[str, Any], QueuedJob]:
620
+ """Run any registered server command; the adapter's standard call waits for it by default.
621
+
622
+ Pass ``auto_poll=False`` to get a
623
+ :class:`~code_analysis_client.queue_wait.QueuedJob` handle back
624
+ immediately for any command (call ``await handle.wait(...)`` when
625
+ you're ready to block on it). See :meth:`_execute` for the full contract.
626
+ """
627
+ return await self._execute(
628
+ command,
629
+ params,
630
+ use_cmd_endpoint=use_cmd_endpoint,
631
+ auto_poll=auto_poll,
632
+ timeout=timeout,
633
+ poll_interval=poll_interval,
634
+ status_hook=status_hook,
635
+ )
636
+
637
+ @overload
638
+ async def call_unified(
639
+ self,
640
+ command: str,
641
+ params: Optional[Dict[str, Any]] = None,
642
+ *,
643
+ use_cmd_endpoint: bool = False,
644
+ expect_queue: Optional[bool] = None,
645
+ auto_poll: Literal[True] = True,
646
+ poll_interval: Optional[float] = None,
647
+ timeout: Optional[float] = None,
648
+ status_hook: Optional[StatusHook] = None,
649
+ ) -> Dict[str, Any]: ...
650
+
651
+ @overload
652
+ async def call_unified(
653
+ self,
654
+ command: str,
655
+ params: Optional[Dict[str, Any]] = None,
656
+ *,
657
+ use_cmd_endpoint: bool = False,
658
+ expect_queue: Optional[bool] = None,
659
+ auto_poll: Literal[False],
660
+ poll_interval: Optional[float] = None,
661
+ timeout: Optional[float] = None,
662
+ status_hook: Optional[StatusHook] = None,
663
+ ) -> QueuedJob: ...
664
+
665
+ @overload
666
+ async def call_unified(
667
+ self,
668
+ command: str,
669
+ params: Optional[Dict[str, Any]] = None,
670
+ *,
671
+ use_cmd_endpoint: bool = False,
672
+ expect_queue: Optional[bool] = None,
673
+ auto_poll: bool,
674
+ poll_interval: Optional[float] = None,
675
+ timeout: Optional[float] = None,
676
+ status_hook: Optional[StatusHook] = None,
677
+ ) -> Union[Dict[str, Any], QueuedJob]: ...
678
+
679
+ async def call_unified(
680
+ self,
681
+ command: str,
682
+ params: Optional[Dict[str, Any]] = None,
683
+ *,
684
+ use_cmd_endpoint: bool = False,
685
+ expect_queue: Optional[bool] = None,
686
+ auto_poll: bool = True,
687
+ poll_interval: Optional[float] = None,
688
+ timeout: Optional[float] = None,
689
+ status_hook: Optional[StatusHook] = None,
690
+ ) -> Union[Dict[str, Any], QueuedJob]:
691
+ """Deprecated alias for :meth:`call`.
692
+
693
+ ``expect_queue`` remains accepted-and-ignored (documented no-op).
694
+ ``auto_poll`` is now LIVE again and forwards straight to the shared
695
+ :meth:`_execute` core: ``auto_poll=False`` returns a
696
+ :class:`~code_analysis_client.queue_wait.QueuedJob` handle instead of
697
+ a plain dict when the response is a queued envelope — a return-type
698
+ change versus the previous era where this parameter was silently
699
+ ignored. Prefer :meth:`call` directly; this alias emits
700
+ ``DeprecationWarning`` on every call.
701
+ """
702
+ warnings.warn(
703
+ "call_unified is deprecated; use call() directly (its "
704
+ "'auto_poll' keyword works the same way).",
705
+ DeprecationWarning,
706
+ stacklevel=2,
707
+ )
708
+ return await self._execute(
709
+ command,
710
+ params,
711
+ use_cmd_endpoint=use_cmd_endpoint,
712
+ auto_poll=auto_poll,
713
+ timeout=timeout,
714
+ poll_interval=poll_interval,
715
+ status_hook=status_hook,
716
+ )
717
+
718
+ async def close(self) -> None:
719
+ """Close the underlying JSON-RPC transport."""
720
+ await self._rpc.close()
721
+
722
+ async def __aenter__(self) -> CodeAnalysisAsyncClient:
723
+ """Enter async context manager and return this client."""
724
+ return self
725
+
726
+ async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
727
+ """Close the client when leaving an async context manager block."""
728
+ await self.close()