kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
Merge pull request #706 from transitive-bullshit/feature/moar-improvements
commit
762723bd58
|
@ -55,6 +55,7 @@
|
|||
{
|
||||
"group": "Tools",
|
||||
"pages": [
|
||||
"tools/airtable",
|
||||
"tools/apollo",
|
||||
"tools/arxiv",
|
||||
"tools/bing",
|
||||
|
@ -70,6 +71,8 @@
|
|||
"tools/hacker-news",
|
||||
"tools/gravatar",
|
||||
"tools/google-custom-search",
|
||||
"tools/google-docs",
|
||||
"tools/google-drive",
|
||||
"tools/hunter",
|
||||
"tools/jina",
|
||||
"tools/leadmagic",
|
||||
|
@ -83,6 +86,7 @@
|
|||
"tools/polygon",
|
||||
"tools/predict-leads",
|
||||
"tools/proxycurl",
|
||||
"tools/reddit",
|
||||
"tools/rocketreach",
|
||||
"tools/searxng",
|
||||
"tools/serpapi",
|
||||
|
@ -92,10 +96,12 @@
|
|||
"tools/tavily",
|
||||
"tools/twilio",
|
||||
"tools/twitter",
|
||||
"tools/typeform",
|
||||
"tools/weather",
|
||||
"tools/wikidata",
|
||||
"tools/wikipedia",
|
||||
"tools/wolfram-alpha",
|
||||
"tools/youtube",
|
||||
"tools/zoominfo"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
---
|
||||
title: Airtable
|
||||
description: Airtable is a no-code spreadsheets, CRM, and database.
|
||||
---
|
||||
|
||||
- package: `@agentic/airtable`
|
||||
- exports: `class AirtableClient`, `namespace airtable`
|
||||
- env vars: `AIRTABLE_API_KEY`
|
||||
- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/airtable/src/airtable-client.ts)
|
||||
- [airtable api docs](https://airtable.com/developers/web/api/introduction)
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
```bash npm
|
||||
npm install @agentic/airtable
|
||||
```
|
||||
|
||||
```bash yarn
|
||||
yarn add @agentic/airtable
|
||||
```
|
||||
|
||||
```bash pnpm
|
||||
pnpm add @agentic/airtable
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { AirtableClient } from '@agentic/airtable'
|
||||
|
||||
const airtable = new AirtableClient()
|
||||
const { bases } = await airtable.listBases()
|
||||
console.log('bases', tables)
|
||||
|
||||
const baseId = bases[0]!.id
|
||||
const tables = await airtable.listTables({ baseId })
|
||||
console.log('tables', tables)
|
||||
|
||||
const tableId = tables[0]!.id
|
||||
const searchResults = await airtable.searchRecords({
|
||||
baseId,
|
||||
tableId,
|
||||
searchTerm: 'Travis'
|
||||
})
|
||||
console.log('search results', searchResults)
|
||||
```
|
||||
|
||||
(this is just an example of how you'd use the client)
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
title: Google Docs
|
||||
description: Simplified Google Docs API.
|
||||
---
|
||||
|
||||
- package: `@agentic/google-docs`
|
||||
- exports: `class GoogleDocsClient`, `namespace googleDocs`
|
||||
- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/google-docs/src/google-docs-client.ts)
|
||||
- [google docs docs](https://developers.google.com/workspace/docs/api)
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
```bash npm
|
||||
npm install @agentic/google-docs googleapis @google-cloud/local-auth
|
||||
```
|
||||
|
||||
```bash yarn
|
||||
yarn add @agentic/google-docs googleapis @google-cloud/local-auth
|
||||
```
|
||||
|
||||
```bash pnpm
|
||||
pnpm add @agentic/google-docs googleapis @google-cloud/local-auth
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Example Usage
|
||||
|
||||
```ts
|
||||
import { GoogleDriveClient } from '@agentic/google-drive'
|
||||
import { authenticate } from '@google-cloud/local-auth'
|
||||
import { google } from 'googleapis'
|
||||
|
||||
// (in a real app, store these auth credentials and reuse them)
|
||||
const auth = await authenticate({
|
||||
scopes: ['https://www.googleapis.com/auth/documents.readonly'],
|
||||
keyfilePath: process.env.GOOGLE_CREDENTIALS_PATH
|
||||
})
|
||||
const docs = google.docs({ version: 'v1', auth })
|
||||
const client = new GoogleDocsClient({ docs })
|
||||
|
||||
const document = await client.getDocument({ documentId: 'TODO' })
|
||||
console.log(document)
|
||||
```
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: Google Drive
|
||||
description: Simplified Google Drive API.
|
||||
---
|
||||
|
||||
- package: `@agentic/google-drive`
|
||||
- exports: `class GoogleDriveClient`, `namespace googleDrive`
|
||||
- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/google-drive/src/google-drive-client.ts)
|
||||
- [google drive docs](https://developers.google.com/workspace/drive/api)
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
```bash npm
|
||||
npm install @agentic/google-drive googleapis google-auth-library
|
||||
```
|
||||
|
||||
```bash yarn
|
||||
yarn add @agentic/google-drive googleapis google-auth-library
|
||||
```
|
||||
|
||||
```bash pnpm
|
||||
pnpm add @agentic/google-drive googleapis google-auth-library
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Example Usage
|
||||
|
||||
```ts
|
||||
import { GoogleDriveClient } from '@agentic/google-drive'
|
||||
import { GoogleAuth } from 'google-auth-library'
|
||||
import { google } from 'googleapis'
|
||||
|
||||
const auth = new GoogleAuth({ scopes: 'https://www.googleapis.com/auth/drive' })
|
||||
const drive = google.drive({ version: 'v3', auth })
|
||||
const client = new GoogleDriveClient({ drive })
|
||||
|
||||
const result = await client.listFiles()
|
||||
|
||||
const file = result.files[0]!
|
||||
const metadata = await client.getFile({ fileId: file.id })
|
||||
const content = await client.exportFile({
|
||||
fileId: file.id,
|
||||
mimeType: 'application/pdf'
|
||||
})
|
||||
```
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
title: Reddit
|
||||
description: Basic readonly Reddit API for getting top/hot/new/rising posts from subreddits.
|
||||
---
|
||||
|
||||
- package: `@agentic/reddit`
|
||||
- exports: `class RedditClient`, `namespace reddit`
|
||||
- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/reddit/src/reddit-client.ts)
|
||||
- [reddit legacy api docs](https://old.reddit.com/dev/api)
|
||||
|
||||
<Note>
|
||||
This client uses Reddit's free, legacy JSON API aimed at RSS feeds, so no auth
|
||||
is required. With that being said, Reddit does impose rate limits on the API,
|
||||
so be considerate.
|
||||
</Note>
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
```bash npm
|
||||
npm install @agentic/reddit
|
||||
```
|
||||
|
||||
```bash yarn
|
||||
yarn add @agentic/reddit
|
||||
```
|
||||
|
||||
```bash pnpm
|
||||
pnpm add @agentic/reddit
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { RedditClient } from '@agentic/reddit'
|
||||
|
||||
const reddit = new RedditClient()
|
||||
const result = await reddit.getSubredditPosts({
|
||||
subreddit: 'AskReddit',
|
||||
type: 'hot',
|
||||
limit: 10
|
||||
})
|
||||
```
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
title: Typeform
|
||||
description: Readonly Typeform API client for fetching form insights and responses.
|
||||
---
|
||||
|
||||
- package: `@agentic/typeform`
|
||||
- exports: `class TypeformClient`, `namespace typeform`
|
||||
- env vars: `TYPEFORM_API_KEY`
|
||||
- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/typeform/src/typeform-client.ts)
|
||||
- [typeform api docs](https://www.typeform.com/developers/get-started/)
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
```bash npm
|
||||
npm install @agentic/typeform
|
||||
```
|
||||
|
||||
```bash yarn
|
||||
yarn add @agentic/typeform
|
||||
```
|
||||
|
||||
```bash pnpm
|
||||
pnpm add @agentic/typeform
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { TypeformClient } from '@agentic/typeform'
|
||||
|
||||
const typeform = new TypeformClient()
|
||||
|
||||
const responses = await typeform.getResponsesForForm({
|
||||
formId: 'TODO'
|
||||
})
|
||||
|
||||
const insights = await typeform.getInsightsForForm({
|
||||
formId: 'TODO'
|
||||
})
|
||||
```
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
title: YouTube
|
||||
description: YouTube data API v3 client for searching YT videos and channels.
|
||||
---
|
||||
|
||||
- package: `@agentic/youtube`
|
||||
- exports: `class YouTubeClient`, `namespace youtube`
|
||||
- env vars: `YOUTUBE_API_KEY`
|
||||
- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/youtube/src/youtube-client.ts)
|
||||
- [youtube api docs](https://developers.google.com/youtube/v3)
|
||||
- [search docs](https://developers.google.com/youtube/v3/docs/search/list)
|
||||
|
||||
## Install
|
||||
|
||||
<CodeGroup>
|
||||
```bash npm
|
||||
npm install @agentic/youtube
|
||||
```
|
||||
|
||||
```bash yarn
|
||||
yarn add @agentic/youtube
|
||||
```
|
||||
|
||||
```bash pnpm
|
||||
pnpm add @agentic/youtube
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { YouTubeClient } from '@agentic/youtube'
|
||||
|
||||
const youtube = new YouTubeClient()
|
||||
const res = await youtube.searchVideos({
|
||||
query: 'cute kittens'
|
||||
})
|
||||
```
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/ai-sdk"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "@agentic/airtable",
|
||||
"version": "7.6.3",
|
||||
"description": "Agentic SDK for Airtable.",
|
||||
"author": "Travis Fischer <travis@transitivebullsh.it>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/airtable"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
"types": "./dist/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"clean": "del dist",
|
||||
"test": "run-s test:*",
|
||||
"test:lint": "eslint .",
|
||||
"test:typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentic/core": "workspace:*",
|
||||
"ky": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<p align="center">
|
||||
<a href="https://agentic.so">
|
||||
<img alt="Agentic" src="https://raw.githubusercontent.com/transitive-bullshit/agentic/main/docs/media/agentic-header.jpg" width="308">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<em>AI agent stdlib that works with any LLM and TypeScript AI SDK.</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml"><img alt="Build Status" src="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml/badge.svg" /></a>
|
||||
<a href="https://www.npmjs.com/package/@agentic/stdlib"><img alt="NPM" src="https://img.shields.io/npm/v/@agentic/stdlib.svg" /></a>
|
||||
<a href="https://github.com/transitive-bullshit/agentic/blob/main/license"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue" /></a>
|
||||
<a href="https://prettier.io"><img alt="Prettier Code Formatting" src="https://img.shields.io/badge/code_style-prettier-brightgreen.svg" /></a>
|
||||
</p>
|
||||
|
||||
# Agentic
|
||||
|
||||
**See the [github repo](https://github.com/transitive-bullshit/agentic) or [docs](https://agentic.so) for more info.**
|
||||
|
||||
## License
|
||||
|
||||
MIT © [Travis Fischer](https://x.com/transitive_bs)
|
|
@ -0,0 +1,423 @@
|
|||
import {
|
||||
aiFunction,
|
||||
AIFunctionsProvider,
|
||||
assert,
|
||||
getEnv,
|
||||
sanitizeSearchParams
|
||||
} from '@agentic/core'
|
||||
import defaultKy, { type KyInstance } from 'ky'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { airtable } from './airtable'
|
||||
|
||||
/**
|
||||
* Airtable API client.
|
||||
*
|
||||
* @see https://airtable.com/developers/web/api/introduction
|
||||
*/
|
||||
export class AirtableClient extends AIFunctionsProvider {
|
||||
protected readonly ky: KyInstance
|
||||
protected readonly apiKey: string
|
||||
protected readonly apiBaseUrl: string
|
||||
|
||||
constructor({
|
||||
apiKey = getEnv('AIRTABLE_API_KEY'),
|
||||
apiBaseUrl = airtable.API_BASE_URL,
|
||||
timeoutMs = 60_000,
|
||||
ky = defaultKy
|
||||
}: {
|
||||
apiKey?: string
|
||||
apiBaseUrl?: string
|
||||
timeoutMs?: number
|
||||
ky?: KyInstance
|
||||
} = {}) {
|
||||
assert(
|
||||
apiKey,
|
||||
`AirtableClient missing required "apiKey" (defaults to "AIRTABLE_API_KEY")`
|
||||
)
|
||||
super()
|
||||
|
||||
this.apiKey = apiKey
|
||||
this.apiBaseUrl = apiBaseUrl
|
||||
|
||||
this.ky = ky.extend({
|
||||
prefixUrl: apiBaseUrl,
|
||||
timeout: timeoutMs,
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all of the bases that the user has access to.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'airtable_list_bases',
|
||||
description: 'Lists all accessible Airtable bases.',
|
||||
inputSchema: z.object({})
|
||||
})
|
||||
async listBases(): Promise<airtable.ListBasesResponse> {
|
||||
return this.ky.get('v0/meta/bases').json<airtable.ListBasesResponse>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all of the tables in a base.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'airtable_list_tables',
|
||||
description: 'Lists all of the tables in a base.',
|
||||
inputSchema: airtable.ListTablesArgsSchema
|
||||
})
|
||||
async listTables<
|
||||
TDetailLevel extends airtable.TableDetailLevel = 'full'
|
||||
>(args: {
|
||||
baseId: string
|
||||
detailLevel?: TDetailLevel
|
||||
}): Promise<Array<airtable.AirtableTableToDetailLevel<TDetailLevel>>> {
|
||||
const { baseId, detailLevel = 'full' } = args
|
||||
|
||||
const res = await this.ky
|
||||
.get(`/v0/meta/bases/${baseId}/tables`)
|
||||
.json<airtable.BaseSchemaResponse>()
|
||||
|
||||
return res.tables.map((table) =>
|
||||
transformTableDetailLevel<TDetailLevel>({
|
||||
table,
|
||||
detailLevel: detailLevel as TDetailLevel
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a single table's schema in a base.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'airtable_get_table',
|
||||
description: "Gets a single table's schema in a base.",
|
||||
inputSchema: airtable.DescribeTableArgsSchema
|
||||
})
|
||||
async getTable<
|
||||
TDetailLevel extends airtable.TableDetailLevel = 'full'
|
||||
>(args: {
|
||||
baseId: string
|
||||
tableId: string
|
||||
detailLevel?: TDetailLevel
|
||||
}): Promise<airtable.AirtableTableToDetailLevel<TDetailLevel>> {
|
||||
const tables = await this.listTables<TDetailLevel>(args)
|
||||
const table = tables.find((t) => t.id === args.tableId)
|
||||
assert(table, `Table ${args.tableId} not found in base ${args.baseId}`)
|
||||
return table
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists records from a table.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'airtable_list_records',
|
||||
description: 'Lists records from a table.',
|
||||
inputSchema: airtable.ListRecordsArgsSchema
|
||||
})
|
||||
async listRecords(
|
||||
args: airtable.ListRecordsArgs
|
||||
): Promise<airtable.AirtableRecord[]> {
|
||||
const { baseId, tableId, ...options } = args
|
||||
return this.ky
|
||||
.get(`/v0/${baseId}/${tableId}`, {
|
||||
searchParams: sanitizeSearchParams(options)
|
||||
})
|
||||
.json<airtable.AirtableRecord[]>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all records from a table.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'airtable_list_all_records',
|
||||
description: 'Lists all records from a table.',
|
||||
inputSchema: airtable.ListRecordsArgsSchema
|
||||
})
|
||||
async listAllRecords(
|
||||
args: airtable.ListRecordsArgs
|
||||
): Promise<airtable.AirtableRecord[]> {
|
||||
const allRecords: airtable.AirtableRecord[] = []
|
||||
let offset = args.offset ?? 0
|
||||
|
||||
do {
|
||||
const res = await this.listRecords({
|
||||
...args,
|
||||
offset
|
||||
})
|
||||
if (!res.length) {
|
||||
break
|
||||
}
|
||||
|
||||
allRecords.push(...res)
|
||||
offset += res.length
|
||||
} while (true)
|
||||
|
||||
return allRecords
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a single record from a table.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'airtable_get_record',
|
||||
description: 'Gets a single record from a table.',
|
||||
inputSchema: airtable.GetRecordArgsSchema
|
||||
})
|
||||
async getRecord(
|
||||
args: airtable.GetRecordArgs
|
||||
): Promise<airtable.AirtableRecord> {
|
||||
const { baseId, tableId, recordId } = args
|
||||
return this.ky
|
||||
.get(`/v0/${baseId}/${tableId}/${recordId}`)
|
||||
.json<airtable.AirtableRecord>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a record in a table.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'airtable_create_record',
|
||||
description: 'Creates a record in a table.',
|
||||
inputSchema: airtable.CreateRecordArgsSchema
|
||||
})
|
||||
async createRecord(
|
||||
args: airtable.CreateRecordArgs
|
||||
): Promise<airtable.AirtableRecord> {
|
||||
const { baseId, tableId, ...body } = args
|
||||
return this.ky
|
||||
.post(`/v0/${baseId}/${tableId}`, {
|
||||
json: body
|
||||
})
|
||||
.json<airtable.AirtableRecord>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates records in a table.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'airtable_update_records',
|
||||
description: 'Updates records in a table.',
|
||||
inputSchema: airtable.UpdateRecordsArgsSchema
|
||||
})
|
||||
async updateRecords(
|
||||
args: airtable.UpdateRecordsArgs
|
||||
): Promise<airtable.AirtableRecord[]> {
|
||||
const { baseId, tableId, ...body } = args
|
||||
return this.ky
|
||||
.patch(`/v0/${baseId}/${tableId}`, {
|
||||
json: body
|
||||
})
|
||||
.json<airtable.AirtableRecord[]>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes records from a table.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'airtable_delete_records',
|
||||
description: 'Deletes records from a table.',
|
||||
inputSchema: airtable.DeleteRecordsArgsSchema
|
||||
})
|
||||
async deleteRecords(
|
||||
args: airtable.DeleteRecordsArgs
|
||||
): Promise<{ id: string }[]> {
|
||||
const { baseId, tableId, recordIds } = args
|
||||
const queryString = recordIds.map((id) => `records[]=${id}`).join('&')
|
||||
|
||||
const res = await this.ky
|
||||
.delete(`/v0/${baseId}/${tableId}?${queryString}`)
|
||||
.json<{ records: { id: string; deleted: boolean }[] }>()
|
||||
|
||||
return res.records.map(({ id }) => ({ id }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a table in a base.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'airtable_create_table',
|
||||
description: 'Creates a table in a base.',
|
||||
inputSchema: airtable.CreateTableArgsSchema
|
||||
})
|
||||
async createTable(args: airtable.CreateTableArgs): Promise<airtable.Table> {
|
||||
const { baseId, ...body } = args
|
||||
|
||||
return this.ky
|
||||
.post(`/v0/meta/bases/${baseId}/tables`, {
|
||||
json: body
|
||||
})
|
||||
.json<airtable.Table>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a table in a base.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'airtable_update_table',
|
||||
description: 'Updates a table in a base.',
|
||||
inputSchema: airtable.UpdateTableArgsSchema
|
||||
})
|
||||
async updateTable(args: airtable.UpdateTableArgs): Promise<airtable.Table> {
|
||||
const { baseId, tableId, ...body } = args
|
||||
return this.ky
|
||||
.patch(`/v0/meta/bases/${baseId}/tables/${tableId}`, {
|
||||
json: body
|
||||
})
|
||||
.json<airtable.Table>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a field in a table.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'airtable_create_field',
|
||||
description: 'Creates a field in a table.',
|
||||
inputSchema: airtable.CreateFieldArgsSchema
|
||||
})
|
||||
async createField(args: airtable.CreateFieldArgs): Promise<airtable.Field> {
|
||||
const { baseId, tableId, body } = args
|
||||
return this.ky
|
||||
.post(`/v0/meta/bases/${baseId}/tables/${tableId}/fields`, {
|
||||
json: body.field
|
||||
})
|
||||
.json<airtable.Field>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a field in a table.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'airtable_update_field',
|
||||
description: 'Updates a field in a table.',
|
||||
inputSchema: airtable.UpdateFieldArgsSchema
|
||||
})
|
||||
async updateField(args: airtable.UpdateFieldArgs): Promise<airtable.Field> {
|
||||
const { baseId, tableId, fieldId, ...body } = args
|
||||
return this.ky
|
||||
.patch(`/v0/meta/bases/${baseId}/tables/${tableId}/fields/${fieldId}`, {
|
||||
json: body
|
||||
})
|
||||
.json<airtable.Field>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for records in a table which contain specific text.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'airtable_search_records',
|
||||
description: 'Searches for records in a table which contain specific text.',
|
||||
inputSchema: airtable.SearchRecordsArgsSchema
|
||||
})
|
||||
async searchRecords(
|
||||
args: airtable.SearchRecordsArgs
|
||||
): Promise<airtable.AirtableRecord[]> {
|
||||
const { baseId, tableId, fieldIds, searchTerm, ...opts } = args
|
||||
// Validate and get search fields
|
||||
const searchFieldIds = await this.validateAndGetSearchFields({
|
||||
baseId,
|
||||
tableId,
|
||||
fieldIds
|
||||
})
|
||||
|
||||
// Escape the search term to prevent formula injection
|
||||
const escapedSearchTerm = searchTerm.replaceAll(/["\\]/g, '\\$&')
|
||||
|
||||
// Build OR(FIND("term", field1), FIND("term", field2), ...)
|
||||
const filterByFormula = `OR(${searchFieldIds
|
||||
.map((fieldId) => `FIND("${escapedSearchTerm}", {${fieldId}})`)
|
||||
.join(',')})`
|
||||
|
||||
return this.listRecords({ ...opts, baseId, tableId, filterByFormula })
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and gets the searchable text fields in a table.
|
||||
*/
|
||||
protected async validateAndGetSearchFields({
|
||||
baseId,
|
||||
tableId,
|
||||
fieldIds
|
||||
}: {
|
||||
baseId: string
|
||||
tableId: string
|
||||
fieldIds?: string[]
|
||||
}): Promise<string[]> {
|
||||
const table = await this.getTable({ baseId, tableId })
|
||||
|
||||
const searchableFieldTypes = new Set([
|
||||
'singleLineText',
|
||||
'multilineText',
|
||||
'richText',
|
||||
'email',
|
||||
'url',
|
||||
'phoneNumber'
|
||||
])
|
||||
|
||||
const searchableFieldIds = new Set(
|
||||
table.fields
|
||||
.filter((field) => searchableFieldTypes.has(field.type))
|
||||
.map((field) => field.id)
|
||||
)
|
||||
|
||||
if (!searchableFieldIds.size) {
|
||||
throw new Error('No text fields available to search')
|
||||
}
|
||||
|
||||
// If specific fields were requested, validate that they exist and are, in
|
||||
// fact, valid searchable text fields.
|
||||
if (fieldIds && fieldIds.length > 0) {
|
||||
// Check if any requested fields were invalid
|
||||
const invalidFieldIds = fieldIds.filter(
|
||||
(fieldId) => !searchableFieldIds.has(fieldId)
|
||||
)
|
||||
if (invalidFieldIds.length > 0) {
|
||||
throw new Error(
|
||||
`Invalid fields requested: ${invalidFieldIds.join(', ')}`
|
||||
)
|
||||
}
|
||||
|
||||
return fieldIds
|
||||
}
|
||||
|
||||
return Array.from(searchableFieldIds)
|
||||
}
|
||||
}
|
||||
|
||||
function transformTableDetailLevel<
|
||||
T extends airtable.TableDetailLevel = 'full'
|
||||
>({
|
||||
table,
|
||||
detailLevel
|
||||
}: {
|
||||
table: airtable.Table
|
||||
detailLevel: T
|
||||
}): airtable.AirtableTableToDetailLevel<T> {
|
||||
switch (detailLevel) {
|
||||
case 'tableIdentifiersOnly':
|
||||
return {
|
||||
id: table.id,
|
||||
name: table.name
|
||||
} as any
|
||||
|
||||
case 'identifiersOnly':
|
||||
return {
|
||||
id: table.id,
|
||||
name: table.name,
|
||||
fields: table.fields.map((field) => ({
|
||||
id: field.id,
|
||||
name: field.name
|
||||
})),
|
||||
views: table.views.map((view) => ({
|
||||
id: view.id,
|
||||
name: view.name
|
||||
}))
|
||||
} as any
|
||||
|
||||
default:
|
||||
return table as any
|
||||
}
|
||||
}
|
|
@ -0,0 +1,665 @@
|
|||
import { z } from 'zod'
|
||||
|
||||
export namespace airtable {
|
||||
export const API_BASE_URL = 'https://api.airtable.com'
|
||||
|
||||
export const BaseSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
permissionLevel: z.string()
|
||||
})
|
||||
export type Base = z.infer<typeof BaseSchema>
|
||||
|
||||
export const ListBasesResponseSchema = z.object({
|
||||
bases: z.array(BaseSchema),
|
||||
offset: z.string().optional()
|
||||
})
|
||||
export type ListBasesResponse = z.infer<typeof ListBasesResponseSchema>
|
||||
|
||||
export const FieldOptionsSchema = z
|
||||
.object({
|
||||
isReversed: z.boolean().optional(),
|
||||
inverseLinkFieldId: z.string().optional(),
|
||||
linkedTableId: z.string().optional(),
|
||||
prefersSingleRecordLink: z.boolean().optional(),
|
||||
color: z.string().optional(),
|
||||
icon: z.string().optional()
|
||||
})
|
||||
.passthrough()
|
||||
export type FieldOptions = z.infer<typeof FieldOptionsSchema>
|
||||
|
||||
export const FieldSchema = z
|
||||
.object({
|
||||
name: z.string(),
|
||||
description: z.string().optional()
|
||||
})
|
||||
.and(
|
||||
// Extracted from Airtable API docs
|
||||
z.union([
|
||||
z.object({ type: z.literal('autoNumber') }),
|
||||
z.object({ type: z.literal('barcode') }),
|
||||
z.object({ type: z.literal('button') }),
|
||||
z
|
||||
.object({
|
||||
options: z.object({
|
||||
color: z
|
||||
.enum([
|
||||
'greenBright',
|
||||
'tealBright',
|
||||
'cyanBright',
|
||||
'blueBright',
|
||||
'purpleBright',
|
||||
'pinkBright',
|
||||
'redBright',
|
||||
'orangeBright',
|
||||
'yellowBright',
|
||||
'grayBright'
|
||||
])
|
||||
.describe('The color of the checkbox.'),
|
||||
icon: z
|
||||
.enum([
|
||||
'check',
|
||||
'xCheckbox',
|
||||
'star',
|
||||
'heart',
|
||||
'thumbsUp',
|
||||
'flag',
|
||||
'dot'
|
||||
])
|
||||
.describe('The icon name of the checkbox.')
|
||||
}),
|
||||
type: z.literal('checkbox')
|
||||
})
|
||||
.describe(
|
||||
"Bases on a free or plus plan are limited to using the `'check'` icon and `'greenBright'` color."
|
||||
),
|
||||
z.object({ type: z.literal('createdBy') }),
|
||||
z.object({
|
||||
options: z.object({
|
||||
result: z
|
||||
.union([
|
||||
z.object({
|
||||
options: z.object({
|
||||
dateFormat: z.object({
|
||||
format: z
|
||||
.enum(['l', 'LL', 'M/D/YYYY', 'D/M/YYYY', 'YYYY-MM-DD'])
|
||||
.describe(
|
||||
'`format` is always provided when reading.\n(`l` for local, `LL` for friendly, `M/D/YYYY` for us, `D/M/YYYY` for european, `YYYY-MM-DD` for iso)'
|
||||
),
|
||||
name: z.enum([
|
||||
'local',
|
||||
'friendly',
|
||||
'us',
|
||||
'european',
|
||||
'iso'
|
||||
])
|
||||
})
|
||||
}),
|
||||
type: z.literal('date')
|
||||
}),
|
||||
z.object({
|
||||
options: z.object({
|
||||
dateFormat: z.object({
|
||||
format: z
|
||||
.enum(['l', 'LL', 'M/D/YYYY', 'D/M/YYYY', 'YYYY-MM-DD'])
|
||||
.describe(
|
||||
'`format` is always provided when reading.\n(`l` for local, `LL` for friendly, `M/D/YYYY` for us, `D/M/YYYY` for european, `YYYY-MM-DD` for iso)'
|
||||
),
|
||||
name: z.enum([
|
||||
'local',
|
||||
'friendly',
|
||||
'us',
|
||||
'european',
|
||||
'iso'
|
||||
])
|
||||
}),
|
||||
timeFormat: z.object({
|
||||
format: z.enum(['h:mma', 'HH:mm']),
|
||||
name: z.enum(['12hour', '24hour'])
|
||||
}),
|
||||
timeZone: z.any()
|
||||
}),
|
||||
type: z.literal('dateTime')
|
||||
})
|
||||
])
|
||||
.describe(
|
||||
'This will always be a `date` or `dateTime` field config.'
|
||||
)
|
||||
.optional()
|
||||
}),
|
||||
type: z.literal('createdTime')
|
||||
}),
|
||||
z.object({
|
||||
options: z.object({
|
||||
isValid: z
|
||||
.boolean()
|
||||
.describe(
|
||||
'`false` when recordLinkFieldId is null, e.g. the referenced column was deleted.'
|
||||
),
|
||||
recordLinkFieldId: z.union([z.string(), z.null()]).optional()
|
||||
}),
|
||||
type: z.literal('count')
|
||||
}),
|
||||
z.any(),
|
||||
z.object({
|
||||
options: z.object({
|
||||
isValid: z
|
||||
.boolean()
|
||||
.describe(
|
||||
'False if this formula/field configuation has an error'
|
||||
),
|
||||
referencedFieldIds: z
|
||||
.union([z.array(z.string()), z.null()])
|
||||
.describe('The fields to check the last modified time of'),
|
||||
result: z
|
||||
.union([
|
||||
z.object({
|
||||
options: z.object({
|
||||
dateFormat: z.object({
|
||||
format: z
|
||||
.enum(['l', 'LL', 'M/D/YYYY', 'D/M/YYYY', 'YYYY-MM-DD'])
|
||||
.describe(
|
||||
'`format` is always provided when reading.\n(`l` for local, `LL` for friendly, `M/D/YYYY` for us, `D/M/YYYY` for european, `YYYY-MM-DD` for iso)'
|
||||
),
|
||||
name: z.enum([
|
||||
'local',
|
||||
'friendly',
|
||||
'us',
|
||||
'european',
|
||||
'iso'
|
||||
])
|
||||
})
|
||||
}),
|
||||
type: z.literal('date')
|
||||
}),
|
||||
z.object({
|
||||
options: z.object({
|
||||
dateFormat: z.object({
|
||||
format: z
|
||||
.enum(['l', 'LL', 'M/D/YYYY', 'D/M/YYYY', 'YYYY-MM-DD'])
|
||||
.describe(
|
||||
'`format` is always provided when reading.\n(`l` for local, `LL` for friendly, `M/D/YYYY` for us, `D/M/YYYY` for european, `YYYY-MM-DD` for iso)'
|
||||
),
|
||||
name: z.enum([
|
||||
'local',
|
||||
'friendly',
|
||||
'us',
|
||||
'european',
|
||||
'iso'
|
||||
])
|
||||
}),
|
||||
timeFormat: z.object({
|
||||
format: z.enum(['h:mma', 'HH:mm']),
|
||||
name: z.enum(['12hour', '24hour'])
|
||||
}),
|
||||
timeZone: z.any()
|
||||
}),
|
||||
type: z.literal('dateTime')
|
||||
}),
|
||||
z.null()
|
||||
])
|
||||
.describe(
|
||||
'This will always be a `date` or `dateTime` field config.'
|
||||
)
|
||||
}),
|
||||
type: z.literal('lastModifiedTime')
|
||||
}),
|
||||
z.object({ type: z.literal('lastModifiedBy') }),
|
||||
z.object({
|
||||
options: z.object({
|
||||
fieldIdInLinkedTable: z
|
||||
.union([z.string(), z.null()])
|
||||
.describe(
|
||||
'The field in the linked table that this field is looking up.'
|
||||
),
|
||||
isValid: z
|
||||
.boolean()
|
||||
.describe(
|
||||
'Is the field currently valid (e.g. false if the linked record field has\nbeen deleted)'
|
||||
),
|
||||
recordLinkFieldId: z
|
||||
.union([z.string(), z.null()])
|
||||
.describe('The linked record field in the current table.'),
|
||||
result: z
|
||||
.union([z.any(), z.null()])
|
||||
.describe(
|
||||
'The field type and options inside of the linked table. See other field\ntype configs on this page for the possible values. Can be null if invalid.'
|
||||
)
|
||||
}),
|
||||
type: z.literal('lookup')
|
||||
}),
|
||||
z.object({
|
||||
options: z.object({
|
||||
precision: z
|
||||
.number()
|
||||
.describe(
|
||||
'Indicates the number of digits shown to the right of the decimal point for this field. (0-8 inclusive)'
|
||||
)
|
||||
}),
|
||||
type: z.literal('number')
|
||||
}),
|
||||
z.object({
|
||||
options: z.object({
|
||||
precision: z
|
||||
.number()
|
||||
.describe(
|
||||
'Indicates the number of digits shown to the right of the decimal point for this field. (0-8 inclusive)'
|
||||
)
|
||||
}),
|
||||
type: z.literal('percent')
|
||||
}),
|
||||
z.object({
|
||||
options: z.object({
|
||||
precision: z
|
||||
.number()
|
||||
.describe(
|
||||
'Indicates the number of digits shown to the right of the decimal point for this field. (0-7 inclusive)'
|
||||
),
|
||||
symbol: z.string().describe('Currency symbol to use.')
|
||||
}),
|
||||
type: z.literal('currency')
|
||||
}),
|
||||
z.object({
|
||||
options: z.object({
|
||||
durationFormat: z.enum([
|
||||
'h:mm',
|
||||
'h:mm:ss',
|
||||
'h:mm:ss.S',
|
||||
'h:mm:ss.SS',
|
||||
'h:mm:ss.SSS'
|
||||
])
|
||||
}),
|
||||
type: z.literal('duration')
|
||||
}),
|
||||
z.object({ type: z.literal('multilineText') }),
|
||||
z.object({ type: z.literal('phoneNumber') }),
|
||||
z
|
||||
.object({
|
||||
options: z.object({
|
||||
color: z
|
||||
.enum([
|
||||
'yellowBright',
|
||||
'orangeBright',
|
||||
'redBright',
|
||||
'pinkBright',
|
||||
'purpleBright',
|
||||
'blueBright',
|
||||
'cyanBright',
|
||||
'tealBright',
|
||||
'greenBright',
|
||||
'grayBright'
|
||||
])
|
||||
.describe('The color of selected icons.'),
|
||||
icon: z
|
||||
.enum(['star', 'heart', 'thumbsUp', 'flag', 'dot'])
|
||||
.describe('The icon name used to display the rating.'),
|
||||
max: z
|
||||
.number()
|
||||
.describe(
|
||||
'The maximum value for the rating, from 1 to 10 inclusive.'
|
||||
)
|
||||
}),
|
||||
type: z.literal('rating')
|
||||
})
|
||||
.describe(
|
||||
"Bases on a free or plus plan are limited to using the 'star' icon and 'yellowBright' color."
|
||||
),
|
||||
z.object({ type: z.literal('richText') }),
|
||||
z.object({
|
||||
options: z.object({
|
||||
fieldIdInLinkedTable: z
|
||||
.string()
|
||||
.describe('The id of the field in the linked table')
|
||||
.optional(),
|
||||
isValid: z.boolean().optional(),
|
||||
recordLinkFieldId: z
|
||||
.string()
|
||||
.describe('The linked field id')
|
||||
.optional(),
|
||||
referencedFieldIds: z
|
||||
.array(z.string())
|
||||
.describe(
|
||||
'The ids of any fields referenced in the rollup formula'
|
||||
)
|
||||
.optional(),
|
||||
result: z
|
||||
.union([z.any(), z.null()])
|
||||
.describe(
|
||||
'The resulting field type and options for the rollup. See other field\ntype configs on this page for the possible values. Can be null if invalid.'
|
||||
)
|
||||
.optional()
|
||||
}),
|
||||
type: z.literal('rollup')
|
||||
}),
|
||||
z.object({ type: z.literal('singleLineText') }),
|
||||
z.object({ type: z.literal('email') }),
|
||||
z.object({ type: z.literal('url') }),
|
||||
z.object({
|
||||
options: z.object({
|
||||
choices: z.array(
|
||||
z.object({
|
||||
color: z
|
||||
.string()
|
||||
.describe(
|
||||
'Optional when the select field is configured to not use colors.\n\nAllowed values: "blueLight2", "cyanLight2", "tealLight2", "greenLight2", "yellowLight2", "orangeLight2", "redLight2", "pinkLight2", "purpleLight2", "grayLight2", "blueLight1", "cyanLight1", "tealLight1", "greenLight1", "yellowLight1", "orangeLight1", "redLight1", "pinkLight1", "purpleLight1", "grayLight1", "blueBright", "cyanBright", "tealBright", "greenBright", "yellowBright", "orangeBright", "redBright", "pinkBright", "purpleBright", "grayBright", "blueDark1", "cyanDark1", "tealDark1", "greenDark1", "yellowDark1", "orangeDark1", "redDark1", "pinkDark1", "purpleDark1", "grayDark1"'
|
||||
)
|
||||
.optional(),
|
||||
id: z.string(),
|
||||
name: z.string()
|
||||
})
|
||||
)
|
||||
}),
|
||||
type: z.literal('externalSyncSource')
|
||||
}),
|
||||
z.object({
|
||||
options: z.object({
|
||||
prompt: z
|
||||
.array(
|
||||
z.union([
|
||||
z.string(),
|
||||
z.object({ field: z.object({ fieldId: z.string() }) })
|
||||
])
|
||||
)
|
||||
.describe(
|
||||
'The prompt that is used to generate the results in the AI field, additional object\ntypes may be added in the future. Currently, this is an array of strings or objects that identify any fields interpolated into the prompt.\n\nThe prompt will not currently be provided if this field config is within another\nfields configuration (like a lookup field)'
|
||||
)
|
||||
.optional(),
|
||||
referencedFieldIds: z
|
||||
.array(z.string())
|
||||
.describe(
|
||||
'The other fields in the record that are used in the ai field\n\nThe referencedFieldIds will not currently be provided if this field config is within another\nfields configuration (like a lookup field)'
|
||||
)
|
||||
.optional()
|
||||
}),
|
||||
type: z.literal('aiText')
|
||||
}),
|
||||
z
|
||||
.object({
|
||||
options: z.object({
|
||||
linkedTableId: z
|
||||
.string()
|
||||
.describe('The ID of the table this field links to'),
|
||||
viewIdForRecordSelection: z
|
||||
.string()
|
||||
.describe(
|
||||
'The ID of the view in the linked table\nto use when showing a list of records to select from'
|
||||
)
|
||||
.optional()
|
||||
}),
|
||||
type: z.literal('multipleRecordLinks')
|
||||
})
|
||||
.describe(
|
||||
'Creating "multipleRecordLinks" fields is supported but updating options for\nexisting "multipleRecordLinks" fields is not supported.'
|
||||
),
|
||||
z.object({
|
||||
options: z.object({
|
||||
choices: z.array(
|
||||
z.object({
|
||||
color: z
|
||||
.string()
|
||||
.describe('Optional when creating an option.')
|
||||
.optional(),
|
||||
id: z
|
||||
.string()
|
||||
.describe(
|
||||
'This is not specified when creating new options, useful when specifing existing\noptions (for example: reordering options, keeping old options and adding new ones, etc)'
|
||||
)
|
||||
.optional(),
|
||||
name: z.string()
|
||||
})
|
||||
)
|
||||
}),
|
||||
type: z.literal('singleSelect')
|
||||
}),
|
||||
z.object({
|
||||
options: z.object({
|
||||
choices: z.array(
|
||||
z.object({
|
||||
color: z
|
||||
.string()
|
||||
.describe('Optional when creating an option.')
|
||||
.optional(),
|
||||
id: z
|
||||
.string()
|
||||
.describe(
|
||||
'This is not specified when creating new options, useful when specifing existing\noptions (for example: reordering options, keeping old options and adding new ones, etc)'
|
||||
)
|
||||
.optional(),
|
||||
name: z.string()
|
||||
})
|
||||
)
|
||||
}),
|
||||
type: z.literal('multipleSelects')
|
||||
}),
|
||||
z.object({
|
||||
options: z.record(z.any()).optional(),
|
||||
type: z.literal('singleCollaborator')
|
||||
}),
|
||||
z.object({
|
||||
options: z.record(z.any()).optional(),
|
||||
type: z.literal('multipleCollaborators')
|
||||
}),
|
||||
z.object({
|
||||
options: z.object({
|
||||
dateFormat: z.object({
|
||||
format: z
|
||||
.enum(['l', 'LL', 'M/D/YYYY', 'D/M/YYYY', 'YYYY-MM-DD'])
|
||||
.describe(
|
||||
'Format is optional when writing, but it must match\nthe corresponding name if provided.\n\n(`l` for local, `LL` for friendly, `M/D/YYYY` for us, `D/M/YYYY` for european, `YYYY-MM-DD` for iso)'
|
||||
)
|
||||
.optional(),
|
||||
name: z.enum(['local', 'friendly', 'us', 'european', 'iso'])
|
||||
})
|
||||
}),
|
||||
type: z.literal('date')
|
||||
}),
|
||||
z.object({
|
||||
options: z.object({
|
||||
dateFormat: z.object({
|
||||
format: z
|
||||
.enum(['l', 'LL', 'M/D/YYYY', 'D/M/YYYY', 'YYYY-MM-DD'])
|
||||
.describe(
|
||||
'Format is optional when writing, but it must match\nthe corresponding name if provided.\n\n(`l` for local, `LL` for friendly, `M/D/YYYY` for us, `D/M/YYYY` for european, `YYYY-MM-DD` for iso)'
|
||||
)
|
||||
.optional(),
|
||||
name: z.enum(['local', 'friendly', 'us', 'european', 'iso'])
|
||||
}),
|
||||
timeFormat: z.object({
|
||||
format: z.enum(['h:mma', 'HH:mm']).optional(),
|
||||
name: z.enum(['12hour', '24hour'])
|
||||
}),
|
||||
timeZone: z.any()
|
||||
}),
|
||||
type: z.literal('dateTime')
|
||||
}),
|
||||
z.object({
|
||||
options: z.object({ isReversed: z.boolean() }).optional(),
|
||||
type: z.literal('multipleAttachments')
|
||||
})
|
||||
])
|
||||
)
|
||||
.describe(
|
||||
'The config of a field. NB: Formula fields cannot be created with this MCP due to a limitation in the Airtable API.'
|
||||
)
|
||||
export type Field = z.infer<typeof FieldSchema> & { id: string }
|
||||
|
||||
export const ViewSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
type: z.string()
|
||||
})
|
||||
export type View = z.infer<typeof ViewSchema>
|
||||
|
||||
export const TableSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string().optional(),
|
||||
primaryFieldId: z.string(),
|
||||
fields: z.array(FieldSchema.and(z.object({ id: z.string() }))),
|
||||
views: z.array(ViewSchema)
|
||||
})
|
||||
export type Table = z.infer<typeof TableSchema>
|
||||
|
||||
export const BaseSchemaResponseSchema = z.object({
|
||||
tables: z.array(TableSchema)
|
||||
})
|
||||
export type BaseSchemaResponse = z.infer<typeof BaseSchemaResponseSchema>
|
||||
|
||||
// Zod schemas for tool arguments
|
||||
export const ListRecordsArgsSchema = z.object({
|
||||
baseId: z.string(),
|
||||
tableId: z.string(),
|
||||
offset: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe('Offset to start the list from. Defaults to 0.'),
|
||||
maxRecords: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe('Maximum number of records to return. Defaults to 100.'),
|
||||
filterByFormula: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Airtable formula to filter records')
|
||||
})
|
||||
export type ListRecordsArgs = z.infer<typeof ListRecordsArgsSchema>
|
||||
|
||||
export const SearchRecordsArgsSchema = z.object({
|
||||
baseId: z.string(),
|
||||
tableId: z.string(),
|
||||
searchTerm: z.string().describe('Text to search for in records'),
|
||||
fieldIds: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe(
|
||||
'Specific field ids to search in. If not provided, searches all text-based fields.'
|
||||
),
|
||||
maxRecords: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe('Maximum number of records to return. Defaults to 100.')
|
||||
})
|
||||
export type SearchRecordsArgs = z.infer<typeof SearchRecordsArgsSchema>
|
||||
|
||||
export const TableDetailLevelSchema = z.enum([
|
||||
'tableIdentifiersOnly',
|
||||
'identifiersOnly',
|
||||
'full'
|
||||
]).describe(`Detail level for table information:
|
||||
- tableIdentifiersOnly: table IDs and names
|
||||
- identifiersOnly: table, field, and view IDs and names
|
||||
- full: complete details including field types, descriptions, and configurations
|
||||
|
||||
Note for LLMs: To optimize context window usage, request the minimum detail level needed:
|
||||
- Use 'tableIdentifiersOnly' when you only need to list or reference tables
|
||||
- Use 'identifiersOnly' when you need to work with field or view references
|
||||
- Only use 'full' when you need field types, descriptions, or other detailed configuration
|
||||
|
||||
If you only need detailed information on a few tables in a base with many complex tables, it might be more efficient for you to use list_tables with tableIdentifiersOnly, then describe_table with full on the specific tables you want.`)
|
||||
export type TableDetailLevel = z.infer<typeof TableDetailLevelSchema>
|
||||
|
||||
export const DescribeTableArgsSchema = z.object({
|
||||
baseId: z.string(),
|
||||
tableId: z.string(),
|
||||
detailLevel: TableDetailLevelSchema.optional().default('full')
|
||||
})
|
||||
export type DescribeTableArgs = z.infer<typeof DescribeTableArgsSchema>
|
||||
|
||||
export const ListTablesArgsSchema = z.object({
|
||||
baseId: z.string(),
|
||||
detailLevel: TableDetailLevelSchema.optional().default('full')
|
||||
})
|
||||
export type ListTablesArgs = z.infer<typeof ListTablesArgsSchema>
|
||||
|
||||
export const GetRecordArgsSchema = z.object({
|
||||
baseId: z.string(),
|
||||
tableId: z.string(),
|
||||
recordId: z.string()
|
||||
})
|
||||
export type GetRecordArgs = z.infer<typeof GetRecordArgsSchema>
|
||||
|
||||
export const CreateRecordArgsSchema = z.object({
|
||||
baseId: z.string(),
|
||||
tableId: z.string(),
|
||||
fields: z.record(z.any())
|
||||
})
|
||||
export type CreateRecordArgs = z.infer<typeof CreateRecordArgsSchema>
|
||||
|
||||
export const UpdateRecordsArgsSchema = z.object({
|
||||
baseId: z.string(),
|
||||
tableId: z.string(),
|
||||
records: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
fields: z.record(z.any())
|
||||
})
|
||||
)
|
||||
})
|
||||
export type UpdateRecordsArgs = z.infer<typeof UpdateRecordsArgsSchema>
|
||||
|
||||
export const DeleteRecordsArgsSchema = z.object({
|
||||
baseId: z.string(),
|
||||
tableId: z.string(),
|
||||
recordIds: z.array(z.string())
|
||||
})
|
||||
export type DeleteRecordsArgs = z.infer<typeof DeleteRecordsArgsSchema>
|
||||
|
||||
export const CreateTableArgsSchema = z.object({
|
||||
baseId: z.string(),
|
||||
name: z
|
||||
.string()
|
||||
.describe('Name for the new table. Must be unique in the base.'),
|
||||
description: z.string().optional(),
|
||||
fields: z.array(FieldSchema).describe(`Table fields. Rules:
|
||||
- At least one field must be specified.
|
||||
- The primary (first) field must be one of: single line text, long text, date, phone number, email, URL, number, currency, percent, duration, formula, autonumber, barcode.`)
|
||||
})
|
||||
export type CreateTableArgs = z.infer<typeof CreateTableArgsSchema>
|
||||
|
||||
export const UpdateTableArgsSchema = z.object({
|
||||
baseId: z.string(),
|
||||
tableId: z.string(),
|
||||
name: z.string().optional(),
|
||||
description: z.string().optional()
|
||||
})
|
||||
export type UpdateTableArgs = z.infer<typeof UpdateTableArgsSchema>
|
||||
|
||||
export const CreateFieldArgsSchema = z.object({
|
||||
baseId: z.string(),
|
||||
tableId: z.string(),
|
||||
// This is used as a workaround for https://github.com/orgs/modelcontextprotocol/discussions/90
|
||||
body: z.object({
|
||||
field: FieldSchema
|
||||
})
|
||||
})
|
||||
export type CreateFieldArgs = z.infer<typeof CreateFieldArgsSchema>
|
||||
|
||||
export const UpdateFieldArgsSchema = z.object({
|
||||
baseId: z.string(),
|
||||
tableId: z.string(),
|
||||
fieldId: z.string(),
|
||||
name: z.string().optional(),
|
||||
description: z.string().optional()
|
||||
})
|
||||
export type UpdateFieldArgs = z.infer<typeof UpdateFieldArgsSchema>
|
||||
|
||||
export type FieldSet = Record<string, any>
|
||||
export type AirtableRecord = { id: string; fields: FieldSet }
|
||||
|
||||
export type AirtableTableToDetailLevel<
|
||||
TDetailLevel extends TableDetailLevel | undefined = 'full'
|
||||
> = TDetailLevel extends undefined
|
||||
? airtable.Table
|
||||
: TDetailLevel extends 'full'
|
||||
? airtable.Table
|
||||
: TDetailLevel extends 'identifiersOnly'
|
||||
? {
|
||||
id: string
|
||||
name: string
|
||||
fields: { id: string; name: string }[]
|
||||
views: { id: string; name: string }[]
|
||||
}
|
||||
: TDetailLevel extends 'tableIdentifiersOnly'
|
||||
? { id: string; name: string }
|
||||
: never
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from './airtable'
|
||||
export * from './airtable-client'
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": "@fisch0920/config/tsconfig-node",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
|
@ -293,7 +293,7 @@ export class ApolloClient extends AIFunctionsProvider {
|
|||
} = {}) {
|
||||
assert(
|
||||
apiKey,
|
||||
`ApolloClient missing required "username" (defaults to "APOLLO_API_KEY")`
|
||||
`ApolloClient missing required "apiKey" (defaults to "APOLLO_API_KEY")`
|
||||
)
|
||||
super()
|
||||
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/arxiv"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/bing"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/calculator"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/clearbit"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/core"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/dexa"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/dexter"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/diffbot"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/duck-duck-go"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/e2b"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/exa"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/firecrawl"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/genkit"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/github"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"name": "@agentic/google-docs",
|
||||
"version": "7.6.3",
|
||||
"description": "Agentic SDK for Google Docs.",
|
||||
"author": "Travis Fischer <travis@transitivebullsh.it>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/google-docs"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
"types": "./dist/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"clean": "del dist",
|
||||
"test": "run-s test:*",
|
||||
"test:lint": "eslint .",
|
||||
"test:typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentic/core": "workspace:*",
|
||||
"type-fest": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"googleapis": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"googleapis": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<p align="center">
|
||||
<a href="https://agentic.so">
|
||||
<img alt="Agentic" src="https://raw.githubusercontent.com/transitive-bullshit/agentic/main/docs/media/agentic-header.jpg" width="308">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<em>AI agent stdlib that works with any LLM and TypeScript AI SDK.</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml"><img alt="Build Status" src="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml/badge.svg" /></a>
|
||||
<a href="https://www.npmjs.com/package/@agentic/stdlib"><img alt="NPM" src="https://img.shields.io/npm/v/@agentic/stdlib.svg" /></a>
|
||||
<a href="https://github.com/transitive-bullshit/agentic/blob/main/license"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue" /></a>
|
||||
<a href="https://prettier.io"><img alt="Prettier Code Formatting" src="https://img.shields.io/badge/code_style-prettier-brightgreen.svg" /></a>
|
||||
</p>
|
||||
|
||||
# Agentic
|
||||
|
||||
**See the [github repo](https://github.com/transitive-bullshit/agentic) or [docs](https://agentic.so) for more info.**
|
||||
|
||||
## License
|
||||
|
||||
MIT © [Travis Fischer](https://x.com/transitive_bs)
|
|
@ -0,0 +1,76 @@
|
|||
import type * as google from 'googleapis'
|
||||
import type { SetNonNullable, Simplify } from 'type-fest'
|
||||
import {
|
||||
aiFunction,
|
||||
AIFunctionsProvider,
|
||||
pruneNullOrUndefinedDeep,
|
||||
type SetRequired
|
||||
} from '@agentic/core'
|
||||
import { z } from 'zod'
|
||||
|
||||
export namespace googleDocs {
|
||||
export type Document = Simplify<
|
||||
SetNonNullable<google.docs_v1.Schema$Document>
|
||||
>
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplified Google Docs API client.
|
||||
*
|
||||
* @see https://developers.google.com/workspace/drive/api
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { GoogleDocsClient } from '@agentic/google-docs'
|
||||
* import { authenticate } from '@google-cloud/local-auth'
|
||||
* import { google } from 'googleapis'
|
||||
*
|
||||
* // (in a real app, store these auth credentials and reuse them)
|
||||
* const auth = await authenticate({
|
||||
* scopes: ['https://www.googleapis.com/auth/documents.readonly'],
|
||||
* keyfilePath: process.env.GOOGLE_CREDENTIALS_PATH
|
||||
* })
|
||||
* const docs = google.docs({ version: 'v1', auth })
|
||||
* const client = new GoogleDocsClient({ docs })
|
||||
* ```
|
||||
*/
|
||||
export class GoogleDocsClient extends AIFunctionsProvider {
|
||||
protected readonly docs: google.docs_v1.Docs
|
||||
|
||||
constructor({ docs }: { docs: google.docs_v1.Docs }) {
|
||||
super()
|
||||
|
||||
this.docs = docs
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Google Docs document by ID.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'google_docs_get_document',
|
||||
description: 'Gets a Google Docs document by ID.',
|
||||
inputSchema: z.object({
|
||||
documentId: z.string()
|
||||
})
|
||||
})
|
||||
async getDocument(
|
||||
args: Simplify<
|
||||
SetRequired<google.docs_v1.Params$Resource$Documents$Get, 'documentId'>
|
||||
>
|
||||
): Promise<googleDocs.Document> {
|
||||
const { documentId, ...opts } = args
|
||||
|
||||
const { data } = await this.docs.documents.get({
|
||||
...opts,
|
||||
documentId
|
||||
})
|
||||
|
||||
return convertDocument(data)
|
||||
}
|
||||
}
|
||||
|
||||
function convertDocument(
|
||||
data: google.docs_v1.Schema$Document
|
||||
): googleDocs.Document {
|
||||
return pruneNullOrUndefinedDeep(data)
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './google-docs-client'
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": "@fisch0920/config/tsconfig-node",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"name": "@agentic/google-drive",
|
||||
"version": "7.6.3",
|
||||
"description": "Agentic SDK for Google Drive.",
|
||||
"author": "Travis Fischer <travis@transitivebullsh.it>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/google-drive"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
"types": "./dist/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"clean": "del dist",
|
||||
"test": "run-s test:*",
|
||||
"test:lint": "eslint .",
|
||||
"test:typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentic/core": "workspace:*",
|
||||
"type-fest": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"googleapis": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"googleapis": "catalog:",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<p align="center">
|
||||
<a href="https://agentic.so">
|
||||
<img alt="Agentic" src="https://raw.githubusercontent.com/transitive-bullshit/agentic/main/docs/media/agentic-header.jpg" width="308">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<em>AI agent stdlib that works with any LLM and TypeScript AI SDK.</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml"><img alt="Build Status" src="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml/badge.svg" /></a>
|
||||
<a href="https://www.npmjs.com/package/@agentic/stdlib"><img alt="NPM" src="https://img.shields.io/npm/v/@agentic/stdlib.svg" /></a>
|
||||
<a href="https://github.com/transitive-bullshit/agentic/blob/main/license"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue" /></a>
|
||||
<a href="https://prettier.io"><img alt="Prettier Code Formatting" src="https://img.shields.io/badge/code_style-prettier-brightgreen.svg" /></a>
|
||||
</p>
|
||||
|
||||
# Agentic
|
||||
|
||||
**See the [github repo](https://github.com/transitive-bullshit/agentic) or [docs](https://agentic.so) for more info.**
|
||||
|
||||
## License
|
||||
|
||||
MIT © [Travis Fischer](https://x.com/transitive_bs)
|
|
@ -0,0 +1,201 @@
|
|||
import type * as google from 'googleapis'
|
||||
import type { SetNonNullable, Simplify } from 'type-fest'
|
||||
import {
|
||||
aiFunction,
|
||||
AIFunctionsProvider,
|
||||
pick,
|
||||
pruneNullOrUndefined,
|
||||
pruneNullOrUndefinedDeep
|
||||
} from '@agentic/core'
|
||||
import { z } from 'zod'
|
||||
|
||||
export namespace googleDrive {
|
||||
export type File = Simplify<
|
||||
SetNonNullable<
|
||||
Pick<
|
||||
google.drive_v3.Schema$File,
|
||||
| 'id'
|
||||
| 'name'
|
||||
| 'mimeType'
|
||||
| 'webViewLink'
|
||||
| 'webContentLink'
|
||||
| 'size'
|
||||
| 'createdTime'
|
||||
| 'modifiedTime'
|
||||
| 'parents'
|
||||
>
|
||||
>
|
||||
>
|
||||
|
||||
export const fileFields: readonly (keyof File)[] = [
|
||||
'id',
|
||||
'name',
|
||||
'mimeType',
|
||||
'webViewLink',
|
||||
'webContentLink',
|
||||
'size',
|
||||
'createdTime',
|
||||
'modifiedTime',
|
||||
'parents'
|
||||
]
|
||||
export const requestFileFields = `files(${fileFields.join(',')}),nextPageToken`
|
||||
|
||||
export interface ListFilesResponse {
|
||||
files: File[]
|
||||
nextPageToken?: string
|
||||
}
|
||||
|
||||
export interface DownloadResponse {
|
||||
content: string
|
||||
metadata: File
|
||||
}
|
||||
|
||||
export const ListFilesParamsSchema = z.object({
|
||||
folderId: z.string().optional(),
|
||||
query: z.string().optional(),
|
||||
pageSize: z.number().optional(),
|
||||
pageToken: z.string().optional()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplified Google Drive API client.
|
||||
*
|
||||
* @see https://developers.google.com/workspace/drive/api
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { GoogleDriveClient } from '@agentic/google-drive'
|
||||
* import { GoogleAuth } from 'google-auth-library'
|
||||
* import { google } from 'googleapis'
|
||||
*
|
||||
* const auth = new GoogleAuth({ scopes: 'https://www.googleapis.com/auth/drive' })
|
||||
* const drive = google.drive({ version: 'v3', auth })
|
||||
* const client = new GoogleDriveClient({ drive })
|
||||
* ```
|
||||
*/
|
||||
export class GoogleDriveClient extends AIFunctionsProvider {
|
||||
protected readonly drive: google.drive_v3.Drive
|
||||
|
||||
constructor({ drive }: { drive: google.drive_v3.Drive }) {
|
||||
super()
|
||||
|
||||
this.drive = drive
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists files and folders in a Google Drive folder.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'google_drive_list_files',
|
||||
description: 'Lists files and folders in a Google Drive folder.',
|
||||
inputSchema: googleDrive.ListFilesParamsSchema
|
||||
})
|
||||
async listFiles(
|
||||
args: {
|
||||
folderId?: string
|
||||
query?: string
|
||||
} & google.drive_v3.Params$Resource$Files$Get
|
||||
): Promise<googleDrive.ListFilesResponse> {
|
||||
const { folderId, query, ...opts } = args
|
||||
// Build the query conditions
|
||||
const conditions = ['trashed = false'] // Always exclude trashed files
|
||||
|
||||
if (folderId) {
|
||||
conditions.push(`'${folderId}' in parents`)
|
||||
}
|
||||
|
||||
if (query) {
|
||||
conditions.push(`name contains '${query}'`)
|
||||
}
|
||||
|
||||
// Combine all conditions with AND
|
||||
const q = conditions.join(' and ')
|
||||
|
||||
const { data } = await this.drive.files.list({
|
||||
fields: googleDrive.requestFileFields,
|
||||
...opts,
|
||||
q
|
||||
})
|
||||
const files = (data.files ?? []).map(convertFile)
|
||||
|
||||
return pruneNullOrUndefined({
|
||||
files,
|
||||
nextPageToken: data.nextPageToken
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a file's metadata from Google Drive.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'google_drive_get_file',
|
||||
description: "Gets a file's metadata from Google Drive.",
|
||||
inputSchema: z.object({ fileId: z.string() })
|
||||
})
|
||||
async getFile(
|
||||
opts: google.drive_v3.Params$Resource$Files$Get
|
||||
): Promise<googleDrive.File> {
|
||||
const { data } = await this.drive.files.get({
|
||||
fields: googleDrive.requestFileFields,
|
||||
...opts
|
||||
})
|
||||
|
||||
return convertFile(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a file from Google Drive.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'google_drive_export_file',
|
||||
description: 'Exports a file from Google Drive to a given mime-type.',
|
||||
inputSchema: z.object({
|
||||
fileId: z.string().describe('The ID of the file to export.'),
|
||||
mimeType: z
|
||||
.string()
|
||||
.describe('The MIME type of the format requested for this export.')
|
||||
})
|
||||
})
|
||||
async exportFile(
|
||||
opts: google.drive_v3.Params$Resource$Files$Export
|
||||
): Promise<unknown> {
|
||||
return this.drive.files.export(opts)
|
||||
}
|
||||
|
||||
@aiFunction({
|
||||
name: 'google_drive_create_folder',
|
||||
description: 'Creates a new folder in Google Drive.',
|
||||
inputSchema: z.object({
|
||||
name: z.string().describe('The name of the folder to create.'),
|
||||
parentId: z.string().describe('The ID of the parent folder.').optional()
|
||||
})
|
||||
})
|
||||
async createFolder(
|
||||
opts: Omit<
|
||||
google.drive_v3.Params$Resource$Files$Create,
|
||||
'media' | 'requestBody'
|
||||
> & {
|
||||
name: string
|
||||
parentId?: string
|
||||
}
|
||||
): Promise<googleDrive.File> {
|
||||
const { data } = await this.drive.files.create({
|
||||
requestBody: {
|
||||
mimeType: 'application/vnd.google-apps.folder',
|
||||
name: opts.name,
|
||||
parents: opts.parentId ? [opts.parentId] : undefined
|
||||
},
|
||||
fields: googleDrive.requestFileFields,
|
||||
...opts
|
||||
})
|
||||
|
||||
return convertFile(data)
|
||||
}
|
||||
}
|
||||
|
||||
function convertFile(data: google.drive_v3.Schema$File): googleDrive.File {
|
||||
return pruneNullOrUndefinedDeep(
|
||||
pick(data, ...googleDrive.fileFields)
|
||||
) as googleDrive.File
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './google-drive-client'
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": "@fisch0920/config/tsconfig-node",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/hacker-news"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/hunter"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/jigsawstack"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/jina"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/langchain"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -131,7 +131,7 @@ export class LeadMagicClient extends AIFunctionsProvider {
|
|||
} = {}) {
|
||||
assert(
|
||||
apiKey,
|
||||
`LeadMagicClient missing required "username" (defaults to "LEADMAGIC_API_KEY")`
|
||||
`LeadMagicClient missing required "apiKey" (defaults to "LEADMAGIC_API_KEY")`
|
||||
)
|
||||
super()
|
||||
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/llamaindex"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/mastra"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/mcp"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/midjourney"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/novu"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/open-meteo"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -28,10 +28,12 @@ export class OpenMeteoClient extends AIFunctionsProvider {
|
|||
constructor({
|
||||
apiKey = getEnv('OPEN_METEO_API_KEY'),
|
||||
apiBaseUrl = openmeteo.apiBaseUrl,
|
||||
timeoutMs = 60_000,
|
||||
ky = defaultKy
|
||||
}: {
|
||||
apiKey?: string
|
||||
apiBaseUrl?: string
|
||||
timeoutMs?: number
|
||||
ky?: KyInstance
|
||||
} = {}) {
|
||||
super()
|
||||
|
@ -41,6 +43,7 @@ export class OpenMeteoClient extends AIFunctionsProvider {
|
|||
|
||||
this.ky = ky.extend({
|
||||
prefixUrl: apiBaseUrl,
|
||||
timeout: timeoutMs,
|
||||
...(apiKey
|
||||
? {
|
||||
headers: {
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/openapi-to-ts"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/people-data-labs"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/perigon"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/polygon"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/predict-leads"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/proxycurl"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "@agentic/reddit",
|
||||
"version": "0.1.0",
|
||||
"description": "Agentic SDK for Reddit.",
|
||||
"author": "Travis Fischer <travis@transitivebullsh.it>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/reddit"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
"types": "./dist/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"clean": "del dist",
|
||||
"test": "run-s test:*",
|
||||
"test:lint": "eslint .",
|
||||
"test:typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentic/core": "workspace:*",
|
||||
"ky": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<p align="center">
|
||||
<a href="https://agentic.so">
|
||||
<img alt="Agentic" src="https://raw.githubusercontent.com/transitive-bullshit/agentic/main/docs/media/agentic-header.jpg" width="308">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<em>AI agent stdlib that works with any LLM and TypeScript AI SDK.</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml"><img alt="Build Status" src="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml/badge.svg" /></a>
|
||||
<a href="https://www.npmjs.com/package/@agentic/stdlib"><img alt="NPM" src="https://img.shields.io/npm/v/@agentic/stdlib.svg" /></a>
|
||||
<a href="https://github.com/transitive-bullshit/agentic/blob/main/license"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue" /></a>
|
||||
<a href="https://prettier.io"><img alt="Prettier Code Formatting" src="https://img.shields.io/badge/code_style-prettier-brightgreen.svg" /></a>
|
||||
</p>
|
||||
|
||||
# Agentic
|
||||
|
||||
**See the [github repo](https://github.com/transitive-bullshit/agentic) or [docs](https://agentic.so) for more info.**
|
||||
|
||||
## License
|
||||
|
||||
MIT © [Travis Fischer](https://x.com/transitive_bs)
|
|
@ -0,0 +1 @@
|
|||
export * from './reddit-client'
|
|
@ -0,0 +1,455 @@
|
|||
import {
|
||||
aiFunction,
|
||||
AIFunctionsProvider,
|
||||
pick,
|
||||
sanitizeSearchParams
|
||||
} from '@agentic/core'
|
||||
import defaultKy, { type KyInstance } from 'ky'
|
||||
import { z } from 'zod'
|
||||
|
||||
export namespace reddit {
|
||||
export const BASE_URL = 'https://www.reddit.com'
|
||||
|
||||
export interface Post {
|
||||
id: string
|
||||
name: string // name is `t3_<id>`
|
||||
title: string
|
||||
subreddit: string
|
||||
selftext?: string
|
||||
author: string
|
||||
author_fullname: string
|
||||
url: string
|
||||
permalink: string
|
||||
thumbnail?: string
|
||||
thumbnail_width?: number
|
||||
thumbnail_height?: number
|
||||
score: number
|
||||
ups: number
|
||||
downs: number
|
||||
num_comments: number
|
||||
created_utc: number
|
||||
is_self: boolean
|
||||
is_video: boolean
|
||||
}
|
||||
|
||||
export interface FullPost {
|
||||
id: string
|
||||
name: string
|
||||
author: string
|
||||
title: string
|
||||
subreddit: string
|
||||
subreddit_name_prefixed: string
|
||||
score: number
|
||||
approved_at_utc: string | null
|
||||
selftext?: string
|
||||
author_fullname: string
|
||||
is_self: boolean
|
||||
saved: boolean
|
||||
url: string
|
||||
permalink: string
|
||||
mod_reason_title: string | null
|
||||
gilded: number
|
||||
clicked: boolean
|
||||
link_flair_richtext: any[]
|
||||
hidden: boolean
|
||||
pwls: number
|
||||
link_flair_css_class: string
|
||||
downs: number
|
||||
thumbnail_height: any
|
||||
top_awarded_type: any
|
||||
hide_score: boolean
|
||||
quarantine: boolean
|
||||
link_flair_text_color: string
|
||||
upvote_ratio: number
|
||||
author_flair_background_color: any
|
||||
subreddit_type: string
|
||||
ups: number
|
||||
total_awards_received: number
|
||||
media_embed?: any
|
||||
secure_media_embed?: any
|
||||
thumbnail_width: any
|
||||
author_flair_template_id: any
|
||||
is_original_content: boolean
|
||||
user_reports: any[]
|
||||
secure_media: any
|
||||
is_reddit_media_domain: boolean
|
||||
is_meta: boolean
|
||||
category: any
|
||||
link_flair_text: string
|
||||
can_mod_post: boolean
|
||||
approved_by: any
|
||||
is_created_from_ads_ui: boolean
|
||||
author_premium: boolean
|
||||
thumbnail?: string
|
||||
edited: boolean
|
||||
author_flair_css_class: any
|
||||
author_flair_richtext: any[]
|
||||
gildings?: any
|
||||
content_categories: any
|
||||
mod_note: any
|
||||
created: number
|
||||
link_flair_type: string
|
||||
wls: number
|
||||
removed_by_category: any
|
||||
banned_by: any
|
||||
author_flair_type: string
|
||||
domain: string
|
||||
allow_live_comments: boolean
|
||||
selftext_html: string
|
||||
likes: any
|
||||
suggested_sort: any
|
||||
banned_at_utc: any
|
||||
view_count: any
|
||||
archived: boolean
|
||||
no_follow: boolean
|
||||
is_crosspostable: boolean
|
||||
pinned: boolean
|
||||
over_18: boolean
|
||||
all_awardings: any[]
|
||||
awarders: any[]
|
||||
media_only: boolean
|
||||
link_flair_template_id: string
|
||||
can_gild: boolean
|
||||
spoiler: boolean
|
||||
locked: boolean
|
||||
author_flair_text: any
|
||||
treatment_tags: any[]
|
||||
visited: boolean
|
||||
removed_by: any
|
||||
num_reports: any
|
||||
distinguished: any
|
||||
subreddit_id: string
|
||||
author_is_blocked: boolean
|
||||
mod_reason_by: any
|
||||
removal_reason: any
|
||||
link_flair_background_color: string
|
||||
is_robot_indexable: boolean
|
||||
report_reasons: any
|
||||
discussion_type: any
|
||||
num_comments: number
|
||||
send_replies: boolean
|
||||
contest_mode: boolean
|
||||
mod_reports: any[]
|
||||
author_patreon_flair: boolean
|
||||
author_flair_text_color: any
|
||||
stickied: boolean
|
||||
subreddit_subscribers: number
|
||||
created_utc: number
|
||||
num_crossposts: number
|
||||
media?: any
|
||||
is_video: boolean
|
||||
|
||||
// preview images
|
||||
preview?: {
|
||||
enabled: boolean
|
||||
images: Array<{
|
||||
id: string
|
||||
source: Image
|
||||
resolutions: Image[]
|
||||
variants?: Record<
|
||||
string,
|
||||
{
|
||||
id: string
|
||||
source: Image
|
||||
resolutions: Image[]
|
||||
}
|
||||
>
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export interface Image {
|
||||
url: string
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export interface PostT3 {
|
||||
kind: 't3'
|
||||
data: FullPost
|
||||
}
|
||||
|
||||
export interface PostListingResponse {
|
||||
kind: 'Listing'
|
||||
data: {
|
||||
after: string
|
||||
dist: number
|
||||
modhash: string
|
||||
geo_filter?: null
|
||||
children: PostT3[]
|
||||
}
|
||||
before?: null
|
||||
}
|
||||
|
||||
export type PostFilter = 'hot' | 'top' | 'new' | 'rising'
|
||||
|
||||
export type GeoFilter =
|
||||
| 'GLOBAL'
|
||||
| 'US'
|
||||
| 'AR'
|
||||
| 'AU'
|
||||
| 'BG'
|
||||
| 'CA'
|
||||
| 'CL'
|
||||
| 'CO'
|
||||
| 'HR'
|
||||
| 'CZ'
|
||||
| 'FI'
|
||||
| 'FR'
|
||||
| 'DE'
|
||||
| 'GR'
|
||||
| 'HU'
|
||||
| 'IS'
|
||||
| 'IN'
|
||||
| 'IE'
|
||||
| 'IT'
|
||||
| 'JP'
|
||||
| 'MY'
|
||||
| 'MX'
|
||||
| 'NZ'
|
||||
| 'PH'
|
||||
| 'PL'
|
||||
| 'PT'
|
||||
| 'PR'
|
||||
| 'RO'
|
||||
| 'RS'
|
||||
| 'SG'
|
||||
| 'ES'
|
||||
| 'SE'
|
||||
| 'TW'
|
||||
| 'TH'
|
||||
| 'TR'
|
||||
| 'GB'
|
||||
| 'US_WA'
|
||||
| 'US_DE'
|
||||
| 'US_DC'
|
||||
| 'US_WI'
|
||||
| 'US_WV'
|
||||
| 'US_HI'
|
||||
| 'US_FL'
|
||||
| 'US_WY'
|
||||
| 'US_NH'
|
||||
| 'US_NJ'
|
||||
| 'US_NM'
|
||||
| 'US_TX'
|
||||
| 'US_LA'
|
||||
| 'US_NC'
|
||||
| 'US_ND'
|
||||
| 'US_NE'
|
||||
| 'US_TN'
|
||||
| 'US_NY'
|
||||
| 'US_PA'
|
||||
| 'US_CA'
|
||||
| 'US_NV'
|
||||
| 'US_VA'
|
||||
| 'US_CO'
|
||||
| 'US_AK'
|
||||
| 'US_AL'
|
||||
| 'US_AR'
|
||||
| 'US_VT'
|
||||
| 'US_IL'
|
||||
| 'US_GA'
|
||||
| 'US_IN'
|
||||
| 'US_IA'
|
||||
| 'US_OK'
|
||||
| 'US_AZ'
|
||||
| 'US_ID'
|
||||
| 'US_CT'
|
||||
| 'US_ME'
|
||||
| 'US_MD'
|
||||
| 'US_MA'
|
||||
| 'US_OH'
|
||||
| 'US_UT'
|
||||
| 'US_MO'
|
||||
| 'US_MN'
|
||||
| 'US_MI'
|
||||
| 'US_RI'
|
||||
| 'US_KS'
|
||||
| 'US_MT'
|
||||
| 'US_MS'
|
||||
| 'US_SC'
|
||||
| 'US_KY'
|
||||
| 'US_OR'
|
||||
| 'US_SD'
|
||||
|
||||
export type TimePeriod = 'hour' | 'day' | 'week' | 'month' | 'year' | 'all'
|
||||
|
||||
export type GetSubredditPostsOptions = {
|
||||
subreddit: string
|
||||
type?: PostFilter
|
||||
|
||||
// Pagination size and offset (count)
|
||||
limit?: number
|
||||
count?: number
|
||||
|
||||
// Pagination by fullnames of posts
|
||||
before?: string
|
||||
after?: string
|
||||
|
||||
/**
|
||||
* Geographical filter. Only applicable to 'hot' posts.
|
||||
*/
|
||||
geo?: GeoFilter
|
||||
|
||||
/**
|
||||
* Filter by time period. Only applicable to 'top' posts.
|
||||
*/
|
||||
time?: TimePeriod
|
||||
}
|
||||
|
||||
export interface PostListingResult {
|
||||
subreddit: string
|
||||
type: PostFilter
|
||||
geo?: GeoFilter
|
||||
time?: TimePeriod
|
||||
posts: Post[]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic readonly Reddit API for fetching top/hot/new/rising posts from subreddits.
|
||||
*
|
||||
* Uses Reddit's legacy JSON API aimed at RSS feeds.
|
||||
*
|
||||
* @see https://old.reddit.com/dev/api
|
||||
*/
|
||||
export class RedditClient extends AIFunctionsProvider {
|
||||
protected readonly ky: KyInstance
|
||||
protected readonly baseUrl: string
|
||||
|
||||
constructor({
|
||||
baseUrl = reddit.BASE_URL,
|
||||
userAgent = 'agentic-reddit-client/1.0.0',
|
||||
timeoutMs = 60_000,
|
||||
ky = defaultKy
|
||||
}: {
|
||||
baseUrl?: string
|
||||
userAgent?: string
|
||||
timeoutMs?: number
|
||||
ky?: KyInstance
|
||||
} = {}) {
|
||||
super()
|
||||
|
||||
this.baseUrl = baseUrl
|
||||
|
||||
this.ky = ky.extend({
|
||||
prefixUrl: this.baseUrl,
|
||||
timeout: timeoutMs,
|
||||
headers: {
|
||||
'User-Agent': userAgent
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches posts from a subreddit.
|
||||
*
|
||||
* @see https://old.reddit.com/dev/api/#GET_hot
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'reddit_get_subreddit_posts',
|
||||
description: 'Fetches posts from a subreddit.',
|
||||
inputSchema: z.object({
|
||||
subreddit: z.string().describe('The subreddit to fetch posts from.'),
|
||||
type: z
|
||||
.union([
|
||||
z.literal('hot'),
|
||||
z.literal('top'),
|
||||
z.literal('new'),
|
||||
z.literal('rising')
|
||||
])
|
||||
.optional()
|
||||
.describe('Type of posts to fetch (defaults to "hot").'),
|
||||
limit: z
|
||||
.number()
|
||||
.int()
|
||||
.max(100)
|
||||
.optional()
|
||||
.describe('Max number of posts to return (defaults to 5).'),
|
||||
count: z
|
||||
.number()
|
||||
.int()
|
||||
.optional()
|
||||
.describe('Number of posts to offset by (defaults to 0).'),
|
||||
time: z
|
||||
.union([
|
||||
z.literal('hour'),
|
||||
z.literal('day'),
|
||||
z.literal('week'),
|
||||
z.literal('month'),
|
||||
z.literal('year'),
|
||||
z.literal('all')
|
||||
])
|
||||
.optional()
|
||||
.describe(
|
||||
'Time period to filter posts by (defaults to "all"). Only applicable to "top" posts type.'
|
||||
)
|
||||
})
|
||||
})
|
||||
async getSubredditPosts(
|
||||
subredditOrOpts: string | reddit.GetSubredditPostsOptions
|
||||
): Promise<reddit.PostListingResult> {
|
||||
const params =
|
||||
typeof subredditOrOpts === 'string'
|
||||
? { subreddit: subredditOrOpts }
|
||||
: subredditOrOpts
|
||||
const { subreddit, type = 'hot', limit = 5, geo, time, ...opts } = params
|
||||
|
||||
const res = await this.ky
|
||||
.get(`r/${subreddit}/${type}.json`, {
|
||||
searchParams: sanitizeSearchParams({
|
||||
...opts,
|
||||
limit,
|
||||
g: type === 'hot' ? geo : undefined,
|
||||
t: type === 'top' ? time : undefined
|
||||
})
|
||||
})
|
||||
.json<reddit.PostListingResponse>()
|
||||
|
||||
return {
|
||||
subreddit,
|
||||
type,
|
||||
geo: type === 'hot' ? geo : undefined,
|
||||
time: type === 'top' ? time : undefined,
|
||||
posts: res.data.children.map((child) => {
|
||||
const post = child.data
|
||||
|
||||
// Trim the post data to only include the bare minimum
|
||||
// TODO: add preview images
|
||||
// TODO: add video media info
|
||||
return {
|
||||
...pick(
|
||||
post,
|
||||
'id',
|
||||
'name',
|
||||
'title',
|
||||
'subreddit',
|
||||
'selftext',
|
||||
'author',
|
||||
'author_fullname',
|
||||
'url',
|
||||
'permalink',
|
||||
'thumbnail',
|
||||
'thumbnail_width',
|
||||
'thumbnail_height',
|
||||
'score',
|
||||
'ups',
|
||||
'downs',
|
||||
'num_comments',
|
||||
'created_utc',
|
||||
'is_self',
|
||||
'is_video'
|
||||
),
|
||||
permalink: `${this.baseUrl}${post.permalink}`,
|
||||
thumbnail:
|
||||
post.thumbnail !== 'self' &&
|
||||
post.thumbnail !== 'default' &&
|
||||
post.thumbnail !== 'spoiler' &&
|
||||
post.thumbnail !== 'nsfw'
|
||||
? post.thumbnail
|
||||
: undefined
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": "@fisch0920/config/tsconfig-node",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
|
@ -163,7 +163,7 @@ export class RocketReachClient extends AIFunctionsProvider {
|
|||
} = {}) {
|
||||
assert(
|
||||
apiKey,
|
||||
`RocketReachClient missing required "username" (defaults to "ROCKETREACH_API_KEY")`
|
||||
`RocketReachClient missing required "apiKey" (defaults to "ROCKETREACH_API_KEY")`
|
||||
)
|
||||
super()
|
||||
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/searxng"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/serpapi"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/serper"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/slack"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/social-data"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/stdlib"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
@ -31,6 +32,7 @@
|
|||
"test:typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentic/airtable": "workspace:*",
|
||||
"@agentic/apollo": "workspace:*",
|
||||
"@agentic/arxiv": "workspace:*",
|
||||
"@agentic/bing": "workspace:*",
|
||||
|
@ -46,6 +48,8 @@
|
|||
"@agentic/firecrawl": "workspace:*",
|
||||
"@agentic/github": "workspace:*",
|
||||
"@agentic/google-custom-search": "workspace:*",
|
||||
"@agentic/google-docs": "workspace:*",
|
||||
"@agentic/google-drive": "workspace:*",
|
||||
"@agentic/gravatar": "workspace:*",
|
||||
"@agentic/hacker-news": "workspace:*",
|
||||
"@agentic/hunter": "workspace:*",
|
||||
|
@ -61,6 +65,7 @@
|
|||
"@agentic/polygon": "workspace:*",
|
||||
"@agentic/predict-leads": "workspace:*",
|
||||
"@agentic/proxycurl": "workspace:*",
|
||||
"@agentic/reddit": "workspace:*",
|
||||
"@agentic/rocketreach": "workspace:*",
|
||||
"@agentic/searxng": "workspace:*",
|
||||
"@agentic/serpapi": "workspace:*",
|
||||
|
@ -70,10 +75,12 @@
|
|||
"@agentic/tavily": "workspace:*",
|
||||
"@agentic/twilio": "workspace:*",
|
||||
"@agentic/twitter": "workspace:*",
|
||||
"@agentic/typeform": "workspace:*",
|
||||
"@agentic/weather": "workspace:*",
|
||||
"@agentic/wikidata": "workspace:*",
|
||||
"@agentic/wikipedia": "workspace:*",
|
||||
"@agentic/wolfram-alpha": "workspace:*",
|
||||
"@agentic/youtube": "workspace:*",
|
||||
"@agentic/zoominfo": "workspace:*",
|
||||
"@e2b/code-interpreter": "catalog:"
|
||||
},
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * from '@agentic/airtable'
|
||||
export * from '@agentic/apollo'
|
||||
export * from '@agentic/arxiv'
|
||||
export * from '@agentic/bing'
|
||||
|
@ -12,6 +13,8 @@ export * from '@agentic/exa'
|
|||
export * from '@agentic/firecrawl'
|
||||
export * from '@agentic/github'
|
||||
export * from '@agentic/google-custom-search'
|
||||
export * from '@agentic/google-docs'
|
||||
export * from '@agentic/google-drive'
|
||||
export * from '@agentic/gravatar'
|
||||
export * from '@agentic/hacker-news'
|
||||
export * from '@agentic/hunter'
|
||||
|
@ -27,6 +30,7 @@ export * from '@agentic/perigon'
|
|||
export * from '@agentic/polygon'
|
||||
export * from '@agentic/predict-leads'
|
||||
export * from '@agentic/proxycurl'
|
||||
export * from '@agentic/reddit'
|
||||
export * from '@agentic/rocketreach'
|
||||
export * from '@agentic/searxng'
|
||||
export * from '@agentic/serpapi'
|
||||
|
@ -36,8 +40,10 @@ export * from '@agentic/social-data'
|
|||
export * from '@agentic/tavily'
|
||||
export * from '@agentic/twilio'
|
||||
export * from '@agentic/twitter'
|
||||
export * from '@agentic/typeform'
|
||||
export * from '@agentic/weather'
|
||||
export * from '@agentic/wikidata'
|
||||
export * from '@agentic/wikipedia'
|
||||
export * from '@agentic/wolfram-alpha'
|
||||
export * from '@agentic/youtube'
|
||||
export * from '@agentic/zoominfo'
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/tavily"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/twilio"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/twitter"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -141,10 +141,10 @@ const twitterApiRateLimitsByPlan: Record<
|
|||
}
|
||||
|
||||
/**
|
||||
* Twitter API v2 client wrapper with rate-limited methods and `@aiFunction`
|
||||
* Twitter/X API v2 client wrapper with rate-limited methods and `@aiFunction`
|
||||
* compatibility.
|
||||
*
|
||||
* Rate limits differ by plan, so make sure theh `twitterApiPlan` parameter is
|
||||
* Rate limits differ by plan, so make sure the `twitterApiPlan` parameter is
|
||||
* properly set to maximize your rate-limit usage.
|
||||
*
|
||||
* @note This class does not handle distributed rate-limits. It assumes a
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "@agentic/typeform",
|
||||
"version": "7.6.3",
|
||||
"description": "Agentic SDK for the Typeform API.",
|
||||
"author": "Travis Fischer <travis@transitivebullsh.it>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/typeform"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
"types": "./dist/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"clean": "del dist",
|
||||
"test": "run-s test:*",
|
||||
"test:lint": "eslint .",
|
||||
"test:typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentic/core": "workspace:*",
|
||||
"ky": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<p align="center">
|
||||
<a href="https://agentic.so">
|
||||
<img alt="Agentic" src="https://raw.githubusercontent.com/transitive-bullshit/agentic/main/docs/media/agentic-header.jpg" width="308">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<em>AI agent stdlib that works with any LLM and TypeScript AI SDK.</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml"><img alt="Build Status" src="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml/badge.svg" /></a>
|
||||
<a href="https://www.npmjs.com/package/@agentic/stdlib"><img alt="NPM" src="https://img.shields.io/npm/v/@agentic/stdlib.svg" /></a>
|
||||
<a href="https://github.com/transitive-bullshit/agentic/blob/main/license"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue" /></a>
|
||||
<a href="https://prettier.io"><img alt="Prettier Code Formatting" src="https://img.shields.io/badge/code_style-prettier-brightgreen.svg" /></a>
|
||||
</p>
|
||||
|
||||
# Agentic
|
||||
|
||||
**See the [github repo](https://github.com/transitive-bullshit/agentic) or [docs](https://agentic.so) for more info.**
|
||||
|
||||
## License
|
||||
|
||||
MIT © [Travis Fischer](https://x.com/transitive_bs)
|
|
@ -0,0 +1 @@
|
|||
export * from './typeform-client'
|
|
@ -0,0 +1,197 @@
|
|||
import {
|
||||
aiFunction,
|
||||
AIFunctionsProvider,
|
||||
assert,
|
||||
getEnv,
|
||||
sanitizeSearchParams
|
||||
} from '@agentic/core'
|
||||
import defaultKy, { type KyInstance } from 'ky'
|
||||
import { z } from 'zod'
|
||||
|
||||
export namespace typeform {
|
||||
export const API_BASE_URL = 'https://api.typeform.com'
|
||||
|
||||
export interface GetInsightsForFormResponse {
|
||||
fields: Array<{
|
||||
dropoffs: number
|
||||
id: string
|
||||
label: string
|
||||
ref: string
|
||||
title: string
|
||||
type: string
|
||||
views: number
|
||||
}>
|
||||
form: {
|
||||
platforms: Array<{
|
||||
average_time: number
|
||||
completion_rate: number
|
||||
platform: string
|
||||
responses_count: number
|
||||
total_visits: number
|
||||
unique_visits: number
|
||||
}>
|
||||
summary: {
|
||||
average_time: number
|
||||
completion_rate: number
|
||||
responses_count: number
|
||||
total_visits: number
|
||||
unique_visits: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface GetResponsesForFormParams {
|
||||
formId: string
|
||||
pageSize?: number
|
||||
since?: string
|
||||
until?: string
|
||||
completed?: boolean
|
||||
}
|
||||
|
||||
export interface GetResponsesForFormResponse {
|
||||
total_items: number
|
||||
page_count: number
|
||||
items: Array<{
|
||||
landing_id: string
|
||||
token: string
|
||||
landed_at: string
|
||||
submitted_at: string
|
||||
metadata: {
|
||||
user_agent: string
|
||||
platform: string
|
||||
referer: string
|
||||
network_id: string
|
||||
browser: string
|
||||
}
|
||||
answers: Array<{
|
||||
field: {
|
||||
id: string
|
||||
type: string
|
||||
ref: string
|
||||
}
|
||||
type: string
|
||||
[key: string]: any
|
||||
}>
|
||||
hidden: Record<string, any>
|
||||
calculated: {
|
||||
score: number
|
||||
}
|
||||
variables: Array<{
|
||||
key: string
|
||||
type: string
|
||||
[key: string]: any
|
||||
}>
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Readonly Typeform API client for fetching form insights and responses.
|
||||
*
|
||||
* @see https://www.typeform.com/developers/get-started/
|
||||
*/
|
||||
export class TypeformClient extends AIFunctionsProvider {
|
||||
protected readonly ky: KyInstance
|
||||
protected readonly apiKey: string
|
||||
protected readonly apiBaseUrl: string
|
||||
|
||||
constructor({
|
||||
apiKey = getEnv('TYPEFORM_API_KEY'),
|
||||
apiBaseUrl = typeform.API_BASE_URL,
|
||||
timeoutMs = 60_000,
|
||||
ky = defaultKy
|
||||
}: {
|
||||
/** Typeform Personal Access Token */
|
||||
apiKey?: string
|
||||
apiBaseUrl?: string
|
||||
timeoutMs?: number
|
||||
ky?: KyInstance
|
||||
} = {}) {
|
||||
assert(
|
||||
apiKey,
|
||||
'TypeformClient missing required "apiKey" (defaults to "TYPEFORM_API_KEY")'
|
||||
)
|
||||
super()
|
||||
|
||||
this.apiKey = apiKey
|
||||
this.apiBaseUrl = apiBaseUrl
|
||||
|
||||
this.ky = ky.extend({
|
||||
prefixUrl: this.apiBaseUrl,
|
||||
timeout: timeoutMs,
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.apiKey}`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves insights and analytics for a Typeform form.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'typeform_get_insights_for_form',
|
||||
description: 'Retrieve insights and analytics for a Typeform form.',
|
||||
inputSchema: z.object({
|
||||
formId: z
|
||||
.string()
|
||||
.describe('The ID of the Typeform form to get insights for.')
|
||||
})
|
||||
})
|
||||
async getInsightsForForm(
|
||||
formIdOrOptions: string | { formId: string }
|
||||
): Promise<typeform.GetInsightsForFormResponse> {
|
||||
const { formId } =
|
||||
typeof formIdOrOptions === 'string'
|
||||
? { formId: formIdOrOptions }
|
||||
: formIdOrOptions
|
||||
|
||||
const encodedFormId = encodeURIComponent(formId)
|
||||
return this.ky
|
||||
.get(`insights/${encodedFormId}/summary`)
|
||||
.json<typeform.GetInsightsForFormResponse>()
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves responses for a Typeform form.
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'typeform_get_responses_for_form',
|
||||
description: 'Retrieve responses for a Typeform form.',
|
||||
inputSchema: z.object({
|
||||
formId: z
|
||||
.string()
|
||||
.describe('The ID of the Typeform form to get responses for.'),
|
||||
pageSize: z
|
||||
.number()
|
||||
.describe('The number of responses to retrieve per page.')
|
||||
.optional(),
|
||||
since: z
|
||||
.string()
|
||||
.describe('The date to start retrieving responses from.')
|
||||
.optional(),
|
||||
until: z
|
||||
.string()
|
||||
.describe('The date to stop retrieving responses at.')
|
||||
.optional(),
|
||||
completed: z
|
||||
.boolean()
|
||||
.describe('Filter responses by completion status.')
|
||||
.optional()
|
||||
})
|
||||
})
|
||||
async getResponsesForForm(
|
||||
formIdOrOptions: string | typeform.GetResponsesForFormParams
|
||||
): Promise<typeform.GetResponsesForFormResponse> {
|
||||
const { formId, ...params } =
|
||||
typeof formIdOrOptions === 'string'
|
||||
? { formId: formIdOrOptions }
|
||||
: formIdOrOptions
|
||||
|
||||
const encodedFormId = encodeURIComponent(formId)
|
||||
return this.ky
|
||||
.get(`forms/${encodedFormId}/responses`, {
|
||||
searchParams: sanitizeSearchParams(params)
|
||||
})
|
||||
.json<typeform.GetResponsesForFormResponse>()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": "@fisch0920/config/tsconfig-node",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/weather"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/wikidata"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/wikipedia"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/wolfram-alpha"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/xsai"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "@agentic/youtube",
|
||||
"version": "7.6.3",
|
||||
"description": "Agentic SDK for the YouTube data API.",
|
||||
"author": "Travis Fischer <travis@transitivebullsh.it>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/transitive-bullshit/agentic.git",
|
||||
"directory": "packages/youtube"
|
||||
},
|
||||
"type": "module",
|
||||
"source": "./src/index.ts",
|
||||
"types": "./dist/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"clean": "del dist",
|
||||
"test": "run-s test:*",
|
||||
"test:lint": "eslint .",
|
||||
"test:typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentic/core": "workspace:*",
|
||||
"ky": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<p align="center">
|
||||
<a href="https://agentic.so">
|
||||
<img alt="Agentic" src="https://raw.githubusercontent.com/transitive-bullshit/agentic/main/docs/media/agentic-header.jpg" width="308">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<em>AI agent stdlib that works with any LLM and TypeScript AI SDK.</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml"><img alt="Build Status" src="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml/badge.svg" /></a>
|
||||
<a href="https://www.npmjs.com/package/@agentic/stdlib"><img alt="NPM" src="https://img.shields.io/npm/v/@agentic/stdlib.svg" /></a>
|
||||
<a href="https://github.com/transitive-bullshit/agentic/blob/main/license"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue" /></a>
|
||||
<a href="https://prettier.io"><img alt="Prettier Code Formatting" src="https://img.shields.io/badge/code_style-prettier-brightgreen.svg" /></a>
|
||||
</p>
|
||||
|
||||
# Agentic
|
||||
|
||||
**See the [github repo](https://github.com/transitive-bullshit/agentic) or [docs](https://agentic.so) for more info.**
|
||||
|
||||
## License
|
||||
|
||||
MIT © [Travis Fischer](https://x.com/transitive_bs)
|
|
@ -0,0 +1 @@
|
|||
export * from './youtube-client'
|
|
@ -0,0 +1,270 @@
|
|||
import {
|
||||
aiFunction,
|
||||
AIFunctionsProvider,
|
||||
assert,
|
||||
getEnv,
|
||||
sanitizeSearchParams
|
||||
} from '@agentic/core'
|
||||
import defaultKy, { type KyInstance } from 'ky'
|
||||
import { z } from 'zod'
|
||||
|
||||
export namespace youtube {
|
||||
export const API_BASE_URL = 'https://www.googleapis.com/youtube/v3'
|
||||
|
||||
export interface SearchOptions {
|
||||
query: string
|
||||
maxResults?: number
|
||||
pageToken?: string
|
||||
channelId?: string
|
||||
channelType?: 'any' | 'show'
|
||||
eventType?: 'live' | 'completed' | 'upcoming'
|
||||
location?: string
|
||||
locationRadius?: string
|
||||
order?:
|
||||
| 'relevance'
|
||||
| 'date'
|
||||
| 'rating'
|
||||
| 'title'
|
||||
| 'videoCount'
|
||||
| 'viewCount'
|
||||
// The value is an RFC 3339 formatted date-time value (1970-01-01T00:00:00Z).
|
||||
publishedAfter?: string
|
||||
publishedBefore?: string
|
||||
// The regionCode parameter instructs the API to return search results for videos that can be viewed in the specified country. The parameter value is an ISO 3166-1 alpha-2 country code.
|
||||
regionCode?: string
|
||||
relevanceLanguage?: string
|
||||
safeSearch?: 'moderate' | 'none' | 'strict'
|
||||
topicId?: string
|
||||
videoCaption?: 'any' | 'closedCaption' | 'none'
|
||||
videoCategoryId?: string
|
||||
videoDefinition?: 'any' | 'high' | 'standard'
|
||||
videoDimension?: '2d' | '3d' | 'any'
|
||||
videoDuration?: 'any' | 'long' | 'medium' | 'short'
|
||||
videoEmbeddable?: 'any' | 'true'
|
||||
videoLicense?: 'any' | 'creativeCommon' | 'youtube'
|
||||
videoPaidProductPlacement?: 'any' | 'true'
|
||||
videoSyndicated?: 'any' | 'true'
|
||||
videoType?: 'any' | 'episode' | 'movie'
|
||||
}
|
||||
|
||||
export type SearchType = 'video' | 'channel' | 'playlist'
|
||||
|
||||
export interface SearchVideosResult {
|
||||
videoId: string
|
||||
title: string
|
||||
description: string
|
||||
thumbnail: string
|
||||
channelId: string
|
||||
channelTitle: string
|
||||
publishedAt: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export interface SearchChannelsResult {
|
||||
channelId: string
|
||||
title: string
|
||||
description: string
|
||||
thumbnail: string
|
||||
publishedAt: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export type SearchResponse<T extends SearchType> = {
|
||||
results: T extends 'video'
|
||||
? SearchVideosResult[]
|
||||
: T extends 'channel'
|
||||
? SearchChannelsResult[]
|
||||
: never
|
||||
totalResults: number
|
||||
prevPageToken?: string
|
||||
nextPageToken?: string
|
||||
}
|
||||
|
||||
export type SearchVideosResponse = SearchResponse<'video'>
|
||||
export type SearchChannelsResponse = SearchResponse<'channel'>
|
||||
}
|
||||
|
||||
/**
|
||||
* YouTube data API v3 client.
|
||||
*
|
||||
* @see https://developers.google.com/youtube/v3
|
||||
*/
|
||||
export class YouTubeClient extends AIFunctionsProvider {
|
||||
protected readonly ky: KyInstance
|
||||
protected readonly apiKey: string
|
||||
protected readonly apiBaseUrl: string
|
||||
|
||||
constructor({
|
||||
apiKey = getEnv('YOUTUBE_API_KEY'),
|
||||
apiBaseUrl = youtube.API_BASE_URL,
|
||||
timeoutMs = 30_000,
|
||||
ky = defaultKy
|
||||
}: {
|
||||
apiKey?: string
|
||||
apiBaseUrl?: string
|
||||
timeoutMs?: number
|
||||
ky?: KyInstance
|
||||
} = {}) {
|
||||
assert(
|
||||
apiKey,
|
||||
'YouTubeClient missing required "apiKey" (defaults to "YOUTUBE_API_KEY")'
|
||||
)
|
||||
super()
|
||||
|
||||
this.apiKey = apiKey
|
||||
this.apiBaseUrl = apiBaseUrl
|
||||
|
||||
this.ky = ky.extend({
|
||||
prefixUrl: this.apiBaseUrl,
|
||||
timeout: timeoutMs
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for videos on YouTube.
|
||||
*
|
||||
* @see https://developers.google.com/youtube/v3/docs/search/list
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'youtube_search_videos',
|
||||
description: 'Searches for videos on YouTube.',
|
||||
inputSchema: z.object({
|
||||
query: z.string().describe(`The query to search for.
|
||||
|
||||
Your request can optionally use the Boolean NOT (-) and OR (|) operators to exclude videos or to find videos that are associated with one of several search terms. For example, to search for videos matching either "boating" or "sailing", set the query parameter value to boating|sailing. Similarly, to search for videos matching either "boating" or "sailing" but not "fishing", set the query parameter value to boating|sailing -fishing.`),
|
||||
maxResults: z
|
||||
.number()
|
||||
.int()
|
||||
.optional()
|
||||
.describe('The maximum number of results to return (defaults to 5).')
|
||||
})
|
||||
})
|
||||
async searchVideos(
|
||||
queryOrOpts: string | youtube.SearchOptions
|
||||
): Promise<youtube.SearchVideosResponse> {
|
||||
const opts =
|
||||
typeof queryOrOpts === 'string' ? { query: queryOrOpts } : queryOrOpts
|
||||
|
||||
const data = await this._search({
|
||||
...opts,
|
||||
type: 'video'
|
||||
})
|
||||
|
||||
const results = (data.items || [])
|
||||
.map((item: any) => {
|
||||
const snippet = item.snippet
|
||||
if (!snippet) return null
|
||||
|
||||
const videoId = item.id?.videoId
|
||||
if (!videoId) return null
|
||||
|
||||
const thumbnails = snippet.thumbnails
|
||||
if (!thumbnails) return null
|
||||
|
||||
return {
|
||||
videoId,
|
||||
title: snippet.title,
|
||||
description: snippet.description,
|
||||
// https://i.ytimg.com/vi/MRtg6A1f2Ko/maxresdefault.jpg
|
||||
thumbnail:
|
||||
thumbnails.high?.url ||
|
||||
thumbnails.medium?.url ||
|
||||
thumbnails.default?.url ||
|
||||
`https://i.ytimg.com/vi/${videoId}/maxresdefault.jpg`,
|
||||
channelId: snippet.channelId,
|
||||
channelTitle: snippet.channelTitle,
|
||||
publishedAt: snippet.publishedAt,
|
||||
url: `https://www.youtube.com/watch?v=${videoId}`
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
||||
return {
|
||||
results,
|
||||
totalResults: data.pageInfo?.totalResults || 0,
|
||||
prevPageToken: data.prevPageToken,
|
||||
nextPageToken: data.nextPageToken
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for channels on YouTube.
|
||||
*
|
||||
* @see https://developers.google.com/youtube/v3/docs/search/list
|
||||
*/
|
||||
@aiFunction({
|
||||
name: 'youtube_search_channels',
|
||||
description: 'Searches for channels on YouTube.',
|
||||
inputSchema: z.object({
|
||||
query: z.string().describe('The query to search for.'),
|
||||
maxResults: z
|
||||
.number()
|
||||
.int()
|
||||
.optional()
|
||||
.describe('The maximum number of results to return (defaults to 5).')
|
||||
})
|
||||
})
|
||||
async searchChannels(
|
||||
queryOrOpts: string | youtube.SearchOptions
|
||||
): Promise<youtube.SearchChannelsResponse> {
|
||||
const opts =
|
||||
typeof queryOrOpts === 'string' ? { query: queryOrOpts } : queryOrOpts
|
||||
|
||||
const data = await this._search({
|
||||
...opts,
|
||||
type: 'channel'
|
||||
})
|
||||
|
||||
const results = (data.items || [])
|
||||
.map((item: any) => {
|
||||
const snippet = item.snippet
|
||||
if (!snippet) return null
|
||||
|
||||
const channelId = item.id?.channelId
|
||||
if (!channelId) return null
|
||||
|
||||
const thumbnails = snippet.thumbnails
|
||||
if (!thumbnails) return null
|
||||
|
||||
return {
|
||||
channelId,
|
||||
title: snippet.title,
|
||||
description: snippet.description,
|
||||
thumbnail:
|
||||
thumbnails.high?.url ||
|
||||
thumbnails.medium?.url ||
|
||||
thumbnails.default?.url,
|
||||
publishedAt: snippet.publishedAt,
|
||||
url: `https://www.youtube.com/channel/${channelId}`
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
||||
return {
|
||||
results,
|
||||
totalResults: data.pageInfo?.totalResults || 0,
|
||||
prevPageToken: data.prevPageToken,
|
||||
nextPageToken: data.nextPageToken
|
||||
}
|
||||
}
|
||||
|
||||
protected async _search(
|
||||
opts: youtube.SearchOptions & {
|
||||
type: youtube.SearchType
|
||||
}
|
||||
) {
|
||||
const { query, ...params } = opts
|
||||
|
||||
return this.ky
|
||||
.get('search', {
|
||||
searchParams: sanitizeSearchParams({
|
||||
q: query,
|
||||
part: 'snippet',
|
||||
maxResults: 5,
|
||||
...params,
|
||||
key: this.apiKey
|
||||
})
|
||||
})
|
||||
.json<any>()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": "@fisch0920/config/tsconfig-node",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
Plik diff jest za duży
Load Diff
|
@ -6,20 +6,21 @@ updateConfig:
|
|||
- p-throttle
|
||||
- eslint
|
||||
catalog:
|
||||
'@ai-sdk/openai': ^1.3.7
|
||||
'@apidevtools/swagger-parser': ^10.1.1
|
||||
'@dexaai/dexter': ^4.1.1
|
||||
'@e2b/code-interpreter': ^1.1.0
|
||||
'@fisch0920/config': ^1.0.2
|
||||
'@langchain/core': ^0.3.43
|
||||
'@langchain/openai': ^0.5.4
|
||||
'@mastra/core': ^0.7.0
|
||||
'@modelcontextprotocol/sdk': ^1.8.0
|
||||
'@nangohq/node': 0.42.22 # pinned for now
|
||||
'@types/jsrsasign': ^10.5.15
|
||||
'@types/node': ^22.14.0
|
||||
'@xsai/tool': ^0.2.0-beta.3
|
||||
ai: ^4.3.2
|
||||
"@ai-sdk/openai": ^1.3.9
|
||||
"@apidevtools/swagger-parser": ^10.1.1
|
||||
"@dexaai/dexter": ^4.1.1
|
||||
"@e2b/code-interpreter": ^1.1.0
|
||||
"@fisch0920/config": ^1.0.2
|
||||
"@googleapis/customsearch": ^3.2.0
|
||||
"@langchain/core": ^0.3.44
|
||||
"@langchain/openai": ^0.5.5
|
||||
"@mastra/core": ^0.8.1
|
||||
"@modelcontextprotocol/sdk": ^1.9.0
|
||||
"@nangohq/node": 0.42.22
|
||||
"@types/jsrsasign": ^10.5.15
|
||||
"@types/node": ^22.14.0
|
||||
"@xsai/tool": ^0.2.0-beta.3
|
||||
ai: ^4.3.4
|
||||
bumpp: ^10.1.0
|
||||
camelcase: ^8.0.0
|
||||
cleye: ^1.3.4
|
||||
|
@ -33,26 +34,27 @@ catalog:
|
|||
execa: ^9.5.2
|
||||
exit-hook: ^4.0.0
|
||||
fast-xml-parser: ^5.2.0
|
||||
genkit: ^1.5.0
|
||||
genkit: ^1.6.0
|
||||
genkitx-openai: ^0.20.2
|
||||
'@googleapis/customsearch': ^3.2.0
|
||||
google-auth-library: ^9.15.1
|
||||
googleapis: ^148.0.0
|
||||
json-schema-to-zod: ^2.6.1
|
||||
jsonrepair: ^3.12.0
|
||||
jsrsasign: ^10.9.0
|
||||
ky: ^1.8.0
|
||||
langchain: ^0.3.20
|
||||
langchain: ^0.3.21
|
||||
lint-staged: ^15.5.0
|
||||
llamaindex: ^0.9.16
|
||||
llamaindex: ^0.9.17
|
||||
mathjs: ^13.2.3
|
||||
npm-run-all2: ^7.0.2
|
||||
octokit: ^4.1.2
|
||||
only-allow: ^1.2.1
|
||||
openai: ^4.91.1
|
||||
openai: ^4.93.0
|
||||
openai-fetch: ^3.4.2
|
||||
openai-zod-to-json-schema: ^1.0.3
|
||||
openapi-types: ^12.1.3
|
||||
p-map: ^7.0.3
|
||||
p-throttle: 6.2.0 # pinned for now
|
||||
p-throttle: 6.2.0
|
||||
prettier: ^3.5.3
|
||||
restore-cursor: ^5.1.0
|
||||
simple-git-hooks: ^2.12.1
|
||||
|
|
|
@ -180,6 +180,7 @@ Full docs are available at [agentic.so](https://agentic.so).
|
|||
|
||||
| Service / Tool | Package | Docs | Description |
|
||||
| ------------------------------------------------------------------------------- | ------------------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [Airtable](https://airtable.com/developers/web/api/introduction) | `@agentic/airtable` | [docs](https://agentic.so/tools/airtable) | No-code spreadsheets, CRM, and database. |
|
||||
| [Apollo](https://docs.apollo.io) | `@agentic/apollo` | [docs](https://agentic.so/tools/apollo) | B2B person and company enrichment API. |
|
||||
| [ArXiv](https://arxiv.org) | `@agentic/arxiv` | [docs](https://agentic.so/tools/arxiv) | Search for research articles. |
|
||||
| [Bing](https://www.microsoft.com/en-us/bing/apis/bing-web-search-api) | `@agentic/bing` | [docs](https://agentic.so/tools/bing) | Bing web search. |
|
||||
|
@ -193,6 +194,8 @@ Full docs are available at [agentic.so](https://agentic.so).
|
|||
| [Exa](https://docs.exa.ai) | `@agentic/exa` | [docs](https://agentic.so/tools/exa) | Web search tailored for LLMs. |
|
||||
| [Firecrawl](https://www.firecrawl.dev) | `@agentic/firecrawl` | [docs](https://agentic.so/tools/firecrawl) | Website scraping and structured data extraction. |
|
||||
| [Google Custom Search](https://developers.google.com/custom-search/v1/overview) | `@agentic/google-custom-search` | [docs](https://agentic.so/tools/google-custom-search) | Official Google Custom Search API. |
|
||||
| [Google Drive](https://developers.google.com/workspace/drive/api) | `@agentic/google-drive` | [docs](https://agentic.so/tools/google-drive) | Simplified Google Drive API. |
|
||||
| [Google Docs](https://developers.google.com/workspace/docs/api) | `@agentic/google-docs` | [docs](https://agentic.so/tools/google-docs) | Simplified Google Docs API. |
|
||||
| [Gravatar](https://docs.gravatar.com/api/profiles/rest-api/) | `@agentic/gravatar` | [docs](https://agentic.so/tools/gravatar) | Gravatar profile API. |
|
||||
| [HackerNews](https://github.com/HackerNews/API) | `@agentic/hacker-news` | [docs](https://agentic.so/tools/hacker-news) | Official HackerNews API. |
|
||||
| [Hunter](https://hunter.io) | `@agentic/hunter` | [docs](https://agentic.so/tools/hunter) | Email finder, verifier, and enrichment. |
|
||||
|
@ -208,6 +211,7 @@ Full docs are available at [agentic.so](https://agentic.so).
|
|||
| [Polygon](https://polygon.io) | `@agentic/polygon` | [docs](https://agentic.so/tools/polygon) | Stock market and company financial data. |
|
||||
| [PredictLeads](https://predictleads.com) | `@agentic/predict-leads` | [docs](https://agentic.so/tools/predict-leads) | In-depth company data including signals like fundraising events, hiring news, product launches, technologies used, etc. |
|
||||
| [Proxycurl](https://nubela.co/proxycurl) | `@agentic/proxycurl` | [docs](https://agentic.so/tools/proxycurl) | People and company data from LinkedIn & Crunchbase. |
|
||||
| [Reddit](https://old.reddit.com/dev/api) | `@agentic/reddit` | [docs](https://agentic.so/tools/reddit) | Basic readonly Reddit API for getting top/hot/new/rising posts from subreddits. |
|
||||
| [RocketReach](https://rocketreach.co/api/v2/docs) | `@agentic/rocketreach` | [docs](https://agentic.so/tools/rocketreach) | B2B person and company enrichment API. |
|
||||
| [Searxng](https://docs.searxng.org) | `@agentic/searxng` | [docs](https://agentic.so/tools/searxng) | OSS meta search engine capable of searching across many providers like Reddit, Google, Brave, Arxiv, Genius, IMDB, Rotten Tomatoes, Wikidata, Wolfram Alpha, YouTube, GitHub, [etc](https://docs.searxng.org/user/configured_engines.html#configured-engines). |
|
||||
| [SerpAPI](https://serpapi.com/search-api) | `@agentic/serpapi` | [docs](https://agentic.so/tools/serpapi) | Lightweight wrapper around SerpAPI for Google search. |
|
||||
|
@ -217,10 +221,12 @@ Full docs are available at [agentic.so](https://agentic.so).
|
|||
| [Tavily](https://tavily.com) | `@agentic/tavily` | [docs](https://agentic.so/tools/tavily) | Web search API tailored for LLMs. |
|
||||
| [Twilio](https://www.twilio.com/docs/conversations/api) | `@agentic/twilio` | [docs](https://agentic.so/tools/twilio) | Twilio conversation API to send and receive SMS messages. |
|
||||
| [Twitter](https://developer.x.com/en/docs/twitter-api) | `@agentic/twitter` | [docs](https://agentic.so/tools/twitter) | Basic Twitter API methods for fetching users, tweets, and searching recent tweets. Includes support for plan-aware rate-limiting. Uses [Nango](https://www.nango.dev) for OAuth support. |
|
||||
| [Typeform](https://www.typeform.com/developers/get-started/) | `@agentic/typeform` | [docs](https://agentic.so/tools/typeform) | Readonly Typeform client for fetching form insights and responses. |
|
||||
| [Weather](https://www.weatherapi.com) | `@agentic/weather` | [docs](https://agentic.so/tools/weather) | Basic access to current weather data based on location. |
|
||||
| [Wikidata](https://www.wikidata.org/wiki/Wikidata:Data_access) | `@agentic/wikidata` | [docs](https://agentic.so/tools/wikidata) | Basic Wikidata client. |
|
||||
| [Wikipedia](https://www.mediawiki.org/wiki/API) | `@agentic/wikipedia` | [docs](https://agentic.so/tools/wikipedia) | Wikipedia page search and summaries. |
|
||||
| [Wolfram Alpha](https://products.wolframalpha.com/llm-api/documentation) | `@agentic/wolfram-alpha` | [docs](https://agentic.so/tools/wolfram-alpha) | Wolfram Alpha LLM API client for answering computational, mathematical, and scientific questions. |
|
||||
| [YouTube](https://developers.google.com/youtube/v3) | `@agentic/youtube` | [docs](https://agentic.so/tools/youtube) | YouTube data API v3 for searching YT videos and channels. |
|
||||
| [ZoomInfo](https://api-docs.zoominfo.com) | `@agentic/zoominfo` | [docs](https://agentic.so/tools/zoominfo) | Powerful B2B person and company data enrichment. |
|
||||
|
||||
> [!NOTE]
|
||||
|
|
Ładowanie…
Reference in New Issue