From c5f1d399585612764f809e1797163edfbc6aa00b Mon Sep 17 00:00:00 2001 From: despiegk Date: Wed, 29 Oct 2025 07:53:34 +0400 Subject: [PATCH] ... --- examples/{aiexamples => ai}/groq.vsh | 0 examples/{aiexamples => ai}/jetconvertor.vsh | 0 examples/{aiexamples => ai}/jina.vsh | 0 .../jina_example.vsh => ai/jina2.vsh} | 0 examples/ai/openaiclient_openrouter.vsh | 42 ++++++ examples/{clients => ai}/openrouter/README.md | 2 +- .../openrouter/openrouter_example.vsh | 0 .../openrouter/openrouter_hello.vsh} | 0 .../openrouter/openrouter_init.vsh} | 2 + .../openrouter_two_model_pipeline.v | 0 examples/{aiexamples => ai}/qdrant.vsh | 0 lib/clients/openai/openai_model.v | 3 + lib/core/playcmds/play_all.v | 4 + lib/data/encoderhero/decoder.v | 7 +- lib/virt/kubernetes/kubernetes_actions.v | 6 +- lib/virt/kubernetes/kubernetes_client.v | 138 ++++++++++-------- lib/virt/kubernetes/kubernetes_model.v | 9 +- .../kubernetes/kubernetes_resources_model.v | 35 ----- 18 files changed, 138 insertions(+), 110 deletions(-) rename examples/{aiexamples => ai}/groq.vsh (100%) rename examples/{aiexamples => ai}/jetconvertor.vsh (100%) rename examples/{aiexamples => ai}/jina.vsh (100%) rename examples/{clients/jina_example.vsh => ai/jina2.vsh} (100%) create mode 100755 examples/ai/openaiclient_openrouter.vsh rename examples/{clients => ai}/openrouter/README.md (93%) rename examples/{clients => ai}/openrouter/openrouter_example.vsh (100%) rename examples/{clients/openrouter/openrouter_hello.v => ai/openrouter/openrouter_hello.vsh} (100%) rename examples/{clients/aiclient_example.vsh => ai/openrouter/openrouter_init.vsh} (86%) rename examples/{clients => ai}/openrouter/openrouter_two_model_pipeline.v (100%) rename examples/{aiexamples => ai}/qdrant.vsh (100%) diff --git a/examples/aiexamples/groq.vsh b/examples/ai/groq.vsh similarity index 100% rename from examples/aiexamples/groq.vsh rename to examples/ai/groq.vsh diff --git a/examples/aiexamples/jetconvertor.vsh b/examples/ai/jetconvertor.vsh similarity index 100% rename from examples/aiexamples/jetconvertor.vsh rename to examples/ai/jetconvertor.vsh diff --git a/examples/aiexamples/jina.vsh b/examples/ai/jina.vsh similarity index 100% rename from examples/aiexamples/jina.vsh rename to examples/ai/jina.vsh diff --git a/examples/clients/jina_example.vsh b/examples/ai/jina2.vsh similarity index 100% rename from examples/clients/jina_example.vsh rename to examples/ai/jina2.vsh diff --git a/examples/ai/openaiclient_openrouter.vsh b/examples/ai/openaiclient_openrouter.vsh new file mode 100755 index 00000000..3626234e --- /dev/null +++ b/examples/ai/openaiclient_openrouter.vsh @@ -0,0 +1,42 @@ +#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run + +import incubaid.herolib.clients.openai +import incubaid.herolib.core.playcmds + +playcmds.run( + heroscript: ' + !!openai.configure name:"default" + url:"https://openrouter.ai/api/v1" + model_default:"gpt-oss-120b" + ' + reset: false +)! + +// Get the client instance +mut client := openai.get() or { + eprintln('Failed to get client: ${err}') + return +} + +println(client.list_models()!) + +println('Sending message to OpenRouter...\n') + +// Simple hello message +response := client.chat_completion( + model: 'qwen/qwen-2.5-coder-32b-instruct' + message: 'Say hello in a friendly way!' + temperature: 0.7 + max_completion_tokens: 100 +) or { + eprintln('Failed to get completion: ${err}') + return +} + +println('Response from AI:') +println('─'.repeat(50)) +println(response.result) +println('─'.repeat(50)) +println('\nTokens used: ${response.usage.total_tokens}') +println(' - Prompt: ${response.usage.prompt_tokens}') +println(' - Completion: ${response.usage.completion_tokens}') diff --git a/examples/clients/openrouter/README.md b/examples/ai/openrouter/README.md similarity index 93% rename from examples/clients/openrouter/README.md rename to examples/ai/openrouter/README.md index 12fc7e91..c3c31327 100644 --- a/examples/clients/openrouter/README.md +++ b/examples/ai/openrouter/README.md @@ -2,7 +2,7 @@ ## Overview -This folder contains **three example scripts** demonstrating the usage of the OpenRouter V client (`herolib.clients.openrouter`). +This folder contains **example scripts** demonstrating the usage of the OpenRouter V client (`herolib.clients.openrouter`). * **Goal:** Show how to send messages to OpenRouter models, run a **two-model pipeline** for code enhancement, and illustrate multi-model usage. diff --git a/examples/clients/openrouter/openrouter_example.vsh b/examples/ai/openrouter/openrouter_example.vsh similarity index 100% rename from examples/clients/openrouter/openrouter_example.vsh rename to examples/ai/openrouter/openrouter_example.vsh diff --git a/examples/clients/openrouter/openrouter_hello.v b/examples/ai/openrouter/openrouter_hello.vsh similarity index 100% rename from examples/clients/openrouter/openrouter_hello.v rename to examples/ai/openrouter/openrouter_hello.vsh diff --git a/examples/clients/aiclient_example.vsh b/examples/ai/openrouter/openrouter_init.vsh similarity index 86% rename from examples/clients/aiclient_example.vsh rename to examples/ai/openrouter/openrouter_init.vsh index 3b0cc2ac..8b2f17b3 100755 --- a/examples/clients/aiclient_example.vsh +++ b/examples/ai/openrouter/openrouter_init.vsh @@ -3,6 +3,8 @@ import incubaid.herolib.clients.openai import incubaid.herolib.core.playcmds +//to set the API key, either set it here, or set the OPENAI_API_KEY environment variable + playcmds.run( heroscript: ' !!openai.configure name: "default" key: "" url: "https://openrouter.ai/api/v1" model_default: "gpt-oss-120b" diff --git a/examples/clients/openrouter/openrouter_two_model_pipeline.v b/examples/ai/openrouter/openrouter_two_model_pipeline.v similarity index 100% rename from examples/clients/openrouter/openrouter_two_model_pipeline.v rename to examples/ai/openrouter/openrouter_two_model_pipeline.v diff --git a/examples/aiexamples/qdrant.vsh b/examples/ai/qdrant.vsh similarity index 100% rename from examples/aiexamples/qdrant.vsh rename to examples/ai/qdrant.vsh diff --git a/lib/clients/openai/openai_model.v b/lib/clients/openai/openai_model.v index 2b217129..5308cda6 100644 --- a/lib/clients/openai/openai_model.v +++ b/lib/clients/openai/openai_model.v @@ -46,6 +46,9 @@ fn obj_init(mycfg_ OpenAI) !OpenAI { } } } + if mycfg.api_key == '' { + return error('OpenAI client "${mycfg.name}" missing api_key') + } return mycfg } diff --git a/lib/core/playcmds/play_all.v b/lib/core/playcmds/play_all.v index d4211893..8b16a183 100644 --- a/lib/core/playcmds/play_all.v +++ b/lib/core/playcmds/play_all.v @@ -10,6 +10,7 @@ import incubaid.herolib.clients.meilisearch import incubaid.herolib.clients.mycelium import incubaid.herolib.clients.mycelium_rpc import incubaid.herolib.clients.openai +import incubaid.herolib.clients.openrouter import incubaid.herolib.clients.postgresql_client import incubaid.herolib.clients.qdrant import incubaid.herolib.clients.rclone @@ -47,6 +48,7 @@ import incubaid.herolib.installers.web.tailwind4 import incubaid.herolib.installers.web.traefik import incubaid.herolib.installers.web.zola import incubaid.herolib.virt.hetznermanager +import incubaid.herolib.virt.kubernetes pub fn run_all(args_ PlayArgs) ! { mut args := args_ @@ -64,6 +66,7 @@ pub fn run_all(args_ PlayArgs) ! { mycelium.play(mut plbook)! mycelium_rpc.play(mut plbook)! openai.play(mut plbook)! + openrouter.play(mut plbook)! postgresql_client.play(mut plbook)! qdrant.play(mut plbook)! rclone.play(mut plbook)! @@ -101,4 +104,5 @@ pub fn run_all(args_ PlayArgs) ! { traefik.play(mut plbook)! zola.play(mut plbook)! hetznermanager.play(mut plbook)! + kubernetes.play(mut plbook)! } diff --git a/lib/data/encoderhero/decoder.v b/lib/data/encoderhero/decoder.v index 701838bb..e463e9df 100644 --- a/lib/data/encoderhero/decoder.v +++ b/lib/data/encoderhero/decoder.v @@ -21,6 +21,7 @@ fn decode_struct[T](_ T, data string) !T { $if T is $struct { obj_name := texttools.snake_case(T.name.all_after_last('.')) + obj_name2 := texttools.name_fix(T.name.all_after_last('.')) // Define possible action name formats to try action_names_to_try := [ @@ -28,6 +29,10 @@ fn decode_struct[T](_ T, data string) !T { 'configure.${obj_name}', '${obj_name}.define', '${obj_name}.configure', + 'define.${obj_name2}', + 'configure.${obj_name2}', + '${obj_name2}.define', + '${obj_name2}.configure', ] mut found_action_name := '' @@ -47,7 +52,7 @@ fn decode_struct[T](_ T, data string) !T { } if found_action_name == '' { - return error('Data does not contain expected action format for ${obj_name}\nData: ${data}') + return error('Data does not contain expected action format for obj:${obj_name} or obj:${obj_name2}\nData: ${data}') } if actions.len > 1 { diff --git a/lib/virt/kubernetes/kubernetes_actions.v b/lib/virt/kubernetes/kubernetes_actions.v index 55553d4a..2d84cdc0 100644 --- a/lib/virt/kubernetes/kubernetes_actions.v +++ b/lib/virt/kubernetes/kubernetes_actions.v @@ -54,6 +54,6 @@ fn destroy() ! { // No cleanup needed for kubectl wrapper } -fn configure() ! { - console.print_debug('Kubernetes client configured') -} +// fn configure() ! { +// console.print_debug('Kubernetes client configured') +// } diff --git a/lib/virt/kubernetes/kubernetes_client.v b/lib/virt/kubernetes/kubernetes_client.v index 9751709c..73371451 100644 --- a/lib/virt/kubernetes/kubernetes_client.v +++ b/lib/virt/kubernetes/kubernetes_client.v @@ -9,36 +9,36 @@ import os // Execute kubectl command with proper error handling pub fn (mut k KubeClient) kubectl_exec(args KubectlExecArgs) !KubectlResult { - mut cmd := '${k.kubectl_path} ' + mut cmd := 'kubectl' - if !k.config.namespace.is_empty() { + if k.config.namespace.len > 0 { cmd += '--namespace=${k.config.namespace} ' } - if !k.kubeconfig_path.is_empty() { + if k.kubeconfig_path.len > 0 { cmd += '--kubeconfig=${k.kubeconfig_path} ' } - if !k.config.context.is_empty() { + if k.config.context.len > 0 { cmd += '--context=${k.config.context} ' } cmd += args.command - console.print_debug("executing: ${cmd}") + console.print_debug('executing: ${cmd}') job := osal.exec( - cmd: cmd - timeout: args.timeout - retry: args.retry + cmd: cmd + timeout: args.timeout + retry: args.retry raise_error: false )! return KubectlResult{ exit_code: job.exit_code - stdout: job.output - stderr: job.error - success: job.exit_code == 0 + stdout: job.output + stderr: job.error + success: job.exit_code == 0 } } @@ -76,80 +76,90 @@ pub fn (mut k KubeClient) cluster_info() !ClusterInfo { return error('Failed to get cluster version: ${result.stderr}') } - version_data := json.decode(map[string]interface{}, result.stdout)! - server_version := version_data['serverVersion'] or { return error('No serverVersion') } + println(result.stdout) - // Get node count - nodes_result := k.kubectl_exec(command: 'get nodes -o json')! - nodes_count := if nodes_result.success { - nodes_data := json.decode(map[string]interface{}, nodes_result.stdout)! - items := nodes_data['items'] or { []interface{}{} } - items.len - } else { - 0 - } + $dbg; + // version_data := json.decode(map[string]interface{}, result.stdout)! + // server_version := version_data['serverVersion'] or { return error('No serverVersion') } - // Get namespace count - ns_result := k.kubectl_exec(command: 'get namespaces -o json')! - ns_count := if ns_result.success { - ns_data := json.decode(map[string]interface{}, ns_result.stdout)! - items := ns_data['items'] or { []interface{}{} } - items.len - } else { - 0 - } + // // Get node count + // nodes_result := k.kubectl_exec(command: 'get nodes -o json')! + // nodes_count := if nodes_result.success { + // nodes_data := json.decode(map[string]interface{}, nodes_result.stdout)! + // items := nodes_data['items'] or { []interface{}{} } + // items.len + // } else { + // 0 + // } - // Get running pods count - pods_result := k.kubectl_exec(command: 'get pods --all-namespaces -o json')! - pods_count := if pods_result.success { - pods_data := json.decode(map[string]interface{}, pods_result.stdout)! - items := pods_data['items'] or { []interface{}{} } - items.len - } else { - 0 - } + // // Get namespace count + // ns_result := k.kubectl_exec(command: 'get namespaces -o json')! + // ns_count := if ns_result.success { + // ns_data := json.decode(map[string]interface{}, ns_result.stdout)! + // items := ns_data['items'] or { []interface{}{} } + // items.len + // } else { + // 0 + // } - return ClusterInfo{ - version: 'v1.0.0' - nodes: nodes_count - namespaces: ns_count - running_pods: pods_count - api_server: k.config.api_server - } + // // Get running pods count + // pods_result := k.kubectl_exec(command: 'get pods --all-namespaces -o json')! + // pods_count := if pods_result.success { + // pods_data := json.decode(map[string]interface{}, pods_result.stdout)! + // items := pods_data['items'] or { []interface{}{} } + // items.len + // } else { + // 0 + // } + + // return ClusterInfo{ + // version: 'v1.0.0' + // nodes: nodes_count + // namespaces: ns_count + // running_pods: pods_count + // api_server: k.config.api_server + // } + return ClusterInfo{} } // Get resources (Pods, Deployments, Services, etc.) -pub fn (mut k KubeClient) get_pods(namespace string) ![]map[string]interface{} { +pub fn (mut k KubeClient) get_pods(namespace string) ! { result := k.kubectl_exec(command: 'get pods -n ${namespace} -o json')! if !result.success { return error('Failed to get pods: ${result.stderr}') } - data := json.decode(map[string]interface{}, result.stdout)! - items := data['items'] or { []interface{}{} } - return items as []map[string]interface{} + println(result.stdout) + $dbg; + // data := json.decode(map[string]interface{}, result.stdout)! + // items := data['items'] or { []interface{}{} } + // return items as []map[string]interface{} + + panic('Not implemented') } -pub fn (mut k KubeClient) get_deployments(namespace string) ![]map[string]interface{} { +pub fn (mut k KubeClient) get_deployments(namespace string) ! { result := k.kubectl_exec(command: 'get deployments -n ${namespace} -o json')! if !result.success { return error('Failed to get deployments: ${result.stderr}') } - data := json.decode(map[string]interface{}, result.stdout)! - items := data['items'] or { []interface{}{} } - return items as []map[string]interface{} + // data := json.decode(map[string]interface{}, result.stdout)! + // items := data['items'] or { []interface{}{} } + // return items as []map[string]interface{} + panic('Not implemented') } -pub fn (mut k KubeClient) get_services(namespace string) ![]map[string]interface{} { +pub fn (mut k KubeClient) get_services(namespace string) ! { result := k.kubectl_exec(command: 'get services -n ${namespace} -o json')! if !result.success { return error('Failed to get services: ${result.stderr}') } - data := json.decode(map[string]interface{}, result.stdout)! - items := data['items'] or { []interface{}{} } - return items as []map[string]interface{} + // data := json.decode(map[string]interface{}, result.stdout)! + // items := data['items'] or { []interface{}{} } + // return items as []map[string]interface{} + panic('Not implemented') } // Apply YAML file @@ -157,7 +167,7 @@ pub fn (mut k KubeClient) apply_yaml(yaml_path string) !KubectlResult { // Validate before applying validation := yaml_validate(yaml_path)! if !validation.valid { - return error('YAML validation failed: ${validation.errors.join(", ")}') + return error('YAML validation failed: ${validation.errors.join(', ')}') } result := k.kubectl_exec(command: 'apply -f ${yaml_path}')! @@ -204,11 +214,11 @@ pub fn (mut k KubeClient) logs(pod_name string, namespace string, follow bool) ! // Exec into container pub fn (mut k KubeClient) exec_pod(pod_name string, namespace string, container string, cmd_args []string) !string { - cmd := 'exec -it ${pod_name} -n ${namespace}' - if !container.is_empty() { + mut cmd := 'exec -it ${pod_name} -n ${namespace}' + if container.len > 0 { cmd += ' -c ${container}' } - cmd += ' -- ${cmd_args.join(" ")}' + cmd += ' -- ${cmd_args.join(' ')}' result := k.kubectl_exec(command: cmd, timeout: 300)! if !result.success { @@ -228,4 +238,4 @@ pub fn (mut k KubeClient) watch_deployment(name string, namespace string, timeou cmd := 'rollout status deployment/${name} -n ${namespace} --timeout=${timeout_seconds}s' result := k.kubectl_exec(command: cmd, timeout: timeout_seconds + 10)! return result.success -} \ No newline at end of file +} diff --git a/lib/virt/kubernetes/kubernetes_model.v b/lib/virt/kubernetes/kubernetes_model.v index 80269398..b1ad61c3 100644 --- a/lib/virt/kubernetes/kubernetes_model.v +++ b/lib/virt/kubernetes/kubernetes_model.v @@ -11,16 +11,13 @@ const default = true @[heap] pub struct KubeClient { pub mut: - name string = 'default' name string = 'default' kubeconfig_path string config KubeConfig connected bool api_version string = 'v1' - kubectl_path string = 'kubectl' - cache_enabled bool = true - cache_ttl_seconds int = 300 - + cache_enabled bool = true + cache_ttl_seconds int = 300 } // your checking & initialization code if needed @@ -41,7 +38,7 @@ fn configure() ! { /////////////NORMALLY NO NEED TO TOUCH pub fn heroscript_loads(heroscript string) !KubeClient { - //TODO: will have to be implemented manual + // TODO: will have to be implemented manual mut obj := encoderhero.decode[KubeClient](heroscript)! return obj } diff --git a/lib/virt/kubernetes/kubernetes_resources_model.v b/lib/virt/kubernetes/kubernetes_resources_model.v index cd890d4e..cd2038f9 100644 --- a/lib/virt/kubernetes/kubernetes_resources_model.v +++ b/lib/virt/kubernetes/kubernetes_resources_model.v @@ -1,14 +1,8 @@ module kubernetes -import incubaid.herolib.data.paramsparser import incubaid.herolib.data.encoderhero -import incubaid.herolib.data.ourjson import os -pub const version = '1.0.0' -const singleton = false -const default = true - // K8s API Version and Kind tracking @[params] pub struct K8sMetadata { @@ -166,7 +160,6 @@ pub mut: insecure_skip_tls_verify bool } - // Validation result for YAML files pub struct K8sValidationResult { pub mut: @@ -186,31 +179,3 @@ pub mut: running_pods int api_server string } - -// Initialization -fn obj_init(mut cfg KubeClient) !KubeClient { - // Resolve kubeconfig path - if cfg.kubeconfig_path.is_empty() { - home := os.home_dir() - cfg.kubeconfig_path = '${home}/.kube/config' - } - - // Ensure kubeconfig exists - if !os.path_exists(cfg.kubeconfig_path) { - return error('kubeconfig not found at ${cfg.kubeconfig_path}') - } - - cfg.config.kubeconfig_path = cfg.kubeconfig_path - - return cfg -} - -fn configure() ! { - // Configure any defaults or environment-specific settings -} - -pub fn heroscript_loads(heroscript string) !KubeClient { - // TODO: this will have to be redone, because its much more complicated now, need to define a nice play processors - mut obj := encoderhero.decode[KubeClient](heroscript)! - return obj_init(obj)! -}