tep 0.11.3 → 0.11.4

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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/Makefile +31 -1
  3. data/README.md +4 -4
  4. data/SINATRA_COMPAT.md +20 -20
  5. data/bin/tep +8 -8
  6. data/examples/api_gateway/app.rb +1 -1
  7. data/examples/blog/app.rb +17 -17
  8. data/examples/chat/app.rb +12 -12
  9. data/examples/chatbot/README.md +2 -2
  10. data/examples/chatbot/app.rb +24 -24
  11. data/examples/llm_gateway/app.rb +4 -4
  12. data/lib/spinel_kit/hex.rb +65 -0
  13. data/lib/spinel_kit/json.rb +151 -0
  14. data/lib/spinel_kit/json_decoder.rb +396 -0
  15. data/lib/{tep/logger.rb → spinel_kit/log.rb} +25 -21
  16. data/lib/spinel_kit/url.rb +166 -0
  17. data/lib/tep/auth_bearer_token.rb +6 -6
  18. data/lib/tep/auth_oauth2.rb +4 -4
  19. data/lib/tep/events.rb +37 -37
  20. data/lib/tep/http.rb +3 -3
  21. data/lib/tep/job.rb +2 -2
  22. data/lib/tep/jwt.rb +4 -4
  23. data/lib/tep/live_view.rb +4 -4
  24. data/lib/tep/llm.rb +13 -45
  25. data/lib/tep/mcp.rb +12 -12
  26. data/lib/tep/multipart.rb +1 -1
  27. data/lib/tep/openai_server.rb +102 -94
  28. data/lib/tep/parser.rb +2 -2
  29. data/lib/tep/presence.rb +11 -11
  30. data/lib/tep/proxy.rb +7 -7
  31. data/lib/tep/request.rb +1 -1
  32. data/lib/tep/response.rb +1 -1
  33. data/lib/tep/router.rb +1 -1
  34. data/lib/tep/session.rb +2 -2
  35. data/lib/tep/version.rb +1 -1
  36. data/lib/tep.rb +30 -29
  37. data/test/helper.rb +95 -8
  38. data/test/run_parallel.rb +44 -7
  39. data/test/test_auth.rb +17 -17
  40. data/test/test_auth_oauth2.rb +5 -5
  41. data/test/test_http_pool.rb +4 -4
  42. data/test/test_http_pool_send.rb +3 -3
  43. data/test/test_json.rb +12 -12
  44. data/test/test_jwt.rb +4 -4
  45. data/test/test_live_view.rb +3 -3
  46. data/test/test_llm.rb +12 -9
  47. data/test/test_llm_gateway.rb +2 -2
  48. data/test/test_logger.rb +2 -2
  49. data/test/test_openai_server.rb +10 -1
  50. data/test/test_password.rb +3 -3
  51. data/test/test_real_world.rb +6 -1
  52. data/test/test_shutdown.rb +40 -0
  53. metadata +9 -8
  54. data/lib/tep/json.rb +0 -572
  55. data/lib/tep/url.rb +0 -161
@@ -51,8 +51,8 @@ class TestLlmGateway < TepTest
51
51
  0
52
52
  end
53
53
  def on_stream_end(req, out, stats)
54
- model = Tep::Json.get_str(req.raw_body, "model")
55
- extra = "{" + Tep::Json.encode_pair_str("request_id", "req-1") + "}"
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
- # Tep::Logger -- levelled logger with stderr / file output.
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 = Tep::Logger.new
10
+ LOGGER = SpinelKit::Log.new
11
11
  LOGGER.set_level("debug")
12
12
  LOGGER.to_file("#{TMP_LOG}")
13
13
 
@@ -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
- assert_equal "cmpl-tep", extra["request_id"]
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
@@ -7,14 +7,14 @@ class TestPassword < TepTest
7
7
 
8
8
  post '/hash' do
9
9
  res.headers["Content-Type"] = "text/plain"
10
- pwd = Tep::Json.get_str(req.raw_body, "password")
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 = Tep::Json.get_str(req.raw_body, "password")
17
- hash = Tep::Json.get_str(req.raw_body, "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
 
@@ -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
- r_login = net.post(uri.path, "user=alice&password=hunter2")
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.3
4
+ version: 0.11.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ori Pekelman
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-06-02 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: prism
@@ -88,6 +87,11 @@ files:
88
87
  - examples/qdrant/README.md
89
88
  - examples/sinatra_style.rb
90
89
  - examples/websocket_echo.rb
90
+ - lib/spinel_kit/hex.rb
91
+ - lib/spinel_kit/json.rb
92
+ - lib/spinel_kit/json_decoder.rb
93
+ - lib/spinel_kit/log.rb
94
+ - lib/spinel_kit/url.rb
91
95
  - lib/tep.rb
92
96
  - lib/tep/agent_delegation.rb
93
97
  - lib/tep/app.rb
@@ -107,11 +111,9 @@ files:
107
111
  - lib/tep/http.rb
108
112
  - lib/tep/identity.rb
109
113
  - lib/tep/job.rb
110
- - lib/tep/json.rb
111
114
  - lib/tep/jwt.rb
112
115
  - lib/tep/live_view.rb
113
116
  - lib/tep/llm.rb
114
- - lib/tep/logger.rb
115
117
  - lib/tep/mcp.rb
116
118
  - lib/tep/multipart.rb
117
119
  - lib/tep/net.rb
@@ -137,7 +139,6 @@ files:
137
139
  - lib/tep/streamer.rb
138
140
  - lib/tep/tep_pg.c
139
141
  - lib/tep/tep_sqlite.c
140
- - lib/tep/url.rb
141
142
  - lib/tep/version.rb
142
143
  - lib/tep/websocket.rb
143
144
  - lib/tep/websocket/connection.rb
@@ -215,6 +216,7 @@ files:
215
216
  - test/test_server_scheduled.rb
216
217
  - test/test_sessions.rb
217
218
  - test/test_shell.rb
219
+ - test/test_shutdown.rb
218
220
  - test/test_sqlite.rb
219
221
  - test/test_sqlite_cached.rb
220
222
  - test/test_static.rb
@@ -257,8 +259,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
257
259
  - !ruby/object:Gem::Version
258
260
  version: '0'
259
261
  requirements: []
260
- rubygems_version: 3.4.20
261
- signing_key:
262
+ rubygems_version: 4.0.3
262
263
  specification_version: 4
263
264
  summary: A Sinatra-flavoured web framework that compiles to a native binary via Spinel
264
265
  test_files: []