Skip to content

Commit 2b15087

Browse files
committed
Update to new SDK version (v2.1.x)
1 parent 735980c commit 2b15087

13 files changed

Lines changed: 1582 additions & 376 deletions

File tree

intent_bus/__init__.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
1-
from .client.sync import IntentClient, ClaimResponse
2-
from .worker.runtime import WorkerRuntime
3-
from .models.intent import ClaimedIntent, IntentStatus, IntentResult
4-
from .exceptions import IntentBusError
1+
from .client.sync import ClaimResponse, IntentClient
2+
from .exceptions import (
3+
IntentBusAuthError,
4+
IntentBusError,
5+
IntentBusLeaseLostError,
6+
IntentBusNetworkError,
7+
IntentBusProtocolError,
8+
IntentBusRateLimitError,
9+
)
10+
from .models.intent import ClaimedIntent, IntentBase, IntentResult, IntentStatus
511
from .version import __version__
12+
from .worker.runtime import WorkerRuntime
613

714
__all__ = [
815
"IntentClient",
916
"ClaimResponse",
1017
"WorkerRuntime",
18+
"IntentBase",
1119
"ClaimedIntent",
1220
"IntentStatus",
1321
"IntentResult",
1422
"IntentBusError",
23+
"IntentBusAuthError",
24+
"IntentBusProtocolError",
25+
"IntentBusRateLimitError",
26+
"IntentBusNetworkError",
27+
"IntentBusLeaseLostError",
1528
"__version__",
1629
]

intent_bus/cli.py

Lines changed: 114 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,77 @@
11
import argparse
22
import json
3+
import logging
34
import sys
45
import time
5-
import logging
66
from typing import Any
77

8-
from intent_bus import IntentClient, WorkerRuntime, IntentBusError
8+
from intent_bus import IntentBusError, IntentClient, WorkerRuntime
9+
from intent_bus.exceptions import IntentBusLeaseLostError
10+
911

1012
def log(level: str, event: str, message: str = '', silent: bool = False):
11-
if silent and level.lower() not in ('error', 'crit'):
13+
if silent and level.lower() not in ('error', 'crit', 'critical'):
1214
return
1315
ts = time.strftime('%H:%M:%S')
1416
print(f'[{ts}] {level.upper():<5} | {event:<12} | {message}')
1517

18+
19+
def _redact_payload(payload: Any) -> Any:
20+
"""
21+
Mask sensitive fields to prevent accidental credential leaks in CI/CD logs.
22+
23+
Redacts common secret-like keys while avoiding overly broad matches
24+
such as 'monkey' or 'keypad'.
25+
"""
26+
sensitive_keys = {
27+
'secret',
28+
'token',
29+
'api_key',
30+
'apikey',
31+
'password',
32+
'passwd',
33+
'auth',
34+
'credential',
35+
'credentials',
36+
'private_key',
37+
}
38+
39+
def is_sensitive_key(key: Any) -> bool:
40+
k = str(key).strip().lower()
41+
return (
42+
k in sensitive_keys
43+
or k.endswith('_secret')
44+
or k.endswith('_token')
45+
or k.endswith('_key')
46+
or k.endswith('_password')
47+
or k.endswith('_passwd')
48+
or k.endswith('_auth')
49+
or k.endswith('_credential')
50+
or k.endswith('_credentials')
51+
or k.endswith('_private_key')
52+
)
53+
54+
if isinstance(payload, dict):
55+
redacted = {}
56+
for k, v in payload.items():
57+
if is_sensitive_key(k):
58+
redacted[k] = '********'
59+
else:
60+
redacted[k] = _redact_payload(v)
61+
return redacted
62+
63+
if isinstance(payload, list):
64+
return [_redact_payload(item) for item in payload]
65+
66+
return payload
67+
68+
1669
def main():
1770
parser = argparse.ArgumentParser(
18-
prog='intent-bus',
71+
prog='intent-bus',
1972
formatter_class=argparse.RawDescriptionHelpFormatter,
2073
description=(
21-
"Intent Bus CLI | Protocol v2.0\n"
74+
"Intent Bus CLI | Protocol v2.1\n"
2275
"A lightweight, distributed job bus toolkit."
2376
),
2477
epilog=(
@@ -28,44 +81,49 @@ def main():
2881
"\n"
2982
"environment variables:\n"
3083
" INTENT_API_KEY Your API key (or place it in ~/.apikey)\n"
31-
)
84+
),
3285
)
33-
86+
3487
base_parser = argparse.ArgumentParser(add_help=False)
35-
base_parser.add_argument('-s', '--silent', action='store_true', help='Suppress non-critical logs, payloads, and network warnings')
88+
base_parser.add_argument(
89+
'-s', '--silent',
90+
action='store_true',
91+
help='Suppress non-critical logs, payloads, and network warnings',
92+
)
3693

