Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
eea6c2d0
"vscode:/vscode.git/clone" did not exist on "46ea9170cbf4334decbcd28be9d76a76db2a75ab"
Commit
eea6c2d0
authored
Jan 13, 2026
by
yangjianbo
Browse files
fix(网关): 补齐Codex指令回退与输入过滤
parents
70eaa450
55796a11
Changes
3
Expand all
Show whitespace changes
Inline
Side-by-side
backend/internal/service/openai_codex_transform.go
View file @
eea6c2d0
package
service
import
(
_
"embed"
"encoding/json"
"fmt"
"io"
...
...
@@ -16,6 +17,9 @@ const (
codexCacheTTL
=
15
*
time
.
Minute
)
//go:embed prompts/codex_cli_instructions.md
var
codexCLIInstructions
string
var
codexModelMap
=
map
[
string
]
string
{
"gpt-5.1-codex"
:
"gpt-5.1-codex"
,
"gpt-5.1-codex-low"
:
"gpt-5.1-codex"
,
...
...
@@ -129,6 +133,13 @@ func applyCodexOAuthTransform(reqBody map[string]any) codexTransformResult {
reqBody
[
"instructions"
]
=
instructions
result
.
Modified
=
true
}
}
else
if
existingInstructions
==
""
{
// 未获取到 opencode 指令时,回退使用 Codex CLI 指令。
codexInstructions
:=
strings
.
TrimSpace
(
getCodexCLIInstructions
())
if
codexInstructions
!=
""
{
reqBody
[
"instructions"
]
=
codexInstructions
result
.
Modified
=
true
}
}
// 续链场景保留 item_reference 与 id,避免 call_id 上下文丢失。
...
...
@@ -246,13 +257,72 @@ func getOpenCodeCachedPrompt(url, cacheFileName, metaFileName string) string {
}
func
getOpenCodeCodexHeader
()
string
{
return
getOpenCodeCachedPrompt
(
opencodeCodexHeaderURL
,
"opencode-codex-header.txt"
,
"opencode-codex-header-meta.json"
)
// 优先从 opencode 仓库缓存获取指令。
opencodeInstructions
:=
getOpenCodeCachedPrompt
(
opencodeCodexHeaderURL
,
"opencode-codex-header.txt"
,
"opencode-codex-header-meta.json"
)
// 若 opencode 指令可用,直接返回。
if
opencodeInstructions
!=
""
{
return
opencodeInstructions
}
// 否则回退使用本地 Codex CLI 指令。
return
getCodexCLIInstructions
()
}
func
getCodexCLIInstructions
()
string
{
return
codexCLIInstructions
}
func
GetOpenCodeInstructions
()
string
{
return
getOpenCodeCodexHeader
()
}
// GetCodexCLIInstructions 返回内置的 Codex CLI 指令内容。
func
GetCodexCLIInstructions
()
string
{
return
getCodexCLIInstructions
()
}
// ReplaceWithCodexInstructions 将请求 instructions 替换为内置 Codex 指令(必要时)。
func
ReplaceWithCodexInstructions
(
reqBody
map
[
string
]
any
)
bool
{
codexInstructions
:=
strings
.
TrimSpace
(
getCodexCLIInstructions
())
if
codexInstructions
==
""
{
return
false
}
existingInstructions
,
_
:=
reqBody
[
"instructions"
]
.
(
string
)
if
strings
.
TrimSpace
(
existingInstructions
)
!=
codexInstructions
{
reqBody
[
"instructions"
]
=
codexInstructions
return
true
}
return
false
}
// IsInstructionError 判断错误信息是否与指令格式/系统提示相关。
func
IsInstructionError
(
errorMessage
string
)
bool
{
if
errorMessage
==
""
{
return
false
}
lowerMsg
:=
strings
.
ToLower
(
errorMessage
)
instructionKeywords
:=
[]
string
{
"instruction"
,
"instructions"
,
"system prompt"
,
"system message"
,
"invalid prompt"
,
"prompt format"
,
}
for
_
,
keyword
:=
range
instructionKeywords
{
if
strings
.
Contains
(
lowerMsg
,
keyword
)
{
return
true
}
}
return
false
}
// filterCodexInput 按需过滤 item_reference 与 id。
// preserveReferences 为 true 时保持引用与 id,以满足续链请求对上下文的依赖。
func
filterCodexInput
(
input
[]
any
,
preserveReferences
bool
)
[]
any
{
...
...
@@ -263,24 +333,62 @@ func filterCodexInput(input []any, preserveReferences bool) []any {
filtered
=
append
(
filtered
,
item
)
continue
}
if
typ
,
ok
:=
m
[
"type"
]
.
(
string
);
ok
&&
typ
==
"item_reference"
{
typ
,
_
:=
m
[
"type"
]
.
(
string
)
if
typ
==
"item_reference"
{
if
!
preserveReferences
{
continue
}
newItem
:=
make
(
map
[
string
]
any
,
len
(
m
))
for
key
,
value
:=
range
m
{
newItem
[
key
]
=
value
}
filtered
=
append
(
filtered
,
newItem
)
continue
}
newItem
:=
m
if
!
preserveReferences
{
copied
:=
false
// 仅在需要修改字段时创建副本,避免直接改写原始输入。
ensureCopy
:=
func
()
{
if
copied
{
return
}
newItem
=
make
(
map
[
string
]
any
,
len
(
m
))
for
key
,
value
:=
range
m
{
newItem
[
key
]
=
value
}
copied
=
true
}
if
isCodexToolCallItemType
(
typ
)
{
if
callID
,
ok
:=
m
[
"call_id"
]
.
(
string
);
!
ok
||
strings
.
TrimSpace
(
callID
)
==
""
{
if
id
,
ok
:=
m
[
"id"
]
.
(
string
);
ok
&&
strings
.
TrimSpace
(
id
)
!=
""
{
ensureCopy
()
newItem
[
"call_id"
]
=
id
}
}
}
if
!
preserveReferences
{
ensureCopy
()
delete
(
newItem
,
"id"
)
if
!
isCodexToolCallItemType
(
typ
)
{
delete
(
newItem
,
"call_id"
)
}
}
filtered
=
append
(
filtered
,
newItem
)
}
return
filtered
}
func
isCodexToolCallItemType
(
typ
string
)
bool
{
if
typ
==
""
{
return
false
}
return
strings
.
HasSuffix
(
typ
,
"_call"
)
||
strings
.
HasSuffix
(
typ
,
"_call_output"
)
}
func
normalizeCodexTools
(
reqBody
map
[
string
]
any
)
bool
{
rawTools
,
ok
:=
reqBody
[
"tools"
]
if
!
ok
||
rawTools
==
nil
{
...
...
backend/internal/service/openai_gateway_service.go
View file @
eea6c2d0
...
...
@@ -42,6 +42,7 @@ var openaiSSEDataRe = regexp.MustCompile(`^data:\s*`)
var
openaiAllowedHeaders
=
map
[
string
]
bool
{
"accept-language"
:
true
,
"content-type"
:
true
,
"conversation_id"
:
true
,
"user-agent"
:
true
,
"originator"
:
true
,
"session_id"
:
true
,
...
...
@@ -545,7 +546,7 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
isCodexCLI
:=
openai
.
IsCodexCLIRequest
(
c
.
GetHeader
(
"User-Agent"
))
//
Apply model mapping for all requests (including
Codex CLI
)
//
对所有请求执行模型映射(包含
Codex CLI
)。
mappedModel
:=
account
.
GetMappedModel
(
reqModel
)
if
mappedModel
!=
reqModel
{
log
.
Printf
(
"[OpenAI] Model mapping applied: %s -> %s (account: %s, isCodexCLI: %v)"
,
reqModel
,
mappedModel
,
account
.
Name
,
isCodexCLI
)
...
...
@@ -553,6 +554,27 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
bodyModified
=
true
}
// 针对所有 OpenAI 账号执行 Codex 模型名规范化,确保上游识别一致。
if
model
,
ok
:=
reqBody
[
"model"
]
.
(
string
);
ok
{
normalizedModel
:=
normalizeCodexModel
(
model
)
if
normalizedModel
!=
""
&&
normalizedModel
!=
model
{
log
.
Printf
(
"[OpenAI] Codex model normalization: %s -> %s (account: %s, type: %s, isCodexCLI: %v)"
,
model
,
normalizedModel
,
account
.
Name
,
account
.
Type
,
isCodexCLI
)
reqBody
[
"model"
]
=
normalizedModel
mappedModel
=
normalizedModel
bodyModified
=
true
}
}
// 规范化 reasoning.effort 参数(minimal -> none),与上游允许值对齐。
if
reasoning
,
ok
:=
reqBody
[
"reasoning"
]
.
(
map
[
string
]
any
);
ok
{
if
effort
,
ok
:=
reasoning
[
"effort"
]
.
(
string
);
ok
&&
effort
==
"minimal"
{
reasoning
[
"effort"
]
=
"none"
bodyModified
=
true
log
.
Printf
(
"[OpenAI] Normalized reasoning.effort: minimal -> none (account: %s)"
,
account
.
Name
)
}
}
if
account
.
Type
==
AccountTypeOAuth
&&
!
isCodexCLI
{
codexResult
:=
applyCodexOAuthTransform
(
reqBody
)
if
codexResult
.
Modified
{
...
...
@@ -783,9 +805,6 @@ func (s *OpenAIGatewayService) buildUpstreamRequest(ctx context.Context, c *gin.
if
promptCacheKey
!=
""
{
req
.
Header
.
Set
(
"conversation_id"
,
promptCacheKey
)
req
.
Header
.
Set
(
"session_id"
,
promptCacheKey
)
}
else
{
req
.
Header
.
Del
(
"conversation_id"
)
req
.
Header
.
Del
(
"session_id"
)
}
}
...
...
backend/internal/service/prompts/codex_cli_instructions.md
0 → 100644
View file @
eea6c2d0
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment