language-operator 0.1.61 → 0.1.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/persona.md +9 -0
  3. data/.claude/commands/task.md +46 -1
  4. data/.rubocop.yml +13 -0
  5. data/.rubocop_custom/use_ux_helper.rb +44 -0
  6. data/CHANGELOG.md +8 -0
  7. data/Gemfile.lock +12 -1
  8. data/Makefile +26 -7
  9. data/Makefile.common +50 -0
  10. data/bin/aictl +8 -1
  11. data/components/agent/Gemfile +1 -1
  12. data/components/agent/bin/langop-agent +7 -0
  13. data/docs/README.md +58 -0
  14. data/docs/{dsl/best-practices.md → best-practices.md} +4 -4
  15. data/docs/cli-reference.md +274 -0
  16. data/docs/{dsl/constraints.md → constraints.md} +5 -5
  17. data/docs/how-agents-work.md +156 -0
  18. data/docs/installation.md +218 -0
  19. data/docs/quickstart.md +299 -0
  20. data/docs/understanding-generated-code.md +265 -0
  21. data/docs/using-tools.md +457 -0
  22. data/docs/webhooks.md +509 -0
  23. data/examples/ux_helpers_demo.rb +296 -0
  24. data/lib/language_operator/agent/base.rb +11 -1
  25. data/lib/language_operator/agent/executor.rb +23 -6
  26. data/lib/language_operator/agent/safety/safe_executor.rb +41 -39
  27. data/lib/language_operator/agent/task_executor.rb +346 -63
  28. data/lib/language_operator/agent/web_server.rb +110 -14
  29. data/lib/language_operator/agent/webhook_authenticator.rb +39 -5
  30. data/lib/language_operator/agent.rb +88 -2
  31. data/lib/language_operator/cli/base_command.rb +17 -11
  32. data/lib/language_operator/cli/command_loader.rb +72 -0
  33. data/lib/language_operator/cli/commands/agent/base.rb +837 -0
  34. data/lib/language_operator/cli/commands/agent/code_operations.rb +102 -0
  35. data/lib/language_operator/cli/commands/agent/helpers/cluster_llm_client.rb +116 -0
  36. data/lib/language_operator/cli/commands/agent/helpers/code_parser.rb +115 -0
  37. data/lib/language_operator/cli/commands/agent/helpers/synthesis_watcher.rb +96 -0
  38. data/lib/language_operator/cli/commands/agent/learning.rb +289 -0
  39. data/lib/language_operator/cli/commands/agent/lifecycle.rb +102 -0
  40. data/lib/language_operator/cli/commands/agent/logs.rb +125 -0
  41. data/lib/language_operator/cli/commands/agent/workspace.rb +327 -0
  42. data/lib/language_operator/cli/commands/cluster.rb +129 -84
  43. data/lib/language_operator/cli/commands/install.rb +1 -1
  44. data/lib/language_operator/cli/commands/model/base.rb +215 -0
  45. data/lib/language_operator/cli/commands/model/test.rb +165 -0
  46. data/lib/language_operator/cli/commands/persona.rb +16 -34
  47. data/lib/language_operator/cli/commands/quickstart.rb +3 -2
  48. data/lib/language_operator/cli/commands/status.rb +40 -67
  49. data/lib/language_operator/cli/commands/system/base.rb +44 -0
  50. data/lib/language_operator/cli/commands/system/exec.rb +147 -0
  51. data/lib/language_operator/cli/commands/system/helpers/llm_synthesis.rb +183 -0
  52. data/lib/language_operator/cli/commands/system/helpers/pod_manager.rb +212 -0
  53. data/lib/language_operator/cli/commands/system/helpers/template_loader.rb +57 -0
  54. data/lib/language_operator/cli/commands/system/helpers/template_validator.rb +174 -0
  55. data/lib/language_operator/cli/commands/system/schema.rb +92 -0
  56. data/lib/language_operator/cli/commands/system/synthesis_template.rb +151 -0
  57. data/lib/language_operator/cli/commands/system/synthesize.rb +224 -0
  58. data/lib/language_operator/cli/commands/system/validate_template.rb +130 -0
  59. data/lib/language_operator/cli/commands/tool/base.rb +271 -0
  60. data/lib/language_operator/cli/commands/tool/install.rb +255 -0
  61. data/lib/language_operator/cli/commands/tool/search.rb +69 -0
  62. data/lib/language_operator/cli/commands/tool/test.rb +115 -0
  63. data/lib/language_operator/cli/commands/use.rb +29 -6
  64. data/lib/language_operator/cli/errors/handler.rb +20 -17
  65. data/lib/language_operator/cli/errors/suggestions.rb +3 -5
  66. data/lib/language_operator/cli/errors/thor_errors.rb +55 -0
  67. data/lib/language_operator/cli/formatters/code_formatter.rb +4 -11
  68. data/lib/language_operator/cli/formatters/log_formatter.rb +8 -15
  69. data/lib/language_operator/cli/formatters/progress_formatter.rb +6 -8
  70. data/lib/language_operator/cli/formatters/status_formatter.rb +26 -7
  71. data/lib/language_operator/cli/formatters/table_formatter.rb +47 -36
  72. data/lib/language_operator/cli/formatters/value_formatter.rb +75 -0
  73. data/lib/language_operator/cli/helpers/cluster_context.rb +5 -3
  74. data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +2 -1
  75. data/lib/language_operator/cli/helpers/label_utils.rb +97 -0
  76. data/lib/language_operator/{ux/concerns/provider_helpers.rb → cli/helpers/provider_helper.rb} +10 -29
  77. data/lib/language_operator/cli/helpers/schedule_builder.rb +21 -1
  78. data/lib/language_operator/cli/helpers/user_prompts.rb +19 -11
  79. data/lib/language_operator/cli/helpers/ux_helper.rb +538 -0
  80. data/lib/language_operator/{ux/concerns/input_validation.rb → cli/helpers/validation_helper.rb} +13 -66
  81. data/lib/language_operator/cli/main.rb +50 -40
  82. data/lib/language_operator/cli/templates/tools/generic.yaml +3 -0
  83. data/lib/language_operator/cli/wizards/agent_wizard.rb +12 -20
  84. data/lib/language_operator/cli/wizards/model_wizard.rb +271 -0
  85. data/lib/language_operator/cli/wizards/quickstart_wizard.rb +8 -34
  86. data/lib/language_operator/client/base.rb +28 -0
  87. data/lib/language_operator/client/config.rb +4 -1
  88. data/lib/language_operator/client/mcp_connector.rb +1 -1
  89. data/lib/language_operator/config/cluster_config.rb +3 -2
  90. data/lib/language_operator/config.rb +38 -11
  91. data/lib/language_operator/constants/kubernetes_labels.rb +80 -0
  92. data/lib/language_operator/constants.rb +13 -0
  93. data/lib/language_operator/dsl/http.rb +127 -10
  94. data/lib/language_operator/dsl.rb +153 -6
  95. data/lib/language_operator/errors.rb +50 -0
  96. data/lib/language_operator/kubernetes/client.rb +11 -6
  97. data/lib/language_operator/kubernetes/resource_builder.rb +58 -84
  98. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
  99. data/lib/language_operator/templates/schema/agent_dsl_schema.json +1 -1
  100. data/lib/language_operator/type_coercion.rb +118 -34
  101. data/lib/language_operator/utils/secure_path.rb +74 -0
  102. data/lib/language_operator/utils.rb +7 -0
  103. data/lib/language_operator/validators.rb +54 -2
  104. data/lib/language_operator/version.rb +1 -1
  105. data/synth/001/Makefile +10 -2
  106. data/synth/001/agent.rb +16 -15
  107. data/synth/001/output.log +27 -10
  108. data/synth/002/Makefile +10 -2
  109. data/synth/003/Makefile +1 -1
  110. data/synth/003/README.md +205 -133
  111. data/synth/003/agent.optimized.rb +66 -0
  112. data/synth/003/agent.synthesized.rb +41 -0
  113. metadata +111 -35
  114. data/docs/dsl/agent-reference.md +0 -604
  115. data/docs/dsl/mcp-integration.md +0 -1177
  116. data/docs/dsl/webhooks.md +0 -932
  117. data/docs/dsl/workflows.md +0 -744
  118. data/lib/language_operator/cli/commands/agent.rb +0 -1712
  119. data/lib/language_operator/cli/commands/model.rb +0 -366
  120. data/lib/language_operator/cli/commands/system.rb +0 -1259
  121. data/lib/language_operator/cli/commands/tool.rb +0 -654
  122. data/lib/language_operator/cli/formatters/optimization_formatter.rb +0 -226
  123. data/lib/language_operator/cli/helpers/pastel_helper.rb +0 -24
  124. data/lib/language_operator/learning/adapters/base_adapter.rb +0 -149
  125. data/lib/language_operator/learning/adapters/jaeger_adapter.rb +0 -221
  126. data/lib/language_operator/learning/adapters/signoz_adapter.rb +0 -435
  127. data/lib/language_operator/learning/adapters/tempo_adapter.rb +0 -239
  128. data/lib/language_operator/learning/optimizer.rb +0 -319
  129. data/lib/language_operator/learning/pattern_detector.rb +0 -260
  130. data/lib/language_operator/learning/task_synthesizer.rb +0 -288
  131. data/lib/language_operator/learning/trace_analyzer.rb +0 -285
  132. data/lib/language_operator/templates/task_synthesis.tmpl +0 -98
  133. data/lib/language_operator/ux/base.rb +0 -81
  134. data/lib/language_operator/ux/concerns/README.md +0 -155
  135. data/lib/language_operator/ux/concerns/headings.rb +0 -90
  136. data/lib/language_operator/ux/create_agent.rb +0 -255
  137. data/lib/language_operator/ux/create_model.rb +0 -267
  138. data/lib/language_operator/ux/quickstart.rb +0 -594
  139. data/synth/003/agent.rb +0 -41
  140. data/synth/003/output.log +0 -68
  141. /data/docs/{architecture/agent-runtime.md → agent-internals.md} +0 -0
  142. /data/docs/{dsl/chat-endpoints.md → chat-endpoints.md} +0 -0
  143. /data/docs/{dsl/SCHEMA_VERSION.md → schema-versioning.md} +0 -0
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LanguageOperator
4
+ module CLI
5
+ module Commands
6
+ module Agent
7
+ # Code viewing and editing for agents
8
+ module CodeOperations
9
+ def self.included(base)
10
+ base.class_eval do
11
+ desc 'code NAME', 'Display synthesized agent code'
12
+ option :cluster, type: :string, desc: 'Override current cluster context'
13
+ option :raw, type: :boolean, default: false, desc: 'Output raw code without formatting'
14
+ def code(name)
15
+ handle_command_error('get code') do
16
+ require_relative '../../formatters/code_formatter'
17
+
18
+ ctx = CLI::Helpers::ClusterContext.from_options(options)
19
+
20
+ # Get the code ConfigMap for this agent
21
+ configmap_name = "#{name}-code"
22
+ begin
23
+ configmap = ctx.client.get_resource('ConfigMap', configmap_name, ctx.namespace)
24
+ rescue K8s::Error::NotFound
25
+ Formatters::ProgressFormatter.error("Synthesized code not found for agent '#{name}'")
26
+ puts
27
+ puts 'Possible reasons:'
28
+ puts ' - Agent synthesis not yet complete'
29
+ puts ' - Agent synthesis failed'
30
+ puts
31
+ puts 'Check synthesis status with:'
32
+ puts " aictl agent inspect #{name}"
33
+ exit 1
34
+ end
35
+
36
+ # Get the agent.rb code from the ConfigMap
37
+ code_content = configmap.dig('data', 'agent.rb')
38
+ unless code_content
39
+ Formatters::ProgressFormatter.error('Code content not found in ConfigMap')
40
+ exit 1
41
+ end
42
+
43
+ # Raw output mode - just print the code
44
+ if options[:raw]
45
+ puts code_content
46
+ return
47
+ end
48
+
49
+ # Display with syntax highlighting
50
+ Formatters::CodeFormatter.display_ruby_code(
51
+ code_content,
52
+ title: "Synthesized Code for Agent: #{name}"
53
+ )
54
+ end
55
+ end
56
+
57
+ desc 'edit NAME', 'Edit agent instructions'
58
+ option :cluster, type: :string, desc: 'Override current cluster context'
59
+ def edit(name)
60
+ handle_command_error('edit agent') do
61
+ ctx = CLI::Helpers::ClusterContext.from_options(options)
62
+
63
+ # Get current agent
64
+ agent = get_resource_or_exit(LanguageOperator::Constants::RESOURCE_AGENT, name)
65
+
66
+ current_instructions = agent.dig('spec', 'instructions')
67
+
68
+ # Edit instructions in user's editor
69
+ new_instructions = Helpers::EditorHelper.edit_content(
70
+ current_instructions,
71
+ 'agent-instructions-',
72
+ '.txt'
73
+ ).strip
74
+
75
+ # Check if changed
76
+ if new_instructions == current_instructions
77
+ Formatters::ProgressFormatter.info('No changes made')
78
+ return
79
+ end
80
+
81
+ # Update agent resource
82
+ agent['spec']['instructions'] = new_instructions
83
+
84
+ Formatters::ProgressFormatter.with_spinner('Updating agent instructions') do
85
+ ctx.client.apply_resource(agent)
86
+ end
87
+
88
+ Formatters::ProgressFormatter.success('Agent instructions updated')
89
+ puts
90
+ puts 'The operator will automatically re-synthesize the agent code.'
91
+ puts
92
+ puts 'Watch synthesis progress with:'
93
+ puts " aictl agent inspect #{name}"
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require 'faraday'
5
+ require 'json'
6
+
7
+ module LanguageOperator
8
+ module CLI
9
+ module Commands
10
+ module Agent
11
+ module Helpers
12
+ # LLM client that uses port-forwarding to cluster model deployments (LiteLLM proxy)
13
+ class ClusterLLMClient
14
+ def initialize(ctx:, model_name:, model_id:, agent_command:)
15
+ @ctx = ctx
16
+ @model_name = model_name
17
+ @model_id = model_id
18
+ @agent_command = agent_command
19
+ end
20
+
21
+ def chat(prompt)
22
+ pod = get_model_pod
23
+ pod_name = pod.dig('metadata', 'name')
24
+
25
+ local_port = find_available_port
26
+ port_forward_pid = nil
27
+
28
+ begin
29
+ port_forward_pid = start_port_forward(pod_name, local_port, 4000)
30
+ wait_for_port(local_port)
31
+
32
+ conn = Faraday.new(url: "http://localhost:#{local_port}") do |f|
33
+ f.request :json
34
+ f.response :json
35
+ f.adapter Faraday.default_adapter
36
+ f.options.timeout = 120
37
+ f.options.open_timeout = 10
38
+ end
39
+
40
+ payload = {
41
+ model: @model_id,
42
+ messages: [{ role: 'user', content: prompt }],
43
+ max_tokens: 4000,
44
+ temperature: 0.3
45
+ }
46
+
47
+ response = conn.post('/v1/chat/completions', payload)
48
+ result = response.body
49
+
50
+ raise "LLM error: #{result['error']['message'] || result['error']}" if result['error']
51
+
52
+ result.dig('choices', 0, 'message', 'content')
53
+ ensure
54
+ cleanup_port_forward(port_forward_pid) if port_forward_pid
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def get_model_pod
61
+ # Get the deployment for the model
62
+ deployment = @ctx.client.get_resource('Deployment', @model_name, @ctx.namespace)
63
+ raise "Deployment '#{@model_name}' not found in namespace '#{@ctx.namespace}'" if deployment.nil?
64
+
65
+ labels = deployment.dig('spec', 'selector', 'matchLabels')
66
+
67
+ # Find matching pods using centralized utility
68
+ pods = CLI::Helpers::LabelUtils.find_pods_by_deployment_labels(@ctx, @model_name, labels)
69
+ raise "No pods found for model '#{@model_name}'" if pods.empty?
70
+
71
+ running_pods = pods.select { |p| p.dig('status', 'phase') == 'Running' }
72
+ raise "No running pods found for model '#{@model_name}'" if running_pods.empty?
73
+
74
+ running_pods.first
75
+ end
76
+
77
+ def find_available_port
78
+ server = TCPServer.new('127.0.0.1', 0)
79
+ port = server.addr[1]
80
+ server.close
81
+ port
82
+ end
83
+
84
+ def start_port_forward(pod_name, local_port, remote_port)
85
+ pid = spawn(
86
+ 'kubectl', 'port-forward',
87
+ '-n', @ctx.namespace,
88
+ "pod/#{pod_name}",
89
+ "#{local_port}:#{remote_port}",
90
+ %i[out err] => '/dev/null'
91
+ )
92
+ Process.detach(pid)
93
+ pid
94
+ end
95
+
96
+ def wait_for_port(port, max_attempts: 30)
97
+ max_attempts.times do
98
+ TCPSocket.new('127.0.0.1', port).close
99
+ return true
100
+ rescue Errno::ECONNREFUSED
101
+ sleep 0.1
102
+ end
103
+ raise "Port #{port} not available after #{max_attempts} attempts"
104
+ end
105
+
106
+ def cleanup_port_forward(pid)
107
+ Process.kill('TERM', pid)
108
+ rescue Errno::ESRCH
109
+ # Process already gone
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LanguageOperator
4
+ module CLI
5
+ module Commands
6
+ module Agent
7
+ module Helpers
8
+ # Helper methods for parsing agent code from ConfigMaps
9
+ module CodeParser
10
+ # Load agent definition from ConfigMap
11
+ #
12
+ # @param ctx [ClusterContext] Cluster context
13
+ # @param agent_name [String] Name of the agent
14
+ # @return [Object, nil] Agent definition or nil if not found
15
+ def load_agent_definition(ctx, agent_name)
16
+ # Try to get the agent code ConfigMap
17
+ configmap_name = "#{agent_name}-code"
18
+ begin
19
+ configmap = ctx.client.get_resource('ConfigMap', configmap_name, ctx.namespace)
20
+ code_content = configmap.dig('data', 'agent.rb')
21
+
22
+ return nil unless code_content
23
+
24
+ # Parse the code to extract agent definition
25
+ # For now, we'll create a mock definition with the task structure
26
+ # In a full implementation, this would eval the code safely
27
+ parse_agent_code(code_content)
28
+ rescue K8s::Error::NotFound
29
+ nil
30
+ rescue StandardError => e
31
+ @logger&.error("Failed to load agent definition: #{e.message}")
32
+ nil
33
+ end
34
+ end
35
+
36
+ # Parse agent code to extract definition
37
+ #
38
+ # @param code [String] Ruby agent code
39
+ # @return [Object] Agent definition structure
40
+ def parse_agent_code(code)
41
+ require_relative '../../../../dsl/agent_definition'
42
+
43
+ # Create a minimal agent definition structure
44
+ agent_def = Struct.new(:tasks, :name, :mcp_servers) do
45
+ def initialize
46
+ super({}, 'agent', {})
47
+ end
48
+ end
49
+
50
+ agent = agent_def.new
51
+
52
+ # Parse tasks from code - extract full task definitions
53
+ code.scan(/task\s+:(\w+),?\s*(.*?)(?=\n\s*(?:task\s+:|main\s+do|end\s*$))/m) do |match|
54
+ task_name = match[0].to_sym
55
+ task_block = match[1]
56
+
57
+ # Check if neural (has instructions but no do block) or symbolic
58
+ is_neural = task_block.include?('instructions:') && !task_block.match?(/\bdo\s*\|/)
59
+
60
+ # Extract instructions
61
+ instructions = extract_string_value(task_block, 'instructions')
62
+
63
+ # Extract inputs hash
64
+ inputs = extract_hash_value(task_block, 'inputs')
65
+
66
+ # Extract outputs hash
67
+ outputs = extract_hash_value(task_block, 'outputs')
68
+
69
+ task = Struct.new(:name, :neural?, :instructions, :inputs, :outputs).new(
70
+ task_name, is_neural, instructions, inputs, outputs
71
+ )
72
+
73
+ agent.tasks[task_name] = task
74
+ end
75
+
76
+ agent
77
+ end
78
+
79
+ # Extract a string value from DSL code (e.g., instructions: "...")
80
+ #
81
+ # @param code [String] Code snippet
82
+ # @param key [String] Key to extract
83
+ # @return [String] Extracted value or empty string
84
+ def extract_string_value(code, key)
85
+ # Match both single and double quoted strings, including multi-line
86
+ match = code.match(/#{key}:\s*(['"])(.*?)\1/m) ||
87
+ code.match(/#{key}:\s*(['"])(.+?)\1/m)
88
+ match ? match[2] : ''
89
+ end
90
+
91
+ # Extract a hash value from DSL code (e.g., inputs: { foo: 'bar' })
92
+ #
93
+ # @param code [String] Code snippet
94
+ # @param key [String] Key to extract
95
+ # @return [Hash] Extracted hash or empty hash
96
+ def extract_hash_value(code, key)
97
+ match = code.match(/#{key}:\s*\{([^}]*)\}/)
98
+ return {} unless match
99
+
100
+ hash_content = match[1].strip
101
+ return {} if hash_content.empty?
102
+
103
+ # Parse simple key: 'value' or key: "value" pairs
104
+ result = {}
105
+ hash_content.scan(/(\w+):\s*(['"])([^'"]*)\2/) do |k, _quote, v|
106
+ result[k.to_sym] = v
107
+ end
108
+ result
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LanguageOperator
4
+ module CLI
5
+ module Commands
6
+ module Agent
7
+ module Helpers
8
+ # Helper methods for watching agent synthesis status
9
+ module SynthesisWatcher
10
+ # Watch synthesis status with progress spinner
11
+ #
12
+ # @param k8s [Kubernetes::Client] Kubernetes client
13
+ # @param agent_name [String] Agent name
14
+ # @param namespace [String] Namespace
15
+ # @return [Hash] Synthesis result with success status and metadata
16
+ def watch_synthesis_status(k8s, agent_name, namespace)
17
+ max_wait = 600 # Wait up to 10 minutes (local models can be slow)
18
+ interval = 2 # Check every 2 seconds
19
+ elapsed = 0
20
+ start_time = Time.now
21
+ synthesis_data = {}
22
+
23
+ Formatters::ProgressFormatter.with_spinner('Synthesizing code from instructions') do
24
+ synthesis_result = nil
25
+ loop do
26
+ status = check_synthesis_status(k8s, agent_name, namespace, synthesis_data, start_time)
27
+ if status
28
+ synthesis_result = status
29
+ break
30
+ end
31
+
32
+ # Timeout check
33
+ if elapsed >= max_wait
34
+ Formatters::ProgressFormatter.warn('Synthesis taking longer than expected, continuing in background...')
35
+ puts
36
+ puts 'Check synthesis status with:'
37
+ puts " aictl agent inspect #{agent_name}"
38
+ synthesis_result = { success: true, timeout: true }
39
+ break
40
+ end
41
+
42
+ sleep interval
43
+ elapsed += interval
44
+ end
45
+ synthesis_result
46
+ rescue K8s::Error::NotFound
47
+ # Agent not found yet, keep waiting
48
+ sleep interval
49
+ elapsed += interval
50
+ retry if elapsed < max_wait
51
+
52
+ Formatters::ProgressFormatter.error('Agent resource not found')
53
+ return { success: false }
54
+ rescue StandardError => e
55
+ Formatters::ProgressFormatter.warn("Could not watch synthesis: #{e.message}")
56
+ return { success: true } # Continue anyway
57
+ end
58
+ end
59
+
60
+ # Check synthesis status for a specific agent
61
+ #
62
+ # @param k8s [Kubernetes::Client] Kubernetes client
63
+ # @param agent_name [String] Agent name
64
+ # @param namespace [String] Namespace
65
+ # @param synthesis_data [Hash] Hash to populate with synthesis metadata
66
+ # @param start_time [Time] Synthesis start time
67
+ # @return [Hash, nil] Synthesis status or nil if not complete
68
+ def check_synthesis_status(k8s, agent_name, namespace, synthesis_data, start_time)
69
+ agent = k8s.get_resource('LanguageAgent', agent_name, namespace)
70
+ conditions = agent.dig('status', 'conditions') || []
71
+ synthesis_status = agent.dig('status', 'synthesis')
72
+
73
+ # Capture synthesis metadata
74
+ if synthesis_status
75
+ synthesis_data[:model] = synthesis_status['model']
76
+ synthesis_data[:token_count] = synthesis_status['tokenCount']
77
+ end
78
+
79
+ # Check for synthesis completion
80
+ synthesized = conditions.find { |c| c['type'] == 'Synthesized' }
81
+ return nil unless synthesized
82
+
83
+ if synthesized['status'] == 'True'
84
+ duration = Time.now - start_time
85
+ { success: true, duration: duration, **synthesis_data }
86
+ elsif synthesized['status'] == 'False'
87
+ Formatters::ProgressFormatter.error("Synthesis failed: #{synthesized['message']}")
88
+ { success: false }
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end