3794
subparsers = parser.add_subparsers(dest='command', title='commands')
3895

3996
listen_parser = subparsers.add_parser(
40-
'listen',
97+
'listen',
4198
parents=[base_parser],
4299
help='Start a worker node loop to claim and process intents',
43100
formatter_class=argparse.RawDescriptionHelpFormatter,
44101
description="Start a continuous worker polling loop. Honors server-directed backoffs.",
45-
epilog="example:\n intent-bus listen encode_video -n media -c ffmpeg,gpu --interval 2.5"
102+
epilog="example:\n intent-bus listen encode_video -n media -c ffmpeg,gpu --interval 2.5",
46103
)
47104
listen_parser.add_argument('goal', help='The specific intent goal to claim (e.g., process_image)')
48105
listen_parser.add_argument('-n', '--namespace', default='default', help='Routing namespace (default: default)')
49106
listen_parser.add_argument('-w', '--worker-id', help='Explicit worker identity for tracking')
50107
listen_parser.add_argument('-c', '--capabilities', help='Comma-separated worker capabilities (e.g., gpu,ocr)')
51108
listen_parser.add_argument('--once', action='store_true', help='Process exactly one job and exit immediately')
52109
listen_parser.add_argument('--interval', type=float, default=5.0, help='Base polling interval in seconds (default: 5.0)')
110+
listen_parser.add_argument('--show-payload', action='store_true', help='Print full, unredacted JSON payloads (WARNING: may leak secrets)')
53111

54112
pub_parser = subparsers.add_parser(
55-
'publish',
113+
'publish',
56114
parents=[base_parser],
57115
help='Publish a new intent to the bus',
58116
formatter_class=argparse.RawDescriptionHelpFormatter,
59117
description="Push a new JSON payload to the bus for workers to claim.",
60-
epilog="example:\n intent-bus publish send_email '{\"to\": \"user@ext.com\"}' -n comms"
118+
epilog="example:\n intent-bus publish send_email '{\"to\": \"user@ext.com\"}' -n comms",
61119
)
62120
pub_parser.add_argument('goal', help='The goal/target for this intent')
63121
pub_parser.add_argument('data', help='Strict JSON payload string (must use double quotes for keys)')
64122
pub_parser.add_argument('-n', '--namespace', default='default', help='Routing namespace (default: default)')
65123
pub_parser.add_argument('-p', '--public', action='store_true', help='Make intent visible across all namespaces')
66124

67125
args = parser.parse_args()
68-
126+
69127
if not args.command:
70128
parser.print_help()
71129
sys.exit(0)
@@ -75,9 +133,9 @@ def main():
75133
logging.basicConfig(
76134
level=logging.ERROR if is_silent else logging.INFO,
77135
format='[%(asctime)s] %(levelname)-5s | %(name)s | %(message)s',
78-
datefmt='%H:%M:%S'
136+
datefmt='%H:%M:%S',
79137
)
80-
138+
81139
if is_silent:
82140
logging.disable(logging.WARNING)
83141

@@ -87,63 +145,87 @@ def main():
87145
log('crit', 'auth_fail', str(e), is_silent)
88146
sys.exit(1)
89147

90-
caps = ([c.strip() for c in args.capabilities.split(',') if c.strip()] if args.capabilities else None)
148+
caps = (
149+
[c.strip() for c in args.capabilities.split(',') if c.strip()]
150+
if getattr(args, 'capabilities', None)
151+
else None
152+
)
91153

