ocai 0.1.1__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.
- ocai-0.1.1/.gitignore +60 -0
- ocai-0.1.1/CHANGELOG.md +37 -0
- ocai-0.1.1/PKG-INFO +83 -0
- ocai-0.1.1/README.md +56 -0
- ocai-0.1.1/pyproject.toml +51 -0
- ocai-0.1.1/src/ocai/chat/__init__.py +271 -0
- ocai-0.1.1/src/ocai/chat/canonical.py +37 -0
- ocai-0.1.1/src/ocai/chat/facade.py +1572 -0
- ocai-0.1.1/src/ocai/chat/protocol.py +128 -0
- ocai-0.1.1/src/ocai/chat/py.typed +0 -0
- ocai-0.1.1/src/ocai/chat/types/__init__.py +195 -0
- ocai-0.1.1/src/ocai/chat/types/annotations.py +237 -0
- ocai-0.1.1/src/ocai/chat/types/config.py +336 -0
- ocai-0.1.1/src/ocai/chat/types/finish.py +36 -0
- ocai-0.1.1/src/ocai/chat/types/message.py +198 -0
- ocai-0.1.1/src/ocai/chat/types/parts.py +821 -0
- ocai-0.1.1/src/ocai/chat/types/request.py +124 -0
- ocai-0.1.1/src/ocai/chat/types/response.py +145 -0
- ocai-0.1.1/src/ocai/chat/types/role.py +90 -0
- ocai-0.1.1/src/ocai/chat/types/stream.py +1203 -0
- ocai-0.1.1/src/ocai/chat/types/tools.py +467 -0
- ocai-0.1.1/src/ocai/chat/types/usage.py +22 -0
- ocai-0.1.1/src/ocai/convert/__init__.py +89 -0
- ocai-0.1.1/src/ocai/convert/catalog.py +1029 -0
- ocai-0.1.1/src/ocai/convert/conformance.py +800 -0
- ocai-0.1.1/src/ocai/convert/contract.py +403 -0
- ocai-0.1.1/src/ocai/convert/graph.py +625 -0
- ocai-0.1.1/src/ocai/convert/hosts.py +70 -0
- ocai-0.1.1/src/ocai/convert/inspection.py +68 -0
- ocai-0.1.1/src/ocai/convert/migrate.py +69 -0
- ocai-0.1.1/src/ocai/convert/py.typed +0 -0
- ocai-0.1.1/src/ocai/convert/resources.py +148 -0
- ocai-0.1.1/src/ocai/core/__init__.py +212 -0
- ocai-0.1.1/src/ocai/core/_headers.py +45 -0
- ocai-0.1.1/src/ocai/core/canonjson.py +156 -0
- ocai-0.1.1/src/ocai/core/credentials.py +177 -0
- ocai-0.1.1/src/ocai/core/errors.py +190 -0
- ocai-0.1.1/src/ocai/core/fidelity.py +624 -0
- ocai-0.1.1/src/ocai/core/finish.py +396 -0
- ocai-0.1.1/src/ocai/core/identity.py +213 -0
- ocai-0.1.1/src/ocai/core/ir.py +328 -0
- ocai-0.1.1/src/ocai/core/jsonschema.py +326 -0
- ocai-0.1.1/src/ocai/core/primitives.py +114 -0
- ocai-0.1.1/src/ocai/core/profile.py +154 -0
- ocai-0.1.1/src/ocai/core/py.typed +0 -0
- ocai-0.1.1/src/ocai/core/refs.py +168 -0
- ocai-0.1.1/src/ocai/core/result.py +135 -0
- ocai-0.1.1/src/ocai/core/sources.py +251 -0
- ocai-0.1.1/src/ocai/core/traits.py +171 -0
- ocai-0.1.1/src/ocai/core/usage.py +246 -0
- ocai-0.1.1/src/ocai/embedding/__init__.py +28 -0
- ocai-0.1.1/src/ocai/embedding/protocol.py +77 -0
- ocai-0.1.1/src/ocai/embedding/py.typed +0 -0
- ocai-0.1.1/src/ocai/embedding/types/__init__.py +26 -0
- ocai-0.1.1/src/ocai/embedding/types/document.py +77 -0
- ocai-0.1.1/src/ocai/embedding/types/embedding.py +78 -0
- ocai-0.1.1/src/ocai/embedding/types/request.py +124 -0
- ocai-0.1.1/src/ocai/embedding/types/response.py +64 -0
- ocai-0.1.1/src/ocai/runtime/__init__.py +75 -0
- ocai-0.1.1/src/ocai/runtime/binding.py +120 -0
- ocai-0.1.1/src/ocai/runtime/deadline.py +58 -0
- ocai-0.1.1/src/ocai/runtime/decisions.py +217 -0
- ocai-0.1.1/src/ocai/runtime/drivers.py +772 -0
- ocai-0.1.1/src/ocai/runtime/dual.py +317 -0
- ocai-0.1.1/src/ocai/runtime/interceptors.py +307 -0
- ocai-0.1.1/src/ocai/runtime/observe.py +193 -0
- ocai-0.1.1/src/ocai/runtime/py.typed +0 -0
- ocai-0.1.1/src/ocai/vectorstore/__init__.py +26 -0
- ocai-0.1.1/src/ocai/vectorstore/protocol.py +255 -0
- ocai-0.1.1/src/ocai/vectorstore/py.typed +0 -0
- ocai-0.1.1/src/ocai/vectorstore/types/__init__.py +22 -0
- ocai-0.1.1/src/ocai/vectorstore/types/filter.py +14 -0
- ocai-0.1.1/src/ocai/vectorstore/types/record.py +106 -0
- ocai-0.1.1/src/ocai/wire/__init__.py +67 -0
- ocai-0.1.1/src/ocai/wire/framing.py +138 -0
- ocai-0.1.1/src/ocai/wire/hosting.py +497 -0
- ocai-0.1.1/src/ocai/wire/http.py +118 -0
- ocai-0.1.1/src/ocai/wire/policies.py +188 -0
- ocai-0.1.1/src/ocai/wire/py.typed +0 -0
- ocai-0.1.1/src/ocai/wire/sse.py +144 -0
- ocai-0.1.1/src/ocai/wire/transport.py +385 -0
- ocai-0.1.1/tests/conftest.py +76 -0
- ocai-0.1.1/tests/http/anthropic_messages.json +30 -0
- ocai-0.1.1/tests/http/anthropic_messages_auth_error.json +28 -0
- ocai-0.1.1/tests/http/anthropic_messages_overloaded_error.json +28 -0
- ocai-0.1.1/tests/http/anthropic_messages_stream.json +35 -0
- ocai-0.1.1/tests/http/anthropic_messages_stream_error.json +31 -0
- ocai-0.1.1/tests/http/anthropic_messages_tool_call.json +57 -0
- ocai-0.1.1/tests/http/anthropic_server_tool_unsupported.json +52 -0
- ocai-0.1.1/tests/http/anthropic_server_tool_unsupported_stream.json +49 -0
- ocai-0.1.1/tests/http/anthropic_thinking_replay.json +68 -0
- ocai-0.1.1/tests/http/anthropic_thinking_replay_stream.json +65 -0
- ocai-0.1.1/tests/http/anthropic_thinking_seed.json +50 -0
- ocai-0.1.1/tests/http/anthropic_vertex_version.json +43 -0
- ocai-0.1.1/tests/http/anthropic_web_search_error.json +76 -0
- ocai-0.1.1/tests/http/anthropic_web_search_error_stream.json +57 -0
- ocai-0.1.1/tests/http/anthropic_web_search_error_stream_paired.json +76 -0
- ocai-0.1.1/tests/http/anthropic_web_search_interrupted.json +49 -0
- ocai-0.1.1/tests/http/anthropic_web_search_replay.json +150 -0
- ocai-0.1.1/tests/http/anthropic_web_search_stream.json +58 -0
- ocai-0.1.1/tests/http/anthropic_web_search_stream_paired.json +96 -0
- ocai-0.1.1/tests/http/custom_host_arn.json +53 -0
- ocai-0.1.1/tests/http/custom_host_stream.json +34 -0
- ocai-0.1.1/tests/http/custom_host_unary.json +53 -0
- ocai-0.1.1/tests/http/gateway_anthropic_tools.json +75 -0
- ocai-0.1.1/tests/http/gateway_anthropic_tools_stream.json +63 -0
- ocai-0.1.1/tests/http/gateway_openai_tools.json +67 -0
- ocai-0.1.1/tests/http/gateway_openai_tools_stream.json +55 -0
- ocai-0.1.1/tests/http/openai_chat_citation_unqualified.json +53 -0
- ocai-0.1.1/tests/http/openai_chat_completion.json +31 -0
- ocai-0.1.1/tests/http/openai_chat_completion_auth_error.json +24 -0
- ocai-0.1.1/tests/http/openai_chat_completion_context_length.json +24 -0
- ocai-0.1.1/tests/http/openai_chat_completion_reasoning.json +31 -0
- ocai-0.1.1/tests/http/openai_chat_completion_retry.json +45 -0
- ocai-0.1.1/tests/http/openai_chat_completion_stream.json +34 -0
- ocai-0.1.1/tests/http/openai_chat_completion_stream_abort.json +31 -0
- ocai-0.1.1/tests/http/openai_chat_completion_stream_extra_choice.json +34 -0
- ocai-0.1.1/tests/http/openai_chat_completion_stream_paired.json +53 -0
- ocai-0.1.1/tests/http/openai_chat_completion_tool_call.json +73 -0
- ocai-0.1.1/tests/http/openai_chat_framed_stream.json +34 -0
- ocai-0.1.1/tests/http/openai_chat_framed_stream_paired.json +53 -0
- ocai-0.1.1/tests/http/openai_server_tool_unsupported.json +64 -0
- ocai-0.1.1/tests/http/openai_server_tool_unsupported_stream.json +45 -0
- ocai-0.1.1/tests/test_anthropic_search.py +308 -0
- ocai-0.1.1/tests/test_catalog_hardening.py +271 -0
- ocai-0.1.1/tests/test_citations.py +176 -0
- ocai-0.1.1/tests/test_conformance.py +202 -0
- ocai-0.1.1/tests/test_conformance_resources.py +219 -0
- ocai-0.1.1/tests/test_context_deadline.py +243 -0
- ocai-0.1.1/tests/test_contract_certify.py +302 -0
- ocai-0.1.1/tests/test_credential_isolation.py +174 -0
- ocai-0.1.1/tests/test_credentials.py +60 -0
- ocai-0.1.1/tests/test_cross_conversion.py +363 -0
- ocai-0.1.1/tests/test_errors.py +73 -0
- ocai-0.1.1/tests/test_facade_stream.py +339 -0
- ocai-0.1.1/tests/test_facade_stream_anthropic.py +389 -0
- ocai-0.1.1/tests/test_facade_unary.py +453 -0
- ocai-0.1.1/tests/test_facade_unary_anthropic.py +490 -0
- ocai-0.1.1/tests/test_fidelity_policy.py +148 -0
- ocai-0.1.1/tests/test_fold_law.py +425 -0
- ocai-0.1.1/tests/test_front_doors.py +376 -0
- ocai-0.1.1/tests/test_gateway_qualification.py +416 -0
- ocai-0.1.1/tests/test_host_composition.py +378 -0
- ocai-0.1.1/tests/test_host_traits.py +303 -0
- ocai-0.1.1/tests/test_import_contracts.py +86 -0
- ocai-0.1.1/tests/test_item_proposal.py +131 -0
- ocai-0.1.1/tests/test_lifecycle_events.py +268 -0
- ocai-0.1.1/tests/test_namespace.py +59 -0
- ocai-0.1.1/tests/test_no_sdk.py +51 -0
- ocai-0.1.1/tests/test_profile_gate.py +348 -0
- ocai-0.1.1/tests/test_public_preflight.py +233 -0
- ocai-0.1.1/tests/test_replay_anchors.py +204 -0
- ocai-0.1.1/tests/test_schema_conformance.py +551 -0
- ocai-0.1.1/tests/test_server_tool_intent.py +203 -0
- ocai-0.1.1/tests/test_skeleton.py +161 -0
- ocai-0.1.1/tests/test_spec_schemas.py +370 -0
- ocai-0.1.1/tests/test_stream_framing.py +660 -0
- ocai-0.1.1/tests/test_stream_middleware.py +236 -0
- ocai-0.1.1/tests/test_stream_ownership.py +185 -0
- ocai-0.1.1/tests/test_stream_planning.py +466 -0
- ocai-0.1.1/tests/test_streaming.py +169 -0
- ocai-0.1.1/tests/test_transport.py +103 -0
- ocai-0.1.1/tests/test_unknown_inputs.py +188 -0
- ocai-0.1.1/tests/test_version_fidelity.py +175 -0
ocai-0.1.1/.gitignore
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
*.so
|
|
9
|
+
|
|
10
|
+
# uv / venv
|
|
11
|
+
.venv/
|
|
12
|
+
uv.lock.bak
|
|
13
|
+
|
|
14
|
+
# Tooling caches
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.ruff_cache/
|
|
17
|
+
.mypy_cache/
|
|
18
|
+
.import_linter_cache/
|
|
19
|
+
.coverage
|
|
20
|
+
htmlcov/
|
|
21
|
+
|
|
22
|
+
# Docs site build output and page cache (zensical build)
|
|
23
|
+
/site/
|
|
24
|
+
/.cache/
|
|
25
|
+
|
|
26
|
+
# Node (future typescript/)
|
|
27
|
+
node_modules/
|
|
28
|
+
*.tsbuildinfo
|
|
29
|
+
|
|
30
|
+
# Go (future go/)
|
|
31
|
+
/go/bin/
|
|
32
|
+
|
|
33
|
+
# Editors / OS. Shared editor settings ARE committed: the multi-root workspace
|
|
34
|
+
# (ocai.code-workspace) and each folder's .vscode/settings.json + extensions.json
|
|
35
|
+
# give Pylance/Ruff the right interpreter per project. Personal files stay local.
|
|
36
|
+
.idea/
|
|
37
|
+
.vscode/*
|
|
38
|
+
!.vscode/settings.json
|
|
39
|
+
!.vscode/extensions.json
|
|
40
|
+
.DS_Store
|
|
41
|
+
|
|
42
|
+
# Per-user agent settings (never commit personal permissions)
|
|
43
|
+
.claude/settings.local.json
|
|
44
|
+
.codex/config.toml
|
|
45
|
+
|
|
46
|
+
# Platform build and browser-test artifacts
|
|
47
|
+
.nuxt/
|
|
48
|
+
.output/
|
|
49
|
+
platform/test-results/
|
|
50
|
+
platform/playwright-report/
|
|
51
|
+
|
|
52
|
+
# Local environment files: the root .env is the active one, .envs/ holds the
|
|
53
|
+
# per-environment variants (`task env -- <ENV>` copies .envs/.env.<ENV> to .env).
|
|
54
|
+
# Never committed: they hold credentials.
|
|
55
|
+
/.env
|
|
56
|
+
/.envs/
|
|
57
|
+
.local/
|
|
58
|
+
|
|
59
|
+
# Task execution fingerprints
|
|
60
|
+
.task/
|
ocai-0.1.1/CHANGELOG.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Changelog — ocai
|
|
2
|
+
|
|
3
|
+
All notable changes to the `ocai` distribution. Generated from the change files in
|
|
4
|
+
`.changeset/` by the release workflow (ADR 0010); do not edit released sections by hand.
|
|
5
|
+
## 0.1.1 (2026-11-11)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
#### Compose explicit host endpoints and profile aliases
|
|
10
|
+
|
|
11
|
+
Compose explicit host endpoints and profile aliases. (WU-38, #35)
|
|
12
|
+
|
|
13
|
+
#### Select stream framing independently of semantic conversion
|
|
14
|
+
|
|
15
|
+
Select stream framing independently of semantic conversion. (WU-39, #69)
|
|
16
|
+
|
|
17
|
+
#### Validate and preserve canonical URL citations
|
|
18
|
+
|
|
19
|
+
Validate and preserve canonical URL citations. (WU-40, #77)
|
|
20
|
+
|
|
21
|
+
#### Admit provider-executed server-tool definitions with honest support reports
|
|
22
|
+
|
|
23
|
+
Admit provider-executed server-tool definitions with honest support reports. (WU-41, #80)
|
|
24
|
+
|
|
25
|
+
#### Preserve scoped request and replay anchors
|
|
26
|
+
|
|
27
|
+
Preserve scoped request and replay anchors. (WU-42, #83)
|
|
28
|
+
|
|
29
|
+
#### Qualify basic Anthropic web search with replay and measured accounting
|
|
30
|
+
|
|
31
|
+
Qualify basic Anthropic web search with replay and measured accounting. (WU-43, #87)
|
|
32
|
+
|
|
33
|
+
### Fixes
|
|
34
|
+
|
|
35
|
+
#### Package metadata points at the real repository and the DeepSeek plugin has an extra
|
|
36
|
+
|
|
37
|
+
Homepage, Source, Documentation and Changelog URLs now point at `github.com/ocarinow/ocai` and the docs site. `ocai[deepseek]` installs the DeepSeek plugin and `ocai[all]` includes it. No runtime behavior changed.
|
ocai-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: ocai
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: One Common interface for AI: ports & adapters + middleware + a canonical-IR conversion graph. Source-available.
|
|
5
|
+
Project-URL: Homepage, https://github.com/ocarinow/ocai
|
|
6
|
+
Project-URL: Source, https://github.com/ocarinow/ocai
|
|
7
|
+
Project-URL: Documentation, https://ocarinow.github.io/ocai/
|
|
8
|
+
Project-URL: Changelog, https://github.com/ocarinow/ocai/blob/main/python/packages/ocai-core/CHANGELOG.md
|
|
9
|
+
Author: ocai maintainers
|
|
10
|
+
License-Expression: FSL-1.1-ALv2
|
|
11
|
+
Keywords: adapters,ai,interoperability,llm,providers,rag
|
|
12
|
+
Requires-Python: >=3.13
|
|
13
|
+
Provides-Extra: all
|
|
14
|
+
Requires-Dist: httpx>=0.27; extra == 'all'
|
|
15
|
+
Requires-Dist: ocai-anthropic; extra == 'all'
|
|
16
|
+
Requires-Dist: ocai-deepseek; extra == 'all'
|
|
17
|
+
Requires-Dist: ocai-openai; extra == 'all'
|
|
18
|
+
Provides-Extra: anthropic
|
|
19
|
+
Requires-Dist: ocai-anthropic; extra == 'anthropic'
|
|
20
|
+
Provides-Extra: deepseek
|
|
21
|
+
Requires-Dist: ocai-deepseek; extra == 'deepseek'
|
|
22
|
+
Provides-Extra: http
|
|
23
|
+
Requires-Dist: httpx>=0.27; extra == 'http'
|
|
24
|
+
Provides-Extra: openai
|
|
25
|
+
Requires-Dist: ocai-openai; extra == 'openai'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# `ocai` (core)
|
|
29
|
+
|
|
30
|
+
The core distribution of [ocai](https://github.com/ocarinow/ocai), One Common
|
|
31
|
+
interface for AI. Distribution name: **`ocai`**. Requires Python 3.13+.
|
|
32
|
+
|
|
33
|
+
It ships the shared layers into the `ocai` PEP 420 namespace:
|
|
34
|
+
|
|
35
|
+
- `ocai.core` — pure-stdlib data contracts: identity, fidelity, errors,
|
|
36
|
+
`CallResult`, credentials, provider references, the IR envelope, canonical
|
|
37
|
+
JSON.
|
|
38
|
+
- `ocai.chat`, `ocai.embedding`, `ocai.vectorstore` — capability ports and
|
|
39
|
+
canonical IR types; chat is the one exercised end to end today, the other
|
|
40
|
+
two are provisional contracts.
|
|
41
|
+
- `ocai.convert` — governed conversion catalogs, graph planning, edge
|
|
42
|
+
contracts, the conformance harness.
|
|
43
|
+
- `ocai.runtime` — sans-io drivers, bindings, policy decisions, interceptors.
|
|
44
|
+
- `ocai.wire` — HTTP/SSE transport, hosting, wire policies.
|
|
45
|
+
|
|
46
|
+
There is deliberately **no** `ocai/__init__.py` in this distribution, so
|
|
47
|
+
provider distributions (`ocai-openai`, `ocai-anthropic`, community
|
|
48
|
+
`ocai-contrib-*` packages) can add their own subpackages to the same
|
|
49
|
+
namespace. Importing `ocai.core` or `ocai.convert` needs no third-party
|
|
50
|
+
package at all.
|
|
51
|
+
|
|
52
|
+
## Extras
|
|
53
|
+
|
|
54
|
+
- `ocai[http]` — `httpx`, used only by `ocai.wire.transport.HttpxTransport`
|
|
55
|
+
and imported lazily there. Required for live calls.
|
|
56
|
+
- `ocai[openai]`, `ocai[anthropic]` — the sibling plugin distributions
|
|
57
|
+
(`ocai-openai`, `ocai-anthropic`); the extra and the standalone package
|
|
58
|
+
resolve to the same wheel.
|
|
59
|
+
- `ocai[all]` — all of the above.
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
import ocai.chat
|
|
65
|
+
from ocai.chat import ChatRequest
|
|
66
|
+
from ocai.chat import Message
|
|
67
|
+
from ocai.core import ApiKey
|
|
68
|
+
|
|
69
|
+
model = ocai.chat.model("openai/gpt-4o", credentials=ApiKey.from_env("OPENAI_API_KEY"))
|
|
70
|
+
result = model.chat(ChatRequest(messages=[Message.user("Hello!")]))
|
|
71
|
+
print(result.value.message.text, result.report.lossless)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
A live call needs `ocai[http]`, an installed provider plugin, and a real key;
|
|
75
|
+
credentials are explicit and never discovered from the environment unless
|
|
76
|
+
`ApiKey.from_env` is called. Every port returns
|
|
77
|
+
`CallResult(value, route, report, usage, raw)`. The full guide is the
|
|
78
|
+
[Python README](https://github.com/ocarinow/ocai/blob/main/python/README.md).
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
Source-available under FSL-1.1-ALv2 (converting to Apache-2.0 two years after
|
|
83
|
+
each release). ocai is not open source.
|
ocai-0.1.1/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# `ocai` (core)
|
|
2
|
+
|
|
3
|
+
The core distribution of [ocai](https://github.com/ocarinow/ocai), One Common
|
|
4
|
+
interface for AI. Distribution name: **`ocai`**. Requires Python 3.13+.
|
|
5
|
+
|
|
6
|
+
It ships the shared layers into the `ocai` PEP 420 namespace:
|
|
7
|
+
|
|
8
|
+
- `ocai.core` — pure-stdlib data contracts: identity, fidelity, errors,
|
|
9
|
+
`CallResult`, credentials, provider references, the IR envelope, canonical
|
|
10
|
+
JSON.
|
|
11
|
+
- `ocai.chat`, `ocai.embedding`, `ocai.vectorstore` — capability ports and
|
|
12
|
+
canonical IR types; chat is the one exercised end to end today, the other
|
|
13
|
+
two are provisional contracts.
|
|
14
|
+
- `ocai.convert` — governed conversion catalogs, graph planning, edge
|
|
15
|
+
contracts, the conformance harness.
|
|
16
|
+
- `ocai.runtime` — sans-io drivers, bindings, policy decisions, interceptors.
|
|
17
|
+
- `ocai.wire` — HTTP/SSE transport, hosting, wire policies.
|
|
18
|
+
|
|
19
|
+
There is deliberately **no** `ocai/__init__.py` in this distribution, so
|
|
20
|
+
provider distributions (`ocai-openai`, `ocai-anthropic`, community
|
|
21
|
+
`ocai-contrib-*` packages) can add their own subpackages to the same
|
|
22
|
+
namespace. Importing `ocai.core` or `ocai.convert` needs no third-party
|
|
23
|
+
package at all.
|
|
24
|
+
|
|
25
|
+
## Extras
|
|
26
|
+
|
|
27
|
+
- `ocai[http]` — `httpx`, used only by `ocai.wire.transport.HttpxTransport`
|
|
28
|
+
and imported lazily there. Required for live calls.
|
|
29
|
+
- `ocai[openai]`, `ocai[anthropic]` — the sibling plugin distributions
|
|
30
|
+
(`ocai-openai`, `ocai-anthropic`); the extra and the standalone package
|
|
31
|
+
resolve to the same wheel.
|
|
32
|
+
- `ocai[all]` — all of the above.
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import ocai.chat
|
|
38
|
+
from ocai.chat import ChatRequest
|
|
39
|
+
from ocai.chat import Message
|
|
40
|
+
from ocai.core import ApiKey
|
|
41
|
+
|
|
42
|
+
model = ocai.chat.model("openai/gpt-4o", credentials=ApiKey.from_env("OPENAI_API_KEY"))
|
|
43
|
+
result = model.chat(ChatRequest(messages=[Message.user("Hello!")]))
|
|
44
|
+
print(result.value.message.text, result.report.lossless)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
A live call needs `ocai[http]`, an installed provider plugin, and a real key;
|
|
48
|
+
credentials are explicit and never discovered from the environment unless
|
|
49
|
+
`ApiKey.from_env` is called. Every port returns
|
|
50
|
+
`CallResult(value, route, report, usage, raw)`. The full guide is the
|
|
51
|
+
[Python README](https://github.com/ocarinow/ocai/blob/main/python/README.md).
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
Source-available under FSL-1.1-ALv2 (converting to Apache-2.0 two years after
|
|
56
|
+
each release). ocai is not open source.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
# Distribution name is "ocai" — the meta-package / core. Plugin distributions
|
|
7
|
+
# ship additional subpackages into the SAME `ocai` namespace.
|
|
8
|
+
name = "ocai"
|
|
9
|
+
version = "0.1.1" # library public API version (semver), kept stable
|
|
10
|
+
description = "One Common interface for AI: ports & adapters + middleware + a canonical-IR conversion graph. Source-available."
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
requires-python = ">=3.13"
|
|
13
|
+
license = "FSL-1.1-ALv2" # source-available (FSL → Apache-2.0 after 2 years)
|
|
14
|
+
authors = [{ name = "ocai maintainers" }]
|
|
15
|
+
keywords = ["llm", "ai", "providers", "adapters", "rag", "interoperability"]
|
|
16
|
+
# Zero hard runtime deps: the contract layers are pure stdlib (rule 11), and
|
|
17
|
+
# even the wire stack imports httpx lazily inside HttpxTransport, behind the
|
|
18
|
+
# [http] extra — importing ocai.core / ocai.convert never requires httpx.
|
|
19
|
+
dependencies = []
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
# The HTTP leaf: ocai owns the wire (no provider SDKs, rule 12); httpx is used
|
|
23
|
+
# solely by ocai.wire.transport.HttpxTransport and imported lazily there.
|
|
24
|
+
http = ["httpx>=0.27"]
|
|
25
|
+
|
|
26
|
+
# Extras pull in sibling PLUGIN distributions (Airflow pattern). Each plugin
|
|
27
|
+
# adapts a provider via the shared wire stack — never a provider SDK (rule 12).
|
|
28
|
+
# `pip install ocai[openai]` and `pip install ocai-openai` resolve to the same wheel.
|
|
29
|
+
openai = ["ocai-openai"]
|
|
30
|
+
anthropic = ["ocai-anthropic"]
|
|
31
|
+
deepseek = ["ocai-deepseek"]
|
|
32
|
+
|
|
33
|
+
# Convenience aggregate.
|
|
34
|
+
all = ["ocai[http,openai,anthropic,deepseek]"]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/ocarinow/ocai"
|
|
38
|
+
Source = "https://github.com/ocarinow/ocai"
|
|
39
|
+
Documentation = "https://ocarinow.github.io/ocai/"
|
|
40
|
+
Changelog = "https://github.com/ocarinow/ocai/blob/main/python/packages/ocai-core/CHANGELOG.md"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
# Map src/ocai -> the `ocai` namespace in the wheel. There is intentionally NO
|
|
44
|
+
# src/ocai/__init__.py (PEP 420), so sibling distributions can contribute their
|
|
45
|
+
# own subpackages into the same namespace.
|
|
46
|
+
packages = ["src/ocai"]
|
|
47
|
+
|
|
48
|
+
# Add the src dir to sys.path on editable installs so the PEP 420 `ocai`
|
|
49
|
+
# namespace is discovered and merged across multiple editable distributions.
|
|
50
|
+
[tool.hatch.build]
|
|
51
|
+
dev-mode-dirs = ["src"]
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""`ocai.chat` — the chat capability contract (architecture §3).
|
|
2
|
+
|
|
3
|
+
The port (`ChatModel`), the canonical chat IR (`ocai.chat.types`), the
|
|
4
|
+
canonical graph nodes (`ocai.chat.canonical`), the wire-backed facade
|
|
5
|
+
(`WireChatModel`), and the §3.4 front door (`model`). Everything except
|
|
6
|
+
`facade.py` depends on `ocai.core` only; `facade.py` is the one designated
|
|
7
|
+
module that composes convert/runtime/wire behind the port (named `facade`
|
|
8
|
+
so the `ocai.chat.model()` front-door factory is never shadowed by a
|
|
9
|
+
module — the factory below composes lazily, inside the call, through the
|
|
10
|
+
same sanctioned seams).
|
|
11
|
+
|
|
12
|
+
The curated exports below are the user-facing surface: build a model with
|
|
13
|
+
`model("openai/gpt-5", credentials=...)`, build a `ChatRequest` from
|
|
14
|
+
`Message`s and `Tool`s, call any `ChatModel`, receive a
|
|
15
|
+
`CallResult[ChatResponse]` or a `ChatStream` of `ChatChunk`s.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import TYPE_CHECKING
|
|
21
|
+
|
|
22
|
+
from ocai.chat.canonical import CANONICAL_CHAT_CHUNK
|
|
23
|
+
from ocai.chat.canonical import CANONICAL_CHAT_REQUEST
|
|
24
|
+
from ocai.chat.canonical import CANONICAL_CHAT_RESPONSE
|
|
25
|
+
from ocai.chat.canonical import OCAI_INTERFACE
|
|
26
|
+
from ocai.chat.canonical import REVISION
|
|
27
|
+
from ocai.chat.facade import WireChatModel
|
|
28
|
+
from ocai.chat.protocol import ChatModel
|
|
29
|
+
from ocai.chat.types import AsyncChatStream
|
|
30
|
+
from ocai.chat.types import ChatChunk
|
|
31
|
+
from ocai.chat.types import ChatConfig
|
|
32
|
+
from ocai.chat.types import ChatRequest
|
|
33
|
+
from ocai.chat.types import ChatResponse
|
|
34
|
+
from ocai.chat.types import ChatStream
|
|
35
|
+
from ocai.chat.types import FinishInfo
|
|
36
|
+
from ocai.chat.types import Message
|
|
37
|
+
from ocai.chat.types import Part
|
|
38
|
+
from ocai.chat.types import Role
|
|
39
|
+
from ocai.chat.types import ServerTool
|
|
40
|
+
from ocai.chat.types import ServerToolLocation
|
|
41
|
+
from ocai.chat.types import StreamAccumulator
|
|
42
|
+
from ocai.chat.types import StreamEnd
|
|
43
|
+
from ocai.chat.types import StreamOutcome
|
|
44
|
+
from ocai.chat.types import Tool
|
|
45
|
+
from ocai.chat.types import ToolChoice
|
|
46
|
+
from ocai.chat.types import ToolDefinition
|
|
47
|
+
from ocai.chat.types import ToolUnknown
|
|
48
|
+
from ocai.chat.types import Usage
|
|
49
|
+
|
|
50
|
+
if TYPE_CHECKING:
|
|
51
|
+
from collections.abc import Sequence
|
|
52
|
+
|
|
53
|
+
from ocai.chat.facade import InterceptedChatModel
|
|
54
|
+
from ocai.chat.facade import ObservedChatModel
|
|
55
|
+
from ocai.convert.catalog import Catalog
|
|
56
|
+
from ocai.core.credentials import Credential
|
|
57
|
+
from ocai.core.fidelity import FidelityPolicy
|
|
58
|
+
from ocai.core.fidelity import Route
|
|
59
|
+
from ocai.runtime.decisions import WirePolicy
|
|
60
|
+
from ocai.runtime.interceptors import CallInterceptor
|
|
61
|
+
from ocai.runtime.observe import Observer
|
|
62
|
+
from ocai.wire.hosting import Host
|
|
63
|
+
from ocai.wire.transport import SyncTransport
|
|
64
|
+
from ocai.wire.transport import Transport
|
|
65
|
+
|
|
66
|
+
__all__ = [
|
|
67
|
+
"CANONICAL_CHAT_CHUNK",
|
|
68
|
+
"CANONICAL_CHAT_REQUEST",
|
|
69
|
+
"CANONICAL_CHAT_RESPONSE",
|
|
70
|
+
"OCAI_INTERFACE",
|
|
71
|
+
"REVISION",
|
|
72
|
+
"AsyncChatStream",
|
|
73
|
+
"ChatChunk",
|
|
74
|
+
"ChatConfig",
|
|
75
|
+
"ChatModel",
|
|
76
|
+
"ChatRequest",
|
|
77
|
+
"ChatResponse",
|
|
78
|
+
"ChatStream",
|
|
79
|
+
"FinishInfo",
|
|
80
|
+
"Message",
|
|
81
|
+
"Part",
|
|
82
|
+
"Role",
|
|
83
|
+
"StreamAccumulator",
|
|
84
|
+
"StreamEnd",
|
|
85
|
+
"StreamOutcome",
|
|
86
|
+
"ServerTool",
|
|
87
|
+
"ServerToolLocation",
|
|
88
|
+
"ToolUnknown",
|
|
89
|
+
"ToolDefinition",
|
|
90
|
+
"Tool",
|
|
91
|
+
"ToolChoice",
|
|
92
|
+
"Usage",
|
|
93
|
+
"WireChatModel",
|
|
94
|
+
"model",
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def model(
|
|
99
|
+
spec: str,
|
|
100
|
+
*,
|
|
101
|
+
credentials: Credential,
|
|
102
|
+
host: Host | str | None = None,
|
|
103
|
+
catalog: Catalog | None = None,
|
|
104
|
+
transport: Transport | SyncTransport | None = None,
|
|
105
|
+
interceptors: Sequence[CallInterceptor] = (),
|
|
106
|
+
policies: Sequence[WirePolicy] = (),
|
|
107
|
+
observers: Sequence[Observer] = (),
|
|
108
|
+
conversion_policy: FidelityPolicy | None = None,
|
|
109
|
+
interface: str | None = None,
|
|
110
|
+
tag: str | None = None,
|
|
111
|
+
) -> WireChatModel | InterceptedChatModel | ObservedChatModel:
|
|
112
|
+
"""Build a chat model from one spec string — the §3.4 core front door.
|
|
113
|
+
|
|
114
|
+
The whole provider+model choice is ONE data value
|
|
115
|
+
(`model(os.environ["OCAI_MODEL"], ...)` — switching providers is a
|
|
116
|
+
deployment change, zero code):
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
import ocai.chat
|
|
120
|
+
from ocai.core import ApiKey
|
|
121
|
+
|
|
122
|
+
chat = ocai.chat.model("openai/gpt-5", credentials=ApiKey.from_env("OPENAI_API_KEY"))
|
|
123
|
+
res = chat.chat(ChatRequest(messages=[Message.user("hello")]))
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The factory resolves the provider against the catalog, builds the codec
|
|
127
|
+
from the serving fragment's registered component factory, composes the
|
|
128
|
+
`Binding` internally (a `Direct` host at the fragment's declared
|
|
129
|
+
first-party origin unless `host` is passed), and binds the model name to
|
|
130
|
+
the returned object — requests stay pure conversation. Unpinned
|
|
131
|
+
resolution considers only interfaces the catalog can actually *serve*
|
|
132
|
+
(those with a registered chat component); a nodes-only stub interface is
|
|
133
|
+
reachable by explicit `interface=` pin only.
|
|
134
|
+
|
|
135
|
+
Everything composed here is reachable by hand (`WireChatModel` + a
|
|
136
|
+
`Binding` + a `Catalog`); this door adds nothing a power user cannot
|
|
137
|
+
build — and the plugin `chat_model` helpers are sugar over exactly this
|
|
138
|
+
path, never a separate one.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
spec: The model spec `"<provider>/<model>"` (the LiteLLM/OpenRouter
|
|
142
|
+
convention). Split on the FIRST slash only — the model part may
|
|
143
|
+
itself contain slashes (`"openai/meta-llama/Llama-3.3-70B"` on a
|
|
144
|
+
vLLM gateway). The spec never carries an interface; pinning is
|
|
145
|
+
the `interface`/`tag` kwargs.
|
|
146
|
+
credentials: The credential the driver applies at the leaf, per
|
|
147
|
+
attempt. Required — there is no ambient discovery (rule 13).
|
|
148
|
+
host: A hosting target, a host name explicitly declared in the catalog,
|
|
149
|
+
or None for first-party `Direct` at the dialect's declared origin.
|
|
150
|
+
catalog: The governed conversion catalog, or None for the
|
|
151
|
+
entry-point-discovered `default_catalog()`.
|
|
152
|
+
transport: The wire transport, or None for a fresh `HttpxTransport`.
|
|
153
|
+
interceptors: The port-tier chain wrapped around the facade,
|
|
154
|
+
outermost first; empty for the bare facade.
|
|
155
|
+
conversion_policy: Caller-defined acceptance over field losses and shifts.
|
|
156
|
+
observers: Authorized passive metadata-only lifecycle observers.
|
|
157
|
+
policies: The wire-tier hooks (retry, rate-limit, wiretap) the
|
|
158
|
+
drivers consult on every attempt, in order.
|
|
159
|
+
interface: An interface pin (e.g. `"chat_completions"`), or None to
|
|
160
|
+
let the catalog choose among serveable interfaces.
|
|
161
|
+
tag: A revision-tag pin, or None for latest eligible.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
A composed `ChatModel` bound to the parsed model name.
|
|
165
|
+
|
|
166
|
+
Raises:
|
|
167
|
+
ValueError: If `spec` is not of the `"<provider>/<model>"` form.
|
|
168
|
+
ResolutionError: If the provider is not in the catalog (the message
|
|
169
|
+
names the providers that are), or no serveable interface
|
|
170
|
+
matches the pins.
|
|
171
|
+
LookupError: If a pinned interface has no registered component
|
|
172
|
+
factory (a declared stub, or a missing plugin distribution).
|
|
173
|
+
CompositionError: If the dialect declares no first-party endpoint
|
|
174
|
+
and no explicit `host` was given.
|
|
175
|
+
"""
|
|
176
|
+
# Deferred imports: `ocai.chat` sits below convert/runtime/wire in the
|
|
177
|
+
# layer map, so the front door reaches up only at call time, through the
|
|
178
|
+
# same documented seams as the facade (see [tool.importlinter] ignores).
|
|
179
|
+
from ocai.chat.facade import InterceptedChatModel
|
|
180
|
+
from ocai.chat.facade import ObservedChatModel
|
|
181
|
+
from ocai.convert.catalog import ResolutionError
|
|
182
|
+
from ocai.convert.catalog import default_catalog
|
|
183
|
+
from ocai.core.identity import CANONICAL_PROVIDER
|
|
184
|
+
from ocai.core.identity import CHAT
|
|
185
|
+
from ocai.runtime.binding import Binding
|
|
186
|
+
from ocai.runtime.binding import CompositionError
|
|
187
|
+
from ocai.wire.hosting import Custom
|
|
188
|
+
from ocai.wire.hosting import Direct
|
|
189
|
+
|
|
190
|
+
provider, slash, model_name = spec.partition("/")
|
|
191
|
+
if not slash or not provider or not model_name:
|
|
192
|
+
raise ValueError(
|
|
193
|
+
f"chat model spec must be '<provider>/<model>' (split on the first "
|
|
194
|
+
f"slash; the model part may contain slashes), got {spec!r}"
|
|
195
|
+
)
|
|
196
|
+
composed = catalog if catalog is not None else default_catalog()
|
|
197
|
+
components = composed.components_for(provider, CHAT)
|
|
198
|
+
if not components:
|
|
199
|
+
known = sorted(
|
|
200
|
+
{
|
|
201
|
+
node.provider
|
|
202
|
+
for fragment in composed.fragments
|
|
203
|
+
for node in fragment.nodes
|
|
204
|
+
if node.provider != CANONICAL_PROVIDER
|
|
205
|
+
}
|
|
206
|
+
)
|
|
207
|
+
raise ResolutionError(
|
|
208
|
+
f"no chat component for provider {provider!r} (from spec {spec!r}) in "
|
|
209
|
+
f"this catalog; providers composed here: {', '.join(known) or 'none'}. "
|
|
210
|
+
f"If the plugin is not installed, install it with: pip install "
|
|
211
|
+
f"ocai-{provider}"
|
|
212
|
+
)
|
|
213
|
+
if interface is not None:
|
|
214
|
+
route = composed.resolve(CHAT, provider, interface=interface, tag=tag)
|
|
215
|
+
else:
|
|
216
|
+
# Unpinned: auto-select only among component-backed interfaces (the
|
|
217
|
+
# ones this catalog can serve). Multiple eligible sibling APIs require
|
|
218
|
+
# an explicit pin; newer dates do not imply interface preference.
|
|
219
|
+
routes: list[Route] = []
|
|
220
|
+
for component in components:
|
|
221
|
+
try:
|
|
222
|
+
routes.append(
|
|
223
|
+
composed.resolve(CHAT, provider, interface=component.interface, tag=tag)
|
|
224
|
+
)
|
|
225
|
+
except ResolutionError:
|
|
226
|
+
continue
|
|
227
|
+
if not routes:
|
|
228
|
+
pin = f" tag {tag!r}" if tag is not None else ""
|
|
229
|
+
raise ResolutionError(
|
|
230
|
+
f"no serveable chat interface for provider {provider!r}{pin} in this catalog"
|
|
231
|
+
)
|
|
232
|
+
if len(routes) > 1:
|
|
233
|
+
names = ", ".join(sorted(route.node.interface for route in routes))
|
|
234
|
+
raise ResolutionError(
|
|
235
|
+
f"ambiguous serveable interfaces for {provider}: {names}; "
|
|
236
|
+
"pass interface= explicitly"
|
|
237
|
+
)
|
|
238
|
+
route = routes[0]
|
|
239
|
+
codec = composed.component(provider, route.node.interface, CHAT)()
|
|
240
|
+
if isinstance(host, str):
|
|
241
|
+
host = Custom.from_descriptor(composed.host(host))
|
|
242
|
+
if host is None:
|
|
243
|
+
base_url = codec.dialect_traits().first_party_base_url
|
|
244
|
+
if base_url is None:
|
|
245
|
+
raise CompositionError(
|
|
246
|
+
f"{provider}/{route.node.interface} declares no first-party "
|
|
247
|
+
"endpoint (DialectTraits.first_party_base_url); pass host= "
|
|
248
|
+
"explicitly (Direct/Custom/Vertex, ocai.wire.hosting)"
|
|
249
|
+
)
|
|
250
|
+
host = Direct(base_url)
|
|
251
|
+
if transport is None:
|
|
252
|
+
from ocai.wire.transport import HttpxTransport
|
|
253
|
+
|
|
254
|
+
transport = HttpxTransport(provider=provider)
|
|
255
|
+
binding = Binding(host=host, credentials=credentials, transport=transport)
|
|
256
|
+
facade: WireChatModel | InterceptedChatModel | ObservedChatModel = WireChatModel(
|
|
257
|
+
codec,
|
|
258
|
+
binding,
|
|
259
|
+
composed,
|
|
260
|
+
model_name=model_name,
|
|
261
|
+
interface=interface,
|
|
262
|
+
tag=tag,
|
|
263
|
+
policies=policies,
|
|
264
|
+
attempt_observers=observers,
|
|
265
|
+
conversion_policy=conversion_policy,
|
|
266
|
+
)
|
|
267
|
+
if interceptors:
|
|
268
|
+
facade = InterceptedChatModel(facade, interceptors)
|
|
269
|
+
if observers:
|
|
270
|
+
facade = ObservedChatModel(facade, observers)
|
|
271
|
+
return facade
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""The canonical chat graph nodes (architecture §2.1, §3).
|
|
2
|
+
|
|
3
|
+
The canonical IR is **just another interface** — provider `ocai`, interface
|
|
4
|
+
`ocai` — with dated revisions. These constants name the three canonical chat
|
|
5
|
+
nodes at the current revision; conversion edges to/from every provider
|
|
6
|
+
interface terminate on them, and IR self-migration (`ocai@2026-07 →
|
|
7
|
+
ocai@2027-01`) is ordinary edge traversal between two such node sets.
|
|
8
|
+
|
|
9
|
+
The chat IR dataclasses stamp these on `__node__`, and `WireChatModel` uses
|
|
10
|
+
them as the fixed end of every request/response/chunk conversion.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Final
|
|
16
|
+
|
|
17
|
+
from ocai.core.identity import CANONICAL_INTERFACE
|
|
18
|
+
from ocai.core.identity import CANONICAL_REVISION
|
|
19
|
+
from ocai.core.identity import CHAT_CHUNK
|
|
20
|
+
from ocai.core.identity import CHAT_REQUEST
|
|
21
|
+
from ocai.core.identity import CHAT_RESPONSE
|
|
22
|
+
from ocai.core.identity import NodeId
|
|
23
|
+
from ocai.core.identity import canonical_node
|
|
24
|
+
|
|
25
|
+
# The interface name of the canonical IR — canonical is not a special case:
|
|
26
|
+
# it is provider "ocai" publishing interface "ocai".
|
|
27
|
+
OCAI_INTERFACE: Final[str] = CANONICAL_INTERFACE
|
|
28
|
+
|
|
29
|
+
# The current canonical chat revision tag — a dated token on the `ocai`
|
|
30
|
+
# interface ("2026-07"), never the library semver, never a provider tag.
|
|
31
|
+
REVISION: Final[str] = CANONICAL_REVISION
|
|
32
|
+
|
|
33
|
+
# The canonical chat nodes at the current revision — the fixed endpoints of
|
|
34
|
+
# every chat conversion path.
|
|
35
|
+
CANONICAL_CHAT_REQUEST: Final[NodeId] = canonical_node(CHAT_REQUEST, REVISION)
|
|
36
|
+
CANONICAL_CHAT_RESPONSE: Final[NodeId] = canonical_node(CHAT_RESPONSE, REVISION)
|
|
37
|
+
CANONICAL_CHAT_CHUNK: Final[NodeId] = canonical_node(CHAT_CHUNK, REVISION)
|