@@ -43,6 +43,19 @@ func NewHTTPHandler(log logging.Logger, schedulerHTTP http.Handler, allowedOrigi
4343 h .router .HandleFunc ("GET /v1" + APIPrefix + "/{id}" , h .handleGet )
4444 h .router .HandleFunc ("GET /v1" + APIPrefix + "/{id}/input_items" , h .handleListInputItems )
4545 h .router .HandleFunc ("DELETE /v1" + APIPrefix + "/{id}" , h .handleDelete )
46+ // Also register /engines/responses and /engines/v1/responses routes.
47+ h .router .HandleFunc ("POST /engines" + APIPrefix , h .handleCreate )
48+ h .router .HandleFunc ("GET /engines" + APIPrefix + "/{id}" , h .handleGet )
49+ h .router .HandleFunc ("GET /engines" + APIPrefix + "/{id}/input_items" , h .handleListInputItems )
50+ h .router .HandleFunc ("DELETE /engines" + APIPrefix + "/{id}" , h .handleDelete )
51+ h .router .HandleFunc ("POST /engines/v1" + APIPrefix , h .handleCreate )
52+ h .router .HandleFunc ("GET /engines/v1" + APIPrefix + "/{id}" , h .handleGet )
53+ h .router .HandleFunc ("GET /engines/v1" + APIPrefix + "/{id}/input_items" , h .handleListInputItems )
54+ h .router .HandleFunc ("DELETE /engines/v1" + APIPrefix + "/{id}" , h .handleDelete )
55+ h .router .HandleFunc ("POST /engines/{backend}/v1" + APIPrefix , h .handleCreate )
56+ h .router .HandleFunc ("GET /engines/{backend}/v1" + APIPrefix + "/{id}" , h .handleGet )
57+ h .router .HandleFunc ("GET /engines/{backend}/v1" + APIPrefix + "/{id}/input_items" , h .handleListInputItems )
58+ h .router .HandleFunc ("DELETE /engines/{backend}/v1" + APIPrefix + "/{id}" , h .handleDelete )
4659
4760 // Apply CORS middleware
4861 h .httpHandler = middleware .CorsMiddleware (allowedOrigins , h .router )
@@ -97,14 +110,21 @@ func (h *HTTPHandler) handleCreate(w http.ResponseWriter, r *http.Request) {
97110 h .sendError (w , http .StatusBadRequest , "invalid_request" , "model is required" )
98111 return
99112 }
113+ if err := validateUnsupportedRequestFields (& req ); err != nil {
114+ h .sendError (w , http .StatusBadRequest , "invalid_request" , err .Error ())
115+ return
116+ }
100117
101118 // Create a new response
102119 respID := GenerateResponseID ()
103120 resp := NewResponse (respID , req .Model )
121+ store := shouldStore (& req )
104122 resp .Instructions = nilIfEmpty (req .Instructions )
105123 resp .Temperature = req .Temperature
106124 resp .TopP = req .TopP
107125 resp .MaxOutputTokens = req .MaxOutputTokens
126+ resp .Store = & store
127+ resp .Text = req .Text
108128 resp .Tools = req .Tools
109129 resp .ToolChoice = req .ToolChoice
110130 resp .ParallelToolCalls = req .ParallelToolCalls
@@ -134,7 +154,7 @@ func (h *HTTPHandler) handleCreate(w http.ResponseWriter, r *http.Request) {
134154 }
135155
136156 // Create upstream request
137- upstreamReq , err := http .NewRequestWithContext (r .Context (), http .MethodPost , "/engines/v1/chat/completions" , bytes .NewReader (chatBody ))
157+ upstreamReq , err := http .NewRequestWithContext (r .Context (), http .MethodPost , chatCompletionPathForRequest ( r ) , bytes .NewReader (chatBody ))
138158 if err != nil {
139159 h .sendError (w , http .StatusInternalServerError , "internal_error" , "Failed to create request" )
140160 return
@@ -147,24 +167,29 @@ func (h *HTTPHandler) handleCreate(w http.ResponseWriter, r *http.Request) {
147167
148168 if req .Stream {
149169 // Handle streaming response
150- h .handleStreaming (w , upstreamReq , resp )
170+ h .handleStreaming (w , upstreamReq , resp , store )
151171 } else {
152172 // Handle non-streaming response
153- h .handleNonStreaming (w , upstreamReq , resp )
173+ h .handleNonStreaming (w , upstreamReq , resp , store )
154174 }
155175}
156176
157177// handleStreaming handles streaming responses.
158- func (h * HTTPHandler ) handleStreaming (w http.ResponseWriter , upstreamReq * http.Request , resp * Response ) {
178+ func (h * HTTPHandler ) handleStreaming (w http.ResponseWriter , upstreamReq * http.Request , resp * Response , store bool ) {
179+ responseStore := h .store
180+ if ! store {
181+ responseStore = nil
182+ }
183+
159184 // Create streaming writer
160- streamWriter := NewStreamingResponseWriter (w , resp , h . store )
185+ streamWriter := NewStreamingResponseWriter (w , resp , responseStore )
161186
162187 // Forward to scheduler
163188 h .schedulerHTTP .ServeHTTP (streamWriter , upstreamReq )
164189}
165190
166191// handleNonStreaming handles non-streaming responses.
167- func (h * HTTPHandler ) handleNonStreaming (w http.ResponseWriter , upstreamReq * http.Request , resp * Response ) {
192+ func (h * HTTPHandler ) handleNonStreaming (w http.ResponseWriter , upstreamReq * http.Request , resp * Response , store bool ) {
168193 // Capture upstream response
169194 capture := NewNonStreamingResponseCapture ()
170195
@@ -187,7 +212,9 @@ func (h *HTTPHandler) handleNonStreaming(w http.ResponseWriter, upstreamReq *htt
187212 Code : errResp .Error .Code ,
188213 Message : errResp .Error .Message ,
189214 }
190- h .store .Save (resp )
215+ if store {
216+ h .store .Save (resp )
217+ }
191218 h .sendJSON (w , capture .StatusCode , resp )
192219 return
193220 }
@@ -197,7 +224,9 @@ func (h *HTTPHandler) handleNonStreaming(w http.ResponseWriter, upstreamReq *htt
197224 Code : "upstream_error" ,
198225 Message : capture .Body .String (),
199226 }
200- h .store .Save (resp )
227+ if store {
228+ h .store .Save (resp )
229+ }
201230 h .sendJSON (w , capture .StatusCode , resp )
202231 return
203232 }
@@ -210,7 +239,9 @@ func (h *HTTPHandler) handleNonStreaming(w http.ResponseWriter, upstreamReq *htt
210239 Code : "parse_error" ,
211240 Message : "Failed to parse upstream response" ,
212241 }
213- h .store .Save (resp )
242+ if store {
243+ h .store .Save (resp )
244+ }
214245 h .sendJSON (w , http .StatusInternalServerError , resp )
215246 return
216247 }
@@ -222,6 +253,8 @@ func (h *HTTPHandler) handleNonStreaming(w http.ResponseWriter, upstreamReq *htt
222253 finalResp .Temperature = resp .Temperature
223254 finalResp .TopP = resp .TopP
224255 finalResp .MaxOutputTokens = resp .MaxOutputTokens
256+ finalResp .Store = resp .Store
257+ finalResp .Text = resp .Text
225258 finalResp .Tools = resp .Tools
226259 finalResp .ToolChoice = resp .ToolChoice
227260 finalResp .ParallelToolCalls = resp .ParallelToolCalls
@@ -232,7 +265,9 @@ func (h *HTTPHandler) handleNonStreaming(w http.ResponseWriter, upstreamReq *htt
232265 finalResp .CreatedAt = resp .CreatedAt
233266
234267 // Store the response
235- h .store .Save (finalResp )
268+ if store {
269+ h .store .Save (finalResp )
270+ }
236271
237272 // Send response
238273 h .sendJSON (w , http .StatusOK , finalResp )
@@ -332,6 +367,54 @@ func nilIfEmpty(s string) *string {
332367 return & s
333368}
334369
370+ func shouldStore (req * CreateRequest ) bool {
371+ return req .Store == nil || * req .Store
372+ }
373+
374+ func chatCompletionPathForRequest (r * http.Request ) string {
375+ if backend := r .PathValue ("backend" ); backend != "" {
376+ return "/engines/" + backend + "/v1/chat/completions"
377+ }
378+ return "/engines/v1/chat/completions"
379+ }
380+
381+ func validateUnsupportedRequestFields (req * CreateRequest ) error {
382+ if len (req .Include ) > 0 {
383+ return fmt .Errorf ("include is not supported by Docker Model Runner Responses compatibility layer" )
384+ }
385+ if req .StreamOptions != nil && ! isNullJSON (req .StreamOptions ) {
386+ return fmt .Errorf ("stream_options is not supported by Docker Model Runner Responses compatibility layer" )
387+ }
388+ if req .TopLogprobs != nil {
389+ return fmt .Errorf ("top_logprobs is not supported by Docker Model Runner Responses compatibility layer" )
390+ }
391+ switch req .Truncation {
392+ case "" , "disabled" :
393+ default :
394+ return fmt .Errorf ("truncation value %q is not supported by Docker Model Runner Responses compatibility layer" , req .Truncation )
395+ }
396+ if req .Background != nil && * req .Background {
397+ return fmt .Errorf ("background responses are not supported by Docker Model Runner Responses compatibility layer" )
398+ }
399+ if req .Conversation != nil && ! isNullJSON (req .Conversation ) {
400+ return fmt .Errorf ("conversation is not supported by Docker Model Runner Responses compatibility layer; use previous_response_id instead" )
401+ }
402+ if req .Prompt != nil && ! isNullJSON (req .Prompt ) {
403+ return fmt .Errorf ("prompt is not supported by Docker Model Runner Responses compatibility layer" )
404+ }
405+ if req .ServiceTier != "" {
406+ return fmt .Errorf ("service_tier is not supported by Docker Model Runner Responses compatibility layer" )
407+ }
408+ if req .SafetyIdentifier != "" {
409+ return fmt .Errorf ("safety_identifier is not supported by Docker Model Runner Responses compatibility layer" )
410+ }
411+ return nil
412+ }
413+
414+ func isNullJSON (raw json.RawMessage ) bool {
415+ return strings .TrimSpace (string (raw )) == "null"
416+ }
417+
335418// GetStore returns the response store (for testing).
336419func (h * HTTPHandler ) GetStore () * Store {
337420 return h .store
0 commit comments