signalcore 0.1.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.
- signalcore-0.1.0/.gitignore +40 -0
- signalcore-0.1.0/LICENSE +21 -0
- signalcore-0.1.0/PKG-INFO +168 -0
- signalcore-0.1.0/README.md +142 -0
- signalcore-0.1.0/pyproject.toml +47 -0
- signalcore-0.1.0/src/signalcore/__init__.py +40 -0
- signalcore-0.1.0/src/signalcore/_client.py +177 -0
- signalcore-0.1.0/src/signalcore/_http.py +139 -0
- signalcore-0.1.0/src/signalcore/_pages.py +40 -0
- signalcore-0.1.0/src/signalcore/_sse.py +60 -0
- signalcore-0.1.0/src/signalcore/_sync.py +151 -0
- signalcore-0.1.0/src/signalcore/_version.py +5 -0
- signalcore-0.1.0/src/signalcore/errors.py +105 -0
- signalcore-0.1.0/src/signalcore/events.py +74 -0
- signalcore-0.1.0/src/signalcore/py.typed +0 -0
- signalcore-0.1.0/src/signalcore/resources/__init__.py +1 -0
- signalcore-0.1.0/src/signalcore/resources/_base.py +31 -0
- signalcore-0.1.0/src/signalcore/resources/agents.py +157 -0
- signalcore-0.1.0/src/signalcore/resources/api_keys.py +29 -0
- signalcore-0.1.0/src/signalcore/resources/billing.py +10 -0
- signalcore-0.1.0/src/signalcore/resources/browser.py +21 -0
- signalcore-0.1.0/src/signalcore/resources/build.py +59 -0
- signalcore-0.1.0/src/signalcore/resources/channels.py +185 -0
- signalcore-0.1.0/src/signalcore/resources/connections.py +72 -0
- signalcore-0.1.0/src/signalcore/resources/contacts.py +43 -0
- signalcore-0.1.0/src/signalcore/resources/conversations.py +82 -0
- signalcore-0.1.0/src/signalcore/resources/documents.py +75 -0
- signalcore-0.1.0/src/signalcore/resources/end_users.py +56 -0
- signalcore-0.1.0/src/signalcore/resources/files.py +56 -0
- signalcore-0.1.0/src/signalcore/resources/live.py +22 -0
- signalcore-0.1.0/src/signalcore/resources/memories.py +41 -0
- signalcore-0.1.0/src/signalcore/resources/org.py +15 -0
- signalcore-0.1.0/src/signalcore/resources/orgs.py +20 -0
- signalcore-0.1.0/src/signalcore/resources/playground.py +45 -0
- signalcore-0.1.0/src/signalcore/resources/publishable_keys.py +28 -0
- signalcore-0.1.0/src/signalcore/resources/records.py +48 -0
- signalcore-0.1.0/src/signalcore/resources/requests.py +38 -0
- signalcore-0.1.0/src/signalcore/resources/runs.py +64 -0
- signalcore-0.1.0/src/signalcore/resources/skills.py +53 -0
- signalcore-0.1.0/src/signalcore/resources/ui.py +53 -0
- signalcore-0.1.0/src/signalcore/resources/usage.py +11 -0
- signalcore-0.1.0/src/signalcore/resources/vault.py +44 -0
- signalcore-0.1.0/src/signalcore/types.py +1535 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
.venv/
|
|
5
|
+
.pytest_cache/
|
|
6
|
+
.ruff_cache/
|
|
7
|
+
.mypy_cache/
|
|
8
|
+
*.egg-info/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
|
|
12
|
+
# Node
|
|
13
|
+
node_modules/
|
|
14
|
+
.next/
|
|
15
|
+
out/
|
|
16
|
+
.turbo/
|
|
17
|
+
|
|
18
|
+
# Env & secrets
|
|
19
|
+
.env
|
|
20
|
+
.env.*
|
|
21
|
+
!.env.example
|
|
22
|
+
*.pem
|
|
23
|
+
*.key
|
|
24
|
+
|
|
25
|
+
# OS / editors
|
|
26
|
+
.DS_Store
|
|
27
|
+
Thumbs.db
|
|
28
|
+
.idea/
|
|
29
|
+
.vscode/*
|
|
30
|
+
!.vscode/extensions.json
|
|
31
|
+
|
|
32
|
+
# Terraform / OpenTofu (plans and state can contain secrets; the lock file IS committed)
|
|
33
|
+
.terraform/
|
|
34
|
+
*.tfplan
|
|
35
|
+
*.tfstate
|
|
36
|
+
*.tfstate.*
|
|
37
|
+
crash.log
|
|
38
|
+
|
|
39
|
+
# Local object storage (SC_FILES_DIR)
|
|
40
|
+
.files/
|
signalcore-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SignalCore
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: signalcore
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The Python client for SignalCore: create agents, talk to them and run everything around them.
|
|
5
|
+
Project-URL: Homepage, https://signalcore.ai
|
|
6
|
+
Project-URL: Documentation, https://signalcore.ai/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/OmnistGroup/signalcore/tree/staging/clients/python
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: agents,ai,api,sdk,signalcore
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Framework :: AsyncIO
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: anyio>=4.5
|
|
23
|
+
Requires-Dist: httpx>=0.28
|
|
24
|
+
Requires-Dist: pydantic>=2.7
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# signalcore
|
|
28
|
+
|
|
29
|
+
The Python client for [SignalCore](https://signalcore.ai): create agents, talk to them, and run
|
|
30
|
+
everything around them (conversations, approvals, knowledge, records, apps) from your own code.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install signalcore
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Python 3.11 or newer. Create an API key in the web app and export it:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
export SIGNALCORE_API_KEY=sk_live_...
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quickstart
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
import asyncio
|
|
46
|
+
|
|
47
|
+
from signalcore import AsyncSignalcore
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def main() -> None:
|
|
51
|
+
async with AsyncSignalcore() as client: # reads SIGNALCORE_API_KEY
|
|
52
|
+
agent = await client.agents.create("Reception", instructions="You answer for Clínica Sol.")
|
|
53
|
+
await client.agents.publish(agent.id)
|
|
54
|
+
|
|
55
|
+
answer = await client.agents.invoke(
|
|
56
|
+
agent.id, input="Do you open on Saturdays?", thread="ticket-7"
|
|
57
|
+
)
|
|
58
|
+
print(answer.text)
|
|
59
|
+
|
|
60
|
+
async for event in client.runs.stream(answer.run_id): # the run's journal, live
|
|
61
|
+
print(event.event, event.json())
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
asyncio.run(main())
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Code that does not use `asyncio` imports `Signalcore` instead: the same client, same names,
|
|
68
|
+
no `await`.
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from signalcore import Signalcore
|
|
72
|
+
|
|
73
|
+
with Signalcore() as client:
|
|
74
|
+
for agent in client.agents.list().items:
|
|
75
|
+
print(agent.name)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## What you get
|
|
79
|
+
|
|
80
|
+
- **Every endpoint of the API**, one attribute per family: `client.agents`, `client.runs`,
|
|
81
|
+
`client.conversations`, `client.requests`, `client.documents`, `client.records`,
|
|
82
|
+
`client.vault`, `client.connections`, `client.channels`, `client.skills` and the rest. Each
|
|
83
|
+
method names the operation it calls.
|
|
84
|
+
- **Typed answers.** Responses are [pydantic](https://docs.pydantic.dev) models generated from
|
|
85
|
+
the API's contract (`signalcore.types`), so your editor and type checker know every field.
|
|
86
|
+
- **Errors you can catch.** Failures raise `NotFoundError`, `ForbiddenError`, `InvalidError`
|
|
87
|
+
and the like (all `SignalcoreError`), carrying the API's `status`, `code`, `detail` and, for
|
|
88
|
+
a body that did not fit, the `errors` field by field.
|
|
89
|
+
- **Lists that page.** Every list answers `items` and `next_cursor`. To read a whole list:
|
|
90
|
+
`client.iterate(client.conversations.list, status="open")`.
|
|
91
|
+
- **Streams.** `client.runs.stream(run_id)` yields Server-Sent Events until the run is `done`
|
|
92
|
+
or `paused`; pass `last_event_id` to resume after a drop. `client.build.sessions.say(...)`
|
|
93
|
+
streams a builder turn.
|
|
94
|
+
- **Several businesses.** An agency's key acts as each of its clients:
|
|
95
|
+
`client.for_org("org_...").agents.list()`.
|
|
96
|
+
- **Live streams.** What a run shows as it happens is watched from a browser, over a WebSocket.
|
|
97
|
+
`client.live.url(stream_id)` is its address (`wss://<api>/v1/live/<id>?token=...`, with this
|
|
98
|
+
client's credential in the query): give it to `@signalcore/ui`'s `LiveView` or the TypeScript
|
|
99
|
+
client's `live.connect`, only when the client holds an end user's token or a publishable key. It
|
|
100
|
+
carries the credential, so never hand a browser one made with a secret key.
|
|
101
|
+
|
|
102
|
+
## End users in the browser
|
|
103
|
+
|
|
104
|
+
The people who use your product can talk to your agents without your secret key ever reaching
|
|
105
|
+
their browser. Your backend asks for a token for a person it knows and hands it over:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
minted = await client.end_users.mint_token("user-42", name="Ana", email="ana@example.com")
|
|
109
|
+
# Hand minted.token to their browser: new Signalcore({ endUserToken }) in @signalcore/sdk,
|
|
110
|
+
# or AsyncSignalcore(end_user_token=...) from Python.
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
For visitors who are not signed in, make a publishable key that works only from your pages
|
|
114
|
+
(`client.publishable_keys.create("Shop", ["https://shop.example.com"])`) and give the client
|
|
115
|
+
`publishable_key=` with a `visitor_id=` the browser keeps. Both credentials can talk to live
|
|
116
|
+
agents and read their own runs, and nothing else; `caller` is who the credential says.
|
|
117
|
+
|
|
118
|
+
## Your own components
|
|
119
|
+
|
|
120
|
+
What an agent can draw (cards, tables, forms) is a closed catalog, but your product has cards of
|
|
121
|
+
its own. Register one by name, with the props it takes and a text for doors that cannot draw it,
|
|
122
|
+
and agents can show it; your app draws it:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
await client.ui.components.put(
|
|
126
|
+
"PropertyCard",
|
|
127
|
+
"Property card",
|
|
128
|
+
"A listing the customer asked about", # what the agent should use it for
|
|
129
|
+
{
|
|
130
|
+
"type": "object",
|
|
131
|
+
"properties": {"address": {"type": "string"}, "price": {"type": "number"}},
|
|
132
|
+
"required": ["address", "price"],
|
|
133
|
+
},
|
|
134
|
+
"{address}\nPrice: {price} EUR", # the text twin; {prop} is filled from the props
|
|
135
|
+
)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The schema is a small subset of JSON Schema: strings, numbers, whole numbers, booleans, lists of
|
|
139
|
+
those and one level of object; anything else is refused. An agent shows the component as a
|
|
140
|
+
`Host` node, `{"id": "home", "component": "Host", "name": "PropertyCard", "props": {...}}`
|
|
141
|
+
(`sc.ui.host("PropertyCard", {...})` in agent code). Its props are checked against your schema
|
|
142
|
+
before anyone sees them, and the text twin is the surface's text when the agent wrote none.
|
|
143
|
+
`client.ui.catalog()` lists your components after the built-in ones.
|
|
144
|
+
|
|
145
|
+
## Configuration
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
AsyncSignalcore(
|
|
149
|
+
api_key=None, # else SIGNALCORE_API_KEY
|
|
150
|
+
end_user_token=None, # instead of api_key: a person of your product, in their browser
|
|
151
|
+
publishable_key=None, # instead of api_key: a visitor, with visitor_id (8-128 characters)
|
|
152
|
+
visitor_id=None,
|
|
153
|
+
base_url=None, # else SIGNALCORE_BASE_URL, else https://signalcore.ai
|
|
154
|
+
org=None, # act as a business this key manages
|
|
155
|
+
timeout=30, # seconds
|
|
156
|
+
)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Pass exactly one of `api_key`, `end_user_token` and `publishable_key`.
|
|
160
|
+
|
|
161
|
+
Keys carry scopes (`agents:read`, `runs:write`...), so a key made for one job can do only
|
|
162
|
+
that job; a call outside its scopes raises `ForbiddenError`.
|
|
163
|
+
|
|
164
|
+
## Documentation
|
|
165
|
+
|
|
166
|
+
Guides and the full API reference: <https://signalcore.ai/docs>.
|
|
167
|
+
|
|
168
|
+
MIT licensed.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# signalcore
|
|
2
|
+
|
|
3
|
+
The Python client for [SignalCore](https://signalcore.ai): create agents, talk to them, and run
|
|
4
|
+
everything around them (conversations, approvals, knowledge, records, apps) from your own code.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pip install signalcore
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Python 3.11 or newer. Create an API key in the web app and export it:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
export SIGNALCORE_API_KEY=sk_live_...
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quickstart
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
import asyncio
|
|
20
|
+
|
|
21
|
+
from signalcore import AsyncSignalcore
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def main() -> None:
|
|
25
|
+
async with AsyncSignalcore() as client: # reads SIGNALCORE_API_KEY
|
|
26
|
+
agent = await client.agents.create("Reception", instructions="You answer for Clínica Sol.")
|
|
27
|
+
await client.agents.publish(agent.id)
|
|
28
|
+
|
|
29
|
+
answer = await client.agents.invoke(
|
|
30
|
+
agent.id, input="Do you open on Saturdays?", thread="ticket-7"
|
|
31
|
+
)
|
|
32
|
+
print(answer.text)
|
|
33
|
+
|
|
34
|
+
async for event in client.runs.stream(answer.run_id): # the run's journal, live
|
|
35
|
+
print(event.event, event.json())
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
asyncio.run(main())
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Code that does not use `asyncio` imports `Signalcore` instead: the same client, same names,
|
|
42
|
+
no `await`.
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from signalcore import Signalcore
|
|
46
|
+
|
|
47
|
+
with Signalcore() as client:
|
|
48
|
+
for agent in client.agents.list().items:
|
|
49
|
+
print(agent.name)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## What you get
|
|
53
|
+
|
|
54
|
+
- **Every endpoint of the API**, one attribute per family: `client.agents`, `client.runs`,
|
|
55
|
+
`client.conversations`, `client.requests`, `client.documents`, `client.records`,
|
|
56
|
+
`client.vault`, `client.connections`, `client.channels`, `client.skills` and the rest. Each
|
|
57
|
+
method names the operation it calls.
|
|
58
|
+
- **Typed answers.** Responses are [pydantic](https://docs.pydantic.dev) models generated from
|
|
59
|
+
the API's contract (`signalcore.types`), so your editor and type checker know every field.
|
|
60
|
+
- **Errors you can catch.** Failures raise `NotFoundError`, `ForbiddenError`, `InvalidError`
|
|
61
|
+
and the like (all `SignalcoreError`), carrying the API's `status`, `code`, `detail` and, for
|
|
62
|
+
a body that did not fit, the `errors` field by field.
|
|
63
|
+
- **Lists that page.** Every list answers `items` and `next_cursor`. To read a whole list:
|
|
64
|
+
`client.iterate(client.conversations.list, status="open")`.
|
|
65
|
+
- **Streams.** `client.runs.stream(run_id)` yields Server-Sent Events until the run is `done`
|
|
66
|
+
or `paused`; pass `last_event_id` to resume after a drop. `client.build.sessions.say(...)`
|
|
67
|
+
streams a builder turn.
|
|
68
|
+
- **Several businesses.** An agency's key acts as each of its clients:
|
|
69
|
+
`client.for_org("org_...").agents.list()`.
|
|
70
|
+
- **Live streams.** What a run shows as it happens is watched from a browser, over a WebSocket.
|
|
71
|
+
`client.live.url(stream_id)` is its address (`wss://<api>/v1/live/<id>?token=...`, with this
|
|
72
|
+
client's credential in the query): give it to `@signalcore/ui`'s `LiveView` or the TypeScript
|
|
73
|
+
client's `live.connect`, only when the client holds an end user's token or a publishable key. It
|
|
74
|
+
carries the credential, so never hand a browser one made with a secret key.
|
|
75
|
+
|
|
76
|
+
## End users in the browser
|
|
77
|
+
|
|
78
|
+
The people who use your product can talk to your agents without your secret key ever reaching
|
|
79
|
+
their browser. Your backend asks for a token for a person it knows and hands it over:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
minted = await client.end_users.mint_token("user-42", name="Ana", email="ana@example.com")
|
|
83
|
+
# Hand minted.token to their browser: new Signalcore({ endUserToken }) in @signalcore/sdk,
|
|
84
|
+
# or AsyncSignalcore(end_user_token=...) from Python.
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
For visitors who are not signed in, make a publishable key that works only from your pages
|
|
88
|
+
(`client.publishable_keys.create("Shop", ["https://shop.example.com"])`) and give the client
|
|
89
|
+
`publishable_key=` with a `visitor_id=` the browser keeps. Both credentials can talk to live
|
|
90
|
+
agents and read their own runs, and nothing else; `caller` is who the credential says.
|
|
91
|
+
|
|
92
|
+
## Your own components
|
|
93
|
+
|
|
94
|
+
What an agent can draw (cards, tables, forms) is a closed catalog, but your product has cards of
|
|
95
|
+
its own. Register one by name, with the props it takes and a text for doors that cannot draw it,
|
|
96
|
+
and agents can show it; your app draws it:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
await client.ui.components.put(
|
|
100
|
+
"PropertyCard",
|
|
101
|
+
"Property card",
|
|
102
|
+
"A listing the customer asked about", # what the agent should use it for
|
|
103
|
+
{
|
|
104
|
+
"type": "object",
|
|
105
|
+
"properties": {"address": {"type": "string"}, "price": {"type": "number"}},
|
|
106
|
+
"required": ["address", "price"],
|
|
107
|
+
},
|
|
108
|
+
"{address}\nPrice: {price} EUR", # the text twin; {prop} is filled from the props
|
|
109
|
+
)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The schema is a small subset of JSON Schema: strings, numbers, whole numbers, booleans, lists of
|
|
113
|
+
those and one level of object; anything else is refused. An agent shows the component as a
|
|
114
|
+
`Host` node, `{"id": "home", "component": "Host", "name": "PropertyCard", "props": {...}}`
|
|
115
|
+
(`sc.ui.host("PropertyCard", {...})` in agent code). Its props are checked against your schema
|
|
116
|
+
before anyone sees them, and the text twin is the surface's text when the agent wrote none.
|
|
117
|
+
`client.ui.catalog()` lists your components after the built-in ones.
|
|
118
|
+
|
|
119
|
+
## Configuration
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
AsyncSignalcore(
|
|
123
|
+
api_key=None, # else SIGNALCORE_API_KEY
|
|
124
|
+
end_user_token=None, # instead of api_key: a person of your product, in their browser
|
|
125
|
+
publishable_key=None, # instead of api_key: a visitor, with visitor_id (8-128 characters)
|
|
126
|
+
visitor_id=None,
|
|
127
|
+
base_url=None, # else SIGNALCORE_BASE_URL, else https://signalcore.ai
|
|
128
|
+
org=None, # act as a business this key manages
|
|
129
|
+
timeout=30, # seconds
|
|
130
|
+
)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Pass exactly one of `api_key`, `end_user_token` and `publishable_key`.
|
|
134
|
+
|
|
135
|
+
Keys carry scopes (`agents:read`, `runs:write`...), so a key made for one job can do only
|
|
136
|
+
that job; a call outside its scopes raises `ForbiddenError`.
|
|
137
|
+
|
|
138
|
+
## Documentation
|
|
139
|
+
|
|
140
|
+
Guides and the full API reference: <https://signalcore.ai/docs>.
|
|
141
|
+
|
|
142
|
+
MIT licensed.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "signalcore"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "The Python client for SignalCore: create agents, talk to them and run everything around them."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
keywords = ["signalcore", "agents", "ai", "sdk", "api"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Framework :: AsyncIO",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"Operating System :: OS Independent",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
20
|
+
"Typing :: Typed",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"anyio>=4.5",
|
|
24
|
+
"httpx>=0.28",
|
|
25
|
+
"pydantic>=2.7",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://signalcore.ai"
|
|
30
|
+
Documentation = "https://signalcore.ai/docs"
|
|
31
|
+
Repository = "https://github.com/OmnistGroup/signalcore/tree/staging/clients/python"
|
|
32
|
+
|
|
33
|
+
[build-system]
|
|
34
|
+
requires = ["hatchling"]
|
|
35
|
+
build-backend = "hatchling.build"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.build.targets.wheel]
|
|
38
|
+
packages = ["src/signalcore"]
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.sdist]
|
|
41
|
+
include = ["src/signalcore", "README.md", "LICENSE"]
|
|
42
|
+
|
|
43
|
+
# Ruff reads the nearest config: this one keeps the repo's rules and lowers the target to the
|
|
44
|
+
# oldest Python the package supports (the rest of the repo runs on 3.12).
|
|
45
|
+
[tool.ruff]
|
|
46
|
+
extend = "../../pyproject.toml"
|
|
47
|
+
target-version = "py311"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""The Python client for SignalCore.
|
|
2
|
+
|
|
3
|
+
from signalcore import AsyncSignalcore, Signalcore
|
|
4
|
+
|
|
5
|
+
``AsyncSignalcore`` is the client; ``Signalcore`` is the same one for code that blocks.
|
|
6
|
+
Responses are the models in ``signalcore.types``, generated from the API's contract.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from signalcore._client import AsyncSignalcore
|
|
10
|
+
from signalcore._sse import ServerSentEvent
|
|
11
|
+
from signalcore._sync import Signalcore
|
|
12
|
+
from signalcore._version import __version__
|
|
13
|
+
from signalcore.errors import (
|
|
14
|
+
ConflictError,
|
|
15
|
+
ForbiddenError,
|
|
16
|
+
InvalidError,
|
|
17
|
+
NotFoundError,
|
|
18
|
+
RateLimitedError,
|
|
19
|
+
ServerError,
|
|
20
|
+
SignalcoreError,
|
|
21
|
+
UnauthenticatedError,
|
|
22
|
+
)
|
|
23
|
+
from signalcore.events import RunEvent, UnknownEvent
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"AsyncSignalcore",
|
|
27
|
+
"ConflictError",
|
|
28
|
+
"ForbiddenError",
|
|
29
|
+
"InvalidError",
|
|
30
|
+
"NotFoundError",
|
|
31
|
+
"RateLimitedError",
|
|
32
|
+
"RunEvent",
|
|
33
|
+
"ServerError",
|
|
34
|
+
"ServerSentEvent",
|
|
35
|
+
"Signalcore",
|
|
36
|
+
"SignalcoreError",
|
|
37
|
+
"UnauthenticatedError",
|
|
38
|
+
"UnknownEvent",
|
|
39
|
+
"__version__",
|
|
40
|
+
]
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""The async client: credentials and a connection, with the API's families mounted on it."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from collections.abc import AsyncIterator, Awaitable, Callable
|
|
5
|
+
from types import TracebackType
|
|
6
|
+
from typing import Any, Self
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from signalcore._http import ORG_HEADER, VISITOR_HEADER, Http
|
|
11
|
+
from signalcore._pages import Item, Page, pages
|
|
12
|
+
from signalcore._version import __version__
|
|
13
|
+
from signalcore.resources.agents import Agents
|
|
14
|
+
from signalcore.resources.api_keys import ApiKeys
|
|
15
|
+
from signalcore.resources.billing import Billing
|
|
16
|
+
from signalcore.resources.browser import Browser
|
|
17
|
+
from signalcore.resources.build import Build
|
|
18
|
+
from signalcore.resources.channels import Channels
|
|
19
|
+
from signalcore.resources.connections import Connections
|
|
20
|
+
from signalcore.resources.contacts import Contacts
|
|
21
|
+
from signalcore.resources.conversations import Conversations
|
|
22
|
+
from signalcore.resources.documents import Documents
|
|
23
|
+
from signalcore.resources.end_users import EndUsers
|
|
24
|
+
from signalcore.resources.files import Files
|
|
25
|
+
from signalcore.resources.live import Live
|
|
26
|
+
from signalcore.resources.memories import Memories
|
|
27
|
+
from signalcore.resources.org import Org
|
|
28
|
+
from signalcore.resources.orgs import Orgs
|
|
29
|
+
from signalcore.resources.playground import Playground
|
|
30
|
+
from signalcore.resources.publishable_keys import PublishableKeys
|
|
31
|
+
from signalcore.resources.records import Records
|
|
32
|
+
from signalcore.resources.requests import Requests
|
|
33
|
+
from signalcore.resources.runs import Runs
|
|
34
|
+
from signalcore.resources.skills import Skills
|
|
35
|
+
from signalcore.resources.ui import Ui
|
|
36
|
+
from signalcore.resources.usage import Usage
|
|
37
|
+
from signalcore.resources.vault import Vault
|
|
38
|
+
|
|
39
|
+
DEFAULT_BASE_URL = "https://signalcore.ai"
|
|
40
|
+
API_KEY_VARIABLE = "SIGNALCORE_API_KEY"
|
|
41
|
+
BASE_URL_VARIABLE = "SIGNALCORE_BASE_URL"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _credential(
|
|
45
|
+
api_key: str | None,
|
|
46
|
+
end_user_token: str | None,
|
|
47
|
+
publishable_key: str | None,
|
|
48
|
+
visitor_id: str | None,
|
|
49
|
+
) -> dict[str, str]:
|
|
50
|
+
"""The headers that say who is calling: exactly one of a secret key, an end user's token,
|
|
51
|
+
or a publishable key together with the visitor's id."""
|
|
52
|
+
passed = {
|
|
53
|
+
name: value
|
|
54
|
+
for name, value in (
|
|
55
|
+
("api_key", api_key),
|
|
56
|
+
("end_user_token", end_user_token),
|
|
57
|
+
("publishable_key", publishable_key),
|
|
58
|
+
)
|
|
59
|
+
if value
|
|
60
|
+
}
|
|
61
|
+
if len(passed) > 1:
|
|
62
|
+
raise ValueError(
|
|
63
|
+
f"Pass one of api_key, end_user_token or publishable_key, not {', '.join(passed)}"
|
|
64
|
+
)
|
|
65
|
+
if visitor_id and not publishable_key:
|
|
66
|
+
raise ValueError("visitor_id goes with publishable_key")
|
|
67
|
+
if publishable_key:
|
|
68
|
+
if not visitor_id:
|
|
69
|
+
raise ValueError("A publishable_key needs the visitor_id the browser keeps")
|
|
70
|
+
return {"Authorization": f"Bearer {publishable_key}", VISITOR_HEADER: visitor_id}
|
|
71
|
+
if end_user_token:
|
|
72
|
+
return {"Authorization": f"Bearer {end_user_token}"}
|
|
73
|
+
key = api_key or os.environ.get(API_KEY_VARIABLE)
|
|
74
|
+
if not key:
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"Pass api_key, end_user_token or publishable_key, or set {API_KEY_VARIABLE}"
|
|
77
|
+
)
|
|
78
|
+
return {"Authorization": f"Bearer {key}"}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class AsyncSignalcore:
|
|
82
|
+
"""A client for the SignalCore API.
|
|
83
|
+
|
|
84
|
+
It acts with one credential. Your servers use a secret key, from ``api_key`` or
|
|
85
|
+
``SIGNALCORE_API_KEY``. Where a browser holds the client, pass ``end_user_token`` (minted
|
|
86
|
+
for a person of your product by ``end_users.mint_token``) or ``publishable_key`` with the
|
|
87
|
+
``visitor_id`` the browser keeps; those can talk to agents and read their own runs, nothing
|
|
88
|
+
more. The address comes from ``base_url`` or ``SIGNALCORE_BASE_URL``. ``org`` makes every
|
|
89
|
+
call act as a business the key manages (``for_org`` does it for a few). ``transport``
|
|
90
|
+
replaces the network, to run against an app in-process in tests.
|
|
91
|
+
|
|
92
|
+
async with AsyncSignalcore() as client:
|
|
93
|
+
agent = await client.agents.create("Reception")
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(
|
|
97
|
+
self,
|
|
98
|
+
api_key: str | None = None,
|
|
99
|
+
*,
|
|
100
|
+
end_user_token: str | None = None,
|
|
101
|
+
publishable_key: str | None = None,
|
|
102
|
+
visitor_id: str | None = None,
|
|
103
|
+
base_url: str | None = None,
|
|
104
|
+
org: str | None = None,
|
|
105
|
+
timeout: float = 30,
|
|
106
|
+
transport: httpx.AsyncBaseTransport | None = None,
|
|
107
|
+
) -> None:
|
|
108
|
+
address = base_url or os.environ.get(BASE_URL_VARIABLE) or DEFAULT_BASE_URL
|
|
109
|
+
headers = {
|
|
110
|
+
**_credential(api_key, end_user_token, publishable_key, visitor_id),
|
|
111
|
+
"Accept": "application/json",
|
|
112
|
+
"User-Agent": f"signalcore-python/{__version__}",
|
|
113
|
+
**({ORG_HEADER: org} if org else {}),
|
|
114
|
+
}
|
|
115
|
+
self._mount(
|
|
116
|
+
Http(
|
|
117
|
+
httpx.AsyncClient(base_url=address, timeout=timeout, transport=transport),
|
|
118
|
+
headers,
|
|
119
|
+
timeout=timeout,
|
|
120
|
+
owns_client=True,
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def _mount(self, http: Http) -> None:
|
|
125
|
+
self._http = http
|
|
126
|
+
self.orgs = Orgs(http)
|
|
127
|
+
self.api_keys = ApiKeys(http)
|
|
128
|
+
self.org = Org(http)
|
|
129
|
+
self.agents = Agents(http)
|
|
130
|
+
self.build = Build(http)
|
|
131
|
+
self.runs = Runs(http)
|
|
132
|
+
self.playground = Playground(http)
|
|
133
|
+
self.conversations = Conversations(http)
|
|
134
|
+
self.contacts = Contacts(http)
|
|
135
|
+
self.requests = Requests(http)
|
|
136
|
+
self.documents = Documents(http)
|
|
137
|
+
self.end_users = EndUsers(http)
|
|
138
|
+
self.publishable_keys = PublishableKeys(http)
|
|
139
|
+
self.memories = Memories(http)
|
|
140
|
+
self.records = Records(http)
|
|
141
|
+
self.files = Files(http)
|
|
142
|
+
self.vault = Vault(http)
|
|
143
|
+
self.connections = Connections(http)
|
|
144
|
+
self.channels = Channels(http)
|
|
145
|
+
self.skills = Skills(http)
|
|
146
|
+
self.usage = Usage(http)
|
|
147
|
+
self.billing = Billing(http)
|
|
148
|
+
self.ui = Ui(http)
|
|
149
|
+
self.browser = Browser(http)
|
|
150
|
+
self.live = Live(http)
|
|
151
|
+
|
|
152
|
+
def for_org(self, org_id: str) -> Self:
|
|
153
|
+
"""A client acting as a business this one manages (``orgs.create``), sharing its
|
|
154
|
+
connection. Closing it leaves the connection to the client it came from."""
|
|
155
|
+
client = object.__new__(type(self))
|
|
156
|
+
client._mount(self._http.acting_as(org_id))
|
|
157
|
+
return client
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def iterate(fetch: Callable[..., Awaitable[Page[Item]]], **params: Any) -> AsyncIterator[Item]:
|
|
161
|
+
"""Every item of a paged list, page after page:
|
|
162
|
+
``client.iterate(client.conversations.list, status="open")``."""
|
|
163
|
+
return pages(fetch, **params)
|
|
164
|
+
|
|
165
|
+
async def aclose(self) -> None:
|
|
166
|
+
await self._http.aclose()
|
|
167
|
+
|
|
168
|
+
async def __aenter__(self) -> Self:
|
|
169
|
+
return self
|
|
170
|
+
|
|
171
|
+
async def __aexit__(
|
|
172
|
+
self,
|
|
173
|
+
exc_type: type[BaseException] | None,
|
|
174
|
+
exc: BaseException | None,
|
|
175
|
+
traceback: TracebackType | None,
|
|
176
|
+
) -> None:
|
|
177
|
+
await self.aclose()
|