gitcode-api 1.2.9__tar.gz → 1.2.11__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.
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/PKG-INFO +36 -26
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/README.md +35 -25
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/llm/__init__.py +4 -1
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/llm/jiuwen.py +29 -6
- gitcode_api-1.2.11/gitcode_api/llm/openai.py +88 -0
- gitcode_api-1.2.11/gitcode_api/version.txt +1 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api.egg-info/PKG-INFO +36 -26
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/pyproject.toml +1 -1
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/tests/test_llm_tools.py +3 -16
- gitcode_api-1.2.9/gitcode_api/llm/openai.py +0 -56
- gitcode_api-1.2.9/gitcode_api/version.txt +0 -1
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/LICENSE +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/__init__.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/__main__.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/_base_client.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/_base_resource.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/_cli_banner.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/_client.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/_exceptions.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/_models.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/cli.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/llm/_tool.py +4 -4
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/llm/mcp.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/py.typed +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/resources/__init__.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/resources/_shared.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/resources/account.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/resources/collaboration.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/resources/misc.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/resources/repositories.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api/run_mcp.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api.egg-info/SOURCES.txt +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api.egg-info/dependency_links.txt +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api.egg-info/entry_points.txt +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api.egg-info/requires.txt +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/gitcode_api.egg-info/top_level.txt +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/setup.cfg +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/tests/test_base_client.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/tests/test_build_manifest.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/tests/test_cli.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/tests/test_client.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/tests/test_models.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/tests/test_resources_account.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/tests/test_resources_collaboration.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/tests/test_resources_misc.py +0 -0
- {gitcode_api-1.2.9 → gitcode_api-1.2.11}/tests/test_resources_repositories.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitcode-api
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.11
|
|
4
4
|
Summary: Easy to use Python SDK for the GitCode REST API. Providing builtin CLI tool, and optional LLM integration (MCP, OpenAI tool, and openJiuwen tool) for agents. Community-maintained.
|
|
5
5
|
Author-email: Hugo Huang <hugo@hugohuang.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -231,7 +231,7 @@ Every resource group inherits a cached `methods` property from the shared resour
|
|
|
231
231
|
|
|
232
232
|
## LLM tools, MCP, and openJiuwen
|
|
233
233
|
|
|
234
|
-
The `gitcode_api.llm` module exposes a single
|
|
234
|
+
The `gitcode_api.llm` module exposes a single unified tool, **`gitcode_api_tool`**, that routes calls to respective sync or async SDK resource groups. Model-facing parameters match the JSON schema used by OpenAI-style function tools:
|
|
235
235
|
|
|
236
236
|
| Parameter | Role |
|
|
237
237
|
| --- | --- |
|
|
@@ -240,11 +240,11 @@ The `gitcode_api.llm` module exposes a single logical tool, **`gitcode_api_tool`
|
|
|
240
240
|
| `params` | Keyword arguments for the method as a JSON object; omitted or `null` is treated as `{}`. |
|
|
241
241
|
| `help` | When `true`, returns formatted help (available methods or a target signature) instead of performing a normal API call where applicable. |
|
|
242
242
|
|
|
243
|
-
|
|
243
|
+
Tool payloads are JSON-serialized strings: successes look like plain objects (`APIObject.to_dict()`, base64-wrapped `bytes`, and similar); failures use `"error": true`, a `"message"` string, and optional extra fields on HTTP/configuration errors.
|
|
244
244
|
|
|
245
245
|
### OpenAI tool (`GitCodeOpenAITool`)
|
|
246
246
|
|
|
247
|
-
No extra dependencies beyond the core package. Build a Chat Completions–style tool definition with `.tool` or `.to_dict()`, then invoke the same instance with the arguments above (sync) or configure async mode for `await`.
|
|
247
|
+
No extra dependencies beyond the core package. Build a Chat Completions–style tool definition with `.tool` or `.to_dict()`, then invoke the same instance with the arguments above (sync) or configure async mode for `await`. Each invocation applies `json.dumps` to payload so the return type is **always `str`**. The default keyword argument `indent=2` pretty-prints JSON; pass `indent=None` for a compact single-line string.
|
|
248
248
|
|
|
249
249
|
```python
|
|
250
250
|
from gitcode_api.llm import GitCodeOpenAITool
|
|
@@ -265,33 +265,43 @@ and handle tool calls directly:
|
|
|
265
265
|
|
|
266
266
|
```python
|
|
267
267
|
import json
|
|
268
|
-
|
|
269
|
-
from
|
|
268
|
+
import os
|
|
269
|
+
from typing import Dict, List
|
|
270
270
|
|
|
271
|
-
|
|
272
|
-
"gitcode_api_tool": GitCodeOpenAITool(owner="SushiNinja", repo="GitCode-API"),
|
|
273
|
-
}
|
|
274
|
-
client = OpenAI(
|
|
275
|
-
api_key="your-openai-compatible-api-key",
|
|
276
|
-
base_url="https://your-openai-compatible-base-url/v1",
|
|
277
|
-
)
|
|
271
|
+
from openai import OpenAI
|
|
278
272
|
|
|
279
|
-
|
|
280
|
-
model="gpt-4.1-mini",
|
|
281
|
-
messages=[{"role": "user", "content": "List the last 5 commits."}],
|
|
282
|
-
tools=[tools["gitcode_api_tool"].tool],
|
|
283
|
-
)
|
|
273
|
+
from gitcode_api.llm import GitCodeOpenAITool
|
|
284
274
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
275
|
+
MESSAGE_SEP = "\n" + "=" * 60 + "\n"
|
|
276
|
+
USER_QUERY = "List the repos owned by SushiNinja."
|
|
277
|
+
CONVERSATION: List[Dict[str, str]] = [dict(role="user", content=USER_QUERY)]
|
|
278
|
+
|
|
279
|
+
tools = {"gitcode_api_tool": GitCodeOpenAITool()}
|
|
280
|
+
client = OpenAI()
|
|
281
|
+
|
|
282
|
+
print("U:\n" + USER_QUERY + MESSAGE_SEP)
|
|
283
|
+
while True:
|
|
284
|
+
response = (
|
|
285
|
+
client.chat.completions.create(
|
|
286
|
+
model="gpt-5.4-nano",
|
|
287
|
+
messages=CONVERSATION,
|
|
288
|
+
tools=[tools["gitcode_api_tool"].tool],
|
|
289
|
+
)
|
|
290
|
+
.choices[0]
|
|
291
|
+
.message
|
|
292
|
+
)
|
|
293
|
+
CONVERSATION.append(response.to_dict())
|
|
294
|
+
print("A:\n" + (response.content or ""))
|
|
295
|
+
for tool_call in response.tool_calls or []:
|
|
296
|
+
selected_tool = tools[tool_call.function.name]
|
|
297
|
+
result = selected_tool(**json.loads(tool_call.function.arguments))
|
|
298
|
+
CONVERSATION.append(dict(role="tool", tool_call_id=tool_call.id, content=result))
|
|
299
|
+
print(f"<Calling tool {tool_call.function.name}({tool_call.function.arguments})>")
|
|
300
|
+
print(MESSAGE_SEP)
|
|
301
|
+
if not response.tool_calls:
|
|
302
|
+
break
|
|
291
303
|
```
|
|
292
304
|
|
|
293
|
-
Constructor options mirror `GitCode` / `AsyncGitCode`: `client=`, `async_client=`, `api_key=`, `owner=`, `repo=`, `base_url=`, `timeout=`, and `decrypt=`. For dict-driven setups that reserve the name `async`, you may pass `**{"async": True}` instead of `async_mode=True` (but not both).
|
|
294
|
-
|
|
295
305
|
### MCP server and MCP tool (FastMCP)
|
|
296
306
|
|
|
297
307
|
[MCP](https://modelcontextprotocol.io) integration uses [FastMCP](https://github.com/jlowin/fastmcp). Install the optional extra (requires **Python 3.10+** because of the `fastmcp` dependency):
|
|
@@ -194,7 +194,7 @@ Every resource group inherits a cached `methods` property from the shared resour
|
|
|
194
194
|
|
|
195
195
|
## LLM tools, MCP, and openJiuwen
|
|
196
196
|
|
|
197
|
-
The `gitcode_api.llm` module exposes a single
|
|
197
|
+
The `gitcode_api.llm` module exposes a single unified tool, **`gitcode_api_tool`**, that routes calls to respective sync or async SDK resource groups. Model-facing parameters match the JSON schema used by OpenAI-style function tools:
|
|
198
198
|
|
|
199
199
|
| Parameter | Role |
|
|
200
200
|
| --- | --- |
|
|
@@ -203,11 +203,11 @@ The `gitcode_api.llm` module exposes a single logical tool, **`gitcode_api_tool`
|
|
|
203
203
|
| `params` | Keyword arguments for the method as a JSON object; omitted or `null` is treated as `{}`. |
|
|
204
204
|
| `help` | When `true`, returns formatted help (available methods or a target signature) instead of performing a normal API call where applicable. |
|
|
205
205
|
|
|
206
|
-
|
|
206
|
+
Tool payloads are JSON-serialized strings: successes look like plain objects (`APIObject.to_dict()`, base64-wrapped `bytes`, and similar); failures use `"error": true`, a `"message"` string, and optional extra fields on HTTP/configuration errors.
|
|
207
207
|
|
|
208
208
|
### OpenAI tool (`GitCodeOpenAITool`)
|
|
209
209
|
|
|
210
|
-
No extra dependencies beyond the core package. Build a Chat Completions–style tool definition with `.tool` or `.to_dict()`, then invoke the same instance with the arguments above (sync) or configure async mode for `await`.
|
|
210
|
+
No extra dependencies beyond the core package. Build a Chat Completions–style tool definition with `.tool` or `.to_dict()`, then invoke the same instance with the arguments above (sync) or configure async mode for `await`. Each invocation applies `json.dumps` to payload so the return type is **always `str`**. The default keyword argument `indent=2` pretty-prints JSON; pass `indent=None` for a compact single-line string.
|
|
211
211
|
|
|
212
212
|
```python
|
|
213
213
|
from gitcode_api.llm import GitCodeOpenAITool
|
|
@@ -228,33 +228,43 @@ and handle tool calls directly:
|
|
|
228
228
|
|
|
229
229
|
```python
|
|
230
230
|
import json
|
|
231
|
-
|
|
232
|
-
from
|
|
231
|
+
import os
|
|
232
|
+
from typing import Dict, List
|
|
233
233
|
|
|
234
|
-
|
|
235
|
-
"gitcode_api_tool": GitCodeOpenAITool(owner="SushiNinja", repo="GitCode-API"),
|
|
236
|
-
}
|
|
237
|
-
client = OpenAI(
|
|
238
|
-
api_key="your-openai-compatible-api-key",
|
|
239
|
-
base_url="https://your-openai-compatible-base-url/v1",
|
|
240
|
-
)
|
|
234
|
+
from openai import OpenAI
|
|
241
235
|
|
|
242
|
-
|
|
243
|
-
model="gpt-4.1-mini",
|
|
244
|
-
messages=[{"role": "user", "content": "List the last 5 commits."}],
|
|
245
|
-
tools=[tools["gitcode_api_tool"].tool],
|
|
246
|
-
)
|
|
236
|
+
from gitcode_api.llm import GitCodeOpenAITool
|
|
247
237
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
238
|
+
MESSAGE_SEP = "\n" + "=" * 60 + "\n"
|
|
239
|
+
USER_QUERY = "List the repos owned by SushiNinja."
|
|
240
|
+
CONVERSATION: List[Dict[str, str]] = [dict(role="user", content=USER_QUERY)]
|
|
241
|
+
|
|
242
|
+
tools = {"gitcode_api_tool": GitCodeOpenAITool()}
|
|
243
|
+
client = OpenAI()
|
|
244
|
+
|
|
245
|
+
print("U:\n" + USER_QUERY + MESSAGE_SEP)
|
|
246
|
+
while True:
|
|
247
|
+
response = (
|
|
248
|
+
client.chat.completions.create(
|
|
249
|
+
model="gpt-5.4-nano",
|
|
250
|
+
messages=CONVERSATION,
|
|
251
|
+
tools=[tools["gitcode_api_tool"].tool],
|
|
252
|
+
)
|
|
253
|
+
.choices[0]
|
|
254
|
+
.message
|
|
255
|
+
)
|
|
256
|
+
CONVERSATION.append(response.to_dict())
|
|
257
|
+
print("A:\n" + (response.content or ""))
|
|
258
|
+
for tool_call in response.tool_calls or []:
|
|
259
|
+
selected_tool = tools[tool_call.function.name]
|
|
260
|
+
result = selected_tool(**json.loads(tool_call.function.arguments))
|
|
261
|
+
CONVERSATION.append(dict(role="tool", tool_call_id=tool_call.id, content=result))
|
|
262
|
+
print(f"<Calling tool {tool_call.function.name}({tool_call.function.arguments})>")
|
|
263
|
+
print(MESSAGE_SEP)
|
|
264
|
+
if not response.tool_calls:
|
|
265
|
+
break
|
|
254
266
|
```
|
|
255
267
|
|
|
256
|
-
Constructor options mirror `GitCode` / `AsyncGitCode`: `client=`, `async_client=`, `api_key=`, `owner=`, `repo=`, `base_url=`, `timeout=`, and `decrypt=`. For dict-driven setups that reserve the name `async`, you may pass `**{"async": True}` instead of `async_mode=True` (but not both).
|
|
257
|
-
|
|
258
268
|
### MCP server and MCP tool (FastMCP)
|
|
259
269
|
|
|
260
270
|
[MCP](https://modelcontextprotocol.io) integration uses [FastMCP](https://github.com/jlowin/fastmcp). Install the optional extra (requires **Python 3.10+** because of the `fastmcp` dependency):
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
from importlib import import_module
|
|
4
4
|
from typing import TYPE_CHECKING, Any, Dict
|
|
5
5
|
|
|
6
|
+
from ._tool import GitCodeLLMTool
|
|
7
|
+
|
|
6
8
|
if TYPE_CHECKING:
|
|
7
9
|
from .jiuwen import create_openjiuwen_gitcode_api_tool
|
|
8
10
|
from .mcp import (
|
|
@@ -25,7 +27,7 @@ _IMPORT_MAP = {
|
|
|
25
27
|
"create_openjiuwen_gitcode_api_tool": ".jiuwen",
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
_IMPORT_CACHE: Dict[str, Any] = {}
|
|
30
|
+
_IMPORT_CACHE: Dict[str, Any] = {"GitCodeLLMTool": GitCodeLLMTool}
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
def __getattr__(name: str) -> object:
|
|
@@ -50,6 +52,7 @@ def __dir__() -> list[str]:
|
|
|
50
52
|
|
|
51
53
|
|
|
52
54
|
__all__ = [
|
|
55
|
+
"GitCodeLLMTool",
|
|
53
56
|
"GitCodeOpenAITool",
|
|
54
57
|
"GitCodeMCP",
|
|
55
58
|
"create_mcp_gitcode_api_tool",
|
|
@@ -11,6 +11,7 @@ from ._tool import TOOL_DESCRIPTION, TOOL_NAME, TOOL_PARAMETERS, GitCodeLLMTool
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
from openjiuwen.core.foundation.tool import LocalFunction
|
|
13
13
|
|
|
14
|
+
|
|
14
15
|
def _missing_openjiuwen_error() -> ImportError:
|
|
15
16
|
return ImportError(
|
|
16
17
|
"The openJiuwen tool support requires the optional dependency: pip install openjiuwen. "
|
|
@@ -29,8 +30,30 @@ def _openjiuwen_tool_decorator() -> Callable[..., Any]:
|
|
|
29
30
|
class _GitCodeJiuwenTool(GitCodeLLMTool):
|
|
30
31
|
"""Build an openJiuwen ``LocalFunction`` bound to :class:`GitCodeLLMTool` (async invoke only)."""
|
|
31
32
|
|
|
32
|
-
def __init__(
|
|
33
|
-
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
*,
|
|
36
|
+
name: Optional[str] = None,
|
|
37
|
+
description: Optional[str] = None,
|
|
38
|
+
client: Optional[GitCode] = None,
|
|
39
|
+
async_client: Optional[AsyncGitCode] = None,
|
|
40
|
+
api_key: Optional[str] = None,
|
|
41
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
42
|
+
timeout: Optional[float] = None,
|
|
43
|
+
decrypt: Optional[Callable[..., Any]] = None,
|
|
44
|
+
owner: Optional[str] = None,
|
|
45
|
+
repo: Optional[str] = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
super().__init__(
|
|
48
|
+
client=client,
|
|
49
|
+
async_client=async_client,
|
|
50
|
+
api_key=api_key,
|
|
51
|
+
owner=owner,
|
|
52
|
+
repo=repo,
|
|
53
|
+
base_url=base_url,
|
|
54
|
+
timeout=timeout,
|
|
55
|
+
decrypt=decrypt,
|
|
56
|
+
)
|
|
34
57
|
tool_name = name if name is not None else TOOL_NAME
|
|
35
58
|
tool_description = description if description is not None else TOOL_DESCRIPTION
|
|
36
59
|
oj_tool = _openjiuwen_tool_decorator()
|
|
@@ -59,11 +82,11 @@ def create_openjiuwen_gitcode_api_tool(
|
|
|
59
82
|
client: Optional[GitCode] = None,
|
|
60
83
|
async_client: Optional[AsyncGitCode] = None,
|
|
61
84
|
api_key: Optional[str] = None,
|
|
62
|
-
owner: Optional[str] = None,
|
|
63
|
-
repo: Optional[str] = None,
|
|
64
85
|
base_url: str = DEFAULT_BASE_URL,
|
|
65
86
|
timeout: Optional[float] = None,
|
|
66
87
|
decrypt: Optional[Callable[..., Any]] = None,
|
|
88
|
+
owner: Optional[str] = None,
|
|
89
|
+
repo: Optional[str] = None,
|
|
67
90
|
) -> "LocalFunction":
|
|
68
91
|
"""Create an openJiuwen ``LocalFunction`` for the GitCode API tool.
|
|
69
92
|
|
|
@@ -72,11 +95,11 @@ def create_openjiuwen_gitcode_api_tool(
|
|
|
72
95
|
:param client: Optional synchronous GitCode client.
|
|
73
96
|
:param async_client: Optional asynchronous GitCode client.
|
|
74
97
|
:param api_key: Personal access token when clients are not supplied.
|
|
75
|
-
:param owner: Default repository owner for generated clients.
|
|
76
|
-
:param repo: Default repository name for generated clients.
|
|
77
98
|
:param base_url: Base URL for generated clients.
|
|
78
99
|
:param timeout: Request timeout for generated clients.
|
|
79
100
|
:param decrypt: Optional decryption function for encrypted access tokens.
|
|
101
|
+
:param owner: Default repository owner for generated clients.
|
|
102
|
+
:param repo: Default repository name for generated clients.
|
|
80
103
|
:returns: openJiuwen ``LocalFunction`` with the standard parameter schema.
|
|
81
104
|
"""
|
|
82
105
|
adapter = _GitCodeJiuwenTool(
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""OpenAI tool adapter for the GitCode SDK."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from functools import cached_property
|
|
5
|
+
from typing import Any, Callable, Coroutine, Dict, Optional, Union
|
|
6
|
+
|
|
7
|
+
from gitcode_api import AsyncGitCode, GitCode
|
|
8
|
+
from gitcode_api._base_client import DEFAULT_BASE_URL
|
|
9
|
+
|
|
10
|
+
from ._tool import TOOL_DESCRIPTION, TOOL_NAME, TOOL_PARAMETERS, GitCodeLLMTool
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class GitCodeOpenAITool(GitCodeLLMTool):
|
|
14
|
+
"""OpenAI-compatible callable tool for invoking GitCode SDK resources.
|
|
15
|
+
|
|
16
|
+
:param async_mode: When true, calling the instance returns the async tool coroutine.
|
|
17
|
+
:param indent: ``indent`` argument passed to :func:`json.dumps` when serializing
|
|
18
|
+
invocation results (default ``2``).
|
|
19
|
+
:param client: Optional synchronous GitCode client.
|
|
20
|
+
:param async_client: Optional asynchronous GitCode client.
|
|
21
|
+
:param api_key: Personal access token used when clients are not supplied.
|
|
22
|
+
:param base_url: Base URL for generated clients.
|
|
23
|
+
:param timeout: Request timeout for generated clients.
|
|
24
|
+
:param decrypt: Optional decryption function for encrypted access tokens.
|
|
25
|
+
:param owner: Default repository owner for generated clients.
|
|
26
|
+
:param repo: Default repository name for generated clients.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
name = TOOL_NAME
|
|
30
|
+
description = TOOL_DESCRIPTION
|
|
31
|
+
parameters = TOOL_PARAMETERS
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
async_mode: bool = False,
|
|
36
|
+
indent: int = 2,
|
|
37
|
+
*,
|
|
38
|
+
client: Optional[GitCode] = None,
|
|
39
|
+
async_client: Optional[AsyncGitCode] = None,
|
|
40
|
+
api_key: Optional[str] = None,
|
|
41
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
42
|
+
timeout: Optional[float] = None,
|
|
43
|
+
decrypt: Optional[Callable] = None,
|
|
44
|
+
owner: Optional[str] = None,
|
|
45
|
+
repo: Optional[str] = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Create an OpenAI tool wrapper."""
|
|
48
|
+
self.indent = indent
|
|
49
|
+
self.async_mode = bool(async_mode)
|
|
50
|
+
super().__init__(
|
|
51
|
+
client=client,
|
|
52
|
+
async_client=async_client,
|
|
53
|
+
api_key=api_key,
|
|
54
|
+
owner=owner,
|
|
55
|
+
repo=repo,
|
|
56
|
+
base_url=base_url,
|
|
57
|
+
timeout=timeout,
|
|
58
|
+
decrypt=decrypt,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
@cached_property
|
|
62
|
+
def tool(self) -> Dict[str, Any]:
|
|
63
|
+
"""Return this tool in OpenAI Chat Completions tool format."""
|
|
64
|
+
return {
|
|
65
|
+
"type": "function",
|
|
66
|
+
"function": {
|
|
67
|
+
"name": self.name,
|
|
68
|
+
"description": self.description,
|
|
69
|
+
"parameters": self.parameters,
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
74
|
+
"""Return this tool in OpenAI Chat Completions tool format."""
|
|
75
|
+
return self.tool
|
|
76
|
+
|
|
77
|
+
async def __async_call__(self, *args, **kwargs) -> str:
|
|
78
|
+
result = await super().__async_call__(*args, **kwargs)
|
|
79
|
+
return json.dumps(result, ensure_ascii=False, indent=self.indent)
|
|
80
|
+
|
|
81
|
+
def __call__(self, *args, **kwargs) -> Union[str, Coroutine[Any, Any, str]]:
|
|
82
|
+
"""Invoke the configured sync or async tool callable."""
|
|
83
|
+
if self.async_mode:
|
|
84
|
+
return self.__async_call__(*args, **kwargs)
|
|
85
|
+
return json.dumps(super().__call__(*args, **kwargs), ensure_ascii=False, indent=self.indent)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
__all__ = ["GitCodeOpenAITool"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.2.11
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitcode-api
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.11
|
|
4
4
|
Summary: Easy to use Python SDK for the GitCode REST API. Providing builtin CLI tool, and optional LLM integration (MCP, OpenAI tool, and openJiuwen tool) for agents. Community-maintained.
|
|
5
5
|
Author-email: Hugo Huang <hugo@hugohuang.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -231,7 +231,7 @@ Every resource group inherits a cached `methods` property from the shared resour
|
|
|
231
231
|
|
|
232
232
|
## LLM tools, MCP, and openJiuwen
|
|
233
233
|
|
|
234
|
-
The `gitcode_api.llm` module exposes a single
|
|
234
|
+
The `gitcode_api.llm` module exposes a single unified tool, **`gitcode_api_tool`**, that routes calls to respective sync or async SDK resource groups. Model-facing parameters match the JSON schema used by OpenAI-style function tools:
|
|
235
235
|
|
|
236
236
|
| Parameter | Role |
|
|
237
237
|
| --- | --- |
|
|
@@ -240,11 +240,11 @@ The `gitcode_api.llm` module exposes a single logical tool, **`gitcode_api_tool`
|
|
|
240
240
|
| `params` | Keyword arguments for the method as a JSON object; omitted or `null` is treated as `{}`. |
|
|
241
241
|
| `help` | When `true`, returns formatted help (available methods or a target signature) instead of performing a normal API call where applicable. |
|
|
242
242
|
|
|
243
|
-
|
|
243
|
+
Tool payloads are JSON-serialized strings: successes look like plain objects (`APIObject.to_dict()`, base64-wrapped `bytes`, and similar); failures use `"error": true`, a `"message"` string, and optional extra fields on HTTP/configuration errors.
|
|
244
244
|
|
|
245
245
|
### OpenAI tool (`GitCodeOpenAITool`)
|
|
246
246
|
|
|
247
|
-
No extra dependencies beyond the core package. Build a Chat Completions–style tool definition with `.tool` or `.to_dict()`, then invoke the same instance with the arguments above (sync) or configure async mode for `await`.
|
|
247
|
+
No extra dependencies beyond the core package. Build a Chat Completions–style tool definition with `.tool` or `.to_dict()`, then invoke the same instance with the arguments above (sync) or configure async mode for `await`. Each invocation applies `json.dumps` to payload so the return type is **always `str`**. The default keyword argument `indent=2` pretty-prints JSON; pass `indent=None` for a compact single-line string.
|
|
248
248
|
|
|
249
249
|
```python
|
|
250
250
|
from gitcode_api.llm import GitCodeOpenAITool
|
|
@@ -265,33 +265,43 @@ and handle tool calls directly:
|
|
|
265
265
|
|
|
266
266
|
```python
|
|
267
267
|
import json
|
|
268
|
-
|
|
269
|
-
from
|
|
268
|
+
import os
|
|
269
|
+
from typing import Dict, List
|
|
270
270
|
|
|
271
|
-
|
|
272
|
-
"gitcode_api_tool": GitCodeOpenAITool(owner="SushiNinja", repo="GitCode-API"),
|
|
273
|
-
}
|
|
274
|
-
client = OpenAI(
|
|
275
|
-
api_key="your-openai-compatible-api-key",
|
|
276
|
-
base_url="https://your-openai-compatible-base-url/v1",
|
|
277
|
-
)
|
|
271
|
+
from openai import OpenAI
|
|
278
272
|
|
|
279
|
-
|
|
280
|
-
model="gpt-4.1-mini",
|
|
281
|
-
messages=[{"role": "user", "content": "List the last 5 commits."}],
|
|
282
|
-
tools=[tools["gitcode_api_tool"].tool],
|
|
283
|
-
)
|
|
273
|
+
from gitcode_api.llm import GitCodeOpenAITool
|
|
284
274
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
275
|
+
MESSAGE_SEP = "\n" + "=" * 60 + "\n"
|
|
276
|
+
USER_QUERY = "List the repos owned by SushiNinja."
|
|
277
|
+
CONVERSATION: List[Dict[str, str]] = [dict(role="user", content=USER_QUERY)]
|
|
278
|
+
|
|
279
|
+
tools = {"gitcode_api_tool": GitCodeOpenAITool()}
|
|
280
|
+
client = OpenAI()
|
|
281
|
+
|
|
282
|
+
print("U:\n" + USER_QUERY + MESSAGE_SEP)
|
|
283
|
+
while True:
|
|
284
|
+
response = (
|
|
285
|
+
client.chat.completions.create(
|
|
286
|
+
model="gpt-5.4-nano",
|
|
287
|
+
messages=CONVERSATION,
|
|
288
|
+
tools=[tools["gitcode_api_tool"].tool],
|
|
289
|
+
)
|
|
290
|
+
.choices[0]
|
|
291
|
+
.message
|
|
292
|
+
)
|
|
293
|
+
CONVERSATION.append(response.to_dict())
|
|
294
|
+
print("A:\n" + (response.content or ""))
|
|
295
|
+
for tool_call in response.tool_calls or []:
|
|
296
|
+
selected_tool = tools[tool_call.function.name]
|
|
297
|
+
result = selected_tool(**json.loads(tool_call.function.arguments))
|
|
298
|
+
CONVERSATION.append(dict(role="tool", tool_call_id=tool_call.id, content=result))
|
|
299
|
+
print(f"<Calling tool {tool_call.function.name}({tool_call.function.arguments})>")
|
|
300
|
+
print(MESSAGE_SEP)
|
|
301
|
+
if not response.tool_calls:
|
|
302
|
+
break
|
|
291
303
|
```
|
|
292
304
|
|
|
293
|
-
Constructor options mirror `GitCode` / `AsyncGitCode`: `client=`, `async_client=`, `api_key=`, `owner=`, `repo=`, `base_url=`, `timeout=`, and `decrypt=`. For dict-driven setups that reserve the name `async`, you may pass `**{"async": True}` instead of `async_mode=True` (but not both).
|
|
294
|
-
|
|
295
305
|
### MCP server and MCP tool (FastMCP)
|
|
296
306
|
|
|
297
307
|
[MCP](https://modelcontextprotocol.io) integration uses [FastMCP](https://github.com/jlowin/fastmcp). Install the optional extra (requires **Python 3.10+** because of the `fastmcp` dependency):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "gitcode-api"
|
|
3
|
-
version = "1.2.
|
|
3
|
+
version = "1.2.11"
|
|
4
4
|
description = "Easy to use Python SDK for the GitCode REST API. Providing builtin CLI tool, and optional LLM integration (MCP, OpenAI tool, and openJiuwen tool) for agents. Community-maintained."
|
|
5
5
|
keywords = [
|
|
6
6
|
"gitcode", "git", "devops", "api", "sdk", "python", "httpx", "client",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import importlib.util
|
|
2
|
+
import json
|
|
2
3
|
from typing import Any, Dict
|
|
3
4
|
|
|
4
5
|
import httpx
|
|
@@ -44,7 +45,7 @@ def test_openai_tool_invokes_sync_client(sync_client_factory: Any) -> None:
|
|
|
44
45
|
http_client.close()
|
|
45
46
|
|
|
46
47
|
assert "/repos/SushiNinja/GitCode-API" in captured["url"]
|
|
47
|
-
assert result == {"full_name": "SushiNinja/GitCode-API"}
|
|
48
|
+
assert json.loads(result) == {"full_name": "SushiNinja/GitCode-API"}
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
@pytest.mark.asyncio
|
|
@@ -63,7 +64,7 @@ async def test_openai_tool_invokes_async_client(async_client_factory: Any) -> No
|
|
|
63
64
|
await http_client.aclose()
|
|
64
65
|
|
|
65
66
|
assert "/users/octocat" in captured["url"]
|
|
66
|
-
assert result == {"login": "octocat"}
|
|
67
|
+
assert json.loads(result) == {"login": "octocat"}
|
|
67
68
|
|
|
68
69
|
|
|
69
70
|
def test_tool_help_does_not_require_api_key(monkeypatch: Any) -> None:
|
|
@@ -129,20 +130,6 @@ def test_help_resource_index_body() -> None:
|
|
|
129
130
|
assert f"gitcode-api://help/{name}" in body
|
|
130
131
|
|
|
131
132
|
|
|
132
|
-
@pytest.mark.asyncio
|
|
133
|
-
async def test_create_mcp_server_registers_help_resources() -> None:
|
|
134
|
-
mcp = create_mcp_server()
|
|
135
|
-
static = await mcp._list_resources_mcp()
|
|
136
|
-
templates = await mcp._list_resource_templates_mcp()
|
|
137
|
-
assert any(str(r.uri) == "gitcode-api://help" for r in static)
|
|
138
|
-
assert any(
|
|
139
|
-
"{op_type}" in str(getattr(t, "uri_template", None) or getattr(t, "uriTemplate", None)) for t in templates
|
|
140
|
-
)
|
|
141
|
-
read = await mcp._read_resource_mcp("gitcode-api://help/pulls")
|
|
142
|
-
payload = read[0].content
|
|
143
|
-
assert "Resource: pulls" in payload
|
|
144
|
-
|
|
145
|
-
|
|
146
133
|
def test_mcp_registers_with_existing_server() -> None:
|
|
147
134
|
class DummyMCP:
|
|
148
135
|
def __init__(self) -> None:
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
"""OpenAI tool adapter for the GitCode SDK."""
|
|
2
|
-
|
|
3
|
-
from functools import cached_property
|
|
4
|
-
from typing import Any, Dict, Optional
|
|
5
|
-
|
|
6
|
-
from ._tool import TOOL_DESCRIPTION, TOOL_NAME, TOOL_PARAMETERS, GitCodeLLMTool
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class GitCodeOpenAITool(GitCodeLLMTool):
|
|
10
|
-
"""OpenAI-compatible callable tool for invoking GitCode SDK resources.
|
|
11
|
-
|
|
12
|
-
:param async_mode: When true, calling the instance returns the async tool coroutine.
|
|
13
|
-
:param kwargs: Forwarded to :class:`gitcode_api.llm.GitCodeLLMTool`. ``{"async": True}``
|
|
14
|
-
is also accepted for frameworks that construct tools from dictionaries.
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
name = TOOL_NAME
|
|
18
|
-
description = TOOL_DESCRIPTION
|
|
19
|
-
parameters = TOOL_PARAMETERS
|
|
20
|
-
|
|
21
|
-
def __init__(self, async_mode: Optional[bool] = None, **kwargs) -> None:
|
|
22
|
-
"""Create an OpenAI tool wrapper."""
|
|
23
|
-
async_kw = kwargs.pop("async", None)
|
|
24
|
-
if async_mode is None:
|
|
25
|
-
async_mode = bool(async_kw)
|
|
26
|
-
elif async_kw is not None:
|
|
27
|
-
raise TypeError("Pass only one of async_mode or async")
|
|
28
|
-
self.async_mode = bool(async_mode)
|
|
29
|
-
super().__init__(**kwargs)
|
|
30
|
-
if self.async_mode:
|
|
31
|
-
self.__call__ = self.__async_call__ # type: ignore[method-assign]
|
|
32
|
-
|
|
33
|
-
@cached_property
|
|
34
|
-
def tool(self) -> Dict[str, Any]:
|
|
35
|
-
"""Return this tool in OpenAI Chat Completions tool format."""
|
|
36
|
-
return {
|
|
37
|
-
"type": "function",
|
|
38
|
-
"function": {
|
|
39
|
-
"name": self.name,
|
|
40
|
-
"description": self.description,
|
|
41
|
-
"parameters": self.parameters,
|
|
42
|
-
},
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
def to_dict(self) -> Dict[str, Any]:
|
|
46
|
-
"""Return this tool in OpenAI Chat Completions tool format."""
|
|
47
|
-
return self.tool
|
|
48
|
-
|
|
49
|
-
def __call__(self, *args: Any, **kwargs) -> Any:
|
|
50
|
-
"""Invoke the configured sync or async tool callable."""
|
|
51
|
-
if self.async_mode:
|
|
52
|
-
return self.__async_call__(*args, **kwargs)
|
|
53
|
-
return super().__call__(*args, **kwargs)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
__all__ = ["GitCodeOpenAITool"]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.2.9
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -227,11 +227,11 @@ class GitCodeLLMTool:
|
|
|
227
227
|
:param client: Optional synchronous GitCode client.
|
|
228
228
|
:param async_client: Optional asynchronous GitCode client.
|
|
229
229
|
:param api_key: Personal access token used when clients are not supplied.
|
|
230
|
-
:param owner: Default repository owner for generated clients.
|
|
231
|
-
:param repo: Default repository name for generated clients.
|
|
232
230
|
:param base_url: Base URL for generated clients.
|
|
233
231
|
:param timeout: Request timeout for generated clients.
|
|
234
232
|
:param decrypt: Optional decryption function for encrypted access tokens.
|
|
233
|
+
:param owner: Default repository owner for generated clients.
|
|
234
|
+
:param repo: Default repository name for generated clients.
|
|
235
235
|
"""
|
|
236
236
|
|
|
237
237
|
def __init__(
|
|
@@ -240,11 +240,11 @@ class GitCodeLLMTool:
|
|
|
240
240
|
client: Optional[GitCode] = None,
|
|
241
241
|
async_client: Optional[AsyncGitCode] = None,
|
|
242
242
|
api_key: Optional[str] = None,
|
|
243
|
-
owner: Optional[str] = None,
|
|
244
|
-
repo: Optional[str] = None,
|
|
245
243
|
base_url: str = DEFAULT_BASE_URL,
|
|
246
244
|
timeout: Optional[float] = None,
|
|
247
245
|
decrypt: Optional[Callable] = None,
|
|
246
|
+
owner: Optional[str] = None,
|
|
247
|
+
repo: Optional[str] = None,
|
|
248
248
|
) -> None:
|
|
249
249
|
"""Create a reusable tool with lazy sync and async clients."""
|
|
250
250
|
self._client = client
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|