tep 0.11.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/LICENSE +21 -0
- data/Makefile +134 -0
- data/README.md +247 -0
- data/SINATRA_COMPAT.md +376 -0
- data/bin/tep +2156 -0
- data/examples/agentic_chat/README.md +103 -0
- data/examples/agentic_chat/app.rb +310 -0
- data/examples/api_gateway/README.md +49 -0
- data/examples/api_gateway/app.rb +66 -0
- data/examples/blog/app.rb +367 -0
- data/examples/blog/views/index.erb +36 -0
- data/examples/blog/views/login.erb +28 -0
- data/examples/blog/views/new_post.erb +25 -0
- data/examples/blog/views/show.erb +16 -0
- data/examples/chat/app.rb +278 -0
- data/examples/chat/assets/logo.svg +13 -0
- data/examples/chat/assets/style.css +209 -0
- data/examples/chat/views/index.erb +142 -0
- data/examples/chatbot/README.md +111 -0
- data/examples/chatbot/app.rb +1024 -0
- data/examples/chatbot/assets/chat.js +249 -0
- data/examples/chatbot/assets/compare.js +93 -0
- data/examples/chatbot/assets/markdown.js +84 -0
- data/examples/chatbot/assets/style.css +215 -0
- data/examples/chatbot/schema.sql +25 -0
- data/examples/chatbot/views/compare.erb +43 -0
- data/examples/chatbot/views/index.erb +42 -0
- data/examples/chatbot/views/login.erb +22 -0
- data/examples/chatbot/views/setup.erb +23 -0
- data/examples/counter/README.md +68 -0
- data/examples/counter/app.rb +85 -0
- data/examples/experiments/AGENTS.md +91 -0
- data/examples/experiments/README.md +99 -0
- data/examples/experiments/app.rb +225 -0
- data/examples/geohash/Gemfile +11 -0
- data/examples/geohash/Gemfile.lock +17 -0
- data/examples/geohash/README.md +58 -0
- data/examples/geohash/app.rb +33 -0
- data/examples/hello.rb +120 -0
- data/examples/llm_gateway/README.md +73 -0
- data/examples/llm_gateway/app.rb +91 -0
- data/examples/maidenhead/Gemfile +7 -0
- data/examples/maidenhead/Gemfile.lock +17 -0
- data/examples/maidenhead/README.md +47 -0
- data/examples/maidenhead/app.rb +46 -0
- data/examples/pg_hello.rb +76 -0
- data/examples/qdrant/Gemfile +11 -0
- data/examples/qdrant/Gemfile.lock +29 -0
- data/examples/qdrant/README.md +54 -0
- data/examples/sinatra_style.rb +32 -0
- data/examples/websocket_echo.rb +37 -0
- data/lib/tep/agent_delegation.rb +35 -0
- data/lib/tep/app.rb +291 -0
- data/lib/tep/assets.rb +52 -0
- data/lib/tep/auth.rb +78 -0
- data/lib/tep/auth_bearer_token.rb +126 -0
- data/lib/tep/auth_oauth2.rb +189 -0
- data/lib/tep/auth_oauth2_client.rb +29 -0
- data/lib/tep/auth_oauth2_code.rb +40 -0
- data/lib/tep/auth_session_cookie.rb +132 -0
- data/lib/tep/broadcast.rb +265 -0
- data/lib/tep/broadcast_subscription.rb +42 -0
- data/lib/tep/cache.rb +49 -0
- data/lib/tep/events.rb +257 -0
- data/lib/tep/filter.rb +21 -0
- data/lib/tep/handler.rb +35 -0
- data/lib/tep/http.rb +599 -0
- data/lib/tep/identity.rb +67 -0
- data/lib/tep/job.rb +186 -0
- data/lib/tep/json.rb +572 -0
- data/lib/tep/jwt.rb +126 -0
- data/lib/tep/live_view.rb +219 -0
- data/lib/tep/llm.rb +505 -0
- data/lib/tep/logger.rb +85 -0
- data/lib/tep/mcp.rb +203 -0
- data/lib/tep/multipart.rb +98 -0
- data/lib/tep/net.rb +155 -0
- data/lib/tep/openai_server.rb +725 -0
- data/lib/tep/parallel.rb +168 -0
- data/lib/tep/parser.rb +81 -0
- data/lib/tep/password.rb +102 -0
- data/lib/tep/pg.rb +1128 -0
- data/lib/tep/presence.rb +589 -0
- data/lib/tep/presence_entry.rb +52 -0
- data/lib/tep/proxy.rb +801 -0
- data/lib/tep/request.rb +194 -0
- data/lib/tep/response.rb +134 -0
- data/lib/tep/router.rb +137 -0
- data/lib/tep/scheduler.rb +342 -0
- data/lib/tep/security.rb +140 -0
- data/lib/tep/server.rb +276 -0
- data/lib/tep/server_scheduled.rb +375 -0
- data/lib/tep/session.rb +98 -0
- data/lib/tep/shell.rb +62 -0
- data/lib/tep/sphttp.c +858 -0
- data/lib/tep/sqlite.rb +215 -0
- data/lib/tep/streamer.rb +31 -0
- data/lib/tep/tep_pg.c +769 -0
- data/lib/tep/tep_sqlite.c +320 -0
- data/lib/tep/url.rb +161 -0
- data/lib/tep/version.rb +3 -0
- data/lib/tep/websocket/connection.rb +171 -0
- data/lib/tep/websocket/driver.rb +169 -0
- data/lib/tep/websocket/frame.rb +238 -0
- data/lib/tep/websocket/handshake.rb +159 -0
- data/lib/tep/websocket.rb +68 -0
- data/lib/tep.rb +981 -0
- data/public/hello.txt +1 -0
- data/public/style.css +4 -0
- data/spinel-ext.json +33 -0
- data/test/helper.rb +248 -0
- data/test/real_world/01_simple.rb +5 -0
- data/test/real_world/02_lifecycle.rb +20 -0
- data/test/real_world/03_chat.rb +75 -0
- data/test/real_world/04_health_api.rb +25 -0
- data/test/real_world/05_todo_api.rb +57 -0
- data/test/real_world/06_basic_auth.rb +25 -0
- data/test/real_world/07_bbc_rest_api.rb +228 -0
- data/test/real_world/07_sklise_things.rb +109 -0
- data/test/real_world/08_jwd83_helloworld.rb +56 -0
- data/test/run_all.rb +7 -0
- data/test/run_parallel.rb +89 -0
- data/test/spinel_scheduled_burst_segv_repro.rb +33 -0
- data/test/test_api_gateway.rb +76 -0
- data/test/test_auth.rb +223 -0
- data/test/test_auth_oauth2.rb +208 -0
- data/test/test_auth_session_cookie.rb +198 -0
- data/test/test_broadcast.rb +197 -0
- data/test/test_broadcast_pg.rb +135 -0
- data/test/test_cache.rb +98 -0
- data/test/test_cache_static.rb +48 -0
- data/test/test_cookies.rb +52 -0
- data/test/test_erb.rb +53 -0
- data/test/test_erb_ivars.rb +58 -0
- data/test/test_events.rb +114 -0
- data/test/test_filters.rb +41 -0
- data/test/test_geohash_example.rb +89 -0
- data/test/test_http.rb +137 -0
- data/test/test_http_pool.rb +122 -0
- data/test/test_http_pool_send.rb +57 -0
- data/test/test_identity.rb +165 -0
- data/test/test_inbound_tls.rb +101 -0
- data/test/test_inbound_tls_scheduled.rb +101 -0
- data/test/test_job.rb +108 -0
- data/test/test_json.rb +168 -0
- data/test/test_jwt.rb +143 -0
- data/test/test_live_view.rb +324 -0
- data/test/test_llm.rb +250 -0
- data/test/test_llm_gateway.rb +95 -0
- data/test/test_logger.rb +101 -0
- data/test/test_maidenhead_example.rb +86 -0
- data/test/test_mcp.rb +264 -0
- data/test/test_misc_v02.rb +54 -0
- data/test/test_modular.rb +43 -0
- data/test/test_multi_filters.rb +40 -0
- data/test/test_mustache.rb +57 -0
- data/test/test_openai_server.rb +598 -0
- data/test/test_optional_segments.rb +45 -0
- data/test/test_parallel.rb +102 -0
- data/test/test_params.rb +99 -0
- data/test/test_pass.rb +42 -0
- data/test/test_password.rb +101 -0
- data/test/test_pg.rb +673 -0
- data/test/test_presence.rb +374 -0
- data/test/test_presence_pg.rb +309 -0
- data/test/test_proxy.rb +556 -0
- data/test/test_proxy_dsl.rb +119 -0
- data/test/test_proxy_streaming.rb +146 -0
- data/test/test_real_world.rb +397 -0
- data/test/test_regex_routes.rb +52 -0
- data/test/test_request_methods.rb +102 -0
- data/test/test_response.rb +123 -0
- data/test/test_routing.rb +109 -0
- data/test/test_scheduler.rb +153 -0
- data/test/test_security.rb +72 -0
- data/test/test_server_scheduled.rb +56 -0
- data/test/test_sessions.rb +59 -0
- data/test/test_shell.rb +54 -0
- data/test/test_sqlite.rb +148 -0
- data/test/test_sqlite_cached.rb +171 -0
- data/test/test_static.rb +57 -0
- data/test/test_streaming.rb +96 -0
- data/test/test_unsupported.rb +32 -0
- data/test/test_websocket.rb +152 -0
- data/test/test_websocket_echo.rb +138 -0
- data/test/views/greet.erb +5 -0
- data/test/views/hello.erb +5 -0
- data/test/views/list.erb +5 -0
- data/test/views/m_ivars.mustache +3 -0
- data/test/views/m_simple.mustache +4 -0
- data/test/views/mixed.erb +3 -0
- metadata +264 -0
data/lib/tep.rb
ADDED
|
@@ -0,0 +1,981 @@
|
|
|
1
|
+
# Tep -- a Sinatra-flavoured framework that compiles to a native
|
|
2
|
+
# binary via Spinel.
|
|
3
|
+
#
|
|
4
|
+
# require_relative "../tep/lib/tep"
|
|
5
|
+
#
|
|
6
|
+
# class Root < Tep::Handler
|
|
7
|
+
# def handle(req, res)
|
|
8
|
+
# "<h1>hello, world</h1>"
|
|
9
|
+
# end
|
|
10
|
+
# end
|
|
11
|
+
# Tep.get "/", Root.new
|
|
12
|
+
#
|
|
13
|
+
# Tep.run!(4567, 1, false)
|
|
14
|
+
#
|
|
15
|
+
# Sinatra-classic source (with `do ... end` blocks) is supported via
|
|
16
|
+
# `bin/tep build app.rb`, which translates blocks into Handler
|
|
17
|
+
# subclasses before invoking spinel.
|
|
18
|
+
|
|
19
|
+
# --- stock-Ruby guard --------------------------------------------------
|
|
20
|
+
# tep's lib/ is Spinel-AOT source: it's COMPILED into a native binary
|
|
21
|
+
# (or inlined by `tep build`), not run under CRuby. A plain
|
|
22
|
+
# `require "tep"` on MRI/JRuby/TruffleRuby has no FFI runtime and would
|
|
23
|
+
# otherwise die with a cryptic `undefined method 'ffi_cflags'` deep in a
|
|
24
|
+
# sub-file. RUBY_ENGINE is defined on every stock Ruby but NOT in a
|
|
25
|
+
# Spinel binary, so this fires only under a real `require` and stays a
|
|
26
|
+
# dead no-op once compiled (it inlines harmlessly into every app).
|
|
27
|
+
if defined?(RUBY_ENGINE)
|
|
28
|
+
raise "tep is a Spinel-AOT framework, not a CRuby library -- " \
|
|
29
|
+
"`require \"tep\"` has no runtime here. tep compiles your " \
|
|
30
|
+
"Sinatra-style app to a native binary; build it with the " \
|
|
31
|
+
"translator:\n" \
|
|
32
|
+
" tep build app.rb && ./app -p 4567\n" \
|
|
33
|
+
"or declare `gem \"tep\"` in a bundler-spinel (spinelgems) " \
|
|
34
|
+
"Gemfile and let `spinel-compat vendor` inline it. " \
|
|
35
|
+
"See https://github.com/OriPekelman/tep"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
require_relative "tep/version"
|
|
39
|
+
require_relative "tep/url"
|
|
40
|
+
require_relative "tep/multipart"
|
|
41
|
+
require_relative "tep/net"
|
|
42
|
+
require_relative "tep/agent_delegation"
|
|
43
|
+
require_relative "tep/identity"
|
|
44
|
+
# Auth + Broadcast + Presence data classes (no deps; storage on
|
|
45
|
+
# Tep::App references them, so they must load before app.rb).
|
|
46
|
+
require_relative "tep/auth_oauth2_client"
|
|
47
|
+
require_relative "tep/auth_oauth2_code"
|
|
48
|
+
require_relative "tep/broadcast_subscription"
|
|
49
|
+
require_relative "tep/presence_entry"
|
|
50
|
+
require_relative "tep/session"
|
|
51
|
+
require_relative "tep/request"
|
|
52
|
+
require_relative "tep/response"
|
|
53
|
+
require_relative "tep/cache"
|
|
54
|
+
require_relative "tep/handler"
|
|
55
|
+
require_relative "tep/filter"
|
|
56
|
+
require_relative "tep/streamer"
|
|
57
|
+
require_relative "tep/parser"
|
|
58
|
+
require_relative "tep/router"
|
|
59
|
+
require_relative "tep/app"
|
|
60
|
+
# Auth provider classes land after App so Tep::AuthFilter < Tep::Filter
|
|
61
|
+
# resolves and the install! helper can reach Tep::APP. References to
|
|
62
|
+
# Tep::Jwt / Tep::Json inside their method bodies resolve at runtime.
|
|
63
|
+
require_relative "tep/auth_bearer_token"
|
|
64
|
+
require_relative "tep/auth_session_cookie"
|
|
65
|
+
require_relative "tep/auth_oauth2"
|
|
66
|
+
require_relative "tep/auth"
|
|
67
|
+
require_relative "tep/broadcast"
|
|
68
|
+
require_relative "tep/presence"
|
|
69
|
+
require_relative "tep/live_view"
|
|
70
|
+
require_relative "tep/server"
|
|
71
|
+
require_relative "tep/server_scheduled"
|
|
72
|
+
require_relative "tep/sqlite"
|
|
73
|
+
require_relative "tep/pg"
|
|
74
|
+
require_relative "tep/json"
|
|
75
|
+
require_relative "tep/mcp"
|
|
76
|
+
require_relative "tep/logger"
|
|
77
|
+
require_relative "tep/jwt"
|
|
78
|
+
require_relative "tep/password"
|
|
79
|
+
require_relative "tep/security"
|
|
80
|
+
require_relative "tep/assets"
|
|
81
|
+
require_relative "tep/scheduler"
|
|
82
|
+
require_relative "tep/shell"
|
|
83
|
+
require_relative "tep/http"
|
|
84
|
+
require_relative "tep/proxy"
|
|
85
|
+
require_relative "tep/events"
|
|
86
|
+
require_relative "tep/llm"
|
|
87
|
+
require_relative "tep/openai_server"
|
|
88
|
+
require_relative "tep/websocket"
|
|
89
|
+
require_relative "tep/parallel"
|
|
90
|
+
require_relative "tep/job"
|
|
91
|
+
|
|
92
|
+
module Tep
|
|
93
|
+
# Helper: spinel won't infer types on an empty `{}`, so we seed
|
|
94
|
+
# with one entry then delete it. Used by Request/Response so
|
|
95
|
+
# users get the natural Hash[] / Hash[]= surface (Sinatra-style
|
|
96
|
+
# `params["name"]` works without a bespoke Bag wrapper).
|
|
97
|
+
# Holder for a Fiber so we can keep them in a typed array.
|
|
98
|
+
# Spinel's `[Fiber.new { ... }]` array literal infers IntArray
|
|
99
|
+
# (Fiber is a built-in pointer type, not a user class spinel
|
|
100
|
+
# tracks via PtrArray), so a one-attribute wrapper class is the
|
|
101
|
+
# cheapest way to put them in a homogeneous container.
|
|
102
|
+
class FiberSlot
|
|
103
|
+
attr_accessor :f
|
|
104
|
+
def initialize(f)
|
|
105
|
+
@f = f
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def self.seed_fiber_noop
|
|
110
|
+
0
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# A canonical no-op fiber, used to type-seed Fiber-bearing
|
|
114
|
+
# collections without running anything user-visible. The body is
|
|
115
|
+
# a single method call (Fiber tests don't currently support
|
|
116
|
+
# arbitrary inline-block bodies in spinel).
|
|
117
|
+
def self.seed_fiber
|
|
118
|
+
Fiber.new { Tep.seed_fiber_noop }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def self.str_hash
|
|
123
|
+
h = {"" => ""}
|
|
124
|
+
h.delete("")
|
|
125
|
+
h
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# str_find -- naive substring search returning the int position of
|
|
129
|
+
# `needle` in `s` starting from `start`, or -1 if not found.
|
|
130
|
+
#
|
|
131
|
+
# History: workaround for spinel `0210389` which made `String#index`
|
|
132
|
+
# return nil for not-found (was -1). spinel `28545ff` (matz/spinel#550)
|
|
133
|
+
# added int|nil narrowing after an explicit nil-guard, so the
|
|
134
|
+
# nil-side risk is upstream-resolved AND spinel supports the
|
|
135
|
+
# offset overload `s.index(needle, start)` directly (emits
|
|
136
|
+
# `sp_str_index_from_poly`). The helper stays solely for callsite
|
|
137
|
+
# ergonomics: the 17 callers all use `if x < 0` style int comparison
|
|
138
|
+
# (which can't narrow against int|nil under spinel's current
|
|
139
|
+
# narrowing model). Removing it would require a mechanical
|
|
140
|
+
# `< 0` -> `.nil?` refactor across http.rb / parser.rb / url.rb /
|
|
141
|
+
# jwt.rb / app.rb. Worth doing eventually; not urgent.
|
|
142
|
+
def self.str_find(s, needle, start)
|
|
143
|
+
nlen = needle.length
|
|
144
|
+
slen = s.length
|
|
145
|
+
pos = start
|
|
146
|
+
while pos <= slen - nlen
|
|
147
|
+
if s[pos, nlen] == needle
|
|
148
|
+
return pos
|
|
149
|
+
end
|
|
150
|
+
pos += 1
|
|
151
|
+
end
|
|
152
|
+
-1
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# HTML-escape: minimum safe set for attribute and PCDATA contexts.
|
|
156
|
+
# Used by the build-time Mustache compiler for the default
|
|
157
|
+
# `{{var}}` (escaped) form. Char-by-char to avoid `gsub` (spinel's
|
|
158
|
+
# gsub coverage on string-typed receivers is uneven).
|
|
159
|
+
def self.h(s)
|
|
160
|
+
out = ""
|
|
161
|
+
i = 0
|
|
162
|
+
n = s.length
|
|
163
|
+
while i < n
|
|
164
|
+
c = s[i]
|
|
165
|
+
if c == "&"
|
|
166
|
+
out = out + "&"
|
|
167
|
+
elsif c == "<"
|
|
168
|
+
out = out + "<"
|
|
169
|
+
elsif c == ">"
|
|
170
|
+
out = out + ">"
|
|
171
|
+
elsif c == "\""
|
|
172
|
+
out = out + """
|
|
173
|
+
elsif c == "'"
|
|
174
|
+
out = out + "'"
|
|
175
|
+
else
|
|
176
|
+
out = out + c
|
|
177
|
+
end
|
|
178
|
+
i += 1
|
|
179
|
+
end
|
|
180
|
+
out
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Session signing secret. Empty by default, which disables session
|
|
184
|
+
# writes (the Set-Cookie path no-ops). Set at app load time:
|
|
185
|
+
#
|
|
186
|
+
# Tep.session_secret = ENV.fetch("TEP_SESSION_SECRET")
|
|
187
|
+
#
|
|
188
|
+
# Stored on the APP instance (spinel doesn't reliably type-track
|
|
189
|
+
# module-level `@@cvars` or globals).
|
|
190
|
+
|
|
191
|
+
APP = App.new
|
|
192
|
+
|
|
193
|
+
def self.session_secret; APP.session_secret; end
|
|
194
|
+
def self.session_secret=(v); APP.set_session_secret(v); end
|
|
195
|
+
|
|
196
|
+
# Inbound TLS (tep#148 phase 2). Point these at a PEM cert + key and
|
|
197
|
+
# Tep::Server terminates HTTPS itself; unset (default) = plain HTTP.
|
|
198
|
+
# Tep.tls_cert = "cert.pem"; Tep.tls_key = "key.pem"
|
|
199
|
+
def self.tls_cert; APP.tls_cert; end
|
|
200
|
+
def self.tls_cert=(v); APP.set_tls_cert(v); end
|
|
201
|
+
def self.tls_key; APP.tls_key; end
|
|
202
|
+
def self.tls_key=(v); APP.set_tls_key(v); end
|
|
203
|
+
|
|
204
|
+
# Spinel infers method parameter types from concrete call sites.
|
|
205
|
+
# If a user app never calls Tep.before / Tep.not_found / etc.,
|
|
206
|
+
# spinel falls back to int and the underlying set_* assignment
|
|
207
|
+
# mismatches the typed ivar. Force-calling each setter here with
|
|
208
|
+
# the canonical default ensures the parameter type is locked in
|
|
209
|
+
# regardless of which DSL methods the user app actually invokes.
|
|
210
|
+
APP.set_static_root("")
|
|
211
|
+
APP.set_before(Filter.new)
|
|
212
|
+
APP.set_after(Filter.new)
|
|
213
|
+
APP.set_auth_filter(Filter.new)
|
|
214
|
+
APP.set_auth_bearer_secret("")
|
|
215
|
+
# Broadcast PG-backend setter seeds. enable_pg_backend reaches
|
|
216
|
+
# these via set_broadcast_pg_conn / _channel / _enabled when a
|
|
217
|
+
# connect succeeds; the empty-conninfo seed below short-circuits
|
|
218
|
+
# before getting there, so we exercise the setters directly.
|
|
219
|
+
APP.set_broadcast_pg_enabled(0)
|
|
220
|
+
APP.set_broadcast_pg_channel("")
|
|
221
|
+
APP.set_broadcast_pg_conn(PG::Connection.new(""))
|
|
222
|
+
APP.set_not_found(Handler.new)
|
|
223
|
+
# Type-seeding: methods that may not be called by a given user app
|
|
224
|
+
# would otherwise default their param C types to mrb_int and
|
|
225
|
+
# mismatch the typed ivars they touch.
|
|
226
|
+
_tep_seed_res = Response.new
|
|
227
|
+
_tep_seed_res.set_cookie("", "", str_hash)
|
|
228
|
+
APP.set_session_secret("")
|
|
229
|
+
APP.set_tls_cert("")
|
|
230
|
+
APP.set_tls_key("")
|
|
231
|
+
_tep_seed_sess = Session.new
|
|
232
|
+
_tep_seed_sess.load_from("", "")
|
|
233
|
+
_tep_seed_sess.to_cookie_value("")
|
|
234
|
+
_tep_seed_sess.set("a", "")
|
|
235
|
+
_tep_seed_sess.get("a")
|
|
236
|
+
_tep_seed_sess.has?("a")
|
|
237
|
+
_tep_seed_res.start_stream(Streamer.new)
|
|
238
|
+
_tep_seed_stream = Stream.new(0)
|
|
239
|
+
_tep_seed_res.streamer.pump(_tep_seed_stream)
|
|
240
|
+
_tep_seed_stream.write("") # pin the parameter type to :str
|
|
241
|
+
Tep.h("") # pin Tep.h(s)'s param to :str
|
|
242
|
+
# Multipart parser: pin all param types so the server-side
|
|
243
|
+
# branches that call Tep::Multipart.parse have proper signatures
|
|
244
|
+
# even when no user app exercises multipart on its own.
|
|
245
|
+
Tep::Multipart.parse("", "")
|
|
246
|
+
Tep::Multipart.extract_boundary("")
|
|
247
|
+
Tep::Multipart.extract_field_name("")
|
|
248
|
+
_tep_seed_res.start_websocket("", Tep::WebSocket::Driver.new(0))
|
|
249
|
+
|
|
250
|
+
# AuthOAuth2 type-seeding. Every public cmeth needs at least one
|
|
251
|
+
# top-level call so spinel locks the param C types in compile
|
|
252
|
+
# units that don't otherwise exercise OAuth2 (e.g. test_llm.rb
|
|
253
|
+
# builds an app that never touches Tep::AuthOAuth2; without
|
|
254
|
+
# these seeds spinel defaults the params to mrb_int and the
|
|
255
|
+
# AuthOAuth2Client / AuthOAuth2Code constructor calls inside
|
|
256
|
+
# the methods mismatch the typed ivars).
|
|
257
|
+
_tep_seed_oauth2_caps = [:_seed]
|
|
258
|
+
_tep_seed_oauth2_caps.delete_at(0)
|
|
259
|
+
Tep::AuthOAuth2.register_client("_seed", "", "", _tep_seed_oauth2_caps)
|
|
260
|
+
Tep::AuthOAuth2.unregister_client("_seed")
|
|
261
|
+
Tep::AuthOAuth2.find_client("_seed")
|
|
262
|
+
_tep_seed_oauth2_code = Tep::AuthOAuth2.issue_code("_seed", "_seed", "", 0)
|
|
263
|
+
Tep::AuthOAuth2.exchange_code(_tep_seed_oauth2_code, "_seed", 0)
|
|
264
|
+
|
|
265
|
+
# Broadcast type-seeding. Same pattern: pin every cmeth's param C
|
|
266
|
+
# types so compile units that don't otherwise exercise pub/sub
|
|
267
|
+
# still get correct signatures.
|
|
268
|
+
_tep_seed_broadcast_sub = Tep::Broadcast.subscribe("_seed", -1)
|
|
269
|
+
Tep::Broadcast.subscribe_ws("_seed", -1)
|
|
270
|
+
Tep::Broadcast.publish("_seed", "")
|
|
271
|
+
Tep::Broadcast.subscribers_for("_seed")
|
|
272
|
+
Tep::Broadcast.unsubscribe(_tep_seed_broadcast_sub)
|
|
273
|
+
Tep::Broadcast.unsubscribe_fd(-1)
|
|
274
|
+
Tep::Broadcast.subscriber_count
|
|
275
|
+
Tep::Broadcast.clear
|
|
276
|
+
|
|
277
|
+
# Broadcast PG-backend seeds. enable_pg_backend("", "") tries to
|
|
278
|
+
# open a PG connection -- empty conninfo behaves the same as the
|
|
279
|
+
# PG::Connection.new("") seed above: connect fails, returns -1.
|
|
280
|
+
# The point is to pin parameter types on every cmeth.
|
|
281
|
+
Tep::Broadcast.enable_pg_backend("", "")
|
|
282
|
+
Tep::Broadcast.poll_pg_once(0)
|
|
283
|
+
Tep::Broadcast.disable_pg_backend
|
|
284
|
+
Tep::Broadcast.encode_wire("", "")
|
|
285
|
+
Tep::Broadcast.deliver_wire_local("0:")
|
|
286
|
+
Tep::Broadcast.publish_local_only("_seed", "")
|
|
287
|
+
# The new PG::Connection LISTEN/NOTIFY method seeds live further
|
|
288
|
+
# down with the rest of the PG seeds, where _tep_seed_pg_conn is
|
|
289
|
+
# already defined.
|
|
290
|
+
|
|
291
|
+
# Presence type-seeding. Same pattern as Broadcast: pin every
|
|
292
|
+
# cmeth's param C types so compile units that don't otherwise
|
|
293
|
+
# touch Presence still get correct signatures. track() requires
|
|
294
|
+
# a req with a populated identity -- construct a synthetic one.
|
|
295
|
+
_tep_seed_presence_caps = [:_seed]
|
|
296
|
+
_tep_seed_presence_caps.delete_at(0)
|
|
297
|
+
_tep_seed_presence_req = Tep::Request.new
|
|
298
|
+
_tep_seed_presence_req.identity = Tep::Identity.new(
|
|
299
|
+
"_seed", nil, _tep_seed_presence_caps)
|
|
300
|
+
Tep::Presence.track(_tep_seed_presence_req, "_seed", -1)
|
|
301
|
+
Tep::Presence.find_entry("_seed", -1)
|
|
302
|
+
Tep::Presence.list("_seed")
|
|
303
|
+
Tep::Presence.count("_seed")
|
|
304
|
+
Tep::Presence.count_humans("_seed")
|
|
305
|
+
Tep::Presence.count_agents("_seed")
|
|
306
|
+
Tep::Presence.count_filtered("_seed", :both)
|
|
307
|
+
Tep::Presence.set_status("_seed", -1, :busy, "", 0)
|
|
308
|
+
Tep::Presence.clear_status("_seed", -1)
|
|
309
|
+
Tep::Presence.untrack("_seed", -1)
|
|
310
|
+
Tep::Presence.untrack_by_fd(-1)
|
|
311
|
+
Tep::Presence.clear
|
|
312
|
+
# Diff + auto-expiry seeds (chunk 3.2).
|
|
313
|
+
Tep::Presence.diff_topic("_seed")
|
|
314
|
+
_tep_seed_presence_entry = Tep::PresenceEntry.new(
|
|
315
|
+
"_seed", "_seed", :human, "", -1, 0)
|
|
316
|
+
Tep::Presence.encode_diff("join", _tep_seed_presence_entry)
|
|
317
|
+
Tep::Presence.publish_diff("join", _tep_seed_presence_entry)
|
|
318
|
+
Tep::Presence.sweep_expired_status
|
|
319
|
+
# PG mirror seeds (chunk 3.3). enable_pg_mirror("") fails the
|
|
320
|
+
# connect cleanly (-1) but still pins param types.
|
|
321
|
+
Tep::Presence.enable_pg_mirror("")
|
|
322
|
+
Tep::Presence.schema_sql
|
|
323
|
+
Tep::Presence.mirror_insert(_tep_seed_presence_entry)
|
|
324
|
+
Tep::Presence.mirror_delete("_seed", -1)
|
|
325
|
+
Tep::Presence.mirror_status("_seed", -1, :available, "", 0)
|
|
326
|
+
Tep::Presence.list_global("_seed")
|
|
327
|
+
Tep::Presence.count_global("_seed")
|
|
328
|
+
Tep::Presence.worker_schema_sql
|
|
329
|
+
Tep::Presence.heartbeat
|
|
330
|
+
Tep::Presence.prune_stale_workers(90)
|
|
331
|
+
Tep::Presence.disable_pg_mirror
|
|
332
|
+
# Same APP-setter-via-constant pattern as the broadcast_pg_conn
|
|
333
|
+
# seed: PG::Connection.new can't run inside App#initialize
|
|
334
|
+
# (Tep::APP is mid-construction; sched_current read segfaults).
|
|
335
|
+
APP.set_presence_pg_enabled(0)
|
|
336
|
+
APP.set_presence_pg_worker_id("")
|
|
337
|
+
APP.set_presence_pg_conn(PG::Connection.new(""))
|
|
338
|
+
|
|
339
|
+
# LiveView type-seeding (chunk 4.1). The render_page + dispatch_event
|
|
340
|
+
# cmeths get pinned via top-level calls; the base-class mount /
|
|
341
|
+
# render / handle_event imeths are pinned via a single noop
|
|
342
|
+
# instance call so subclass dispatch widens cleanly.
|
|
343
|
+
_tep_seed_live_view = Tep::LiveView.new
|
|
344
|
+
_tep_seed_live_view_req = Tep::Request.new
|
|
345
|
+
_tep_seed_live_view.mount(_tep_seed_live_view_req)
|
|
346
|
+
_tep_seed_live_view.render
|
|
347
|
+
_tep_seed_live_view.handle_event("", "", _tep_seed_live_view_req)
|
|
348
|
+
_tep_seed_live_view.dispatch_event_json("{}", _tep_seed_live_view_req)
|
|
349
|
+
_tep_seed_live_view.topic
|
|
350
|
+
_tep_seed_live_view.broadcast_render
|
|
351
|
+
_tep_seed_live_view.handle_presence_diff("{}")
|
|
352
|
+
_tep_seed_live_view.apply_presence_diff_json("{}")
|
|
353
|
+
Tep::LiveView.render_page("", "")
|
|
354
|
+
|
|
355
|
+
# SQLite type-seeding. Each method below pins a parameter type
|
|
356
|
+
# (or pulls the FFI return into use) so spinel emits the correct
|
|
357
|
+
# signatures even for apps that include the require but don't hit
|
|
358
|
+
# every method. We open an anonymous in-memory database, run a
|
|
359
|
+
# tiny round-trip, then close -- the leak is one malloc'd handle
|
|
360
|
+
# per process at startup, which exits with the worker.
|
|
361
|
+
_tep_seed_db = Tep::SQLite.new
|
|
362
|
+
if _tep_seed_db.open(":memory:")
|
|
363
|
+
_tep_seed_db.exec("CREATE TABLE _seed (k TEXT, v INTEGER)")
|
|
364
|
+
_tep_seed_db.prepare("INSERT INTO _seed (k, v) VALUES (?, ?)")
|
|
365
|
+
_tep_seed_db.bind_str(1, "")
|
|
366
|
+
_tep_seed_db.bind_int(2, 0)
|
|
367
|
+
_tep_seed_db.step
|
|
368
|
+
_tep_seed_db.finalize
|
|
369
|
+
_tep_seed_db.last_rowid
|
|
370
|
+
_tep_seed_db.prepare("SELECT k, v FROM _seed")
|
|
371
|
+
_tep_seed_db.step
|
|
372
|
+
_tep_seed_db.col_str(0)
|
|
373
|
+
_tep_seed_db.col_int(1)
|
|
374
|
+
_tep_seed_db.col_count
|
|
375
|
+
_tep_seed_db.reset
|
|
376
|
+
_tep_seed_db.finalize
|
|
377
|
+
_tep_seed_db.first_str("SELECT k FROM _seed", "")
|
|
378
|
+
_tep_seed_db.first_int("SELECT v FROM _seed", "")
|
|
379
|
+
# Pin the prepare_cached param type so apps that don't call it
|
|
380
|
+
# still see the FFI shape (`Sqlite.tep_sqlite_prepare_cached(int,
|
|
381
|
+
# str)`) at module-load. Cache hit / miss / reuse paths are
|
|
382
|
+
# exercised by test/test_sqlite_cached.rb at runtime.
|
|
383
|
+
_tep_seed_db.prepare_cached("SELECT k FROM _seed")
|
|
384
|
+
_tep_seed_db.step
|
|
385
|
+
_tep_seed_db.finalize
|
|
386
|
+
_tep_seed_db.close
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
# PG type-seeding. PG::Connection.new("") returns a connection-
|
|
390
|
+
# failed instance (@pgh=-1) rather than raising, so this is safe
|
|
391
|
+
# at module load regardless of whether libpq has a reachable
|
|
392
|
+
# server. The point is to pin parameter / return types on every
|
|
393
|
+
# public Connection / Result method so apps that don't exercise
|
|
394
|
+
# one method still compile cleanly.
|
|
395
|
+
_tep_seed_pg_conn = PG::Connection.new("")
|
|
396
|
+
_tep_seed_pg_conn.connected?
|
|
397
|
+
_tep_seed_pg_conn.status
|
|
398
|
+
_tep_seed_pg_conn.transaction_status
|
|
399
|
+
_tep_seed_pg_conn.server_version
|
|
400
|
+
_tep_seed_pg_conn.error_message
|
|
401
|
+
_tep_seed_pg_conn.escape_string("")
|
|
402
|
+
_tep_seed_pg_conn.escape_identifier("")
|
|
403
|
+
_tep_seed_pg_conn.escape_literal("")
|
|
404
|
+
_tep_seed_pg_conn.last_sqlstate = ""
|
|
405
|
+
_tep_seed_pg_conn.last_error_message = ""
|
|
406
|
+
_tep_seed_pg_conn.last_result_rh = -1
|
|
407
|
+
# Async surface seed -- calling these on a failed-conn instance
|
|
408
|
+
# is harmless (the C shim short-circuits on conn slot < 1).
|
|
409
|
+
_tep_seed_pg_conn.async_exec("")
|
|
410
|
+
_tep_seed_pg_seed_arr = [""]
|
|
411
|
+
_tep_seed_pg_seed_arr.delete_at(0)
|
|
412
|
+
_tep_seed_pg_conn.async_exec_params("", _tep_seed_pg_seed_arr)
|
|
413
|
+
# Async connect cmeth. Returns -1 for empty conninfo from a
|
|
414
|
+
# non-scheduled context (the shim's PQconnectStart-then-FAILED
|
|
415
|
+
# path), which is type-equivalent to the success path.
|
|
416
|
+
PG::Connection.async_connect("")
|
|
417
|
+
# LISTEN / NOTIFY surface (Tep::Broadcast PG backend lands here).
|
|
418
|
+
_tep_seed_pg_conn.listen("_seed")
|
|
419
|
+
_tep_seed_pg_conn.unlisten("_seed")
|
|
420
|
+
_tep_seed_pg_conn.notify("_seed", "")
|
|
421
|
+
_tep_seed_pg_conn.poll_notification(0)
|
|
422
|
+
_tep_seed_pg_conn.last_notify_channel
|
|
423
|
+
_tep_seed_pg_conn.last_notify_payload
|
|
424
|
+
_tep_seed_pg_res = PG::Result.new(-1)
|
|
425
|
+
_tep_seed_pg_res.ntuples
|
|
426
|
+
_tep_seed_pg_res.nfields
|
|
427
|
+
_tep_seed_pg_res.fname(0)
|
|
428
|
+
_tep_seed_pg_res.fnumber("")
|
|
429
|
+
_tep_seed_pg_res.ftype(0)
|
|
430
|
+
_tep_seed_pg_res.fformat(0)
|
|
431
|
+
_tep_seed_pg_res.fmod(0)
|
|
432
|
+
_tep_seed_pg_res.getvalue(0, 0)
|
|
433
|
+
_tep_seed_pg_res.getisnull(0, 0)
|
|
434
|
+
_tep_seed_pg_res.getlength(0, 0)
|
|
435
|
+
_tep_seed_pg_res.value(0, 0)
|
|
436
|
+
_tep_seed_pg_res.error_field(67)
|
|
437
|
+
_tep_seed_pg_res.cmd_status
|
|
438
|
+
_tep_seed_pg_res.cmd_tuples
|
|
439
|
+
_tep_seed_pg_res.error_message
|
|
440
|
+
_tep_seed_pg_res.sql_state
|
|
441
|
+
_tep_seed_pg_res.fields
|
|
442
|
+
_tep_seed_pg_res.values
|
|
443
|
+
_tep_seed_pg_res.column_values(0)
|
|
444
|
+
_tep_seed_pg_res.clear
|
|
445
|
+
_tep_seed_pg_conn.close
|
|
446
|
+
# Pool seed -- size 0 so we don't try to open real conns at load.
|
|
447
|
+
_tep_seed_pg_pool = PG::Pool.new("", 0)
|
|
448
|
+
_tep_seed_pg_pool.healthy?
|
|
449
|
+
_tep_seed_pg_pool.available
|
|
450
|
+
_tep_seed_pg_pool.size
|
|
451
|
+
_tep_seed_pg_pool.set_checkout_timeout_ms(0)
|
|
452
|
+
_tep_seed_pg_pool.close_all
|
|
453
|
+
# NB: don't checkout/checkin against the size-0 seed pool; it'd
|
|
454
|
+
# spin until timeout. The seed has @free.length=0 forever.
|
|
455
|
+
|
|
456
|
+
# Tep::Json type-seeding. Pin every public method's parameter
|
|
457
|
+
# types so an app that uses one method but not another still
|
|
458
|
+
# compiles cleanly. Calls have no side effects beyond producing
|
|
459
|
+
# discardable strings.
|
|
460
|
+
Tep::Json.escape("")
|
|
461
|
+
Tep::Json.quote("")
|
|
462
|
+
Tep::Json.encode_pair_str("", "")
|
|
463
|
+
Tep::Json.encode_pair_int("", 0)
|
|
464
|
+
_tep_seed_str_h = Tep.str_hash
|
|
465
|
+
_tep_seed_str_h["k"] = "v"
|
|
466
|
+
Tep::Json.from_str_hash(_tep_seed_str_h)
|
|
467
|
+
_tep_seed_int_h = {"" => 0}
|
|
468
|
+
_tep_seed_int_h.delete("")
|
|
469
|
+
_tep_seed_int_h["k"] = 1
|
|
470
|
+
Tep::Json.from_int_hash(_tep_seed_int_h)
|
|
471
|
+
|
|
472
|
+
# Tep::Logger seed -- pin parameter types for every method even
|
|
473
|
+
# when an app uses one but not another. The level-name string
|
|
474
|
+
# ("info") and the messages ("") pin the :str shape; the file-
|
|
475
|
+
# path setter pins to_file's :str arg.
|
|
476
|
+
_tep_seed_logger = Tep::Logger.new
|
|
477
|
+
_tep_seed_logger.set_level("info")
|
|
478
|
+
_tep_seed_logger.to_file("")
|
|
479
|
+
_tep_seed_logger.to_stderr
|
|
480
|
+
_tep_seed_logger.debug("")
|
|
481
|
+
_tep_seed_logger.info("")
|
|
482
|
+
_tep_seed_logger.warn("")
|
|
483
|
+
_tep_seed_logger.error("")
|
|
484
|
+
Tep::Logger.level_value("info")
|
|
485
|
+
|
|
486
|
+
# Tep::Jwt seed -- pin every method's :str arg types. The
|
|
487
|
+
# secret + payload are blank but the call shapes pin the FFI
|
|
488
|
+
# signature dispatch.
|
|
489
|
+
Tep::Jwt.encode_hs256("", "")
|
|
490
|
+
Tep::Jwt.verify_hs256("", "")
|
|
491
|
+
Tep::Jwt.decode_payload("")
|
|
492
|
+
Tep::Jwt.verify_and_decode("", "")
|
|
493
|
+
Tep::Jwt.timing_safe_eq("", "")
|
|
494
|
+
|
|
495
|
+
# Tep::Password seed -- one cheap PBKDF2 round at startup, just
|
|
496
|
+
# to pin every method's parameter types. iters=1 keeps the cost
|
|
497
|
+
# negligible.
|
|
498
|
+
_tep_seed_pwd_hash = Tep::Password.hash("seed")
|
|
499
|
+
Tep::Password.verify("seed", _tep_seed_pwd_hash)
|
|
500
|
+
Tep::Password.split4("a$b$c$d")
|
|
501
|
+
|
|
502
|
+
# Tep::Security seeding -- pin the filter classes' params.
|
|
503
|
+
_tep_seed_cors = Tep::Security::Cors.new
|
|
504
|
+
_tep_seed_cors.set_origin("")
|
|
505
|
+
_tep_seed_cors.set_allowed_verbs("")
|
|
506
|
+
_tep_seed_cors.set_allowed_headers("")
|
|
507
|
+
_tep_seed_cors.set_max_age(0)
|
|
508
|
+
_tep_seed_hdrs = Tep::Security::Headers.new
|
|
509
|
+
_tep_seed_hdrs.set_hsts(0)
|
|
510
|
+
|
|
511
|
+
# Tep::Assets seed -- pin the str args before any user-supplied
|
|
512
|
+
# _add calls land. The asset hash starts empty; user apps that
|
|
513
|
+
# have `<app>/assets/` get _add lines emitted by bin/tep at
|
|
514
|
+
# build time.
|
|
515
|
+
Tep::Assets._add("", "", "")
|
|
516
|
+
Tep::Assets.has?("")
|
|
517
|
+
_tep_seed_assets_res = Response.new
|
|
518
|
+
Tep::Assets.serve("", _tep_seed_assets_res)
|
|
519
|
+
|
|
520
|
+
# Tep::Scheduler seed -- run every public method once so spinel
|
|
521
|
+
# pins the param/return types. The seed Fiber's body is an
|
|
522
|
+
# immediately-finishing Tep.seed_fiber_noop, so resume + tick are
|
|
523
|
+
# cheap. io_wait gets seeded outside any fiber context, which
|
|
524
|
+
# exercises the idx < 0 single-shot poll path (fd=-1 returns
|
|
525
|
+
# immediately on most kernels with a POLLNVAL, which we collapse
|
|
526
|
+
# to 0 in sphttp_poll_ready).
|
|
527
|
+
_tep_seed_fiber = Tep.seed_fiber
|
|
528
|
+
Tep::Scheduler.spawn_fiber(_tep_seed_fiber)
|
|
529
|
+
Tep::Scheduler.tick(0)
|
|
530
|
+
Tep::Scheduler.poll_round(0)
|
|
531
|
+
Tep::Scheduler.any_io_waiter
|
|
532
|
+
Tep::Scheduler.alive_count
|
|
533
|
+
Tep::Scheduler.next_wake
|
|
534
|
+
Tep::Scheduler.run_until_empty
|
|
535
|
+
Tep::Scheduler.run_for(0)
|
|
536
|
+
Tep::Scheduler.pause(0)
|
|
537
|
+
Tep::Scheduler.io_wait(-1, Tep::Scheduler::READ, 0)
|
|
538
|
+
Tep::Scheduler.clear
|
|
539
|
+
|
|
540
|
+
# Tep::Shell seed -- pin :str args at the FFI boundary.
|
|
541
|
+
Tep::Shell.run(":")
|
|
542
|
+
Tep::Shell.run_limited(":", 1)
|
|
543
|
+
Tep::Shell.read("/etc/hostname")
|
|
544
|
+
Tep::Shell.read_limited("/etc/hostname", 64)
|
|
545
|
+
|
|
546
|
+
# Tep::Url seed -- the new split_url has to land at compile time.
|
|
547
|
+
Tep::Url.split_url("http://x/")
|
|
548
|
+
|
|
549
|
+
# Tep::Http seed -- every public method gets one canonical call so
|
|
550
|
+
# spinel pins the param types. The URL "http://127.0.0.1:1/" won't
|
|
551
|
+
# connect; send_req returns the empty Response, which is the
|
|
552
|
+
# type-pinning behaviour we want without any real I/O.
|
|
553
|
+
_tep_seed_http_headers = Tep.str_hash
|
|
554
|
+
_tep_seed_http_headers["k"] = "v"
|
|
555
|
+
Tep::Http.send_req("GET", "http://127.0.0.1:1/", "", _tep_seed_http_headers)
|
|
556
|
+
Tep::Http.get("http://127.0.0.1:1/")
|
|
557
|
+
Tep::Http.post("http://127.0.0.1:1/", "")
|
|
558
|
+
Tep::Http.put("http://127.0.0.1:1/", "")
|
|
559
|
+
Tep::Http.patch("http://127.0.0.1:1/", "")
|
|
560
|
+
Tep::Http.delete("http://127.0.0.1:1/")
|
|
561
|
+
Tep::Http.head("http://127.0.0.1:1/")
|
|
562
|
+
Tep::Http.empty_headers
|
|
563
|
+
# Pool seed (chunk 6.7a). Pin the (str, int) -> int / (int, str, int) -> int
|
|
564
|
+
# arities so the FFI bindings resolve. Each call site exercises one
|
|
565
|
+
# primitive against the empty pool -- harmless at boot.
|
|
566
|
+
Tep::Http::Pool.claim("127.0.0.1", 1)
|
|
567
|
+
Tep::Http::Pool.release(-1, "127.0.0.1", 1)
|
|
568
|
+
Tep::Http::Pool.close_idle(30)
|
|
569
|
+
Tep::Http::Pool.stats
|
|
570
|
+
_tep_seed_http = Tep::Http.new("http://127.0.0.1:1")
|
|
571
|
+
_tep_seed_http.set_header("k", "v")
|
|
572
|
+
_tep_seed_http.do_get("/")
|
|
573
|
+
_tep_seed_http.do_post("/", "")
|
|
574
|
+
_tep_seed_http.do_put("/", "")
|
|
575
|
+
_tep_seed_http.do_patch("/", "")
|
|
576
|
+
_tep_seed_http.do_delete("/")
|
|
577
|
+
_tep_seed_http.do_head("/")
|
|
578
|
+
# parse_response and index_from are internal; let spinel infer
|
|
579
|
+
# their types from the send_req call site rather than seeding
|
|
580
|
+
# separately (which widens `out` to poly).
|
|
581
|
+
|
|
582
|
+
# Tep::Proxy seed -- a base-class Proxy instance pins the handler
|
|
583
|
+
# slot + every overridable hook signature so subclass call sites
|
|
584
|
+
# in user code resolve cleanly (same idiom as set_before(Filter.new)
|
|
585
|
+
# and the Parallel/Job seeds). handle() exercises the full forward
|
|
586
|
+
# path against a dead port (status 0 -> 502), which fails fast like
|
|
587
|
+
# the Tep::Http seed above.
|
|
588
|
+
_tep_seed_proxy = Tep::Proxy.new("http://127.0.0.1:1")
|
|
589
|
+
_tep_seed_proxy_req = Tep::Request.new
|
|
590
|
+
_tep_seed_proxy_res = Response.new
|
|
591
|
+
_tep_seed_proxy_ureq = Tep::Proxy::UpstreamRequest.new
|
|
592
|
+
_tep_seed_proxy_ureq.set_header("k", "v")
|
|
593
|
+
_tep_seed_proxy.rewrite_path("/")
|
|
594
|
+
_tep_seed_proxy.before_forward(_tep_seed_proxy_req, _tep_seed_proxy_res, _tep_seed_proxy_ureq)
|
|
595
|
+
_tep_seed_proxy.after_forward(_tep_seed_proxy_req, Tep::Http::Response.new, _tep_seed_proxy_res)
|
|
596
|
+
_tep_seed_proxy.handle(_tep_seed_proxy_req, _tep_seed_proxy_res)
|
|
597
|
+
Tep::Proxy.hop_by_hop?("connection")
|
|
598
|
+
# Streaming surface (chunk 6.2). Pin the hook signatures + the
|
|
599
|
+
# UpstreamHead parser + the proxy's non-IO pump helpers. run_stream,
|
|
600
|
+
# pump and read_upstream_head are NOT called here -- they do blocking
|
|
601
|
+
# io_wait on a real fd; their param/return types self-pin from their
|
|
602
|
+
# bodies, and ProxyStreamer flows into res.start_stream from
|
|
603
|
+
# start_streaming_forward (statically reachable from handle), which
|
|
604
|
+
# wires ProxyStreamer.pump (-> run_stream) into the Streamer dispatch.
|
|
605
|
+
# 6.4 per-request upstream picker. Pin the Tep::Request param +
|
|
606
|
+
# the :str return so subclass overrides resolve cleanly.
|
|
607
|
+
_tep_seed_proxy.pick_upstream(_tep_seed_proxy_req)
|
|
608
|
+
# 6.6 body-cap accessors. Type-pin the int setters / getters so
|
|
609
|
+
# subclass overrides + block-DSL setters compile.
|
|
610
|
+
_tep_seed_proxy.max_request_body_bytes = 1
|
|
611
|
+
_tep_seed_proxy.max_response_body_bytes = 1
|
|
612
|
+
# 6.5 retry policy. Pin the RetryPolicy slot via instantiation +
|
|
613
|
+
# the hook return type via a call to #retry_policy(req).
|
|
614
|
+
_tep_seed_retry_policy = Tep::Proxy::RetryPolicy.new
|
|
615
|
+
_tep_seed_retry_policy.max_attempts = 1
|
|
616
|
+
_tep_seed_retry_policy.base_backoff_ms = 0
|
|
617
|
+
_tep_seed_retry_policy.backoff_multiplier = 2
|
|
618
|
+
_tep_seed_retry_policy.retry_on_status = [502, 503, 504]
|
|
619
|
+
# Float-seconds setter (#133). Pin the Float -> int(ms) lowering
|
|
620
|
+
# so the conversion call site resolves.
|
|
621
|
+
_tep_seed_retry_policy.base_backoff_secs = 0.0
|
|
622
|
+
_tep_seed_retry_policy.base_backoff_secs
|
|
623
|
+
# Pin Sock.sphttp_sleep_ms's :int param so the backoff call site
|
|
624
|
+
# resolves (called from Tep::Proxy#handle).
|
|
625
|
+
Sock.sphttp_sleep_ms(0)
|
|
626
|
+
# Tep::Json.get_float seed (#133). Pin the (String, String) -> Float
|
|
627
|
+
# surface so callers (CompletionsHandler temperature/top_p,
|
|
628
|
+
# backends that parse their own bodies) resolve cleanly.
|
|
629
|
+
Tep::Json.get_float("{\"temperature\":0.7}", "temperature")
|
|
630
|
+
_tep_seed_retry_policy.backoff_for(0)
|
|
631
|
+
_tep_seed_retry_policy.retriable?(502)
|
|
632
|
+
_tep_seed_proxy.retry_policy(_tep_seed_proxy_req)
|
|
633
|
+
_tep_seed_proxy.stream_request?(_tep_seed_proxy_req)
|
|
634
|
+
_tep_seed_pstats = Tep::Proxy::StreamStats.new
|
|
635
|
+
_tep_seed_pchunk = Tep::Proxy::StreamChunk.new("data: x\n\n")
|
|
636
|
+
_tep_seed_proxy.on_stream_chunk(_tep_seed_pchunk, _tep_seed_stream, _tep_seed_pstats)
|
|
637
|
+
_tep_seed_proxy.on_stream_end(_tep_seed_proxy_req, _tep_seed_stream, _tep_seed_pstats)
|
|
638
|
+
_tep_seed_proxy.drain_events(_tep_seed_stream, _tep_seed_pstats, "data: x\n\n")
|
|
639
|
+
_tep_seed_proxy.dispatch_one(_tep_seed_stream, _tep_seed_pstats, "data: x\n\n")
|
|
640
|
+
_tep_seed_uhead = Tep::Proxy::UpstreamHead.new
|
|
641
|
+
_tep_seed_uhead.fill_from("HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nTransfer-Encoding: chunked")
|
|
642
|
+
_tep_seed_pstreamer = Tep::Proxy::ProxyStreamer.new
|
|
643
|
+
_tep_seed_pstreamer.proxy = _tep_seed_proxy
|
|
644
|
+
_tep_seed_proxy_res.start_stream(_tep_seed_pstreamer)
|
|
645
|
+
|
|
646
|
+
# Tep::Events seed (toy/v1 emitter). Seeded with a disabled ("")
|
|
647
|
+
# path so the guards short-circuit before any File I/O at boot;
|
|
648
|
+
# the JSON-building bodies + sphttp_iso8601_utc call still compile
|
|
649
|
+
# statically, and the call args pin every param type.
|
|
650
|
+
_tep_seed_events = Tep::Events.new("")
|
|
651
|
+
_tep_seed_events.enabled?
|
|
652
|
+
_tep_seed_events.run_start("host", "cpu", "model", "/path", "{}")
|
|
653
|
+
_tep_seed_events.inference("model", 0, 0, 0, "{}")
|
|
654
|
+
_tep_seed_events.record_error
|
|
655
|
+
_tep_seed_events.run_end("ok")
|
|
656
|
+
# #128: aggregated run_end (parent reads JSONL + sums). The seed
|
|
657
|
+
# path is "" so the call short-circuits before any File I/O at
|
|
658
|
+
# boot; this exists to pin the method's surface area so spinel's
|
|
659
|
+
# codegen emits it.
|
|
660
|
+
_tep_seed_events.run_end_aggregated("completed")
|
|
661
|
+
_tep_seed_events.rel_t
|
|
662
|
+
Sock.sphttp_iso8601_utc(0)
|
|
663
|
+
|
|
664
|
+
# Tep::Llm::OpenAI::Server seed (Battery 7, chunk 7.1a). Pin the
|
|
665
|
+
# Backend slot + interface + the ModelsHandler dispatch through
|
|
666
|
+
# APP.openai_backend. serve! is NOT called here -- it mounts a route
|
|
667
|
+
# (a global side effect); its types self-pin from its body.
|
|
668
|
+
_tep_seed_oai_backend = Tep::Llm::OpenAI::Backend.new
|
|
669
|
+
Tep::APP.set_openai_backend(_tep_seed_oai_backend)
|
|
670
|
+
# 7.1c openai_events slot. Re-uses _tep_seed_events (already declared
|
|
671
|
+
# above as the Tep::Events seed). Pins APP.openai_events so the route
|
|
672
|
+
# handlers' `Tep::APP.openai_events.inference(...)` dispatch resolves.
|
|
673
|
+
Tep::APP.set_openai_events(_tep_seed_events)
|
|
674
|
+
Tep::Llm::OpenAI::Server.use(_tep_seed_oai_backend)
|
|
675
|
+
_tep_seed_oai_backend.list_models
|
|
676
|
+
_tep_seed_oai_backend.supports_chat?
|
|
677
|
+
_tep_seed_oai_backend.device_kind
|
|
678
|
+
_tep_seed_oai_backend.supports_embeddings?
|
|
679
|
+
_tep_seed_oai_models = Tep::Llm::OpenAI::ModelsHandler.new
|
|
680
|
+
_tep_seed_oai_models.handle(_tep_seed_proxy_req, _tep_seed_proxy_res)
|
|
681
|
+
# 7.1b /v1/completions surface.
|
|
682
|
+
Tep::Json.get_int_array("{}", "prompt")
|
|
683
|
+
_tep_seed_oai_sampling = Tep::Llm::OpenAI::Sampling.new
|
|
684
|
+
_tep_seed_oai_sampling.max_tokens = 0
|
|
685
|
+
_tep_seed_oai_sampling.temperature = 1.0
|
|
686
|
+
_tep_seed_oai_sampling.top_p = 1.0
|
|
687
|
+
_tep_seed_oai_comp = Tep::Llm::OpenAI::Completion.new
|
|
688
|
+
_tep_seed_oai_backend.generate_from_tokens("m", Tep::Json.get_int_array("{}", "prompt"), _tep_seed_oai_sampling)
|
|
689
|
+
_tep_seed_oai_completions = Tep::Llm::OpenAI::CompletionsHandler.new
|
|
690
|
+
_tep_seed_oai_completions.handle(_tep_seed_proxy_req, _tep_seed_proxy_res)
|
|
691
|
+
# Chat completions skeleton (POST /v1/chat/completions). Default
|
|
692
|
+
# backend.supports_chat? is false -> ChatCompletionsHandler returns
|
|
693
|
+
# 501; the override path (supports_chat? = true, chat_completion
|
|
694
|
+
# overridden) dispatches to the backend's chat_completion. Pin
|
|
695
|
+
# Backend#chat_completion's `req` param + the ChatCompletionsHandler
|
|
696
|
+
# dispatch through APP.openai_backend.
|
|
697
|
+
_tep_seed_oai_backend.chat_completion(_tep_seed_proxy_req)
|
|
698
|
+
# parse_messages helper. Type-pin the [Tep::Llm::Message] return so
|
|
699
|
+
# backends that call `messages = Tep::Llm::OpenAI.parse_messages(...)`
|
|
700
|
+
# get a typed array.
|
|
701
|
+
Tep::Llm::OpenAI.parse_messages(
|
|
702
|
+
"{\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}")
|
|
703
|
+
Tep::Llm::OpenAI.find_obj_key_str("{}", 0, 2, "role")
|
|
704
|
+
_tep_seed_oai_chat = Tep::Llm::OpenAI::ChatCompletionsHandler.new
|
|
705
|
+
_tep_seed_oai_chat.handle(_tep_seed_proxy_req, _tep_seed_proxy_res)
|
|
706
|
+
# 7.2 streaming completions: pin StreamSink + CompletionsStreamer
|
|
707
|
+
# slots + exercise the backend's generate_stream_from_tokens(sink)
|
|
708
|
+
# arity so the param type resolves. emit_token is called against a
|
|
709
|
+
# fd=-1 Stream -- the seed never runs the actual write (the streamer
|
|
710
|
+
# pump is never invoked at boot), it just gives spinel the surface.
|
|
711
|
+
_tep_seed_oai_stream = Tep::Stream.new(-1)
|
|
712
|
+
_tep_seed_oai_sink = Tep::Llm::OpenAI::StreamSink.new
|
|
713
|
+
_tep_seed_oai_sink.out = _tep_seed_oai_stream
|
|
714
|
+
_tep_seed_oai_sink.model = "m"
|
|
715
|
+
_tep_seed_oai_sink.completion_count
|
|
716
|
+
# emit_token call site pins the `piece` parameter to String. fd=-1
|
|
717
|
+
# makes the underlying sphttp_write_chunk a harmless EBADF at boot.
|
|
718
|
+
_tep_seed_oai_sink.emit_token("seed")
|
|
719
|
+
_tep_seed_oai_backend.generate_stream_from_tokens(
|
|
720
|
+
"m", Tep::Json.get_int_array("{}", "prompt"), _tep_seed_oai_sampling, _tep_seed_oai_sink)
|
|
721
|
+
_tep_seed_oai_cstreamer = Tep::Llm::OpenAI::CompletionsStreamer.new
|
|
722
|
+
_tep_seed_oai_cstreamer.model = "m"
|
|
723
|
+
_tep_seed_oai_cstreamer.token_ids = Tep::Json.get_int_array("{}", "prompt")
|
|
724
|
+
_tep_seed_oai_cstreamer.sampling = _tep_seed_oai_sampling
|
|
725
|
+
_tep_seed_oai_cstreamer.prompt_tokens = 0
|
|
726
|
+
_tep_seed_oai_cstreamer.t0 = 0
|
|
727
|
+
_tep_seed_oai_cstreamer.request_id = ""
|
|
728
|
+
_tep_seed_oai_cstreamer.principal_id = ""
|
|
729
|
+
_tep_seed_proxy_res.start_stream(_tep_seed_oai_cstreamer)
|
|
730
|
+
|
|
731
|
+
# #127 chat streaming: ChatStreamSink + ChatCompletionsStreamer.
|
|
732
|
+
# Mirror the 7.2 seed shape so spinel pins the sink's emit_*
|
|
733
|
+
# arities + the streamer's accessor slots.
|
|
734
|
+
_tep_seed_oai_chat_sink = Tep::Llm::OpenAI::ChatStreamSink.new
|
|
735
|
+
_tep_seed_oai_chat_sink.out = _tep_seed_oai_stream
|
|
736
|
+
_tep_seed_oai_chat_sink.model = "m"
|
|
737
|
+
_tep_seed_oai_chat_sink.completion_count
|
|
738
|
+
_tep_seed_oai_chat_sink.emit_role_prelude("assistant")
|
|
739
|
+
_tep_seed_oai_chat_sink.emit_token("seed")
|
|
740
|
+
_tep_seed_oai_chat_sink.emit_finish("stop")
|
|
741
|
+
_tep_seed_oai_backend.chat_completion_stream(_tep_seed_proxy_req, _tep_seed_oai_chat_sink)
|
|
742
|
+
_tep_seed_oai_chat_streamer = Tep::Llm::OpenAI::ChatCompletionsStreamer.new
|
|
743
|
+
_tep_seed_oai_chat_streamer.req_ref = _tep_seed_proxy_req
|
|
744
|
+
_tep_seed_oai_chat_streamer.model = "m"
|
|
745
|
+
_tep_seed_oai_chat_streamer.prompt_tokens = 0
|
|
746
|
+
_tep_seed_oai_chat_streamer.t0 = 0
|
|
747
|
+
_tep_seed_oai_chat_streamer.request_id = ""
|
|
748
|
+
_tep_seed_oai_chat_streamer.principal_id = ""
|
|
749
|
+
_tep_seed_proxy_res.start_stream(_tep_seed_oai_chat_streamer)
|
|
750
|
+
|
|
751
|
+
# Tep::Shell.write seed.
|
|
752
|
+
Tep::Shell.write("/dev/null", "")
|
|
753
|
+
|
|
754
|
+
# Tep::Parallel seed -- a base-class Parallel instance pins the
|
|
755
|
+
# `worker` slot type to ParallelWorker; subclass call sites at
|
|
756
|
+
# user code get auto-cast (same idiom as set_before(Filter.new)).
|
|
757
|
+
_tep_seed_par = Tep::Parallel.new(Tep::ParallelWorker.new)
|
|
758
|
+
_tep_seed_par_items = [""]
|
|
759
|
+
_tep_seed_par_items.delete_at(0)
|
|
760
|
+
_tep_seed_par.map_processes(_tep_seed_par_items)
|
|
761
|
+
_tep_seed_par.each_process(_tep_seed_par_items)
|
|
762
|
+
Tep::Parallel.scratch_dir
|
|
763
|
+
|
|
764
|
+
# Tep::Job seed -- pin every public-surface method's parameter
|
|
765
|
+
# types against an in-memory SQLite so the leak is one malloc'd
|
|
766
|
+
# handle per process at startup. The base `perform(arg)` is also
|
|
767
|
+
# pinned to :str so subclass overrides resolve cleanly.
|
|
768
|
+
Tep::Job.init_schema(":memory:")
|
|
769
|
+
_tep_seed_job = Tep::Job.new
|
|
770
|
+
_tep_seed_job.perform("")
|
|
771
|
+
Tep::Job.enqueue("seed", "", ":memory:")
|
|
772
|
+
Tep::Job.fetch_next(":memory:")
|
|
773
|
+
Tep::Job.mark_done(":memory:", 0, "")
|
|
774
|
+
Tep::Job.mark_failed(":memory:", 0)
|
|
775
|
+
_tep_seed_str_arr = [""]
|
|
776
|
+
_tep_seed_str_arr.delete_at(0)
|
|
777
|
+
Tep::Json.from_str_array(_tep_seed_str_arr)
|
|
778
|
+
_tep_seed_int_arr = [0]
|
|
779
|
+
_tep_seed_int_arr.delete_at(0)
|
|
780
|
+
Tep::Json.from_int_array(_tep_seed_int_arr)
|
|
781
|
+
Tep::Json.get_str("{}", "")
|
|
782
|
+
Tep::Json.get_int("{}", "")
|
|
783
|
+
Tep::Json.has_key?("{}", "")
|
|
784
|
+
|
|
785
|
+
# Tep::MCP seeds (chunk 5.1). Tools register at compile time via
|
|
786
|
+
# bin/tep's mcp_tool DSL; the runtime helpers below are the
|
|
787
|
+
# shared shapes the translator-emitted dispatcher leans on.
|
|
788
|
+
# Seed both Result-construction paths so neither widens via the
|
|
789
|
+
# "no concrete caller -> int default" route. Read text + is_error
|
|
790
|
+
# off both so attr_accessor types pin to String / Integer.
|
|
791
|
+
_tep_seed_mcp_result = Tep::MCP.text("seed")
|
|
792
|
+
_tep_seed_mcp_result_err = Tep::MCP.error("seed")
|
|
793
|
+
Tep::MCP.nested_extract("{}", "")
|
|
794
|
+
Tep::MCP.initialize_envelope(0, "", "")
|
|
795
|
+
Tep::MCP.tools_list_envelope(0, "[]")
|
|
796
|
+
Tep::MCP.tools_call_envelope(0, "", 0)
|
|
797
|
+
Tep::MCP.tools_call_envelope(0, "", 1)
|
|
798
|
+
Tep::MCP.unknown_tool_envelope(0, "")
|
|
799
|
+
Tep::MCP.method_not_found_envelope(0, "")
|
|
800
|
+
# Resource seeds (chunk 5.3). resource_text gives us a typed
|
|
801
|
+
# ResourceContent for the resources/read path; the envelope
|
|
802
|
+
# builders take scalars to keep param-type inference tight.
|
|
803
|
+
_tep_seed_mcp_rc = Tep::MCP.resource_text("seed-uri", "seed-text")
|
|
804
|
+
_tep_seed_mcp_rc_uri = _tep_seed_mcp_rc.uri
|
|
805
|
+
_tep_seed_mcp_rc_mime = _tep_seed_mcp_rc.mime
|
|
806
|
+
_tep_seed_mcp_rc_text = _tep_seed_mcp_rc.text
|
|
807
|
+
Tep::MCP.resources_list_envelope(0, "[]")
|
|
808
|
+
Tep::MCP.resources_read_envelope(0, "", "text/plain", "")
|
|
809
|
+
Tep::MCP.unknown_resource_envelope(0, "")
|
|
810
|
+
|
|
811
|
+
# Tep::Llm seeds. attr_accessor return types default to mrb_int
|
|
812
|
+
# if spinel sees no concrete callsite -- and Tep::Llm.build_request_body
|
|
813
|
+
# passes msg.role / msg.content into Tep::Json.quote(String) which
|
|
814
|
+
# then mismatches. Pin Message + Response attrs to String, and
|
|
815
|
+
# run one full encode + parse round-trip so the static analyzer
|
|
816
|
+
# sees every public method called with concrete types.
|
|
817
|
+
_tep_seed_llm_msg = Tep::Llm::Message.new("user", "")
|
|
818
|
+
_tep_seed_llm_msg.role = ""
|
|
819
|
+
_tep_seed_llm_msg.content = ""
|
|
820
|
+
_tep_seed_llm_msgs = [_tep_seed_llm_msg]
|
|
821
|
+
Tep::Llm.build_request_body("", "", _tep_seed_llm_msgs)
|
|
822
|
+
_tep_seed_llm_resp = Tep::Llm::Response.new
|
|
823
|
+
_tep_seed_llm_resp.content = ""
|
|
824
|
+
_tep_seed_llm_resp.role = ""
|
|
825
|
+
_tep_seed_llm_resp.stop_reason = ""
|
|
826
|
+
_tep_seed_llm_http_res = Tep::Http::Response.new
|
|
827
|
+
Tep::Llm.parse_response(_tep_seed_llm_http_res)
|
|
828
|
+
Tep::Llm.extract_str_field("", "", 0)
|
|
829
|
+
_tep_seed_llm_client = Tep::Llm.new("")
|
|
830
|
+
_tep_seed_llm_client.set_model("")
|
|
831
|
+
_tep_seed_llm_client.set_api_key("")
|
|
832
|
+
_tep_seed_llm_client.set_system_prompt("")
|
|
833
|
+
# Streaming surface seeds. The chat_stream signature wants a
|
|
834
|
+
# Tep::Stream `out_stream` -- using fd=0 (stdin) for the seed
|
|
835
|
+
# never executes the .write path here (this block runs at module
|
|
836
|
+
# init; the chat_stream call below is type-seed-only and would
|
|
837
|
+
# need a real connection to actually fire). Tep::Llm::StreamState
|
|
838
|
+
# likewise pinned via attr writes.
|
|
839
|
+
_tep_seed_llm_state = Tep::Llm::StreamState.new
|
|
840
|
+
_tep_seed_llm_state.acc = ""
|
|
841
|
+
_tep_seed_llm_state.leftover = ""
|
|
842
|
+
_tep_seed_llm_state.done = false
|
|
843
|
+
_tep_seed_llm_stream = Tep::Stream.new(0)
|
|
844
|
+
Tep::Llm.consume_sse_events(_tep_seed_llm_stream, _tep_seed_llm_state)
|
|
845
|
+
Tep::Llm.dechunk_consume("")
|
|
846
|
+
Tep::Llm.dechunk_leftover("")
|
|
847
|
+
Tep::Llm.dechunk_pass("")
|
|
848
|
+
Tep::Llm.drain_sse_buf("", _tep_seed_llm_stream, "")
|
|
849
|
+
Tep::Llm.hex_to_int("")
|
|
850
|
+
|
|
851
|
+
# Tep::WebSocket seeds. Pins frame/handshake/driver/connection
|
|
852
|
+
# surfaces to concrete typed callsites so the analyzer doesn't
|
|
853
|
+
# default param types to mrb_int.
|
|
854
|
+
_tep_seed_ws_frame = Tep::WebSocket::Frame.new(true, 1, "")
|
|
855
|
+
_tep_seed_ws_frame.fin = true
|
|
856
|
+
_tep_seed_ws_frame.opcode = 1
|
|
857
|
+
_tep_seed_ws_frame.payload = ""
|
|
858
|
+
_tep_seed_ws_frame.encode_unmasked
|
|
859
|
+
Tep::WebSocket::Frame.byte_to_chr(0)
|
|
860
|
+
Tep::WebSocket::Frame.parse_from_buf(0, 0)
|
|
861
|
+
Tep::WebSocket::Frame.reserved_opcode?(0)
|
|
862
|
+
Tep::WebSocket::Frame.control_opcode?(0)
|
|
863
|
+
_tep_seed_ws_pr = Tep::WebSocket::ParseResult.new
|
|
864
|
+
_tep_seed_ws_pr.outcome = ""
|
|
865
|
+
_tep_seed_ws_pr.consumed = 0
|
|
866
|
+
_tep_seed_ws_pr.close_code = 0
|
|
867
|
+
_tep_seed_ws_pr.frame = _tep_seed_ws_frame
|
|
868
|
+
_tep_seed_ws_hsres = Tep::WebSocket::Handshake::Result.new
|
|
869
|
+
_tep_seed_ws_hsres.valid = false
|
|
870
|
+
_tep_seed_ws_hsres.reason = ""
|
|
871
|
+
_tep_seed_ws_hsres.accept_key = ""
|
|
872
|
+
Tep::WebSocket::Handshake.build_response("", "")
|
|
873
|
+
Tep::WebSocket::Handshake.icontains("", "")
|
|
874
|
+
Tep::WebSocket::Handshake.downcase("")
|
|
875
|
+
Tep::WebSocket::Handshake.trim("")
|
|
876
|
+
_tep_seed_ws_csv = Tep::WebSocket::Handshake.split_csv("")
|
|
877
|
+
_tep_seed_ws_handler = Tep::WebSocket::Handler.new
|
|
878
|
+
_tep_seed_ws_event = Tep::WebSocket::Event.new
|
|
879
|
+
_tep_seed_ws_event.data = ""
|
|
880
|
+
_tep_seed_ws_event.code = 0
|
|
881
|
+
_tep_seed_ws_event.reason = ""
|
|
882
|
+
_tep_seed_ws_handler.handle_event(_tep_seed_ws_event)
|
|
883
|
+
_tep_seed_ws_drv = Tep::WebSocket::Driver.new(0)
|
|
884
|
+
_tep_seed_ws_drv.set_max_frame_size(0)
|
|
885
|
+
_tep_seed_ws_drv.set_subprotocol("")
|
|
886
|
+
_tep_seed_ws_drv.set_on_open(_tep_seed_ws_handler)
|
|
887
|
+
_tep_seed_ws_drv.set_on_message(_tep_seed_ws_handler)
|
|
888
|
+
_tep_seed_ws_drv.set_on_close(_tep_seed_ws_handler)
|
|
889
|
+
_tep_seed_ws_drv.set_on_ping(_tep_seed_ws_handler)
|
|
890
|
+
_tep_seed_ws_drv.set_on_pong(_tep_seed_ws_handler)
|
|
891
|
+
_tep_seed_ws_drv.set_on_error(_tep_seed_ws_handler)
|
|
892
|
+
_tep_seed_ws_drv.text("")
|
|
893
|
+
_tep_seed_ws_drv.binary("")
|
|
894
|
+
_tep_seed_ws_drv.ping("")
|
|
895
|
+
_tep_seed_ws_drv.pong("")
|
|
896
|
+
_tep_seed_ws_drv.close(1000, "")
|
|
897
|
+
Tep::WebSocket::Driver.encode_close_payload(0, "")
|
|
898
|
+
_tep_seed_ws_conn = Tep::WebSocket::Connection.new(_tep_seed_ws_drv)
|
|
899
|
+
_tep_seed_ws_conn.set_idle_timeout(0)
|
|
900
|
+
_tep_seed_ws_cs = Tep::WebSocket::ConnectionState.new
|
|
901
|
+
_tep_seed_ws_cs.start = 0
|
|
902
|
+
_tep_seed_ws_cs.avail = 0
|
|
903
|
+
|
|
904
|
+
# ---------------- DSL ----------------
|
|
905
|
+
# Spinel emits every defined method whether called or not, and
|
|
906
|
+
# infers parameter types from concrete call sites; methods nobody
|
|
907
|
+
# calls fall back to int parameters that mismatch the typed ivars
|
|
908
|
+
# they assign. So the v0.1 surface only exposes what the bundled
|
|
909
|
+
# demos actually use; richer DSL methods (before/after/not_found)
|
|
910
|
+
# are layered on as the demos grow to exercise them.
|
|
911
|
+
|
|
912
|
+
def self.get(pattern, handler); APP.add_route("GET", pattern, handler); end
|
|
913
|
+
def self.post(pattern, handler); APP.add_route("POST", pattern, handler); end
|
|
914
|
+
def self.put(pattern, handler); APP.add_route("PUT", pattern, handler); end
|
|
915
|
+
def self.patch(pattern, handler); APP.add_route("PATCH", pattern, handler); end
|
|
916
|
+
def self.delete(pattern, handler); APP.add_route("DELETE", pattern, handler); end
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
def self.public_dir(root)
|
|
920
|
+
APP.set_static_root(root)
|
|
921
|
+
end
|
|
922
|
+
|
|
923
|
+
def self.before(filter)
|
|
924
|
+
APP.set_before(filter)
|
|
925
|
+
end
|
|
926
|
+
|
|
927
|
+
def self.after(filter)
|
|
928
|
+
APP.set_after(filter)
|
|
929
|
+
end
|
|
930
|
+
|
|
931
|
+
def self.not_found(handler)
|
|
932
|
+
APP.set_not_found(handler)
|
|
933
|
+
end
|
|
934
|
+
|
|
935
|
+
# ARGV access only emits `sp_argv` when used at top level, so the
|
|
936
|
+
# translator emits the option-parsing loop itself before calling
|
|
937
|
+
# `Tep.run!`. The `scheduled` flag picks between the prefork
|
|
938
|
+
# blocking server (default) and the fiber-per-connection
|
|
939
|
+
# Tep::Server::Scheduled (opt-in via `set :scheduler, :scheduled`
|
|
940
|
+
# in the app source, or `-s` on the CLI). At the next major tep
|
|
941
|
+
# release Scheduled becomes the default and Blocking is deleted;
|
|
942
|
+
# the parallel-classes period exists only to make the rollback
|
|
943
|
+
# path obvious during the transition.
|
|
944
|
+
#
|
|
945
|
+
# Single dispatch method (rather than parallel run! / run_scheduled!)
|
|
946
|
+
# because spinel's codegen mis-declares heap-cell parameters when
|
|
947
|
+
# two same-arity sibling methods are called from an if/else --
|
|
948
|
+
# both branches reference `quiet` as a heap-cell but only the first
|
|
949
|
+
# path declares it. Bundling the decision inside one method
|
|
950
|
+
# sidesteps the codegen miss.
|
|
951
|
+
#
|
|
952
|
+
# `scheduled` defaults to false so apps that ship the historical
|
|
953
|
+
# 3-arg call (Tep.run!(port, workers, quiet)) keep building. Spinel
|
|
954
|
+
# accepts the call without the 4th arg only because it supports
|
|
955
|
+
# default-value params; without this, the 3-arg call silently
|
|
956
|
+
# miscompiled (matz/spinel arity-warning shape, tep#13).
|
|
957
|
+
def self.run!(port, workers, quiet, scheduled = false)
|
|
958
|
+
if scheduled
|
|
959
|
+
Server::Scheduled.new(APP).run(port, workers, quiet)
|
|
960
|
+
else
|
|
961
|
+
Server.new(APP).run(port, workers, quiet)
|
|
962
|
+
end
|
|
963
|
+
end
|
|
964
|
+
|
|
965
|
+
# Called by the SERVER PARENT (workers>1) or the single process
|
|
966
|
+
# (workers=1) at SIGTERM/SIGINT, AFTER the worker children have
|
|
967
|
+
# exited. Children no longer emit run_end themselves -- #128 moved
|
|
968
|
+
# the emission here so a multi-worker deployment writes exactly ONE
|
|
969
|
+
# run_end with aggregated stats from the events.jsonl, not N per
|
|
970
|
+
# worker.
|
|
971
|
+
#
|
|
972
|
+
# reason: "completed" -- matches toy/v1 vocabulary (was "ok"; #115).
|
|
973
|
+
# Cheap when nothing is configured: openai_events is seeded with an
|
|
974
|
+
# empty path, whose enabled? short-circuits.
|
|
975
|
+
def self.on_shutdown
|
|
976
|
+
if APP.openai_events.enabled?
|
|
977
|
+
APP.openai_events.run_end_aggregated("completed")
|
|
978
|
+
end
|
|
979
|
+
0
|
|
980
|
+
end
|
|
981
|
+
end
|