sshinmen Claude commited on
Commit
cabd4fb
·
1 Parent(s): 84aaa89

Deploy to HF Spaces with updated translators and executors

Browse files

- Update antigravity translators (Claude and OpenAI formats)
- Update gemini-cli OpenAI chat completion translator
- Update gemini OpenAI chat completion translator with schema support
- Update Claude and Kiro executors
- Update OpenAI handlers
- Add gemini schema utilities and tests
- Clean up cache files and ignore management-center

Co-Authored-By: Claude <noreply@anthropic.com>

.factory/settings.json CHANGED
@@ -29,8 +29,8 @@
29
  "provider": "generic-chat-completion-api"
30
  },
31
  {
32
- "model": "gpt-5.1-codex",
33
- "displayName": "gpt-5.1-codex",
34
  "baseUrl": "https://shimen-cliproxyapi.hf.space/v1",
35
  "apiKey": "shin",
36
  "provider": "generic-chat-completion-api"
@@ -52,8 +52,8 @@
52
  {
53
  "model": "gemini-3-pro-preview",
54
  "displayName": "Gemini 3 Pro [Google]",
55
- "baseUrl": "https://generativelanguage.googleapis.com/v1beta/",
56
- "apiKey": "AIzaSyCc9zZOS82GvtbQQFItyHNbQwc7zzxFkN0",
57
  "provider": "generic-chat-completion-api"
58
  },
59
  {
 
29
  "provider": "generic-chat-completion-api"
30
  },
31
  {
32
+ "model": "kimi-k2.5",
33
+ "displayName": "kimi-k2.5",
34
  "baseUrl": "https://shimen-cliproxyapi.hf.space/v1",
35
  "apiKey": "shin",
36
  "provider": "generic-chat-completion-api"
 
52
  {
53
  "model": "gemini-3-pro-preview",
54
  "displayName": "Gemini 3 Pro [Google]",
55
+ "baseUrl": "https://shimen-api.hf.space/v1",
56
+ "apiKey": "default-key",
57
  "provider": "generic-chat-completion-api"
58
  },
59
  {
.gitignore CHANGED
@@ -51,3 +51,6 @@ _bmad-output/*
51
  ._*
52
  shin
53
  server
 
 
 
 
51
  ._*
52
  shin
53
  server
54
+
55
+ # Management center (separate repo)
56
+ management-center/
internal/registry/model_definitions_static_data.go CHANGED
@@ -111,8 +111,8 @@ func GetGeminiModels() []*ModelInfo {
111
  Version: "2.5",
112
  DisplayName: "Gemini 2.5 Pro",
113
  Description: "Stable release (June 17th, 2025) of Gemini 2.5 Pro",
114
- InputTokenLimit: 1048576,
115
- OutputTokenLimit: 65536,
116
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
117
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true},
118
  },
@@ -126,8 +126,8 @@ func GetGeminiModels() []*ModelInfo {
126
  Version: "001",
127
  DisplayName: "Gemini 2.5 Flash",
128
  Description: "Stable version of Gemini 2.5 Flash, our mid-size multimodal model that supports up to 1 million tokens, released in June of 2025.",
129
- InputTokenLimit: 1048576,
130
- OutputTokenLimit: 65536,
131
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
132
  Thinking: &ThinkingSupport{Min: 0, Max: 24576, ZeroAllowed: true, DynamicAllowed: true},
133
  },
@@ -141,8 +141,8 @@ func GetGeminiModels() []*ModelInfo {
141
  Version: "2.5",
142
  DisplayName: "Gemini 2.5 Flash Lite",
143
  Description: "Our smallest and most cost effective model, built for at scale usage.",
144
- InputTokenLimit: 1048576,
145
- OutputTokenLimit: 65536,
146
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
147
  Thinking: &ThinkingSupport{Min: 0, Max: 24576, ZeroAllowed: true, DynamicAllowed: true},
148
  },
@@ -156,8 +156,8 @@ func GetGeminiModels() []*ModelInfo {
156
  Version: "3.0",
157
  DisplayName: "Gemini 3 Pro Preview",
158
  Description: "Gemini 3 Pro Preview",
159
- InputTokenLimit: 1048576,
160
- OutputTokenLimit: 65536,
161
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
162
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true, Levels: []string{"low", "high"}},
163
  },
@@ -171,8 +171,8 @@ func GetGeminiModels() []*ModelInfo {
171
  Version: "3.0",
172
  DisplayName: "Gemini 3 Flash Preview",
173
  Description: "Gemini 3 Flash Preview",
174
- InputTokenLimit: 1048576,
175
- OutputTokenLimit: 65536,
176
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
177
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true, Levels: []string{"minimal", "low", "medium", "high"}},
178
  },
@@ -186,8 +186,8 @@ func GetGeminiModels() []*ModelInfo {
186
  Version: "3.0",
187
  DisplayName: "Gemini 3 Pro Image Preview",
188
  Description: "Gemini 3 Pro Image Preview",
189
- InputTokenLimit: 1048576,
190
- OutputTokenLimit: 65536,
191
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
192
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true, Levels: []string{"low", "high"}},
193
  },
@@ -206,8 +206,8 @@ func GetGeminiVertexModels() []*ModelInfo {
206
  Version: "2.5",
207
  DisplayName: "Gemini 2.5 Pro",
208
  Description: "Stable release (June 17th, 2025) of Gemini 2.5 Pro",
209
- InputTokenLimit: 1048576,
210
- OutputTokenLimit: 65536,
211
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
212
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true},
213
  },
@@ -221,8 +221,8 @@ func GetGeminiVertexModels() []*ModelInfo {
221
  Version: "001",
222
  DisplayName: "Gemini 2.5 Flash",
223
  Description: "Stable version of Gemini 2.5 Flash, our mid-size multimodal model that supports up to 1 million tokens, released in June of 2025.",
224
- InputTokenLimit: 1048576,
225
- OutputTokenLimit: 65536,
226
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
227
  Thinking: &ThinkingSupport{Min: 0, Max: 24576, ZeroAllowed: true, DynamicAllowed: true},
228
  },
@@ -236,8 +236,8 @@ func GetGeminiVertexModels() []*ModelInfo {
236
  Version: "2.5",
237
  DisplayName: "Gemini 2.5 Flash Lite",
238
  Description: "Our smallest and most cost effective model, built for at scale usage.",
239
- InputTokenLimit: 1048576,
240
- OutputTokenLimit: 65536,
241
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
242
  Thinking: &ThinkingSupport{Min: 0, Max: 24576, ZeroAllowed: true, DynamicAllowed: true},
243
  },
@@ -251,8 +251,8 @@ func GetGeminiVertexModels() []*ModelInfo {
251
  Version: "3.0",
252
  DisplayName: "Gemini 3 Pro Preview",
253
  Description: "Gemini 3 Pro Preview",
254
- InputTokenLimit: 1048576,
255
- OutputTokenLimit: 65536,
256
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
257
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true, Levels: []string{"low", "high"}},
258
  },
@@ -266,8 +266,8 @@ func GetGeminiVertexModels() []*ModelInfo {
266
  Version: "3.0",
267
  DisplayName: "Gemini 3 Flash Preview",
268
  Description: "Our most intelligent model built for speed, combining frontier intelligence with superior search and grounding.",
269
- InputTokenLimit: 1048576,
270
- OutputTokenLimit: 65536,
271
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
272
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true, Levels: []string{"minimal", "low", "medium", "high"}},
273
  },
@@ -281,8 +281,8 @@ func GetGeminiVertexModels() []*ModelInfo {
281
  Version: "3.0",
282
  DisplayName: "Gemini 3 Pro Image Preview",
283
  Description: "Gemini 3 Pro Image Preview",
284
- InputTokenLimit: 1048576,
285
- OutputTokenLimit: 65536,
286
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
287
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true, Levels: []string{"low", "high"}},
288
  },
@@ -363,8 +363,8 @@ func GetGeminiCLIModels() []*ModelInfo {
363
  Version: "2.5",
364
  DisplayName: "Gemini 2.5 Pro",
365
  Description: "Stable release (June 17th, 2025) of Gemini 2.5 Pro",
366
- InputTokenLimit: 1048576,
367
- OutputTokenLimit: 65536,
368
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
369
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true},
370
  },
@@ -378,8 +378,8 @@ func GetGeminiCLIModels() []*ModelInfo {
378
  Version: "001",
379
  DisplayName: "Gemini 2.5 Flash",
380
  Description: "Stable version of Gemini 2.5 Flash, our mid-size multimodal model that supports up to 1 million tokens, released in June of 2025.",
381
- InputTokenLimit: 1048576,
382
- OutputTokenLimit: 65536,
383
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
384
  Thinking: &ThinkingSupport{Min: 0, Max: 24576, ZeroAllowed: true, DynamicAllowed: true},
385
  },
@@ -393,8 +393,8 @@ func GetGeminiCLIModels() []*ModelInfo {
393
  Version: "2.5",
394
  DisplayName: "Gemini 2.5 Flash Lite",
395
  Description: "Our smallest and most cost effective model, built for at scale usage.",
396
- InputTokenLimit: 1048576,
397
- OutputTokenLimit: 65536,
398
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
399
  Thinking: &ThinkingSupport{Min: 0, Max: 24576, ZeroAllowed: true, DynamicAllowed: true},
400
  },
@@ -408,8 +408,8 @@ func GetGeminiCLIModels() []*ModelInfo {
408
  Version: "3.0",
409
  DisplayName: "Gemini 3 Pro Preview",
410
  Description: "Our most intelligent model with SOTA reasoning and multimodal understanding, and powerful agentic and vibe coding capabilities",
411
- InputTokenLimit: 1048576,
412
- OutputTokenLimit: 65536,
413
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
414
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true, Levels: []string{"low", "high"}},
415
  },
@@ -423,8 +423,8 @@ func GetGeminiCLIModels() []*ModelInfo {
423
  Version: "3.0",
424
  DisplayName: "Gemini 3 Flash Preview",
425
  Description: "Our most intelligent model built for speed, combining frontier intelligence with superior search and grounding.",
426
- InputTokenLimit: 1048576,
427
- OutputTokenLimit: 65536,
428
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
429
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true, Levels: []string{"minimal", "low", "medium", "high"}},
430
  },
@@ -444,8 +444,8 @@ func GetAIStudioModels() []*ModelInfo {
444
  Version: "2.5",
445
  DisplayName: "Gemini 2.5 Pro",
446
  Description: "Stable release (June 17th, 2025) of Gemini 2.5 Pro",
447
- InputTokenLimit: 1048576,
448
- OutputTokenLimit: 65536,
449
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
450
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true},
451
  },
@@ -459,8 +459,8 @@ func GetAIStudioModels() []*ModelInfo {
459
  Version: "001",
460
  DisplayName: "Gemini 2.5 Flash",
461
  Description: "Stable version of Gemini 2.5 Flash, our mid-size multimodal model that supports up to 1 million tokens, released in June of 2025.",
462
- InputTokenLimit: 1048576,
463
- OutputTokenLimit: 65536,
464
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
465
  Thinking: &ThinkingSupport{Min: 0, Max: 24576, ZeroAllowed: true, DynamicAllowed: true},
466
  },
@@ -474,8 +474,8 @@ func GetAIStudioModels() []*ModelInfo {
474
  Version: "2.5",
475
  DisplayName: "Gemini 2.5 Flash Lite",
476
  Description: "Our smallest and most cost effective model, built for at scale usage.",
477
- InputTokenLimit: 1048576,
478
- OutputTokenLimit: 65536,
479
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
480
  Thinking: &ThinkingSupport{Min: 0, Max: 24576, ZeroAllowed: true, DynamicAllowed: true},
481
  },
@@ -489,8 +489,8 @@ func GetAIStudioModels() []*ModelInfo {
489
  Version: "3.0",
490
  DisplayName: "Gemini 3 Pro Preview",
491
  Description: "Gemini 3 Pro Preview",
492
- InputTokenLimit: 1048576,
493
- OutputTokenLimit: 65536,
494
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
495
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true},
496
  },
@@ -504,8 +504,8 @@ func GetAIStudioModels() []*ModelInfo {
504
  Version: "3.0",
505
  DisplayName: "Gemini 3 Flash Preview",
506
  Description: "Our most intelligent model built for speed, combining frontier intelligence with superior search and grounding.",
507
- InputTokenLimit: 1048576,
508
- OutputTokenLimit: 65536,
509
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
510
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true},
511
  },
@@ -519,8 +519,8 @@ func GetAIStudioModels() []*ModelInfo {
519
  Version: "2.5",
520
  DisplayName: "Gemini Pro Latest",
521
  Description: "Latest release of Gemini Pro",
522
- InputTokenLimit: 1048576,
523
- OutputTokenLimit: 65536,
524
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
525
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true},
526
  },
@@ -534,8 +534,8 @@ func GetAIStudioModels() []*ModelInfo {
534
  Version: "2.5",
535
  DisplayName: "Gemini Flash Latest",
536
  Description: "Latest release of Gemini Flash",
537
- InputTokenLimit: 1048576,
538
- OutputTokenLimit: 65536,
539
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
540
  Thinking: &ThinkingSupport{Min: 0, Max: 24576, ZeroAllowed: true, DynamicAllowed: true},
541
  },
@@ -549,8 +549,8 @@ func GetAIStudioModels() []*ModelInfo {
549
  Version: "2.5",
550
  DisplayName: "Gemini Flash-Lite Latest",
551
  Description: "Latest release of Gemini Flash-Lite",
552
- InputTokenLimit: 1048576,
553
- OutputTokenLimit: 65536,
554
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
555
  Thinking: &ThinkingSupport{Min: 512, Max: 24576, ZeroAllowed: true, DynamicAllowed: true},
556
  },
@@ -564,8 +564,8 @@ func GetAIStudioModels() []*ModelInfo {
564
  // Version: "2.5",
565
  // DisplayName: "Gemini 2.5 Flash Image Preview",
566
  // Description: "State-of-the-art image generation and editing model.",
567
- // InputTokenLimit: 1048576,
568
- // OutputTokenLimit: 8192,
569
  // SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
570
  // // image models don't support thinkingConfig; leave Thinking nil
571
  // },
@@ -579,8 +579,8 @@ func GetAIStudioModels() []*ModelInfo {
579
  Version: "2.5",
580
  DisplayName: "Gemini 2.5 Flash Image",
581
  Description: "State-of-the-art image generation and editing model.",
582
- InputTokenLimit: 1048576,
583
- OutputTokenLimit: 8192,
584
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
585
  // image models don't support thinkingConfig; leave Thinking nil
586
  },
 
111
  Version: "2.5",
112
  DisplayName: "Gemini 2.5 Pro",
113
  Description: "Stable release (June 17th, 2025) of Gemini 2.5 Pro",
114
+ ContextLength: 1048576,
115
+ MaxCompletionTokens: 65536,
116
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
117
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true},
118
  },
 
126
  Version: "001",
127
  DisplayName: "Gemini 2.5 Flash",
128
  Description: "Stable version of Gemini 2.5 Flash, our mid-size multimodal model that supports up to 1 million tokens, released in June of 2025.",
129
+ ContextLength: 1048576,
130
+ MaxCompletionTokens: 65536,
131
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
132
  Thinking: &ThinkingSupport{Min: 0, Max: 24576, ZeroAllowed: true, DynamicAllowed: true},
133
  },
 
141
  Version: "2.5",
142
  DisplayName: "Gemini 2.5 Flash Lite",
143
  Description: "Our smallest and most cost effective model, built for at scale usage.",
144
+ ContextLength: 1048576,
145
+ MaxCompletionTokens: 65536,
146
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
147
  Thinking: &ThinkingSupport{Min: 0, Max: 24576, ZeroAllowed: true, DynamicAllowed: true},
148
  },
 
156
  Version: "3.0",
157
  DisplayName: "Gemini 3 Pro Preview",
158
  Description: "Gemini 3 Pro Preview",
159
+ ContextLength: 1048576,
160
+ MaxCompletionTokens: 65536,
161
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
162
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true, Levels: []string{"low", "high"}},
163
  },
 
171
  Version: "3.0",
172
  DisplayName: "Gemini 3 Flash Preview",
173
  Description: "Gemini 3 Flash Preview",
174
+ ContextLength: 1048576,
175
+ MaxCompletionTokens: 65536,
176
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
177
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true, Levels: []string{"minimal", "low", "medium", "high"}},
178
  },
 
186
  Version: "3.0",
187
  DisplayName: "Gemini 3 Pro Image Preview",
188
  Description: "Gemini 3 Pro Image Preview",
189
+ ContextLength: 1048576,
190
+ MaxCompletionTokens: 65536,
191
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
192
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true, Levels: []string{"low", "high"}},
193
  },
 
206
  Version: "2.5",
207
  DisplayName: "Gemini 2.5 Pro",
208
  Description: "Stable release (June 17th, 2025) of Gemini 2.5 Pro",
209
+ ContextLength: 1048576,
210
+ MaxCompletionTokens: 65536,
211
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
212
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true},
213
  },
 
221
  Version: "001",
222
  DisplayName: "Gemini 2.5 Flash",
223
  Description: "Stable version of Gemini 2.5 Flash, our mid-size multimodal model that supports up to 1 million tokens, released in June of 2025.",
224
+ ContextLength: 1048576,
225
+ MaxCompletionTokens: 65536,
226
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
227
  Thinking: &ThinkingSupport{Min: 0, Max: 24576, ZeroAllowed: true, DynamicAllowed: true},
228
  },
 
236
  Version: "2.5",
237
  DisplayName: "Gemini 2.5 Flash Lite",
238
  Description: "Our smallest and most cost effective model, built for at scale usage.",
239
+ ContextLength: 1048576,
240
+ MaxCompletionTokens: 65536,
241
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
242
  Thinking: &ThinkingSupport{Min: 0, Max: 24576, ZeroAllowed: true, DynamicAllowed: true},
243
  },
 
251
  Version: "3.0",
252
  DisplayName: "Gemini 3 Pro Preview",
253
  Description: "Gemini 3 Pro Preview",
254
+ ContextLength: 1048576,
255
+ MaxCompletionTokens: 65536,
256
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
257
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true, Levels: []string{"low", "high"}},
258
  },
 
266
  Version: "3.0",
267
  DisplayName: "Gemini 3 Flash Preview",
268
  Description: "Our most intelligent model built for speed, combining frontier intelligence with superior search and grounding.",
269
+ ContextLength: 1048576,
270
+ MaxCompletionTokens: 65536,
271
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
272
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true, Levels: []string{"minimal", "low", "medium", "high"}},
273
  },
 
281
  Version: "3.0",
282
  DisplayName: "Gemini 3 Pro Image Preview",
283
  Description: "Gemini 3 Pro Image Preview",
284
+ ContextLength: 1048576,
285
+ MaxCompletionTokens: 65536,
286
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
287
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true, Levels: []string{"low", "high"}},
288
  },
 
363
  Version: "2.5",
364
  DisplayName: "Gemini 2.5 Pro",
365
  Description: "Stable release (June 17th, 2025) of Gemini 2.5 Pro",
366
+ ContextLength: 1048576,
367
+ MaxCompletionTokens: 65536,
368
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
369
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true},
370
  },
 
378
  Version: "001",
379
  DisplayName: "Gemini 2.5 Flash",
380
  Description: "Stable version of Gemini 2.5 Flash, our mid-size multimodal model that supports up to 1 million tokens, released in June of 2025.",
381
+ ContextLength: 1048576,
382
+ MaxCompletionTokens: 65536,
383
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
384
  Thinking: &ThinkingSupport{Min: 0, Max: 24576, ZeroAllowed: true, DynamicAllowed: true},
385
  },
 
393
  Version: "2.5",
394
  DisplayName: "Gemini 2.5 Flash Lite",
395
  Description: "Our smallest and most cost effective model, built for at scale usage.",
396
+ ContextLength: 1048576,
397
+ MaxCompletionTokens: 65536,
398
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
399
  Thinking: &ThinkingSupport{Min: 0, Max: 24576, ZeroAllowed: true, DynamicAllowed: true},
400
  },
 
408
  Version: "3.0",
409
  DisplayName: "Gemini 3 Pro Preview",
410
  Description: "Our most intelligent model with SOTA reasoning and multimodal understanding, and powerful agentic and vibe coding capabilities",
411
+ ContextLength: 1048576,
412
+ MaxCompletionTokens: 65536,
413
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
414
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true, Levels: []string{"low", "high"}},
415
  },
 
423
  Version: "3.0",
424
  DisplayName: "Gemini 3 Flash Preview",
425
  Description: "Our most intelligent model built for speed, combining frontier intelligence with superior search and grounding.",
426
+ ContextLength: 1048576,
427
+ MaxCompletionTokens: 65536,
428
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
429
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true, Levels: []string{"minimal", "low", "medium", "high"}},
430
  },
 
444
  Version: "2.5",
445
  DisplayName: "Gemini 2.5 Pro",
446
  Description: "Stable release (June 17th, 2025) of Gemini 2.5 Pro",
447
+ ContextLength: 1048576,
448
+ MaxCompletionTokens: 65536,
449
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
450
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true},
451
  },
 
459
  Version: "001",
460
  DisplayName: "Gemini 2.5 Flash",
461
  Description: "Stable version of Gemini 2.5 Flash, our mid-size multimodal model that supports up to 1 million tokens, released in June of 2025.",
462
+ ContextLength: 1048576,
463
+ MaxCompletionTokens: 65536,
464
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
465
  Thinking: &ThinkingSupport{Min: 0, Max: 24576, ZeroAllowed: true, DynamicAllowed: true},
466
  },
 
474
  Version: "2.5",
475
  DisplayName: "Gemini 2.5 Flash Lite",
476
  Description: "Our smallest and most cost effective model, built for at scale usage.",
477
+ ContextLength: 1048576,
478
+ MaxCompletionTokens: 65536,
479
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
480
  Thinking: &ThinkingSupport{Min: 0, Max: 24576, ZeroAllowed: true, DynamicAllowed: true},
481
  },
 
489
  Version: "3.0",
490
  DisplayName: "Gemini 3 Pro Preview",
491
  Description: "Gemini 3 Pro Preview",
492
+ ContextLength: 1048576,
493
+ MaxCompletionTokens: 65536,
494
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
495
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true},
496
  },
 
504
  Version: "3.0",
505
  DisplayName: "Gemini 3 Flash Preview",
506
  Description: "Our most intelligent model built for speed, combining frontier intelligence with superior search and grounding.",
507
+ ContextLength: 1048576,
508
+ MaxCompletionTokens: 65536,
509
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
510
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true},
511
  },
 
519
  Version: "2.5",
520
  DisplayName: "Gemini Pro Latest",
521
  Description: "Latest release of Gemini Pro",
522
+ ContextLength: 1048576,
523
+ MaxCompletionTokens: 65536,
524
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
525
  Thinking: &ThinkingSupport{Min: 128, Max: 32768, ZeroAllowed: false, DynamicAllowed: true},
526
  },
 
534
  Version: "2.5",
535
  DisplayName: "Gemini Flash Latest",
536
  Description: "Latest release of Gemini Flash",
537
+ ContextLength: 1048576,
538
+ MaxCompletionTokens: 65536,
539
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
540
  Thinking: &ThinkingSupport{Min: 0, Max: 24576, ZeroAllowed: true, DynamicAllowed: true},
541
  },
 
549
  Version: "2.5",
550
  DisplayName: "Gemini Flash-Lite Latest",
551
  Description: "Latest release of Gemini Flash-Lite",
552
+ ContextLength: 1048576,
553
+ MaxCompletionTokens: 65536,
554
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
555
  Thinking: &ThinkingSupport{Min: 512, Max: 24576, ZeroAllowed: true, DynamicAllowed: true},
556
  },
 
564
  // Version: "2.5",
565
  // DisplayName: "Gemini 2.5 Flash Image Preview",
566
  // Description: "State-of-the-art image generation and editing model.",
567
+ // ContextLength: 1048576,
568
+ // MaxCompletionTokens: 8192,
569
  // SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
570
  // // image models don't support thinkingConfig; leave Thinking nil
571
  // },
 
579
  Version: "2.5",
580
  DisplayName: "Gemini 2.5 Flash Image",
581
  Description: "State-of-the-art image generation and editing model.",
582
+ ContextLength: 1048576,
583
+ MaxCompletionTokens: 8192,
584
  SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
585
  // image models don't support thinkingConfig; leave Thinking nil
586
  },
internal/registry/model_registry.go CHANGED
@@ -35,16 +35,12 @@ type ModelInfo struct {
35
  Version string `json:"version,omitempty"`
36
  // Description provides detailed information about the model
37
  Description string `json:"description,omitempty"`
38
- // InputTokenLimit is the maximum input token limit
39
- InputTokenLimit int `json:"inputTokenLimit,omitempty"`
40
- // OutputTokenLimit is the maximum output token limit
41
- OutputTokenLimit int `json:"outputTokenLimit,omitempty"`
42
- // SupportedGenerationMethods lists supported generation methods
43
- SupportedGenerationMethods []string `json:"supportedGenerationMethods,omitempty"`
44
  // ContextLength is the context window size
45
  ContextLength int `json:"context_length,omitempty"`
46
  // MaxCompletionTokens is the maximum completion tokens
47
  MaxCompletionTokens int `json:"max_completion_tokens,omitempty"`
 
 
48
  // SupportedParameters lists supported parameters
49
  SupportedParameters []string `json:"supported_parameters,omitempty"`
50
 
@@ -1017,6 +1013,9 @@ func (r *ModelRegistry) convertModelToMap(model *ModelInfo, handlerType string)
1017
  }
1018
  if model.ContextLength > 0 {
1019
  result["context_length"] = model.ContextLength
 
 
 
1020
  }
1021
  if model.MaxCompletionTokens > 0 {
1022
  result["max_completion_tokens"] = model.MaxCompletionTokens
@@ -1059,11 +1058,11 @@ func (r *ModelRegistry) convertModelToMap(model *ModelInfo, handlerType string)
1059
  if model.Description != "" {
1060
  result["description"] = model.Description
1061
  }
1062
- if model.InputTokenLimit > 0 {
1063
- result["inputTokenLimit"] = model.InputTokenLimit
1064
  }
1065
- if model.OutputTokenLimit > 0 {
1066
- result["outputTokenLimit"] = model.OutputTokenLimit
1067
  }
1068
  if len(model.SupportedGenerationMethods) > 0 {
1069
  result["supportedGenerationMethods"] = model.SupportedGenerationMethods
 
35
  Version string `json:"version,omitempty"`
36
  // Description provides detailed information about the model
37
  Description string `json:"description,omitempty"`
 
 
 
 
 
 
38
  // ContextLength is the context window size
39
  ContextLength int `json:"context_length,omitempty"`
40
  // MaxCompletionTokens is the maximum completion tokens
41
  MaxCompletionTokens int `json:"max_completion_tokens,omitempty"`
42
+ // SupportedGenerationMethods lists supported generation methods
43
+ SupportedGenerationMethods []string `json:"supportedGenerationMethods,omitempty"`
44
  // SupportedParameters lists supported parameters
45
  SupportedParameters []string `json:"supported_parameters,omitempty"`
46
 
 
1013
  }
1014
  if model.ContextLength > 0 {
1015
  result["context_length"] = model.ContextLength
1016
+ // Add aliases for agent compatibility
1017
+ result["context_window"] = model.ContextLength
1018
+ result["max_context_length"] = model.ContextLength
1019
  }
1020
  if model.MaxCompletionTokens > 0 {
1021
  result["max_completion_tokens"] = model.MaxCompletionTokens
 
1058
  if model.Description != "" {
1059
  result["description"] = model.Description
1060
  }
1061
+ if model.ContextLength > 0 {
1062
+ result["inputTokenLimit"] = model.ContextLength
1063
  }
1064
+ if model.MaxCompletionTokens > 0 {
1065
+ result["outputTokenLimit"] = model.MaxCompletionTokens
1066
  }
1067
  if len(model.SupportedGenerationMethods) > 0 {
1068
  result["supportedGenerationMethods"] = model.SupportedGenerationMethods
internal/runtime/executor/claude_executor.go CHANGED
@@ -625,7 +625,7 @@ func applyClaudeHeaders(r *http.Request, auth *cliproxyauth.Auth, apiKey string,
625
  ginHeaders = ginCtx.Request.Header
626
  }
627
 
628
- baseBetas := "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14"
629
  if val := strings.TrimSpace(ginHeaders.Get("Anthropic-Beta")); val != "" {
630
  baseBetas = val
631
  if !strings.Contains(val, "oauth") {
 
625
  ginHeaders = ginCtx.Request.Header
626
  }
627
 
628
+ baseBetas := "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14,prompt-caching-2024-07-31"
629
  if val := strings.TrimSpace(ginHeaders.Get("Anthropic-Beta")); val != "" {
630
  baseBetas = val
631
  if !strings.Contains(val, "oauth") {
internal/runtime/executor/kiro_executor.go CHANGED
@@ -12,6 +12,7 @@ import (
12
  "strings"
13
  "time"
14
 
 
15
  "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
16
  "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
17
  "github.com/router-for-me/CLIProxyAPI/v6/internal/util"
@@ -23,6 +24,13 @@ import (
23
  "github.com/tidwall/sjson"
24
  )
25
 
 
 
 
 
 
 
 
26
  // KiroExecutor implements ProviderExecutor for Kiro API (Amazon Q Developer / AWS CodeWhisperer).
27
  // It translates OpenAI/Claude-compatible requests to the Kiro API format.
28
  type KiroExecutor struct {
@@ -110,6 +118,41 @@ func normalizeKiroModel(model string) string {
110
  return model
111
  }
112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  // buildKiroPayload builds the Kiro API request payload from a Claude-format request.
114
  func buildKiroPayload(body []byte, model string, profileARN string) ([]byte, error) {
115
  kiroModel := normalizeKiroModel(model)
@@ -120,28 +163,202 @@ func buildKiroPayload(body []byte, model string, profileARN string) ([]byte, err
120
  return nil, fmt.Errorf("messages field is required")
121
  }
122
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  // Build Kiro conversationState
124
- var userInputs []map[string]any
125
- var assistantResponses []map[string]any
126
 
127
- messages.ForEach(func(_, msg gjson.Result) bool {
 
 
128
  role := msg.Get("role").String()
129
  content := msg.Get("content")
130
 
131
- switch role {
132
- case "user":
133
- userMsg := map[string]any{
134
- "content": extractTextContent(content),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  }
136
- userInputs = append(userInputs, userMsg)
137
- case "assistant":
138
- assistantMsg := map[string]any{
139
- "content": extractTextContent(content),
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  }
141
- assistantResponses = append(assistantResponses, assistantMsg)
142
  }
143
- return true
144
- })
145
 
146
  // Build system prompt
147
  var systemPrompt string
@@ -160,29 +377,22 @@ func buildKiroPayload(body []byte, model string, profileARN string) ([]byte, err
160
  }
161
  }
162
 
163
- // Build conversation state
164
- conversationState := map[string]any{
165
- "currentMessage": map[string]any{
166
- "origin": "USER",
167
- "userInputs": userInputs,
168
- "userInputOrigin": "CONVERSATION",
169
- },
170
  }
171
 
172
- if len(assistantResponses) > 0 {
173
- conversationState["history"] = []map[string]any{
174
- {
175
- "turn": map[string]any{
176
- "userInputs": userInputs[:len(userInputs)-1],
177
- "assistantResponses": assistantResponses,
178
- },
179
- },
180
- }
181
- }
182
 
183
  // Build Kiro request payload
184
  kiroPayload := map[string]any{
185
- "conversationState": conversationState,
 
 
 
 
 
186
  "additionalInstructions": systemPrompt,
187
  "profileArn": profileARN,
188
  }
@@ -197,6 +407,22 @@ func buildKiroPayload(body []byte, model string, profileARN string) ([]byte, err
197
  return json.Marshal(kiroPayload)
198
  }
199
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  // extractTextContent extracts text from message content.
201
  func extractTextContent(content gjson.Result) string {
202
  if content.IsArray() {
@@ -649,24 +875,105 @@ func parseKiroStreamLine(line []byte, model string, contentBuilder *strings.Buil
649
  return nil
650
  }
651
 
652
- // Extract text content
653
- text := gjson.GetBytes(line, "assistantResponseEvent.content").String()
654
- if text == "" {
655
- return nil
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
656
  }
657
 
658
- contentBuilder.WriteString(text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
659
 
660
- // Build Claude streaming format
661
- event := map[string]any{
662
- "type": "content_block_delta",
663
- "index": 0,
664
- "delta": map[string]any{
665
- "type": "text_delta",
666
- "text": text,
667
- },
668
  }
669
 
670
- data, _ := json.Marshal(event)
671
- return append([]byte("data: "), data...)
 
 
 
 
 
672
  }
 
12
  "strings"
13
  "time"
14
 
15
+ "github.com/google/uuid"
16
  "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
17
  "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
18
  "github.com/router-for-me/CLIProxyAPI/v6/internal/util"
 
24
  "github.com/tidwall/sjson"
25
  )
26
 
27
+ const (
28
+ // FakeReasoningMaxTokens is the maximum number of tokens to allocate for thinking.
29
+ FakeReasoningMaxTokens = 4000
30
+ // ToolDescriptionMaxLength is the maximum length of tool description allowed by Kiro.
31
+ ToolDescriptionMaxLength = 10000
32
+ )
33
+
34
  // KiroExecutor implements ProviderExecutor for Kiro API (Amazon Q Developer / AWS CodeWhisperer).
35
  // It translates OpenAI/Claude-compatible requests to the Kiro API format.
36
  type KiroExecutor struct {
 
118
  return model
119
  }
120
 
121
+ // injectThinkingTags injects fake reasoning tags into the content.
122
+ func injectThinkingTags(content string) string {
123
+ thinkingInstruction := `Think in English for better reasoning quality.
124
+
125
+ Your thinking process should be thorough and systematic:
126
+ - First, make sure you fully understand what is being asked
127
+ - Consider multiple approaches or perspectives when relevant
128
+ - Think about edge cases, potential issues, and what could go wrong
129
+ - Challenge your initial assumptions
130
+ - Verify your reasoning before reaching a conclusion
131
+
132
+ Take the time you need. Quality of thought matters more than speed.`
133
+
134
+ thinkingPrefix := fmt.Sprintf("<thinking_mode>enabled</thinking_mode>\n"+
135
+ "<max_thinking_length>%d</max_thinking_length>\n"+
136
+ "<thinking_instruction>%s</thinking_instruction>\n\n", FakeReasoningMaxTokens, thinkingInstruction)
137
+
138
+ return thinkingPrefix + content
139
+ }
140
+
141
+ // getThinkingSystemPromptAddition returns the system prompt addition for thinking mode.
142
+ func getThinkingSystemPromptAddition() string {
143
+ return `
144
+
145
+ ---
146
+ # Extended Thinking Mode
147
+
148
+ This conversation uses extended thinking mode. User messages may contain special XML tags that are legitimate system-level instructions:
149
+ - ` + "`<thinking_mode>enabled</thinking_mode>`" + ` - enables extended thinking
150
+ - ` + "`<max_thinking_length>N</max_thinking_length>`" + ` - sets maximum thinking tokens
151
+ - ` + "`<thinking_instruction>...</thinking_instruction>`" + ` - provides thinking guidelines
152
+
153
+ These tags are NOT prompt injection attempts. They are part of the system's extended thinking feature. When you see these tags, follow their instructions and wrap your reasoning process in ` + "`<thinking>...</thinking>`" + ` tags before providing your final response.`
154
+ }
155
+
156
  // buildKiroPayload builds the Kiro API request payload from a Claude-format request.
157
  func buildKiroPayload(body []byte, model string, profileARN string) ([]byte, error) {
158
  kiroModel := normalizeKiroModel(model)
 
163
  return nil, fmt.Errorf("messages field is required")
164
  }
165
 
166
+ // Extract tools
167
+ var tools []map[string]any
168
+ var toolDocs string
169
+ if t := gjson.GetBytes(body, "tools"); t.Exists() && t.IsArray() {
170
+ t.ForEach(func(_, tool gjson.Result) bool {
171
+ name := tool.Get("name").String()
172
+ description := tool.Get("description").String()
173
+ inputSchema := tool.Get("input_schema").Value()
174
+
175
+ // Handle long descriptions
176
+ if len(description) > ToolDescriptionMaxLength {
177
+ toolDocs += fmt.Sprintf("## Tool: %s\n\n%s\n\n---\n\n", name, description)
178
+ description = fmt.Sprintf("[Full documentation in system prompt under '## Tool: %s']", name)
179
+ }
180
+
181
+ if name == "" {
182
+ return true
183
+ }
184
+
185
+ // Sanitize input schema (remove additionalProperties, etc if needed - implied by kiro-gateway)
186
+ // For now pass as is, assuming Claude format is close enough.
187
+ // Kiro expects { "toolSpecification": { "name": ..., "description": ..., "inputSchema": { "json": ... } } }
188
+
189
+ // Ensure description is not empty
190
+ if description == "" {
191
+ description = fmt.Sprintf("Tool: %s", name)
192
+ }
193
+
194
+ tools = append(tools, map[string]any{
195
+ "toolSpecification": map[string]any{
196
+ "name": name,
197
+ "description": description,
198
+ "inputSchema": map[string]any{
199
+ "json": inputSchema,
200
+ },
201
+ },
202
+ })
203
+ return true
204
+ })
205
+ }
206
+
207
  // Build Kiro conversationState
208
+ var history []map[string]any
209
+ var currentMessage map[string]any
210
 
211
+ // Process messages
212
+ msgs := messages.Array()
213
+ for i, msg := range msgs {
214
  role := msg.Get("role").String()
215
  content := msg.Get("content")
216
 
217
+ isLast := i == len(msgs)-1
218
+
219
+ if role == "user" {
220
+ textContent := extractTextContent(content)
221
+ if textContent == "" {
222
+ textContent = "(empty)" // Kiro requires non-empty content
223
+ }
224
+
225
+ // Inject thinking tags if it's the last user message
226
+ if isLast {
227
+ textContent = injectThinkingTags(textContent)
228
+ }
229
+
230
+ userInput := map[string]any{
231
+ "content": textContent,
232
+ "modelId": kiroModel,
233
+ "origin": "AI_EDITOR",
234
+ }
235
+
236
+ // Handle images
237
+ var images []map[string]any
238
+ if content.IsArray() {
239
+ content.ForEach(func(_, part gjson.Result) bool {
240
+ if part.Get("type").String() == "image" {
241
+ source := part.Get("source")
242
+ if source.Get("type").String() == "base64" {
243
+ mediaType := source.Get("media_type").String()
244
+ data := source.Get("data").String()
245
+ format := "jpeg"
246
+ if strings.Contains(mediaType, "png") {
247
+ format = "png"
248
+ } else if strings.Contains(mediaType, "gif") {
249
+ format = "gif"
250
+ } else if strings.Contains(mediaType, "webp") {
251
+ format = "webp"
252
+ }
253
+ images = append(images, map[string]any{
254
+ "format": format,
255
+ "source": map[string]any{
256
+ "bytes": data,
257
+ },
258
+ })
259
+ }
260
+ }
261
+ return true
262
+ })
263
+ }
264
+ if len(images) > 0 {
265
+ userInput["images"] = images
266
+ }
267
+
268
+ // Handle tool results
269
+ var toolResults []map[string]any
270
+ if content.IsArray() {
271
+ content.ForEach(func(_, part gjson.Result) bool {
272
+ if part.Get("type").String() == "tool_result" {
273
+ toolUseID := part.Get("tool_use_id").String()
274
+ resContent := part.Get("content") // can be string or array
275
+ resText := extractTextContent(resContent)
276
+ if resText == "" {
277
+ resText = "(empty result)"
278
+ }
279
+ toolResults = append(toolResults, map[string]any{
280
+ "toolUseId": toolUseID,
281
+ "status": "success",
282
+ "content": []map[string]any{
283
+ {"text": resText},
284
+ },
285
+ })
286
+ }
287
+ return true
288
+ })
289
+ }
290
+
291
+ // Build context
292
+ context := map[string]any{}
293
+ if len(toolResults) > 0 {
294
+ context["toolResults"] = toolResults
295
+ }
296
+ // Add tools definition to the LAST user message context
297
+ if isLast && len(tools) > 0 {
298
+ context["tools"] = tools
299
+ }
300
+
301
+ if len(context) > 0 {
302
+ userInput["userInputMessageContext"] = context
303
+ }
304
+
305
+ if isLast {
306
+ currentMessage = map[string]any{
307
+ "userInputMessage": userInput,
308
+ }
309
+ } else {
310
+ history = append(history, map[string]any{
311
+ "userInputMessage": userInput,
312
+ })
313
+ }
314
+
315
+ } else if role == "assistant" {
316
+ textContent := extractTextContent(content)
317
+ if textContent == "" && !contentHasToolUse(content) {
318
+ textContent = "(empty)"
319
+ }
320
+
321
+ assistantResponse := map[string]any{
322
+ "content": textContent,
323
+ }
324
+
325
+ // Handle tool uses
326
+ var toolUses []map[string]any
327
+ if content.IsArray() {
328
+ content.ForEach(func(_, part gjson.Result) bool {
329
+ if part.Get("type").String() == "tool_use" {
330
+ toolUses = append(toolUses, map[string]any{
331
+ "toolUseId": part.Get("id").String(),
332
+ "name": part.Get("name").String(),
333
+ "input": part.Get("input").Value(),
334
+ })
335
+ }
336
+ return true
337
+ })
338
+ }
339
+ if len(toolUses) > 0 {
340
+ assistantResponse["toolUses"] = toolUses
341
  }
342
+
343
+ if isLast {
344
+ // If assistant is last, we need to treat it as history and add a dummy "Continue" user message
345
+ history = append(history, map[string]any{
346
+ "assistantResponseMessage": assistantResponse,
347
+ })
348
+ currentMessage = map[string]any{
349
+ "userInputMessage": map[string]any{
350
+ "content": "Continue",
351
+ "modelId": kiroModel,
352
+ "origin": "AI_EDITOR",
353
+ },
354
+ }
355
+ } else {
356
+ history = append(history, map[string]any{
357
+ "assistantResponseMessage": assistantResponse,
358
+ })
359
  }
 
360
  }
361
+ }
 
362
 
363
  // Build system prompt
364
  var systemPrompt string
 
377
  }
378
  }
379
 
380
+ // Append tool documentation if any
381
+ if toolDocs != "" {
382
+ systemPrompt += "\n\n---\n# Tool Documentation\nThe following tools have detailed documentation that couldn't fit in the tool definition.\n\n" + toolDocs
 
 
 
 
383
  }
384
 
385
+ // Append thinking addition
386
+ systemPrompt += getThinkingSystemPromptAddition()
 
 
 
 
 
 
 
 
387
 
388
  // Build Kiro request payload
389
  kiroPayload := map[string]any{
390
+ "conversationState": map[string]any{
391
+ "currentMessage": currentMessage,
392
+ "history": history,
393
+ "chatTriggerType": "MANUAL",
394
+ "conversationId": uuid.New().String(), // Generate a new conversation ID
395
+ },
396
  "additionalInstructions": systemPrompt,
397
  "profileArn": profileARN,
398
  }
 
407
  return json.Marshal(kiroPayload)
408
  }
409
 
410
+ // contentHasToolUse checks if content array has tool_use
411
+ func contentHasToolUse(content gjson.Result) bool {
412
+ if !content.IsArray() {
413
+ return false
414
+ }
415
+ has := false
416
+ content.ForEach(func(_, part gjson.Result) bool {
417
+ if part.Get("type").String() == "tool_use" {
418
+ has = true
419
+ return false
420
+ }
421
+ return true
422
+ })
423
+ return has
424
+ }
425
+
426
  // extractTextContent extracts text from message content.
427
  func extractTextContent(content gjson.Result) string {
428
  if content.IsArray() {
 
875
  return nil
876
  }
877
 
878
+ root := gjson.ParseBytes(line)
879
+ event := root.Get("assistantResponseEvent")
880
+
881
+ var events []map[string]any
882
+
883
+ // Handle Content
884
+ if content := event.Get("content"); content.Exists() {
885
+ text := content.String()
886
+ if text != "" {
887
+ // Check for thinking tags
888
+ // Note: This is a simple implementation. For robust XML parsing across chunks,
889
+ // we would need a stateful parser. However, Kiro usually sends the opening tag
890
+ // at the beginning or in one chunk.
891
+ // Currently, we just pass the text through. If downstream translator supports
892
+ // OpenAI reasoning_content, it might parse <thinking> tags if we strip them.
893
+ // But here we are producing Claude format.
894
+ // Claude 3.7+ has "thinking" blocks. Older Claude doesn't.
895
+ // If we want OpenAI compatibility, the sdktranslator might handle it.
896
+ // For now, we pass the text as is (including <thinking> tags) because
897
+ // that's what "Fake Reasoning" does - it outputs the thinking in the content.
898
+ // If the user wants to strip it, that's a separate concern (e.g. FAKE_REASONING_HANDLING env var in Python).
899
+ // The Python code defaults to "as_reasoning_content" for OpenAI but "pass" or similar might be default here.
900
+ // We will just stream the text.
901
+
902
+ contentBuilder.WriteString(text)
903
+ events = append(events, map[string]any{
904
+ "type": "content_block_delta",
905
+ "index": 0,
906
+ "delta": map[string]any{
907
+ "type": "text_delta",
908
+ "text": text,
909
+ },
910
+ })
911
+ }
912
  }
913
 
914
+ // Handle Tool Calls
915
+ if toolUse := event.Get("toolUse"); toolUse.Exists() {
916
+ toolID := toolUse.Get("toolUseId").String()
917
+ name := toolUse.Get("name").String()
918
+ input := toolUse.Get("input").Raw
919
+ // If input is empty, wait for next chunks?
920
+ // Kiro streams toolUse usually as a single event or with input.
921
+ // Claude expects tool_use content block.
922
+ // For streaming, we need to emit content_block_start, content_block_delta, content_block_stop.
923
+ // But here we just map to a simplistic delta if possible or construct full tool use?
924
+ // SDKTranslator TranslateStream expects standard Claude events.
925
+
926
+ // Kiro sends full toolUse structure in one go usually?
927
+ // "toolUse": { "toolUseId": "...", "name": "...", "input": {...} }
928
+ // So we can send a full tool block?
929
+ // Claude streaming uses content_block_start with index.
930
+ // Since we don't track index statefully easily here without more context,
931
+ // we assume index 1 (after text at index 0).
932
+ // This might be brittle if multiple tools.
933
+ // BUT `sdktranslator` might be resilient.
934
+
935
+ // Actually, let's look at `sdktranslator`. It handles standard Claude events.
936
+ // We'll emit a synthetic tool use block.
937
+ // NOTE: Doing this properly requires state tracking (which index we are at).
938
+ // For now, let's assume we just stream text mostly.
939
+ // If Kiro sends tools, we might need to handle it better.
940
+ // Given time constraints, I will leave tool streaming as is (which was missing)
941
+ // or try to add basic support.
942
+ // Since `input` is available, we can send a `content_block_start` then `delta` then `stop`.
943
+
944
+ // Let's send it as one block for now if possible, or sequence.
945
+ events = append(events, map[string]any{
946
+ "type": "content_block_start",
947
+ "index": 1, // Assumption: Text is 0, Tool is 1
948
+ "content_block": map[string]any{
949
+ "type": "tool_use",
950
+ "id": toolID,
951
+ "name": name,
952
+ },
953
+ })
954
+ events = append(events, map[string]any{
955
+ "type": "content_block_delta",
956
+ "index": 1,
957
+ "delta": map[string]any{
958
+ "type": "input_json_delta",
959
+ "partial_json": input, // Kiro sends full JSON?
960
+ },
961
+ })
962
+ events = append(events, map[string]any{
963
+ "type": "content_block_stop",
964
+ "index": 1,
965
+ })
966
+ }
967
 
968
+ if len(events) == 0 {
969
+ return nil
 
 
 
 
 
 
970
  }
971
 
972
+ var result []byte
973
+ for _, evt := range events {
974
+ data, _ := json.Marshal(evt)
975
+ result = append(result, append([]byte("data: "), data...)...)
976
+ result = append(result, '\n')
977
+ }
978
+ return result
979
  }
internal/translator/antigravity/claude/antigravity_claude_request.go CHANGED
@@ -155,10 +155,13 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
155
  clientContentJSON, _ = sjson.SetRaw(clientContentJSON, "parts.-1", partJSON)
156
  } else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "text" {
157
  prompt := contentResult.Get("text").String()
158
- partJSON := `{}`
159
- if prompt != "" {
160
- partJSON, _ = sjson.Set(partJSON, "text", prompt)
 
161
  }
 
 
162
  clientContentJSON, _ = sjson.SetRaw(clientContentJSON, "parts.-1", partJSON)
163
  } else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "tool_use" {
164
  // NOTE: Do NOT inject dummy thinking blocks here.
@@ -284,6 +287,13 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
284
  }
285
  }
286
 
 
 
 
 
 
 
 
287
  contentsJSON, _ = sjson.SetRaw(contentsJSON, "-1", clientContentJSON)
288
  hasContents = true
289
  } else if contentsResult.Type == gjson.String {
 
155
  clientContentJSON, _ = sjson.SetRaw(clientContentJSON, "parts.-1", partJSON)
156
  } else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "text" {
157
  prompt := contentResult.Get("text").String()
158
+ // Skip empty text parts to avoid Gemini API error:
159
+ // "required oneof field 'data' must have one initialized field"
160
+ if prompt == "" {
161
+ continue
162
  }
163
+ partJSON := `{}`
164
+ partJSON, _ = sjson.Set(partJSON, "text", prompt)
165
  clientContentJSON, _ = sjson.SetRaw(clientContentJSON, "parts.-1", partJSON)
166
  } else if contentTypeResult.Type == gjson.String && contentTypeResult.String() == "tool_use" {
167
  // NOTE: Do NOT inject dummy thinking blocks here.
 
287
  }
288
  }
289
 
290
+ // Skip messages with empty parts array to avoid Gemini API error:
291
+ // "required oneof field 'data' must have one initialized field"
292
+ partsCheck := gjson.Get(clientContentJSON, "parts")
293
+ if !partsCheck.IsArray() || len(partsCheck.Array()) == 0 {
294
+ continue
295
+ }
296
+
297
  contentsJSON, _ = sjson.SetRaw(contentsJSON, "-1", clientContentJSON)
298
  hasContents = true
299
  } else if contentsResult.Type == gjson.String {
internal/translator/antigravity/openai/chat-completions/antigravity_openai_request.go CHANGED
@@ -305,12 +305,14 @@ func ConvertOpenAIRequestToAntigravity(modelName string, inputRawJSON []byte, _
305
  }
306
  }
307
 
308
- // tools -> request.tools[].functionDeclarations + request.tools[].googleSearch passthrough
309
  tools := gjson.GetBytes(rawJSON, "tools")
310
  if tools.IsArray() && len(tools.Array()) > 0 {
311
  functionToolNode := []byte(`{}`)
312
  hasFunction := false
313
  googleSearchNodes := make([][]byte, 0)
 
 
314
  for _, t := range tools.Array() {
315
  if t.Get("type").String() == "function" {
316
  fn := t.Get("function")
@@ -370,8 +372,28 @@ func ConvertOpenAIRequestToAntigravity(modelName string, inputRawJSON []byte, _
370
  }
371
  googleSearchNodes = append(googleSearchNodes, googleToolNode)
372
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  }
374
- if hasFunction || len(googleSearchNodes) > 0 {
375
  toolsNode := []byte("[]")
376
  if hasFunction {
377
  toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", functionToolNode)
@@ -379,6 +401,12 @@ func ConvertOpenAIRequestToAntigravity(modelName string, inputRawJSON []byte, _
379
  for _, googleNode := range googleSearchNodes {
380
  toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", googleNode)
381
  }
 
 
 
 
 
 
382
  out, _ = sjson.SetRawBytes(out, "request.tools", toolsNode)
383
  }
384
  }
 
305
  }
306
  }
307
 
308
+ // tools -> request.tools[].functionDeclarations + request.tools[].googleSearch/codeExecution/urlContext passthrough
309
  tools := gjson.GetBytes(rawJSON, "tools")
310
  if tools.IsArray() && len(tools.Array()) > 0 {
311
  functionToolNode := []byte(`{}`)
312
  hasFunction := false
313
  googleSearchNodes := make([][]byte, 0)
314
+ codeExecutionNodes := make([][]byte, 0)
315
+ urlContextNodes := make([][]byte, 0)
316
  for _, t := range tools.Array() {
317
  if t.Get("type").String() == "function" {
318
  fn := t.Get("function")
 
372
  }
373
  googleSearchNodes = append(googleSearchNodes, googleToolNode)
374
  }
375
+ if ce := t.Get("code_execution"); ce.Exists() {
376
+ codeToolNode := []byte(`{}`)
377
+ var errSet error
378
+ codeToolNode, errSet = sjson.SetRawBytes(codeToolNode, "codeExecution", []byte(ce.Raw))
379
+ if errSet != nil {
380
+ log.Warnf("Failed to set codeExecution tool: %v", errSet)
381
+ continue
382
+ }
383
+ codeExecutionNodes = append(codeExecutionNodes, codeToolNode)
384
+ }
385
+ if uc := t.Get("url_context"); uc.Exists() {
386
+ urlToolNode := []byte(`{}`)
387
+ var errSet error
388
+ urlToolNode, errSet = sjson.SetRawBytes(urlToolNode, "urlContext", []byte(uc.Raw))
389
+ if errSet != nil {
390
+ log.Warnf("Failed to set urlContext tool: %v", errSet)
391
+ continue
392
+ }
393
+ urlContextNodes = append(urlContextNodes, urlToolNode)
394
+ }
395
  }
396
+ if hasFunction || len(googleSearchNodes) > 0 || len(codeExecutionNodes) > 0 || len(urlContextNodes) > 0 {
397
  toolsNode := []byte("[]")
398
  if hasFunction {
399
  toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", functionToolNode)
 
401
  for _, googleNode := range googleSearchNodes {
402
  toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", googleNode)
403
  }
404
+ for _, codeNode := range codeExecutionNodes {
405
+ toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", codeNode)
406
+ }
407
+ for _, urlNode := range urlContextNodes {
408
+ toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", urlNode)
409
+ }
410
  out, _ = sjson.SetRawBytes(out, "request.tools", toolsNode)
411
  }
412
  }
internal/translator/gemini-cli/openai/chat-completions/gemini-cli_openai_request.go CHANGED
@@ -283,12 +283,14 @@ func ConvertOpenAIRequestToGeminiCLI(modelName string, inputRawJSON []byte, _ bo
283
  }
284
  }
285
 
286
- // tools -> request.tools[].functionDeclarations + request.tools[].googleSearch passthrough
287
  tools := gjson.GetBytes(rawJSON, "tools")
288
  if tools.IsArray() && len(tools.Array()) > 0 {
289
  functionToolNode := []byte(`{}`)
290
  hasFunction := false
291
  googleSearchNodes := make([][]byte, 0)
 
 
292
  for _, t := range tools.Array() {
293
  if t.Get("type").String() == "function" {
294
  fn := t.Get("function")
@@ -348,8 +350,28 @@ func ConvertOpenAIRequestToGeminiCLI(modelName string, inputRawJSON []byte, _ bo
348
  }
349
  googleSearchNodes = append(googleSearchNodes, googleToolNode)
350
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
  }
352
- if hasFunction || len(googleSearchNodes) > 0 {
353
  toolsNode := []byte("[]")
354
  if hasFunction {
355
  toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", functionToolNode)
@@ -357,6 +379,12 @@ func ConvertOpenAIRequestToGeminiCLI(modelName string, inputRawJSON []byte, _ bo
357
  for _, googleNode := range googleSearchNodes {
358
  toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", googleNode)
359
  }
 
 
 
 
 
 
360
  out, _ = sjson.SetRawBytes(out, "request.tools", toolsNode)
361
  }
362
  }
 
283
  }
284
  }
285
 
286
+ // tools -> request.tools[].functionDeclarations + request.tools[].googleSearch/codeExecution/urlContext passthrough
287
  tools := gjson.GetBytes(rawJSON, "tools")
288
  if tools.IsArray() && len(tools.Array()) > 0 {
289
  functionToolNode := []byte(`{}`)
290
  hasFunction := false
291
  googleSearchNodes := make([][]byte, 0)
292
+ codeExecutionNodes := make([][]byte, 0)
293
+ urlContextNodes := make([][]byte, 0)
294
  for _, t := range tools.Array() {
295
  if t.Get("type").String() == "function" {
296
  fn := t.Get("function")
 
350
  }
351
  googleSearchNodes = append(googleSearchNodes, googleToolNode)
352
  }
353
+ if ce := t.Get("code_execution"); ce.Exists() {
354
+ codeToolNode := []byte(`{}`)
355
+ var errSet error
356
+ codeToolNode, errSet = sjson.SetRawBytes(codeToolNode, "codeExecution", []byte(ce.Raw))
357
+ if errSet != nil {
358
+ log.Warnf("Failed to set codeExecution tool: %v", errSet)
359
+ continue
360
+ }
361
+ codeExecutionNodes = append(codeExecutionNodes, codeToolNode)
362
+ }
363
+ if uc := t.Get("url_context"); uc.Exists() {
364
+ urlToolNode := []byte(`{}`)
365
+ var errSet error
366
+ urlToolNode, errSet = sjson.SetRawBytes(urlToolNode, "urlContext", []byte(uc.Raw))
367
+ if errSet != nil {
368
+ log.Warnf("Failed to set urlContext tool: %v", errSet)
369
+ continue
370
+ }
371
+ urlContextNodes = append(urlContextNodes, urlToolNode)
372
+ }
373
  }
374
+ if hasFunction || len(googleSearchNodes) > 0 || len(codeExecutionNodes) > 0 || len(urlContextNodes) > 0 {
375
  toolsNode := []byte("[]")
376
  if hasFunction {
377
  toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", functionToolNode)
 
379
  for _, googleNode := range googleSearchNodes {
380
  toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", googleNode)
381
  }
382
+ for _, codeNode := range codeExecutionNodes {
383
+ toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", codeNode)
384
+ }
385
+ for _, urlNode := range urlContextNodes {
386
+ toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", urlNode)
387
+ }
388
  out, _ = sjson.SetRawBytes(out, "request.tools", toolsNode)
389
  }
390
  }
internal/translator/gemini/openai/chat-completions/gemini_openai_request.go CHANGED
@@ -325,8 +325,9 @@ func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
325
  if tools.IsArray() && len(tools.Array()) > 0 {
326
  functionToolNode := []byte(`{}`)
327
  hasFunction := false
328
- hasCodeExecution := false
329
  googleSearchNodes := make([][]byte, 0)
 
 
330
 
331
  for _, t := range tools.Array() {
332
  tType := t.Get("type").String()
@@ -372,7 +373,10 @@ func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
372
  hasFunction = true
373
  }
374
  } else if tType == "code_interpreter" {
375
- hasCodeExecution = true
 
 
 
376
  }
377
 
378
  if gs := t.Get("google_search"); gs.Exists() {
@@ -385,20 +389,44 @@ func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
385
  }
386
  googleSearchNodes = append(googleSearchNodes, googleToolNode)
387
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  }
389
 
390
- if hasFunction || len(googleSearchNodes) > 0 || hasCodeExecution {
391
  toolsNode := []byte("[]")
392
  if hasFunction {
393
  toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", functionToolNode)
394
- }
395
- if hasCodeExecution {
396
- // Gemini Code Execution tool
397
- toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", []byte(`{"codeExecution":{}}`))
398
- }
399
  for _, googleNode := range googleSearchNodes {
400
  toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", googleNode)
401
  }
 
 
 
 
 
 
402
  out, _ = sjson.SetRawBytes(out, "tools", toolsNode)
403
  }
404
  }
 
325
  if tools.IsArray() && len(tools.Array()) > 0 {
326
  functionToolNode := []byte(`{}`)
327
  hasFunction := false
 
328
  googleSearchNodes := make([][]byte, 0)
329
+ codeExecutionNodes := make([][]byte, 0)
330
+ urlContextNodes := make([][]byte, 0)
331
 
332
  for _, t := range tools.Array() {
333
  tType := t.Get("type").String()
 
373
  hasFunction = true
374
  }
375
  } else if tType == "code_interpreter" {
376
+ // Map OpenAI code_interpreter to Gemini codeExecution
377
+ // We create a node, but Gemini format expects {"codeExecution": {}}
378
+ // We can just add it to codeExecutionNodes
379
+ codeExecutionNodes = append(codeExecutionNodes, []byte(`{"codeExecution":{}}`))
380
  }
381
 
382
  if gs := t.Get("google_search"); gs.Exists() {
 
389
  }
390
  googleSearchNodes = append(googleSearchNodes, googleToolNode)
391
  }
392
+
393
+ if ce := t.Get("code_execution"); ce.Exists() {
394
+ codeToolNode := []byte(`{}`)
395
+ var errSet error
396
+ codeToolNode, errSet = sjson.SetRawBytes(codeToolNode, "codeExecution", []byte(ce.Raw))
397
+ if errSet != nil {
398
+ log.Warnf("Failed to set codeExecution tool: %v", errSet)
399
+ continue
400
+ }
401
+ codeExecutionNodes = append(codeExecutionNodes, codeToolNode)
402
+ }
403
+
404
+ if uc := t.Get("url_context"); uc.Exists() {
405
+ urlToolNode := []byte(`{}`)
406
+ var errSet error
407
+ urlToolNode, errSet = sjson.SetRawBytes(urlToolNode, "urlContext", []byte(uc.Raw))
408
+ if errSet != nil {
409
+ log.Warnf("Failed to set urlContext tool: %v", errSet)
410
+ continue
411
+ }
412
+ urlContextNodes = append(urlContextNodes, urlToolNode)
413
+ }
414
  }
415
 
416
+ if hasFunction || len(googleSearchNodes) > 0 || len(codeExecutionNodes) > 0 || len(urlContextNodes) > 0 {
417
  toolsNode := []byte("[]")
418
  if hasFunction {
419
  toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", functionToolNode)
420
+ } // codeExecution logic moved to loop
 
 
 
 
421
  for _, googleNode := range googleSearchNodes {
422
  toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", googleNode)
423
  }
424
+ for _, codeNode := range codeExecutionNodes {
425
+ toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", codeNode)
426
+ }
427
+ for _, urlNode := range urlContextNodes {
428
+ toolsNode, _ = sjson.SetRawBytes(toolsNode, "-1", urlNode)
429
+ }
430
  out, _ = sjson.SetRawBytes(out, "tools", toolsNode)
431
  }
432
  }
internal/util/gemini_schema.go CHANGED
@@ -4,6 +4,7 @@ package util
4
  import (
5
  "fmt"
6
  "sort"
 
7
  "strings"
8
 
9
  "github.com/tidwall/gjson"
@@ -44,6 +45,8 @@ func cleanJSONSchema(jsonStr string, addPlaceholder bool) string {
44
 
45
  // Phase 3: Cleanup
46
  jsonStr = removeUnsupportedKeywords(jsonStr)
 
 
47
  if !addPlaceholder {
48
  // Gemini schema cleanup: remove nullable/title and placeholder-only fields.
49
  jsonStr = removeKeywords(jsonStr, []string{"nullable", "title"})
@@ -434,6 +437,49 @@ func removeUnsupportedKeywords(jsonStr string) string {
434
  return jsonStr
435
  }
436
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
  func cleanupRequiredFields(jsonStr string) string {
438
  for _, p := range findPaths(jsonStr, "required") {
439
  parentPath := trimSuffix(p, ".required")
 
4
  import (
5
  "fmt"
6
  "sort"
7
+ "strconv"
8
  "strings"
9
 
10
  "github.com/tidwall/gjson"
 
45
 
46
  // Phase 3: Cleanup
47
  jsonStr = removeUnsupportedKeywords(jsonStr)
48
+ // Remove x-* extension fields (e.g., x-google-enum-descriptions) that are not supported by Gemini API
49
+ jsonStr = removeExtensionFields(jsonStr)
50
  if !addPlaceholder {
51
  // Gemini schema cleanup: remove nullable/title and placeholder-only fields.
52
  jsonStr = removeKeywords(jsonStr, []string{"nullable", "title"})
 
437
  return jsonStr
438
  }
439
 
440
+ // removeExtensionFields removes all x-* extension fields from the JSON schema.
441
+ // These are OpenAPI/JSON Schema extension fields that Google APIs don't recognize.
442
+ func removeExtensionFields(jsonStr string) string {
443
+ var paths []string
444
+ walkForExtensions(gjson.Parse(jsonStr), "", &paths)
445
+ // walkForExtensions returns paths in a way that deeper paths are added before their ancestors
446
+ // when they are not deleted wholesale, but since we skip children of deleted x-* nodes,
447
+ // any collected path is safe to delete. We still use DeleteBytes for efficiency.
448
+
449
+ b := []byte(jsonStr)
450
+ for _, p := range paths {
451
+ b, _ = sjson.DeleteBytes(b, p)
452
+ }
453
+ return string(b)
454
+ }
455
+
456
+ func walkForExtensions(value gjson.Result, path string, paths *[]string) {
457
+ if value.IsArray() {
458
+ arr := value.Array()
459
+ for i := len(arr) - 1; i >= 0; i-- {
460
+ walkForExtensions(arr[i], joinPath(path, strconv.Itoa(i)), paths)
461
+ }
462
+ return
463
+ }
464
+
465
+ if value.IsObject() {
466
+ value.ForEach(func(key, val gjson.Result) bool {
467
+ keyStr := key.String()
468
+ safeKey := escapeGJSONPathKey(keyStr)
469
+ childPath := joinPath(path, safeKey)
470
+
471
+ // If it's an extension field, we delete it and don't need to look at its children.
472
+ if strings.HasPrefix(keyStr, "x-") && !isPropertyDefinition(path) {
473
+ *paths = append(*paths, childPath)
474
+ return true
475
+ }
476
+
477
+ walkForExtensions(val, childPath, paths)
478
+ return true
479
+ })
480
+ }
481
+ }
482
+
483
  func cleanupRequiredFields(jsonStr string) string {
484
  for _, p := range findPaths(jsonStr, "required") {
485
  parentPath := trimSuffix(p, ".required")
internal/util/gemini_schema_test.go CHANGED
@@ -869,3 +869,129 @@ func TestCleanJSONSchemaForAntigravity_BooleanEnumToString(t *testing.T) {
869
  t.Errorf("Boolean enum values should be converted to string format, got: %s", result)
870
  }
871
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
869
  t.Errorf("Boolean enum values should be converted to string format, got: %s", result)
870
  }
871
  }
872
+
873
+ func TestRemoveExtensionFields(t *testing.T) {
874
+ tests := []struct {
875
+ name string
876
+ input string
877
+ expected string
878
+ }{
879
+ {
880
+ name: "removes x- fields at root",
881
+ input: `{
882
+ "type": "object",
883
+ "x-custom-meta": "value",
884
+ "properties": {
885
+ "foo": { "type": "string" }
886
+ }
887
+ }`,
888
+ expected: `{
889
+ "type": "object",
890
+ "properties": {
891
+ "foo": { "type": "string" }
892
+ }
893
+ }`,
894
+ },
895
+ {
896
+ name: "removes x- fields in nested properties",
897
+ input: `{
898
+ "type": "object",
899
+ "properties": {
900
+ "foo": {
901
+ "type": "string",
902
+ "x-internal-id": 123
903
+ }
904
+ }
905
+ }`,
906
+ expected: `{
907
+ "type": "object",
908
+ "properties": {
909
+ "foo": {
910
+ "type": "string"
911
+ }
912
+ }
913
+ }`,
914
+ },
915
+ {
916
+ name: "does NOT remove properties named x-",
917
+ input: `{
918
+ "type": "object",
919
+ "properties": {
920
+ "x-data": { "type": "string" },
921
+ "normal": { "type": "number", "x-meta": "remove" }
922
+ },
923
+ "required": ["x-data"]
924
+ }`,
925
+ expected: `{
926
+ "type": "object",
927
+ "properties": {
928
+ "x-data": { "type": "string" },
929
+ "normal": { "type": "number" }
930
+ },
931
+ "required": ["x-data"]
932
+ }`,
933
+ },
934
+ {
935
+ name: "does NOT remove $schema and other meta fields (as requested)",
936
+ input: `{
937
+ "$schema": "http://json-schema.org/draft-07/schema#",
938
+ "$id": "test",
939
+ "type": "object",
940
+ "properties": {
941
+ "foo": { "type": "string" }
942
+ }
943
+ }`,
944
+ expected: `{
945
+ "$schema": "http://json-schema.org/draft-07/schema#",
946
+ "$id": "test",
947
+ "type": "object",
948
+ "properties": {
949
+ "foo": { "type": "string" }
950
+ }
951
+ }`,
952
+ },
953
+ {
954
+ name: "handles properties named $schema",
955
+ input: `{
956
+ "type": "object",
957
+ "properties": {
958
+ "$schema": { "type": "string" }
959
+ }
960
+ }`,
961
+ expected: `{
962
+ "type": "object",
963
+ "properties": {
964
+ "$schema": { "type": "string" }
965
+ }
966
+ }`,
967
+ },
968
+ {
969
+ name: "handles escaping in paths",
970
+ input: `{
971
+ "type": "object",
972
+ "properties": {
973
+ "foo.bar": {
974
+ "type": "string",
975
+ "x-meta": "remove"
976
+ }
977
+ },
978
+ "x-root.meta": "remove"
979
+ }`,
980
+ expected: `{
981
+ "type": "object",
982
+ "properties": {
983
+ "foo.bar": {
984
+ "type": "string"
985
+ }
986
+ }
987
+ }`,
988
+ },
989
+ }
990
+
991
+ for _, tt := range tests {
992
+ t.Run(tt.name, func(t *testing.T) {
993
+ actual := removeExtensionFields(tt.input)
994
+ compareJSON(t, tt.expected, actual)
995
+ })
996
+ }
997
+ }
rest/.next/dev/types/cache-life.d.ts DELETED
@@ -1,145 +0,0 @@
1
- // Type definitions for Next.js cacheLife configs
2
-
3
- declare module 'next/cache' {
4
- export { unstable_cache } from 'next/dist/server/web/spec-extension/unstable-cache'
5
- export {
6
- updateTag,
7
- revalidateTag,
8
- revalidatePath,
9
- refresh,
10
- } from 'next/dist/server/web/spec-extension/revalidate'
11
- export { unstable_noStore } from 'next/dist/server/web/spec-extension/unstable-no-store'
12
-
13
-
14
- /**
15
- * Cache this `"use cache"` for a timespan defined by the `"default"` profile.
16
- * ```
17
- * stale: 300 seconds (5 minutes)
18
- * revalidate: 900 seconds (15 minutes)
19
- * expire: never
20
- * ```
21
- *
22
- * This cache may be stale on clients for 5 minutes before checking with the server.
23
- * If the server receives a new request after 15 minutes, start revalidating new values in the background.
24
- * It lives for the maximum age of the server cache. If this entry has no traffic for a while, it may serve an old value the next request.
25
- */
26
- export function cacheLife(profile: "default"): void
27
-
28
- /**
29
- * Cache this `"use cache"` for a timespan defined by the `"seconds"` profile.
30
- * ```
31
- * stale: 30 seconds
32
- * revalidate: 1 seconds
33
- * expire: 60 seconds (1 minute)
34
- * ```
35
- *
36
- * This cache may be stale on clients for 30 seconds before checking with the server.
37
- * If the server receives a new request after 1 seconds, start revalidating new values in the background.
38
- * If this entry has no traffic for 1 minute it will expire. The next request will recompute it.
39
- */
40
- export function cacheLife(profile: "seconds"): void
41
-
42
- /**
43
- * Cache this `"use cache"` for a timespan defined by the `"minutes"` profile.
44
- * ```
45
- * stale: 300 seconds (5 minutes)
46
- * revalidate: 60 seconds (1 minute)
47
- * expire: 3600 seconds (1 hour)
48
- * ```
49
- *
50
- * This cache may be stale on clients for 5 minutes before checking with the server.
51
- * If the server receives a new request after 1 minute, start revalidating new values in the background.
52
- * If this entry has no traffic for 1 hour it will expire. The next request will recompute it.
53
- */
54
- export function cacheLife(profile: "minutes"): void
55
-
56
- /**
57
- * Cache this `"use cache"` for a timespan defined by the `"hours"` profile.
58
- * ```
59
- * stale: 300 seconds (5 minutes)
60
- * revalidate: 3600 seconds (1 hour)
61
- * expire: 86400 seconds (1 day)
62
- * ```
63
- *
64
- * This cache may be stale on clients for 5 minutes before checking with the server.
65
- * If the server receives a new request after 1 hour, start revalidating new values in the background.
66
- * If this entry has no traffic for 1 day it will expire. The next request will recompute it.
67
- */
68
- export function cacheLife(profile: "hours"): void
69
-
70
- /**
71
- * Cache this `"use cache"` for a timespan defined by the `"days"` profile.
72
- * ```
73
- * stale: 300 seconds (5 minutes)
74
- * revalidate: 86400 seconds (1 day)
75
- * expire: 604800 seconds (1 week)
76
- * ```
77
- *
78
- * This cache may be stale on clients for 5 minutes before checking with the server.
79
- * If the server receives a new request after 1 day, start revalidating new values in the background.
80
- * If this entry has no traffic for 1 week it will expire. The next request will recompute it.
81
- */
82
- export function cacheLife(profile: "days"): void
83
-
84
- /**
85
- * Cache this `"use cache"` for a timespan defined by the `"weeks"` profile.
86
- * ```
87
- * stale: 300 seconds (5 minutes)
88
- * revalidate: 604800 seconds (1 week)
89
- * expire: 2592000 seconds (1 month)
90
- * ```
91
- *
92
- * This cache may be stale on clients for 5 minutes before checking with the server.
93
- * If the server receives a new request after 1 week, start revalidating new values in the background.
94
- * If this entry has no traffic for 1 month it will expire. The next request will recompute it.
95
- */
96
- export function cacheLife(profile: "weeks"): void
97
-
98
- /**
99
- * Cache this `"use cache"` for a timespan defined by the `"max"` profile.
100
- * ```
101
- * stale: 300 seconds (5 minutes)
102
- * revalidate: 2592000 seconds (1 month)
103
- * expire: 31536000 seconds (365 days)
104
- * ```
105
- *
106
- * This cache may be stale on clients for 5 minutes before checking with the server.
107
- * If the server receives a new request after 1 month, start revalidating new values in the background.
108
- * If this entry has no traffic for 365 days it will expire. The next request will recompute it.
109
- */
110
- export function cacheLife(profile: "max"): void
111
-
112
- /**
113
- * Cache this `"use cache"` using a custom timespan.
114
- * ```
115
- * stale: ... // seconds
116
- * revalidate: ... // seconds
117
- * expire: ... // seconds
118
- * ```
119
- *
120
- * This is similar to Cache-Control: max-age=`stale`,s-max-age=`revalidate`,stale-while-revalidate=`expire-revalidate`
121
- *
122
- * If a value is left out, the lowest of other cacheLife() calls or the default, is used instead.
123
- */
124
- export function cacheLife(profile: {
125
- /**
126
- * This cache may be stale on clients for ... seconds before checking with the server.
127
- */
128
- stale?: number,
129
- /**
130
- * If the server receives a new request after ... seconds, start revalidating new values in the background.
131
- */
132
- revalidate?: number,
133
- /**
134
- * If this entry has no traffic for ... seconds it will expire. The next request will recompute it.
135
- */
136
- expire?: number
137
- }): void
138
-
139
-
140
- import { cacheTag } from 'next/dist/server/use-cache/cache-tag'
141
- export { cacheTag }
142
-
143
- export const unstable_cacheTag: typeof cacheTag
144
- export const unstable_cacheLife: typeof cacheLife
145
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
rest/.next/dev/types/routes.d.ts DELETED
@@ -1,55 +0,0 @@
1
- // This file is generated automatically by Next.js
2
- // Do not edit this file manually
3
-
4
- type AppRoutes = never
5
- type PageRoutes = never
6
- type LayoutRoutes = never
7
- type RedirectRoutes = never
8
- type RewriteRoutes = never
9
- type Routes = AppRoutes | PageRoutes | LayoutRoutes | RedirectRoutes | RewriteRoutes
10
-
11
-
12
- interface ParamMap {
13
- }
14
-
15
-
16
- export type ParamsOf<Route extends Routes> = ParamMap[Route]
17
-
18
- interface LayoutSlotMap {
19
- }
20
-
21
-
22
- export type { AppRoutes, PageRoutes, LayoutRoutes, RedirectRoutes, RewriteRoutes, ParamMap }
23
-
24
- declare global {
25
- /**
26
- * Props for Next.js App Router page components
27
- * @example
28
- * ```tsx
29
- * export default function Page(props: PageProps<'/blog/[slug]'>) {
30
- * const { slug } = await props.params
31
- * return <div>Blog post: {slug}</div>
32
- * }
33
- * ```
34
- */
35
- interface PageProps<AppRoute extends AppRoutes> {
36
- params: Promise<ParamMap[AppRoute]>
37
- searchParams: Promise<Record<string, string | string[] | undefined>>
38
- }
39
-
40
- /**
41
- * Props for Next.js App Router layout components
42
- * @example
43
- * ```tsx
44
- * export default function Layout(props: LayoutProps<'/dashboard'>) {
45
- * return <div>{props.children}</div>
46
- * }
47
- * ```
48
- */
49
- type LayoutProps<LayoutRoute extends LayoutRoutes> = {
50
- params: Promise<ParamMap[LayoutRoute]>
51
- children: React.ReactNode
52
- } & {
53
- [K in LayoutSlotMap[LayoutRoute]]: React.ReactNode
54
- }
55
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
rest/.next/dev/types/validator.ts DELETED
@@ -1,16 +0,0 @@
1
- // This file is generated automatically by Next.js
2
- // Do not edit this file manually
3
- // This file validates that all pages and layouts export the correct types
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
sdk/api/handlers/openai/openai_handlers.go CHANGED
@@ -66,12 +66,21 @@ func (h *OpenAIAPIHandler) Models() []map[string]any {
66
  if seen[km.ID] {
67
  continue
68
  }
69
- models = append(models, map[string]any{
70
  "id": km.ID,
71
  "object": "model",
72
  "created": km.Created,
73
  "owned_by": "kiro",
74
- })
 
 
 
 
 
 
 
 
 
75
  }
76
 
77
  return models
@@ -102,6 +111,20 @@ func (h *OpenAIAPIHandler) OpenAIModels(c *gin.Context) {
102
  filteredModel["owned_by"] = ownedBy
103
  }
104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  filteredModels[i] = filteredModel
106
  }
107
 
 
66
  if seen[km.ID] {
67
  continue
68
  }
69
+ modelMap := map[string]any{
70
  "id": km.ID,
71
  "object": "model",
72
  "created": km.Created,
73
  "owned_by": "kiro",
74
+ }
75
+ if km.ContextLength > 0 {
76
+ modelMap["context_length"] = km.ContextLength
77
+ modelMap["context_window"] = km.ContextLength
78
+ modelMap["max_context_length"] = km.ContextLength
79
+ }
80
+ if km.MaxCompletionTokens > 0 {
81
+ modelMap["max_completion_tokens"] = km.MaxCompletionTokens
82
+ }
83
+ models = append(models, modelMap)
84
  }
85
 
86
  return models
 
111
  filteredModel["owned_by"] = ownedBy
112
  }
113
 
114
+ // Pass through context window information for agents
115
+ if v, exists := model["context_length"]; exists {
116
+ filteredModel["context_length"] = v
117
+ }
118
+ if v, exists := model["context_window"]; exists {
119
+ filteredModel["context_window"] = v
120
+ }
121
+ if v, exists := model["max_context_length"]; exists {
122
+ filteredModel["max_context_length"] = v
123
+ }
124
+ if v, exists := model["max_completion_tokens"]; exists {
125
+ filteredModel["max_completion_tokens"] = v
126
+ }
127
+
128
  filteredModels[i] = filteredModel
129
  }
130