cortexdbai 0.10.4__tar.gz → 0.11.0__tar.gz
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.
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/.gitignore +7 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/PKG-INFO +2 -2
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/__init__.py +3 -1
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/v1/__init__.py +2 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/v1/client.py +238 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/v1/exceptions.py +21 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/pyproject.toml +1 -1
- cortexdbai-0.11.0/tests/test_code_namespace.py +104 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/tests/test_v093_surfaces.py +482 -443
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/README.md +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/ag2/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/ag2/agent.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/ag2/tools.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/agno/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/agno/memory.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/agno/tools.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/autogen/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/autogen/agent.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/autogen/tools.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/beeai/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/beeai/memory.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/beeai/tools.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/camel/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/camel/memory.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/camel/tools.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/controlflow/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/controlflow/memory.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/crewai/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/crewai/memory.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/crewai/tools.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/deepinfra/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/deepinfra/config.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/deepinfra/embeddings.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/deepinfra/openai_compat.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/dify/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/dify/manifest.yaml +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/dify/tool.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/dspy/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/dspy/memory.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/dspy/retriever.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/fireworks/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/fireworks/config.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/fireworks/embeddings.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/fireworks/openai_compat.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/flowise/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/flowise/node.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/google_adk/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/google_adk/tools.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/groq/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/groq/config.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/groq/embeddings.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/groq/openai_compat.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/hermes/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/hermes/provider.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/instructor/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/instructor/memory.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/langchain/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/langchain/memory.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/langchain/retriever.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/langchain/tools.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/langgraph/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/langgraph/memory.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/letta/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/letta/storage.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/llamaindex/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/llamaindex/memory.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/nemo_guardrails/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/nemo_guardrails/actions.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/nemo_guardrails/knowledge_base.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/ollama/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/ollama/config.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/ollama/embeddings.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/openai_agents/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/openai_agents/tools.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/prefect/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/prefect/blocks.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/prefect/tasks.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/pydanticai/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/pydanticai/dependencies.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/pydanticai/tools.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/smolagents/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/smolagents/tools.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/temporal/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/temporal/activities.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/temporal/interceptor.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/together/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/together/config.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/together/embeddings.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/together/openai_compat.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/vllm/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/vllm/config.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/vllm/embeddings.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/integrations/vllm/openai_compat.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/cortexdb/py.typed +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/tests/__init__.py +0 -0
- {cortexdbai-0.10.4 → cortexdbai-0.11.0}/tests/test_conflicts.py +0 -0
|
@@ -88,7 +88,14 @@ benchmarks/data_*/
|
|
|
88
88
|
benchmarks/**/fast_results/
|
|
89
89
|
data_*/
|
|
90
90
|
*_data/
|
|
91
|
+
*_data_20*/
|
|
91
92
|
cortexdb_data*/
|
|
92
93
|
server_results/
|
|
93
94
|
*.log
|
|
94
95
|
docker_build.log
|
|
96
|
+
|
|
97
|
+
# local gate/bench data stores
|
|
98
|
+
gate*_data_*/
|
|
99
|
+
|
|
100
|
+
# Code-plane benchmark local run outputs (official results are committed deliberately)
|
|
101
|
+
benchmarks/codeplane/runs/
|
|
@@ -36,6 +36,7 @@ from cortexdb.v1 import V1Client, AsyncV1Client
|
|
|
36
36
|
from cortexdb.v1.exceptions import (
|
|
37
37
|
V1APIError,
|
|
38
38
|
V1AuthError,
|
|
39
|
+
V1InvalidRequestError,
|
|
39
40
|
V1ConnectionError,
|
|
40
41
|
V1Error,
|
|
41
42
|
V1NotConfiguredError,
|
|
@@ -49,7 +50,7 @@ from cortexdb import v1 # the explicit submodule namespace
|
|
|
49
50
|
Cortex = V1Client
|
|
50
51
|
AsyncCortex = AsyncV1Client
|
|
51
52
|
|
|
52
|
-
__version__ = "0.
|
|
53
|
+
__version__ = "0.11.0"
|
|
53
54
|
__all__ = [
|
|
54
55
|
# Clients (preferred top-level names)
|
|
55
56
|
"Cortex",
|
|
@@ -63,6 +64,7 @@ __all__ = [
|
|
|
63
64
|
"V1Error",
|
|
64
65
|
"V1APIError",
|
|
65
66
|
"V1AuthError",
|
|
67
|
+
"V1InvalidRequestError",
|
|
66
68
|
"V1ConnectionError",
|
|
67
69
|
"V1TimeoutError",
|
|
68
70
|
"V1RateLimitError",
|
|
@@ -38,6 +38,7 @@ from cortexdb.v1.client import AsyncV1Client, V1Client
|
|
|
38
38
|
from cortexdb.v1.exceptions import (
|
|
39
39
|
V1APIError,
|
|
40
40
|
V1AuthError,
|
|
41
|
+
V1InvalidRequestError,
|
|
41
42
|
V1ConnectionError,
|
|
42
43
|
V1Error,
|
|
43
44
|
V1NotConfiguredError,
|
|
@@ -52,6 +53,7 @@ __all__ = [
|
|
|
52
53
|
"V1Error",
|
|
53
54
|
"V1APIError",
|
|
54
55
|
"V1AuthError",
|
|
56
|
+
"V1InvalidRequestError",
|
|
55
57
|
"V1ConnectionError",
|
|
56
58
|
"V1TimeoutError",
|
|
57
59
|
"V1RateLimitError",
|
|
@@ -28,6 +28,7 @@ except ImportError:
|
|
|
28
28
|
from cortexdb.v1.exceptions import (
|
|
29
29
|
V1APIError,
|
|
30
30
|
V1ConnectionError,
|
|
31
|
+
V1InvalidRequestError,
|
|
31
32
|
V1TimeoutError,
|
|
32
33
|
)
|
|
33
34
|
|
|
@@ -237,6 +238,21 @@ def _forget_body(
|
|
|
237
238
|
if memory_ids:
|
|
238
239
|
selector["memory_ids"] = list(memory_ids)
|
|
239
240
|
if selector:
|
|
241
|
+
# KAN-146: `confirm_all` authorizes a SCOPE-WIDE operation; combined
|
|
242
|
+
# with a selector it is ambiguous ("delete these ids" vs "delete
|
|
243
|
+
# everything") and the server refuses it with
|
|
244
|
+
# AMBIGUOUS_SELECTOR_CONFIRM_ALL. Refuse it here instead, so a
|
|
245
|
+
# caller that wires both up never reaches a destructive endpoint and
|
|
246
|
+
# gets a message that names the two supported modes.
|
|
247
|
+
if confirm_all:
|
|
248
|
+
raise V1InvalidRequestError(
|
|
249
|
+
"AMBIGUOUS_SELECTOR_CONFIRM_ALL",
|
|
250
|
+
"selector + confirm_all is ambiguous: confirm_all authorizes "
|
|
251
|
+
"a scope-wide operation and would ignore the selector. Send "
|
|
252
|
+
"confirm_all=False (or omit it) to delete exactly the "
|
|
253
|
+
"selected memories, or pass no selector with "
|
|
254
|
+
"confirm_all=True for a scope-wide forget",
|
|
255
|
+
)
|
|
240
256
|
body["selector"] = selector
|
|
241
257
|
if reason:
|
|
242
258
|
body["reason"] = reason
|
|
@@ -256,6 +272,17 @@ def _erasure_body(
|
|
|
256
272
|
and the server refuses them here."""
|
|
257
273
|
body: dict = {"scope": scope, "confirm_all": confirm_all}
|
|
258
274
|
if memory_ids:
|
|
275
|
+
# KAN-146: same rule as /v1/forget — confirm_all authorizes a
|
|
276
|
+
# WHOLE-SCOPE erasure and is only valid with an empty selector. The
|
|
277
|
+
# server refuses the combination (AMBIGUOUS_SELECTOR); refuse it
|
|
278
|
+
# before the irreversible call is made.
|
|
279
|
+
if confirm_all:
|
|
280
|
+
raise V1InvalidRequestError(
|
|
281
|
+
"AMBIGUOUS_SELECTOR",
|
|
282
|
+
"confirm_all=True is only valid with an EMPTY selector "
|
|
283
|
+
"(whole-scope erasure); to erase specific records pass "
|
|
284
|
+
"memory_ids WITHOUT confirm_all",
|
|
285
|
+
)
|
|
259
286
|
body["selector"] = {"memory_ids": list(memory_ids)}
|
|
260
287
|
if audit_note:
|
|
261
288
|
body["audit_note"] = audit_note
|
|
@@ -418,6 +445,8 @@ class V1Client:
|
|
|
418
445
|
self.timeout = timeout
|
|
419
446
|
self._session = requests.Session()
|
|
420
447
|
self._session.headers.update(_build_headers(actor, bearer))
|
|
448
|
+
#: ``client.code.*`` — the Code Intelligence Plane (T-906).
|
|
449
|
+
self.code = CodeNamespace(self)
|
|
421
450
|
|
|
422
451
|
# ── auth ──────────────────────────────────────────────────────────
|
|
423
452
|
|
|
@@ -1917,3 +1946,212 @@ class AsyncV1Client:
|
|
|
1917
1946
|
|
|
1918
1947
|
async def __aexit__(self, *_) -> None:
|
|
1919
1948
|
await self.aclose()
|
|
1949
|
+
|
|
1950
|
+
|
|
1951
|
+
class CodeNamespace:
|
|
1952
|
+
"""``client.code.*`` — the Code Intelligence Plane (T-906).
|
|
1953
|
+
|
|
1954
|
+
Thin typed wrappers over ``/v1/code/*``. Requires the "Code memory"
|
|
1955
|
+
feature bundle on the server; with it off every call raises the
|
|
1956
|
+
server's 503. Envelopes and certificates pass through verbatim —
|
|
1957
|
+
coverage, uncertainty and warnings reach your code unedited.
|
|
1958
|
+
|
|
1959
|
+
Example::
|
|
1960
|
+
|
|
1961
|
+
client = V1Client("http://localhost:3141", bearer=token)
|
|
1962
|
+
client.code.register_repo("shop", "/path/to/checkout")
|
|
1963
|
+
ctx = client.code.context("shop", "how does settlement work?")
|
|
1964
|
+
for item in ctx["envelope"]["items"]:
|
|
1965
|
+
print(item["citation"])
|
|
1966
|
+
"""
|
|
1967
|
+
|
|
1968
|
+
def __init__(self, client: "V1Client") -> None:
|
|
1969
|
+
self._client = client
|
|
1970
|
+
|
|
1971
|
+
def register_repo(self, name: str, path: str) -> dict:
|
|
1972
|
+
"""``POST /v1/code/repos`` — register + index a local checkout."""
|
|
1973
|
+
return self._client._post("/v1/code/repos", {"name": name, "path": path})
|
|
1974
|
+
|
|
1975
|
+
def repos(self) -> dict:
|
|
1976
|
+
"""``GET /v1/code/repos`` — every repo this tenant registered."""
|
|
1977
|
+
return self._client._get("/v1/code/repos")
|
|
1978
|
+
|
|
1979
|
+
def repo_status(self, name: str) -> dict:
|
|
1980
|
+
"""``GET /v1/code/repos/{name}`` — snapshot, stats, scope."""
|
|
1981
|
+
return self._client._get(f"/v1/code/repos/{name}")
|
|
1982
|
+
|
|
1983
|
+
def remove_repo(self, name: str) -> dict:
|
|
1984
|
+
"""``DELETE /v1/code/repos/{name}``."""
|
|
1985
|
+
client = self._client
|
|
1986
|
+
try:
|
|
1987
|
+
r = client._session.delete(
|
|
1988
|
+
f"{client.api_url}/v1/code/repos/{name}", timeout=client.timeout
|
|
1989
|
+
)
|
|
1990
|
+
except requests.Timeout as e:
|
|
1991
|
+
raise V1TimeoutError(str(e)) from e
|
|
1992
|
+
except requests.ConnectionError as e:
|
|
1993
|
+
raise V1ConnectionError(str(e)) from e
|
|
1994
|
+
_raise_for_response(r.status_code, r.text)
|
|
1995
|
+
return r.json() if r.text else {}
|
|
1996
|
+
|
|
1997
|
+
def resolve_symbol(self, repo: str, name: str, *, limit: int = 50) -> dict:
|
|
1998
|
+
"""``GET /v1/code/symbol/resolve`` — every in-scope match; an
|
|
1999
|
+
ambiguous name returns ALL candidates, never a guess."""
|
|
2000
|
+
return self._client._get(
|
|
2001
|
+
"/v1/code/symbol/resolve",
|
|
2002
|
+
params={"repo": repo, "name": name, "limit": limit},
|
|
2003
|
+
)
|
|
2004
|
+
|
|
2005
|
+
def calls(
|
|
2006
|
+
self,
|
|
2007
|
+
repo: str,
|
|
2008
|
+
symbol: str,
|
|
2009
|
+
*,
|
|
2010
|
+
direction: str = "out",
|
|
2011
|
+
depth: int = 1,
|
|
2012
|
+
include_dynamic: bool = False,
|
|
2013
|
+
) -> dict:
|
|
2014
|
+
"""``GET /v1/code/calls`` — call edges, tier-labeled; dynamic
|
|
2015
|
+
dispatch fan-out only with ``include_dynamic=True``."""
|
|
2016
|
+
return self._client._get(
|
|
2017
|
+
"/v1/code/calls",
|
|
2018
|
+
params={
|
|
2019
|
+
"repo": repo,
|
|
2020
|
+
"symbol": symbol,
|
|
2021
|
+
"direction": direction,
|
|
2022
|
+
"depth": depth,
|
|
2023
|
+
"include_dynamic": str(include_dynamic).lower(),
|
|
2024
|
+
},
|
|
2025
|
+
)
|
|
2026
|
+
|
|
2027
|
+
def impact(self, repo: str, symbol: str, *, depth: int = 3) -> dict:
|
|
2028
|
+
"""``GET /v1/code/impact`` — reverse closure; each reached symbol
|
|
2029
|
+
reports hops and the WEAKEST precision tier on its path."""
|
|
2030
|
+
return self._client._get(
|
|
2031
|
+
"/v1/code/impact", params={"repo": repo, "symbol": symbol, "depth": depth}
|
|
2032
|
+
)
|
|
2033
|
+
|
|
2034
|
+
def graph_query(
|
|
2035
|
+
self,
|
|
2036
|
+
repo: str,
|
|
2037
|
+
roots: list[str],
|
|
2038
|
+
*,
|
|
2039
|
+
predicates: Optional[list[str]] = None,
|
|
2040
|
+
direction: str = "outgoing",
|
|
2041
|
+
depth: int = 1,
|
|
2042
|
+
max_nodes: int = 250,
|
|
2043
|
+
max_edges: int = 500,
|
|
2044
|
+
include_dynamic: bool = False,
|
|
2045
|
+
) -> dict:
|
|
2046
|
+
"""``POST /v1/code/graph/query`` — a bounded multi-predicate
|
|
2047
|
+
traversal with tier and status retained on every edge."""
|
|
2048
|
+
return self._client._post(
|
|
2049
|
+
"/v1/code/graph/query",
|
|
2050
|
+
{
|
|
2051
|
+
"repo": repo,
|
|
2052
|
+
"roots": roots,
|
|
2053
|
+
"predicates": predicates or ["calls"],
|
|
2054
|
+
"direction": direction,
|
|
2055
|
+
"depth": depth,
|
|
2056
|
+
"max_nodes": max_nodes,
|
|
2057
|
+
"max_edges": max_edges,
|
|
2058
|
+
"include_dynamic": include_dynamic,
|
|
2059
|
+
},
|
|
2060
|
+
)
|
|
2061
|
+
|
|
2062
|
+
def inventory(
|
|
2063
|
+
self,
|
|
2064
|
+
repo: str,
|
|
2065
|
+
kind: str,
|
|
2066
|
+
*,
|
|
2067
|
+
limit: int = 200,
|
|
2068
|
+
cursor: Optional[int] = None,
|
|
2069
|
+
) -> dict:
|
|
2070
|
+
"""``POST /v1/code/inventory`` — bounded definitions, routes,
|
|
2071
|
+
architecture, or unreferenced-definition candidates without a full
|
|
2072
|
+
database scan."""
|
|
2073
|
+
body = {"repo": repo, "kind": kind, "limit": limit}
|
|
2074
|
+
if cursor is not None:
|
|
2075
|
+
body["cursor"] = cursor
|
|
2076
|
+
return self._client._post("/v1/code/inventory", body)
|
|
2077
|
+
|
|
2078
|
+
def context(
|
|
2079
|
+
self,
|
|
2080
|
+
repo: str,
|
|
2081
|
+
task: str,
|
|
2082
|
+
*,
|
|
2083
|
+
changed_paths: Optional[list] = None,
|
|
2084
|
+
token_budget: int = 2000,
|
|
2085
|
+
) -> dict:
|
|
2086
|
+
"""``POST /v1/code/context`` — the task/diff evidence envelope:
|
|
2087
|
+
verbatim snippets with citations, coverage, uncertainty notes and
|
|
2088
|
+
pre-action warnings, under a hard token budget."""
|
|
2089
|
+
return self._client._post(
|
|
2090
|
+
"/v1/code/context",
|
|
2091
|
+
{
|
|
2092
|
+
"repo": repo,
|
|
2093
|
+
"task": task,
|
|
2094
|
+
"changed_paths": changed_paths or [],
|
|
2095
|
+
"token_budget": token_budget,
|
|
2096
|
+
},
|
|
2097
|
+
)
|
|
2098
|
+
|
|
2099
|
+
def workspace_context(
|
|
2100
|
+
self,
|
|
2101
|
+
task: str,
|
|
2102
|
+
*,
|
|
2103
|
+
repos: Optional[list[str]] = None,
|
|
2104
|
+
changed_paths: Optional[dict[str, list[str]]] = None,
|
|
2105
|
+
token_budget: int = 2000,
|
|
2106
|
+
) -> dict:
|
|
2107
|
+
"""``POST /v1/code/workspace/context`` — one tenant-scoped,
|
|
2108
|
+
token-budgeted context query across selected repositories. Empty
|
|
2109
|
+
``repos`` searches every repository visible to the caller."""
|
|
2110
|
+
return self._client._post(
|
|
2111
|
+
"/v1/code/workspace/context",
|
|
2112
|
+
{
|
|
2113
|
+
"repos": repos or [],
|
|
2114
|
+
"task": task,
|
|
2115
|
+
"changed_paths": changed_paths or {},
|
|
2116
|
+
"token_budget": token_budget,
|
|
2117
|
+
},
|
|
2118
|
+
)
|
|
2119
|
+
|
|
2120
|
+
def coverage_status(self, repo: str) -> dict:
|
|
2121
|
+
"""``GET /v1/code/coverage/status``."""
|
|
2122
|
+
return self._client._get("/v1/code/coverage/status", params={"repo": repo})
|
|
2123
|
+
|
|
2124
|
+
def history_for_symbol(
|
|
2125
|
+
self, repo: str, name: str, *, cursor: Optional[int] = None, limit: int = 50
|
|
2126
|
+
) -> dict:
|
|
2127
|
+
"""``GET /v1/code/history/for-symbol`` — lineage-merged events,
|
|
2128
|
+
newest first, cursor-paginated."""
|
|
2129
|
+
params: dict = {"repo": repo, "name": name, "limit": limit}
|
|
2130
|
+
if cursor is not None:
|
|
2131
|
+
params["cursor"] = cursor
|
|
2132
|
+
return self._client._get("/v1/code/history/for-symbol", params=params)
|
|
2133
|
+
|
|
2134
|
+
def tests_for_change(self, repo: str, name: str) -> dict:
|
|
2135
|
+
"""``GET /v1/code/tests/for-change`` — rows labeled static
|
|
2136
|
+
(reachability) vs observed (recorded coverage); absent coverage
|
|
2137
|
+
is disclosed, never upgraded."""
|
|
2138
|
+
return self._client._get(
|
|
2139
|
+
"/v1/code/tests/for-change", params={"repo": repo, "name": name}
|
|
2140
|
+
)
|
|
2141
|
+
|
|
2142
|
+
def certify(self, repo: str, question: str, symbol: str) -> dict:
|
|
2143
|
+
"""``POST /v1/code/certify`` — ``list-implementations`` or
|
|
2144
|
+
``count-call-sites``; returns a hash-verified certificate or
|
|
2145
|
+
``incomplete`` with reasons."""
|
|
2146
|
+
return self._client._post(
|
|
2147
|
+
"/v1/code/certify", {"repo": repo, "question": question, "symbol": symbol}
|
|
2148
|
+
)
|
|
2149
|
+
|
|
2150
|
+
def gc(self, repo: str) -> dict:
|
|
2151
|
+
"""``POST /v1/code/gc`` — one two-phase GC cycle; the report
|
|
2152
|
+
enumerates every destructive step."""
|
|
2153
|
+
return self._client._post("/v1/code/gc", {"repo": repo})
|
|
2154
|
+
|
|
2155
|
+
def matrix(self) -> dict:
|
|
2156
|
+
"""``GET /v1/code/matrix`` — the earned capability matrix."""
|
|
2157
|
+
return self._client._get("/v1/code/matrix")
|
|
@@ -63,6 +63,27 @@ class V1APIError(V1Error):
|
|
|
63
63
|
)
|
|
64
64
|
|
|
65
65
|
|
|
66
|
+
class V1InvalidRequestError(V1APIError):
|
|
67
|
+
"""Client-side refusal, raised BEFORE any network call.
|
|
68
|
+
|
|
69
|
+
The request is self-contradictory in a way the server also refuses, so
|
|
70
|
+
the SDK stops it locally rather than sending it — nothing is mutated and
|
|
71
|
+
the caller gets the same ``error_code`` the server would return.
|
|
72
|
+
Subclasses :class:`V1APIError` (with ``status=400``) so existing
|
|
73
|
+
``except V1APIError`` handlers keep working.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(self, error_code: str, message: str):
|
|
77
|
+
super().__init__(
|
|
78
|
+
status=400,
|
|
79
|
+
error_code=error_code,
|
|
80
|
+
message=message,
|
|
81
|
+
request_id=None,
|
|
82
|
+
details=None,
|
|
83
|
+
retriable=False,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
66
87
|
class V1AuthError(V1APIError):
|
|
67
88
|
"""401 — missing/invalid token, actor mismatch, revoked token, wrong tenant."""
|
|
68
89
|
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""T-906: ``client.code.*`` mirrors the /v1/code surface."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from unittest.mock import patch
|
|
6
|
+
|
|
7
|
+
from cortexdb.v1.client import V1Client
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _client() -> V1Client:
|
|
11
|
+
return V1Client("http://localhost:3141", actor="user:test")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestCodeNamespace:
|
|
15
|
+
def test_namespace_is_attached(self) -> None:
|
|
16
|
+
client = _client()
|
|
17
|
+
assert hasattr(client, "code")
|
|
18
|
+
|
|
19
|
+
def test_reads_route_to_the_right_paths(self) -> None:
|
|
20
|
+
client = _client()
|
|
21
|
+
calls: list[tuple] = []
|
|
22
|
+
|
|
23
|
+
def fake_get(path, *, params=None):
|
|
24
|
+
calls.append(("GET", path, dict(params or {})))
|
|
25
|
+
return {"ok": True}
|
|
26
|
+
|
|
27
|
+
with patch.object(client, "_get", side_effect=fake_get):
|
|
28
|
+
client.code.repos()
|
|
29
|
+
client.code.repo_status("shop")
|
|
30
|
+
client.code.resolve_symbol("shop", "settle")
|
|
31
|
+
client.code.calls("shop", "settle", direction="in", depth=2)
|
|
32
|
+
client.code.impact("shop", "settle", depth=4)
|
|
33
|
+
client.code.coverage_status("shop")
|
|
34
|
+
client.code.history_for_symbol("shop", "settle", cursor=3)
|
|
35
|
+
client.code.tests_for_change("shop", "settle")
|
|
36
|
+
client.code.matrix()
|
|
37
|
+
paths = [c[1] for c in calls]
|
|
38
|
+
assert paths == [
|
|
39
|
+
"/v1/code/repos",
|
|
40
|
+
"/v1/code/repos/shop",
|
|
41
|
+
"/v1/code/symbol/resolve",
|
|
42
|
+
"/v1/code/calls",
|
|
43
|
+
"/v1/code/impact",
|
|
44
|
+
"/v1/code/coverage/status",
|
|
45
|
+
"/v1/code/history/for-symbol",
|
|
46
|
+
"/v1/code/tests/for-change",
|
|
47
|
+
"/v1/code/matrix",
|
|
48
|
+
]
|
|
49
|
+
calls_params = calls[3][2]
|
|
50
|
+
assert calls_params["direction"] == "in"
|
|
51
|
+
assert calls_params["depth"] == 2
|
|
52
|
+
assert calls[6][2]["cursor"] == 3
|
|
53
|
+
|
|
54
|
+
def test_writes_route_with_bodies(self) -> None:
|
|
55
|
+
client = _client()
|
|
56
|
+
posts: list[tuple] = []
|
|
57
|
+
|
|
58
|
+
def fake_post(path, body):
|
|
59
|
+
posts.append((path, body))
|
|
60
|
+
return {"ok": True}
|
|
61
|
+
|
|
62
|
+
with patch.object(client, "_post", side_effect=fake_post):
|
|
63
|
+
client.code.register_repo("shop", "/src/shop")
|
|
64
|
+
client.code.context(
|
|
65
|
+
"shop", "how does settle work?", changed_paths=["a.ts"], token_budget=500
|
|
66
|
+
)
|
|
67
|
+
client.code.workspace_context(
|
|
68
|
+
"trace the request",
|
|
69
|
+
repos=["api", "worker"],
|
|
70
|
+
changed_paths={"api": ["src/client.ts"]},
|
|
71
|
+
token_budget=900,
|
|
72
|
+
)
|
|
73
|
+
client.code.graph_query(
|
|
74
|
+
"shop",
|
|
75
|
+
["invoice.created"],
|
|
76
|
+
predicates=["handles_event", "emits_event"],
|
|
77
|
+
direction="incoming",
|
|
78
|
+
include_dynamic=True,
|
|
79
|
+
)
|
|
80
|
+
client.code.inventory("shop", "architecture", limit=50, cursor=7)
|
|
81
|
+
client.code.certify("shop", "count-call-sites", "sym")
|
|
82
|
+
client.code.gc("shop")
|
|
83
|
+
assert [p[0] for p in posts] == [
|
|
84
|
+
"/v1/code/repos",
|
|
85
|
+
"/v1/code/context",
|
|
86
|
+
"/v1/code/workspace/context",
|
|
87
|
+
"/v1/code/graph/query",
|
|
88
|
+
"/v1/code/inventory",
|
|
89
|
+
"/v1/code/certify",
|
|
90
|
+
"/v1/code/gc",
|
|
91
|
+
]
|
|
92
|
+
assert posts[1][1]["token_budget"] == 500
|
|
93
|
+
assert posts[1][1]["changed_paths"] == ["a.ts"]
|
|
94
|
+
assert posts[2][1]["repos"] == ["api", "worker"]
|
|
95
|
+
assert posts[2][1]["changed_paths"] == {"api": ["src/client.ts"]}
|
|
96
|
+
assert posts[2][1]["token_budget"] == 900
|
|
97
|
+
assert posts[3][1]["predicates"] == ["handles_event", "emits_event"]
|
|
98
|
+
assert posts[3][1]["include_dynamic"] is True
|
|
99
|
+
assert posts[4][1] == {
|
|
100
|
+
"repo": "shop",
|
|
101
|
+
"kind": "architecture",
|
|
102
|
+
"limit": 50,
|
|
103
|
+
"cursor": 7,
|
|
104
|
+
}
|