jharness-kernel 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.
- jharness_kernel-0.1.0/.gitignore +45 -0
- jharness_kernel-0.1.0/LICENSE +21 -0
- jharness_kernel-0.1.0/PKG-INFO +49 -0
- jharness_kernel-0.1.0/README.md +24 -0
- jharness_kernel-0.1.0/pyproject.toml +36 -0
- jharness_kernel-0.1.0/src/jharness/kernel/__init__.py +200 -0
- jharness_kernel-0.1.0/src/jharness/kernel/_digest.py +242 -0
- jharness_kernel-0.1.0/src/jharness/kernel/_engine/__init__.py +1 -0
- jharness_kernel-0.1.0/src/jharness/kernel/_engine/change.py +117 -0
- jharness_kernel-0.1.0/src/jharness/kernel/_engine/commit.py +82 -0
- jharness_kernel-0.1.0/src/jharness/kernel/_engine/deadline.py +108 -0
- jharness_kernel-0.1.0/src/jharness/kernel/_engine/engine.py +428 -0
- jharness_kernel-0.1.0/src/jharness/kernel/_engine/planning.py +263 -0
- jharness_kernel-0.1.0/src/jharness/kernel/_engine/tooling.py +563 -0
- jharness_kernel-0.1.0/src/jharness/kernel/_engine/verification.py +315 -0
- jharness_kernel-0.1.0/src/jharness/kernel/_history.py +132 -0
- jharness_kernel-0.1.0/src/jharness/kernel/_validation.py +133 -0
- jharness_kernel-0.1.0/src/jharness/kernel/approval.py +66 -0
- jharness_kernel-0.1.0/src/jharness/kernel/checkpoint.py +468 -0
- jharness_kernel-0.1.0/src/jharness/kernel/commands.py +124 -0
- jharness_kernel-0.1.0/src/jharness/kernel/context.py +55 -0
- jharness_kernel-0.1.0/src/jharness/kernel/control.py +163 -0
- jharness_kernel-0.1.0/src/jharness/kernel/diagnostics/__init__.py +21 -0
- jharness_kernel-0.1.0/src/jharness/kernel/diagnostics/trace.py +162 -0
- jharness_kernel-0.1.0/src/jharness/kernel/diagnostics/verification.py +604 -0
- jharness_kernel-0.1.0/src/jharness/kernel/errors.py +109 -0
- jharness_kernel-0.1.0/src/jharness/kernel/events.py +59 -0
- jharness_kernel-0.1.0/src/jharness/kernel/history.py +49 -0
- jharness_kernel-0.1.0/src/jharness/kernel/invocation.py +180 -0
- jharness_kernel-0.1.0/src/jharness/kernel/json_values.py +243 -0
- jharness_kernel-0.1.0/src/jharness/kernel/limits.py +58 -0
- jharness_kernel-0.1.0/src/jharness/kernel/messages.py +316 -0
- jharness_kernel-0.1.0/src/jharness/kernel/models.py +356 -0
- jharness_kernel-0.1.0/src/jharness/kernel/py.typed +0 -0
- jharness_kernel-0.1.0/src/jharness/kernel/repository.py +320 -0
- jharness_kernel-0.1.0/src/jharness/kernel/runtime.py +168 -0
- jharness_kernel-0.1.0/src/jharness/kernel/snapshot.py +79 -0
- jharness_kernel-0.1.0/src/jharness/kernel/state.py +165 -0
- jharness_kernel-0.1.0/src/jharness/kernel/tools.py +430 -0
- jharness_kernel-0.1.0/src/jharness/kernel/wire/__init__.py +107 -0
- jharness_kernel-0.1.0/src/jharness/kernel/wire/_helpers.py +175 -0
- jharness_kernel-0.1.0/src/jharness/kernel/wire/checkpoint.py +362 -0
- jharness_kernel-0.1.0/src/jharness/kernel/wire/events.py +311 -0
- jharness_kernel-0.1.0/src/jharness/kernel/wire/messages.py +377 -0
- jharness_kernel-0.1.0/src/jharness/kernel/wire/models.py +132 -0
- jharness_kernel-0.1.0/src/jharness/kernel/wire/requests.py +127 -0
- jharness_kernel-0.1.0/src/jharness/kernel/wire/snapshot.py +109 -0
- jharness_kernel-0.1.0/src/jharness/kernel/wire/state.py +169 -0
- jharness_kernel-0.1.0/src/jharness/kernel/wire/tools.py +175 -0
- jharness_kernel-0.1.0/src/jharness/kernel/wire/trace.py +102 -0
- jharness_kernel-0.1.0/tests/test_diagnostics.py +750 -0
- jharness_kernel-0.1.0/tests/test_runtime.py +473 -0
- jharness_kernel-0.1.0/tests/test_runtime_concurrency_edges.py +308 -0
- jharness_kernel-0.1.0/tests/test_values.py +535 -0
- jharness_kernel-0.1.0/tests/test_wire.py +1135 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# OS/editor noise
|
|
2
|
+
.DS_Store
|
|
3
|
+
Thumbs.db
|
|
4
|
+
*.swp
|
|
5
|
+
*.swo
|
|
6
|
+
|
|
7
|
+
# Local AI assistant/workspace state
|
|
8
|
+
.codex/
|
|
9
|
+
.claude/
|
|
10
|
+
.gemini/
|
|
11
|
+
.aider*
|
|
12
|
+
|
|
13
|
+
# Pinned specification snapshot generated by scripts/sync_spec.py
|
|
14
|
+
.jharness-spec/
|
|
15
|
+
|
|
16
|
+
# Python
|
|
17
|
+
__pycache__/
|
|
18
|
+
*.py[cod]
|
|
19
|
+
.python-version
|
|
20
|
+
.venv/
|
|
21
|
+
venv/
|
|
22
|
+
ENV/
|
|
23
|
+
|
|
24
|
+
# Python tooling caches
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.ruff_cache/
|
|
27
|
+
.pyright/
|
|
28
|
+
.mypy_cache/
|
|
29
|
+
.coverage
|
|
30
|
+
htmlcov/
|
|
31
|
+
|
|
32
|
+
# Packaging/build artifacts
|
|
33
|
+
dist/
|
|
34
|
+
build/
|
|
35
|
+
*.egg-info/
|
|
36
|
+
|
|
37
|
+
# Node/TypeScript SDK artifacts
|
|
38
|
+
node_modules/
|
|
39
|
+
coverage/
|
|
40
|
+
.turbo/
|
|
41
|
+
.next/
|
|
42
|
+
*.tsbuildinfo
|
|
43
|
+
|
|
44
|
+
# Logs
|
|
45
|
+
*.log
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
+MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 JHarness contributors
|
|
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,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jharness-kernel
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: JHarness model-neutral immutable runtime kernel
|
|
5
|
+
Project-URL: Documentation, https://github.com/Ezio2000/jharness-python/tree/main/docs
|
|
6
|
+
Project-URL: Homepage, https://github.com/Ezio2000/jharness-python
|
|
7
|
+
Project-URL: Issues, https://github.com/Ezio2000/jharness-python/issues
|
|
8
|
+
Project-URL: Repository, https://github.com/Ezio2000/jharness-python.git
|
|
9
|
+
Author: JHarness contributors
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: agent,ai,model,runtime,tools
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# jharness-kernel
|
|
27
|
+
|
|
28
|
+
`jharness-kernel` is the dependency-free, model-neutral runtime distribution. It owns
|
|
29
|
+
immutable run state, `Runtime` and `Invocation`, model and tool protocols,
|
|
30
|
+
control, events, limits, atomic checkpoints, repository ports, and explicit
|
|
31
|
+
wire codecs.
|
|
32
|
+
|
|
33
|
+
Stable runtime concepts are imported from `jharness.kernel`. Portable codecs and
|
|
34
|
+
diagnostics are opt-in namespaces:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
uv add jharness-kernel
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from jharness.kernel import Message, Runtime
|
|
42
|
+
from jharness.kernel.diagnostics import build_trace, verify_trace
|
|
43
|
+
from jharness.kernel.wire import decode_checkpoint, encode_checkpoint
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
See the repository
|
|
47
|
+
[architecture](https://github.com/Ezio2000/jharness/blob/v0.1.0/docs/architecture.md),
|
|
48
|
+
[state machine](https://github.com/Ezio2000/jharness/blob/v0.1.0/docs/state-machine.md), and
|
|
49
|
+
[Python package boundaries](https://github.com/Ezio2000/jharness-python/blob/main/docs/python-package-boundaries.md).
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# jharness-kernel
|
|
2
|
+
|
|
3
|
+
`jharness-kernel` is the dependency-free, model-neutral runtime distribution. It owns
|
|
4
|
+
immutable run state, `Runtime` and `Invocation`, model and tool protocols,
|
|
5
|
+
control, events, limits, atomic checkpoints, repository ports, and explicit
|
|
6
|
+
wire codecs.
|
|
7
|
+
|
|
8
|
+
Stable runtime concepts are imported from `jharness.kernel`. Portable codecs and
|
|
9
|
+
diagnostics are opt-in namespaces:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
uv add jharness-kernel
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from jharness.kernel import Message, Runtime
|
|
17
|
+
from jharness.kernel.diagnostics import build_trace, verify_trace
|
|
18
|
+
from jharness.kernel.wire import decode_checkpoint, encode_checkpoint
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
See the repository
|
|
22
|
+
[architecture](https://github.com/Ezio2000/jharness/blob/v0.1.0/docs/architecture.md),
|
|
23
|
+
[state machine](https://github.com/Ezio2000/jharness/blob/v0.1.0/docs/state-machine.md), and
|
|
24
|
+
[Python package boundaries](https://github.com/Ezio2000/jharness-python/blob/main/docs/python-package-boundaries.md).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "jharness-kernel"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "JHarness model-neutral immutable runtime kernel"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
authors = [{ name = "JHarness contributors" }]
|
|
10
|
+
keywords = ["ai", "agent", "model", "runtime", "tools"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 3 - Alpha",
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"Operating System :: OS Independent",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Programming Language :: Python :: 3.14",
|
|
21
|
+
"Typing :: Typed",
|
|
22
|
+
]
|
|
23
|
+
dependencies = []
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Documentation = "https://github.com/Ezio2000/jharness-python/tree/main/docs"
|
|
27
|
+
Homepage = "https://github.com/Ezio2000/jharness-python"
|
|
28
|
+
Issues = "https://github.com/Ezio2000/jharness-python/issues"
|
|
29
|
+
Repository = "https://github.com/Ezio2000/jharness-python.git"
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build.targets.wheel]
|
|
32
|
+
packages = ["src/jharness"]
|
|
33
|
+
|
|
34
|
+
[build-system]
|
|
35
|
+
requires = ["hatchling"]
|
|
36
|
+
build-backend = "hatchling.build"
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""Model-neutral immutable execution kernel."""
|
|
2
|
+
|
|
3
|
+
from jharness.kernel.approval import (
|
|
4
|
+
ApprovalAllow,
|
|
5
|
+
ApprovalDecision,
|
|
6
|
+
ApprovalDeny,
|
|
7
|
+
ApprovalPolicy,
|
|
8
|
+
ApprovalRequest,
|
|
9
|
+
ApprovalSuspend,
|
|
10
|
+
)
|
|
11
|
+
from jharness.kernel.checkpoint import (
|
|
12
|
+
Checkpoint,
|
|
13
|
+
ControlFact,
|
|
14
|
+
ConversationInsertFact,
|
|
15
|
+
Fact,
|
|
16
|
+
FailedControl,
|
|
17
|
+
HistoryRewriteFact,
|
|
18
|
+
LimitedControl,
|
|
19
|
+
ModelTurnFact,
|
|
20
|
+
ModelTurnResult,
|
|
21
|
+
ResumedFact,
|
|
22
|
+
StartedFact,
|
|
23
|
+
SuspendedControl,
|
|
24
|
+
SuspensionView,
|
|
25
|
+
ToolBatchFact,
|
|
26
|
+
ToolOutcomeKind,
|
|
27
|
+
)
|
|
28
|
+
from jharness.kernel.commands import SuspensionSelector
|
|
29
|
+
from jharness.kernel.context import RunContext
|
|
30
|
+
from jharness.kernel.errors import (
|
|
31
|
+
CommitError,
|
|
32
|
+
KernelError,
|
|
33
|
+
ModelError,
|
|
34
|
+
ModelErrorInfo,
|
|
35
|
+
ProtocolError,
|
|
36
|
+
RepositoryError,
|
|
37
|
+
RevisionConflict,
|
|
38
|
+
ToolError,
|
|
39
|
+
)
|
|
40
|
+
from jharness.kernel.events import Event, EventKind
|
|
41
|
+
from jharness.kernel.history import HistoryReducer, HistoryRewrite
|
|
42
|
+
from jharness.kernel.invocation import Invocation
|
|
43
|
+
from jharness.kernel.json_values import (
|
|
44
|
+
JsonScalar,
|
|
45
|
+
JsonValue,
|
|
46
|
+
freeze_json_value,
|
|
47
|
+
thaw_json_value,
|
|
48
|
+
)
|
|
49
|
+
from jharness.kernel.limits import LimitReason, RunLimits
|
|
50
|
+
from jharness.kernel.messages import ArtifactRef, ContentPart, ErrorInfo, Message, TaskRef, ToolCall
|
|
51
|
+
from jharness.kernel.models import (
|
|
52
|
+
DeltaSink,
|
|
53
|
+
Model,
|
|
54
|
+
ModelCapabilities,
|
|
55
|
+
ModelContentDelta,
|
|
56
|
+
ModelDelta,
|
|
57
|
+
ModelOptions,
|
|
58
|
+
ModelReasoningDelta,
|
|
59
|
+
ModelRequest,
|
|
60
|
+
ModelResponse,
|
|
61
|
+
ModelToolCallDelta,
|
|
62
|
+
ModelUsage,
|
|
63
|
+
ModelUsageDelta,
|
|
64
|
+
ResponseFormat,
|
|
65
|
+
ToolChoice,
|
|
66
|
+
)
|
|
67
|
+
from jharness.kernel.repository import EphemeralRepository, RunRepository
|
|
68
|
+
from jharness.kernel.runtime import Runtime
|
|
69
|
+
from jharness.kernel.snapshot import RunSnapshot
|
|
70
|
+
from jharness.kernel.state import (
|
|
71
|
+
ActiveState,
|
|
72
|
+
Completed,
|
|
73
|
+
Failed,
|
|
74
|
+
Limited,
|
|
75
|
+
Planning,
|
|
76
|
+
RunMetrics,
|
|
77
|
+
RunState,
|
|
78
|
+
Suspended,
|
|
79
|
+
Suspension,
|
|
80
|
+
ToolsPending,
|
|
81
|
+
)
|
|
82
|
+
from jharness.kernel.tools import (
|
|
83
|
+
BatchPolicy,
|
|
84
|
+
SettledOutcome,
|
|
85
|
+
SettledResult,
|
|
86
|
+
ToolAccepted,
|
|
87
|
+
ToolBatch,
|
|
88
|
+
ToolBinding,
|
|
89
|
+
ToolCatalog,
|
|
90
|
+
ToolCatalogProvider,
|
|
91
|
+
ToolContext,
|
|
92
|
+
ToolExecution,
|
|
93
|
+
ToolFailure,
|
|
94
|
+
ToolOutcome,
|
|
95
|
+
ToolResult,
|
|
96
|
+
ToolRisk,
|
|
97
|
+
ToolSpec,
|
|
98
|
+
ToolSuccess,
|
|
99
|
+
ToolWaiting,
|
|
100
|
+
WaitingResult,
|
|
101
|
+
tool_message,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
__all__ = [
|
|
105
|
+
"ActiveState",
|
|
106
|
+
"ApprovalAllow",
|
|
107
|
+
"ApprovalDecision",
|
|
108
|
+
"ApprovalDeny",
|
|
109
|
+
"ApprovalPolicy",
|
|
110
|
+
"ApprovalRequest",
|
|
111
|
+
"ApprovalSuspend",
|
|
112
|
+
"ArtifactRef",
|
|
113
|
+
"BatchPolicy",
|
|
114
|
+
"Checkpoint",
|
|
115
|
+
"CommitError",
|
|
116
|
+
"Completed",
|
|
117
|
+
"ContentPart",
|
|
118
|
+
"ControlFact",
|
|
119
|
+
"ConversationInsertFact",
|
|
120
|
+
"DeltaSink",
|
|
121
|
+
"EphemeralRepository",
|
|
122
|
+
"ErrorInfo",
|
|
123
|
+
"Event",
|
|
124
|
+
"EventKind",
|
|
125
|
+
"Fact",
|
|
126
|
+
"Failed",
|
|
127
|
+
"FailedControl",
|
|
128
|
+
"HistoryReducer",
|
|
129
|
+
"HistoryRewrite",
|
|
130
|
+
"HistoryRewriteFact",
|
|
131
|
+
"Invocation",
|
|
132
|
+
"JsonScalar",
|
|
133
|
+
"JsonValue",
|
|
134
|
+
"KernelError",
|
|
135
|
+
"LimitReason",
|
|
136
|
+
"Limited",
|
|
137
|
+
"LimitedControl",
|
|
138
|
+
"Message",
|
|
139
|
+
"Model",
|
|
140
|
+
"ModelCapabilities",
|
|
141
|
+
"ModelContentDelta",
|
|
142
|
+
"ModelDelta",
|
|
143
|
+
"ModelError",
|
|
144
|
+
"ModelErrorInfo",
|
|
145
|
+
"ModelOptions",
|
|
146
|
+
"ModelReasoningDelta",
|
|
147
|
+
"ModelRequest",
|
|
148
|
+
"ModelResponse",
|
|
149
|
+
"ModelToolCallDelta",
|
|
150
|
+
"ModelTurnFact",
|
|
151
|
+
"ModelTurnResult",
|
|
152
|
+
"ModelUsage",
|
|
153
|
+
"ModelUsageDelta",
|
|
154
|
+
"Planning",
|
|
155
|
+
"ProtocolError",
|
|
156
|
+
"RepositoryError",
|
|
157
|
+
"ResponseFormat",
|
|
158
|
+
"ResumedFact",
|
|
159
|
+
"RevisionConflict",
|
|
160
|
+
"RunContext",
|
|
161
|
+
"RunLimits",
|
|
162
|
+
"RunMetrics",
|
|
163
|
+
"RunRepository",
|
|
164
|
+
"RunSnapshot",
|
|
165
|
+
"RunState",
|
|
166
|
+
"Runtime",
|
|
167
|
+
"SettledOutcome",
|
|
168
|
+
"SettledResult",
|
|
169
|
+
"StartedFact",
|
|
170
|
+
"Suspended",
|
|
171
|
+
"SuspendedControl",
|
|
172
|
+
"Suspension",
|
|
173
|
+
"SuspensionSelector",
|
|
174
|
+
"SuspensionView",
|
|
175
|
+
"TaskRef",
|
|
176
|
+
"ToolAccepted",
|
|
177
|
+
"ToolBatch",
|
|
178
|
+
"ToolBatchFact",
|
|
179
|
+
"ToolBinding",
|
|
180
|
+
"ToolCall",
|
|
181
|
+
"ToolCatalog",
|
|
182
|
+
"ToolCatalogProvider",
|
|
183
|
+
"ToolChoice",
|
|
184
|
+
"ToolContext",
|
|
185
|
+
"ToolError",
|
|
186
|
+
"ToolExecution",
|
|
187
|
+
"ToolFailure",
|
|
188
|
+
"ToolOutcome",
|
|
189
|
+
"ToolOutcomeKind",
|
|
190
|
+
"ToolResult",
|
|
191
|
+
"ToolRisk",
|
|
192
|
+
"ToolSpec",
|
|
193
|
+
"ToolSuccess",
|
|
194
|
+
"ToolWaiting",
|
|
195
|
+
"ToolsPending",
|
|
196
|
+
"WaitingResult",
|
|
197
|
+
"freeze_json_value",
|
|
198
|
+
"thaw_json_value",
|
|
199
|
+
"tool_message",
|
|
200
|
+
]
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""Small explicit digest writer for immutable kernel content."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping, Sequence
|
|
6
|
+
from hashlib import sha256
|
|
7
|
+
from typing import Protocol, cast
|
|
8
|
+
|
|
9
|
+
from jharness.kernel.messages import ArtifactRef, ContentPart, ErrorInfo, Message, TaskRef, ToolCall
|
|
10
|
+
from jharness.kernel.tools import ToolAccepted, ToolFailure, ToolOutcome, ToolWaiting
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class _Hash(Protocol):
|
|
14
|
+
def update(self, data: bytes, /) -> None: ...
|
|
15
|
+
|
|
16
|
+
def digest(self) -> bytes: ...
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DigestWriter:
|
|
20
|
+
"""Domain-separated, length-prefixed writer with explicit scalar types."""
|
|
21
|
+
|
|
22
|
+
__slots__ = ("_hash",)
|
|
23
|
+
|
|
24
|
+
def __init__(self, domain: str) -> None:
|
|
25
|
+
self._hash: _Hash = sha256()
|
|
26
|
+
self._chunk(b"d", domain.encode())
|
|
27
|
+
|
|
28
|
+
def field(self, name: str) -> None:
|
|
29
|
+
self._chunk(b"k", name.encode())
|
|
30
|
+
|
|
31
|
+
def none(self) -> None:
|
|
32
|
+
self._hash.update(b"n")
|
|
33
|
+
|
|
34
|
+
def boolean(self, value: bool) -> None:
|
|
35
|
+
self._hash.update(b"b1" if value else b"b0")
|
|
36
|
+
|
|
37
|
+
def integer(self, value: int) -> None:
|
|
38
|
+
self._chunk(b"i", str(value).encode())
|
|
39
|
+
|
|
40
|
+
def number(self, value: float) -> None:
|
|
41
|
+
self._chunk(b"f", value.hex().encode())
|
|
42
|
+
|
|
43
|
+
def string(self, value: str) -> None:
|
|
44
|
+
self._chunk(b"s", value.encode())
|
|
45
|
+
|
|
46
|
+
def bytes(self, value: bytes) -> None:
|
|
47
|
+
self._chunk(b"x", value)
|
|
48
|
+
|
|
49
|
+
def sequence(self, length: int) -> None:
|
|
50
|
+
self._chunk(b"q", str(length).encode())
|
|
51
|
+
|
|
52
|
+
def mapping(self, length: int) -> None:
|
|
53
|
+
self._chunk(b"m", str(length).encode())
|
|
54
|
+
|
|
55
|
+
def json(self, value: object) -> None:
|
|
56
|
+
"""Write a validated JSON value with stable mapping order and scalar types."""
|
|
57
|
+
|
|
58
|
+
if value is None:
|
|
59
|
+
self.none()
|
|
60
|
+
elif isinstance(value, bool):
|
|
61
|
+
self.boolean(value)
|
|
62
|
+
elif isinstance(value, int):
|
|
63
|
+
self.integer(value)
|
|
64
|
+
elif isinstance(value, float):
|
|
65
|
+
self.number(value)
|
|
66
|
+
elif isinstance(value, str):
|
|
67
|
+
self.string(value)
|
|
68
|
+
elif isinstance(value, Mapping):
|
|
69
|
+
self._json_mapping(cast(Mapping[object, object], value))
|
|
70
|
+
elif isinstance(value, Sequence) and not isinstance(value, bytes | bytearray):
|
|
71
|
+
items = cast(Sequence[object], value)
|
|
72
|
+
self.sequence(len(items))
|
|
73
|
+
for item in items:
|
|
74
|
+
self.json(item)
|
|
75
|
+
else:
|
|
76
|
+
raise TypeError(f"unsupported JSON digest value: {type(value).__qualname__}")
|
|
77
|
+
|
|
78
|
+
def finish(self) -> bytes:
|
|
79
|
+
return self._hash.digest()
|
|
80
|
+
|
|
81
|
+
def _json_mapping(self, value: Mapping[object, object]) -> None:
|
|
82
|
+
if any(not isinstance(key, str) for key in value):
|
|
83
|
+
raise TypeError("JSON digest mapping keys must be strings")
|
|
84
|
+
items = cast(Mapping[str, object], value)
|
|
85
|
+
keys = sorted(items)
|
|
86
|
+
self.mapping(len(keys))
|
|
87
|
+
for key in keys:
|
|
88
|
+
self.string(key)
|
|
89
|
+
self.json(items[key])
|
|
90
|
+
|
|
91
|
+
def _chunk(self, tag: bytes, value: bytes) -> None:
|
|
92
|
+
self._hash.update(tag)
|
|
93
|
+
self._hash.update(len(value).to_bytes(8, "big"))
|
|
94
|
+
self._hash.update(value)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def empty_history_digest() -> bytes:
|
|
98
|
+
return DigestWriter("jharness.kernel.history.empty.v0").finish()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def append_history_digest(previous: bytes, message: Message) -> bytes:
|
|
102
|
+
writer = DigestWriter("jharness.kernel.history.append.v0")
|
|
103
|
+
writer.field("previous")
|
|
104
|
+
writer.bytes(previous)
|
|
105
|
+
writer.field("message")
|
|
106
|
+
write_message(writer, message)
|
|
107
|
+
return writer.finish()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def write_message(writer: DigestWriter, value: Message) -> None:
|
|
111
|
+
writer.field("role")
|
|
112
|
+
writer.string(value.role)
|
|
113
|
+
writer.field("parts")
|
|
114
|
+
write_parts(writer, value.parts)
|
|
115
|
+
writer.field("tool_calls")
|
|
116
|
+
write_tool_calls(writer, value.tool_calls)
|
|
117
|
+
writer.field("tool_call_id")
|
|
118
|
+
write_optional_string(writer, value.tool_call_id)
|
|
119
|
+
writer.field("outcome")
|
|
120
|
+
if value.outcome is None:
|
|
121
|
+
writer.none()
|
|
122
|
+
else:
|
|
123
|
+
_write_tool_outcome(writer, value.outcome)
|
|
124
|
+
writer.field("metadata")
|
|
125
|
+
writer.json(value.metadata)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def write_parts(writer: DigestWriter, values: tuple[ContentPart, ...]) -> None:
|
|
129
|
+
writer.sequence(len(values))
|
|
130
|
+
for value in values:
|
|
131
|
+
_write_content_part(writer, value)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _write_content_part(writer: DigestWriter, value: ContentPart) -> None:
|
|
135
|
+
writer.field("content_part")
|
|
136
|
+
writer.field("type")
|
|
137
|
+
writer.string(value.type)
|
|
138
|
+
writer.field("text")
|
|
139
|
+
write_optional_string(writer, value.text)
|
|
140
|
+
writer.field("uri")
|
|
141
|
+
write_optional_string(writer, value.uri)
|
|
142
|
+
writer.field("artifact")
|
|
143
|
+
if value.artifact is None:
|
|
144
|
+
writer.none()
|
|
145
|
+
else:
|
|
146
|
+
_write_artifact(writer, value.artifact)
|
|
147
|
+
writer.field("media_type")
|
|
148
|
+
write_optional_string(writer, value.media_type)
|
|
149
|
+
writer.field("name")
|
|
150
|
+
write_optional_string(writer, value.name)
|
|
151
|
+
writer.field("data")
|
|
152
|
+
writer.json(value.data)
|
|
153
|
+
writer.field("metadata")
|
|
154
|
+
writer.json(value.metadata)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _write_artifact(writer: DigestWriter, value: ArtifactRef) -> None:
|
|
158
|
+
writer.field("artifact_ref")
|
|
159
|
+
writer.field("ref")
|
|
160
|
+
writer.string(value.ref)
|
|
161
|
+
writer.field("media_type")
|
|
162
|
+
write_optional_string(writer, value.media_type)
|
|
163
|
+
writer.field("name")
|
|
164
|
+
write_optional_string(writer, value.name)
|
|
165
|
+
writer.field("size_bytes")
|
|
166
|
+
write_optional_integer(writer, value.size_bytes)
|
|
167
|
+
writer.field("sha256")
|
|
168
|
+
write_optional_string(writer, value.sha256)
|
|
169
|
+
writer.field("metadata")
|
|
170
|
+
writer.json(value.metadata)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def write_tool_calls(writer: DigestWriter, values: tuple[ToolCall, ...]) -> None:
|
|
174
|
+
writer.sequence(len(values))
|
|
175
|
+
for value in values:
|
|
176
|
+
_write_tool_call(writer, value)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _write_tool_call(writer: DigestWriter, value: ToolCall) -> None:
|
|
180
|
+
writer.field("tool_call")
|
|
181
|
+
writer.field("id")
|
|
182
|
+
writer.string(value.id)
|
|
183
|
+
writer.field("name")
|
|
184
|
+
writer.string(value.name)
|
|
185
|
+
writer.field("arguments")
|
|
186
|
+
writer.json(value.arguments)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _write_tool_outcome(writer: DigestWriter, value: ToolOutcome) -> None:
|
|
190
|
+
writer.field("tool_outcome")
|
|
191
|
+
writer.field("kind")
|
|
192
|
+
writer.string(value.kind)
|
|
193
|
+
writer.field("parts")
|
|
194
|
+
write_parts(writer, value.parts)
|
|
195
|
+
writer.field("structured_content")
|
|
196
|
+
writer.json(value.structured_content)
|
|
197
|
+
if isinstance(value, ToolFailure):
|
|
198
|
+
writer.field("error")
|
|
199
|
+
write_error(writer, value.error)
|
|
200
|
+
elif isinstance(value, ToolAccepted):
|
|
201
|
+
writer.field("correlation_id")
|
|
202
|
+
writer.string(value.correlation_id)
|
|
203
|
+
writer.field("task")
|
|
204
|
+
_write_optional_task(writer, value.task)
|
|
205
|
+
elif isinstance(value, ToolWaiting):
|
|
206
|
+
writer.field("task")
|
|
207
|
+
_write_optional_task(writer, value.task)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _write_optional_task(writer: DigestWriter, value: TaskRef | None) -> None:
|
|
211
|
+
if value is None:
|
|
212
|
+
writer.none()
|
|
213
|
+
return
|
|
214
|
+
writer.field("task_ref")
|
|
215
|
+
writer.field("id")
|
|
216
|
+
writer.string(value.id)
|
|
217
|
+
writer.field("status")
|
|
218
|
+
writer.string(value.status)
|
|
219
|
+
writer.field("metadata")
|
|
220
|
+
writer.json(value.metadata)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def write_error(writer: DigestWriter, value: ErrorInfo) -> None:
|
|
224
|
+
writer.field("error_info")
|
|
225
|
+
writer.field("code")
|
|
226
|
+
writer.string(value.code)
|
|
227
|
+
writer.field("message")
|
|
228
|
+
writer.string(value.message)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def write_optional_string(writer: DigestWriter, value: str | None) -> None:
|
|
232
|
+
if value is None:
|
|
233
|
+
writer.none()
|
|
234
|
+
else:
|
|
235
|
+
writer.string(value)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def write_optional_integer(writer: DigestWriter, value: int | None) -> None:
|
|
239
|
+
if value is None:
|
|
240
|
+
writer.none()
|
|
241
|
+
else:
|
|
242
|
+
writer.integer(value)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Private execution engine."""
|