python-codex 0.3.3__py3-none-any.whl → 0.4.0__py3-none-any.whl
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.
- pycodex/__init__.py +3 -27
- pycodex/bootstrap.py +5 -45
- pycodex/cli.py +1 -14
- pycodex/context.py +1 -1
- pycodex/events.py +1 -1
- pycodex/model.py +2 -2
- pycodex/portable/__init__.py +45 -0
- pycodex/{portable.py → portable/client.py} +4 -7
- pycodex/{portable_server.py → portable/server.py} +3 -3
- pycodex/protocol.py +1 -1
- pycodex/runtime.py +1 -1
- pycodex/tools/base_tool.py +1 -1
- pycodex/tools/close_agent_tool.py +1 -1
- pycodex/tools/code_mode_manager.py +1 -1
- pycodex/tools/request_permissions_tool.py +1 -1
- pycodex/tools/resume_agent_tool.py +1 -1
- pycodex/{runtime_services.py → tools/runtime_services.py} +5 -5
- pycodex/tools/send_input_tool.py +1 -1
- pycodex/tools/spawn_agent_tool.py +1 -1
- pycodex/tools/unified_exec_manager.py +1 -1
- pycodex/tools/update_plan_tool.py +1 -1
- pycodex/tools/view_image_tool.py +5 -0
- pycodex/tools/wait_agent_tool.py +1 -1
- pycodex/utils/__init__.py +0 -4
- pycodex/utils/_aes_gcm/LICENSE +56 -0
- pycodex/utils/_aes_gcm/README.md +61 -0
- pycodex/utils/_aes_gcm/__init__.py +1 -0
- pycodex/utils/_aes_gcm/aes.py +175 -0
- pycodex/utils/_aes_gcm/gcm.py +73 -0
- pycodex/utils/_aes_gcm/source-sha256.json +4 -0
- pycodex/{compat.py → utils/compat.py} +2 -0
- pycodex/utils/crypto.py +35 -0
- pycodex/utils/debug.py +24 -0
- pycodex/{doctor.py → utils/doctor.py} +3 -3
- pycodex/utils/image_utils.py +3 -2
- pycodex/{model_metadata.py → utils/model_metadata.py} +2 -2
- {python_codex-0.3.3.dist-info → python_codex-0.4.0.dist-info}/METADATA +120 -42
- {python_codex-0.3.3.dist-info → python_codex-0.4.0.dist-info}/RECORD +42 -50
- {python_codex-0.3.3.dist-info → python_codex-0.4.0.dist-info}/entry_points.txt +0 -1
- responses_server/app.py +28 -15
- pycodex/feishu_card.py +0 -793
- pycodex/feishu_link.py +0 -440
- pycodex/tools/ipython_tool.py +0 -142
- workspace_server/__init__.py +0 -39
- workspace_server/__main__.py +0 -4
- workspace_server/app.py +0 -1906
- workspace_server/content_views.LICENSE.txt +0 -470
- workspace_server/content_views.css +0 -1
- workspace_server/content_views.mjs +0 -25
- workspace_server/editor.css +0 -1
- workspace_server/editor.mjs +0 -1260
- workspace_server/owned_process.py +0 -27
- workspace_server/workspace.html +0 -2267
- workspace_server/workspace_views.py +0 -533
- workspace_server/workspaces.html +0 -550
- workspace_server/workspaces.py +0 -632
- {python_codex-0.3.3.dist-info → python_codex-0.4.0.dist-info}/WHEEL +0 -0
- {python_codex-0.3.3.dist-info → python_codex-0.4.0.dist-info}/licenses/LICENSE +0 -0
pycodex/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from .compat import patch_asyncio
|
|
1
|
+
from .utils.compat import patch_asyncio
|
|
2
2
|
|
|
3
3
|
patch_asyncio()
|
|
4
4
|
|
|
@@ -27,7 +27,7 @@ from .protocol import (
|
|
|
27
27
|
UserMessage,
|
|
28
28
|
)
|
|
29
29
|
from .runtime import AgentRuntime
|
|
30
|
-
from .runtime_services import (
|
|
30
|
+
from .tools.runtime_services import (
|
|
31
31
|
PlanStore,
|
|
32
32
|
RequestPermissionsManager,
|
|
33
33
|
RequestUserInputManager,
|
|
@@ -64,31 +64,7 @@ from .tools import (
|
|
|
64
64
|
WebSearchTool,
|
|
65
65
|
WriteStdinTool,
|
|
66
66
|
)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def debug(stop: "bool" = False):
|
|
70
|
-
|
|
71
|
-
import socket
|
|
72
|
-
|
|
73
|
-
import debugpy
|
|
74
|
-
|
|
75
|
-
if debugpy.is_client_connected():
|
|
76
|
-
return
|
|
77
|
-
try:
|
|
78
|
-
host = socket.gethostbyname(socket.getfqdn(socket.gethostname()))
|
|
79
|
-
port = 5000
|
|
80
|
-
|
|
81
|
-
debugpy.listen((host, port), in_process_debug_adapter=False)
|
|
82
|
-
print("Waiting for debugger...\a")
|
|
83
|
-
print(f"ip: {host} port:{port}", flush=True)
|
|
84
|
-
debugpy.wait_for_client()
|
|
85
|
-
print("Connected.")
|
|
86
|
-
if stop:
|
|
87
|
-
debugpy.breakpoint()
|
|
88
|
-
except Exception as e:
|
|
89
|
-
import traceback
|
|
90
|
-
|
|
91
|
-
print("\n".join(traceback.format_exception(e)))
|
|
67
|
+
from .utils.debug import debug
|
|
92
68
|
|
|
93
69
|
|
|
94
70
|
__all__ = [
|
pycodex/bootstrap.py
CHANGED
|
@@ -13,7 +13,10 @@ from .model import (
|
|
|
13
13
|
ResponsesProviderConfig,
|
|
14
14
|
)
|
|
15
15
|
from .runtime import AgentRuntime
|
|
16
|
-
from .runtime_services import
|
|
16
|
+
from .tools.runtime_services import (
|
|
17
|
+
AgentRuntimeEnvironment,
|
|
18
|
+
create_agent_runtime_environment,
|
|
19
|
+
)
|
|
17
20
|
from .utils import get_debug_dir, load_codex_dotenv, uuid7_string
|
|
18
21
|
from .utils.session_persist import rollout_path_for_session
|
|
19
22
|
|
|
@@ -371,47 +374,4 @@ def build_model(
|
|
|
371
374
|
|
|
372
375
|
|
|
373
376
|
def build_runtime(agent: "Agent") -> "AgentRuntime":
|
|
374
|
-
|
|
375
|
-
register_connection_commands(runtime)
|
|
376
|
-
return runtime
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
def register_connection_commands(runtime):
|
|
380
|
-
link = None
|
|
381
|
-
|
|
382
|
-
async def unlink(argument):
|
|
383
|
-
nonlocal link
|
|
384
|
-
if argument:
|
|
385
|
-
raise ValueError("Usage: /unlink")
|
|
386
|
-
if link is None:
|
|
387
|
-
return {"kind": "connection", "lines": ["No Feishu card is linked."]}
|
|
388
|
-
link.detach()
|
|
389
|
-
link = None
|
|
390
|
-
return {"kind": "connection", "lines": ["Unlinked Feishu card."]}
|
|
391
|
-
|
|
392
|
-
async def connect(target):
|
|
393
|
-
nonlocal link
|
|
394
|
-
if not target:
|
|
395
|
-
raise ValueError("Usage: /link <feishu-email|open_id|chat_id>")
|
|
396
|
-
if link is not None:
|
|
397
|
-
raise RuntimeError("A Feishu card is already linked. Use /unlink first.")
|
|
398
|
-
from .feishu_link import PycodexRuntimeLink
|
|
399
|
-
|
|
400
|
-
link = await PycodexRuntimeLink(runtime, target).start_async()
|
|
401
|
-
return {
|
|
402
|
-
"kind": "connection",
|
|
403
|
-
"lines": [
|
|
404
|
-
"Linked Feishu card: session_key={0} message_id={1}".format(
|
|
405
|
-
link.session_key,
|
|
406
|
-
link.message_id or "-",
|
|
407
|
-
),
|
|
408
|
-
],
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
async def close_link():
|
|
412
|
-
if link is not None:
|
|
413
|
-
await unlink("")
|
|
414
|
-
|
|
415
|
-
runtime.register_command("link", connect)
|
|
416
|
-
runtime.register_command("unlink", unlink)
|
|
417
|
-
runtime.add_close_handler(close_link)
|
|
377
|
+
return AgentRuntime(agent)
|
pycodex/cli.py
CHANGED
|
@@ -354,23 +354,10 @@ class CliSessionView:
|
|
|
354
354
|
self.prompter.close()
|
|
355
355
|
|
|
356
356
|
|
|
357
|
-
def ipython_agent(config_path=DEFAULT_CODEX_CONFIG_PATH):
|
|
358
|
-
from loguru import logger
|
|
359
|
-
|
|
360
|
-
from .tools.ipython_tool import attach_ipython_tool
|
|
361
|
-
|
|
362
|
-
logger.remove()
|
|
363
|
-
logger.add(sys.stderr, level="INFO")
|
|
364
|
-
model = build_model(config_path)
|
|
365
|
-
agent = build_agent(client=model, config_path=config_path)
|
|
366
|
-
attach_ipython_tool(agent)
|
|
367
|
-
return agent
|
|
368
|
-
|
|
369
|
-
|
|
370
357
|
def main(argv=None):
|
|
371
358
|
raw_args = list(argv) if argv is not None else sys.argv[1:]
|
|
372
359
|
if raw_args and raw_args[0] == "doctor":
|
|
373
|
-
from .doctor import build_doctor_parser, run_doctor_cli
|
|
360
|
+
from .utils.doctor import build_doctor_parser, run_doctor_cli
|
|
374
361
|
|
|
375
362
|
parser = build_doctor_parser()
|
|
376
363
|
args = parser.parse_args(raw_args[1:])
|
pycodex/context.py
CHANGED
|
@@ -11,7 +11,7 @@ try:
|
|
|
11
11
|
except ModuleNotFoundError: # pragma: no cover - Python 3.10 path
|
|
12
12
|
import tomli as tomllib
|
|
13
13
|
|
|
14
|
-
from .model_metadata import model_metadata
|
|
14
|
+
from .utils.model_metadata import model_metadata
|
|
15
15
|
from .protocol import ContextMessage, ConversationItem, JSONDict, Prompt, ToolSpec
|
|
16
16
|
from .utils.get_env import (
|
|
17
17
|
get_sandbox_tag,
|
pycodex/events.py
CHANGED
|
@@ -73,7 +73,7 @@ class EventDisplay:
|
|
|
73
73
|
|
|
74
74
|
def begin_turn(self, event):
|
|
75
75
|
self.finish_stream()
|
|
76
|
-
self.set_status(
|
|
76
|
+
self.set_status("understood")
|
|
77
77
|
submission_id = event.submission_id or event.turn_id
|
|
78
78
|
for prompts in (self.inserted_steer_prompts, self.queued_steer_prompts):
|
|
79
79
|
for prompt in prompts.pop(submission_id, []):
|
pycodex/model.py
CHANGED
|
@@ -12,7 +12,7 @@ from typing import Callable
|
|
|
12
12
|
|
|
13
13
|
import requests
|
|
14
14
|
|
|
15
|
-
from .compat import Protocol
|
|
15
|
+
from .utils.compat import Protocol
|
|
16
16
|
|
|
17
17
|
try:
|
|
18
18
|
import tomllib
|
|
@@ -26,7 +26,7 @@ from .events import (
|
|
|
26
26
|
TokenCountEvent,
|
|
27
27
|
ToolCalledEvent,
|
|
28
28
|
)
|
|
29
|
-
from .model_metadata import model_metadata
|
|
29
|
+
from .utils.model_metadata import model_metadata
|
|
30
30
|
from .protocol import (
|
|
31
31
|
AssistantMessage,
|
|
32
32
|
JSONDict,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Portable Codex home storage client API."""
|
|
2
|
+
|
|
3
|
+
from .client import (
|
|
4
|
+
ALLOWED_TOP_LEVEL_DIRS,
|
|
5
|
+
ALLOWED_TOP_LEVEL_FILES,
|
|
6
|
+
DEFAULT_ENTRY_CONFIG,
|
|
7
|
+
DEFAULT_STORAGE_SERVER,
|
|
8
|
+
ENCRYPTED_BUNDLE_MAGIC,
|
|
9
|
+
HEALTHCHECK_PATH,
|
|
10
|
+
NONCE_LENGTH,
|
|
11
|
+
STORAGE_API_PREFIX,
|
|
12
|
+
STORAGE_CACHE_DIRNAME,
|
|
13
|
+
STORAGE_ROOT_ENV,
|
|
14
|
+
STORAGE_SERVER_ENV,
|
|
15
|
+
TOKEN_BASE58_ALPHABET,
|
|
16
|
+
ProgressHandler,
|
|
17
|
+
RemoteStorageError,
|
|
18
|
+
bootstrap_called_home,
|
|
19
|
+
resolve_put_source_dir,
|
|
20
|
+
resolve_storage_root,
|
|
21
|
+
resolve_storage_server,
|
|
22
|
+
upload_codex_home,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"ALLOWED_TOP_LEVEL_DIRS",
|
|
27
|
+
"ALLOWED_TOP_LEVEL_FILES",
|
|
28
|
+
"DEFAULT_ENTRY_CONFIG",
|
|
29
|
+
"DEFAULT_STORAGE_SERVER",
|
|
30
|
+
"ENCRYPTED_BUNDLE_MAGIC",
|
|
31
|
+
"HEALTHCHECK_PATH",
|
|
32
|
+
"NONCE_LENGTH",
|
|
33
|
+
"STORAGE_API_PREFIX",
|
|
34
|
+
"STORAGE_CACHE_DIRNAME",
|
|
35
|
+
"STORAGE_ROOT_ENV",
|
|
36
|
+
"STORAGE_SERVER_ENV",
|
|
37
|
+
"TOKEN_BASE58_ALPHABET",
|
|
38
|
+
"ProgressHandler",
|
|
39
|
+
"RemoteStorageError",
|
|
40
|
+
"bootstrap_called_home",
|
|
41
|
+
"resolve_put_source_dir",
|
|
42
|
+
"resolve_storage_root",
|
|
43
|
+
"resolve_storage_server",
|
|
44
|
+
"upload_codex_home",
|
|
45
|
+
]
|
|
@@ -11,7 +11,8 @@ from typing import Callable
|
|
|
11
11
|
from urllib.parse import quote, urlparse
|
|
12
12
|
|
|
13
13
|
import requests
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
from ..utils.crypto import aes_gcm_decrypt, aes_gcm_encrypt
|
|
15
16
|
|
|
16
17
|
try:
|
|
17
18
|
import tomllib
|
|
@@ -240,8 +241,7 @@ def _normalize_optional_relative_file(
|
|
|
240
241
|
|
|
241
242
|
def _encrypt_bundle(bundle_bytes: "bytes", secret: "str") -> "bytes":
|
|
242
243
|
nonce = os.urandom(NONCE_LENGTH)
|
|
243
|
-
|
|
244
|
-
ciphertext, tag = cipher.encrypt_and_digest(bundle_bytes)
|
|
244
|
+
ciphertext, tag = aes_gcm_encrypt(_encryption_key(secret), nonce, bundle_bytes)
|
|
245
245
|
return ENCRYPTED_BUNDLE_MAGIC + nonce + ciphertext + tag
|
|
246
246
|
|
|
247
247
|
|
|
@@ -257,10 +257,7 @@ def _decrypt_bundle(payload: "bytes", secret: "str") -> "bytes":
|
|
|
257
257
|
ciphertext = encrypted[:-16]
|
|
258
258
|
tag = encrypted[-16:]
|
|
259
259
|
try:
|
|
260
|
-
|
|
261
|
-
plaintext = cipher.decrypt(ciphertext)
|
|
262
|
-
cipher.verify(tag)
|
|
263
|
-
return plaintext
|
|
260
|
+
return aes_gcm_decrypt(_encryption_key(secret), nonce, ciphertext, tag)
|
|
264
261
|
except ValueError as exc:
|
|
265
262
|
raise RemoteStorageError(
|
|
266
263
|
"call secret is invalid or bundle is corrupted"
|
|
@@ -7,8 +7,8 @@ from http.server import BaseHTTPRequestHandler
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from urllib.parse import unquote, urlparse
|
|
9
9
|
|
|
10
|
-
from .compat import ThreadingHTTPServer
|
|
11
|
-
from .
|
|
10
|
+
from ..utils.compat import ThreadingHTTPServer
|
|
11
|
+
from .client import (
|
|
12
12
|
DEFAULT_STORAGE_SERVER,
|
|
13
13
|
HEALTHCHECK_PATH,
|
|
14
14
|
STORAGE_API_PREFIX,
|
|
@@ -166,7 +166,7 @@ class CodexStorageServer:
|
|
|
166
166
|
|
|
167
167
|
def build_parser() -> "argparse.ArgumentParser":
|
|
168
168
|
parser = argparse.ArgumentParser(
|
|
169
|
-
prog="python -m pycodex.
|
|
169
|
+
prog="python -m pycodex.portable.server",
|
|
170
170
|
description="Run a pycodex remote storage service for --put/--call testing.",
|
|
171
171
|
)
|
|
172
172
|
parser.add_argument(
|
pycodex/protocol.py
CHANGED
|
@@ -19,7 +19,7 @@ from copy import deepcopy
|
|
|
19
19
|
from dataclasses import dataclass, field
|
|
20
20
|
from typing import Any
|
|
21
21
|
|
|
22
|
-
from .compat import Literal, TypeAlias
|
|
22
|
+
from .utils.compat import Literal, TypeAlias
|
|
23
23
|
|
|
24
24
|
JSONValue: "TypeAlias" = Any
|
|
25
25
|
JSONDict: "TypeAlias" = typing.Dict[str, Any]
|
pycodex/runtime.py
CHANGED
|
@@ -4,7 +4,7 @@ from collections import deque
|
|
|
4
4
|
from dataclasses import dataclass, field, replace
|
|
5
5
|
|
|
6
6
|
from .agent import BASE_EVENT_HANDLER, Agent, TurnInterrupted
|
|
7
|
-
from .compat import Literal
|
|
7
|
+
from .utils.compat import Literal
|
|
8
8
|
from .events import (
|
|
9
9
|
AssistantDeltaEvent,
|
|
10
10
|
AutoCompactCompletedEvent,
|
pycodex/tools/base_tool.py
CHANGED
|
@@ -27,8 +27,8 @@ from ..protocol import (
|
|
|
27
27
|
ToolSpec,
|
|
28
28
|
UserMessage,
|
|
29
29
|
)
|
|
30
|
-
from ..runtime_services import AgentRuntimeEnvironment
|
|
31
30
|
from ..utils import get_debug_dir
|
|
31
|
+
from .runtime_services import AgentRuntimeEnvironment
|
|
32
32
|
|
|
33
33
|
if typing.TYPE_CHECKING:
|
|
34
34
|
from ..agent import Agent
|
|
@@ -9,9 +9,9 @@ Expected behavior:
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
from ..protocol import JSONDict, JSONValue
|
|
12
|
-
from ..runtime_services import SubAgentManager
|
|
13
12
|
from .agent_tool_schemas import AGENT_STATUS_SCHEMA
|
|
14
13
|
from .base_tool import BaseTool, ToolContext
|
|
14
|
+
from .runtime_services import SubAgentManager
|
|
15
15
|
|
|
16
16
|
CLOSE_AGENT_OUTPUT_SCHEMA = {
|
|
17
17
|
"type": "object",
|
|
@@ -21,7 +21,7 @@ from pathlib import Path
|
|
|
21
21
|
|
|
22
22
|
from loguru import logger
|
|
23
23
|
|
|
24
|
-
from ..compat import is_ascii, stream_writer_is_closing
|
|
24
|
+
from ..utils.compat import is_ascii, stream_writer_is_closing
|
|
25
25
|
from ..protocol import JSONDict, JSONValue, ToolCall
|
|
26
26
|
from ..utils.truncation import DEFAULT_MAX_OUTPUT_TOKENS
|
|
27
27
|
from .base_tool import StructuredToolOutput, ToolContext, ToolRegistry
|
|
@@ -10,8 +10,8 @@ Expected behavior:
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
from ..protocol import JSONDict, JSONValue
|
|
13
|
-
from ..runtime_services import RequestPermissionsManager
|
|
14
13
|
from .base_tool import BaseTool, ToolContext
|
|
14
|
+
from .runtime_services import RequestPermissionsManager
|
|
15
15
|
|
|
16
16
|
NETWORK_PERMISSIONS_SCHEMA = {
|
|
17
17
|
"type": "object",
|
|
@@ -9,9 +9,9 @@ Expected behavior:
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
from ..protocol import JSONDict, JSONValue
|
|
12
|
-
from ..runtime_services import SubAgentManager
|
|
13
12
|
from .agent_tool_schemas import AGENT_STATUS_SCHEMA
|
|
14
13
|
from .base_tool import BaseTool, ToolContext
|
|
14
|
+
from .runtime_services import SubAgentManager
|
|
15
15
|
|
|
16
16
|
RESUME_AGENT_OUTPUT_SCHEMA = {
|
|
17
17
|
"type": "object",
|
|
@@ -5,8 +5,7 @@ import typing
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from typing import TYPE_CHECKING, Awaitable, Callable
|
|
7
7
|
|
|
8
|
-
from
|
|
9
|
-
from .events import (
|
|
8
|
+
from ..events import (
|
|
10
9
|
CompactCompletedEvent,
|
|
11
10
|
CompactFailedEvent,
|
|
12
11
|
CompactStartedEvent,
|
|
@@ -16,11 +15,12 @@ from .events import (
|
|
|
16
15
|
TurnInterruptedEvent,
|
|
17
16
|
TurnStartedEvent,
|
|
18
17
|
)
|
|
19
|
-
from
|
|
20
|
-
from
|
|
18
|
+
from ..protocol import ConversationItem, ToolCall, ToolResult
|
|
19
|
+
from ..utils import uuid7_string
|
|
20
|
+
from ..utils.compat import Literal
|
|
21
21
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
23
|
-
from
|
|
23
|
+
from ..runtime import AgentRuntime
|
|
24
24
|
|
|
25
25
|
PlanStatus = Literal["pending", "in_progress", "completed"]
|
|
26
26
|
AgentStatus = typing.Union[
|
pycodex/tools/send_input_tool.py
CHANGED
|
@@ -12,9 +12,9 @@ Expected behavior:
|
|
|
12
12
|
import typing
|
|
13
13
|
|
|
14
14
|
from ..protocol import JSONDict, JSONValue
|
|
15
|
-
from ..runtime_services import SubAgentManager
|
|
16
15
|
from .agent_tool_schemas import COLLAB_INPUT_ITEMS_SCHEMA
|
|
17
16
|
from .base_tool import BaseTool, ToolContext
|
|
17
|
+
from .runtime_services import SubAgentManager
|
|
18
18
|
|
|
19
19
|
SEND_INPUT_OUTPUT_SCHEMA = {
|
|
20
20
|
"type": "object",
|
|
@@ -12,9 +12,9 @@ Expected behavior:
|
|
|
12
12
|
import typing
|
|
13
13
|
|
|
14
14
|
from ..protocol import JSONDict, JSONValue
|
|
15
|
-
from ..runtime_services import SubAgentManager
|
|
16
15
|
from .agent_tool_schemas import COLLAB_INPUT_ITEMS_SCHEMA
|
|
17
16
|
from .base_tool import BaseTool, ToolContext
|
|
17
|
+
from .runtime_services import SubAgentManager
|
|
18
18
|
|
|
19
19
|
SPAWN_AGENT_OUTPUT_SCHEMA = {
|
|
20
20
|
"type": "object",
|
|
@@ -22,7 +22,7 @@ from pathlib import Path
|
|
|
22
22
|
|
|
23
23
|
from loguru import logger
|
|
24
24
|
|
|
25
|
-
from ..compat import shlex_join, stream_writer_is_closing
|
|
25
|
+
from ..utils.compat import shlex_join, stream_writer_is_closing
|
|
26
26
|
from ..utils.truncation import (
|
|
27
27
|
DEFAULT_MAX_OUTPUT_TOKENS,
|
|
28
28
|
approx_token_count,
|
|
@@ -13,8 +13,8 @@ Expected behavior:
|
|
|
13
13
|
import typing
|
|
14
14
|
|
|
15
15
|
from ..protocol import JSONDict, JSONValue
|
|
16
|
-
from ..runtime_services import PlanItem, PlanStore
|
|
17
16
|
from .base_tool import BaseTool, ToolContext
|
|
17
|
+
from .runtime_services import PlanItem, PlanStore
|
|
18
18
|
|
|
19
19
|
VALID_PLAN_STATUSES = {"pending", "in_progress", "completed"}
|
|
20
20
|
|
pycodex/tools/view_image_tool.py
CHANGED
|
@@ -8,6 +8,7 @@ Expected behavior:
|
|
|
8
8
|
into the next model request.
|
|
9
9
|
- Resize the image down to `MAX_DIMENSION` unless the caller asks for
|
|
10
10
|
`original` detail, matching upstream `PromptImageMode::ResizeToFit`.
|
|
11
|
+
- Use `original` detail when Pillow is unavailable.
|
|
11
12
|
- Accept the documented `path` argument plus optional `detail: "high" |
|
|
12
13
|
"original"` hint.
|
|
13
14
|
- Return both the JSON object result and the structured `input_image` content
|
|
@@ -15,6 +16,7 @@ Expected behavior:
|
|
|
15
16
|
"""
|
|
16
17
|
|
|
17
18
|
import typing
|
|
19
|
+
from importlib.util import find_spec
|
|
18
20
|
from pathlib import Path
|
|
19
21
|
|
|
20
22
|
from ..protocol import JSONDict, JSONValue
|
|
@@ -95,6 +97,9 @@ class ViewImageTool(BaseTool):
|
|
|
95
97
|
if not path.is_file():
|
|
96
98
|
return f"Error: image path `{path}` is not a file."
|
|
97
99
|
|
|
100
|
+
if detail == "high" and find_spec("PIL") is None:
|
|
101
|
+
detail = "original"
|
|
102
|
+
|
|
98
103
|
try:
|
|
99
104
|
image_url = load_image_data_url(path, detail != "original")
|
|
100
105
|
except ImageProcessingError as exc:
|
pycodex/tools/wait_agent_tool.py
CHANGED
|
@@ -13,9 +13,9 @@ import json
|
|
|
13
13
|
import typing
|
|
14
14
|
|
|
15
15
|
from ..protocol import JSONDict, JSONValue, UserMessage
|
|
16
|
-
from ..runtime_services import SubAgentManager
|
|
17
16
|
from .agent_tool_schemas import AGENT_STATUS_SCHEMA
|
|
18
17
|
from .base_tool import BaseTool, ToolContext
|
|
18
|
+
from .runtime_services import SubAgentManager
|
|
19
19
|
|
|
20
20
|
DEFAULT_WAIT_AGENT_TIMEOUT_MS = 30_000
|
|
21
21
|
MIN_WAIT_AGENT_TIMEOUT_MS = 10_000
|
pycodex/utils/__init__.py
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
from .compactor import DEFAULT_COMPACT_PROMPT, SUMMARY_PREFIX, compact
|
|
2
1
|
from .debug import get_debug_dir
|
|
3
2
|
from .dotenv import DOTENV_FILENAME, load_codex_dotenv, parse_dotenv, parse_dotenv_value
|
|
4
3
|
from .get_env import build_user_agent, get_shell_name, get_timezone_name
|
|
5
4
|
from .random_ids import uuid7_string
|
|
6
5
|
|
|
7
6
|
__all__ = [
|
|
8
|
-
"DEFAULT_COMPACT_PROMPT",
|
|
9
7
|
"DOTENV_FILENAME",
|
|
10
|
-
"SUMMARY_PREFIX",
|
|
11
8
|
"build_user_agent",
|
|
12
9
|
"get_debug_dir",
|
|
13
10
|
"get_shell_name",
|
|
@@ -15,6 +12,5 @@ __all__ = [
|
|
|
15
12
|
"load_codex_dotenv",
|
|
16
13
|
"parse_dotenv",
|
|
17
14
|
"parse_dotenv_value",
|
|
18
|
-
"compact",
|
|
19
15
|
"uuid7_string",
|
|
20
16
|
]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
aes.py is adapted from pyaes 1.6.1 under the following license:
|
|
2
|
+
|
|
3
|
+
The MIT License (MIT)
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2014 Richard Moore
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in
|
|
15
|
+
all copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
23
|
+
THE SOFTWARE.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
gcm.py is adapted from Google-authored tlslite/utils/aesgcm.py in tlslite-ng
|
|
28
|
+
0.8.2. Its LICENSE provides the following terms for code written by Google:
|
|
29
|
+
|
|
30
|
+
Copyright (c) 2008, The Chromium Authors
|
|
31
|
+
All rights reserved.
|
|
32
|
+
|
|
33
|
+
Redistribution and use in source and binary forms, with or without
|
|
34
|
+
modification, are permitted provided that the following conditions are met:
|
|
35
|
+
|
|
36
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
37
|
+
list of conditions and the following disclaimer.
|
|
38
|
+
|
|
39
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
40
|
+
this list of conditions and the following disclaimer in the documentation
|
|
41
|
+
and/or other materials provided with the distribution.
|
|
42
|
+
|
|
43
|
+
* Neither the name of the Google Inc. nor the names of its contributors may
|
|
44
|
+
be used to endorse or promote products derived from this software without
|
|
45
|
+
specific prior written permission.
|
|
46
|
+
|
|
47
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
48
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
49
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
50
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
|
51
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
52
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
53
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
54
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
55
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
56
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Bundled pure Python AES-GCM
|
|
2
|
+
|
|
3
|
+
Portable encryption uses this subset through `pycodex.utils.crypto`. It needs
|
|
4
|
+
only Python's standard library, with no compiler, shared library, optional
|
|
5
|
+
crypto package or platform-specific build. Core ships a `py3-none-any` wheel.
|
|
6
|
+
|
|
7
|
+
The format remains AES-256-GCM with a 32-byte key, 12-byte nonce, 16-byte tag and
|
|
8
|
+
empty associated data. The existing `PCX1` layout and SHA-256 key derivation are
|
|
9
|
+
unchanged, so previously issued call tokens continue to work.
|
|
10
|
+
|
|
11
|
+
## Sources and licenses
|
|
12
|
+
|
|
13
|
+
- `aes.py`: the AES encryption primitive from
|
|
14
|
+
[pyaes 1.6.1](https://pypi.org/project/pyaes/1.6.1/), by Richard Moore, under MIT.
|
|
15
|
+
Upstream repository: https://github.com/ricmoo/pyaes.
|
|
16
|
+
Source archive SHA-256:
|
|
17
|
+
`02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f`.
|
|
18
|
+
- `gcm.py`: adapted from the Google-authored `tlslite/utils/aesgcm.py` in
|
|
19
|
+
[tlslite-ng 0.8.2](https://pypi.org/project/tlslite-ng/0.8.2/), under the BSD
|
|
20
|
+
terms specified for Google's code in its LICENSE. That implementation derives
|
|
21
|
+
GCM multiplication from Go's `crypto/cipher`.
|
|
22
|
+
Upstream repository: https://github.com/tlsfuzzer/tlslite-ng.
|
|
23
|
+
|
|
24
|
+
`LICENSE` retains the applicable notices. `source-sha256.json` identifies the
|
|
25
|
+
original input files, before the local adaptations below.
|
|
26
|
+
|
|
27
|
+
## Local scope
|
|
28
|
+
|
|
29
|
+
- AES supports only the 256-bit key and 128-bit block used by portable.
|
|
30
|
+
Decryption tables, the inverse AES primitive, other modes, padding and Python 2
|
|
31
|
+
compatibility code are removed. GCM uses the AES encryption primitive in both
|
|
32
|
+
directions.
|
|
33
|
+
- The AES S-box and T1 entries are unchanged. T2/T3/T4 are byte rotations of T1;
|
|
34
|
+
their values were checked against all upstream entries. These tables are
|
|
35
|
+
immutable class data. Key schedules and GCM product tables belong to each call.
|
|
36
|
+
- GCM retains the upstream four-bit multiplication and reduction tables. Its
|
|
37
|
+
fixed nonce, tag and empty-associated-data contract removes generic TLS
|
|
38
|
+
helpers. Counter processing is local and does not allocate a full keystream.
|
|
39
|
+
- `crypto.py` validates lengths and the GCM message-size bound. Decryption checks
|
|
40
|
+
the tag with `hmac.compare_digest` before decrypting any ciphertext.
|
|
41
|
+
|
|
42
|
+
Pure Python execution is deliberately slower than native crypto, especially
|
|
43
|
+
for multi-megabyte bundles. These table-based Python primitives do not provide
|
|
44
|
+
constant-time AES/GHASH execution; constant-time tag comparison does not change
|
|
45
|
+
that property.
|
|
46
|
+
|
|
47
|
+
## Verification
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
uv sync --dev
|
|
51
|
+
uv run --dev pytest tests/test_crypto.py tests/test_portable.py
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Tests cover NIST AES-256-GCM vectors, comparison with PyCryptodome, empty and
|
|
55
|
+
partial blocks, a 1 MiB payload, altered key/nonce/ciphertext/tag, input length
|
|
56
|
+
and counter bounds, authentication before decryption, and existing `PCX1`
|
|
57
|
+
bundles. PyCryptodome is a development-only reference dependency.
|
|
58
|
+
|
|
59
|
+
The publish workflow builds ordinary Python wheels and source distributions.
|
|
60
|
+
`tests/smoke_core_wheel.py` checks a clean core installation without Crypto or
|
|
61
|
+
Cryptodome, including portable round-trip and CLI entry points.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Vendored pure Python AES-GCM subset; see README.md and LICENSE."""
|