diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 9be42a362eb..6b0ba9d2552 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -6849,3 +6849,17 @@ export function HexIcon(props: SVGProps) { ) } + +export function RestCountriesIcon(props: SVGProps) { + return ( + + + + + ) +} diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 609f37382d7..7814a833edd 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -151,6 +151,7 @@ import { RedisIcon, ReductoIcon, ResendIcon, + RestCountriesIcon, RevenueCatIcon, RipplingIcon, RootlyIcon, @@ -370,6 +371,7 @@ export const blockTypeToIconMap: Record = { reducto: ReductoIcon, reducto_v2: ReductoIcon, resend: ResendIcon, + restcountries: RestCountriesIcon, revenuecat: RevenueCatIcon, rippling: RipplingIcon, rootly: RootlyIcon, diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index ad0f6b437ad..5ac9e6ee1e5 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -147,6 +147,7 @@ "redis", "reducto", "resend", + "restcountries", "revenuecat", "rippling", "rootly", diff --git a/apps/docs/content/docs/en/tools/restcountries.mdx b/apps/docs/content/docs/en/tools/restcountries.mdx new file mode 100644 index 00000000000..feb52e89881 --- /dev/null +++ b/apps/docs/content/docs/en/tools/restcountries.mdx @@ -0,0 +1,112 @@ +--- +title: REST Countries +description: Look up country reference data +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +## Usage Instructions + +Look up country information using the REST Countries API. Search by name, code, region, currency, or language. Does not require OAuth or an API key. + + + +## Tools + +### `restcountries_search_by_name` + +Search for countries by common or official country name using the REST Countries API. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `name` | string | Yes | Country name to search for, such as "Canada" or "Republic of Korea" | +| `fullText` | boolean | No | Require an exact full-name match when true | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `countries` | json | Countries matching the name search | +| `count` | number | Number of countries returned | +| `firstCountry` | json | First matching country, or null when there are no matches | + +### `restcountries_get_by_code` + +Get country information by ISO 3166-1 alpha-2, alpha-3, numeric, or IOC country code. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `code` | string | Yes | Country code, such as "US", "USA", "840", or "CAN" | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `countries` | json | Array containing the country matching the provided code | +| `count` | number | Number of countries returned | +| `firstCountry` | json | The matching country, or null when there is no match | + +### `restcountries_list_by_region` + +List countries in a world region using the REST Countries API. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `region` | string | Yes | Region name, such as "Africa", "Americas", "Asia", "Europe", or "Oceania" | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `countries` | json | Countries in the requested region | +| `count` | number | Number of countries returned | +| `firstCountry` | json | First country in the returned list, or null when there are no matches | + +### `restcountries_list_by_currency` + +List countries by currency code or currency name using the REST Countries API. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `currency` | string | Yes | Currency code or name, such as "USD", "EUR", or "dollar" | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `countries` | json | Countries using the requested currency | +| `count` | number | Number of countries returned | +| `firstCountry` | json | First country in the returned list, or null when there are no matches | + +### `restcountries_list_by_language` + +List countries by official language code or language name using REST Countries. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `language` | string | Yes | Language code or name, such as "en", "Spanish", or "French" | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `countries` | json | Countries using the requested language | +| `count` | number | Number of countries returned | +| `firstCountry` | json | First country in the returned list, or null when there are no matches | + + diff --git a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts index f80e850cebe..3fec2d734b3 100644 --- a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts +++ b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts @@ -151,6 +151,7 @@ import { RedisIcon, ReductoIcon, ResendIcon, + RestCountriesIcon, RevenueCatIcon, RipplingIcon, RootlyIcon, @@ -352,6 +353,7 @@ export const blockTypeToIconMap: Record = { redis: RedisIcon, reducto_v2: ReductoIcon, resend: ResendIcon, + restcountries: RestCountriesIcon, revenuecat: RevenueCatIcon, rippling: RipplingIcon, rootly: RootlyIcon, diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index 3e6de1c0eef..7f0e2b59d15 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -4045,6 +4045,10 @@ "name": "Read", "description": "Parse one or more uploaded files or files from URLs (text, PDF, CSV, images, etc.)" }, + { + "name": "Get", + "description": "Get a workspace file object from a selected file or canonical workspace file ID." + }, { "name": "Write", "description": "Create a new workspace file. If a file with the same name already exists, a numeric suffix is added (e.g., " @@ -4054,7 +4058,7 @@ "description": "Append content to an existing workspace file. The file must already exist. Content is added to the end of the file." } ], - "operationCount": 3, + "operationCount": 4, "triggers": [], "triggerCount": 0, "authType": "none", @@ -10767,6 +10771,45 @@ "integrationTypes": ["email", "communication"], "tags": ["email-marketing", "messaging"] }, + { + "type": "restcountries", + "slug": "rest-countries", + "name": "REST Countries", + "description": "Look up country reference data", + "longDescription": "Look up country information using the REST Countries API. Search by name, code, region, currency, or language. Does not require OAuth or an API key.", + "bgColor": "#E8F2FF", + "iconName": "RestCountriesIcon", + "docsUrl": "https://docs.sim.ai/tools/restcountries", + "operations": [ + { + "name": "Search by Name", + "description": "Search for countries by common or official country name using the REST Countries API." + }, + { + "name": "Get by Code", + "description": "Get country information by ISO 3166-1 alpha-2, alpha-3, numeric, or IOC country code." + }, + { + "name": "List by Region", + "description": "List countries in a world region using the REST Countries API." + }, + { + "name": "List by Currency", + "description": "List countries by currency code or currency name using the REST Countries API." + }, + { + "name": "List by Language", + "description": "List countries by official language code or language name using REST Countries." + } + ], + "operationCount": 5, + "triggers": [], + "triggerCount": 0, + "authType": "none", + "category": "tools", + "integrationTypes": ["analytics", "documents", "search"], + "tags": ["data-analytics", "knowledge-base"] + }, { "type": "revenuecat", "slug": "revenuecat", diff --git a/apps/sim/blocks/blocks/restcountries.ts b/apps/sim/blocks/blocks/restcountries.ts new file mode 100644 index 00000000000..2d4a105cba2 --- /dev/null +++ b/apps/sim/blocks/blocks/restcountries.ts @@ -0,0 +1,125 @@ +import { RestCountriesIcon } from '@/components/icons' +import { type BlockConfig, IntegrationType } from '@/blocks/types' +import type { RestCountriesResponse } from '@/tools/restcountries/types' + +export const RestCountriesBlock: BlockConfig = { + type: 'restcountries', + name: 'REST Countries', + description: 'Look up country reference data', + longDescription: + 'Look up country information using the REST Countries API. Search by name, code, region, currency, or language. Does not require OAuth or an API key.', + docsLink: 'https://docs.sim.ai/tools/restcountries', + category: 'tools', + integrationType: IntegrationType.Analytics, + tags: ['data-analytics', 'knowledge-base'], + bgColor: '#E8F2FF', + icon: RestCountriesIcon, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Search by Name', id: 'restcountries_search_by_name' }, + { label: 'Get by Code', id: 'restcountries_get_by_code' }, + { label: 'List by Region', id: 'restcountries_list_by_region' }, + { label: 'List by Currency', id: 'restcountries_list_by_currency' }, + { label: 'List by Language', id: 'restcountries_list_by_language' }, + ], + value: () => 'restcountries_search_by_name', + }, + { + id: 'name', + title: 'Country Name', + type: 'short-input', + placeholder: 'Canada', + condition: { field: 'operation', value: 'restcountries_search_by_name' }, + required: { field: 'operation', value: 'restcountries_search_by_name' }, + }, + { + id: 'fullText', + title: 'Exact Match', + type: 'switch', + condition: { field: 'operation', value: 'restcountries_search_by_name' }, + mode: 'advanced', + }, + { + id: 'code', + title: 'Country Code', + type: 'short-input', + placeholder: 'US, USA, 840', + condition: { field: 'operation', value: 'restcountries_get_by_code' }, + required: { field: 'operation', value: 'restcountries_get_by_code' }, + }, + { + id: 'region', + title: 'Region', + type: 'dropdown', + options: [ + { label: 'Africa', id: 'Africa' }, + { label: 'Americas', id: 'Americas' }, + { label: 'Asia', id: 'Asia' }, + { label: 'Europe', id: 'Europe' }, + { label: 'Oceania', id: 'Oceania' }, + ], + value: () => 'Europe', + condition: { field: 'operation', value: 'restcountries_list_by_region' }, + required: { field: 'operation', value: 'restcountries_list_by_region' }, + }, + { + id: 'currency', + title: 'Currency', + type: 'short-input', + placeholder: 'USD, EUR, dollar', + condition: { field: 'operation', value: 'restcountries_list_by_currency' }, + required: { field: 'operation', value: 'restcountries_list_by_currency' }, + }, + { + id: 'language', + title: 'Language', + type: 'short-input', + placeholder: 'English, Spanish, en', + condition: { field: 'operation', value: 'restcountries_list_by_language' }, + required: { field: 'operation', value: 'restcountries_list_by_language' }, + }, + ], + tools: { + access: [ + 'restcountries_search_by_name', + 'restcountries_get_by_code', + 'restcountries_list_by_region', + 'restcountries_list_by_currency', + 'restcountries_list_by_language', + ], + config: { + tool: (params) => { + switch (params.operation) { + case 'restcountries_get_by_code': + return 'restcountries_get_by_code' + case 'restcountries_list_by_region': + return 'restcountries_list_by_region' + case 'restcountries_list_by_currency': + return 'restcountries_list_by_currency' + case 'restcountries_list_by_language': + return 'restcountries_list_by_language' + default: + return 'restcountries_search_by_name' + } + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Country lookup operation to perform' }, + name: { type: 'string', description: 'Country name to search for' }, + fullText: { type: 'boolean', description: 'Require an exact country-name match' }, + code: { type: 'string', description: 'Country code to look up' }, + region: { type: 'string', description: 'World region to list countries from' }, + currency: { type: 'string', description: 'Currency code or name to search for' }, + language: { type: 'string', description: 'Language code or name to search for' }, + }, + outputs: { + countries: { type: 'json', description: 'Countries returned by REST Countries' }, + count: { type: 'number', description: 'Number of countries returned' }, + firstCountry: { type: 'json', description: 'First country in the returned list' }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index d458289879a..7e3c30a09b2 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -165,6 +165,7 @@ import { RedisBlock } from '@/blocks/blocks/redis' import { ReductoBlock, ReductoV2Block } from '@/blocks/blocks/reducto' import { ResendBlock } from '@/blocks/blocks/resend' import { ResponseBlock } from '@/blocks/blocks/response' +import { RestCountriesBlock } from '@/blocks/blocks/restcountries' import { RevenueCatBlock } from '@/blocks/blocks/revenuecat' import { RipplingBlock } from '@/blocks/blocks/rippling' import { RootlyBlock } from '@/blocks/blocks/rootly' @@ -419,6 +420,7 @@ export const registry: Record = { reducto_v2: ReductoV2Block, resend: ResendBlock, response: ResponseBlock, + restcountries: RestCountriesBlock, revenuecat: RevenueCatBlock, rippling: RipplingBlock, rootly: RootlyBlock, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 95502bf3ff6..433733ac596 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -6872,3 +6872,24 @@ export function SnowflakeIcon(props: SVGProps) { ) } + +export function RestCountriesIcon(props: SVGProps) { + return ( + + + + + + ) +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 014ca723df2..8f823221c2b 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -2111,6 +2111,13 @@ import { resendSendTool, resendUpdateContactTool, } from '@/tools/resend' +import { + restCountriesGetByCodeTool, + restCountriesListByCurrencyTool, + restCountriesListByLanguageTool, + restCountriesListByRegionTool, + restCountriesSearchByNameTool, +} from '@/tools/restcountries' import { revenuecatCreatePurchaseTool, revenuecatDeferGoogleSubscriptionTool, @@ -4233,6 +4240,11 @@ export const tools: Record = { redis_llen: redisLLenTool, redis_hdel: redisHDelTool, redis_persist: redisPersistTool, + restcountries_search_by_name: restCountriesSearchByNameTool, + restcountries_get_by_code: restCountriesGetByCodeTool, + restcountries_list_by_region: restCountriesListByRegionTool, + restcountries_list_by_currency: restCountriesListByCurrencyTool, + restcountries_list_by_language: restCountriesListByLanguageTool, revenuecat_get_customer: revenuecatGetCustomerTool, revenuecat_delete_customer: revenuecatDeleteCustomerTool, revenuecat_create_purchase: revenuecatCreatePurchaseTool, diff --git a/apps/sim/tools/restcountries/get_by_code.ts b/apps/sim/tools/restcountries/get_by_code.ts new file mode 100644 index 00000000000..9f09b598d28 --- /dev/null +++ b/apps/sim/tools/restcountries/get_by_code.ts @@ -0,0 +1,56 @@ +import type { + RestCountriesGetByCodeParams, + RestCountriesResponse, +} from '@/tools/restcountries/types' +import { + buildRestCountriesUrl, + encodeRestCountriesPathSegment, + transformRestCountriesResponse, +} from '@/tools/restcountries/utils' +import type { ToolConfig } from '@/tools/types' + +export const restCountriesGetByCodeTool: ToolConfig< + RestCountriesGetByCodeParams, + RestCountriesResponse +> = { + id: 'restcountries_get_by_code', + name: 'REST Countries Get by Code', + description: + 'Get country information by ISO 3166-1 alpha-2, alpha-3, numeric, or IOC country code.', + version: '1.0.0', + + params: { + code: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Country code, such as "US", "USA", "840", or "CAN"', + }, + }, + + request: { + url: (params) => buildRestCountriesUrl(`alpha/${encodeRestCountriesPathSegment(params.code)}`), + method: 'GET', + headers: () => ({ + Accept: 'application/json', + }), + }, + + transformResponse: transformRestCountriesResponse, + + outputs: { + countries: { + type: 'json', + description: 'Array containing the country matching the provided code', + }, + count: { + type: 'number', + description: 'Number of countries returned', + }, + firstCountry: { + type: 'json', + description: 'The matching country, or null when there is no match', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/restcountries/index.ts b/apps/sim/tools/restcountries/index.ts new file mode 100644 index 00000000000..283081e6a73 --- /dev/null +++ b/apps/sim/tools/restcountries/index.ts @@ -0,0 +1,5 @@ +export { restCountriesGetByCodeTool } from '@/tools/restcountries/get_by_code' +export { restCountriesListByCurrencyTool } from '@/tools/restcountries/list_by_currency' +export { restCountriesListByLanguageTool } from '@/tools/restcountries/list_by_language' +export { restCountriesListByRegionTool } from '@/tools/restcountries/list_by_region' +export { restCountriesSearchByNameTool } from '@/tools/restcountries/search_by_name' diff --git a/apps/sim/tools/restcountries/list_by_currency.ts b/apps/sim/tools/restcountries/list_by_currency.ts new file mode 100644 index 00000000000..8b5ed0a6e2f --- /dev/null +++ b/apps/sim/tools/restcountries/list_by_currency.ts @@ -0,0 +1,56 @@ +import type { + RestCountriesListByCurrencyParams, + RestCountriesResponse, +} from '@/tools/restcountries/types' +import { + buildRestCountriesUrl, + encodeRestCountriesPathSegment, + transformRestCountriesResponse, +} from '@/tools/restcountries/utils' +import type { ToolConfig } from '@/tools/types' + +export const restCountriesListByCurrencyTool: ToolConfig< + RestCountriesListByCurrencyParams, + RestCountriesResponse +> = { + id: 'restcountries_list_by_currency', + name: 'REST Countries List by Currency', + description: 'List countries by currency code or currency name using the REST Countries API.', + version: '1.0.0', + + params: { + currency: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Currency code or name, such as "USD", "EUR", or "dollar"', + }, + }, + + request: { + url: (params) => + buildRestCountriesUrl(`currency/${encodeRestCountriesPathSegment(params.currency)}`), + method: 'GET', + headers: () => ({ + Accept: 'application/json', + }), + }, + + transformResponse: transformRestCountriesResponse, + + outputs: { + countries: { + type: 'json', + description: 'Countries using the requested currency', + }, + count: { + type: 'number', + description: 'Number of countries returned', + }, + firstCountry: { + type: 'json', + description: 'First country in the returned list, or null when there are no matches', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/restcountries/list_by_language.ts b/apps/sim/tools/restcountries/list_by_language.ts new file mode 100644 index 00000000000..30c8f389ce7 --- /dev/null +++ b/apps/sim/tools/restcountries/list_by_language.ts @@ -0,0 +1,56 @@ +import type { + RestCountriesListByLanguageParams, + RestCountriesResponse, +} from '@/tools/restcountries/types' +import { + buildRestCountriesUrl, + encodeRestCountriesPathSegment, + transformRestCountriesResponse, +} from '@/tools/restcountries/utils' +import type { ToolConfig } from '@/tools/types' + +export const restCountriesListByLanguageTool: ToolConfig< + RestCountriesListByLanguageParams, + RestCountriesResponse +> = { + id: 'restcountries_list_by_language', + name: 'REST Countries List by Language', + description: 'List countries by official language code or language name using REST Countries.', + version: '1.0.0', + + params: { + language: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Language code or name, such as "en", "Spanish", or "French"', + }, + }, + + request: { + url: (params) => + buildRestCountriesUrl(`lang/${encodeRestCountriesPathSegment(params.language)}`), + method: 'GET', + headers: () => ({ + Accept: 'application/json', + }), + }, + + transformResponse: transformRestCountriesResponse, + + outputs: { + countries: { + type: 'json', + description: 'Countries using the requested language', + }, + count: { + type: 'number', + description: 'Number of countries returned', + }, + firstCountry: { + type: 'json', + description: 'First country in the returned list, or null when there are no matches', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/restcountries/list_by_region.ts b/apps/sim/tools/restcountries/list_by_region.ts new file mode 100644 index 00000000000..27f80eb311c --- /dev/null +++ b/apps/sim/tools/restcountries/list_by_region.ts @@ -0,0 +1,56 @@ +import type { + RestCountriesListByRegionParams, + RestCountriesResponse, +} from '@/tools/restcountries/types' +import { + buildRestCountriesUrl, + encodeRestCountriesPathSegment, + transformRestCountriesResponse, +} from '@/tools/restcountries/utils' +import type { ToolConfig } from '@/tools/types' + +export const restCountriesListByRegionTool: ToolConfig< + RestCountriesListByRegionParams, + RestCountriesResponse +> = { + id: 'restcountries_list_by_region', + name: 'REST Countries List by Region', + description: 'List countries in a world region using the REST Countries API.', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Region name, such as "Africa", "Americas", "Asia", "Europe", or "Oceania"', + }, + }, + + request: { + url: (params) => + buildRestCountriesUrl(`region/${encodeRestCountriesPathSegment(params.region)}`), + method: 'GET', + headers: () => ({ + Accept: 'application/json', + }), + }, + + transformResponse: transformRestCountriesResponse, + + outputs: { + countries: { + type: 'json', + description: 'Countries in the requested region', + }, + count: { + type: 'number', + description: 'Number of countries returned', + }, + firstCountry: { + type: 'json', + description: 'First country in the returned list, or null when there are no matches', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/restcountries/search_by_name.ts b/apps/sim/tools/restcountries/search_by_name.ts new file mode 100644 index 00000000000..a84c68c0061 --- /dev/null +++ b/apps/sim/tools/restcountries/search_by_name.ts @@ -0,0 +1,65 @@ +import type { + RestCountriesResponse, + RestCountriesSearchByNameParams, +} from '@/tools/restcountries/types' +import { + buildRestCountriesUrl, + encodeRestCountriesPathSegment, + transformRestCountriesResponse, +} from '@/tools/restcountries/utils' +import type { ToolConfig } from '@/tools/types' + +export const restCountriesSearchByNameTool: ToolConfig< + RestCountriesSearchByNameParams, + RestCountriesResponse +> = { + id: 'restcountries_search_by_name', + name: 'REST Countries Search by Name', + description: + 'Search for countries by common or official country name using the REST Countries API.', + version: '1.0.0', + + params: { + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Country name to search for, such as "Canada" or "Republic of Korea"', + }, + fullText: { + type: 'boolean', + required: false, + visibility: 'user-only', + description: 'Require an exact full-name match when true', + }, + }, + + request: { + url: (params) => + buildRestCountriesUrl(`name/${encodeRestCountriesPathSegment(params.name)}`, { + ...(params.fullText ? { fullText: 'true' } : {}), + }), + method: 'GET', + headers: () => ({ + Accept: 'application/json', + }), + }, + + transformResponse: transformRestCountriesResponse, + + outputs: { + countries: { + type: 'json', + description: 'Countries matching the name search', + }, + count: { + type: 'number', + description: 'Number of countries returned', + }, + firstCountry: { + type: 'json', + description: 'First matching country, or null when there are no matches', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/restcountries/types.ts b/apps/sim/tools/restcountries/types.ts new file mode 100644 index 00000000000..673e45daa27 --- /dev/null +++ b/apps/sim/tools/restcountries/types.ts @@ -0,0 +1,66 @@ +import type { ToolResponse } from '@/tools/types' + +export interface RestCountriesSearchByNameParams { + name: string + fullText?: boolean +} + +export interface RestCountriesGetByCodeParams { + code: string +} + +export interface RestCountriesListByRegionParams { + region: string +} + +export interface RestCountriesListByCurrencyParams { + currency: string +} + +export interface RestCountriesListByLanguageParams { + language: string +} + +export interface RestCountriesCurrency { + code: string + name: string | null + symbol: string | null +} + +export interface RestCountriesLanguage { + code: string + name: string +} + +export interface RestCountriesFlag { + png: string | null + svg: string | null + alt: string | null +} + +export interface RestCountriesCountry { + name: string | null + officialName: string | null + countryCode2: string | null + countryCode3: string | null + capital: string[] + region: string | null + subregion: string | null + population: number | null + area: number | null + currencies: RestCountriesCurrency[] + languages: RestCountriesLanguage[] + timezones: string[] + latlng: number[] + flag: RestCountriesFlag | null + googleMapsUrl: string | null + openStreetMapsUrl: string | null +} + +export interface RestCountriesResponse extends ToolResponse { + output: { + countries: RestCountriesCountry[] + count: number + firstCountry: RestCountriesCountry | null + } +} diff --git a/apps/sim/tools/restcountries/utils.ts b/apps/sim/tools/restcountries/utils.ts new file mode 100644 index 00000000000..e744d5a7e78 --- /dev/null +++ b/apps/sim/tools/restcountries/utils.ts @@ -0,0 +1,143 @@ +import type { + RestCountriesCountry, + RestCountriesCurrency, + RestCountriesFlag, + RestCountriesLanguage, + RestCountriesResponse, +} from '@/tools/restcountries/types' + +export const REST_COUNTRIES_FIELDS = [ + 'name', + 'cca2', + 'cca3', + 'capital', + 'region', + 'subregion', + 'population', + 'area', + 'currencies', + 'languages', + 'timezones', + 'latlng', + 'flags', + 'maps', +].join(',') + +export function buildRestCountriesUrl(path: string, query?: Record): string { + const url = new URL(`https://restcountries.com/v3.1/${path}`) + url.searchParams.set('fields', REST_COUNTRIES_FIELDS) + + for (const [key, value] of Object.entries(query ?? {})) { + url.searchParams.set(key, value) + } + + return url.toString() +} + +export function encodeRestCountriesPathSegment(value: string): string { + return encodeURIComponent(value.trim()) +} + +export async function transformRestCountriesResponse( + response: Response +): Promise { + if (!response.ok) { + const errorText = await response.text() + throw new Error(`REST Countries API error: ${response.status} - ${errorText}`) + } + + const data = await response.json() + const countries = parseCountries(data) + + return { + success: true, + output: { + countries, + count: countries.length, + firstCountry: countries[0] ?? null, + }, + } +} + +function parseCountries(data: unknown): RestCountriesCountry[] { + if (!Array.isArray(data)) return [] + return data.map(parseCountry) +} + +function parseCountry(country: unknown): RestCountriesCountry { + const record = asRecord(country) + const name = asRecord(record.name) + const maps = asRecord(record.maps) + + return { + name: stringOrNull(name.common), + officialName: stringOrNull(name.official), + countryCode2: stringOrNull(record.cca2), + countryCode3: stringOrNull(record.cca3), + capital: stringArray(record.capital), + region: stringOrNull(record.region), + subregion: stringOrNull(record.subregion), + population: numberOrNull(record.population), + area: numberOrNull(record.area), + currencies: parseCurrencies(record.currencies), + languages: parseLanguages(record.languages), + timezones: stringArray(record.timezones), + latlng: numberArray(record.latlng), + flag: parseFlag(record.flags), + googleMapsUrl: stringOrNull(maps.googleMaps), + openStreetMapsUrl: stringOrNull(maps.openStreetMaps), + } +} + +function parseCurrencies(value: unknown): RestCountriesCurrency[] { + const currencies = asRecord(value) + return Object.entries(currencies).map(([code, details]) => { + const record = asRecord(details) + return { + code, + name: stringOrNull(record.name), + symbol: stringOrNull(record.symbol), + } + }) +} + +function parseLanguages(value: unknown): RestCountriesLanguage[] { + const languages = asRecord(value) + return Object.entries(languages).flatMap(([code, name]) => { + if (typeof name !== 'string') return [] + return [{ code, name }] + }) +} + +function parseFlag(value: unknown): RestCountriesFlag | null { + const flag = asRecord(value) + const png = stringOrNull(flag.png) + const svg = stringOrNull(flag.svg) + const alt = stringOrNull(flag.alt) + + if (!png && !svg && !alt) return null + return { png, svg, alt } +} + +function asRecord(value: unknown): Record { + if (!value || typeof value !== 'object' || Array.isArray(value)) return {} + return value as Record +} + +function stringOrNull(value: unknown): string | null { + return typeof value === 'string' ? value : null +} + +function numberOrNull(value: unknown): number | null { + return typeof value === 'number' ? value : null +} + +function stringArray(value: unknown): string[] { + if (!Array.isArray(value)) return [] + return value.filter((item): item is string => typeof item === 'string') +} + +function numberArray(value: unknown): number[] { + if (!Array.isArray(value)) return [] + return value.filter((item): item is number => typeof item === 'number') +}