tep 0.11.3 → 0.11.5
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 +4 -4
- data/Makefile +42 -2
- data/README.md +4 -4
- data/SINATRA_COMPAT.md +20 -20
- data/bin/tep +47 -10
- data/examples/api_gateway/app.rb +1 -1
- data/examples/blog/app.rb +17 -17
- data/examples/chat/app.rb +12 -12
- data/examples/chatbot/README.md +2 -2
- data/examples/chatbot/app.rb +24 -24
- data/examples/llm_gateway/app.rb +4 -4
- data/examples/pg_hello.rb +11 -1
- data/lib/spinel_kit/hex.rb +65 -0
- data/lib/spinel_kit/json.rb +151 -0
- data/lib/spinel_kit/json_decoder.rb +396 -0
- data/lib/{tep/logger.rb → spinel_kit/log.rb} +25 -21
- data/lib/spinel_kit/url.rb +166 -0
- data/lib/tep/auth_bearer_token.rb +6 -6
- data/lib/tep/auth_oauth2.rb +4 -4
- data/lib/tep/broadcast.rb +18 -80
- data/lib/tep/events.rb +37 -37
- data/lib/tep/http.rb +3 -3
- data/lib/tep/job.rb +2 -2
- data/lib/tep/jwt.rb +4 -4
- data/lib/tep/live_view.rb +4 -4
- data/lib/tep/llm.rb +13 -45
- data/lib/tep/mcp.rb +12 -12
- data/lib/tep/multipart.rb +1 -1
- data/lib/tep/net.rb +8 -3
- data/lib/tep/openai_server.rb +102 -94
- data/lib/tep/parser.rb +2 -2
- data/lib/tep/pg.rb +468 -14
- data/lib/tep/presence.rb +33 -329
- data/lib/tep/proxy.rb +7 -7
- data/lib/tep/request.rb +1 -1
- data/lib/tep/response.rb +1 -1
- data/lib/tep/router.rb +1 -1
- data/lib/tep/session.rb +2 -2
- data/lib/tep/version.rb +1 -1
- data/lib/tep.rb +57 -137
- data/spinel-ext.json +6 -0
- data/test/helper.rb +95 -8
- data/test/run_parallel.rb +44 -7
- data/test/test_auth.rb +17 -17
- data/test/test_auth_oauth2.rb +5 -5
- data/test/test_broadcast_pg.rb +1 -0
- data/test/test_http_pool.rb +4 -4
- data/test/test_http_pool_send.rb +3 -3
- data/test/test_json.rb +12 -12
- data/test/test_jwt.rb +4 -4
- data/test/test_live_view.rb +3 -3
- data/test/test_llm.rb +12 -9
- data/test/test_llm_gateway.rb +2 -2
- data/test/test_logger.rb +2 -2
- data/test/test_openai_server.rb +10 -1
- data/test/test_password.rb +3 -3
- data/test/test_pg.rb +1 -0
- data/test/test_presence_pg.rb +1 -0
- data/test/test_real_world.rb +6 -1
- data/test/test_shutdown.rb +40 -0
- metadata +23 -8
- data/lib/tep/json.rb +0 -572
- data/lib/tep/url.rb +0 -161
data/test/test_http_pool.rb
CHANGED
|
@@ -51,10 +51,10 @@ class TestHttpPool < TepTest
|
|
|
51
51
|
res.headers["Content-Type"] = "application/json"
|
|
52
52
|
s = Tep::Http::Pool.stats
|
|
53
53
|
"{" +
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
SpinelKit::Json.encode_pair_int("checkouts", s["checkouts"].to_i) + "," +
|
|
55
|
+
SpinelKit::Json.encode_pair_int("checkins", s["checkins"].to_i) + "," +
|
|
56
|
+
SpinelKit::Json.encode_pair_int("hits", s["hits"].to_i) + "," +
|
|
57
|
+
SpinelKit::Json.encode_pair_int("misses", s["misses"].to_i) +
|
|
58
58
|
"}"
|
|
59
59
|
end
|
|
60
60
|
|
data/test/test_http_pool_send.rb
CHANGED
|
@@ -33,9 +33,9 @@ class TestHttpPoolSend < TepTest
|
|
|
33
33
|
r2 = Tep::Http.get(base + "/ping")
|
|
34
34
|
h1 = Tep::Http::Pool.stats["hits"].to_i
|
|
35
35
|
"{" +
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
SpinelKit::Json.encode_pair_int("hits_delta", h1 - h0) + "," +
|
|
37
|
+
SpinelKit::Json.encode_pair_str("b1", r1.body) + "," +
|
|
38
|
+
SpinelKit::Json.encode_pair_str("b2", r2.body) +
|
|
39
39
|
"}"
|
|
40
40
|
end
|
|
41
41
|
RB
|
data/test/test_json.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require_relative "helper"
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# SpinelKit::Json -- pure-Ruby JSON encode primitives + flat-key decode.
|
|
4
4
|
class TestJson < TepTest
|
|
5
5
|
app_source <<~RB
|
|
6
6
|
require 'sinatra'
|
|
@@ -8,56 +8,56 @@ class TestJson < TepTest
|
|
|
8
8
|
# ---- encode side ----
|
|
9
9
|
get '/escape' do
|
|
10
10
|
res.headers["Content-Type"] = "application/json"
|
|
11
|
-
|
|
11
|
+
SpinelKit::Json.quote("a\\"b\\nc")
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
get '/object' do
|
|
15
15
|
res.headers["Content-Type"] = "application/json"
|
|
16
|
-
"{" +
|
|
17
|
-
|
|
16
|
+
"{" + SpinelKit::Json.encode_pair_str("name", "alice") + "," +
|
|
17
|
+
SpinelKit::Json.encode_pair_int("age", 30) + "}"
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
get '/array' do
|
|
21
21
|
res.headers["Content-Type"] = "application/json"
|
|
22
|
-
|
|
22
|
+
SpinelKit::Json.from_str_array(["a", "b", "c"])
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
get '/int_array' do
|
|
26
26
|
res.headers["Content-Type"] = "application/json"
|
|
27
|
-
|
|
27
|
+
SpinelKit::Json.from_int_array([1, 2, 3])
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
get '/echo_html' do
|
|
31
31
|
res.headers["Content-Type"] = "application/json"
|
|
32
|
-
"{" +
|
|
32
|
+
"{" + SpinelKit::Json.encode_pair_str("payload", "<script>alert(1)</script>") + "}"
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
# ---- decode side ----
|
|
36
36
|
post '/parse_str' do
|
|
37
37
|
res.headers["Content-Type"] = "text/plain"
|
|
38
|
-
|
|
38
|
+
SpinelKit::Json.get_str(req.raw_body, "name")
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
post '/parse_int' do
|
|
42
42
|
res.headers["Content-Type"] = "text/plain"
|
|
43
|
-
|
|
43
|
+
SpinelKit::Json.get_int(req.raw_body, "n").to_s
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
post '/has_key' do
|
|
47
47
|
res.headers["Content-Type"] = "text/plain"
|
|
48
|
-
|
|
48
|
+
SpinelKit::Json.has_key?(req.raw_body, "x") ? "yes" : "no"
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
post '/skip_nested' do
|
|
52
52
|
# Read a top-level key past a nested object (skip_value should
|
|
53
53
|
# walk the nested object correctly).
|
|
54
54
|
res.headers["Content-Type"] = "text/plain"
|
|
55
|
-
|
|
55
|
+
SpinelKit::Json.get_str(req.raw_body, "after")
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
post '/parse_float' do
|
|
59
59
|
res.headers["Content-Type"] = "text/plain"
|
|
60
|
-
|
|
60
|
+
SpinelKit::Json.get_float(req.raw_body, "x").to_s
|
|
61
61
|
end
|
|
62
62
|
RB
|
|
63
63
|
|
data/test/test_jwt.rb
CHANGED
|
@@ -10,8 +10,8 @@ class TestJwt < TepTest
|
|
|
10
10
|
|
|
11
11
|
post '/issue' do
|
|
12
12
|
res.headers["Content-Type"] = "text/plain"
|
|
13
|
-
user =
|
|
14
|
-
payload = "{" +
|
|
13
|
+
user = SpinelKit::Json.get_str(req.raw_body, "user")
|
|
14
|
+
payload = "{" + SpinelKit::Json.encode_pair_str("sub", user) + "}"
|
|
15
15
|
Tep::Jwt.encode_hs256(payload, SECRET)
|
|
16
16
|
end
|
|
17
17
|
|
|
@@ -42,8 +42,8 @@ class TestJwt < TepTest
|
|
|
42
42
|
|
|
43
43
|
post '/timing_eq' do
|
|
44
44
|
res.headers["Content-Type"] = "text/plain"
|
|
45
|
-
a =
|
|
46
|
-
b =
|
|
45
|
+
a = SpinelKit::Json.get_str(req.raw_body, "a")
|
|
46
|
+
b = SpinelKit::Json.get_str(req.raw_body, "b")
|
|
47
47
|
Tep::Jwt.timing_safe_eq(a, b) ? "yes" : "no"
|
|
48
48
|
end
|
|
49
49
|
RB
|
data/test/test_live_view.rb
CHANGED
|
@@ -150,9 +150,9 @@ class TestLiveView < TepTest
|
|
|
150
150
|
"<div id='tep-live-root'>" + @last_principal + ":" + @last_state + "</div>"
|
|
151
151
|
end
|
|
152
152
|
def handle_presence_diff(diff_json)
|
|
153
|
-
@last_principal =
|
|
154
|
-
@last_kind =
|
|
155
|
-
@last_state =
|
|
153
|
+
@last_principal = SpinelKit::Json.get_str(diff_json, "principal")
|
|
154
|
+
@last_kind = SpinelKit::Json.get_str(diff_json, "kind")
|
|
155
|
+
@last_state = SpinelKit::Json.get_str(diff_json, "state")
|
|
156
156
|
0
|
|
157
157
|
end
|
|
158
158
|
end
|
data/test/test_llm.rb
CHANGED
|
@@ -82,14 +82,15 @@ class TestLlm < TepTest
|
|
|
82
82
|
# --- Phase B: chunked decode + SSE event consume ---
|
|
83
83
|
|
|
84
84
|
get "/hex_to_int_valid" do
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
SpinelKit::Hex.to_int("ff").to_s + "|" +
|
|
86
|
+
SpinelKit::Hex.to_int("a").to_s + "|" +
|
|
87
|
+
SpinelKit::Hex.to_int("100").to_s
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
get "/
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
get "/hex_to_int_leading" do
|
|
91
|
+
SpinelKit::Hex.to_int("zz").to_s + "|" + # no leading hex digit -> 0
|
|
92
|
+
SpinelKit::Hex.to_int("").to_s + "|" + # empty -> 0
|
|
93
|
+
SpinelKit::Hex.to_int("ff;ext").to_s # leading hex, stops at ext -> 255
|
|
93
94
|
end
|
|
94
95
|
|
|
95
96
|
# One chunked body: 5 bytes "Hello", then last-chunk 0.
|
|
@@ -212,9 +213,11 @@ class TestLlm < TepTest
|
|
|
212
213
|
assert_equal "255|10|256", res.body
|
|
213
214
|
end
|
|
214
215
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
# SpinelKit::Hex.to_int parses LEADING hex (>= 0), unlike the old strict
|
|
217
|
+
# Tep::Llm.hex_to_int that returned -1 on any malformation. See dechunk_*.
|
|
218
|
+
def test_hex_to_int_leading_parse
|
|
219
|
+
res = get("/hex_to_int_leading")
|
|
220
|
+
assert_equal "0|0|255", res.body
|
|
218
221
|
end
|
|
219
222
|
|
|
220
223
|
def test_dechunk_complete_single_chunk
|
data/test/test_llm_gateway.rb
CHANGED
|
@@ -51,8 +51,8 @@ class TestLlmGateway < TepTest
|
|
|
51
51
|
0
|
|
52
52
|
end
|
|
53
53
|
def on_stream_end(req, out, stats)
|
|
54
|
-
model =
|
|
55
|
-
extra = "{" +
|
|
54
|
+
model = SpinelKit::Json.get_str(req.raw_body, "model")
|
|
55
|
+
extra = "{" + SpinelKit::Json.encode_pair_str("request_id", "req-1") + "}"
|
|
56
56
|
EVENTS.inference(model, 0, stats.chunk_count, 1000000, extra)
|
|
57
57
|
0
|
|
58
58
|
end
|
data/test/test_logger.rb
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
require_relative "helper"
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# SpinelKit::Log -- levelled logger with stderr / file output.
|
|
4
4
|
class TestLogger < TepTest
|
|
5
5
|
TMP_LOG = "/tmp/tep_logger_test_#{$$}.log"
|
|
6
6
|
|
|
7
7
|
app_source <<~RB
|
|
8
8
|
require 'sinatra'
|
|
9
9
|
|
|
10
|
-
LOGGER =
|
|
10
|
+
LOGGER = SpinelKit::Log.new
|
|
11
11
|
LOGGER.set_level("debug")
|
|
12
12
|
LOGGER.to_file("#{TMP_LOG}")
|
|
13
13
|
|
data/test/test_openai_server.rb
CHANGED
|
@@ -89,6 +89,8 @@ class TestOpenAIServer < TepTest
|
|
|
89
89
|
assert_equal "200", res.code
|
|
90
90
|
body = JSON.parse(res.body)
|
|
91
91
|
assert_equal "text_completion", body["object"]
|
|
92
|
+
# Backend leaves Completion#id default -> "cmpl-tep" (back-compat).
|
|
93
|
+
assert_equal "cmpl-tep", body["id"]
|
|
92
94
|
assert_equal "echo-1", body["model"]
|
|
93
95
|
assert_equal "echoed 3 tokens t=1.0 p=1.0", body["choices"][0]["text"]
|
|
94
96
|
assert_equal "stop", body["choices"][0]["finish_reason"]
|
|
@@ -131,6 +133,9 @@ class TestOpenAIServerEvents < TepTest
|
|
|
131
133
|
c.text = "echoed " + token_ids.length.to_s + " tokens"
|
|
132
134
|
c.prompt_tokens = token_ids.length
|
|
133
135
|
c.completion_tokens = sampling.max_tokens
|
|
136
|
+
# Backend-minted per-request id (#209): must surface as the
|
|
137
|
+
# response `id` AND the inference event's request_id.
|
|
138
|
+
c.id = "cmpl-evt-" + token_ids.length.to_s
|
|
134
139
|
c
|
|
135
140
|
end
|
|
136
141
|
end
|
|
@@ -168,6 +173,8 @@ class TestOpenAIServerEvents < TepTest
|
|
|
168
173
|
res = post("/v1/completions",
|
|
169
174
|
"{\"model\":\"echo-1\",\"prompt\":[1,2,3,4],\"max_tokens\":7}")
|
|
170
175
|
assert_equal "200", res.code
|
|
176
|
+
# Backend-minted id (#209) surfaces as the response `id`.
|
|
177
|
+
assert_equal "cmpl-evt-4", JSON.parse(res.body)["id"]
|
|
171
178
|
|
|
172
179
|
lines2 = File.readlines(EVENTS_PATH).map { |l| JSON.parse(l) }
|
|
173
180
|
# #136: inference events are kind:"eval"+name:"request"; per-request
|
|
@@ -182,7 +189,9 @@ class TestOpenAIServerEvents < TepTest
|
|
|
182
189
|
assert_equal 7, extra["completion_tokens"]
|
|
183
190
|
assert_kind_of Integer, extra["latency_us"]
|
|
184
191
|
assert extra["latency_us"] >= 0
|
|
185
|
-
|
|
192
|
+
# request_id tracks the backend-minted Completion#id, same value as
|
|
193
|
+
# the response `id` above (the request_id == response id invariant).
|
|
194
|
+
assert_equal "cmpl-evt-4", extra["request_id"]
|
|
186
195
|
assert_match(/\Auser:/, extra["principal_id"])
|
|
187
196
|
end
|
|
188
197
|
end
|
data/test/test_password.rb
CHANGED
|
@@ -7,14 +7,14 @@ class TestPassword < TepTest
|
|
|
7
7
|
|
|
8
8
|
post '/hash' do
|
|
9
9
|
res.headers["Content-Type"] = "text/plain"
|
|
10
|
-
pwd =
|
|
10
|
+
pwd = SpinelKit::Json.get_str(req.raw_body, "password")
|
|
11
11
|
Tep::Password.hash(pwd)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
post '/verify' do
|
|
15
15
|
res.headers["Content-Type"] = "text/plain"
|
|
16
|
-
pwd =
|
|
17
|
-
hash =
|
|
16
|
+
pwd = SpinelKit::Json.get_str(req.raw_body, "password")
|
|
17
|
+
hash = SpinelKit::Json.get_str(req.raw_body, "hash")
|
|
18
18
|
Tep::Password.verify(pwd, hash) ? "ok" : "bad"
|
|
19
19
|
end
|
|
20
20
|
|
data/test/test_pg.rb
CHANGED
|
@@ -42,6 +42,7 @@ class TestPg < TepTest
|
|
|
42
42
|
# in the compiled binary.
|
|
43
43
|
app_source <<~RB
|
|
44
44
|
require 'sinatra'
|
|
45
|
+
require "tep/pg" # opt-in PG backend (#216)
|
|
45
46
|
|
|
46
47
|
# The PG test app runs under the default prefork server. We
|
|
47
48
|
# exercise the async surface explicitly via /async_exec and
|
data/test/test_presence_pg.rb
CHANGED
data/test/test_real_world.rb
CHANGED
|
@@ -245,7 +245,12 @@ class TestRealWorld < TepTest
|
|
|
245
245
|
# With a successful login + cookie jar...
|
|
246
246
|
uri = URI("http://127.0.0.1:#{@port}/login")
|
|
247
247
|
net = Net::HTTP.new(uri.host, uri.port)
|
|
248
|
-
|
|
248
|
+
# Explicit form Content-Type: this direct net.post bypasses the
|
|
249
|
+
# harness req() helper, and Ruby 4.0's Net::HTTP no longer auto-sets
|
|
250
|
+
# it for a bodied request (3.x did), so without it the login form
|
|
251
|
+
# isn't parsed -> 401 instead of 302.
|
|
252
|
+
r_login = net.post(uri.path, "user=alice&password=hunter2",
|
|
253
|
+
{"Content-Type" => "application/x-www-form-urlencoded"})
|
|
249
254
|
assert_equal "302", r_login.code
|
|
250
255
|
cookie = r_login["Set-Cookie"]
|
|
251
256
|
refute_nil cookie
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require_relative "helper"
|
|
2
|
+
|
|
3
|
+
# #188 regression guard for the #186 fix.
|
|
4
|
+
#
|
|
5
|
+
# A server that does NOT mount the OpenAI events surface (the common case)
|
|
6
|
+
# must not SIGSEGV on SIGTERM. The #186 bug: App#initialize never set
|
|
7
|
+
# @openai_events, so Tep.on_shutdown's unconditional `openai_events.enabled?`
|
|
8
|
+
# was a null-receiver deref -- a hard SIGSEGV under Spinel (exit 139) on a
|
|
9
|
+
# clean `kill -TERM`. #186 (0.11.2) initialises @openai_events to a disabled
|
|
10
|
+
# default, so on_shutdown is a safe no-op for apps that never call
|
|
11
|
+
# Tep::Llm::OpenAI::Server.serve!.
|
|
12
|
+
#
|
|
13
|
+
# The events-mounted path is already covered by
|
|
14
|
+
# test_openai_server#test_sigterm_emits_run_end; this is the no-events path
|
|
15
|
+
# that the original bug actually hit. We assert the exit is NOT a SEGV.
|
|
16
|
+
# Whether the clean exit is 143 (signal-terminated) or 0 (graceful return)
|
|
17
|
+
# is the build/timing-sensitive residual tracked in #188 and is acceptable
|
|
18
|
+
# here -- only a SIGSEGV is the regression.
|
|
19
|
+
class TestShutdownNoEvents < TepTest
|
|
20
|
+
app_source <<~RB
|
|
21
|
+
require 'sinatra'
|
|
22
|
+
|
|
23
|
+
get '/ping' do
|
|
24
|
+
"pong"
|
|
25
|
+
end
|
|
26
|
+
RB
|
|
27
|
+
|
|
28
|
+
def test_sigterm_on_no_events_app_does_not_segv
|
|
29
|
+
assert_equal "pong", get("/ping").body, "server should be live before SIGTERM"
|
|
30
|
+
|
|
31
|
+
status = TepHarness.terminate_status(@port)
|
|
32
|
+
refute_nil status, "spawned server not found / never reaped"
|
|
33
|
+
|
|
34
|
+
segv = Signal.list["SEGV"] # 11 -> exit 139
|
|
35
|
+
crashed = status.signaled? && status.termsig == segv
|
|
36
|
+
refute crashed,
|
|
37
|
+
"no-events app SIGSEGV'd on SIGTERM (the #186 regression) -- " \
|
|
38
|
+
"termsig=#{status.termsig.inspect} exitstatus=#{status.exitstatus.inspect}"
|
|
39
|
+
end
|
|
40
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tep
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.11.
|
|
4
|
+
version: 0.11.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ori Pekelman
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: prism
|
|
@@ -24,6 +23,20 @@ dependencies:
|
|
|
24
23
|
- - "~>"
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
25
|
version: '1.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: spinel_kit
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.2'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.2'
|
|
27
40
|
description: |-
|
|
28
41
|
tep is a small Sinatra-style DSL targeting the Spinel AOT Ruby
|
|
29
42
|
compiler. The translator turns a Sinatra-classic source file into
|
|
@@ -88,6 +101,11 @@ files:
|
|
|
88
101
|
- examples/qdrant/README.md
|
|
89
102
|
- examples/sinatra_style.rb
|
|
90
103
|
- examples/websocket_echo.rb
|
|
104
|
+
- lib/spinel_kit/hex.rb
|
|
105
|
+
- lib/spinel_kit/json.rb
|
|
106
|
+
- lib/spinel_kit/json_decoder.rb
|
|
107
|
+
- lib/spinel_kit/log.rb
|
|
108
|
+
- lib/spinel_kit/url.rb
|
|
91
109
|
- lib/tep.rb
|
|
92
110
|
- lib/tep/agent_delegation.rb
|
|
93
111
|
- lib/tep/app.rb
|
|
@@ -107,11 +125,9 @@ files:
|
|
|
107
125
|
- lib/tep/http.rb
|
|
108
126
|
- lib/tep/identity.rb
|
|
109
127
|
- lib/tep/job.rb
|
|
110
|
-
- lib/tep/json.rb
|
|
111
128
|
- lib/tep/jwt.rb
|
|
112
129
|
- lib/tep/live_view.rb
|
|
113
130
|
- lib/tep/llm.rb
|
|
114
|
-
- lib/tep/logger.rb
|
|
115
131
|
- lib/tep/mcp.rb
|
|
116
132
|
- lib/tep/multipart.rb
|
|
117
133
|
- lib/tep/net.rb
|
|
@@ -137,7 +153,6 @@ files:
|
|
|
137
153
|
- lib/tep/streamer.rb
|
|
138
154
|
- lib/tep/tep_pg.c
|
|
139
155
|
- lib/tep/tep_sqlite.c
|
|
140
|
-
- lib/tep/url.rb
|
|
141
156
|
- lib/tep/version.rb
|
|
142
157
|
- lib/tep/websocket.rb
|
|
143
158
|
- lib/tep/websocket/connection.rb
|
|
@@ -215,6 +230,7 @@ files:
|
|
|
215
230
|
- test/test_server_scheduled.rb
|
|
216
231
|
- test/test_sessions.rb
|
|
217
232
|
- test/test_shell.rb
|
|
233
|
+
- test/test_shutdown.rb
|
|
218
234
|
- test/test_sqlite.rb
|
|
219
235
|
- test/test_sqlite_cached.rb
|
|
220
236
|
- test/test_static.rb
|
|
@@ -257,8 +273,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
257
273
|
- !ruby/object:Gem::Version
|
|
258
274
|
version: '0'
|
|
259
275
|
requirements: []
|
|
260
|
-
rubygems_version:
|
|
261
|
-
signing_key:
|
|
276
|
+
rubygems_version: 4.0.3
|
|
262
277
|
specification_version: 4
|
|
263
278
|
summary: A Sinatra-flavoured web framework that compiles to a native binary via Spinel
|
|
264
279
|
test_files: []
|