Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions .github/workflows/docker-ghcr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
name: Publish Docker image to GHCR

# 基于官方 docker-image-branch.yml 改造:
# - 登录从 Docker Hub 改为 GitHub Container Registry (GITHUB_TOKEN 自动认证)
# - 镜像名从 calciumion/new-api 改为 ghcr.io/${{ github.repository }}
# - 触发:推送到 main 分支自动构建(也支持手动 workflow_dispatch)
# - 去掉 cosign 签名(fork 自用不需要)
# 多架构构建逻辑(单架构 build + manifest 合并)与官方一致。

on:
push:
branches: [main]
workflow_dispatch:
inputs:
branch:
description: "Branch name to build (默认 main)"
required: false
default: main
type: string
Comment on lines +10 to +19

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Serialize publications of mutable tags.

Two main runs can complete out of order. The older run can then overwrite the newer main and latest manifests.

Add a workflow-level concurrency group that uses the resolved build branch.

Proposed fix
 on:
   push:
     branches: [main]
   workflow_dispatch:
     inputs:
       branch:
         description: "Branch name to build (默认 main)"
         required: false
         default: main
         type: string

+concurrency:
+  group: ghcr-${{ github.repository }}-${{ inputs.branch || github.ref_name }}
+  cancel-in-progress: true
+
 env:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
on:
push:
branches: [main]
workflow_dispatch:
inputs:
branch:
description: "Branch name to build (默认 main)"
required: false
default: main
type: string
on:
push:
branches: [main]
workflow_dispatch:
inputs:
branch:
description: "Branch name to build (默认 main)"
required: false
default: main
type: string
concurrency:
group: ghcr-${{ github.repository }}-${{ inputs.branch || github.ref_name }}
cancel-in-progress: true
env:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/docker-ghcr.yml around lines 10 - 19, Add a workflow-level
concurrency group in the Docker GHCR workflow using the resolved build branch,
so runs targeting the same branch serialize and prevent older publications from
overwriting newer main/latest manifests. Preserve the existing push and
workflow_dispatch behavior.


env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
prepare:
name: Prepare Docker tags
runs-on: ubuntu-latest
outputs:
branch: ${{ steps.version.outputs.branch }}
sha: ${{ steps.version.outputs.sha }}
tag_prefix: ${{ steps.version.outputs.tag_prefix }}
version: ${{ steps.version.outputs.version }}
permissions:
contents: read
steps:
- name: Check out branch
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 1
ref: ${{ inputs.branch || github.ref }}
Comment on lines +37 to +41

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Disable persisted checkout credentials.

actions/checkout persists its token in the workspace by default. The build job later supplies that workspace as the Docker build context. Disable credential persistence because the shown commands do not need Git authentication after checkout.

  • .github/workflows/docker-ghcr.yml#L37-L41: add persist-credentials: false.
  • .github/workflows/docker-ghcr.yml#L84-L88: add persist-credentials: false.
🧰 Tools
🪛 zizmor (1.28.0)

[warning] 37-41: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

📍 Affects 1 file
  • .github/workflows/docker-ghcr.yml#L37-L41 (this comment)
  • .github/workflows/docker-ghcr.yml#L84-L88
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/docker-ghcr.yml around lines 37 - 41, Disable persisted
checkout credentials by adding persist-credentials: false to the
actions/checkout steps at .github/workflows/docker-ghcr.yml lines 37-41 and
84-88; apply the same change to both checkout sites.

Source: Linters/SAST tools


- name: Resolve Docker tags
id: version
env:
BRANCH_NAME: ${{ inputs.branch || github.ref_name }}
run: |
TAG_PREFIX=$(printf '%s' "$BRANCH_NAME" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9_.-]+/-/g; s/^[.-]+//; s/[.-]+$//')
TAG_PREFIX=${TAG_PREFIX:0:105}
TAG_PREFIX=$(printf '%s' "$TAG_PREFIX" | sed -E 's/[.-]+$//')
if [ -z "$TAG_PREFIX" ]; then
echo "::error::Branch '$BRANCH_NAME' cannot be converted to a valid Docker tag prefix"
exit 1
fi

SHA=$(git rev-parse HEAD)
SHORT_SHA=$(git rev-parse --short HEAD)
VERSION="${TAG_PREFIX}-$(date +'%Y%m%d')-${SHORT_SHA}"

echo "branch=$BRANCH_NAME" >> "$GITHUB_OUTPUT"
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
echo "tag_prefix=$TAG_PREFIX" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Prepared Docker tags for $BRANCH_NAME at $SHORT_SHA"

build_single_arch:
name: Build & push (${{ matrix.arch }}) [native]
needs: [prepare]
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
platform: linux/amd64
runner: ubuntu-latest
- arch: arm64
platform: linux/arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
permissions:
contents: read
packages: write
steps:
- name: Check out branch
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 1
ref: ${{ needs.prepare.outputs.sha }}

- name: Write VERSION
run: |
echo "${{ needs.prepare.outputs.version }}" > VERSION
echo "Publishing version: ${{ needs.prepare.outputs.version }} for ${{ matrix.arch }}"

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0

- name: Log in to GHCR
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (labels)
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

- name: Build & push single-arch
id: build
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
platforms: ${{ matrix.platform }}
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.tag_prefix }}-${{ matrix.arch }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.version }}-${{ matrix.arch }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Output digest
run: |
echo "### Docker Image Digest (${{ matrix.arch }})" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.tag_prefix }}-${{ matrix.arch }}" >> $GITHUB_STEP_SUMMARY
echo "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.version }}-${{ matrix.arch }}" >> $GITHUB_STEP_SUMMARY
echo "${{ steps.build.outputs.digest }}" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY

create_manifests:
name: Create multi-arch manifests (GHCR)
needs: [prepare, build_single_arch]
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- name: Log in to GHCR
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4.3.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Create & push manifest (GHCR - branch)
run: |
docker buildx imagetools create \
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.tag_prefix }} \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.tag_prefix }}-amd64 \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.tag_prefix }}-arm64

- name: Create & push manifest (GHCR - versioned)
run: |
docker buildx imagetools create \
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.version }} \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.version }}-amd64 \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.version }}-arm64

- name: Create & push manifest (latest)
run: |
docker buildx imagetools create \
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.tag_prefix }}-amd64 \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.tag_prefix }}-arm64
Comment on lines +162 to +167

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Publish latest only from main.

A manual run can select a non-main branch. This step still overwrites :latest with that branch image.

Add a condition that permits this manifest only when needs.prepare.outputs.branch is main.

Proposed fix
       - name: Create & push manifest (latest)
+        if: needs.prepare.outputs.branch == 'main'
         run: |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Create & push manifest (latest)
run: |
docker buildx imagetools create \
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.tag_prefix }}-amd64 \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.tag_prefix }}-arm64
- name: Create & push manifest (latest)
if: needs.prepare.outputs.branch == 'main'
run: |
docker buildx imagetools create \
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.tag_prefix }}-amd64 \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.tag_prefix }}-arm64
🧰 Tools
🪛 zizmor (1.28.0)

[warning] 165-165: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[warning] 166-166: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[info] 166-166: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[warning] 167-167: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[info] 167-167: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/docker-ghcr.yml around lines 162 - 167, Restrict the
“Create & push manifest (latest)” step to runs where
needs.prepare.outputs.branch equals main by adding the workflow condition at the
step level. Keep the existing manifest creation command unchanged for
main-branch runs.


