bingocode 1.1.200-beta.16 → 1.1.200-beta.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bingocode",
3
- "version": "1.1.200-beta.16",
3
+ "version": "1.1.200-beta.18",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "claude": "bin/claude-win.cjs",
@@ -24,7 +24,7 @@ import {
24
24
  } from '../types/provider.js'
25
25
  import type { SlotName } from '../types/provider.js'
26
26
  import { ApiError, errorResponse } from '../middleware/errorHandler.js'
27
- import { requireAuth } from '../middleware/auth.js'
27
+
28
28
 
29
29
  const providerService = new ProviderService()
30
30
  providerService.init().catch((err) =>
@@ -105,11 +105,6 @@ export async function handleProvidersApi(
105
105
  { status: 403 },
106
106
  )
107
107
  }
108
- // Also enforce Bearer-token auth (mirrors the global /api/ guard,
109
- // but active even when the server is bound to localhost)
110
- const authError = requireAuth(req)
111
- if (authError) return authError
112
-
113
108
  if (req.method === 'GET') {
114
109
  const status = await providerService.getVscodeStatus()
115
110
  return Response.json(status)
@@ -1,208 +1,229 @@
1
- version: 2
2
-
3
- # Provider Preset Configuration
4
- # fields array declares fields to be filled when adding
5
- # key: 'name' | 'apiKey' | 'baseUrl' mapped directly to top-level fields, others saved in extra.<key>
6
- # secret: true uses mask display in frontend
7
- #
8
- # modelsUrl: model list path relative to baseUrl, empty string means dynamic pulling not supported
9
- # modelsAuthStyle: bearer → Authorization: Bearer <apiKey>
10
- # x-api-key → x-api-key: <apiKey> + anthropic-version header
11
- # modelsDataPath: record field name for models array in JSON response (almost always 'data')
12
-
13
- presets:
14
- - id: official
15
- name: Claude Official
16
- baseUrl: ''
17
- apiFormat: anthropic
18
- needsApiKey: false
19
- websiteUrl: https://www.anthropic.com/claude-code
20
- modelsUrl: /v1/models
21
- modelsAuthStyle: x-api-key
22
- modelsDataPath: data
23
- fields:
24
- - key: name
25
- label: Provider Nickname
26
- required: true
27
- secret: false
28
- placeholder: 'e.g. Claude Official'
29
-
30
- - id: openai
31
- name: OpenAI
32
- baseUrl: https://api.openai.com/v1
33
- apiFormat: openai_chat
34
- needsApiKey: true
35
- websiteUrl: https://platform.openai.com
36
- modelsUrl: /v1/models
37
- modelsAuthStyle: bearer
38
- modelsDataPath: data
39
- fields:
40
- - key: name
41
- label: Provider Nickname
42
- required: true
43
- secret: false
44
- placeholder: 'e.g. My OpenAI'
45
- - key: apiKey
46
- label: API Key
47
- required: true
48
- secret: true
49
- placeholder: 'sk-...'
50
- - key: baseUrl
51
- label: Base URL (Optional)
52
- required: false
53
- secret: false
54
- default: https://api.openai.com/v1
55
- placeholder: 'https://api.openai.com/v1'
56
-
57
- - id: gemini
58
- name: Google Gemini
59
- baseUrl: https://generativelanguage.googleapis.com/v1beta/openai
60
- apiFormat: openai_chat
61
- needsApiKey: true
62
- websiteUrl: https://aistudio.google.com
63
- modelsUrl: /v1/models
64
- modelsAuthStyle: bearer
65
- modelsDataPath: data
66
- fields:
67
- - key: name
68
- label: Provider Nickname
69
- required: true
70
- secret: false
71
- placeholder: 'e.g. My Gemini'
72
- - key: apiKey
73
- label: API Key
74
- required: true
75
- secret: true
76
- placeholder: 'Gemini API Key'
77
-
78
- - id: mistral
79
- name: Mistral AI
80
- baseUrl: https://api.mistral.ai/v1
81
- apiFormat: openai_chat
82
- needsApiKey: true
83
- websiteUrl: https://console.mistral.ai
84
- modelsUrl: /v1/models
85
- modelsAuthStyle: bearer
86
- modelsDataPath: data
87
- fields:
88
- - key: name
89
- label: Provider Nickname
90
- required: true
91
- secret: false
92
- placeholder: 'e.g. My Mistral'
93
- - key: apiKey
94
- label: API Key
95
- required: true
96
- secret: true
97
- placeholder: 'Mistral API Key'
98
-
99
- - id: deepseek
100
- name: DeepSeek
101
- baseUrl: https://api.deepseek.com/anthropic
102
- apiFormat: anthropic
103
- needsApiKey: true
104
- websiteUrl: https://platform.deepseek.com
105
- # Models list follows OpenAI endpoint (no /v1/models under /anthropic)
106
- modelsUrl: https://api.deepseek.com/v1/models
107
- modelsAuthStyle: bearer
108
- modelsDataPath: data
109
- fields:
110
- - key: name
111
- label: Provider Nickname
112
- required: true
113
- secret: false
114
- placeholder: 'e.g. My DeepSeek'
115
- - key: apiKey
116
- label: API Key
117
- required: true
118
- secret: true
119
- placeholder: 'sk-...'
120
-
121
- - id: zhipuglm
122
- name: Zhipu GLM
123
- baseUrl: https://open.bigmodel.cn/api/paas/v4
124
- apiFormat: openai_chat
125
- needsApiKey: true
126
- websiteUrl: https://open.bigmodel.cn
127
- modelsUrl: /v4/models
128
- modelsAuthStyle: bearer
129
- modelsDataPath: data
130
- fields:
131
- - key: name
132
- label: Provider Nickname
133
- required: true
134
- secret: false
135
- placeholder: 'e.g. My GLM'
136
- - key: apiKey
137
- label: API Key
138
- required: true
139
- secret: true
140
- placeholder: 'Zhipu API Key (glm-5.1)'
141
-
142
- - id: kimi
143
- name: Kimi
144
- baseUrl: https://api.moonshot.ai/v1
145
- apiFormat: openai_chat
146
- needsApiKey: true
147
- websiteUrl: https://platform.moonshot.cn
148
- modelsUrl: /models
149
- modelsAuthStyle: bearer
150
- modelsDataPath: data
151
- fields:
152
- - key: name
153
- label: Provider Nickname
154
- required: true
155
- secret: false
156
- placeholder: 'e.g. My Kimi'
157
- - key: apiKey
158
- label: API Key
159
- required: true
160
- secret: true
161
- placeholder: 'Moonshot API Key (kimi-k2.6)'
162
-
163
- - id: minimax
164
- name: MiniMax
165
- baseUrl: https://api.minimax.io/v1
166
- apiFormat: openai_chat
167
- needsApiKey: true
168
- websiteUrl: https://platform.minimaxi.com
169
- modelsUrl: /models
170
- modelsAuthStyle: bearer
171
- modelsDataPath: data
172
- fields:
173
- - key: name
174
- label: Provider Nickname
175
- required: true
176
- secret: false
177
- placeholder: 'e.g. My MiniMax'
178
- - key: apiKey
179
- label: API Key
180
- required: true
181
- secret: true
182
- placeholder: 'MiniMax API Key (MiniMax-M2.7)'
183
-
184
- - id: custom
185
- name: Custom
186
- baseUrl: ''
187
- apiFormat: openai_chat
188
- needsApiKey: true
189
- websiteUrl: ''
190
- modelsUrl: /v1/models
191
- modelsAuthStyle: bearer
192
- modelsDataPath: data
193
- fields:
194
- - key: name
195
- label: Provider Nickname
196
- required: true
197
- secret: false
198
- placeholder: 'e.g. My Custom Provider'
199
- - key: baseUrl
200
- label: Base URL
201
- required: true
202
- secret: false
203
- placeholder: 'https://your-api-endpoint.com/v1'
204
- - key: apiKey
205
- label: API Key
206
- required: false
207
- secret: true
208
- placeholder: '(Optional) API Key'
1
+ version: 2
2
+
3
+ # Provider Preset Configuration
4
+ # fields array declares fields to be filled when adding
5
+ # key: 'name' | 'apiKey' | 'baseUrl' mapped directly to top-level fields, others saved in extra.<key>
6
+ # secret: true uses mask display in frontend
7
+ #
8
+ # modelsUrl: model list path relative to baseUrl, empty string means dynamic pulling not supported
9
+ # modelsAuthStyle: bearer → Authorization: Bearer <apiKey>
10
+ # x-api-key → x-api-key: <apiKey> + anthropic-version header
11
+ # modelsDataPath: record field name for models array in JSON response (almost always 'data')
12
+
13
+ presets:
14
+ - id: official
15
+ name: Claude Official
16
+ baseUrl: ''
17
+ apiFormat: anthropic
18
+ needsApiKey: false
19
+ websiteUrl: https://www.anthropic.com/claude-code
20
+ modelsUrl: /v1/models
21
+ modelsAuthStyle: x-api-key
22
+ modelsDataPath: data
23
+ fields:
24
+ - key: name
25
+ label: Provider Nickname
26
+ required: true
27
+ secret: false
28
+ placeholder: 'e.g. Claude Official'
29
+
30
+ - id: openai
31
+ name: OpenAI
32
+ baseUrl: https://api.openai.com/v1
33
+ apiFormat: openai_chat
34
+ needsApiKey: true
35
+ websiteUrl: https://platform.openai.com
36
+ modelsUrl: /v1/models
37
+ modelsAuthStyle: bearer
38
+ modelsDataPath: data
39
+ fields:
40
+ - key: name
41
+ label: Provider Nickname
42
+ required: true
43
+ secret: false
44
+ placeholder: 'e.g. My OpenAI'
45
+ - key: apiKey
46
+ label: API Key
47
+ required: true
48
+ secret: true
49
+ placeholder: 'sk-...'
50
+ - key: baseUrl
51
+ label: Base URL (Optional)
52
+ required: false
53
+ secret: false
54
+ default: https://api.openai.com/v1
55
+ placeholder: 'https://api.openai.com/v1'
56
+
57
+ - id: gemini
58
+ name: Google Gemini
59
+ baseUrl: https://generativelanguage.googleapis.com/v1beta/openai
60
+ apiFormat: openai_chat
61
+ needsApiKey: true
62
+ websiteUrl: https://aistudio.google.com
63
+ modelsUrl: /v1/models
64
+ modelsAuthStyle: bearer
65
+ modelsDataPath: data
66
+ fields:
67
+ - key: name
68
+ label: Provider Nickname
69
+ required: true
70
+ secret: false
71
+ placeholder: 'e.g. My Gemini'
72
+ - key: apiKey
73
+ label: API Key
74
+ required: true
75
+ secret: true
76
+ placeholder: 'Gemini API Key'
77
+
78
+ - id: mistral
79
+ name: Mistral AI
80
+ baseUrl: https://api.mistral.ai/v1
81
+ apiFormat: openai_chat
82
+ needsApiKey: true
83
+ websiteUrl: https://console.mistral.ai
84
+ modelsUrl: /v1/models
85
+ modelsAuthStyle: bearer
86
+ modelsDataPath: data
87
+ fields:
88
+ - key: name
89
+ label: Provider Nickname
90
+ required: true
91
+ secret: false
92
+ placeholder: 'e.g. My Mistral'
93
+ - key: apiKey
94
+ label: API Key
95
+ required: true
96
+ secret: true
97
+ placeholder: 'Mistral API Key'
98
+
99
+ - id: deepseek
100
+ name: DeepSeek
101
+ baseUrl: https://api.deepseek.com/anthropic
102
+ apiFormat: anthropic
103
+ needsApiKey: true
104
+ websiteUrl: https://platform.deepseek.com
105
+ # Models list follows OpenAI endpoint (no /v1/models under /anthropic)
106
+ modelsUrl: https://api.deepseek.com/v1/models
107
+ modelsAuthStyle: bearer
108
+ modelsDataPath: data
109
+ fields:
110
+ - key: name
111
+ label: Provider Nickname
112
+ required: true
113
+ secret: false
114
+ placeholder: 'e.g. My DeepSeek'
115
+ - key: apiKey
116
+ label: API Key
117
+ required: true
118
+ secret: true
119
+ placeholder: 'sk-...'
120
+
121
+ - id: nvidia
122
+ name: NVIDIA API
123
+ baseUrl: https://integrate.api.nvidia.com/v1
124
+ apiFormat: openai_chat
125
+ needsApiKey: true
126
+ websiteUrl: https://build.nvidia.com/explore/discover
127
+ modelsUrl: /models
128
+ modelsAuthStyle: bearer
129
+ modelsDataPath: data
130
+ fields:
131
+ - key: name
132
+ label: Provider Nickname
133
+ required: true
134
+ secret: false
135
+ placeholder: 'e.g. My NVIDIA'
136
+ - key: apiKey
137
+ label: API Key
138
+ required: true
139
+ secret: true
140
+ placeholder: 'nvapi-...'
141
+
142
+ - id: zhipuglm
143
+ name: Zhipu GLM
144
+ baseUrl: https://open.bigmodel.cn/api/paas/v4
145
+ apiFormat: openai_chat
146
+ needsApiKey: true
147
+ websiteUrl: https://open.bigmodel.cn
148
+ modelsUrl: /v4/models
149
+ modelsAuthStyle: bearer
150
+ modelsDataPath: data
151
+ fields:
152
+ - key: name
153
+ label: Provider Nickname
154
+ required: true
155
+ secret: false
156
+ placeholder: 'e.g. My GLM'
157
+ - key: apiKey
158
+ label: API Key
159
+ required: true
160
+ secret: true
161
+ placeholder: 'Zhipu API Key (glm-5.1)'
162
+
163
+ - id: kimi
164
+ name: Kimi
165
+ baseUrl: https://api.moonshot.ai/v1
166
+ apiFormat: openai_chat
167
+ needsApiKey: true
168
+ websiteUrl: https://platform.moonshot.cn
169
+ modelsUrl: /models
170
+ modelsAuthStyle: bearer
171
+ modelsDataPath: data
172
+ fields:
173
+ - key: name
174
+ label: Provider Nickname
175
+ required: true
176
+ secret: false
177
+ placeholder: 'e.g. My Kimi'
178
+ - key: apiKey
179
+ label: API Key
180
+ required: true
181
+ secret: true
182
+ placeholder: 'Moonshot API Key (kimi-k2.6)'
183
+
184
+ - id: minimax
185
+ name: MiniMax
186
+ baseUrl: https://api.minimax.io/v1
187
+ apiFormat: openai_chat
188
+ needsApiKey: true
189
+ websiteUrl: https://platform.minimaxi.com
190
+ modelsUrl: /models
191
+ modelsAuthStyle: bearer
192
+ modelsDataPath: data
193
+ fields:
194
+ - key: name
195
+ label: Provider Nickname
196
+ required: true
197
+ secret: false
198
+ placeholder: 'e.g. My MiniMax'
199
+ - key: apiKey
200
+ label: API Key
201
+ required: true
202
+ secret: true
203
+ placeholder: 'MiniMax API Key (MiniMax-M2.7)'
204
+
205
+ - id: custom
206
+ name: Custom
207
+ baseUrl: ''
208
+ apiFormat: openai_chat
209
+ needsApiKey: true
210
+ websiteUrl: ''
211
+ modelsUrl: /v1/models
212
+ modelsAuthStyle: bearer
213
+ modelsDataPath: data
214
+ fields:
215
+ - key: name
216
+ label: Provider Nickname
217
+ required: true
218
+ secret: false
219
+ placeholder: 'e.g. My Custom Provider'
220
+ - key: baseUrl
221
+ label: Base URL
222
+ required: true
223
+ secret: false
224
+ placeholder: 'https://your-api-endpoint.com/v1'
225
+ - key: apiKey
226
+ label: API Key
227
+ required: false
228
+ secret: true
229
+ placeholder: '(Optional) API Key'
@@ -35,6 +35,19 @@ async function logToFile(message: string) {
35
35
  // Disabled log output for production
36
36
  }
37
37
 
38
+ /**
39
+ * Normalize a base URL that may already contain a /v1 suffix,
40
+ * then append /v1/<path> so that double /v1 segments are avoided.
41
+ *
42
+ * Example:
43
+ * "https://integrate.api.nvidia.com/v1" + "chat/completions"
44
+ * => "https://integrate.api.nvidia.com/v1/chat/completions" (not /v1/v1/...)
45
+ */
46
+ function v1Url(baseUrl: string, path: string): string {
47
+ const normalized = baseUrl.replace(/\/v1\/?$/, '')
48
+ return `${normalized}/v1/${path.replace(/^\//, '')}`
49
+ }
50
+
38
51
  function sendAnthropicError(message: string, _model: string | undefined, status = 502): Response {
39
52
  const fullMessage = `[Bingo Proxy] ${message}`
40
53
  void logToFile(`ERROR: ${fullMessage} (status: ${status})`)
@@ -176,7 +189,7 @@ async function handleAnthropicPassthrough(
176
189
  uiLabel: string | null = null,
177
190
  betaHeader: string | null = null,
178
191
  ): Promise<Response> {
179
- const url = `${baseUrl}/v1/messages`
192
+ const url = v1Url(baseUrl, 'messages')
180
193
  const upstreamHeaders: Record<string, string> = {
181
194
  'Content-Type': 'application/json',
182
195
  'x-api-key': apiKey,
@@ -226,7 +239,7 @@ async function handleOpenaiChat(
226
239
  uiLabel: string | null = null,
227
240
  ): Promise<Response> {
228
241
  const transformed = anthropicToOpenaiChat(body)
229
- const url = `${baseUrl}/v1/chat/completions`
242
+ const url = v1Url(baseUrl, 'chat/completions')
230
243
 
231
244
  const upstream = await fetch(url, {
232
245
  method: 'POST',
@@ -277,7 +290,7 @@ async function handleOpenaiResponses(
277
290
  uiLabel: string | null = null,
278
291
  ): Promise<Response> {
279
292
  const transformed = anthropicToOpenaiResponses(body)
280
- const url = `${baseUrl}/v1/responses`
293
+ const url = v1Url(baseUrl, 'responses')
281
294
 
282
295
  const upstream = await fetch(url, {
283
296
  method: 'POST',
@@ -1010,10 +1010,10 @@ export class ProviderService {
1010
1010
  let transformedBody: unknown
1011
1011
  if (format === 'openai_chat') {
1012
1012
  transformedBody = anthropicToOpenaiChat(anthropicReq)
1013
- upstreamUrl = `${base}/v1/chat/completions`
1013
+ upstreamUrl = v1Url(base, 'chat/completions')
1014
1014
  } else {
1015
1015
  transformedBody = anthropicToOpenaiResponses(anthropicReq)
1016
- upstreamUrl = `${base}/v1/responses`
1016
+ upstreamUrl = v1Url(base, 'responses')
1017
1017
  }
1018
1018
 
1019
1019
  // Call upstream with transformed request
@@ -1060,6 +1060,15 @@ export class ProviderService {
1060
1060
 
1061
1061
  // ─── Helpers ───────────────────────────────────────────────
1062
1062
 
1063
+ /**
1064
+ * Normalize a base URL that may already contain a /v1 suffix,
1065
+ * then append /v1/<path> so that double /v1 segments are avoided.
1066
+ */
1067
+ function v1Url(base: string, path: string): string {
1068
+ const normalized = base.replace(/\/v1\/?$/, '')
1069
+ return `${normalized}/v1/${path.replace(/^\//, '')}`
1070
+ }
1071
+
1063
1072
  function buildDirectTestRequest(
1064
1073
  base: string,
1065
1074
  apiKey: string,
@@ -1070,21 +1079,21 @@ function buildDirectTestRequest(
1070
1079
 
1071
1080
  if (format === 'openai_chat') {
1072
1081
  return {
1073
- url: `${base}/v1/chat/completions`,
1082
+ url: v1Url(base, 'chat/completions'),
1074
1083
  headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
1075
1084
  body: { model: modelId, max_tokens: 16, messages: [{ role: 'user', content: prompt }] },
1076
1085
  }
1077
1086
  }
1078
1087
  if (format === 'openai_responses') {
1079
1088
  return {
1080
- url: `${base}/v1/responses`,
1089
+ url: v1Url(base, 'responses'),
1081
1090
  headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
1082
1091
  body: { model: modelId, max_output_tokens: 16, input: [{ type: 'message', role: 'user', content: prompt }] },
1083
1092
  }
1084
1093
  }
1085
1094
  // anthropic
1086
1095
  return {
1087
- url: `${base}/v1/messages`,
1096
+ url: v1Url(base, 'messages'),
1088
1097
  headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' },
1089
1098
  body: { model: modelId, max_tokens: 16, messages: [{ role: 'user', content: prompt }] },
1090
1099
  }
@@ -55,7 +55,7 @@ import {
55
55
  } from '../../utils/permissions/yoloClassifier.js'
56
56
  import { emitTaskProgress as emitTaskProgressEvent } from '../../utils/task/sdkProgress.js'
57
57
  import { isInProcessTeammate } from '../../utils/teammateContext.js'
58
- import { getTokenCountFromUsage } from '../../utils/tokens.js'
58
+ import { getTokenCountFromMessages } from '../../utils/tokens.js'
59
59
  import { EXIT_PLAN_MODE_V2_TOOL_NAME } from '../ExitPlanModeTool/constants.js'
60
60
  import { AGENT_TOOL_NAME, LEGACY_AGENT_TOOL_NAME } from './constants.js'
61
61
  import type { AgentDefinition } from './loadAgentsDir.js'
@@ -316,7 +316,7 @@ export function finalizeAgentTool(
316
316
  }
317
317
  }
318
318
 
319
- const totalTokens = getTokenCountFromUsage(lastAssistantMessage.message.usage)
319
+ const totalTokens = getTokenCountFromMessages(agentMessages)
320
320
  const totalToolUseCount = countToolUses(agentMessages)
321
321
 
322
322
  logEvent('tengu_agent_tool_completed', {
@@ -52,6 +52,29 @@ export function getTokenCountFromUsage(usage: Usage): number {
52
52
  )
53
53
  }
54
54
 
55
+ /**
56
+ * Calculate total tokens across all agent messages, matching the ProgressTracker
57
+ * accumulation logic in LocalAgentTask. In Claude's API:
58
+ * - input_tokens (and cache tokens) are cumulative per turn — each call already
59
+ * includes all previous context, so we keep only the LATEST value.
60
+ * - output_tokens are per-turn independent, so we SUM them across all turns.
61
+ */
62
+ export function getTokenCountFromMessages(messages: Message[]): number {
63
+ let latestInputTokens = 0
64
+ let cumulativeOutputTokens = 0
65
+ for (const message of messages) {
66
+ const usage = getTokenUsage(message)
67
+ if (usage) {
68
+ latestInputTokens =
69
+ usage.input_tokens +
70
+ (usage.cache_creation_input_tokens ?? 0) +
71
+ (usage.cache_read_input_tokens ?? 0)
72
+ cumulativeOutputTokens += usage.output_tokens
73
+ }
74
+ }
75
+ return latestInputTokens + cumulativeOutputTokens
76
+ }
77
+
55
78
  export function tokenCountFromLastAPIResponse(messages: Message[]): number {
56
79
  let i = messages.length - 1
57
80
  while (i >= 0) {