tama-rb 0.1.2
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +11 -0
- data/LICENSE +21 -0
- data/README.md +228 -0
- data/lib/tama/agentic/author.rb +23 -0
- data/lib/tama/agentic/message.rb +11 -0
- data/lib/tama/agentic/message_params.rb +46 -0
- data/lib/tama/agentic/service.rb +50 -0
- data/lib/tama/agentic/thread.rb +22 -0
- data/lib/tama/broadcast.rb +76 -0
- data/lib/tama/client.rb +20 -0
- data/lib/tama/error.rb +45 -0
- data/lib/tama/http.rb +169 -0
- data/lib/tama/memory/entity.rb +11 -0
- data/lib/tama/memory/entity_params.rb +34 -0
- data/lib/tama/memory/service.rb +21 -0
- data/lib/tama/model.rb +81 -0
- data/lib/tama/neural/klass.rb +14 -0
- data/lib/tama/neural/operation.rb +20 -0
- data/lib/tama/neural/operation_params.rb +27 -0
- data/lib/tama/neural/service.rb +34 -0
- data/lib/tama/neural/space.rb +11 -0
- data/lib/tama/params.rb +29 -0
- data/lib/tama/perception/chain.rb +11 -0
- data/lib/tama/perception/concept.rb +22 -0
- data/lib/tama/perception/generator.rb +19 -0
- data/lib/tama/perception/service.rb +29 -0
- data/lib/tama/sse_parser.rb +60 -0
- data/lib/tama/version.rb +5 -0
- data/lib/tama.rb +27 -0
- metadata +125 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a8e4b7c980bd24d10783272afb5e38e76a8863d6879d849cc9b62f00493abbab
|
|
4
|
+
data.tar.gz: 786b9d8c94414e52f0f8b9879317de943bdb2d59bd5dc74a730c9e7b78bc2f04
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 88083d85c2da41b50c4c4bd231005e53e94f05222235dcd26817dfb23d92fbbc80bd6645c4c59dd2bb7c8c96dbe908c293d726466605e1e4ccb968e7546b3717
|
|
7
|
+
data.tar.gz: 340e80ecebb7cefad5e2e6341e82e740a116e76bb989fc6f342aa3baa8e27b7fdd47d55624c9f92300686573c728372687fcfc84b44cde9df001e59f2c8e36ec
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.1 - 2026-08-21
|
|
4
|
+
|
|
5
|
+
- Fix the Trusted Publishing workflow and align release tags with Bundler's `v` prefix convention.
|
|
6
|
+
|
|
7
|
+
## 0.1.0 - 2026-08-21
|
|
8
|
+
|
|
9
|
+
- Initial release with Neural, Memory, Perception, and Agentic services.
|
|
10
|
+
- Add strict immutable models and the complete Broadcast parser hierarchy.
|
|
11
|
+
- Add typed errors, retry support, path encoding, and incremental SSE parsing.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zack Siri
|
|
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.
|
data/README.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# Tama Ruby
|
|
2
|
+
|
|
3
|
+
`tama-rb` is the Ruby client for Tama APIs. It provides namespace-aware Faraday services, strict immutable response models, typed errors, and incremental server-sent event parsing.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add the gem to your bundle:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem "tama-rb", "~> 0.1.0"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then run `bundle install` and load the client with:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
require "tama"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Ruby 3.2 or newer is required.
|
|
20
|
+
|
|
21
|
+
## Client Setup
|
|
22
|
+
|
|
23
|
+
Create a client with the base URL for the API namespace being used:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
client = Tama::Client.new(
|
|
27
|
+
base_url: "https://api.example.com/provision",
|
|
28
|
+
headers: {"Authorization" => "Bearer token"},
|
|
29
|
+
timeout: 300,
|
|
30
|
+
retries: 2
|
|
31
|
+
)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Timeouts are expressed in seconds. Global headers are merged with per-request headers, with per-request values taking precedence. Retries use `faraday-retry` for retryable requests and statuses. The default transport is Faraday's Net::HTTP adapter.
|
|
35
|
+
|
|
36
|
+
An existing connection can be injected when custom Faraday middleware or an adapter is needed. The injected connection is used as configured; `base_url` is still required for request URL construction and namespace validation.
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
connection = Faraday.new do |faraday|
|
|
40
|
+
faraday.request :json
|
|
41
|
+
faraday.request :retry, max: 3
|
|
42
|
+
faraday.adapter :net_http
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
client = Tama::Client.new(
|
|
46
|
+
base_url: "https://api.example.com/perception",
|
|
47
|
+
connection: connection
|
|
48
|
+
)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Namespace URLs
|
|
52
|
+
|
|
53
|
+
The final base URL path segment is validated for each operation. Prefixes such as `/api/v1` are preserved.
|
|
54
|
+
|
|
55
|
+
| Namespace | Operations |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| `provision` | Neural space/class/operation, perception chain lookup |
|
|
58
|
+
| `ingest` | Memory entity creation |
|
|
59
|
+
| `perception` | Perception concept listing |
|
|
60
|
+
| `agentic` | Agentic message creation and streaming |
|
|
61
|
+
|
|
62
|
+
Use separate clients when an application calls multiple namespaces.
|
|
63
|
+
|
|
64
|
+
## Neural
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
client = Tama::Client.new(base_url: "https://api.example.com/provision")
|
|
68
|
+
|
|
69
|
+
space = client.neural.get_space("my-space")
|
|
70
|
+
klass = client.neural.get_class(space, "article")
|
|
71
|
+
|
|
72
|
+
operation = client.neural.create_class_operation(
|
|
73
|
+
klass,
|
|
74
|
+
chain_ids: ["chain-1", "chain-2"],
|
|
75
|
+
node_type: "compute"
|
|
76
|
+
)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`get_class` accepts a `Tama::Neural::Space` or a space ID. `create_class_operation` accepts a `Tama::Neural::Klass` or class ID and either a hash or `Tama::Neural::OperationParams`.
|
|
80
|
+
|
|
81
|
+
## Memory
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
client = Tama::Client.new(base_url: "https://api.example.com/ingest")
|
|
85
|
+
|
|
86
|
+
entity = client.memory.create_entity(
|
|
87
|
+
klass,
|
|
88
|
+
identifier: "article-123",
|
|
89
|
+
record: {title: "Tama with Ruby"},
|
|
90
|
+
validate_record: true
|
|
91
|
+
)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`validate_record` defaults to `true`. A `Tama::Memory::EntityParams` object can be passed instead of a hash.
|
|
95
|
+
|
|
96
|
+
## Perception
|
|
97
|
+
|
|
98
|
+
Chain lookup uses a provision client:
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
client = Tama::Client.new(base_url: "https://api.example.com/provision")
|
|
102
|
+
chain = client.perception.get_chain(space, "summarize")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`get_chain` accepts a `Tama::Neural::Space` or space ID. Concept listing uses a perception client and supports query parameters:
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
client = Tama::Client.new(base_url: "https://api.example.com/perception")
|
|
109
|
+
|
|
110
|
+
concepts = client.perception.list_concepts(
|
|
111
|
+
"entity-123",
|
|
112
|
+
query: {limit: 20, offset: 0, relation: "reply"}
|
|
113
|
+
)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Agentic
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
client = Tama::Client.new(base_url: "https://api.example.com/agentic")
|
|
120
|
+
|
|
121
|
+
message = client.agentic.create_message(
|
|
122
|
+
recipient: "user-123",
|
|
123
|
+
identifier: "message-123",
|
|
124
|
+
content: "Hello",
|
|
125
|
+
index: 1,
|
|
126
|
+
author: {identifier: "agent-1", source: "system"},
|
|
127
|
+
thread: {identifier: "thread-1"}
|
|
128
|
+
)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Message class defaults are `user-message`, `actor`, and `thread`. A `Tama::Agentic::MessageParams` object can also be supplied.
|
|
132
|
+
|
|
133
|
+
### Streaming
|
|
134
|
+
|
|
135
|
+
Streaming requires either `callback:` or a block. Each handler call receives one decoded JSON event. The parser buffers fragmented chunks, supports LF and CRLF separators, multiline `data:` fields, multiple events per chunk, and `[DONE]`.
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
response = client.agentic.create_message(message_attributes, stream: true) do |event|
|
|
139
|
+
puts event.inspect
|
|
140
|
+
end
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The streaming form returns the successful `Faraday::Response`. Non-streaming creation parses the API's top-level JSON response into a `Tama::Agentic::Message`.
|
|
144
|
+
|
|
145
|
+
## Request Options
|
|
146
|
+
|
|
147
|
+
Every operation accepts `headers:` and `timeout:` request options where applicable:
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
space = client.neural.get_space(
|
|
151
|
+
"my-space",
|
|
152
|
+
headers: {"X-Request-ID" => "request-1"},
|
|
153
|
+
timeout: 30
|
|
154
|
+
)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Dynamic path segments are percent-encoded. Query parameters should be passed only through `query:` on `list_concepts`.
|
|
158
|
+
|
|
159
|
+
## Models
|
|
160
|
+
|
|
161
|
+
Response and parser models are immutable Ruby `Data` objects:
|
|
162
|
+
|
|
163
|
+
- `Tama::Neural::Space`, `Tama::Neural::Klass`, `Tama::Neural::Operation`, `Tama::Neural::OperationParams`
|
|
164
|
+
- `Tama::Memory::Entity`, `Tama::Memory::EntityParams`
|
|
165
|
+
- `Tama::Perception::Chain`, `Tama::Perception::Concept`, `Tama::Perception::Generator`
|
|
166
|
+
- `Tama::Agentic::Message`, `Tama::Agentic::MessageParams`, `Tama::Agentic::Author`, `Tama::Agentic::Thread`
|
|
167
|
+
- `Tama::Broadcast` and `Tama::Broadcast::Event`, `Metadata`, `Step`, `Concept`, `Thought`, `Chain`, `Branch`, `Flow`, `OriginEntity`
|
|
168
|
+
|
|
169
|
+
Broadcast payloads are parsed without making an HTTP request. Top-level `event` and `step` values are required:
|
|
170
|
+
|
|
171
|
+
```ruby
|
|
172
|
+
broadcast = Tama::Broadcast.parse(
|
|
173
|
+
event: {name: "step.updated", domain: "workflow"},
|
|
174
|
+
step: {id: "step-1", concepts: []}
|
|
175
|
+
)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Response models also expose `.parse(hash_or_json)`. Invalid JSON, required fields, types, enums, or nested models raise rather than creating partial empty models.
|
|
179
|
+
|
|
180
|
+
## Errors
|
|
181
|
+
|
|
182
|
+
Failures raise subclasses of `Tama::Error`:
|
|
183
|
+
|
|
184
|
+
- `Tama::Error::ConfigurationError` for malformed client configuration
|
|
185
|
+
- `Tama::Error::InvalidNamespaceError` for a mismatched final base URL segment
|
|
186
|
+
- `Tama::Error::ValidationError` for local parameters and HTTP 422 responses; inspect `errors`
|
|
187
|
+
- `Tama::Error::NotFoundError` for HTTP 404 responses
|
|
188
|
+
- `Tama::Error::HTTPError` for other non-2xx responses; inspect `status` and `body`
|
|
189
|
+
- `Tama::Error::TransportError` for Faraday transport failures; inspect `cause`
|
|
190
|
+
- `Tama::Error::ParseError` for malformed JSON, response envelopes, models, or SSE events
|
|
191
|
+
|
|
192
|
+
All HTTP statuses from 200 through 299 are successful. Neural, Memory, and Perception model endpoints require the API's exact `{ "data": ... }` response envelope; Agentic message creation uses its top-level response object.
|
|
193
|
+
|
|
194
|
+
## Development
|
|
195
|
+
|
|
196
|
+
```sh
|
|
197
|
+
bin/setup
|
|
198
|
+
bundle exec rspec
|
|
199
|
+
bundle exec standardrb
|
|
200
|
+
bundle exec rake build
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
`bin/console` starts an IRB session with Tama loaded.
|
|
204
|
+
|
|
205
|
+
## Releasing
|
|
206
|
+
|
|
207
|
+
Releases use RubyGems Trusted Publishing, so the repository does not store a long-lived RubyGems API key.
|
|
208
|
+
|
|
209
|
+
Before the first release, create a pending trusted publisher at <https://rubygems.org/profile/oidc/pending_trusted_publishers> with:
|
|
210
|
+
|
|
211
|
+
- Gem name: `tama-rb`
|
|
212
|
+
- Repository owner: `upmaru`
|
|
213
|
+
- Repository name: `tama-rb`
|
|
214
|
+
- Workflow filename: `publish.yml`
|
|
215
|
+
- GitHub environment: `release`
|
|
216
|
+
|
|
217
|
+
Create the `release` environment in the GitHub repository settings. After updating `Tama::VERSION` and `CHANGELOG.md`, publish by pushing a matching version tag:
|
|
218
|
+
|
|
219
|
+
```sh
|
|
220
|
+
git tag v0.1.0
|
|
221
|
+
git push origin v0.1.0
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The first successful workflow run publishes the gem and converts the pending publisher into a trusted publisher. RubyGems organizations are currently in private beta; organization ownership requires beta access followed by transferring the published gem to the organization.
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
The gem is available under the MIT License.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tama
|
|
4
|
+
module Agentic
|
|
5
|
+
Author = Data.define(:identifier, :source, :klass) do
|
|
6
|
+
def initialize(identifier:, source:, klass: "actor")
|
|
7
|
+
Params.require_string!({"identifier" => identifier}, "identifier")
|
|
8
|
+
Params.require_string!({"source" => source}, "source")
|
|
9
|
+
Params.require_string!({"class" => klass}, "class")
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.parse(value)
|
|
14
|
+
data = Params.hash(value, "author")
|
|
15
|
+
new(identifier: data["identifier"], source: data["source"], klass: data.fetch("class", "actor"))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_h
|
|
19
|
+
{"identifier" => identifier, "source" => source, "class" => klass}
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tama
|
|
4
|
+
module Agentic
|
|
5
|
+
Message = Model.define(
|
|
6
|
+
:id, :status, :message, :identifier, :current_state,
|
|
7
|
+
required: %i[id],
|
|
8
|
+
types: {id: String, status: String, message: Hash, identifier: String, current_state: String}
|
|
9
|
+
)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tama
|
|
4
|
+
module Agentic
|
|
5
|
+
MessageParams = Data.define(:recipient, :klass, :identifier, :content, :index, :author, :thread) do
|
|
6
|
+
def initialize(recipient:, identifier:, content:, index:, author:, thread:, klass: "user-message")
|
|
7
|
+
Params.require_string!({"recipient" => recipient}, "recipient")
|
|
8
|
+
Params.require_string!({"class" => klass}, "class")
|
|
9
|
+
Params.require_string!({"identifier" => identifier}, "identifier")
|
|
10
|
+
Params.require_string!({"content" => content}, "content")
|
|
11
|
+
unless index.is_a?(Integer)
|
|
12
|
+
raise Error::ValidationError.new("index is required", errors: {index: "must be an integer"})
|
|
13
|
+
end
|
|
14
|
+
raise Error::ValidationError, "author is invalid" unless author.is_a?(Author)
|
|
15
|
+
raise Error::ValidationError, "thread is invalid" unless thread.is_a?(Thread)
|
|
16
|
+
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.parse(value)
|
|
21
|
+
data = Params.hash(value, "message")
|
|
22
|
+
new(
|
|
23
|
+
recipient: data["recipient"],
|
|
24
|
+
klass: data.fetch("class", "user-message"),
|
|
25
|
+
identifier: data["identifier"],
|
|
26
|
+
content: data["content"],
|
|
27
|
+
index: data["index"],
|
|
28
|
+
author: Author.parse(data["author"]),
|
|
29
|
+
thread: Thread.parse(data["thread"])
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def to_h
|
|
34
|
+
{
|
|
35
|
+
"recipient" => recipient,
|
|
36
|
+
"class" => klass,
|
|
37
|
+
"identifier" => identifier,
|
|
38
|
+
"content" => content,
|
|
39
|
+
"index" => index,
|
|
40
|
+
"author" => author.to_h,
|
|
41
|
+
"thread" => thread.to_h
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tama
|
|
4
|
+
module Agentic
|
|
5
|
+
class Service
|
|
6
|
+
def initialize(http)
|
|
7
|
+
@http = http
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def create_message(attributes = nil, stream: false, callback: nil, headers: {}, timeout: nil, **attribute_keywords, &block)
|
|
11
|
+
@http.validate_namespace!("agentic")
|
|
12
|
+
attributes ||= attribute_keywords
|
|
13
|
+
params = attributes.is_a?(MessageParams) ? attributes : MessageParams.parse(attributes)
|
|
14
|
+
handler = callback || block
|
|
15
|
+
|
|
16
|
+
if stream
|
|
17
|
+
raise Error::ValidationError, "A callback or block is required when streaming" unless handler.respond_to?(:call)
|
|
18
|
+
|
|
19
|
+
create_stream(params, handler, headers:, timeout:)
|
|
20
|
+
else
|
|
21
|
+
response = @http.request(
|
|
22
|
+
:post,
|
|
23
|
+
"messages",
|
|
24
|
+
body: {"stream" => false, "message" => params.to_h},
|
|
25
|
+
headers:,
|
|
26
|
+
timeout:
|
|
27
|
+
)
|
|
28
|
+
@http.parse_body(response, Message)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def create_stream(params, handler, headers:, timeout:)
|
|
35
|
+
parser = SSEParser.new(handler)
|
|
36
|
+
stream_headers = {"Accept" => "text/event-stream"}.merge(headers)
|
|
37
|
+
response = @http.request(
|
|
38
|
+
:post,
|
|
39
|
+
"messages",
|
|
40
|
+
body: {"stream" => true, "message" => params.to_h},
|
|
41
|
+
headers: stream_headers,
|
|
42
|
+
timeout:
|
|
43
|
+
) { |chunk, *| parser.feed(chunk) }
|
|
44
|
+
@http.check_status!(response)
|
|
45
|
+
parser.finish
|
|
46
|
+
response
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tama
|
|
4
|
+
module Agentic
|
|
5
|
+
Thread = Data.define(:identifier, :klass) do
|
|
6
|
+
def initialize(identifier:, klass: "thread")
|
|
7
|
+
Params.require_string!({"identifier" => identifier}, "identifier")
|
|
8
|
+
Params.require_string!({"class" => klass}, "class")
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.parse(value)
|
|
13
|
+
data = Params.hash(value, "thread")
|
|
14
|
+
new(identifier: data["identifier"], klass: data.fetch("class", "thread"))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def to_h
|
|
18
|
+
{"identifier" => identifier, "class" => klass}
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tama
|
|
4
|
+
Broadcast = Model.define(
|
|
5
|
+
:event, :step,
|
|
6
|
+
required: %i[event step],
|
|
7
|
+
transforms: {
|
|
8
|
+
event: ->(value) { Broadcast::Event.parse(value) },
|
|
9
|
+
step: ->(value) { Broadcast::Step.parse(value) }
|
|
10
|
+
}
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
class Broadcast
|
|
14
|
+
Metadata = Model.define(
|
|
15
|
+
:changes, :comment, :parameters,
|
|
16
|
+
types: {changes: Hash, comment: String, parameters: Hash}
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
Event = Model.define(
|
|
20
|
+
:name, :domain, :metadata,
|
|
21
|
+
types: {name: String, domain: String, metadata: Metadata},
|
|
22
|
+
transforms: {metadata: ->(value) { Metadata.parse(value) }}
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
Concept = Model.define(
|
|
26
|
+
:id, :relation, :content,
|
|
27
|
+
types: {id: String, relation: String, content: Hash}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
Chain = Model.define(
|
|
31
|
+
:id, :name,
|
|
32
|
+
types: {id: String, name: String}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
Thought = Model.define(
|
|
36
|
+
:chain, :relation, :index,
|
|
37
|
+
types: {chain: Chain, relation: String, index: Integer},
|
|
38
|
+
transforms: {chain: ->(value) { Chain.parse(value) }}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
OriginEntity = Model.define(
|
|
42
|
+
:id, :current_state, :identifier,
|
|
43
|
+
types: {id: String, current_state: String, identifier: String}
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
Flow = Model.define(
|
|
47
|
+
:id, :origin_entity,
|
|
48
|
+
types: {id: String, origin_entity: OriginEntity},
|
|
49
|
+
transforms: {origin_entity: ->(value) { OriginEntity.parse(value) }}
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
Branch = Model.define(
|
|
53
|
+
:id, :chain_id, :current_state, :flow,
|
|
54
|
+
types: {id: String, chain_id: String, current_state: String, flow: Flow},
|
|
55
|
+
transforms: {flow: ->(value) { Flow.parse(value) }}
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
Step = Model.define(
|
|
59
|
+
:id, :current_state, :index, :attempt, :concepts, :thought, :branch,
|
|
60
|
+
defaults: {concepts: []},
|
|
61
|
+
types: {
|
|
62
|
+
id: String, current_state: String, index: Integer, attempt: Integer,
|
|
63
|
+
concepts: Array, thought: Thought, branch: Branch
|
|
64
|
+
},
|
|
65
|
+
transforms: {
|
|
66
|
+
concepts: lambda do |value|
|
|
67
|
+
raise Error::ValidationError, "concepts must be an array" unless value.is_a?(Array)
|
|
68
|
+
|
|
69
|
+
value.map { |concept| Concept.parse(concept) }
|
|
70
|
+
end,
|
|
71
|
+
thought: ->(value) { Thought.parse(value) },
|
|
72
|
+
branch: ->(value) { Branch.parse(value) }
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
end
|
data/lib/tama/client.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tama
|
|
4
|
+
class Client
|
|
5
|
+
DEFAULT_TIMEOUT = 300
|
|
6
|
+
DEFAULT_RETRIES = 2
|
|
7
|
+
|
|
8
|
+
attr_reader :neural, :memory, :perception, :agentic, :connection
|
|
9
|
+
|
|
10
|
+
def initialize(base_url:, headers: {}, timeout: DEFAULT_TIMEOUT, retries: DEFAULT_RETRIES, connection: nil)
|
|
11
|
+
http = HTTP.new(base_url:, headers:, timeout:, retries:, connection:)
|
|
12
|
+
@connection = http.connection
|
|
13
|
+
@neural = Neural::Service.new(http)
|
|
14
|
+
@memory = Memory::Service.new(http)
|
|
15
|
+
@perception = Perception::Service.new(http)
|
|
16
|
+
@agentic = Agentic::Service.new(http)
|
|
17
|
+
freeze
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/tama/error.rb
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tama
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
class ConfigurationError < Error; end
|
|
6
|
+
class InvalidNamespaceError < ConfigurationError; end
|
|
7
|
+
|
|
8
|
+
class ValidationError < Error
|
|
9
|
+
attr_reader :errors, :status
|
|
10
|
+
|
|
11
|
+
def initialize(message = "Validation failed", errors: {}, status: nil)
|
|
12
|
+
@errors = errors
|
|
13
|
+
@status = status
|
|
14
|
+
super(message)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class HTTPError < Error
|
|
19
|
+
attr_reader :status, :body
|
|
20
|
+
|
|
21
|
+
def initialize(message = nil, status:, body: nil)
|
|
22
|
+
@status = status
|
|
23
|
+
@body = body
|
|
24
|
+
super(message || "HTTP request failed with status #{status}")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class NotFoundError < HTTPError
|
|
29
|
+
def initialize(body: nil)
|
|
30
|
+
super("Resource not found", status: 404, body: body)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class TransportError < Error
|
|
35
|
+
attr_reader :cause
|
|
36
|
+
|
|
37
|
+
def initialize(message = "HTTP transport failed", cause: nil)
|
|
38
|
+
@cause = cause
|
|
39
|
+
super(message)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class ParseError < Error; end
|
|
44
|
+
end
|
|
45
|
+
end
|