feat: codegraph
This commit is contained in:
@@ -15,3 +15,4 @@ AGENTCI_PLAN_REVIEW_ROUNDS=4
|
|||||||
AGENTCI_IMPLEMENT_REVIEW_ROUNDS=3
|
AGENTCI_IMPLEMENT_REVIEW_ROUNDS=3
|
||||||
AGENTCI_TURN_TIMEOUT_SECONDS=3600
|
AGENTCI_TURN_TIMEOUT_SECONDS=3600
|
||||||
CODEX_VERSION=0.144.6
|
CODEX_VERSION=0.144.6
|
||||||
|
CODEGRAPH_VERSION=1.3.1
|
||||||
|
|||||||
+13
-3
@@ -1,7 +1,10 @@
|
|||||||
FROM node:24-bookworm-slim AS codex
|
FROM node:24-bookworm-slim AS codex
|
||||||
|
|
||||||
ARG CODEX_VERSION=0.144.6
|
ARG CODEX_VERSION=0.144.6
|
||||||
RUN npm install --global "@openai/codex@${CODEX_VERSION}"
|
ARG CODEGRAPH_VERSION=1.3.1
|
||||||
|
RUN npm install --global \
|
||||||
|
"@openai/codex@${CODEX_VERSION}" \
|
||||||
|
"@colbymchenry/codegraph@${CODEGRAPH_VERSION}"
|
||||||
|
|
||||||
FROM ghcr.io/astral-sh/uv:0.8.14 AS uv
|
FROM ghcr.io/astral-sh/uv:0.8.14 AS uv
|
||||||
|
|
||||||
@@ -11,6 +14,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
|
|||||||
PYTHONUNBUFFERED=1 \
|
PYTHONUNBUFFERED=1 \
|
||||||
UV_LINK_MODE=copy \
|
UV_LINK_MODE=copy \
|
||||||
UV_NO_DEV=1 \
|
UV_NO_DEV=1 \
|
||||||
|
CODEGRAPH_TELEMETRY=0 \
|
||||||
CODEX_HOME=/var/lib/codex \
|
CODEX_HOME=/var/lib/codex \
|
||||||
PATH=/opt/agentci/.venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
PATH=/opt/agentci/.venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
|
||||||
@@ -23,7 +27,10 @@ RUN apt-get update \
|
|||||||
COPY --from=uv /uv /uvx /usr/local/bin/
|
COPY --from=uv /uv /uvx /usr/local/bin/
|
||||||
COPY --from=codex /usr/local/bin/node /usr/local/bin/node
|
COPY --from=codex /usr/local/bin/node /usr/local/bin/node
|
||||||
COPY --from=codex /usr/local/lib/node_modules/@openai/codex /usr/local/lib/node_modules/@openai/codex
|
COPY --from=codex /usr/local/lib/node_modules/@openai/codex /usr/local/lib/node_modules/@openai/codex
|
||||||
RUN ln -s /usr/local/lib/node_modules/@openai/codex/bin/codex.js /usr/local/bin/codex
|
COPY --from=codex /usr/local/lib/node_modules/@colbymchenry /usr/local/lib/node_modules/@colbymchenry
|
||||||
|
RUN ln -s /usr/local/lib/node_modules/@openai/codex/bin/codex.js /usr/local/bin/codex \
|
||||||
|
&& ln -s /usr/local/lib/node_modules/@colbymchenry/codegraph/npm-shim.js \
|
||||||
|
/usr/local/bin/codegraph
|
||||||
|
|
||||||
WORKDIR /opt/agentci
|
WORKDIR /opt/agentci
|
||||||
COPY pyproject.toml uv.lock README.md ./
|
COPY pyproject.toml uv.lock README.md ./
|
||||||
@@ -31,7 +38,10 @@ COPY src ./src
|
|||||||
COPY scripts ./scripts
|
COPY scripts ./scripts
|
||||||
COPY codex/config.toml /etc/codex/config.toml
|
COPY codex/config.toml /etc/codex/config.toml
|
||||||
|
|
||||||
RUN chmod 0755 /opt/agentci/scripts/gitea-askpass.sh /usr/local/bin/codex \
|
RUN chmod 0755 \
|
||||||
|
/opt/agentci/scripts/gitea-askpass.sh \
|
||||||
|
/usr/local/bin/codex \
|
||||||
|
/usr/local/bin/codegraph \
|
||||||
&& uv sync --frozen --no-dev \
|
&& uv sync --frozen --no-dev \
|
||||||
&& chown -R agentci:agentci /opt/agentci /var/lib/agentci /var/lib/codex
|
&& chown -R agentci:agentci /opt/agentci /var/lib/agentci /var/lib/codex
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,12 @@ without authentication at lower rate limits; set the optional
|
|||||||
`AGENTCI_CONTEXT7_API_KEY` for authenticated usage. The key is passed only to
|
`AGENTCI_CONTEXT7_API_KEY` for authenticated usage. The key is passed only to
|
||||||
Codex's Context7 MCP transport and is excluded from agent shell environments.
|
Codex's Context7 MCP transport and is excluded from agent shell environments.
|
||||||
|
|
||||||
|
Planning, implementation, and all review sessions also receive the local
|
||||||
|
CodeGraph MCP server for repository structure, symbol relationships, and change
|
||||||
|
impact. Agent CI initializes or refreshes the index before every Codex turn and
|
||||||
|
locally excludes `.codegraph/` from Git. The research subagent intentionally
|
||||||
|
does not receive CodeGraph.
|
||||||
|
|
||||||
Planning/review commands can only read their workflow clone and have no shell
|
Planning/review commands can only read their workflow clone and have no shell
|
||||||
network access. Implementation/fix commands can edit the clone but cannot
|
network access. Implementation/fix commands can edit the clone but cannot
|
||||||
modify `.git`; they can reach public internet destinations while private and
|
modify `.git`; they can reach public internet destinations while private and
|
||||||
|
|||||||
@@ -7,8 +7,19 @@ developer_instructions = """
|
|||||||
A custom research subagent named `research` is available in every workflow. Delegate focused
|
A custom research subagent named `research` is available in every workflow. Delegate focused
|
||||||
external research to it when current documentation, web evidence, or public code examples would
|
external research to it when current documentation, web evidence, or public code examples would
|
||||||
materially improve the plan or implementation. Keep repository analysis and edits in the parent.
|
materially improve the plan or implementation. Keep repository analysis and edits in the parent.
|
||||||
|
Use CodeGraph for repository architecture, symbol relationships, and change-impact analysis when
|
||||||
|
it is useful. It is available to planning, implementation, and review agents, but not research.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
[mcp_servers.codegraph]
|
||||||
|
command = "codegraph"
|
||||||
|
args = ["serve", "--mcp"]
|
||||||
|
startup_timeout_sec = 60
|
||||||
|
tool_timeout_sec = 60
|
||||||
|
|
||||||
|
[mcp_servers.codegraph.env]
|
||||||
|
CODEGRAPH_TELEMETRY = "0"
|
||||||
|
|
||||||
[agents]
|
[agents]
|
||||||
max_threads = 4
|
max_threads = 4
|
||||||
max_depth = 1
|
max_depth = 1
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
args:
|
args:
|
||||||
CODEX_VERSION: ${CODEX_VERSION:-0.144.6}
|
CODEX_VERSION: ${CODEX_VERSION:-0.144.6}
|
||||||
|
CODEGRAPH_VERSION: ${CODEGRAPH_VERSION:-1.3.1}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
AGENTCI_GITEA_URL: ${AGENTCI_GITEA_URL:-http://gitea:3000}
|
AGENTCI_GITEA_URL: ${AGENTCI_GITEA_URL:-http://gitea:3000}
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from time import monotonic
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CodeGraphError(RuntimeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CodeGraphClient:
|
||||||
|
async def prepare(self, workspace: Path) -> None:
|
||||||
|
self._exclude_index(workspace)
|
||||||
|
command = "sync" if (workspace / ".codegraph").is_dir() else "init"
|
||||||
|
started = monotonic()
|
||||||
|
log.info(
|
||||||
|
"CodeGraph index preparation started",
|
||||||
|
extra={"operation": f"codegraph.{command}"},
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
process = await asyncio.create_subprocess_exec(
|
||||||
|
"codegraph",
|
||||||
|
command,
|
||||||
|
str(workspace),
|
||||||
|
cwd=workspace,
|
||||||
|
env={**os.environ, "CODEGRAPH_TELEMETRY": "0"},
|
||||||
|
stdout=asyncio.subprocess.PIPE,
|
||||||
|
stderr=asyncio.subprocess.PIPE,
|
||||||
|
)
|
||||||
|
_, stderr = await process.communicate()
|
||||||
|
except OSError as exc:
|
||||||
|
log.exception(
|
||||||
|
"CodeGraph could not start",
|
||||||
|
extra={
|
||||||
|
"operation": f"codegraph.{command}",
|
||||||
|
"duration_ms": _elapsed_ms(started),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
raise CodeGraphError(f"Could not run CodeGraph: {exc}") from exc
|
||||||
|
if process.returncode:
|
||||||
|
detail = stderr.decode(errors="replace").strip()
|
||||||
|
log.error(
|
||||||
|
"CodeGraph index preparation failed",
|
||||||
|
extra={
|
||||||
|
"operation": f"codegraph.{command}",
|
||||||
|
"duration_ms": _elapsed_ms(started),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
raise CodeGraphError(
|
||||||
|
f"codegraph {command} failed: {detail[-1000:]}"
|
||||||
|
)
|
||||||
|
log.info(
|
||||||
|
"CodeGraph index preparation completed",
|
||||||
|
extra={
|
||||||
|
"operation": f"codegraph.{command}",
|
||||||
|
"duration_ms": _elapsed_ms(started),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _exclude_index(workspace: Path) -> None:
|
||||||
|
exclude = workspace / ".git" / "info" / "exclude"
|
||||||
|
exclude.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
content = exclude.read_text() if exclude.exists() else ""
|
||||||
|
patterns = {
|
||||||
|
line.strip()
|
||||||
|
for line in content.splitlines()
|
||||||
|
if line.strip() and not line.lstrip().startswith("#")
|
||||||
|
}
|
||||||
|
if ".codegraph/" in patterns:
|
||||||
|
return
|
||||||
|
separator = "" if not content or content.endswith("\n") else "\n"
|
||||||
|
exclude.write_text(f"{content}{separator}.codegraph/\n")
|
||||||
|
|
||||||
|
|
||||||
|
def _elapsed_ms(started: float) -> int:
|
||||||
|
return round((monotonic() - started) * 1000)
|
||||||
@@ -11,6 +11,8 @@ from typing import TypeVar
|
|||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from agentci.adapters.codegraph import CodeGraphClient
|
||||||
|
|
||||||
T = TypeVar("T", bound=BaseModel)
|
T = TypeVar("T", bound=BaseModel)
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -29,11 +31,13 @@ class CodexClient:
|
|||||||
research_model: str,
|
research_model: str,
|
||||||
research_reasoning: str,
|
research_reasoning: str,
|
||||||
context7_api_key: str | None,
|
context7_api_key: str | None,
|
||||||
|
codegraph: CodeGraphClient | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.codex_home = codex_home
|
self.codex_home = codex_home
|
||||||
self.schemas_dir = schemas_dir
|
self.schemas_dir = schemas_dir
|
||||||
self.timeout_seconds = timeout_seconds
|
self.timeout_seconds = timeout_seconds
|
||||||
self.context7_api_key = context7_api_key
|
self.context7_api_key = context7_api_key
|
||||||
|
self.codegraph = codegraph or CodeGraphClient()
|
||||||
self._write_research_agent(research_model, research_reasoning)
|
self._write_research_agent(research_model, research_reasoning)
|
||||||
|
|
||||||
async def login_ready(self) -> bool:
|
async def login_ready(self) -> bool:
|
||||||
@@ -61,6 +65,7 @@ class CodexClient:
|
|||||||
schema_name: str,
|
schema_name: str,
|
||||||
result_type: type[T],
|
result_type: type[T],
|
||||||
) -> tuple[str, T]:
|
) -> tuple[str, T]:
|
||||||
|
await self.codegraph.prepare(workspace)
|
||||||
args = [
|
args = [
|
||||||
"codex",
|
"codex",
|
||||||
"exec",
|
"exec",
|
||||||
@@ -84,9 +89,11 @@ class CodexClient:
|
|||||||
model: str,
|
model: str,
|
||||||
reasoning: str,
|
reasoning: str,
|
||||||
permission: str,
|
permission: str,
|
||||||
|
workspace: Path,
|
||||||
schema_name: str,
|
schema_name: str,
|
||||||
result_type: type[T],
|
result_type: type[T],
|
||||||
) -> T:
|
) -> T:
|
||||||
|
await self.codegraph.prepare(workspace)
|
||||||
args = [
|
args = [
|
||||||
"codex",
|
"codex",
|
||||||
"exec",
|
"exec",
|
||||||
@@ -198,8 +205,12 @@ Use Context7 for library documentation, gh_grep for real public-code examples, a
|
|||||||
primary sources or broader verification. Prefer authoritative sources, report links, distinguish
|
primary sources or broader verification. Prefer authoritative sources, report links, distinguish
|
||||||
facts from inference, and return a concise evidence-focused summary. You may inspect the workspace
|
facts from inference, and return a concise evidence-focused summary. You may inspect the workspace
|
||||||
but must not modify it. Never include secrets or proprietary source in external queries.
|
but must not modify it. Never include secrets or proprietary source in external queries.
|
||||||
|
Do not use CodeGraph; repository analysis belongs to the parent agent.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
[mcp_servers.codegraph]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
[mcp_servers.context7]
|
[mcp_servers.context7]
|
||||||
url = "https://mcp.context7.com/mcp"
|
url = "https://mcp.context7.com/mcp"
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ class CodeReviewLoop:
|
|||||||
model=self.deps.settings.implement_model,
|
model=self.deps.settings.implement_model,
|
||||||
reasoning=self.deps.settings.implement_reasoning,
|
reasoning=self.deps.settings.implement_reasoning,
|
||||||
permission="agentci-write",
|
permission="agentci-write",
|
||||||
|
workspace=workflow.workspace_path,
|
||||||
schema_name="agent_result.json",
|
schema_name="agent_result.json",
|
||||||
result_type=AgentResult,
|
result_type=AgentResult,
|
||||||
)
|
)
|
||||||
@@ -54,6 +55,7 @@ class CodeReviewLoop:
|
|||||||
model=self.deps.settings.implement_model,
|
model=self.deps.settings.implement_model,
|
||||||
reasoning=self.deps.settings.implement_reasoning,
|
reasoning=self.deps.settings.implement_reasoning,
|
||||||
permission="agentci-read",
|
permission="agentci-read",
|
||||||
|
workspace=workflow.workspace_path,
|
||||||
schema_name="review.json",
|
schema_name="review.json",
|
||||||
result_type=ReviewReport,
|
result_type=ReviewReport,
|
||||||
)
|
)
|
||||||
@@ -74,4 +76,3 @@ def _required(value: str | None) -> str:
|
|||||||
if value is None:
|
if value is None:
|
||||||
raise RuntimeError("Expected a persisted Codex session ID")
|
raise RuntimeError("Expected a persisted Codex session ID")
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ class PlanWorkflow:
|
|||||||
model=self.deps.settings.plan_model,
|
model=self.deps.settings.plan_model,
|
||||||
reasoning=self.deps.settings.plan_reasoning,
|
reasoning=self.deps.settings.plan_reasoning,
|
||||||
permission="agentci-read",
|
permission="agentci-read",
|
||||||
|
workspace=workflow.workspace_path,
|
||||||
schema_name="discussion.json",
|
schema_name="discussion.json",
|
||||||
result_type=DiscussionReply,
|
result_type=DiscussionReply,
|
||||||
)
|
)
|
||||||
@@ -121,6 +122,7 @@ class PlanWorkflow:
|
|||||||
model=self.deps.settings.plan_model,
|
model=self.deps.settings.plan_model,
|
||||||
reasoning=self.deps.settings.plan_reasoning,
|
reasoning=self.deps.settings.plan_reasoning,
|
||||||
permission="agentci-read",
|
permission="agentci-read",
|
||||||
|
workspace=workflow.workspace_path,
|
||||||
schema_name="plan.json",
|
schema_name="plan.json",
|
||||||
result_type=PlanArtifact,
|
result_type=PlanArtifact,
|
||||||
)
|
)
|
||||||
@@ -128,11 +130,7 @@ class PlanWorkflow:
|
|||||||
await self._finish(job, workflow, artifact, report)
|
await self._finish(job, workflow, artifact, report)
|
||||||
|
|
||||||
async def _review_loop(
|
async def _review_loop(
|
||||||
self,
|
self, job: Job, workflow: Workflow, context: str, artifact: PlanArtifact
|
||||||
job: Job,
|
|
||||||
workflow: Workflow,
|
|
||||||
context: str,
|
|
||||||
artifact: PlanArtifact,
|
|
||||||
) -> ReviewReport:
|
) -> ReviewReport:
|
||||||
report = ReviewReport(summary="", findings=[])
|
report = ReviewReport(summary="", findings=[])
|
||||||
for round_index in range(self.deps.settings.plan_review_rounds):
|
for round_index in range(self.deps.settings.plan_review_rounds):
|
||||||
@@ -160,16 +158,14 @@ class PlanWorkflow:
|
|||||||
model=self.deps.settings.plan_model,
|
model=self.deps.settings.plan_model,
|
||||||
reasoning=self.deps.settings.plan_reasoning,
|
reasoning=self.deps.settings.plan_reasoning,
|
||||||
permission="agentci-read",
|
permission="agentci-read",
|
||||||
|
workspace=workflow.workspace_path,
|
||||||
schema_name="plan.json",
|
schema_name="plan.json",
|
||||||
result_type=PlanArtifact,
|
result_type=PlanArtifact,
|
||||||
)
|
)
|
||||||
return report
|
return report
|
||||||
|
|
||||||
async def _review(
|
async def _review(
|
||||||
self,
|
self, workflow: Workflow, context: str, artifact: PlanArtifact
|
||||||
workflow: Workflow,
|
|
||||||
context: str,
|
|
||||||
artifact: PlanArtifact,
|
|
||||||
) -> ReviewReport:
|
) -> ReviewReport:
|
||||||
prompt = self.deps.prompts.render(
|
prompt = self.deps.prompts.render(
|
||||||
"plan_review", context=context, artifact=artifact.plan_markdown
|
"plan_review", context=context, artifact=artifact.plan_markdown
|
||||||
@@ -181,6 +177,7 @@ class PlanWorkflow:
|
|||||||
model=self.deps.settings.plan_model,
|
model=self.deps.settings.plan_model,
|
||||||
reasoning=self.deps.settings.plan_reasoning,
|
reasoning=self.deps.settings.plan_reasoning,
|
||||||
permission="agentci-read",
|
permission="agentci-read",
|
||||||
|
workspace=workflow.workspace_path,
|
||||||
schema_name="review.json",
|
schema_name="review.json",
|
||||||
result_type=ReviewReport,
|
result_type=ReviewReport,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ class PullRequestWorkflow:
|
|||||||
model=self.deps.settings.implement_model,
|
model=self.deps.settings.implement_model,
|
||||||
reasoning=self.deps.settings.implement_reasoning,
|
reasoning=self.deps.settings.implement_reasoning,
|
||||||
permission="agentci-write",
|
permission="agentci-write",
|
||||||
|
workspace=workflow.workspace_path,
|
||||||
schema_name="agent_result.json",
|
schema_name="agent_result.json",
|
||||||
result_type=AgentResult,
|
result_type=AgentResult,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from agentci.adapters.codegraph import CodeGraphClient
|
||||||
|
|
||||||
|
|
||||||
|
class FakeProcess:
|
||||||
|
returncode = 0
|
||||||
|
|
||||||
|
async def communicate(self) -> tuple[bytes, bytes]:
|
||||||
|
return b"", b""
|
||||||
|
|
||||||
|
|
||||||
|
async def test_initializes_index_and_excludes_it_from_git(
|
||||||
|
tmp_path: Path, monkeypatch
|
||||||
|
) -> None:
|
||||||
|
workspace = tmp_path / "repo"
|
||||||
|
(workspace / ".git" / "info").mkdir(parents=True)
|
||||||
|
calls: list[tuple[object, ...]] = []
|
||||||
|
|
||||||
|
async def create_subprocess_exec(*args, **_kwargs):
|
||||||
|
calls.append(args)
|
||||||
|
return FakeProcess()
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"agentci.adapters.codegraph.asyncio.create_subprocess_exec",
|
||||||
|
create_subprocess_exec,
|
||||||
|
)
|
||||||
|
|
||||||
|
await CodeGraphClient().prepare(workspace)
|
||||||
|
|
||||||
|
assert calls == [("codegraph", "init", str(workspace))]
|
||||||
|
assert (workspace / ".git" / "info" / "exclude").read_text() == ".codegraph/\n"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_syncs_an_existing_index_without_duplicating_exclude(
|
||||||
|
tmp_path: Path, monkeypatch
|
||||||
|
) -> None:
|
||||||
|
workspace = tmp_path / "repo"
|
||||||
|
(workspace / ".git" / "info").mkdir(parents=True)
|
||||||
|
(workspace / ".codegraph").mkdir()
|
||||||
|
exclude = workspace / ".git" / "info" / "exclude"
|
||||||
|
exclude.write_text("# local excludes\n.codegraph/\n")
|
||||||
|
calls: list[tuple[object, ...]] = []
|
||||||
|
|
||||||
|
async def create_subprocess_exec(*args, **_kwargs):
|
||||||
|
calls.append(args)
|
||||||
|
return FakeProcess()
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"agentci.adapters.codegraph.asyncio.create_subprocess_exec",
|
||||||
|
create_subprocess_exec,
|
||||||
|
)
|
||||||
|
|
||||||
|
await CodeGraphClient().prepare(workspace)
|
||||||
|
|
||||||
|
assert calls == [("codegraph", "sync", str(workspace))]
|
||||||
|
assert exclude.read_text() == "# local excludes\n.codegraph/\n"
|
||||||
@@ -1,8 +1,19 @@
|
|||||||
import tomllib
|
import tomllib
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from agentci.adapters.codex import CodexClient, _session_id
|
from agentci.adapters.codex import CodexClient, _session_id
|
||||||
|
|
||||||
|
|
||||||
|
def test_enables_codegraph_in_shared_codex_config() -> None:
|
||||||
|
root = Path(__file__).parents[1]
|
||||||
|
config = tomllib.loads((root / "codex" / "config.toml").read_text())
|
||||||
|
|
||||||
|
codegraph = config["mcp_servers"]["codegraph"]
|
||||||
|
assert codegraph["command"] == "codegraph"
|
||||||
|
assert codegraph["args"] == ["serve", "--mcp"]
|
||||||
|
assert codegraph["env"]["CODEGRAPH_TELEMETRY"] == "0"
|
||||||
|
|
||||||
|
|
||||||
def test_extracts_thread_id_from_jsonl() -> None:
|
def test_extracts_thread_id_from_jsonl() -> None:
|
||||||
output = '\n'.join(
|
output = '\n'.join(
|
||||||
[
|
[
|
||||||
@@ -49,6 +60,7 @@ def test_configures_research_agent_and_optional_context7_key(tmp_path) -> None:
|
|||||||
assert parsed["model"] == "research-model"
|
assert parsed["model"] == "research-model"
|
||||||
assert parsed["model_reasoning_effort"] == "high"
|
assert parsed["model_reasoning_effort"] == "high"
|
||||||
assert parsed["web_search"] == "live"
|
assert parsed["web_search"] == "live"
|
||||||
|
assert parsed["mcp_servers"]["codegraph"]["enabled"] is False
|
||||||
assert 'url = "https://mcp.context7.com/mcp"' in agent
|
assert 'url = "https://mcp.context7.com/mcp"' in agent
|
||||||
assert 'url = "https://mcp.grep.app"' in agent
|
assert 'url = "https://mcp.grep.app"' in agent
|
||||||
assert "ctx7-secret" not in agent
|
assert "ctx7-secret" not in agent
|
||||||
|
|||||||
Reference in New Issue
Block a user