Skip to content

Commit dca3797

Browse files
committed
Add ChatGPT provider with OAuth authentication and specialized API handling
- create a new provider 'ChatGPT' - handle 'ChatGPT' provider authentication flow - in `CodeWebChat/apps/editor/src/views/settings/backend/message-handlers/handle-edit-model-provider-key.ts`, handle 'ChatGPT' authentication flow - configuration upsert of "ChatGPT" provider should show static list of models: 'gpt-5.2', 'gpt-5.3-codex', 'gpt-5.3-codex-spark'. Hide "Advanced". Supported reasoning efforts: low, medium, high, extra high. - in chatGPT in `CodeWebChat/apps/editor/src/views/utils/config-editing.ts`, remove duplicated descriptions - in case of ChatGPT provider, `CodeWebChat/apps/editor/src/utils/make-api-request.ts`should send requests differently (refer to `Roo-Code-main/src/api/providers/openai-codex.ts`) - in `CodeWebChat/apps/editor/src/utils/make-api-request.ts`, when sending with ChatGPT, the logged request body is invalid [Pasted text] - `CodeWebChat/apps/editor/src/utils/apply-reasoning-effort.ts`is missing else for generic setting - in case of ChatGPT, convert "extra high" reasoning effort to 'xhigh'
1 parent 7a47b58 commit dca3797

10 files changed

Lines changed: 453 additions & 52 deletions

File tree

apps/editor/src/commands/generate-commit-message-command/utils/get-commit-message-config.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as vscode from 'vscode'
22
import {
33
ModelProvidersManager,
4-
ReasoningEffort,
54
get_tool_config_id,
65
ToolConfig
76
} from '@/services/model-providers-manager'
@@ -16,7 +15,7 @@ export interface CommitMessageConfig {
1615
provider_name: string
1716
model: string
1817
temperature?: number
19-
reasoning_effort?: ReasoningEffort
18+
reasoning_effort?: string
2019
}
2120

2221
export const get_commit_message_config = async (

apps/editor/src/services/model-providers-manager.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,12 @@ export type CustomProvider = {
1717

1818
export type Provider = BuiltInProvider | CustomProvider
1919

20-
export type ReasoningEffort = 'none' | 'minimal' | 'low' | 'medium' | 'high'
21-
2220
export type ToolConfig = {
2321
provider_type: string
2422
provider_name: string
2523
model: string
2624
temperature?: number
27-
reasoning_effort?: ReasoningEffort
25+
reasoning_effort?: string
2826
system_instructions_override?: string
2927
is_pinned?: boolean
3028
}
@@ -162,7 +160,7 @@ export class ModelProvidersManager {
162160
providerName: string
163161
model: string
164162
temperature?: number
165-
reasoningEffort?: ReasoningEffort
163+
reasoningEffort?: string
166164
systemInstructionsOverride?: string
167165
isPinned?: boolean
168166
}[]
@@ -244,7 +242,7 @@ export class ModelProvidersManager {
244242
providerName: string
245243
model: string
246244
temperature?: number
247-
reasoningEffort?: ReasoningEffort
245+
reasoningEffort?: string
248246
systemInstructionsOverride?: string
249247
isDefault?: boolean
250248
isPinned?: boolean

apps/editor/src/utils/apply-reasoning-effort.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,16 @@ export const apply_reasoning_effort = (params: {
2222
}
2323
}
2424
}
25+
} else if (params.provider.name == 'ChatGPT') {
26+
if (params.reasoning_effort) {
27+
params.body.reasoning_effort =
28+
params.reasoning_effort == 'extra high'
29+
? 'xhigh'
30+
: params.reasoning_effort
31+
}
32+
} else {
33+
if (params.reasoning_effort) {
34+
params.body.reasoning_effort = params.reasoning_effort
35+
}
2536
}
2637
}

