multiwoven-integrations 0.40.1 → 0.41.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/multiwoven/integrations/config.rb +4 -1
  3. data/lib/multiwoven/integrations/core/base_connector.rb +33 -6
  4. data/lib/multiwoven/integrations/core/constants.rb +1 -0
  5. data/lib/multiwoven/integrations/core/open_router_catalog.rb +212 -0
  6. data/lib/multiwoven/integrations/protocol/protocol.json +54 -0
  7. data/lib/multiwoven/integrations/protocol/protocol.rb +39 -1
  8. data/lib/multiwoven/integrations/rollout.rb +3 -1
  9. data/lib/multiwoven/integrations/service.rb +17 -8
  10. data/lib/multiwoven/integrations/source/aisquared/config/meta.json +8 -2
  11. data/lib/multiwoven/integrations/source/aisquared/config/models.json +120 -0
  12. data/lib/multiwoven/integrations/source/aisquared/config/spec.json +6 -1
  13. data/lib/multiwoven/integrations/source/anthropic/config/meta.json +9 -2
  14. data/lib/multiwoven/integrations/source/anthropic/config/models.json +4 -0
  15. data/lib/multiwoven/integrations/source/anthropic/config/spec.json +7 -1
  16. data/lib/multiwoven/integrations/source/aws_bedrock_model/config/meta.json +8 -2
  17. data/lib/multiwoven/integrations/source/aws_bedrock_model/config/models.json +96 -0
  18. data/lib/multiwoven/integrations/source/aws_sagemaker_model/config/meta.json +8 -2
  19. data/lib/multiwoven/integrations/source/databrics_model/config/meta.json +20 -15
  20. data/lib/multiwoven/integrations/source/generic_open_ai/client.rb +15 -8
  21. data/lib/multiwoven/integrations/source/generic_open_ai/config/meta.json +8 -1
  22. data/lib/multiwoven/integrations/source/google_gemini/client.rb +18 -0
  23. data/lib/multiwoven/integrations/source/google_gemini/config/catalog.json +6 -0
  24. data/lib/multiwoven/integrations/source/google_gemini/config/meta.json +23 -0
  25. data/lib/multiwoven/integrations/source/google_gemini/config/models.json +26 -0
  26. data/lib/multiwoven/integrations/source/google_gemini/config/spec.json +71 -0
  27. data/lib/multiwoven/integrations/source/google_gemini/icon.svg +10 -0
  28. data/lib/multiwoven/integrations/source/google_vertex_model/config/meta.json +7 -2
  29. data/lib/multiwoven/integrations/source/http_model/config/meta.json +7 -1
  30. data/lib/multiwoven/integrations/source/john_snow_labs/client.rb +18 -0
  31. data/lib/multiwoven/integrations/source/john_snow_labs/config/catalog.json +6 -0
  32. data/lib/multiwoven/integrations/source/john_snow_labs/config/meta.json +24 -0
  33. data/lib/multiwoven/integrations/source/john_snow_labs/config/models.json +119 -0
  34. data/lib/multiwoven/integrations/source/john_snow_labs/config/spec.json +71 -0
  35. data/lib/multiwoven/integrations/source/john_snow_labs/icon.svg +16 -0
  36. data/lib/multiwoven/integrations/source/open_ai/config/meta.json +9 -2
  37. data/lib/multiwoven/integrations/source/open_ai/config/models.json +26 -0
  38. data/lib/multiwoven/integrations/source/open_ai/config/spec.json +7 -1
  39. data/lib/multiwoven/integrations/source/watsonx_ai/config/meta.json +8 -2
  40. data/lib/multiwoven/integrations/source/watsonx_ai/config/models.json +73 -0
  41. data/lib/multiwoven/integrations.rb +4 -0
  42. metadata +20 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d02ac40ce23d00833bf9387f1363d3f871c0abdfb500c235e3d44a37110c9d8f
4
- data.tar.gz: e76e2c81bf09fc64962b2639addb8d11ff7d9ad37a38c88b1267e0778d77bd95
3
+ metadata.gz: ad58beae1f92f76c184383a3e8c1741ee51356e6f3692bfae35ec73be5f9e351
4
+ data.tar.gz: 3b8e0bd9360c53a532157172c2e70d0a38d53a3c8aa9f26d11af35b99082f912
5
5
  SHA512:
6
- metadata.gz: 2bf4882833f8c7e2f823b0077b3a4402d78f5c6acbdee0dc068063dd0be1310b0005c3f73305f26389e4bbf34d17ed6011aa6251852dfc0b17a7956a2928bba2
7
- data.tar.gz: d460e9bd2b1e2b26fff0c1dd33148952c5f8f1d241cb208bc4da5c4cefbdd9e33b16e6e90ecc449c8e1ddd047b2a40c718dcb51f20aa0b7a9e4d61e21f301229
6
+ metadata.gz: 317c2605f975ec6726413bb1d26cb8716f5c4150c515bb666f26ec3a7265f4a905eddef60d739effc07d492dc9238bb2c1ecef9358118921b3e88e52a90f58c9
7
+ data.tar.gz: ca2256921a2839449fee7e9a97d3c76a6ea54474df87f67124c8f5e31acc58df4077f34c7762e0525c66ea2fcc2536adcb21887eea41f0b0d7c949d7bfc479eb
@@ -3,11 +3,14 @@
3
3
  module Multiwoven
4
4
  module Integrations
5
5
  class Config
6
- attr_accessor :logger, :exception_reporter
6
+ # cache: optional host store responding to read(key), write(key, value,
7
+ # expires_in:) and delete(key). Without one, caching is a per-process memo.
8
+ attr_accessor :logger, :exception_reporter, :cache
7
9
 
8
10
  def initialize(params = {})
9
11
  @logger = params[:logger]
10
12
  @exception_reporter = params[:exception_reporter]
13
+ @cache = params[:cache]
11
14
  end
12
15
  end
13
16
  end
@@ -8,20 +8,47 @@ module Multiwoven
8
8
  include Constants
9
9
 
10
10
  def connector_spec
11
- @connector_spec ||= begin
12
- spec_json = keys_to_symbols(read_json(CONNECTOR_SPEC_PATH)).to_json
13
- # returns Protocol::ConnectorSpecification
14
- ConnectorSpecification.from_json(spec_json)
15
- end
11
+ @connector_spec ||= ConnectorSpecification.from_json(keys_to_symbols(read_json(CONNECTOR_SPEC_PATH)).to_json)
16
12
  end
17
13
 
18
14
  def meta_data
15
+ return @meta_data if @meta_data
16
+
19
17
  client_meta_data = read_json(META_DATA_PATH).deep_symbolize_keys
20
18
  icon_name = client_meta_data[:data][:icon]
21
19
  icon_url = "https://raw.githubusercontent.com/Multiwoven/multiwoven/main/integrations#{relative_path}/#{icon_name}"
22
20
  client_meta_data[:data][:icon] = icon_url
23
21
  # returns hash
24
- @meta_data ||= client_meta_data
22
+ @meta_data = client_meta_data
23
+ end
24
+
25
+ def model_catalog
26
+ @model_catalog ||= ModelCatalog.new(models: (live_models + curated_models).uniq { |model| model[:id] })
27
+ rescue Dry::Struct::Error => e
28
+ Integrations::Service.logger.error("#{self.class}: unusable model catalog: #{e.message}")
29
+ @model_catalog = ModelCatalog.new(models: [])
30
+ end
31
+
32
+ # Symbolized to match models from upstream; read_json hands back string keys,
33
+ # which leaves callers reading nil.
34
+ def curated_models
35
+ @curated_models ||= Array(read_json(MODELS_SPEC_PATH)["models"]).map(&:deep_symbolize_keys)
36
+ rescue Errno::ENOENT
37
+ @curated_models = []
38
+ rescue StandardError => e
39
+ Integrations::Service.logger.error("#{self.class}: unusable models.json: #{e.message}")
40
+ @curated_models = []
41
+ end
42
+
43
+ def live_models
44
+ slug = meta_data[:data][:openrouter_slug]
45
+ return [] if slug.blank?
46
+
47
+ overrides = meta_data[:data][:model_id_overrides] || {}
48
+ Integrations::Core::OpenRouterCatalog.fetch.models_for(slug).map do |model|
49
+ pinned = overrides[model[:id].to_sym] || overrides[model[:id].to_s]
50
+ pinned.present? ? model.merge(id: pinned) : model
51
+ end
25
52
  end
