coding-cli-runtime 0.1.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.
- coding_cli_runtime/__init__.py +108 -0
- coding_cli_runtime/auth.py +55 -0
- coding_cli_runtime/codex_cli.py +95 -0
- coding_cli_runtime/contracts.py +72 -0
- coding_cli_runtime/copilot_reasoning_baseline.json +66 -0
- coding_cli_runtime/copilot_reasoning_logs.py +81 -0
- coding_cli_runtime/failure_classification.py +183 -0
- coding_cli_runtime/json_io.py +81 -0
- coding_cli_runtime/provider_controls.py +101 -0
- coding_cli_runtime/provider_specs.py +749 -0
- coding_cli_runtime/py.typed +1 -0
- coding_cli_runtime/reasoning.py +95 -0
- coding_cli_runtime/redaction.py +20 -0
- coding_cli_runtime/schema_validation.py +101 -0
- coding_cli_runtime/schemas/normalized_run_result.v1.json +37 -0
- coding_cli_runtime/schemas/reasoning_metadata.v1.json +14 -0
- coding_cli_runtime/session_execution.py +604 -0
- coding_cli_runtime/session_logs.py +129 -0
- coding_cli_runtime/subprocess_runner.py +346 -0
- coding_cli_runtime-0.1.0.dist-info/METADATA +179 -0
- coding_cli_runtime-0.1.0.dist-info/RECORD +24 -0
- coding_cli_runtime-0.1.0.dist-info/WHEEL +5 -0
- coding_cli_runtime-0.1.0.dist-info/licenses/LICENSE +21 -0
- coding_cli_runtime-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Reasoning control policy helpers shared across Claude callers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .contracts import ClaudeReasoningPolicy
|
|
6
|
+
|
|
7
|
+
CLAUDE_ADAPTIVE_THINKING_MODELS: tuple[str, ...] = ("claude-sonnet-4-6", "claude-opus-4-6")
|
|
8
|
+
CLAUDE_EFFORT_CHOICES: tuple[str, ...] = ("low", "medium", "high")
|
|
9
|
+
CLAUDE_DEFAULT_THINKING_TOKENS = 8192
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _normalize_claude_effort(value: str | None) -> str | None:
|
|
13
|
+
if not isinstance(value, str):
|
|
14
|
+
return None
|
|
15
|
+
normalized = value.strip().lower()
|
|
16
|
+
if normalized in CLAUDE_EFFORT_CHOICES:
|
|
17
|
+
return normalized
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _uses_claude_adaptive_thinking_model(model: str | None) -> bool:
|
|
22
|
+
normalized = (model or "").strip().lower()
|
|
23
|
+
if not normalized:
|
|
24
|
+
return False
|
|
25
|
+
return any(normalized.startswith(prefix) for prefix in CLAUDE_ADAPTIVE_THINKING_MODELS)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def resolve_claude_reasoning_policy(
|
|
29
|
+
*,
|
|
30
|
+
model: str | None,
|
|
31
|
+
thinking_tokens: int | None,
|
|
32
|
+
effort: str | None,
|
|
33
|
+
) -> ClaudeReasoningPolicy:
|
|
34
|
+
normalized_tokens: int | None = None
|
|
35
|
+
if isinstance(thinking_tokens, int):
|
|
36
|
+
normalized_tokens = max(int(thinking_tokens), 0)
|
|
37
|
+
normalized_effort = _normalize_claude_effort(effort)
|
|
38
|
+
adaptive_model = _uses_claude_adaptive_thinking_model(model)
|
|
39
|
+
|
|
40
|
+
if adaptive_model:
|
|
41
|
+
if normalized_effort:
|
|
42
|
+
# --effort explicitly set: use it, ignore thinking tokens.
|
|
43
|
+
return ClaudeReasoningPolicy(
|
|
44
|
+
mode="effort",
|
|
45
|
+
effective_effort=normalized_effort,
|
|
46
|
+
effort_source="flag",
|
|
47
|
+
thinking_tokens=None,
|
|
48
|
+
)
|
|
49
|
+
if normalized_tokens is None:
|
|
50
|
+
# Neither --effort nor --thinking-tokens: let Claude Code
|
|
51
|
+
# handle its own defaults.
|
|
52
|
+
return ClaudeReasoningPolicy(
|
|
53
|
+
mode="none",
|
|
54
|
+
effective_effort=None,
|
|
55
|
+
effort_source=None,
|
|
56
|
+
thinking_tokens=None,
|
|
57
|
+
)
|
|
58
|
+
if normalized_tokens <= 0:
|
|
59
|
+
return ClaudeReasoningPolicy(
|
|
60
|
+
mode="disabled",
|
|
61
|
+
effective_effort=None,
|
|
62
|
+
effort_source="thinking_tokens<=0",
|
|
63
|
+
thinking_tokens=0,
|
|
64
|
+
disable_via_env_setting=True,
|
|
65
|
+
)
|
|
66
|
+
# --thinking-tokens passed without --effort: respect the explicit
|
|
67
|
+
# token budget and pass it through to the CLI as-is.
|
|
68
|
+
return ClaudeReasoningPolicy(
|
|
69
|
+
mode="legacy_tokens",
|
|
70
|
+
effective_effort=None,
|
|
71
|
+
effort_source=None,
|
|
72
|
+
thinking_tokens=normalized_tokens,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if normalized_tokens is None:
|
|
76
|
+
return ClaudeReasoningPolicy(
|
|
77
|
+
mode="none",
|
|
78
|
+
effective_effort=None,
|
|
79
|
+
effort_source=None,
|
|
80
|
+
thinking_tokens=None,
|
|
81
|
+
)
|
|
82
|
+
if normalized_tokens > 0:
|
|
83
|
+
return ClaudeReasoningPolicy(
|
|
84
|
+
mode="legacy_tokens",
|
|
85
|
+
effective_effort=None,
|
|
86
|
+
effort_source=None,
|
|
87
|
+
thinking_tokens=normalized_tokens,
|
|
88
|
+
)
|
|
89
|
+
return ClaudeReasoningPolicy(
|
|
90
|
+
mode="disabled",
|
|
91
|
+
effective_effort=None,
|
|
92
|
+
effort_source="thinking_tokens<=0",
|
|
93
|
+
thinking_tokens=0,
|
|
94
|
+
disable_via_env_setting=False,
|
|
95
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Log redaction helpers for shared runtime diagnostics."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
SENSITIVE_PATTERNS = (
|
|
8
|
+
re.compile(r"(api[_-]?key\s*[=:]\s*)([^\s\"']+)", re.IGNORECASE),
|
|
9
|
+
re.compile(r"(authorization\s*[:=]\s*bearer\s+)([^\s\"']+)", re.IGNORECASE),
|
|
10
|
+
re.compile(r"(token\s*[=:]\s*)([^\s\"']+)", re.IGNORECASE),
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def redact_text(text: str) -> str:
|
|
15
|
+
if not text:
|
|
16
|
+
return text
|
|
17
|
+
redacted = text
|
|
18
|
+
for pattern in SENSITIVE_PATTERNS:
|
|
19
|
+
redacted = pattern.sub(r"\1<redacted>", redacted)
|
|
20
|
+
return redacted
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Minimal JSON-schema-like validation for shared runtime contracts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from .json_io import load_packaged_json_object, packaged_resource_exists
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SchemaValidationError(ValueError):
|
|
11
|
+
"""Raised when payload validation against shared schema fails."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _schema_resource_name(schema_name: str) -> str:
|
|
15
|
+
if not schema_name.endswith(".json"):
|
|
16
|
+
schema_name = f"{schema_name}.json"
|
|
17
|
+
return schema_name
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def load_schema(schema_name: str) -> dict[str, Any]:
|
|
21
|
+
resource_name = _schema_resource_name(schema_name)
|
|
22
|
+
if not packaged_resource_exists("schemas", resource_name):
|
|
23
|
+
raise FileNotFoundError(f"schema not found: schemas/{resource_name}")
|
|
24
|
+
return load_packaged_json_object("schemas", resource_name)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _python_type_matches(value: Any, expected_type: str) -> bool:
|
|
28
|
+
if expected_type == "string":
|
|
29
|
+
return isinstance(value, str)
|
|
30
|
+
if expected_type == "integer":
|
|
31
|
+
return isinstance(value, int) and not isinstance(value, bool)
|
|
32
|
+
if expected_type == "number":
|
|
33
|
+
return (isinstance(value, int) or isinstance(value, float)) and not isinstance(value, bool)
|
|
34
|
+
if expected_type == "object":
|
|
35
|
+
return isinstance(value, dict)
|
|
36
|
+
if expected_type == "null":
|
|
37
|
+
return value is None
|
|
38
|
+
if expected_type == "array":
|
|
39
|
+
return isinstance(value, list)
|
|
40
|
+
if expected_type == "boolean":
|
|
41
|
+
return isinstance(value, bool)
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _allowed_types(prop_schema: dict[str, Any]) -> list[str]:
|
|
46
|
+
type_field = prop_schema.get("type")
|
|
47
|
+
if isinstance(type_field, str):
|
|
48
|
+
return [type_field]
|
|
49
|
+
if isinstance(type_field, list):
|
|
50
|
+
return [item for item in type_field if isinstance(item, str)]
|
|
51
|
+
return []
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def validate_payload(payload: Any, schema_name: str) -> None:
|
|
55
|
+
schema = load_schema(schema_name)
|
|
56
|
+
if not isinstance(payload, dict):
|
|
57
|
+
raise SchemaValidationError("payload must be an object")
|
|
58
|
+
|
|
59
|
+
required = schema.get("required") or []
|
|
60
|
+
for key in required:
|
|
61
|
+
if key not in payload:
|
|
62
|
+
raise SchemaValidationError(f"missing required field: {key}")
|
|
63
|
+
|
|
64
|
+
properties = schema.get("properties")
|
|
65
|
+
if not isinstance(properties, dict):
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
for key, prop_schema in properties.items():
|
|
69
|
+
if key not in payload:
|
|
70
|
+
continue
|
|
71
|
+
if not isinstance(prop_schema, dict):
|
|
72
|
+
continue
|
|
73
|
+
value = payload[key]
|
|
74
|
+
allowed_types = _allowed_types(prop_schema)
|
|
75
|
+
if allowed_types and not any(_python_type_matches(value, t) for t in allowed_types):
|
|
76
|
+
raise SchemaValidationError(
|
|
77
|
+
f"invalid type for field '{key}': "
|
|
78
|
+
f"expected {allowed_types}, got {type(value).__name__}"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
nested_required = prop_schema.get("required") or []
|
|
82
|
+
if isinstance(value, dict) and isinstance(nested_required, list):
|
|
83
|
+
for nested_key in nested_required:
|
|
84
|
+
if nested_key not in value:
|
|
85
|
+
raise SchemaValidationError(f"missing required field: {key}.{nested_key}")
|
|
86
|
+
|
|
87
|
+
nested_props = prop_schema.get("properties")
|
|
88
|
+
if isinstance(nested_props, dict):
|
|
89
|
+
for nested_key, nested_schema in nested_props.items():
|
|
90
|
+
if nested_key not in value or not isinstance(nested_schema, dict):
|
|
91
|
+
continue
|
|
92
|
+
nested_value = value[nested_key]
|
|
93
|
+
nested_types = _allowed_types(nested_schema)
|
|
94
|
+
if nested_types and not any(
|
|
95
|
+
_python_type_matches(nested_value, t) for t in nested_types
|
|
96
|
+
):
|
|
97
|
+
raise SchemaValidationError(
|
|
98
|
+
"invalid type for field "
|
|
99
|
+
f"'{key}.{nested_key}': expected {nested_types}, "
|
|
100
|
+
f"got {type(nested_value).__name__}"
|
|
101
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"title": "NormalizedCliRunResultV1",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": [
|
|
6
|
+
"provider",
|
|
7
|
+
"model",
|
|
8
|
+
"run_id",
|
|
9
|
+
"status",
|
|
10
|
+
"error_code",
|
|
11
|
+
"duration_seconds"
|
|
12
|
+
],
|
|
13
|
+
"properties": {
|
|
14
|
+
"provider": {"type": "string"},
|
|
15
|
+
"model": {"type": "string"},
|
|
16
|
+
"run_id": {"type": "string"},
|
|
17
|
+
"status": {"type": "string"},
|
|
18
|
+
"error_code": {"type": "string"},
|
|
19
|
+
"error_message": {"type": ["string", "null"]},
|
|
20
|
+
"duration_seconds": {"type": "number"},
|
|
21
|
+
"stdout_path": {"type": ["string", "null"]},
|
|
22
|
+
"stderr_path": {"type": ["string", "null"]},
|
|
23
|
+
"session_path": {"type": ["string", "null"]},
|
|
24
|
+
"created_at": {"type": ["string", "null"]},
|
|
25
|
+
"reasoning": {
|
|
26
|
+
"type": ["object", "null"],
|
|
27
|
+
"required": ["mode", "effort", "thinking_budget_tokens"],
|
|
28
|
+
"properties": {
|
|
29
|
+
"mode": {"type": ["string", "null"]},
|
|
30
|
+
"effort": {"type": ["string", "null"]},
|
|
31
|
+
"thinking_budget_tokens": {"type": ["integer", "null"]}
|
|
32
|
+
},
|
|
33
|
+
"additionalProperties": true
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"additionalProperties": true
|
|
37
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"title": "ReasoningMetadataV1",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": ["mode", "effort", "thinking_budget_tokens"],
|
|
6
|
+
"properties": {
|
|
7
|
+
"mode": {"type": ["string", "null"]},
|
|
8
|
+
"effort": {"type": ["string", "null"]},
|
|
9
|
+
"thinking_budget_tokens": {"type": ["integer", "null"]},
|
|
10
|
+
"source": {"type": ["string", "null"]},
|
|
11
|
+
"schema": {"type": ["string", "null"]}
|
|
12
|
+
},
|
|
13
|
+
"additionalProperties": true
|
|
14
|
+
}
|