fora-sdk 0.1.7__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.
fora_sdk/__init__.py ADDED
@@ -0,0 +1,35 @@
1
+ """Fora SDK — typed builder for voice-AI workflows.
2
+
3
+ Runtime SDK: fetches the spec catalog from the Fora backend at session
4
+ start and validates every `Workflow.add()` call against it. LLMs don't
5
+ need to import per-node-type classes — the `type` argument is a string
6
+ keyed against the fetched spec catalog.
7
+
8
+ from fora_sdk import ForaClient, Workflow
9
+
10
+ with ForaClient(base_url="http://localhost:8000", api_key=...) as client:
11
+ wf = Workflow(client=client, name="loan_qualification")
12
+ start = wf.add(type="startCall", name="greeting", prompt="...")
13
+ qualify = wf.add(type="agentNode", name="qualify", prompt="...")
14
+ wf.edge(start, qualify, label="interested", condition="...")
15
+ client.save_workflow(workflow_id=123, workflow=wf)
16
+
17
+ For typed IDE autocomplete, generate per-node dataclasses via the SDK
18
+ codegen (Phase 6) — the runtime and typed SDKs share this same core.
19
+ """
20
+
21
+ from .client import ForaClient
22
+ from .errors import ApiError, ForaSdkError, SpecMismatchError, ValidationError
23
+ from .typed._base import TypedNode
24
+ from .workflow import NodeRef, Workflow
25
+
26
+ __all__ = [
27
+ "ApiError",
28
+ "ForaClient",
29
+ "ForaSdkError",
30
+ "NodeRef",
31
+ "SpecMismatchError",
32
+ "TypedNode",
33
+ "ValidationError",
34
+ "Workflow",
35
+ ]
@@ -0,0 +1,114 @@
1
+ """GENERATED — do not edit. Source: filtered OpenAPI from `api.app`.
2
+
3
+ Regenerate with `./scripts/generate_sdk.sh`.
4
+
5
+ `ForaClient` mixes in this class to get HTTP methods for every route
6
+ decorated with `sdk_expose(...)` on the backend. Request/response types
7
+ come from `_generated_models` (datamodel-codegen output).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Any
13
+
14
+ from fora_sdk._generated_models import (
15
+ CreateToolRequest,
16
+ CreateWorkflowRequest,
17
+ CredentialResponse,
18
+ DocumentListResponseSchema,
19
+ InitiateCallRequest,
20
+ NodeSpec,
21
+ NodeTypesResponse,
22
+ RecordingListResponseSchema,
23
+ ToolResponse,
24
+ UpdateWorkflowRequest,
25
+ WorkflowListResponse,
26
+ WorkflowResponse,
27
+ )
28
+
29
+
30
+ class _GeneratedClient:
31
+ # `ForaClient.__init__` installs `self._request` (see client.py).
32
+
33
+ def create_tool(self, *, body: CreateToolRequest) -> ToolResponse:
34
+ """Create a reusable tool for the authenticated organization."""
35
+ data = self._request("POST", "/tools/", json=body.model_dump(mode="json", exclude_none=True))
36
+ return ToolResponse.model_validate(data)
37
+
38
+ def create_workflow(self, *, body: CreateWorkflowRequest) -> WorkflowResponse:
39
+ """Create a new workflow from a workflow definition."""
40
+ data = self._request("POST", "/workflow/create/definition", json=body.model_dump(mode="json", exclude_none=True))
41
+ return WorkflowResponse.model_validate(data)
42
+
43
+ def get_node_type(self, name: str) -> NodeSpec:
44
+ """Fetch a single node spec by name."""
45
+ data = self._request("GET", f"/node-types/{name}")
46
+ return NodeSpec.model_validate(data)
47
+
48
+ def get_workflow(self, workflow_id: int) -> WorkflowResponse:
49
+ """Get a single workflow by ID (returns draft if one exists, else published)."""
50
+ data = self._request("GET", f"/workflow/fetch/{workflow_id}")
51
+ return WorkflowResponse.model_validate(data)
52
+
53
+ def list_credentials(self) -> list[CredentialResponse]:
54
+ """List webhook credentials available to the authenticated organization."""
55
+ data = self._request("GET", "/credentials/")
56
+ return [CredentialResponse.model_validate(x) for x in data]
57
+
58
+ def list_documents(self, *, status: str | None = None, limit: int | None = None, offset: int | None = None) -> DocumentListResponseSchema:
59
+ """List knowledge base documents available to the authenticated organization."""
60
+ params: dict[str, Any] = {}
61
+ if status is not None:
62
+ params["status"] = status
63
+ if limit is not None:
64
+ params["limit"] = limit
65
+ if offset is not None:
66
+ params["offset"] = offset
67
+ data = self._request("GET", "/knowledge-base/documents", params=params)
68
+ return DocumentListResponseSchema.model_validate(data)
69
+
70
+ def list_node_types(self) -> NodeTypesResponse:
71
+ """List every registered node type with its spec. Pinned to spec_version."""
72
+ data = self._request("GET", "/node-types")
73
+ return NodeTypesResponse.model_validate(data)
74
+
75
+ def list_recordings(self, *, workflow_id: int | None = None, tts_provider: str | None = None, tts_model: str | None = None, tts_voice_id: str | None = None) -> RecordingListResponseSchema:
76
+ """List workflow recordings available to the authenticated organization."""
77
+ params: dict[str, Any] = {}
78
+ if workflow_id is not None:
79
+ params["workflow_id"] = workflow_id
80
+ if tts_provider is not None:
81
+ params["tts_provider"] = tts_provider
82
+ if tts_model is not None:
83
+ params["tts_model"] = tts_model
84
+ if tts_voice_id is not None:
85
+ params["tts_voice_id"] = tts_voice_id
86
+ data = self._request("GET", "/workflow-recordings/", params=params)
87
+ return RecordingListResponseSchema.model_validate(data)
88
+
89
+ def list_tools(self, *, status: str | None = None, category: str | None = None) -> list[ToolResponse]:
90
+ """List tools available to the authenticated organization."""
91
+ params: dict[str, Any] = {}
92
+ if status is not None:
93
+ params["status"] = status
94
+ if category is not None:
95
+ params["category"] = category
96
+ data = self._request("GET", "/tools/", params=params)
97
+ return [ToolResponse.model_validate(x) for x in data]
98
+
99
+ def list_workflows(self, *, status: str | None = None) -> list[WorkflowListResponse]:
100
+ """List all workflows in the authenticated organization."""
101
+ params: dict[str, Any] = {}
102
+ if status is not None:
103
+ params["status"] = status
104
+ data = self._request("GET", "/workflow/fetch", params=params)
105
+ return [WorkflowListResponse.model_validate(x) for x in data]
106
+
107
+ def test_phone_call(self, *, body: InitiateCallRequest) -> Any:
108
+ """Place a test call from a workflow to a phone number."""
109
+ return self._request("POST", "/telephony/initiate-call", json=body.model_dump(mode="json", exclude_none=True))
110
+
111
+ def update_workflow(self, workflow_id: int, *, body: UpdateWorkflowRequest) -> WorkflowResponse:
112
+ """Update a workflow's name and/or definition. Saves as a new draft."""
113
+ data = self._request("PUT", f"/workflow/{workflow_id}", json=body.model_dump(mode="json", exclude_none=True))
114
+ return WorkflowResponse.model_validate(data)