26
53
 
27
54
  def relative_path
@@ -7,6 +7,7 @@ module Multiwoven
7
7
  META_DATA_PATH = "config/meta.json"
8
8
  CONNECTOR_SPEC_PATH = "config/spec.json"
9
9
  CATALOG_SPEC_PATH = "config/catalog.json"
10
+ MODELS_SPEC_PATH = "config/models.json"
10
11
  SNOWFLAKE_MAC_DRIVER_PATH = "/opt/snowflake/snowflakeodbc/lib/universal/libSnowflake.dylib"
11
12
  DATABRICKS_MAC_DRIVER_PATH = "/Library/simba/spark/lib/libsparkodbc_sb64-universal.dylib"
12
13
 
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Multiwoven
4
+ module Integrations::Core
5
+ # Live model metadata from OpenRouter's public /models endpoint, so a connector
6
+ # need not ship prices that go stale. Providers opt in with an openrouter_slug;
7
+ # embeddings and partner-hosted models stay curated in models.json.
8
+ class OpenRouterCatalog
9
+ MODELS_URL = "https://openrouter.ai/api/v1/models"
10
+ CACHE_KEY = "multiwoven_open_router_catalog_v1"
11
+ CACHE_TTL = 86_400
12
+ REQUEST_CONFIG = { timeout: 10, open_timeout: 5 }.freeze
13
+ PER_MILLION = 1_000_000
14
+
15
+ Result = Struct.new(:models_by_slug, :fetched_at, :source, keyword_init: true) do
16
+ def models_for(slug)
17
+ return [] if slug.blank?
18
+
19
+ models_by_slug.fetch(slug.to_s.downcase, [])
20
+ end
21
+ end
22
+
23
+ class << self
24
+ # Two layers: a host cache may be absent or a null store, so the memo is
25
+ # what prevents a re-fetch of half a megabyte per request.
26
+ def fetch
27
+ return @memo if live_memo?
28
+
29
+ result = to_result(cached_payload)
30
+ @memo = result if result.source == "live"
31
+ result
32
+ end
33
+
34
+ def reset!
35
+ @memo = nil
36
+ cache&.delete(CACHE_KEY)
37
+ rescue StandardError => e
38
+ log_cache_failure("delete", e)
39
+ end
40
+
41
+ private
42
+
43
+ def cache
44
+ Integrations::Service.config.cache
45
+ end
46
+
47
+ def live_memo?
48
+ @memo&.source == "live" && @memo.fetched_at && @memo.fetched_at > Time.now - CACHE_TTL
49
+ end
50
+
51
+ def cached_payload
52
+ store = cache
53
+ return build_payload if store.nil?
54
+
55
+ cached = read_cache(store)
56
+ return cached if live_payload?(cached)
57
+
58
+ payload = build_payload
59
+ write_cache(store, payload) if live_payload?(payload)
60
+ payload
61
+ end
62
+
63
+ # A failing host cache degrades to a direct fetch rather than taking the
64
+ # whole catalog down with it.
65
+ def read_cache(store)
66
+ store.read(CACHE_KEY)
67
+ rescue StandardError => e
68
+ log_cache_failure("read", e)
69
+ nil
70
+ end
71
+
72
+ def write_cache(store, payload)
73
+ store.write(CACHE_KEY, payload, expires_in: CACHE_TTL)
74
+ rescue StandardError => e
75
+ log_cache_failure("write", e)
76
+ end
77
+
78
+ def log_cache_failure(operation, error)
79
+ Integrations::Service.logger.error(
80
+ "OpenRouterCatalog: cache #{operation} failed: #{error.class}: #{error.message}"
81
+ )
82
+ end
83
+
84
+ def live_payload?(payload)
85
+ payload.present? && payload.with_indifferent_access[:source] == "live"
86
+ end
87
+
88
+ # Slugs stay strings because models_for looks them up that way; the model
89
+ # hashes are symbolized because every reader indexes them with symbols.
90
+ def symbolize_models(by_slug)
91
+ (by_slug || {}).each_with_object({}) do |(slug, models), acc|
92
+ acc[slug.to_s.downcase] = Array(models).map(&:deep_symbolize_keys)
93
+ end
94
+ end
95
+
96
+ def to_result(payload)
97
+ payload = payload.transform_keys(&:to_sym)
98
+ Result.new(
99
+ models_by_slug: symbolize_models(payload[:models_by_slug]),
100
+ fetched_at: payload[:fetched_at],
101
+ source: payload[:source]
102
+ )
103
+ end
104
+
105
+ def build_payload
106
+ response = Integrations::Core::HttpClient.request(
107
+ MODELS_URL, "GET", options: { config: REQUEST_CONFIG }
108
+ )
109
+ body = JSON.parse(response.body)
110
+ raise "OpenRouter returned no data" unless body["data"].is_a?(Array)
111
+
112
+ { models_by_slug: group_by_slug(body["data"]), fetched_at: Time.now, source: "live" }
113
+ rescue StandardError => e
114
+ # Fail soft: callers still see curated models rather than an exception.
115
+ Integrations::Service.logger.error("OpenRouterCatalog: #{e.class}: #{e.message}")
116
+ { models_by_slug: {}, fetched_at: Time.now, source: "unavailable" }
117
+ end
118
+
119
+ def group_by_slug(raw_models)
120
+ grouped = raw_models.each_with_object({}) do |raw, acc|
121
+ slug, = raw["id"].to_s.split("/", 2)
122
+ next if slug.blank?
123
+
124
+ (acc[slug.downcase] ||= []) << normalize(raw, slug.downcase)
125
+ end
126
+ grouped.transform_values { |models| sort_models(models) }
127
+ end
128
+
129
+ # Newest first: a live list has no curation.
130
+ def sort_models(models)
131
+ models.sort_by { |model| [-model[:released_at].to_i, model[:name].to_s.downcase] }
132
+ .each { |model| model.delete(:released_at) }
133
+ end
134
+
135
+ def normalize(raw, slug)
136
+ architecture = raw["architecture"] || {}
137
+ inputs = Array(architecture["input_modalities"])
138
+ outputs = Array(architecture["output_modalities"])
139
+ pricing = raw["pricing"] || {}
140
+
141
+ {
142
+ id: strip_slug(raw["id"]),
143
+ openrouter_id: raw["id"],
144
+ name: display_name(raw, slug),
145
+ model_type: outputs.include?("image") ? "vision" : "llm",
146
+ type: outputs.include?("image") ? "image" : "completion",
147
+ tasks: derive_tasks(raw, outputs),
148
+ capabilities: derive_capabilities(raw, inputs, pricing),
149
+ context_window: raw["context_length"]&.to_i,
150
+ max_output: raw.dig("top_provider", "max_completion_tokens")&.to_i,
151
+ pricing: {
152
+ input: per_million(pricing["prompt"]),
153
+ output: per_million(pricing["completion"]),
154
+ cached_read: per_million(pricing["input_cache_read"]),
155
+ cached_write: per_million(pricing["input_cache_write"]),
156
+ unit: "per_1m_tokens"
157
+ },
158
+ availability: "available",
159
+ unavailable_reason: nil,
160
+ released_at: raw["created"]
161
+ }
162
+ end
163
+
164
+ # Ids are "<provider-slug>/<model-slug>", and the slug is OpenRouter's own
165
+ # normalization — a provider may pin the real one.
166
+ def strip_slug(id)
167
+ id.to_s.split("/", 2).last.to_s
168
+ end
169
+
170
+ # Names are prefixed with the vendor ("OpenAI: GPT-4o") and the UI already
171
+ # shows it. Drop it only when the prefix really is the provider.
172
+ def display_name(raw, slug)
173
+ name = raw["name"].presence || strip_slug(raw["id"])
174
+ prefix, remainder = name.split(":", 2)
175
+ return name if remainder.blank?
176
+ return name unless prefix.to_s.downcase.gsub(/[^a-z0-9]/, "") == slug.gsub(/[^a-z0-9]/, "")
177
+
178
+ remainder.strip
179
+ end
180
+
181
+ def derive_tasks(raw, outputs)
182
+ tasks = []
183
+ tasks << "Text Generation" if outputs.include?("text")
184
+ tasks << "Text-to-Image" if outputs.include?("image")
185
+ params = Array(raw["supported_parameters"])
186
+ tasks << "Reasoning" if params.include?("reasoning") || params.include?("include_reasoning")
187
+ tasks.uniq
188
+ end
189
+
190
+ def derive_capabilities(raw, inputs, pricing)
191
+ params = Array(raw["supported_parameters"])
192
+ capabilities = []
193
+ capabilities << "Image analysis" if inputs.include?("image")
194
+ capabilities << "Documents" if inputs.include?("file")
195
+ capabilities << "Audio" if inputs.include?("audio")
196
+ capabilities << "Web search" if pricing["web_search"].present?
197
+ capabilities << "Tool use" if params.include?("tools")
198
+ capabilities << "Structured output" if params.include?("structured_outputs")
199
+ capabilities
200
+ end
201
+
202
+ # Quoted per token as strings; the contract is per million. A genuine zero
203
+ # stays 0.0, a missing field stays nil so the UI renders "—" not "$0".
204
+ def per_million(value)
205
+ return nil if value.nil? || value.to_s.strip.empty?
206
+
207
+ (value.to_f * PER_MILLION).round(4)
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
@@ -155,6 +155,60 @@
155
155
  },
