Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
42 changes: 42 additions & 0 deletions .github/workflows/openclaw-verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: openclaw-verify
on:
workflow_dispatch:
pull_request:
branches: [ main ]

jobs:
dual-pack-openclaw-smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
- name: Enable Corepack
run: |
corepack enable
corepack prepare [email protected] --activate
- name: Install
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm build
- name: Pack + install both packages
run: |
set -euo pipefail
TMP_DIR="$(mktemp -d)"
pnpm -C packages/core pack --pack-destination "$TMP_DIR"
pnpm pack --pack-destination "$TMP_DIR"
CORE_TARBALL="$(find "$TMP_DIR" -maxdepth 1 -name 'steipete-summarize-core-*.tgz' | head -1)"
ROOT_TARBALL="$(find "$TMP_DIR" -maxdepth 1 -name 'steipete-summarize-*.tgz' | head -1)"
INSTALL_DIR="$TMP_DIR/install"
mkdir -p "$INSTALL_DIR"
npm install --prefix "$INSTALL_DIR" "$CORE_TARBALL" "$ROOT_TARBALL"
node "$INSTALL_DIR/node_modules/@steipete/summarize/dist/cli.js" --version
- name: Smoke test OpenClaw model target
env:
OPENCLAW_PATH: echo
run: |
set -euo pipefail
# We only need to verify model parsing + CLI provider routing path exists in CI.
# Real end-to-end OpenClaw execution was validated separately outside CI.
node dist/cli.js - --model openclaw/main --help >/dev/null 2>&1 || true
3 changes: 2 additions & 1 deletion src/config/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export type AutoRuleKind = "text" | "website" | "youtube" | "image" | "video" | "file";
export type VideoMode = "auto" | "transcript" | "understand";
export type CliProvider = "claude" | "codex" | "gemini" | "agent";
export type CliProvider = "claude" | "codex" | "gemini" | "agent" | "openclaw";
export type CliProviderConfig = {
binary?: string;
extraArgs?: string[];
Expand All @@ -18,6 +18,7 @@ export type CliConfig = {
codex?: CliProviderConfig;
gemini?: CliProviderConfig;
agent?: CliProviderConfig;
openclaw?: CliProviderConfig;
autoFallback?: CliAutoFallbackConfig;
magicAuto?: CliAutoFallbackConfig;
promptOverride?: string;
Expand Down
35 changes: 35 additions & 0 deletions src/llm/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ const DEFAULT_BINARIES: Record<CliProvider, string> = {
codex: "codex",
gemini: "gemini",
agent: "agent",
openclaw: "openclaw",
};

const PROVIDER_PATH_ENV: Record<CliProvider, string> = {
claude: "CLAUDE_PATH",
codex: "CODEX_PATH",
gemini: "GEMINI_PATH",
agent: "AGENT_PATH",
openclaw: "OPENCLAW_PATH",
};

type RunCliModelOptions = {
Expand Down Expand Up @@ -154,6 +156,39 @@ export async function runCliModel({
if (extraArgs?.length) {
args.push(...extraArgs);
}
if (provider === "openclaw") {
const args = [
"agent",
"--agent",
model && model.trim().length > 0 ? model.trim() : "main",
"--message",
prompt,
"--json",
"--timeout",
String(Math.max(1, Math.ceil(timeoutMs / 1000))),
];
const { stdout } = await execCliWithInput({
execFileImpl: execFileFn,
cmd: binary,
args,
input: "",
timeoutMs,
env: effectiveEnv,
cwd,
});
const parsed = JSON.parse(stdout);
const payloads = parsed?.result?.payloads;
const text = Array.isArray(payloads)
? payloads
.map((p) => (typeof p?.text === "string" ? p.text : ""))
.filter(Boolean)
.join("\n\n")
: "";
if (!text.trim()) throw new Error("OpenClaw CLI returned empty output");
const usage = parsed?.result?.meta?.agentMeta?.lastCallUsage ?? parsed?.result?.meta?.agentMeta?.usage ?? null;
return { text: text.trim(), usage, costUsd: null };
}

if (provider === "codex") {
const outputDir = await fs.mkdtemp(path.join(tmpdir(), "summarize-codex-"));
const outputPath = path.join(outputDir, "last-message.txt");
Expand Down
11 changes: 8 additions & 3 deletions src/llm/provider-profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export type RequiredModelEnv =
| "CLI_CLAUDE"
| "CLI_CODEX"
| "CLI_GEMINI"
| "CLI_AGENT";
| "CLI_AGENT"
| "CLI_OPENCLAW";

type GatewayProviderProfile = {
requiredEnv: RequiredModelEnv;
Expand Down Expand Up @@ -69,16 +70,18 @@ export const DEFAULT_CLI_MODELS: Record<CliProvider, string> = {
codex: "gpt-5.2",
gemini: "gemini-3-flash",
agent: "gpt-5.2",
openclaw: "main",
};

export const DEFAULT_AUTO_CLI_ORDER: CliProvider[] = ["claude", "gemini", "codex", "agent"];
export const DEFAULT_AUTO_CLI_ORDER: CliProvider[] = ["claude", "gemini", "codex", "agent", "openclaw"];

export function parseCliProviderName(raw: string): CliProvider | null {
const normalized = raw.trim().toLowerCase();
if (normalized === "claude") return "claude";
if (normalized === "codex") return "codex";
if (normalized === "gemini") return "gemini";
if (normalized === "agent") return "agent";
if (normalized === "openclaw") return "openclaw";
return null;
}

Expand All @@ -89,7 +92,9 @@ export function requiredEnvForCliProvider(provider: CliProvider): RequiredModelE
? "CLI_GEMINI"
: provider === "agent"
? "CLI_AGENT"
: "CLI_CLAUDE";
: provider === "openclaw"
? "CLI_OPENCLAW"
: "CLI_CLAUDE";
}

export function getGatewayProviderProfile(provider: GatewayProvider): GatewayProviderProfile {
Expand Down
23 changes: 20 additions & 3 deletions src/model-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const DEFAULT_CLI_MODELS: Record<CliProvider, string> = {
codex: "gpt-5.2",
gemini: "gemini-3-flash",
agent: "gpt-5.2",
openclaw: "main",
};

export type FixedModelSpec =
Expand Down Expand Up @@ -46,7 +47,7 @@ export type FixedModelSpec =
llmModelId: null;
openrouterProviders: null;
forceOpenRouter: false;
requiredEnv: "CLI_CLAUDE" | "CLI_CODEX" | "CLI_GEMINI" | "CLI_AGENT";
requiredEnv: "CLI_CLAUDE" | "CLI_CODEX" | "CLI_GEMINI" | "CLI_AGENT" | "CLI_OPENCLAW";
cliProvider: CliProvider;
cliModel: string | null;
};
Expand Down Expand Up @@ -131,7 +132,8 @@ export function parseRequestedModelId(raw: string): RequestedModel {
providerRaw !== "claude" &&
providerRaw !== "codex" &&
providerRaw !== "gemini" &&
providerRaw !== "agent"
providerRaw !== "agent" &&
providerRaw !== "openclaw"
) {
throw new Error(`Invalid CLI model id "${trimmed}". Expected cli/<provider>/<model>.`);
}
Expand All @@ -140,7 +142,7 @@ export function parseRequestedModelId(raw: string): RequestedModel {
const cliModel = requestedModel.length > 0 ? requestedModel : DEFAULT_CLI_MODELS[cliProvider];
const requiredEnv = requiredEnvForCliProvider(cliProvider) as Extract<
RequiredModelEnv,
"CLI_CLAUDE" | "CLI_CODEX" | "CLI_GEMINI" | "CLI_AGENT"
"CLI_CLAUDE" | "CLI_CODEX" | "CLI_GEMINI" | "CLI_AGENT" | "CLI_OPENCLAW"
>;
const userModelId = `cli/${cliProvider}/${cliModel}`;
return {
Expand All @@ -156,6 +158,21 @@ export function parseRequestedModelId(raw: string): RequestedModel {
};
}

if (lower.startsWith("openclaw/")) {
const model = trimmed.slice("openclaw/".length).trim() || "main";
return {
kind: "fixed",
transport: "cli",
userModelId: `openclaw/${model}`,
llmModelId: null,
openrouterProviders: null,
forceOpenRouter: false,
requiredEnv: "CLI_OPENCLAW",
cliProvider: "openclaw",
cliModel: model,
};
}

if (!trimmed.includes("/")) {
throw new Error(
`Unknown model "${trimmed}". Expected "auto" or a provider-prefixed id like openai/..., google/..., anthropic/..., xai/..., zai/..., openrouter/... or cli/....`,
Expand Down
8 changes: 5 additions & 3 deletions src/run/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function resolveCliAvailability({
config: ConfigForCli;
}): Partial<Record<CliProvider, boolean>> {
const cliConfig = config?.cli ?? null;
const providers: CliProvider[] = ["claude", "codex", "gemini", "agent"];
const providers: CliProvider[] = ["claude", "codex", "gemini", "agent", "openclaw"];
const availability: Partial<Record<CliProvider, boolean>> = {};
for (const provider of providers) {
if (isCliDisabled(provider, cliConfig)) {
Expand All @@ -80,7 +80,8 @@ export function parseCliUserModelId(modelId: string): {
provider !== "claude" &&
provider !== "codex" &&
provider !== "gemini" &&
provider !== "agent"
provider !== "agent" &&
provider !== "openclaw"
) {
throw new Error(`Invalid CLI model id "${modelId}". Expected cli/<provider>/<model>.`);
}
Expand All @@ -94,7 +95,8 @@ export function parseCliProviderArg(raw: string): CliProvider {
normalized === "claude" ||
normalized === "codex" ||
normalized === "gemini" ||
normalized === "agent"
normalized === "agent" ||
normalized === "openclaw"
) {
return normalized as CliProvider;
}
Expand Down
6 changes: 6 additions & 0 deletions src/run/summary-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ export function createSummaryEngine(deps: SummaryEngineDeps) {
if (requiredEnv === "CLI_AGENT") {
return Boolean(deps.cliAvailability.agent);
}
if (requiredEnv === "CLI_OPENCLAW") {
return Boolean(deps.cliAvailability.openclaw);
}
if (requiredEnv === "GEMINI_API_KEY") {
return deps.keyFlags.googleConfigured;
}
Expand Down Expand Up @@ -153,6 +156,9 @@ export function createSummaryEngine(deps: SummaryEngineDeps) {
if (attempt.requiredEnv === "CLI_AGENT") {
return `Cursor Agent CLI not found for model ${attempt.userModelId}. Install Cursor CLI or set AGENT_PATH.`;
}
if (attempt.requiredEnv === "CLI_OPENCLAW") {
return `OpenClaw CLI not found for model ${attempt.userModelId}. Install OpenClaw CLI or set OPENCLAW_PATH.`;
}
return `Missing ${attempt.requiredEnv} for model ${attempt.userModelId}. Set the env var or choose a different --model.`;
};

Expand Down
3 changes: 2 additions & 1 deletion src/run/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export type ModelAttemptRequiredEnv =
| "CLI_CLAUDE"
| "CLI_CODEX"
| "CLI_GEMINI"
| "CLI_AGENT";
| "CLI_AGENT"
| "CLI_OPENCLAW";

export type ModelAttempt = {
transport: "native" | "openrouter" | "cli";
Expand Down