-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathrestapi.moon.1
279 lines (228 loc) · 8.12 KB
/
restapi.moon.1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
ltn12 = require "ltn12"
json = json or require "cjson"
STREAMED = "Streamed successfully"
class Plain
new: (@api_key, apibase, @headers) =>
if apibase
@api_base = apibase
create_stream_sink: (delta_callback, ctx) =>
streamed = false
(chunk, err) ->
if chunk==nil
if err
delta_callback nil, err
else
ctx.response = streamed and STREAMED or ""
else
delta_callback chunk
streamed = true
chunk, err
authorization: { "authorization", "Bearer %s" }
_request: (method, path, payload, delta_callback, extra_headers) =>
headers = "accept": delta_callback and "text/event-stream" or "application/json"
local source
if payload
body = payload
if type(payload)=="table"
body = json.encode payload
headers["content-type"] = "application/json"
headers["content-length"] = #body
source = ltn12.source.string body
if @api_key
{name, template} = @authorization
headers[name or "authorization"] = template and template\format(@api_key) or @api_key
for _headers in *{ @headers, extra_headers }
if _headers then for k,v in pairs _headers
headers[k\lower()] = v or nil -- false becomes nil
out = {}
sink = if delta_callback
@create_stream_sink delta_callback, out
else
ltn12.sink.table out
success, status_code, response_headers, status_line = @get_provider!.request {
url: @api_base .. path
:sink
:source
:method
:headers
}
if success
response = if delta_callback then out.response else table.concat out
pcall -> response = assert json.decode response
response, status_code, response_headers, status_line
else
nil, status_code
request: (method, endpoint, payload, delta_callback, headers) =>
if method\match"^[/?]"
endpoint, payload, delta_callback, headers = method, endpoint, payload, delta_callback
method = payload and "POST" or "GET"
response, status_code, headers, statusline = @_request method, endpoint, payload, delta_callback, headers
if not response
nil, status_code
elseif status_code<200 or status_code>299
nil, statusline, response~=STREAMED and response or nil
else
response, statusline, headers
generate: (endpoint, payload, delta_callback, headers) =>
@request "POST", endpoint, payload, delta_callback, headers
providers: {
http: "socket.http"
https: "ssl.https"
}
get_provider: =>
unless @provider
scheme = @api_base\match"^(.-)://"
provider = scheme and @providers[scheme]
if provider
@provider = require provider
else
error "unknown scheme: "..@api_base
@provider
class LinesStream extends Plain
separatorPattern: "^(.-)\r?\n(.-)$"
consumeBufferRest: false
create_stream_sink: (delta_callback, ctx) =>
acc_buffer, streamed, accumulate_only = "", false, false
(chunk, err) ->
if chunk==nil
if streamed
if not err and @consumeBufferRest and acc_buffer~=""
@process_line acc_buffer, delta_callback, ctx
elseif err = err or acc_buffer~="" and not acc_buffer\match"data: ?%[DONE%]" and "buffer not empty"
delta_callback nil, err, acc_buffer
unless err
ctx.response = streamed and STREAMED or acc_buffer
else
acc_buffer ..= chunk
unless accumulate_only
line = true
while line
line, rest = string.match acc_buffer, @separatorPattern
if line
if @process_line line, delta_callback, ctx
acc_buffer = rest
streamed = true
elseif streamed
delta_callback nil, "Unable to parse line", line
return nil, "Unable to parse line"
else
accumulate_only = true
break
chunk, err
process_line: (line, delta_callback, ctx) =>
if line=="" then return true
@process delta_callback, line
process: (delta_callback, data, extra) =>
if data=="[DONE]" or data\match"^ -$" then return true
success, parsed = pcall -> assert json.decode data
if success
if @is_valid parsed
delta_callback parsed, nil, extra
else
delta_callback nil, "Unexpected json chunk", parsed
true
is_valid: (parsed) =>
true -- stub
defaultEndpoint: "/chat"
generate: (endpoint, payload, delta_callback, headers) =>
if type(endpoint)~="string"
endpoint, payload, delta_callback, headers = @defaultEndpoint, endpoint, payload, delta_callback
response, status_code, _, statusline = @_request "POST", endpoint, payload, delta_callback, headers
if not response
nil, status_code
elseif status_code<200 or status_code>299
nil, statusline, response~=STREAMED and response or nil
elseif response~=STREAMED and (type(response)~="table" or not (@is_valid_final or @is_valid) @,response)
nil, statusline, response
else
response, statusline
--modelsKeys: {models:"data", id:"id"}
models: (endpoint) =>
response, status_code, _, statusline = @_request "GET", endpoint or "/models"
if not response
nil, status_code
elseif status_code==200
if type(response)~="table"
return nil, response
if @modelsKeys then
keys = @modelsKeys
models = keys.models and response[keys.models] or response
models.keyId = keys.id
return models
-- else use heuristics
models = response.data or -- openai
response.models or -- cohere, google gemini
response.result or -- cloudflare
response -- together, github
item = models[2]
if not item
return nil, "Unexpected response"
keyId = item.name and not item.name\match" " and "name" or -- cloudflare, cohere, github, google gemini
item.id and "id" -- openai, copilot, openrouter
if not keyId
return nil, "Unexpected response"
models.keyId = keyId
models
else
nil, statusline, response or ""
class BasicSSE extends LinesStream
process_line: (line, delta_callback) =>
if line=="" or line\sub(1,1)==":" or line\match"^event:"
true
elseif data = line\match"^data: ?(.+)$"
@process delta_callback, data
class SSE extends LinesStream
process_line: (line, delta_callback, ctx) =>
ctx.event = ctx.event or {}
e = ctx.event
-- https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation
if line==""
if next e
ctx.event = {} -- reset
if e.data
return @process delta_callback, e.data, e
else
unless e[""] -- comment
delta_callback nil, "data not found", e
else
field, value = line\match"^(.-):%s?(.+)$"
switch field
when "data"
e.data = e.data and e.data.."\n"..value or value
when "event","id","retry","" --comment
e[field] = value
when nil
unless ({event:1,id:1,retry:1,data:1})[line]
return false -- do not try to parse SSE anymore
else
if field\match"^%w+$"
delta_callback nil, 'Unknown field "%s" with value "%s"'\format(field, value), e
return nil
true
class OpenAI extends BasicSSE
api_base: "https://api.openai.com/v1"
defaultEndpoint: "/chat/completions"
generate: (payload, delta_callback=nil) =>
super payload, if payload.stream then delta_callback
is_valid: (parsed) =>
parsed.choices
class GoogleGemini extends BasicSSE
api_base: "https://generativelanguage.googleapis.com/v1beta"
authorization: { "x-goog-api-key" }
generate: (payload, model, stream, delta_callback=nil) =>
assert model, "Model not specified"
task = stream and "streamGenerateContent" or "generateContent"
endpoint = "/models/%s:%s%s"\format model, task, stream and "?alt=sse" or ""
super endpoint, payload, if stream then delta_callback
is_valid: (parsed) =>
parsed.candidates
modelsKeys: {models:"models", id:"name"}
{
:STREAMED
:Plain
:LinesStream
:BasicSSE
:SSE
:OpenAI
:GoogleGemini
}