jusi-gigacode 0.1.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.
- jusi_gigacode-0.1.0/.gitignore +7 -0
- jusi_gigacode-0.1.0/LICENSE +21 -0
- jusi_gigacode-0.1.0/PKG-INFO +133 -0
- jusi_gigacode-0.1.0/README.md +90 -0
- jusi_gigacode-0.1.0/pyproject.toml +43 -0
- jusi_gigacode-0.1.0/src/jusi_gigacode/__init__.py +11 -0
- jusi_gigacode-0.1.0/src/jusi_gigacode/catalog.py +27 -0
- jusi_gigacode-0.1.0/src/jusi_gigacode/kernel.py +12 -0
- jusi_gigacode-0.1.0/src/jusi_gigacode/worker.py +93 -0
- jusi_gigacode-0.1.0/tests/test_catalog.py +30 -0
- jusi_gigacode-0.1.0/tests/test_kernel.py +11 -0
- jusi_gigacode-0.1.0/tests/test_worker.py +97 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 notawhaleble
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: jusi-gigacode
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: GigaCode exact-provider plugin for the Jusi ACP family
|
|
5
|
+
Project-URL: Repository, https://github.com/notawhaleble/jusi-gigacode
|
|
6
|
+
Author: notawhaleble
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2026 notawhaleble
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Keywords: acp,agent,gigacode,jupyter,jusi
|
|
30
|
+
Classifier: Development Status :: 3 - Alpha
|
|
31
|
+
Classifier: Framework :: Jupyter
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
38
|
+
Requires-Python: >=3.10
|
|
39
|
+
Requires-Dist: jusi-acp<0.2,>=0.1.0
|
|
40
|
+
Provides-Extra: dev
|
|
41
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
44
|
+
# jusi-gigacode
|
|
45
|
+
|
|
46
|
+
`jusi-gigacode` is the exact GigaCode provider for the shared Jusi ACP family.
|
|
47
|
+
It launches GigaCode over Agent Client Protocol while `jusi-acp` supplies the
|
|
48
|
+
`%%acp` magic, interactive VisiData application, permission choices, durable
|
|
49
|
+
follow-ups, cancellation, session controls, completion, and editor actions.
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
Install this package, `jusi-acp`, and Jusi in the Python environment used by the
|
|
54
|
+
target Jupyter kernel. The `gigacode` executable must be available on that
|
|
55
|
+
target's `PATH`.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install jusi-gigacode
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For development from sibling checkouts:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
python3.12 -m venv .venv
|
|
65
|
+
.venv/bin/pip install -e ../jusi-acp -e '.[dev]'
|
|
66
|
+
.venv/bin/pytest
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Configuration
|
|
70
|
+
|
|
71
|
+
Add an ACP alias to `~/.jusi/jusi.toml` on the kernel target:
|
|
72
|
+
|
|
73
|
+
```toml
|
|
74
|
+
[acp.work]
|
|
75
|
+
provider = "gigacode"
|
|
76
|
+
path = "/path/to/project"
|
|
77
|
+
additional_directories = ["../shared"]
|
|
78
|
+
model = "provider-owned-model-name"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Then run:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
%%acp work
|
|
85
|
+
Inspect the project and implement the requested change.
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The provider launches `gigacode --acp`. A live GigaCode handshake has confirmed
|
|
89
|
+
ACP protocol version 1, the browser-backed `gigacode` authentication method,
|
|
90
|
+
session load/resume support, and HTTP/SSE MCP support.
|
|
91
|
+
|
|
92
|
+
Provider-specific alias options are:
|
|
93
|
+
|
|
94
|
+
- `executable`: executable name or path; defaults to `gigacode`.
|
|
95
|
+
- `model`: passed as `--model VALUE`.
|
|
96
|
+
- `approval_mode`: passed as `--approval-mode VALUE`. Leaving it unset allows
|
|
97
|
+
ACP permission requests to reach the interactive Jusi client.
|
|
98
|
+
- `arguments`: an array of additional command-line arguments.
|
|
99
|
+
- `environment`: environment-variable overrides for the GigaCode process.
|
|
100
|
+
- `auth_method`: ACP authentication method ID. It defaults to `gigacode`, as
|
|
101
|
+
advertised by GigaCode, or may be set to an empty string to skip the proactive
|
|
102
|
+
authentication call.
|
|
103
|
+
|
|
104
|
+
The executable inherits the target environment, including desktop/session
|
|
105
|
+
variables used by browser launchers. With the default authentication method,
|
|
106
|
+
`jusi-acp` calls ACP `authenticate` before creating the session. GigaCode owns
|
|
107
|
+
opening its authorization URL and waiting for the callback; after it completes,
|
|
108
|
+
the original cell body is submitted automatically.
|
|
109
|
+
|
|
110
|
+
Session controls and family commands include:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
%%acp work --load SESSION_ID
|
|
114
|
+
Replay and continue a stored session.
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
%%acp work --resume SESSION_ID
|
|
119
|
+
Continue a stored session without replay.
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Follow-up cells can use `/mode ID`, `/config ID VALUE`, `/auth METHOD_ID`, and
|
|
123
|
+
`/cancel`. Slash commands advertised by GigaCode are also completed and sent as
|
|
124
|
+
ordinary ACP prompts.
|
|
125
|
+
|
|
126
|
+
## Verified ACP handshake
|
|
127
|
+
|
|
128
|
+
GigaCode identifies its agent implementation as `qwen-code` with the title
|
|
129
|
+
`GigaCode`. It advertises authentication method ID `gigacode`; authentication is
|
|
130
|
+
handled by the GigaCode backend through its browser flow. The initialize call
|
|
131
|
+
waits for that flow to finish, after which `jusi-acp` completes ACP
|
|
132
|
+
authentication, establishes the session, and submits the original cell body
|
|
133
|
+
without requiring the user to repeat it.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# jusi-gigacode
|
|
2
|
+
|
|
3
|
+
`jusi-gigacode` is the exact GigaCode provider for the shared Jusi ACP family.
|
|
4
|
+
It launches GigaCode over Agent Client Protocol while `jusi-acp` supplies the
|
|
5
|
+
`%%acp` magic, interactive VisiData application, permission choices, durable
|
|
6
|
+
follow-ups, cancellation, session controls, completion, and editor actions.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
Install this package, `jusi-acp`, and Jusi in the Python environment used by the
|
|
11
|
+
target Jupyter kernel. The `gigacode` executable must be available on that
|
|
12
|
+
target's `PATH`.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install jusi-gigacode
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
For development from sibling checkouts:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
python3.12 -m venv .venv
|
|
22
|
+
.venv/bin/pip install -e ../jusi-acp -e '.[dev]'
|
|
23
|
+
.venv/bin/pytest
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Configuration
|
|
27
|
+
|
|
28
|
+
Add an ACP alias to `~/.jusi/jusi.toml` on the kernel target:
|
|
29
|
+
|
|
30
|
+
```toml
|
|
31
|
+
[acp.work]
|
|
32
|
+
provider = "gigacode"
|
|
33
|
+
path = "/path/to/project"
|
|
34
|
+
additional_directories = ["../shared"]
|
|
35
|
+
model = "provider-owned-model-name"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Then run:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
%%acp work
|
|
42
|
+
Inspect the project and implement the requested change.
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The provider launches `gigacode --acp`. A live GigaCode handshake has confirmed
|
|
46
|
+
ACP protocol version 1, the browser-backed `gigacode` authentication method,
|
|
47
|
+
session load/resume support, and HTTP/SSE MCP support.
|
|
48
|
+
|
|
49
|
+
Provider-specific alias options are:
|
|
50
|
+
|
|
51
|
+
- `executable`: executable name or path; defaults to `gigacode`.
|
|
52
|
+
- `model`: passed as `--model VALUE`.
|
|
53
|
+
- `approval_mode`: passed as `--approval-mode VALUE`. Leaving it unset allows
|
|
54
|
+
ACP permission requests to reach the interactive Jusi client.
|
|
55
|
+
- `arguments`: an array of additional command-line arguments.
|
|
56
|
+
- `environment`: environment-variable overrides for the GigaCode process.
|
|
57
|
+
- `auth_method`: ACP authentication method ID. It defaults to `gigacode`, as
|
|
58
|
+
advertised by GigaCode, or may be set to an empty string to skip the proactive
|
|
59
|
+
authentication call.
|
|
60
|
+
|
|
61
|
+
The executable inherits the target environment, including desktop/session
|
|
62
|
+
variables used by browser launchers. With the default authentication method,
|
|
63
|
+
`jusi-acp` calls ACP `authenticate` before creating the session. GigaCode owns
|
|
64
|
+
opening its authorization URL and waiting for the callback; after it completes,
|
|
65
|
+
the original cell body is submitted automatically.
|
|
66
|
+
|
|
67
|
+
Session controls and family commands include:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
%%acp work --load SESSION_ID
|
|
71
|
+
Replay and continue a stored session.
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
%%acp work --resume SESSION_ID
|
|
76
|
+
Continue a stored session without replay.
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Follow-up cells can use `/mode ID`, `/config ID VALUE`, `/auth METHOD_ID`, and
|
|
80
|
+
`/cancel`. Slash commands advertised by GigaCode are also completed and sent as
|
|
81
|
+
ordinary ACP prompts.
|
|
82
|
+
|
|
83
|
+
## Verified ACP handshake
|
|
84
|
+
|
|
85
|
+
GigaCode identifies its agent implementation as `qwen-code` with the title
|
|
86
|
+
`GigaCode`. It advertises authentication method ID `gigacode`; authentication is
|
|
87
|
+
handled by the GigaCode backend through its browser flow. The initialize call
|
|
88
|
+
waits for that flow to finish, after which `jusi-acp` completes ACP
|
|
89
|
+
authentication, establishes the session, and submits the original cell body
|
|
90
|
+
without requiring the user to repeat it.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.21"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "jusi-gigacode"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "GigaCode exact-provider plugin for the Jusi ACP family"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [{ name = "notawhaleble" }]
|
|
13
|
+
keywords = ["acp", "gigacode", "jusi", "jupyter", "agent"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Framework :: Jupyter",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"jusi-acp>=0.1.0,<0.2",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
dev = ["pytest>=8"]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Repository = "https://github.com/notawhaleble/jusi-gigacode"
|
|
33
|
+
|
|
34
|
+
[project.entry-points."jusi.plugins.v1"]
|
|
35
|
+
gigacode = "jusi_gigacode.catalog:catalog_entry"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.wheel]
|
|
38
|
+
packages = ["src/jusi_gigacode"]
|
|
39
|
+
|
|
40
|
+
[tool.pytest.ini_options]
|
|
41
|
+
testpaths = ["tests"]
|
|
42
|
+
pythonpath = ["src", "../jusi-acp/src"]
|
|
43
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""GigaCode exact-provider plugin for Jusi ACP."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = version("jusi-gigacode")
|
|
7
|
+
except PackageNotFoundError: # source checkout
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
|
|
10
|
+
__all__ = ["__version__"]
|
|
11
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Side-effect-free Jusi catalog entry for the exact GigaCode provider."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from . import __version__
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def catalog_entry() -> dict[str, Any]:
|
|
10
|
+
# Keep this claim byte-for-byte compatible with jusi_acp.family_claim().
|
|
11
|
+
# Discovery must not import the worker or start the ACP application.
|
|
12
|
+
return {
|
|
13
|
+
"plugin_id": "gigacode",
|
|
14
|
+
"plugin_version": __version__,
|
|
15
|
+
"distribution": "jusi-gigacode",
|
|
16
|
+
"families": [{
|
|
17
|
+
"family_id": "acp",
|
|
18
|
+
"magic_name": "acp",
|
|
19
|
+
"capabilities": ["execute", "followup", "complete", "interrupt", "editor_actions"],
|
|
20
|
+
"presentation": {"syntax": "markdown", "indent": "markdown"},
|
|
21
|
+
}],
|
|
22
|
+
"kernel_extensions": ["jusi_gigacode.kernel"],
|
|
23
|
+
"worker_entry_point": "jusi_gigacode.worker:create_worker",
|
|
24
|
+
"media_types": ["text/x-ansi"],
|
|
25
|
+
"interaction": "terminal_interactive",
|
|
26
|
+
}
|
|
27
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Exact-provider attestation backed by the shared ACP kernel dispatcher."""
|
|
2
|
+
|
|
3
|
+
from jusi_acp import KernelProviderAdapter
|
|
4
|
+
|
|
5
|
+
from . import __version__
|
|
6
|
+
|
|
7
|
+
_adapter = KernelProviderAdapter("gigacode", __version__)
|
|
8
|
+
|
|
9
|
+
jusi_kernel_adapter_v1 = _adapter.manifest
|
|
10
|
+
configure_jusi_runtime_v1 = _adapter.configure
|
|
11
|
+
load_ipython_extension = _adapter.load_ipython_extension
|
|
12
|
+
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""GigaCode launch validation and shared ACP worker construction."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from collections.abc import Mapping
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import shutil
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from jusi_acp import ACPWorker, AgentLaunch, ProviderSpec
|
|
11
|
+
|
|
12
|
+
from . import __version__
|
|
13
|
+
|
|
14
|
+
_COMMON_KEYS = {"provider", "path", "additional_directories", "mcp_servers"}
|
|
15
|
+
_PROVIDER_KEYS = {
|
|
16
|
+
"executable", "arguments", "model", "approval_mode", "environment", "auth_method",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def resolve_launch(configuration: Mapping[str, Any], cwd: Path) -> AgentLaunch:
|
|
21
|
+
"""Validate one alias and construct a GigaCode ACP invocation."""
|
|
22
|
+
unknown = sorted(set(configuration) - _COMMON_KEYS - _PROVIDER_KEYS)
|
|
23
|
+
if unknown:
|
|
24
|
+
raise ValueError(f"unsupported GigaCode option(s): {', '.join(unknown)}")
|
|
25
|
+
|
|
26
|
+
executable = _string(configuration.get("executable", "gigacode"), "executable")
|
|
27
|
+
resolved_executable = _resolve_executable(executable or "gigacode")
|
|
28
|
+
arguments = _string_list(configuration.get("arguments", []), "arguments")
|
|
29
|
+
|
|
30
|
+
argv = [resolved_executable, "--acp"]
|
|
31
|
+
model = _string(configuration.get("model", ""), "model")
|
|
32
|
+
if model:
|
|
33
|
+
argv.extend(("--model", model))
|
|
34
|
+
approval_mode = _string(configuration.get("approval_mode", ""), "approval_mode")
|
|
35
|
+
if approval_mode:
|
|
36
|
+
argv.extend(("--approval-mode", approval_mode))
|
|
37
|
+
argv.extend(arguments)
|
|
38
|
+
|
|
39
|
+
environment = configuration.get("environment", {})
|
|
40
|
+
if not isinstance(environment, Mapping):
|
|
41
|
+
raise TypeError("environment must be a table of string values")
|
|
42
|
+
clean_environment: dict[str, str] = {}
|
|
43
|
+
for key, value in environment.items():
|
|
44
|
+
clean_key = _string(key, "environment name")
|
|
45
|
+
clean_value = _string(value, f"environment value for {clean_key!r}", allow_empty=True)
|
|
46
|
+
if not clean_key or "=" in clean_key:
|
|
47
|
+
raise ValueError("environment names must be non-empty and cannot contain '='")
|
|
48
|
+
clean_environment[clean_key] = clean_value
|
|
49
|
+
|
|
50
|
+
# GigaCode advertises this browser-backed method in its ACP v1 initialize
|
|
51
|
+
# response. Set auth_method = "" only when proactive authentication should
|
|
52
|
+
# be skipped for an already authenticated or specially configured install.
|
|
53
|
+
auth_method = _string(configuration.get("auth_method", "gigacode"), "auth_method")
|
|
54
|
+
return AgentLaunch(tuple(argv), cwd, clean_environment, auth_method)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _resolve_executable(value: str) -> str:
|
|
58
|
+
expanded = os.path.expanduser(value)
|
|
59
|
+
if os.path.sep in expanded:
|
|
60
|
+
path = Path(expanded).resolve()
|
|
61
|
+
if not path.is_file():
|
|
62
|
+
raise ValueError(f"GigaCode executable does not exist: {path}")
|
|
63
|
+
if not os.access(path, os.X_OK):
|
|
64
|
+
raise ValueError(f"GigaCode executable is not executable: {path}")
|
|
65
|
+
return str(path)
|
|
66
|
+
found = shutil.which(expanded)
|
|
67
|
+
if found is None:
|
|
68
|
+
raise ValueError(f"GigaCode executable {expanded!r} was not found on PATH")
|
|
69
|
+
return found
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _string(value: Any, label: str, *, allow_empty: bool = True) -> str:
|
|
73
|
+
if not isinstance(value, str):
|
|
74
|
+
raise TypeError(f"{label} must be a string")
|
|
75
|
+
if any(character in value for character in ("\x00", "\n", "\r")):
|
|
76
|
+
raise ValueError(f"{label} contains an invalid control character")
|
|
77
|
+
result = value.strip()
|
|
78
|
+
if not allow_empty and not result:
|
|
79
|
+
raise ValueError(f"{label} cannot be empty")
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _string_list(value: Any, label: str) -> list[str]:
|
|
84
|
+
if not isinstance(value, list):
|
|
85
|
+
raise TypeError(f"{label} must be an array of strings")
|
|
86
|
+
return [_string(item, f"{label} item", allow_empty=False) for item in value]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
PROVIDER = ProviderSpec("gigacode", __version__, resolve_launch)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def create_worker(context: Any) -> ACPWorker:
|
|
93
|
+
return ACPWorker(context, PROVIDER)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from importlib.metadata import entry_points
|
|
2
|
+
|
|
3
|
+
from jusi.plugin_api import validate_catalog_claims
|
|
4
|
+
from jusi_acp import family_claim
|
|
5
|
+
|
|
6
|
+
from jusi_gigacode import __version__
|
|
7
|
+
from jusi_gigacode.catalog import catalog_entry
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_catalog_is_an_exact_compatible_acp_provider() -> None:
|
|
11
|
+
entry = catalog_entry()
|
|
12
|
+
assert entry["plugin_id"] == "gigacode"
|
|
13
|
+
assert entry["plugin_version"] == __version__
|
|
14
|
+
assert entry["distribution"] == "jusi-gigacode"
|
|
15
|
+
assert entry["families"] == [family_claim()]
|
|
16
|
+
assert entry["kernel_extensions"] == ["jusi_gigacode.kernel"]
|
|
17
|
+
assert entry["worker_entry_point"] == "jusi_gigacode.worker:create_worker"
|
|
18
|
+
validate_catalog_claims({
|
|
19
|
+
"protocol_version": 1,
|
|
20
|
+
"catalog_version": 1,
|
|
21
|
+
"discovery_id": "jusi_gigacode_tests",
|
|
22
|
+
"plugins": [entry],
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_installed_distribution_exposes_exact_entry_point() -> None:
|
|
27
|
+
matches = [item for item in entry_points(group="jusi.plugins.v1") if item.name == "gigacode"]
|
|
28
|
+
if matches:
|
|
29
|
+
assert matches[0].value == "jusi_gigacode.catalog:catalog_entry"
|
|
30
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from jusi_gigacode import __version__
|
|
2
|
+
from jusi_gigacode.kernel import jusi_kernel_adapter_v1
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_kernel_attests_exact_provider_and_family() -> None:
|
|
6
|
+
assert jusi_kernel_adapter_v1() == {
|
|
7
|
+
"plugin_id": "gigacode",
|
|
8
|
+
"plugin_version": __version__,
|
|
9
|
+
"families": [{"family_id": "acp", "magic_name": "acp"}],
|
|
10
|
+
}
|
|
11
|
+
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from types import SimpleNamespace
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from jusi_acp import ACPWorker
|
|
8
|
+
from jusi_gigacode.worker import create_worker, resolve_launch
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_default_launch_uses_acp_and_browser_auth(tmp_path: Path, monkeypatch) -> None: # type: ignore[no-untyped-def]
|
|
12
|
+
monkeypatch.setattr("jusi_gigacode.worker.shutil.which", lambda command: "/tools/gigacode")
|
|
13
|
+
launch = resolve_launch({"provider": "gigacode", "path": str(tmp_path)}, tmp_path)
|
|
14
|
+
assert launch.argv == ("/tools/gigacode", "--acp")
|
|
15
|
+
assert launch.cwd == tmp_path
|
|
16
|
+
assert launch.auth_method == "gigacode"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_launch_maps_provider_options(tmp_path: Path, monkeypatch) -> None: # type: ignore[no-untyped-def]
|
|
20
|
+
monkeypatch.setattr("jusi_gigacode.worker.shutil.which", lambda command: "/tools/custom")
|
|
21
|
+
launch = resolve_launch({
|
|
22
|
+
"executable": "custom",
|
|
23
|
+
"model": "coder-model",
|
|
24
|
+
"approval_mode": "default",
|
|
25
|
+
"arguments": ["--debug"],
|
|
26
|
+
"environment": {"GIGACODE_PROFILE": "work"},
|
|
27
|
+
"auth_method": "gigacode-login",
|
|
28
|
+
}, tmp_path)
|
|
29
|
+
assert launch.argv == (
|
|
30
|
+
"/tools/custom", "--acp", "--model", "coder-model",
|
|
31
|
+
"--approval-mode", "default", "--debug",
|
|
32
|
+
)
|
|
33
|
+
assert launch.environment == {"GIGACODE_PROFILE": "work"}
|
|
34
|
+
assert launch.auth_method == "gigacode-login"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_auth_can_be_disabled_for_an_already_authenticated_install(tmp_path: Path, monkeypatch) -> None: # type: ignore[no-untyped-def]
|
|
38
|
+
monkeypatch.setattr("jusi_gigacode.worker.shutil.which", lambda command: "/tools/gigacode")
|
|
39
|
+
assert resolve_launch({"auth_method": ""}, tmp_path).auth_method == ""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.mark.parametrize("configuration, message", [
|
|
43
|
+
({"unknown": True}, "unsupported GigaCode option"),
|
|
44
|
+
({"arguments": "--debug"}, "arguments must be an array"),
|
|
45
|
+
({"environment": []}, "environment must be a table"),
|
|
46
|
+
({"model": 7}, "model must be a string"),
|
|
47
|
+
])
|
|
48
|
+
def test_invalid_provider_configuration_is_rejected(
|
|
49
|
+
tmp_path: Path, monkeypatch, configuration: dict, message: str
|
|
50
|
+
) -> None: # type: ignore[no-untyped-def]
|
|
51
|
+
monkeypatch.setattr("jusi_gigacode.worker.shutil.which", lambda command: "/tools/gigacode")
|
|
52
|
+
with pytest.raises((TypeError, ValueError), match=message):
|
|
53
|
+
resolve_launch(configuration, tmp_path)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_missing_executable_is_rejected(tmp_path: Path, monkeypatch) -> None: # type: ignore[no-untyped-def]
|
|
57
|
+
monkeypatch.setattr("jusi_gigacode.worker.shutil.which", lambda command: None)
|
|
58
|
+
with pytest.raises(ValueError, match="not found on PATH"):
|
|
59
|
+
resolve_launch({}, tmp_path)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_factory_returns_shared_worker_for_exact_provider() -> None:
|
|
63
|
+
worker = create_worker(SimpleNamespace(plugin_id="gigacode"))
|
|
64
|
+
try:
|
|
65
|
+
assert isinstance(worker, ACPWorker)
|
|
66
|
+
assert worker.provider.plugin_id == "gigacode"
|
|
67
|
+
finally:
|
|
68
|
+
worker.close()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_exact_worker_hands_authenticated_launch_to_shared_application(
|
|
72
|
+
tmp_path: Path, monkeypatch
|
|
73
|
+
) -> None: # type: ignore[no-untyped-def]
|
|
74
|
+
monkeypatch.setattr("jusi_gigacode.worker.shutil.which", lambda command: "/tools/gigacode")
|
|
75
|
+
worker = create_worker(SimpleNamespace(plugin_id="gigacode"))
|
|
76
|
+
try:
|
|
77
|
+
result = worker.handle("execute", {
|
|
78
|
+
"alias": "work",
|
|
79
|
+
"body": "continue after login",
|
|
80
|
+
"cwd": str(tmp_path),
|
|
81
|
+
"configuration": {"auth_method": "gigacode-login"},
|
|
82
|
+
"additional_directories": [],
|
|
83
|
+
"mcp_servers": [],
|
|
84
|
+
"session_action": "new",
|
|
85
|
+
"session_id": "",
|
|
86
|
+
})
|
|
87
|
+
surface = result.core_requests[0]
|
|
88
|
+
payload = json.loads(Path(surface.argv[3]).read_text(encoding="utf-8"))
|
|
89
|
+
assert payload["launch"] == {
|
|
90
|
+
"argv": ["/tools/gigacode", "--acp"],
|
|
91
|
+
"cwd": str(tmp_path),
|
|
92
|
+
"environment": {},
|
|
93
|
+
"auth_method": "gigacode-login",
|
|
94
|
+
}
|
|
95
|
+
assert payload["submission"]["body"] == "continue after login"
|
|
96
|
+
finally:
|
|
97
|
+
worker.close()
|