diff --git a/.air/build-errors.log b/.air/build-errors.log index 701b5e5eee..70db047e8a 100644 --- a/.air/build-errors.log +++ b/.air/build-errors.log @@ -1 +1 @@ -exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1signal: terminatedexit status 1exit status 1signal: terminatedexit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1signal: terminatedsignal: terminatedexit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file +exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1signal: terminatedexit status 1exit status 1signal: terminatedexit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1signal: terminatedsignal: terminatedexit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000000..15b2e2b799 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,71 @@ +name: VitePress Pages + +on: + pull_request: + branches: [main] + paths: + - "docs/**" + - "package.json" + push: + branches: [main] + paths: + - "docs/**" + - "package.json" + workflow_dispatch: + +concurrency: + group: pages-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + name: Build Docs + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: docs/package.json + + - name: Install dependencies + working-directory: docs + run: npm install --frozen-lockfile + + - name: Build docs + working-directory: docs + run: npm run docs:build + + - name: Verify built docs + run: test -f docs/.vitepress/dist/index.html + + - name: Upload pages artifact + if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' + uses: actions/upload-pages-artifact@v3 + with: + path: docs/.vitepress/dist/ + + deploy: + name: Deploy Pages + if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Configure Pages + uses: actions/configure-pages@v5 + + - name: Deploy + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/vitepress-pages.yml b/.github/workflows/vitepress-pages.yml index 880e3a9aa8..67d5b81828 100644 --- a/.github/workflows/vitepress-pages.yml +++ b/.github/workflows/vitepress-pages.yml @@ -14,7 +14,7 @@ permissions: id-token: write concurrency: - group: cliproxy-vitepress-pages + group: cliproxy++-vitepress-pages cancel-in-progress: false jobs: diff --git a/README.md b/README.md index 12eaec1040..5992d1ad4e 100644 --- a/README.md +++ b/README.md @@ -68,11 +68,42 @@ providers: ## Documentation -- `docs/start-here.md` - Getting started guide -- `docs/provider-usage.md` - Provider configuration -- `docs/provider-quickstarts.md` - Per-provider guides -- `docs/api/` - API reference -- `docs/sdk-usage.md` - SDK guides +### Quick Links +- **[Getting Started](docs/getting-started.md)** - First run and configuration +- **[Installation](docs/install.md)** - Docker, binary, and source options +- **[Provider Usage](docs/provider-usage.md)** - Provider strategy and setup +- **[Provider Quickstarts](docs/provider-quickstarts.md)** - 5-minute success paths per provider +- **[API Reference](docs/api/)** - OpenAI-compatible and management APIs +- **[Operations](docs/operations/)** - Health, metrics, and incident workflows + +### Documentation Structure + +``` +docs/ +├── start-here.md # Start here guide +├── getting-started.md # First run tutorial +├── install.md # Installation options +├── provider-usage.md # Provider configuration +├── provider-quickstarts.md # Per-provider guides +├── provider-catalog.md # Provider reference +├── provider-operations.md # Operational runbooks +├── api/ # API documentation +│ ├── openai-compatible.md +│ ├── management.md +│ └── operations.md +├── guides/ # How-to guides +├── features/ # Feature deep-dives +├── tutorials/ # Step-by-step tutorials +├── reference/ # Command and config reference +└── operations/ # Runbooks and procedures +``` + +### GitHub Pages + +Documentation is automatically built and deployed via GitHub Actions: +- **Workflow**: `.github/workflows/docs.yml` +- **URL**: `https://kooshapari.github.io/cliproxy-plus-plus/` (after first deploy) +- **Local Dev**: `cd docs && npm run docs:dev` ## Environment diff --git a/internal/registry/model_definitions.go b/internal/registry/model_definitions.go index 1b69021d2c..9b8972275f 100644 --- a/internal/registry/model_definitions.go +++ b/internal/registry/model_definitions.go @@ -178,7 +178,7 @@ func GetGitHubCopilotModels() []*ModelInfo { Type: "github-copilot", DisplayName: "GPT-5", Description: "OpenAI GPT-5 via GitHub Copilot", - ContextLength: 200000, + ContextLength: 128000, MaxCompletionTokens: 32768, SupportedEndpoints: []string{"/chat/completions", "/responses"}, Thinking: &ThinkingSupport{Levels: []string{"low", "medium", "high"}}, @@ -204,7 +204,7 @@ func GetGitHubCopilotModels() []*ModelInfo { Type: "github-copilot", DisplayName: "GPT-5 Codex", Description: "OpenAI GPT-5 Codex via GitHub Copilot", - ContextLength: 200000, + ContextLength: 128000, MaxCompletionTokens: 32768, SupportedEndpoints: []string{"/responses"}, Thinking: &ThinkingSupport{Levels: []string{"low", "medium", "high"}}, @@ -217,7 +217,7 @@ func GetGitHubCopilotModels() []*ModelInfo { Type: "github-copilot", DisplayName: "GPT-5.1", Description: "OpenAI GPT-5.1 via GitHub Copilot", - ContextLength: 200000, + ContextLength: 128000, MaxCompletionTokens: 32768, SupportedEndpoints: []string{"/chat/completions", "/responses"}, Thinking: &ThinkingSupport{Levels: []string{"none", "low", "medium", "high"}}, @@ -230,7 +230,7 @@ func GetGitHubCopilotModels() []*ModelInfo { Type: "github-copilot", DisplayName: "GPT-5.1 Codex", Description: "OpenAI GPT-5.1 Codex via GitHub Copilot", - ContextLength: 200000, + ContextLength: 128000, MaxCompletionTokens: 32768, SupportedEndpoints: []string{"/responses"}, Thinking: &ThinkingSupport{Levels: []string{"none", "low", "medium", "high"}}, diff --git a/internal/translator/gemini/openai/responses/gemini_openai-responses_request.go b/internal/translator/gemini/openai/responses/gemini_openai-responses_request.go index aca0171781..087ee0e322 100644 --- a/internal/translator/gemini/openai/responses/gemini_openai-responses_request.go +++ b/internal/translator/gemini/openai/responses/gemini_openai-responses_request.go @@ -357,16 +357,9 @@ func ConvertOpenAIResponsesRequestToGemini(modelName string, inputRawJSON []byte // Convert parameter types from OpenAI format to Gemini format cleaned := params.Raw // Convert type values to uppercase for Gemini - paramsResult := gjson.Parse(cleaned) - if properties := paramsResult.Get("properties"); properties.Exists() { - properties.ForEach(func(key, value gjson.Result) bool { - if propType := value.Get("type"); propType.Exists() { - upperType := strings.ToUpper(propType.String()) - cleaned, _ = sjson.Set(cleaned, "properties."+key.String()+".type", upperType) - } - return true - }) - } + // Skip type uppercasing - let CleanJSONSchemaForGemini handle type arrays + // This fixes the bug where nullable type arrays like ["string","null"] were + // incorrectly converted to strings causing 400 errors on Gemini API // Set the overall type to OBJECT cleaned, _ = sjson.Set(cleaned, "type", "OBJECT") funcDecl, _ = sjson.SetRaw(funcDecl, "parametersJsonSchema", cleaned) diff --git a/internal/translator/kiro/claude/truncation_detector.go b/internal/translator/kiro/claude/truncation_detector.go index b05ec11acd..65c5f5a87e 100644 --- a/internal/translator/kiro/claude/truncation_detector.go +++ b/internal/translator/kiro/claude/truncation_detector.go @@ -63,7 +63,7 @@ var RequiredFieldsByTool = map[string][]string{ "edit_file": {"path"}, "apply_diff": {"path", "diff"}, "str_replace_editor": {"path", "old_str", "new_str"}, - "Bash": {"command"}, + "Bash": {"command", "cmd"}, // Ampcode uses "cmd", others use "command" "execute": {"command"}, "run_command": {"command"}, } diff --git a/pkg/llmproxy/executor/codex_executor.go b/pkg/llmproxy/executor/codex_executor.go index fb5f47ed11..b65c970c7d 100644 --- a/pkg/llmproxy/executor/codex_executor.go +++ b/pkg/llmproxy/executor/codex_executor.go @@ -364,7 +364,20 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au } appendAPIResponseChunk(ctx, e.cfg, data) logWithRequestID(ctx).Debugf("request error, error status: %d, error message: %s", httpResp.StatusCode, summarizeErrorBody(httpResp.Header.Get("Content-Type"), data)) - err = statusErr{code: httpResp.StatusCode, msg: string(data)} + // Check for unsupported model errors and provide a clearer message + errMsg := string(data) + if httpResp.StatusCode == 400 && strings.Contains(errMsg, "not supported") { + // Provide a user-friendly error for unsupported models with ChatGPT cookies + if strings.Contains(errMsg, "gpt-5.3-codex-spark") || strings.Contains(errMsg, "codex-spark") { + err = statusErr{ + code: httpResp.StatusCode, + msg: "Model gpt-5.3-codex-spark requires a Plus/Team/Enterprise ChatGPT account. Please upgrade your plan or use a different provider. Original error: " + errMsg, + } + return nil, err + } + } + + err = statusErr{code: httpResp.StatusCode, msg: errMsg} return nil, err } out := make(chan cliproxyexecutor.StreamChunk)