Add RTK integration for token-optimized command output

When enableRtk is set in adapter config, the adapter:
- Adds an init container (curlimages/curl) to download the RTK binary
- Mounts RTK binary in the main container via shared emptyDir volume
- Runs `rtk install claude-code` before invoking Claude to set up hooks
- Disables RTK telemetry (RTK_NO_TELEMETRY=1) for automated environments
- Supports optional rtkVersion config for pinning specific versions

RTK filters CLI command output before it reaches the LLM context,
reducing token consumption by ~80%.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-04-14 01:27:28 +00:00
parent 77ba40d9bf
commit d074cb2a8c
14 changed files with 238 additions and 16 deletions
+14
View File
@@ -108,6 +108,20 @@ export function getConfigSchema(): AdapterConfigSchema {
label: "Memory Limit",
hint: "Memory limit for Job pods (e.g. 128Mi, 512Mi, 1Gi).",
},
// RTK (token optimization)
{
type: "toggle",
key: "enableRtk",
label: "Enable RTK",
hint: "Install and enable RTK (rtk-ai/rtk) to reduce token usage by filtering command output. Adds an init container to download the RTK binary.",
default: false,
},
{
type: "text",
key: "rtkVersion",
label: "RTK Version",
hint: "RTK version to install. Defaults to 'latest'.",
},
// Scheduling
{
type: "textarea",
+90
View File
@@ -497,6 +497,96 @@ describe("buildJobManifest", () => {
});
});
describe("RTK integration", () => {
it("does not add RTK init container by default", () => {
const { job } = buildJobManifest({ ctx, selfPod });
const inits = job.spec?.template?.spec?.initContainers ?? [];
expect(inits).toHaveLength(1);
expect(inits[0]?.name).toBe("write-prompt");
});
it("adds install-rtk init container when enableRtk is true", () => {
ctx.config = { enableRtk: true };
const { job } = buildJobManifest({ ctx, selfPod });
const inits = job.spec?.template?.spec?.initContainers ?? [];
expect(inits).toHaveLength(2);
expect(inits[1]?.name).toBe("install-rtk");
expect(inits[1]?.image).toBe("curlimages/curl:8.12.1");
});
it("adds rtk-bin emptyDir volume when enableRtk is true", () => {
ctx.config = { enableRtk: true };
const { job } = buildJobManifest({ ctx, selfPod });
const rtkVol = job.spec?.template?.spec?.volumes?.find((v) => v.name === "rtk-bin");
expect(rtkVol?.emptyDir).toEqual({});
});
it("mounts rtk-bin in main container when enableRtk is true", () => {
ctx.config = { enableRtk: true };
const { job } = buildJobManifest({ ctx, selfPod });
const rtkMount = job.spec?.template?.spec?.containers[0]?.volumeMounts?.find(
(vm) => vm.name === "rtk-bin",
);
expect(rtkMount?.mountPath).toBe("/tmp/rtk-bin");
});
it("prepends rtk setup to main command when enableRtk is true", () => {
ctx.config = { enableRtk: true };
const { job } = buildJobManifest({ ctx, selfPod });
const command = job.spec?.template?.spec?.containers[0]?.command;
expect(command?.[2]).toContain("export PATH=\"/tmp/rtk-bin:$PATH\"");
expect(command?.[2]).toContain("rtk install claude-code");
expect(command?.[2]).toContain("cat /tmp/prompt/prompt.txt | claude");
});
it("does not prepend rtk setup when enableRtk is false", () => {
ctx.config = { enableRtk: false };
const { job } = buildJobManifest({ ctx, selfPod });
const command = job.spec?.template?.spec?.containers[0]?.command;
expect(command?.[2]).not.toContain("rtk");
expect(command?.[2]).toMatch(/^cat \/tmp\/prompt\/prompt\.txt/);
});
it("does not add rtk-bin volume when enableRtk is false", () => {
ctx.config = { enableRtk: false };
const { job } = buildJobManifest({ ctx, selfPod });
expect(job.spec?.template?.spec?.volumes?.find((v) => v.name === "rtk-bin")).toBeUndefined();
});
it("sets RTK_NO_TELEMETRY env var when enableRtk is true", () => {
ctx.config = { enableRtk: true };
const { job } = buildJobManifest({ ctx, selfPod });
const rtkTelemetry = job.spec?.template?.spec?.containers[0]?.env?.find(
(e) => e.name === "RTK_NO_TELEMETRY",
);
expect(rtkTelemetry?.value).toBe("1");
});
it("does not set RTK_NO_TELEMETRY when enableRtk is false", () => {
const { job } = buildJobManifest({ ctx, selfPod });
const rtkTelemetry = job.spec?.template?.spec?.containers[0]?.env?.find(
(e) => e.name === "RTK_NO_TELEMETRY",
);
expect(rtkTelemetry).toBeUndefined();
});
it("uses custom rtkVersion in install command", () => {
ctx.config = { enableRtk: true, rtkVersion: "0.5.0" };
const { job } = buildJobManifest({ ctx, selfPod });
const inits = job.spec?.template?.spec?.initContainers ?? [];
const rtkInit = inits.find((c) => c.name === "install-rtk");
expect(rtkInit?.command?.[2]).toContain("RTK_VERSION=0.5.0");
});
it("mounts rtk-bin in install-rtk init container", () => {
ctx.config = { enableRtk: true };
const { job } = buildJobManifest({ ctx, selfPod });
const inits = job.spec?.template?.spec?.initContainers ?? [];
const rtkInit = inits.find((c) => c.name === "install-rtk");
expect(rtkInit?.volumeMounts).toContainEqual({ name: "rtk-bin", mountPath: "/tmp/rtk-bin" });
});
});
describe("return value", () => {
it("returns job, jobName, namespace, prompt, claudeArgs, promptMetrics", () => {
const result = buildJobManifest({ ctx, selfPod });
+37 -1
View File
@@ -148,6 +148,10 @@ function buildEnvVars(
// HOME must be /paperclip to match PVC mount and enable session resume
merged.HOME = "/paperclip";
if (asBoolean(config.enableRtk, false)) {
merged.RTK_NO_TELEMETRY = "1";
}
// Convert to V1EnvVar array
const envVars: k8s.V1EnvVar[] = Object.entries(merged).map(([name, value]) => ({
name,
@@ -171,6 +175,8 @@ export function buildJobManifest(input: JobBuildInput): JobBuildResult {
// K8s Job pods are always unattended — no one to approve permission prompts
const dangerouslySkipPermissions = asBoolean(config.dangerouslySkipPermissions, true);
const extraArgs = asStringArray(config.extraArgs);
const enableRtk = asBoolean(config.enableRtk, false);
const rtkVersion = asString(config.rtkVersion, "latest");
const timeoutSec = asNumber(config.timeoutSec, 0);
const ttlSeconds = asNumber(config.ttlSecondsAfterFinished, 300);
const resources = parseObject(config.resources);
@@ -282,6 +288,11 @@ export function buildJobManifest(input: JobBuildInput): JobBuildResult {
},
];
if (enableRtk) {
volumes.push({ name: "rtk-bin", emptyDir: {} });
volumeMounts.push({ name: "rtk-bin", mountPath: "/tmp/rtk-bin" });
}
// Mount shared PVC for /paperclip (session state, workspaces, data)
if (selfPod.pvcClaimName) {
volumes.push({
@@ -326,7 +337,10 @@ export function buildJobManifest(input: JobBuildInput): JobBuildResult {
// Build the claude command string for the main container
const claudeArgsEscaped = claudeArgs.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ");
const mainCommand = `cat /tmp/prompt/prompt.txt | claude ${claudeArgsEscaped}`;
const claudeCommand = `cat /tmp/prompt/prompt.txt | claude ${claudeArgsEscaped}`;
const mainCommand = enableRtk
? `export PATH="/tmp/rtk-bin:$PATH" && rtk install claude-code && ${claudeCommand}`
: claudeCommand;
const job: k8s.V1Job = {
apiVersion: "batch/v1",
@@ -368,6 +382,28 @@ export function buildJobManifest(input: JobBuildInput): JobBuildResult {
limits: { cpu: "100m", memory: "64Mi" },
},
},
...(enableRtk
? [
{
name: "install-rtk",
image: "curlimages/curl:8.12.1",
imagePullPolicy: "IfNotPresent" as const,
command: [
"sh",
"-c",
rtkVersion === "latest"
? "curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | RTK_INSTALL_DIR=/tmp/rtk-bin sh"
: `curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | RTK_INSTALL_DIR=/tmp/rtk-bin RTK_VERSION=${rtkVersion} sh`,
],
volumeMounts: [{ name: "rtk-bin", mountPath: "/tmp/rtk-bin" }],
securityContext,
resources: {
requests: { cpu: "10m", memory: "32Mi" },
limits: { cpu: "200m", memory: "128Mi" },
},
},
]
: []),
],
containers: [
{