sfdc-agentforce-byoc-python-sdk 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.
Files changed (39) hide show
  1. agentforce_byoc/__init__.py +26 -0
  2. agentforce_byoc/_http.py +142 -0
  3. agentforce_byoc/cli/__init__.py +8 -0
  4. agentforce_byoc/cli/commands.py +913 -0
  5. agentforce_byoc/cli/main.py +44 -0
  6. agentforce_byoc/client.py +224 -0
  7. agentforce_byoc/deploy_client.py +246 -0
  8. agentforce_byoc/gateway/__init__.py +26 -0
  9. agentforce_byoc/gateway/base.py +66 -0
  10. agentforce_byoc/gateway/mock_relay_client.py +54 -0
  11. agentforce_byoc/gateway/relay_client.py +394 -0
  12. agentforce_byoc/logging/__init__.py +20 -0
  13. agentforce_byoc/logging/formatter.py +96 -0
  14. agentforce_byoc/logging/logger.py +59 -0
  15. agentforce_byoc/runtime/__init__.py +21 -0
  16. agentforce_byoc/runtime/agentforce_runtime.py +47 -0
  17. agentforce_byoc/runtime/base.py +54 -0
  18. agentforce_byoc/runtime/mock_runtime.py +50 -0
  19. agentforce_byoc/schema_utils/__init__.py +35 -0
  20. agentforce_byoc/schema_utils/entry_func.py +48 -0
  21. agentforce_byoc/schema_utils/generator.py +694 -0
  22. agentforce_byoc/schema_utils/schema.py +79 -0
  23. agentforce_byoc/templates/__init__.py +4 -0
  24. agentforce_byoc/templates/agent/.dockerignore.template +51 -0
  25. agentforce_byoc/templates/agent/.gitignore.template +64 -0
  26. agentforce_byoc/templates/agent/.python-version.template +1 -0
  27. agentforce_byoc/templates/agent/Dockerfile.template +41 -0
  28. agentforce_byoc/templates/agent/Makefile.template +51 -0
  29. agentforce_byoc/templates/agent/README.md.template +358 -0
  30. agentforce_byoc/templates/agent/config.json.template +3 -0
  31. agentforce_byoc/templates/agent/entry_func.py.template +51 -0
  32. agentforce_byoc/templates/agent/main.py.template +149 -0
  33. agentforce_byoc/templates/agent/pyproject.toml.template +24 -0
  34. agentforce_byoc/templates/agent/schema.py.template +73 -0
  35. sfdc_agentforce_byoc_python_sdk-0.1.0.dist-info/METADATA +439 -0
  36. sfdc_agentforce_byoc_python_sdk-0.1.0.dist-info/RECORD +39 -0
  37. sfdc_agentforce_byoc_python_sdk-0.1.0.dist-info/WHEEL +4 -0
  38. sfdc_agentforce_byoc_python_sdk-0.1.0.dist-info/entry_points.txt +3 -0
  39. sfdc_agentforce_byoc_python_sdk-0.1.0.dist-info/licenses/LICENSE +206 -0