156
156
  "additionalProperties": true
157
157
  },
158
+ "ModelCatalogType": {
159
+ "type": "string",
160
+ "enum": ["llm", "embedding", "vision"]
161
+ },
162
+ "ModelCatalogKind": {
163
+ "type": "string",
164
+ "enum": ["completion", "embedding", "image"]
165
+ },
166
+ "ModelCatalogAvailability": {
167
+ "type": "string",
168
+ "enum": ["available", "unavailable"]
169
+ },
170
+ "ModelCatalogPricing": {
171
+ "type": "object",
172
+ "properties": {
173
+ "input": { "type": ["number", "null"] },
174
+ "output": { "type": ["number", "null"] },
175
+ "cached_read": { "type": ["number", "null"] },
176
+ "cached_write": { "type": ["number", "null"] },
177
+ "unit": { "type": ["string", "null"], "default": "per_1m_tokens" }
178
+ },
179
+ "additionalProperties": true
180
+ },
181
+ "ModelCatalogEntry": {
182
+ "type": "object",
183
+ "properties": {
184
+ "id": { "type": "string" },
185
+ "name": { "type": "string" },
186
+ "model_type": { "$ref": "#/definitions/ModelCatalogType" },
187
+ "type": { "anyOf": [{ "$ref": "#/definitions/ModelCatalogKind" }, { "type": "null" }] },
188
+ "openrouter_id": { "type": ["string", "null"] },
189
+ "tasks": { "type": "array", "items": { "type": "string" } },
190
+ "capabilities": { "type": "array", "items": { "type": "string" } },
191
+ "context_window": { "type": ["integer", "null"] },
192
+ "max_output": { "type": ["integer", "null"] },
193
+ "pricing": { "anyOf": [{ "$ref": "#/definitions/ModelCatalogPricing" }, { "type": "null" }] },
194
+ "availability": { "$ref": "#/definitions/ModelCatalogAvailability" },
195
+ "unavailable_reason": { "type": ["string", "null"] }
196
+ },
197
+ "required": ["id", "name", "model_type"],
198
+ "additionalProperties": true
199
+ },
200
+ "ModelCatalog": {
201
+ "type": "object",
202
+ "properties": {
203
+ "models": {
204
+ "type": "array",
205
+ "items": {
206
+ "$ref": "#/definitions/ModelCatalogEntry"
207
+ }
208
+ }
209
+ },
210
+ "additionalProperties": true
211
+ },
158
212
  "SyncConfig": {
159
213
  "type": "object",
160
214
  "properties": {
@@ -18,8 +18,11 @@ module Multiwoven
18
18
  MultiwovenMessageType = Types::String.enum(
19
19
  "record", "log", "connector_spec",
20
20
  "connection_status", "catalog", "control",
21
- "tracking"
21
+ "tracking", "model_catalog"
22
22
  )
23
+ ModelCatalogType = Types::String.enum("llm", "embedding", "vision")
24
+ ModelCatalogKind = Types::String.enum("completion", "embedding", "image")
25
+ ModelCatalogAvailability = Types::String.default("available").enum("available", "unavailable")
23
26
  ControlMessageType = Types::String.enum(
24
27
  "rate_limit", "connection_config", "full_refresh"
25
28
  )
@@ -166,6 +169,40 @@ module Multiwoven
166
169
  end
167
170
  end
168
171
 
172
+ class ModelCatalogPricing < ProtocolModel
173
+ attribute? :input, Types::Coercible::Float.optional
174
+ attribute? :output, Types::Coercible::Float.optional
175
+ attribute? :cached_read, Types::Coercible::Float.optional
176
+ attribute? :cached_write, Types::Coercible::Float.optional
177
+ attribute? :unit, Types::String.optional.default("per_1m_tokens")
178
+ end
179
+
180
+ class ModelCatalogEntry < ProtocolModel
181
+ attribute :id, Types::String
182
+ attribute :name, Types::String
183
+ attribute :model_type, ModelCatalogType
184
+ attribute? :type, ModelCatalogKind.optional
185
+ attribute? :openrouter_id, Types::String.optional
186
+ attribute :tasks, Types::Array.of(Types::String).default([].freeze)
187
+ attribute :capabilities, Types::Array.of(Types::String).default([].freeze)
188
+ attribute? :context_window, Types::Integer.optional
189
+ attribute? :max_output, Types::Integer.optional
190
+ attribute? :pricing, ModelCatalogPricing.optional
191
+ attribute :availability, ModelCatalogAvailability
192
+ attribute? :unavailable_reason, Types::String.optional
193
+ end
194
+
195
+ class ModelCatalog < ProtocolModel
196
+ attribute :models, Types::Array.of(ModelCatalogEntry).default([].freeze)
197
+
198
+ def to_multiwoven_message
199
+ MultiwovenMessage.new(
200
+ type: MultiwovenMessageType["model_catalog"],
201
+ model_catalog: self
202
+ )
203
+ end
204
+ end
205
+
169
206
  class IncrementStrategyConfig < ProtocolModel
170
207
  attr_accessor :offset, :limit, :offset_variable, :limit_variable
171
208
 
@@ -230,6 +267,7 @@ module Multiwoven
230
267
  attribute? :connection_status, ConnectionStatus.optional
231
268
  attribute? :connector_spec, ConnectorSpecification.optional
232
269
  attribute? :catalog, Catalog.optional
270
+ attribute? :model_catalog, ModelCatalog.optional
233
271
  attribute? :record, RecordMessage.optional
234
272
  attribute? :control, ControlMessage.optional
235
273
  attribute? :tracking, TrackingMessage.optional
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Multiwoven
4
4
  module Integrations
5
- VERSION = "0.40.1"
5
+ VERSION = "0.41.0"
6
6
 
7
7
  ENABLED_SOURCES = %w[
8
8
  Snowflake
@@ -27,6 +27,8 @@ module Multiwoven
27
27
  Anthropic
28
28
  AwsBedrockModel
29
29
  GenericOpenAI
30
+ GoogleGemini
31
+ JohnSnowLabs
30
32
  IntuitQuickBooks
31
33
  PineconeDB
32
34
  Qdrant
@@ -7,15 +7,17 @@ module Multiwoven
7
7
  yield(self.class.config) if block_given?
8
8
  end
9
9
  class << self
10
+ # deep_dup, not dup: the entries are the clients' own memoized metadata, so a
11
+ # shallow copy lets one caller's mutation reach every later caller.
10
12
  def connectors
11
- {
12
- source: build_connectors(
13
- ENABLED_SOURCES, "Source"
14
- ),
15
- destination: build_connectors(
16
- ENABLED_DESTINATIONS, "Destination"
17
- )
18
- }
13
+ built = built_connectors
14
+ { source: built[:source].deep_dup, destination: built[:destination].deep_dup }
15
+ end
16
+
17
+ # The build is memoized for the life of the process, so anything that
18
+ # changes what would be built has to clear it.
19
+ def reset_connectors!
20
+ @built_connectors = nil
19
21
  end
20
22
 
21
23
  def connector_class(connector_type, connector_name)
@@ -38,6 +40,13 @@ module Multiwoven
38
40
 
39
41
  private
40
42
 
43
+ def built_connectors
44
+ @built_connectors ||= {
45
+ source: build_connectors(ENABLED_SOURCES, "Source"),
46
+ destination: build_connectors(ENABLED_DESTINATIONS, "Destination")
47
+ }
48
+ end
49
+
41
50
  def build_connectors(enabled_connectors, type)
42
51
  enabled_connectors.map do |connector|
43
52
  client = connector_class(type, connector).new
@@ -1,16 +1,22 @@
1
1
  {
2
2
  "data": {
3
3
  "name": "Aisquared",
4
- "title": "AI Squared Bolt",
4
+ "title": "AISquared",
5
5
  "connector_type": "source",
6
6
  "category": "AI Model",
7
7
  "sub_category": "LLM",
8
+ "provider_type": "built_in",
9
+ "display_order": 10,
10
+ "description": "AISquared's own model portfolio \u2014 Bolt Instruct, Vision and Embedding models. Pre-configured with no setup required.",
8
11
  "documentation_url": "https://docs.squared.ai/activation/ai-ml-sources/aisquared-bolt",
9
12
  "github_issue_label": "source-aisquared-bolt",
10
13
  "icon": "icon.svg",
11
14
  "license": "MIT",
12
15
  "release_stage": "alpha",
13
16
  "support_level": "community",
14
- "tags": ["language:ruby", "multiwoven"]
17
+ "tags": [
18
+ "language:ruby",
19
+ "multiwoven"
20
+ ]
15
21
  }
16
22
  }
@@ -0,0 +1,120 @@
1
+ {
2
+ "models": [
3
+ {
4
+ "id": "bolt-instruct-1b",
5
+ "name": "Bolt-Instruct-1B",
6
+ "model_type": "llm",
7
+ "type": "completion",
8
+ "tasks": [
9
+ "Text Classification",
10
+ "Guardrails"
11
+ ],
12
+ "context_window": 32000,
13
+ "max_output": 4096,
14
+ "pricing": {
15
+ "input": 0.0,
16
+ "output": 0.0,
17
+ "cached_read": 0.0,
18
+ "cached_write": 0.0,
19
+ "unit": "per_1m_tokens"
20
+ },
21
+ "capabilities": [
22
+ "Documents"
23
+ ]
24
+ },
25
+ {
26
+ "id": "bolt-instruct-7b",
27
+ "name": "Bolt-Instruct-7B",
28
+ "model_type": "llm",
29
+ "type": "completion",
30
+ "tasks": [
31
+ "Text Generation",
32
+ "Routing",
33
+ "Orchestration"
34
+ ],
35
+ "context_window": 32000,
36
+ "max_output": 8192,
37
+ "pricing": {
38
+ "input": 0.0,
39
+ "output": 0.0,
40
+ "cached_read": 0.0,
41
+ "cached_write": 0.0,
42
+ "unit": "per_1m_tokens"
43
+ },
44
+ "capabilities": [
45
+ "Documents",
46
+ "Data analyst"
47
+ ]
48
+ },
49
+ {
50
+ "id": "bolt-instruct-32b",
51
+ "name": "Bolt-Instruct-32B",
52
+ "model_type": "llm",
53
+ "type": "completion",
54
+ "tasks": [
55
+ "Text Generation",
56
+ "Reasoning"
57
+ ],
58
+ "context_window": 128000,
59
+ "max_output": 16384,
60
+ "pricing": {
61
+ "input": 0.0,
62
+ "output": 0.0,
63
+ "cached_read": 0.0,
64
+ "cached_write": 0.0,
65
+ "unit": "per_1m_tokens"
66
+ },
67
+ "capabilities": [
68
+ "Documents",
69
+ "Data analyst"
70
+ ]
71
+ },
72
+ {
73
+ "id": "bolt-embedding-large",
74
+ "name": "Bolt-Embedding-Large",
75
+ "model_type": "embedding",
76
+ "type": "embedding",
77
+ "tasks": [
78
+ "Text Embeddings",
79
+ "Indexing",
80
+ "Retrieval"
81
+ ],
82
+ "context_window": 8192,
83
+ "max_output": null,
84
+ "pricing": {
85
+ "input": 0.0,
86
+ "output": null,
87
+ "cached_read": null,
88
+ "cached_write": null,
89
+ "unit": "per_1m_tokens"
90
+ },
91
+ "capabilities": [
92
+ "Documents"
93
+ ]
94
+ },
95
+ {
96
+ "id": "bolt-vision-9b",
97
+ "name": "Bolt-Vision-9B",
98
+ "model_type": "vision",
99
+ "type": "completion",
100
+ "tasks": [
101
+ "Image-to-Text",
102
+ "OCR",
103
+ "Document Parsing"
104
+ ],
105
+ "context_window": 32000,
106
+ "max_output": 8192,
107
+ "pricing": {
108
+ "input": 0.0,
109
+ "output": 0.0,
110
+ "cached_read": 0.0,
111
+ "cached_write": 0.0,
112
+ "unit": "per_1m_tokens"
113
+ },
114
+ "capabilities": [
115
+ "Documents",
116
+ "Image analysis"
117
+ ]
118
+ }
119
+ ]
120
+ }
@@ -6,7 +6,12 @@
6
6
  "$schema": "http://json-schema.org/draft-07/schema#",
7
7
  "title": "AI Squared Bolt",
8
8
  "type": "object",
9
- "required": ["request_format", "response_format"],
9
+ "x-dynamic-input-path": "messages.0.content",
10
+ "x-output-path": "choices.0.message.content",
11
+ "required": [
12
+ "request_format",
13
+ "response_format"
14
+ ],
10
15
  "properties": {
11
16
  "request_format": {
12
17
  "title": "Request Format",
@@ -1,16 +1,23 @@
1
1
  {
2
2
  "data": {
3
3
  "name": "Anthropic",
4
- "title": "Anthropic Model Endpoint",
4
+ "title": "Anthropic",
5
5
  "connector_type": "source",
6
6
  "category": "AI Model",
7
7
  "sub_category": "LLM",
8
+ "provider_type": "third_party",
9
+ "display_order": 30,
10
+ "openrouter_slug": "anthropic",
11
+ "description": "Claude model family for nuanced reasoning, analysis, and generation. Strong safety alignment and instruction-following across all model sizes.",
8
12
  "documentation_url": "https://docs.squared.ai/activation/ai-ml-sources/anthropic-model",
9
13
  "github_issue_label": "source-anthropic-model",
10
14
  "icon": "icon.svg",
11
15
  "license": "MIT",
12
16
  "release_stage": "alpha",
13
17
  "support_level": "community",
14
- "tags": ["language:ruby", "multiwoven"]
18
+ "tags": [
19
+ "language:ruby",
20
+ "multiwoven"
21
+ ]
15
22
  }
16
23
  }
@@ -0,0 +1,4 @@
1
+ {
2
+ "//models": "Sourced live from OpenRouter via the openrouter_slug in meta.json. Do not hand-curate; add entries here only for models OpenRouter does not list.",
3
+ "models": []
4
+ }