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,79 @@
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
+ from __future__ import annotations
17
+
18
+ from typing import Any, Callable, Type, TypeVar
19
+
20
+ F = TypeVar("F", bound=Callable[..., Any])
21
+
22
+
23
+ def requestSchema(cls: Type[Any]) -> Callable[[F], F]:
24
+ """Decorator that declares the request body schema for an ``@entry_func`` function.
25
+
26
+ The *cls* argument must be a class with typed fields (e.g. a
27
+ ``TypedDict`` subclass). At runtime the decorator is a pass-through —
28
+ it returns the original function unchanged. The schema generator
29
+ (:mod:`agentforce_byoc.schema_utils.generator`) reads the decorator at
30
+ the AST level to produce OpenAPI ``properties``.
31
+
32
+ Example::
33
+
34
+ from typing import TypedDict
35
+ from agentforce_byoc.schema_utils import requestSchema
36
+
37
+ class AddRequest(TypedDict):
38
+ a: int
39
+ b: int
40
+
41
+ @entry_func
42
+ @requestSchema(AddRequest)
43
+ def add(request: dict) -> dict:
44
+ ...
45
+ """
46
+
47
+ def decorator(fn: F) -> F:
48
+ return fn
49
+
50
+ return decorator
51
+
52
+
53
+ def responseSchema(cls: Type[Any]) -> Callable[[F], F]:
54
+ """Decorator that declares the response body schema for an ``@entry_func`` function.
55
+
56
+ The *cls* argument must be a class with typed fields (e.g. a
57
+ ``TypedDict`` subclass). At runtime the decorator is a pass-through —
58
+ it returns the original function unchanged. The schema generator
59
+ (:mod:`agentforce_byoc.schema_utils.generator`) reads the decorator at
60
+ the AST level to produce OpenAPI ``properties``.
61
+
62
+ Example::
63
+
64
+ from typing import TypedDict
65
+ from agentforce_byoc.schema_utils import responseSchema
66
+
67
+ class AddResponse(TypedDict):
68
+ result: int
69
+
70
+ @entry_func
71
+ @responseSchema(AddResponse)
72
+ def add(request: dict) -> dict:
73
+ ...
74
+ """
75
+
76
+ def decorator(fn: F) -> F:
77
+ return fn
78
+
79
+ return decorator
@@ -0,0 +1,4 @@
1
+ # Copyright (c) 2026, Salesforce, Inc.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Project templates for agentforce init command."""
@@ -0,0 +1,51 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ *.egg-info/
8
+ dist/
9
+ build/
10
+
11
+ # Virtual environments
12
+ .venv/
13
+ venv/
14
+ ENV/
15
+ env/
16
+
17
+ # IDE
18
+ .vscode/
19
+ .idea/
20
+ *.swp
21
+ *.swo
22
+ *~
23
+
24
+ # Testing
25
+ .pytest_cache/
26
+ .coverage
27
+ htmlcov/
28
+ .tox/
29
+
30
+ # Git
31
+ .git/
32
+ .gitignore
33
+
34
+ # Documentation
35
+ *.md
36
+ docs/
37
+
38
+ # CI/CD
39
+ .github/
40
+ .gitlab-ci.yml
41
+
42
+ # Logs
43
+ *.log
44
+
45
+ # OS
46
+ .DS_Store
47
+ Thumbs.db
48
+
49
+ # Temporary files
50
+ *.tmp
51
+ *.bak
@@ -0,0 +1,64 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtual environments
24
+ .venv/
25
+ venv/
26
+ ENV/
27
+ env/
28
+
29
+ # PyCharm
30
+ .idea/
31
+
32
+ # VS Code
33
+ .vscode/
34
+
35
+ # Testing
36
+ .pytest_cache/
37
+ .coverage
38
+ .coverage.*
39
+ htmlcov/
40
+ .tox/
41
+ .nox/
42
+
43
+ # Jupyter Notebook
44
+ .ipynb_checkpoints
45
+
46
+ # pyenv
47
+ .python-version
48
+
49
+ # Poetry
50
+ poetry.lock
51
+
52
+ # Logs
53
+ *.log
54
+
55
+ # OS
56
+ .DS_Store
57
+ Thumbs.db
58
+
59
+ # Environment variables
60
+ .env
61
+ .env.local
62
+
63
+ # Deployment artifacts
64
+ *.zip
@@ -0,0 +1,41 @@
1
+ # Agentforce BYOC Function - {{project_name}}
2
+ # Python 3.11 runtime
3
+
4
+ FROM python:3.11-slim
5
+
6
+ # Set working directory
7
+ WORKDIR /app
8
+
9
+ # Install system dependencies
10
+ RUN apt-get update && apt-get install -y \
11
+ gcc \
12
+ && rm -rf /var/lib/apt/lists/*
13
+
14
+ # Copy dependency files
15
+ COPY pyproject.toml poetry.lock* ./
16
+
17
+ # Install Poetry
18
+ RUN pip install --no-cache-dir poetry==1.7.1
19
+
20
+ # Configure Poetry to not create virtual environment
21
+ RUN poetry config virtualenvs.create false
22
+
23
+ # Install dependencies
24
+ RUN poetry install --no-dev --no-interaction --no-ansi
25
+
26
+ # Copy application code
27
+ COPY main.py ./
28
+ COPY entry_func.py ./
29
+ COPY schema.py ./
30
+ COPY config.json ./
31
+
32
+ # Set environment variables
33
+ ENV PYTHONUNBUFFERED=1
34
+ ENV FUNCTION_ENTRY_POINT=main:function
35
+
36
+ # Health check (optional)
37
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
38
+ CMD python -c "import main; print('OK')" || exit 1
39
+
40
+ # Run the function (this will be overridden by the BYOC runtime)
41
+ CMD ["python", "-c", "from main import function; import json; result = function({}); print(json.dumps(result))"]
@@ -0,0 +1,51 @@
1
+ # Makefile for {{project_name}}
2
+
3
+ .PHONY: help
4
+ help: ## Show this help message
5
+ @echo "{{project_name}} - Agentforce BYOC Agent"
6
+ @echo ""
7
+ @echo "Available targets:"
8
+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %s\n", $$1, $$2}'
9
+
10
+ .PHONY: install
11
+ install: ## Install dependencies with Poetry
12
+ poetry install
13
+
14
+ .PHONY: run
15
+ run: ## Run the agent locally (requires ORG_JWT_TOKEN + SFDC_CORE_TENANT_ID + SFDC_USER_ID + SFDC_APP_CONTEXT env vars)
16
+ @echo "Make sure ORG_JWT_TOKEN, SFDC_CORE_TENANT_ID, SFDC_USER_ID, and SFDC_APP_CONTEXT are set:"
17
+ @echo " export ORG_JWT_TOKEN=<org-jwt>"
18
+ @echo " export SFDC_CORE_TENANT_ID=core/<instance>-<fd>/<org-id>"
19
+ @echo " export SFDC_USER_ID=<user-id>"
20
+ @echo " export SFDC_APP_CONTEXT=<app-context>"
21
+ @echo ""
22
+ poetry run agentforce run main.py
23
+
24
+ .PHONY: test
25
+ test: ## Run tests
26
+ poetry run pytest
27
+
28
+ .PHONY: format
29
+ format: ## Format code with black
30
+ poetry run black .
31
+
32
+ .PHONY: validate
33
+ validate: ## Validate agent package
34
+ poetry run agentforce validate .
35
+
36
+ .PHONY: zip
37
+ zip: ## Create a deployment-ready package archive
38
+ poetry run agentforce zip --package-dir .
39
+
40
+ .PHONY: package
41
+ package: ## Build end-to-end: validate -> run -> zip
42
+ poetry run agentforce package .
43
+
44
+ .PHONY: deploy
45
+ deploy: ## Register + upload to Agentforce BYOC, then poll status (requires ORG_JWT_TOKEN + SFDC_CORE_TENANT_ID + SFDC_USER_ID + SFDC_APP_CONTEXT env vars)
46
+ poetry run agentforce deploy --package-name "{{project_name}}" --package-file {{project_name}}-0.1.0.tar.gz
47
+ poetry run agentforce deploy-status --package-name "{{project_name}}" --version 1.0
48
+
49
+ .PHONY: clean
50
+ clean: ## Clean build artifacts
51
+ rm -rf dist/ build/ *.egg-info .pytest_cache __pycache__
@@ -0,0 +1,358 @@
1
+ # {{project_name}}
2
+
3
+ Agentforce BYOC Function created with the Agentforce SDK.
4
+
5
+ ## Overview
6
+
7
+ This is a BYOC (Bring Your Own Code) function that can be deployed to Salesforce and executed within Agentforce workflows. Functions are defined using decorators for schema validation and can be tested locally or deployed to Salesforce.
8
+
9
+ ## Project Structure
10
+
11
+ ```
12
+ {{project_name}}/
13
+ ├── main.py # Your function implementation with decorators
14
+ ├── entry_func.py # Schema decorator utilities (entry point)
15
+ ├── schema.py # Schema decorators (request/response)
16
+ ├── agentforce_byoc/ # Vendored SDK (until it's published to PyPI)
17
+ ├── config.json # Function configuration (entryPoint)
18
+ ├── Dockerfile # Container definition for deployment
19
+ ├── pyproject.toml # Python dependencies (Poetry)
20
+ ├── .python-version # Python 3.11
21
+ ├── .gitignore # Git ignore patterns
22
+ ├── .dockerignore # Docker ignore patterns
23
+ ├── Makefile # Development tasks
24
+ └── README.md # This file
25
+ ```
26
+
27
+ > `agentforce_byoc/` is a copy of the SDK runtime, vendored by `agentforce init`
28
+ > because the SDK isn't on PyPI yet. It's what lets your deployed package
29
+ > `import agentforce_byoc`. When the SDK is published, this directory goes away
30
+ > and it becomes a normal dependency.
31
+
32
+ ## Quick Start
33
+
34
+ ### 1. Install Dependencies
35
+
36
+ ```bash
37
+ poetry install
38
+ ```
39
+
40
+ ### 2. Test Locally
41
+
42
+ Set your credentials, then run the agent against the Agentforce BYOC runtime:
43
+
44
+ ```bash
45
+ export ORG_JWT_TOKEN=<org-jwt>
46
+ export SFDC_CORE_TENANT_ID=core/<instance>-<fd>/<org-id>
47
+ export SFDC_USER_ID=<user-id>
48
+ export SFDC_APP_CONTEXT=<app-context>
49
+ agentforce run main.py
50
+ ```
51
+
52
+ ### 3. Implement Your Function
53
+
54
+ Edit `main.py` and define your schemas:
55
+
56
+ ```python
57
+ # TODO: Define your function schemas here
58
+ class MyRequest(TypedDict):
59
+ input_text: str
60
+ max_length: int
61
+
62
+ class MyResponse(TypedDict):
63
+ output_text: str
64
+ status: str
65
+
66
+ @entry_func
67
+ @requestSchema(MyRequest)
68
+ @responseSchema(MyResponse)
69
+ def function(request: dict) -> dict:
70
+ # Your implementation
71
+ return {"output_text": "result", "status": "success"}
72
+ ```
73
+
74
+ ## Running Locally
75
+
76
+ Run against the Agentforce BYOC runtime. Set your credentials first (the
77
+ command aborts with instructions if they're unset):
78
+
79
+ ```bash
80
+ export ORG_JWT_TOKEN=<org-jwt>
81
+ export SFDC_CORE_TENANT_ID=core/<instance>-<fd>/<org-id>
82
+ export SFDC_USER_ID=<user-id>
83
+ export SFDC_APP_CONTEXT=<app-context>
84
+ agentforce run main.py
85
+ ```
86
+
87
+ `run` executes your function on this machine; relay calls go out to the real
88
+ Agentforce service. The tenant id, user id, and app context are read from the
89
+ environment (`SFDC_CORE_TENANT_ID` / `SFDC_USER_ID` / `SFDC_APP_CONTEXT`).
90
+
91
+ ---
92
+
93
+ ## Function Schema Definition
94
+
95
+ Your function uses decorators to define input/output schemas:
96
+
97
+ ### `@entry_func`
98
+ Marks the function as a BYOC entry point.
99
+
100
+ ### `@requestSchema(TypedDictClass)`
101
+ Declares the expected request structure.
102
+
103
+ ### `@responseSchema(TypedDictClass)`
104
+ Declares the expected response structure.
105
+
106
+ **Example:**
107
+ ```python
108
+ from typing import TypedDict, List
109
+
110
+ class MyRequest(TypedDict):
111
+ items: List[str]
112
+ separator: str
113
+
114
+ class MyResponse(TypedDict):
115
+ result: str
116
+ status: str
117
+
118
+ @entry_func
119
+ @requestSchema(MyRequest)
120
+ @responseSchema(MyResponse)
121
+ def function(request: dict) -> dict:
122
+ items = request.get("items", [])
123
+ separator = request.get("separator", " ")
124
+
125
+ return {
126
+ "result": separator.join(items),
127
+ "status": "success"
128
+ }
129
+ ```
130
+
131
+ ## Configuration
132
+
133
+ ### config.json
134
+
135
+ Simple configuration with function entry point:
136
+
137
+ ```json
138
+ {
139
+ "entryPoint": "main.py"
140
+ }
141
+ ```
142
+
143
+ This tells the runtime which file contains your function.
144
+
145
+ ## Adding Dependencies
146
+
147
+ ```bash
148
+ # Add a package
149
+ poetry add <package-name>
150
+
151
+ # Add a dev dependency
152
+ poetry add --dev <package-name>
153
+
154
+ # Update all dependencies
155
+ poetry update
156
+ ```
157
+
158
+ **Example:**
159
+ ```bash
160
+ poetry add requests pandas numpy
161
+ poetry add --dev pytest black mypy
162
+ ```
163
+
164
+ ## Building and Deploying
165
+
166
+ ### 1. Package Your Function
167
+
168
+ Create a deployment-ready gzip tar archive:
169
+
170
+ ```bash
171
+ agentforce zip .
172
+ ```
173
+
174
+ This creates `{{project_name}}-0.1.0.tar.gz` containing:
175
+ - `main.py` - Your function code
176
+ - `entry_func.py` - Schema decorators
177
+ - `schema.py` - Schema validation
178
+ - `config.json` - Configuration
179
+ - `Dockerfile` - Container definition
180
+ - `pyproject.toml` - Dependencies
181
+ - `requirements.txt` - Exported dependencies
182
+
183
+ You can also build end-to-end (validate → run → zip) in one step:
184
+
185
+ ```bash
186
+ agentforce package .
187
+ ```
188
+
189
+ ### 2. Deploy to Agentforce BYOC
190
+
191
+ Auth and identity come from environment variables (required — commands abort if unset):
192
+
193
+ ```bash
194
+ export ORG_JWT_TOKEN=<org-jwt>
195
+ export SFDC_CORE_TENANT_ID=core/<instance>-<fd>/<org-id>
196
+ export SFDC_USER_ID=<user-id>
197
+ export SFDC_APP_CONTEXT=<app-context>
198
+ ```
199
+
200
+ Then deploy in one step — `deploy` registers the metadata **and** uploads the
201
+ archive — and optionally poll the status:
202
+
203
+ ```bash
204
+ # Register + upload: registers metadata AND PUTs the package to the presigned URL
205
+ agentforce deploy --package-name {{project_name}} --package-file {{project_name}}-0.1.0.tar.gz
206
+
207
+ # (Optional) Poll status — read-only: reports registrationStatus + uploadStatus
208
+ agentforce deploy-status --package-name {{project_name}} --version 1.0
209
+ ```
210
+
211
+ **Environment variables (used by `deploy`, `deploy-status`, and `run` — all four required):**
212
+ - `ORG_JWT_TOKEN` - Org JWT for authentication (required)
213
+ - `SFDC_CORE_TENANT_ID` - core tenant id `core/<instance>-<fd>/<org-id>` (required)
214
+ - `SFDC_USER_ID` - caller's user id (sent as `x-sfdc-user-id`)
215
+ - `SFDC_APP_CONTEXT` - app context (sent as `x-sfdc-app-context`)
216
+
217
+ **`deploy` options:**
218
+ - `--package-name <name>` - Package name (required)
219
+ - `--package-file <path>` - Package archive from `agentforce zip` (required)
220
+ - `--entry-point <file>` - Function entry point (default: main.py)
221
+ - `--version <ver>` - Package version (default: 1.0)
222
+ - `--max-live-duration <seconds>` - Max live duration (default: 300)
223
+
224
+ **`deploy-status` options:** (read-only poll — never writes)
225
+ - `--package-name <name>` - Package name (required)
226
+ - `--version <ver>` - Package version (default: 1.0)
227
+
228
+ ## Development Workflow
229
+
230
+ ```bash
231
+ # 1. Set credentials (all four required for run, deploy, and deploy-status)
232
+ export ORG_JWT_TOKEN=<org-jwt>
233
+ export SFDC_CORE_TENANT_ID=core/<instance>-<fd>/<org-id>
234
+ export SFDC_USER_ID=<user-id>
235
+ export SFDC_APP_CONTEXT=<app-context>
236
+
237
+ # 2. Develop + test locally against the Agentforce BYOC runtime
238
+ agentforce run main.py
239
+
240
+ # 3. Package
241
+ agentforce zip . # Create deployment archive
242
+
243
+ # 4. Deploy: register + upload in one step, then optionally poll status
244
+ agentforce deploy --package-name {{project_name}} --package-file {{project_name}}-0.1.0.tar.gz
245
+ agentforce deploy-status --package-name {{project_name}} --version 1.0
246
+ ```
247
+
248
+ ## Testing
249
+
250
+ ### Unit Tests
251
+
252
+ Create a `tests/` directory:
253
+
254
+ ```bash
255
+ mkdir tests
256
+ poetry add --dev pytest
257
+ ```
258
+
259
+ **Example test (`tests/test_main.py`):**
260
+ ```python
261
+ from main import function
262
+
263
+ def test_function_missing_input():
264
+ # No text -> error, no relay call
265
+ response = function({})
266
+ assert response["status"] == "error"
267
+ ```
268
+
269
+ > The success path calls the relay (a network request needing a valid
270
+ > `ORG_JWT_TOKEN`), so mock the relay client or test it as an integration test
271
+ > rather than a plain unit test.
272
+
273
+ **Run tests:**
274
+ ```bash
275
+ poetry run pytest
276
+ poetry run pytest --cov=main
277
+ ```
278
+
279
+ ## Docker
280
+
281
+ Build and test locally with Docker:
282
+
283
+ ```bash
284
+ # Build container
285
+ docker build -t {{project_name}}:latest .
286
+
287
+ # Run container
288
+ docker run --rm {{project_name}}:latest
289
+
290
+ # Run with environment variables
291
+ docker run --rm \
292
+ -e ORG_JWT_TOKEN=<org-jwt> \
293
+ {{project_name}}:latest
294
+ ```
295
+
296
+ ## Troubleshooting
297
+
298
+ ### Function fails locally
299
+
300
+ 1. Check Python version: `python --version` (should be 3.11+)
301
+ 2. Install dependencies: `poetry install`
302
+ 3. Check imports in `main.py`
303
+ 4. Verify `ORG_JWT_TOKEN`, `SFDC_CORE_TENANT_ID`, `SFDC_USER_ID`, and `SFDC_APP_CONTEXT` are exported
304
+
305
+ ### Agentforce BYOC runtime fails
306
+
307
+ 1. Check `ORG_JWT_TOKEN` is set: `echo $ORG_JWT_TOKEN`
308
+ 2. Verify the JWT is valid and not expired
309
+ 3. Check `SFDC_CORE_TENANT_ID` is set and well-formed (core/<instance>-<fd>/<org-id>)
310
+ 4. Check `SFDC_USER_ID` is set (relay sends it as `x-sfdc-user-id`)
311
+ 5. Check `SFDC_APP_CONTEXT` is set (relay sends it as `x-sfdc-app-context`)
312
+
313
+ ### Deployment fails
314
+
315
+ 1. Verify `ORG_JWT_TOKEN`, `SFDC_CORE_TENANT_ID`, `SFDC_USER_ID`, and `SFDC_APP_CONTEXT` env vars are set (commands abort if unset)
316
+ 2. Check the JWT is valid and not expired
317
+ 3. Check package structure: `tar -tzf {{project_name}}-*.tar.gz`
318
+
319
+ ## CLI Command Reference
320
+
321
+ ```bash
322
+ # Initialize new function
323
+ agentforce init <name>
324
+
325
+ # Run locally (requires ORG_JWT_TOKEN + SFDC_CORE_TENANT_ID + SFDC_USER_ID + SFDC_APP_CONTEXT)
326
+ agentforce run main.py
327
+
328
+ # Package for deployment
329
+ agentforce zip .
330
+ agentforce zip . --output custom-name.tar.gz
331
+
332
+ # Build end-to-end (validate -> run -> zip)
333
+ agentforce package .
334
+
335
+ # Deploy to Agentforce BYOC (set ORG_JWT_TOKEN + SFDC_CORE_TENANT_ID + SFDC_USER_ID + SFDC_APP_CONTEXT env vars first)
336
+ agentforce deploy --package-name <name> --package-file <name>-0.1.0.tar.gz
337
+ agentforce deploy-status --package-name <name> --version 1.0
338
+
339
+ # Get help
340
+ agentforce --help
341
+ agentforce run --help
342
+ ```
343
+
344
+ ## Environment Variables
345
+
346
+ ### Agentforce BYOC Runtime
347
+ ```bash
348
+ ORG_JWT_TOKEN=<org-jwt> # Required
349
+ SFDC_CORE_TENANT_ID=core/<instance>-<fd>/<org-id> # Required
350
+ SFDC_USER_ID=<user-id> # Required by run + deploy
351
+ SFDC_APP_CONTEXT=<app-context> # Required by run + deploy
352
+ ```
353
+
354
+ ## Learn More
355
+
356
+ - [Agentforce BYOC SDK Documentation](https://github.com/salesforce/agentforce-byoc-python-sdk)
357
+ - [Poetry Documentation](https://python-poetry.org/docs/)
358
+ - [TypedDict Documentation](https://docs.python.org/3/library/typing.html#typing.TypedDict)
@@ -0,0 +1,3 @@
1
+ {
2
+ "entryPoint": "main.py"
3
+ }
@@ -0,0 +1,51 @@
1
+ # Copyright (c) 2025, Salesforce, Inc.
2
+ # SPDX-License-Identifier: Apache-2
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
+ from __future__ import annotations
17
+
18
+ import functools
19
+ from typing import (
20
+ Any,
21
+ Callable,
22
+ TypeVar,
23
+ )
24
+
25
+ F = TypeVar("F", bound=Callable[..., Any])
26
+
27
+
28
+ def entry_func(fn: F) -> F:
29
+ """Mark a function as the BYOC entry point.
30
+
31
+ The ``@entry_func`` decorator identifies a function whose signature
32
+ will be extracted by ``generate_byoc_schema.py`` to produce an
33
+ OpenAPI specification. At runtime the decorator is a pass-through —
34
+ it returns the original function unchanged.
35
+
36
+ All parameters of the decorated function **must** have type
37
+ annotations; the schema generator will reject untyped parameters.
38
+
39
+ Example::
40
+
41
+ from entry_func import entry_func
42
+
43
+ @entry_func
44
+ def add(a: int, b: int = 0) -> int:
45
+ return a + b
46
+ """
47
+ @functools.wraps(fn)
48
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
49
+ return fn(*args, **kwargs)
50
+
51
+ return wrapper # type: ignore[return-value]