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.
@@ -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
+ }