tsikol 0.1.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +22 -0
- data/CONTRIBUTING.md +84 -0
- data/LICENSE +21 -0
- data/README.md +579 -0
- data/Rakefile +12 -0
- data/docs/README.md +69 -0
- data/docs/api/middleware.md +721 -0
- data/docs/api/prompt.md +858 -0
- data/docs/api/resource.md +651 -0
- data/docs/api/server.md +509 -0
- data/docs/api/test-helpers.md +591 -0
- data/docs/api/tool.md +527 -0
- data/docs/cookbook/authentication.md +651 -0
- data/docs/cookbook/caching.md +877 -0
- data/docs/cookbook/dynamic-tools.md +970 -0
- data/docs/cookbook/error-handling.md +887 -0
- data/docs/cookbook/logging.md +1044 -0
- data/docs/cookbook/rate-limiting.md +717 -0
- data/docs/examples/code-assistant.md +922 -0
- data/docs/examples/complete-server.md +726 -0
- data/docs/examples/database-manager.md +1198 -0
- data/docs/examples/devops-tools.md +1382 -0
- data/docs/examples/echo-server.md +501 -0
- data/docs/examples/weather-service.md +822 -0
- data/docs/guides/completion.md +472 -0
- data/docs/guides/getting-started.md +462 -0
- data/docs/guides/middleware.md +823 -0
- data/docs/guides/project-structure.md +434 -0
- data/docs/guides/prompts.md +920 -0
- data/docs/guides/resources.md +720 -0
- data/docs/guides/sampling.md +804 -0
- data/docs/guides/testing.md +863 -0
- data/docs/guides/tools.md +627 -0
- data/examples/README.md +92 -0
- data/examples/advanced_features.rb +129 -0
- data/examples/basic-migrated/app/prompts/weather_chat.rb +44 -0
- data/examples/basic-migrated/app/resources/weather_alerts.rb +18 -0
- data/examples/basic-migrated/app/tools/get_current_weather.rb +34 -0
- data/examples/basic-migrated/app/tools/get_forecast.rb +30 -0
- data/examples/basic-migrated/app/tools/get_weather_by_coords.rb +48 -0
- data/examples/basic-migrated/server.rb +25 -0
- data/examples/basic.rb +73 -0
- data/examples/full_featured.rb +175 -0
- data/examples/middleware_example.rb +112 -0
- data/examples/sampling_example.rb +104 -0
- data/examples/weather-service/app/prompts/weather/chat.rb +90 -0
- data/examples/weather-service/app/resources/weather/alerts.rb +59 -0
- data/examples/weather-service/app/tools/weather/get_current.rb +82 -0
- data/examples/weather-service/app/tools/weather/get_forecast.rb +90 -0
- data/examples/weather-service/server.rb +28 -0
- data/exe/tsikol +6 -0
- data/lib/tsikol/cli/templates/Gemfile.erb +10 -0
- data/lib/tsikol/cli/templates/README.md.erb +38 -0
- data/lib/tsikol/cli/templates/gitignore.erb +49 -0
- data/lib/tsikol/cli/templates/prompt.rb.erb +53 -0
- data/lib/tsikol/cli/templates/resource.rb.erb +29 -0
- data/lib/tsikol/cli/templates/server.rb.erb +24 -0
- data/lib/tsikol/cli/templates/tool.rb.erb +60 -0
- data/lib/tsikol/cli.rb +203 -0
- data/lib/tsikol/error_handler.rb +141 -0
- data/lib/tsikol/health.rb +198 -0
- data/lib/tsikol/http_transport.rb +72 -0
- data/lib/tsikol/lifecycle.rb +149 -0
- data/lib/tsikol/middleware.rb +168 -0
- data/lib/tsikol/prompt.rb +101 -0
- data/lib/tsikol/resource.rb +53 -0
- data/lib/tsikol/router.rb +190 -0
- data/lib/tsikol/server.rb +660 -0
- data/lib/tsikol/stdio_transport.rb +108 -0
- data/lib/tsikol/test_helpers.rb +261 -0
- data/lib/tsikol/tool.rb +111 -0
- data/lib/tsikol/version.rb +5 -0
- data/lib/tsikol.rb +72 -0
- metadata +219 -0
@@ -0,0 +1,129 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative '../lib/tsikol'
|
5
|
+
|
6
|
+
# Example showing advanced features: lifecycle hooks, health monitoring, error handling
|
7
|
+
|
8
|
+
Tsikol.server "advanced-server" do
|
9
|
+
# Configure middleware with error handling
|
10
|
+
use Tsikol::ValidationMiddleware
|
11
|
+
use Tsikol::LoggingMiddleware, logger: self
|
12
|
+
|
13
|
+
# Lifecycle hooks
|
14
|
+
before_start do
|
15
|
+
log :info, "Initializing server resources..."
|
16
|
+
# Initialize connections, load configs, etc.
|
17
|
+
@startup_time = Time.now
|
18
|
+
end
|
19
|
+
|
20
|
+
after_start do
|
21
|
+
log :info, "Server ready to accept requests"
|
22
|
+
log :info, "Health endpoint available at: resource health"
|
23
|
+
end
|
24
|
+
|
25
|
+
before_stop do
|
26
|
+
log :info, "Gracefully shutting down..."
|
27
|
+
# Close connections, save state, etc.
|
28
|
+
end
|
29
|
+
|
30
|
+
after_stop do
|
31
|
+
uptime = Time.now - @startup_time
|
32
|
+
log :info, "Server stopped after #{uptime.round(2)} seconds"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Tool-specific hooks
|
36
|
+
before_tool "critical_operation" do |params|
|
37
|
+
log :warning, "Executing critical operation", data: params
|
38
|
+
end
|
39
|
+
|
40
|
+
after_tool "critical_operation" do |params, result|
|
41
|
+
log :info, "Critical operation completed", data: { params: params, result: result }
|
42
|
+
end
|
43
|
+
|
44
|
+
# General tool hooks for metrics
|
45
|
+
before_tool do |tool_name, params|
|
46
|
+
log :debug, "Calling tool: #{tool_name}"
|
47
|
+
end
|
48
|
+
|
49
|
+
after_tool do |tool_name, params, result|
|
50
|
+
log :debug, "Tool #{tool_name} completed"
|
51
|
+
end
|
52
|
+
|
53
|
+
# Tools with different error scenarios
|
54
|
+
tool "safe_operation" do |input:|
|
55
|
+
"Processed: #{input}"
|
56
|
+
end
|
57
|
+
|
58
|
+
tool "risky_operation" do |should_fail: false|
|
59
|
+
raise "Simulated failure" if should_fail
|
60
|
+
"Operation successful"
|
61
|
+
end
|
62
|
+
|
63
|
+
tool "critical_operation" do |data:, validate: true|
|
64
|
+
if validate && data.nil?
|
65
|
+
raise Tsikol::ValidationError, "Data cannot be nil"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Simulate some work
|
69
|
+
sleep(0.1)
|
70
|
+
"Critical operation completed for: #{data}"
|
71
|
+
end
|
72
|
+
|
73
|
+
tool "slow_operation" do |timeout: 5|
|
74
|
+
log :info, "Starting slow operation", data: { timeout: timeout }
|
75
|
+
|
76
|
+
# Simulate work that might timeout
|
77
|
+
timeout.times do |i|
|
78
|
+
sleep(1)
|
79
|
+
log :debug, "Progress: #{i + 1}/#{timeout}"
|
80
|
+
end
|
81
|
+
|
82
|
+
"Completed after #{timeout} seconds"
|
83
|
+
end
|
84
|
+
|
85
|
+
# Tool that demonstrates circuit breaker
|
86
|
+
tool "flaky_service" do
|
87
|
+
# This would fail randomly to demonstrate circuit breaker
|
88
|
+
if rand > 0.7
|
89
|
+
raise "Service temporarily unavailable"
|
90
|
+
end
|
91
|
+
"Service responded successfully"
|
92
|
+
end
|
93
|
+
|
94
|
+
# Resource showing detailed metrics
|
95
|
+
resource "admin/metrics" do
|
96
|
+
{
|
97
|
+
server: {
|
98
|
+
name: @name,
|
99
|
+
version: @version,
|
100
|
+
uptime: Time.now - @startup_time,
|
101
|
+
health_status: health_status
|
102
|
+
},
|
103
|
+
metrics: @metrics.to_h,
|
104
|
+
error_handler: {
|
105
|
+
circuit_breakers: @error_handler.instance_variable_get(:@circuit_breakers).keys,
|
106
|
+
error_counts: @error_handler.instance_variable_get(:@error_counts)
|
107
|
+
}
|
108
|
+
}.to_json
|
109
|
+
end
|
110
|
+
|
111
|
+
# Resource for testing error scenarios
|
112
|
+
resource "test/error" do
|
113
|
+
raise "This is a test error"
|
114
|
+
end
|
115
|
+
|
116
|
+
# Prompt that tracks usage
|
117
|
+
prompt "analyze" do |data:, depth: "medium"|
|
118
|
+
@metrics.increment("prompts:analyze:usage")
|
119
|
+
"Analyze the following data with #{depth} depth analysis: #{data}"
|
120
|
+
end
|
121
|
+
|
122
|
+
# Enable all capabilities
|
123
|
+
logging true
|
124
|
+
completion true
|
125
|
+
sampling true
|
126
|
+
|
127
|
+
# Log server configuration
|
128
|
+
log :info, "Advanced server configured with lifecycle hooks and monitoring"
|
129
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class WeatherChat < Tsikol::Prompt
|
4
|
+
name "weather_chat"
|
5
|
+
description "Weather chat prompt"
|
6
|
+
|
7
|
+
argument :city do
|
8
|
+
type :string
|
9
|
+
required
|
10
|
+
description "City to ask about weather"
|
11
|
+
|
12
|
+
complete do |partial|
|
13
|
+
# List of cities for autocomplete
|
14
|
+
cities = [
|
15
|
+
"New York", "London", "Tokyo", "Paris", "Berlin",
|
16
|
+
"Sydney", "Toronto", "Mumbai", "Beijing", "Moscow",
|
17
|
+
"Los Angeles", "Chicago", "Houston", "Phoenix", "Philadelphia"
|
18
|
+
]
|
19
|
+
|
20
|
+
# Filter cities that start with the partial input (case-insensitive)
|
21
|
+
cities.select { |city| city.downcase.start_with?(partial.downcase) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_messages(city:)
|
26
|
+
[
|
27
|
+
{
|
28
|
+
role: "user",
|
29
|
+
content: {
|
30
|
+
type: "text",
|
31
|
+
text: "What's the weather like in #{city}? Please give me current conditions and a forecast."
|
32
|
+
}
|
33
|
+
}
|
34
|
+
]
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_server(server)
|
38
|
+
@server = server
|
39
|
+
|
40
|
+
define_singleton_method(:log) do |level, message, data: nil, logger: nil|
|
41
|
+
@server.log(level, message, data: data, logger: logger)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class WeatherAlerts < Tsikol::Resource
|
4
|
+
uri "weather/alerts"
|
5
|
+
description "Active weather alerts"
|
6
|
+
|
7
|
+
def read
|
8
|
+
"No active weather alerts"
|
9
|
+
end
|
10
|
+
|
11
|
+
def set_server(server)
|
12
|
+
@server = server
|
13
|
+
|
14
|
+
define_singleton_method(:log) do |level, message, data: nil, logger: nil|
|
15
|
+
@server.log(level, message, data: data, logger: logger)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class GetCurrentWeather < Tsikol::Tool
|
4
|
+
description "Get current weather for a location"
|
5
|
+
|
6
|
+
parameter :location do
|
7
|
+
type :string
|
8
|
+
required
|
9
|
+
description "Location to get weather for"
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute(location:)
|
13
|
+
log :info, "Getting weather for #{location}" if respond_to?(:log)
|
14
|
+
|
15
|
+
temps = { "New York" => 72, "London" => 61, "Tokyo" => 77 }
|
16
|
+
temp = temps[location] || 70
|
17
|
+
|
18
|
+
if temps[location]
|
19
|
+
log :debug, "Found temperature in cache", data: { location: location, temp: temp } if respond_to?(:log)
|
20
|
+
else
|
21
|
+
log :warning, "Location not found, using default", data: { location: location } if respond_to?(:log)
|
22
|
+
end
|
23
|
+
|
24
|
+
"Currently #{temp}°F in #{location}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_server(server)
|
28
|
+
@server = server
|
29
|
+
|
30
|
+
define_singleton_method(:log) do |level, message, data: nil, logger: nil|
|
31
|
+
@server.log(level, message, data: data, logger: logger)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class GetForecast < Tsikol::Tool
|
4
|
+
description "Get weather forecast for a location"
|
5
|
+
|
6
|
+
parameter :location do
|
7
|
+
type :string
|
8
|
+
required
|
9
|
+
description "Location to get forecast for"
|
10
|
+
end
|
11
|
+
|
12
|
+
parameter :days do
|
13
|
+
type :integer
|
14
|
+
optional
|
15
|
+
default 3
|
16
|
+
description "Number of days to forecast"
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute(location:, days: 3)
|
20
|
+
"#{days}-day forecast for #{location}: Mostly sunny"
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_server(server)
|
24
|
+
@server = server
|
25
|
+
|
26
|
+
define_singleton_method(:log) do |level, message, data: nil, logger: nil|
|
27
|
+
@server.log(level, message, data: data, logger: logger)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class GetWeatherByCoords < Tsikol::Tool
|
4
|
+
description "Get weather by coordinates"
|
5
|
+
|
6
|
+
parameter :latitude do
|
7
|
+
type :number
|
8
|
+
required
|
9
|
+
description "Latitude coordinate"
|
10
|
+
end
|
11
|
+
|
12
|
+
parameter :longitude do
|
13
|
+
type :number
|
14
|
+
required
|
15
|
+
description "Longitude coordinate"
|
16
|
+
end
|
17
|
+
|
18
|
+
parameter :location_name do
|
19
|
+
type :string
|
20
|
+
required
|
21
|
+
description "Name of the location"
|
22
|
+
|
23
|
+
complete do |partial|
|
24
|
+
# In a real app, this might reverse geocode based on lat/long
|
25
|
+
locations = [
|
26
|
+
"Central Park, NYC",
|
27
|
+
"Times Square, NYC",
|
28
|
+
"Brooklyn Bridge, NYC",
|
29
|
+
"Statue of Liberty, NYC",
|
30
|
+
"Empire State Building, NYC"
|
31
|
+
]
|
32
|
+
|
33
|
+
locations.select { |loc| loc.downcase.include?(partial.downcase) }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def execute(latitude:, longitude:, location_name:)
|
38
|
+
"Weather at #{location_name} (#{latitude}, #{longitude}): Sunny, 75°F"
|
39
|
+
end
|
40
|
+
|
41
|
+
def set_server(server)
|
42
|
+
@server = server
|
43
|
+
|
44
|
+
define_singleton_method(:log) do |level, message, data: nil, logger: nil|
|
45
|
+
@server.log(level, message, data: data, logger: logger)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative '../../lib/tsikol'
|
5
|
+
|
6
|
+
# Option 1: Direct class references (requires explicit require)
|
7
|
+
require_relative 'app/tools/get_current_weather'
|
8
|
+
require_relative 'app/tools/get_forecast'
|
9
|
+
require_relative 'app/tools/get_weather_by_coords'
|
10
|
+
require_relative 'app/resources/weather_alerts'
|
11
|
+
require_relative 'app/prompts/weather_chat'
|
12
|
+
|
13
|
+
Tsikol.start(name: "weather-service") do
|
14
|
+
# Direct class references
|
15
|
+
tool GetCurrentWeather
|
16
|
+
tool GetForecast
|
17
|
+
tool GetWeatherByCoords
|
18
|
+
resource WeatherAlerts
|
19
|
+
prompt WeatherChat
|
20
|
+
|
21
|
+
# Inline definition still works
|
22
|
+
resource "server/health" do
|
23
|
+
"Weather service is healthy"
|
24
|
+
end
|
25
|
+
end
|
data/examples/basic.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require_relative '../lib/tsikol'
|
2
|
+
|
3
|
+
# NOTE: This example uses the original DSL-style API.
|
4
|
+
# For the new Rails-like structure with separate files, see examples/basic-migrated/
|
5
|
+
# or examples/weather-service/ for a more complete example.
|
6
|
+
|
7
|
+
Tsikol.server "weather-service" do
|
8
|
+
# Declare server capabilities (optional - features auto-enable when used)
|
9
|
+
# capabilities do
|
10
|
+
# logging # Auto-enabled when using 'log'
|
11
|
+
# completion # Auto-enabled when using 'completion_for'
|
12
|
+
# sampling # Must be explicitly enabled
|
13
|
+
# end
|
14
|
+
|
15
|
+
tool "get_current_weather" do |location:|
|
16
|
+
log :info, "Getting weather for #{location}"
|
17
|
+
|
18
|
+
temps = { "New York" => 72, "London" => 61, "Tokyo" => 77 }
|
19
|
+
temp = temps[location] || 70
|
20
|
+
|
21
|
+
if temps[location]
|
22
|
+
log :debug, "Found temperature in cache", data: { location: location, temp: temp }
|
23
|
+
else
|
24
|
+
log :warning, "Location not found, using default", data: { location: location }
|
25
|
+
end
|
26
|
+
|
27
|
+
"Currently #{temp}°F in #{location}"
|
28
|
+
end
|
29
|
+
|
30
|
+
tool "get_forecast" do |location:, days: 3|
|
31
|
+
"#{days}-day forecast for #{location}: Mostly sunny"
|
32
|
+
end
|
33
|
+
|
34
|
+
resource "weather/alerts" do
|
35
|
+
"No active weather alerts"
|
36
|
+
end
|
37
|
+
|
38
|
+
prompt "weather_chat" do |city:|
|
39
|
+
"What's the weather like in #{city}? Please give me current conditions and a forecast."
|
40
|
+
end
|
41
|
+
|
42
|
+
# Define completions for the weather_chat prompt's city argument
|
43
|
+
completion_for "prompt", "weather_chat", "city" do |partial|
|
44
|
+
# List of cities for autocomplete
|
45
|
+
cities = [
|
46
|
+
"New York", "London", "Tokyo", "Paris", "Berlin",
|
47
|
+
"Sydney", "Toronto", "Mumbai", "Beijing", "Moscow",
|
48
|
+
"Los Angeles", "Chicago", "Houston", "Phoenix", "Philadelphia"
|
49
|
+
]
|
50
|
+
|
51
|
+
# Filter cities that start with the partial input (case-insensitive)
|
52
|
+
cities.select { |city| city.downcase.start_with?(partial.downcase) }
|
53
|
+
end
|
54
|
+
|
55
|
+
# Tool for demonstrating location autocomplete
|
56
|
+
tool "get_weather_by_coords" do |latitude:, longitude:, location_name:|
|
57
|
+
"Weather at #{location_name} (#{latitude}, #{longitude}): Sunny, 75°F"
|
58
|
+
end
|
59
|
+
|
60
|
+
# Completion for location names based on coordinates
|
61
|
+
completion_for "tool", "get_weather_by_coords", "location_name" do |partial|
|
62
|
+
# In a real app, this might reverse geocode based on lat/long
|
63
|
+
locations = [
|
64
|
+
"Central Park, NYC",
|
65
|
+
"Times Square, NYC",
|
66
|
+
"Brooklyn Bridge, NYC",
|
67
|
+
"Statue of Liberty, NYC",
|
68
|
+
"Empire State Building, NYC"
|
69
|
+
]
|
70
|
+
|
71
|
+
locations.select { |loc| loc.downcase.include?(partial.downcase) }
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative '../lib/tsikol'
|
5
|
+
|
6
|
+
# Full-featured example showing all Tsikol capabilities
|
7
|
+
|
8
|
+
# Custom middleware for API key validation
|
9
|
+
class ApiKeyMiddleware < Tsikol::Middleware
|
10
|
+
def initialize(app, valid_keys: [])
|
11
|
+
super(app)
|
12
|
+
@valid_keys = valid_keys
|
13
|
+
end
|
14
|
+
|
15
|
+
def before_request(message)
|
16
|
+
# Skip auth for certain methods
|
17
|
+
return message if %w[initialize tools/list].include?(message["method"])
|
18
|
+
|
19
|
+
api_key = message.dig("metadata", "api_key")
|
20
|
+
unless @valid_keys.include?(api_key)
|
21
|
+
raise "Invalid API key"
|
22
|
+
end
|
23
|
+
|
24
|
+
message
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Tsikol.start(name: "full-featured-server") do
|
29
|
+
# Configure middleware stack
|
30
|
+
use Tsikol::ValidationMiddleware
|
31
|
+
use Tsikol::LoggingMiddleware
|
32
|
+
use ApiKeyMiddleware, valid_keys: ["demo-key-123", "test-key-456"]
|
33
|
+
use Tsikol::RateLimitMiddleware, max_requests: 100, window: 60
|
34
|
+
|
35
|
+
# Enable all capabilities
|
36
|
+
logging true
|
37
|
+
completion true
|
38
|
+
|
39
|
+
# Configure sampling with AI assistance
|
40
|
+
on_sampling do |request|
|
41
|
+
messages = request[:messages]
|
42
|
+
system_prompt = request[:system_prompt] || "You are a helpful assistant."
|
43
|
+
|
44
|
+
# Log sampling request
|
45
|
+
log :info, "AI assistance requested", data: {
|
46
|
+
messages: messages.size,
|
47
|
+
system: system_prompt
|
48
|
+
}
|
49
|
+
|
50
|
+
# Simulate AI response (in production, MCP client handles this)
|
51
|
+
{
|
52
|
+
role: "assistant",
|
53
|
+
content: {
|
54
|
+
type: "text",
|
55
|
+
text: "AI response would be generated by the MCP client (e.g., Claude Code)"
|
56
|
+
}
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
# Advanced tool with parameter validation and completion
|
61
|
+
tool "data_processor" do |data:, format: "json", validate: true|
|
62
|
+
log :info, "Processing data", data: { format: format, validate: validate }
|
63
|
+
|
64
|
+
begin
|
65
|
+
case format
|
66
|
+
when "json"
|
67
|
+
parsed = JSON.parse(data)
|
68
|
+
"Processed #{parsed.keys.size} fields"
|
69
|
+
when "csv"
|
70
|
+
lines = data.lines.count
|
71
|
+
"Processed #{lines} CSV rows"
|
72
|
+
when "xml"
|
73
|
+
"XML processing not implemented (demo)"
|
74
|
+
else
|
75
|
+
raise "Unsupported format: #{format}"
|
76
|
+
end
|
77
|
+
rescue => e
|
78
|
+
log :error, "Processing failed", data: { error: e.message }
|
79
|
+
"Error: #{e.message}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Tool with completions
|
84
|
+
tool "query_database" do |table:, query_type: "select"|
|
85
|
+
"Executed #{query_type.upcase} on table: #{table}"
|
86
|
+
end
|
87
|
+
|
88
|
+
# Define completions for database tool
|
89
|
+
completion_for "tool", "query_database", "table" do |partial|
|
90
|
+
tables = ["users", "orders", "products", "categories", "reviews"]
|
91
|
+
tables.select { |t| t.start_with?(partial.downcase) }
|
92
|
+
end
|
93
|
+
|
94
|
+
completion_for "tool", "query_database", "query_type" do |partial|
|
95
|
+
types = ["select", "insert", "update", "delete", "count"]
|
96
|
+
types.select { |t| t.start_with?(partial.downcase) }
|
97
|
+
end
|
98
|
+
|
99
|
+
# Resource with dynamic content
|
100
|
+
resource "system/status" do
|
101
|
+
{
|
102
|
+
server: "full-featured-server",
|
103
|
+
version: "1.0.0",
|
104
|
+
uptime: Time.now - $server_start_time,
|
105
|
+
capabilities: {
|
106
|
+
logging: true,
|
107
|
+
completion: true,
|
108
|
+
sampling: true,
|
109
|
+
middleware: true
|
110
|
+
},
|
111
|
+
stats: {
|
112
|
+
requests_handled: rand(1000..5000),
|
113
|
+
active_connections: rand(1..10),
|
114
|
+
memory_usage: "#{rand(50..200)}MB"
|
115
|
+
}
|
116
|
+
}.to_json
|
117
|
+
end
|
118
|
+
|
119
|
+
# Advanced prompt with multiple arguments
|
120
|
+
prompt "code_review" do |language:, code:, focus: "general"|
|
121
|
+
prompt_text = "Please review this #{language} code"
|
122
|
+
prompt_text += " with focus on #{focus}" unless focus == "general"
|
123
|
+
prompt_text += ":\n\n```#{language}\n#{code}\n```"
|
124
|
+
prompt_text
|
125
|
+
end
|
126
|
+
|
127
|
+
# Prompt completions
|
128
|
+
completion_for "prompt", "code_review", "language" do |partial|
|
129
|
+
languages = ["ruby", "python", "javascript", "typescript", "go", "rust", "java"]
|
130
|
+
languages.select { |l| l.start_with?(partial.downcase) }
|
131
|
+
end
|
132
|
+
|
133
|
+
completion_for "prompt", "code_review", "focus" do |partial|
|
134
|
+
focuses = ["general", "performance", "security", "style", "bugs", "architecture"]
|
135
|
+
focuses.select { |f| f.include?(partial.downcase) }
|
136
|
+
end
|
137
|
+
|
138
|
+
# Tool that uses sampling for enhanced functionality
|
139
|
+
tool "generate_documentation" do |code:, style: "markdown"|
|
140
|
+
log :info, "Generating documentation for code"
|
141
|
+
|
142
|
+
# This would trigger sampling in a real implementation
|
143
|
+
<<~DOC
|
144
|
+
# Generated Documentation
|
145
|
+
|
146
|
+
This documentation would be AI-generated based on the provided code.
|
147
|
+
Style: #{style}
|
148
|
+
|
149
|
+
## Overview
|
150
|
+
[AI would analyze and document the code here]
|
151
|
+
|
152
|
+
## Usage
|
153
|
+
[AI would provide usage examples]
|
154
|
+
|
155
|
+
## API Reference
|
156
|
+
[AI would document the API]
|
157
|
+
DOC
|
158
|
+
end
|
159
|
+
|
160
|
+
# Resource showing middleware info
|
161
|
+
resource "debug/middleware" do
|
162
|
+
"Active middleware: Validation, Logging, ApiKey, RateLimit"
|
163
|
+
end
|
164
|
+
|
165
|
+
# Admin tool protected by middleware
|
166
|
+
tool "admin/reset_stats" do
|
167
|
+
log :warning, "Stats reset requested"
|
168
|
+
"Statistics reset successfully (requires valid API key)"
|
169
|
+
end
|
170
|
+
|
171
|
+
# Initialize server start time for uptime tracking
|
172
|
+
$server_start_time = Time.now
|
173
|
+
|
174
|
+
log :info, "Full-featured server started with all capabilities enabled"
|
175
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative '../lib/tsikol'
|
5
|
+
|
6
|
+
# Custom middleware example
|
7
|
+
class TimingMiddleware < Tsikol::Middleware
|
8
|
+
def before_request(message)
|
9
|
+
message["_start_time"] = Time.now
|
10
|
+
message
|
11
|
+
end
|
12
|
+
|
13
|
+
def after_response(response, original_message)
|
14
|
+
if original_message["_start_time"]
|
15
|
+
duration = Time.now - original_message["_start_time"]
|
16
|
+
puts "Request #{original_message['method']} took #{(duration * 1000).round(2)}ms"
|
17
|
+
end
|
18
|
+
response
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Audit middleware
|
23
|
+
class AuditMiddleware < Tsikol::Middleware
|
24
|
+
def initialize(app, audit_file: "audit.log")
|
25
|
+
super(app)
|
26
|
+
@audit_file = audit_file
|
27
|
+
end
|
28
|
+
|
29
|
+
def before_request(message)
|
30
|
+
File.open(@audit_file, "a") do |f|
|
31
|
+
f.puts "[#{Time.now}] REQUEST: #{message['method']} (id: #{message['id']})"
|
32
|
+
f.puts " Params: #{message['params'].inspect}" if message['params']
|
33
|
+
end
|
34
|
+
message
|
35
|
+
end
|
36
|
+
|
37
|
+
def after_response(response, original_message)
|
38
|
+
File.open(@audit_file, "a") do |f|
|
39
|
+
if response["error"]
|
40
|
+
f.puts "[#{Time.now}] ERROR: #{response['error']['message']}"
|
41
|
+
else
|
42
|
+
f.puts "[#{Time.now}] SUCCESS: #{original_message['method']}"
|
43
|
+
end
|
44
|
+
f.puts "-" * 50
|
45
|
+
end
|
46
|
+
response
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Create server with middleware
|
51
|
+
Tsikol.server "middleware-demo" do
|
52
|
+
# Add middleware
|
53
|
+
use Tsikol::LoggingMiddleware
|
54
|
+
use TimingMiddleware
|
55
|
+
use AuditMiddleware, audit_file: "server_audit.log"
|
56
|
+
use Tsikol::RateLimitMiddleware, max_requests: 10, window: 60
|
57
|
+
|
58
|
+
# Middleware for specific methods only
|
59
|
+
use Tsikol::AuthenticationMiddleware do |auth_info, message|
|
60
|
+
# Simple auth check - in production this would verify tokens, etc.
|
61
|
+
if message["method"] == "admin/shutdown"
|
62
|
+
auth_info["admin"] == true
|
63
|
+
else
|
64
|
+
true # Allow all other methods
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Regular tools
|
69
|
+
tool "echo" do |message:|
|
70
|
+
"Echo: #{message}"
|
71
|
+
end
|
72
|
+
|
73
|
+
tool "slow_operation" do |duration: 1|
|
74
|
+
sleep(duration.to_f)
|
75
|
+
"Operation completed after #{duration} seconds"
|
76
|
+
end
|
77
|
+
|
78
|
+
tool "error_prone" do |should_fail: false|
|
79
|
+
raise "Intentional error" if should_fail
|
80
|
+
"Success!"
|
81
|
+
end
|
82
|
+
|
83
|
+
# Admin tool (requires auth)
|
84
|
+
tool "admin/shutdown" do
|
85
|
+
"Server would shutdown (demo mode - not actually shutting down)"
|
86
|
+
end
|
87
|
+
|
88
|
+
# Resource showing middleware info
|
89
|
+
resource "middleware/stack" do
|
90
|
+
{
|
91
|
+
middlewares: [
|
92
|
+
"LoggingMiddleware - Logs all requests and responses",
|
93
|
+
"TimingMiddleware - Measures request duration",
|
94
|
+
"AuditMiddleware - Writes audit log to file",
|
95
|
+
"RateLimitMiddleware - Limits to 10 requests per minute",
|
96
|
+
"AuthenticationMiddleware - Protects admin endpoints"
|
97
|
+
],
|
98
|
+
benefits: [
|
99
|
+
"Cross-cutting concerns handled separately",
|
100
|
+
"Easy to add/remove functionality",
|
101
|
+
"Consistent request/response processing",
|
102
|
+
"Better debugging and monitoring"
|
103
|
+
]
|
104
|
+
}.to_json
|
105
|
+
end
|
106
|
+
|
107
|
+
# Enable logging for middleware demo
|
108
|
+
logging true
|
109
|
+
|
110
|
+
# Set log level to debug to see middleware logs
|
111
|
+
log :info, "Server started with middleware stack"
|
112
|
+
end
|