hindsight-ruby 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/LICENSE.txt +21 -0
- data/README.md +325 -0
- data/hindsight-ruby.gemspec +37 -0
- data/lib/hindsight/bank.rb +204 -0
- data/lib/hindsight/client.rb +296 -0
- data/lib/hindsight/errors.rb +36 -0
- data/lib/hindsight/option_validation.rb +255 -0
- data/lib/hindsight/resources/banks.rb +129 -0
- data/lib/hindsight/resources/base.rb +118 -0
- data/lib/hindsight/resources/chunks.rb +14 -0
- data/lib/hindsight/resources/config.rb +21 -0
- data/lib/hindsight/resources/directives.rb +58 -0
- data/lib/hindsight/resources/documents.rb +30 -0
- data/lib/hindsight/resources/entities.rb +27 -0
- data/lib/hindsight/resources/graph.rb +21 -0
- data/lib/hindsight/resources/memories.rb +30 -0
- data/lib/hindsight/resources/mental_models.rb +65 -0
- data/lib/hindsight/resources/observations.rb +16 -0
- data/lib/hindsight/resources/operations.rb +92 -0
- data/lib/hindsight/resources/tags.rb +18 -0
- data/lib/hindsight/types/fact.rb +76 -0
- data/lib/hindsight/types/operation_receipt.rb +63 -0
- data/lib/hindsight/types/operation_status.rb +57 -0
- data/lib/hindsight/types/payload.rb +33 -0
- data/lib/hindsight/types/recall_result.rb +63 -0
- data/lib/hindsight/types/reflection.rb +49 -0
- data/lib/hindsight/upload_normalizer.rb +142 -0
- data/lib/hindsight/version.rb +5 -0
- data/lib/hindsight-ruby.rb +3 -0
- data/lib/hindsight.rb +30 -0
- metadata +141 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
require_relative 'payload'
|
|
5
|
+
|
|
6
|
+
module Hindsight
|
|
7
|
+
module Types
|
|
8
|
+
class Fact
|
|
9
|
+
attr_reader :text, :type, :entities, :occurred_at, :context, :metadata, :raw
|
|
10
|
+
|
|
11
|
+
def initialize(text:, type: nil, entities: [], occurred_at: nil, context: nil, metadata: {}, raw: nil)
|
|
12
|
+
@text = text.to_s
|
|
13
|
+
@type = type&.to_sym
|
|
14
|
+
@entities = normalize_entities(entities)
|
|
15
|
+
@occurred_at = coerce_time(occurred_at)
|
|
16
|
+
@context = context
|
|
17
|
+
@metadata = metadata || {}
|
|
18
|
+
@raw = raw || {}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.from_api(payload)
|
|
22
|
+
raw = payload || {}
|
|
23
|
+
return new(text: raw.to_s, raw: {}) unless raw.is_a?(Hash)
|
|
24
|
+
|
|
25
|
+
value = Payload.stringify_keys(raw)
|
|
26
|
+
|
|
27
|
+
new(
|
|
28
|
+
text: value['text'] || value['content'],
|
|
29
|
+
type: value['type'] || value['kind'],
|
|
30
|
+
entities: value['entities'] || [],
|
|
31
|
+
occurred_at: value['occurred_at'],
|
|
32
|
+
context: value['context'],
|
|
33
|
+
metadata: value['metadata'] || {},
|
|
34
|
+
raw: value
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def inspect
|
|
39
|
+
truncated = text.length > 60 ? "#{text.slice(0, 60)}..." : text
|
|
40
|
+
"#<#{self.class} text=#{truncated.inspect} type=#{type.inspect} entities=#{entities.inspect}>"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def to_s
|
|
44
|
+
text
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_h
|
|
48
|
+
{
|
|
49
|
+
text: text,
|
|
50
|
+
type: type,
|
|
51
|
+
entities: entities,
|
|
52
|
+
occurred_at: occurred_at,
|
|
53
|
+
context: context,
|
|
54
|
+
metadata: metadata
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def coerce_time(value)
|
|
61
|
+
return value if value.is_a?(Time)
|
|
62
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
63
|
+
|
|
64
|
+
Time.parse(value.to_s)
|
|
65
|
+
rescue ArgumentError
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def normalize_entities(value)
|
|
70
|
+
Array(value).map do |entity|
|
|
71
|
+
entity.is_a?(Hash) ? Payload.stringify_keys(entity) : entity.to_s
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'payload'
|
|
4
|
+
require_relative '../errors'
|
|
5
|
+
|
|
6
|
+
module Hindsight
|
|
7
|
+
module Types
|
|
8
|
+
class OperationReceipt
|
|
9
|
+
attr_reader :operation_ids, :raw, :bank
|
|
10
|
+
|
|
11
|
+
def initialize(operation_ids:, raw: nil, bank: nil)
|
|
12
|
+
@operation_ids = Array(operation_ids).map(&:to_s)
|
|
13
|
+
@raw = raw || {}
|
|
14
|
+
@bank = bank
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def inspect
|
|
18
|
+
"#<#{self.class} operation_ids=#{operation_ids.inspect}>"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_s
|
|
22
|
+
operation_ids.join(', ')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.from_api(payload = nil, bank: nil)
|
|
26
|
+
body = Payload.stringify_keys(payload || {})
|
|
27
|
+
operation_ids = body['operation_ids'] || Array(body['operation_id']).compact
|
|
28
|
+
new(operation_ids: operation_ids, raw: body, bank: bank)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def submitted?
|
|
32
|
+
operation_ids.any?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def operation_id
|
|
36
|
+
operation_ids.first
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def fetch(bank: nil)
|
|
40
|
+
resolved_bank = resolve_bank(bank)
|
|
41
|
+
operation_ids.map { |id| resolved_bank.operations.get(id) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def wait(bank: nil, interval: 1.0, timeout: 120.0)
|
|
45
|
+
resolved_bank = resolve_bank(bank)
|
|
46
|
+
resolved_bank.operations.wait(operation_ids, interval: interval, timeout: timeout)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def to_h
|
|
50
|
+
{ operation_ids: operation_ids }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def resolve_bank(bank)
|
|
56
|
+
resolved_bank = bank || @bank
|
|
57
|
+
return resolved_bank if resolved_bank
|
|
58
|
+
|
|
59
|
+
raise ValidationError, 'bank is required when receipt was not created from a bank-scoped operation'
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'payload'
|
|
4
|
+
|
|
5
|
+
module Hindsight
|
|
6
|
+
module Types
|
|
7
|
+
class OperationStatus
|
|
8
|
+
TERMINAL_STATES = %i[completed failed cancelled error].freeze
|
|
9
|
+
SUCCESS_STATES = %i[completed].freeze
|
|
10
|
+
FAILURE_STATES = %i[failed cancelled error].freeze
|
|
11
|
+
|
|
12
|
+
attr_reader :id, :status, :error, :result, :raw
|
|
13
|
+
|
|
14
|
+
def initialize(id:, status:, error: nil, result: nil, raw: nil)
|
|
15
|
+
@id = id.to_s
|
|
16
|
+
@status = status.to_sym
|
|
17
|
+
@error = error
|
|
18
|
+
@result = result
|
|
19
|
+
@raw = raw || {}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def terminal?
|
|
23
|
+
TERMINAL_STATES.include?(status)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def successful?
|
|
27
|
+
SUCCESS_STATES.include?(status)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def failed?
|
|
31
|
+
FAILURE_STATES.include?(status)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def inspect
|
|
35
|
+
"#<#{self.class} id=#{id.inspect} status=#{status.inspect}>"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_s
|
|
39
|
+
"#{id}: #{status}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def to_h
|
|
43
|
+
{ id: id, status: status, error: error, result: result }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.from_api(payload = nil, operation_id: nil)
|
|
47
|
+
body = Payload.stringify_keys(payload || {})
|
|
48
|
+
id = operation_id || body['operation_id'] || body['id']
|
|
49
|
+
status = body['status'] || 'unknown'
|
|
50
|
+
error = body['error']
|
|
51
|
+
result = body['result']
|
|
52
|
+
|
|
53
|
+
new(id: id, status: status, error: error, result: result, raw: body)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../errors'
|
|
4
|
+
|
|
5
|
+
module Hindsight
|
|
6
|
+
module Types
|
|
7
|
+
module Payload
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def stringify_keys(value)
|
|
11
|
+
h = value || {}
|
|
12
|
+
raise ValidationError, 'payload must be a Hash' unless h.is_a?(Hash)
|
|
13
|
+
|
|
14
|
+
deep_stringify(h)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def deep_stringify(value)
|
|
18
|
+
case value
|
|
19
|
+
when Hash
|
|
20
|
+
value.each_with_object({}) do |(key, item), result|
|
|
21
|
+
result[key.to_s] = deep_stringify(item)
|
|
22
|
+
end
|
|
23
|
+
when Array
|
|
24
|
+
value.map { |item| deep_stringify(item) }
|
|
25
|
+
else
|
|
26
|
+
value
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private_class_method :deep_stringify
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'payload'
|
|
4
|
+
require_relative 'fact'
|
|
5
|
+
|
|
6
|
+
module Hindsight
|
|
7
|
+
module Types
|
|
8
|
+
class RecallResult
|
|
9
|
+
include Enumerable
|
|
10
|
+
|
|
11
|
+
attr_reader :facts, :token_count, :query, :budget, :raw
|
|
12
|
+
|
|
13
|
+
def initialize(facts:, token_count: nil, query: nil, budget: nil, raw: nil)
|
|
14
|
+
@facts = Array(facts)
|
|
15
|
+
@token_count = token_count
|
|
16
|
+
@query = query
|
|
17
|
+
@budget = budget&.to_sym
|
|
18
|
+
@raw = raw || {}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def inspect
|
|
22
|
+
parts = ["facts=#{facts.size}", ("token_count=#{token_count}" if token_count), "query=#{query.inspect}"]
|
|
23
|
+
"#<#{self.class} #{parts.compact.join(' ')}>"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_s
|
|
27
|
+
facts.map(&:to_s).join("\n")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def each(&block)
|
|
31
|
+
facts.each(&block)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def empty?
|
|
35
|
+
facts.empty?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_h
|
|
39
|
+
{ facts: facts.map(&:to_h), token_count: token_count, query: query, budget: budget }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.empty(query: nil, budget: nil)
|
|
43
|
+
new(facts: [], token_count: 0, query: query, budget: budget, raw: {})
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.from_api(payload = nil, query: nil, budget: nil)
|
|
47
|
+
body = Payload.stringify_keys(payload || {})
|
|
48
|
+
facts_payload = body['facts'] || body['memories'] || body['results'] || []
|
|
49
|
+
token_count = body['token_count'] || body.dig('usage', 'total_tokens')
|
|
50
|
+
|
|
51
|
+
facts = Array(facts_payload).map { |item| Fact.from_api(item) }
|
|
52
|
+
|
|
53
|
+
new(
|
|
54
|
+
facts: facts,
|
|
55
|
+
token_count: token_count,
|
|
56
|
+
query: query || body['query'],
|
|
57
|
+
budget: budget || body['budget'],
|
|
58
|
+
raw: body
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'payload'
|
|
4
|
+
require_relative 'fact'
|
|
5
|
+
|
|
6
|
+
module Hindsight
|
|
7
|
+
module Types
|
|
8
|
+
class Reflection
|
|
9
|
+
attr_reader :raw, :text, :data
|
|
10
|
+
|
|
11
|
+
def initialize(raw: nil, text: nil, based_on: [], data: nil)
|
|
12
|
+
@raw = raw || {}
|
|
13
|
+
@text = text
|
|
14
|
+
@raw_based_on = based_on
|
|
15
|
+
@data = data
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def based_on
|
|
19
|
+
@based_on ||= Array(@raw_based_on).map { |fact| Fact.from_api(fact) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.from_api(payload)
|
|
23
|
+
body = Payload.stringify_keys(payload || {})
|
|
24
|
+
text = body['text'] || body['answer'] || body['reflection']
|
|
25
|
+
based_on = body['based_on'] || body['facts'] || []
|
|
26
|
+
data = body['structured_output'] || body['data'] || body['json']
|
|
27
|
+
|
|
28
|
+
new(raw: body, text: text, based_on: based_on, data: data)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def inspect
|
|
32
|
+
truncated = text && text.length > 60 ? "#{text.slice(0, 60)}..." : text
|
|
33
|
+
"#<#{self.class} text=#{truncated.inspect} based_on=#{based_on.size}>"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def to_s
|
|
37
|
+
text.to_s
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def to_h
|
|
41
|
+
{ text: text, based_on: based_on.map(&:to_h), data: data }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def json
|
|
45
|
+
data
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
require_relative "errors"
|
|
5
|
+
|
|
6
|
+
module Hindsight
|
|
7
|
+
module UploadNormalizer
|
|
8
|
+
CONTENT_TYPES = {
|
|
9
|
+
".pdf" => "application/pdf",
|
|
10
|
+
".png" => "image/png",
|
|
11
|
+
".jpg" => "image/jpeg",
|
|
12
|
+
".jpeg" => "image/jpeg",
|
|
13
|
+
".gif" => "image/gif",
|
|
14
|
+
".webp" => "image/webp",
|
|
15
|
+
".mp3" => "audio/mpeg",
|
|
16
|
+
".wav" => "audio/wav",
|
|
17
|
+
".m4a" => "audio/mp4",
|
|
18
|
+
".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
19
|
+
".pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
20
|
+
".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
# Normalize file entries into upload hashes suitable for multipart upload.
|
|
26
|
+
# When +allowed_paths+ is set, path-based entries are restricted to those
|
|
27
|
+
# directories (symlinks are resolved via File.realpath before checking).
|
|
28
|
+
def normalize_uploads(files, allowed_paths: nil)
|
|
29
|
+
entries = (files.is_a?(Array) ? files : [files]).flatten.compact
|
|
30
|
+
raise ValidationError, "files must contain at least one entry" if entries.empty?
|
|
31
|
+
|
|
32
|
+
closers = []
|
|
33
|
+
begin
|
|
34
|
+
uploads = entries.map do |entry|
|
|
35
|
+
normalize_upload(entry, closers, allowed_paths: allowed_paths)
|
|
36
|
+
end
|
|
37
|
+
[uploads, closers]
|
|
38
|
+
rescue StandardError
|
|
39
|
+
closers.each do |io|
|
|
40
|
+
io.close unless io.closed?
|
|
41
|
+
rescue StandardError
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
raise
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def normalize_upload(entry, closers, allowed_paths: nil)
|
|
49
|
+
case entry
|
|
50
|
+
when String, Pathname
|
|
51
|
+
ensure_allowed_paths_present!(allowed_paths)
|
|
52
|
+
build_upload_from_path(entry, closers, allowed_paths: allowed_paths)
|
|
53
|
+
when Hash
|
|
54
|
+
build_upload_from_hash(entry)
|
|
55
|
+
else
|
|
56
|
+
raise ValidationError,
|
|
57
|
+
"Invalid file entry: #{entry.inspect}. Expected path string, Pathname, or upload hash"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def build_upload_from_path(path_like, closers, allowed_paths: nil)
|
|
62
|
+
path = File.realpath(path_like.to_s)
|
|
63
|
+
validate_path_allowed!(path, path_like, allowed_paths) if allowed_paths
|
|
64
|
+
raise ValidationError, "Path for retain_files is not a regular file: #{path_like.inspect}" unless File.file?(path)
|
|
65
|
+
raise ValidationError, "File not readable for retain_files: #{path_like.inspect}" unless File.readable?(path)
|
|
66
|
+
filename = sanitize_multipart_value(File.basename(path))
|
|
67
|
+
raise ValidationError, "Invalid filename for retain_files: #{path_like.inspect}" if filename.empty?
|
|
68
|
+
|
|
69
|
+
io = File.open(path, "rb")
|
|
70
|
+
closers << io
|
|
71
|
+
{
|
|
72
|
+
io: io,
|
|
73
|
+
filename: filename,
|
|
74
|
+
content_type: content_type_for_filename(path)
|
|
75
|
+
}
|
|
76
|
+
rescue Errno::ENOENT
|
|
77
|
+
raise ValidationError, "File not found for retain_files: #{path_like.inspect}"
|
|
78
|
+
rescue Errno::EACCES
|
|
79
|
+
raise ValidationError, "File not readable for retain_files: #{path_like.inspect}"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def build_upload_from_hash(entry)
|
|
83
|
+
io = entry[:io] || entry["io"]
|
|
84
|
+
filename = entry[:filename] || entry["filename"]
|
|
85
|
+
content_type = entry[:content_type] || entry["content_type"] || content_type_for_filename(filename)
|
|
86
|
+
|
|
87
|
+
raise ValidationError, "Invalid upload hash: missing :io" unless io.respond_to?(:read)
|
|
88
|
+
raise ValidationError, "Invalid upload hash: missing :filename" if filename.to_s.strip.empty?
|
|
89
|
+
|
|
90
|
+
sanitized_filename = sanitize_multipart_value(filename)
|
|
91
|
+
raise ValidationError, "Invalid upload hash: empty :filename after sanitization" if sanitized_filename.empty?
|
|
92
|
+
|
|
93
|
+
sanitized_content_type = sanitize_multipart_value(content_type)
|
|
94
|
+
raise ValidationError, "Invalid upload hash: empty :content_type after sanitization" if sanitized_content_type.empty?
|
|
95
|
+
|
|
96
|
+
{
|
|
97
|
+
io: io,
|
|
98
|
+
filename: sanitized_filename,
|
|
99
|
+
content_type: sanitized_content_type
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def ensure_allowed_paths_present!(allowed_paths)
|
|
104
|
+
return unless allowed_paths.nil?
|
|
105
|
+
|
|
106
|
+
raise ValidationError,
|
|
107
|
+
"Path uploads require allowed_paths to be set. " \
|
|
108
|
+
"Use upload hashes ({io:, filename:, content_type:}) when you intentionally need unrestricted IO."
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def validate_path_allowed!(resolved, original, allowed_paths)
|
|
112
|
+
dirs = normalize_allowed_paths(allowed_paths)
|
|
113
|
+
return if dirs.any? { |dir| resolved.start_with?("#{dir}/") || resolved == dir }
|
|
114
|
+
|
|
115
|
+
raise ValidationError,
|
|
116
|
+
"Path #{original.inspect} resolves to #{resolved.inspect} which is outside allowed_paths"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def normalize_allowed_paths(allowed_paths)
|
|
120
|
+
Array(allowed_paths).map.with_index do |dir, index|
|
|
121
|
+
path = dir.to_s.strip
|
|
122
|
+
raise ValidationError, "allowed_paths[#{index}] must be a non-empty path" if path.empty?
|
|
123
|
+
|
|
124
|
+
begin
|
|
125
|
+
File.realpath(path)
|
|
126
|
+
rescue Errno::ENOENT
|
|
127
|
+
File.expand_path(path)
|
|
128
|
+
rescue ArgumentError, SystemCallError
|
|
129
|
+
raise ValidationError, "Invalid allowed_paths[#{index}]: #{dir.inspect}"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def sanitize_multipart_value(value)
|
|
135
|
+
value.to_s.gsub(/[\r\n\0"]/, "").strip
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def content_type_for_filename(filename)
|
|
139
|
+
CONTENT_TYPES.fetch(File.extname(filename.to_s).downcase, "application/octet-stream")
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
data/lib/hindsight.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'hindsight/version'
|
|
4
|
+
require_relative 'hindsight/errors'
|
|
5
|
+
require_relative 'hindsight/option_validation'
|
|
6
|
+
require_relative 'hindsight/client'
|
|
7
|
+
require_relative 'hindsight/resources/base'
|
|
8
|
+
require_relative 'hindsight/resources/banks'
|
|
9
|
+
require_relative 'hindsight/resources/memories'
|
|
10
|
+
require_relative 'hindsight/resources/mental_models'
|
|
11
|
+
require_relative 'hindsight/resources/directives'
|
|
12
|
+
require_relative 'hindsight/resources/chunks'
|
|
13
|
+
require_relative 'hindsight/resources/documents'
|
|
14
|
+
require_relative 'hindsight/resources/entities'
|
|
15
|
+
require_relative 'hindsight/resources/operations'
|
|
16
|
+
require_relative 'hindsight/resources/observations'
|
|
17
|
+
require_relative 'hindsight/resources/tags'
|
|
18
|
+
require_relative 'hindsight/resources/config'
|
|
19
|
+
require_relative 'hindsight/resources/graph'
|
|
20
|
+
require_relative 'hindsight/upload_normalizer'
|
|
21
|
+
require_relative 'hindsight/bank'
|
|
22
|
+
require_relative 'hindsight/types/payload'
|
|
23
|
+
require_relative 'hindsight/types/fact'
|
|
24
|
+
require_relative 'hindsight/types/recall_result'
|
|
25
|
+
require_relative 'hindsight/types/reflection'
|
|
26
|
+
require_relative 'hindsight/types/operation_receipt'
|
|
27
|
+
require_relative 'hindsight/types/operation_status'
|
|
28
|
+
|
|
29
|
+
module Hindsight
|
|
30
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: hindsight-ruby
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Andrey Samsonov
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: faraday
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: faraday-multipart
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rake
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '13.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '13.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rspec
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '3.13'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '3.13'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: webmock
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '3.0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '3.0'
|
|
82
|
+
description: Framework-agnostic ruby client for Hindsight APIs.
|
|
83
|
+
executables: []
|
|
84
|
+
extensions: []
|
|
85
|
+
extra_rdoc_files: []
|
|
86
|
+
files:
|
|
87
|
+
- LICENSE.txt
|
|
88
|
+
- README.md
|
|
89
|
+
- hindsight-ruby.gemspec
|
|
90
|
+
- lib/hindsight-ruby.rb
|
|
91
|
+
- lib/hindsight.rb
|
|
92
|
+
- lib/hindsight/bank.rb
|
|
93
|
+
- lib/hindsight/client.rb
|
|
94
|
+
- lib/hindsight/errors.rb
|
|
95
|
+
- lib/hindsight/option_validation.rb
|
|
96
|
+
- lib/hindsight/resources/banks.rb
|
|
97
|
+
- lib/hindsight/resources/base.rb
|
|
98
|
+
- lib/hindsight/resources/chunks.rb
|
|
99
|
+
- lib/hindsight/resources/config.rb
|
|
100
|
+
- lib/hindsight/resources/directives.rb
|
|
101
|
+
- lib/hindsight/resources/documents.rb
|
|
102
|
+
- lib/hindsight/resources/entities.rb
|
|
103
|
+
- lib/hindsight/resources/graph.rb
|
|
104
|
+
- lib/hindsight/resources/memories.rb
|
|
105
|
+
- lib/hindsight/resources/mental_models.rb
|
|
106
|
+
- lib/hindsight/resources/observations.rb
|
|
107
|
+
- lib/hindsight/resources/operations.rb
|
|
108
|
+
- lib/hindsight/resources/tags.rb
|
|
109
|
+
- lib/hindsight/types/fact.rb
|
|
110
|
+
- lib/hindsight/types/operation_receipt.rb
|
|
111
|
+
- lib/hindsight/types/operation_status.rb
|
|
112
|
+
- lib/hindsight/types/payload.rb
|
|
113
|
+
- lib/hindsight/types/recall_result.rb
|
|
114
|
+
- lib/hindsight/types/reflection.rb
|
|
115
|
+
- lib/hindsight/upload_normalizer.rb
|
|
116
|
+
- lib/hindsight/version.rb
|
|
117
|
+
homepage: https://github.com/kryzhovnik/hindsight-ruby
|
|
118
|
+
licenses:
|
|
119
|
+
- MIT
|
|
120
|
+
metadata:
|
|
121
|
+
source_code_uri: https://github.com/kryzhovnik/hindsight-ruby
|
|
122
|
+
bug_tracker_uri: https://github.com/kryzhovnik/hindsight-ruby/issues
|
|
123
|
+
changelog_uri: https://github.com/kryzhovnik/hindsight-ruby/releases
|
|
124
|
+
rdoc_options: []
|
|
125
|
+
require_paths:
|
|
126
|
+
- lib
|
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '3.1'
|
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
133
|
+
requirements:
|
|
134
|
+
- - ">="
|
|
135
|
+
- !ruby/object:Gem::Version
|
|
136
|
+
version: '0'
|
|
137
|
+
requirements: []
|
|
138
|
+
rubygems_version: 4.0.3
|
|
139
|
+
specification_version: 4
|
|
140
|
+
summary: Standalone Hindsight API client for Ruby
|
|
141
|
+
test_files: []
|