Skip to content

Commit 57ac5a9

Browse files
committed
feat: add support for codex and openai api key auth methods
1 parent b048674 commit 57ac5a9

3 files changed

Lines changed: 135 additions & 2 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ curl -H "Authorization: Bearer <token>" \
5858
## Environment Variables
5959

6060
* `A2A_AGENT_COMMAND`: The command to execute the Zed ACP agent.
61-
* `A2A_AGENT_API_KEY`: (Optional) The API key for the Zed ACP agent.
61+
* `A2A_AGENT_API_KEY`: (Optional) The API key for the Zed ACP agent. Supports `apikey`, `gemini-api-key`, `codex-api-key`, and `openai-api-key` authentication methods.
6262
* `A2A_AUTH_TOKEN`: The bearer token for authenticating with the A2A-ACP server.
6363
* `PORT`: (Optional) The port to run the server on. Defaults to `8001`.
6464

src/a2a_acp/zed_agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ async def initialize(self) -> dict[str, Any] | None:
325325
# Look for API key authentication method (support multiple agent types)
326326
api_key_method = None
327327
api_key_method_id = None
328-
supported_api_key_methods = ["apikey", "gemini-api-key"] # Support both codex and gemini
328+
supported_api_key_methods = ["apikey", "gemini-api-key", "codex-api-key", "openai-api-key"] # Support codex, gemini and openai
329329

330330
for method in auth_methods:
331331
method_id = method.get("id")

tests/test_zed_agent_auth.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
2+
import asyncio
3+
import json
4+
from unittest.mock import MagicMock, patch, AsyncMock
5+
import pytest
6+
from a2a_acp.zed_agent import ZedAgentConnection, AgentProcessError
7+
8+
class TestZedAgentAuthenticationExtended:
9+
"""Test extended authentication functionality for codex and openai keys."""
10+
11+
@patch('asyncio.create_subprocess_exec')
12+
@pytest.mark.asyncio
13+
async def test_initialize_with_codex_auth_required(self, mock_create_subprocess):
14+
"""Test initialization with Codex API key authentication required."""
15+
mock_process = AsyncMock()
16+
mock_process.stdin = AsyncMock()
17+
mock_process.stdin.write = MagicMock()
18+
mock_process.stdin.drain = AsyncMock()
19+
mock_process.stdout = AsyncMock()
20+
mock_process.stdout.readline = AsyncMock(side_effect=[
21+
b'{"jsonrpc": "2.0", "result": {"protocolVersion": "v1", "capabilities": {}, "authMethods": [{"id": "codex-api-key"}]}, "id": 1}\n',
22+
b'', # EOF
23+
])
24+
mock_process.stderr = AsyncMock()
25+
mock_process.stderr.readline = AsyncMock(return_value=b'')
26+
mock_create_subprocess.return_value = mock_process
27+
28+
api_key = "codex-test-api-key"
29+
connection = ZedAgentConnection(["echo", "test"], api_key=api_key)
30+
await connection.start()
31+
32+
# Mock authentication method
33+
auth_call_count = 0
34+
auth_method_used = None
35+
36+
async def mock_authenticate(method_id, api_key=None):
37+
nonlocal auth_call_count, auth_method_used
38+
auth_call_count += 1
39+
auth_method_used = method_id
40+
return {"authenticated": True}
41+
42+
connection.authenticate = mock_authenticate
43+
44+
result = await connection.initialize()
45+
46+
assert auth_call_count == 1
47+
assert auth_method_used == "codex-api-key"
48+
assert result is not None
49+
50+
@patch('asyncio.create_subprocess_exec')
51+
@pytest.mark.asyncio
52+
async def test_initialize_with_openai_auth_required(self, mock_create_subprocess):
53+
"""Test initialization with OpenAI API key authentication required."""
54+
mock_process = AsyncMock()
55+
mock_process.stdin = AsyncMock()
56+
mock_process.stdin.write = MagicMock()
57+
mock_process.stdin.drain = AsyncMock()
58+
mock_process.stdout = AsyncMock()
59+
mock_process.stdout.readline = AsyncMock(side_effect=[
60+
b'{"jsonrpc": "2.0", "result": {"protocolVersion": "v1", "capabilities": {}, "authMethods": [{"id": "openai-api-key"}]}, "id": 1}\n',
61+
b'', # EOF
62+
])
63+
mock_process.stderr = AsyncMock()
64+
mock_process.stderr.readline = AsyncMock(return_value=b'')
65+
mock_create_subprocess.return_value = mock_process
66+
67+
api_key = "openai-test-api-key"
68+
connection = ZedAgentConnection(["echo", "test"], api_key=api_key)
69+
await connection.start()
70+
71+
# Mock authentication method
72+
auth_call_count = 0
73+
auth_method_used = None
74+
75+
async def mock_authenticate(method_id, api_key=None):
76+
nonlocal auth_call_count, auth_method_used
77+
auth_call_count += 1
78+
auth_method_used = method_id
79+
return {"authenticated": True}
80+
81+
connection.authenticate = mock_authenticate
82+
83+
result = await connection.initialize()
84+
85+
assert auth_call_count == 1
86+
assert auth_method_used == "openai-api-key"
87+
assert result is not None
88+
89+
@patch('asyncio.create_subprocess_exec')
90+
@pytest.mark.asyncio
91+
async def test_initialize_with_mixed_auth_methods(self, mock_create_subprocess):
92+
"""Test initialization with multiple auth methods including supported ones."""
93+
mock_process = AsyncMock()
94+
mock_process.stdin = AsyncMock()
95+
mock_process.stdin.write = MagicMock()
96+
mock_process.stdin.drain = AsyncMock()
97+
mock_process.stdout = AsyncMock()
98+
mock_process.stdout.readline = AsyncMock(side_effect=[
99+
b'{"jsonrpc": "2.0", "result": {"protocolVersion": "v1", "capabilities": {}, "authMethods": [{"id": "chatgpt"}, {"id": "codex-api-key"}, {"id": "openai-api-key"}]}, "id": 1}\n',
100+
b'', # EOF
101+
])
102+
mock_process.stderr = AsyncMock()
103+
mock_process.stderr.readline = AsyncMock(return_value=b'')
104+
mock_create_subprocess.return_value = mock_process
105+
106+
api_key = "test-api-key"
107+
connection = ZedAgentConnection(["echo", "test"], api_key=api_key)
108+
await connection.start()
109+
110+
# Mock authentication method
111+
auth_call_count = 0
112+
auth_method_used = None
113+
114+
async def mock_authenticate(method_id, api_key=None):
115+
nonlocal auth_call_count, auth_method_used
116+
auth_call_count += 1
117+
auth_method_used = method_id
118+
return {"authenticated": True}
119+
120+
connection.authenticate = mock_authenticate
121+
122+
result = await connection.initialize()
123+
124+
assert auth_call_count == 1
125+
# It should pick the first supported one found in the loop or list
126+
# In the code:
127+
# supported_api_key_methods = ["apikey", "gemini-api-key", "codex-api-key", "openai-api-key"]
128+
# for method in auth_methods: ...
129+
# auth_methods order: chatgpt, codex-api-key, openai-api-key
130+
# chatgpt is not supported.
131+
# codex-api-key is supported.
132+
assert auth_method_used == "codex-api-key"
133+
assert result is not None

0 commit comments

Comments
 (0)