diff --git a/install/0000_00_cluster-version-operator_45_lightspeed-crd-proposals.yaml b/install/0000_00_cluster-version-operator_45_lightspeed-crd-proposals.yaml index 621779646..682127b1e 100644 --- a/install/0000_00_cluster-version-operator_45_lightspeed-crd-proposals.yaml +++ b/install/0000_00_cluster-version-operator_45_lightspeed-crd-proposals.yaml @@ -35,13 +35,13 @@ spec: agentic.openshift.io/v1alpha1\n\tkind: Proposal\n\tmetadata:\n\t name: one-off-investigation\n\tspec:\n\t request: \"Investigate why pod foo is crashlooping\"\n\t targetNamespaces:\n\t - lightspeed-demo\n\t tools:\n\t - \ skills:\n\t - image: registry.redhat.io/acs/acs-lightspeed-skills:latest\n\t + \ skills:\n\t - image: registry.redhat.io/acs/acs-agentic-skills:latest\n\t \ analysis:\n\t agent: smart\n\nExample — full remediation (analyze → execute → verify):\n\n\tapiVersion: agentic.openshift.io/v1alpha1\n\tkind: Proposal\n\tmetadata:\n\t name: fix-nginx-cve-2024-1234\n\t namespace: stackrox\n\tspec:\n\t request: \"Fix CVE-2024-1234 in nginx:1.21\"\n\t \ targetNamespaces:\n\t - lightspeed-demo\n\t tools:\n\t skills:\n\t - \ - image: registry.redhat.io/acs/acs-lightspeed-skills:latest\n\t requiredSecrets:\n\t + \ - image: registry.redhat.io/acs/acs-agentic-skills:latest\n\t requiredSecrets:\n\t \ - name: acs-api-token\n\t mountAs:\n\t type: EnvVar\n\t \ envVar:\n\t name: ACS_API_TOKEN\n\t analysis:\n\t \ agent: smart\n\t execution: {}\n\t verification:\n\t agent: fast" @@ -236,8 +236,8 @@ spec: items: description: |- SecretRequirement declares a Kubernetes Secret that the sandbox needs - at runtime. The cluster admin creates the actual Secret in the same - namespace as the Proposal. + at runtime. The Secret must exist in the operator namespace (where + sandbox pods run), not in the Proposal's namespace. properties: description: description: |- @@ -318,7 +318,7 @@ spec: : !has(self.filePath)' name: description: |- - name of the Secret (must exist in the same namespace as the Proposal). + name of the Secret (must exist in the operator namespace). Must be a valid RFC 1123 DNS subdomain. maxLength: 253 minLength: 1 @@ -345,17 +345,14 @@ spec: skills directory. Each image must be unique within the list. items: description: "SkillsSource defines an OCI image containing - skills and optionally which\npaths within that image to - mount. Skills are mounted as Kubernetes image\nvolumes - in the agent's sandbox pod.\n\nWhen paths is omitted, - the entire image is mounted. When paths is specified,\nonly - those directories are mounted (each as a separate subPath - volumeMount),\nallowing selective composition of skills - from large shared images.\n\nExample — mount all skills - from a custom image:\n\n\tskills:\n\t - image: quay.io/my-org/my-skills:latest\n\nExample - — selectively mount two skills from a shared image:\n\n\tskills:\n\t - \ - image: registry.ci.openshift.org/ocp/5.0:agentic-skills\n\t - \ paths:\n\t - /skills/prometheus\n\t - /skills/cluster-update/update-advisor" + skills and which paths\nwithin that image to mount. Skills + are mounted as Kubernetes image\nvolumes in the agent's + sandbox pod.\n\nEach path is mounted as a separate subPath + volumeMount, allowing\nselective composition of skills + from shared images.\n\nExample — mount specific skills + from the agentic-skills image:\n\n\tskills:\n\t - image: + registry.ci.openshift.org/ocp/5.0:agentic-skills\n\t paths:\n\t + \ - /skills/cluster-update/update-advisor" properties: image: description: |- @@ -405,7 +402,7 @@ spec: : true' paths: description: |- - paths restricts which directories from the image are mounted. + paths specifies which directories from the image are mounted. Each path is mounted as a separate subPath volumeMount into the agent's skills directory. The last segment of each path becomes the mount name (e.g., "/skills/prometheus" mounts as "prometheus"). @@ -414,7 +411,6 @@ spec: or "." segments, no double slashes, no trailing slash, and only alphanumeric characters, hyphens, underscores, dots, and slashes. - When omitted, the entire image is mounted as a single volume. Maximum 50 items. items: maxLength: 512 @@ -441,6 +437,7 @@ spec: rule: self.all(p, p.matches('^[a-zA-Z0-9/_.-]+$')) required: - image + - paths type: object maxItems: 20 minItems: 1 @@ -450,6 +447,42 @@ spec: x-kubernetes-list-type: map type: object type: object + analysisOutput: + description: |- + analysisOutput configures the analysis step's structured output. + The mode field controls which built-in properties are included + (Default: all; Minimal: only title). The schema field optionally + defines adapter-specific structured data injected as "components". + + When omitted, the analysis uses the full default schema with all + built-in properties and no custom components. + + Immutable: the output contract is fixed at creation. + minProperties: 1 + properties: + mode: + default: Default + description: |- + mode controls which built-in properties the analysis output schema + includes. Default includes all built-in properties (diagnosis, + proposal, summary, rbac, verification). Minimal includes only the + base structure (options array with title per option). Omit or set + to "Default" for standard remediation workflows. + enum: + - Default + - Minimal + type: string + schema: + description: |- + schema is a JSON Schema injected as a required "components" + property in each analysis output option. Use this to require + adapter-specific structured data beyond the base analysis schema. + type: object + x-kubernetes-preserve-unknown-fields: true + type: object + x-kubernetes-validations: + - message: schema is required when mode is Minimal + rule: self.mode != 'Minimal' || has(self.schema) execution: description: |- execution defines per-step configuration for the execution step. @@ -620,8 +653,8 @@ spec: items: description: |- SecretRequirement declares a Kubernetes Secret that the sandbox needs - at runtime. The cluster admin creates the actual Secret in the same - namespace as the Proposal. + at runtime. The Secret must exist in the operator namespace (where + sandbox pods run), not in the Proposal's namespace. properties: description: description: |- @@ -702,7 +735,7 @@ spec: : !has(self.filePath)' name: description: |- - name of the Secret (must exist in the same namespace as the Proposal). + name of the Secret (must exist in the operator namespace). Must be a valid RFC 1123 DNS subdomain. maxLength: 253 minLength: 1 @@ -729,17 +762,14 @@ spec: skills directory. Each image must be unique within the list. items: description: "SkillsSource defines an OCI image containing - skills and optionally which\npaths within that image to - mount. Skills are mounted as Kubernetes image\nvolumes - in the agent's sandbox pod.\n\nWhen paths is omitted, - the entire image is mounted. When paths is specified,\nonly - those directories are mounted (each as a separate subPath - volumeMount),\nallowing selective composition of skills - from large shared images.\n\nExample — mount all skills - from a custom image:\n\n\tskills:\n\t - image: quay.io/my-org/my-skills:latest\n\nExample - — selectively mount two skills from a shared image:\n\n\tskills:\n\t - \ - image: registry.ci.openshift.org/ocp/5.0:agentic-skills\n\t - \ paths:\n\t - /skills/prometheus\n\t - /skills/cluster-update/update-advisor" + skills and which paths\nwithin that image to mount. Skills + are mounted as Kubernetes image\nvolumes in the agent's + sandbox pod.\n\nEach path is mounted as a separate subPath + volumeMount, allowing\nselective composition of skills + from shared images.\n\nExample — mount specific skills + from the agentic-skills image:\n\n\tskills:\n\t - image: + registry.ci.openshift.org/ocp/5.0:agentic-skills\n\t paths:\n\t + \ - /skills/cluster-update/update-advisor" properties: image: description: |- @@ -789,7 +819,7 @@ spec: : true' paths: description: |- - paths restricts which directories from the image are mounted. + paths specifies which directories from the image are mounted. Each path is mounted as a separate subPath volumeMount into the agent's skills directory. The last segment of each path becomes the mount name (e.g., "/skills/prometheus" mounts as "prometheus"). @@ -798,7 +828,6 @@ spec: or "." segments, no double slashes, no trailing slash, and only alphanumeric characters, hyphens, underscores, dots, and slashes. - When omitted, the entire image is mounted as a single volume. Maximum 50 items. items: maxLength: 512 @@ -825,6 +854,7 @@ spec: rule: self.all(p, p.matches('^[a-zA-Z0-9/_.-]+$')) required: - image + - paths type: object maxItems: 20 minItems: 1 @@ -834,16 +864,6 @@ spec: x-kubernetes-list-type: map type: object type: object - outputSchema: - description: |- - outputSchema is a JSON Schema injected as a required "components" - property in the analysis output. Use this to require adapter-specific - structured data beyond the base analysis schema (diagnosis, proposal, - RBAC, verification plan). - - Immutable: the output contract is fixed at creation. - type: object - x-kubernetes-preserve-unknown-fields: true request: description: |- request is the user's original request, alert description, or a @@ -1050,8 +1070,8 @@ spec: items: description: |- SecretRequirement declares a Kubernetes Secret that the sandbox needs - at runtime. The cluster admin creates the actual Secret in the same - namespace as the Proposal. + at runtime. The Secret must exist in the operator namespace (where + sandbox pods run), not in the Proposal's namespace. properties: description: description: |- @@ -1131,7 +1151,7 @@ spec: : !has(self.filePath)' name: description: |- - name of the Secret (must exist in the same namespace as the Proposal). + name of the Secret (must exist in the operator namespace). Must be a valid RFC 1123 DNS subdomain. maxLength: 253 minLength: 1 @@ -1158,17 +1178,13 @@ spec: skills directory. Each image must be unique within the list. items: description: "SkillsSource defines an OCI image containing skills - and optionally which\npaths within that image to mount. Skills - are mounted as Kubernetes image\nvolumes in the agent's sandbox - pod.\n\nWhen paths is omitted, the entire image is mounted. - When paths is specified,\nonly those directories are mounted - (each as a separate subPath volumeMount),\nallowing selective - composition of skills from large shared images.\n\nExample - — mount all skills from a custom image:\n\n\tskills:\n\t - - image: quay.io/my-org/my-skills:latest\n\nExample — selectively - mount two skills from a shared image:\n\n\tskills:\n\t - - image: registry.ci.openshift.org/ocp/5.0:agentic-skills\n\t - \ paths:\n\t - /skills/prometheus\n\t - /skills/cluster-update/update-advisor" + and which paths\nwithin that image to mount. Skills are mounted + as Kubernetes image\nvolumes in the agent's sandbox pod.\n\nEach + path is mounted as a separate subPath volumeMount, allowing\nselective + composition of skills from shared images.\n\nExample — mount + specific skills from the agentic-skills image:\n\n\tskills:\n\t + \ - image: registry.ci.openshift.org/ocp/5.0:agentic-skills\n\t + \ paths:\n\t - /skills/cluster-update/update-advisor" properties: image: description: |- @@ -1217,7 +1233,7 @@ spec: : true' paths: description: |- - paths restricts which directories from the image are mounted. + paths specifies which directories from the image are mounted. Each path is mounted as a separate subPath volumeMount into the agent's skills directory. The last segment of each path becomes the mount name (e.g., "/skills/prometheus" mounts as "prometheus"). @@ -1226,7 +1242,6 @@ spec: or "." segments, no double slashes, no trailing slash, and only alphanumeric characters, hyphens, underscores, dots, and slashes. - When omitted, the entire image is mounted as a single volume. Maximum 50 items. items: maxLength: 512 @@ -1253,6 +1268,7 @@ spec: rule: self.all(p, p.matches('^[a-zA-Z0-9/_.-]+$')) required: - image + - paths type: object maxItems: 20 minItems: 1 @@ -1431,8 +1447,8 @@ spec: items: description: |- SecretRequirement declares a Kubernetes Secret that the sandbox needs - at runtime. The cluster admin creates the actual Secret in the same - namespace as the Proposal. + at runtime. The Secret must exist in the operator namespace (where + sandbox pods run), not in the Proposal's namespace. properties: description: description: |- @@ -1513,7 +1529,7 @@ spec: : !has(self.filePath)' name: description: |- - name of the Secret (must exist in the same namespace as the Proposal). + name of the Secret (must exist in the operator namespace). Must be a valid RFC 1123 DNS subdomain. maxLength: 253 minLength: 1 @@ -1540,17 +1556,14 @@ spec: skills directory. Each image must be unique within the list. items: description: "SkillsSource defines an OCI image containing - skills and optionally which\npaths within that image to - mount. Skills are mounted as Kubernetes image\nvolumes - in the agent's sandbox pod.\n\nWhen paths is omitted, - the entire image is mounted. When paths is specified,\nonly - those directories are mounted (each as a separate subPath - volumeMount),\nallowing selective composition of skills - from large shared images.\n\nExample — mount all skills - from a custom image:\n\n\tskills:\n\t - image: quay.io/my-org/my-skills:latest\n\nExample - — selectively mount two skills from a shared image:\n\n\tskills:\n\t - \ - image: registry.ci.openshift.org/ocp/5.0:agentic-skills\n\t - \ paths:\n\t - /skills/prometheus\n\t - /skills/cluster-update/update-advisor" + skills and which paths\nwithin that image to mount. Skills + are mounted as Kubernetes image\nvolumes in the agent's + sandbox pod.\n\nEach path is mounted as a separate subPath + volumeMount, allowing\nselective composition of skills + from shared images.\n\nExample — mount specific skills + from the agentic-skills image:\n\n\tskills:\n\t - image: + registry.ci.openshift.org/ocp/5.0:agentic-skills\n\t paths:\n\t + \ - /skills/cluster-update/update-advisor" properties: image: description: |- @@ -1600,7 +1613,7 @@ spec: : true' paths: description: |- - paths restricts which directories from the image are mounted. + paths specifies which directories from the image are mounted. Each path is mounted as a separate subPath volumeMount into the agent's skills directory. The last segment of each path becomes the mount name (e.g., "/skills/prometheus" mounts as "prometheus"). @@ -1609,7 +1622,6 @@ spec: or "." segments, no double slashes, no trailing slash, and only alphanumeric characters, hyphens, underscores, dots, and slashes. - When omitted, the entire image is mounted as a single volume. Maximum 50 items. items: maxLength: 512 @@ -1636,6 +1648,7 @@ spec: rule: self.all(p, p.matches('^[a-zA-Z0-9/_.-]+$')) required: - image + - paths type: object maxItems: 20 minItems: 1 @@ -1655,9 +1668,13 @@ spec: - message: targetNamespaces is immutable once set rule: '!has(oldSelf.targetNamespaces) || (has(self.targetNamespaces) && self.targetNamespaces == oldSelf.targetNamespaces)' - - message: outputSchema is immutable once set - rule: '!has(oldSelf.outputSchema) || (has(self.outputSchema) && self.outputSchema - == oldSelf.outputSchema)' + - message: analysisOutput is immutable once set + rule: '!has(oldSelf.analysisOutput) || (has(self.analysisOutput) && + self.analysisOutput == oldSelf.analysisOutput)' + - message: analysisOutput mode Minimal is only allowed for analysis-only + proposals (no execution or verification steps) + rule: '!has(self.analysisOutput) || self.analysisOutput.mode != ''Minimal'' + || (!has(self.execution) && !has(self.verification))' - message: tools is immutable once set rule: '!has(oldSelf.tools) || (has(self.tools) && self.tools == oldSelf.tools)' - message: analysis is immutable once set @@ -1732,7 +1749,7 @@ spec: - status - type type: object - maxItems: 8 + maxItems: 9 minItems: 1 type: array x-kubernetes-list-map-keys: diff --git a/install/0000_00_cluster-version-operator_46_lightspeed-crd-agents.yaml b/install/0000_00_cluster-version-operator_46_lightspeed-crd-agents.yaml index e49810173..dfc856026 100644 --- a/install/0000_00_cluster-version-operator_46_lightspeed-crd-agents.yaml +++ b/install/0000_00_cluster-version-operator_46_lightspeed-crd-agents.yaml @@ -5,7 +5,7 @@ metadata: annotations: include.release.openshift.io/self-managed-high-availability: "true" release.openshift.io/feature-set: TechPreviewNoUpgrade - controller-gen.kubebuilder.io/version: v0.20.1 + controller-gen.kubebuilder.io/version: v0.19.0 name: agents.agentic.openshift.io spec: group: agentic.openshift.io @@ -20,6 +20,9 @@ spec: - jsonPath: .spec.llmProvider.name name: LLM type: string + - jsonPath: .spec.model + name: Model + type: string - jsonPath: .status.conditions[?(@.type=="Ready")].status name: Ready type: string @@ -37,12 +40,12 @@ spec: are optional (the\noperator auto-links to \"default\" if absent).\n\nExample — a high-capability agent tier:\n\n\tapiVersion: agentic.openshift.io/v1alpha1\n\tkind: Agent\n\tmetadata:\n\t name: smart\n\tspec:\n\t llmProvider:\n\t name: - vertex-opus\n\t timeouts:\n\t analysisSeconds: 300\n\t executionSeconds: - 600\n\t maxTurns: 200\n\t providerSettings:\n\t reasoningEffort: \"high\"\n\nExample - — a fast, cost-efficient agent tier:\n\n\tapiVersion: agentic.openshift.io/v1alpha1\n\tkind: + vertex-ai\n\t model: claude-opus-4-6\n\t timeouts:\n\t analysisSeconds: + 300\n\t executionSeconds: 600\n\t maxTurns: 200\n\nExample — a fast, + cost-efficient agent tier:\n\n\tapiVersion: agentic.openshift.io/v1alpha1\n\tkind: Agent\n\tmetadata:\n\t name: fast\n\tspec:\n\t llmProvider:\n\t name: - vertex-haiku\n\t timeouts:\n\t analysisSeconds: 120\n\t executionSeconds: - 300\n\t maxTurns: 100\n\t providerSettings:\n\t reasoningEffort: \"low\"" + vertex-ai\n\t model: claude-haiku-4-5\n\t timeouts:\n\t analysisSeconds: + 120\n\t executionSeconds: 300\n\t maxTurns: 100" properties: apiVersion: description: |- @@ -70,10 +73,15 @@ spec: LLM backend for this agent tier. properties: name: - description: name of the LLMProvider. + description: name of the LLMProvider. Must be a valid RFC 1123 + DNS subdomain. maxLength: 253 minLength: 1 type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' required: - name type: object @@ -81,21 +89,32 @@ spec: description: |- maxTurns is the maximum number of tool-use turns the agent may take in a single step invocation. Prevents runaway loops. + When omitted, the agent sandbox uses its built-in default. + Minimum 1, maximum 500. format: int32 maximum: 500 minimum: 1 type: integer - providerSettings: - additionalProperties: - type: string + model: description: |- - providerSettings is a freeform key-value map passed through to the - LLM SDK. Use this for provider-specific tuning parameters such as - temperature, reasoningEffort, topP, etc. The operator does not - validate these keys — they are forwarded as-is to the SDK. - type: object + model is the LLM model identifier as recognized by the provider + (e.g., "claude-opus-4-6", "claude-haiku-4-5", "gpt-4o"). + Must start with an alphanumeric character and may contain + alphanumerics, dots, hyphens, underscores, slashes, colons, + and at-signs. Maximum 256 characters. + maxLength: 256 + minLength: 1 + type: string + x-kubernetes-validations: + - message: model must start with an alphanumeric character and contain + only alphanumerics, dots, hyphens, underscores, slashes, colons, + and at-signs + rule: self.matches('^[a-zA-Z0-9][a-zA-Z0-9._\\-/:@]*$') timeouts: - description: timeouts configures per-step and per-turn timeout limits. + description: |- + timeouts configures per-step and per-turn timeout limits. + When omitted, the agent sandbox uses its built-in defaults. + minProperties: 1 properties: analysisSeconds: description: analysisSeconds is the timeout for the analysis step @@ -128,16 +147,17 @@ spec: type: object required: - llmProvider + - model type: object status: description: status defines the observed state of Agent. + minProperties: 1 properties: conditions: description: |- conditions represent the latest available observations of the Agent's state. The Ready condition summarizes whether all referenced resources (LLMProvider, Secrets) are present. - Empty until the operator's first reconcile. items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -193,6 +213,7 @@ spec: - type type: object maxItems: 8 + minItems: 1 type: array x-kubernetes-list-map-keys: - type diff --git a/install/0000_00_cluster-version-operator_47_lightspeed-crd-analysisresults.yaml b/install/0000_00_cluster-version-operator_47_lightspeed-crd-analysisresults.yaml index 6c09d321c..b5584fc45 100644 --- a/install/0000_00_cluster-version-operator_47_lightspeed-crd-analysisresults.yaml +++ b/install/0000_00_cluster-version-operator_47_lightspeed-crd-analysisresults.yaml @@ -146,9 +146,9 @@ spec: RemediationOption represents a single remediation approach produced by the analysis agent. The agent may return multiple options, each with its own diagnosis, remediation plan, verification strategy, and RBAC - requirements. The user selects one option after analysis - (recorded in AnalysisStepStatus.selectedOption), and the operator uses - that option's RBAC and plan for the execution step. + requirements. When the user approves execution, the operator trims + the AnalysisResult to keep only the approved option and uses its + RBAC and plan for the execution step. The components field is an extensibility point for adapter-specific UI data. For example, an ACS adapter might include violation details or @@ -158,13 +158,15 @@ spec: components: description: |- components contains optional adapter-defined structured data whose - shape is determined by spec.outputSchema on the Proposal. The - operator passes this through to the AnalysisResult CR; the console - renders it using adapter-specific UI components. + shape is determined by spec.analysisOutput.schema on the Proposal. + The operator passes this through to the AnalysisResult CR; the + console renders it using adapter-specific UI components. x-kubernetes-preserve-unknown-fields: true diagnosis: - description: diagnosis contains the root cause analysis specific - to this option. + description: |- + diagnosis contains the root cause analysis specific to this option. + Present when analysisOutput mode is Default (or omitted). Omitted + when mode is Minimal. properties: confidence: description: |- @@ -197,8 +199,10 @@ spec: - summary type: object proposal: - description: proposal contains the remediation plan for this - option. + description: |- + proposal contains the remediation plan for this option. + Present when analysisOutput mode is Default (or omitted). Omitted + when mode is Minimal without an execution step. properties: actions: description: |- @@ -576,10 +580,13 @@ spec: - description type: object required: - - diagnosis - - proposal - title type: object + x-kubernetes-validations: + - message: proposal is required when diagnosis is present + rule: '!has(self.diagnosis) || has(self.proposal)' + - message: diagnosis is required when proposal is present + rule: '!has(self.proposal) || has(self.diagnosis)' maxItems: 10 minItems: 1 type: array diff --git a/install/0000_00_cluster-version-operator_47_lightspeed-crd-llmproviders.yaml b/install/0000_00_cluster-version-operator_47_lightspeed-crd-llmproviders.yaml index 63836253c..0ccfcee44 100644 --- a/install/0000_00_cluster-version-operator_47_lightspeed-crd-llmproviders.yaml +++ b/install/0000_00_cluster-version-operator_47_lightspeed-crd-llmproviders.yaml @@ -5,7 +5,7 @@ metadata: annotations: include.release.openshift.io/self-managed-high-availability: "true" release.openshift.io/feature-set: TechPreviewNoUpgrade - controller-gen.kubebuilder.io/version: v0.20.1 + controller-gen.kubebuilder.io/version: v0.19.0 name: llmproviders.agentic.openshift.io spec: group: agentic.openshift.io @@ -20,9 +20,6 @@ spec: - jsonPath: .spec.type name: Type type: string - - jsonPath: .spec.model - name: Model - type: string - jsonPath: .metadata.creationTimestamp name: Age type: date @@ -33,21 +30,16 @@ spec: first link in\nthe CRD chain (LLMProvider -> Agent -> Workflow -> Proposal) and is\nreferenced by Agent resources via spec.llmProvider.\n\nLLMProvider is cluster-scoped — the cluster admin manages LLM infrastructure\ncentrally. - The operator uses the credentials and model to configure the LLM\nclient - inside agent sandbox pods.\n\nTypically you create a small number of providers - representing different\ncapability/cost tiers (e.g., \"smart\" for complex - analysis, \"fast\" for\nroutine execution) and then reference them from - multiple Agent resources.\n\nExample — a high-capability provider for analysis - tasks:\n\n\tapiVersion: agentic.openshift.io/v1alpha1\n\tkind: LLMProvider\n\tmetadata:\n\t - \ name: smart\n\tspec:\n\t type: GoogleCloudVertex\n\t model: claude-opus-4-6\n\t + The operator uses the credentials to configure the LLM client\ninside agent + sandbox pods. The model is specified on the Agent CR, allowing\nmultiple + agents to share one LLMProvider with different models.\n\nTypically you + create one provider per backend (e.g., one for Vertex AI)\nand then reference + it from multiple Agent resources with different models.\n\nExample — a Vertex + AI provider (model specified on Agent, not here):\n\n\tapiVersion: agentic.openshift.io/v1alpha1\n\tkind: + LLMProvider\n\tmetadata:\n\t name: vertex-ai\n\tspec:\n\t type: GoogleCloudVertex\n\t \ googleCloudVertex:\n\t credentialsSecret:\n\t name: llm-credentials\n\t - \ namespace: openshift-lightspeed\n\t project: my-gcp-project\n\t - \ region: us-central1\n\nExample — a fast, cost-efficient provider for - execution tasks:\n\n\tapiVersion: agentic.openshift.io/v1alpha1\n\tkind: - LLMProvider\n\tmetadata:\n\t name: fast\n\tspec:\n\t type: GoogleCloudVertex\n\t - \ model: claude-haiku-4-5\n\t googleCloudVertex:\n\t credentialsSecret:\n\t - \ name: llm-credentials\n\t namespace: openshift-lightspeed\n\t - \ project: my-gcp-project\n\t region: us-central1" + \ projectID: my-gcp-project\n\t region: us-central1\n\t modelProvider: + Anthropic" properties: apiVersion: description: |- @@ -76,23 +68,42 @@ spec: properties: credentialsSecret: description: |- - credentialsSecret references a Secret containing an ANTHROPIC_API_KEY. - Since LLMProvider is cluster-scoped, both name and namespace are required. + credentialsSecret references a Secret in the operator namespace + (openshift-lightspeed) containing the Anthropic API credentials. + The Secret must contain the key ANTHROPIC_API_KEY. properties: name: - description: name of the Secret. + description: name of the Secret. Must be a valid RFC 1123 + DNS subdomain. maxLength: 253 minLength: 1 type: string - namespace: - description: namespace of the Secret. - maxLength: 63 - minLength: 1 - type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' required: - name - - namespace type: object + url: + description: |- + url is an optional override for the Anthropic API endpoint. + Only needed for custom deployments or API proxies. + Must be a valid HTTP or HTTPS URL with a hostname. Paths and query + parameters are allowed. Fragments and userinfo are not permitted. + Maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must use http or https scheme + rule: isURL(self) && url(self).getScheme() in ['http', 'https'] + - message: must include a hostname + rule: isURL(self) && url(self).getHostname() != '' + - message: userinfo is not allowed in URL + rule: '!self.contains(''@'')' + - message: fragments are not allowed in URL + rule: '!self.contains(''#'')' required: - credentialsSecret type: object @@ -103,30 +114,55 @@ spec: properties: credentialsSecret: description: |- - credentialsSecret references a Secret containing AWS_ACCESS_KEY_ID - and AWS_SECRET_ACCESS_KEY. Since LLMProvider is cluster-scoped, - both name and namespace are required. + credentialsSecret references a Secret in the operator namespace + (openshift-lightspeed) containing AWS credentials. The Secret must + contain the keys AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. properties: name: - description: name of the Secret. + description: name of the Secret. Must be a valid RFC 1123 + DNS subdomain. maxLength: 253 minLength: 1 type: string - namespace: - description: namespace of the Secret. - maxLength: 63 - minLength: 1 - type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' required: - name - - namespace type: object region: - description: region is the AWS region for the Bedrock endpoint - (e.g., "us-east-1"). + description: |- + region is the AWS region for the Bedrock endpoint. + Must begin with a lowercase letter and end with a lowercase + alphanumeric character. May contain lowercase letters, digits, + and hyphens (e.g., "us-east-1", "eu-west-2", "ap-southeast-1"). maxLength: 63 + minLength: 2 + type: string + x-kubernetes-validations: + - message: region must contain only lowercase letters, digits, + and hyphens, start with a letter, and not end with a hyphen + rule: self.matches('^[a-z][a-z0-9-]*[a-z0-9]$') + url: + description: |- + url is an optional override for the AWS Bedrock API endpoint. + Only needed for custom deployments or API proxies. + Must be a valid HTTP or HTTPS URL with a hostname. Paths and query + parameters are allowed. Fragments and userinfo are not permitted. + Maximum 2048 characters. + maxLength: 2048 minLength: 1 type: string + x-kubernetes-validations: + - message: must use http or https scheme + rule: isURL(self) && url(self).getScheme() in ['http', 'https'] + - message: must include a hostname + rule: isURL(self) && url(self).getHostname() != '' + - message: userinfo is not allowed in URL + rule: '!self.contains(''@'')' + - message: fragments are not allowed in URL + rule: '!self.contains(''#'')' required: - credentialsSecret - region @@ -138,39 +174,75 @@ spec: properties: apiVersion: description: |- - apiVersion is the Azure OpenAI API version (e.g., "2024-02-01"). + apiVersion is the Azure OpenAI API version. Azure API versions use + a date-based format: YYYY-MM-DD with an optional "-preview" suffix + (e.g., "2024-02-01", "2024-08-01-preview"). When omitted, the SDK default is used. maxLength: 32 + minLength: 1 type: string + x-kubernetes-validations: + - message: apiVersion must be a date in YYYY-MM-DD format with + an optional -preview suffix + rule: self.matches('^[0-9]{4}-[0-9]{2}-[0-9]{2}(-preview)?$') credentialsSecret: description: |- - credentialsSecret references a Secret containing an AZURE_OPENAI_API_KEY. - Since LLMProvider is cluster-scoped, both name and namespace are required. + credentialsSecret references a Secret in the operator namespace + (openshift-lightspeed) containing the Azure OpenAI API credentials. + The Secret must contain the key AZURE_OPENAI_API_KEY. properties: name: - description: name of the Secret. + description: name of the Secret. Must be a valid RFC 1123 + DNS subdomain. maxLength: 253 minLength: 1 type: string - namespace: - description: namespace of the Secret. - maxLength: 63 - minLength: 1 - type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' required: - name - - namespace type: object endpoint: description: |- endpoint is the Azure OpenAI resource endpoint (e.g., "https://my-resource.openai.azure.com"). + Must be a valid HTTP or HTTPS URL with a hostname. Paths and query + parameters are allowed. Fragments and userinfo are not permitted. + Maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must use http or https scheme + rule: isURL(self) && url(self).getScheme() in ['http', 'https'] + - message: must include a hostname + rule: isURL(self) && url(self).getHostname() != '' + - message: userinfo is not allowed in URL + rule: '!self.contains(''@'')' + - message: fragments are not allowed in URL + rule: '!self.contains(''#'')' + url: + description: |- + url is an optional override for the Azure OpenAI API endpoint. + Only needed for custom deployments or API proxies. This is separate + from the required 'endpoint' field which identifies the Azure resource. + Must be a valid HTTP or HTTPS URL with a hostname. Paths and query + parameters are allowed. Fragments and userinfo are not permitted. + Maximum 2048 characters. maxLength: 2048 minLength: 1 type: string x-kubernetes-validations: - - message: endpoint must be a valid HTTP or HTTPS URL + - message: must use http or https scheme rule: isURL(self) && url(self).getScheme() in ['http', 'https'] + - message: must include a hostname + rule: isURL(self) && url(self).getHostname() != '' + - message: userinfo is not allowed in URL + rule: '!self.contains(''@'')' + - message: fragments are not allowed in URL + rule: '!self.contains(''#'')' required: - credentialsSecret - endpoint @@ -182,58 +254,86 @@ spec: properties: credentialsSecret: description: |- - credentialsSecret references a Secret containing a service account JSON - key (stored under the key GOOGLE_APPLICATION_CREDENTIALS). Since - LLMProvider is cluster-scoped, both name and namespace are required. + credentialsSecret references a Secret in the operator namespace + (openshift-lightspeed) containing a GCP service account JSON key. + The Secret must contain the key GOOGLE_APPLICATION_CREDENTIALS. properties: name: - description: name of the Secret. + description: name of the Secret. Must be a valid RFC 1123 + DNS subdomain. maxLength: 253 minLength: 1 type: string - namespace: - description: namespace of the Secret. - maxLength: 63 - minLength: 1 - type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' required: - name - - namespace type: object - project: - description: project is the GCP project ID where Vertex AI is - enabled. - maxLength: 63 - minLength: 1 + modelProvider: + description: |- + modelProvider selects which model provider stack talks to Vertex AI (required). + "Anthropic" uses Anthropic models on Vertex; "Google" uses Google models on Vertex; + "OpenAI" uses OpenAI-compatible models on Vertex. + Enum is defined on GoogleCloudVertexModelProvider. + enum: + - Anthropic + - Google + - OpenAI + type: string + projectID: + description: |- + projectID is the Google Cloud Project ID where Vertex AI is enabled. + A Project ID is a globally unique identifier that must be 6 to 30 + characters in length, can only contain lowercase letters, digits, and + hyphens, must start with a letter, and cannot end with a hyphen. + maxLength: 30 + minLength: 6 type: string + x-kubernetes-validations: + - message: projectID must start with a lowercase letter, contain + only lowercase letters, digits, and hyphens, and cannot end + with a hyphen + rule: self.matches('^[a-z][a-z0-9-]*[a-z0-9]$') region: - description: region is the GCP region for the Vertex AI endpoint - (e.g., "us-central1"). + description: |- + region is the GCP region for the Vertex AI endpoint. + Must begin with a lowercase letter and end with a lowercase + alphanumeric character. May contain lowercase letters, digits, + and hyphens (e.g., "us-central1", "europe-west4", "asia-southeast1"). maxLength: 63 + minLength: 2 + type: string + x-kubernetes-validations: + - message: region must contain only lowercase letters, digits, + and hyphens, start with a letter, and not end with a hyphen + rule: self.matches('^[a-z][a-z0-9-]*[a-z0-9]$') + url: + description: |- + url is an optional override for the Vertex AI API endpoint. + Only needed for custom deployments or API proxies. + Must be a valid HTTP or HTTPS URL with a hostname. Paths and query + parameters are allowed. Fragments and userinfo are not permitted. + Maximum 2048 characters. + maxLength: 2048 minLength: 1 type: string + x-kubernetes-validations: + - message: must use http or https scheme + rule: isURL(self) && url(self).getScheme() in ['http', 'https'] + - message: must include a hostname + rule: isURL(self) && url(self).getHostname() != '' + - message: userinfo is not allowed in URL + rule: '!self.contains(''@'')' + - message: fragments are not allowed in URL + rule: '!self.contains(''#'')' required: - credentialsSecret - - project + - modelProvider + - projectID - region type: object - model: - description: |- - model is the LLM model identifier as recognized by the provider - (e.g., "claude-opus-4-6", "claude-haiku-4-5", "gpt-4o"). - Different agents can reference different LLMProviders to use different - models for different tasks (e.g., a capable model for analysis, - a fast model for execution). Must be 1-256 characters, starting with - an alphanumeric character and containing only alphanumerics, dots, - hyphens, underscores, slashes, colons, and at-signs. - maxLength: 256 - minLength: 1 - type: string - x-kubernetes-validations: - - message: model must start with an alphanumeric character and contain - only alphanumerics, dots, hyphens, underscores, slashes, colons, - and at-signs - rule: self.matches('^[a-zA-Z0-9][a-zA-Z0-9._\\-/:@]*$') openAI: description: |- openAI contains OpenAI-specific configuration. @@ -241,31 +341,69 @@ spec: properties: credentialsSecret: description: |- - credentialsSecret references a Secret containing an OPENAI_API_KEY. - Since LLMProvider is cluster-scoped, both name and namespace are required. + credentialsSecret references a Secret in the operator namespace + (openshift-lightspeed) containing the OpenAI API credentials. + The Secret must contain the key OPENAI_API_KEY. properties: name: - description: name of the Secret. + description: name of the Secret. Must be a valid RFC 1123 + DNS subdomain. maxLength: 253 minLength: 1 type: string - namespace: - description: namespace of the Secret. - maxLength: 63 - minLength: 1 - type: string + x-kubernetes-validations: + - message: 'must be a valid DNS subdomain: lowercase alphanumeric + characters, hyphens, and dots' + rule: '!format.dns1123Subdomain().validate(self).hasValue()' required: - name - - namespace type: object + url: + description: |- + url is an optional override for the OpenAI API endpoint. + Only needed for custom deployments or API proxies. + Must be a valid HTTP or HTTPS URL with a hostname. Paths and query + parameters are allowed. Fragments and userinfo are not permitted. + Maximum 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must use http or https scheme + rule: isURL(self) && url(self).getScheme() in ['http', 'https'] + - message: must include a hostname + rule: isURL(self) && url(self).getHostname() != '' + - message: userinfo is not allowed in URL + rule: '!self.contains(''@'')' + - message: fragments are not allowed in URL + rule: '!self.contains(''#'')' required: - credentialsSecret type: object type: description: |- - type is the LLM provider backend. Determines which per-provider - configuration field must be set. Allowed values: "Anthropic", - "GoogleCloudVertex", "OpenAI", "AzureOpenAI", "AWSBedrock". + type is a required field that configures which LLM provider backend + should be used. + + Allowed values are Anthropic, GoogleCloudVertex, OpenAI, AzureOpenAI, + and AWSBedrock. + + When set to Anthropic, agents referencing this provider will use the + Anthropic API directly, and the 'anthropic' field must be configured. + + When set to GoogleCloudVertex, agents referencing this provider will + use Google Cloud Vertex AI, and the 'googleCloudVertex' field must be + configured. + + When set to OpenAI, agents referencing this provider will use an + OpenAI-compatible API, and the 'openAI' field must be configured. + + When set to AzureOpenAI, agents referencing this provider will use + the Azure OpenAI Service, and the 'azureOpenAI' field must be + configured. + + When set to AWSBedrock, agents referencing this provider will use + AWS Bedrock, and the 'awsBedrock' field must be configured. enum: - Anthropic - GoogleCloudVertex @@ -273,24 +411,7 @@ spec: - AzureOpenAI - AWSBedrock type: string - url: - description: |- - url is an optional override for the provider API endpoint. - Most providers have well-known endpoints that the operator resolves - automatically, so this is only needed for custom deployments or - API proxies. This is not related to the cluster-wide egress proxy - (config.openshift.io/v1 Proxy). The operator honors the cluster - proxy configuration (HTTP_PROXY, HTTPS_PROXY, NO_PROXY) independently - when making requests to the provider endpoint. Must be an HTTP or - HTTPS URL, maximum 2048 characters. - maxLength: 2048 - minLength: 1 - type: string - x-kubernetes-validations: - - message: url must be a valid HTTP or HTTPS URL - rule: isURL(self) && url(self).getScheme() in ['http', 'https'] required: - - model - type type: object x-kubernetes-validations: