-
Notifications
You must be signed in to change notification settings - Fork 1k
feat(provider): add official CodeBuddy Global and CN providers #3340
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
Flowershangfromthebranches
wants to merge
4
commits into
lidge-jun:dev
Choose a base branch
from
Flowershangfromthebranches:feat/codebuddy-official-providers
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
7e56b63
feat(provider): add official CodeBuddy Global and CN providers
Flowershangfromthebranches f651611
fix(provider): harden CodeBuddy CLI lifecycle
Flowershangfromthebranches 18530f8
fix(provider): address CodeBuddy runtime review
Flowershangfromthebranches 4b705e9
docs(codebuddy): document adapter factory and turn input contracts
Flowershangfromthebranches File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| import type { AdapterEvent, OcxParsedRequest, OcxProviderConfig } from "../../types"; | ||
| import type { AdapterRequest, ProviderAdapter } from "../base"; | ||
| import { mapReasoningEffort } from "../../reasoning-effort"; | ||
| import { buildSystemPrompt } from "../coding-agent/protocol"; | ||
| import { baseScopedEnv, runCodingAgentTurn, type CodingAgentDeps, type SpawnFn } from "../coding-agent/turn"; | ||
| import { CODEBUDDY_PROFILES, type CodeBuddyProfile } from "./profiles"; | ||
|
|
||
| export type { SpawnFn } from "../coding-agent/turn"; | ||
| export type CodeBuddyAdapterDeps = CodingAgentDeps; | ||
|
|
||
| /** | ||
| * Build the scoped child-process environment for a CodeBuddy turn (§六/§十四). | ||
| * | ||
| * The region switch and credential are layered on top of the shared base env, which never inherits a | ||
| * parent `CODEBUDDY_*`. `CODEBUDDY_CODE_DISABLE_BACKGROUND_TASKS=1` matches the vendor SDK's own | ||
| * single-shot behavior (a `-p` turn stops at the first result and cannot receive cross-turn | ||
| * background push-back). | ||
| */ | ||
| export function buildChildEnv(profile: CodeBuddyProfile, apiKey: string): Record<string, string> { | ||
| return { | ||
| ...baseScopedEnv(), | ||
| CODEBUDDY_API_KEY: apiKey, | ||
| CODEBUDDY_INTERNET_ENVIRONMENT: profile.internetEnvironment, | ||
| CODEBUDDY_CODE_DISABLE_BACKGROUND_TASKS: "1", | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Build the headless CLI arguments (§七/§十一). | ||
| * | ||
| * Tool ownership stays with Codex: `--tools ""` disables every built-in tool and `--strict-mcp-config` | ||
| * (with no `--mcp-config`) blocks MCP tools, so the CLI can neither read, write, exec, nor browse the | ||
| * workspace. `-y/--dangerously-skip-permissions` is deliberately NOT passed, so any operation that | ||
| * would require authorization is blocked. The turn is a single text/reasoning pass over stream-json; | ||
| * Codex's tool catalog is not advertised in v1 (the control-protocol tool bridge is a fast-follow). | ||
| */ | ||
| export function buildArgs(profile: CodeBuddyProfile, parsed: OcxParsedRequest, provider: OcxProviderConfig): string[] { | ||
| const args: string[] = [ | ||
| "-p", | ||
| "--output-format", "stream-json", | ||
| "--input-format", "stream-json", | ||
| "--include-partial-messages", | ||
| "--verbose", | ||
| "--no-session-persistence", | ||
| "--tools", "", | ||
| "--strict-mcp-config", | ||
| "--max-turns", "1", | ||
| "--model", parsed.modelId, | ||
| ]; | ||
| const effort = mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning); | ||
| if (effort) args.push("--effort", effort); | ||
| const system = buildSystemPrompt(parsed); | ||
| if (system) args.push("--append-system-prompt", system); | ||
| // profile is retained for symmetry with the region-isolated design and future per-region flags. | ||
| void profile; | ||
| return args; | ||
| } | ||
|
|
||
| /** Create the shared CodeBuddy adapter: region profile selects Global vs CN, one turn runs tools-disabled. */ | ||
| export function createCodeBuddyAdapter(provider: OcxProviderConfig, deps: CodeBuddyAdapterDeps = {}): ProviderAdapter { | ||
| return { | ||
| name: "codebuddy", | ||
|
|
||
| // runTurn owns the turn; buildRequest/parseStream are the disabled HTTP path (mirrors cursor). | ||
| buildRequest(): AdapterRequest { | ||
| return { url: provider.baseUrl, method: "POST", headers: {}, body: "" }; | ||
| }, | ||
| async *parseStream(): AsyncGenerator<AdapterEvent> { | ||
| yield { type: "error", message: "CodeBuddy adapter uses runTurn; the fetch/parseStream path is disabled." }; | ||
| }, | ||
|
|
||
| async runTurn(parsed, incoming, emit): Promise<void> { | ||
| await runCodingAgentTurn({ | ||
| profiles: CODEBUDDY_PROFILES, | ||
| provider, | ||
| parsed, | ||
| incoming, | ||
| emit, | ||
| buildArgs: (resolved, req, prov) => buildArgs(resolved as CodeBuddyProfile, req, prov), | ||
| buildEnv: (resolved, apiKey) => buildChildEnv(resolved as CodeBuddyProfile, apiKey), | ||
| deps, | ||
| }); | ||
| }, | ||
| }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import { clearCodingAgentBinaryCache, type CodingAgentProviderProfile } from "../coding-agent/profile"; | ||
|
|
||
| /** | ||
| * Region-isolated profiles for the official CodeBuddy Code CLI. | ||
| * | ||
| * CodeBuddy Global and CodeBuddy CN are SEPARATE credential destinations (§五/§十四/§十六). They | ||
| * share one adapter, one binary name, and the shared coding-agent stream-json parser; the region is | ||
| * fixed by the officially documented `CODEBUDDY_INTERNET_ENVIRONMENT` value (`public` for the | ||
| * overseas/global product, `internal` for the China product) — the vendor states: "使用 | ||
| * CODEBUDDY_API_KEY 时,必须根据版本正确配置 CODEBUDDY_INTERNET_ENVIRONMENT". A global key is never | ||
| * sent to the CN environment or vice versa. | ||
| * | ||
| * Evidence (verified 2026-09-03): npm `@tencent-ai/codebuddy-code` v2.143.0 (Tencent Cloud); | ||
| * keys https://www.codebuddy.ai/profile/keys (Global) / https://copilot.tencent.com/profile/keys (CN); | ||
| * headless https://www.codebuddy.ai/docs/cli/headless. | ||
| */ | ||
| export interface CodeBuddyProfile extends CodingAgentProviderProfile { | ||
| family: "codebuddy"; | ||
| /** Official `CODEBUDDY_INTERNET_ENVIRONMENT` value for this region. */ | ||
| internetEnvironment: "public" | "internal"; | ||
| } | ||
|
|
||
| export const CODEBUDDY_GLOBAL_PROFILE: CodeBuddyProfile = { | ||
| providerId: "codebuddy", | ||
| family: "codebuddy", | ||
| region: "global", | ||
| label: "CodeBuddy", | ||
| internetEnvironment: "public", | ||
| canonicalBaseUrl: "https://www.codebuddy.ai", | ||
| binaryCandidates: ["codebuddy", "cbc", "codebuddy-code"], | ||
| tokenEnv: "CODEBUDDY_API_KEY", | ||
| installHint: "npm install -g @tencent-ai/codebuddy-code", | ||
| documentationUrl: "https://www.codebuddy.ai/docs/cli/headless", | ||
| }; | ||
|
|
||
| export const CODEBUDDY_CN_PROFILE: CodeBuddyProfile = { | ||
| providerId: "codebuddy-cn", | ||
| family: "codebuddy", | ||
| region: "cn", | ||
| label: "CodeBuddy CN", | ||
| internetEnvironment: "internal", | ||
| canonicalBaseUrl: "https://www.codebuddy.cn", | ||
| binaryCandidates: ["codebuddy", "cbc", "codebuddy-code"], | ||
| tokenEnv: "CODEBUDDY_API_KEY", | ||
| installHint: "npm install -g @tencent-ai/codebuddy-code", | ||
| documentationUrl: "https://www.codebuddy.cn/docs/cli/headless", | ||
| }; | ||
|
|
||
| export const CODEBUDDY_PROFILES: readonly CodeBuddyProfile[] = [CODEBUDDY_GLOBAL_PROFILE, CODEBUDDY_CN_PROFILE]; | ||
|
|
||
| /** Binary-discovery cache is shared across coding-agent families; re-exported for test isolation. */ | ||
| export const clearCodeBuddyBinaryCache = clearCodingAgentBinaryCache; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| import { existsSync } from "node:fs"; | ||
| import { delimiter, join } from "node:path"; | ||
|
|
||
| /** | ||
| * One region-isolated official coding-agent CLI target (§三十一). | ||
| * | ||
| * A profile is the ONLY place a family encodes its per-region differences (binary, credential env | ||
| * var, canonical destination, install hint). Adapters stay profile-driven so there is no scattered | ||
| * `if (provider === "codebuddy-cn")` branching, and so a family's Global and CN variants share one | ||
| * adapter and one parser (§十三). | ||
| */ | ||
| export interface CodingAgentProviderProfile { | ||
| /** Canonical OpenCodex provider id this profile serves. */ | ||
| providerId: string; | ||
| /** Vendor family; selects the arg/env builder in the family adapter. */ | ||
| family: "codebuddy"; | ||
| /** Region; drives the vendor's own region switch and keeps credentials deterministic. */ | ||
| region: "global" | "cn"; | ||
| /** Human label for diagnostics/error copy (never sent upstream). */ | ||
| label: string; | ||
| /** | ||
| * Canonical upstream destination and region identity. The CLI performs the real transport, but | ||
| * this host selects the profile and fails closed when overridden, so a region-scoped credential is | ||
| * never handed to an unexpected environment (§十六). | ||
| */ | ||
| canonicalBaseUrl: string; | ||
| /** Executable names to resolve on PATH, in preference order. */ | ||
| binaryCandidates: readonly string[]; | ||
| /** Official credential environment variable consumed by the CLI. */ | ||
| tokenEnv: string; | ||
| /** Install command surfaced when the CLI is missing (§二十六). */ | ||
| installHint: string; | ||
| /** Official documentation for the automation surface. */ | ||
| documentationUrl: string; | ||
| } | ||
|
|
||
| /** Test seam: report the resolved path of a candidate executable, or undefined. */ | ||
| export type WhichFn = (candidate: string) => string | undefined; | ||
|
|
||
| const binaryCache = new Map<string, string>(); | ||
|
|
||
| /** Reset the discovery cache (tests, or an explicit provider re-check). */ | ||
| export function clearCodingAgentBinaryCache(): void { | ||
| binaryCache.clear(); | ||
| } | ||
|
|
||
| /** Default PATH scan: return the first existing executable path for a candidate name. */ | ||
| export function whichFromPath(candidate: string): string | undefined { | ||
| const pathVar = process.env.PATH ?? ""; | ||
| if (!pathVar) return undefined; | ||
| const extensions = process.platform === "win32" ? [".cmd", ".exe", ".bat", ""] : [""]; | ||
| for (const dir of pathVar.split(delimiter)) { | ||
| if (!dir) continue; | ||
| for (const ext of extensions) { | ||
| const full = join(dir, `${candidate}${ext}`); | ||
| try { | ||
| if (existsSync(full)) return full; | ||
| } catch { | ||
| // An unreadable PATH entry must not abort discovery; skip it. | ||
| } | ||
| } | ||
| } | ||
| return undefined; | ||
| } | ||
|
|
||
| /** | ||
| * Discover the CLI executable BEFORE a request is sent (§二十六), so a missing CLI is a clear | ||
| * pre-flight error rather than a mid-turn ENOENT. Only positive hits are cached (§三十): a CLI | ||
| * installed after startup is found on the next turn instead of being masked by a cached negative. | ||
| */ | ||
| export function resolveCodingAgentBinary( | ||
| profile: CodingAgentProviderProfile, | ||
| which: WhichFn = whichFromPath, | ||
| ): string | undefined { | ||
| for (const candidate of profile.binaryCandidates) { | ||
| const cacheKey = `${profile.providerId}:${candidate}`; | ||
| const cached = binaryCache.get(cacheKey); | ||
| if (cached) return cached; | ||
| const resolved = which(candidate); | ||
| if (resolved) { | ||
| binaryCache.set(cacheKey, resolved); | ||
| return resolved; | ||
| } | ||
| } | ||
| return undefined; | ||
| } | ||
|
|
||
| /** | ||
| * Resolve the profile whose canonical base URL matches the provider's configured destination. | ||
| * Returns undefined for any other host, so the adapter fails closed rather than sending a | ||
| * region-scoped credential to an unknown environment (§十六). | ||
| */ | ||
| export function resolveProfileByBaseUrl( | ||
| profiles: readonly CodingAgentProviderProfile[], | ||
| baseUrl: string | undefined, | ||
| ): CodingAgentProviderProfile | undefined { | ||
| if (!baseUrl) return undefined; | ||
| const normalized = baseUrl.replace(/\/+$/, "").toLowerCase(); | ||
| return profiles.find(profile => normalized === profile.canonicalBaseUrl.toLowerCase()); | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.