- name: Output manifest digest
run: |
echo "### Multi-arch Manifest Digests" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare.outputs.tag_prefix }} >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
2 changes: 2 additions & 0 deletions common/api_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ func ChannelType2APIType(channelType int) (int, bool) {
apiType = constant.APITypeSub2API
case constant.ChannelTypeNewAPI:
apiType = constant.APITypeNewAPI
case constant.ChannelTypeBaichuan:
apiType = constant.APITypeBaichuan
}
if apiType == -1 {
return constant.APITypeOpenAI, false
Expand Down
1 change: 1 addition & 0 deletions constant/api_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ const (
APITypeAdvancedCustom
APITypeSub2API
APITypeNewAPI
APITypeBaichuan
APITypeDummy // this one is only for count, do not add any channel after this
)
3 changes: 3 additions & 0 deletions constant/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const (
ChannelTypeAdvancedCustom = 58
ChannelTypeSub2API = 59
ChannelTypeNewAPI = 60
ChannelTypeBaichuan = 61
ChannelTypeDummy // this one is only for count, do not add any channel after this

)
Expand Down Expand Up @@ -124,6 +125,7 @@ var ChannelBaseURLs = []string{
"", //58
"", //59
"", //60
"https://api.baichuan-ai.com", //61
}

var ChannelTypeNames = map[int]string{
Expand Down Expand Up @@ -184,6 +186,7 @@ var ChannelTypeNames = map[int]string{
ChannelTypeAdvancedCustom: "Advanced Custom",
ChannelTypeSub2API: "Sub2API",
ChannelTypeNewAPI: "New API",
ChannelTypeBaichuan: "Baichuan",
}

func GetChannelTypeName(channelType int) string {
Expand Down
40 changes: 34 additions & 6 deletions controller/channel-test.go
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,24 @@ func detectErrorMessageFromJSONBytes(jsonBytes []byte) string {
return message
}

// useMaxCompletionTokens reports whether the channel test request should send
// max_completion_tokens instead of max_tokens for the given model. The rule is
// kept in sync with the relay-side conversion in relay/channel/openai/adaptor.go
// (o-series and gpt-5-series always use max_completion_tokens). Azure OpenAI
// additionally rejects max_tokens for the gpt-4o / gpt-4.1 family, so those are
// routed through max_completion_tokens as well when the channel is Azure.
func useMaxCompletionTokens(model string, channel *model.Channel) bool {
if dto.IsOpenAIReasoningOModel(model) || dto.IsOpenAIGPT5Model(model) {
return true
}
if channel != nil && channel.Type == constant.ChannelTypeAzure {
if strings.HasPrefix(model, "gpt-4o") || strings.HasPrefix(model, "gpt-4.1") {
return true
}
}
return false
}

func buildTestRequest(model string, endpointType string, channel *model.Channel, isStream bool) dto.Request {
testResponsesInput := json.RawMessage(`[{"role":"user","content":"hi"}]`)

Expand Down Expand Up @@ -735,10 +753,6 @@ func buildTestRequest(model string, endpointType string, channel *model.Channel,
}
case constant.EndpointTypeAnthropic, constant.EndpointTypeGemini, constant.EndpointTypeOpenAI:
// 返回 GeneralOpenAIRequest
maxTokens := uint(16)
if constant.EndpointType(endpointType) == constant.EndpointTypeGemini {
maxTokens = 3000
}
req := &dto.GeneralOpenAIRequest{
Model: model,
Stream: lo.ToPtr(isStream),
Expand All @@ -748,11 +762,20 @@ func buildTestRequest(model string, endpointType string, channel *model.Channel,
Content: "hi",
},
},
MaxTokens: lo.ToPtr(maxTokens),
}
if isStream {
req.StreamOptions = &dto.StreamOptions{IncludeUsage: true}
}
// Gemini 期望较大的 max_tokens;其余模型默认 16。对要求
// max_completion_tokens 的模型(o 系列 / gpt-5 系列,以及 Azure 上
// 的 gpt-4o / gpt-4.1 系列),改用 MaxCompletionTokens。
if constant.EndpointType(endpointType) == constant.EndpointTypeGemini {
req.MaxTokens = lo.ToPtr(uint(3000))
} else if useMaxCompletionTokens(model, channel) {
req.MaxCompletionTokens = lo.ToPtr(uint(16))
} else {
req.MaxTokens = lo.ToPtr(uint(16))
}
return req
}
}
Expand Down Expand Up @@ -810,7 +833,12 @@ func buildTestRequest(model string, endpointType string, channel *model.Channel,
testRequest.StreamOptions = &dto.StreamOptions{IncludeUsage: true}
}

if dto.IsOpenAIReasoningOModel(model) {
// Determine which max-tokens parameter to use. This must stay aligned with
// the relay-side conversion in relay/channel/openai/adaptor.go (o-series and
// gpt-5-series use max_completion_tokens). Azure additionally rejects
// max_tokens for the gpt-4o / gpt-4.1 family, so route those through
// max_completion_tokens too when the channel is Azure.
if useMaxCompletionTokens(model, channel) {
testRequest.MaxCompletionTokens = lo.ToPtr(uint(16))
} else if strings.Contains(model, "thinking") {
if !strings.Contains(model, "claude") {
Expand Down
18 changes: 18 additions & 0 deletions controller/channel_upstream_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,24 @@ func fetchChannelUpstreamModelIDs(channel *model.Channel) ([]string, error) {
return nil, sanitizeFetchModelsError(err, key)
}

// Baichuan returns model names under data[].model instead of the OpenAI-standard data[].id
if channel.Type == constant.ChannelTypeBaichuan {
var baichuanResult struct {
Data []struct {
Model string `json:"model"`
} `json:"data"`
}
if err := common.Unmarshal(body, &baichuanResult); err != nil {
return nil, err
}
ids := lo.Map(baichuanResult.Data, func(item struct {
Model string `json:"model"`
}, _ int) string {
return item.Model
})
return normalizeModelNames(ids), nil
}

var result OpenAIModelsResponse
if err := common.Unmarshal(body, &result); err != nil {
return nil, err
Expand Down
30 changes: 20 additions & 10 deletions relay/channel/openai/adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,17 +153,27 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
return relaycommon.GetFullRequestURL(info.ChannelBaseUrl, requestURL, info.ChannelType), nil
}

model_ := info.UpstreamModelName
// 2025年5月10日后创建的渠道不移除.
if info.ChannelCreateTime < constant.AzureNoRemoveDotTime {
model_ = strings.Replace(model_, ".", "", -1)
}
// https://github.com/songquanpeng/one-api/issues/67
requestURL = fmt.Sprintf("/openai/deployments/%s/%s", model_, task)
if info.RelayMode == relayconstant.RelayModeRealtime {
requestURL = fmt.Sprintf("/openai/realtime?deployment=%s&api-version=%s", model_, apiVersion)
}
model_ := info.UpstreamModelName
// 2025年5月10日后创建的渠道不移除.
if info.ChannelCreateTime < constant.AzureNoRemoveDotTime {
model_ = strings.Replace(model_, ".", "", -1)
}
// 新版 Azure AI Foundry 资源(endpoint 形如 xxx.services.ai.azure.com)
// 使用标准 OpenAI 路径(/openai/v1/chat/completions),模型名放在请求体里而非 URL,
// 也不需要 api-version 查询参数。仅当原始请求路径以 /v1 开头时走此分支;
// 此时把 /v1 替换为 /openai/v1 拼接到域名 base URL 上。
// 经典 Azure OpenAI 资源(cognitiveservices.azure.com)仍走 deployment 路径。
// 注意:base URL 只填到域名(不要带 /openai/v1 后缀,否则路径会重复)。
if strings.HasPrefix(info.RequestURLPath, "/v1") {
requestURL := "/openai" + info.RequestURLPath
return relaycommon.GetFullRequestURL(info.ChannelBaseUrl, requestURL, info.ChannelType), nil
}
// https://github.com/songquanpeng/one-api/issues/67
requestURL = fmt.Sprintf("/openai/deployments/%s/%s", model_, task)
if info.RelayMode == relayconstant.RelayModeRealtime {
requestURL = fmt.Sprintf("/openai/realtime?deployment=%s&api-version=%s", model_, apiVersion)
}
return relaycommon.GetFullRequestURL(info.ChannelBaseUrl, requestURL, info.ChannelType), nil
//case constant.ChannelTypeMiniMax:
// return minimax.GetRequestURL(info)
case constant.ChannelTypeCustom:
Expand Down
1 change: 1 addition & 0 deletions relay/common/relay_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ var streamSupportedChannels = map[int]bool{
constant.ChannelTypeSub2API: true,
constant.ChannelTypeNewAPI: true,
constant.ChannelTypeTencent: true,
constant.ChannelTypeBaichuan: true,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Expect the StreamOptions clearing logic to honor SupportStreamOptions
# or explicitly allow ChannelTypeBaichuan.
rg -n -C 8 \
  'request\.StreamOptions = nil|SupportStreamOptions|ChannelTypeBaichuan' \
  --glob '*.go' .

Repository: QuantumNous/new-api

Length of output: 15042


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '== relay_info init/StreamOptions section ==\n'
sed -n '200,245p' relay/common/relay_info.go
printf '\n== streamSupportedChannels section ==\n'
sed -n '330,355p' relay/common/relay_info.go

printf '\n== relay adaptor routing around ChannelTypeBaichuan ==\n'
sed -n '110,150p' relay/relay_adaptor.go

printf '\n== relevant OpenAI adaptor snippets ==\n'
sed -n '60,95p' relay/channel/openai/adaptor.go
sed -n '244,252p' relay/channel/openai/adaptor.go

printf '\n== channel adapter ConvertStreamOpenAIRequest references ==\n'
rg -n -C 5 'ConvertStreamOpenAIRequest|ConvertOpenAIRequest|SupportStreamOptions|StreamOptions' relay/channel --glob '*.go'

Repository: QuantumNous/new-api

Length of output: 50375


Preserve stream_options through the OpenAI adaptor.

ChannelTypeBaichuan is routed to openai.Adaptor, and SupportStreamOptions is set to true, but openai.Adaptor.ConvertOpenAIRequest still clears request.StreamOptions for non-OpenAI/non-Azure channels. Baichuan streaming requests lose stream_options before the upstream call. Add ChannelTypeBaichuan to the OpenAI adaptor’s allow-list, or have ConvertOpenAIRequest preserve stream options when info.SupportStreamOptions == true. Add a conversion test that checks Baichuan streaming requests keep stream_options.include_usage.

Suggested fix in the OpenAI adaptor
-	if info.ChannelType != constant.ChannelTypeOpenAI && info.ChannelType != constant.ChannelTypeAzure {
+	if !info.SupportStreamOptions &&
+		info.ChannelType != constant.ChannelTypeOpenAI &&
+		info.ChannelType != constant.ChannelTypeAzure {
		request.StreamOptions = nil
	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@relay/common/relay_info.go` at line 351, Update
openai.Adaptor.ConvertOpenAIRequest to preserve StreamOptions for
ChannelTypeBaichuan, matching its SupportStreamOptions configuration instead of
clearing the field for non-OpenAI/non-Azure channels. Add a conversion test
covering a Baichuan streaming request and verify stream_options.include_usage
remains present.

Source: Coding guidelines

}

func GenRelayInfoWs(c *gin.Context, ws *websocket.Conn) *RelayInfo {
Expand Down
2 changes: 2 additions & 0 deletions relay/relay_adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ func GetAdaptor(apiType int) channel.Adaptor {
return &sub2api.Adaptor{}
case constant.APITypeNewAPI:
return &newapi.Adaptor{}
case constant.APITypeBaichuan:
return &openai.Adaptor{}
}
return nil
}
Expand Down
Loading