20 files changed
+29764
-655
docs/openapi.json
new
+28602
@@ -0,0 +1,28602 @@
1
+{
2
+ "openapi": "3.1.0",
3
+ "info": {
4
+ "title": "OpenRouter API",
5
+ "version": "1.0.0",
6
+ "description": "OpenAI-compatible API with additional OpenRouter features",
7
+ "contact": {
8
+ "name": "OpenRouter Support",
9
+ "url": "https://openrouter.ai/docs",
10
+ "email": "support@openrouter.ai"
11
+ },
12
+ "license": {
13
+ "name": "MIT",
14
+ "url": "https://opensource.org/licenses/MIT"
15
+ }
16
+ },
17
+ "servers": [
18
+ {
19
+ "url": "https://openrouter.ai/api/v1",
20
+ "description": "Production server",
21
+ "x-speakeasy-server-id": "production"
22
+ }
23
+ ],
24
+ "security": [
25
+ {
26
+ "apiKey": []
27
+ }
28
+ ],
29
+ "externalDocs": {
30
+ "description": "OpenRouter Documentation",
31
+ "url": "https://openrouter.ai/docs"
32
+ },
33
+ "components": {
34
+ "schemas": {
35
+ "OpenAIResponsesResponseStatus": {
36
+ "type": "string",
37
+ "enum": [
38
+ "completed",
39
+ "incomplete",
40
+ "in_progress",
41
+ "failed",
42
+ "cancelled",
43
+ "queued"
44
+ ],
45
+ "x-speakeasy-unknown-values": "allow"
46
+ },
47
+ "FileCitation": {
48
+ "type": "object",
49
+ "properties": {
50
+ "type": {
51
+ "type": "string",
52
+ "enum": [
53
+ "file_citation"
54
+ ]
55
+ },
56
+ "file_id": {
57
+ "type": "string"
58
+ },
59
+ "filename": {
60
+ "type": "string"
61
+ },
62
+ "index": {
63
+ "type": "number"
64
+ }
65
+ },
66
+ "required": [
67
+ "type",
68
+ "file_id",
69
+ "filename",
70
+ "index"
71
+ ],
72
+ "example": {
73
+ "type": "file_citation",
74
+ "file_id": "file-abc123",
75
+ "filename": "research_paper.pdf",
76
+ "index": 0
77
+ }
78
+ },
79
+ "URLCitation": {
80
+ "type": "object",
81
+ "properties": {
82
+ "type": {
83
+ "type": "string",
84
+ "enum": [
85
+ "url_citation"
86
+ ]
87
+ },
88
+ "url": {
89
+ "type": "string"
90
+ },
91
+ "title": {
92
+ "type": "string"
93
+ },
94
+ "start_index": {
95
+ "type": "number"
96
+ },
97
+ "end_index": {
98
+ "type": "number"
99
+ }
100
+ },
101
+ "required": [
102
+ "type",
103
+ "url",
104
+ "title",
105
+ "start_index",
106
+ "end_index"
107
+ ],
108
+ "example": {
109
+ "type": "url_citation",
110
+ "start_index": 0,
111
+ "end_index": 42,
112
+ "title": "OpenRouter Documentation",
113
+ "url": "https://openrouter.ai/docs"
114
+ }
115
+ },
116
+ "FilePath": {
117
+ "type": "object",
118
+ "properties": {
119
+ "type": {
120
+ "type": "string",
121
+ "enum": [
122
+ "file_path"
123
+ ]
124
+ },
125
+ "file_id": {
126
+ "type": "string"
127
+ },
128
+ "index": {
129
+ "type": "number"
130
+ }
131
+ },
132
+ "required": [
133
+ "type",
134
+ "file_id",
135
+ "index"
136
+ ],
137
+ "example": {
138
+ "type": "file_path",
139
+ "file_id": "file-xyz789",
140
+ "index": 0
141
+ }
142
+ },
143
+ "OpenAIResponsesAnnotation": {
144
+ "anyOf": [
145
+ {
146
+ "$ref": "#/components/schemas/FileCitation"
147
+ },
148
+ {
149
+ "$ref": "#/components/schemas/URLCitation"
150
+ },
151
+ {
152
+ "$ref": "#/components/schemas/FilePath"
153
+ }
154
+ ]
155
+ },
156
+ "ResponseOutputText": {
157
+ "type": "object",
158
+ "properties": {
159
+ "type": {
160
+ "type": "string",
161
+ "enum": [
162
+ "output_text"
163
+ ]
164
+ },
165
+ "text": {
166
+ "type": "string"
167
+ },
168
+ "annotations": {
169
+ "type": "array",
170
+ "items": {
171
+ "$ref": "#/components/schemas/OpenAIResponsesAnnotation"
172
+ }
173
+ },
174
+ "logprobs": {
175
+ "type": "array",
176
+ "items": {
177
+ "type": "object",
178
+ "properties": {
179
+ "token": {
180
+ "type": "string"
181
+ },
182
+ "bytes": {
183
+ "type": "array",
184
+ "items": {
185
+ "type": "number"
186
+ }
187
+ },
188
+ "logprob": {
189
+ "type": "number"
190
+ },
191
+ "top_logprobs": {
192
+ "type": "array",
193
+ "items": {
194
+ "type": "object",
195
+ "properties": {
196
+ "token": {
197
+ "type": "string"
198
+ },
199
+ "bytes": {
200
+ "type": "array",
201
+ "items": {
202
+ "type": "number"
203
+ }
204
+ },
205
+ "logprob": {
206
+ "type": "number"
207
+ }
208
+ },
209
+ "required": [
210
+ "token",
211
+ "bytes",
212
+ "logprob"
213
+ ]
214
+ }
215
+ }
216
+ },
217
+ "required": [
218
+ "token",
219
+ "bytes",
220
+ "logprob",
221
+ "top_logprobs"
222
+ ]
223
+ }
224
+ }
225
+ },
226
+ "required": [
227
+ "type",
228
+ "text"
229
+ ],
230
+ "example": {
231
+ "type": "output_text",
232
+ "text": "The capital of France is Paris.",
233
+ "annotations": [
234
+ {
235
+ "type": "url_citation",
236
+ "start_index": 0,
237
+ "end_index": 42,
238
+ "title": "Paris - Wikipedia",
239
+ "url": "https://en.wikipedia.org/wiki/Paris"
240
+ }
241
+ ]
242
+ }
243
+ },
244
+ "OpenAIResponsesRefusalContent": {
245
+ "type": "object",
246
+ "properties": {
247
+ "type": {
248
+ "type": "string",
249
+ "enum": [
250
+ "refusal"
251
+ ]
252
+ },
253
+ "refusal": {
254
+ "type": "string"
255
+ }
256
+ },
257
+ "required": [
258
+ "type",
259
+ "refusal"
260
+ ],
261
+ "example": {
262
+ "type": "refusal",
263
+ "refusal": "I'm sorry, I cannot assist with that request"
264
+ }
265
+ },
266
+ "OutputMessage": {
267
+ "type": "object",
268
+ "properties": {
269
+ "id": {
270
+ "type": "string"
271
+ },
272
+ "role": {
273
+ "type": "string",
274
+ "enum": [
275
+ "assistant"
276
+ ]
277
+ },
278
+ "type": {
279
+ "type": "string",
280
+ "enum": [
281
+ "message"
282
+ ]
283
+ },
284
+ "status": {
285
+ "anyOf": [
286
+ {
287
+ "type": "string",
288
+ "enum": [
289
+ "completed"
290
+ ]
291
+ },
292
+ {
293
+ "type": "string",
294
+ "enum": [
295
+ "incomplete"
296
+ ]
297
+ },
298
+ {
299
+ "type": "string",
300
+ "enum": [
301
+ "in_progress"
302
+ ]
303
+ }
304
+ ]
305
+ },
306
+ "content": {
307
+ "type": "array",
308
+ "items": {
309
+ "anyOf": [
310
+ {
311
+ "$ref": "#/components/schemas/ResponseOutputText"
312
+ },
313
+ {
314
+ "$ref": "#/components/schemas/OpenAIResponsesRefusalContent"
315
+ }
316
+ ]
317
+ }
318
+ },
319
+ "phase": {
320
+ "anyOf": [
321
+ {
322
+ "type": "string",
323
+ "enum": [
324
+ "commentary"
325
+ ]
326
+ },
327
+ {
328
+ "type": "string",
329
+ "enum": [
330
+ "final_answer"
331
+ ]
332
+ },
333
+ {
334
+ "nullable": true
335
+ }
336
+ ],
337
+ "description": "The phase of an assistant message. Use `commentary` for an intermediate assistant message and `final_answer` for the final assistant message. For follow-up requests with models like `gpt-5.3-codex` and later, preserve and resend phase on all assistant messages. Omitting it can degrade performance. Not used for user messages."
338
+ }
339
+ },
340
+ "required": [
341
+ "id",
342
+ "role",
343
+ "type",
344
+ "content"
345
+ ],
346
+ "example": {
347
+ "id": "msg-abc123",
348
+ "role": "assistant",
349
+ "type": "message",
350
+ "status": "completed",
351
+ "content": [
352
+ {
353
+ "type": "output_text",
354
+ "text": "Hello! How can I help you today?"
355
+ }
356
+ ]
357
+ }
358
+ },
359
+ "OutputMessageItem": {
360
+ "allOf": [
361
+ {
362
+ "$ref": "#/components/schemas/OutputMessage"
363
+ },
364
+ {
365
+ "type": "object",
366
+ "properties": {}
367
+ }
368
+ ],
369
+ "example": {
370
+ "id": "msg-123",
371
+ "type": "message",
372
+ "role": "assistant",
373
+ "status": "completed",
374
+ "content": [
375
+ {
376
+ "type": "output_text",
377
+ "text": "Hello! How can I help you?",
378
+ "annotations": []
379
+ }
380
+ ]
381
+ },
382
+ "description": "An output message item"
383
+ },
384
+ "ReasoningTextContent": {
385
+ "type": "object",
386
+ "properties": {
387
+ "type": {
388
+ "type": "string",
389
+ "enum": [
390
+ "reasoning_text"
391
+ ]
392
+ },
393
+ "text": {
394
+ "type": "string"
395
+ }
396
+ },
397
+ "required": [
398
+ "type",
399
+ "text"
400
+ ],
401
+ "example": {
402
+ "type": "reasoning_text",
403
+ "text": "Let me think step by step about this problem..."
404
+ }
405
+ },
406
+ "ReasoningSummaryText": {
407
+ "type": "object",
408
+ "properties": {
409
+ "type": {
410
+ "type": "string",
411
+ "enum": [
412
+ "summary_text"
413
+ ]
414
+ },
415
+ "text": {
416
+ "type": "string"
417
+ }
418
+ },
419
+ "required": [
420
+ "type",
421
+ "text"
422
+ ],
423
+ "example": {
424
+ "type": "summary_text",
425
+ "text": "Analyzed the problem using first principles"
426
+ }
427
+ },
428
+ "OutputItemReasoning": {
429
+ "type": "object",
430
+ "properties": {
431
+ "type": {
432
+ "type": "string",
433
+ "enum": [
434
+ "reasoning"
435
+ ]
436
+ },
437
+ "id": {
438
+ "type": "string"
439
+ },
440
+ "content": {
441
+ "type": "array",
442
+ "items": {
443
+ "$ref": "#/components/schemas/ReasoningTextContent"
444
+ }
445
+ },
446
+ "summary": {
447
+ "type": "array",
448
+ "items": {
449
+ "$ref": "#/components/schemas/ReasoningSummaryText"
450
+ }
451
+ },
452
+ "encrypted_content": {
453
+ "type": "string",
454
+ "nullable": true
455
+ },
456
+ "status": {
457
+ "anyOf": [
458
+ {
459
+ "type": "string",
460
+ "enum": [
461
+ "completed"
462
+ ]
463
+ },
464
+ {
465
+ "type": "string",
466
+ "enum": [
467
+ "incomplete"
468
+ ]
469
+ },
470
+ {
471
+ "type": "string",
472
+ "enum": [
473
+ "in_progress"
474
+ ]
475
+ }
476
+ ]
477
+ }
478
+ },
479
+ "required": [
480
+ "type",
481
+ "id",
482
+ "summary"
483
+ ],
484
+ "example": {
485
+ "type": "reasoning",
486
+ "id": "reasoning-abc123",
487
+ "summary": [
488
+ {
489
+ "type": "summary_text",
490
+ "text": "Analyzed the problem using first principles"
491
+ }
492
+ ]
493
+ }
494
+ },
495
+ "OutputReasoningItem": {
496
+ "allOf": [
497
+ {
498
+ "$ref": "#/components/schemas/OutputItemReasoning"
499
+ },
500
+ {
501
+ "type": "object",
502
+ "properties": {
503
+ "content": {
504
+ "type": "array",
505
+ "nullable": true,
506
+ "items": {
507
+ "$ref": "#/components/schemas/ReasoningTextContent"
508
+ }
509
+ },
510
+ "signature": {
511
+ "type": "string",
512
+ "nullable": true,
513
+ "description": "A signature for the reasoning content, used for verification",
514
+ "example": "EvcBCkgIChABGAIqQKkSDbRuVEQUk9qN1odC098l9SEj..."
515
+ },
516
+ "format": {
517
+ "type": "string",
518
+ "nullable": true,
519
+ "enum": [
520
+ "unknown",
521
+ "openai-responses-v1",
522
+ "azure-openai-responses-v1",
523
+ "xai-responses-v1",
524
+ "anthropic-claude-v1",
525
+ "google-gemini-v1"
526
+ ],
527
+ "description": "The format of the reasoning content",
528
+ "example": "anthropic-claude-v1",
529
+ "x-speakeasy-unknown-values": "allow"
530
+ }
531
+ }
532
+ }
533
+ ],
534
+ "example": {
535
+ "id": "reasoning-123",
536
+ "type": "reasoning",
537
+ "status": "completed",
538
+ "summary": [
539
+ {
540
+ "type": "summary_text",
541
+ "text": "Analyzed the problem and found the optimal solution."
542
+ }
543
+ ],
544
+ "content": [
545
+ {
546
+ "type": "reasoning_text",
547
+ "text": "First, we analyze the problem..."
548
+ }
549
+ ],
550
+ "signature": "EvcBCkgIChABGAIqQKkSDbRuVEQUk9qN1odC098l9SEj...",
551
+ "format": "anthropic-claude-v1"
552
+ },
553
+ "description": "An output item containing reasoning"
554
+ },
555
+ "OutputItemFunctionCall": {
556
+ "type": "object",
557
+ "properties": {
558
+ "type": {
559
+ "type": "string",
560
+ "enum": [
561
+ "function_call"
562
+ ]
563
+ },
564
+ "id": {
565
+ "type": "string"
566
+ },
567
+ "name": {
568
+ "type": "string"
569
+ },
570
+ "arguments": {
571
+ "type": "string"
572
+ },
573
+ "call_id": {
574
+ "type": "string"
575
+ },
576
+ "status": {
577
+ "anyOf": [
578
+ {
579
+ "type": "string",
580
+ "enum": [
581
+ "completed"
582
+ ]
583
+ },
584
+ {
585
+ "type": "string",
586
+ "enum": [
587
+ "incomplete"
588
+ ]
589
+ },
590
+ {
591
+ "type": "string",
592
+ "enum": [
593
+ "in_progress"
594
+ ]
595
+ }
596
+ ]
597
+ }
598
+ },
599
+ "required": [
600
+ "type",
601
+ "name",
602
+ "arguments",
603
+ "call_id"
604
+ ],
605
+ "example": {
606
+ "type": "function_call",
607
+ "id": "call-abc123",
608
+ "name": "get_weather",
609
+ "arguments": "{\"location\":\"San Francisco\",\"unit\":\"celsius\"}",
610
+ "call_id": "call-abc123"
611
+ }
612
+ },
613
+ "OutputFunctionCallItem": {
614
+ "allOf": [
615
+ {
616
+ "$ref": "#/components/schemas/OutputItemFunctionCall"
617
+ },
618
+ {
619
+ "type": "object",
620
+ "properties": {}
621
+ }
622
+ ],
623
+ "example": {
624
+ "type": "function_call",
625
+ "id": "call-abc123",
626
+ "name": "get_weather",
627
+ "arguments": "{\"location\":\"San Francisco\",\"unit\":\"celsius\"}",
628
+ "call_id": "call-abc123"
629
+ }
630
+ },
631
+ "WebSearchStatus": {
632
+ "type": "string",
633
+ "enum": [
634
+ "completed",
635
+ "searching",
636
+ "in_progress",
637
+ "failed"
638
+ ],
639
+ "example": "completed",
640
+ "x-speakeasy-unknown-values": "allow"
641
+ },
642
+ "OutputItemWebSearchCall": {
643
+ "type": "object",
644
+ "properties": {
645
+ "type": {
646
+ "type": "string",
647
+ "enum": [
648
+ "web_search_call"
649
+ ]
650
+ },
651
+ "id": {
652
+ "type": "string"
653
+ },
654
+ "action": {
655
+ "oneOf": [
656
+ {
657
+ "type": "object",
658
+ "properties": {
659
+ "type": {
660
+ "type": "string",
661
+ "enum": [
662
+ "search"
663
+ ]
664
+ },
665
+ "query": {
666
+ "type": "string"
667
+ },
668
+ "queries": {
669
+ "type": "array",
670
+ "items": {
671
+ "type": "string"
672
+ }
673
+ },
674
+ "sources": {
675
+ "type": "array",
676
+ "items": {
677
+ "type": "object",
678
+ "properties": {
679
+ "type": {
680
+ "type": "string",
681
+ "enum": [
682
+ "url"
683
+ ]
684
+ },
685
+ "url": {
686
+ "type": "string"
687
+ }
688
+ },
689
+ "required": [
690
+ "type",
691
+ "url"
692
+ ]
693
+ }
694
+ }
695
+ },
696
+ "required": [
697
+ "type",
698
+ "query"
699
+ ]
700
+ },
701
+ {
702
+ "type": "object",
703
+ "properties": {
704
+ "type": {
705
+ "type": "string",
706
+ "enum": [
707
+ "open_page"
708
+ ]
709
+ },
710
+ "url": {
711
+ "type": "string",
712
+ "nullable": true
713
+ }
714
+ },
715
+ "required": [
716
+ "type"
717
+ ]
718
+ },
719
+ {
720
+ "type": "object",
721
+ "properties": {
722
+ "type": {
723
+ "type": "string",
724
+ "enum": [
725
+ "find_in_page"
726
+ ]
727
+ },
728
+ "pattern": {
729
+ "type": "string"
730
+ },
731
+ "url": {
732
+ "type": "string"
733
+ }
734
+ },
735
+ "required": [
736
+ "type",
737
+ "pattern",
738
+ "url"
739
+ ]
740
+ }
741
+ ]
742
+ },
743
+ "status": {
744
+ "$ref": "#/components/schemas/WebSearchStatus"
745
+ }
746
+ },
747
+ "required": [
748
+ "type",
749
+ "id",
750
+ "action",
751
+ "status"
752
+ ],
753
+ "example": {
754
+ "type": "web_search_call",
755
+ "id": "search-abc123",
756
+ "action": {
757
+ "type": "search",
758
+ "query": "OpenAI API"
759
+ },
760
+ "status": "completed"
761
+ }
762
+ },
763
+ "OutputWebSearchCallItem": {
764
+ "allOf": [
765
+ {
766
+ "$ref": "#/components/schemas/OutputItemWebSearchCall"
767
+ },
768
+ {
769
+ "type": "object",
770
+ "properties": {}
771
+ }
772
+ ],
773
+ "example": {
774
+ "type": "web_search_call",
775
+ "id": "search-abc123",
776
+ "action": {
777
+ "type": "search",
778
+ "query": "OpenAI API"
779
+ },
780
+ "status": "completed"
781
+ }
782
+ },
783
+ "OutputItemFileSearchCall": {
784
+ "type": "object",
785
+ "properties": {
786
+ "type": {
787
+ "type": "string",
788
+ "enum": [
789
+ "file_search_call"
790
+ ]
791
+ },
792
+ "id": {
793
+ "type": "string"
794
+ },
795
+ "queries": {
796
+ "type": "array",
797
+ "items": {
798
+ "type": "string"
799
+ }
800
+ },
801
+ "status": {
802
+ "$ref": "#/components/schemas/WebSearchStatus"
803
+ }
804
+ },
805
+ "required": [
806
+ "type",
807
+ "id",
808
+ "queries",
809
+ "status"
810
+ ],
811
+ "example": {
812
+ "type": "file_search_call",
813
+ "id": "filesearch-abc123",
814
+ "queries": [
815
+ "machine learning algorithms",
816
+ "neural networks"
817
+ ],
818
+ "status": "completed"
819
+ }
820
+ },
821
+ "OutputFileSearchCallItem": {
822
+ "allOf": [
823
+ {
824
+ "$ref": "#/components/schemas/OutputItemFileSearchCall"
825
+ },
826
+ {
827
+ "type": "object",
828
+ "properties": {}
829
+ }
830
+ ],
831
+ "example": {
832
+ "type": "file_search_call",
833
+ "id": "filesearch-abc123",
834
+ "queries": [
835
+ "machine learning algorithms",
836
+ "neural networks"
837
+ ],
838
+ "status": "completed"
839
+ }
840
+ },
841
+ "ImageGenerationStatus": {
842
+ "type": "string",
843
+ "enum": [
844
+ "in_progress",
845
+ "completed",
846
+ "generating",
847
+ "failed"
848
+ ],
849
+ "example": "completed",
850
+ "x-speakeasy-unknown-values": "allow"
851
+ },
852
+ "OutputItemImageGenerationCall": {
853
+ "type": "object",
854
+ "properties": {
855
+ "type": {
856
+ "type": "string",
857
+ "enum": [
858
+ "image_generation_call"
859
+ ]
860
+ },
861
+ "id": {
862
+ "type": "string"
863
+ },
864
+ "result": {
865
+ "type": "string",
866
+ "nullable": true,
867
+ "default": null
868
+ },
869
+ "status": {
870
+ "$ref": "#/components/schemas/ImageGenerationStatus"
871
+ }
872
+ },
873
+ "required": [
874
+ "type",
875
+ "id",
876
+ "status"
877
+ ],
878
+ "example": {
879
+ "type": "image_generation_call",
880
+ "id": "imagegen-abc123",
881
+ "result": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
882
+ "status": "completed"
883
+ }
884
+ },
885
+ "OutputImageGenerationCallItem": {
886
+ "allOf": [
887
+ {
888
+ "$ref": "#/components/schemas/OutputItemImageGenerationCall"
889
+ },
890
+ {
891
+ "type": "object",
892
+ "properties": {}
893
+ }
894
+ ],
895
+ "example": {
896
+ "type": "image_generation_call",
897
+ "id": "imagegen-abc123",
898
+ "result": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
899
+ "status": "completed"
900
+ }
901
+ },
902
+ "OutputServerToolItem": {
903
+ "type": "object",
904
+ "properties": {
905
+ "type": {
906
+ "type": "string",
907
+ "pattern": "^openrouter:",
908
+ "description": "Server tool type (e.g. openrouter:datetime, openrouter:web_search)"
909
+ },
910
+ "id": {
911
+ "type": "string"
912
+ },
913
+ "status": {
914
+ "type": "string",
915
+ "enum": [
916
+ "completed",
917
+ "in_progress",
918
+ "incomplete"
919
+ ],
920
+ "x-speakeasy-unknown-values": "allow"
921
+ }
922
+ },
923
+ "required": [
924
+ "type",
925
+ "status"
926
+ ],
927
+ "additionalProperties": {
928
+ "nullable": true
929
+ },
930
+ "description": "A generic OpenRouter server tool output item",
931
+ "example": {
932
+ "type": "openrouter:web_search",
933
+ "id": "ws_tmp_abc123",
934
+ "status": "completed"
935
+ }
936
+ },
937
+ "OutputItems": {
938
+ "anyOf": [
939
+ {
940
+ "$ref": "#/components/schemas/OutputMessageItem"
941
+ },
942
+ {
943
+ "$ref": "#/components/schemas/OutputReasoningItem"
944
+ },
945
+ {
946
+ "$ref": "#/components/schemas/OutputFunctionCallItem"
947
+ },
948
+ {
949
+ "$ref": "#/components/schemas/OutputWebSearchCallItem"
950
+ },
951
+ {
952
+ "$ref": "#/components/schemas/OutputFileSearchCallItem"
953
+ },
954
+ {
955
+ "$ref": "#/components/schemas/OutputImageGenerationCallItem"
956
+ },
957
+ {
958
+ "$ref": "#/components/schemas/OutputServerToolItem"
959
+ }
960
+ ],
961
+ "description": "An output item from the response",
962
+ "example": {
963
+ "id": "msg-abc123",
964
+ "role": "assistant",
965
+ "type": "message",
966
+ "status": "completed",
967
+ "content": [
968
+ {
969
+ "type": "output_text",
970
+ "text": "Hello! How can I help you today?"
971
+ }
972
+ ]
973
+ }
974
+ },
975
+ "ResponsesErrorField": {
976
+ "type": "object",
977
+ "nullable": true,
978
+ "properties": {
979
+ "code": {
980
+ "type": "string",
981
+ "enum": [
982
+ "server_error",
983
+ "rate_limit_exceeded",
984
+ "invalid_prompt",
985
+ "vector_store_timeout",
986
+ "invalid_image",
987
+ "invalid_image_format",
988
+ "invalid_base64_image",
989
+ "invalid_image_url",
990
+ "image_too_large",
991
+ "image_too_small",
992
+ "image_parse_error",
993
+ "image_content_policy_violation",
994
+ "invalid_image_mode",
995
+ "image_file_too_large",
996
+ "unsupported_image_media_type",
997
+ "empty_image_file",
998
+ "failed_to_download_image",
999
+ "image_file_not_found"
1000
+ ],
1001
+ "x-speakeasy-unknown-values": "allow"
1002
+ },
1003
+ "message": {
1004
+ "type": "string"
1005
+ }
1006
+ },
1007
+ "required": [
1008
+ "code",
1009
+ "message"
1010
+ ],
1011
+ "description": "Error information returned from the API",
1012
+ "example": {
1013
+ "code": "rate_limit_exceeded",
1014
+ "message": "Rate limit exceeded. Please try again later."
1015
+ }
1016
+ },
1017
+ "IncompleteDetails": {
1018
+ "type": "object",
1019
+ "nullable": true,
1020
+ "properties": {
1021
+ "reason": {
1022
+ "type": "string",
1023
+ "enum": [
1024
+ "max_output_tokens",
1025
+ "content_filter"
1026
+ ],
1027
+ "x-speakeasy-unknown-values": "allow"
1028
+ }
1029
+ }
1030
+ },
1031
+ "OpenAIResponsesUsage": {
1032
+ "type": "object",
1033
+ "properties": {
1034
+ "input_tokens": {
1035
+ "type": "number"
1036
+ },
1037
+ "input_tokens_details": {
1038
+ "type": "object",
1039
+ "properties": {
1040
+ "cached_tokens": {
1041
+ "type": "number"
1042
+ }
1043
+ },
1044
+ "required": [
1045
+ "cached_tokens"
1046
+ ]
1047
+ },
1048
+ "output_tokens": {
1049
+ "type": "number"
1050
+ },
1051
+ "output_tokens_details": {
1052
+ "type": "object",
1053
+ "properties": {
1054
+ "reasoning_tokens": {
1055
+ "type": "number"
1056
+ }
1057
+ },
1058
+ "required": [
1059
+ "reasoning_tokens"
1060
+ ]
1061
+ },
1062
+ "total_tokens": {
1063
+ "type": "number"
1064
+ }
1065
+ },
1066
+ "required": [
1067
+ "input_tokens",
1068
+ "input_tokens_details",
1069
+ "output_tokens",
1070
+ "output_tokens_details",
1071
+ "total_tokens"
1072
+ ]
1073
+ },
1074
+ "Usage": {
1075
+ "allOf": [
1076
+ {
1077
+ "$ref": "#/components/schemas/OpenAIResponsesUsage"
1078
+ },
1079
+ {
1080
+ "type": "object",
1081
+ "nullable": true,
1082
+ "properties": {
1083
+ "cost": {
1084
+ "type": "number",
1085
+ "nullable": true,
1086
+ "description": "Cost of the completion"
1087
+ },
1088
+ "is_byok": {
1089
+ "type": "boolean",
1090
+ "description": "Whether a request was made using a Bring Your Own Key configuration"
1091
+ },
1092
+ "cost_details": {
1093
+ "type": "object",
1094
+ "properties": {
1095
+ "upstream_inference_cost": {
1096
+ "type": "number",
1097
+ "format": "double"
1098
+ },
1099
+ "upstream_inference_input_cost": {
1100
+ "type": "number",
1101
+ "format": "double"
1102
+ },
1103
+ "upstream_inference_output_cost": {
1104
+ "type": "number",
1105
+ "format": "double"
1106
+ }
1107
+ },
1108
+ "required": [
1109
+ "upstream_inference_input_cost",
1110
+ "upstream_inference_output_cost"
1111
+ ]
1112
+ }
1113
+ }
1114
+ }
1115
+ ],
1116
+ "description": "Token usage information for the response",
1117
+ "example": {
1118
+ "input_tokens": 10,
1119
+ "output_tokens": 25,
1120
+ "total_tokens": 35,
1121
+ "input_tokens_details": {
1122
+ "cached_tokens": 0
1123
+ },
1124
+ "output_tokens_details": {
1125
+ "reasoning_tokens": 0
1126
+ },
1127
+ "cost": 0.0012,
1128
+ "cost_details": {
1129
+ "upstream_inference_cost": null,
1130
+ "upstream_inference_input_cost": 0.0008,
1131
+ "upstream_inference_output_cost": 0.0004
1132
+ }
1133
+ }
1134
+ },
1135
+ "InputText": {
1136
+ "type": "object",
1137
+ "properties": {
1138
+ "type": {
1139
+ "type": "string",
1140
+ "enum": [
1141
+ "input_text"
1142
+ ]
1143
+ },
1144
+ "text": {
1145
+ "type": "string"
1146
+ }
1147
+ },
1148
+ "required": [
1149
+ "type",
1150
+ "text"
1151
+ ],
1152
+ "description": "Text input content item",
1153
+ "example": {
1154
+ "type": "input_text",
1155
+ "text": "Hello, how can I help you?"
1156
+ }
1157
+ },
1158
+ "InputImage": {
1159
+ "type": "object",
1160
+ "properties": {
1161
+ "type": {
1162
+ "type": "string",
1163
+ "enum": [
1164
+ "input_image"
1165
+ ]
1166
+ },
1167
+ "detail": {
1168
+ "type": "string",
1169
+ "enum": [
1170
+ "auto",
1171
+ "high",
1172
+ "low"
1173
+ ],
1174
+ "x-speakeasy-unknown-values": "allow"
1175
+ },
1176
+ "image_url": {
1177
+ "type": "string",
1178
+ "nullable": true
1179
+ }
1180
+ },
1181
+ "required": [
1182
+ "type",
1183
+ "detail"
1184
+ ],
1185
+ "description": "Image input content item",
1186
+ "example": {
1187
+ "type": "input_image",
1188
+ "detail": "auto",
1189
+ "image_url": "https://example.com/image.jpg"
1190
+ }
1191
+ },
1192
+ "InputFile": {
1193
+ "type": "object",
1194
+ "properties": {
1195
+ "type": {
1196
+ "type": "string",
1197
+ "enum": [
1198
+ "input_file"
1199
+ ]
1200
+ },
1201
+ "file_id": {
1202
+ "type": "string",
1203
+ "nullable": true
1204
+ },
1205
+ "file_data": {
1206
+ "type": "string"
1207
+ },
1208
+ "filename": {
1209
+ "type": "string"
1210
+ },
1211
+ "file_url": {
1212
+ "type": "string"
1213
+ }
1214
+ },
1215
+ "required": [
1216
+ "type"
1217
+ ],
1218
+ "description": "File input content item",
1219
+ "example": {
1220
+ "type": "input_file",
1221
+ "file_id": "file-abc123",
1222
+ "filename": "document.pdf"
1223
+ }
1224
+ },
1225
+ "InputAudio": {
1226
+ "type": "object",
1227
+ "properties": {
1228
+ "type": {
1229
+ "type": "string",
1230
+ "enum": [
1231
+ "input_audio"
1232
+ ]
1233
+ },
1234
+ "input_audio": {
1235
+ "type": "object",
1236
+ "properties": {
1237
+ "data": {
1238
+ "type": "string"
1239
+ },
1240
+ "format": {
1241
+ "type": "string",
1242
+ "enum": [
1243
+ "mp3",
1244
+ "wav"
1245
+ ],
1246
+ "x-speakeasy-unknown-values": "allow"
1247
+ }
1248
+ },
1249
+ "required": [
1250
+ "data",
1251
+ "format"
1252
+ ]
1253
+ }
1254
+ },
1255
+ "required": [
1256
+ "type",
1257
+ "input_audio"
1258
+ ],
1259
+ "description": "Audio input content item",
1260
+ "example": {
1261
+ "type": "input_audio",
1262
+ "input_audio": {
1263
+ "data": "SGVsbG8gV29ybGQ=",
1264
+ "format": "mp3"
1265
+ }
1266
+ }
1267
+ },
1268
+ "ToolCallStatusEnum": {
1269
+ "type": "string",
1270
+ "nullable": true,
1271
+ "enum": [
1272
+ "in_progress",
1273
+ "completed",
1274
+ "incomplete"
1275
+ ],
1276
+ "example": "completed",
1277
+ "x-speakeasy-unknown-values": "allow"
1278
+ },
1279
+ "BaseInputs": {
1280
+ "anyOf": [
1281
+ {
1282
+ "type": "string"
1283
+ },
1284
+ {
1285
+ "type": "array",
1286
+ "items": {
1287
+ "anyOf": [
1288
+ {
1289
+ "type": "object",
1290
+ "properties": {
1291
+ "type": {
1292
+ "type": "string",
1293
+ "enum": [
1294
+ "message"
1295
+ ]
1296
+ },
1297
+ "role": {
1298
+ "anyOf": [
1299
+ {
1300
+ "type": "string",
1301
+ "enum": [
1302
+ "user"
1303
+ ]
1304
+ },
1305
+ {
1306
+ "type": "string",
1307
+ "enum": [
1308
+ "system"
1309
+ ]
1310
+ },
1311
+ {
1312
+ "type": "string",
1313
+ "enum": [
1314
+ "assistant"
1315
+ ]
1316
+ },
1317
+ {
1318
+ "type": "string",
1319
+ "enum": [
1320
+ "developer"
1321
+ ]
1322
+ }
1323
+ ]
1324
+ },
1325
+ "content": {
1326
+ "anyOf": [
1327
+ {
1328
+ "type": "array",
1329
+ "items": {
1330
+ "oneOf": [
1331
+ {
1332
+ "$ref": "#/components/schemas/InputText"
1333
+ },
1334
+ {
1335
+ "$ref": "#/components/schemas/InputImage"
1336
+ },
1337
+ {
1338
+ "$ref": "#/components/schemas/InputFile"
1339
+ },
1340
+ {
1341
+ "$ref": "#/components/schemas/InputAudio"
1342
+ }
1343
+ ],
1344
+ "discriminator": {
1345
+ "propertyName": "type",
1346
+ "mapping": {
1347
+ "input_text": "#/components/schemas/InputText",
1348
+ "input_image": "#/components/schemas/InputImage",
1349
+ "input_file": "#/components/schemas/InputFile",
1350
+ "input_audio": "#/components/schemas/InputAudio"
1351
+ }
1352
+ }
1353
+ }
1354
+ },
1355
+ {
1356
+ "type": "string"
1357
+ }
1358
+ ]
1359
+ },
1360
+ "phase": {
1361
+ "anyOf": [
1362
+ {
1363
+ "type": "string",
1364
+ "enum": [
1365
+ "commentary"
1366
+ ]
1367
+ },
1368
+ {
1369
+ "type": "string",
1370
+ "enum": [
1371
+ "final_answer"
1372
+ ]
1373
+ },
1374
+ {
1375
+ "nullable": true
1376
+ }
1377
+ ]
1378
+ }
1379
+ },
1380
+ "required": [
1381
+ "role",
1382
+ "content"
1383
+ ]
1384
+ },
1385
+ {
1386
+ "type": "object",
1387
+ "properties": {
1388
+ "id": {
1389
+ "type": "string"
1390
+ },
1391
+ "type": {
1392
+ "type": "string",
1393
+ "enum": [
1394
+ "message"
1395
+ ]
1396
+ },
1397
+ "role": {
1398
+ "anyOf": [
1399
+ {
1400
+ "type": "string",
1401
+ "enum": [
1402
+ "user"
1403
+ ]
1404
+ },
1405
+ {
1406
+ "type": "string",
1407
+ "enum": [
1408
+ "system"
1409
+ ]
1410
+ },
1411
+ {
1412
+ "type": "string",
1413
+ "enum": [
1414
+ "developer"
1415
+ ]
1416
+ }
1417
+ ]
1418
+ },
1419
+ "content": {
1420
+ "type": "array",
1421
+ "items": {
1422
+ "oneOf": [
1423
+ {
1424
+ "$ref": "#/components/schemas/InputText"
1425
+ },
1426
+ {
1427
+ "$ref": "#/components/schemas/InputImage"
1428
+ },
1429
+ {
1430
+ "$ref": "#/components/schemas/InputFile"
1431
+ },
1432
+ {
1433
+ "$ref": "#/components/schemas/InputAudio"
1434
+ }
1435
+ ],
1436
+ "discriminator": {
1437
+ "propertyName": "type",
1438
+ "mapping": {
1439
+ "input_text": "#/components/schemas/InputText",
1440
+ "input_image": "#/components/schemas/InputImage",
1441
+ "input_file": "#/components/schemas/InputFile",
1442
+ "input_audio": "#/components/schemas/InputAudio"
1443
+ }
1444
+ }
1445
+ }
1446
+ }
1447
+ },
1448
+ "required": [
1449
+ "id",
1450
+ "role",
1451
+ "content"
1452
+ ]
1453
+ },
1454
+ {
1455
+ "type": "object",
1456
+ "properties": {
1457
+ "type": {
1458
+ "type": "string",
1459
+ "enum": [
1460
+ "function_call_output"
1461
+ ]
1462
+ },
1463
+ "id": {
1464
+ "type": "string",
1465
+ "nullable": true
1466
+ },
1467
+ "call_id": {
1468
+ "type": "string"
1469
+ },
1470
+ "output": {
1471
+ "anyOf": [
1472
+ {
1473
+ "type": "string"
1474
+ },
1475
+ {
1476
+ "type": "array",
1477
+ "items": {
1478
+ "oneOf": [
1479
+ {
1480
+ "$ref": "#/components/schemas/InputText"
1481
+ },
1482
+ {
1483
+ "$ref": "#/components/schemas/InputImage"
1484
+ },
1485
+ {
1486
+ "$ref": "#/components/schemas/InputFile"
1487
+ }
1488
+ ],
1489
+ "discriminator": {
1490
+ "propertyName": "type",
1491
+ "mapping": {
1492
+ "input_text": "#/components/schemas/InputText",
1493
+ "input_image": "#/components/schemas/InputImage",
1494
+ "input_file": "#/components/schemas/InputFile"
1495
+ }
1496
+ }
1497
+ }
1498
+ }
1499
+ ]
1500
+ },
1501
+ "status": {
1502
+ "$ref": "#/components/schemas/ToolCallStatusEnum"
1503
+ }
1504
+ },
1505
+ "required": [
1506
+ "type",
1507
+ "call_id",
1508
+ "output"
1509
+ ]
1510
+ },
1511
+ {
1512
+ "type": "object",
1513
+ "properties": {
1514
+ "type": {
1515
+ "type": "string",
1516
+ "enum": [
1517
+ "function_call"
1518
+ ]
1519
+ },
1520
+ "call_id": {
1521
+ "type": "string"
1522
+ },
1523
+ "name": {
1524
+ "type": "string"
1525
+ },
1526
+ "arguments": {
1527
+ "type": "string"
1528
+ },
1529
+ "id": {
1530
+ "type": "string"
1531
+ },
1532
+ "status": {
1533
+ "$ref": "#/components/schemas/ToolCallStatusEnum"
1534
+ }
1535
+ },
1536
+ "required": [
1537
+ "type",
1538
+ "call_id",
1539
+ "name",
1540
+ "arguments"
1541
+ ]
1542
+ },
1543
+ {
1544
+ "$ref": "#/components/schemas/OutputItemImageGenerationCall"
1545
+ },
1546
+ {
1547
+ "$ref": "#/components/schemas/OutputMessage"
1548
+ }
1549
+ ]
1550
+ }
1551
+ },
1552
+ {
1553
+ "nullable": true
1554
+ }
1555
+ ]
1556
+ },
1557
+ "RequestMetadata": {
1558
+ "type": "object",
1559
+ "nullable": true,
1560
+ "additionalProperties": {
1561
+ "type": "string",
1562
+ "maxLength": 512
1563
+ },
1564
+ "description": "Metadata key-value pairs for the request. Keys must be ≤64 characters and cannot contain brackets. Values must be ≤512 characters. Maximum 16 pairs allowed.",
1565
+ "example": {
1566
+ "user_id": "123",
1567
+ "session_id": "abc-def-ghi"
1568
+ }
1569
+ },
1570
+ "FunctionTool": {
1571
+ "type": "object",
1572
+ "properties": {
1573
+ "type": {
1574
+ "type": "string",
1575
+ "enum": [
1576
+ "function"
1577
+ ]
1578
+ },
1579
+ "name": {
1580
+ "type": "string"
1581
+ },
1582
+ "description": {
1583
+ "type": "string",
1584
+ "nullable": true
1585
+ },
1586
+ "strict": {
1587
+ "type": "boolean",
1588
+ "nullable": true
1589
+ },
1590
+ "parameters": {
1591
+ "type": "object",
1592
+ "nullable": true,
1593
+ "additionalProperties": {
1594
+ "nullable": true
1595
+ }
1596
+ }
1597
+ },
1598
+ "required": [
1599
+ "type",
1600
+ "name",
1601
+ "parameters"
1602
+ ],
1603
+ "description": "Function tool definition",
1604
+ "example": {
1605
+ "type": "function",
1606
+ "name": "get_weather",
1607
+ "description": "Get the current weather in a location",
1608
+ "parameters": {
1609
+ "type": "object",
1610
+ "properties": {
1611
+ "location": {
1612
+ "type": "string",
1613
+ "description": "The city and state"
1614
+ },
1615
+ "unit": {
1616
+ "type": "string",
1617
+ "enum": [
1618
+ "celsius",
1619
+ "fahrenheit"
1620
+ ],
1621
+ "x-speakeasy-unknown-values": "allow"
1622
+ }
1623
+ },
1624
+ "required": [
1625
+ "location"
1626
+ ]
1627
+ }
1628
+ }
1629
+ },
1630
+ "SearchContextSizeEnum": {
1631
+ "type": "string",
1632
+ "enum": [
1633
+ "low",
1634
+ "medium",
1635
+ "high"
1636
+ ],
1637
+ "description": "Size of the search context for web search tools",
1638
+ "example": "medium",
1639
+ "x-speakeasy-unknown-values": "allow"
1640
+ },
1641
+ "Preview_WebSearchUserLocation": {
1642
+ "type": "object",
1643
+ "nullable": true,
1644
+ "properties": {
1645
+ "type": {
1646
+ "type": "string",
1647
+ "enum": [
1648
+ "approximate"
1649
+ ]
1650
+ },
1651
+ "city": {
1652
+ "type": "string",
1653
+ "nullable": true
1654
+ },
1655
+ "country": {
1656
+ "type": "string",
1657
+ "nullable": true
1658
+ },
1659
+ "region": {
1660
+ "type": "string",
1661
+ "nullable": true
1662
+ },
1663
+ "timezone": {
1664
+ "type": "string",
1665
+ "nullable": true
1666
+ }
1667
+ },
1668
+ "required": [
1669
+ "type"
1670
+ ]
1671
+ },
1672
+ "Preview_WebSearchServerTool": {
1673
+ "type": "object",
1674
+ "properties": {
1675
+ "type": {
1676
+ "type": "string",
1677
+ "enum": [
1678
+ "web_search_preview"
1679
+ ]
1680
+ },
1681
+ "search_context_size": {
1682
+ "$ref": "#/components/schemas/SearchContextSizeEnum"
1683
+ },
1684
+ "user_location": {
1685
+ "$ref": "#/components/schemas/Preview_WebSearchUserLocation"
1686
+ },
1687
+ "engine": {
1688
+ "type": "string",
1689
+ "enum": [
1690
+ "auto",
1691
+ "native",
1692
+ "exa",
1693
+ "firecrawl",
1694
+ "parallel"
1695
+ ],
1696
+ "description": "Which search engine to use. \"auto\" (default) uses native if the provider supports it, otherwise Exa. \"native\" forces the provider's built-in search. \"exa\" forces the Exa search API. \"firecrawl\" uses Firecrawl (requires BYOK). \"parallel\" uses the Parallel search API.",
1697
+ "example": "auto",
1698
+ "x-speakeasy-unknown-values": "allow"
1699
+ },
1700
+ "max_results": {
1701
+ "type": "number",
1702
+ "minimum": 1,
1703
+ "maximum": 25,
1704
+ "description": "Maximum number of search results to return per search call. Defaults to 5. Applies to Exa, Firecrawl, and Parallel engines; ignored with native provider search.",
1705
+ "example": 5
1706
+ },
1707
+ "filters": {
1708
+ "type": "object",
1709
+ "nullable": true,
1710
+ "properties": {
1711
+ "allowed_domains": {
1712
+ "type": "array",
1713
+ "nullable": true,
1714
+ "items": {
1715
+ "type": "string"
1716
+ }
1717
+ },
1718
+ "excluded_domains": {
1719
+ "type": "array",
1720
+ "nullable": true,
1721
+ "items": {
1722
+ "type": "string"
1723
+ }
1724
+ }
1725
+ }
1726
+ }
1727
+ },
1728
+ "required": [
1729
+ "type"
1730
+ ],
1731
+ "description": "Web search preview tool configuration",
1732
+ "example": {
1733
+ "type": "web_search_preview"
1734
+ }
1735
+ },
1736
+ "Preview_20250311_WebSearchServerTool": {
1737
+ "type": "object",
1738
+ "properties": {
1739
+ "type": {
1740
+ "type": "string",
1741
+ "enum": [
1742
+ "web_search_preview_2025_03_11"
1743
+ ]
1744
+ },
1745
+ "search_context_size": {
1746
+ "$ref": "#/components/schemas/SearchContextSizeEnum"
1747
+ },
1748
+ "user_location": {
1749
+ "$ref": "#/components/schemas/Preview_WebSearchUserLocation"
1750
+ },
1751
+ "engine": {
1752
+ "type": "string",
1753
+ "enum": [
1754
+ "auto",
1755
+ "native",
1756
+ "exa",
1757
+ "firecrawl",
1758
+ "parallel"
1759
+ ],
1760
+ "description": "Which search engine to use. \"auto\" (default) uses native if the provider supports it, otherwise Exa. \"native\" forces the provider's built-in search. \"exa\" forces the Exa search API. \"firecrawl\" uses Firecrawl (requires BYOK). \"parallel\" uses the Parallel search API.",
1761
+ "example": "auto",
1762
+ "x-speakeasy-unknown-values": "allow"
1763
+ },
1764
+ "max_results": {
1765
+ "type": "number",
1766
+ "minimum": 1,
1767
+ "maximum": 25,
1768
+ "description": "Maximum number of search results to return per search call. Defaults to 5. Applies to Exa, Firecrawl, and Parallel engines; ignored with native provider search.",
1769
+ "example": 5
1770
+ },
1771
+ "filters": {
1772
+ "type": "object",
1773
+ "nullable": true,
1774
+ "properties": {
1775
+ "allowed_domains": {
1776
+ "type": "array",
1777
+ "nullable": true,
1778
+ "items": {
1779
+ "type": "string"
1780
+ }
1781
+ },
1782
+ "excluded_domains": {
1783
+ "type": "array",
1784
+ "nullable": true,
1785
+ "items": {
1786
+ "type": "string"
1787
+ }
1788
+ }
1789
+ }
1790
+ }
1791
+ },
1792
+ "required": [
1793
+ "type"
1794
+ ],
1795
+ "description": "Web search preview tool configuration (2025-03-11 version)",
1796
+ "example": {
1797
+ "type": "web_search_preview_2025_03_11"
1798
+ }
1799
+ },
1800
+ "WebSearchUserLocation": {
1801
+ "type": "object",
1802
+ "nullable": true,
1803
+ "properties": {
1804
+ "type": {
1805
+ "type": "string",
1806
+ "enum": [
1807
+ "approximate"
1808
+ ]
1809
+ },
1810
+ "city": {
1811
+ "type": "string",
1812
+ "nullable": true
1813
+ },
1814
+ "country": {
1815
+ "type": "string",
1816
+ "nullable": true
1817
+ },
1818
+ "region": {
1819
+ "type": "string",
1820
+ "nullable": true
1821
+ },
1822
+ "timezone": {
1823
+ "type": "string",
1824
+ "nullable": true
1825
+ }
1826
+ },
1827
+ "description": "User location information for web search",
1828
+ "example": {
1829
+ "type": "approximate",
1830
+ "city": "San Francisco",
1831
+ "country": "USA",
1832
+ "region": "California",
1833
+ "timezone": "America/Los_Angeles"
1834
+ }
1835
+ },
1836
+ "Legacy_WebSearchServerTool": {
1837
+ "type": "object",
1838
+ "properties": {
1839
+ "type": {
1840
+ "type": "string",
1841
+ "enum": [
1842
+ "web_search"
1843
+ ]
1844
+ },
1845
+ "filters": {
1846
+ "type": "object",
1847
+ "nullable": true,
1848
+ "properties": {
1849
+ "allowed_domains": {
1850
+ "type": "array",
1851
+ "nullable": true,
1852
+ "items": {
1853
+ "type": "string"
1854
+ }
1855
+ },
1856
+ "excluded_domains": {
1857
+ "type": "array",
1858
+ "nullable": true,
1859
+ "items": {
1860
+ "type": "string"
1861
+ }
1862
+ }
1863
+ }
1864
+ },
1865
+ "search_context_size": {
1866
+ "$ref": "#/components/schemas/SearchContextSizeEnum"
1867
+ },
1868
+ "user_location": {
1869
+ "$ref": "#/components/schemas/WebSearchUserLocation"
1870
+ },
1871
+ "engine": {
1872
+ "type": "string",
1873
+ "enum": [
1874
+ "auto",
1875
+ "native",
1876
+ "exa",
1877
+ "firecrawl",
1878
+ "parallel"
1879
+ ],
1880
+ "description": "Which search engine to use. \"auto\" (default) uses native if the provider supports it, otherwise Exa. \"native\" forces the provider's built-in search. \"exa\" forces the Exa search API. \"firecrawl\" uses Firecrawl (requires BYOK). \"parallel\" uses the Parallel search API.",
1881
+ "example": "auto",
1882
+ "x-speakeasy-unknown-values": "allow"
1883
+ },
1884
+ "max_results": {
1885
+ "type": "number",
1886
+ "minimum": 1,
1887
+ "maximum": 25,
1888
+ "description": "Maximum number of search results to return per search call. Defaults to 5. Applies to Exa, Firecrawl, and Parallel engines; ignored with native provider search.",
1889
+ "example": 5
1890
+ }
1891
+ },
1892
+ "required": [
1893
+ "type"
1894
+ ],
1895
+ "description": "Web search tool configuration",
1896
+ "example": {
1897
+ "type": "web_search",
1898
+ "engine": "auto",
1899
+ "filters": {
1900
+ "allowed_domains": [
1901
+ "example.com"
1902
+ ]
1903
+ }
1904
+ }
1905
+ },
1906
+ "WebSearchServerTool": {
1907
+ "type": "object",
1908
+ "properties": {
1909
+ "type": {
1910
+ "type": "string",
1911
+ "enum": [
1912
+ "web_search_2025_08_26"
1913
+ ]
1914
+ },
1915
+ "filters": {
1916
+ "type": "object",
1917
+ "nullable": true,
1918
+ "properties": {
1919
+ "allowed_domains": {
1920
+ "type": "array",
1921
+ "nullable": true,
1922
+ "items": {
1923
+ "type": "string"
1924
+ }
1925
+ },
1926
+ "excluded_domains": {
1927
+ "type": "array",
1928
+ "nullable": true,
1929
+ "items": {
1930
+ "type": "string"
1931
+ }
1932
+ }
1933
+ }
1934
+ },
1935
+ "search_context_size": {
1936
+ "$ref": "#/components/schemas/SearchContextSizeEnum"
1937
+ },
1938
+ "user_location": {
1939
+ "$ref": "#/components/schemas/WebSearchUserLocation"
1940
+ },
1941
+ "engine": {
1942
+ "type": "string",
1943
+ "enum": [
1944
+ "auto",
1945
+ "native",
1946
+ "exa",
1947
+ "firecrawl",
1948
+ "parallel"
1949
+ ],
1950
+ "description": "Which search engine to use. \"auto\" (default) uses native if the provider supports it, otherwise Exa. \"native\" forces the provider's built-in search. \"exa\" forces the Exa search API. \"firecrawl\" uses Firecrawl (requires BYOK). \"parallel\" uses the Parallel search API.",
1951
+ "example": "auto",
1952
+ "x-speakeasy-unknown-values": "allow"
1953
+ },
1954
+ "max_results": {
1955
+ "type": "number",
1956
+ "minimum": 1,
1957
+ "maximum": 25,
1958
+ "description": "Maximum number of search results to return per search call. Defaults to 5. Applies to Exa, Firecrawl, and Parallel engines; ignored with native provider search.",
1959
+ "example": 5
1960
+ }
1961
+ },
1962
+ "required": [
1963
+ "type"
1964
+ ],
1965
+ "description": "Web search tool configuration (2025-08-26 version)",
1966
+ "example": {
1967
+ "type": "web_search_2025_08_26",
1968
+ "engine": "auto",
1969
+ "filters": {
1970
+ "allowed_domains": [
1971
+ "example.com"
1972
+ ]
1973
+ }
1974
+ }
1975
+ },
1976
+ "CompoundFilter": {
1977
+ "type": "object",
1978
+ "properties": {
1979
+ "type": {
1980
+ "type": "string",
1981
+ "enum": [
1982
+ "and",
1983
+ "or"
1984
+ ],
1985
+ "x-speakeasy-unknown-values": "allow"
1986
+ },
1987
+ "filters": {
1988
+ "type": "array",
1989
+ "items": {
1990
+ "type": "object",
1991
+ "additionalProperties": {
1992
+ "nullable": true
1993
+ }
1994
+ }
1995
+ }
1996
+ },
1997
+ "required": [
1998
+ "type",
1999
+ "filters"
2000
+ ],
2001
+ "description": "A compound filter that combines multiple comparison or compound filters"
2002
+ },
2003
+ "FileSearchServerTool": {
2004
+ "type": "object",
2005
+ "properties": {
2006
+ "type": {
2007
+ "type": "string",
2008
+ "enum": [
2009
+ "file_search"
2010
+ ]
2011
+ },
2012
+ "vector_store_ids": {
2013
+ "type": "array",
2014
+ "items": {
2015
+ "type": "string"
2016
+ }
2017
+ },
2018
+ "filters": {
2019
+ "anyOf": [
2020
+ {
2021
+ "type": "object",
2022
+ "properties": {
2023
+ "key": {
2024
+ "type": "string"
2025
+ },
2026
+ "type": {
2027
+ "type": "string",
2028
+ "enum": [
2029
+ "eq",
2030
+ "ne",
2031
+ "gt",
2032
+ "gte",
2033
+ "lt",
2034
+ "lte"
2035
+ ],
2036
+ "x-speakeasy-unknown-values": "allow"
2037
+ },
2038
+ "value": {
2039
+ "anyOf": [
2040
+ {
2041
+ "type": "string"
2042
+ },
2043
+ {
2044
+ "type": "number",
2045
+ "format": "double"
2046
+ },
2047
+ {
2048
+ "type": "boolean"
2049
+ },
2050
+ {
2051
+ "type": "array",
2052
+ "items": {
2053
+ "anyOf": [
2054
+ {
2055
+ "type": "string"
2056
+ },
2057
+ {
2058
+ "type": "number",
2059
+ "format": "double"
2060
+ }
2061
+ ]
2062
+ }
2063
+ }
2064
+ ]
2065
+ }
2066
+ },
2067
+ "required": [
2068
+ "key",
2069
+ "type",
2070
+ "value"
2071
+ ]
2072
+ },
2073
+ {
2074
+ "$ref": "#/components/schemas/CompoundFilter"
2075
+ },
2076
+ {
2077
+ "nullable": true
2078
+ }
2079
+ ]
2080
+ },
2081
+ "max_num_results": {
2082
+ "type": "integer"
2083
+ },
2084
+ "ranking_options": {
2085
+ "type": "object",
2086
+ "properties": {
2087
+ "ranker": {
2088
+ "type": "string",
2089
+ "enum": [
2090
+ "auto",
2091
+ "default-2024-11-15"
2092
+ ],
2093
+ "x-speakeasy-unknown-values": "allow"
2094
+ },
2095
+ "score_threshold": {
2096
+ "type": "number",
2097
+ "format": "double"
2098
+ }
2099
+ }
2100
+ }
2101
+ },
2102
+ "required": [
2103
+ "type",
2104
+ "vector_store_ids"
2105
+ ],
2106
+ "description": "File search tool configuration",
2107
+ "example": {
2108
+ "type": "file_search",
2109
+ "vector_store_ids": [
2110
+ "vs_abc123"
2111
+ ]
2112
+ }
2113
+ },
2114
+ "ComputerUseServerTool": {
2115
+ "type": "object",
2116
+ "properties": {
2117
+ "type": {
2118
+ "type": "string",
2119
+ "enum": [
2120
+ "computer_use_preview"
2121
+ ]
2122
+ },
2123
+ "display_height": {
2124
+ "type": "integer"
2125
+ },
2126
+ "display_width": {
2127
+ "type": "integer"
2128
+ },
2129
+ "environment": {
2130
+ "type": "string",
2131
+ "enum": [
2132
+ "windows",
2133
+ "mac",
2134
+ "linux",
2135
+ "ubuntu",
2136
+ "browser"
2137
+ ],
2138
+ "x-speakeasy-unknown-values": "allow"
2139
+ }
2140
+ },
2141
+ "required": [
2142
+ "type",
2143
+ "display_height",
2144
+ "display_width",
2145
+ "environment"
2146
+ ],
2147
+ "description": "Computer use preview tool configuration",
2148
+ "example": {
2149
+ "type": "computer_use_preview",
2150
+ "display_height": 768,
2151
+ "display_width": 1024,
2152
+ "environment": "linux"
2153
+ }
2154
+ },
2155
+ "CodeInterpreterServerTool": {
2156
+ "type": "object",
2157
+ "properties": {
2158
+ "type": {
2159
+ "type": "string",
2160
+ "enum": [
2161
+ "code_interpreter"
2162
+ ]
2163
+ },
2164
+ "container": {
2165
+ "anyOf": [
2166
+ {
2167
+ "type": "string"
2168
+ },
2169
+ {
2170
+ "type": "object",
2171
+ "properties": {
2172
+ "type": {
2173
+ "type": "string",
2174
+ "enum": [
2175
+ "auto"
2176
+ ]
2177
+ },
2178
+ "file_ids": {
2179
+ "type": "array",
2180
+ "items": {
2181
+ "type": "string"
2182
+ }
2183
+ },
2184
+ "memory_limit": {
2185
+ "type": "string",
2186
+ "nullable": true,
2187
+ "enum": [
2188
+ "1g",
2189
+ "4g",
2190
+ "16g",
2191
+ "64g"
2192
+ ],
2193
+ "x-speakeasy-unknown-values": "allow"
2194
+ }
2195
+ },
2196
+ "required": [
2197
+ "type"
2198
+ ]
2199
+ }
2200
+ ]
2201
+ }
2202
+ },
2203
+ "required": [
2204
+ "type",
2205
+ "container"
2206
+ ],
2207
+ "description": "Code interpreter tool configuration",
2208
+ "example": {
2209
+ "type": "code_interpreter",
2210
+ "container": "auto"
2211
+ }
2212
+ },
2213
+ "McpServerTool": {
2214
+ "type": "object",
2215
+ "properties": {
2216
+ "type": {
2217
+ "type": "string",
2218
+ "enum": [
2219
+ "mcp"
2220
+ ]
2221
+ },
2222
+ "server_label": {
2223
+ "type": "string"
2224
+ },
2225
+ "allowed_tools": {
2226
+ "anyOf": [
2227
+ {
2228
+ "type": "array",
2229
+ "items": {
2230
+ "type": "string"
2231
+ }
2232
+ },
2233
+ {
2234
+ "type": "object",
2235
+ "properties": {
2236
+ "tool_names": {
2237
+ "type": "array",
2238
+ "items": {
2239
+ "type": "string"
2240
+ }
2241
+ },
2242
+ "read_only": {
2243
+ "type": "boolean"
2244
+ }
2245
+ }
2246
+ },
2247
+ {
2248
+ "nullable": true
2249
+ },
2250
+ {
2251
+ "nullable": true
2252
+ }
2253
+ ]
2254
+ },
2255
+ "authorization": {
2256
+ "type": "string"
2257
+ },
2258
+ "connector_id": {
2259
+ "type": "string",
2260
+ "enum": [
2261
+ "connector_dropbox",
2262
+ "connector_gmail",
2263
+ "connector_googlecalendar",
2264
+ "connector_googledrive",
2265
+ "connector_microsoftteams",
2266
+ "connector_outlookcalendar",
2267
+ "connector_outlookemail",
2268
+ "connector_sharepoint"
2269
+ ],
2270
+ "x-speakeasy-unknown-values": "allow"
2271
+ },
2272
+ "headers": {
2273
+ "type": "object",
2274
+ "nullable": true,
2275
+ "additionalProperties": {
2276
+ "type": "string"
2277
+ }
2278
+ },
2279
+ "require_approval": {
2280
+ "anyOf": [
2281
+ {
2282
+ "type": "object",
2283
+ "properties": {
2284
+ "never": {
2285
+ "type": "object",
2286
+ "properties": {
2287
+ "tool_names": {
2288
+ "type": "array",
2289
+ "items": {
2290
+ "type": "string"
2291
+ }
2292
+ }
2293
+ }
2294
+ },
2295
+ "always": {
2296
+ "type": "object",
2297
+ "properties": {
2298
+ "tool_names": {
2299
+ "type": "array",
2300
+ "items": {
2301
+ "type": "string"
2302
+ }
2303
+ }
2304
+ }
2305
+ }
2306
+ }
2307
+ },
2308
+ {
2309
+ "type": "string",
2310
+ "enum": [
2311
+ "always"
2312
+ ]
2313
+ },
2314
+ {
2315
+ "type": "string",
2316
+ "enum": [
2317
+ "never"
2318
+ ]
2319
+ },
2320
+ {
2321
+ "nullable": true
2322
+ },
2323
+ {
2324
+ "nullable": true
2325
+ }
2326
+ ]
2327
+ },
2328
+ "server_description": {
2329
+ "type": "string"
2330
+ },
2331
+ "server_url": {
2332
+ "type": "string"
2333
+ }
2334
+ },
2335
+ "required": [
2336
+ "type",
2337
+ "server_label"
2338
+ ],
2339
+ "description": "MCP (Model Context Protocol) tool configuration",
2340
+ "example": {
2341
+ "type": "mcp",
2342
+ "server_label": "my-server",
2343
+ "server_url": "https://example.com/mcp"
2344
+ }
2345
+ },
2346
+ "ImageGenerationServerTool": {
2347
+ "type": "object",
2348
+ "properties": {
2349
+ "type": {
2350
+ "type": "string",
2351
+ "enum": [
2352
+ "image_generation"
2353
+ ]
2354
+ },
2355
+ "background": {
2356
+ "type": "string",
2357
+ "enum": [
2358
+ "transparent",
2359
+ "opaque",
2360
+ "auto"
2361
+ ],
2362
+ "x-speakeasy-unknown-values": "allow"
2363
+ },
2364
+ "input_fidelity": {
2365
+ "type": "string",
2366
+ "nullable": true,
2367
+ "enum": [
2368
+ "high",
2369
+ "low"
2370
+ ],
2371
+ "x-speakeasy-unknown-values": "allow"
2372
+ },
2373
+ "input_image_mask": {
2374
+ "type": "object",
2375
+ "properties": {
2376
+ "image_url": {
2377
+ "type": "string"
2378
+ },
2379
+ "file_id": {
2380
+ "type": "string"
2381
+ }
2382
+ }
2383
+ },
2384
+ "model": {
2385
+ "type": "string",
2386
+ "enum": [
2387
+ "gpt-image-1",
2388
+ "gpt-image-1-mini"
2389
+ ],
2390
+ "x-speakeasy-unknown-values": "allow"
2391
+ },
2392
+ "moderation": {
2393
+ "type": "string",
2394
+ "enum": [
2395
+ "auto",
2396
+ "low"
2397
+ ],
2398
+ "x-speakeasy-unknown-values": "allow"
2399
+ },
2400
+ "output_compression": {
2401
+ "type": "integer"
2402
+ },
2403
+ "output_format": {
2404
+ "type": "string",
2405
+ "enum": [
2406
+ "png",
2407
+ "webp",
2408
+ "jpeg"
2409
+ ],
2410
+ "x-speakeasy-unknown-values": "allow"
2411
+ },
2412
+ "partial_images": {
2413
+ "type": "integer"
2414
+ },
2415
+ "quality": {
2416
+ "type": "string",
2417
+ "enum": [
2418
+ "low",
2419
+ "medium",
2420
+ "high",
2421
+ "auto"
2422
+ ],
2423
+ "x-speakeasy-unknown-values": "allow"
2424
+ },
2425
+ "size": {
2426
+ "type": "string",
2427
+ "enum": [
2428
+ "1024x1024",
2429
+ "1024x1536",
2430
+ "1536x1024",
2431
+ "auto"
2432
+ ],
2433
+ "x-speakeasy-unknown-values": "allow"
2434
+ }
2435
+ },
2436
+ "required": [
2437
+ "type"
2438
+ ],
2439
+ "description": "Image generation tool configuration",
2440
+ "example": {
2441
+ "type": "image_generation",
2442
+ "quality": "high"
2443
+ }
2444
+ },
2445
+ "CodexLocalShellTool": {
2446
+ "type": "object",
2447
+ "properties": {
2448
+ "type": {
2449
+ "type": "string",
2450
+ "enum": [
2451
+ "local_shell"
2452
+ ]
2453
+ }
2454
+ },
2455
+ "required": [
2456
+ "type"
2457
+ ],
2458
+ "description": "Local shell tool configuration",
2459
+ "example": {
2460
+ "type": "local_shell"
2461
+ }
2462
+ },
2463
+ "ShellServerTool": {
2464
+ "type": "object",
2465
+ "properties": {
2466
+ "type": {
2467
+ "type": "string",
2468
+ "enum": [
2469
+ "shell"
2470
+ ]
2471
+ }
2472
+ },
2473
+ "required": [
2474
+ "type"
2475
+ ],
2476
+ "description": "Shell tool configuration",
2477
+ "example": {
2478
+ "type": "shell"
2479
+ }
2480
+ },
2481
+ "ApplyPatchServerTool": {
2482
+ "type": "object",
2483
+ "properties": {
2484
+ "type": {
2485
+ "type": "string",
2486
+ "enum": [
2487
+ "apply_patch"
2488
+ ]
2489
+ }
2490
+ },
2491
+ "required": [
2492
+ "type"
2493
+ ],
2494
+ "description": "Apply patch tool configuration",
2495
+ "example": {
2496
+ "type": "apply_patch"
2497
+ }
2498
+ },
2499
+ "CustomTool": {
2500
+ "type": "object",
2501
+ "properties": {
2502
+ "type": {
2503
+ "type": "string",
2504
+ "enum": [
2505
+ "custom"
2506
+ ]
2507
+ },
2508
+ "name": {
2509
+ "type": "string"
2510
+ },
2511
+ "description": {
2512
+ "type": "string"
2513
+ },
2514
+ "format": {
2515
+ "anyOf": [
2516
+ {
2517
+ "type": "object",
2518
+ "properties": {
2519
+ "type": {
2520
+ "type": "string",
2521
+ "enum": [
2522
+ "text"
2523
+ ]
2524
+ }
2525
+ },
2526
+ "required": [
2527
+ "type"
2528
+ ]
2529
+ },
2530
+ {
2531
+ "type": "object",
2532
+ "properties": {
2533
+ "type": {
2534
+ "type": "string",
2535
+ "enum": [
2536
+ "grammar"
2537
+ ]
2538
+ },
2539
+ "definition": {
2540
+ "type": "string"
2541
+ },
2542
+ "syntax": {
2543
+ "type": "string",
2544
+ "enum": [
2545
+ "lark",
2546
+ "regex"
2547
+ ],
2548
+ "x-speakeasy-unknown-values": "allow"
2549
+ }
2550
+ },
2551
+ "required": [
2552
+ "type",
2553
+ "definition",
2554
+ "syntax"
2555
+ ]
2556
+ }
2557
+ ]
2558
+ }
2559
+ },
2560
+ "required": [
2561
+ "type",
2562
+ "name"
2563
+ ],
2564
+ "description": "Custom tool configuration",
2565
+ "example": {
2566
+ "type": "custom",
2567
+ "name": "my_tool"
2568
+ }
2569
+ },
2570
+ "ToolChoiceAllowed": {
2571
+ "type": "object",
2572
+ "properties": {
2573
+ "type": {
2574
+ "type": "string",
2575
+ "enum": [
2576
+ "allowed_tools"
2577
+ ]
2578
+ },
2579
+ "mode": {
2580
+ "anyOf": [
2581
+ {
2582
+ "type": "string",
2583
+ "enum": [
2584
+ "auto"
2585
+ ]
2586
+ },
2587
+ {
2588
+ "type": "string",
2589
+ "enum": [
2590
+ "required"
2591
+ ]
2592
+ }
2593
+ ]
2594
+ },
2595
+ "tools": {
2596
+ "type": "array",
2597
+ "items": {
2598
+ "type": "object",
2599
+ "additionalProperties": {
2600
+ "nullable": true
2601
+ }
2602
+ }
2603
+ }
2604
+ },
2605
+ "required": [
2606
+ "type",
2607
+ "mode",
2608
+ "tools"
2609
+ ],
2610
+ "description": "Constrains the model to a pre-defined set of allowed tools",
2611
+ "example": {
2612
+ "type": "allowed_tools",
2613
+ "mode": "auto",
2614
+ "tools": [
2615
+ {
2616
+ "type": "function",
2617
+ "name": "get_weather"
2618
+ }
2619
+ ]
2620
+ }
2621
+ },
2622
+ "OpenAIResponsesToolChoice": {
2623
+ "anyOf": [
2624
+ {
2625
+ "type": "string",
2626
+ "enum": [
2627
+ "auto"
2628
+ ]
2629
+ },
2630
+ {
2631
+ "type": "string",
2632
+ "enum": [
2633
+ "none"
2634
+ ]
2635
+ },
2636
+ {
2637
+ "type": "string",
2638
+ "enum": [
2639
+ "required"
2640
+ ]
2641
+ },
2642
+ {
2643
+ "type": "object",
2644
+ "properties": {
2645
+ "type": {
2646
+ "type": "string",
2647
+ "enum": [
2648
+ "function"
2649
+ ]
2650
+ },
2651
+ "name": {
2652
+ "type": "string"
2653
+ }
2654
+ },
2655
+ "required": [
2656
+ "type",
2657
+ "name"
2658
+ ]
2659
+ },
2660
+ {
2661
+ "type": "object",
2662
+ "properties": {
2663
+ "type": {
2664
+ "anyOf": [
2665
+ {
2666
+ "type": "string",
2667
+ "enum": [
2668
+ "web_search_preview_2025_03_11"
2669
+ ]
2670
+ },
2671
+ {
2672
+ "type": "string",
2673
+ "enum": [
2674
+ "web_search_preview"
2675
+ ]
2676
+ }
2677
+ ]
2678
+ }
2679
+ },
2680
+ "required": [
2681
+ "type"
2682
+ ]
2683
+ },
2684
+ {
2685
+ "$ref": "#/components/schemas/ToolChoiceAllowed"
2686
+ }
2687
+ ]
2688
+ },
2689
+ "StoredPromptTemplate": {
2690
+ "type": "object",
2691
+ "nullable": true,
2692
+ "properties": {
2693
+ "id": {
2694
+ "type": "string"
2695
+ },
2696
+ "variables": {
2697
+ "type": "object",
2698
+ "nullable": true,
2699
+ "additionalProperties": {
2700
+ "anyOf": [
2701
+ {
2702
+ "type": "string"
2703
+ },
2704
+ {
2705
+ "$ref": "#/components/schemas/InputText"
2706
+ },
2707
+ {
2708
+ "$ref": "#/components/schemas/InputImage"
2709
+ },
2710
+ {
2711
+ "$ref": "#/components/schemas/InputFile"
2712
+ }
2713
+ ]
2714
+ }
2715
+ }
2716
+ },
2717
+ "required": [
2718
+ "id"
2719
+ ]
2720
+ },
2721
+ "ReasoningEffortEnum": {
2722
+ "type": "string",
2723
+ "nullable": true,
2724
+ "enum": [
2725
+ "xhigh",
2726
+ "high",
2727
+ "medium",
2728
+ "low",
2729
+ "minimal",
2730
+ "none"
2731
+ ],
2732
+ "x-speakeasy-unknown-values": "allow"
2733
+ },
2734
+ "ReasoningSummaryVerbosityEnum": {
2735
+ "type": "string",
2736
+ "nullable": true,
2737
+ "enum": [
2738
+ "auto",
2739
+ "concise",
2740
+ "detailed"
2741
+ ],
2742
+ "example": "auto",
2743
+ "x-speakeasy-unknown-values": "allow"
2744
+ },
2745
+ "BaseReasoningConfig": {
2746
+ "type": "object",
2747
+ "nullable": true,
2748
+ "properties": {
2749
+ "effort": {
2750
+ "$ref": "#/components/schemas/ReasoningEffortEnum"
2751
+ },
2752
+ "summary": {
2753
+ "$ref": "#/components/schemas/ReasoningSummaryVerbosityEnum"
2754
+ }
2755
+ }
2756
+ },
2757
+ "TruncationEnum": {
2758
+ "type": "string",
2759
+ "nullable": true,
2760
+ "enum": [
2761
+ "auto",
2762
+ "disabled"
2763
+ ],
2764
+ "x-speakeasy-unknown-values": "allow"
2765
+ },
2766
+ "FormatTextConfig": {
2767
+ "type": "object",
2768
+ "properties": {
2769
+ "type": {
2770
+ "type": "string",
2771
+ "enum": [
2772
+ "text"
2773
+ ]
2774
+ }
2775
+ },
2776
+ "required": [
2777
+ "type"
2778
+ ],
2779
+ "description": "Plain text response format",
2780
+ "example": {
2781
+ "type": "text"
2782
+ }
2783
+ },
2784
+ "FormatJsonObjectConfig": {
2785
+ "type": "object",
2786
+ "properties": {
2787
+ "type": {
2788
+ "type": "string",
2789
+ "enum": [
2790
+ "json_object"
2791
+ ]
2792
+ }
2793
+ },
2794
+ "required": [
2795
+ "type"
2796
+ ],
2797
+ "description": "JSON object response format",
2798
+ "example": {
2799
+ "type": "json_object"
2800
+ }
2801
+ },
2802
+ "FormatJsonSchemaConfig": {
2803
+ "type": "object",
2804
+ "properties": {
2805
+ "type": {
2806
+ "type": "string",
2807
+ "enum": [
2808
+ "json_schema"
2809
+ ]
2810
+ },
2811
+ "name": {
2812
+ "type": "string"
2813
+ },
2814
+ "description": {
2815
+ "type": "string"
2816
+ },
2817
+ "strict": {
2818
+ "type": "boolean",
2819
+ "nullable": true
2820
+ },
2821
+ "schema": {
2822
+ "type": "object",
2823
+ "additionalProperties": {
2824
+ "nullable": true
2825
+ }
2826
+ }
2827
+ },
2828
+ "required": [
2829
+ "type",
2830
+ "name",
2831
+ "schema"
2832
+ ],
2833
+ "description": "JSON schema constrained response format",
2834
+ "example": {
2835
+ "type": "json_schema",
2836
+ "name": "user_info",
2837
+ "description": "User information schema",
2838
+ "schema": {
2839
+ "type": "object",
2840
+ "properties": {
2841
+ "name": {
2842
+ "type": "string"
2843
+ },
2844
+ "age": {
2845
+ "type": "number"
2846
+ }
2847
+ },
2848
+ "required": [
2849
+ "name"
2850
+ ]
2851
+ }
2852
+ }
2853
+ },
2854
+ "Formats": {
2855
+ "anyOf": [
2856
+ {
2857
+ "$ref": "#/components/schemas/FormatTextConfig"
2858
+ },
2859
+ {
2860
+ "$ref": "#/components/schemas/FormatJsonObjectConfig"
2861
+ },
2862
+ {
2863
+ "$ref": "#/components/schemas/FormatJsonSchemaConfig"
2864
+ }
2865
+ ],
2866
+ "description": "Text response format configuration",
2867
+ "example": {
2868
+ "type": "text"
2869
+ }
2870
+ },
2871
+ "TextConfig": {
2872
+ "type": "object",
2873
+ "properties": {
2874
+ "format": {
2875
+ "$ref": "#/components/schemas/Formats"
2876
+ },
2877
+ "verbosity": {
2878
+ "type": "string",
2879
+ "nullable": true,
2880
+ "enum": [
2881
+ "high",
2882
+ "low",
2883
+ "medium"
2884
+ ],
2885
+ "x-speakeasy-unknown-values": "allow"
2886
+ }
2887
+ },
2888
+ "description": "Text output configuration including format and verbosity",
2889
+ "example": {
2890
+ "format": {
2891
+ "type": "text"
2892
+ },
2893
+ "verbosity": "medium"
2894
+ }
2895
+ },
2896
+ "ServiceTierEnum": {
2897
+ "type": "string",
2898
+ "nullable": true,
2899
+ "enum": [
2900
+ "auto",
2901
+ "default",
2902
+ "flex",
2903
+ "priority",
2904
+ "scale"
2905
+ ],
2906
+ "x-speakeasy-unknown-values": "allow"
2907
+ },
2908
+ "BaseResponsesResult": {
2909
+ "type": "object",
2910
+ "properties": {
2911
+ "id": {
2912
+ "type": "string"
2913
+ },
2914
+ "object": {
2915
+ "type": "string",
2916
+ "enum": [
2917
+ "response"
2918
+ ]
2919
+ },
2920
+ "created_at": {
2921
+ "type": "integer"
2922
+ },
2923
+ "model": {
2924
+ "type": "string"
2925
+ },
2926
+ "status": {
2927
+ "$ref": "#/components/schemas/OpenAIResponsesResponseStatus"
2928
+ },
2929
+ "completed_at": {
2930
+ "type": "integer"
2931
+ },
2932
+ "output": {
2933
+ "type": "array",
2934
+ "items": {
2935
+ "oneOf": [
2936
+ {
2937
+ "$ref": "#/components/schemas/OutputMessage"
2938
+ },
2939
+ {
2940
+ "$ref": "#/components/schemas/OutputItemReasoning"
2941
+ },
2942
+ {
2943
+ "$ref": "#/components/schemas/OutputItemFunctionCall"
2944
+ },
2945
+ {
2946
+ "$ref": "#/components/schemas/OutputItemWebSearchCall"
2947
+ },
2948
+ {
2949
+ "$ref": "#/components/schemas/OutputItemFileSearchCall"
2950
+ },
2951
+ {
2952
+ "$ref": "#/components/schemas/OutputItemImageGenerationCall"
2953
+ }
2954
+ ],
2955
+ "discriminator": {
2956
+ "propertyName": "type",
2957
+ "mapping": {
2958
+ "message": "#/components/schemas/OutputMessage",
2959
+ "reasoning": "#/components/schemas/OutputItemReasoning",
2960
+ "function_call": "#/components/schemas/OutputItemFunctionCall",
2961
+ "web_search_call": "#/components/schemas/OutputItemWebSearchCall",
2962
+ "file_search_call": "#/components/schemas/OutputItemFileSearchCall",
2963
+ "image_generation_call": "#/components/schemas/OutputItemImageGenerationCall"
2964
+ }
2965
+ }
2966
+ }
2967
+ },
2968
+ "user": {
2969
+ "type": "string",
2970
+ "nullable": true
2971
+ },
2972
+ "output_text": {
2973
+ "type": "string"
2974
+ },
2975
+ "prompt_cache_key": {
2976
+ "type": "string",
2977
+ "nullable": true
2978
+ },
2979
+ "safety_identifier": {
2980
+ "type": "string",
2981
+ "nullable": true
2982
+ },
2983
+ "error": {
2984
+ "$ref": "#/components/schemas/ResponsesErrorField"
2985
+ },
2986
+ "incomplete_details": {
2987
+ "$ref": "#/components/schemas/IncompleteDetails"
2988
+ },
2989
+ "usage": {
2990
+ "$ref": "#/components/schemas/OpenAIResponsesUsage"
2991
+ },
2992
+ "max_tool_calls": {
2993
+ "type": "integer"
2994
+ },
2995
+ "top_logprobs": {
2996
+ "type": "integer"
2997
+ },
2998
+ "max_output_tokens": {
2999
+ "type": "integer"
3000
+ },
3001
+ "temperature": {
3002
+ "type": "number",
3003
+ "format": "double"
3004
+ },
3005
+ "top_p": {
3006
+ "type": "number",
3007
+ "format": "double"
3008
+ },
3009
+ "presence_penalty": {
3010
+ "type": "number",
3011
+ "format": "double"
3012
+ },
3013
+ "frequency_penalty": {
3014
+ "type": "number",
3015
+ "format": "double"
3016
+ },
3017
+ "instructions": {
3018
+ "$ref": "#/components/schemas/BaseInputs"
3019
+ },
3020
+ "metadata": {
3021
+ "$ref": "#/components/schemas/RequestMetadata"
3022
+ },
3023
+ "tools": {
3024
+ "type": "array",
3025
+ "items": {
3026
+ "oneOf": [
3027
+ {
3028
+ "allOf": [
3029
+ {
3030
+ "$ref": "#/components/schemas/FunctionTool"
3031
+ },
3032
+ {
3033
+ "type": "object",
3034
+ "properties": {}
3035
+ }
3036
+ ],
3037
+ "description": "Function tool definition",
3038
+ "example": {
3039
+ "type": "function",
3040
+ "name": "get_weather",
3041
+ "description": "Get the current weather in a location",
3042
+ "parameters": {
3043
+ "type": "object",
3044
+ "properties": {
3045
+ "location": {
3046
+ "type": "string",
3047
+ "description": "The city and state"
3048
+ },
3049
+ "unit": {
3050
+ "type": "string",
3051
+ "enum": [
3052
+ "celsius",
3053
+ "fahrenheit"
3054
+ ],
3055
+ "x-speakeasy-unknown-values": "allow"
3056
+ }
3057
+ },
3058
+ "required": [
3059
+ "location"
3060
+ ]
3061
+ }
3062
+ }
3063
+ },
3064
+ {
3065
+ "$ref": "#/components/schemas/Preview_WebSearchServerTool"
3066
+ },
3067
+ {
3068
+ "$ref": "#/components/schemas/Preview_20250311_WebSearchServerTool"
3069
+ },
3070
+ {
3071
+ "$ref": "#/components/schemas/Legacy_WebSearchServerTool"
3072
+ },
3073
+ {
3074
+ "$ref": "#/components/schemas/WebSearchServerTool"
3075
+ },
3076
+ {
3077
+ "$ref": "#/components/schemas/FileSearchServerTool"
3078
+ },
3079
+ {
3080
+ "$ref": "#/components/schemas/ComputerUseServerTool"
3081
+ },
3082
+ {
3083
+ "$ref": "#/components/schemas/CodeInterpreterServerTool"
3084
+ },
3085
+ {
3086
+ "$ref": "#/components/schemas/McpServerTool"
3087
+ },
3088
+ {
3089
+ "$ref": "#/components/schemas/ImageGenerationServerTool"
3090
+ },
3091
+ {
3092
+ "$ref": "#/components/schemas/CodexLocalShellTool"
3093
+ },
3094
+ {
3095
+ "$ref": "#/components/schemas/ShellServerTool"
3096
+ },
3097
+ {
3098
+ "$ref": "#/components/schemas/ApplyPatchServerTool"
3099
+ },
3100
+ {
3101
+ "$ref": "#/components/schemas/CustomTool"
3102
+ }
3103
+ ]
3104
+ }
3105
+ },
3106
+ "tool_choice": {
3107
+ "$ref": "#/components/schemas/OpenAIResponsesToolChoice"
3108
+ },
3109
+ "parallel_tool_calls": {
3110
+ "type": "boolean"
3111
+ },
3112
+ "prompt": {
3113
+ "$ref": "#/components/schemas/StoredPromptTemplate"
3114
+ },
3115
+ "background": {
3116
+ "type": "boolean",
3117
+ "nullable": true
3118
+ },
3119
+ "previous_response_id": {
3120
+ "type": "string",
3121
+ "nullable": true
3122
+ },
3123
+ "reasoning": {
3124
+ "$ref": "#/components/schemas/BaseReasoningConfig"
3125
+ },
3126
+ "service_tier": {
3127
+ "$ref": "#/components/schemas/ServiceTierEnum"
3128
+ },
3129
+ "store": {
3130
+ "type": "boolean"
3131
+ },
3132
+ "truncation": {
3133
+ "$ref": "#/components/schemas/TruncationEnum"
3134
+ },
3135
+ "text": {
3136
+ "$ref": "#/components/schemas/TextConfig"
3137
+ }
3138
+ },
3139
+ "required": [
3140
+ "id",
3141
+ "object",
3142
+ "created_at",
3143
+ "model",
3144
+ "status",
3145
+ "completed_at",
3146
+ "output",
3147
+ "error",
3148
+ "incomplete_details",
3149
+ "temperature",
3150
+ "top_p",
3151
+ "presence_penalty",
3152
+ "frequency_penalty",
3153
+ "instructions",
3154
+ "metadata",
3155
+ "tools",
3156
+ "tool_choice",
3157
+ "parallel_tool_calls"
3158
+ ]
3159
+ },
3160
+ "OpenResponsesResult": {
3161
+ "allOf": [
3162
+ {
3163
+ "$ref": "#/components/schemas/BaseResponsesResult"
3164
+ },
3165
+ {
3166
+ "type": "object",
3167
+ "properties": {
3168
+ "output": {
3169
+ "type": "array",
3170
+ "items": {
3171
+ "$ref": "#/components/schemas/OutputItems"
3172
+ }
3173
+ },
3174
+ "usage": {
3175
+ "$ref": "#/components/schemas/Usage"
3176
+ },
3177
+ "service_tier": {
3178
+ "type": "string",
3179
+ "nullable": true
3180
+ }
3181
+ }
3182
+ }
3183
+ ],
3184
+ "description": "Complete non-streaming response from the Responses API",
3185
+ "example": {
3186
+ "id": "resp-abc123",
3187
+ "object": "response",
3188
+ "created_at": 1704067200,
3189
+ "model": "gpt-4",
3190
+ "status": "completed",
3191
+ "output": [
3192
+ {
3193
+ "type": "message",
3194
+ "id": "msg-abc123",
3195
+ "status": "completed",
3196
+ "role": "assistant",
3197
+ "content": [
3198
+ {
3199
+ "type": "output_text",
3200
+ "text": "Hello! How can I help you today?",
3201
+ "annotations": []
3202
+ }
3203
+ ]
3204
+ }
3205
+ ],
3206
+ "usage": {
3207
+ "input_tokens": 10,
3208
+ "output_tokens": 25,
3209
+ "total_tokens": 35,
3210
+ "input_tokens_details": {
3211
+ "cached_tokens": 0
3212
+ },
3213
+ "output_tokens_details": {
3214
+ "reasoning_tokens": 0
3215
+ }
3216
+ },
3217
+ "tools": [],
3218
+ "tool_choice": "auto",
3219
+ "parallel_tool_calls": true,
3220
+ "error": null,
3221
+ "incomplete_details": null,
3222
+ "temperature": null,
3223
+ "top_p": null,
3224
+ "max_output_tokens": null,
3225
+ "metadata": null,
3226
+ "instructions": null
3227
+ }
3228
+ },
3229
+ "CreatedEvent": {
3230
+ "type": "object",
3231
+ "properties": {
3232
+ "type": {
3233
+ "type": "string",
3234
+ "enum": [
3235
+ "response.created"
3236
+ ]
3237
+ },
3238
+ "response": {
3239
+ "$ref": "#/components/schemas/BaseResponsesResult"
3240
+ },
3241
+ "sequence_number": {
3242
+ "type": "integer"
3243
+ }
3244
+ },
3245
+ "required": [
3246
+ "type",
3247
+ "response",
3248
+ "sequence_number"
3249
+ ],
3250
+ "description": "Event emitted when a response is created",
3251
+ "example": {
3252
+ "type": "response.created",
3253
+ "response": {
3254
+ "id": "resp-abc123",
3255
+ "object": "response",
3256
+ "created_at": 1704067200,
3257
+ "model": "gpt-4",
3258
+ "status": "in_progress",
3259
+ "output": [],
3260
+ "tools": [],
3261
+ "tool_choice": "auto",
3262
+ "parallel_tool_calls": true,
3263
+ "error": null,
3264
+ "incomplete_details": null,
3265
+ "metadata": null,
3266
+ "instructions": null,
3267
+ "temperature": null,
3268
+ "top_p": null,
3269
+ "max_output_tokens": null
3270
+ },
3271
+ "sequence_number": 0
3272
+ }
3273
+ },
3274
+ "InProgressEvent": {
3275
+ "type": "object",
3276
+ "properties": {
3277
+ "type": {
3278
+ "type": "string",
3279
+ "enum": [
3280
+ "response.in_progress"
3281
+ ]
3282
+ },
3283
+ "response": {
3284
+ "$ref": "#/components/schemas/BaseResponsesResult"
3285
+ },
3286
+ "sequence_number": {
3287
+ "type": "integer"
3288
+ }
3289
+ },
3290
+ "required": [
3291
+ "type",
3292
+ "response",
3293
+ "sequence_number"
3294
+ ],
3295
+ "description": "Event emitted when a response is in progress",
3296
+ "example": {
3297
+ "type": "response.in_progress",
3298
+ "response": {
3299
+ "id": "resp-abc123",
3300
+ "object": "response",
3301
+ "created_at": 1704067200,
3302
+ "model": "gpt-4",
3303
+ "status": "in_progress",
3304
+ "output": [],
3305
+ "tools": [],
3306
+ "tool_choice": "auto",
3307
+ "parallel_tool_calls": true,
3308
+ "error": null,
3309
+ "incomplete_details": null,
3310
+ "metadata": null,
3311
+ "instructions": null,
3312
+ "temperature": null,
3313
+ "top_p": null,
3314
+ "max_output_tokens": null
3315
+ },
3316
+ "sequence_number": 1
3317
+ }
3318
+ },
3319
+ "CompletedEvent": {
3320
+ "type": "object",
3321
+ "properties": {
3322
+ "type": {
3323
+ "type": "string",
3324
+ "enum": [
3325
+ "response.completed"
3326
+ ]
3327
+ },
3328
+ "response": {
3329
+ "$ref": "#/components/schemas/BaseResponsesResult"
3330
+ },
3331
+ "sequence_number": {
3332
+ "type": "integer"
3333
+ }
3334
+ },
3335
+ "required": [
3336
+ "type",
3337
+ "response",
3338
+ "sequence_number"
3339
+ ],
3340
+ "description": "Event emitted when a response has completed successfully",
3341
+ "example": {
3342
+ "type": "response.completed",
3343
+ "response": {
3344
+ "id": "resp-abc123",
3345
+ "object": "response",
3346
+ "created_at": 1704067200,
3347
+ "model": "gpt-4",
3348
+ "status": "completed",
3349
+ "output": [
3350
+ {
3351
+ "id": "item-1",
3352
+ "type": "message",
3353
+ "status": "completed",
3354
+ "role": "assistant",
3355
+ "content": [
3356
+ {
3357
+ "type": "output_text",
3358
+ "text": "Hello! How can I help you?",
3359
+ "annotations": []
3360
+ }
3361
+ ]
3362
+ }
3363
+ ],
3364
+ "tools": [],
3365
+ "tool_choice": "auto",
3366
+ "parallel_tool_calls": true,
3367
+ "error": null,
3368
+ "incomplete_details": null,
3369
+ "metadata": null,
3370
+ "instructions": null,
3371
+ "temperature": null,
3372
+ "top_p": null,
3373
+ "max_output_tokens": null
3374
+ },
3375
+ "sequence_number": 10
3376
+ }
3377
+ },
3378
+ "IncompleteEvent": {
3379
+ "type": "object",
3380
+ "properties": {
3381
+ "type": {
3382
+ "type": "string",
3383
+ "enum": [
3384
+ "response.incomplete"
3385
+ ]
3386
+ },
3387
+ "response": {
3388
+ "$ref": "#/components/schemas/BaseResponsesResult"
3389
+ },
3390
+ "sequence_number": {
3391
+ "type": "integer"
3392
+ }
3393
+ },
3394
+ "required": [
3395
+ "type",
3396
+ "response",
3397
+ "sequence_number"
3398
+ ],
3399
+ "description": "Event emitted when a response is incomplete",
3400
+ "example": {
3401
+ "type": "response.incomplete",
3402
+ "response": {
3403
+ "id": "resp-abc123",
3404
+ "object": "response",
3405
+ "created_at": 1704067200,
3406
+ "model": "gpt-4",
3407
+ "status": "incomplete",
3408
+ "output": [],
3409
+ "tools": [],
3410
+ "tool_choice": "auto",
3411
+ "parallel_tool_calls": true,
3412
+ "error": null,
3413
+ "incomplete_details": null,
3414
+ "metadata": null,
3415
+ "instructions": null,
3416
+ "temperature": null,
3417
+ "top_p": null,
3418
+ "max_output_tokens": null
3419
+ },
3420
+ "sequence_number": 5
3421
+ }
3422
+ },
3423
+ "FailedEvent": {
3424
+ "type": "object",
3425
+ "properties": {
3426
+ "type": {
3427
+ "type": "string",
3428
+ "enum": [
3429
+ "response.failed"
3430
+ ]
3431
+ },
3432
+ "response": {
3433
+ "$ref": "#/components/schemas/BaseResponsesResult"
3434
+ },
3435
+ "sequence_number": {
3436
+ "type": "integer"
3437
+ }
3438
+ },
3439
+ "required": [
3440
+ "type",
3441
+ "response",
3442
+ "sequence_number"
3443
+ ],
3444
+ "description": "Event emitted when a response has failed",
3445
+ "example": {
3446
+ "type": "response.failed",
3447
+ "response": {
3448
+ "id": "resp-abc123",
3449
+ "object": "response",
3450
+ "created_at": 1704067200,
3451
+ "model": "gpt-4",
3452
+ "status": "failed",
3453
+ "output": [],
3454
+ "tools": [],
3455
+ "tool_choice": "auto",
3456
+ "parallel_tool_calls": true,
3457
+ "error": null,
3458
+ "incomplete_details": null,
3459
+ "metadata": null,
3460
+ "instructions": null,
3461
+ "temperature": null,
3462
+ "top_p": null,
3463
+ "max_output_tokens": null
3464
+ },
3465
+ "sequence_number": 3
3466
+ }
3467
+ },
3468
+ "BaseErrorEvent": {
3469
+ "type": "object",
3470
+ "properties": {
3471
+ "type": {
3472
+ "type": "string",
3473
+ "enum": [
3474
+ "error"
3475
+ ]
3476
+ },
3477
+ "code": {
3478
+ "type": "string",
3479
+ "nullable": true
3480
+ },
3481
+ "message": {
3482
+ "type": "string"
3483
+ },
3484
+ "param": {
3485
+ "type": "string",
3486
+ "nullable": true
3487
+ },
3488
+ "sequence_number": {
3489
+ "type": "integer"
3490
+ }
3491
+ },
3492
+ "required": [
3493
+ "type",
3494
+ "code",
3495
+ "message",
3496
+ "param",
3497
+ "sequence_number"
3498
+ ],
3499
+ "description": "Event emitted when an error occurs during streaming",
3500
+ "example": {
3501
+ "type": "error",
3502
+ "code": "rate_limit_exceeded",
3503
+ "message": "Rate limit exceeded. Please try again later.",
3504
+ "param": null,
3505
+ "sequence_number": 2
3506
+ }
3507
+ },
3508
+ "ErrorEvent": {
3509
+ "allOf": [
3510
+ {
3511
+ "$ref": "#/components/schemas/BaseErrorEvent"
3512
+ },
3513
+ {
3514
+ "type": "object",
3515
+ "properties": {}
3516
+ }
3517
+ ],
3518
+ "description": "Event emitted when an error occurs during streaming",
3519
+ "example": {
3520
+ "type": "error",
3521
+ "code": "rate_limit_exceeded",
3522
+ "message": "Rate limit exceeded. Please try again later.",
3523
+ "param": null,
3524
+ "sequence_number": 2
3525
+ }
3526
+ },
3527
+ "OutputItemAddedEvent": {
3528
+ "type": "object",
3529
+ "properties": {
3530
+ "type": {
3531
+ "type": "string",
3532
+ "enum": [
3533
+ "response.output_item.added"
3534
+ ]
3535
+ },
3536
+ "output_index": {
3537
+ "type": "integer"
3538
+ },
3539
+ "item": {
3540
+ "oneOf": [
3541
+ {
3542
+ "$ref": "#/components/schemas/OutputMessage"
3543
+ },
3544
+ {
3545
+ "$ref": "#/components/schemas/OutputItemReasoning"
3546
+ },
3547
+ {
3548
+ "$ref": "#/components/schemas/OutputItemFunctionCall"
3549
+ },
3550
+ {
3551
+ "$ref": "#/components/schemas/OutputItemWebSearchCall"
3552
+ },
3553
+ {
3554
+ "$ref": "#/components/schemas/OutputItemFileSearchCall"
3555
+ },
3556
+ {
3557
+ "$ref": "#/components/schemas/OutputItemImageGenerationCall"
3558
+ }
3559
+ ],
3560
+ "discriminator": {
3561
+ "propertyName": "type",
3562
+ "mapping": {
3563
+ "message": "#/components/schemas/OutputMessage",
3564
+ "reasoning": "#/components/schemas/OutputItemReasoning",
3565
+ "function_call": "#/components/schemas/OutputItemFunctionCall",
3566
+ "web_search_call": "#/components/schemas/OutputItemWebSearchCall",
3567
+ "file_search_call": "#/components/schemas/OutputItemFileSearchCall",
3568
+ "image_generation_call": "#/components/schemas/OutputItemImageGenerationCall"
3569
+ }
3570
+ }
3571
+ },
3572
+ "sequence_number": {
3573
+ "type": "integer"
3574
+ }
3575
+ },
3576
+ "required": [
3577
+ "type",
3578
+ "output_index",
3579
+ "item",
3580
+ "sequence_number"
3581
+ ],
3582
+ "description": "Event emitted when a new output item is added to the response",
3583
+ "example": {
3584
+ "type": "response.output_item.added",
3585
+ "output_index": 0,
3586
+ "item": {
3587
+ "id": "item-1",
3588
+ "type": "message",
3589
+ "status": "in_progress",
3590
+ "role": "assistant",
3591
+ "content": []
3592
+ },
3593
+ "sequence_number": 2
3594
+ }
3595
+ },
3596
+ "OutputItemDoneEvent": {
3597
+ "type": "object",
3598
+ "properties": {
3599
+ "type": {
3600
+ "type": "string",
3601
+ "enum": [
3602
+ "response.output_item.done"
3603
+ ]
3604
+ },
3605
+ "output_index": {
3606
+ "type": "integer"
3607
+ },
3608
+ "item": {
3609
+ "oneOf": [
3610
+ {
3611
+ "$ref": "#/components/schemas/OutputMessage"
3612
+ },
3613
+ {
3614
+ "$ref": "#/components/schemas/OutputItemReasoning"
3615
+ },
3616
+ {
3617
+ "$ref": "#/components/schemas/OutputItemFunctionCall"
3618
+ },
3619
+ {
3620
+ "$ref": "#/components/schemas/OutputItemWebSearchCall"
3621
+ },
3622
+ {
3623
+ "$ref": "#/components/schemas/OutputItemFileSearchCall"
3624
+ },
3625
+ {
3626
+ "$ref": "#/components/schemas/OutputItemImageGenerationCall"
3627
+ }
3628
+ ],
3629
+ "discriminator": {
3630
+ "propertyName": "type",
3631
+ "mapping": {
3632
+ "message": "#/components/schemas/OutputMessage",
3633
+ "reasoning": "#/components/schemas/OutputItemReasoning",
3634
+ "function_call": "#/components/schemas/OutputItemFunctionCall",
3635
+ "web_search_call": "#/components/schemas/OutputItemWebSearchCall",
3636
+ "file_search_call": "#/components/schemas/OutputItemFileSearchCall",
3637
+ "image_generation_call": "#/components/schemas/OutputItemImageGenerationCall"
3638
+ }
3639
+ }
3640
+ },
3641
+ "sequence_number": {
3642
+ "type": "integer"
3643
+ }
3644
+ },
3645
+ "required": [
3646
+ "type",
3647
+ "output_index",
3648
+ "item",
3649
+ "sequence_number"
3650
+ ],
3651
+ "description": "Event emitted when an output item is complete",
3652
+ "example": {
3653
+ "type": "response.output_item.done",
3654
+ "output_index": 0,
3655
+ "item": {
3656
+ "id": "item-1",
3657
+ "type": "message",
3658
+ "status": "completed",
3659
+ "role": "assistant",
3660
+ "content": [
3661
+ {
3662
+ "type": "output_text",
3663
+ "text": "Hello! How can I help you?",
3664
+ "annotations": []
3665
+ }
3666
+ ]
3667
+ },
3668
+ "sequence_number": 8
3669
+ }
3670
+ },
3671
+ "BaseContentPartAddedEvent": {
3672
+ "type": "object",
3673
+ "properties": {
3674
+ "type": {
3675
+ "type": "string",
3676
+ "enum": [
3677
+ "response.content_part.added"
3678
+ ]
3679
+ },
3680
+ "output_index": {
3681
+ "type": "integer"
3682
+ },
3683
+ "item_id": {
3684
+ "type": "string"
3685
+ },
3686
+ "content_index": {
3687
+ "type": "integer"
3688
+ },
3689
+ "part": {
3690
+ "anyOf": [
3691
+ {
3692
+ "$ref": "#/components/schemas/ResponseOutputText"
3693
+ },
3694
+ {
3695
+ "$ref": "#/components/schemas/OpenAIResponsesRefusalContent"
3696
+ }
3697
+ ]
3698
+ },
3699
+ "sequence_number": {
3700
+ "type": "integer"
3701
+ }
3702
+ },
3703
+ "required": [
3704
+ "type",
3705
+ "output_index",
3706
+ "item_id",
3707
+ "content_index",
3708
+ "part",
3709
+ "sequence_number"
3710
+ ],
3711
+ "description": "Event emitted when a new content part is added to an output item",
3712
+ "example": {
3713
+ "type": "response.content_part.added",
3714
+ "output_index": 0,
3715
+ "item_id": "item-1",
3716
+ "content_index": 0,
3717
+ "part": {
3718
+ "type": "output_text",
3719
+ "text": "",
3720
+ "annotations": []
3721
+ },
3722
+ "sequence_number": 3
3723
+ }
3724
+ },
3725
+ "ContentPartAddedEvent": {
3726
+ "allOf": [
3727
+ {
3728
+ "$ref": "#/components/schemas/BaseContentPartAddedEvent"
3729
+ },
3730
+ {
3731
+ "type": "object",
3732
+ "properties": {
3733
+ "part": {
3734
+ "anyOf": [
3735
+ {
3736
+ "$ref": "#/components/schemas/ResponseOutputText"
3737
+ },
3738
+ {
3739
+ "$ref": "#/components/schemas/ReasoningTextContent"
3740
+ },
3741
+ {
3742
+ "$ref": "#/components/schemas/OpenAIResponsesRefusalContent"
3743
+ }
3744
+ ]
3745
+ }
3746
+ }
3747
+ }
3748
+ ],
3749
+ "description": "Event emitted when a new content part is added to an output item",
3750
+ "example": {
3751
+ "type": "response.content_part.added",
3752
+ "output_index": 0,
3753
+ "item_id": "item-1",
3754
+ "content_index": 0,
3755
+ "part": {
3756
+ "type": "output_text",
3757
+ "text": "",
3758
+ "annotations": []
3759
+ },
3760
+ "sequence_number": 3
3761
+ }
3762
+ },
3763
+ "BaseContentPartDoneEvent": {
3764
+ "type": "object",
3765
+ "properties": {
3766
+ "type": {
3767
+ "type": "string",
3768
+ "enum": [
3769
+ "response.content_part.done"
3770
+ ]
3771
+ },
3772
+ "output_index": {
3773
+ "type": "integer"
3774
+ },
3775
+ "item_id": {
3776
+ "type": "string"
3777
+ },
3778
+ "content_index": {
3779
+ "type": "integer"
3780
+ },
3781
+ "part": {
3782
+ "anyOf": [
3783
+ {
3784
+ "$ref": "#/components/schemas/ResponseOutputText"
3785
+ },
3786
+ {
3787
+ "$ref": "#/components/schemas/OpenAIResponsesRefusalContent"
3788
+ }
3789
+ ]
3790
+ },
3791
+ "sequence_number": {
3792
+ "type": "integer"
3793
+ }
3794
+ },
3795
+ "required": [
3796
+ "type",
3797
+ "output_index",
3798
+ "item_id",
3799
+ "content_index",
3800
+ "part",
3801
+ "sequence_number"
3802
+ ],
3803
+ "description": "Event emitted when a content part is complete",
3804
+ "example": {
3805
+ "type": "response.content_part.done",
3806
+ "output_index": 0,
3807
+ "item_id": "item-1",
3808
+ "content_index": 0,
3809
+ "part": {
3810
+ "type": "output_text",
3811
+ "text": "Hello! How can I help you?",
3812
+ "annotations": []
3813
+ },
3814
+ "sequence_number": 7
3815
+ }
3816
+ },
3817
+ "ContentPartDoneEvent": {
3818
+ "allOf": [
3819
+ {
3820
+ "$ref": "#/components/schemas/BaseContentPartDoneEvent"
3821
+ },
3822
+ {
3823
+ "type": "object",
3824
+ "properties": {
3825
+ "part": {
3826
+ "anyOf": [
3827
+ {
3828
+ "$ref": "#/components/schemas/ResponseOutputText"
3829
+ },
3830
+ {
3831
+ "$ref": "#/components/schemas/ReasoningTextContent"
3832
+ },
3833
+ {
3834
+ "$ref": "#/components/schemas/OpenAIResponsesRefusalContent"
3835
+ }
3836
+ ]
3837
+ }
3838
+ }
3839
+ }
3840
+ ],
3841
+ "description": "Event emitted when a content part is complete",
3842
+ "example": {
3843
+ "type": "response.content_part.done",
3844
+ "output_index": 0,
3845
+ "item_id": "item-1",
3846
+ "content_index": 0,
3847
+ "part": {
3848
+ "type": "output_text",
3849
+ "text": "Hello! How can I help you?",
3850
+ "annotations": []
3851
+ },
3852
+ "sequence_number": 7
3853
+ }
3854
+ },
3855
+ "OpenResponsesTopLogprobs": {
3856
+ "type": "object",
3857
+ "properties": {
3858
+ "token": {
3859
+ "type": "string"
3860
+ },
3861
+ "logprob": {
3862
+ "type": "number"
3863
+ },
3864
+ "bytes": {
3865
+ "type": "array",
3866
+ "items": {
3867
+ "type": "number"
3868
+ }
3869
+ }
3870
+ },
3871
+ "description": "Alternative token with its log probability",
3872
+ "example": {
3873
+ "token": "hello",
3874
+ "logprob": -0.5
3875
+ }
3876
+ },
3877
+ "OpenResponsesLogProbs": {
3878
+ "type": "object",
3879
+ "properties": {
3880
+ "logprob": {
3881
+ "type": "number"
3882
+ },
3883
+ "token": {
3884
+ "type": "string"
3885
+ },
3886
+ "top_logprobs": {
3887
+ "type": "array",
3888
+ "items": {
3889
+ "$ref": "#/components/schemas/OpenResponsesTopLogprobs"
3890
+ }
3891
+ },
3892
+ "bytes": {
3893
+ "type": "array",
3894
+ "items": {
3895
+ "type": "number"
3896
+ }
3897
+ }
3898
+ },
3899
+ "required": [
3900
+ "logprob",
3901
+ "token"
3902
+ ],
3903
+ "description": "Log probability information for a token",
3904
+ "example": {
3905
+ "logprob": -0.1,
3906
+ "token": "world",
3907
+ "top_logprobs": [
3908
+ {
3909
+ "token": "hello",
3910
+ "logprob": -0.5
3911
+ }
3912
+ ]
3913
+ }
3914
+ },
3915
+ "BaseTextDeltaEvent": {
3916
+ "type": "object",
3917
+ "properties": {
3918
+ "type": {
3919
+ "type": "string",
3920
+ "enum": [
3921
+ "response.output_text.delta"
3922
+ ]
3923
+ },
3924
+ "logprobs": {
3925
+ "type": "array",
3926
+ "items": {
3927
+ "$ref": "#/components/schemas/OpenResponsesLogProbs"
3928
+ }
3929
+ },
3930
+ "output_index": {
3931
+ "type": "integer"
3932
+ },
3933
+ "item_id": {
3934
+ "type": "string"
3935
+ },
3936
+ "content_index": {
3937
+ "type": "integer"
3938
+ },
3939
+ "delta": {
3940
+ "type": "string"
3941
+ },
3942
+ "sequence_number": {
3943
+ "type": "integer"
3944
+ }
3945
+ },
3946
+ "required": [
3947
+ "type",
3948
+ "logprobs",
3949
+ "output_index",
3950
+ "item_id",
3951
+ "content_index",
3952
+ "delta",
3953
+ "sequence_number"
3954
+ ],
3955
+ "description": "Event emitted when a text delta is streamed",
3956
+ "example": {
3957
+ "type": "response.output_text.delta",
3958
+ "logprobs": [],
3959
+ "output_index": 0,
3960
+ "item_id": "item-1",
3961
+ "content_index": 0,
3962
+ "delta": "Hello",
3963
+ "sequence_number": 4
3964
+ }
3965
+ },
3966
+ "TextDeltaEvent": {
3967
+ "allOf": [
3968
+ {
3969
+ "$ref": "#/components/schemas/BaseTextDeltaEvent"
3970
+ },
3971
+ {
3972
+ "type": "object",
3973
+ "properties": {
3974
+ "logprobs": {
3975
+ "type": "array",
3976
+ "items": {
3977
+ "allOf": [
3978
+ {
3979
+ "$ref": "#/components/schemas/OpenResponsesLogProbs"
3980
+ },
3981
+ {
3982
+ "type": "object",
3983
+ "properties": {
3984
+ "top_logprobs": {
3985
+ "type": "array",
3986
+ "items": {
3987
+ "allOf": [
3988
+ {
3989
+ "$ref": "#/components/schemas/OpenResponsesTopLogprobs"
3990
+ },
3991
+ {
3992
+ "type": "object",
3993
+ "properties": {
3994
+ "bytes": {
3995
+ "type": "array",
3996
+ "items": {
3997
+ "type": "integer"
3998
+ }
3999
+ }
4000
+ }
4001
+ }
4002
+ ],
4003
+ "description": "Alternative token with its log probability",
4004
+ "example": {
4005
+ "token": "hello",
4006
+ "logprob": -0.5
4007
+ }
4008
+ }
4009
+ },
4010
+ "bytes": {
4011
+ "type": "array",
4012
+ "items": {
4013
+ "type": "integer"
4014
+ }
4015
+ }
4016
+ }
4017
+ }
4018
+ ],
4019
+ "description": "Log probability information for a token",
4020
+ "example": {
4021
+ "logprob": -0.1,
4022
+ "token": "world",
4023
+ "top_logprobs": [
4024
+ {
4025
+ "token": "hello",
4026
+ "logprob": -0.5
4027
+ }
4028
+ ]
4029
+ }
4030
+ }
4031
+ }
4032
+ }
4033
+ }
4034
+ ],
4035
+ "description": "Event emitted when a text delta is streamed",
4036
+ "example": {
4037
+ "type": "response.output_text.delta",
4038
+ "logprobs": [],
4039
+ "output_index": 0,
4040
+ "item_id": "item-1",
4041
+ "content_index": 0,
4042
+ "delta": "Hello",
4043
+ "sequence_number": 4
4044
+ }
4045
+ },
4046
+ "BaseTextDoneEvent": {
4047
+ "type": "object",
4048
+ "properties": {
4049
+ "type": {
4050
+ "type": "string",
4051
+ "enum": [
4052
+ "response.output_text.done"
4053
+ ]
4054
+ },
4055
+ "output_index": {
4056
+ "type": "integer"
4057
+ },
4058
+ "item_id": {
4059
+ "type": "string"
4060
+ },
4061
+ "content_index": {
4062
+ "type": "integer"
4063
+ },
4064
+ "text": {
4065
+ "type": "string"
4066
+ },
4067
+ "sequence_number": {
4068
+ "type": "integer"
4069
+ },
4070
+ "logprobs": {
4071
+ "type": "array",
4072
+ "items": {
4073
+ "$ref": "#/components/schemas/OpenResponsesLogProbs"
4074
+ }
4075
+ }
4076
+ },
4077
+ "required": [
4078
+ "type",
4079
+ "output_index",
4080
+ "item_id",
4081
+ "content_index",
4082
+ "text",
4083
+ "sequence_number",
4084
+ "logprobs"
4085
+ ],
4086
+ "description": "Event emitted when text streaming is complete",
4087
+ "example": {
4088
+ "type": "response.output_text.done",
4089
+ "output_index": 0,
4090
+ "item_id": "item-1",
4091
+ "content_index": 0,
4092
+ "text": "Hello! How can I help you?",
4093
+ "sequence_number": 6,
4094
+ "logprobs": []
4095
+ }
4096
+ },
4097
+ "TextDoneEvent": {
4098
+ "allOf": [
4099
+ {
4100
+ "$ref": "#/components/schemas/BaseTextDoneEvent"
4101
+ },
4102
+ {
4103
+ "type": "object",
4104
+ "properties": {
4105
+ "logprobs": {
4106
+ "type": "array",
4107
+ "items": {
4108
+ "allOf": [
4109
+ {
4110
+ "$ref": "#/components/schemas/OpenResponsesLogProbs"
4111
+ },
4112
+ {
4113
+ "type": "object",
4114
+ "properties": {
4115
+ "top_logprobs": {
4116
+ "type": "array",
4117
+ "items": {
4118
+ "allOf": [
4119
+ {
4120
+ "$ref": "#/components/schemas/OpenResponsesTopLogprobs"
4121
+ },
4122
+ {
4123
+ "type": "object",
4124
+ "properties": {
4125
+ "bytes": {
4126
+ "type": "array",
4127
+ "items": {
4128
+ "type": "integer"
4129
+ }
4130
+ }
4131
+ }
4132
+ }
4133
+ ],
4134
+ "description": "Alternative token with its log probability",
4135
+ "example": {
4136
+ "token": "hello",
4137
+ "logprob": -0.5
4138
+ }
4139
+ }
4140
+ },
4141
+ "bytes": {
4142
+ "type": "array",
4143
+ "items": {
4144
+ "type": "integer"
4145
+ }
4146
+ }
4147
+ }
4148
+ }
4149
+ ],
4150
+ "description": "Log probability information for a token",
4151
+ "example": {
4152
+ "logprob": -0.1,
4153
+ "token": "world",
4154
+ "top_logprobs": [
4155
+ {
4156
+ "token": "hello",
4157
+ "logprob": -0.5
4158
+ }
4159
+ ]
4160
+ }
4161
+ }
4162
+ }
4163
+ }
4164
+ }
4165
+ ],
4166
+ "description": "Event emitted when text streaming is complete",
4167
+ "example": {
4168
+ "type": "response.output_text.done",
4169
+ "output_index": 0,
4170
+ "item_id": "item-1",
4171
+ "content_index": 0,
4172
+ "text": "Hello! How can I help you?",
4173
+ "sequence_number": 6,
4174
+ "logprobs": []
4175
+ }
4176
+ },
4177
+ "BaseRefusalDeltaEvent": {
4178
+ "type": "object",
4179
+ "properties": {
4180
+ "type": {
4181
+ "type": "string",
4182
+ "enum": [
4183
+ "response.refusal.delta"
4184
+ ]
4185
+ },
4186
+ "output_index": {
4187
+ "type": "integer"
4188
+ },
4189
+ "item_id": {
4190
+ "type": "string"
4191
+ },
4192
+ "content_index": {
4193
+ "type": "integer"
4194
+ },
4195
+ "delta": {
4196
+ "type": "string"
4197
+ },
4198
+ "sequence_number": {
4199
+ "type": "integer"
4200
+ }
4201
+ },
4202
+ "required": [
4203
+ "type",
4204
+ "output_index",
4205
+ "item_id",
4206
+ "content_index",
4207
+ "delta",
4208
+ "sequence_number"
4209
+ ],
4210
+ "description": "Event emitted when a refusal delta is streamed",
4211
+ "example": {
4212
+ "type": "response.refusal.delta",
4213
+ "output_index": 0,
4214
+ "item_id": "item-1",
4215
+ "content_index": 0,
4216
+ "delta": "I'm sorry",
4217
+ "sequence_number": 4
4218
+ }
4219
+ },
4220
+ "RefusalDeltaEvent": {
4221
+ "allOf": [
4222
+ {
4223
+ "$ref": "#/components/schemas/BaseRefusalDeltaEvent"
4224
+ },
4225
+ {
4226
+ "type": "object",
4227
+ "properties": {}
4228
+ }
4229
+ ],
4230
+ "description": "Event emitted when a refusal delta is streamed",
4231
+ "example": {
4232
+ "type": "response.refusal.delta",
4233
+ "output_index": 0,
4234
+ "item_id": "item-1",
4235
+ "content_index": 0,
4236
+ "delta": "I'm sorry",
4237
+ "sequence_number": 4
4238
+ }
4239
+ },
4240
+ "BaseRefusalDoneEvent": {
4241
+ "type": "object",
4242
+ "properties": {
4243
+ "type": {
4244
+ "type": "string",
4245
+ "enum": [
4246
+ "response.refusal.done"
4247
+ ]
4248
+ },
4249
+ "output_index": {
4250
+ "type": "integer"
4251
+ },
4252
+ "item_id": {
4253
+ "type": "string"
4254
+ },
4255
+ "content_index": {
4256
+ "type": "integer"
4257
+ },
4258
+ "refusal": {
4259
+ "type": "string"
4260
+ },
4261
+ "sequence_number": {
4262
+ "type": "integer"
4263
+ }
4264
+ },
4265
+ "required": [
4266
+ "type",
4267
+ "output_index",
4268
+ "item_id",
4269
+ "content_index",
4270
+ "refusal",
4271
+ "sequence_number"
4272
+ ],
4273
+ "description": "Event emitted when refusal streaming is complete",
4274
+ "example": {
4275
+ "type": "response.refusal.done",
4276
+ "output_index": 0,
4277
+ "item_id": "item-1",
4278
+ "content_index": 0,
4279
+ "refusal": "I'm sorry, but I can't assist with that request.",
4280
+ "sequence_number": 6
4281
+ }
4282
+ },
4283
+ "RefusalDoneEvent": {
4284
+ "allOf": [
4285
+ {
4286
+ "$ref": "#/components/schemas/BaseRefusalDoneEvent"
4287
+ },
4288
+ {
4289
+ "type": "object",
4290
+ "properties": {}
4291
+ }
4292
+ ],
4293
+ "description": "Event emitted when refusal streaming is complete",
4294
+ "example": {
4295
+ "type": "response.refusal.done",
4296
+ "output_index": 0,
4297
+ "item_id": "item-1",
4298
+ "content_index": 0,
4299
+ "refusal": "I'm sorry, but I can't assist with that request.",
4300
+ "sequence_number": 6
4301
+ }
4302
+ },
4303
+ "BaseAnnotationAddedEvent": {
4304
+ "type": "object",
4305
+ "properties": {
4306
+ "type": {
4307
+ "type": "string",
4308
+ "enum": [
4309
+ "response.output_text.annotation.added"
4310
+ ]
4311
+ },
4312
+ "output_index": {
4313
+ "type": "integer"
4314
+ },
4315
+ "item_id": {
4316
+ "type": "string"
4317
+ },
4318
+ "content_index": {
4319
+ "type": "integer"
4320
+ },
4321
+ "sequence_number": {
4322
+ "type": "integer"
4323
+ },
4324
+ "annotation_index": {
4325
+ "type": "integer"
4326
+ },
4327
+ "annotation": {
4328
+ "$ref": "#/components/schemas/OpenAIResponsesAnnotation"
4329
+ }
4330
+ },
4331
+ "required": [
4332
+ "type",
4333
+ "output_index",
4334
+ "item_id",
4335
+ "content_index",
4336
+ "sequence_number",
4337
+ "annotation_index",
4338
+ "annotation"
4339
+ ],
4340
+ "description": "Event emitted when a text annotation is added to output",
4341
+ "example": {
4342
+ "type": "response.output_text.annotation.added",
4343
+ "output_index": 0,
4344
+ "item_id": "item-1",
4345
+ "content_index": 0,
4346
+ "sequence_number": 5,
4347
+ "annotation_index": 0,
4348
+ "annotation": {
4349
+ "type": "url_citation",
4350
+ "url": "https://example.com",
4351
+ "title": "Example",
4352
+ "start_index": 0,
4353
+ "end_index": 7
4354
+ }
4355
+ }
4356
+ },
4357
+ "AnnotationAddedEvent": {
4358
+ "allOf": [
4359
+ {
4360
+ "$ref": "#/components/schemas/BaseAnnotationAddedEvent"
4361
+ },
4362
+ {
4363
+ "type": "object",
4364
+ "properties": {}
4365
+ }
4366
+ ],
4367
+ "description": "Event emitted when a text annotation is added to output",
4368
+ "example": {
4369
+ "type": "response.output_text.annotation.added",
4370
+ "output_index": 0,
4371
+ "item_id": "item-1",
4372
+ "content_index": 0,
4373
+ "sequence_number": 5,
4374
+ "annotation_index": 0,
4375
+ "annotation": {
4376
+ "type": "url_citation",
4377
+ "url": "https://example.com",
4378
+ "title": "Example",
4379
+ "start_index": 0,
4380
+ "end_index": 7
4381
+ }
4382
+ }
4383
+ },
4384
+ "BaseFunctionCallArgsDeltaEvent": {
4385
+ "type": "object",
4386
+ "properties": {
4387
+ "type": {
4388
+ "type": "string",
4389
+ "enum": [
4390
+ "response.function_call_arguments.delta"
4391
+ ]
4392
+ },
4393
+ "item_id": {
4394
+ "type": "string"
4395
+ },
4396
+ "output_index": {
4397
+ "type": "integer"
4398
+ },
4399
+ "delta": {
4400
+ "type": "string"
4401
+ },
4402
+ "sequence_number": {
4403
+ "type": "integer"
4404
+ }
4405
+ },
4406
+ "required": [
4407
+ "type",
4408
+ "item_id",
4409
+ "output_index",
4410
+ "delta",
4411
+ "sequence_number"
4412
+ ],
4413
+ "description": "Event emitted when function call arguments are being streamed",
4414
+ "example": {
4415
+ "type": "response.function_call_arguments.delta",
4416
+ "item_id": "item-1",
4417
+ "output_index": 0,
4418
+ "delta": "{\"city\": \"San",
4419
+ "sequence_number": 4
4420
+ }
4421
+ },
4422
+ "FunctionCallArgsDeltaEvent": {
4423
+ "allOf": [
4424
+ {
4425
+ "$ref": "#/components/schemas/BaseFunctionCallArgsDeltaEvent"
4426
+ },
4427
+ {
4428
+ "type": "object",
4429
+ "properties": {}
4430
+ }
4431
+ ],
4432
+ "description": "Event emitted when function call arguments are being streamed",
4433
+ "example": {
4434
+ "type": "response.function_call_arguments.delta",
4435
+ "item_id": "item-1",
4436
+ "output_index": 0,
4437
+ "delta": "{\"city\": \"San",
4438
+ "sequence_number": 4
4439
+ }
4440
+ },
4441
+ "BaseFunctionCallArgsDoneEvent": {
4442
+ "type": "object",
4443
+ "properties": {
4444
+ "type": {
4445
+ "type": "string",
4446
+ "enum": [
4447
+ "response.function_call_arguments.done"
4448
+ ]
4449
+ },
4450
+ "item_id": {
4451
+ "type": "string"
4452
+ },
4453
+ "output_index": {
4454
+ "type": "integer"
4455
+ },
4456
+ "name": {
4457
+ "type": "string"
4458
+ },
4459
+ "arguments": {
4460
+ "type": "string"
4461
+ },
4462
+ "sequence_number": {
4463
+ "type": "integer"
4464
+ }
4465
+ },
4466
+ "required": [
4467
+ "type",
4468
+ "item_id",
4469
+ "output_index",
4470
+ "name",
4471
+ "arguments",
4472
+ "sequence_number"
4473
+ ],
4474
+ "description": "Event emitted when function call arguments streaming is complete",
4475
+ "example": {
4476
+ "type": "response.function_call_arguments.done",
4477
+ "item_id": "item-1",
4478
+ "output_index": 0,
4479
+ "name": "get_weather",
4480
+ "arguments": "{\"city\": \"San Francisco\", \"units\": \"celsius\"}",
4481
+ "sequence_number": 6
4482
+ }
4483
+ },
4484
+ "FunctionCallArgsDoneEvent": {
4485
+ "allOf": [
4486
+ {
4487
+ "$ref": "#/components/schemas/BaseFunctionCallArgsDoneEvent"
4488
+ },
4489
+ {
4490
+ "type": "object",
4491
+ "properties": {}
4492
+ }
4493
+ ],
4494
+ "description": "Event emitted when function call arguments streaming is complete",
4495
+ "example": {
4496
+ "type": "response.function_call_arguments.done",
4497
+ "item_id": "item-1",
4498
+ "output_index": 0,
4499
+ "name": "get_weather",
4500
+ "arguments": "{\"city\": \"San Francisco\", \"units\": \"celsius\"}",
4501
+ "sequence_number": 6
4502
+ }
4503
+ },
4504
+ "BaseReasoningDeltaEvent": {
4505
+ "type": "object",
4506
+ "properties": {
4507
+ "type": {
4508
+ "type": "string",
4509
+ "enum": [
4510
+ "response.reasoning_text.delta"
4511
+ ]
4512
+ },
4513
+ "output_index": {
4514
+ "type": "integer"
4515
+ },
4516
+ "item_id": {
4517
+ "type": "string"
4518
+ },
4519
+ "content_index": {
4520
+ "type": "integer"
4521
+ },
4522
+ "delta": {
4523
+ "type": "string"
4524
+ },
4525
+ "sequence_number": {
4526
+ "type": "integer"
4527
+ }
4528
+ },
4529
+ "required": [
4530
+ "type",
4531
+ "output_index",
4532
+ "item_id",
4533
+ "content_index",
4534
+ "delta",
4535
+ "sequence_number"
4536
+ ],
4537
+ "description": "Event emitted when reasoning text delta is streamed",
4538
+ "example": {
4539
+ "type": "response.reasoning_text.delta",
4540
+ "output_index": 0,
4541
+ "item_id": "item-1",
4542
+ "content_index": 0,
4543
+ "delta": "First, we need",
4544
+ "sequence_number": 4
4545
+ }
4546
+ },
4547
+ "ReasoningDeltaEvent": {
4548
+ "allOf": [
4549
+ {
4550
+ "$ref": "#/components/schemas/BaseReasoningDeltaEvent"
4551
+ },
4552
+ {
4553
+ "type": "object",
4554
+ "properties": {}
4555
+ }
4556
+ ],
4557
+ "description": "Event emitted when reasoning text delta is streamed",
4558
+ "example": {
4559
+ "type": "response.reasoning_text.delta",
4560
+ "output_index": 0,
4561
+ "item_id": "item-1",
4562
+ "content_index": 0,
4563
+ "delta": "First, we need",
4564
+ "sequence_number": 4
4565
+ }
4566
+ },
4567
+ "BaseReasoningDoneEvent": {
4568
+ "type": "object",
4569
+ "properties": {
4570
+ "type": {
4571
+ "type": "string",
4572
+ "enum": [
4573
+ "response.reasoning_text.done"
4574
+ ]
4575
+ },
4576
+ "output_index": {
4577
+ "type": "integer"
4578
+ },
4579
+ "item_id": {
4580
+ "type": "string"
4581
+ },
4582
+ "content_index": {
4583
+ "type": "integer"
4584
+ },
4585
+ "text": {
4586
+ "type": "string"
4587
+ },
4588
+ "sequence_number": {
4589
+ "type": "integer"
4590
+ }
4591
+ },
4592
+ "required": [
4593
+ "type",
4594
+ "output_index",
4595
+ "item_id",
4596
+ "content_index",
4597
+ "text",
4598
+ "sequence_number"
4599
+ ],
4600
+ "description": "Event emitted when reasoning text streaming is complete",
4601
+ "example": {
4602
+ "type": "response.reasoning_text.done",
4603
+ "output_index": 0,
4604
+ "item_id": "item-1",
4605
+ "content_index": 0,
4606
+ "text": "First, we need to identify the key components and then combine them logically.",
4607
+ "sequence_number": 6
4608
+ }
4609
+ },
4610
+ "ReasoningDoneEvent": {
4611
+ "allOf": [
4612
+ {
4613
+ "$ref": "#/components/schemas/BaseReasoningDoneEvent"
4614
+ },
4615
+ {
4616
+ "type": "object",
4617
+ "properties": {}
4618
+ }
4619
+ ],
4620
+ "description": "Event emitted when reasoning text streaming is complete",
4621
+ "example": {
4622
+ "type": "response.reasoning_text.done",
4623
+ "output_index": 0,
4624
+ "item_id": "item-1",
4625
+ "content_index": 0,
4626
+ "text": "First, we need to identify the key components and then combine them logically.",
4627
+ "sequence_number": 6
4628
+ }
4629
+ },
4630
+ "BaseReasoningSummaryPartAddedEvent": {
4631
+ "type": "object",
4632
+ "properties": {
4633
+ "type": {
4634
+ "type": "string",
4635
+ "enum": [
4636
+ "response.reasoning_summary_part.added"
4637
+ ]
4638
+ },
4639
+ "output_index": {
4640
+ "type": "integer"
4641
+ },
4642
+ "item_id": {
4643
+ "type": "string"
4644
+ },
4645
+ "summary_index": {
4646
+ "type": "integer"
4647
+ },
4648
+ "part": {
4649
+ "$ref": "#/components/schemas/ReasoningSummaryText"
4650
+ },
4651
+ "sequence_number": {
4652
+ "type": "integer"
4653
+ }
4654
+ },
4655
+ "required": [
4656
+ "type",
4657
+ "output_index",
4658
+ "item_id",
4659
+ "summary_index",
4660
+ "part",
4661
+ "sequence_number"
4662
+ ],
4663
+ "description": "Event emitted when a reasoning summary part is added",
4664
+ "example": {
4665
+ "type": "response.reasoning_summary_part.added",
4666
+ "output_index": 0,
4667
+ "item_id": "item-1",
4668
+ "summary_index": 0,
4669
+ "part": {
4670
+ "type": "summary_text",
4671
+ "text": ""
4672
+ },
4673
+ "sequence_number": 3
4674
+ }
4675
+ },
4676
+ "ReasoningSummaryPartAddedEvent": {
4677
+ "allOf": [
4678
+ {
4679
+ "$ref": "#/components/schemas/BaseReasoningSummaryPartAddedEvent"
4680
+ },
4681
+ {
4682
+ "type": "object",
4683
+ "properties": {}
4684
+ }
4685
+ ],
4686
+ "description": "Event emitted when a reasoning summary part is added",
4687
+ "example": {
4688
+ "type": "response.reasoning_summary_part.added",
4689
+ "output_index": 0,
4690
+ "item_id": "item-1",
4691
+ "summary_index": 0,
4692
+ "part": {
4693
+ "type": "summary_text",
4694
+ "text": ""
4695
+ },
4696
+ "sequence_number": 3
4697
+ }
4698
+ },
4699
+ "BaseReasoningSummaryPartDoneEvent": {
4700
+ "type": "object",
4701
+ "properties": {
4702
+ "type": {
4703
+ "type": "string",
4704
+ "enum": [
4705
+ "response.reasoning_summary_part.done"
4706
+ ]
4707
+ },
4708
+ "output_index": {
4709
+ "type": "integer"
4710
+ },
4711
+ "item_id": {
4712
+ "type": "string"
4713
+ },
4714
+ "summary_index": {
4715
+ "type": "integer"
4716
+ },
4717
+ "part": {
4718
+ "$ref": "#/components/schemas/ReasoningSummaryText"
4719
+ },
4720
+ "sequence_number": {
4721
+ "type": "integer"
4722
+ }
4723
+ },
4724
+ "required": [
4725
+ "type",
4726
+ "output_index",
4727
+ "item_id",
4728
+ "summary_index",
4729
+ "part",
4730
+ "sequence_number"
4731
+ ],
4732
+ "description": "Event emitted when a reasoning summary part is complete",
4733
+ "example": {
4734
+ "type": "response.reasoning_summary_part.done",
4735
+ "output_index": 0,
4736
+ "item_id": "item-1",
4737
+ "summary_index": 0,
4738
+ "part": {
4739
+ "type": "summary_text",
4740
+ "text": "Analyzing the problem step by step to find the optimal solution."
4741
+ },
4742
+ "sequence_number": 7
4743
+ }
4744
+ },
4745
+ "ReasoningSummaryPartDoneEvent": {
4746
+ "allOf": [
4747
+ {
4748
+ "$ref": "#/components/schemas/BaseReasoningSummaryPartDoneEvent"
4749
+ },
4750
+ {
4751
+ "type": "object",
4752
+ "properties": {}
4753
+ }
4754
+ ],
4755
+ "description": "Event emitted when a reasoning summary part is complete",
4756
+ "example": {
4757
+ "type": "response.reasoning_summary_part.done",
4758
+ "output_index": 0,
4759
+ "item_id": "item-1",
4760
+ "summary_index": 0,
4761
+ "part": {
4762
+ "type": "summary_text",
4763
+ "text": "Analyzing the problem step by step to find the optimal solution."
4764
+ },
4765
+ "sequence_number": 7
4766
+ }
4767
+ },
4768
+ "BaseReasoningSummaryTextDeltaEvent": {
4769
+ "type": "object",
4770
+ "properties": {
4771
+ "type": {
4772
+ "type": "string",
4773
+ "enum": [
4774
+ "response.reasoning_summary_text.delta"
4775
+ ]
4776
+ },
4777
+ "item_id": {
4778
+ "type": "string"
4779
+ },
4780
+ "output_index": {
4781
+ "type": "integer"
4782
+ },
4783
+ "summary_index": {
4784
+ "type": "integer"
4785
+ },
4786
+ "delta": {
4787
+ "type": "string"
4788
+ },
4789
+ "sequence_number": {
4790
+ "type": "integer"
4791
+ }
4792
+ },
4793
+ "required": [
4794
+ "type",
4795
+ "item_id",
4796
+ "output_index",
4797
+ "summary_index",
4798
+ "delta",
4799
+ "sequence_number"
4800
+ ],
4801
+ "description": "Event emitted when reasoning summary text delta is streamed",
4802
+ "example": {
4803
+ "type": "response.reasoning_summary_text.delta",
4804
+ "item_id": "item-1",
4805
+ "output_index": 0,
4806
+ "summary_index": 0,
4807
+ "delta": "Analyzing",
4808
+ "sequence_number": 4
4809
+ }
4810
+ },
4811
+ "ReasoningSummaryTextDeltaEvent": {
4812
+ "allOf": [
4813
+ {
4814
+ "$ref": "#/components/schemas/BaseReasoningSummaryTextDeltaEvent"
4815
+ },
4816
+ {
4817
+ "type": "object",
4818
+ "properties": {}
4819
+ }
4820
+ ],
4821
+ "description": "Event emitted when reasoning summary text delta is streamed",
4822
+ "example": {
4823
+ "type": "response.reasoning_summary_text.delta",
4824
+ "item_id": "item-1",
4825
+ "output_index": 0,
4826
+ "summary_index": 0,
4827
+ "delta": "Analyzing",
4828
+ "sequence_number": 4
4829
+ }
4830
+ },
4831
+ "BaseReasoningSummaryTextDoneEvent": {
4832
+ "type": "object",
4833
+ "properties": {
4834
+ "type": {
4835
+ "type": "string",
4836
+ "enum": [
4837
+ "response.reasoning_summary_text.done"
4838
+ ]
4839
+ },
4840
+ "item_id": {
4841
+ "type": "string"
4842
+ },
4843
+ "output_index": {
4844
+ "type": "integer"
4845
+ },
4846
+ "summary_index": {
4847
+ "type": "integer"
4848
+ },
4849
+ "text": {
4850
+ "type": "string"
4851
+ },
4852
+ "sequence_number": {
4853
+ "type": "integer"
4854
+ }
4855
+ },
4856
+ "required": [
4857
+ "type",
4858
+ "item_id",
4859
+ "output_index",
4860
+ "summary_index",
4861
+ "text",
4862
+ "sequence_number"
4863
+ ],
4864
+ "description": "Event emitted when reasoning summary text streaming is complete",
4865
+ "example": {
4866
+ "type": "response.reasoning_summary_text.done",
4867
+ "item_id": "item-1",
4868
+ "output_index": 0,
4869
+ "summary_index": 0,
4870
+ "text": "Analyzing the problem step by step to find the optimal solution.",
4871
+ "sequence_number": 6
4872
+ }
4873
+ },
4874
+ "ReasoningSummaryTextDoneEvent": {
4875
+ "allOf": [
4876
+ {
4877
+ "$ref": "#/components/schemas/BaseReasoningSummaryTextDoneEvent"
4878
+ },
4879
+ {
4880
+ "type": "object",
4881
+ "properties": {}
4882
+ }
4883
+ ],
4884
+ "description": "Event emitted when reasoning summary text streaming is complete",
4885
+ "example": {
4886
+ "type": "response.reasoning_summary_text.done",
4887
+ "item_id": "item-1",
4888
+ "output_index": 0,
4889
+ "summary_index": 0,
4890
+ "text": "Analyzing the problem step by step to find the optimal solution.",
4891
+ "sequence_number": 6
4892
+ }
4893
+ },
4894
+ "OpenAIResponsesImageGenCallInProgress": {
4895
+ "type": "object",
4896
+ "properties": {
4897
+ "type": {
4898
+ "type": "string",
4899
+ "enum": [
4900
+ "response.image_generation_call.in_progress"
4901
+ ]
4902
+ },
4903
+ "item_id": {
4904
+ "type": "string"
4905
+ },
4906
+ "output_index": {
4907
+ "type": "number"
4908
+ },
4909
+ "sequence_number": {
4910
+ "type": "number"
4911
+ }
4912
+ },
4913
+ "required": [
4914
+ "type",
4915
+ "item_id",
4916
+ "output_index",
4917
+ "sequence_number"
4918
+ ]
4919
+ },
4920
+ "ImageGenCallInProgressEvent": {
4921
+ "allOf": [
4922
+ {
4923
+ "$ref": "#/components/schemas/OpenAIResponsesImageGenCallInProgress"
4924
+ },
4925
+ {
4926
+ "type": "object",
4927
+ "properties": {}
4928
+ }
4929
+ ],
4930
+ "description": "Image generation call in progress",
4931
+ "example": {
4932
+ "type": "response.image_generation_call.in_progress",
4933
+ "output_index": 0,
4934
+ "sequence_number": 1,
4935
+ "item_id": "call-123"
4936
+ }
4937
+ },
4938
+ "OpenAIResponsesImageGenCallGenerating": {
4939
+ "type": "object",
4940
+ "properties": {
4941
+ "type": {
4942
+ "type": "string",
4943
+ "enum": [
4944
+ "response.image_generation_call.generating"
4945
+ ]
4946
+ },
4947
+ "item_id": {
4948
+ "type": "string"
4949
+ },
4950
+ "output_index": {
4951
+ "type": "number"
4952
+ },
4953
+ "sequence_number": {
4954
+ "type": "number"
4955
+ }
4956
+ },
4957
+ "required": [
4958
+ "type",
4959
+ "item_id",
4960
+ "output_index",
4961
+ "sequence_number"
4962
+ ]
4963
+ },
4964
+ "ImageGenCallGeneratingEvent": {
4965
+ "allOf": [
4966
+ {
4967
+ "$ref": "#/components/schemas/OpenAIResponsesImageGenCallGenerating"
4968
+ },
4969
+ {
4970
+ "type": "object",
4971
+ "properties": {}
4972
+ }
4973
+ ],
4974
+ "description": "Image generation call is generating",
4975
+ "example": {
4976
+ "type": "response.image_generation_call.generating",
4977
+ "output_index": 0,
4978
+ "sequence_number": 2,
4979
+ "item_id": "call-123"
4980
+ }
4981
+ },
4982
+ "OpenAIResponsesImageGenCallPartialImage": {
4983
+ "type": "object",
4984
+ "properties": {
4985
+ "type": {
4986
+ "type": "string",
4987
+ "enum": [
4988
+ "response.image_generation_call.partial_image"
4989
+ ]
4990
+ },
4991
+ "item_id": {
4992
+ "type": "string"
4993
+ },
4994
+ "output_index": {
4995
+ "type": "number"
4996
+ },
4997
+ "sequence_number": {
4998
+ "type": "number"
4999
+ },
This file is too large to show in full.
ios/Podfile.lock
-18
@@ -1,6 +1,4 @@
1
PODS:
2
- - device_info_plus (0.0.1):
3
- - Flutter
2
- DKImagePickerController/Core (4.3.9):
3
- DKImagePickerController/ImageDataManager
4
- DKImagePickerController/Resource
@@ -40,23 +38,16 @@ PODS:
38
- Flutter
39
- haptic_feedback (0.5.1):
40
- Flutter
43
- - irondash_engine_context (0.0.1):
44
- - Flutter
41
- SDWebImage (5.21.7):
42
- SDWebImage/Core (= 5.21.7)
43
- SDWebImage/Core (5.21.7)
48
- - super_native_extensions (0.0.1):
49
- - Flutter
44
- SwiftyGif (5.4.5)
45
46
DEPENDENCIES:
53
- - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
47
- file_picker (from `.symlinks/plugins/file_picker/ios`)
48
- Flutter (from `Flutter`)
49
- flutter_tts (from `.symlinks/plugins/flutter_tts/ios`)
50
- haptic_feedback (from `.symlinks/plugins/haptic_feedback/ios`)
58
- - irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`)
59
- - super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`)
51
52
SPEC REPOS:
53
trunk:
@@ -66,8 +57,6 @@ SPEC REPOS:
57
- SwiftyGif
58
59
EXTERNAL SOURCES:
69
- device_info_plus:
70
- :path: ".symlinks/plugins/device_info_plus/ios"
60
file_picker:
61
:path: ".symlinks/plugins/file_picker/ios"
62
Flutter:
@@ -76,22 +65,15 @@ EXTERNAL SOURCES:
65
:path: ".symlinks/plugins/flutter_tts/ios"
66
haptic_feedback:
67
:path: ".symlinks/plugins/haptic_feedback/ios"
79
- irondash_engine_context:
80
- :path: ".symlinks/plugins/irondash_engine_context/ios"
81
- super_native_extensions:
82
- :path: ".symlinks/plugins/super_native_extensions/ios"
68
69
SPEC CHECKSUMS:
85
- device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
70
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
71
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
72
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
73
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
74
flutter_tts: 91ad1884033cd95ef6e9338754adad54f01f090f
75
haptic_feedback: a366a27ec760f0e865651605b95de8a9bb04ef01
92
- irondash_engine_context: 3458bf979b90d616ffb8ae03a150bafe2e860cc9
76
SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
94
- super_native_extensions: 4916b3c627a9c7fffdc48a23a9eca0b1ac228fa7
77
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
78
79
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
ios/Runner/Info.plist
+1
-1
@@ -14,7 +14,7 @@
14
<key>CFBundleDevelopmentRegion</key>
15
<string>$(DEVELOPMENT_LANGUAGE)</string>
16
<key>CFBundleDisplayName</key>
17
- <string>Localmind</string>
17
+ <string>LocalMind</string>
18
<key>CFBundleExecutable</key>
19
<string>$(EXECUTABLE_NAME)</string>
20
<key>CFBundleIdentifier</key>
lib/core/logger/app_logger.dart
new
+47
@@ -0,0 +1,47 @@
1
+import 'package:flutter/foundation.dart';
2
+import 'package:logger/logger.dart';
3
+
4
+class Log {
5
+ late Logger _logger;
6
+ static const lineLength = 80;
7
+
8
+ Log._internal() {
9
+ if (kDebugMode) {
10
+ _logger = Logger(
11
+ printer: PrettyPrinter(
12
+ methodCount: 2,
13
+ errorMethodCount: 8,
14
+ lineLength: lineLength,
15
+ colors: true,
16
+ printEmojis: true,
17
+ dateTimeFormat: DateTimeFormat.onlyTime,
18
+ ),
19
+ );
20
+ } else {
21
+ _logger = Logger(
22
+ printer: PrettyPrinter(
23
+ methodCount: 0,
24
+ errorMethodCount: 0,
25
+ lineLength: 0,
26
+ colors: false,
27
+ printEmojis: false,
28
+ dateTimeFormat: DateTimeFormat.onlyTime,
29
+ ),
30
+ level: Level.off,
31
+ );
32
+ }
33
+ }
34
+
35
+ static final Log _singleton = Log._internal();
36
+
37
+ static void fatal({required Object error, required StackTrace stackTrace}) =>
38
+ _singleton._logger.f('Fatal', error: error, stackTrace: stackTrace);
39
+
40
+ static void debug(String message) => _singleton._logger.d(message);
41
+
42
+ static void info(String message) => _singleton._logger.i(message);
43
+
44
+ static void error(String message) => _singleton._logger.e(message);
45
+
46
+ static void warning(String message) => _singleton._logger.w(message);
47
+}
lib/core/providers/app_providers.dart
+4
-2
@@ -75,8 +75,10 @@ class SettingsNotifier extends Notifier<AppSettings> {
75
void setHasCompletedOnboarding(bool value) =>
76
_update(state.copyWith(hasCompletedOnboarding: value));
77
void setMcpEnabled(bool value) => _update(state.copyWith(mcpEnabled: value));
78
- void setCodeTheme(SyntaxThemeName value) =>
79
- _update(state.copyWith(codeTheme: value));
78
+ void setCodeThemeDark(SyntaxThemeName value) =>
79
+ _update(state.copyWith(codeThemeDark: value));
80
+ void setCodeThemeLight(SyntaxThemeName value) =>
81
+ _update(state.copyWith(codeThemeLight: value));
82
83
Future<void> _update(AppSettings updated) async {
84
state = updated;
lib/core/providers/storage_providers.dart
+6
@@ -1,4 +1,6 @@
1
+import 'dart:io';
2
import 'package:flutter_riverpod/flutter_riverpod.dart';
3
+import 'package:path_provider/path_provider.dart';
4
import 'package:hive_ce/hive.dart';
5
import 'package:localmind/features/conversations/data/models/conversation.dart';
6
import 'package:localmind/features/personas/data/models/persona.dart';
@@ -39,3 +41,7 @@ final personasProvider = Provider<List<Persona>>((ref) {
41
final box = ref.watch(personasBoxProvider);
42
return box.values.toList();
43
});
44
+
45
+final storageDirectoryProvider = FutureProvider<Directory>((ref) async {
46
+ return await getApplicationDocumentsDirectory();
47
+});
lib/features/chat/data/chat_service.dart
+423
-156
@@ -6,6 +6,7 @@ import 'package:localmind/features/chat/data/models/message.dart';
6
import 'package:localmind/features/chat/data/models/chat_parameters.dart';
7
import 'package:localmind/features/chat/data/models/mcp_integration.dart';
8
import 'package:localmind/core/models/enums.dart';
9
+import 'package:localmind/core/logger/app_logger.dart';
10
11
abstract class ChatService {
12
Stream<ChatResponse> sendMessage({
@@ -51,7 +52,16 @@ class ChatResponse {
52
});
53
}
54
54
-enum ChatResponseType { message, reasoning, toolCall, invalidToolCall, done }
55
+enum ChatResponseType {
56
+ message,
57
+ reasoning,
58
+ toolCall,
59
+ invalidToolCall,
60
+ done,
61
+ processing,
62
+ timeoutError,
63
+ error,
64
+}
65
66
class ToolCallData {
67
final String tool;
@@ -155,52 +165,62 @@ class LMStudioChatService implements ChatService {
165
body['previous_response_id'] = previousResponseId;
166
}
167
158
- final response = await _dio.post<ResponseBody>(
159
- server.chatEndpoint,
160
- data: body,
161
- options: Options(
162
- responseType: ResponseType.stream,
163
- headers: {
164
- 'Content-Type': 'application/json',
165
- if (server.apiKey != null) 'Authorization': 'Bearer ${server.apiKey}',
166
- },
167
- ),
168
- cancelToken: _cancelToken,
169
- );
170
-
171
- final stream = response.data!.stream.cast<List<int>>().transform(utf8.decoder);
172
- String buffer = '';
173
- String currentEventType = '';
174
-
175
- await for (final chunk in stream) {
176
- buffer += chunk;
177
- final lines = buffer.split('\n');
178
- buffer = lines.removeLast();
179
-
180
- for (final line in lines) {
181
- final trimmedLine = line.trim();
182
- if (trimmedLine.isEmpty) continue;
183
-
184
- if (trimmedLine.startsWith('event: ')) {
185
- currentEventType = trimmedLine.substring(7);
186
- } else if (trimmedLine.startsWith('data: ')) {
187
- final data = trimmedLine.substring(6);
188
- if (data.isEmpty) continue;
189
-
190
- try {
191
- final json = jsonDecode(data) as Map<String, dynamic>;
192
- await for (final response in _handleSseEvent(
193
- currentEventType,
194
- json,
195
- )) {
196
- yield response;
168
+ try {
169
+ final response = await _dio.post<ResponseBody>(
170
+ server.chatEndpoint,
171
+ data: body,
172
+ options: Options(
173
+ responseType: ResponseType.stream,
174
+ headers: {
175
+ 'Content-Type': 'application/json',
176
+ if (server.apiKey != null) 'Authorization': 'Bearer ${server.apiKey}',
177
+ },
178
+ ),
179
+ cancelToken: _cancelToken,
180
+ );
181
+
182
+ final stream = response.data!.stream.cast<List<int>>().transform(
183
+ utf8.decoder,
184
+ );
185
+ String buffer = '';
186
+ String currentEventType = '';
187
+
188
+ await for (final chunk in stream) {
189
+ buffer += chunk;
190
+ final lines = buffer.split('\n');
191
+ buffer = lines.removeLast();
192
+
193
+ for (final line in lines) {
194
+ final trimmedLine = line.trim();
195
+ if (trimmedLine.isEmpty) continue;
196
+
197
+ if (trimmedLine.startsWith('event: ')) {
198
+ currentEventType = trimmedLine.substring(7);
199
+ } else if (trimmedLine.startsWith('data: ')) {
200
+ final data = trimmedLine.substring(6);
201
+ if (data.isEmpty) continue;
202
+
203
+ try {
204
+ final json = jsonDecode(data) as Map<String, dynamic>;
205
+ await for (final response in _handleSseEvent(
206
+ currentEventType,
207
+ json,
208
+ )) {
209
+ yield response;
210
+ }
211
+ currentEventType = '';
212
+ } catch (e) {
213
+ currentEventType = '';
214
}
198
- currentEventType = '';
199
- } catch (e) {
200
- currentEventType = '';
215
}
216
}
217
}
218
+ } catch (e) {
219
+ Log.error('LMStudio connection error: $e');
220
+ yield ChatResponse(
221
+ type: ChatResponseType.error,
222
+ content: _handleChatError(e),
223
+ );
224
}
225
}
226
@@ -375,6 +395,7 @@ class LMStudioChatService implements ChatService {
395
class OpenAICompatibleChatService implements ChatService {
396
final Dio _dio;
397
CancelToken? _cancelToken;
398
+ Timer? _timeoutTimer;
399
400
OpenAICompatibleChatService(this._dio);
401
@@ -400,64 +421,156 @@ class OpenAICompatibleChatService implements ChatService {
421
'stream': true,
422
};
423
403
- final response = await _dio.post<ResponseBody>(
404
- server.chatEndpoint,
405
- data: body,
406
- options: Options(
407
- responseType: ResponseType.stream,
408
- headers: {
409
- 'Content-Type': 'application/json',
410
- if (server.apiKey != null) 'Authorization': 'Bearer ${server.apiKey}',
411
- },
412
- ),
413
- cancelToken: _cancelToken,
424
+ Log.debug(
425
+ 'OpenAICompatible: Sending request to ${server.chatEndpoint} with model: $modelId',
426
);
427
416
- final stream = response.data!.stream.cast<List<int>>().transform(utf8.decoder);
417
- String buffer = '';
428
+ try {
429
+ final response = await _dio.post<ResponseBody>(
430
+ server.chatEndpoint,
431
+ data: body,
432
+ options: Options(
433
+ responseType: ResponseType.stream,
434
+ headers: {
435
+ 'Content-Type': 'application/json',
436
+ if (server.apiKey != null) 'Authorization': 'Bearer ${server.apiKey}',
437
+ },
438
+ ),
439
+ cancelToken: _cancelToken,
440
+ );
441
+
442
+ final stream = response.data!.stream.cast<List<int>>().transform(
443
+ utf8.decoder,
444
+ );
445
+ String buffer = '';
446
+ DateTime? lastContentReceived;
447
+ int emptyDeltaCount = 0;
448
+ int logCounter = 0;
449
+ bool timeoutTriggered = false;
450
+
451
+ // Start timeout timer
452
+ _timeoutTimer = Timer(const Duration(seconds: 45), () {
453
+ timeoutTriggered = true;
454
+ });
455
+
456
+ Log.debug('OpenAICompatible: Starting to receive stream...');
457
+
458
+ await for (final chunk in stream) {
459
+ // Check if timeout was triggered
460
+ if (timeoutTriggered && lastContentReceived == null) {
461
+ _timeoutTimer?.cancel();
462
+ yield const ChatResponse(
463
+ type: ChatResponseType.timeoutError,
464
+ content:
465
+ 'Model is taking too long to respond. This may happen with free tier models.',
466
+ );
467
+ return;
468
+ }
469
419
- await for (final chunk in stream) {
420
- buffer += chunk;
421
- final lines = buffer.split('\n');
422
- buffer = lines.removeLast();
470
+ buffer += chunk;
471
+ final lines = buffer.split('\n');
472
+ buffer = lines.removeLast();
473
424
- for (final line in lines) {
425
- if (line.startsWith('data: ')) {
426
- final data = line.substring(6);
427
- if (data == '[DONE]') {
428
- yield const ChatResponse(type: ChatResponseType.done);
429
- return;
474
+ for (final line in lines) {
475
+ logCounter++;
476
+ if (logCounter % 10 == 0) {
477
+ Log.debug('OpenAICompatible SSE line #$logCounter');
478
}
431
- try {
432
- final json = jsonDecode(data) as Map<String, dynamic>;
433
- final delta = json['choices']?[0]?['delta'] as Map<String, dynamic>?;
434
- if (delta != null) {
435
- final content = delta['content'] as String?;
436
- final reasoning = (delta['reasoning'] ?? delta['reasoning_content']) as String?;
437
-
438
- if (content != null && content.isNotEmpty) {
479
+
480
+ if (line.startsWith('data: ')) {
481
+ final data = line.substring(6);
482
+ if (data == '[DONE]') {
483
+ _timeoutTimer?.cancel();
484
+ Log.debug('OpenAICompatible: Received [DONE]');
485
+ yield const ChatResponse(type: ChatResponseType.done);
486
+ return;
487
+ }
488
+ try {
489
+ final json = jsonDecode(data) as Map<String, dynamic>;
490
+
491
+ // Check for errors in the response
492
+ if (json['error'] != null) {
493
+ final error = json['error'] as Map<String, dynamic>;
494
+ final errorMsg = error['message'] ?? 'Unknown API error';
495
+ Log.error('OpenAICompatible mid-stream error: $errorMsg');
496
yield ChatResponse(
440
- type: ChatResponseType.message,
441
- content: content,
497
+ type: ChatResponseType.error,
498
+ content: 'API Error: $errorMsg',
499
);
500
+ return;
501
}
444
- if (reasoning != null && reasoning.isNotEmpty) {
445
- yield ChatResponse(
446
- type: ChatResponseType.reasoning,
447
- reasoningContent: reasoning,
448
- );
502
+
503
+ final choices = json['choices'] as List<dynamic>?;
504
+ if (choices == null || choices.isEmpty) {
505
+ continue;
506
+ }
507
+
508
+ final firstChoice = choices[0] as Map<String, dynamic>?;
509
+ if (firstChoice == null) continue;
510
+
511
+ final delta = firstChoice['delta'];
512
+ if (delta == null || delta is! Map<String, dynamic>) continue;
513
+
514
+ final content = (delta['content'] ?? delta['text']) as String?;
515
+ final reasoning =
516
+ (delta['reasoning'] ?? delta['reasoning_content']) as String?;
517
+ final refusal = delta['refusal'] as String?;
518
+
519
+ // Check if content is empty/null (processing but not outputting)
520
+ final hasContent = content != null && content.isNotEmpty;
521
+ final hasReasoning = reasoning != null && reasoning.isNotEmpty;
522
+ final hasRefusal = refusal != null && refusal.isNotEmpty;
523
+
524
+ if (!hasContent && !hasReasoning && !hasRefusal) {
525
+ emptyDeltaCount++;
526
+ if (emptyDeltaCount % 10 == 0) {
527
+ yield const ChatResponse(type: ChatResponseType.processing);
528
+ }
529
+ } else {
530
+ emptyDeltaCount = 0;
531
+ lastContentReceived = DateTime.now();
532
+
533
+ if (hasRefusal) {
534
+ yield ChatResponse(
535
+ type: ChatResponseType.error,
536
+ content: 'Refusal: $refusal',
537
+ );
538
+ return;
539
+ }
540
+ if (hasContent) {
541
+ yield ChatResponse(
542
+ type: ChatResponseType.message,
543
+ content: content,
544
+ );
545
+ }
546
+ if (hasReasoning) {
547
+ yield ChatResponse(
548
+ type: ChatResponseType.reasoning,
549
+ reasoningContent: reasoning,
550
+ );
551
+ }
552
}
553
+ } catch (e) {
554
+ Log.error('OpenAICompatible parsing error: $e');
555
}
451
- } catch (e) {
452
- // ignore: empty_catches
556
}
557
}
558
}
559
+ } catch (e) {
560
+ Log.error('OpenAICompatible connection error: $e');
561
+ yield ChatResponse(
562
+ type: ChatResponseType.error,
563
+ content: _handleChatError(e),
564
+ );
565
}
566
+
567
+ _timeoutTimer?.cancel();
568
+ Log.debug('OpenAICompatible: Stream ended');
569
}
570
571
@override
572
void cancelStream() {
573
+ _timeoutTimer?.cancel();
574
_cancelToken?.cancel('User cancelled');
575
}
576
}
@@ -492,41 +605,51 @@ class OllamaChatService implements ChatService {
605
},
606
};
607
495
- final response = await _dio.post<ResponseBody>(
496
- server.chatEndpoint,
497
- data: body,
498
- options: Options(responseType: ResponseType.stream),
499
- cancelToken: _cancelToken,
500
- );
501
-
502
- final stream = response.data!.stream.cast<List<int>>().transform(utf8.decoder);
503
- String buffer = '';
504
-
505
- await for (final chunk in stream) {
506
- buffer += chunk;
507
- final lines = buffer.split('\n');
508
- buffer = lines.removeLast();
509
-
510
- for (final line in lines) {
511
- if (line.isNotEmpty) {
512
- try {
513
- final json = jsonDecode(line) as Map<String, dynamic>;
514
- final content = json['message']?['content'];
515
- if (content != null && content is String) {
516
- yield ChatResponse(
517
- type: ChatResponseType.message,
518
- content: content,
519
- );
520
- }
521
- if (json['done'] == true) {
522
- yield const ChatResponse(type: ChatResponseType.done);
523
- return;
608
+ try {
609
+ final response = await _dio.post<ResponseBody>(
610
+ server.chatEndpoint,
611
+ data: body,
612
+ options: Options(responseType: ResponseType.stream),
613
+ cancelToken: _cancelToken,
614
+ );
615
+
616
+ final stream = response.data!.stream.cast<List<int>>().transform(
617
+ utf8.decoder,
618
+ );
619
+ String buffer = '';
620
+
621
+ await for (final chunk in stream) {
622
+ buffer += chunk;
623
+ final lines = buffer.split('\n');
624
+ buffer = lines.removeLast();
625
+
626
+ for (final line in lines) {
627
+ if (line.isNotEmpty) {
628
+ try {
629
+ final json = jsonDecode(line) as Map<String, dynamic>;
630
+ final content = json['message']?['content'];
631
+ if (content != null && content is String) {
632
+ yield ChatResponse(
633
+ type: ChatResponseType.message,
634
+ content: content,
635
+ );
636
+ }
637
+ if (json['done'] == true) {
638
+ yield const ChatResponse(type: ChatResponseType.done);
639
+ return;
640
+ }
641
+ } catch (e) {
642
+ // ignore: empty_catches
643
}
525
- } catch (e) {
526
- // ignore: empty_catches
644
}
645
}
646
}
647
+ } catch (e) {
648
+ Log.error('Ollama connection error: $e');
649
+ yield ChatResponse(
650
+ type: ChatResponseType.error,
651
+ content: _handleChatError(e),
652
+ );
653
}
654
}
655
@@ -539,6 +662,7 @@ class OllamaChatService implements ChatService {
662
class OpenRouterChatService implements ChatService {
663
final Dio _dio;
664
CancelToken? _cancelToken;
665
+ Timer? _timeoutTimer;
666
667
OpenRouterChatService(this._dio);
668
@@ -564,70 +688,213 @@ class OpenRouterChatService implements ChatService {
688
'stream': true,
689
};
690
567
- final response = await _dio.post<ResponseBody>(
568
- server.chatEndpoint,
569
- data: body,
570
- options: Options(
571
- responseType: ResponseType.stream,
572
- headers: {
573
- 'Content-Type': 'application/json',
574
- 'Authorization': 'Bearer ${server.apiKey}',
575
- 'HTTP-Referer': 'https://localmind.app',
576
- 'X-Title': 'LocalMind',
577
- },
578
- ),
579
- cancelToken: _cancelToken,
691
+ Log.debug(
692
+ 'OpenRouter: Sending request to ${server.chatEndpoint} with model: $modelId',
693
);
694
582
- final stream = response.data!.stream.cast<List<int>>().transform(utf8.decoder);
583
- String buffer = '';
695
+ try {
696
+ final response = await _dio.post<ResponseBody>(
697
+ server.chatEndpoint,
698
+ data: body,
699
+ options: Options(
700
+ responseType: ResponseType.stream,
701
+ headers: {
702
+ 'Content-Type': 'application/json',
703
+ 'Authorization': 'Bearer ${server.apiKey}',
704
+ 'HTTP-Referer': 'https://localmind.app',
705
+ 'X-Title': 'LocalMind',
706
+ },
707
+ ),
708
+ cancelToken: _cancelToken,
709
+ );
710
+
711
+ final stream = response.data!.stream.cast<List<int>>().transform(
712
+ utf8.decoder,
713
+ );
714
+ String buffer = '';
715
+ DateTime? lastContentReceived;
716
+ int emptyDeltaCount = 0;
717
+ int logCounter = 0;
718
+ bool timeoutTriggered = false;
719
+
720
+ // Start timeout timer
721
+ _timeoutTimer = Timer(const Duration(seconds: 45), () {
722
+ timeoutTriggered = true;
723
+ });
724
+
725
+ Log.debug('OpenRouter: Starting to receive stream...');
726
+
727
+ await for (final chunk in stream) {
728
+ // Check if timeout was triggered
729
+ if (timeoutTriggered && lastContentReceived == null) {
730
+ _timeoutTimer?.cancel();
731
+ yield const ChatResponse(
732
+ type: ChatResponseType.timeoutError,
733
+ content:
734
+ 'Model is taking too long to respond. This may happen with free tier models.',
735
+ );
736
+ return;
737
+ }
738
585
- await for (final chunk in stream) {
586
- buffer += chunk;
587
- final lines = buffer.split('\n');
588
- buffer = lines.removeLast();
739
+ buffer += chunk;
740
+ final lines = buffer.split('\n');
741
+ buffer = lines.removeLast();
742
590
- for (final line in lines) {
591
- if (line.startsWith('data: ')) {
592
- final data = line.substring(6);
593
- if (data == '[DONE]') {
594
- yield const ChatResponse(type: ChatResponseType.done);
595
- return;
743
+ for (final line in lines) {
744
+ logCounter++;
745
+ if (logCounter % 10 == 0) {
746
+ Log.debug('OpenRouter SSE line #$logCounter: $line');
747
}
597
- try {
598
- final json = jsonDecode(data) as Map<String, dynamic>;
599
- final delta = json['choices']?[0]?['delta'] as Map<String, dynamic>?;
600
- if (delta != null) {
601
- final content = delta['content'] as String?;
602
- final reasoning = (delta['reasoning'] ?? delta['reasoning_content']) as String?;
603
-
604
- if (content != null && content.isNotEmpty) {
748
+
749
+ if (line.startsWith('data: ')) {
750
+ final data = line.substring(6);
751
+ if (data == '[DONE]') {
752
+ _timeoutTimer?.cancel();
753
+ Log.debug('OpenRouter: Received [DONE]');
754
+ yield const ChatResponse(type: ChatResponseType.done);
755
+ return;
756
+ }
757
+ try {
758
+ final json = jsonDecode(data) as Map<String, dynamic>;
759
+
760
+ // Check for errors in the response
761
+ if (json['error'] != null) {
762
+ final error = json['error'] as Map<String, dynamic>;
763
+ final errorMsg = error['message'] ?? 'Unknown API error';
764
+ Log.error('OpenRouter mid-stream error: $errorMsg');
765
yield ChatResponse(
606
- type: ChatResponseType.message,
607
- content: content,
766
+ type: ChatResponseType.error,
767
+ content: 'API Error: $errorMsg',
768
);
769
+ return;
770
}
610
- if (reasoning != null && reasoning.isNotEmpty) {
611
- yield ChatResponse(
612
- type: ChatResponseType.reasoning,
613
- reasoningContent: reasoning,
614
- );
771
+
772
+ final choices = json['choices'] as List<dynamic>?;
773
+ if (choices == null || choices.isEmpty) {
774
+ continue;
775
+ }
776
+
777
+ final firstChoice = choices[0] as Map<String, dynamic>?;
778
+ if (firstChoice == null) continue;
779
+
780
+ final delta = firstChoice['delta'];
781
+ if (delta == null || delta is! Map<String, dynamic>) continue;
782
+
783
+ final content = (delta['content'] ?? delta['text']) as String?;
784
+ final reasoning =
785
+ (delta['reasoning'] ?? delta['reasoning_content']) as String?;
786
+ final refusal = delta['refusal'] as String?;
787
+
788
+ // Check if content is empty/null (processing but not outputting)
789
+ final hasContent = content != null && content.isNotEmpty;
790
+ final hasReasoning = reasoning != null && reasoning.isNotEmpty;
791
+ final hasRefusal = refusal != null && refusal.isNotEmpty;
792
+
793
+ if (!hasContent && !hasReasoning && !hasRefusal) {
794
+ emptyDeltaCount++;
795
+ if (emptyDeltaCount % 10 == 0) {
796
+ yield const ChatResponse(type: ChatResponseType.processing);
797
+ }
798
+ } else {
799
+ emptyDeltaCount = 0;
800
+ lastContentReceived = DateTime.now();
801
+
802
+ if (hasRefusal) {
803
+ yield ChatResponse(
804
+ type: ChatResponseType.error,
805
+ content: 'Refusal: $refusal',
806
+ );
807
+ return;
808
+ }
809
+ if (hasContent) {
810
+ yield ChatResponse(
811
+ type: ChatResponseType.message,
812
+ content: content,
813
+ );
814
+ }
815
+ if (hasReasoning) {
816
+ yield ChatResponse(
817
+ type: ChatResponseType.reasoning,
818
+ reasoningContent: reasoning,
819
+ );
820
+ }
821
}
822
+ } catch (e) {
823
+ Log.error('OpenRouter parsing error: $e');
824
+ }
825
+ } else if (line.startsWith(': ')) {
826
+ // SSE comment (keep-alive)
827
+ if (logCounter % 50 == 0) {
828
+ Log.debug('OpenRouter SSE comment: $line');
829
}
617
- } catch (e) {
618
- // ignore: empty_catches
830
}
831
}
832
}
833
+ } catch (e) {
834
+ Log.error('OpenRouter connection error: $e');
835
+ yield ChatResponse(
836
+ type: ChatResponseType.error,
837
+ content: _handleChatError(e),
838
+ );
839
}
840
+
841
+ _timeoutTimer?.cancel();
842
+ Log.debug('OpenRouter: Stream ended');
843
}
844
845
@override
846
void cancelStream() {
847
+ _timeoutTimer?.cancel();
848
_cancelToken?.cancel('User cancelled');
849
}
850
}
851
852
+String _handleChatError(dynamic e) {
853
+ if (e is DioException) {
854
+ if (e.response != null) {
855
+ final status = e.response!.statusCode;
856
+
857
+ switch (status) {
858
+ case 401:
859
+ return 'Invalid API key or unauthorized.';
860
+ case 402:
861
+ return 'Monthly credit limit reached or insufficient balance.';
862
+ case 403:
863
+ return 'Access forbidden. Your API key might not have permission for this model.';
864
+ case 404:
865
+ return 'Model not found or API endpoint is incorrect.';
866
+ case 408:
867
+ return 'Request timeout. The server took too long to respond.';
868
+ case 429:
869
+ return 'Rate limit exceeded. Please wait a moment before trying again.';
870
+ case 500:
871
+ return 'Internal server error from the AI provider.';
872
+ case 502:
873
+ return 'Bad gateway. The provider is having issues.';
874
+ case 503:
875
+ return 'Service unavailable. The provider might be overloaded.';
876
+ case 504:
877
+ return 'Gateway timeout. The model took too long to respond.';
878
+ }
879
+ return 'Server error ($status): ${e.message}';
880
+ }
881
+
882
+ switch (e.type) {
883
+ case DioExceptionType.connectionTimeout:
884
+ case DioExceptionType.sendTimeout:
885
+ case DioExceptionType.receiveTimeout:
886
+ return 'Connection timed out. Check your internet connection.';
887
+ case DioExceptionType.connectionError:
888
+ return 'Connection failed. Check if the server is accessible.';
889
+ case DioExceptionType.cancel:
890
+ return 'Request cancelled.';
891
+ default:
892
+ return 'Connection error: ${e.message}';
893
+ }
894
+ }
895
+ return e.toString();
896
+}
897
+
898
String _roleToString(MessageRole role) {
899
switch (role) {
900
case MessageRole.user:
lib/features/chat/data/models/message.dart
+6
@@ -47,6 +47,9 @@ class Message extends HiveObject {
47
@HiveField(13)
48
final String? toolCallId;
49
50
+ @HiveField(14)
51
+ final bool isProcessing;
52
+
53
Message({
54
required this.id,
55
required this.conversationId,
@@ -62,6 +65,7 @@ class Message extends HiveObject {
65
this.reasoningContent,
66
this.toolCalls,
67
this.toolCallId,
68
+ this.isProcessing = false,
69
});
70
71
Message copyWith({
@@ -79,6 +83,7 @@ class Message extends HiveObject {
83
String? reasoningContent,
84
List<ToolCallData>? toolCalls,
85
String? toolCallId,
86
+ bool? isProcessing,
87
}) {
88
return Message(
89
id: id ?? this.id,
@@ -95,6 +100,7 @@ class Message extends HiveObject {
100
reasoningContent: reasoningContent ?? this.reasoningContent,
101
toolCalls: toolCalls ?? this.toolCalls,
102
toolCallId: toolCallId ?? this.toolCallId,
103
+ isProcessing: isProcessing ?? this.isProcessing,
104
);
105
}
106
}
lib/features/chat/data/models/message.g.dart
+5
-2
@@ -33,13 +33,14 @@ class MessageAdapter extends TypeAdapter<Message> {
33
reasoningContent: fields[11] as String?,
34
toolCalls: (fields[12] as List?)?.cast<ToolCallData>(),
35
toolCallId: fields[13] as String?,
36
+ isProcessing: fields[14] == null ? false : fields[14] as bool,
37
);
38
}
39
40
@override
41
void write(BinaryWriter writer, Message obj) {
42
writer
42
- ..writeByte(14)
43
+ ..writeByte(15)
44
..writeByte(0)
45
..write(obj.id)
46
..writeByte(1)
@@ -67,7 +68,9 @@ class MessageAdapter extends TypeAdapter<Message> {
68
..writeByte(12)
69
..write(obj.toolCalls)
70
..writeByte(13)
70
- ..write(obj.toolCallId);
71
+ ..write(obj.toolCallId)
72
+ ..writeByte(14)
73
+ ..write(obj.isProcessing);
74
}
75
76
@override
lib/features/chat/providers/chat_providers.dart
+179
-40
@@ -1,4 +1,5 @@
1
import 'dart:async';
2
+import 'dart:io';
3
import 'dart:math';
4
import 'package:flutter_riverpod/flutter_riverpod.dart';
5
import 'package:localmind/core/models/enums.dart';
@@ -27,6 +28,10 @@ class SelectedModelNotifier extends Notifier<ModelInfo?> {
28
void setModel(ModelInfo? model) {
29
state = model;
30
}
31
+
32
+ void clear() {
33
+ state = null;
34
+ }
35
}
36
37
final autoSelectFirstLoadedModelProvider = FutureProvider<void>((ref) async {
@@ -313,7 +318,7 @@ class ChatNotifier extends Notifier<ChatState> {
318
.join('-');
319
}
320
316
- Future<void> sendMessage(String content) async {
321
+ Future<void> sendMessage(String content, {List<File>? attachments}) async {
322
final server = ref.read(activeServerProvider);
323
final selectedModel = ref.read(selectedModelProvider);
324
final chatParams = ref.read(chatParamsProvider);
@@ -351,6 +356,23 @@ class ChatNotifier extends Notifier<ChatState> {
356
ref.read(selectedPersonaProvider.notifier).clear();
357
}
358
359
+ final List<String> savedPaths = [];
360
+ if (attachments != null && attachments.isNotEmpty) {
361
+ final appDir = await ref.read(storageDirectoryProvider.future);
362
+ final attachmentsDir = Directory('${appDir.path}/attachments');
363
+ if (!await attachmentsDir.exists()) {
364
+ await attachmentsDir.create(recursive: true);
365
+ }
366
+
367
+ for (final file in attachments) {
368
+ final fileName = file.path.split('/').last;
369
+ final timestamp = DateTime.now().millisecondsSinceEpoch;
370
+ final newPath = '${attachmentsDir.path}/${timestamp}_$fileName';
371
+ await file.copy(newPath);
372
+ savedPaths.add(newPath);
373
+ }
374
+ }
375
+
376
final userMessage = Message(
377
id: generateUuid(),
378
conversationId: _currentConversationId!,
@@ -358,6 +380,7 @@ class ChatNotifier extends Notifier<ChatState> {
380
content: content,
381
createdAt: DateTime.now(),
382
status: MessageStatus.complete,
383
+ attachmentPaths: savedPaths.isNotEmpty ? savedPaths : null,
384
);
385
386
final assistantMessageId = generateUuid();
@@ -396,22 +419,27 @@ class ChatNotifier extends Notifier<ChatState> {
419
params: chatParams,
420
)
421
.listen(
399
- (response) {
422
+ (response) async {
423
switch (response.type) {
424
case ChatResponseType.message:
425
final currentContent = state.streamingMessage?.content ?? '';
426
final updatedMessage =
427
(state.streamingMessage ?? assistantMessage).copyWith(
428
content: currentContent + (response.content ?? ''),
429
+ isProcessing: false,
430
);
407
- state = state.copyWith(streamingMessage: updatedMessage);
431
final messageIndex = state.messages.indexWhere(
432
(m) => m.id == assistantMessageId,
433
);
434
if (messageIndex != -1) {
435
final updatedMessages = List<Message>.from(state.messages);
436
updatedMessages[messageIndex] = updatedMessage;
414
- state = state.copyWith(messages: updatedMessages);
437
+ state = state.copyWith(
438
+ streamingMessage: updatedMessage,
439
+ messages: updatedMessages,
440
+ );
441
+ } else {
442
+ state = state.copyWith(streamingMessage: updatedMessage);
443
}
444
break;
445
case ChatResponseType.reasoning:
@@ -419,6 +447,7 @@ class ChatNotifier extends Notifier<ChatState> {
447
final reasoningMessage =
448
(state.streamingMessage ?? assistantMessage).copyWith(
449
reasoningContent: reasoningContent,
450
+ isProcessing: false,
451
);
452
state = state.copyWith(streamingMessage: reasoningMessage);
453
final msgIndex = state.messages.indexWhere(
@@ -430,6 +459,54 @@ class ChatNotifier extends Notifier<ChatState> {
459
state = state.copyWith(messages: updatedMessages);
460
}
461
break;
462
+ case ChatResponseType.processing:
463
+ // Model is working but hasn't output content yet
464
+ final processingMessage =
465
+ (state.streamingMessage ?? assistantMessage).copyWith(
466
+ isProcessing: true,
467
+ );
468
+ state = state.copyWith(streamingMessage: processingMessage);
469
+ final procIndex = state.messages.indexWhere(
470
+ (m) => m.id == assistantMessageId,
471
+ );
472
+ if (procIndex != -1) {
473
+ final updatedMessages = List<Message>.from(state.messages);
474
+ updatedMessages[procIndex] = processingMessage;
475
+ state = state.copyWith(messages: updatedMessages);
476
+ }
477
+ break;
478
+ case ChatResponseType.timeoutError:
479
+ case ChatResponseType.error:
480
+ // Error reached without receiving content or mid-stream
481
+ final errorMessage =
482
+ (state.streamingMessage ?? assistantMessage).copyWith(
483
+ status: MessageStatus.error,
484
+ errorMessage:
485
+ response.content ??
486
+ (response.type == ChatResponseType.timeoutError
487
+ ? 'Model is taking too long to respond. This may happen with free tier models.'
488
+ : 'An unknown error occurred.'),
489
+ isProcessing: false,
490
+ );
491
+ state = state.copyWith(
492
+ streamingMessage: errorMessage,
493
+ isStreaming: false,
494
+ );
495
+ final errIndex = state.messages.indexWhere(
496
+ (m) => m.id == assistantMessageId,
497
+ );
498
+ if (errIndex != -1) {
499
+ final updatedMessages = List<Message>.from(state.messages);
500
+ updatedMessages[errIndex] = errorMessage;
501
+ state = state.copyWith(
502
+ messages: updatedMessages,
503
+ errorMessage: errorMessage.errorMessage,
504
+ clearStreaming: true,
505
+ );
506
+ }
507
+ // Save error message to storage
508
+ await boxes.messages.put(errorMessage.id, errorMessage);
509
+ break;
510
case ChatResponseType.toolCall:
511
case ChatResponseType.invalidToolCall:
512
case ChatResponseType.done:
@@ -437,44 +514,77 @@ class ChatNotifier extends Notifier<ChatState> {
514
}
515
},
516
onDone: () async {
440
- final finalMessage = state.streamingMessage?.copyWith(
441
- status: MessageStatus.complete,
442
- );
443
- if (finalMessage != null) {
444
- await boxes.messages.put(finalMessage.id, finalMessage);
445
- final messageIndex = state.messages.indexWhere(
446
- (m) => m.id == assistantMessageId,
447
- );
448
- if (messageIndex != -1) {
449
- final updatedMessages = List<Message>.from(state.messages);
450
- updatedMessages[messageIndex] = finalMessage;
451
- state = state.copyWith(
452
- messages: updatedMessages,
453
- isStreaming: false,
454
- clearStreaming: true,
517
+ final streamingMessage = state.streamingMessage;
518
+ if (streamingMessage != null) {
519
+ // Check if content is empty - this happens when free models refuse or fail
520
+ final hasContent =
521
+ streamingMessage.content.isNotEmpty ||
522
+ (streamingMessage.reasoningContent?.isNotEmpty ?? false);
523
+
524
+ if (!hasContent) {
525
+ // Mark as error if no content received
526
+ final errorMessage = streamingMessage.copyWith(
527
+ status: MessageStatus.error,
528
+ errorMessage:
529
+ 'Model failed to respond. This may happen with free tier models that refuse certain prompts or when the service is busy.',
530
+ isProcessing: false,
531
);
456
- }
457
- if (_currentConversationId != null) {
458
- final preview = finalMessage.content.length > 100
459
- ? '${finalMessage.content.substring(0, 100)}...'
460
- : finalMessage.content;
461
- await ref
462
- .read(conv.conversationsProvider.notifier)
463
- .updatePreview(
464
- _currentConversationId!,
465
- preview,
466
- DateTime.now(),
467
- );
468
-
469
- final userMessage = state.messages
470
- .where((m) => m.role == MessageRole.user)
471
- .firstOrNull;
472
- if (userMessage != null && userMessage.content.length > 10) {
473
- _autoGenerateTitle(
474
- userMessage.content,
475
- finalMessage.content,
532
+ await boxes.messages.put(errorMessage.id, errorMessage);
533
+ final messageIndex = state.messages.indexWhere(
534
+ (m) => m.id == assistantMessageId,
535
+ );
536
+ if (messageIndex != -1) {
537
+ final updatedMessages = List<Message>.from(state.messages);
538
+ updatedMessages[messageIndex] = errorMessage;
539
+ state = state.copyWith(
540
+ messages: updatedMessages,
541
+ isStreaming: false,
542
+ errorMessage: errorMessage.errorMessage,
543
+ clearStreaming: true,
544
);
545
}
546
+ } else {
547
+ // Normal completion with content
548
+ final finalMessage = streamingMessage.copyWith(
549
+ status: MessageStatus.complete,
550
+ isProcessing: false,
551
+ );
552
+ await boxes.messages.put(finalMessage.id, finalMessage);
553
+ final messageIndex = state.messages.indexWhere(
554
+ (m) => m.id == assistantMessageId,
555
+ );
556
+ if (messageIndex != -1) {
557
+ final updatedMessages = List<Message>.from(state.messages);
558
+ updatedMessages[messageIndex] = finalMessage;
559
+ state = state.copyWith(
560
+ messages: updatedMessages,
561
+ isStreaming: false,
562
+ clearStreaming: true,
563
+ );
564
+ }
565
+ if (_currentConversationId != null) {
566
+ final preview = finalMessage.content.length > 100
567
+ ? '${finalMessage.content.substring(0, 100)}...'
568
+ : finalMessage.content;
569
+ await ref
570
+ .read(conv.conversationsProvider.notifier)
571
+ .updatePreview(
572
+ _currentConversationId!,
573
+ preview,
574
+ DateTime.now(),
575
+ );
576
+
577
+ final userMessage = state.messages
578
+ .where((m) => m.role == MessageRole.user)
579
+ .firstOrNull;
580
+ if (userMessage != null &&
581
+ userMessage.content.length > 10) {
582
+ _autoGenerateTitle(
583
+ userMessage.content,
584
+ finalMessage.content,
585
+ );
586
+ }
587
+ }
588
}
589
}
590
},
@@ -645,11 +755,40 @@ class ChatNotifier extends Notifier<ChatState> {
755
clearStreaming: true,
756
);
757
648
- await sendMessage(userMessage.content);
758
+ await sendMessage(userMessage.content, attachments: userMessage.attachmentPaths?.map((p) => File(p)).toList());
759
}
760
}
761
}
762
763
+ Future<void> retryMessage(String messageId) async {
764
+ final messageIndex = state.messages.indexWhere((m) => m.id == messageId);
765
+ if (messageIndex == -1) return;
766
+
767
+ final message = state.messages[messageIndex];
768
+ if (message.role != MessageRole.assistant) return;
769
+
770
+ // Find the preceding user message
771
+ if (messageIndex > 0 &&
772
+ state.messages[messageIndex - 1].role == MessageRole.user) {
773
+ final userMessage = state.messages[messageIndex - 1];
774
+
775
+ // Remove this assistant message and all subsequent messages
776
+ final messagesToRemove = state.messages.sublist(messageIndex);
777
+ final boxes = ref.read(hiveBoxesProvider);
778
+
779
+ for (final msg in messagesToRemove) {
780
+ await boxes.messages.delete(msg.id);
781
+ }
782
+
783
+ state = state.copyWith(
784
+ messages: state.messages.sublist(0, messageIndex),
785
+ clearStreaming: true,
786
+ );
787
+
788
+ await sendMessage(userMessage.content, attachments: userMessage.attachmentPaths?.map((p) => File(p)).toList());
789
+ }
790
+ }
791
+
792
Future<void> deleteMessage(String messageId) async {
793
final boxes = ref.read(hiveBoxesProvider);
794
await boxes.messages.delete(messageId);
lib/features/chat/views/chat_screen.dart
+44
-140
@@ -30,7 +30,6 @@ class ChatScreen extends ConsumerStatefulWidget {
30
31
class _ChatScreenState extends ConsumerState<ChatScreen> {
32
final ScrollController _scrollController = ScrollController();
33
- bool _showScrollToBottom = false;
33
34
static const List<String> _quickPrompts = [
35
'Help me write a function',
@@ -42,7 +41,6 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
41
@override
42
void initState() {
43
super.initState();
45
- _scrollController.addListener(_onScroll);
44
}
45
46
@override
@@ -51,26 +49,6 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
49
super.dispose();
50
}
51
54
- void _onScroll() {
55
- final chatState = ref.read(chatProvider);
56
- if (chatState.isStreaming) return;
57
-
58
- final isNearBottom =
59
- _scrollController.position.pixels >=
60
- _scrollController.position.maxScrollExtent - 100;
61
- if (_showScrollToBottom != !isNearBottom) {
62
- setState(() => _showScrollToBottom = !isNearBottom);
63
- }
64
- }
65
-
66
- void _scrollToBottom() {
67
- _scrollController.animateTo(
68
- _scrollController.position.maxScrollExtent,
69
- duration: const Duration(milliseconds: 300),
70
- curve: Curves.easeOut,
71
- );
72
- }
73
-
52
@override
53
Widget build(BuildContext context) {
54
ref.watch(autoSelectFirstLoadedModelProvider);
@@ -87,27 +65,8 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
65
final theme = Theme.of(context);
66
final isDark = theme.brightness == Brightness.dark;
67
90
- ref.listen(chatProvider, (previous, next) {
91
- final newMessage = previous?.messages.length != next.messages.length;
92
- final newStreaming =
93
- next.isStreaming &&
94
- next.streamingMessage != null &&
95
- next.streamingMessage != previous?.streamingMessage;
96
-
97
- if (newMessage || newStreaming) {
98
- WidgetsBinding.instance.addPostFrameCallback((_) {
99
- if (_scrollController.hasClients) {
100
- if (next.isStreaming) {
101
- _scrollController.jumpTo(
102
- _scrollController.position.maxScrollExtent,
103
- );
104
- } else {
105
- _scrollToBottom();
106
- }
107
- }
108
- });
109
- }
110
- });
68
+ // Autoscroll disabled as per user request
69
+ // ref.listen(chatProvider, (previous, next) { ... });
70
71
return Column(
72
children: [
@@ -152,10 +111,9 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
111
height: 8,
112
decoration: BoxDecoration(
113
shape: BoxShape.circle,
155
- color:
156
- connectionStatus == ConnectionStatus.connected
157
- ? Colors.green
158
- : Colors.grey,
114
+ color: connectionStatus == ConnectionStatus.connected
115
+ ? Colors.green
116
+ : Colors.grey,
117
),
118
),
119
],
@@ -270,7 +228,8 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
228
selectedModel: selectedModel,
229
onModelTap: () => _showModelPicker(context),
230
selectedPersona: ref.watch(selectedPersonaProvider),
273
- onPersonaTap: () => _showPersonaPickerForPreselection(context),
231
+ onPersonaTap: () =>
232
+ _showPersonaPickerForPreselection(context),
233
)
234
: _MessageList(
235
scrollController: _scrollController,
@@ -278,7 +237,7 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
237
streamingMessage: chatState.streamingMessage,
238
isStreaming: chatState.isStreaming,
239
onRetry: (messageId) {
281
- ref.read(chatProvider.notifier).retryLastMessage();
240
+ ref.read(chatProvider.notifier).retryMessage(messageId);
241
},
242
onDelete: (messageId) {
243
ref
@@ -289,75 +248,9 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
248
!chatState.isStreaming &&
249
ref.read(smartRepliesProvider).isNotEmpty,
250
),
292
- if (_showScrollToBottom && chatState.messages.isNotEmpty)
293
- Positioned(
294
- bottom: 20,
295
- left: 0,
296
- right: 0,
297
- child: Center(
298
- child: TweenAnimationBuilder<double>(
299
- tween: Tween(begin: 0.0, end: 1.0),
300
- duration: const Duration(milliseconds: 200),
301
- curve: Curves.easeOutCubic,
302
- builder: (context, value, child) {
303
- return Opacity(
304
- opacity: value,
305
- child: Transform.translate(
306
- offset: Offset(0, 20 * (1 - value)),
307
- child: child,
308
- ),
309
- );
310
- },
311
- child: GestureDetector(
312
- onTap: _scrollToBottom,
313
- child: Container(
314
- padding: const EdgeInsets.symmetric(
315
- horizontal: 12,
316
- vertical: 6,
317
- ),
318
- decoration: BoxDecoration(
319
- color: isDark
320
- ? const Color(0xFF3A3A3A)
321
- : const Color(0xFFE5E5E5),
322
- borderRadius: BorderRadius.circular(16),
323
- boxShadow: [
324
- BoxShadow(
325
- color: Colors.black.withValues(alpha: 0.1),
326
- blurRadius: 8,
327
- spreadRadius: 1,
328
- ),
329
- ],
330
- ),
331
- child: Row(
332
- mainAxisSize: MainAxisSize.min,
333
- children: [
334
- Icon(
335
- Icons.arrow_downward,
336
- size: 14,
337
- color: isDark
338
- ? const Color(0xFF888888)
339
- : const Color(0xFF666666),
340
- ),
341
- const SizedBox(width: 4),
342
- Text(
343
- 'New messages',
344
- style: TextStyle(
345
- fontSize: 12,
346
- color: isDark
347
- ? const Color(0xFF888888)
348
- : const Color(0xFF666666),
349
- ),
350
- ),
351
- ],
352
- ),
353
- ),
354
- ),
355
- ),
356
- ),
357
- ),
251
if (!chatState.isStreaming)
252
Positioned(
360
- bottom: 8,
253
+ bottom: 90,
254
left: 0,
255
right: 0,
256
child: _SmartReplyChips(
@@ -366,18 +259,25 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
259
},
260
),
261
),
262
+ Positioned(
263
+ bottom: 0,
264
+ left: 0,
265
+ right: 0,
266
+ child: ChatInputBar(
267
+ isStreaming: chatState.isStreaming,
268
+ onSend: (message, {attachments}) {
269
+ ref
270
+ .read(chatProvider.notifier)
271
+ .sendMessage(message, attachments: attachments);
272
+ },
273
+ onStop: () {
274
+ ref.read(chatProvider.notifier).cancelStream();
275
+ },
276
+ ),
277
+ ),
278
],
279
),
280
),
372
- ChatInputBar(
373
- isStreaming: chatState.isStreaming,
374
- onSend: (message) {
375
- ref.read(chatProvider.notifier).sendMessage(message);
376
- },
377
- onStop: () {
378
- ref.read(chatProvider.notifier).cancelStream();
379
- },
380
- ),
281
],
282
);
283
}
@@ -626,10 +526,9 @@ class _ChatScreenState extends ConsumerState<ChatScreen> {
526
}
527
528
Widget _buildServerIcon(BuildContext context, Server server) {
629
- final iconData =
630
- server.iconName != null
631
- ? getHugeIconByName(server.iconName)
632
- : getDefaultServerIcon(server.type.name);
529
+ final iconData = server.iconName != null
530
+ ? getHugeIconByName(server.iconName)
531
+ : getDefaultServerIcon(server.type.name);
532
533
if (iconData == null) {
534
return const Icon(Icons.dns, size: 18);
@@ -666,13 +565,19 @@ class _ModelTopBar extends StatelessWidget {
565
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
566
child: Row(
567
mainAxisAlignment: MainAxisAlignment.center,
568
+ mainAxisSize: MainAxisSize.min,
569
children: [
670
- Text(
671
- selectedModel?.displayName ?? 'Select Model',
672
- style: TextStyle(
673
- fontSize: 13,
674
- fontWeight: FontWeight.w500,
675
- color: isDark ? Colors.white70 : Colors.black87,
570
+ Flexible(
571
+ child: Text(
572
+ selectedModel?.displayName ?? 'Select Model',
573
+ style: TextStyle(
574
+ fontSize: 13,
575
+ fontWeight: FontWeight.w500,
576
+ color: isDark ? Colors.white70 : Colors.black87,
577
+ ),
578
+ maxLines: 2,
579
+ textAlign: TextAlign.center,
580
+ overflow: TextOverflow.ellipsis,
581
),
582
),
583
const SizedBox(width: 4),
@@ -1180,9 +1085,10 @@ class _MessageList extends StatelessWidget {
1085
1086
return ListView.builder(
1087
controller: scrollController,
1088
+ cacheExtent: 1000,
1089
padding: EdgeInsets.only(
1090
top: 16,
1185
- bottom: 16 + (hasSmartReplies ? 56 : 0),
1091
+ bottom: 120 + (hasSmartReplies ? 56 : 0),
1092
),
1093
itemCount:
1094
allMessages.length +
@@ -1371,6 +1277,7 @@ class _SmartReplyChipsState extends ConsumerState<_SmartReplyChips>
1277
);
1278
}
1279
}
1280
+
1281
class _PersonaIndicator extends StatelessWidget {
1282
const _PersonaIndicator({
1283
required this.persona,
@@ -1407,10 +1314,7 @@ class _PersonaIndicator extends StatelessWidget {
1314
child: Row(
1315
mainAxisSize: MainAxisSize.min,
1316
children: [
1410
- Text(
1411
- persona.emoji,
1412
- style: const TextStyle(fontSize: 16),
1413
- ),
1317
+ Text(persona.emoji, style: const TextStyle(fontSize: 16)),
1318
const SizedBox(width: 6),
1319
Text(
1320
persona.name,
lib/features/chat/views/components/chat_bubble.dart
+232
-56
@@ -1,9 +1,11 @@
1
+import 'dart:io';
2
import 'package:flutter/material.dart';
3
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
4
import 'package:localmind/core/models/enums.dart';
5
import 'package:localmind/features/chat/data/models/message.dart';
6
import 'package:localmind/features/chat/views/components/code_block.dart';
7
import 'package:localmind/features/chat/views/components/message_action_bar.dart';
8
+import 'package:localmind/features/chat/views/components/processing_indicator.dart';
9
import 'package:localmind/features/chat/views/components/typing_indicator.dart';
10
import 'package:localmind/features/chat/views/components/reasoning_widget.dart';
11
@@ -25,6 +27,12 @@ class ChatBubble extends StatelessWidget {
27
28
@override
29
Widget build(BuildContext context) {
30
+ return RepaintBoundary(
31
+ child: _buildBubble(context),
32
+ );
33
+ }
34
+
35
+ Widget _buildBubble(BuildContext context) {
36
switch (message.role) {
37
case MessageRole.user:
38
return _AnimatedBubble(
@@ -118,6 +126,15 @@ class _UserBubble extends StatelessWidget {
126
child: Column(
127
crossAxisAlignment: CrossAxisAlignment.end,
128
children: [
129
+ if (message.attachmentPaths != null &&
130
+ message.attachmentPaths!.isNotEmpty)
131
+ Padding(
132
+ padding: const EdgeInsets.only(bottom: 8),
133
+ child: _AttachmentList(
134
+ paths: message.attachmentPaths!,
135
+ isUser: true,
136
+ ),
137
+ ),
138
MarkdownBody(
139
data: message.content,
140
styleSheet: MarkdownStyleSheet(
@@ -169,68 +186,80 @@ class _AssistantBubble extends StatelessWidget {
186
final theme = Theme.of(context);
187
final isDark = theme.brightness == Brightness.dark;
188
172
- return Align(
173
- alignment: Alignment.centerLeft,
174
- child: Container(
175
- constraints: BoxConstraints(
176
- maxWidth: MediaQuery.of(context).size.width * 0.8,
177
- ),
178
- margin: const EdgeInsets.only(left: 8, right: 48, top: 4, bottom: 4),
179
- padding: const EdgeInsets.all(12),
180
- decoration: BoxDecoration(
181
- color: isDark ? const Color(0xFF1F1F1F) : const Color(0xFFF5F5F5),
182
- borderRadius: BorderRadius.circular(
183
- 18,
184
- ).copyWith(bottomLeft: const Radius.circular(4)),
185
- border: Border.all(
186
- color: isDark ? const Color(0xFF2A2A2A) : const Color(0xFFE5E5E5),
187
- ),
188
- ),
189
- child: Column(
190
- crossAxisAlignment: CrossAxisAlignment.start,
191
- children: [
192
- if (message.reasoningContent != null &&
193
- message.reasoningContent!.isNotEmpty)
194
- ReasoningWidget(
195
- reasoningContent: message.reasoningContent,
196
- isStreaming: isStreaming,
197
- ),
198
- if (isStreaming && message.content.isEmpty)
199
- const TypingIndicator()
200
- else
201
- _MarkdownContent(content: message.content, isDark: isDark),
202
- if (isStreaming && message.content.isNotEmpty)
203
- const StreamingIndicator(),
204
- const SizedBox(height: 8),
205
- Row(
206
- mainAxisSize: MainAxisSize.min,
207
- children: [
208
- Text(
209
- _formatTime(message.createdAt),
189
+ return Container(
190
+ width: double.infinity,
191
+ margin: const EdgeInsets.symmetric(horizontal: 18, vertical: 8),
192
+ child: Column(
193
+ crossAxisAlignment: CrossAxisAlignment.start,
194
+ children: [
195
+ if (message.reasoningContent != null &&
196
+ message.reasoningContent!.isNotEmpty)
197
+ ReasoningWidget(
198
+ reasoningContent: message.reasoningContent,
199
+ isStreaming: isStreaming,
200
+ ),
201
+ if (isStreaming && message.content.isEmpty && message.isProcessing)
202
+ const ProcessingIndicator()
203
+ else if (isStreaming && message.content.isEmpty)
204
+ const TypingIndicator()
205
+ else
206
+ _MarkdownContent(content: message.content, isDark: isDark),
207
+ if (message.status == MessageStatus.error &&
208
+ message.errorMessage != null &&
209
+ message.errorMessage!.isNotEmpty)
210
+ Padding(
211
+ padding: const EdgeInsets.only(top: 8),
212
+ child: Container(
213
+ padding: const EdgeInsets.all(12),
214
+ decoration: BoxDecoration(
215
+ color: Colors.red.withValues(alpha: 0.1),
216
+ borderRadius: BorderRadius.circular(8),
217
+ border: Border.all(color: Colors.red.withValues(alpha: 0.2)),
218
+ ),
219
+ child: Text(
220
+ message.errorMessage!,
221
style: TextStyle(
211
- fontSize: 11,
212
- color: isDark
213
- ? const Color(0xFF666666)
214
- : const Color(0xFF999999),
222
+ color: Colors.red[400],
223
+ fontSize: 12,
224
+ fontWeight: FontWeight.w500,
225
),
226
),
217
- if (message.status == MessageStatus.error) ...[
218
- const SizedBox(width: 4),
219
- Icon(Icons.error_outline, size: 14, color: Colors.red[400]),
220
- ],
221
- ],
227
+ ),
228
+ ),
229
+ if (isStreaming && message.content.isNotEmpty)
230
+ const Padding(
231
+ padding: EdgeInsets.only(top: 8),
232
+ child: StreamingIndicator(),
233
),
223
- if (!isStreaming && message.status == MessageStatus.complete) ...[
224
- const SizedBox(height: 4),
225
- MessageActionBar(
226
- content: message.content,
227
- onCopy: onCopy,
228
- onRetry: onRetry,
229
- onDelete: onDelete,
234
+ const SizedBox(height: 8),
235
+ Row(
236
+ children: [
237
+ Text(
238
+ _formatTime(message.createdAt),
239
+ style: TextStyle(
240
+ fontSize: 11,
241
+ color: isDark
242
+ ? const Color(0xFF666666)
243
+ : const Color(0xFF999999),
244
+ ),
245
),
246
+ if (message.status == MessageStatus.error) ...[
247
+ const SizedBox(width: 4),
248
+ Icon(Icons.error_outline, size: 14, color: Colors.red[400]),
249
+ ],
250
+ const Spacer(),
251
+ if (!isStreaming &&
252
+ (message.status == MessageStatus.complete ||
253
+ message.status == MessageStatus.error))
254
+ MessageActionBar(
255
+ content: message.content,
256
+ onCopy: onCopy,
257
+ onRetry: onRetry,
258
+ onDelete: onDelete,
259
+ ),
260
],
232
- ],
233
- ),
261
+ ),
262
+ ],
263
),
264
);
265
}
@@ -442,6 +471,153 @@ class _ToolBubble extends StatelessWidget {
471
}
472
}
473
474
+class _AttachmentList extends StatelessWidget {
475
+ const _AttachmentList({required this.paths, required this.isUser});
476
+
477
+ final List<String> paths;
478
+ final bool isUser;
479
+
480
+ @override
481
+ Widget build(BuildContext context) {
482
+ return Wrap(
483
+ spacing: 8,
484
+ runSpacing: 8,
485
+ children: paths.map((path) => _AttachmentItem(path: path)).toList(),
486
+ );
487
+ }
488
+}
489
+
490
+class _AttachmentItem extends StatelessWidget {
491
+ const _AttachmentItem({required this.path});
492
+
493
+ final String path;
494
+
495
+ bool _isImage(String path) {
496
+ final mime = path.toLowerCase();
497
+ return mime.endsWith('.jpg') ||
498
+ mime.endsWith('.jpeg') ||
499
+ mime.endsWith('.png') ||
500
+ mime.endsWith('.gif') ||
501
+ mime.endsWith('.webp');
502
+ }
503
+
504
+ void _viewImage(BuildContext context) {
505
+ showDialog(
506
+ context: context,
507
+ useSafeArea: false,
508
+ builder: (context) => _ImageViewer(path: path),
509
+ );
510
+ }
511
+
512
+ @override
513
+ Widget build(BuildContext context) {
514
+ final theme = Theme.of(context);
515
+ final isDark = theme.brightness == Brightness.dark;
516
+ final file = File(path);
517
+ final fileName = path.split('/').last;
518
+
519
+ if (_isImage(path)) {
520
+ return GestureDetector(
521
+ onTap: () => _viewImage(context),
522
+ child: ClipRRect(
523
+ borderRadius: BorderRadius.circular(12),
524
+ child: Image.file(
525
+ file,
526
+ width: 120,
527
+ height: 120,
528
+ fit: BoxFit.cover,
529
+ errorBuilder: (context, error, stackTrace) => _FilePlaceholder(fileName: fileName),
530
+ ),
531
+ ),
532
+ );
533
+ }
534
+
535
+ return Container(
536
+ width: 160,
537
+ padding: const EdgeInsets.all(12),
538
+ decoration: BoxDecoration(
539
+ color: isDark ? const Color(0xFF2A2A2A) : const Color(0xFFF0F0F0),
540
+ borderRadius: BorderRadius.circular(12),
541
+ border: Border.all(
542
+ color: isDark ? const Color(0xFF3A3A3A) : const Color(0xFFE5E5E5),
543
+ ),
544
+ ),
545
+ child: Row(
546
+ mainAxisSize: MainAxisSize.min,
547
+ children: [
548
+ Icon(
549
+ Icons.insert_drive_file_outlined,
550
+ size: 20,
551
+ color: isDark ? const Color(0xFF888888) : const Color(0xFF666666),
552
+ ),
553
+ const SizedBox(width: 8),
554
+ Expanded(
555
+ child: Text(
556
+ fileName,
557
+ style: TextStyle(
558
+ fontSize: 12,
559
+ color: isDark ? Colors.white70 : Colors.black87,
560
+ ),
561
+ maxLines: 1,
562
+ overflow: TextOverflow.ellipsis,
563
+ ),
564
+ ),
565
+ ],
566
+ ),
567
+ );
568
+ }
569
+}
570
+
571
+class _FilePlaceholder extends StatelessWidget {
572
+ const _FilePlaceholder({required this.fileName});
573
+ final String fileName;
574
+
575
+ @override
576
+ Widget build(BuildContext context) {
577
+ return Container(
578
+ width: 120,
579
+ height: 120,
580
+ color: Colors.grey[800],
581
+ child: Center(
582
+ child: Text(
583
+ fileName.split('.').last.toUpperCase(),
584
+ style: const TextStyle(
585
+ color: Colors.white,
586
+ fontWeight: FontWeight.bold,
587
+ ),
588
+ ),
589
+ ),
590
+ );
591
+ }
592
+}
593
+
594
+class _ImageViewer extends StatelessWidget {
595
+ const _ImageViewer({required this.path});
596
+ final String path;
597
+
598
+ @override
599
+ Widget build(BuildContext context) {
600
+ return Scaffold(
601
+ backgroundColor: Colors.black,
602
+ appBar: AppBar(
603
+ backgroundColor: Colors.transparent,
604
+ elevation: 0,
605
+ leading: IconButton(
606
+ icon: const Icon(Icons.close, color: Colors.white),
607
+ onPressed: () => Navigator.pop(context),
608
+ ),
609
+ ),
610
+ body: Center(
611
+ child: InteractiveViewer(
612
+ minScale: 0.5,
613
+ maxScale: 4.0,
614
+ child: Image.file(File(path)),
615
+ ),
616
+ ),
617
+ );
618
+ }
619
+}
620
+
621
String _formatTime(DateTime dateTime) {
622
final hour = dateTime.hour.toString().padLeft(2, '0');
623
final minute = dateTime.minute.toString().padLeft(2, '0');
lib/features/chat/views/components/chat_input_bar.dart
+138
-229
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
3
import 'package:flutter_riverpod/flutter_riverpod.dart';
4
import 'package:haptic_feedback/haptic_feedback.dart';
5
import 'package:file_picker/file_picker.dart';
6
+import 'package:hugeicons/hugeicons.dart';
7
import 'package:localmind/core/models/enums.dart';
8
import 'package:localmind/features/servers/providers/server_providers.dart';
9
@@ -16,9 +17,9 @@ class ChatInputBar extends ConsumerStatefulWidget {
17
this.isStreaming = false,
18
});
19
19
- final void Function(String message) onSend;
20
+ final void Function(String message, {List<File>? attachments}) onSend;
21
final VoidCallback onStop;
21
- final void Function(List<File> onAttach)? onAttach;
22
+ final void Function(List<File> attachments)? onAttach;
23
final bool enabled;
24
final bool isStreaming;
25
@@ -30,7 +31,6 @@ class _ChatInputBarState extends ConsumerState<ChatInputBar>
31
with SingleTickerProviderStateMixin {
32
final _controller = TextEditingController();
33
final _focusNode = FocusNode();
33
- bool _isComposing = false;
34
final List<File> _attachedFiles = [];
35
late AnimationController _sendButtonAnimController;
36
late Animation<double> _sendButtonScale;
@@ -72,12 +72,14 @@ class _ChatInputBarState extends ConsumerState<ChatInputBar>
72
73
void _handleSubmit() {
74
final text = _controller.text.trim();
75
- if (text.isEmpty) return;
75
+ if (text.isEmpty && _attachedFiles.isEmpty) return;
76
77
Haptics.vibrate(HapticsType.medium);
78
- widget.onSend(text);
78
+ widget.onSend(text, attachments: List.from(_attachedFiles));
79
_controller.clear();
80
- setState(() => _isComposing = false);
80
+ setState(() {
81
+ _attachedFiles.clear();
82
+ });
83
}
84
85
void _handleStop() {
@@ -85,7 +87,11 @@ class _ChatInputBarState extends ConsumerState<ChatInputBar>
87
widget.onStop();
88
}
89
88
- Widget _buildSendButton(bool canSend, bool isDark) {
90
+ Widget _buildActionButton(bool canSend, bool isDark) {
91
+ // Action button theme from image: circular, high contrast (black in light, white in dark)
92
+ final backgroundColor = isDark ? Colors.white : Colors.black;
93
+ final iconColor = isDark ? Colors.black : Colors.white;
94
+
95
return GestureDetector(
96
onTapDown: canSend ? (_) => _sendButtonAnimController.forward() : null,
97
onTapUp: canSend ? (_) => _sendButtonAnimController.reverse() : null,
@@ -95,24 +101,13 @@ class _ChatInputBarState extends ConsumerState<ChatInputBar>
101
child: AnimatedContainer(
102
duration: const Duration(milliseconds: 200),
103
curve: Curves.easeOut,
104
+ width: 44,
105
+ height: 44,
106
decoration: BoxDecoration(
99
- color: canSend
100
- ? (isDark ? const Color(0xFF3B82F6) : const Color(0xFF2563EB))
101
- : (isDark ? const Color(0xFF3A3A3A) : const Color(0xFFE5E5E5)),
107
+ color: backgroundColor.withValues(
108
+ alpha: (canSend || widget.isStreaming) ? 1.0 : 0.2,
109
+ ),
110
shape: BoxShape.circle,
103
- boxShadow: canSend
104
- ? [
105
- BoxShadow(
106
- color:
107
- (isDark
108
- ? const Color(0xFF3B82F6)
109
- : const Color(0xFF2563EB))
110
- .withValues(alpha: 0.3),
111
- blurRadius: 8,
112
- spreadRadius: 2,
113
- ),
114
- ]
115
- : null,
111
),
112
child: IconButton(
113
icon: AnimatedSwitcher(
@@ -120,55 +115,30 @@ class _ChatInputBarState extends ConsumerState<ChatInputBar>
115
transitionBuilder: (child, animation) {
116
return ScaleTransition(scale: animation, child: child);
117
},
123
- child: Icon(
124
- Icons.arrow_upward,
125
- key: ValueKey(canSend),
126
- color: canSend
127
- ? Colors.white
128
- : (isDark
129
- ? const Color(0xFF666666)
130
- : const Color(0xFF999999)),
131
- ),
118
+ child: widget.isStreaming
119
+ ? HugeIcon(
120
+ icon: HugeIcons.strokeRoundedStop,
121
+ key: const ValueKey('stop'),
122
+ color: iconColor,
123
+ size: 20,
124
+ )
125
+ : HugeIcon(
126
+ icon: HugeIcons.strokeRoundedArrowUp01,
127
+ key: ValueKey(canSend),
128
+ color: iconColor,
129
+ size: 20,
130
+ ),
131
),
133
- onPressed: canSend ? _handleSubmit : null,
134
- tooltip: 'Send message',
132
+ onPressed: widget.isStreaming
133
+ ? _handleStop
134
+ : (canSend ? _handleSubmit : null),
135
+ tooltip: widget.isStreaming ? 'Stop generation' : 'Send message',
136
),
137
),
138
),
139
);
140
}
141
141
- Widget _buildStopButton() {
142
- return TweenAnimationBuilder<double>(
143
- tween: Tween(begin: 0.8, end: 1.0),
144
- duration: const Duration(milliseconds: 200),
145
- curve: Curves.elasticOut,
146
- builder: (context, scale, child) {
147
- return Transform.scale(
148
- scale: scale,
149
- child: Container(
150
- decoration: BoxDecoration(
151
- color: Colors.red[400],
152
- shape: BoxShape.circle,
153
- boxShadow: [
154
- BoxShadow(
155
- color: Colors.red.withValues(alpha: 0.3),
156
- blurRadius: 8,
157
- spreadRadius: 2,
158
- ),
159
- ],
160
- ),
161
- child: IconButton(
162
- icon: const Icon(Icons.stop, color: Colors.white),
163
- onPressed: _handleStop,
164
- tooltip: 'Stop generating',
165
- ),
166
- ),
167
- );
168
- },
169
- );
170
- }
171
-
142
@override
143
Widget build(BuildContext context) {
144
final theme = Theme.of(context);
@@ -179,215 +149,154 @@ class _ChatInputBarState extends ConsumerState<ChatInputBar>
149
final canSend =
150
widget.enabled &&
151
isConnected &&
182
- _controller.text.trim().isNotEmpty &&
152
+ (_controller.text.trim().isNotEmpty || _attachedFiles.isNotEmpty) &&
153
!widget.isStreaming;
154
155
return Container(
186
- padding: EdgeInsets.only(
187
- left: 12,
188
- right: 12,
189
- top: 8,
190
- bottom: MediaQuery.of(context).padding.bottom + 8,
191
- ),
156
+ margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
157
+ padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
158
decoration: BoxDecoration(
193
- color: isDark ? const Color(0xFF0A0A0A) : const Color(0xFFFAFAFA),
194
- border: Border(
195
- top: BorderSide(
196
- color: isDark ? const Color(0xFF2A2A2A) : const Color(0xFFE5E5E5),
159
+ color: isDark ? const Color(0xFF1C1C1C) : Colors.white,
160
+ borderRadius: BorderRadius.circular(40),
161
+ boxShadow: [
162
+ BoxShadow(
163
+ color: Colors.black.withValues(alpha: isDark ? 0.3 : 0.08),
164
+ blurRadius: 20,
165
+ offset: const Offset(0, 4),
166
),
167
+ ],
168
+ border: Border.all(
169
+ color: isDark ? const Color(0xFF2E2E2E) : const Color(0xFFE5E5E5),
170
+ width: 1.2,
171
),
172
),
173
child: Column(
174
mainAxisSize: MainAxisSize.min,
175
children: [
176
+ // Attachment Preview Area
177
AnimatedSize(
178
duration: const Duration(milliseconds: 200),
179
curve: Curves.easeOut,
180
child: _attachedFiles.isEmpty
181
? const SizedBox.shrink()
182
: Container(
209
- height: 60,
210
- padding: const EdgeInsets.symmetric(
211
- horizontal: 8,
212
- vertical: 4,
183
+ height: 70,
184
+ padding: const EdgeInsets.only(
185
+ left: 12,
186
+ right: 12,
187
+ top: 8,
188
+ bottom: 4,
189
),
190
child: ListView.separated(
191
scrollDirection: Axis.horizontal,
192
itemCount: _attachedFiles.length,
217
- separatorBuilder: (_, _) => const SizedBox(width: 6),
193
+ separatorBuilder: (_, _) => const SizedBox(width: 8),
194
itemBuilder: (context, index) {
195
final file = _attachedFiles[index];
220
- return TweenAnimationBuilder<double>(
221
- tween: Tween(begin: 0.0, end: 1.0),
222
- duration: Duration(milliseconds: 200 + (index * 50)),
223
- curve: Curves.easeOut,
224
- builder: (context, value, child) {
225
- return Opacity(
226
- opacity: value,
227
- child: Transform.scale(
228
- scale: 0.8 + (0.2 * value),
229
- child: child,
196
+ return Stack(
197
+ clipBehavior: Clip.none,
198
+ children: [
199
+ Container(
200
+ decoration: BoxDecoration(
201
+ borderRadius: BorderRadius.circular(12),
202
+ border: Border.all(
203
+ color: isDark
204
+ ? const Color(0xFF333333)
205
+ : const Color(0xFFE0E0E0),
206
+ ),
207
),
231
- );
232
- },
233
- child: Stack(
234
- children: [
235
- ClipRRect(
236
- borderRadius: BorderRadius.circular(8),
208
+ child: ClipRRect(
209
+ borderRadius: BorderRadius.circular(11),
210
child: Image.file(
211
file,
239
- width: 52,
240
- height: 52,
212
+ width: 48,
213
+ height: 48,
214
fit: BoxFit.cover,
215
),
216
),
244
- Positioned(
245
- top: 0,
246
- right: 0,
247
- child: GestureDetector(
248
- onTap: () => setState(
249
- () => _attachedFiles.removeAt(index),
217
+ ),
218
+ Positioned(
219
+ top: -4,
220
+ right: -4,
221
+ child: GestureDetector(
222
+ onTap: () => setState(
223
+ () => _attachedFiles.removeAt(index),
224
+ ),
225
+ child: Container(
226
+ width: 18,
227
+ height: 18,
228
+ decoration: const BoxDecoration(
229
+ color: Colors.black,
230
+ shape: BoxShape.circle,
231
),
251
- child: Container(
252
- width: 18,
253
- height: 18,
254
- decoration: BoxDecoration(
255
- color: Colors.black54,
256
- shape: BoxShape.circle,
257
- ),
258
- child: const Icon(
259
- Icons.close,
260
- size: 12,
261
- color: Colors.white,
262
- ),
232
+ child: const Icon(
233
+ Icons.close,
234
+ size: 10,
235
+ color: Colors.white,
236
),
237
),
238
),
266
- ],
267
- ),
239
+ ),
240
+ ],
241
);
242
},
243
),
244
),
245
),
246
+ // Input Main Bar
247
Row(
274
- crossAxisAlignment: CrossAxisAlignment.end,
248
children: [
249
+ // Add Attachment Button
250
+ IconButton(
251
+ icon: HugeIcon(
252
+ icon: HugeIcons.strokeRoundedPlusSign,
253
+ color: isDark ? Colors.white70 : Colors.black87,
254
+ size: 22,
255
+ ),
256
+ onPressed: isConnected ? _handleAttach : null,
257
+ tooltip: 'Attach images',
258
+ ),
259
+ const SizedBox(width: 4),
260
+ // Text Field
261
Expanded(
277
- child: Container(
278
- constraints: const BoxConstraints(maxHeight: 150),
279
- decoration: BoxDecoration(
280
- color: isDark
281
- ? const Color(0xFF1F1F1F)
282
- : const Color(0xFFFFFFFF),
283
- borderRadius: BorderRadius.circular(24),
284
- border: Border.all(
285
- color: isDark
286
- ? const Color(0xFF333333)
287
- : const Color(0xFFE5E5E5),
288
- ),
262
+ child: TextField(
263
+ controller: _controller,
264
+ focusNode: _focusNode,
265
+ enabled: widget.enabled,
266
+ maxLines: 5,
267
+ minLines: 1,
268
+ textInputAction: TextInputAction.newline,
269
+ keyboardType: TextInputType.multiline,
270
+ onChanged: (text) {
271
+ setState(() {});
272
+ },
273
+ style: TextStyle(
274
+ fontSize: 15,
275
+ color: isDark ? Colors.white : Colors.black87,
276
),
290
- child: Row(
291
- crossAxisAlignment: CrossAxisAlignment.end,
292
- children: [
293
- IconButton(
294
- icon: Icon(
295
- Icons.attach_file,
296
- size: 20,
297
- color: isDark
298
- ? const Color(0xFF888888)
299
- : const Color(0xFF666666),
300
- ),
301
- onPressed: isConnected ? _handleAttach : null,
302
- tooltip: 'Attach image',
303
- ),
304
- Expanded(
305
- child: TextField(
306
- controller: _controller,
307
- focusNode: _focusNode,
308
- enabled: widget.enabled,
309
- maxLines: null,
310
- textInputAction: TextInputAction.newline,
311
- keyboardType: TextInputType.multiline,
312
- onChanged: (text) {
313
- setState(
314
- () => _isComposing = text.trim().isNotEmpty,
315
- );
316
- },
317
- onSubmitted: (_) => _handleSubmit(),
318
- decoration: InputDecoration(
319
- hintText: 'Message LocalMind...',
320
- hintStyle: TextStyle(
321
- color: isDark
322
- ? const Color(0xFF666666)
323
- : const Color(0xFF999999),
324
- ),
325
- border: InputBorder.none,
326
- contentPadding: const EdgeInsets.symmetric(
327
- horizontal: 8,
328
- vertical: 12,
329
- ),
330
- ),
331
- ),
332
- ),
333
- ValueListenableBuilder<TextEditingValue>(
334
- valueListenable: _controller,
335
- builder: (context, value, child) {
336
- final showCounter = _isComposing && value.text.length >= 300;
337
- return AnimatedSize(
338
- duration: const Duration(milliseconds: 150),
339
- curve: Curves.easeOut,
340
- child: AnimatedOpacity(
341
- opacity: showCounter ? 1.0 : 0.0,
342
- duration: const Duration(milliseconds: 150),
343
- child: showCounter
344
- ? Padding(
345
- padding: const EdgeInsets.only(
346
- right: 12,
347
- bottom: 12,
348
- ),
349
- child: TweenAnimationBuilder<double>(
350
- tween: Tween(begin: 0.5, end: 1.0),
351
- duration: const Duration(milliseconds: 150),
352
- builder: (context, scale, child) {
353
- return Transform.scale(
354
- scale: scale,
355
- child: child,
356
- );
357
- },
358
- child: Text(
359
- '${value.text.length}',
360
- style: TextStyle(
361
- fontSize: 11,
362
- color: isDark
363
- ? const Color(0xFF666666)
364
- : const Color(0xFF999999),
365
- ),
366
- ),
367
- ),
368
- )
369
- : const SizedBox(width: 0),
370
- ),
371
- );
372
- },
373
- ),
374
- ],
277
+ decoration: InputDecoration(
278
+ hintText: 'Reply',
279
+ hintStyle: TextStyle(
280
+ color: isDark ? Colors.white38 : Colors.black38,
281
+ fontSize: 15,
282
+ fontWeight: FontWeight.w400,
283
+ ),
284
+ border: InputBorder.none,
285
+ isDense: true,
286
+ contentPadding: const EdgeInsets.symmetric(vertical: 12),
287
),
288
),
289
),
290
const SizedBox(width: 8),
379
- AnimatedSwitcher(
380
- duration: const Duration(milliseconds: 200),
381
- transitionBuilder: (child, animation) {
382
- return ScaleTransition(
383
- scale: animation,
384
- child: FadeTransition(opacity: animation, child: child),
385
- );
386
- },
387
- child: widget.isStreaming
388
- ? _buildStopButton()
389
- : _buildSendButton(canSend, isDark),
390
- ),
291
+ // Mic Icon (Visual only as per image)
292
+ // HugeIcon(
293
+ // icon: HugeIcons.strokeRoundedMic01,
294
+ // color: isDark ? Colors.white38 : Colors.black38,
295
+ // size: 22,
296
+ // ),
297
+ // const SizedBox(width: 8),
298
+ // Action Button (Send/Stop)
299
+ _buildActionButton(canSend, isDark),
300
],
301
),
302
],
lib/features/chat/views/components/code_block.dart
+2
-1
@@ -87,7 +87,8 @@ class CodeBlock extends ConsumerWidget {
87
final theme = Theme.of(context);
88
final isDark = theme.brightness == Brightness.dark;
89
final settings = ref.watch(settingsProvider);
90
- final codeTheme = _getSyntaxTheme(settings.codeTheme);
90
+ final themeName = isDark ? settings.codeThemeDark : settings.codeThemeLight;
91
+ final codeTheme = _getSyntaxTheme(themeName);
92
93
final backgroundColor = isDark
94
? const Color(0xFF1E1E1E)
lib/features/chat/views/components/processing_indicator.dart
new
+35
@@ -0,0 +1,35 @@
1
+import 'package:flutter/material.dart';
2
+
3
+class ProcessingIndicator extends StatelessWidget {
4
+ const ProcessingIndicator({super.key});
5
+
6
+ @override
7
+ Widget build(BuildContext context) {
8
+ final isDark = Theme.of(context).brightness == Brightness.dark;
9
+
10
+ return Row(
11
+ mainAxisSize: MainAxisSize.min,
12
+ children: [
13
+ SizedBox(
14
+ width: 12,
15
+ height: 12,
16
+ child: CircularProgressIndicator(
17
+ strokeWidth: 2,
18
+ valueColor: AlwaysStoppedAnimation<Color>(
19
+ isDark ? Colors.grey[400]! : Colors.grey[600]!,
20
+ ),
21
+ ),
22
+ ),
23
+ const SizedBox(width: 8),
24
+ Text(
25
+ 'Processing...',
26
+ style: TextStyle(
27
+ color: isDark ? Colors.grey[400] : Colors.grey[600],
28
+ fontSize: 14,
29
+ fontStyle: FontStyle.italic,
30
+ ),
31
+ ),
32
+ ],
33
+ );
34
+ }
35
+}
lib/features/settings/data/models/app_settings.dart
+10
-4
@@ -80,7 +80,10 @@ class AppSettings extends HiveObject {
80
final bool mcpEnabled;
81
82
@HiveField(16, defaultValue: SyntaxThemeName.vscodeDark)
83
- final SyntaxThemeName codeTheme;
83
+ final SyntaxThemeName codeThemeDark;
84
+
85
+ @HiveField(17, defaultValue: SyntaxThemeName.vscodeLight)
86
+ final SyntaxThemeName codeThemeLight;
87
88
AppSettings({
89
this.temperature = 0.7,
@@ -99,7 +102,8 @@ class AppSettings extends HiveObject {
102
this.defaultPersonaId,
103
this.hasCompletedOnboarding = false,
104
this.mcpEnabled = true,
102
- this.codeTheme = SyntaxThemeName.vscodeDark,
105
+ this.codeThemeDark = SyntaxThemeName.vscodeDark,
106
+ this.codeThemeLight = SyntaxThemeName.vscodeLight,
107
});
108
109
AppSettings copyWith({
@@ -119,7 +123,8 @@ class AppSettings extends HiveObject {
123
String? defaultPersonaId,
124
bool? hasCompletedOnboarding,
125
bool? mcpEnabled,
122
- SyntaxThemeName? codeTheme,
126
+ SyntaxThemeName? codeThemeDark,
127
+ SyntaxThemeName? codeThemeLight,
128
}) {
129
return AppSettings(
130
temperature: temperature ?? this.temperature,
@@ -140,7 +145,8 @@ class AppSettings extends HiveObject {
145
hasCompletedOnboarding:
146
hasCompletedOnboarding ?? this.hasCompletedOnboarding,
147
mcpEnabled: mcpEnabled ?? this.mcpEnabled,
143
- codeTheme: codeTheme ?? this.codeTheme,
148
+ codeThemeDark: codeThemeDark ?? this.codeThemeDark,
149
+ codeThemeLight: codeThemeLight ?? this.codeThemeLight,
150
);
151
}
152
}
lib/features/settings/data/models/app_settings.g.dart
+8
-3
@@ -33,16 +33,19 @@ class AppSettingsAdapter extends TypeAdapter<AppSettings> {
33
defaultPersonaId: fields[13] as String?,
34
hasCompletedOnboarding: fields[14] == null ? false : fields[14] as bool,
35
mcpEnabled: fields[15] == null ? true : fields[15] as bool,
36
- codeTheme: fields[16] == null
36
+ codeThemeDark: fields[16] == null
37
? SyntaxThemeName.vscodeDark
38
: fields[16] as SyntaxThemeName,
39
+ codeThemeLight: fields[17] == null
40
+ ? SyntaxThemeName.vscodeLight
41
+ : fields[17] as SyntaxThemeName,
42
);
43
}
44
45
@override
46
void write(BinaryWriter writer, AppSettings obj) {
47
writer
45
- ..writeByte(17)
48
+ ..writeByte(18)
49
..writeByte(0)
50
..write(obj.temperature)
51
..writeByte(1)
@@ -76,7 +79,9 @@ class AppSettingsAdapter extends TypeAdapter<AppSettings> {
79
..writeByte(15)
80
..write(obj.mcpEnabled)
81
..writeByte(16)
79
- ..write(obj.codeTheme);
82
+ ..write(obj.codeThemeDark)
83
+ ..writeByte(17)
84
+ ..write(obj.codeThemeLight);
85
}
86
87
@override
lib/features/settings/screens/settings_screen.dart
+13
-3
@@ -84,9 +84,17 @@ class SettingsScreen extends ConsumerWidget {
84
previewText: 'The quick brown fox jumps over the lazy dog.',
85
),
86
_CodeThemeDropdown(
87
- current: settings.codeTheme,
87
+ label: 'Code Theme (Dark)',
88
+ current: settings.codeThemeDark,
89
onChanged: (v) =>
89
- ref.read(settingsProvider.notifier).setCodeTheme(v),
90
+ ref.read(settingsProvider.notifier).setCodeThemeDark(v),
91
+ isDark: isDark,
92
+ ),
93
+ _CodeThemeDropdown(
94
+ label: 'Code Theme (Light)',
95
+ current: settings.codeThemeLight,
96
+ onChanged: (v) =>
97
+ ref.read(settingsProvider.notifier).setCodeThemeLight(v),
98
isDark: isDark,
99
),
100
const Divider(height: 32),
@@ -723,11 +731,13 @@ class _DropdownSetting extends StatelessWidget {
731
732
class _CodeThemeDropdown extends StatelessWidget {
733
const _CodeThemeDropdown({
734
+ required this.label,
735
required this.current,
736
required this.onChanged,
737
required this.isDark,
738
});
739
740
+ final String label;
741
final SyntaxThemeName current;
742
final ValueChanged<SyntaxThemeName> onChanged;
743
final bool isDark;
@@ -767,7 +777,7 @@ class _CodeThemeDropdown extends StatelessWidget {
777
crossAxisAlignment: CrossAxisAlignment.start,
778
children: [
779
Text(
770
- 'Code Theme',
780
+ label,
781
style: TextStyle(
782
fontSize: 14,
783
fontWeight: FontWeight.w500,
pubspec.lock
+8
@@ -581,6 +581,14 @@ packages:
581
url: "https://pub.dev"
582
source: hosted
583
version: "6.1.0"
584
+ logger:
585
+ dependency: "direct main"
586
+ description:
587
+ name: logger
588
+ sha256: "25aee487596a6257655a1e091ec2ae66bc30e7af663592cc3a27e6591e05035c"
589
+ url: "https://pub.dev"
590
+ source: hosted
591
+ version: "2.7.0"
592
logging:
593
dependency: transitive
594
description:
pubspec.yaml
+1
@@ -49,6 +49,7 @@ dependencies:
49
google_fonts: ^6.2.1
50
flutter_syntax_view: 4.1.7
51
animations: 2.1.2
52
+ logger: ^2.7.0
53
54
dev_dependencies:
55
flutter_test: