cap-sdk-python 1.0.6__tar.gz → 2.0.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.
Files changed (31) hide show
  1. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/PKG-INFO +2 -2
  2. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/README.md +1 -1
  3. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/cap/client.py +11 -1
  4. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/cap/pb/cortex/agent/v1/buspacket_pb2.py +2 -2
  5. cap_sdk_python-2.0.0/cap/pb/cortex/agent/v1/job_pb2.py +55 -0
  6. cap_sdk_python-2.0.0/cap/pb/cortex/agent/v1/safety_pb2.py +48 -0
  7. cap_sdk_python-2.0.0/cap/worker.py +77 -0
  8. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/cap_sdk_python.egg-info/PKG-INFO +2 -2
  9. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/cap_sdk_python.egg-info/SOURCES.txt +2 -1
  10. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/cap_sdk_python.egg-info/top_level.txt +1 -0
  11. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/pyproject.toml +1 -1
  12. cap_sdk_python-2.0.0/tests/test_sdk.py +103 -0
  13. cap_sdk_python-1.0.6/cap/pb/cortex/agent/v1/job_pb2.py +0 -47
  14. cap_sdk_python-1.0.6/cap/pb/cortex/agent/v1/safety_pb2.py +0 -44
  15. cap_sdk_python-1.0.6/cap/worker.py +0 -45
  16. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/cap/__init__.py +0 -0
  17. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/cap/bus.py +0 -0
  18. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/cap/pb/__init__.py +0 -0
  19. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/cap/pb/cortex/__init__.py +0 -0
  20. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/cap/pb/cortex/agent/__init__.py +0 -0
  21. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/cap/pb/cortex/agent/v1/__init__.py +0 -0
  22. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/cap/pb/cortex/agent/v1/alert_pb2.py +0 -0
  23. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/cap/pb/cortex/agent/v1/alert_pb2_grpc.py +0 -0
  24. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/cap/pb/cortex/agent/v1/buspacket_pb2_grpc.py +0 -0
  25. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/cap/pb/cortex/agent/v1/heartbeat_pb2.py +0 -0
  26. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/cap/pb/cortex/agent/v1/heartbeat_pb2_grpc.py +0 -0
  27. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/cap/pb/cortex/agent/v1/job_pb2_grpc.py +0 -0
  28. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/cap/pb/cortex/agent/v1/safety_pb2_grpc.py +0 -0
  29. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/cap_sdk_python.egg-info/dependency_links.txt +0 -0
  30. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/cap_sdk_python.egg-info/requires.txt +0 -0
  31. {cap_sdk_python-1.0.6 → cap_sdk_python-2.0.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cap-sdk-python
3
- Version: 1.0.6
3
+ Version: 2.0.0
4
4
  Summary: CAP (Cortex Agent Protocol) Python SDK
5
5
  Project-URL: Homepage, https://github.com/coretexos/cap
6
6
  Requires-Python: >=3.9
@@ -22,7 +22,7 @@ Asyncio-first SDK with NATS helpers for CAP workers and clients.
22
22
  --grpc_python_out=./cap/pb \
23
23
  ../../proto/cortex/agent/v1/*.proto
24
24
  ```
25
- (Or run `./tools/make_protos.sh` from repo root and copy `generated/python` into `sdk/python/cap/pb`.)
25
+ (Or run `./tools/make_protos.sh` from repo root with `CAP_RUN_PY=1` and copy `/python` into `sdk/python/cap/pb` if you want vendored stubs.)
26
26
 
27
27
  2. Install:
28
28
  ```bash
@@ -11,7 +11,7 @@ Asyncio-first SDK with NATS helpers for CAP workers and clients.
11
11
  --grpc_python_out=./cap/pb \
12
12
  ../../proto/cortex/agent/v1/*.proto
13
13
  ```
14
- (Or run `./tools/make_protos.sh` from repo root and copy `generated/python` into `sdk/python/cap/pb`.)
14
+ (Or run `./tools/make_protos.sh` from repo root with `CAP_RUN_PY=1` and copy `/python` into `sdk/python/cap/pb` if you want vendored stubs.)
15
15
 
16
16
  2. Install:
17
17
  ```bash
@@ -1,12 +1,16 @@
1
+ import nats
1
2
  from google.protobuf import timestamp_pb2
2
3
  from cap.pb.cortex.agent.v1 import buspacket_pb2
4
+ from cryptography.hazmat.primitives.asymmetric import ec
5
+ from cryptography.hazmat.primitives import hashes
6
+ import hashlib
3
7
 
4
8
 
5
9
  DEFAULT_PROTOCOL_VERSION = 1
6
10
  SUBJECT_SUBMIT = "sys.job.submit"
7
11
 
8
12
 
9
- async def submit_job(nc, job_request, trace_id: str, sender_id: str):
13
+ async def submit_job(nc, job_request, trace_id: str, sender_id: str, private_key: ec.EllipticCurvePrivateKey):
10
14
  ts = timestamp_pb2.Timestamp()
11
15
  ts.GetCurrentTime()
12
16
  packet = buspacket_pb2.BusPacket()
@@ -15,4 +19,10 @@ async def submit_job(nc, job_request, trace_id: str, sender_id: str):
15
19
  packet.created_at.CopyFrom(ts)
16
20
  packet.protocol_version = DEFAULT_PROTOCOL_VERSION
17
21
  packet.job_request.CopyFrom(job_request)
22
+
23
+ unsigned_data = packet.SerializeToString()
24
+ digest = hashlib.sha256(unsigned_data).digest()
25
+ signature = private_key.sign(digest, ec.ECDSA(hashes.SHA256()))
26
+ packet.signature = signature
27
+
18
28
  await nc.publish(SUBJECT_SUBMIT, packet.SerializeToString())
@@ -28,7 +28,7 @@ from cortex.agent.v1 import heartbeat_pb2 as cortex_dot_agent_dot_v1_dot_heartbe
28
28
  from cortex.agent.v1 import alert_pb2 as cortex_dot_agent_dot_v1_dot_alert__pb2
29
29
 
30
30
 
31
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1f\x63ortex/agent/v1/buspacket.proto\x12\x0f\x63ortex.agent.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x19\x63ortex/agent/v1/job.proto\x1a\x1f\x63ortex/agent/v1/heartbeat.proto\x1a\x1b\x63ortex/agent/v1/alert.proto\"\xcb\x02\n\tBusPacket\x12\x10\n\x08trace_id\x18\x01 \x01(\t\x12\x11\n\tsender_id\x18\x02 \x01(\t\x12.\n\ncreated_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x18\n\x10protocol_version\x18\x04 \x01(\x05\x12\x32\n\x0bjob_request\x18\n \x01(\x0b\x32\x1b.cortex.agent.v1.JobRequestH\x00\x12\x30\n\njob_result\x18\x0b \x01(\x0b\x32\x1a.cortex.agent.v1.JobResultH\x00\x12/\n\theartbeat\x18\x0c \x01(\x0b\x32\x1a.cortex.agent.v1.HeartbeatH\x00\x12-\n\x05\x61lert\x18\r \x01(\x0b\x32\x1c.cortex.agent.v1.SystemAlertH\x00\x42\t\n\x07payloadB\x7f\n\x16\x61i.cortex.cap.agent.v1P\x01Z+github.com/coretexos/cap/go/cortex/agent/v1\xaa\x02\x0f\x43ortex.Agent.V1\xca\x02\x0f\x43ortex\\Agent\\V1\xea\x02\x11\x43ortex::Agent::V1b\x06proto3')
31
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1f\x63ortex/agent/v1/buspacket.proto\x12\x0f\x63ortex.agent.v1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x19\x63ortex/agent/v1/job.proto\x1a\x1f\x63ortex/agent/v1/heartbeat.proto\x1a\x1b\x63ortex/agent/v1/alert.proto\"\xde\x02\n\tBusPacket\x12\x10\n\x08trace_id\x18\x01 \x01(\t\x12\x11\n\tsender_id\x18\x02 \x01(\t\x12.\n\ncreated_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x18\n\x10protocol_version\x18\x04 \x01(\x05\x12\x32\n\x0bjob_request\x18\n \x01(\x0b\x32\x1b.cortex.agent.v1.JobRequestH\x00\x12\x30\n\njob_result\x18\x0b \x01(\x0b\x32\x1a.cortex.agent.v1.JobResultH\x00\x12/\n\theartbeat\x18\x0c \x01(\x0b\x32\x1a.cortex.agent.v1.HeartbeatH\x00\x12-\n\x05\x61lert\x18\r \x01(\x0b\x32\x1c.cortex.agent.v1.SystemAlertH\x00\x12\x11\n\tsignature\x18\x0e \x01(\x0c\x42\t\n\x07payloadB\x7f\n\x16\x61i.cortex.cap.agent.v1P\x01Z+github.com/coretexos/cap/go/cortex/agent/v1\xaa\x02\x0f\x43ortex.Agent.V1\xca\x02\x0f\x43ortex\\Agent\\V1\xea\x02\x11\x43ortex::Agent::V1b\x06proto3')
32
32
 
33
33
  _globals = globals()
34
34
  _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -37,5 +37,5 @@ if not _descriptor._USE_C_DESCRIPTORS:
37
37
  _globals['DESCRIPTOR']._loaded_options = None
38
38
  _globals['DESCRIPTOR']._serialized_options = b'\n\026ai.cortex.cap.agent.v1P\001Z+github.com/coretexos/cap/go/cortex/agent/v1\252\002\017Cortex.Agent.V1\312\002\017Cortex\\Agent\\V1\352\002\021Cortex::Agent::V1'
39
39
  _globals['_BUSPACKET']._serialized_start=175
40
- _globals['_BUSPACKET']._serialized_end=506
40
+ _globals['_BUSPACKET']._serialized_end=525
41
41
  # @@protoc_insertion_point(module_scope)
@@ -0,0 +1,55 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: cortex/agent/v1/job.proto
5
+ # Protobuf Python Version: 6.31.1
6
+ """Generated protocol buffer code."""
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ from google.protobuf.internal import builder as _builder
12
+ _runtime_version.ValidateProtobufRuntimeVersion(
13
+ _runtime_version.Domain.PUBLIC,
14
+ 6,
15
+ 31,
16
+ 1,
17
+ '',
18
+ 'cortex/agent/v1/job.proto'
19
+ )
20
+ # @@protoc_insertion_point(imports)
21
+
22
+ _sym_db = _symbol_database.Default()
23
+
24
+
25
+
26
+
27
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19\x63ortex/agent/v1/job.proto\x12\x0f\x63ortex.agent.v1\"l\n\x0c\x43ontextHints\x12\x18\n\x10max_input_tokens\x18\x01 \x01(\x05\x12\x1b\n\x13\x61llow_summarization\x18\x02 \x01(\x08\x12\x17\n\x0f\x61llow_retrieval\x18\x03 \x01(\x08\x12\x0c\n\x04tags\x18\x04 \x03(\t\"l\n\x06\x42udget\x12\x18\n\x10max_input_tokens\x18\x01 \x01(\x03\x12\x19\n\x11max_output_tokens\x18\x02 \x01(\x03\x12\x18\n\x10max_total_tokens\x18\x03 \x01(\x03\x12\x13\n\x0b\x64\x65\x61\x64line_ms\x18\x04 \x01(\x03\"\xa6\x04\n\nJobRequest\x12\x0e\n\x06job_id\x18\x01 \x01(\t\x12\r\n\x05topic\x18\x02 \x01(\t\x12.\n\x08priority\x18\x03 \x01(\x0e\x32\x1c.cortex.agent.v1.JobPriority\x12\x13\n\x0b\x63ontext_ptr\x18\x04 \x01(\t\x12\x12\n\nadapter_id\x18\x05 \x01(\t\x12\x31\n\x03\x65nv\x18\x06 \x03(\x0b\x32$.cortex.agent.v1.JobRequest.EnvEntry\x12\x15\n\rparent_job_id\x18\x07 \x01(\t\x12\x13\n\x0bworkflow_id\x18\x08 \x01(\t\x12\x12\n\nstep_index\x18\t \x01(\x05\x12\x11\n\tmemory_id\x18\n \x01(\t\x12\x34\n\rcontext_hints\x18\x0b \x01(\x0b\x32\x1d.cortex.agent.v1.ContextHints\x12\'\n\x06\x62udget\x18\x0c \x01(\x0b\x32\x17.cortex.agent.v1.Budget\x12\x11\n\ttenant_id\x18\r \x01(\t\x12\x14\n\x0cprincipal_id\x18\x0e \x01(\t\x12\x37\n\x06labels\x18\x0f \x03(\x0b\x32\'.cortex.agent.v1.JobRequest.LabelsEntry\x1a*\n\x08\x45nvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xaf\x01\n\tJobResult\x12\x0e\n\x06job_id\x18\x01 \x01(\t\x12*\n\x06status\x18\x02 \x01(\x0e\x32\x1a.cortex.agent.v1.JobStatus\x12\x12\n\nresult_ptr\x18\x03 \x01(\t\x12\x11\n\tworker_id\x18\x04 \x01(\t\x12\x14\n\x0c\x65xecution_ms\x18\x05 \x01(\x03\x12\x12\n\nerror_code\x18\x06 \x01(\t\x12\x15\n\rerror_message\x18\x07 \x01(\t*|\n\x0bJobPriority\x12\x1c\n\x18JOB_PRIORITY_UNSPECIFIED\x10\x00\x12\x1c\n\x18JOB_PRIORITY_INTERACTIVE\x10\x01\x12\x16\n\x12JOB_PRIORITY_BATCH\x10\x02\x12\x19\n\x15JOB_PRIORITY_CRITICAL\x10\x03*\x86\x02\n\tJobStatus\x12\x1a\n\x16JOB_STATUS_UNSPECIFIED\x10\x00\x12\x16\n\x12JOB_STATUS_PENDING\x10\x01\x12\x18\n\x14JOB_STATUS_SCHEDULED\x10\x02\x12\x19\n\x15JOB_STATUS_DISPATCHED\x10\x03\x12\x16\n\x12JOB_STATUS_RUNNING\x10\x04\x12\x18\n\x14JOB_STATUS_SUCCEEDED\x10\x05\x12\x15\n\x11JOB_STATUS_FAILED\x10\x06\x12\x18\n\x14JOB_STATUS_CANCELLED\x10\x07\x12\x15\n\x11JOB_STATUS_DENIED\x10\x08\x12\x16\n\x12JOB_STATUS_TIMEOUT\x10\tB\x7f\n\x16\x61i.cortex.cap.agent.v1P\x01Z+github.com/coretexos/cap/go/cortex/agent/v1\xaa\x02\x0f\x43ortex.Agent.V1\xca\x02\x0f\x43ortex\\Agent\\V1\xea\x02\x11\x43ortex::Agent::V1b\x06proto3')
28
+
29
+ _globals = globals()
30
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
31
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cortex.agent.v1.job_pb2', _globals)
32
+ if not _descriptor._USE_C_DESCRIPTORS:
33
+ _globals['DESCRIPTOR']._loaded_options = None
34
+ _globals['DESCRIPTOR']._serialized_options = b'\n\026ai.cortex.cap.agent.v1P\001Z+github.com/coretexos/cap/go/cortex/agent/v1\252\002\017Cortex.Agent.V1\312\002\017Cortex\\Agent\\V1\352\002\021Cortex::Agent::V1'
35
+ _globals['_JOBREQUEST_ENVENTRY']._loaded_options = None
36
+ _globals['_JOBREQUEST_ENVENTRY']._serialized_options = b'8\001'
37
+ _globals['_JOBREQUEST_LABELSENTRY']._loaded_options = None
38
+ _globals['_JOBREQUEST_LABELSENTRY']._serialized_options = b'8\001'
39
+ _globals['_JOBPRIORITY']._serialized_start=997
40
+ _globals['_JOBPRIORITY']._serialized_end=1121
41
+ _globals['_JOBSTATUS']._serialized_start=1124
42
+ _globals['_JOBSTATUS']._serialized_end=1386
43
+ _globals['_CONTEXTHINTS']._serialized_start=46
44
+ _globals['_CONTEXTHINTS']._serialized_end=154
45
+ _globals['_BUDGET']._serialized_start=156
46
+ _globals['_BUDGET']._serialized_end=264
47
+ _globals['_JOBREQUEST']._serialized_start=267
48
+ _globals['_JOBREQUEST']._serialized_end=817
49
+ _globals['_JOBREQUEST_ENVENTRY']._serialized_start=728
50
+ _globals['_JOBREQUEST_ENVENTRY']._serialized_end=770
51
+ _globals['_JOBREQUEST_LABELSENTRY']._serialized_start=772
52
+ _globals['_JOBREQUEST_LABELSENTRY']._serialized_end=817
53
+ _globals['_JOBRESULT']._serialized_start=820
54
+ _globals['_JOBRESULT']._serialized_end=995
55
+ # @@protoc_insertion_point(module_scope)
@@ -0,0 +1,48 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: cortex/agent/v1/safety.proto
5
+ # Protobuf Python Version: 6.31.1
6
+ """Generated protocol buffer code."""
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ from google.protobuf.internal import builder as _builder
12
+ _runtime_version.ValidateProtobufRuntimeVersion(
13
+ _runtime_version.Domain.PUBLIC,
14
+ 6,
15
+ 31,
16
+ 1,
17
+ '',
18
+ 'cortex/agent/v1/safety.proto'
19
+ )
20
+ # @@protoc_insertion_point(imports)
21
+
22
+ _sym_db = _symbol_database.Default()
23
+
24
+
25
+ from cortex.agent.v1 import job_pb2 as cortex_dot_agent_dot_v1_dot_job__pb2
26
+
27
+
28
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1c\x63ortex/agent/v1/safety.proto\x12\x0f\x63ortex.agent.v1\x1a\x19\x63ortex/agent/v1/job.proto\"\xcd\x02\n\x12PolicyCheckRequest\x12\x0e\n\x06job_id\x18\x01 \x01(\t\x12\r\n\x05topic\x18\x02 \x01(\t\x12\x0e\n\x06tenant\x18\x03 \x01(\t\x12.\n\x08priority\x18\x04 \x01(\x0e\x32\x1c.cortex.agent.v1.JobPriority\x12\x16\n\x0e\x65stimated_cost\x18\x05 \x01(\x01\x12\'\n\x06\x62udget\x18\x06 \x01(\x0b\x32\x17.cortex.agent.v1.Budget\x12\x14\n\x0cprincipal_id\x18\x07 \x01(\t\x12?\n\x06labels\x18\x08 \x03(\x0b\x32/.cortex.agent.v1.PolicyCheckRequest.LabelsEntry\x12\x11\n\tmemory_id\x18\t \x01(\t\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"t\n\x13PolicyCheckResponse\x12/\n\x08\x64\x65\x63ision\x18\x01 \x01(\x0e\x32\x1d.cortex.agent.v1.DecisionType\x12\x0e\n\x06reason\x18\x02 \x01(\t\x12\x1c\n\x14redacted_context_ptr\x18\x03 \x01(\t*\x9b\x01\n\x0c\x44\x65\x63isionType\x12\x1d\n\x19\x44\x45\x43ISION_TYPE_UNSPECIFIED\x10\x00\x12\x17\n\x13\x44\x45\x43ISION_TYPE_ALLOW\x10\x01\x12\x16\n\x12\x44\x45\x43ISION_TYPE_DENY\x10\x02\x12\x1f\n\x1b\x44\x45\x43ISION_TYPE_REQUIRE_HUMAN\x10\x03\x12\x1a\n\x16\x44\x45\x43ISION_TYPE_THROTTLE\x10\x04\x32\x62\n\x0cSafetyKernel\x12R\n\x05\x43heck\x12#.cortex.agent.v1.PolicyCheckRequest\x1a$.cortex.agent.v1.PolicyCheckResponseB\x7f\n\x16\x61i.cortex.cap.agent.v1P\x01Z+github.com/coretexos/cap/go/cortex/agent/v1\xaa\x02\x0f\x43ortex.Agent.V1\xca\x02\x0f\x43ortex\\Agent\\V1\xea\x02\x11\x43ortex::Agent::V1b\x06proto3')
29
+
30
+ _globals = globals()
31
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
32
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cortex.agent.v1.safety_pb2', _globals)
33
+ if not _descriptor._USE_C_DESCRIPTORS:
34
+ _globals['DESCRIPTOR']._loaded_options = None
35
+ _globals['DESCRIPTOR']._serialized_options = b'\n\026ai.cortex.cap.agent.v1P\001Z+github.com/coretexos/cap/go/cortex/agent/v1\252\002\017Cortex.Agent.V1\312\002\017Cortex\\Agent\\V1\352\002\021Cortex::Agent::V1'
36
+ _globals['_POLICYCHECKREQUEST_LABELSENTRY']._loaded_options = None
37
+ _globals['_POLICYCHECKREQUEST_LABELSENTRY']._serialized_options = b'8\001'
38
+ _globals['_DECISIONTYPE']._serialized_start=531
39
+ _globals['_DECISIONTYPE']._serialized_end=686
40
+ _globals['_POLICYCHECKREQUEST']._serialized_start=77
41
+ _globals['_POLICYCHECKREQUEST']._serialized_end=410
42
+ _globals['_POLICYCHECKREQUEST_LABELSENTRY']._serialized_start=365
43
+ _globals['_POLICYCHECKREQUEST_LABELSENTRY']._serialized_end=410
44
+ _globals['_POLICYCHECKRESPONSE']._serialized_start=412
45
+ _globals['_POLICYCHECKRESPONSE']._serialized_end=528
46
+ _globals['_SAFETYKERNEL']._serialized_start=688
47
+ _globals['_SAFETYKERNEL']._serialized_end=786
48
+ # @@protoc_insertion_point(module_scope)
@@ -0,0 +1,77 @@
1
+ import asyncio
2
+ from typing import Callable, Awaitable, Dict
3
+
4
+ import nats
5
+ from google.protobuf import timestamp_pb2
6
+ from cap.pb.cortex.agent.v1 import buspacket_pb2, job_pb2
7
+ from cryptography.hazmat.primitives.asymmetric import ec
8
+ from cryptography.hazmat.primitives import hashes
9
+ import hashlib
10
+
11
+ DEFAULT_PROTOCOL_VERSION = 1
12
+ SUBJECT_RESULT = "sys.job.result"
13
+
14
+
15
+ async def run_worker(nats_url: str, subject: str, handler: Callable[[job_pb2.JobRequest], Awaitable[job_pb2.JobResult]],
16
+ public_keys: Dict[str, ec.EllipticCurvePublicKey] = None,
17
+ private_key: ec.EllipticCurvePrivateKey = None,
18
+ sender_id: str = "cap-worker",
19
+ connect_fn: Callable = nats.connect):
20
+
21
+ # Allow injection for tests; defaults to nats.connect.
22
+ nc = await connect_fn(servers=nats_url, name=sender_id)
23
+
24
+ async def on_msg(msg):
25
+ packet = buspacket_pb2.BusPacket()
26
+ packet.ParseFromString(msg.data)
27
+
28
+ if public_keys:
29
+ public_key = public_keys.get(packet.sender_id)
30
+ if not public_key:
31
+ print(f"worker: no public key found for sender: {packet.sender_id}")
32
+ return
33
+
34
+ signature = packet.signature
35
+ packet.ClearField("signature")
36
+ unsigned_data = packet.SerializeToString()
37
+ digest = hashlib.sha256(unsigned_data).digest()
38
+ try:
39
+ public_key.verify(signature, digest, ec.ECDSA(hashes.SHA256()))
40
+ except Exception:
41
+ print(f"worker: invalid signature from sender: {packet.sender_id}")
42
+ return
43
+
44
+ req = packet.job_request
45
+ if not req.job_id:
46
+ return
47
+ try:
48
+ res = await handler(req)
49
+ except Exception as exc: # noqa: BLE001
50
+ res = job_pb2.JobResult(
51
+ job_id=req.job_id,
52
+ status=job_pb2.JOB_STATUS_FAILED,
53
+ error_message=str(exc),
54
+ )
55
+ ts = timestamp_pb2.Timestamp()
56
+ ts.GetCurrentTime()
57
+ out = buspacket_pb2.BusPacket()
58
+ out.trace_id = packet.trace_id
59
+ out.sender_id = sender_id
60
+ out.protocol_version = DEFAULT_PROTOCOL_VERSION
61
+ out.created_at.CopyFrom(ts)
62
+ out.job_result.CopyFrom(res)
63
+
64
+ if private_key:
65
+ unsigned_data = out.SerializeToString()
66
+ digest = hashlib.sha256(unsigned_data).digest()
67
+ signature = private_key.sign(digest, ec.ECDSA(hashes.SHA256()))
68
+ out.signature = signature
69
+
70
+ await nc.publish(SUBJECT_RESULT, out.SerializeToString())
71
+
72
+ await nc.subscribe(subject, queue=subject, cb=on_msg)
73
+ try:
74
+ while True:
75
+ await asyncio.sleep(1)
76
+ finally:
77
+ await nc.drain()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cap-sdk-python
3
- Version: 1.0.6
3
+ Version: 2.0.0
4
4
  Summary: CAP (Cortex Agent Protocol) Python SDK
5
5
  Project-URL: Homepage, https://github.com/coretexos/cap
6
6
  Requires-Python: >=3.9
@@ -22,7 +22,7 @@ Asyncio-first SDK with NATS helpers for CAP workers and clients.
22
22
  --grpc_python_out=./cap/pb \
23
23
  ../../proto/cortex/agent/v1/*.proto
24
24
  ```
25
- (Or run `./tools/make_protos.sh` from repo root and copy `generated/python` into `sdk/python/cap/pb`.)
25
+ (Or run `./tools/make_protos.sh` from repo root with `CAP_RUN_PY=1` and copy `/python` into `sdk/python/cap/pb` if you want vendored stubs.)
26
26
 
27
27
  2. Install:
28
28
  ```bash
@@ -22,4 +22,5 @@ cap_sdk_python.egg-info/PKG-INFO
22
22
  cap_sdk_python.egg-info/SOURCES.txt
23
23
  cap_sdk_python.egg-info/dependency_links.txt
24
24
  cap_sdk_python.egg-info/requires.txt
25
- cap_sdk_python.egg-info/top_level.txt
25
+ cap_sdk_python.egg-info/top_level.txt
26
+ tests/test_sdk.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cap-sdk-python"
7
- version = "1.0.6"
7
+ version = "2.0.0"
8
8
  description = "CAP (Cortex Agent Protocol) Python SDK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -0,0 +1,103 @@
1
+ import asyncio
2
+ import sys
3
+ import os
4
+ import unittest
5
+ from typing import Callable, Awaitable, Dict
6
+
7
+ # Avoid loading duplicate generated stubs from both /python and sdk/python/cap/pb.
8
+ sys.path = [p for p in sys.path if not p.rstrip("/").endswith("python")]
9
+ # Ensure generated modules under sdk/python/cap/pb are discoverable for `cortex.agent.v1.*`.
10
+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "cap", "pb")))
11
+
12
+ from cryptography.hazmat.primitives.asymmetric import ec
13
+ from cryptography.hazmat.primitives import hashes
14
+
15
+ from cap.pb.cortex.agent.v1 import job_pb2
16
+ from cap import client
17
+ from cap import worker
18
+
19
+
20
+ class MockNATS:
21
+ def __init__(self):
22
+ self.subscriptions = {}
23
+ self.published = asyncio.Queue()
24
+
25
+ async def publish(self, subject, data):
26
+ await self.published.put((subject, data))
27
+
28
+ async def subscribe(self, subject, queue, cb):
29
+ self.subscriptions[subject] = cb
30
+
31
+ async def connect(self, servers, name):
32
+ return self
33
+
34
+ async def drain(self):
35
+ pass
36
+
37
+
38
+ class TestSDK(unittest.TestCase):
39
+ def test_e2e(self):
40
+ async def run_test():
41
+ client_key = ec.generate_private_key(ec.SECP256R1())
42
+ worker_key = ec.generate_private_key(ec.SECP256R1())
43
+
44
+ mock_nats = MockNATS()
45
+
46
+ async def handler(req: job_pb2.JobRequest) -> job_pb2.JobResult:
47
+ return job_pb2.JobResult(status=job_pb2.JOB_STATUS_SUCCEEDED)
48
+
49
+ worker_task = asyncio.create_task(
50
+ worker.run_worker(
51
+ nats_url="",
52
+ subject="test.worker",
53
+ handler=handler,
54
+ public_keys={"test-client": client_key.public_key()},
55
+ private_key=worker_key,
56
+ sender_id="test-worker",
57
+ connect_fn=mock_nats.connect
58
+ )
59
+ )
60
+
61
+ # Wait until the worker subscription is registered on the mock bus.
62
+ for _ in range(20):
63
+ if "test.worker" in mock_nats.subscriptions:
64
+ break
65
+ await asyncio.sleep(0)
66
+ self.assertIn("test.worker", mock_nats.subscriptions)
67
+
68
+ job_request = job_pb2.JobRequest(job_id="test-job-1", topic="test.worker")
69
+ await client.submit_job(
70
+ mock_nats,
71
+ job_request,
72
+ "test-trace",
73
+ "test-client",
74
+ client_key
75
+ )
76
+
77
+ # Get the published job and send it to the worker
78
+ subj, data = await mock_nats.published.get()
79
+ self.assertEqual(subj, client.SUBJECT_SUBMIT)
80
+ await mock_nats.subscriptions["test.worker"](type("obj", (object,), {"data": data})())
81
+
82
+ # Get the result from the worker
83
+ subj, data = await mock_nats.published.get()
84
+ self.assertEqual(subj, worker.SUBJECT_RESULT)
85
+
86
+ # Verify the signature of the result
87
+ from cap.pb.cortex.agent.v1 import buspacket_pb2
88
+ import hashlib
89
+ result_packet = buspacket_pb2.BusPacket()
90
+ result_packet.ParseFromString(data)
91
+ signature = result_packet.signature
92
+ result_packet.ClearField("signature")
93
+ unsigned_data = result_packet.SerializeToString()
94
+ digest = hashlib.sha256(unsigned_data).digest()
95
+ worker_key.public_key().verify(signature, digest, ec.ECDSA(hashes.SHA256()))
96
+
97
+ worker_task.cancel()
98
+
99
+ asyncio.run(run_test())
100
+
101
+
102
+ if __name__ == "__main__":
103
+ unittest.main()
@@ -1,47 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # Generated by the protocol buffer compiler. DO NOT EDIT!
3
- # NO CHECKED-IN PROTOBUF GENCODE
4
- # source: cortex/agent/v1/job.proto
5
- # Protobuf Python Version: 6.31.1
6
- """Generated protocol buffer code."""
7
- from google.protobuf import descriptor as _descriptor
8
- from google.protobuf import descriptor_pool as _descriptor_pool
9
- from google.protobuf import runtime_version as _runtime_version
10
- from google.protobuf import symbol_database as _symbol_database
11
- from google.protobuf.internal import builder as _builder
12
- _runtime_version.ValidateProtobufRuntimeVersion(
13
- _runtime_version.Domain.PUBLIC,
14
- 6,
15
- 31,
16
- 1,
17
- '',
18
- 'cortex/agent/v1/job.proto'
19
- )
20
- # @@protoc_insertion_point(imports)
21
-
22
- _sym_db = _symbol_database.Default()
23
-
24
-
25
-
26
-
27
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19\x63ortex/agent/v1/job.proto\x12\x0f\x63ortex.agent.v1\"\xa3\x02\n\nJobRequest\x12\x0e\n\x06job_id\x18\x01 \x01(\t\x12\r\n\x05topic\x18\x02 \x01(\t\x12.\n\x08priority\x18\x03 \x01(\x0e\x32\x1c.cortex.agent.v1.JobPriority\x12\x13\n\x0b\x63ontext_ptr\x18\x04 \x01(\t\x12\x12\n\nadapter_id\x18\x05 \x01(\t\x12\x31\n\x03\x65nv\x18\x06 \x03(\x0b\x32$.cortex.agent.v1.JobRequest.EnvEntry\x12\x15\n\rparent_job_id\x18\x07 \x01(\t\x12\x13\n\x0bworkflow_id\x18\x08 \x01(\t\x12\x12\n\nstep_index\x18\t \x01(\x05\x1a*\n\x08\x45nvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xaf\x01\n\tJobResult\x12\x0e\n\x06job_id\x18\x01 \x01(\t\x12*\n\x06status\x18\x02 \x01(\x0e\x32\x1a.cortex.agent.v1.JobStatus\x12\x12\n\nresult_ptr\x18\x03 \x01(\t\x12\x11\n\tworker_id\x18\x04 \x01(\t\x12\x14\n\x0c\x65xecution_ms\x18\x05 \x01(\x03\x12\x12\n\nerror_code\x18\x06 \x01(\t\x12\x15\n\rerror_message\x18\x07 \x01(\t*|\n\x0bJobPriority\x12\x1c\n\x18JOB_PRIORITY_UNSPECIFIED\x10\x00\x12\x1c\n\x18JOB_PRIORITY_INTERACTIVE\x10\x01\x12\x16\n\x12JOB_PRIORITY_BATCH\x10\x02\x12\x19\n\x15JOB_PRIORITY_CRITICAL\x10\x03*\x86\x02\n\tJobStatus\x12\x1a\n\x16JOB_STATUS_UNSPECIFIED\x10\x00\x12\x16\n\x12JOB_STATUS_PENDING\x10\x01\x12\x18\n\x14JOB_STATUS_SCHEDULED\x10\x02\x12\x19\n\x15JOB_STATUS_DISPATCHED\x10\x03\x12\x16\n\x12JOB_STATUS_RUNNING\x10\x04\x12\x18\n\x14JOB_STATUS_SUCCEEDED\x10\x05\x12\x15\n\x11JOB_STATUS_FAILED\x10\x06\x12\x18\n\x14JOB_STATUS_CANCELLED\x10\x07\x12\x15\n\x11JOB_STATUS_DENIED\x10\x08\x12\x16\n\x12JOB_STATUS_TIMEOUT\x10\tB\x7f\n\x16\x61i.cortex.cap.agent.v1P\x01Z+github.com/coretexos/cap/go/cortex/agent/v1\xaa\x02\x0f\x43ortex.Agent.V1\xca\x02\x0f\x43ortex\\Agent\\V1\xea\x02\x11\x43ortex::Agent::V1b\x06proto3')
28
-
29
- _globals = globals()
30
- _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
31
- _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cortex.agent.v1.job_pb2', _globals)
32
- if not _descriptor._USE_C_DESCRIPTORS:
33
- _globals['DESCRIPTOR']._loaded_options = None
34
- _globals['DESCRIPTOR']._serialized_options = b'\n\026ai.cortex.cap.agent.v1P\001Z+github.com/coretexos/cap/go/cortex/agent/v1\252\002\017Cortex.Agent.V1\312\002\017Cortex\\Agent\\V1\352\002\021Cortex::Agent::V1'
35
- _globals['_JOBREQUEST_ENVENTRY']._loaded_options = None
36
- _globals['_JOBREQUEST_ENVENTRY']._serialized_options = b'8\001'
37
- _globals['_JOBPRIORITY']._serialized_start=518
38
- _globals['_JOBPRIORITY']._serialized_end=642
39
- _globals['_JOBSTATUS']._serialized_start=645
40
- _globals['_JOBSTATUS']._serialized_end=907
41
- _globals['_JOBREQUEST']._serialized_start=47
42
- _globals['_JOBREQUEST']._serialized_end=338
43
- _globals['_JOBREQUEST_ENVENTRY']._serialized_start=296
44
- _globals['_JOBREQUEST_ENVENTRY']._serialized_end=338
45
- _globals['_JOBRESULT']._serialized_start=341
46
- _globals['_JOBRESULT']._serialized_end=516
47
- # @@protoc_insertion_point(module_scope)
@@ -1,44 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # Generated by the protocol buffer compiler. DO NOT EDIT!
3
- # NO CHECKED-IN PROTOBUF GENCODE
4
- # source: cortex/agent/v1/safety.proto
5
- # Protobuf Python Version: 6.31.1
6
- """Generated protocol buffer code."""
7
- from google.protobuf import descriptor as _descriptor
8
- from google.protobuf import descriptor_pool as _descriptor_pool
9
- from google.protobuf import runtime_version as _runtime_version
10
- from google.protobuf import symbol_database as _symbol_database
11
- from google.protobuf.internal import builder as _builder
12
- _runtime_version.ValidateProtobufRuntimeVersion(
13
- _runtime_version.Domain.PUBLIC,
14
- 6,
15
- 31,
16
- 1,
17
- '',
18
- 'cortex/agent/v1/safety.proto'
19
- )
20
- # @@protoc_insertion_point(imports)
21
-
22
- _sym_db = _symbol_database.Default()
23
-
24
-
25
- from cortex.agent.v1 import job_pb2 as cortex_dot_agent_dot_v1_dot_job__pb2
26
-
27
-
28
- DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1c\x63ortex/agent/v1/safety.proto\x12\x0f\x63ortex.agent.v1\x1a\x19\x63ortex/agent/v1/job.proto\"\x8b\x01\n\x12PolicyCheckRequest\x12\x0e\n\x06job_id\x18\x01 \x01(\t\x12\r\n\x05topic\x18\x02 \x01(\t\x12\x0e\n\x06tenant\x18\x03 \x01(\t\x12.\n\x08priority\x18\x04 \x01(\x0e\x32\x1c.cortex.agent.v1.JobPriority\x12\x16\n\x0e\x65stimated_cost\x18\x05 \x01(\x01\"t\n\x13PolicyCheckResponse\x12/\n\x08\x64\x65\x63ision\x18\x01 \x01(\x0e\x32\x1d.cortex.agent.v1.DecisionType\x12\x0e\n\x06reason\x18\x02 \x01(\t\x12\x1c\n\x14redacted_context_ptr\x18\x03 \x01(\t*\x9b\x01\n\x0c\x44\x65\x63isionType\x12\x1d\n\x19\x44\x45\x43ISION_TYPE_UNSPECIFIED\x10\x00\x12\x17\n\x13\x44\x45\x43ISION_TYPE_ALLOW\x10\x01\x12\x16\n\x12\x44\x45\x43ISION_TYPE_DENY\x10\x02\x12\x1f\n\x1b\x44\x45\x43ISION_TYPE_REQUIRE_HUMAN\x10\x03\x12\x1a\n\x16\x44\x45\x43ISION_TYPE_THROTTLE\x10\x04\x32\x62\n\x0cSafetyKernel\x12R\n\x05\x43heck\x12#.cortex.agent.v1.PolicyCheckRequest\x1a$.cortex.agent.v1.PolicyCheckResponseB\x7f\n\x16\x61i.cortex.cap.agent.v1P\x01Z+github.com/coretexos/cap/go/cortex/agent/v1\xaa\x02\x0f\x43ortex.Agent.V1\xca\x02\x0f\x43ortex\\Agent\\V1\xea\x02\x11\x43ortex::Agent::V1b\x06proto3')
29
-
30
- _globals = globals()
31
- _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
32
- _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cortex.agent.v1.safety_pb2', _globals)
33
- if not _descriptor._USE_C_DESCRIPTORS:
34
- _globals['DESCRIPTOR']._loaded_options = None
35
- _globals['DESCRIPTOR']._serialized_options = b'\n\026ai.cortex.cap.agent.v1P\001Z+github.com/coretexos/cap/go/cortex/agent/v1\252\002\017Cortex.Agent.V1\312\002\017Cortex\\Agent\\V1\352\002\021Cortex::Agent::V1'
36
- _globals['_DECISIONTYPE']._serialized_start=337
37
- _globals['_DECISIONTYPE']._serialized_end=492
38
- _globals['_POLICYCHECKREQUEST']._serialized_start=77
39
- _globals['_POLICYCHECKREQUEST']._serialized_end=216
40
- _globals['_POLICYCHECKRESPONSE']._serialized_start=218
41
- _globals['_POLICYCHECKRESPONSE']._serialized_end=334
42
- _globals['_SAFETYKERNEL']._serialized_start=494
43
- _globals['_SAFETYKERNEL']._serialized_end=592
44
- # @@protoc_insertion_point(module_scope)
@@ -1,45 +0,0 @@
1
- import asyncio
2
- from typing import Callable, Awaitable
3
-
4
- from google.protobuf import timestamp_pb2
5
- from cap.pb.cortex.agent.v1 import buspacket_pb2, job_pb2
6
-
7
- DEFAULT_PROTOCOL_VERSION = 1
8
- SUBJECT_RESULT = "sys.job.result"
9
-
10
-
11
- async def run_worker(nats_url: str, subject: str, handler: Callable[[job_pb2.JobRequest], Awaitable[job_pb2.JobResult]]):
12
- import nats
13
-
14
- nc = await nats.connect(servers=nats_url, name="cap-worker")
15
-
16
- async def on_msg(msg):
17
- packet = buspacket_pb2.BusPacket()
18
- packet.ParseFromString(msg.data)
19
- req = packet.job_request
20
- if not req.job_id:
21
- return
22
- try:
23
- res = await handler(req)
24
- except Exception as exc: # noqa: BLE001
25
- res = job_pb2.JobResult(
26
- job_id=req.job_id,
27
- status=job_pb2.JOB_STATUS_FAILED,
28
- error_message=str(exc),
29
- )
30
- ts = timestamp_pb2.Timestamp()
31
- ts.GetCurrentTime()
32
- out = buspacket_pb2.BusPacket()
33
- out.trace_id = packet.trace_id
34
- out.sender_id = "cap-worker"
35
- out.protocol_version = DEFAULT_PROTOCOL_VERSION
36
- out.created_at.CopyFrom(ts)
37
- out.job_result.CopyFrom(res)
38
- await nc.publish(SUBJECT_RESULT, out.SerializeToString())
39
-
40
- await nc.subscribe(subject, queue=subject, cb=on_msg)
41
- try:
42
- while True:
43
- await asyncio.sleep(1)
44
- finally:
45
- await nc.drain()
File without changes