92154
if args.command == 'listen':
93155
def process_job(payload: Any) -> dict:
94156
if not is_silent:
95157
border = '─' * 40
96158
print(f'\n{border}')
97-
print(json.dumps(payload, indent=2, ensure_ascii=False, default=str))
159+
display_payload = payload if getattr(args, 'show_payload', False) else _redact_payload(payload)
160+
print(json.dumps(display_payload, indent=2, ensure_ascii=False, default=str))
98161
print(f'{border}\n')
99162
return {'status': 'processed_by_cli'}
100163

101164
if args.once:
102165
job = client.claim(
103-
goal=args.goal,
104-
namespace=args.namespace,
105-
worker_id=args.worker_id,
106-
capabilities=caps
166+
goal=args.goal,
167+
namespace=args.namespace,
168+
worker_id=args.worker_id,
169+
capabilities=caps,
107170
)
108-
171+
109172
if job:
110173
log('info', 'claiming', f'ID: {job.id}', is_silent)
111174
try:
112175
res = process_job(job.payload)
113-
client.fulfill(job.id, result=res)
176+
client.fulfill(
177+
intent_id=job.id,
178+
claim_token=job.claim_token,
179+
result=res,
180+
)
114181
log('info', 'fulfilled', f'ID: {job.id}', is_silent)
182+
183+
except IntentBusLeaseLostError as e:
184+
log('error', 'lease_lost', str(e), is_silent)
185+
115186
except Exception as e:
116187
log('error', 'handler_err', str(e), is_silent)
117-
client.fail(job.id, error=str(e))
188+
try:
189+
client.fail(
190+
intent_id=job.id,
191+
claim_token=job.claim_token,
192+
error=str(e),
193+
)
194+
except IntentBusLeaseLostError as fail_err:
195+
log('error', 'lease_lost', str(fail_err), is_silent)
196+
except Exception as fail_err:
197+
log('error', 'fail_err', str(fail_err), is_silent)
198+
118199
else:
119200
log('info', 'idle', f'No jobs found for {args.goal}.', is_silent)
201+
120202
return
121203

122204
runtime = WorkerRuntime(
123205
client=client,
124206
worker_id=args.worker_id,
125207
capabilities=caps,
126208
poll_interval=args.interval,
127-
close_client=True
209+
close_client=True,
128210
)
129211

130212
log('info', 'worker_up', f'Target: {args.namespace}/{args.goal}', is_silent)
131213
runtime.listen(
132214
goal=args.goal,
133215
handler=process_job,
134-
namespace=args.namespace
216+
namespace=args.namespace,
135217
)
136218

137219
elif args.command == 'publish':
138220
try:
139221
payload = json.loads(args.data)
140222
res = client.publish(
141-
goal=args.goal,
142-
payload=payload,
143-
namespace=args.namespace,
144-
visibility='public' if args.public else 'private'
223+
goal=args.goal,
224+
payload=payload,
225+
namespace=args.namespace,
226+
visibility='public' if args.public else 'private',
145227
)
146-
if res:
228+
if res:
147229
log('info', 'pub_done', f'ID: {res.id}', is_silent)
148230
else:
149231
log('error', 'pub_fail', 'Server rejected the intent.', is_silent)
@@ -152,5 +234,6 @@ def process_job(payload: Any) -> dict:
152234
except IntentBusError as e:
153235
log('error', 'pub_err', str(e), is_silent)
154236

237+
155238
if __name__ == '__main__':
156239
main()

intent_bus/client/__init__.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
'''Internal Client API for Intent Bus.'''
22

3+
from .auth import resolve_api_key
4+
from .sync import ClaimResponse, IntentClient
35
from .transport import IntentTransport
4-
from .auth import (
5-
resolve_api_key,
6-
canonical_body,
7-
generate_signature,
8-
)
9-
from .sync import ClaimResponse
106

117
__all__ = [
8+
'IntentClient',
129
'IntentTransport',
1310
'resolve_api_key',
14-
'canonical_body',
15-
'generate_signature',
1611
'ClaimResponse',
1712
]

0 commit comments

Comments
 (0)