From f6b8eff43579b631bbace82916e29a7a80bad1e7 Mon Sep 17 00:00:00 2001 From: walker1211 <13750528578@163.com> Date: Sun, 2 Aug 2026 11:43:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(config):=20=E6=94=AF=E6=8C=81=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=20Codex=20=E6=8E=A8=E7=90=86=E6=A1=A3=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +++- README.zh-CN.md | 4 +++- cmd/imgen/main.go | 1 + configs/config.example.yaml | 1 + docs/openclaw-imgen-integration.md | 2 +- docs/openclaw-imgen-integration.zh-CN.md | 2 +- docs/releases/v0.1.14.md | 14 ++++++++++++++ internal/backend/builtin_codex.go | 4 ++++ internal/backend/builtin_codex_test.go | 10 ++++++++++ internal/config/config.go | 1 + internal/config/load_test.go | 21 +++++++++++++++++++++ 11 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 docs/releases/v0.1.14.md diff --git a/README.md b/README.md index 43183e4..e209785 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,7 @@ backend: type: built_in_codex # Use local Codex CLI with the built-in $imagegen skill command: codex # Codex CLI command name or executable path model: "" # Empty uses the Codex CLI default model; set this only when pinning a model + reasoning_effort: "" # Empty inherits Codex CLI config; set a level supported by the selected model cwd: "" # Codex CLI working directory; empty uses the current process directory timeout: 90s # Timeout for one Codex/imagegen invocation delivery_dir: "" # Optional: copy generated images there; OpenClaw/TG can point this at an allowed workspace/media directory @@ -216,6 +217,7 @@ Configuration fields: - `backend.type`: generation backend type. Currently use `built_in_codex`. - `backend.command`: Codex CLI command. Defaults to `codex`; the built-in backend currently requires this command to support `exec --json`. - `backend.model`: model name passed to Codex CLI. If empty, the configured Codex backend chooses its default model. +- `backend.reasoning_effort`: optional Codex reasoning effort passed as a per-invocation config override. If empty, the Codex CLI configuration is inherited; supported values depend on the selected model. - `backend.cwd`: Codex CLI working directory. If empty, the current process working directory is used. `~/` is expanded. - `backend.timeout`: timeout for one Codex/imagegen invocation. Increase it if generation frequently times out. - `backend.delivery_dir`: optional delivery directory. When set, generated images are copied there and the copied path is returned. OpenClaw/TG can point this at an allowed workspace/media directory. @@ -263,7 +265,7 @@ Notes: - Only local file paths are supported in this version. URLs and uploads are not supported. - Synchronous `run` and asynchronous `submit` use the same `--image` semantics. -- The backend invokes Codex CLI as ` exec --json --image ... -- ''`. +- The backend invokes Codex CLI as ` exec --json [--config model_reasoning_effort=...] --image ... -- ''`. - The `--` separator is required for the native Codex CLI command because variadic `--image` would otherwise consume the prompt. - Wrappers such as `ccs codex` are not automatically compatible; if `ccs codex exec --json` reports `unknown option '--json'`, the current built-in backend cannot use it directly. diff --git a/README.zh-CN.md b/README.zh-CN.md index 18bd31a..9578a44 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -161,6 +161,7 @@ backend: type: built_in_codex # 使用本机 Codex CLI 调用内置 $imagegen command: codex # Codex CLI 命令名或可执行文件路径 model: "" # 为空时使用 Codex CLI 默认模型;需要固定模型时再填写 + reasoning_effort: "" # 为空时继承 Codex CLI 配置;填写所选模型支持的档位 cwd: "" # Codex CLI 工作目录;为空时使用当前进程工作目录 timeout: 90s # 单次 Codex/imagegen 调用超时时间 delivery_dir: "" # 可选:生成后复制图片到该目录;OpenClaw/TG 可指向其允许发送的 workspace/media 目录 @@ -216,6 +217,7 @@ email: - `backend.type`:生成后端类型;当前使用 `built_in_codex`。 - `backend.command`:Codex CLI 命令,默认 `codex`;当前内置 backend 要求该命令支持 `exec --json`。 - `backend.model`:传给 Codex CLI 的模型名;为空时由配置的 Codex backend 使用默认模型。 +- `backend.reasoning_effort`:可选的 Codex 推理档位,以单次调用配置覆盖的方式传入;为空时继承 Codex CLI 配置,具体支持值取决于所选模型。 - `backend.cwd`:Codex CLI 执行工作目录;为空时使用当前进程工作目录,支持 `~/` 展开。 - `backend.timeout`:单次 Codex/imagegen 调用超时;生成经常超时时可适当调大。 - `backend.delivery_dir`:可选投递目录;配置后生成图片会先复制到该目录,再返回复制后的路径。OpenClaw/TG 可指向其允许发送的 workspace/media 目录。 @@ -263,7 +265,7 @@ email: - 当前只支持本机文件路径,不支持 URL 或上传文件。 - 同步 `run` 与异步 `submit` 的 `--image` 参数语义一致。 -- backend 调用 Codex CLI 时会生成 ` exec --json --image ... -- ''`。 +- backend 调用 Codex CLI 时会生成 ` exec --json [--config model_reasoning_effort=...] --image ... -- ''`。 - `--` 分隔符对原生 Codex CLI 是必需的,否则 variadic `--image` 会吞掉 prompt。 - `ccs codex` 等包装命令不一定兼容;如果 `ccs codex exec --json` 报 `unknown option '--json'`,当前内置 backend 不能直接使用它。 diff --git a/cmd/imgen/main.go b/cmd/imgen/main.go index 13becc3..dc80aa7 100644 --- a/cmd/imgen/main.go +++ b/cmd/imgen/main.go @@ -39,6 +39,7 @@ func main() { generator := backend.BuiltinCodex{ Command: cfg.Backend.Command, Model: cfg.Backend.Model, + ReasoningEffort: cfg.Backend.ReasoningEffort, CWD: cfg.Backend.CWD, Timeout: cfg.Backend.Timeout, DeliveryDir: cfg.Backend.DeliveryDir, diff --git a/configs/config.example.yaml b/configs/config.example.yaml index 5fa03bb..8b06027 100644 --- a/configs/config.example.yaml +++ b/configs/config.example.yaml @@ -21,6 +21,7 @@ backend: type: built_in_codex # 使用本机 Codex CLI 调用内置 $imagegen command: codex # Codex CLI 命令名或可执行文件路径 model: "" # 为空时使用 Codex CLI 默认模型;需要固定模型时再填写 + reasoning_effort: "" # 为空时继承 Codex CLI 配置;可按模型支持情况填写 medium、high、xhigh、max 等 cwd: "" # Codex CLI 工作目录;为空时使用当前进程工作目录 timeout: 300s # 单次 Codex/imagegen 调用超时时间 delivery_dir: "" # 可选:生成后复制图片到该目录;OpenClaw/TG 可指向其允许发送的 workspace/media 目录 diff --git a/docs/openclaw-imgen-integration.md b/docs/openclaw-imgen-integration.md index 7964f15..ab58a92 100644 --- a/docs/openclaw-imgen-integration.md +++ b/docs/openclaw-imgen-integration.md @@ -112,7 +112,7 @@ Poll all sessions and send each successful `images[].path` as soon as that sessi - If Telegram says `Media failed`, check from the OpenClaw runtime that the generated path exists, is readable, and is on a shared or copied filesystem. - If Telegram downloads a JPG/photo preview instead of the PNG original, verify that the `message` call used `forceDocument: true` or `asDocument: true`. - If a final visible message like `No extra answer from me.` appears after files are delivered, allow direct `NO_REPLY` silence in OpenClaw and ensure the final assistant reply is exactly `NO_REPLY`. -- If no image paths are returned, inspect `configs/config.yaml` for `backend.command`, `backend.model`, and `backend.cwd`, and confirm the configured backend supports Codex `exec --json`. +- If no image paths are returned, inspect `configs/config.yaml` for `backend.command`, `backend.model`, `backend.reasoning_effort`, and `backend.cwd`, and confirm the configured backend supports Codex `exec --json` and the selected model supports the configured effort. ## OpenClaw active-memory embedded run warning diff --git a/docs/openclaw-imgen-integration.zh-CN.md b/docs/openclaw-imgen-integration.zh-CN.md index 51ee669..889b0c1 100644 --- a/docs/openclaw-imgen-integration.zh-CN.md +++ b/docs/openclaw-imgen-integration.zh-CN.md @@ -112,7 +112,7 @@ final reply: NO_REPLY - 如果 Telegram 返回 `Media failed`,从 OpenClaw 运行环境检查生成路径是否存在、可读,并确认文件系统共享或文件已复制到可访问位置。 - 如果 Telegram 下载的是 JPG/photo 预览而不是 PNG 原图,确认 `message` 调用使用了 `forceDocument: true` 或 `asDocument: true`。 - 如果文件已发送后又出现 `No extra answer from me.` 之类最终可见消息,应允许 OpenClaw direct `NO_REPLY` 静默,并确认最终 assistant 回复严格等于 `NO_REPLY`。 -- 如果没有返回图片路径,检查 `configs/config.yaml` 中的 `backend.command`、`backend.model` 和 `backend.cwd`,并确认配置的 backend 支持 Codex `exec --json`。 +- 如果没有返回图片路径,检查 `configs/config.yaml` 中的 `backend.command`、`backend.model`、`backend.reasoning_effort` 和 `backend.cwd`,并确认配置的 backend 支持 Codex `exec --json`、所选模型支持当前 effort。 ## OpenClaw active-memory 嵌入运行 warning diff --git a/docs/releases/v0.1.14.md b/docs/releases/v0.1.14.md new file mode 100644 index 0000000..449978c --- /dev/null +++ b/docs/releases/v0.1.14.md @@ -0,0 +1,14 @@ +## Highlights + +- Added an optional `backend.reasoning_effort` setting for the built-in Codex backend. +- Passed configured effort as a per-invocation Codex override so image jobs can use a project-specific effort without changing the user's global Codex default. +- Updated the example configuration, bilingual README guidance, OpenClaw troubleshooting notes, and regression coverage. + +## Notes + +Leave `backend.reasoning_effort` empty to inherit the Codex CLI configuration. When set, the selected Codex model must support that effort level. + +## Validation + +- `./scripts/ci-local.sh clean` +- GitHub PR checks: `test`, `Analyze Go`, and `CodeQL` diff --git a/internal/backend/builtin_codex.go b/internal/backend/builtin_codex.go index 20a2361..7c4f92c 100644 --- a/internal/backend/builtin_codex.go +++ b/internal/backend/builtin_codex.go @@ -24,6 +24,7 @@ type CommandRunner interface { type BuiltinCodex struct { Command string Model string + ReasoningEffort string CWD string Timeout time.Duration DeliveryDir string @@ -35,6 +36,9 @@ type BuiltinCodex struct { func (b BuiltinCodex) Generate(ctx context.Context, req GenerateRequest) (GenerateResult, error) { args := []string{"exec", "--json"} + if b.ReasoningEffort != "" { + args = append(args, "--config", "model_reasoning_effort="+strconv.Quote(b.ReasoningEffort)) + } model := req.Model if model == "" { model = b.Model diff --git a/internal/backend/builtin_codex_test.go b/internal/backend/builtin_codex_test.go index 9a77470..363ded7 100644 --- a/internal/backend/builtin_codex_test.go +++ b/internal/backend/builtin_codex_test.go @@ -83,6 +83,16 @@ func TestBuiltinCodexGenerateBuildsExecArgsWithImages(t *testing.T) { } } +func TestBuiltinCodexGenerateBuildsExecArgsWithReasoningEffort(t *testing.T) { + runner := &recordingRunner{} + backend := BuiltinCodex{Command: "codex", Model: "gpt-5.6-terra", ReasoningEffort: "high", Runner: runner} + _, _ = backend.Generate(context.Background(), GenerateRequest{Prompt: "$imagegen draw a dragon"}) + want := []string{"exec", "--json", "--config", `model_reasoning_effort="high"`, "--model", "gpt-5.6-terra", "--", "$imagegen draw a dragon"} + if !reflect.DeepEqual(runner.req.Args, want) { + t.Fatalf("args = %#v, want %#v", runner.req.Args, want) + } +} + func TestBuiltinCodexGeneratePassesCodexHome(t *testing.T) { runner := &recordingRunner{} codexHome := t.TempDir() diff --git a/internal/config/config.go b/internal/config/config.go index 6bd4b69..778fe0d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -37,6 +37,7 @@ type BackendConfig struct { Type string `yaml:"type"` Command string `yaml:"command"` Model string `yaml:"model"` + ReasoningEffort string `yaml:"reasoning_effort"` CWD string `yaml:"cwd"` Timeout time.Duration `yaml:"timeout"` DeliveryDir string `yaml:"delivery_dir"` diff --git a/internal/config/load_test.go b/internal/config/load_test.go index 39510e4..f692ed3 100644 --- a/internal/config/load_test.go +++ b/internal/config/load_test.go @@ -164,6 +164,24 @@ func TestLoadParsesBackendDeliveryDir(t *testing.T) { } } +func TestLoadParsesBackendReasoningEffort(t *testing.T) { + path := filepath.Join(t.TempDir(), "config.yaml") + content := []byte(`backend: + reasoning_effort: high +`) + if err := os.WriteFile(path, content, 0o644); err != nil { + t.Fatalf("WriteFile failed: %v", err) + } + + cfg, err := Load(path) + if err != nil { + t.Fatalf("Load returned error: %v", err) + } + if cfg.Backend.ReasoningEffort != "high" { + t.Fatalf("backend.reasoning_effort = %q", cfg.Backend.ReasoningEffort) + } +} + func TestLoadParsesBackendDeliveryMaxFiles(t *testing.T) { path := filepath.Join(t.TempDir(), "config.yaml") content := []byte(`backend: @@ -300,6 +318,9 @@ func TestLoadExampleConfig(t *testing.T) { if cfg.Backend.Command != "codex" { t.Fatalf("backend.command = %q", cfg.Backend.Command) } + if cfg.Backend.ReasoningEffort != "" { + t.Fatalf("backend.reasoning_effort = %q, want empty", cfg.Backend.ReasoningEffort) + } if cfg.Backend.Timeout != 300*time.Second { t.Fatalf("backend.timeout = %s, want 300s", cfg.Backend.Timeout) }