kagi-api 0.0.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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/LICENSE.adoc +134 -0
  4. data/README.adoc +247 -0
  5. data/kagi-api.gemspec +37 -0
  6. data/lib/kagi/api/client.rb +33 -0
  7. data/lib/kagi/api/configuration/content.rb +12 -0
  8. data/lib/kagi/api/configuration/loader.rb +22 -0
  9. data/lib/kagi/api/container.rb +37 -0
  10. data/lib/kagi/api/contracts/error.rb +18 -0
  11. data/lib/kagi/api/contracts/fast.rb +18 -0
  12. data/lib/kagi/api/contracts/meta.rb +15 -0
  13. data/lib/kagi/api/contracts/reference.rb +14 -0
  14. data/lib/kagi/api/contracts/search.rb +27 -0
  15. data/lib/kagi/api/contracts/summary.rb +17 -0
  16. data/lib/kagi/api/dependencies.rb +9 -0
  17. data/lib/kagi/api/endpoints/container.rb +23 -0
  18. data/lib/kagi/api/endpoints/dependencies.rb +11 -0
  19. data/lib/kagi/api/endpoints/enrich/news.rb +55 -0
  20. data/lib/kagi/api/endpoints/enrich/web.rb +55 -0
  21. data/lib/kagi/api/endpoints/fast.rb +53 -0
  22. data/lib/kagi/api/endpoints/search.rb +53 -0
  23. data/lib/kagi/api/endpoints/summarize.rb +53 -0
  24. data/lib/kagi/api/models/content/error.rb +22 -0
  25. data/lib/kagi/api/models/content/fast.rb +16 -0
  26. data/lib/kagi/api/models/content/meta.rb +20 -0
  27. data/lib/kagi/api/models/content/reference.rb +12 -0
  28. data/lib/kagi/api/models/content/search.rb +35 -0
  29. data/lib/kagi/api/models/content/summary.rb +12 -0
  30. data/lib/kagi/api/models/content/thumbnail.rb +16 -0
  31. data/lib/kagi/api/models/error.rb +19 -0
  32. data/lib/kagi/api/models/fast.rb +19 -0
  33. data/lib/kagi/api/models/search.rb +19 -0
  34. data/lib/kagi/api/models/summary.rb +19 -0
  35. data/lib/kagi/api/requester.rb +31 -0
  36. data/lib/kagi/api.rb +24 -0
  37. data.tar.gz.sig +0 -0
  38. metadata +220 -0
  39. metadata.gz.sig +0 -0
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "containable"
4
+
5
+ module Kagi
6
+ module API
7
+ module Endpoints
8
+ # Registers all endpoints.
9
+ module Container
10
+ extend Containable
11
+
12
+ namespace :enrich do
13
+ register(:news) { Enrich::News.new }
14
+ register(:web) { Enrich::Web.new }
15
+ end
16
+
17
+ register(:fast) { Fast.new }
18
+ register(:search) { Search.new }
19
+ register(:summarize) { Summarize.new }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "infusible"
4
+
5
+ module Kagi
6
+ module API
7
+ module Endpoints
8
+ Dependencies = Infusible[Container]
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/monads"
4
+ require "pipeable"
5
+
6
+ module Kagi
7
+ module API
8
+ module Endpoints
9
+ module Enrich
10
+ # Handles news requests.
11
+ class News
12
+ include Kagi::API::Dependencies[
13
+ :requester,
14
+ contract: "contracts.search",
15
+ error_contract: "contracts.error",
16
+ model: "models.search",
17
+ error_model: "models.error"
18
+ ]
19
+
20
+ include Dry::Monads[:result]
21
+ include Pipeable
22
+
23
+ def call(**params)
24
+ result = requester.get("enrich/news", **params)
25
+
26
+ case result
27
+ in Success then success result
28
+ in Failure(response) then failure response
29
+ else Failure "Unable to parse HTTP response."
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def success result
36
+ pipe result,
37
+ try(:parse, catch: JSON::ParserError),
38
+ validate(contract, as: :to_h),
39
+ to(model, :for)
40
+ end
41
+
42
+ def failure response
43
+ pipe(
44
+ response,
45
+ try(:parse, catch: JSON::ParserError),
46
+ validate(error_contract, as: :to_h),
47
+ to(error_model, :for),
48
+ bind { Failure it }
49
+ )
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/monads"
4
+ require "pipeable"
5
+
6
+ module Kagi
7
+ module API
8
+ module Endpoints
9
+ module Enrich
10
+ # Handles web requests.
11
+ class Web
12
+ include Kagi::API::Dependencies[
13
+ :requester,
14
+ contract: "contracts.search",
15
+ error_contract: "contracts.error",
16
+ model: "models.search",
17
+ error_model: "models.error"
18
+ ]
19
+
20
+ include Dry::Monads[:result]
21
+ include Pipeable
22
+
23
+ def call(**params)
24
+ result = requester.get("enrich/web", **params)
25
+
26
+ case result
27
+ in Success then success result
28
+ in Failure(response) then failure response
29
+ else Failure "Unable to parse HTTP response."
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def success result
36
+ pipe result,
37
+ try(:parse, catch: JSON::ParserError),
38
+ validate(contract, as: :to_h),
39
+ to(model, :for)
40
+ end
41
+
42
+ def failure response
43
+ pipe(
44
+ response,
45
+ try(:parse, catch: JSON::ParserError),
46
+ validate(error_contract, as: :to_h),
47
+ to(error_model, :for),
48
+ bind { Failure it }
49
+ )
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/monads"
4
+ require "pipeable"
5
+
6
+ module Kagi
7
+ module API
8
+ module Endpoints
9
+ # Handles Fast GPT requests.
10
+ class Fast
11
+ include Kagi::API::Dependencies[
12
+ :requester,
13
+ contract: "contracts.fast",
14
+ error_contract: "contracts.error",
15
+ model: "models.fast",
16
+ error_model: "models.error"
17
+ ]
18
+
19
+ include Dry::Monads[:result]
20
+ include Pipeable
21
+
22
+ def call(**params)
23
+ result = requester.post("fastgpt", **params)
24
+
25
+ case result
26
+ in Success then success result
27
+ in Failure(response) then failure response
28
+ else Failure "Unable to parse HTTP response."
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def success result
35
+ pipe result,
36
+ try(:parse, catch: JSON::ParserError),
37
+ validate(contract, as: :to_h),
38
+ to(model, :for)
39
+ end
40
+
41
+ def failure response
42
+ pipe(
43
+ response,
44
+ try(:parse, catch: JSON::ParserError),
45
+ validate(error_contract, as: :to_h),
46
+ to(error_model, :for),
47
+ bind { Failure it }
48
+ )
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/monads"
4
+ require "pipeable"
5
+
6
+ module Kagi
7
+ module API
8
+ module Endpoints
9
+ # Handles search requests.
10
+ class Search
11
+ include Kagi::API::Dependencies[
12
+ :requester,
13
+ contract: "contracts.search",
14
+ error_contract: "contracts.error",
15
+ model: "models.search",
16
+ error_model: "models.error"
17
+ ]
18
+
19
+ include Dry::Monads[:result]
20
+ include Pipeable
21
+
22
+ def call(**params)
23
+ result = requester.get("search", **params)
24
+
25
+ case result
26
+ in Success then success result
27
+ in Failure(response) then failure response
28
+ else Failure "Unable to parse HTTP response."
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def success result
35
+ pipe result,
36
+ try(:parse, catch: JSON::ParserError),
37
+ validate(contract, as: :to_h),
38
+ to(model, :for)
39
+ end
40
+
41
+ def failure response
42
+ pipe(
43
+ response,
44
+ try(:parse, catch: JSON::ParserError),
45
+ validate(error_contract, as: :to_h),
46
+ to(error_model, :for),
47
+ bind { Failure it }
48
+ )
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/monads"
4
+ require "pipeable"
5
+
6
+ module Kagi
7
+ module API
8
+ module Endpoints
9
+ # Handles summarize requests.
10
+ class Summarize
11
+ include Kagi::API::Dependencies[
12
+ :requester,
13
+ contract: "contracts.summary",
14
+ error_contract: "contracts.error",
15
+ model: "models.summary",
16
+ error_model: "models.error"
17
+ ]
18
+
19
+ include Dry::Monads[:result]
20
+ include Pipeable
21
+
22
+ def call(**params)
23
+ result = requester.post("summarize", **params)
24
+
25
+ case result
26
+ in Success then success result
27
+ in Failure(response) then failure response
28
+ else Failure "Unable to parse HTTP response."
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def success result
35
+ pipe result,
36
+ try(:parse, catch: JSON::ParserError),
37
+ validate(contract, as: :to_h),
38
+ to(model, :for)
39
+ end
40
+
41
+ def failure response
42
+ pipe(
43
+ response,
44
+ try(:parse, catch: JSON::ParserError),
45
+ validate(error_contract, as: :to_h),
46
+ to(error_model, :for),
47
+ bind { Failure it }
48
+ )
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kagi
4
+ module API
5
+ module Models
6
+ module Content
7
+ ERROR_MAP = {msg: :message, ref: :reference}.freeze
8
+
9
+ # Models error data.
10
+ Error = Data.define :code, :message, :reference do
11
+ def self.for(key_map: ERROR_MAP, **attributes)
12
+ new(**attributes.transform_keys(key_map))
13
+ end
14
+
15
+ def initialize(reference: nil, **)
16
+ super
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kagi
4
+ module API
5
+ module Models
6
+ module Content
7
+ # Models fast data.
8
+ Fast = Data.define :output, :tokens, :references do
9
+ def self.for(**attributes)
10
+ new(**attributes, references: attributes[:references].map { Reference[**it] })
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kagi
4
+ module API
5
+ module Models
6
+ module Content
7
+ META_MAP = {ms: :duration, api_balance: :balance}.freeze
8
+
9
+ # Models meta data.
10
+ Meta = Data.define :id, :node, :duration, :balance do
11
+ def self.for(key_map: META_MAP, **attributes) = new(**attributes.transform_keys(key_map))
12
+
13
+ def initialize(balance: nil, **)
14
+ super
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kagi
4
+ module API
5
+ module Models
6
+ module Content
7
+ # Models reference data.
8
+ Reference = Data.define :title, :snippet, :url
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kagi
4
+ module API
5
+ module Models
6
+ module Content
7
+ SEARCH_MAP = {t: :type, published: :published_at}.freeze
8
+
9
+ # Models search data.
10
+ Search = Data.define :type, :rank, :title, :url, :snippet, :published_at, :thumbnail do
11
+ def self.for(key_map: SEARCH_MAP, **attributes)
12
+ new(
13
+ **attributes.transform_keys(key_map),
14
+ thumbnail: (Thumbnail[**attributes[:thumbnail]] if attributes.key? :thumbnail)
15
+ )
16
+ end
17
+
18
+ # rubocop:todo Metrics/ParameterLists
19
+ def initialize(
20
+ rank: nil,
21
+ title: nil,
22
+ url: nil,
23
+ snippet: nil,
24
+ published_at: nil,
25
+ thumbnail: nil,
26
+ **attributes
27
+ )
28
+ super
29
+ end
30
+ # rubocop:enable Metrics/ParameterLists
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kagi
4
+ module API
5
+ module Models
6
+ module Content
7
+ # Models summary data.
8
+ Summary = Data.define :output, :tokens
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kagi
4
+ module API
5
+ module Models
6
+ module Content
7
+ # Models thumbnail data.
8
+ Thumbnail = Data.define :url, :width, :height do
9
+ def initialize url:, width: nil, height: nil
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kagi
4
+ module API
5
+ module Models
6
+ # Models the API error.
7
+ Error = Data.define :meta, :error do
8
+ def self.for(**attributes)
9
+ new(
10
+ **attributes.merge!(
11
+ meta: Content::Meta.for(**attributes[:meta]),
12
+ error: attributes[:error].map { Content::Error.for(**it) }
13
+ )
14
+ )
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kagi
4
+ module API
5
+ module Models
6
+ # Models the fast payload.
7
+ Fast = Data.define :meta, :data do
8
+ def self.for(**attributes)
9
+ new(
10
+ **attributes.merge!(
11
+ meta: Content::Meta.for(**attributes[:meta]),
12
+ data: Content::Fast.for(**attributes[:data])
13
+ )
14
+ )
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kagi
4
+ module API
5
+ module Models
6
+ # Models the search payload.
7
+ Search = Data.define :meta, :data do
8
+ def self.for(**attributes)
9
+ new(
10
+ **attributes.merge!(
11
+ meta: Content::Meta.for(**attributes[:meta]),
12
+ data: attributes[:data].map { Content::Search.for(**it) }
13
+ )
14
+ )
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kagi
4
+ module API
5
+ module Models
6
+ # Models the API payload.
7
+ Summary = Data.define :meta, :data do
8
+ def self.for(**attributes)
9
+ new(
10
+ **attributes.merge!(
11
+ meta: Content::Meta.for(**attributes[:meta]),
12
+ data: Content::Summary[**attributes[:data]]
13
+ )
14
+ )
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kagi
4
+ module API
5
+ # The low-level object for making basic HTTP requests.
6
+ class Requester
7
+ include Dependencies[:settings, :http]
8
+ include Dry::Monads[:result]
9
+
10
+ def initialize(**)
11
+ super
12
+ yield settings if block_given?
13
+ end
14
+
15
+ def get(path, **params) = call(__method__, path, params:)
16
+
17
+ def post(path, **json) = call(__method__, path, json:)
18
+
19
+ private
20
+
21
+ attr_reader :settings
22
+
23
+ def call verb, path, **options
24
+ http.auth("Bot #{settings.token}")
25
+ .headers(settings.headers)
26
+ .public_send(verb, "#{settings.uri}/#{path}", options)
27
+ .then { |response| response.status.success? ? Success(response) : Failure(response) }
28
+ end
29
+ end
30
+ end
31
+ end
data/lib/kagi/api.rb ADDED
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/schema"
4
+ require "zeitwerk"
5
+
6
+ Dry::Schema.load_extensions :monads
7
+
8
+ Zeitwerk::Loader.new.then do |loader|
9
+ loader.inflector.inflect "api" => "API"
10
+ loader.tag = "kagi-api"
11
+ loader.push_dir "#{__dir__}/.."
12
+ loader.setup
13
+ end
14
+
15
+ module Kagi
16
+ # Main namespace.
17
+ module API
18
+ def self.loader registry = Zeitwerk::Registry
19
+ @loader ||= registry.loaders.find { |loader| loader.tag == "kagi-api" }
20
+ end
21
+
22
+ def self.new(&) = Client.new(&)
23
+ end
24
+ end
data.tar.gz.sig ADDED
Binary file