@@ -0,0 +1,26 @@
1
+ # Copyright (c) 2026, Salesforce, Inc.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """Agentforce BYOC Python SDK - Functional API."""
17
+
18
+ from agentforce_byoc.client import get_client
19
+ from agentforce_byoc.logging import get_logger
20
+
21
+ __version__ = "0.1.0" # replaced at build time by poetry-dynamic-versioning
22
+
23
+ __all__ = [
24
+ "get_client",
25
+ "get_logger",
26
+ ]
@@ -0,0 +1,142 @@
1
+ # Copyright (c) 2026, Salesforce, Inc.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """Internal HTTP helpers (stdlib only).
17
+
18
+ Small wrapper over ``http.client`` so the relay and deploy clients share one
19
+ request path with no third-party dependencies (nothing extra gets bundled into
20
+ a BYOC package). It sends outgoing header names verbatim.
21
+
22
+ Set ``AGENTFORCE_BYOC_DEBUG=1`` to log the outgoing request (method, URL,
23
+ headers, and body) and the response. The ``Authorization`` bearer token is
24
+ truncated in the log, so debug output never contains the full credential. This
25
+ is invaluable when a call fails in a way that looks like a client bug: e.g. a
26
+ 400 "x-sfdc-core-tenant-id header is required" is usually returned by the
27
+ platform when it cannot resolve the tenant from the OrgJWT (expired/unauthorized
28
+ token) -- not because the SDK omitted the header. The logged request headers
29
+ show exactly which headers left the machine (with the token redacted).
30
+ """
31
+
32
+ import http.client
33
+ import json
34
+ import os
35
+ import urllib.parse
36
+ from typing import Any, Dict, Optional, Tuple
37
+
38
+ from agentforce_byoc.logging import get_logger
39
+
40
+ logger = get_logger(__name__)
41
+
42
+ # Default timeout (seconds) for SDK HTTP calls.
43
+ DEFAULT_TIMEOUT_S = 120
44
+
45
+
46
+ def _debug_enabled() -> bool:
47
+ return os.getenv("AGENTFORCE_BYOC_DEBUG", "").strip().lower() in ("1", "true", "yes")
48
+
49
+
50
+ def _redact_headers(headers: Dict[str, str]) -> Dict[str, str]:
51
+ """Copy headers with the bearer token truncated for safe logging."""
52
+ redacted = {}
53
+ for k, v in headers.items():
54
+ if k.lower() == "authorization" and isinstance(v, str):
55
+ # Keep the scheme + first/last few chars so we can spot a bad token.
56
+ redacted[k] = (v[:18] + "...(" + str(len(v)) + " chars)") if len(v) > 24 else v
57
+ else:
58
+ redacted[k] = v
59
+ return redacted
60
+
61
+
62
+ def _connect(parsed: urllib.parse.ParseResult, timeout: int):
63
+ """Open an HTTP(S) connection for a parsed URL."""
64
+ host = parsed.hostname
65
+ if host is None:
66
+ raise ValueError(f"URL has no host: {parsed.geturl()!r}")
67
+ port = parsed.port
68
+ if parsed.scheme == "https":
69
+ return http.client.HTTPSConnection(host, port, timeout=timeout)
70
+ return http.client.HTTPConnection(host, port, timeout=timeout)
71
+
72
+
73
+ def _path_with_query(parsed: urllib.parse.ParseResult) -> str:
74
+ path = parsed.path or "/"
75
+ if parsed.query:
76
+ path = f"{path}?{parsed.query}"
77
+ return path
78
+
79
+
80
+ def request(
81
+ method: str,
82
+ url: str,
83
+ *,
84
+ headers: Dict[str, str],
85
+ body: Optional[bytes] = None,
86
+ timeout: int = DEFAULT_TIMEOUT_S,
87
+ ) -> Tuple[int, bytes]:
88
+ """
89
+ Send an HTTP request and return the status and raw response body.
90
+
91
+ Returns:
92
+ (status_code, response_body_bytes)
93
+
94
+ Raises:
95
+ OSError: On connection/transport failure (caller maps to its own error).
96
+ """
97
+ parsed = urllib.parse.urlparse(url)
98
+ debug = _debug_enabled()
99
+
100
+ if debug:
101
+ logger.info("[debug] HTTP %s %s", method, url)
102
+ logger.info(
103
+ "[debug] host=%s port=%s scheme=%s", parsed.hostname, parsed.port, parsed.scheme
104
+ )
105
+ logger.info("[debug] request path=%s", _path_with_query(parsed))
106
+ logger.info("[debug] request headers=%s", _redact_headers(headers))
107
+ if body is not None:
108
+ preview = body.decode("utf-8", errors="replace")
109
+ logger.info("[debug] request body=%s", preview)
110
+
111
+ conn = _connect(parsed, timeout)
112
+ try:
113
+ conn.request(method, _path_with_query(parsed), body=body, headers=headers)
114
+ resp = conn.getresponse()
115
+ status = resp.status
116
+ raw = resp.read()
117
+ if debug:
118
+ logger.info("[debug] response status=%s", status)
119
+ logger.info("[debug] response headers=%s", dict(resp.getheaders()))
120
+ logger.info("[debug] response body=%s", raw.decode("utf-8", errors="replace"))
121
+ return status, raw
122
+ finally:
123
+ conn.close()
124
+
125
+
126
+ def post_json(
127
+ url: str,
128
+ body: Dict[str, Any],
129
+ headers: Dict[str, str],
130
+ *,
131
+ timeout: int = DEFAULT_TIMEOUT_S,
132
+ ) -> Tuple[int, str]:
133
+ """
134
+ POST a JSON body. Sets Content-Type and serializes ``body``.
135
+
136
+ Returns:
137
+ (status_code, response_text)
138
+ """
139
+ merged = {"Content-Type": "application/json", **headers}
140
+ data = json.dumps(body).encode("utf-8")
141
+ status, raw = request("POST", url, headers=merged, body=data, timeout=timeout)
142
+ return status, raw.decode("utf-8", errors="replace")
@@ -0,0 +1,8 @@
1
+ # Copyright (c) 2026, Salesforce, Inc.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """CLI for Agentforce BYOC SDK."""
5
+
6
+ from agentforce_byoc.cli.main import cli
7
+
8
+ __all__ = ["cli"]