ask-anychat 0.1.0
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 +22 -0
- data/LICENSE +21 -0
- data/README.md +63 -0
- data/lib/ask/anychat/client.rb +164 -0
- data/lib/ask/anychat/context.rb +31 -0
- data/lib/ask/anychat/errors.rb +26 -0
- data/lib/ask/anychat/version.rb +7 -0
- data/lib/ask/skills/anychat.use_anychat/SKILL.md +107 -0
- data/lib/ask-anychat.rb +28 -0
- metadata +141 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 94d004dde4b043c13876327f103c7c35e21cfd5027aace5cb8df0f3b8c97248a
|
|
4
|
+
data.tar.gz: b7abca34b3ca86d763fe170bd13ea469693a7dd62e135b59b068e2ab9aa8f7b6
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c5d6e51b5b420cdfedf8b8f1feed43fbd3ffa478d3a4783d16ad8ca80b9205d0d070d172342344d2997fc7cbe1369c811bcb1fd80dee37dc9f74a4b4713bab03
|
|
7
|
+
data.tar.gz: dfc675d8e95290db9cfa45a6e0fefc80ea1df01197a3349e838428006c0ab24864b8fb89a9bc7a87081c37f673ce5b65df1aa6925f22c671ca35df79381a7cb4
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
## [0.1.0] - 2026-09-17
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
|
|
5
|
+
- **The Anychat API client** — `Ask::AnyChat.client` resolves its base URL from
|
|
6
|
+
the hosted app and its token through ask-auth (`ANYCHAT_TOKEN`, then the
|
|
7
|
+
credentials file, then Rails credentials, then the database), so most callers
|
|
8
|
+
pass neither.
|
|
9
|
+
|
|
10
|
+
- **Workspace agent CRUD** — `workspace_agents`, `workspace_agent`,
|
|
11
|
+
`create_workspace_agent`, `update_workspace_agent` and
|
|
12
|
+
`destroy_workspace_agent`, addressing an agent by the address it answers on.
|
|
13
|
+
Creating takes a name and derives the address from it, handing a taken one the
|
|
14
|
+
first free variant rather than failing.
|
|
15
|
+
|
|
16
|
+
- **Errors under one parent** — `Ask::AnyChat::Error` with `Unauthorized`,
|
|
17
|
+
`NotFound`, `Invalid`, `Api` and `Unreachable` beneath it, each carrying the
|
|
18
|
+
sentence the API answered with. A workspace the token cannot see is reported
|
|
19
|
+
as not found rather than forbidden, which is the API's answer as well.
|
|
20
|
+
|
|
21
|
+
- **A bundled skill** — `anychat.use_anychat`, so a model that has the gem also
|
|
22
|
+
has the instructions for using it.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kaka Ruto
|
|
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,63 @@
|
|
|
1
|
+
# Ask::AnyChat
|
|
2
|
+
|
|
3
|
+
Anychat API client for the [ask-rb](https://github.com/ask-rb) ecosystem.
|
|
4
|
+
|
|
5
|
+
Anychat gives a business an agent of its own — a page customers can visit, a chat
|
|
6
|
+
they can talk to, and the words it answers with. This gem creates and manages the
|
|
7
|
+
agents a workspace owns.
|
|
8
|
+
|
|
9
|
+
This is the client layer only. Tool framing — MCP servers, native `Ask::Tools` —
|
|
10
|
+
is provided by the consumers.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
gem "ask-anychat"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
client = Ask::AnyChat.client
|
|
22
|
+
|
|
23
|
+
client.workspace_agents("anywaye") # every agent a workspace owns
|
|
24
|
+
client.workspace_agent("anywaye", "support") # one, by the address it answers on
|
|
25
|
+
client.create_workspace_agent("anywaye", display_name: "Support")
|
|
26
|
+
client.update_workspace_agent("anywaye", "support", display_name: "Help desk")
|
|
27
|
+
client.destroy_workspace_agent("anywaye", "support")
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The base URL defaults to the hosted app and the token is resolved through
|
|
31
|
+
[ask-auth](https://github.com/ask-rb/ask-auth) — `ANYCHAT_TOKEN` in the
|
|
32
|
+
environment, then the credentials file, then Rails credentials, then the
|
|
33
|
+
database. Mint a token in Anychat under Settings → API tokens.
|
|
34
|
+
|
|
35
|
+
Point it somewhere else with `Ask::AnyChat.client(base_url:, token:)`.
|
|
36
|
+
|
|
37
|
+
An agent in Anychat is a **workspace agent**: the system templates every agent is
|
|
38
|
+
cloned from are not what this reaches, only the agents a workspace owns. An
|
|
39
|
+
address that belongs to a workspace the token cannot see is reported as not found
|
|
40
|
+
rather than forbidden — the same answer as one that does not exist.
|
|
41
|
+
|
|
42
|
+
### Failures
|
|
43
|
+
|
|
44
|
+
Everything raises under `Ask::AnyChat::Error`, carrying the sentence the API
|
|
45
|
+
answered with.
|
|
46
|
+
|
|
47
|
+
| Raised | What it means |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| `Error::Unauthorized` | The token is missing, expired or refused |
|
|
50
|
+
| `Error::NotFound` | No such workspace, or no such agent in it |
|
|
51
|
+
| `Error::Invalid` | The request was understood and refused — the message says why |
|
|
52
|
+
| `Error::Api` | The API failed, or answered with something unexpected |
|
|
53
|
+
| `Error::Unreachable` | The API could not be reached at all |
|
|
54
|
+
|
|
55
|
+
A refusal is not worth retrying; `Error::Unreachable` and `Error::Api` are.
|
|
56
|
+
|
|
57
|
+
## Contributing
|
|
58
|
+
|
|
59
|
+
Run `bin/setup` once, then `bundle exec rake test`.
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
MIT.
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
require "faraday"
|
|
5
|
+
require "faraday/retry"
|
|
6
|
+
require "ask/auth"
|
|
7
|
+
|
|
8
|
+
module Ask
|
|
9
|
+
module AnyChat
|
|
10
|
+
# A client for the Anychat API.
|
|
11
|
+
#
|
|
12
|
+
# Every call names a workspace, because a workspace is what a token reaches:
|
|
13
|
+
# the API resolves it through the token's user, so a token sees only the
|
|
14
|
+
# workspaces its user belongs to. Naming somebody else's workspace is the
|
|
15
|
+
# same answer as naming one that does not exist, which is the point.
|
|
16
|
+
#
|
|
17
|
+
# client = Ask::AnyChat.client(base_url: "https://anywaye.com", token: "...")
|
|
18
|
+
# client.workspace_agents("anywaye")
|
|
19
|
+
#
|
|
20
|
+
# Failures raise under Error, carrying the sentence the API answered with.
|
|
21
|
+
class Client
|
|
22
|
+
RETRY_STATUSES = [429, 500, 502, 503].freeze
|
|
23
|
+
OPEN_TIMEOUT = 10
|
|
24
|
+
|
|
25
|
+
attr_reader :base_url
|
|
26
|
+
|
|
27
|
+
def initialize(base_url:, token:, timeout: 30)
|
|
28
|
+
@base_url = normalize(base_url)
|
|
29
|
+
@token = token
|
|
30
|
+
@timeout = timeout
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The agents a workspace owns, oldest first.
|
|
34
|
+
def workspace_agents(workspace)
|
|
35
|
+
fetch_key(get(agents_path(workspace)), "agents")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# One agent, by the address it answers on.
|
|
39
|
+
def workspace_agent(workspace, handle)
|
|
40
|
+
fetch_key(get("#{agents_path(workspace)}/#{encode(handle)}"), "agent")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def create_workspace_agent(workspace, **attributes)
|
|
44
|
+
fetch_key(post(agents_path(workspace), agent: attributes), "agent")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def update_workspace_agent(workspace, handle, **attributes)
|
|
48
|
+
fetch_key(patch("#{agents_path(workspace)}/#{encode(handle)}", agent: attributes), "agent")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# An address the owner printed is the reason they care about this one, so
|
|
52
|
+
# it is gone afterwards and free for the next agent to take. Nothing comes
|
|
53
|
+
# back but the fact that it worked; a failure raises.
|
|
54
|
+
def destroy_workspace_agent(workspace, handle)
|
|
55
|
+
delete("#{agents_path(workspace)}/#{encode(handle)}")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# The headers every request carries. Public because it is the honest
|
|
59
|
+
# answer to "what does this client send?" — and because a caller swapping
|
|
60
|
+
# the connection under it should be able to keep them.
|
|
61
|
+
def default_headers
|
|
62
|
+
{
|
|
63
|
+
"Authorization" => "Bearer #{token}",
|
|
64
|
+
"Accept" => "application/json",
|
|
65
|
+
"User-Agent" => "ask-anychat/#{VERSION}"
|
|
66
|
+
}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
attr_reader :token, :timeout
|
|
72
|
+
|
|
73
|
+
def agents_path(workspace)
|
|
74
|
+
"/api/#{API_VERSION}/workspaces/#{encode(workspace)}/agents"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def get(path, params = nil)
|
|
78
|
+
request(:get, path, params: params)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def post(path, body = nil)
|
|
82
|
+
request(:post, path, body: body)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def patch(path, body = nil)
|
|
86
|
+
request(:patch, path, body: body)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def delete(path)
|
|
90
|
+
request(:delete, path)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def request(method, path, body: nil, params: nil)
|
|
94
|
+
response = connection.public_send(method, path) do |request|
|
|
95
|
+
request.params = params if params
|
|
96
|
+
request.body = body if body
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
response.success? ? parse(response) : raise_for(response)
|
|
100
|
+
rescue Faraday::Error => e
|
|
101
|
+
raise Error::Unreachable, "#{base_url} could not be reached: #{e.message}"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def connection
|
|
105
|
+
@connection ||= Faraday.new(url: base_url, headers: default_headers) do |builder|
|
|
106
|
+
builder.request :json
|
|
107
|
+
builder.response :json, content_type: /\bjson$/
|
|
108
|
+
builder.request :retry, max: 3, retry_statuses: RETRY_STATUSES, interval: 0.5
|
|
109
|
+
builder.options.timeout = timeout
|
|
110
|
+
builder.options.open_timeout = OPEN_TIMEOUT
|
|
111
|
+
builder.adapter Faraday.default_adapter
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# A body is an object, or nothing at all — a 204 has nothing to read. A
|
|
116
|
+
# body that is neither is the API answering with something this gem does
|
|
117
|
+
# not know what to do with, which is worth saying out loud.
|
|
118
|
+
def parse(response)
|
|
119
|
+
body = response.body
|
|
120
|
+
return {} if body.nil? || (body.is_a?(String) && body.strip.empty?)
|
|
121
|
+
return body if body.is_a?(Hash)
|
|
122
|
+
|
|
123
|
+
raise Error::Api, "the API answered with #{body.class} where an object was expected"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def raise_for(response)
|
|
127
|
+
message = message_from(response)
|
|
128
|
+
|
|
129
|
+
case response.status
|
|
130
|
+
when 401 then raise Error::Unauthorized, message
|
|
131
|
+
when 404 then raise Error::NotFound, message
|
|
132
|
+
when 422 then raise Error::Invalid, message
|
|
133
|
+
else raise Error::Api, "HTTP #{response.status} from #{base_url}: #{message}"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# A failure is answered as {"error" => "..."} and sometimes with a
|
|
138
|
+
# field-by-field map beside it. The sentence is what a person reads, so it
|
|
139
|
+
# is preferred over the map and over the status code.
|
|
140
|
+
def message_from(response)
|
|
141
|
+
body = response.body
|
|
142
|
+
return body.to_s unless body.is_a?(Hash)
|
|
143
|
+
|
|
144
|
+
sentence = body["error"].to_s.strip
|
|
145
|
+
return sentence unless sentence.empty?
|
|
146
|
+
return body["errors"].to_s if body["errors"]
|
|
147
|
+
|
|
148
|
+
"the request was refused"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def fetch_key(body, key)
|
|
152
|
+
body.fetch(key) { raise Error::Api, "the API answered without #{key.inspect}: #{body.inspect}" }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def encode(segment)
|
|
156
|
+
ERB::Util.url_encode(segment.to_s)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def normalize(url)
|
|
160
|
+
url.to_s.sub(%r{/+\z}, "")
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ask
|
|
4
|
+
module AnyChat
|
|
5
|
+
# What this is, for a model deciding whether to reach for it.
|
|
6
|
+
DESCRIPTION = "Anychat gives a business an agent of its own — a page customers " \
|
|
7
|
+
"can visit, a chat they can talk to, and the words it answers with. " \
|
|
8
|
+
"This client creates and manages those agents."
|
|
9
|
+
|
|
10
|
+
DOMAIN_URL = "https://anywaye.com"
|
|
11
|
+
DOCS_URL = "https://anywaye.com/docs/api"
|
|
12
|
+
|
|
13
|
+
# ask-auth resolves this name in order: ANYCHAT_TOKEN in the environment,
|
|
14
|
+
# the credentials file, Rails credentials, then the database.
|
|
15
|
+
AUTH_NAME = :anychat_token
|
|
16
|
+
AUTH_HOW = "Mint a token in Anychat under Settings → API tokens, then set it as " \
|
|
17
|
+
"ANYCHAT_TOKEN, or pass token: to Ask::AnyChat.client."
|
|
18
|
+
|
|
19
|
+
API_VERSION = "v1"
|
|
20
|
+
|
|
21
|
+
QUICK_START = <<~RUBY
|
|
22
|
+
client = Ask::AnyChat.client(token: ENV["ANYCHAT_TOKEN"])
|
|
23
|
+
|
|
24
|
+
client.workspace_agents("anywaye") # every agent a workspace owns
|
|
25
|
+
client.workspace_agent("anywaye", "support") # one, by the address it answers on
|
|
26
|
+
client.create_workspace_agent("anywaye", display_name: "Support")
|
|
27
|
+
client.update_workspace_agent("anywaye", "support", display_name: "Help desk")
|
|
28
|
+
client.destroy_workspace_agent("anywaye", "support")
|
|
29
|
+
RUBY
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ask
|
|
4
|
+
module AnyChat
|
|
5
|
+
# Everything this gem raises, under one parent, so a caller can rescue the
|
|
6
|
+
# library rather than a list of its moods. Each one carries the sentence the
|
|
7
|
+
# API answered with: "An agent needs a name." is worth more than "422".
|
|
8
|
+
class Error < StandardError
|
|
9
|
+
# The token was missing, expired, or refused.
|
|
10
|
+
class Unauthorized < Error; end
|
|
11
|
+
|
|
12
|
+
# No such workspace — or none this token can reach, which is the same
|
|
13
|
+
# answer on purpose.
|
|
14
|
+
class NotFound < Error; end
|
|
15
|
+
|
|
16
|
+
# The API understood the request and refused it; the message says why.
|
|
17
|
+
class Invalid < Error; end
|
|
18
|
+
|
|
19
|
+
# The API failed, or answered with something this gem did not expect.
|
|
20
|
+
class Api < Error; end
|
|
21
|
+
|
|
22
|
+
# The API could not be reached at all.
|
|
23
|
+
class Unreachable < Error; end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: anychat.use_anychat
|
|
3
|
+
description: Manage the agents an Anychat workspace owns — list them, bring a new one into being, rename or re-title one, or retire one. Use when a request concerns an Anychat agent's name, its address, or the set of agents a workspace has.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Using Anychat
|
|
7
|
+
|
|
8
|
+
Anychat gives a business an agent of its own: a page customers can visit, a chat
|
|
9
|
+
they can talk to, and the words it answers with. This client creates and manages
|
|
10
|
+
those agents.
|
|
11
|
+
|
|
12
|
+
An agent in Anychat is a **workspace agent**. A workspace owns one or many, and
|
|
13
|
+
each answers on `/<workspace>/<handle>` — the link the owner prints. The system
|
|
14
|
+
templates every agent is cloned from are not what this reaches; only the agents a
|
|
15
|
+
workspace owns.
|
|
16
|
+
|
|
17
|
+
## Step 1: get the client
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
client = Ask::AnyChat.client
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The base URL defaults to the hosted app and the token is resolved through
|
|
24
|
+
ask-auth, so this usually needs no arguments. To point somewhere else:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
Ask::AnyChat.client(base_url: "https://staging.anywaye.com", token: ENV["STAGING_TOKEN"])
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Step 2: name the workspace
|
|
31
|
+
|
|
32
|
+
Every call names one, because a workspace is what a token reaches: the API
|
|
33
|
+
resolves it through the token's user, so a token sees only the workspaces its
|
|
34
|
+
user belongs to. Naming somebody else's workspace is the same answer as naming
|
|
35
|
+
one that does not exist — a `NotFound`, not a `Forbidden`.
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
client.workspace_agents("anywaye")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Step 3: read before you write
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
client.workspace_agents("anywaye") # every agent, oldest first
|
|
45
|
+
client.workspace_agent("anywaye", "support") # one, by the address it answers on
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Read first for two reasons: it tells you which addresses are already taken, and
|
|
49
|
+
it gives you the `handle` that `update` and `destroy` need. There is no lookup
|
|
50
|
+
by name — an agent is addressed by its address.
|
|
51
|
+
|
|
52
|
+
## Step 4: create
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
client.create_workspace_agent("anywaye", display_name: "Support")
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Only a name is required. The address is derived from it, and a taken one is not
|
|
59
|
+
a failure — the new agent is handed the first free variant (`support-2`,
|
|
60
|
+
`support-3`). Pass `handle:` to choose the address yourself when the owner has
|
|
61
|
+
one in mind.
|
|
62
|
+
|
|
63
|
+
`description:` and `public:` may be given too; everything else an agent shows
|
|
64
|
+
(branding, greeting, colour) is the owner's to edit afterwards.
|
|
65
|
+
|
|
66
|
+
## Step 5: change one
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
client.update_workspace_agent("anywaye", "support", display_name: "Help desk")
|
|
70
|
+
client.update_workspace_agent("anywaye", "support", handle: "help")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Only the attributes you pass are written. Renaming the address is safe: the
|
|
74
|
+
handler allocates a free one if what you asked for is taken.
|
|
75
|
+
|
|
76
|
+
## Step 6: retire one
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
client.destroy_workspace_agent("anywaye", "support")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The address goes with it and becomes free again. Conversations customers had
|
|
83
|
+
with it are kept — they are a record of what somebody was told — so a retired
|
|
84
|
+
agent still appears in that history.
|
|
85
|
+
|
|
86
|
+
## When it is refused
|
|
87
|
+
|
|
88
|
+
Every failure raises under `Ask::AnyChat::Error`, carrying the sentence the API
|
|
89
|
+
answered with, which is written to be read out:
|
|
90
|
+
|
|
91
|
+
| Raised | What it means |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| `Error::Unauthorized` | The token is missing, expired or refused |
|
|
94
|
+
| `Error::NotFound` | No such workspace, or no such agent in it |
|
|
95
|
+
| `Error::Invalid` | The request was understood and refused — the message says why |
|
|
96
|
+
| `Error::Api` | The API failed, or answered with something unexpected |
|
|
97
|
+
| `Error::Unreachable` | The API could not be reached at all |
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
begin
|
|
101
|
+
client.create_workspace_agent("anywaye", display_name: "")
|
|
102
|
+
rescue Ask::AnyChat::Error::Invalid => e
|
|
103
|
+
e.message # => "An agent needs a name."
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
A refusal is not something to retry. `Error::Unreachable` and `Error::Api` are.
|
data/lib/ask-anychat.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "ask/anychat/version"
|
|
4
|
+
require_relative "ask/anychat/context"
|
|
5
|
+
require_relative "ask/anychat/errors"
|
|
6
|
+
require_relative "ask/anychat/client"
|
|
7
|
+
|
|
8
|
+
module Ask
|
|
9
|
+
# Anychat: an agent for a business — a page, a chat, and the words it answers
|
|
10
|
+
# with — and the client that creates and manages one.
|
|
11
|
+
module AnyChat
|
|
12
|
+
# A client for the Anychat API.
|
|
13
|
+
#
|
|
14
|
+
# Ask::AnyChat.client(token: ENV["ANYCHAT_TOKEN"])
|
|
15
|
+
#
|
|
16
|
+
# The base URL defaults to the hosted app, and the token is resolved through
|
|
17
|
+
# ask-auth (ANYCHAT_TOKEN, then the credentials file, then Rails
|
|
18
|
+
# credentials, then the database), so most callers pass one of the two —
|
|
19
|
+
# often neither.
|
|
20
|
+
def self.client(base_url: nil, token: nil, **options)
|
|
21
|
+
Client.new(
|
|
22
|
+
base_url: base_url || ENV["ANYCHAT_BASE_URL"] || DOMAIN_URL,
|
|
23
|
+
token: token || Auth.resolve(AUTH_NAME),
|
|
24
|
+
**options
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ask-anychat
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Kaka Ruto
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: ask-auth
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.3.2
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.3.2
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: faraday
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: faraday-retry
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '2.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '2.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: minitest
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '5.25'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '5.25'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: mocha
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '3.1'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '3.1'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rake
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '13.0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '13.0'
|
|
96
|
+
description: 'Creates and manages the agents an Anychat workspace owns. Anychat gives
|
|
97
|
+
a business an agent of its own — a page customers can visit, a chat they can talk
|
|
98
|
+
to, and the words it answers with. This is the client layer only: the tool framing
|
|
99
|
+
that exposes it to an agent (MCP servers, native Ask::Tools) is provided by the
|
|
100
|
+
consumers.'
|
|
101
|
+
email:
|
|
102
|
+
- kaka@myrrlabs.com
|
|
103
|
+
executables: []
|
|
104
|
+
extensions: []
|
|
105
|
+
extra_rdoc_files: []
|
|
106
|
+
files:
|
|
107
|
+
- CHANGELOG.md
|
|
108
|
+
- LICENSE
|
|
109
|
+
- README.md
|
|
110
|
+
- lib/ask-anychat.rb
|
|
111
|
+
- lib/ask/anychat/client.rb
|
|
112
|
+
- lib/ask/anychat/context.rb
|
|
113
|
+
- lib/ask/anychat/errors.rb
|
|
114
|
+
- lib/ask/anychat/version.rb
|
|
115
|
+
- lib/ask/skills/anychat.use_anychat/SKILL.md
|
|
116
|
+
homepage: https://github.com/ask-rb/ask-anychat
|
|
117
|
+
licenses:
|
|
118
|
+
- MIT
|
|
119
|
+
metadata:
|
|
120
|
+
homepage_uri: https://github.com/ask-rb/ask-anychat
|
|
121
|
+
source_code_uri: https://github.com/ask-rb/ask-anychat
|
|
122
|
+
changelog_uri: https://github.com/ask-rb/ask-anychat/blob/master/CHANGELOG.md
|
|
123
|
+
rubygems_mfa_required: 'true'
|
|
124
|
+
rdoc_options: []
|
|
125
|
+
require_paths:
|
|
126
|
+
- lib
|
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '3.2'
|
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
133
|
+
requirements:
|
|
134
|
+
- - ">="
|
|
135
|
+
- !ruby/object:Gem::Version
|
|
136
|
+
version: '0'
|
|
137
|
+
requirements: []
|
|
138
|
+
rubygems_version: 4.0.18
|
|
139
|
+
specification_version: 4
|
|
140
|
+
summary: Anychat API client for the ask-rb ecosystem
|
|
141
|
+
test_files: []
|