Fix getConfigSchema to use flat fields array with correct hint keys

The Paperclip AdapterConfigSchema type expects a flat fields array, not
nested sections. Also maps description -> hint per the schema type.
Defines types locally since @paperclipai/adapter-utils@0.3.1 on npm
does not yet export AdapterConfigSchema/ConfigFieldSchema (those exist
in the monorepo but aren't released to npm yet).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-12 10:43:31 -04:00
parent 448889fc94
commit f5fa41fb3a
2 changed files with 110 additions and 167 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@farhoodliquor/paperclip-adapter-claude-k8s", "name": "@farhoodliquor/paperclip-adapter-claude-k8s",
"version": "0.1.8", "version": "0.1.9",
"description": "Paperclip adapter plugin that runs Claude Code agents as Kubernetes Jobs", "description": "Paperclip adapter plugin that runs Claude Code agents as Kubernetes Jobs",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
+109 -166
View File
@@ -1,173 +1,116 @@
export interface AdapterConfigSchema { // NOTE: These types must match what Paperclip's SchemaConfigFields component
sections?: AdapterConfigSection[]; // expects. Paperclip's server at GET /api/adapters/:type/config-schema
// calls adapter.getConfigSchema() and the UI reads the JSON — types are only
// used at build time here. The Paperclip types in @paperclipai/adapter-utils
// may lag behind; these locals are the source of truth for this adapter.
interface ConfigFieldOption {
label: string;
value: string;
group?: string;
} }
export interface AdapterConfigSection { type ConfigFieldSchema =
title: string; | { type: "text"; key: string; label: string; hint?: string; default?: unknown; meta?: Record<string, unknown> }
| { type: "number"; key: string; label: string; hint?: string; default?: unknown; meta?: Record<string, unknown> }
| { type: "toggle"; key: string; label: string; hint?: string; default?: unknown; meta?: Record<string, unknown> }
| { type: "select"; key: string; label: string; hint?: string; options: ConfigFieldOption[]; default?: unknown; meta?: Record<string, unknown> }
| { type: "textarea"; key: string; label: string; hint?: string; default?: unknown; meta?: Record<string, unknown> }
| { type: "combobox"; key: string; label: string; hint?: string; options?: ConfigFieldOption[]; default?: unknown; meta?: Record<string, unknown> };
interface AdapterConfigSchema {
fields: ConfigFieldSchema[]; fields: ConfigFieldSchema[];
} }
export type ConfigFieldSchema =
| TextFieldSchema
| NumberFieldSchema
| ToggleFieldSchema
| SelectFieldSchema
| TextareaFieldSchema;
export interface TextFieldSchema {
type: "text";
key: string;
label: string;
description?: string;
placeholder?: string;
helpLink?: string;
}
export interface NumberFieldSchema {
type: "number";
key: string;
label: string;
description?: string;
placeholder?: string;
helpLink?: string;
}
export interface ToggleFieldSchema {
type: "toggle";
key: string;
label: string;
description?: string;
helpLink?: string;
}
export interface SelectFieldSchema {
type: "select";
key: string;
label: string;
description?: string;
options: { value: string; label: string }[];
helpLink?: string;
}
export interface TextareaFieldSchema {
type: "textarea";
key: string;
label: string;
description?: string;
placeholder?: string;
helpLink?: string;
}
export function getConfigSchema(): AdapterConfigSchema { export function getConfigSchema(): AdapterConfigSchema {
return { const fields: ConfigFieldSchema[] = [
sections: [ // Kubernetes
{ {
title: "Kubernetes", type: "text",
fields: [ key: "namespace",
{ label: "Namespace",
type: "text", hint: "Kubernetes namespace for Jobs. Defaults to the Deployment namespace.",
key: "namespace", },
label: "Namespace", {
description: "Kubernetes namespace for Jobs. Defaults to the Deployment namespace.", type: "text",
}, key: "image",
{ label: "Container Image",
type: "text", hint: "Override the container image used for Job pods. Defaults to the running Deployment image.",
key: "image", },
label: "Container Image", {
description: "Override the container image used for Job pods. Defaults to the running Deployment image.", type: "select",
placeholder: "registry/image:tag", key: "imagePullPolicy",
}, label: "Image Pull Policy",
{ hint: "Image pull policy for the container image.",
type: "select", options: [
key: "imagePullPolicy", { value: "IfNotPresent", label: "IfNotPresent" },
label: "Image Pull Policy", { value: "Always", label: "Always" },
description: "Image pull policy for the container image.", { value: "Never", label: "Never" },
options: [ ],
{ value: "IfNotPresent", label: "IfNotPresent" }, },
{ value: "Always", label: "Always" }, {
{ value: "Never", label: "Never" }, type: "text",
], key: "kubeconfig",
}, label: "Kubeconfig Path",
{ hint: "Absolute path to a kubeconfig file on disk. Defaults to in-cluster service account auth.",
type: "text", },
key: "kubeconfig", {
label: "Kubeconfig Path", type: "number",
description: "Absolute path to a kubeconfig file on disk. Defaults to in-cluster service account auth.", key: "ttlSecondsAfterFinished",
placeholder: "/path/to/kubeconfig", label: "TTL Seconds After Finished",
}, hint: "Auto-cleanup delay for completed Jobs in seconds. Default: 300.",
{ },
type: "number", {
key: "ttlSecondsAfterFinished", type: "toggle",
label: "TTL Seconds After Finished", key: "retainJobs",
description: "Auto-cleanup delay for completed Jobs in seconds. Default: 300.", label: "Retain Jobs",
}, hint: "Skip cleanup of completed Jobs for debugging purposes.",
{ },
type: "toggle", // Resource Limits
key: "retainJobs", {
label: "Retain Jobs", type: "text",
description: "Skip cleanup of completed Jobs for debugging purposes.", key: "resources.requests.cpu",
}, label: "CPU Request",
], hint: "CPU request for Job pods (e.g. 100m, 0.5, 1).",
}, },
{ {
title: "Resource Limits", type: "text",
fields: [ key: "resources.requests.memory",
{ label: "Memory Request",
type: "text", hint: "Memory request for Job pods (e.g. 128Mi, 512Mi, 1Gi).",
key: "resources.requests.cpu", },
label: "CPU Request", {
description: "CPU request for Job pods (e.g. 100m, 0.5, 1).", type: "text",
placeholder: "100m", key: "resources.limits.cpu",
}, label: "CPU Limit",
{ hint: "CPU limit for Job pods (e.g. 100m, 0.5, 1).",
type: "text", },
key: "resources.requests.memory", {
label: "Memory Request", type: "text",
description: "Memory request for Job pods (e.g. 128Mi, 512Mi, 1Gi).", key: "resources.limits.memory",
placeholder: "512Mi", label: "Memory Limit",
}, hint: "Memory limit for Job pods (e.g. 128Mi, 512Mi, 1Gi).",
{ },
type: "text", // Scheduling
key: "resources.limits.cpu", {
label: "CPU Limit", type: "textarea",
description: "CPU limit for Job pods (e.g. 100m, 0.5, 1).", key: "nodeSelector",
placeholder: "1000m", label: "Node Selector",
}, hint: "Node selector for Job pods. One key=value per line (e.g. disktype=ssd).",
{ },
type: "text", {
key: "resources.limits.memory", type: "textarea",
label: "Memory Limit", key: "tolerations",
description: "Memory limit for Job pods (e.g. 128Mi, 512Mi, 1Gi).", label: "Tolerations",
placeholder: "1Gi", hint: "Tolerations for Job pods as JSON array.",
}, },
], {
}, type: "textarea",
{ key: "labels",
title: "Scheduling", label: "Labels",
fields: [ hint: "Extra labels added to Job metadata. One key=value per line.",
{ },
type: "textarea", ];
key: "nodeSelector",
label: "Node Selector", return { fields };
description: "Node selector for Job pods. One key=value per line (e.g. disktype=ssd).",
placeholder: "disktype=ssd\ngpu=true",
},
{
type: "textarea",
key: "tolerations",
label: "Tolerations",
description: "Tolerations for Job pods as JSON array.",
placeholder: '[{"key":"node-type","operator":"Equal","value":"gpu","effect":"NoSchedule"}]',
},
{
type: "textarea",
key: "labels",
label: "Labels",
description: "Extra labels added to Job metadata. One key=value per line.",
placeholder: "team=ai\nenv=prod",
},
],
},
],
};
} }