Class: Rubino::Tools::AttachFileTool

Inherits:
Base
  • Object
show all
Defined in:
lib/rubino/tools/attach_file_tool.rb

Overview

Hands a previously-written file to the surrounding UI as a downloadable artifact. The tool itself does not move bytes — it validates the path against the workspace and the file’s existence, then surfaces a structured artifact payload that the agent loop turns into an ARTIFACT_CREATED event. Downstream consumers (the web UI’s run job, the CLI) fetch the file separately via GET /v1/files.

Why a dedicated tool rather than inferring artifacts from write/edit tool calls: the model writes lots of intermediate files (helper scripts, scratch JSON, downloaded fixtures) that should NOT show up as user-facing downloads. An explicit attach_file call makes that decision intentional and reviewable.

Constant Summary collapse

DEFAULT_CONTENT_TYPE =
"application/octet-stream"
CONTENT_TYPES =

Minimal extension → MIME map. Anything not listed falls back to application/octet-stream; the browser will then decide based on filename. Add entries here only when a real run needs a specific type signalled (e.g. inline PDF preview).

{
  "pdf" => "application/pdf",
  "csv" => "text/csv",
  "txt" => "text/plain",
  "md" => "text/markdown",
  "json" => "application/json",
  "html" => "text/html",
  "htm" => "text/html",
  "xml" => "application/xml",
  "png" => "image/png",
  "jpg" => "image/jpeg",
  "jpeg" => "image/jpeg",
  "gif" => "image/gif",
  "svg" => "image/svg+xml",
  "zip" => "application/zip",
  "tar" => "application/x-tar",
  "gz" => "application/gzip",
  "docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  "pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
  "xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
}.freeze

Instance Attribute Summary

Attributes inherited from Base

#cancel_token, #read_tracker, #stream_chunk

Instance Method Summary collapse

Methods inherited from Base

#cancellation_requested?, #config_key, #emit_chunk, #risky?, #to_tool_definition, workspace_root, workspace_roots

Instance Method Details

#call(arguments) ⇒ Object



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/rubino/tools/attach_file_tool.rb', line 81

def call(arguments)
  file_path = (arguments["file_path"] || arguments[:file_path]).to_s
  return error("file_path is required") if file_path.empty?

  expanded = File.expand_path(file_path)
  return error("File not found: #{file_path}") unless File.exist?(expanded)
  return error("Not a regular file: #{file_path}") unless File.file?(expanded)
  return error("Path escapes the workspace: #{file_path}") unless within_workspace?(expanded)

  display = (arguments["filename"] || arguments[:filename]).to_s
  display = File.basename(expanded) if display.empty?

  size = File.size(expanded)
  artifact = {
    path: expanded,
    filename: display,
    content_type: content_type_for(expanded),
    byte_size: size
  }

  {
    output: "Attached #{display} (#{size} bytes) as a downloadable artifact.",
    metrics: "#{size} bytes",
    artifact: artifact
  }
end

#descriptionObject



52
53
54
55
56
57
58
# File 'lib/rubino/tools/attach_file_tool.rb', line 52

def description
  "Attach a previously-written file to the current turn as a downloadable artifact " \
    "for the user. Call this AFTER you have already created the file with write/edit/shell. " \
    "Pass the absolute or workspace-relative path. The tool does not copy or move the file — " \
    "it just registers it as a deliverable. Use for final user-facing outputs " \
    "(PDF, CSV, ZIP, reports) and not for intermediate helper scripts."
end

#input_schemaObject



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/rubino/tools/attach_file_tool.rb', line 60

def input_schema
  {
    type: "object",
    properties: {
      file_path: {
        type: "string",
        description: "Path to the file to attach. Must exist and live inside the workspace."
      },
      filename: {
        type: "string",
        description: "Optional display name; defaults to the basename of file_path."
      }
    },
    required: %w[file_path]
  }
end

#nameObject



48
49
50
# File 'lib/rubino/tools/attach_file_tool.rb', line 48

def name
  "attach_file"
end

#risk_levelObject



77
78
79
# File 'lib/rubino/tools/attach_file_tool.rb', line 77

def risk_level
  :low
end