apps/editor/src/utils/intelligent-update-utils.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import * as vscode from 'vscode'
22
import axios, { CancelToken } from 'axios'
33
import {
44
ModelProvidersManager,
5-
ReasoningEffort,
65
ToolConfig,
76
get_tool_config_id
87
} from '@/services/model-providers-manager'
@@ -217,7 +216,7 @@ export const process_file = async (params: {
217216
provider: any
218217
model: string
219218
temperature?: number
220-
reasoning_effort?: ReasoningEffort
219+
reasoning_effort?: string
221220
file_path: string
222221
file_content: string
223222
instruction: string

apps/editor/src/utils/make-api-request.ts

Lines changed: 144 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,32 @@ const process_stream_chunk = (
3333
if (!json_string || json_string == DONE_TOKEN) continue
3434

3535
const json_data = JSON.parse(json_string)
36-
const content = json_data.choices?.[0]?.delta?.content
36+
let content = json_data.choices?.[0]?.delta?.content
37+
38+
// Special handling for ChatGPT provider
39+
if (content === undefined && json_data.type) {
40+
if (
41+
[
42+
'response.text.delta',
43+
'response.output_text.delta',
44+
'response.reasoning.delta',
45+
'response.reasoning_text.delta'
46+
].includes(json_data.type)
47+
) {
48+
content = json_data.delta
49+
} else if (json_data.type === 'response.content_part.added') {
50+
content =
51+
typeof json_data.part?.text === 'string'
52+
? json_data.part.text
53+
: json_data.part?.text?.value
54+
} else if (
55+
json_data.type === 'response.output_item.added' &&
56+
json_data.item?.type === 'text'
57+
) {
58+
content = json_data.item?.text
59+
}
60+
}
61+
3762
if (typeof content == 'string') {
3863
new_content += content
3964
}
@@ -76,12 +101,6 @@ export const make_api_request = async (params: {
76101
on_thinking_chunk?: ThinkingStreamCallback
77102
rethrow_error?: boolean
78103
}): Promise<{ response: string; thoughts?: string } | null> => {
79-
Logger.info({
80-
function_name: 'make_api_request',
81-
message: 'API Request Body',
82-
data: params.body
83-
})
84-
85104
let stream_start_time = 0
86105
let last_on_chunk_time = 0
87106
let total_tokens = 0
@@ -120,7 +139,79 @@ export const make_api_request = async (params: {
120139
}
121140

122141
try {
123-
const request_body = { ...params.body, stream: true }
142+
const is_chatgpt = params.endpoint_url.includes('chatgpt.com/backend-api')
143+
const request_url = is_chatgpt
144+
? params.endpoint_url + '/responses'
145+
: params.endpoint_url + '/chat/completions'
146+
147+
let request_body: any = { ...params.body, stream: true }
148+
149+
if (is_chatgpt) {
150+
const system_message = params.body.messages?.find(
151+
(m: any) => m.role === 'system'
152+
)
153+
const other_messages =
154+
params.body.messages?.filter((m: any) => m.role !== 'system') || []
155+
156+
const formatted_input = other_messages.map((m: any) => {
157+
if (m.role == 'user') {
158+
const content = []
159+
if (typeof m.content == 'string') {
160+
content.push({ type: 'input_text', text: m.content })
161+
} else if (Array.isArray(m.content)) {
162+
for (const block of m.content) {
163+
if (block.type == 'text') {
164+
content.push({ type: 'input_text', text: block.text })
165+
} else if (block.type == 'image_url') {
166+
content.push({
167+
type: 'input_image',
168+
image_url: block.image_url.url
169+
})
170+
}
171+
}
172+
}
173+
return { role: 'user', content }
174+
} else if (m.role == 'assistant') {
175+
const content = []
176+
if (typeof m.content == 'string') {
177+
content.push({ type: 'output_text', text: m.content })
178+
} else if (Array.isArray(m.content)) {
179+
for (const block of m.content) {
180+
if (block.type == 'text') {
181+
content.push({ type: 'output_text', text: block.text })
182+
}
183+
}
184+
}
185+
return { role: 'assistant', content }
186+
}
187+
return m
188+
})
189+
190+
request_body = {
191+
model: params.body.model,
192+
input: formatted_input,
193+
stream: true,
194+
store: false
195+
}
196+
if (system_message) {
197+
request_body.instructions = system_message.content
198+
}
199+
if (params.body.temperature !== undefined) {
200+
request_body.temperature = params.body.temperature
201+
}
202+
if (params.body.reasoning_effort) {
203+
request_body.reasoning = {
204+
effort: params.body.reasoning_effort,
205+
summary: 'auto'
206+
}
207+
}
208+
}
209+
210+
Logger.info({
211+
function_name: 'make_api_request',
212+
message: 'API Request Body',
213+
data: request_body
214+
})
124215

125216
let buffer = ''
126217
let full_response = ''
@@ -233,22 +324,28 @@ export const make_api_request = async (params: {
233324
}
234325
}
235326

327+
const headers: Record<string, string> = {
328+
...(params.api_key
329+
? { ['Authorization']: `Bearer ${params.api_key}` }
330+
: {}),
331+
['Content-Type']: 'application/json',
332+
...(params.endpoint_url == 'https://openrouter.ai/api/v1'
333+
? {
334+
'HTTP-Referer': 'https://codeweb.chat/',
335+
'X-Title': 'Code Web Chat'
336+
}
337+
: {})
338+
}
339+
340+
if (is_chatgpt) {
341+
headers['originator'] = 'code-web-chat'
342+
}
343+
236344
const response: AxiosResponse<NodeJS.ReadableStream> = await axios.post(
237-
params.endpoint_url + '/chat/completions',
345+
request_url,
238346
request_body,
239347
{
240-
headers: {
241-
...(params.api_key
242-
? { ['Authorization']: `Bearer ${params.api_key}` }
243-
: {}),
244-
['Content-Type']: 'application/json',
245-
...(params.endpoint_url == 'https://openrouter.ai/api/v1'
246-
? {
247-
'HTTP-Referer': 'https://codeweb.chat/',
248-
'X-Title': 'Code Web Chat'
249-
}
250-
: {})
251-
},
348+
headers,
252349
cancelToken: params.cancellation_token,
253350
responseType: 'stream'
254351
}
@@ -289,7 +386,32 @@ export const make_api_request = async (params: {
289386
const json_string = trimmed_line.slice(DATA_PREFIX.length).trim()
290387
if (json_string && json_string !== DONE_TOKEN) {
291388
const json_data = JSON.parse(json_string)
292-
const content = json_data.choices?.[0]?.delta?.content
389+
let content = json_data.choices?.[0]?.delta?.content
390+
391+
// Special handling for ChatGPT provider
392+
if (content === undefined && json_data.type) {
393+
if (
394+
[
395+
'response.text.delta',
396+
'response.output_text.delta',
397+
'response.reasoning.delta',
398+
'response.reasoning_text.delta'
399+
].includes(json_data.type)
400+
) {
401+
content = json_data.delta
402+
} else if (json_data.type === 'response.content_part.added') {
403+
content =
404+
typeof json_data.part?.text === 'string'
405+
? json_data.part.text
406+
: json_data.part?.text?.value
407+
} else if (
408+
json_data.type === 'response.output_item.added' &&
409+
json_data.item?.type === 'text'
410+
) {
411+
content = json_data.item?.text
412+
}
413+
}
414+
293415
if (typeof content == 'string') {
294416
process_content(content)
295417
}

apps/editor/src/views/settings/backend/message-handlers/handle-edit-model-provider-key.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
import { dictionary } from '@shared/constants/dictionary'
88
import { ChangeModelProviderKeyMessage } from '@/views/settings/types/messages'
99
import { handle_get_model_providers } from './handle-get-model-providers'
10+
import { authenticate_chatgpt } from '../../../utils/upsert-model-provider'
1011

1112
export const handle_change_model_provider_key = async (
1213
provider: SettingsProvider,
@@ -26,14 +27,35 @@ export const handle_change_model_provider_key = async (
2627
return
2728
}
2829

29-
const new_api_key = await vscode.window.showInputBox({
30-
title: `API Key for ${provider_name}`,
31-
prompt: 'Enter your API key.',
32-
password: true,
33-
placeHolder: provider_to_update.api_key
34-
? `...${provider_to_update.api_key.slice(-4)}`
35-
: ''
36-
})
30+
let new_api_key: string | undefined
31+
32+
if (
33+
provider_to_update.name == 'ChatGPT' &&
34+
provider_to_update.type == 'built-in'
35+
) {
36+
try {
37+
new_api_key = await authenticate_chatgpt()
38+
vscode.window.showInformationMessage('Authenticated successfully!', {
39+
modal: true,
40+
detail: 'Your ChatGPT session has been refreshed.'
41+
})
42+
} catch (error) {
43+
vscode.window.showErrorMessage(
44+
`ChatGPT authentication failed: ${error instanceof Error ? error.message : String(error)}`,
45+
{ modal: true }
46+
)
47+
return
48+
}
49+
} else {
50+
new_api_key = await vscode.window.showInputBox({
51+
title: `API Key for ${provider_name}`,
52+
prompt: 'Enter your API key.',
53+
password: true,
54+
placeHolder: provider_to_update.api_key
55+
? `...${provider_to_update.api_key.slice(-4)}`
56+
: ''
57+
})
58+
}
3759

3860
if (new_api_key === undefined) {
3961
return

0 commit comments

Comments
 (0)