14 "github.com/stretchr/testify/require"
17 // modelTestKey is a fake API key.
18 const modelTestKey = "sk-test-not-a-real-key"
20 // ModelFakeCall is one call to a fake endpoint, numbered from 0.
21 type ModelFakeCall struct {
28 // ModelFake is an httptest Responses endpoint. It keeps each call, and reply
29 // writes the response. The default reply is one short message.
30 type ModelFake struct {
34 reply func(http.ResponseWriter, ModelFakeCall)
37 // Call returns call number n.
38 func (f *ModelFake) Call(n int) ModelFakeCall {
40 defer f.mutex.Unlock()
45 // Calls returns the number of calls.
46 func (f *ModelFake) Calls() int {
48 defer f.mutex.Unlock()
53 func (f *ModelFake) serve(w http.ResponseWriter, r *http.Request) {
54 raw, err := io.ReadAll(r.Body)
56 http.Error(w, err.Error(), http.StatusBadRequest)
61 call := ModelFakeCall{
62 header: r.Header.Clone(),
66 err = json.Unmarshal(raw, &call.body)
68 http.Error(w, err.Error(), http.StatusBadRequest)
75 call.number = len(f.calls)
76 f.calls = append(f.calls, call)
83 func newModelFake(t *testing.T) *ModelFake {
87 reply: func(w http.ResponseWriter, _ ModelFakeCall) {
88 modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("a line")}, 100, 0, 5))
92 f.Server = httptest.NewServer(http.HandlerFunc(f.serve))
98 func modelTestRequest() ModelRequest {
102 Input: []any{modelUserMessage("hello")},
106 // modelWriteEvents writes events as a server-sent event stream.
107 func modelWriteEvents(w http.ResponseWriter, events ...any) {
108 w.Header().Set("Content-Type", "text/event-stream")
110 for _, event := range events {
111 data, err := json.Marshal(event)
116 _, err = fmt.Fprintf(w, "event: %s\ndata: %s\n\n", event.(map[string]any)["type"], data)
123 func modelMessageItem(text string) map[string]any {
124 return map[string]any{
128 map[string]any{"type": "output_text", "text": text},
133 func modelCompletedEvent(output []any, input, cached, out int) map[string]any {
138 return map[string]any{
139 "type": "response.completed",
140 "response": map[string]any{
142 "usage": map[string]any{
143 "input_tokens": input,
144 "input_tokens_details": map[string]any{"cached_tokens": cached},
145 "output_tokens": out,
151 func modelReadUsage(t *testing.T, path string) []ModelUsageRecord {
154 file, err := os.Open(path)
155 require.NoError(t, err)
158 require.NoError(t, file.Close())
161 var records []ModelUsageRecord
163 scanner := bufio.NewScanner(file)
165 var record ModelUsageRecord
167 require.NoError(t, json.Unmarshal(scanner.Bytes(), &record))
169 records = append(records, record)
172 require.NoError(t, scanner.Err())
177 func modelReasoningItem(id, encrypted string) map[string]any {
178 return map[string]any{
181 "encrypted_content": encrypted,
186 func modelFunctionCallItem(id, callID, name, arguments string) map[string]any {
187 return map[string]any{
188 "type": "function_call",
192 "arguments": arguments,